Skip to content

fix(pm): an accepted relay dispatch with no run is UNCONFIRMED under auto too, never a direct write - #22101

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-21892-no-run-is-unconfirmed
Oct 7, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-21892-no-run-is-unconfirmed

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21892

Clause-②: no

What changes

After a relay dispatch the platform ACCEPTED (HTTP 204), "no run appeared within the start window" — or a run that never completed — is now answered UNCONFIRMED by every relay sender: exit 6, the shared unconfirmedText sentence (go READ the card, never re-run blind), under auto exactly as under an explicit dispatch. No sender takes a direct write after an accepted dispatch any more, so a relay that is merely queued can no longer turn into a write under the seat's personal login (and a second write when the queued run executes). OS_FLEET_TRANSPORT=direct stays the only way to write as the personal account; the route-selection read (condition ③ in fleet-write/dispatch.mjs: the workflow file's 404, disabled_manually) is unchanged.

sender on main (3d9188502e) under auto + no-run on this branch
scripts/pm/label-write.mjs fallbackText(...) then the direct additive POST/DELETE exit 6, zero requests after the pre-read
scripts/pm/issue-create.mjs fallbackText(...) then the direct POST exit 6, zero requests
scripts/pm/issue-transfer.mjs fallbackText(...) then the direct GraphQL mutation exit 6, zero requests after the pre-read
scripts/pm/post-stamped.mjs fallbackText(...) then the direct comment POST / body PATCH exit 6, zero requests
scripts/pm/close-cards.mjs already exit 6, no direct fall-back unchanged; carries the conformance probe

fallbackText is deleted from fleet-write/dispatch.mjs: after the change it has no caller anywhere in the tree (git grep -n fallbackText at 776ab7f2ff: 0 hits; with-fleet.sh never called it — its auto asks dispatch.mjs --route before anything is sent, and an actions file goes to this file's CLI, which has no direct path).

The enumeration pin (the triage grade's "a tool added later fails the pin")

node scripts/pm/fleet-write/dispatch.mjs --self-test gains one battery, the no-run conformance (27 cases, floor 18 batteries):

  • the senders are DISCOVERED from source on every run — every code file under scripts/ except dispatch.mjs that names sendFleetWrite — with a by-name floor of today's five, so a scan gone blind reds;
  • each sender must export relayMissProbe({ route, send, pace }): ONE write through its real write path against its own offline fake platform. A sender without it fails the pin;
  • each is driven through the REAL sendFleetWrite over a fake platform with an accepted (204) dispatch and (a) no run under auto, (b) no run under explicit dispatch, (c) a run that never completes under auto. Each must answer exit 6, after exactly ONE dispatch, with ZERO direct writes (any request but GET/HEAD), printing the shared UNCONFIRMED sentence;
  • the control drives each probe under direct: its write is issued directly and seen by the recorder, and nothing is dispatched — so the empty lists above are a measurement;
  • dispatch.mjs's own CLI answers the same (exit 6, one dispatch, no other write).

The per-tool pins that said "auto falls back only on no-run" are flipped in label-write, issue-create and issue-transfer to exit 6 and the exact request list after the dispatch (pre-read only, or none); close-cards already pinned it. post-stamped's self-test is synchronous, so its behavioural pin is the conformance; its own gate now also carries the structural half (main writes only through writeThroughRoute, whose one direct write sits on the non-dispatch branch ahead of the relay, and whose no-run/timeout branch returns UNCONFIRMED). CI runs the dispatcher self-test on every PR (the Fleet-write dispatcher self-test step in lint.yml carries no if:).

Inherited commits — read as UNVERIFIED, verdict per hunk

The branch continues the released claim's work at 4c5f2c15fc (7 commits over 607463d736). origin/main was merged in (69f2346dca, merge of 3d9188502e, no rebase — the branch is shared); main had not touched the six files since 607463d736 (git diff --stat 607463d736 origin/main over the six paths: empty), and the merge left the branch's delta at exactly these six files (+402 / -94).

commit hunk verdict
a6bc9b3e08 label-write header paragraph; import drops fallbackText; the auto && no-run arm removed, the remaining arm answers no-run/timeout UNCONFIRMED for every requested transport keep
relayMissProbe over driveOffline (the real runLabelWrite) keep
battery renamed; flipped case asserts exit 6, ONE dispatch, calls equal to the pre-read alone; second case asserts the shared sentence keep
battery pin left at 12 while the battery registers 14 corrected to 14 in 776ab7f2ff
51139a6248 issue-create header, import, branch, relayMissProbe, flipped case (exit 6, number null, zero requests) keep; pin 8 → 9 corrected
b1cebeea98 issue-transfer header, import, branch, relayMissProbe, flipped case (exit 6, pre-read only) keep; pin 8 → 9 corrected
f99f21158a post-stamped deps door on rest / readCardTail / writeArtefact / writeViaRelay (the live path passes none; every default is the old value) keep
writeThroughRoute extracted from main: the old branches, minus the auto + no-run fall-back; main now one call, and its catch still classifies a direct write's failure keep
relayMissProbe; structural pin retargeted to writeThroughRoute( keep; own-gate structural pin added in 776ab7f2ff (battery pin 12 → 13)
27f2f3ed9f close-cards stubs.send passthrough; relayMissProbe spelling the two child tools' writes as the requests they stand for keep
846527f4c0 dispatch.mjs header: never fallen back from under any transport request; the no-run conformance section keep
fallbackText deleted; its self-test case replaced by "accepted (204), no run known" keep (no caller remains)
the conformance battery (27) and floor 17 → 18 keep
RELAY_MISS_PROBE declared between DEFAULT_START_MS and DEFAULT_CEILING_MS corrected: moved below the three timing exports; the success line now names the conformance
4c5f2c15fc a probe that throws fails as a case (drive wrapper), per-tool second cases guarded with ?. keep — ablation B below fails as cases, not as a crash

Dropped: none.

Self-tests at 776ab7f2ff (rule ⑤: each edited script's own; no *.test.* under scripts/ or tests/ names any of the six — git grep exit 1)

✓ fleet-write/dispatch self-test: 200 cases pass across 18 batteries — … (the no-run conformance).
✓ label-write self-test: 92 cases pass across 10 batteries — …
✓ issue-create self-test: 48 cases pass across 7 batteries — …
✓ issue-transfer self-test: 76 cases pass across 9 batteries — …
✓ post-stamped self-test: 630 cases pass across 22 batteries — offline, no network, no token.
OK close-cards self-test: 227 cases pass across 20 batteries — offline, no network, no token.

The inherited tree alone (69f2346dca, before 776ab7f2ff) was green on the same six, post-stamped at 629 cases.

What each probe's recorder sees (a scratch driver handing each relayMissProbe an accepted no-run result, then direct):

close-cards     auto+no-run exit 6  [GET issues/88, GET …/timeline, GET …/comments]
                direct      exit 0  [… PATCH issues/88, POST …/comments, DELETE …/labels]
issue-create    auto+no-run exit 6  []
                direct      exit 0  [POST /issues, GET issues/12]
issue-transfer  auto+no-run exit 6  [GET issues/7]
                direct      exit 0  [GET issues/7, GET repo, POST /graphql, GET issues/7, GET target issues/31]
label-write     auto+no-run exit 6  [GET issues/7]
                direct      exit 0  [GET issues/7, POST issues/7/labels, GET issues/7]
post-stamped    auto+no-run exit 6  []
                direct      exit 0  [POST issues/7/comments, GET issues/comments/501]

Ablation — both directions, at 776ab7f2ff, through scripts/ablation-replace.mjs (anchor must hit, blob must change, restore proven)

leg mutation own self-test dispatcher self-test
A post-stamped re-opens auto + no-run → writeArtefact exit 1: the structural pin got [[],2,true,true,true] exit 1: post-stamped.mjs: auto + an ACCEPTED dispatch + no-run got [0,1,204,"no-run",["POST /repos/objectstack-ai/objectstack/issues/7/comments"],false]
B label-write stops exporting relayMissProbe exit 0 (the export is the conformance's door, not label-write's) exit 1: exports relayMissProbe got "undefined"; battery floor 23 below 27
C label-write re-opens auto + no-run → direct exit 1: 2 cases, calls [GET issues/7, POST issues/7/labels, GET issues/7] exit 1: label-write.mjs: auto + … + no-run got [0,1,204,"no-run",["POST …/issues/7/labels"],false]

Every leg restored: blob equals HEAD (post-stamped.mjs f51c37c8f5, label-write.mjs b364caeacc), git diff HEAD 0 bytes, git status --porcelain empty. The green direction is the same commands on the restored tree (the self-test block above). The first attempt of legs A and C was refused by ablation-replace (the replacement still contained the anchor, "the anchor count moved 1 to 1") — no command ran, not a measurement; both were re-anchored and re-run.

Gate families (derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths: 6 paths vs merge base 3d9188502e, 38 commands)

All at 776ab7f2ff, exit codes captured before any pipe:

  • exit 0 (37): check-ci-filter-parity, check-closing-keyword-parity (+ --self-test), check-comment-mask-corpus, check-declaration-mirrors (+ --self-test), check-scripts-symbol-anchors (+ --self-test), check-self-test-wired (+ --self-test), check-self-test-workflow-commands (+ --self-test), check-whole-set-label-write (+ --self-test), pm/bare-root-worklist --self-test, check:agent-test-spelling, check:bash32-floor, check:cli-command-ids, check:cross-package-test-inputs, check:driver-memory-census, check:entry-guard, check:gitlink-declared, check:nul-bytes, check:parse-guard, check:pm-close-cards, check:pm-fleet-write-dispatch, check:pm-fleet-write-execute, check:pm-fleet-write-validate, check:pm-issue-create, check:pm-issue-transfer, check:pm-label-write, check:pm-post-stamped, check:pm-write-pace, check:pnpm-filter-targets, check:ratchet-remedy-authority, check:refd-timer-probe, check:watch-hint-literal.
  • check:pm-dispatch-gates (the battery, an unlocked run): exit 0 — ✓ check:pm-dispatch-gates --self-test: the exit contract holds in all three directions. then ✓ dispatch-gates self-test: 1976 cases pass. (1279.8 s on this box).
  • --ran reconciliation, exit 0: Run reconciliation — 38 derived, 38 run, 0 NOT-MEASURED, 0 UNRUN. (all 38 carry a recorded exit code). origin/main has since moved to b04a5295f7 without touching the six files or their imports.
  • Roster families whose roster sits in a directory this diff touches: exit 0 — check-published-list-mirrors (+ --self-test), check:engine-double-contract, check:i18n-stale-fill, check:pm-label-desc-cap, check-dts-references --self-test. NOT MEASURED — check:dts-closure and check:published-readme-exports (exit 3 PREREQUISITE NOT MET: no package dist/; this diff touches no package), check:console-injection (exit 0 but skipped: no console dist).
  • Lint, a declared narrowing: pnpm exec eslint --no-inline-config --format json over the six files — 6 files, 0 errors, 0 warnings (none reported as ignored). Population: eslint.config.mjs's **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} block covers scripts/pm/*.mjs. Invariance: the config never enables type-aware linting (no parserOptions.project, said in its own header) and its plugin rules are per-file, so this diff cannot move any untouched file's verdict. The repo-wide pnpm lint is CI's.

Acceptance notes

Writes

git push (twice: the merge, then 776ab7f2ff); REST through the relay as objectstack-fleet[bot]: this pr_create (draft), one label-write (skip-changeset + PR assignee), one os-dev-report comment on the card. MCP GitHub calls: 0. No changeset: scripts/pm/** publishes nothing.


Generated by Claude Code

claude added 9 commits October 5, 2026 16:37
… no run under auto, never a direct write

An accepted relay dispatch whose run had not appeared in the start window
fell back to the direct additive verbs under auto, booking the stroke
against the seat's personal account. That arm is gone: no-run is exit 6
under auto exactly as under an explicit dispatch, and the self-test pin
that held the fall-back now holds the ruled answer.

Claude-Session: https://claude.ai/code/session_011SekRJwTRqXSmsP6xTSci4
Co-authored-by: Claude <noreply@anthropic.com>
…h no run under auto, never the direct POST

Under auto, a no-run outcome printed the fall-back line and created the
card directly as the seat's personal account; the relay run could then
create a second card. No-run is now exit 6 under auto as under an explicit
dispatch, the pin that held the fall-back holds the ruled answer, and the
tool carries the no-run conformance probe.

Claude-Session: https://claude.ai/code/session_011SekRJwTRqXSmsP6xTSci4
Co-authored-by: Claude <noreply@anthropic.com>
…ith no run under auto, never the direct mutation

Under auto, a no-run outcome printed the fall-back line and sent the
transfer mutation as the seat's personal account. No-run is now exit 6
under auto as under an explicit dispatch, the pin that held the fall-back
holds the ruled answer, and the tool carries the no-run conformance probe.

Claude-Session: https://claude.ai/code/session_011SekRJwTRqXSmsP6xTSci4
Co-authored-by: Claude <noreply@anthropic.com>
…h no run under auto, never the direct write

The transport step moves out of main into writeThroughRoute: the relay's
one dispatch or the direct verb, never both. Under auto a no-run outcome
used to null the relay result and fall through to writeArtefact as the
seat's personal account; it is now exit 6 as under an explicit dispatch.
The live layer takes an optional deps door so the no-run conformance probe
drives the real write step offline; the live path passes none.

Claude-Session: https://claude.ai/code/session_011SekRJwTRqXSmsP6xTSci4
Co-authored-by: Claude <noreply@anthropic.com>
close-cards already answers an accepted dispatch with no run as
UNCONFIRMED under every transport request and issues no direct write. It
gains the relayMissProbe export the shared no-run conformance drives,
and its offline driver accepts the conformance's sender; no behaviour of
the tool changes.

Claude-Session: https://claude.ai/code/session_011SekRJwTRqXSmsP6xTSci4
Co-authored-by: Claude <noreply@anthropic.com>
…D with zero direct writes

fallbackText lost its last caller and is deleted with its pin; the header
now says no outcome of an accepted dispatch is ever fallen back from,
under any transport request. A new self-test battery discovers every
sender from the source (any scripts/ file naming sendFleetWrite), drives
each one's relayMissProbe through the real sendFleetWrite with an accepted
dispatch whose run never appears (auto and explicit dispatch) or never
completes (auto), and requires exit 6, one dispatch, zero direct writes and
the shared UNCONFIRMED sentence; a direct-route control proves each
recorder sees a write. A sender without the probe fails until it has one.

Claude-Session: https://claude.ai/code/session_011SekRJwTRqXSmsP6xTSci4
Co-authored-by: Claude <noreply@anthropic.com>
…a crash

Under a re-planted fall-back the result carries no relay outcome, and the
shared-sentence case threw before it could record a failure. Each pin now
reads false when there is no relay outcome, so an ablation reports the
failed case and the self-test still reaches its verdict.

Claude-Session: https://claude.ai/code/session_011SekRJwTRqXSmsP6xTSci4
Co-authored-by: Claude <noreply@anthropic.com>
Continues the released claim's branch at 4c5f2c1 (no rebase: the branch is shared).

Claude-Session: https://claude.ai/code/session_01CXydFDyiQwNbGFkmwrcRQq
Co-authored-by: Claude <noreply@anthropic.com>
…d's write step to one direct write off the relay

- label-write / issue-create / issue-transfer: the relay battery pins rise to the cases they now register (14 / 9 / 9),
  so the added "the answer an EXPLICIT dispatch gives" case is floored rather than riding slack.
- post-stamped: its own gate gains the synchronous half of the no-run rule — main writes only through
  writeThroughRoute, whose ONE direct write sits on the non-dispatch branch ahead of the relay, and whose no-run /
  timeout branch returns UNCONFIRMED (the behaviour stays driven by dispatch.mjs's no-run conformance).
- dispatch.mjs: RELAY_MISS_PROBE moves below the three timing constants it had split, and the success line names the
  no-run conformance it now carries.

Claude-Session: https://claude.ai/code/session_01CXydFDyiQwNbGFkmwrcRQq
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/m label Oct 7, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 7, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 7, 2026 16:20
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 7, 2026 16:20
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 7, 2026
Merged via the queue into main with commit 172be37 Oct 7, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21892-no-run-is-unconfirmed branch October 7, 2026 17:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants