Skip to content

ManifestSchema strips unknown keys silently — a namespace typo inside manifest: parses green and the namespace comes out undefined #14192

Description

@os-sam

Found while implementing #14161 (ADR-0130 D4). Out of that card's scope, filed rather than fixed, and unassigned.

Measured, on origin/main 682d03ba7, against the built packages/spec/dist

const { ObjectStackDefinitionSchema } = require('./packages/spec/dist/index.js');
const r = ObjectStackDefinitionSchema.safeParse({
  manifest: { id:'com.example.p', name:'p', version:'1.0.0', type:'app',
              namesapce:'p', zzzBogusManifestKey: 1 },
});
parse success      : true
bogus key survived : false
typo key survived  : false
namespace value    : undefined

Both the typo (namesapce) and an arbitrary unknown key parse green and are dropped — not preserved, not reported. manifest.namespace is then undefined, and the author is told nothing.

Why this is not a nit

This is the #8687 failure class exactly one level below the door #8687 closed. The top-level stack surface is now strictObject, so namesapce: at the top level is a named refusal carrying a rename suggestion. One level down, inside manifest: — one of the most-authored objects in the product, present in every objectstack.config.ts — the same typo is silent.

And the dropped key is load-bearing rather than decorative:

  • ${namespace}_${shortName} is the kernel-validated object-name rule, and under ADR-0129 D1–D2 the object name is the table name, the REST path, the formula token and the saved-view key;
  • the install-time namespace gate (ADR-0048 §3.2, and the co-ownership correction in ADR-0130 D1/D3) reads manifest.namespace;
  • MAP_SUPPORTED_FIELDS / datasource routing and several datasourceMapping rules key on the namespace.

So the observable symptom of a single transposed letter is not "a warning is missing" — it is objects landing under a different name than the author wrote, decided at parse time with exit 0.

Where the map says otherwise

docs/audits/2026-07-unknown-key-strictness-ledger.md:1321 classifies the whole directory in one cell:

| `kernel/` | wire | plugin/kernel contracts, code-to-code |

kernel/manifest.zod.ts is covered by that verdict, and for most of the directory it is right. But manifest: is not code-to-code — it is a hand-authored block in every project's stack source, in defineStack, in the Studio surfaces and in the artifact os build writes.

⚠️ That makes this the same shape as the finding the ledger's own api/ row records at length: "the flat wire verdict this row used to carry was correct when written and then silently expired, which is the finding." A directory cell is read as licence for every file under it, which is precisely what that row warns about. check:strictness-ledger is green throughout — the gate protects the arithmetic and the coverage, deliberately not the Class cell's correctness, which the ledger states is a human judgement.

Not proposing the fix here

The api/ row also records the order that made closing that shape possible: a naked strictObject was measured and refused first, because the same schema was parsing stored envelopes, and the debt was paid at the layer that owned it (peelStoredEnvelope) before strictness went in. ManifestSchema may well have its own version of that — it is parsed from stack source, from published package records and from artifact bundles — so the first work here is the measurement, not the tightening.

Suggested first step: run the door-reachability / consumer measurement for ManifestSchema, then decide between (a) strictObject with curated guidance for any wrong-layer keys, (b) a narrower split if some callers really do feed stored rows through it, or (c) recording a deliberate open verdict for this file with its reason — which would at least replace an expired directory cell with a measured file-level one.

Whichever way it goes, the kernel/ ledger row should stop covering manifest.zod.ts with a verdict that reads as "nobody authors this".

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

Labels

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

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions