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:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -- package.json scripts/platform-object-tenancy-census.mjs
→ 0 lines mentioning merge-driver
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).
Generated by Claude Code
Filed by the PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk, from a measured CI red on PR #15492 (#14957). Filedfinding,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-censusrow inpackage.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 oncheck:merge-driver(git-merge-regen --self-test):The gate is right. The derivation could not have named it.
The gap, measured
scripts/pm/dispatch-gates.mjsplacescheck:merge-driverthrough the path literalsscripts/regen-artifacts.mjsdeclares (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 touchespackage.json(a newgen:row) and a newscripts/*.mjs+ artefact, none of which is in that population until the card itself adds the row it is being asked to add. Measured onbcf07e622: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)
check:merge-driveralso readspackage.json(andpackages/*/package.jsonfor the declared owners) forgen:/check:script names. AROOT_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.gen:row to anypackage.jsonderivescheck:merge-driver. Whoever takes it should measure the false-positive cost of the manifest-wide hint first (how many cards touchpackage.jsonfor an unrelated reason) and pick.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_ARTIFACTSrow +.gitattributesline).Generated by Claude Code