Skip to content

CRW-622: cancel-safe receipt publish (PublishContext + late withdrawal) - #579

Merged
thisisjun786 merged 6 commits into
devfrom
codex/crw-622-receipt-publish-cancel
Oct 5, 2026
Merged

thisisjun786 merged 6 commits into
devfrom
codex/crw-622-receipt-publish-cancel

Conversation

@thisisjun786

@thisisjun786 thisisjun786 commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

What this changes

crw pabcd receipt test could publish a success receipt after its invocation had been cancelled. The
existing late-cancellation check (added by the preceding receipt-interrupt fix, pull request #548) sits
before the receipt directory work and before crwdir.Publish; a cancellation that landed while the receipt
was encoded, written, fsynced and renamed was never seen, and the command printed the receipt path and
exited 0. The oracle has no such window in its recorded interrupt (its process died at the deferred SIGINT
before its single in-place writeFileSync), and it has no withdrawal at all.

This PR:

  • adds crwdir.PublishContext(ctx, finalPath, data). It runs the existing publish with its step hook and
    returns ctx.Err() at the rename step, the last point at which the publish can still be skipped, so the
    deferred cleanup removes the temp file and no final file appears. Publish, Rename and their six
    other production callers are unchanged.
  • publishes the receipt through PublishContext (context.Background() when the run options carry no
    context); a clean cancellation maps to the existing refusal
    receipt test: the command did not run to completion (interrupted); no receipt written, exit code 1. A
    cancellation joined with a failed temp removal is reported as it happened instead, so a stranded
    temporary file is never hidden behind "no receipt written".
  • after a successful publish checks the context once more: when a cancellation landed after the
    rename-step check, the receipt this run published is unlinked (its absence is fine) and the same refusal
    is returned. The withdrawal removes only the bytes this run wrote, so a receipt another invocation for
    the same session published in the meantime is left alone; a withdrawal that fails returns its unlink
    error rather than the refusal, so a surviving receipt is never reported as absent either.
  • keeps uncancelled runs byte-identical in output, exit code and receipt bytes.
  • shares the pre-publish unlink as removeReceipt and uses one receiptInterrupted constant for the
    refusal at all three sites.

Acceptance criteria

  1. A cancellation that lands at the rename step leaves no final file and no temporary file and returns
    context.Canceled; an uncancelled PublishContext writes the same bytes and mode as Publish.
  2. A cancellation seen between the late-cancellation check and the rename refuses the receipt (the refusal
    text, exit 1, nothing published, and the post-publish check does not run).
  3. A cancellation that lands after the publish withdraws the receipt and refuses; a receipt another
    invocation replaced in the meantime survives, and when the withdrawal itself fails the unlink error
    is returned instead of the refusal.
  4. A cancellation joined with a failed temporary-file removal is reported as it happened, not as the
    refusal.
  5. The existing receipt and late-cancel tests pass unchanged, and the focused packages plus the cxc
    corpus replay pass.

Oracle source ported

  • plugins/codexclaw/components/pabcd-state/src/receipt-cli.ts at v0.2.40 (3c1459ac), lines 115-185:
    the forced rmSync, the blocking spawnSync, the in-place writeFileSync at line 184. The oracle has
    no signal handler and no withdrawal; the port's interruption handling is the intentionally-changed part
    recorded with the preceding fix.
  • The port's own seams at base 26e47fe2: internal/pabcd/crwdir/atomic.go (publish, its step hook
    and deferred cleanup) and internal/pabcd/cli/receipt.go (receiptLateCancelHook, the late check, the
    publish call).

Commands and results

Red first, on base 26e47fe2 before the implementation: go test -count=1 ./internal/pabcd/crwdir ./internal/pabcd/cli fails to build against the missing publishContext, PublishContext,
receiptBeforePublishHook, receiptAfterPublishHook and receiptInterrupted.

Green, on the final head: go test -count=1 ./internal/pabcd/crwdir ./internal/pabcd/cli ./internal/harness -> ok.

Behavioural sensitivity evidence (disposable local edits, reverted before any commit; each makes exactly
its test fail):

Edit Failing test and observation
publish through Publish instead of PublishContext the mapping test fails: the post-publish check runs when it must not
drop the post-publish context check the withdrawal test fails: the receipt stays and the result is its path with code 0
drop the rename-step ctx.Err() return both PublishContext cancellation tests fail with err = <nil>
withdraw with an unconditional unlink the other-invocation test fails: that receipt is deleted
classify with errors.Is alone the stranded-temp test fails: the joined cancellation is answered with the refusal

The full battery on the head (ci validate, ci plugin with no digest change, ci contracts, the
complete cxc replay, make lint, go vet on linux and darwin, CGO_ENABLED=0 go build ./...,
gofmt -l, git diff --check) and the hosted CI run are cited in the delivery handoff.

Corpus classification

Fixtures that reach this issue's units (the receipt CLI's publish path; entry point crw pabcd receipt test, driven as a cli step by the replayer):

Fixture Entry point Can this PR alone make it pass?
cli-help__receipt__{dash_h,dashdash_help,test_dashdash_help,word_help} crw pabcd receipt … --help Already claimed identical by the receipt help/refusal issue; this PR keeps them passing (replayed).
cli__receipt__not_at_phase_c_refused_plain crw pabcd receipt test refusal before publication Already claimed identical by that issue; kept passing (replayed).
cli__receipt__failing_command_recorded crw pabcd receipt test inside the P→A→B→C pipeline No: probed with a temporary identical claim, red at the git call log before the receipt runs; stays pending under the orchestrate issues.
cli__orchestrate__split_cwd_cycle_closes_on_real_commit_receipt same, plus the C→D receipt gate No: probed, red at the git call sequence; not this diff.
cli__orchestrate__bound_c_to_d_requires_test_receipt same No: probed, red (expected git calls absent); not this diff.
cli__evidence__resolve_with_receipt crw evidence resolve (reads a receipt file, never publishes one) Out of this issue's units; probed green, left to its owning issue.

The cancellation window itself has no recorded fixture: the recorder has no signal or cancellation
scenario, which is also why the preceding fix records none. The new Go tests are its evidence, and
contract/notes/cxc/CRW-622.json registers no claim (identical: [], intentionally-changed: [])
because this PR makes no fixture pass that an earlier issue does not already own.

Review findings on this PR

  • Devin Review (red): a cancelled invocation could delete a receipt another invocation published in the
    meantime at the same fixed path. Fixed in 26a84c826: the withdrawal removes the receipt only while
    the bytes on disk are still the ones this run wrote, with TestReceiptPublishCancellationLeavesAnotherInvocationsReceipt.
  • Devin Review (yellow): a cancellation joined with a failed temporary-file removal was answered with the
    plain refusal, hiding the leftover file. Fixed in the same commit through receiptPublishRefusal, with
    TestReceiptPublishCancellationWithAStrandedTempFileIsReported.
  • Codex Review: completed with no findings. Codex Security Review: skipped by the usage-limit notice, as
    the review policy anticipates.

Diff size

346 added and 7 deleted lines (353 changed): implementation 117, tests 227, documentation 9. No generated
data is committed.

Defects

The defect this PR fixes is port-introduced, not an oracle defect. It is recorded as one port: fixed
line in docs/port-cxc/known-defects.md naming the tests that pin it. No other defect was found while
porting this window.

Base and conflicts

The branch is based on 26e47fe2. Dev has moved since; a local merge check against the fetched dev tip
shows the only conflict is the append-only section of docs/port-cxc/known-defects.md (no code conflict,
and the merged tree builds with these tests passing in a scratch checkout that was removed afterwards).
The append-only union is the coordinator's merge-lane step.

Out of scope

No change to Publish/Rename or their other callers; no other path gains cancellability; no hook,
skill, .codex-plugin or recorded fixture change; no merge, release, install or live-state action.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-05T06:54:21.142772Z 8f68a4a PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Devin Review

Comment thread internal/pabcd/cli/receipt.go
Comment thread internal/pabcd/cli/receipt.go
The coordinator refreshed the base. The only conflict was in docs/port-cxc/known-defects.md, where both sides
appended lines at the same place (1 block(s)); both sets are kept, dev's lines first, then this branch's.
@thisisjun786
thisisjun786 merged commit ad706ac into dev Oct 5, 2026
10 checks passed
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