Skip to content

os validate and os lint judge an EMPTY stack when a project declares its metadata only in packages[] — the ADR-0130 D4 union fold (authoringRuleUnionStack) is wired into os build alone #17069

Description

@claude

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions