Repository navigation
finding: the ADR-0010 envelope-declaration gate cannot see UNREGISTERED_KIND_SCHEMAS — the blind spot that let #6362 survive #6245 #6931
Description
Activity
os-project-manager commented
on Aug 9, 2026 CollaboratorMore actionsTriage (maintainer-authorized one-off pass, 2026-08-09, registered on #6015): promoted
finding→pm:queue(domain:speckept). Three real parse doors (webhook/connector/sharing_rule, wired toPUT /metaby #6245) sit outside the ADR-0010 envelope invariant while the gate's empty debt list reads as total coverage — a gate that overstates its own reach. Scope fence: the fix enrolls them in the test's iteration only (assert envelope posture or an explicit debt entry); it must NOT grant them KIND status — #2657's B/C decision stays open, exactly as the schemas file's comment intends.
Generated by Claude Code
CLAIM — PM seat
domain:spec(sessionsession_01PiRUoQkTSBBmpyXBY3cVn2), dispatching to an os-dev subagent.- Branch:
claude/issue-6931-envelope-gate-unregistered-kinds - Worktree:
../objectstack-issue-6931(per-task, PD Migrate documentation site to Fumadocs with monorepo structure and shared content #11) - Dispatch basis (triage promotion, quoted as ruling): "Scope fence: the fix enrolls them in the test's iteration only (assert envelope posture or an explicit debt entry); it must NOT grant them KIND status — Studio metadata coverage gaps: surface remaining types + promote un-typed concepts #2657's B/C decision stays open, exactly as the schemas file's comment intends." = the body's direction 1, with direction 2 (exporting the bound-kind names so the walk derives the set) permitted only if it stays within the fence.
- File surface:
packages/spec/src/**/metadata-type-schemas.test.ts(+ possibly a names export inmetadata-type-schemas.ts), nothing that changes runtime behavior.
Generated by Claude Code
- Branch:
PM bookkeeping:
pm:dispatched→pm:queue. The dispatched os-dev was stopped externally (mass session-level cancellation, 15:21Z patrol census) before pushing any code — no branch, zero work product. Claim released; card returns to the queue intact. The fenced brief (test-iteration enrollment only, no KIND status) remains valid; re-dispatch awaits the maintainer's word.
Generated by Claude Code
RE-CLAIM — PM seat (session
session_01PiRUoQkTSBBmpyXBY3cVn2), re-dispatched as a mode:cloud card under the maintainer's re-acceleration (2026-08-09 chat). Branchclaude/issue-6931-envelope-gate-unregistered-kinds. The scope fence in the triage promotion and the 13:37Z claim comment remain binding (test-iteration enrollment only; no KIND status; #2657 stays open).
Generated by Claude Code
OS-DEV-REPORT
{ "issue": 6931, "status": "complete", "session": "session_01JJpiZS4AgkrDYwZ2Amh5mw", "branch": "claude/issue-6931-envelope-gate-unregistered-kinds", "worktree": "../objectstack-issue-6931", "commit": "775d648a3462f6860c57315d26baa4f0581fb32f", "pr": 7116, "pr_state": "draft", "landing": "left to the PM — no auto-merge, no queue, not armed", "premise": { "verified_on": "origin/main f5a9bc2", "a_invariant_and_empty_debt_list": "HOLDS — UNDECLARED_ENVELOPE is `new Set<string>([])`, commented as the end state", "b_iteration_excludes_unregistered_kinds": "HOLDS — listMetadataTypeSchemaTypes() returns 26 names, none of webhook/connector/sharing_rule", "c_all_three_declare_envelope_today": "HOLDS — each resolves 1 object shape and declares _packageId; new assertion GREEN on arrival, value is prospective", "verdict": "alive — proceeded" }, "implementation": { "test_file": "packages/spec/src/kernel/metadata-type-schemas.test.ts", "second_it_each_over": "listUnregisteredKindSchemaTypes()", "asserts": "envelope posture only — declares ...MetadataProtectionFields, or sits on UNDECLARED_ENVELOPE_UNREGISTERED (own constant, empty, reverse-pinned)", "names_export": "listUnregisteredKindSchemaTypes() in packages/spec/src/kernel/metadata-type-schemas.ts — direction 2, names only, grants nothing", "walker_reuse": "MEASURED — both iterations are in one file and call the same objectShapes walker and the same rejectedEnvelopeKeys probe; no second copy exists to drift", "deliberately_omitted": "no unknown-key/posture case — sharing_rule is strict, connector is not, and that choice is #2657's" }, "fence": { "ruling_followed": "test-iteration enrollment only; no KIND status; #2657 stays open", "list_output_byte_identical": true, "list_output": "[\"action\",\"agent\",\"api\",\"app\",\"book\",\"capability\",\"dashboard\",\"dataset\",\"datasource\",\"doc\",\"email_template\",\"field\",\"flow\",\"hook\",\"job\",\"mapping\",\"object\",\"page\",\"permission\",\"position\",\"report\",\"seed\",\"skill\",\"tool\",\"translation\",\"view\"]", "kind_obligations_checked": [ "#4001 campaign count — still 25 closed / 26 total, green (also green in reverse run 2 with a fake fourth entry present)", "metadata-create-seeds.test.ts — green", "capability-metadata-kind.test.ts, metadata-type-api-registration.test.ts — green", "MetadataTypeSchema / DEFAULT_METADATA_TYPE_REGISTRY — metadata-plugin.zod.ts not in the diff", "check:stack-collection-maps, check:meta-type-normalized — pass" ], "new_pin": "'stays OUT of the registered-kind set' — fails if any of the three enters listMetadataTypeSchemaTypes(); a signpost for a future #2657 promotion, not a veto" }, "reverse_verification": { "direction_predicted_first": true, "run_1": { "edit": "removed ...MetadataProtectionFields from ConnectorSchema (connector.zod.ts:880), then restored", "predicted": "new 'connector DECLARES' RED naming connector; new 'connector does not REJECT' GREEN (non-strict ⇒ silent strip); BOTH original suites fully GREEN", "measured": "1 failed | 120 passed (121) — the single failure is 'connector DECLARES the protection envelope'; all 107 original cases green", "match": true }, "run_2": { "edit": "added fake_kind: z.object({ name: z.string() }) to UNREGISTERED_KIND_SCHEMAS, then removed", "predicted": "'fake_kind DECLARES' RED; its other three new cases GREEN; original suites GREEN incl. the 25/26 count; 125 total, 1 failed", "measured": "1 failed | 124 passed (125) — 'fake_kind DECLARES the protection envelope' RED; campaign count green", "match": true }, "both_edits_reverted": true }, "gates": { "lint_yml_run_one_by_one": "all green", "eslint": "pass", "spec_tsc_noEmit": "pass", "turbo_typecheck": "121/121 pass", "examples_typecheck": "pass", "downstream_contract_typecheck": "pass", "root_check_gates": "45 run; app-nav-i18n / i18n / i18n-coverage first reported 'nothing was measured — build first', all pass after turbo build (70/70)", "check_type_check_coverage_and_debt": "pass — 33 ledger entries re-measured, none above its recorded number", "check_export_origins": "NEW TODAY — the brief's prediction did NOT hold. The change is not purely test-only: the fenced fix needs the names export, so the one added export made api-surface/kernel.json AND export-origins/kernel.json stale (0 breaking, 1 added). Regenerated exactly those two (gen:api-surface, gen:export-origins) — one line added to each — then check:generated reports all 11 artifacts up to date.", "adr_0087": "node scripts/check-adr-0087-registration.mjs --base origin/main → 'no declared-breaking changeset (1 non-breaking changeset(s) seen)' — nothing owed", "changeset": "INCLUDED (.changeset/envelope-gate-unregistered-kinds.md, @objectstack/spec patch). Analog read: #7090 (test(spec) adding a checked-in artifact + gate) carried the same. The skip-changeset 'releases nothing' route does not apply here — this PR adds a public export to a published package.", "tests": "full suite green, 134/134 turbo tasks, zero failures. @objectstack/lint run separately: 68 files / 1770 tests all pass — two earlier full-suite passes hit 5s and 30s vitest timeouts in its corpus sweeps under parallel container load, on cases unrelated to this diff." }, "findings_filed": [], "notes": "Only deviation from the brief worth naming: the brief expected check:export-origins to be untouched by a test-only change. The fenced fix is not purely test-only — a hand-listed set of three names would not inherit a fourth entry, which is the whole prospective value the card is about — so the export was taken (direction 2, explicitly permitted) and the two export-surface artifacts regenerated. No other scope was added." }
Generated by Claude Code
Remote CI on PR #7116: fully green — 26 check runs, 24
success, 2skippedby paths filter (Console Pin Gate,Build Docs). IncludesTypeScript Type Check(the required job carrying the generated-artifact gates, so the regeneratedapi-surface/kernel.json+export-origins/kernel.jsonare confirmed current server-side),ESLint,Check Changeset,Test Core1–3/3,Dogfood Regression Gate1–3/3,Temporal Conformance (live PG + MySQL), andSpec property liveness.PR is draft, auto-merge not armed, not in the merge queue — landing is the PM's, as briefed.
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Oct 7, 2026
Split out of #6362 (PR #6900). Recording only — not claimed, not queued.
The observation
metadata-type-schemas.test.tsholds the ADR-0010 invariant that every metadatatype either declares
...MetadataProtectionFieldsor is named on an explicitdebt list.
UNDECLARED_ENVELOPEis currently empty, and its own commentcalls that "the end state", so the gate reads as total coverage.
It is not. The gate iterates
listMetadataTypeSchemaTypes(), which returnsObject.keys(BUILTIN_METADATA_TYPE_SCHEMAS)plus runtime extras — anddeliberately not
UNREGISTERED_KIND_SCHEMAS. That exclusion is documentedand intentional (the trailing comment in
metadata-type-schemas.tsexplainsthat enrolling
webhook/connector/sharing_rulethere would claim a KINDstatus #6245 was careful not to grant, leaving #2657's B/C decision open).
The side effect was not intended, though: those three names are bound as real
parse doors — #6245 wired them to
PUT /api/v1/meta/:type/:name— while theinvariant that exists to catch "declares no envelope" never runs over them.
Evidence that this is the actual cause, not a theory
connectorsat in exactly that gap and the gate stayed green throughout:sharing_rulewas.strict(), so its undeclared envelope produced a hard 422and meta: bind Zod schemas for webhook / connector / sharing_rule WITHOUT registering the kinds — close the unvalidated
PUT /metawrite hole (#2657 audit, option A) #6245 caught it by hitting the failure, not by the gate reporting it.connectoris a plainz.object, so it silently stripped all seven keys —no 422, no gate finding, nothing. It needed a separate hand-written card
(finding:
ConnectorSchema容忍但不保留 ADR-0010 保护信封 —— package-load 的 connector 每次 round-trip 静默丢_packageId一族 #6362) and a hand-written probe to surface, roughly a day later.webhookhappened to be fine, but only because 未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001 batch 11 had given itthe spread for unrelated reasons. Nothing verified that; finding:
ConnectorSchema容忍但不保留 ADR-0010 保护信封 —— package-load 的 connector 每次 round-trip 静默丢_packageId一族 #6362 had to measureit by hand to find out, which is precisely what a gate is for.
So all three were judged one at a time, by hand, for a property the repo already
has an automated structural walker for.
Why this is observation-class, not a defect
Nothing a user hits today: as of PR #6900 all three bound kinds declare the
envelope, so the gap is currently empty of bugs. The exposure is prospective —
a fourth entry added to
UNREGISTERED_KIND_SCHEMASinherits the samesilence, and the next one may be non-strict too, in which case the failure is
again a silent strip rather than a 422.
Possible directions (deliberately not chosen here)
it.eachoverObject.keys(UNREGISTERED_KIND_SCHEMAS)asserting only the envelope-declaration property — without enrolling them
in
listMetadataTypeSchemaTypes(), so none of the other KIND obligations(create seeds,
MetadataTypeSchemamembership, the campaign count) attach andStudio metadata coverage gaps: surface remaining types + promote un-typed concepts #2657 stays unprejudged.
hand-listing it.
Direction 1 looks like the cheap one and appears to preserve every boundary
#6245 drew, but it is a decision about a contract surface's coverage rules, so
it should be triaged rather than assumed.
Refs: #6362 / PR #6900 (connector, the silent-strip case), #6245 (bound the three
doors; fixed sharing_rule's 422 case), #4001 batch 11 (webhook's spread),
#2657 (the open B/C kind-promotion decision), ADR-0010.