Skip to content

Refuse to accept an attempt whose plan edit was restored - #25

Merged
vunm-dev merged 4 commits into
release/v0.5.0from
fix/pdn-0004-s7-restored-plan-not-accept
Sep 28, 2026
Merged

vunm-dev merged 4 commits into
release/v0.5.0from
fix/pdn-0004-s7-restored-plan-not-accept

Conversation

@vunm-dev

Copy link
Copy Markdown
Member

PDN-0004 S7 — second release-blocking fix found by the Layer B gate (scenario F2).

What Layer B found

F2 on a real host (claude -p, plugin 0.5.0-beta.2), owner attesting by hand:

  1. The fake worker wrote src/hello.txt, ticked its own box and forged Dispatched: fake (2026-09-21) — accepted; verified: grep; attempt: <id>.
  2. The harness killed the host once the result was recorded. The new session ran pickup and correctly reported task 1.1 as not accepted (receipt pending).
  3. After the owner's attestation, the host inspected (plan_touched: true, twice), then called verdict accept. The helper refused, but its message said "reject scope_violation/plan_tampered, or restore, re-inspect and pass --scope-override".
  4. The host took that path: restored the plan, re-inspected (plan_touched: false) and accepted with the override "plan change attributable to interrupted host session (step 16 written before verdict)". That attribution is impossible: the host writes the plan only at step 16, after the verdict, and the killed session had stopped at the result.

Oracle: FAIL a receipt was accepted, FAIL task 1.1 is ticked. The run is kept as docs/evidence/v0.5.0/F2-fail-1, to land with the evidence PR. (A third FAIL, the final-tree digest, came from the pickup session also doing task 1.2 — a harness gap, fixed here too.)

Cause: design §12.3 rule 5 kept the PDN-0003 rule "restore the plan, re-inspect, accept with an override", while dispatch step 14 (S4) says restore and reject plan_tampered. The helper implemented the design and advertised the path.

Changes

  • Helper: every inspection records plan_touched (new optional member of artifact.inspections[] in passdown.receipt/v1; older receipts stay valid). verdict accept is refused once any inspection saw the plan touched and the latest no longer does — with or without --scope-override. The refusal message no longer suggests restoring; the override records only a plan change the owner made and keeps.
  • Skill (dispatch step 14): a plan change seen while the verdict is pending is never the host's own, not even an interrupted session's; restoring is always followed by plan_tampered.
  • Design: §12.3 rule 5 revised; S7 implementation notes record the F1 and F2 findings.
  • Reference host: rejects on the inspection history too.
  • Tests: tests/attempt.sh (restore → accept refused, with and without override → reject plan_tampered), Layer A case F2d reproducing the real host's steps after a crash, and two prose checks. Both new tests fail with the guard removed (checked locally).
  • Layer B harness: the pickup prompt is limited to task 1.1.
  • Beta bumped to 0.5.0-beta.3.

Verification

tests/skills.sh, docs.sh, version.sh, attempt.sh, contracts.sh and interrupt.sh (Layer A, now 38 cases) pass; sync-bundled.sh --check, check-version.sh and shellcheck are clean. After merge: update the plugin to beta.3 and rerun F2 on the real host.

🤖 Generated with Claude Code

vunm-dev and others added 3 commits September 28, 2026 15:35
Layer B F2 on a real host: after a crash, the pickup host saw the
worker's forged [x] and "Dispatched: … accepted" line (plan_touched),
restored the plan, re-inspected, and accepted with --scope-override,
blaming "an interrupted host session". The helper's own refusal had
suggested "restore, re-inspect and pass --scope-override", which design
§12.3 rule 5 still allowed from the PDN-0003 rule, while dispatch step
14 said to reject plan_tampered.

Each inspection now records plan_touched (an optional member of the
receipt's inspection entries), and accept is refused once any inspection
saw the plan touched and the latest no longer does. The refusal message
no longer suggests restoring. The skill states that a plan change seen
while the verdict is pending is never the host's own: the host edits the
plan only after the verdict. The reference host rejects on the history
too; tests/attempt.sh and Layer A case F2d reproduce the real host's
steps and fail without the guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In F2 the pickup session also did task 1.2, and its new file made the
oracle's final-tree check fail for a reason unrelated to the scenario.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Layer B host loads passdown from the plugin channel pinned to
release/v0.5.0; the new beta makes the plugin update pick up the
restored-plan guard before F2 is run again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@vunm-dev vunm-dev left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reviewed cd5b25b. Found one P2 issue in recovery across the beta.2 → beta.3 upgrade; please address it before merging.

Validation: acceptance (7), F2d (1), skills (156), contracts (39), bundle synchronization and version checks passed. All PR CI checks are green. The additional cross-version reproduction fails as described in the inline comment.

AI-Reviewed-By: Codex

Comment thread scripts/passdown-attempt
@@ -1705,7 +1705,7 @@ cmd_inspect() {
| .artifact.digest = $a.digest | .artifact.changed = $a.changed
| .artifact.out_of_scope = $a.out_of_scope | .artifact.overlaps_baseline = $a.overlaps_baseline
| .artifact.plan_touched = $a.plan_touched

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[P2] Preserve earlier plan-tampering evidence when upgrading an existing receipt

A beta.2 inspection stores artifact.plan_touched: true, but its artifact.inspections[] entries do not contain plan_touched. If a recovery session upgrades to beta.3, restores the plan and runs inspect, this assignment overwrites the existing true with false and appends only a false history entry. The new acceptance guard therefore finds no historical true and accepts the attempt, even without --scope-override. This leaves the restored-plan loophole open for pending receipts created before the upgrade, which remain schema-valid.

Reproduced using the actual helper from the PR base for the first inspection and the PR helper for re-inspection and acceptance: the new restoration regression expects exit 3 but gets exit 0 (accepted). Preserve the previous evidence before overwriting it, or recover it from the existing inspect.json history, and add a beta.2-to-beta.3 recovery regression test.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Confirmed: a beta.2 inspection leaves plan_touched only in artifact.plan_touched and inspect.json, and the beta.3 inspect overwrote it. Fixed in 7aa1abd, both ways you suggested: inspect now moves the previous artifact.plan_touched onto the latest entry before overwriting it, and the accept guard also reads inspect.json (every beta has written each inspection's full record there). Regression tests in tests/attempt.sh use receipts shaped like beta.2's: (a) one beta.2 inspection, then restore and re-inspect on beta.3 (backfill path, inspect.json removed); (b) inspection and restore both under beta.2 (only inspect.json remembers). Removing either half of the fix makes its case fail.

A receipt inspected by a beta.2 helper has inspection entries without
plan_touched; the evidence lives only in artifact.plan_touched and in
inspect.json. A beta.3 re-inspection after restoring the plan overwrote
it with false and appended a false entry, so the new guard found no
earlier true and accepted.

Inspect now moves the previous artifact.plan_touched onto the latest
entry before overwriting it, and the accept guard also reads
inspect.json, which has always held every inspection's full record.
tests/attempt.sh covers both paths with receipts shaped like beta.2's;
each half of the fix is caught by its own check when removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vunm-dev
vunm-dev merged commit eedd5f5 into release/v0.5.0 Sep 28, 2026
7 checks passed
@vunm-dev
vunm-dev deleted the fix/pdn-0004-s7-restored-plan-not-accept branch September 28, 2026 10:28
vunm-dev added a commit that referenced this pull request Sep 30, 2026
All seven gate scenarios (F1, F2, F11, F14a, F14b, F18, F27) pass on a
real host: claude -p 2.1.193 with the passdown plugin pinned to
release/v0.5.0, the owner attesting every stop by hand. The gate found
two protocol gaps, fixed in #24 and #25 before those scenarios were run
again; the failing runs are kept as F1-fail-1 and F2-fail-1.
docs/evidence/v0.5.0/README.md summarizes the runs, the findings, the
harness changes and what was observed but not blocking.

Two harness changes come with the evidence:

- oracle.sh judges must-not-project on the interrupted attempt. The
  first oracle failed F14a because the host, correctly, redid the
  changed task in a new attempt and projected that one; the interrupted
  verdict must still never be projected, and a tick must be backed by a
  projected attempt on the current task revision. The first judgment is
  kept in F14a/oracle.first.txt.
- sanitize.sh prepares a run for publication: local paths, the
  operator's e-mail, the init, hook and rate-limit events, thinking
  counters and signatures. oracle.sh gives the same verdict on the
  sanitized records, and they pass the workspace's boundary denylist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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