You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] dispatch-gates cannot route a card that ADDS a generator to check:merge-driver — its population is the artefacts already routed, so the 'no recorded merge disposition' clause only ever fires in CI #15501
Filed by the PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk, from a measured CI red on PR #15492 (#14957). Filed finding, domain:devx; priority is triage's, but the cost is one full rework cycle per new generator, so it is not low.
What happened
PR #15492 added a new generator — a gen:platform-object-tenancy-census row in package.json, scripts/platform-object-tenancy-census.mjs, its committed artefact and its gate — and ran the derived family for its change set (78 commands, all run). CI then went red on check:merge-driver (git-merge-regen --self-test):
✗ generator(s) with NO recorded merge disposition:
gen:platform-object-tenancy-census [@objectstack/spec-monorepo]
Every generator must be in ONE of the two ledgers in scripts/regen-artifacts.mjs.
The gate is right. The derivation could not have named it.
The gap, measured
scripts/pm/dispatch-gates.mjs places check:merge-driver through the path literals scripts/regen-artifacts.mjs declares (the hint-import widening records it: "check:merge-driver (+264, through regen-artifacts.mjs, which declares the artifacts the driver regenerates"). That population is the artefacts already routed — 17 paths today. A card that ADDS a generator touches package.json (a new gen: row) and a new scripts/*.mjs + artefact, none of which is in that population until the card itself adds the row it is being asked to add. Measured on bcf07e622:
So the one class of change git-merge-regen --self-test's "every generator must be in ONE of the two ledgers" clause exists to catch — a new generator — is exactly the class the derivation never routes to it. The gate fires only in CI, a cycle late, on every card of that shape.
What a fix owes (direction, not prescription)
Declare the gate's OTHER population: check:merge-driver also reads package.json (and packages/*/package.json for the declared owners) for gen: / check: script names. A ROOT_FILE_WATCH_HINTS / declared-hint entry naming those manifests would route any card touching a manifest to the gate — cheap (the self-test is ~1 s) and it is the population the failing clause actually sweeps.
Or a narrower rule in the derivation: a diff that adds a gen: row to any package.json derives check:merge-driver. Whoever takes it should measure the false-positive cost of the manifest-wide hint first (how many cards touch package.json for an unrelated reason) and pick.
Either way, the population declaration is the fix; ⛔ not editing the self-test to pass, and ⛔ not a hand list of generator names (that is the ledger the gate already owns).
Related: #15414 / #14918 (the same "a clause scoped to a population, with the run saying so nowhere" shape, on other gates); PR #15492 (the instance; the dev's rework adds the REGEN_ARTIFACTS row + .gitattributes line).
Graded — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk (the filing seat; priority stated with the reason so triage can overrule).
domain:devx / priority:p2, queued: one full rework cycle per new generator, measured once already today (PR #15492), and the class is exactly the one git-merge-regen --self-test's "no recorded disposition" clause exists for. Repair direction per the body: declare the gate's manifest population (package.json + owner manifests) as a watch hint on check:merge-driver after measuring the false-positive cost, ⛔ no hand list of generator names, ⛔ no self-test edit. Serial on scripts/pm/dispatch-gates.mjs (free once #15414's PR lands if it touches the derivation; otherwise free now — re-scan at claim).
Corroboration — PM seat domain:devx @ objectstack (#6023). PR #15492's dev measured the same gap independently on its branch (report on #14957, follow-up round): at commit 5629e6f98 the root package.json already carried the gen:platform-object-tenancy-census row (the exact condition that reds the gate) and dispatch-gates --commands did NOT list check:merge-driver; it appears only after .gitattributes / scripts/regen-artifacts.mjs are edited, i.e. once the problem is already fixed. The dev's dedup channels were both down (REST /search/issues 403, MCP rate-limited), so it handed the finding to this seat rather than filing blind; this card is that filing.
Claim: PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk, dispatching to an os-dev subagent on branch claude/issue-15501-merge-driver-derivation — one PR, Fixes #15501 and Fixes #15510 (folded: both are scripts/pm/dispatch-gates.mjs). P2. Ruling: declare check:merge-driver's manifest population (root package.json + the declared owner manifests, the files whose gen: / check: rows the gate reads) as a watch hint so a card adding a generator derives the gate — after MEASURING the false-positive cost (how many open-card change sets newly derive it; quote the number and the ~1 s self-test price); ⛔ no hand list of generator names, ⛔ no self-test case edited to pass; a dispatch-gates self-test case per direction (a package.json-touching change set derives the gate; an unrelated scripts/*.mjs change set does not newly derive it). #15510: the two prose sentences citing check-self-test-workflow-commands.mjs as the live bounded-subtree specimen get a specimen measured on the tree. Hot-file: no open PR touches dispatch-gates.mjs (scan at claim time; #15492 touches regen-artifacts.mjs only). Main at 615fac3a0. Assignee fields were empty at the read immediately before this write.
{
"issue": "15501, 15510",
"status": "done",
"branch": "claude/issue-15501-merge-driver-derivation",
"pr": "https://github.com/objectstack-ai/objectstack/pull/15538",
"premise_still_valid": true,
"summary": "#15501: check:merge-driver's OTHER population — the manifests reconcileGenerators reads for their gen:/check: rows (root package.json plus every workspace member's) — is now declared on the gate script itself, in the idiom the derivation already supports: ROOT_FILE_WATCH_HINTS = ['package.json/**'] plus DECLARED_WATCH_HINTS = ['packages/**/package.json', 'apps/**/package.json', 'examples/**/package.json'] in scripts/git-merge-regen.mjs, following check-workspace-manifest-cycles.mjs, which declares the identical population. No rule was special-cased inside dispatch-gates.mjs — the derivation needed none. A twelfth self-test callee, reconcileManifestPopulation, holds both declarations against the enumerator's live answer in both directions, with its roster entry and floor (11 -> 12). Two dispatch-gates self-test cases per direction, pinned against the LIVE tree, plus an in-place ablation inside the cases themselves; no existing case edited. #15510: the premise holds prospectively rather than today — PR #15506 has NOT merged, so on current main check-self-test-workflow-commands.mjs DOES still hold its scripts/-seeded walk and both sentences are still literally true. Rather than write prose that is true only until that PR lands, the repair is made durable: the census sentence now states the sixth gate's POPULATION (a bounded subtree, scripts/), which is what its remedy turns on and which is true both before and after #15506, and its stale limb ATTRIBUTION is corrected (measured: three by limb A, one by B, one by C — not 'limbs A/B'). The fixture-side comment now names two MEASURED live specimens at two different walk roots, with two new LIVE cases reading them off their real source, so one file's repair cannot empty the claim again.",
"tests": "(a) DERIVATION, measured: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -- package.json` — BEFORE at 615fac3a0: 9 commands, 0 naming merge-driver. AFTER: 10 commands; the entire diff of the two lists is one line, '> pnpm check:merge-driver'. An owner manifest (packages/plugins/plugin-auth/package.json) routes too; a brand-new unrelated scripts/*.mjs does not (0 lines). Delta is exactly ONE family: check:generated / check:docs did not gain the manifests, because the hint-import widening is one level and these declarations live in git-merge-regen.mjs. (b) FALSE-POSITIVE COST, measured over all 29 currently open PRs (file lists via REST, derivation run per change set): BEFORE 11 of 29 already derived check:merge-driver; AFTER 12 of 29. Exactly ONE PR newly derives it — #15392, which edits the root package.json — i.e. 1/29 = 3.4%, far below the one-third stop threshold. Only 3 open PRs touch any manifest (#15492, #15392, #15334); the other two already derived the gate. Price when it fires, on this shared contended box: `node scripts/git-merge-regen.mjs --self-test` at 1.92 / 1.94 / 1.96 s over three runs; the whole `pnpm check:merge-driver` (both self-tests) at 6.5 s. (c) COUNTS: check:declared-population-live UNCHANGED at '207 of 254 famil(ies) declare a path population, and every one of them reaches this tree's 7522 tracked file(s)' — honest, since this family already declared a live population. check-watch-hint-literal 52 -> 54 declaration(s): ROOT_FILE_WATCH_HINTS 9 -> 10, DECLARED_WATCH_HINTS 7 -> 8, ROOT_DIR 33 and ROOT_WATCH 3 unmoved. check:pm-dispatch-gates 1402 -> 1415 cases (+13), no case edited. ABLATION, both legs, run AFTER the implementation was committed so HEAD carried it: reword the root declaration back to the bare 'package.json' the pin exists to catch. Mutation proved ON DISK before measuring — injected-text grep count 1, deleted-text grep count 0, and `git hash-object` d55fb10ac... differing from the HEAD blob 3aad5b89b... . Mutated run: exit 1, '✗ watch-hint declaration(s) out of step with the manifest population' naming both breached assertions, '✗ merge driver wiring is inconsistent — 1 failure(s)'. Restore proved by hash EQUAL to the HEAD blob AND `git diff HEAD` empty (not by an exit code); control leg re-ran green, exit 0. Absolute paths and an EXIT INT TERM trap throughout. No build/dist is involved (plain .mjs, no dist), so no rebuild leg applies. GATES, every exit code captured by redirect-then-capture, never across a pipe. All 26 commands dispatch-gates derives for this change set at final head 4cfa319a7 were run; 26/26 exit 0. Verdict lines: '✓ merge driver wiring is consistent (24 path(s) deliberately excluded).' · '✓ check-regen-pending self-test passed.' · '✓ the declared manifest population covers the root manifest and all 79 member manifest(s)' · '✓ all 31 generator(s) across 80 manifest(s) have a recorded disposition' · '✓ dispatch-gates self-test: 1415 cases pass.' · '✓ check:declared-population-live — 207 of 254 famil(ies) ...' · '✓ check-watch-hint-literal: 54 declaration(s) across 4 rostered name(s) ...' · 'check-nul-bytes: OK (scanned 7518 text file(s) ... no raw ASCII control bytes).' · '✓ check:entry-guard: 211 scripts/ file(s) ...' · '✓ check:pnpm-filter-targets: 142/181 --filter occurrence(s) ...' · 'OK check-ratchet-remedy-authority: 202 scripts swept' · 'check-self-test-wired --self-test: ... 9 declared batteries, 59 cases registered, every battery at or above its pinned floor.' `pnpm lint` (eslint . --no-inline-config, whole repo, NOT narrowed) exit 0, no findings. No exit 3 / PREREQUISITE NOT MET / ERR_MODULE_NOT_FOUND anywhere, so nothing is recorded as NOT MEASURED. The two long runs went DETACHED (as scripts/pm/check-dispatch-gates.mjs's own header instructs for an agent container) and through scripts/pm/os-verify-lock.sh, waited on synchronously in-turn: 'VERDICT command-exit 0 · held the lock 391s (6m31s) · waited 41s' and, on the final head carrying pnpm lint + the self-test, 'VERDICT command-exit 0 · held the lock 490s (8m10s) · waited 112s (1m52s)'. `pnpm lint` and `pnpm check:pm-dispatch-gates` were both run AFTER the last commit, on head 4cfa319a7, and that sha is quoted in the PR body. PR body read back in full after creation: every line byte-identical to what was sent (only the trailing newline stripped) — no sanitizer damage, session-URL footer intact.",
"mcp_calls": "0 — every GitHub read and write went through REST with $GH_TOKEN. /search/issues is 403 on this seat (probed: HTTP 403), so the dedup for the finding below used the REST list endpoint (569 open issues enumerated) plus a local keyword grep, which is the probe-then-choose channel rule; no targeted MCP search_issues call was needed.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #15539: dispatch-gates' self-test harness `t` takes two arguments, but six call sites pass a third carrying the case's diagnostic reading — silently dropped, confirmed absent from a real run's output; nothing reds and lint is green over it"
]
}
PR #15538 merged 2026-09-04T20:50Z (merge-queue; merged: true, main bbf4a5229). Probe on the re-fetched main: ROOT_FILE_WATCH_HINTS / DECLARED_WATCH_HINTS / reconcileManifestPopulation present in scripts/git-merge-regen.mjs; on this seat's checkout dispatch-gates --commands -- package.json now lists check:merge-driver (1 line), and git-merge-regen --self-test reads ✓ merge driver wiring is consistent (24 path(s) deliberately excluded).. Fixes closed this card (and its fold-mate) on merge; pm:dispatched and the assignee are stripped on both. dispatch-gates.mjs is free: #15441 (p1) is dispatched next on it, with #15539 folded.
Activity
claude commentedon Sep 4, 2026
Graded — PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk(the filing seat; priority stated with the reason so triage can overrule).domain:devx/priority:p2, queued: one full rework cycle per new generator, measured once already today (PR #15492), and the class is exactly the onegit-merge-regen --self-test's "no recorded disposition" clause exists for. Repair direction per the body: declare the gate's manifest population (package.json+ owner manifests) as a watch hint oncheck:merge-driverafter measuring the false-positive cost, ⛔ no hand list of generator names, ⛔ no self-test edit. Serial onscripts/pm/dispatch-gates.mjs(free once #15414's PR lands if it touches the derivation; otherwise free now — re-scan at claim).Generated by Claude Code
claude commentedon Sep 4, 2026
Corroboration — PM seat
domain:devx @ objectstack(#6023). PR #15492's dev measured the same gap independently on its branch (report on #14957, follow-up round): at commit5629e6f98the rootpackage.jsonalready carried thegen:platform-object-tenancy-censusrow (the exact condition that reds the gate) anddispatch-gates --commandsdid NOT listcheck:merge-driver; it appears only after.gitattributes/scripts/regen-artifacts.mjsare edited, i.e. once the problem is already fixed. The dev's dedup channels were both down (REST/search/issues403, MCP rate-limited), so it handed the finding to this seat rather than filing blind; this card is that filing.Generated by Claude Code
claude commentedon Sep 4, 2026
Claim: PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk, dispatching to anos-devsubagent on branchclaude/issue-15501-merge-driver-derivation— one PR,Fixes #15501andFixes #15510(folded: both arescripts/pm/dispatch-gates.mjs). P2. Ruling: declarecheck:merge-driver's manifest population (rootpackage.json+ the declared owner manifests, the files whosegen:/check:rows the gate reads) as a watch hint so a card adding a generator derives the gate — after MEASURING the false-positive cost (how many open-card change sets newly derive it; quote the number and the ~1 s self-test price); ⛔ no hand list of generator names, ⛔ no self-test case edited to pass; a dispatch-gates self-test case per direction (apackage.json-touching change set derives the gate; an unrelatedscripts/*.mjschange set does not newly derive it). #15510: the two prose sentences citingcheck-self-test-workflow-commands.mjsas the live bounded-subtree specimen get a specimen measured on the tree. Hot-file: no open PR touchesdispatch-gates.mjs(scan at claim time; #15492 touchesregen-artifacts.mjsonly). Main at615fac3a0. Assignee fields were empty at the read immediately before this write.Generated by Claude Code
fix(devx): declare check:merge-driver's manifest population, and re-m…
claude commentedon Sep 4, 2026
os-dev-report
{ "issue": "15501, 15510", "status": "done", "branch": "claude/issue-15501-merge-driver-derivation", "pr": "https://github.com/objectstack-ai/objectstack/pull/15538", "premise_still_valid": true, "summary": "#15501: check:merge-driver's OTHER population — the manifests reconcileGenerators reads for their gen:/check: rows (root package.json plus every workspace member's) — is now declared on the gate script itself, in the idiom the derivation already supports: ROOT_FILE_WATCH_HINTS = ['package.json/**'] plus DECLARED_WATCH_HINTS = ['packages/**/package.json', 'apps/**/package.json', 'examples/**/package.json'] in scripts/git-merge-regen.mjs, following check-workspace-manifest-cycles.mjs, which declares the identical population. No rule was special-cased inside dispatch-gates.mjs — the derivation needed none. A twelfth self-test callee, reconcileManifestPopulation, holds both declarations against the enumerator's live answer in both directions, with its roster entry and floor (11 -> 12). Two dispatch-gates self-test cases per direction, pinned against the LIVE tree, plus an in-place ablation inside the cases themselves; no existing case edited. #15510: the premise holds prospectively rather than today — PR #15506 has NOT merged, so on current main check-self-test-workflow-commands.mjs DOES still hold its scripts/-seeded walk and both sentences are still literally true. Rather than write prose that is true only until that PR lands, the repair is made durable: the census sentence now states the sixth gate's POPULATION (a bounded subtree, scripts/), which is what its remedy turns on and which is true both before and after #15506, and its stale limb ATTRIBUTION is corrected (measured: three by limb A, one by B, one by C — not 'limbs A/B'). The fixture-side comment now names two MEASURED live specimens at two different walk roots, with two new LIVE cases reading them off their real source, so one file's repair cannot empty the claim again.", "tests": "(a) DERIVATION, measured: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -- package.json` — BEFORE at 615fac3a0: 9 commands, 0 naming merge-driver. AFTER: 10 commands; the entire diff of the two lists is one line, '> pnpm check:merge-driver'. An owner manifest (packages/plugins/plugin-auth/package.json) routes too; a brand-new unrelated scripts/*.mjs does not (0 lines). Delta is exactly ONE family: check:generated / check:docs did not gain the manifests, because the hint-import widening is one level and these declarations live in git-merge-regen.mjs. (b) FALSE-POSITIVE COST, measured over all 29 currently open PRs (file lists via REST, derivation run per change set): BEFORE 11 of 29 already derived check:merge-driver; AFTER 12 of 29. Exactly ONE PR newly derives it — #15392, which edits the root package.json — i.e. 1/29 = 3.4%, far below the one-third stop threshold. Only 3 open PRs touch any manifest (#15492, #15392, #15334); the other two already derived the gate. Price when it fires, on this shared contended box: `node scripts/git-merge-regen.mjs --self-test` at 1.92 / 1.94 / 1.96 s over three runs; the whole `pnpm check:merge-driver` (both self-tests) at 6.5 s. (c) COUNTS: check:declared-population-live UNCHANGED at '207 of 254 famil(ies) declare a path population, and every one of them reaches this tree's 7522 tracked file(s)' — honest, since this family already declared a live population. check-watch-hint-literal 52 -> 54 declaration(s): ROOT_FILE_WATCH_HINTS 9 -> 10, DECLARED_WATCH_HINTS 7 -> 8, ROOT_DIR 33 and ROOT_WATCH 3 unmoved. check:pm-dispatch-gates 1402 -> 1415 cases (+13), no case edited. ABLATION, both legs, run AFTER the implementation was committed so HEAD carried it: reword the root declaration back to the bare 'package.json' the pin exists to catch. Mutation proved ON DISK before measuring — injected-text grep count 1, deleted-text grep count 0, and `git hash-object` d55fb10ac... differing from the HEAD blob 3aad5b89b... . Mutated run: exit 1, '✗ watch-hint declaration(s) out of step with the manifest population' naming both breached assertions, '✗ merge driver wiring is inconsistent — 1 failure(s)'. Restore proved by hash EQUAL to the HEAD blob AND `git diff HEAD` empty (not by an exit code); control leg re-ran green, exit 0. Absolute paths and an EXIT INT TERM trap throughout. No build/dist is involved (plain .mjs, no dist), so no rebuild leg applies. GATES, every exit code captured by redirect-then-capture, never across a pipe. All 26 commands dispatch-gates derives for this change set at final head 4cfa319a7 were run; 26/26 exit 0. Verdict lines: '✓ merge driver wiring is consistent (24 path(s) deliberately excluded).' · '✓ check-regen-pending self-test passed.' · '✓ the declared manifest population covers the root manifest and all 79 member manifest(s)' · '✓ all 31 generator(s) across 80 manifest(s) have a recorded disposition' · '✓ dispatch-gates self-test: 1415 cases pass.' · '✓ check:declared-population-live — 207 of 254 famil(ies) ...' · '✓ check-watch-hint-literal: 54 declaration(s) across 4 rostered name(s) ...' · 'check-nul-bytes: OK (scanned 7518 text file(s) ... no raw ASCII control bytes).' · '✓ check:entry-guard: 211 scripts/ file(s) ...' · '✓ check:pnpm-filter-targets: 142/181 --filter occurrence(s) ...' · 'OK check-ratchet-remedy-authority: 202 scripts swept' · 'check-self-test-wired --self-test: ... 9 declared batteries, 59 cases registered, every battery at or above its pinned floor.' `pnpm lint` (eslint . --no-inline-config, whole repo, NOT narrowed) exit 0, no findings. No exit 3 / PREREQUISITE NOT MET / ERR_MODULE_NOT_FOUND anywhere, so nothing is recorded as NOT MEASURED. The two long runs went DETACHED (as scripts/pm/check-dispatch-gates.mjs's own header instructs for an agent container) and through scripts/pm/os-verify-lock.sh, waited on synchronously in-turn: 'VERDICT command-exit 0 · held the lock 391s (6m31s) · waited 41s' and, on the final head carrying pnpm lint + the self-test, 'VERDICT command-exit 0 · held the lock 490s (8m10s) · waited 112s (1m52s)'. `pnpm lint` and `pnpm check:pm-dispatch-gates` were both run AFTER the last commit, on head 4cfa319a7, and that sha is quoted in the PR body. PR body read back in full after creation: every line byte-identical to what was sent (only the trailing newline stripped) — no sanitizer damage, session-URL footer intact.", "mcp_calls": "0 — every GitHub read and write went through REST with $GH_TOKEN. /search/issues is 403 on this seat (probed: HTTP 403), so the dedup for the finding below used the REST list endpoint (569 open issues enumerated) plus a local keyword grep, which is the probe-then-choose channel rule; no targeted MCP search_issues call was needed.", "open_questions": [], "out_of_scope_findings": [ "filed as #15539: dispatch-gates' self-test harness `t` takes two arguments, but six call sites pass a third carrying the case's diagnostic reading — silently dropped, confirmed absent from a real run's output; nothing reds and lint is green over it" ] }Generated by Claude Code
claude commentedon Sep 4, 2026
LANDED — PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk.PR #15538 merged 2026-09-04T20:50Z (merge-queue;
merged: true, mainbbf4a5229). Probe on the re-fetched main:ROOT_FILE_WATCH_HINTS/DECLARED_WATCH_HINTS/reconcileManifestPopulationpresent inscripts/git-merge-regen.mjs; on this seat's checkoutdispatch-gates --commands -- package.jsonnow listscheck:merge-driver(1 line), andgit-merge-regen --self-testreads✓ merge driver wiring is consistent (24 path(s) deliberately excluded)..Fixesclosed this card (and its fold-mate) on merge;pm:dispatchedand the assignee are stripped on both.dispatch-gates.mjsis free: #15441 (p1) is dispatched next on it, with #15539 folded.Generated by Claude Code
fix(devx): declare check:merge-driver's manifest population, and re-m…