Skip to content

Six independent enumerations of the stack-collection set, none answerable to stack.zod.ts — one is mis-aimed and two still list retired kinds #6242

Description

@hotlong

Found while running the read-only two-repo coverage audit for #6234 / #2657. Not fixed there (audit card, no code PR). Filed unassigned.

All line numbers are origin/main @ 8e2bbba24f76d56daad166b6b776c69d8c3ac0dc.

The shape

ObjectStackDefinitionSchema (packages/spec/src/stack.zod.ts) is the source of truth for which collections a stack may declare. Six other places re-enumerate that same set, each hand-maintained, and nothing compares any of them to the schema or to each other. They have drifted independently:

# enumeration site observed drift
1 ObjectStackDefinitionSchema packages/spec/src/stack.zod.ts — (the source of truth)
2 PLURAL_TO_SINGULAR / MAP_SUPPORTED_FIELDS packages/spec/src/shared/metadata-collection.zod.ts:80, :110 carries ragPipelines → rag_pipeline; stack.zod.ts declares no ragPipelines
3 metadataArrayKeys (generic registration) packages/objectql/src/engine.ts:2283, duplicated at :2459 lists workflows, approvals, roles, profiles, policies, ragPipelines — none of which stack.zod.ts declares (ADR-0019 / ADR-0020 / ADR-0088 / ADR-0090 retirements)
4 ARTIFACT_FIELD_TO_TYPE packages/metadata/src/plugin.ts:60 see "the concrete part" below
5 MetadataCategoryEnum packages/spec/src/kernel/package-artifact.zod.ts:49 lists retired triggers and workflows; omits ~12 live collections (hooks, mappings, docs, books, jobs, positions, sharingRules, webhooks, connectors, emailTemplates, datasources, capabilities)
6 STACK_COLLECTION_COVERAGE examples/app-showcase/src/coverage.ts:181 tracks 3 of ~9 non-kind collections, and — unlike its sibling KIND_COVERAGE — is not ratcheted against anything

The concrete part — ARTIFACT_FIELD_TO_TYPE

Two distinct problems in packages/metadata/src/plugin.ts:60-93:

(a) data: 'dataset' is mis-aimed. The stack key data: is the seed collection (stack.zod.ts:510 — z.array(SeedSchema); the runtime reads it as seeds at packages/runtime/src/app-plugin.ts:927). It is mapped here to 'dataset', which since ADR-0021 is the analytics semantic layer kind — the exact collision the registry entry warns about in prose (metadata-plugin.zod.ts:620: "NOTE: distinct from the (analytics-bound) dataset name").

Honest scoping: this entry is provably inert today, not a live mis-registration. SeedSchema is a strictObject with no name key (packages/spec/src/data/seed.zod.ts:33), and the ingest loop skips any item without one (plugin.ts:667-674, if (!name) continue;) — the file's own comment at :718 says so: "seeds under data have no name". So it is a dead entry aimed at the wrong kind, which would begin mis-registering the day either side moves.

(b) Five live collections are absent from the map — datasets, jobs, datasources, translations, capabilities. This is exactly the regression class that packages/metadata/src/plugin.test.ts:114-121 pins for docs:

a compiled artifact carries package docs in a top-level docs: DocSchema[] array. The artifact loader registers only the metadata fields enumerated in ARTIFACT_FIELD_TO_TYPE; docs was omitted, so the bundle's docs were silently dropped and GET /meta/doc returned an empty list even though the package shipped docs.

For four of the five, AppPlugin handles the functional consumption directly off the bundle (app-plugin.ts:503 datasources, :825 jobs, :927 seeds, :1421 translations), so this is not a boot breakage — the gap is that they never register as metadata items, so under bootstrap: 'artifact-only' (edge / serverless / immutable-image, metadata-plugin.zod.ts:505-511) GET /meta/job, /meta/translation, /meta/datasource and /meta/dataset would answer empty for a package that ships them. datasets has no AppPlugin handling at all in that path. Not measured on a real artifact-only boot — that verification is part of the fix, not of this filing.

Why file it as one issue

Each row above looks like a one-line typo in isolation, and each has been fixed one-line-at-a-time before (docs in ARTIFACT_FIELD_TO_TYPE, roles → positions in the same map, capabilities in metadataArrayKeys — every one of those carries a "this key was missing and it silently dropped X" comment today). The cause is structural: KIND_COVERAGE is answerable to DEFAULT_METADATA_TYPE_REGISTRY and fails CI when a kind is added without an entry (examples/app-showcase/test/coverage.test.ts:96-104), and the liveness ledger is answerable to the same registry (packages/spec/scripts/liveness/check-liveness.mts:145-153). The collection-key maps have no such gate. A check that compares each of these six against stack.zod.ts — declared-here-not-there and there-not-here, with an explicit waiver list for the deliberate omissions (views has no name, data seeds key by object, translations is a record) — would have caught all of the above and every future instance.

Relevance to in-flight work

Directly relevant to #2657: promoting any declared collection to a registered metadata kind means hand-editing several of these maps, with no gate to catch a miss. PR #5312 (api) had to touch them by hand.

Suggested scope

  1. A scripts/check-stack-collection-maps.mjs-style gate comparing all six against stack.zod.ts, with a named-waiver list carrying a reason per exemption (the KIND_COVERAGE / liveness-ratchet pattern).
  2. Fix the drift the gate then reports — including deciding whether data: should map to seed, be removed, or stay waived.

Filed per AGENTS.md Prime Directive #10 while auditing for #6234. Not assigned; no PR.

Activity

  1. claude commented on Aug 7, 2026

    @claude
    Contributor

    Triage: finding + domain:spec. Held, not queued — reasoning below. The domain label goes on now, deliberately: a finding with no domain:* is invisible to every lane the moment it is promoted, which is the gap #6188 and #6186 hit in earlier rounds.

    Why finding rather than pm:queue

    The body is explicit that it is an audit card with no code PR, and its own honesty markers are what decide the grade:

    • Row 4(a) — data: 'dataset' — is stated as "provably inert today": SeedSchema is a strictObject with no name key, and the ingest loop skips nameless items (plugin.ts:667-674). A dead entry aimed at the wrong kind is drift, not a live mis-registration.
    • Row 4(b) — the five absent collections — is the one live-harm claim, and it is explicitly "not measured on a real artifact-only boot", with the body assigning that verification to the fix rather than to the filing. Four of the five are functionally consumed off the bundle by AppPlugin anyway, so this is not a boot breakage.
    • Rows 3 and 5 are retired kinds still listed and live kinds omitted — dormant enumerations, nothing a user hits today.
    • Row 6 (STACK_COLLECTION_COVERAGE) is an unratcheted example-app tracker.

    That is the finding definition almost line by line: real, and nothing a user hits today. The queue is for concrete defects with a landing site and a repro; the substantive ask here — "a gate comparing six enumerations against stack.zod.ts, with a named-waiver list" — is a design with an open sub-decision inside it (whether data: should map to seed, be removed, or stay waived), and the waiver list has to be argued entry by entry before any of it is dispatchable.

    The findings triage round is where this gets a verdict; it is a strong promotion candidate the moment #6234 / #2657 make the gate concrete, and the body already argues that connection.

    Domain anchoring, and the split this needs when it promotes

    domain:spec. ObjectStackDefinitionSchema (packages/spec/src/stack.zod.ts) is the declared source of truth every row is measured against, and three of the six enumerations are themselves in packages/spec (shared/metadata-collection.zod.ts, kernel/package-artifact.zod.ts). The new gate compares against spec. Per "shared contract surfaces have one owner", the spec seat holds it regardless of who needs it — which is also consistent with #6234 (pm:dispatched, domain:spec), the audit that produced this card, and #2657 (pm:queue + pm:blocked, domain:spec), the work it feeds.

    ⚠️ It does not stay one issue when it promotes. Two of the six enumerations land outside spec — metadataArrayKeys (packages/objectql/src/engine.ts:2283, duplicated at :2459) and ARTIFACT_FIELD_TO_TYPE (packages/metadata/src/plugin.ts:60) are both domain:engine-core, and examples/app-showcase/src/coverage.ts is judged by principal landing site. Contract-first order applies: the gate plus the spec-side enumerations first, the engine-core drift as a sub-issue with Blocked-by: on it. This seat is not doing that split today — splitting an audit card before its scope is ruled produces sub-issues that have to be re-cut.

    Stale-premise note

    Baseline in the body is 8e2bbba24; current origin/main is 72847c5. Individual rows were not re-verified line by line this round — a finding is graded, not dispatched, and the findings triage round does the stale-premise pass at promotion time. What was checked is the structural claim that decides the grade: stack.zod.ts:440 and the surrounding collection declarations are unchanged in shape, so "six independent enumerations, none answerable to the schema" still describes main.

    Dedup (three repos, open issues and PRs)

    ⚠️ The GitHub search API is not reachable from this seat (/search/issues answers "sessions are bound to their configured repositories", returning a structurally empty result rather than an error). Dedup was done by paginating all 442 open issues and PRs across the three repos and matching locally on ARTIFACT_FIELD_TO_TYPE, metadataArrayKeys, MetadataCategoryEnum, STACK_COLLECTION_COVERAGE, PLURAL_TO_SINGULAR.

    No duplicate. One genuine neighbour: #5961 (capability has no DEFAULT_METADATA_TYPE_REGISTRY / schema entry) is a single instance of exactly the class this card generalises — capabilities is also one of the five keys row 4(b) reports missing from ARTIFACT_FIELD_TO_TYPE. Cross-linked rather than merged: #5961 is a concrete, dispatchable single fix; this is the gate that would have caught it. Whoever lands #5961 should not assume it closes any row here.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    Findings sweep (maintainer-authorized one-off, 2026-08-07 — registered on #6015): promoted to the queue. Six hand-maintained enumerations of one set with observed drift (one mis-aimed, two listing retired kinds) is the single-source-of-truth invariant broken five times over — derive or pin them to stack.zod.ts. finding → pm:queue.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions