Observation-class finding, surfaced while fixing #7286 (PR #7436) and filed unassigned so it does not evaporate with that PR. #7286 fixed one entry in this artifact — the object-level ownership enum. This card records what the same sweep found in the rest of the file, plus the mechanism that let all of it drift.
Measured at origin/main @ 07383fe by parsing the artifact's JSON and diffing each enum against the zod enum literal it mirrors.
1. objects[].fields.*.type — 6 phantom values, 21 hidden values
The artifact offers 34 field types. FieldType (packages/spec/src/data/field.zod.ts:17) accepts 49. They are not nested sets — the artifact offers values that are accepted nowhere:
- PHANTOM (artifact offers, schema rejects), 6:
integer, slug, uuid, ip_address, geo_point, encrypted
- MISSING (schema accepts, artifact hides), 21:
secret, toggle, radio, checkboxes, tree, user, avatar, video, audio, summary, composite, repeater, record, location, address, code, slider, signature, qrcode, progress, tags
Two of the phantoms look like renamed-and-never-followed spellings: geo_point against the accepted location, and encrypted against the accepted secret. This is the same defect class as #7286 — IDE autocomplete steering an author to a value no schema accepts — just 6 values wide instead of 2.
It was deliberately not folded into PR #7436: aligning a 34-value list to a 49-value list is not the identical mechanical fix, because it first needs a decision (below) on whether the artifact is meant to offer a curated subset at all.
2. data[].mode — 1 hidden value
Artifact has ["upsert","insert","ignore","replace"]; SeedMode (packages/spec/src/data/seed.zod.ts:12) has 5, including update. Weaker than the above — it hides a valid value rather than offering an invalid one, so no author is steered into a rejected write.
3. The mechanism — why all three drifted
- Nothing guards it. No pin/sync test, no gate, no consumer. The only in-repo mentions of
config-schema.json are prose in one code comment, CHANGELOG.md, and a changeset. check:generated does not cover it. Nothing can go red when an acceptance face moves.
- Its provenance claim is false. The file's own top-level
description says it is "Generated from ObjectStackDefinitionSchema". It is hand-written. That false claim is plausibly why nobody re-derived it as the vocabularies moved — it reads as already-generated.
- It is not shipped.
npm pack --dry-run --ignore-scripts on @objectstack/spec packs 269 entries; zero match config-schema. It is absent from the package's files list, so no npm consumer can reach it.
- A correct generated route already exists.
objectstack generate schema (packages/cli/src/commands/generate.ts:817-865) builds this exact schema live from ObjectStackDefinitionSchema via z.toJSONSchema — it cannot drift by construction. It writes objectstack.schema.json into the user's cwd, not this path.
The decision this needs
Given that the generated route exists and the hand-written file is unshipped and unguarded, the choice is a route decision rather than a patch, and it should be made once for the whole file rather than per-enum:
- A — retire the artifact. Delete
packages/spec/config-schema.json and point authors at objectstack generate schema. Removes the drift surface permanently instead of re-synchronising it by hand every time a vocabulary moves. Cost: anything outside this repo pointing at the path by URL or relative path breaks — though it is unshipped, so no npm consumer can be relying on it.
- B — regenerate it in place. Add a
gen:/check: script pair that writes this path from ObjectStackDefinitionSchema and fails CI when it is stale, the same shape as the package's other generated baselines. Keeps the path alive and makes drift impossible. Cost: one more generated artifact plus its gate.
- C — hand-align the two remaining enums and correct the false provenance line. Cheapest now; leaves the drift surface and its silence intact, so this exact card recurs on the next vocabulary move. Given ADR-0049's enforce-or-remove posture, C is the weakest of the three.
No recommendation is being forced here — this is filed as finding, not queued, and the three options above are the material for triage. Options A and B both structurally prevent the class; C does not.
Refs: #7286 (the ownership entry, fixed), PR #7436, #3244 (confusion class).
Observation-class finding, surfaced while fixing #7286 (PR #7436) and filed unassigned so it does not evaporate with that PR. #7286 fixed one entry in this artifact — the object-level
ownershipenum. This card records what the same sweep found in the rest of the file, plus the mechanism that let all of it drift.Measured at
origin/main@07383feby parsing the artifact's JSON and diffing each enum against the zod enum literal it mirrors.1.
objects[].fields.*.type— 6 phantom values, 21 hidden valuesThe artifact offers 34 field types.
FieldType(packages/spec/src/data/field.zod.ts:17) accepts 49. They are not nested sets — the artifact offers values that are accepted nowhere:integer,slug,uuid,ip_address,geo_point,encryptedsecret,toggle,radio,checkboxes,tree,user,avatar,video,audio,summary,composite,repeater,record,location,address,code,slider,signature,qrcode,progress,tagsTwo of the phantoms look like renamed-and-never-followed spellings:
geo_pointagainst the acceptedlocation, andencryptedagainst the acceptedsecret. This is the same defect class as #7286 — IDE autocomplete steering an author to a value no schema accepts — just 6 values wide instead of 2.It was deliberately not folded into PR #7436: aligning a 34-value list to a 49-value list is not the identical mechanical fix, because it first needs a decision (below) on whether the artifact is meant to offer a curated subset at all.
2.
data[].mode— 1 hidden valueArtifact has
["upsert","insert","ignore","replace"];SeedMode(packages/spec/src/data/seed.zod.ts:12) has 5, includingupdate. Weaker than the above — it hides a valid value rather than offering an invalid one, so no author is steered into a rejected write.3. The mechanism — why all three drifted
config-schema.jsonare prose in one code comment,CHANGELOG.md, and a changeset.check:generateddoes not cover it. Nothing can go red when an acceptance face moves.descriptionsays it is "Generated from ObjectStackDefinitionSchema". It is hand-written. That false claim is plausibly why nobody re-derived it as the vocabularies moved — it reads as already-generated.npm pack --dry-run --ignore-scriptson@objectstack/specpacks 269 entries; zero matchconfig-schema. It is absent from the package'sfileslist, so no npm consumer can reach it.objectstack generate schema(packages/cli/src/commands/generate.ts:817-865) builds this exact schema live fromObjectStackDefinitionSchemaviaz.toJSONSchema— it cannot drift by construction. It writesobjectstack.schema.jsoninto the user's cwd, not this path.The decision this needs
Given that the generated route exists and the hand-written file is unshipped and unguarded, the choice is a route decision rather than a patch, and it should be made once for the whole file rather than per-enum:
packages/spec/config-schema.jsonand point authors atobjectstack generate schema. Removes the drift surface permanently instead of re-synchronising it by hand every time a vocabulary moves. Cost: anything outside this repo pointing at the path by URL or relative path breaks — though it is unshipped, so no npm consumer can be relying on it.gen:/check:script pair that writes this path fromObjectStackDefinitionSchemaand fails CI when it is stale, the same shape as the package's other generated baselines. Keeps the path alive and makes drift impossible. Cost: one more generated artifact plus its gate.No recommendation is being forced here — this is filed as
finding, not queued, and the three options above are the material for triage. Options A and B both structurally prevent the class; C does not.Refs: #7286 (the
ownershipentry, fixed), PR #7436, #3244 (confusion class).