Skip to content

os validate, os build and os info all report Data: 0 Objects for a project whose metadata lives only in packages[] — collectMetadataStats reads the top level alone, and under --strict the false No objects defined warning gates #17527

Description

@os-justin

Symptom

collectMetadataStats counts every collection at the top level only, so on an ADR-0130 D4 / option-B project — every definition inside packages[], none flattened to the top — all three commands that print the metadata summary report a project that declares nothing, and os validate raises two warnings that are false on their face.

Under --strict the false warning is not cosmetic: it is a gating error.

Minimal repro

objectstack.config.ts, no top-level objects, one clean packages[] entry (the lookup resolves, so nothing else can gate):

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_order' },
            } },
        ] } },
  ],
};

Measured on branch claude/issue-17069-union-fold-validate-lint at 7191b900, through the real binaries:

os validate            exit 0    Data: 0 Objects
                                 ⚠ No objects defined — this stack has no data model
                                 ⚠ No apps or plugins defined — this stack may not do much
os validate --strict   exit 1    ✗ Strict mode: warnings treated as errors
os build               exit 0    Data: 0 Objects
os info                exit 0    Data: 0 Objects

Control — the identical object authored at the TOP LEVEL, same command:

os validate --strict   exit 1    Data: 1 Objects  2 Fields
                                 ⚠ No apps or plugins defined — this stack may not do much

One warning instead of two, and a non-zero object count. The extra No objects defined line and the zeroed summary are produced by the reader, not by the stack.

Cause, located

packages/cli/src/utils/format.ts:548

export function collectMetadataStats(config: any): MetadataStats {
  ...
  const objects = Array.isArray(config.objects) ? config.objects :
    (config.objects && typeof config.objects === 'object' ? Object.values(config.objects) : []);
  ...
  return { objects: count(config.objects), objectExtensions: count(config.objectExtensions), ... };

Every member is count(config.KEY) against the top level. Three call sites, all affected:

packages/cli/src/commands/compile.ts:832   const stats = collectMetadataStats(config);
packages/cli/src/commands/info.ts:43       const stats = collectMetadataStats(config);
packages/cli/src/commands/validate.ts:462  const stats = collectMetadataStats(config);

packages/cli/src/utils/stack-collections.ts already ships the fold this reader needs — authoringRuleUnionStack fills exactly the package-owned collections the top level does not carry, and returns a top-level stack by identity — but whether the summary should be folded, or should learn to report per-package counts, is a presentation decision this card does not make. That is why it is filed rather than folded into #17069's PR.

Why it is not covered today

test/option-b-reader-acceptance.pin.test.ts is the acceptance pin for the option-B reader program and its OPTION_B_LOSSES ledger is empty — "every subsystem must see its collections in BOTH shapes". It stays green here because its probe (test/fixtures/option-b-reader-probe.ts) carries no row for collectMetadataStats. That pin's own header names this exact failure mode as the thing it exists to catch: "a NEW reader nobody enumerated, arriving in a later change ⇒ RED on arrival". This reader is older than the probe and was never enumerated, so it arrived under the floor instead of over it. A row for it belongs with whatever fix lands.

Relationship to #17069

Found while landing #17069 (PR #17524), and not created by it: #17069 wires the union fold into the two commands' author-time rule table input, deliberately scoped the way compile.ts scopes it — a rule input that reaches no writer and no printer. The summary is a different reader on a different path, and it reads config directly, before any fold. os build shows the same 0 Objects on the same stack both before and after that change.

Where it was found

Driving the #17069 non-vacuity control — a CLEAN option-B project, which must exit 0 on all three commands. It does; it just reports the project as empty while doing so.

⛔ Deduplication: one targeted search_issues call over open and closed (repo /search/* is refused by this session's egress proxy, HTTP 403 — verified this session). 13 results read out. Nearest neighbours rejected: #14512 (open — the option-B EMITTER half, "stop serializing the metadata twice"; this is a reader, and it is broken in the shape #14512 produces, not by it), #17069 (this card's parent finding — rule-table input, a different reader), #4698 (closed — "declared but never read" metadata, an unrelated class). No card names collectMetadataStats or the metadata summary.


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

    bugSomething isn't workingdomain:clipriority:p1High: required for production / M2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions