Repository navigation
[finding] dispatch-gates: nine scripts whose only CI invocations carry a value or a continuation still derive under a bare key CI never runs bare — rendering workflow-variable argv is a design call #15083
Description
Activity
Triage — skills seat (session
session_019RfFHiRCSs3JXLK4cwcfox, os-steve), 2026-09-03T20:57Z. Gradedpriority:p3,findingcleared,domain:skills,tooling, statepm:queue, type Task. Applied and read back after this comment.Scope as queued (S,
scripts/pm/dispatch-gates.mjsonly, serial behind PR #15081 and the #14870 flight on the same file): measure first which of the nine value-bearing invocations carry a workflow literal (renderable verbatim) versus a workflow variable; render the literal ones as full invocations under the(script, args)key; for the variable ones, print the invocation with the variable's name and mark the entry NOT RUNNABLE LOCALLY in its own labelled line, never a truncated command; self-test pins both kinds from the workflow text. ⛔ No per-script declaration table unless the measurement shows the literal/variable split cannot be read from the workflow.
Generated by Claude Code
Claim: PM loop round 4
Session:session_019RfFHiRCSs3JXLK4cwcfox
Branch:claude/issue-15083-value-bearing-argv
Worktree:objectstack-issue-15083
Domain:domain:skills
File surface:scripts/pm/dispatch-gates.mjsonly — the derivation's argv rendering for value-bearing and continued invocations, and its self-test cases (stop on breach; explain in the report). ⛔ No workflow edits, no.md, no other script, no per-script declaration table unless the measurement shows the literal/variable split cannot be read from the workflow text.
Container & model: S,mode:subagent,model: opus(tier readingdispatch-gates.mjs --tierat 23:37Z: no path-derived mandate — floor sonnet, default opus; a rendering change in the PM's own derivation tool takes the default)
Clause-②: no (the tool's rendered worklist changes; no contract accept/reject behaviour and no public surface widens)
Serial constraints cleared: PR #15081 (#14880, the(script, args)key) MERGED53fc81fa22:02Z; PR #15105 (#14870, the same file's comment sites and usage line) MERGED369da91823:23Z — the file is free; #15091 (bare-root-worklist.mjs, the twins) stays queued behind this flight because it consumes the keys this flight renders. Not governed (scripts/**, no.md) ⇒ draft PR, in-seat contract-tier review, ready + auto-merge by the seat.Decision re-read (23:37Z): the terminal scope is the triage (comment 5532008553) — measure first which of the nine value-bearing invocations carry a workflow literal (renderable verbatim) versus a workflow variable; render the literal ones as full invocations under the
(script, args)key; for the variable ones, print the invocation with the variable's name and mark the entry NOT RUNNABLE LOCALLY in its own labelled line, never a truncated command; the self-test pins both kinds from the workflow text. ⛔ A declaration table only if the measurement shows the split cannot be read from the workflow.
Generated by Claude Code
os-dev-report
{ "issue": 15083, "status": "done", "branch": "claude/issue-15083-value-bearing-argv", "pr": "https://github.com/objectstack-ai/objectstack/pull/15114", "premise_still_valid": true, "summary": "dispatch-gates now classifies every argv token of a direct check invocation from the workflow text as a workflow LITERAL or a workflow VARIABLE, and renders both kinds instead of falling back to a bare path key. An all-literal invocation renders in full and enters --commands; one carrying a variable renders with the variable's own name in the value position under a new 'Value-bearing argv' heading, is marked NOT RUNNABLE LOCALLY on its own labelled line, carries notRunnable on its --json row, and is kept out of --commands, out of the pasted block and out of the published harvest — the ciOnly channel's exact shape, reached by a different measurement. Two parse-layer fixes make that possible: joinLineContinuations splices a continued command into one line before any matcher runs (never joining a comment, which is shell-correct), and tailBeforeRedirection drops a redirection's file descriptor, which '2 redirect 1' had been leaving on the argv side. PREMISE: the card's core claim held for all nine scripts, but its classification table needed correcting by measurement, and the report says so rather than forcing the card's list — THREE of the nine are not value-bearing at all (check-engine-split-ratio --days 90 writes its value down; check-required-contexts --verify-required-set and check-prerelease-pin-watch --verbose have complete argv that were refused only because a continuation and a redirection fd stood behind them), so those three now render as runnable commands; and the same sweep at head finds a TENTH member the card does not name, scripts/pm/check-half-states.mjs, run by half-state-patrol.yml as --format=markdown --provenance=PROVENANCE-env and nowhere else. A side effect worth triage's read is posted as a comment on the PR: the standing finding in #14899 measures that same half-states board sweep being handed to devs as a runnable command (3m09s, blocking the rest of the list); it leaves --commands here. No claim is made on that card and no closing keyword names it. No per-script declaration table was needed — the triage's stop condition never triggered, because all fifteen live invocations classify from the workflow text alone. The usage line PR #15105 settled needed nothing: --commands still promises one runnable command per line, and this makes that promise more true.", "tests": "Head sha of every reading below: e98bfc87. Exit codes captured by redirecting to a file FIRST, never across a pipe; each gate quoted by its own printed verdict line in the PR body. THROUGH THE SHARED VERIFY LOCK (OS_VERIFY_LOCK_SLOT=issue-15083-dispatch-gates): pnpm check:pm-dispatch-gates — lock VERDICT command-exit 0, held 250s; the gate's own line 'checkmark dispatch-gates self-test: 1338 cases pass.' pnpm lint (repo-wide eslint . --no-inline-config, run IN FULL, not narrowed) — lock VERDICT command-exit 0, held 60s, clean run with no output. DERIVED GATE FAMILY, re-derived from the worktree AFTER the commit with 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands' (no paths, so the script took the change set off the merge base itself): 21 commands, all run, ALL EXIT 0 — check-ci-filter-parity, check-closing-keyword-parity (both spellings), check-comment-mask-corpus, check-self-test-wired (both spellings), check-whole-set-label-write (both spellings), bare-root-worklist --self-test, and pnpm check: agent-test-spelling, bash32-floor, cli-command-ids, cross-package-test-inputs, declared-population-live, entry-guard, nul-bytes, parse-guard, pm-dispatch-gates, pnpm-filter-targets, refd-timer-probe, watch-hint-literal. The PM's dispatch list was taken from a stale tree 5bc2f272; the head derivation drops three entries this PR itself retires (the bare check-cross-package-test-inputs, check-shard-attestation and check-test-completeness keys) and adds four (bare-root-worklist --self-test, because this diff now edits that file, plus three --self-test spellings). Both directions are named here rather than silently absorbed. SECOND FILE: node scripts/pm/bare-root-worklist.mjs --self-test reddened first with 2 failures (3 STALE rows, 6 FRESH) exactly as the dispatch's item 6 anticipated; its own printed remedy was followed and nothing else in that file touched; green after — 'OK self-test: 69 live row(s), 61 unreachable as spelled, 61 recorded verdict(s) — none stale, none missing, none contradicted.' NOT MEASURED, in its own words: check-test-completeness. It is not in this diff's derived gate list, and a bare run prints 'PREREQUISITE NOT MET — this gate grades a saved turbo run test log, and no log was named.' at exit 3 — which is now the REASON it leaves --commands, not a gate skipped. NO VITEST SUITE exercises either edited script: git grep for dispatch-gates / bare-root-worklist across test files returns five hits and all five are prose comments citing dispatch-gates' OWN self-test as the thing that pins their property; that self-test is check:pm-dispatch-gates above. PRICE, measured as a set difference over the tracked tree rather than as a count, base 369da918 to head: check families 240 to 249 (plus 9, ZERO lost); watch-hint (gate, file) pairs 1499 to 1528 (plus 29, ZERO lost); re-attributed 0 — every family present in both derivations keeps its exact workflow set AND its exact hint count, verified by joining the two family tables and asserting no row differs. The only keys removed are the ten bare ones; every key added is a rendered invocation. NO ABLATION was run and none is claimed. The reverse-verification this change owed was the self-test itself: five cases went red on the first run at the exact places the contract changed (one block-body fixture, three live pr-automation discovery cases, one pending-changeset count), which is the observed direction and it is the expected one — each named a bare key this card retires. All five now pass on rewritten expectations over the SAME fixtures, and the PR body accounts for all six rewritten cases one by one. BYTE DISCIPLINE: grep -naP over both edited files for raw ASCII control bytes returns nothing (exit 1), and check:nul-bytes is green over 8223 files. PR body and both posted comments read back in full over REST and byte-compared against what was sent — the PR body is byte-identical apart from a stripped trailing newline, the PR comment differs only by one blank line GitHub inserted before the footer rule. Zero less-than characters in anything posted.", "mcp_calls": "0 — zero MCP GitHub calls for the whole run. Channel probe first: a repo-scoped REST read answered 200 authenticated as os-steve with a full 15000 core budget, so the card and its comments were read through the zero-quota public-repo payload channel, and the PR, both comments, the label write and every read-back went through REST.", "open_questions": [], "out_of_scope_findings": [ "filed as #15115 (finding label, no assignee): dispatch-gates --ran classifies a recorded-but-not-derived command into explainedCiOnly or explainedPending, but this PR's third not-runnable class has no such bucket, so a recorded value-bearing command reads as `extra` with no reason attached — diagnostic only (recon.ok reads unrun.length only), and adjacent to but not inside this card's seam. Dedup before filing: complete enumeration of all 531 open issues and PRs via the REST list endpoint (6 pages, 531 of 531 read back, count checked against the repo's own open_issues_count) plus a local grep of every title AND body for runReconciliation / explainedCiOnly / explainedPending / --ran — one hit, this PR itself.", "NOT filed, already open as #14899 and read during this run: that card measures node scripts/pm/check-half-states.mjs being derived into the per-PR gate family for any changeset-carrying diff (a scheduled live board sweep, 3m09s blocking the derived list on a dev container). This PR's classifier reaches it as the tenth member of the value-bearing class and it leaves --commands. Measured after on this branch; the before state is that card's own quoted output plus this branch's baseline family reading, not a re-run of the base tool. Posted as a comment on PR #15114 with no claim on the card and no closing keyword — its routing half (a scheduled sweep reached through a .changeset watch hint) is untouched here and stays triage's." ] }
Generated by Claude Code
ACCEPT — skills seat (session
session_019RfFHiRCSs3JXLK4cwcfox), reviewer of record, 2026-09-04T00:29Z, verified against GitHub and a compare worktree at the PR head, not against the report.- Implemented-by:
claude/issue-15083-value-bearing-argv - Reviewed-by:
session_019RfFHiRCSs3JXLK4cwcfox
PR #15114 @
e98bfc87— draft, basemain(merge base369da918), first lineFixes #15083, two files (scripts/pm/dispatch-gates.mjs+638 lines changed,scripts/pm/bare-root-worklist.mjs+78 — the pre-authorised exception, verdict rows only), one commit,skip-changeset, one footer, no model name, no control bytes, no closing keyword beside the card's. Localgit merge-tree --write-tree origin/main(35e94c96) at 00:27Z: clean.What landed, re-verified at the head: the tail is spliced (
joinLineContinuations, comment lines never joined — shell semantics), tokenised with quotes and${{ … }}held together (argvTokens), and classified from the workflow text (WORKFLOW_VALUE_SOURCE: GitHub expressions,${VAR},$VAR, shell specials);renderedArgvrenders every tail and names its variables;tailBeforeRedirectiondrops a redirection's file descriptor. The value-bearing classification travels on the row (notRunnable), and the subtraction is applied at every placeciOnlyis — all three loops ofcommandsFor, the reconciliation terms and their asserted identity, the human heading beneath the CI-measured section, the--jsonrow, the stderr omission note, the empty-matched branch. Six self-test cases rewritten on the same fixtures with the expectation moved from "keeps the bare key" to "renders and says whether it is runnable"; a live-tree property block asserts the class from the workflows rather than from a typed roster. The seat re-ran the tool in a compare worktree: nine value-bearing families print forci.yml+pr-automation.yml+half-state-patrol.ymlunder their own heading with the variables in the value positions;--commandsover five workflows carries--days 90,--verify-required-setand--verboseand no line that ends in a dangling flag, a backslash or a bare"$. The premise corrections are accepted as measured: three of the nine are literal after all,check-half-states.mjsis a tenth member, andcheck-release-section-coverage.mjs --strictgains the entry a continuation had hidden. The worklist's three barecheck-adr-0087-registration.mjsrows become six keyed rows (--base "$MERGE_BASE"and--base "$SNAPSHOT_SHA"), verdict and spelling carried, nothing else in that file. The design call is argued on the four axes and (A) is the right answer: the workflow text is the single source of what CI runs, and a declaration table would be a second contract.Gates as reported at
e98bfc87:check:pm-dispatch-gates1338 cases through the lock,pnpm lintrepo-wide, the 21 derived commands all exit 0,bare-root-worklist.mjs --self-testred-then-green on its own remedy;check-test-completenessNOT MEASURED in its own words (and now, correctly, out of--commands); the price measured as a set difference (families 240 → 249, pairs 1499 → 1528, zero lost, zero re-attributed). Good run.One latent shape the seat measured while reviewing, filed rather than sent back: a
$(…)substitution or a terminator inside a quoted value is cut at the paren by the matcher's terminator set, and the remainder classifies as LITERAL — a truncated key into--commands. Zero live invocations carry it ate98bfc87(the only paren hits are comment lines), so it is #15116 (XS, this file, serial behind this PR), not a defect in the ruled scope. The dev's own finding #15115 (--ranhas no explained bucket for the value-bearing class) is triaged intopm:queue(XS, serial behind this PR and #15116). The side effect on #14899 (the half-states board sweep now leaves--commandsas a value-bearing family; the routing half is untouched) is recorded on that card by the PR comment's cross-reference; that card stays where it is — its routing question is triage's, and this PR claims nothing on it.Deviations against the dispatch: none; the exception in item 6 was used exactly as bounded. Report
mcp_calls: 0 — within contract.CI at 00:29Z on
e98bfc87: 34 check runs, 22success+ 11skipped, 1 in progress (Lint & Repo Gates); nothing red.Terminal: pure code (
scripts/pm/**, no.md) ⇒ flipped ready and auto-merge armed (SQUASH) by the seat once every check is green; on MERGED the card closes byFixes, is label-cleaned, and #15091 (the worklist twins, serial behind this landing) is dispatched.
Generated by Claude Code
- Implemented-by:
Filed by the
domain:skillsseat (sessionsession_019RfFHiRCSs3JXLK4cwcfox, os-steve) from the #14880 flight (PR #15081), which measured the class and refused to guess at it. Tool-owner design call; this lane's.What
PR #15081 keys the gate derivation on
(script, args)and admits the argv half only when the tool can render the invocation runnably — a complete flag run. Nine scripts whose only CI invocations carry a value or a line continuation therefore still derive under a bare key CI never runs bare:check-adr-0087-registration,check-changeset-no-major,check-empty-changeset(--base VALUE);check-engine-split-ratio(--days 90);check-test-completeness(LOGPATH);check-shard-attestation(--emit/--verify);check-required-contexts(--verify-required-set);check-cross-package-test-inputs(--union-into/--changed);check-prerelease-pin-watch(--verbose).The refusal was deliberate and right: a truncated argv that LOOKS runnable is worse than the bare key (a dev runs it, it answers something CI never asks). Closing the class needs a way to render an invocation whose values are workflow variables — a design choice (render with the workflow's literal when it is a literal; print the variable name and mark the entry NOT RUNNABLE LOCALLY when it is not; or declare per script which flags are value-bearing).
Also from the same flight — a one-line usage understatement
The usage line presents
--changedas a whole-invocation alternative that takes no other flag, while--changed --commandsis legal and answers (measured at8af1a6ec: exit 0, 23 commands). Same class as #15036 (the line disagreeing with the argv chain), opposite direction. Folded into the #14870 flight on this file as a one-line member, not this card's.Refs
#14880 / PR #15081 (the derivation-key change and the measurement) · #15036 (the overstatement half).
Generated by Claude Code