feat(enrich): confidence score in workflow result + configurable thresholds (bookshelf-rppy) #908

Merged
zombor merged 4 commits from bd-bookshelf-rppy into main 2026-07-03 21:06:40 +00:00
Owner

Summary

  • LowConfidenceError struct added to internal/books carrying book_id, confidence, threshold, book_type, winner_provider so the wfengine activity adapter can surface them in the EnrichResult payload (rather than an opaque error string).
  • GetConfidenceThresholds curried function reads ENRICH_EBOOK_CONFIDENCE_THRESHOLD / ENRICH_COMIC_CONFIDENCE_THRESHOLD from app_settings, defaulting to DefaultEbookConfidenceThreshold=0.5 / DefaultComicConfidenceThreshold=0.5 when unset or invalid (validated 0.0–1.0). Wired through RefetchMetadataapplyConfidenceGate.
  • EnrichWorkflow return type changed from error to (EnrichResult, error); low-confidence skips become successful results with SkippedLowConfidence *ConfidenceSkipDetails populated instead of permanent errors.
  • boundedFanOutWithResult[T, R any] generic added to fanout.go for (R, error) sub-workflows; BoundedFanOutEnrichInput routes through it.
  • 100% coverage: LowConfidenceError.Error(), GetConfidenceThresholds all branches, resolveConfidenceThresholds success + error path via RefetchMetadata, boundedFanOutWithResult empty/clamp/alreadyExists paths, boundedFanOut onItemError callback via BoundedFanOutLLMSweepInput.

Test plan

  • make test passes (all packages)
  • make coverage passes — check-coverage: OK — zero uncovered statement blocks
  • golangci-lint run ./internal/books/... ./internal/wfengine/... — 0 issues
  • New tests cover LowConfidenceError.Error(), GetConfidenceThresholds all branches, resolveConfidenceThresholds error path, boundedFanOutWithResult empty/concurrency-clamp/alreadyExists, BoundedFanOutLLMSweepInput onItemError

Closes bead bookshelf-rppy on merge.

## Summary - `LowConfidenceError` struct added to `internal/books` carrying `book_id`, `confidence`, `threshold`, `book_type`, `winner_provider` so the wfengine activity adapter can surface them in the `EnrichResult` payload (rather than an opaque error string). - `GetConfidenceThresholds` curried function reads `ENRICH_EBOOK_CONFIDENCE_THRESHOLD` / `ENRICH_COMIC_CONFIDENCE_THRESHOLD` from `app_settings`, defaulting to `DefaultEbookConfidenceThreshold=0.5` / `DefaultComicConfidenceThreshold=0.5` when unset or invalid (validated 0.0–1.0). Wired through `RefetchMetadata` → `applyConfidenceGate`. - `EnrichWorkflow` return type changed from `error` to `(EnrichResult, error)`; low-confidence skips become successful results with `SkippedLowConfidence *ConfidenceSkipDetails` populated instead of permanent errors. - `boundedFanOutWithResult[T, R any]` generic added to `fanout.go` for `(R, error)` sub-workflows; `BoundedFanOutEnrichInput` routes through it. - 100% coverage: `LowConfidenceError.Error()`, `GetConfidenceThresholds` all branches, `resolveConfidenceThresholds` success + error path via `RefetchMetadata`, `boundedFanOutWithResult` empty/clamp/alreadyExists paths, `boundedFanOut` onItemError callback via `BoundedFanOutLLMSweepInput`. ## Test plan - [x] `make test` passes (all packages) - [x] `make coverage` passes — `check-coverage: OK — zero uncovered statement blocks` - [x] `golangci-lint run ./internal/books/... ./internal/wfengine/...` — 0 issues - [x] New tests cover `LowConfidenceError.Error()`, `GetConfidenceThresholds` all branches, `resolveConfidenceThresholds` error path, `boundedFanOutWithResult` empty/concurrency-clamp/alreadyExists, `BoundedFanOutLLMSweepInput` onItemError Closes bead bookshelf-rppy on merge.
feat(enrich): surface confidence score in workflow result + configurable thresholds (bookshelf-rppy)
All checks were successful
/ JS Unit Tests (pull_request) Successful in 1m8s
/ E2E API (pull_request) Successful in 2m41s
/ Lint (pull_request) Successful in 3m58s
/ Integration (pull_request) Successful in 3m58s
/ E2E Browser (pull_request) Successful in 4m12s
/ Test (pull_request) Successful in 5m2s
1b316c5dc2
- Add LowConfidenceError struct carrying book_id, confidence, threshold,
  book_type, winner_provider so the wfengine activity can map them into
  the structured EnrichResult payload instead of an opaque error string
- Add GetConfidenceThresholds curried function reading ebook/comic thresholds
  from app_settings (ENRICH_EBOOK_CONFIDENCE_THRESHOLD /
  ENRICH_COMIC_CONFIDENCE_THRESHOLD), falling back to the DefaultXxx consts
- Thread getConfidenceThresholds through RefetchMetadata → applyConfidenceGate
  replacing the hardcoded DefaultEbookConfidenceThreshold const
- Wire GetConfidenceThresholds in wire.go and build_extended_deps.go
- Add EnrichResult / ConfidenceSkipDetails structs in wfengine; EnrichWorkflow
  now returns (EnrichResult, error); low-confidence skips become successful
  results with SkippedLowConfidence populated instead of permanent errors
- Add boundedFanOutWithResult generic variant for (R, error) sub-workflows;
  BoundedFanOutEnrichInput routes through it
- 100% coverage: tests for LowConfidenceError.Error(), GetConfidenceThresholds
  all branches, resolveConfidenceThresholds error path via RefetchMetadata,
  and boundedFanOutWithResult empty/clamp/alreadyExists/onItemError paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Recompute Match Score — kebab open screenshot (recompute-match-score-kebab-open)

recompute-match-score-kebab-open

**Recompute Match Score — kebab open screenshot** (recompute-match-score-kebab-open) ![recompute-match-score-kebab-open](/attachments/e44151e8-694d-4145-bd17-e785891f98fe)

Workflow Detail page screenshot (wf-detail-older-execution)

Older completed ContinueAsNew epoch detail — execution ID and state visible, Cancel absent.

wf-detail-older-execution

**Workflow Detail page screenshot** (wf-detail-older-execution) Older completed ContinueAsNew epoch detail — execution ID and state visible, Cancel absent. ![wf-detail-older-execution](/attachments/168f5c68-a2c9-4ada-ab0e-4572b142e8a6)
Author
Owner

Security Review — bookshelf-rppy (PR #908)

Findings

[MAJOR] internal/books/enrich_confidence.go:107 — NaN bypasses the [0.0, 1.0] range clamp in readThreshold

strconv.ParseFloat("NaN", 64) returns (math.NaN(), nil) — no error, no panic. IEEE 754 comparisons with NaN are always false, so the guard v < 0.0 || v > 1.0 evaluates to false || false = false, and NaN is returned as the threshold. Downstream in applyConfidenceGate, confidence < math.NaN() is always false for any finite confidence value, silently disabling the confidence gate for that book type.

Any admin (or DB operator) who stores the literal string "NaN" for ENRICH_EBOOK_CONFIDENCE_THRESHOLD or ENRICH_COMIC_CONFIDENCE_THRESHOLD causes the confidence gate to stop firing — metadata below the intended quality bar is persisted without rejection or log warning.

The project already guards against this exact risk in analogous float-parsing paths:

  • internal/books/metadata_handler.go:399: if f < 0 || f > 10 || math.IsNaN(f) || math.IsInf(f, 0)
  • internal/books/service.go:341: if err != nil || math.IsNaN(f) || math.IsInf(f, 0)

Fix: add || math.IsNaN(v) (and optionally || math.IsInf(v, 0), though +Inf > 1.0 and -Inf < 0.0 already catch infinity via the range check):

if err != nil || v < 0.0 || v > 1.0 || math.IsNaN(v) {
    return defaultVal
}

Other checked areas (no findings)

  • Parse safety: strconv.ParseFloat cannot panic; the error path is handled. ±Infinity is correctly rejected by the existing range check (both fall outside [0.0, 1.0]). Only NaN escapes.
  • Threshold = 0.0 design: Allowing 0.0 as a valid threshold is intentional ("accept everything") and is explicitly covered by tests. Not a gate bypass — it requires admin/DB-level write access to set.
  • Auth-Z on threshold write surface: No HTTP write surface for ENRICH_EBOOK_CONFIDENCE_THRESHOLD or ENRICH_COMIC_CONFIDENCE_THRESHOLD is introduced in this PR. These keys are read-only from the application's perspective; they can only be set via direct DB manipulation, which already implies elevated access.
  • Info leak in EnrichResult: The result payload (ConfidenceSkipDetails) carries book_id, confidence, threshold, book_type, winner_provider. No API keys, tokens, or cross-user data. The workflow detail surface that exposes this (GET /admin/workflows/{instanceID}) is correctly gated behind AdminRequired.
  • Multi-user scoping: The enrich workflow result is exposed only via the admin diagnostics UI (all workflow routes under /admin/workflows/ carry adminRequired). No new per-user-scoped surface is introduced.
  • Architecture boundary: internal/books/enrich_confidence.go imports only stdlib (database/sql, strconv, etc.) and internal domain packages (internal/metadata, internal/middleware). No workflow engine import. Clean boundary preserved.
  • Secrets in logs: The new warn log in applyConfidenceGate emits book_id, book_type, confidence, threshold, winner_provider, trace_id. No credentials or PII.

REVIEW VERDICT: 0 blocker, 1 major, 0 minor

## Security Review — bookshelf-rppy (PR #908) ### Findings [MAJOR] internal/books/enrich_confidence.go:107 — NaN bypasses the [0.0, 1.0] range clamp in readThreshold `strconv.ParseFloat("NaN", 64)` returns `(math.NaN(), nil)` — no error, no panic. IEEE 754 comparisons with NaN are always false, so the guard `v < 0.0 || v > 1.0` evaluates to `false || false = false`, and NaN is returned as the threshold. Downstream in `applyConfidenceGate`, `confidence < math.NaN()` is always false for any finite confidence value, silently disabling the confidence gate for that book type. Any admin (or DB operator) who stores the literal string `"NaN"` for `ENRICH_EBOOK_CONFIDENCE_THRESHOLD` or `ENRICH_COMIC_CONFIDENCE_THRESHOLD` causes the confidence gate to stop firing — metadata below the intended quality bar is persisted without rejection or log warning. The project already guards against this exact risk in analogous float-parsing paths: - `internal/books/metadata_handler.go:399`: `if f < 0 || f > 10 || math.IsNaN(f) || math.IsInf(f, 0)` - `internal/books/service.go:341`: `if err != nil || math.IsNaN(f) || math.IsInf(f, 0)` Fix: add `|| math.IsNaN(v)` (and optionally `|| math.IsInf(v, 0)`, though `+Inf > 1.0` and `-Inf < 0.0` already catch infinity via the range check): ```go if err != nil || v < 0.0 || v > 1.0 || math.IsNaN(v) { return defaultVal } ``` --- ### Other checked areas (no findings) - **Parse safety**: `strconv.ParseFloat` cannot panic; the error path is handled. ±Infinity is correctly rejected by the existing range check (both fall outside [0.0, 1.0]). Only NaN escapes. - **Threshold = 0.0 design**: Allowing 0.0 as a valid threshold is intentional ("accept everything") and is explicitly covered by tests. Not a gate bypass — it requires admin/DB-level write access to set. - **Auth-Z on threshold write surface**: No HTTP write surface for `ENRICH_EBOOK_CONFIDENCE_THRESHOLD` or `ENRICH_COMIC_CONFIDENCE_THRESHOLD` is introduced in this PR. These keys are read-only from the application's perspective; they can only be set via direct DB manipulation, which already implies elevated access. - **Info leak in EnrichResult**: The result payload (`ConfidenceSkipDetails`) carries `book_id`, `confidence`, `threshold`, `book_type`, `winner_provider`. No API keys, tokens, or cross-user data. The workflow detail surface that exposes this (`GET /admin/workflows/{instanceID}`) is correctly gated behind `AdminRequired`. - **Multi-user scoping**: The enrich workflow result is exposed only via the admin diagnostics UI (all workflow routes under `/admin/workflows/` carry `adminRequired`). No new per-user-scoped surface is introduced. - **Architecture boundary**: `internal/books/enrich_confidence.go` imports only stdlib (`database/sql`, `strconv`, etc.) and internal domain packages (`internal/metadata`, `internal/middleware`). No workflow engine import. Clean boundary preserved. - **Secrets in logs**: The new warn log in `applyConfidenceGate` emits `book_id`, `book_type`, `confidence`, `threshold`, `winner_provider`, `trace_id`. No credentials or PII. --- REVIEW VERDICT: 0 blocker, 1 major, 0 minor
Author
Owner

[MAJOR] internal/wfengine/simple_workflows_test.go:290 — Missing EnrichWorkflow-level test for low-confidence result propagation
The EnrichActivities.Refetch unit tests (line 207-287) prove the LowConfidenceError → EnrichResult conversion logic, but there is no EnrichWorkflow workflow-tester-level test that sets stubErr to a *books.LowConfidenceError and asserts workflowResult.SkippedLowConfidence is populated with the correct confidence/threshold. go-workflows serializes the activity result to JSON and deserializes it back into the workflow result; a type registration bug or JSON tag mismatch would make the workflow tester return a zero-valued EnrichResult{} while all activity tests stay green. Per the workflow-integration-test-must-assert-success rule: "go-workflows marks errored-finished as completed — assert Result IS the success value." Add a Context inside the EnrichWorkflow Describe block: set stubErr = &books.LowConfidenceError{BookID:5, Confidence:0.42, Threshold:0.50, BookType:"ebook", WinnerProvider:"google"}, execute, assert workflowResult.SkippedLowConfidence != nil and that the Confidence/Threshold fields are propagated.

[MINOR] internal/wfengine/export_test.go:714 — Redundant type aliases for already-exported types
EnrichResultExport = EnrichResult and ConfidenceSkipDetailsExport = ConfidenceSkipDetails re-alias types that are already exported (uppercase). Tests in wfengine_test package can reference wfengine.EnrichResult directly without the Export alias. The aliases add no capability. Consider removing them and having the tests use the canonical names directly; keeping them just trains readers to expect an Export alias when there is none of the unexported-symbol-bridging that export_test.go aliases normally provide.

[MINOR] internal/books/metadata_service_test.go:358 — Misaligned nil arguments (3 spaces vs tabs)
Several + nil, lines added throughout the test file use 3-space indentation instead of the tab-based indentation that gofmt expects (matching the surrounding lines). Affects lines 358, 492, 500, 508, 533, 541, 549, 557. Run gofmt -w internal/books/metadata_service_test.go to fix.

[MINOR] internal/wfengine/simple_workflows_test.go:264 — Standalone It("returns no error") should be folded into value assertion
The low-confidence context has a separate It("returns no error (skip is reported as result, not failure)") { Expect(err).NotTo(HaveOccurred()) } followed by It("returns a result with SkippedLowConfidence populated") { Expect(result.SkippedLowConfidence).NotTo(BeNil()) }. Per conventions, the nil-error check should be folded into the first value assertion: Expect(result.SkippedLowConfidence, err).NotTo(BeNil()) — asserts both err==nil AND the field is non-nil in a single Expect, eliminating the standalone no-error It.


What this PR gets right:

  • Arch boundary is clean: internal/books has no go-workflows import; LowConfidenceError is a plain struct; wfengine does the mapping in Refetch. ✓
  • Permanent-vs-retryable semantics are intact: LCE is intercepted before isPermanentEnrichErr, transient errors still return as errors and get retried. ✓
  • boundedFanOutWithResult uses the same sliding-window bounded-fan-out semantics as boundedFanOut — concurrency cap is enforced. ✓
  • readThreshold correctly validates 0.0–1.0, falls back to defaults on error/missing/invalid. ✓
  • UI surface: workflow_detail.html:93-97 already renders .Detail.Result as a <pre class="workflow-payload-block"> generic JSON block — the EnrichResult.SkippedLowConfidence JSON will be visible there without template changes. The bead spec "reuse canonical result rendering" is satisfied. ✓

REVIEW VERDICT: 0 blocker, 1 major, 3 minor

[MAJOR] internal/wfengine/simple_workflows_test.go:290 — Missing EnrichWorkflow-level test for low-confidence result propagation The `EnrichActivities.Refetch` unit tests (line 207-287) prove the `LowConfidenceError → EnrichResult` conversion logic, but there is no `EnrichWorkflow` workflow-tester-level test that sets `stubErr` to a `*books.LowConfidenceError` and asserts `workflowResult.SkippedLowConfidence` is populated with the correct confidence/threshold. go-workflows serializes the activity result to JSON and deserializes it back into the workflow result; a type registration bug or JSON tag mismatch would make the workflow tester return a zero-valued `EnrichResult{}` while all activity tests stay green. Per the `workflow-integration-test-must-assert-success` rule: "go-workflows marks errored-finished as completed — assert Result IS the success value." Add a Context inside the `EnrichWorkflow` Describe block: set `stubErr = &books.LowConfidenceError{BookID:5, Confidence:0.42, Threshold:0.50, BookType:"ebook", WinnerProvider:"google"}`, execute, assert `workflowResult.SkippedLowConfidence != nil` and that the Confidence/Threshold fields are propagated. [MINOR] internal/wfengine/export_test.go:714 — Redundant type aliases for already-exported types `EnrichResultExport = EnrichResult` and `ConfidenceSkipDetailsExport = ConfidenceSkipDetails` re-alias types that are already exported (uppercase). Tests in `wfengine_test` package can reference `wfengine.EnrichResult` directly without the Export alias. The aliases add no capability. Consider removing them and having the tests use the canonical names directly; keeping them just trains readers to expect an Export alias when there is none of the unexported-symbol-bridging that export_test.go aliases normally provide. [MINOR] internal/books/metadata_service_test.go:358 — Misaligned nil arguments (3 spaces vs tabs) Several `+ nil,` lines added throughout the test file use 3-space indentation instead of the tab-based indentation that gofmt expects (matching the surrounding lines). Affects lines 358, 492, 500, 508, 533, 541, 549, 557. Run `gofmt -w internal/books/metadata_service_test.go` to fix. [MINOR] internal/wfengine/simple_workflows_test.go:264 — Standalone `It("returns no error")` should be folded into value assertion The low-confidence context has a separate `It("returns no error (skip is reported as result, not failure)") { Expect(err).NotTo(HaveOccurred()) }` followed by `It("returns a result with SkippedLowConfidence populated") { Expect(result.SkippedLowConfidence).NotTo(BeNil()) }`. Per conventions, the nil-error check should be folded into the first value assertion: `Expect(result.SkippedLowConfidence, err).NotTo(BeNil())` — asserts both err==nil AND the field is non-nil in a single Expect, eliminating the standalone no-error It. --- **What this PR gets right:** - Arch boundary is clean: `internal/books` has no go-workflows import; `LowConfidenceError` is a plain struct; `wfengine` does the mapping in `Refetch`. ✓ - Permanent-vs-retryable semantics are intact: LCE is intercepted before `isPermanentEnrichErr`, transient errors still return as errors and get retried. ✓ - `boundedFanOutWithResult` uses the same sliding-window bounded-fan-out semantics as `boundedFanOut` — concurrency cap is enforced. ✓ - `readThreshold` correctly validates 0.0–1.0, falls back to defaults on error/missing/invalid. ✓ - UI surface: `workflow_detail.html:93-97` already renders `.Detail.Result` as a `<pre class="workflow-payload-block">` generic JSON block — the `EnrichResult.SkippedLowConfidence` JSON will be visible there without template changes. The bead spec "reuse canonical result rendering" is satisfied. ✓ REVIEW VERDICT: 0 blocker, 1 major, 3 minor
fix(review): address MAJOR/MINOR findings from PR #908 review
Some checks failed
/ JS Unit Tests (pull_request) Successful in 33s
/ E2E API (pull_request) Successful in 2m36s
/ Integration (pull_request) Successful in 4m2s
/ Lint (pull_request) Successful in 4m7s
/ E2E Browser (pull_request) Successful in 4m16s
/ Test (pull_request) Failing after 5m10s
62db17d252
- [MAJOR security] reject NaN/Inf in readThreshold (enrich_confidence.go):
  strconv.ParseFloat("NaN",64) returns (NaN,nil); NaN<0||NaN>1 is always
  false so a stored "NaN" silently disabled the confidence gate. Add
  math.IsNaN(v)||math.IsInf(v,0) to the rejection condition, matching
  the IsNaN/IsInf guards in metadata_handler.go and service.go. Add
  tests for NaN and Inf falling back to the 0.5 default.

- [MAJOR code] add EnrichWorkflow tester-level test for LowConfidenceError:
  proves SkippedLowConfidence survives go-workflows JSON serialize/
  deserialize round-trip (type-registration or JSON-tag bug would yield
  zero-valued EnrichResult{} while activity tests stayed green).

- [MINOR] remove EnrichResultExport/ConfidenceSkipDetailsExport aliases
  from export_test.go — both EnrichResult and ConfidenceSkipDetails are
  already exported types; the aliases add no bridging value. Update
  simple_workflows_test.go and fanout_test.go to reference the types
  directly.

- [MINOR] fold standalone It("returns no error") into value assertion
  Expect(result.SkippedLowConfidence, err).NotTo(BeNil()) per
  project-conventions one-Expect-per-It rule.

- [MINOR] run gofmt -w on metadata_service_test.go to fix 3-space
  indent on nil args (mixed spaces/tabs).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Recompute Match Score — kebab open screenshot (recompute-match-score-kebab-open)

recompute-match-score-kebab-open

**Recompute Match Score — kebab open screenshot** (recompute-match-score-kebab-open) ![recompute-match-score-kebab-open](/attachments/b391af99-d9a8-412c-b989-9e6ac07981b6)

Workflow Detail page screenshot (wf-detail-older-execution)

Older completed ContinueAsNew epoch detail — execution ID and state visible, Cancel absent.

wf-detail-older-execution

**Workflow Detail page screenshot** (wf-detail-older-execution) Older completed ContinueAsNew epoch detail — execution ID and state visible, Cancel absent. ![wf-detail-older-execution](/attachments/08956b0e-7e36-4db4-9f95-1e934541fcd5)
fix(coverage): add cancel-before-seeding test for BoundedFanOutEnrichInput
All checks were successful
/ JS Unit Tests (pull_request) Successful in 1m39s
/ E2E API (pull_request) Successful in 2m23s
/ Lint (pull_request) Successful in 3m49s
/ Integration (pull_request) Successful in 3m52s
/ E2E Browser (pull_request) Successful in 4m6s
/ Test (pull_request) Successful in 5m16s
3bde3ba9b7
The boundedFanOutWithResult pre-seeding ctx.Err() guard (fanout.go:248)
was not covered by unit tests — only BoundedFanOutInt64 had a cancel-before-
seeding test (which covers boundedFanOut's equivalent check). Add a parallel
test for BoundedFanOutEnrichInput that cancels the workflow context immediately
before calling BoundedFanOutEnrichInput, exercising the return-on-cancel path
in the result-typed generic variant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Workflow Detail page screenshot (wf-detail-older-execution)

Older completed ContinueAsNew epoch detail — execution ID and state visible, Cancel absent.

wf-detail-older-execution

**Workflow Detail page screenshot** (wf-detail-older-execution) Older completed ContinueAsNew epoch detail — execution ID and state visible, Cancel absent. ![wf-detail-older-execution](/attachments/5771fec7-8104-477d-83e3-66e14e5b4697)

Recompute Match Score — kebab open screenshot (recompute-match-score-kebab-open)

recompute-match-score-kebab-open

**Recompute Match Score — kebab open screenshot** (recompute-match-score-kebab-open) ![recompute-match-score-kebab-open](/attachments/5ddf8330-3b72-4c38-a297-6a323234addc)
zombor force-pushed bd-bookshelf-rppy from 3bde3ba9b7
All checks were successful
/ JS Unit Tests (pull_request) Successful in 1m39s
/ E2E API (pull_request) Successful in 2m23s
/ Lint (pull_request) Successful in 3m49s
/ Integration (pull_request) Successful in 3m52s
/ E2E Browser (pull_request) Successful in 4m6s
/ Test (pull_request) Successful in 5m16s
to 019bd55a3c
All checks were successful
/ JS Unit Tests (pull_request) Successful in 1m8s
/ E2E API (pull_request) Successful in 1m50s
/ Lint (pull_request) Successful in 2m37s
/ Integration (pull_request) Successful in 2m45s
/ E2E Browser (pull_request) Successful in 3m9s
/ Test (pull_request) Successful in 3m37s
2026-07-03 19:43:57 +00:00
Compare

Workflow Detail page screenshot (wf-detail-older-execution)

Older completed ContinueAsNew epoch detail — execution ID and state visible, Cancel absent.

wf-detail-older-execution

**Workflow Detail page screenshot** (wf-detail-older-execution) Older completed ContinueAsNew epoch detail — execution ID and state visible, Cancel absent. ![wf-detail-older-execution](/attachments/f2d632d9-59ff-4b76-a677-bf02638bf658)

Recompute Match Score — kebab open screenshot (recompute-match-score-kebab-open)

recompute-match-score-kebab-open

**Recompute Match Score — kebab open screenshot** (recompute-match-score-kebab-open) ![recompute-match-score-kebab-open](/attachments/b98cd3d5-117c-44c1-b348-542d2d1538b9)
zombor force-pushed bd-bookshelf-rppy from 019bd55a3c
All checks were successful
/ JS Unit Tests (pull_request) Successful in 1m8s
/ E2E API (pull_request) Successful in 1m50s
/ Lint (pull_request) Successful in 2m37s
/ Integration (pull_request) Successful in 2m45s
/ E2E Browser (pull_request) Successful in 3m9s
/ Test (pull_request) Successful in 3m37s
to 3c95be3322
All checks were successful
/ E2E API (pull_request) Successful in 2m54s
/ Integration (pull_request) Successful in 4m12s
/ JS Unit Tests (pull_request) Successful in 1m17s
/ Lint (pull_request) Successful in 4m14s
/ E2E Browser (pull_request) Successful in 3m14s
/ Test (pull_request) Successful in 5m6s
2026-07-03 21:00:50 +00:00
Compare
zombor merged commit a7075ae338 into main 2026-07-03 21:06:40 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
zombor/pergamum!908
No description provided.