Symptom
A project whose definitions live only in packages[] — the ADR-0130 D4 artifact shape, with no collections at the top level — is judged by os validate and os lint as if it declared nothing. Both exit 0 with no diagnostic of any severity. os build judges the same stack correctly and exits 1.
Two of the three authoring gates report a clean bill of health on a stack they read nothing of. This is the exact "weakest gate" class #4409 was filed for and #15006 fixed — fixed for os build alone.
Minimal repro
objectstack.config.ts, no top-level objects:
export default {
manifest: { id: 'com.example.ob', name: 'ob', version: '1.0.0', type: 'app', namespace: 'ob' },
packages: [
{ manifest: { id: 'com.example.ob', name: 'ob', version: '1.0.0', type: 'app', namespace: 'ob',
objects: [
{ name: 'ob_order', label: 'Order', sharingModel: 'private',
fields: {
number: { type: 'text', label: 'Number' },
ghost: { type: 'lookup', label: 'Ghost', reference: 'ob_nowhere' },
} },
] } },
],
};
Measured on branch claude/issue-16611-lookup-reference-target-gate at 904c1dceb0, through the real binaries:
os validate exit 0 ✓ Validation passed (89ms)
os lint exit 0 (no finding)
os build exit 1 ✗ Author-time rules failed (1 issue)
rule: object-reference-unknown at objects[0].fields.ghost.reference
The dangling ob_nowhere is only the probe that makes the blindness visible; the same silence covers every author-time rule, because the input those two commands hand the rule table is an empty stack.
Cause, located
compile.ts folds the packages back in before it runs the table:
packages/cli/src/commands/compile.ts: runAuthoringRules('build', {
normalized: authoringRuleUnionStack(normalized),
parsed: authoringRuleUnionStack(result.data), ... })
authoringRuleUnionStack (packages/cli/src/utils/stack-collections.ts) fills every package-owned collection the top level does not carry, from packages[], in dependency order. Its own header states the failure it exists to stop: "Under option B the union run's input is an empty stack, so every cross-package rule reports nothing and os build publishes green. That is the weakest-gate failure #4409 was filed for, arriving a second time through a different door."
Neither sibling command imports it:
git grep -n authoringRuleUnionStack -- packages/cli/src/commands/
packages/cli/src/commands/compile.ts:18 <- the only command
validate.ts:246 and lint.ts:514 call runAuthoringRules on the unfolded stack.
Why this is a contract violation, not a preference
content/docs/deployment/validating-metadata.mdx states the parity as a promise — "So os validate is the fast inner-loop check (no artifact), os build is what ships", and "anything that can fail a build fails os lint too" (line 580). deployment/cli.mdx:1378 says the same: "os validate and os build run — so anything that can fail a build fails here". On this stack shape the promise is false in the loudest direction: the fast inner-loop check passes what the build refuses.
Suggested shape
Hand the same folded stack to the other two call sites. authoringRuleUnionStack returns a stack that still carries its collections by identity, so a single-package project is unaffected by construction — which is what makes this cheap and low-risk. It needs its own measurement of the two commands' populations before it lands, and it is not a rider on another card.
Where it was found
While landing #16611 (PR #17066). Not in that card's scope and not created by it: validate.ts never imported the fold. Found because that PR's non-degeneracy control needed a stack whose union leg is clean, which is the same shape.
⛔ Deduplication was one targeted search_issues call (repo REST /search/* is refused by this session's egress proxy, HTTP 403), over open and closed. Nearest neighbours read out and rejected: #16544 (closed — os validate did not lower inline handlers; a different input defect on the same command, fixed), #11896 / #11643 / #11727 (closed — os build vs os validate parity on advisories and --json payloads, not on which stack is judged). No card names the union fold.
Generated by Claude Code
Symptom
A project whose definitions live only in
packages[]— the ADR-0130 D4 artifact shape, with no collections at the top level — is judged byos validateandos lintas if it declared nothing. Both exit 0 with no diagnostic of any severity.os buildjudges the same stack correctly and exits 1.Two of the three authoring gates report a clean bill of health on a stack they read nothing of. This is the exact "weakest gate" class #4409 was filed for and #15006 fixed — fixed for
os buildalone.Minimal repro
objectstack.config.ts, no top-levelobjects:Measured on branch
claude/issue-16611-lookup-reference-target-gateat904c1dceb0, through the real binaries:The dangling
ob_nowhereis only the probe that makes the blindness visible; the same silence covers every author-time rule, because the input those two commands hand the rule table is an empty stack.Cause, located
compile.tsfolds the packages back in before it runs the table:authoringRuleUnionStack(packages/cli/src/utils/stack-collections.ts) fills every package-owned collection the top level does not carry, frompackages[], in dependency order. Its own header states the failure it exists to stop: "Under option B the union run's input is an empty stack, so every cross-package rule reports nothing andos buildpublishes green. That is the weakest-gate failure #4409 was filed for, arriving a second time through a different door."Neither sibling command imports it:
validate.ts:246andlint.ts:514callrunAuthoringRuleson the unfolded stack.Why this is a contract violation, not a preference
content/docs/deployment/validating-metadata.mdxstates the parity as a promise — "Soos validateis the fast inner-loop check (no artifact),os buildis what ships", and "anything that can fail a build failsos linttoo" (line 580).deployment/cli.mdx:1378says the same: "os validateandos buildrun — so anything that can fail a build fails here". On this stack shape the promise is false in the loudest direction: the fast inner-loop check passes what the build refuses.Suggested shape
Hand the same folded stack to the other two call sites.
authoringRuleUnionStackreturns a stack that still carries its collections by identity, so a single-package project is unaffected by construction — which is what makes this cheap and low-risk. It needs its own measurement of the two commands' populations before it lands, and it is not a rider on another card.Where it was found
While landing #16611 (PR #17066). Not in that card's scope and not created by it:
validate.tsnever imported the fold. Found because that PR's non-degeneracy control needed a stack whose union leg is clean, which is the same shape.⛔ Deduplication was one targeted
search_issuescall (repo REST/search/*is refused by this session's egress proxy, HTTP 403), over open and closed. Nearest neighbours read out and rejected: #16544 (closed —os validatedid not lower inline handlers; a different input defect on the same command, fixed), #11896 / #11643 / #11727 (closed —os buildvsos validateparity on advisories and--jsonpayloads, not on which stack is judged). No card names the union fold.Generated by Claude Code