Repository navigation
Commit cc119fa
docs(audit-skill): the open JSON bag is only a defect when the bag is not open
Auditing an adopter, I found six `field.string` + `@dbColumnType: jsonb` columns and
wrote them up as a wrong-native-type finding — the axis-H2 logic that catches
`field.string` + `@dbColumnType: uuid`. That was wrong, and the repo says so in three
places: the zod emitter calls it "the sanctioned open JSON bag escape hatch — a
genuinely untyped JSON column with no value-object to reference", the inferred-types
emitter renders it `unknown` deliberately and in lock-step, and
`fixtures/api-contract-conformance/jsonb/` gates the contract cross-port. The pattern
is live vocabulary. An audit that flags it on sight convicts every correct use.
The real defect is narrower and worth its own signature: a bag that is not open. When
the consuming code pins a FIXED KEY SET — raw-SQL `->>'key'` reads, casts to a concrete
type, destructuring, a hand validator over the parsed value — the shape exists; it is
just declared in N readers instead of once in the metadata. That is drift signature 6
with zero declarations rather than several, and no gate can see it: `verify --db` finds
a jsonb column that matches, `--codegen` finds generated files that are correct for an
open bag.
Measured rather than asserted. On the audited estate one column carried 12 distinct keys
across 31 raw-SQL path reads and 7 casts in 10 files. Estate-wide the pattern appears 74
times across five projects, which is the argument FOR the discriminator: without it this
signature reports 74 findings, most of them correct code.
Remedy stated with its limit: declare an `object.value` and point the field at it with
`field.object` + `@objectRef` + `@storage: jsonb`. The column stays jsonb, so it is a
contract change rather than necessarily a migration — verify that per column before
promising it. Converting also promotes those keys into signature 11's scope, where the
`->>'key'` literals were previously exempt precisely because the metadata owned nothing.
Carries its own do-not-flag list: a pass-through bag, a third-party or LLM raw response
whose shape is not the adopter's to declare, an array of scalars (that is `isArray`), and
a bag whose metadata comment already justifies it as open.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTcEKXTQMYt84fAjuw5A2M1 parent 9c1dbf2 commit cc119fa
12 files changed
Lines changed: 108 additions & 0 deletions
File tree
- agent-context/skills/metaobjects-audit
- references
- fixtures/agent-context-conformance
- java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-audit
- references
- java-react/expected/.claude/skills/metaobjects-audit
- references
- python/expected/.claude/skills/metaobjects-audit
- references
- ts-react-tanstack/expected/.claude/skills/metaobjects-audit
- references
- ts-requirements/expected/.claude/skills/metaobjects-audit
- references
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
357 | 357 | | |
358 | 358 | | |
359 | 359 | | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
360 | 369 | | |
361 | 370 | | |
362 | 371 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
272 | 272 | | |
273 | 273 | | |
274 | 274 | | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
275 | 284 | | |
276 | 285 | | |
277 | 286 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
357 | 357 | | |
358 | 358 | | |
359 | 359 | | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
360 | 369 | | |
361 | 370 | | |
362 | 371 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
272 | 272 | | |
273 | 273 | | |
274 | 274 | | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
275 | 284 | | |
276 | 285 | | |
277 | 286 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
357 | 357 | | |
358 | 358 | | |
359 | 359 | | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
360 | 369 | | |
361 | 370 | | |
362 | 371 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
272 | 272 | | |
273 | 273 | | |
274 | 274 | | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
275 | 284 | | |
276 | 285 | | |
277 | 286 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
357 | 357 | | |
358 | 358 | | |
359 | 359 | | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
360 | 369 | | |
361 | 370 | | |
362 | 371 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
272 | 272 | | |
273 | 273 | | |
274 | 274 | | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
275 | 284 | | |
276 | 285 | | |
277 | 286 | | |
| |||
0 commit comments