Skip to content

Never let a host line accept a rejected attempt's output - #24

Merged
vunm-dev merged 4 commits into
release/v0.5.0from
fix/pdn-0004-s7-rejected-not-main
Sep 27, 2026
Merged

vunm-dev merged 4 commits into
release/v0.5.0from
fix/pdn-0004-s7-rejected-not-main

Conversation

@vunm-dev

Copy link
Copy Markdown
Member

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

What Layer B found

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

  1. The fake worker created src/hello.txt and ticked its own checkbox.
  2. After the owner's attestation, the host found the tick and rejected the attempt plan_tampered, restoring [ ]. Correct so far.
  3. Then it verified the rejected worker's file in place and appended Dispatched: main — accepted, ticking the task. The fixture's owner routing requires fake for task 1.1 with no fallback.

Oracle: FAIL task 1.1 is ticked (no accepted receipt backs it). The run is kept as evidence (docs/evidence/v0.5.0/F1-fail-1, to land with the evidence PR).

Cause: dispatch step 16 said "If you then finish the task yourself, append Dispatched: main … accepted" with no condition. Taking over the rejected output read as finishing the task, and it bypassed salvage, which does not allow plan_tampered.

Changes

  • passdown-dispatch step 16: a host line never accepts a rejected attempt's output. A rejected owner-mandated task (no fallback) stays pending for the owner. Any other rejected task is finished by the host only through salvage (for the reasons it allows), or after release-claim and cleanup, redoing the work in the open. The routing rule now names the rejected case next to the ineligible one.
  • tests/skills.sh: three prose checks for the rule.
  • Layer B runner: empty answers are ignored and only end finishes a run, with a warning while an attempt is pending. Three early F1 runs were cut short by a stray Enter before the host could act on the attestation.
  • Beta bumped to 0.5.0-beta.2 so the pinned plugin channel picks up the fix before F1 runs again.

Verification

  • tests/skills.sh, docs.sh, version.sh, attempt.sh, contracts.sh, interrupt.sh (Layer A, 37 cases) pass; shellcheck clean; scripts/check-version.sh passes.
  • After merge: update the plugin to beta.2 and rerun F1 on the real host.

🤖 Generated with Claude Code

vunm-dev and others added 3 commits September 27, 2026 15:44
An empty answer used to end a run. In the first F1 runs a stray Enter,
or a paste that began with a newline, ended the run while the host was
still waiting for the owner's attestation, and the oracle judged a run
the host never finished. Empty lines are now ignored, and ending while
an attempt is still pending asks for a second "end".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Layer B F1 on a real host: the worker ticked its own checkbox, the host
rejected the attempt plan_tampered, then verified the rejected worker's
file in place and recorded "Dispatched: main — accepted", although the
owner's routing required that executor with no fallback. Step 16 said
"if you then finish the task yourself, append a main line" with no
condition, so taking over the output read as finishing the task.

A rejected owner-mandated task now stays pending for the owner. Any
other rejected task is finished by the host only through salvage, for
the reasons salvage allows, or after the output is given up and the work
is redone in the open. The routing rule names the rejected case next to
the ineligible one.

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; a new beta version makes the plugin update pick up the
dispatch fix before F1 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.

Found one P2 issue in the new post-rejection recovery instructions; please address it before merging. The owner-mandated/no-fallback branch correctly makes the task remain pending, but the other branch requires a helper operation that fails for already-released claims.

Validation on this head: tests/skills.sh (153 checks), tests/docs.sh (32 checks), tests/version.sh (4 checks), scripts/check-version.sh, scripts/sync-bundled.sh --check, and shellcheck for the changed runner and canonical helper all passed. All seven GitHub CI checks are green. I also reproduced the finding using the deterministic reference host and fake worker in a disposable fixture: tick-checkbox -> rejected:plan_tampered -> claim.state=released -> release-claim exits 3. This was a simulated test-harness attestation, not real Layer B owner evidence. The real-host F1 rerun on beta.2 remains necessary to validate the behavioral fix. No repository files were modified.

Comment on lines +424 to +426
only through salvage, for the reasons salvage allows, or give it up
first (`release-claim`, then *Cleanup after a failed attempt*) and do
the work in the open. Then append `- Dispatched: main (<YYYY-MM-DD>)

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] Skip release-claim when rejection already released the claim

For a task where host takeover is permitted, a stopped attempt rejected as plan_tampered (also scope_violation or verification_failed) has its claim released automatically by verdict reject. The new redo path nevertheless requires release-claim before cleanup. Reproduced with ref-host dispatch --mode tick-checkbox: the receipt ends rejected:plan_tampered with claim.state=released, and the prescribed release-claim exits 3 with 'does not hold a claim'. Since the skill also says to report helper refusals rather than force past them, following this path blocks an otherwise valid cleanup and redo. Make release-claim conditional on a still-held claim; for an already-released claim, proceed to the same attribution-safe cleanup before redoing the work.

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: HOLDING_REASONS in the helper is needs_input blocked invalid_result reemit_wrote integration_failed, so a plan_tampered / scope_violation / verification_failed rejection has already released the claim and release-claim exits 3. Fixed in 1d00309: the redo path runs release-claim only when the rejection kept the claim (those five reasons), and Cleanup after a failed attempt now applies once the claim is released either way. Added a prose check in tests/skills.sh.

A rejection with plan_tampered, scope_violation or verification_failed
releases the claim itself, and release-claim on such an attempt exits 3
("does not hold a claim"). The redo path asked for release-claim
unconditionally, so a host following it hit a helper refusal it must
not force past. Only the claim-holding reasons (needs_input, blocked,
invalid_result, reemit_wrote, integration_failed) need release-claim;
cleanup applies once the claim is released either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vunm-dev
vunm-dev merged commit 42bd2a1 into release/v0.5.0 Sep 27, 2026
7 checks passed
@vunm-dev
vunm-dev deleted the fix/pdn-0004-s7-rejected-not-main branch September 27, 2026 09:35
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