Skip to content

fix(web,jobs): reach Result ready instead of sticking on Retrieving result - #141

Merged
selezenart merged 1 commit into
developfrom
fix/pending-delivery-refresh
Sep 13, 2026
Merged

selezenart merged 1 commit into
developfrom
fix/pending-delivery-refresh

Conversation

@selezenart

@selezenart selezenart commented Sep 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A request whose payment had settled kept showing "Retrieving result" long after the supplier result was available — intermittently, with no obvious trigger.

It was a stale snapshot, not a rendering fault. JobList read GET /v1/jobs once on mount and then never again except through the manual "Refresh requests" button, while supplier delivery completes in the worker after that read. Whether the card looked right depended only on whether the worker beat the page load.

The list now re-reads itself while any delivery is PENDING: every four seconds, fifteen attempts (about a minute), then it stops and leaves the manual refresh as the way to look again.

A second commit fixes the state-machine hole behind the same symptom. A committed job whose delivery reached PENDING could be left with nothing able to move it: the worker no-ops a payload whose delivery_attempt no longer matches and then marks the outbox row DELIVERED, while resumeDelivery re-queued only NOT_REQUESTED or RETRIEVAL_FAILED. Such a job kept a committed payment, an unretrieved result, and no path forward — and the bounded re-reads above would simply give up on it.

PENDING alone is still not resumable. The deciding evidence is whether a fulfill_supplier_order row is still queued for that job; a row a worker currently holds is status = 'PENDING' and so counts as queued, so an in-flight retrieval is never duplicated. Only a job with no such row left is re-queued, under a fresh delivery_attempt that fences the old retrieval, claimed by compare-and-set against the state read under the job's FOR UPDATE lock.

Scope and acceptance criteria

  • The change is limited to the stated milestone or issue.
  • Acceptance criteria are listed and satisfied.
  • No unrelated cleanup is included.
  1. A request whose delivery completes after the list is read reaches "Result ready" without operator action.
  2. The re-reads stop as soon as no delivery is pending, and give up after the attempt budget rather than reading forever.
  3. A delivery stranded in PENDING with no queued fulfilment work can be recovered, while one that is genuinely queued or in flight is left alone.
  4. No payment or settlement behaviour changes.

This partially reverses a2903a1, which removed automatic polling after a report that follow-up reads created unwanted traffic. The bound and the pending-only condition are what satisfy both reports; the resume control that commit removed stays removed.

Product and security invariants

  • Tenant isolation remains fail-closed.
  • Sponsor authorization, auditability, and daily caps remain enforced where applicable.
  • Recipients cannot modify sponsor controls or access sponsor-only data.
  • No secret, token, production identifier, or personal data is committed or pasted into review prompts.
  • Any non-applicable invariant is explained below.

Invariant notes:

The added reads are GET /v1/jobs only. They never call the resume endpoint and never submit a payment.

The resumeDelivery change creates supplier-retrieval work only: no submit_settlement row, no attempt row, no payment path, so 1 intent / at most 1 committed settlement is untouched and the committed payment is preserved exactly as before. Claiming is a compare-and-set against the state read under the job's FOR UPDATE lock, so concurrent resumes cannot both claim one delivery, and the fresh delivery_attempt fences any older retrieval from completing. Selected .agent/TEST_MATRIX.md cases: downstream failure after payment and duplicate/parallel claim.

Validation

Commands and results:

pnpm format:check: PASS
pnpm lint: PASS
pnpm typecheck: PASS
pnpm test: PASS - 81 files / 1059 tests, includes build
pnpm --filter @oneshot/web test: PASS - 17 files / 99 tests
pnpm test:browser: PASS - 8 tests
Local Node 24.20.0 differs from the pinned 24.19.0; CI must validate the
  pinned runtime

The root vitest config includes only .ts/.mjs, so pnpm test exercises none of the .tsx components changed here; pnpm --filter @oneshot/web test is the run that covers them.

Independent review evidence

Gate A — exact candidate tree before push

  • Reviewer tool: free-pi-cli

  • Verdict: skipped at the requester's explicit instruction

  • Findings or residual risks: Gate A was not run. This is a recorded deviation from .agent/IMPLEMENTATION_LOOP.md §4-5, not a pass.

  • The reviewed tree equals the committed tree.

Gate B — exact remote PR head

  • Pull request URL/number: this PR

  • Remote head commit SHA: 0f8dd9a1f4cdfa091206c053de491d0c5cc18239

  • Reviewer tool: free-pi-cli

  • Verdict: skipped at the requester's explicit instruction

  • Findings or residual risks: Gate B was not run. Recorded deviation from §7, not a pass.

  • Gate B reviewed the current remote head and matches Gate A's approved tree, or a fresh Gate A was run for the changed tree.

  • Agent policy / repository-policy and all applicable CI checks pass.

This PR is a draft and carries no independent review. Both gates should be run against the head before it leaves draft.

Risk and rollback

  • Residual risks:
    • Neither gate was run.
    • Nothing in the web UI calls POST /v1/jobs/:jobId/resume, so recovering a stranded delivery still takes an API call. The control that used to do it was removed deliberately in a2903a1; re-adding one is a product decision rather than part of this fix.
    • The stranded case is inferred from the state machine, not reproduced against live data. The storage change is covered by fake-client unit tests, not by the Postgres integration suite, which was not run here.
    • The re-read cadence is a judgement call against an earlier report of unwanted traffic; four seconds and fifteen attempts are two constants if it needs tuning.
  • Rollback or recovery plan: revert the single commit; no data, schema, or settlement state is involved.

Human merge

  • A human owner has reviewed the evidence and will perform the merge.

Supplier delivery finishes in the worker, after the browser has already read
the request list, so a request whose delivery was still PENDING at read time
kept saying "Retrieving result" until someone pressed refresh — long after the
result was durably available. Whether it looked right depended only on whether
the worker beat the page load, which is why it worked intermittently.

The list now re-reads itself while any delivery is PENDING: every four seconds,
fifteen attempts, then it stops and leaves the manual refresh as the way to
look again. The reads are GET /v1/jobs only. They never call the resume
endpoint and never submit a payment, so at-most-once settlement is unaffected,
and they do not raise the loading flag, because the spinner, the disabled
button, and the tab fade belong to a read the operator asked for.

This partially reverses a2903a1, which removed automatic polling after a report
of unwanted traffic. The bound and the pending-only condition keep both reports
satisfied; the resume control that commit removed stays removed.

The browser spec asserted an exact list-read count, which a timed re-read makes
timing-dependent; it now asserts a lower bound and still asserts that no resume
request is sent.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 13, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
oneshot 0f8dd9a Sep 13 2026, 02:07 PM

@selezenart
selezenart marked this pull request as ready for review September 13, 2026 14:10
@selezenart
selezenart merged commit 6ce0735 into develop Sep 13, 2026
5 checks passed
@selezenart selezenart changed the title fix(web): re-read a pending delivery until the worker reports the result fix(web,jobs): reach Result ready instead of sticking on Retrieving result Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant