Skip to content

[finding] metadata-form ↔ zod 对账门的 top-level zodOnly 方向**根本没接线**(只有嵌套列表有),这就是两个已声明键在全门禁绿的情况下缺席表单的原因 —— 本树实测 276 个 top-level zod-only 键 #19188

Description

@os-bill

Path: P2 | studio-authoring(metadata form registry) | 北极星「优先级」4

立卡席:domain:spec seat 2 执行席(座位贴 #18549,session_01JbZnqu8bt6YqfJsr9vaFb3),从 #19085 本轮的 out_of_scope_findings 接出,2026-09-19T09:24Z。⛔ 不认领、⛔ 不派发、⛔ 无 domain:* 无 priority:*。

缺陷

packages/spec/src/system/metadata-form-zod-reconciliation.test.ts 双向对账只对了一半:reconcileNestedLists 算了 zodOnly,而逐类型的 top-level it.each 只断言 formOnly 与 retired,再没有别的。

⇒ 「schema 声明了、表单没有这一行」这一格没有任何机械读者。这正是 #19085 那张卡的缺陷类:两个已声明键(field.relatedListFilter / object.validations)缺席表单,而每一道门禁都是绿的。

量级

276 个 top-level zod-only 键,横跨 17 张表单 —— 用那个测试文件自己的 helper 在本树取的:field 44/73 authorable,object 33/43,view 56/85,action 24/45。

⚠️ 这组数是 #19085 施工席的读数,本席未重跑。它可按同一批 helper 复现。

为什么是自己一张卡

补这一格不是加一行断言:每一个键要么给出一个 offer,要么给出一条记录在案的理由,276 个都要。⇒ 这是一张卡,⛔ 不是 #19085 的搭车项。施工席的处置(加一条定向 pin 盖住本卡的两个键,把普查另立)本席认。

关联,⛔ 不是重复

#14327(已关闭)是同一个对账门的另一格:它修的是嵌套表单列表只走一层。本卡是 top-level 方向压根没接线。同门、不同格。查重跑过(MCP search_issues,开+关卡皆在内,6 条,逐条看过),⛔ 无重复。

查重词:metadata form reconciliation top-level zod-only · declared key with no form row unchecked · zodOnly only nested lists · 276 unauthorable top-level keys · form registry coverage census

Blocked-by: #19332
Blocked-by: #19333


Generated by Claude Code

Activity

  1. os-litant commented on Sep 20, 2026

    @os-litant
    Collaborator

    Claim: PM loop round 2 of the resumed shift, take 3 (the shift's earlier rounds are recorded in seat post #6017)
    Session: session_01LvwGppdonww4zGLWZo5rho
    Branch: claude/issue-19188-metadata-form-zodonly-census
    Worktree: objectstack-issue-19188
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/spec/src/system/metadata-form-zod-reconciliation.test.ts for the instrument, and every packages/spec/src/**/*.form.ts and *.zod.ts READ-ONLY as census input (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgement tier — quoting this round's node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --tier packages/spec/src/system/metadata-form-zod-reconciliation.test.ts, run at 2026-09-20T11:13Z in a scratch worktree at origin/main: "Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s), derived here, not recalled … The tier stays the PM's per-card judgment call".
    Clause-②: no
    Thread-read: 5747751499
    Serial constraints cleared: measured first-hand at 2026-09-20T10:48Z over all 33 open PRs' changed-file pages (367 file rows; firing control packages/spec/src/ui/action.zod.ts → #19283, dark control → nothing). packages/spec/src/system/metadata-form-zod-reconciliation.test.ts is held by NO open PR. ⚠️⚠️ One live sibling, and it is this seat's own: card #17508 was dispatched minutes ago (claim 5749379162) and its dev is editing packages/spec/src/**/*.form.ts right now. ⇒ this round READS the form files and ⛔ writes NONE of them. See the fence below — it is the whole reason this round is shaped the way it is.


    ⭐ Your first deliverable is the SPLIT, not the fix — and that is triage's instruction, not this seat's preference

    Quoted from triage's grading 5747751499 so you work from it:

    ⚠️ This is a census card, and its size is the first thing to settle. 276 top-level zod-only keys across 17 forms (field 44/73 authorable · object 33/43 · view 56/85 · action 24/45). Closing the gate is not adding one assertion — each key needs either an offer or a recorded reason, 276 times. ⇒ the claiming seat's first deliverable is the split, not the fix: bucket the 276 by what they need, then file per-bucket cards. ⛔ Do not dispatch this as one flight, and ⛔ do not add a blanket assertion that would turn 276 absences into 276 red lines with no offers behind them.

    ⇒ this is a measurement round. Its product is a bucketed census with evidence, ⛔ not a gate change and ⛔ not 276 form rows. Filing the per-bucket cards is the seat's act on your numbers, ⛔ not yours — report the buckets and stop.

    ⭐ A round that delivers a census and zero code is a GOOD round here, and ⛔ it is not a failed one. Say so plainly in your report rather than reaching for something to change.

    The defect, so you know what you are counting

    packages/spec/src/system/metadata-form-zod-reconciliation.test.ts reconciles in both directions, but only for nested lists: reconcileNestedLists computes zodOnly, while the per-type top-level it.each asserts only formOnly and retired. ⇒ the cell 「the schema declares this key and the form has no row for it」 has no mechanical reader at all — which is why #19085's two keys (field.relatedListFilter, object.validations) were absent from the form while every gate was green.

    ⛔ Not a duplicate of #14327 (closed): that fixed a different cell of the same gate — nested form lists walking only one level. Same gate, different cell.

    ⚠️ Re-take the numbers — they are not the filer's and not the seat's

    276 / 17 forms / field 44 of 73 authorable / object 33 of 43 / view 56 of 85 / action 24 of 45 are #19085's dev's readings, and both the filing seat and triage say so explicitly. ⭐ The number is what decides the split, so re-derive it with that test file's own helper on your own base, and report what you measure. A different number is a good finding.

    ⛔ Do not count by grepping source. Use the same helper the gate uses, so your census and the gate agree by construction.

    The fence — read this twice

    ⛔ Write NOTHING under packages/spec/src/**/*.form.ts this round. Another os-dev from this same seat is editing those files right now for card #17508 (repeater row-property localisation, all four locales). Your census READS them; it must not touch them. If your bucketing genuinely requires a form edit to be expressible, stop and report — that is the seat's serialisation problem to solve, ⛔ not yours to work around.

    ⛔ Also out of scope this round: changing the gate, adding assertions, and the two keys #19085 already covered with a targeted pin.

    What a bucket is

    For each top-level zod-only key, the bucket is the answer to 「what does this key need?」 — for example: it should be authorable and needs a form row; it is deliberately not authorable and needs a recorded reason; it is retired or being retired; it is machine-written and never author-facing; it needs a ruling before anyone can say. ⭐ Derive the bucket names from what you actually find, ⛔ do not force the keys into the examples above — and say how many keys you could not bucket at all, because that number is the one the seat most needs.

    Acceptance

    • The re-derived census, per metadata type, with the helper named, a lit control (a key known present in a form, > 0) and a dark control (a fabricated key, 0), exit codes captured before any pipe.
    • Every bucket with its count, its criterion stated in one line, and 3–5 named example keys so the seat can sanity-check the bucketing without re-running you.
    • The count of keys you could not bucket, named individually if fewer than twenty.
    • A statement of whether the gate's zodOnly helper can be reused for the top-level direction as-is, or what would have to change — a reading, ⛔ not a change.
    • bash scripts/pm/os-verify-lock.sh --status re-read before your first heavy run; exit 99 is NOT MEASURED, ⛔ not red.
    • If you write no code, there is no PR and no changeset — post the report on the card and say so. If you DO end up writing code, it is a draft PR whose first line is Part of #19188, ⛔ never Fixes, because the split is what closes this card.

    Charter increments for this round

    1. ⛔ Do not leave a second claim comment — this one is the identity.
    2. ⛔ Every GitHub payload, title included, is built from a file by a script — never an inline double-quoted shell string.
    3. ⛔ Capture every exit code BEFORE any pipe; grep -c counts LINES, not occurrences.
    4. ⭐ Every reading states its base sha and the clock it was read at.
    5. ⭐ Report anything here you measure to be false rather than working around it.

    Generated by Claude Code

  2. os-litant commented on Sep 20, 2026

    @os-litant
    Collaborator

    os-dev-report
    {
    "issue": 19188,
    "status": "done",
    "round": "measurement round — the product is the bucketed census; zero code is the designed outcome here, not a shortfall",
    "branch": "claude/issue-19188-metadata-form-zodonly-census",
    "pr": null,
    "base": "596090efbe7e66ae264a691e40409d0db21805a4",
    "session": "session_01LvwGppdonww4zGLWZo5rho",
    "premise_still_valid": true,
    "summary": "The defect reproduces exactly as filed: in packages/spec/src/system/metadata-form-zod-reconciliation.test.ts the per-type top-level it.each asserts only formOnly and retired, while zodOnly is computed ONLY inside reconcileNestedLists — so 'the schema declares this key and the form has no row for it' has no mechanical reader at the top level. Re-derived on my own base (BASE 596090e, probe run 2026-09-20T11:24Z): 274 top-level zod-only keys across 17 forms, NOT 276. The delta is fully explained and is a good finding: field is 43 of 73 authorable (filer: 44) and object is 32 of 43 (filer: 33), while view 56 of 85 and action 24 of 45 reproduce the filer's readings byte for byte. The two missing keys are #19085's own: field.relatedListFilter and object.validations now measure declared=true, offered=true, inCensus=false — #19085 landed the form rows, so the filer's 276 minus those two IS my 274. Nothing else moved. All 274 are bucketed into 8 buckets plus 7 keys I could not bucket. I wrote no code: no PR, no changeset, and the worktree is byte-clean against BASE (git status --porcelain: 0 lines; git diff vs BASE: 0 lines; 0 commits). The fence held provably — I never wrote any packages/spec/src/**/*.form.ts, only read them.",
    "method": "⛔ Not grepped. The census instrument is the GATE FILE'S OWN helper block, sliced VERBATIM (bytes 0..18807, sha256 f6729dae28293c94b885958664a6e9696bcb754d4863967a04504427c7418006, up to the first describe() ) into a temporary probe test inside my worktree, so census and gate share one set of helpers by construction. Helpers named: keysOf, authorableKeysOf, isRetiredAt, topLevelFields, subSchemaOf (and the zodOnly expression itself as reconcileNestedLists spells it). Top-level census = authorableKeysOf(getMetadataTypeSchema(type)) minus topLevelFields(METADATA_FORM_REGISTRY[type]); retired tombstones are excluded by authorableKeysOf exactly as the nested direction excludes them. Both probes were deleted after the run; slicing was proved on disk (prefix region byte-identical) before either run.",
    "tests": "Run 1 (census): bash scripts/pm/os-verify-lock.sh -c 'pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/system/zz-census-19188.tmp.test.ts' — VERDICT command-exit 0, waited 392s, held 4s; 1 file / 1 test passed. Run 2+3 (union shape): same lock entry point, VERDICT command-exit 0 (waited 100s, then 0s). Gate cross-read: pnpm --filter @objectstack/spec check:liveness exit=0 (captured before any pipe; that gate is green and prints 'view 89 classified (dead 11, live 78)'). LIT CONTROL: key 'name' — offered by 17 of 17 forms and declared by 17 of 17 schemas (greater than 0). DARK CONTROL: fabricated key zzFabricatedKey19188DarkControl — 0 forms offer it, 0 schemas declare it, 0 census rows. Both controls are expect()-asserted INSIDE the probe, so the passing run is the control reading. Second lit control on the measured direction itself: #19085's two keys resolve declared=true/offered=true/inCensus=false, which is what a working zod-only census must say about keys whose rows have landed. os-verify-lock --status re-read at 2026-09-20T11:16:07Z before the first heavy run (exit 0; holder was a sibling's spec build, one waiter). No exit 99 was seen; OS_VERIFY_LOCK_SLOT=issue-19188 was set before the first attempt. No ablation and no changeset: this round produced no code.",
    "census_per_type": "type: authorable / offered / top-level zodOnly — view 85/29/56 · field 73/31/43 · object 43/11/32 · action 45/21/24 · permission 20/7/13 · app 22/11/11 · page 23/13/10 · agent 24/14/10 · flow 21/11/10 · position 12/3/9 · skill 16/7/9 · report 21/13/8 · dataset 16/8/8 · dashboard 18/10/8 · tool 14/6/8 · email_template 21/13/8 · hook 21/14/7. TOTAL 274 across 17 forms. formOnly = 0 and offered-but-retired = 0 everywhere, i.e. the two directions the gate DOES assert are currently clean.",
    "buckets": [
    {
    "id": "B1",
    "name": "ADR-0010 provenance/lock overlay — system-stamped, never authoring surface",
    "count": 132,
    "criterion": "the key is in the liveness gate's own FRAMEWORK_FIELDS set (scripts/liveness/check-liveness.mts): 7 underscore keys on all 17 forms (119) plus protection on 13 of them",
    "needs": "ONE recorded reason covering the whole overlay — never 132 form rows",
    "examples": [
    "object._lock",
    "field._provenance",
    "view._packageVersion",
    "flow.protection",
    "email_template.protection"
    ]
    },
    {
    "id": "B2",
    "name": "platform-written, never authored",
    "count": 4,
    "criterion": "the schema's own description states the value is not/never authored (console round-trip state, or machine-managed)",
    "needs": "a recorded reason; offering a control would be the defect",
    "examples": [
    "view.columnState",
    "view.isPinned",
    "view.sortOrder",
    "app._unpublished"
    ]
    },
    {
    "id": "B3",
    "name": "deprecated or legacy alias, deliberately not offered to new authors",
    "count": 4,
    "criterion": "the description carries a bracketed DEPRECATED or LEGACY ALIAS marker",
    "needs": "a ledger omit row of the shape the gate already uses (page.interfaceConfig.sourceView is the precedent)",
    "examples": [
    "object.displayNameField",
    "object.titleFormat",
    "view.drawerWidth",
    "view.groups"
    ]
    },
    {
    "id": "B4",
    "name": "declared, deliberately not enforced yet",
    "count": 5,
    "criterion": "the liveness ledger verdict is planned or experimental",
    "needs": "no offer until it is enforced; the ruling belongs to the enforcement card, not to this gate",
    "examples": [
    "object.externalSharingModel",
    "field.useGrouping",
    "action.onSuccess",
    "page.requires",
    "agent.structuredOutput"
    ]
    },
    {
    "id": "B5",
    "name": "view: key belongs to a view-kind arm the registered view form does not author",
    "count": 16,
    "criterion": "type view, and the key is absent from the list-family union arm that view.form.ts actually authors",
    "needs": "a RULING on whether view reconciliation is per-type or per-arm — no number of rows in the one registered view form can close this",
    "examples": [
    "view.sections",
    "view.drawerSide",
    "view.modalSize",
    "view.submitBehavior",
    "view.splitDirection"
    ]
    },
    {
    "id": "B6",
    "name": "view: genuine gap in the list designer",
    "count": 20,
    "criterion": "type view, key IS on the list-family arm the designer authors, carries a live verdict, and is not offered",
    "needs": "an offer in view.form.ts, or a ledger reason — 3 scalar, 17 structured",
    "examples": [
    "view.allowPrinting",
    "view.bulkActions",
    "view.conditionalFormatting",
    "view.emptyState",
    "view.rowColor"
    ]
    },
    {
    "id": "B8",
    "name": "offer it — scalar control, mechanical row",
    "count": 47,
    "criterion": "object-rooted type, liveness verdict live, and the node is a scalar (string/number/boolean/enum)",
    "needs": "one form row each; the cheapest bucket and the best first per-bucket card",
    "examples": [
    "object.nameField",
    "object.ownership",
    "field.precision",
    "action.shortcutKey",
    "permission.license"
    ]
    },
    {
    "id": "B9",
    "name": "offer it — structured control, needs a designed widget",
    "count": 39,
    "criterion": "object-rooted type, liveness verdict live, and the node is object/array/record/union",
    "needs": "a control design per key (or a curated subset entry); ⛔ not a one-line row",
    "examples": [
    "object.access",
    "object.external",
    "object.fieldGroups",
    "field.summaryOperations",
    "action.params"
    ]
    }
    ],
    "could_not_bucket": {
    "count": 7,
    "all_of_them": [
    "view.config — a required (non-optional) object, "List-family view configuration"; carries no liveness verdict at any coordinate",
    "view.viewKind — the union discriminator literal, no description, no verdict; probably machine/derived but nothing on this tree says so",
    "view.scope — "Identity layer (defaults to package for source-loaded views)": authored, or set by the loader?",
    "view.owner — "Owner user id — set when scope is personal": platform-set on a personal view, or authorable?",
    "view.isDefault — "the object's default view in the switcher": author intent, or per-user state like isPinned?",
    "view.hidden — "Hidden from the switcher (per-user / per-org declutter)": same question as isDefault",
    "view.order — "Sort order within the object's view switcher": same question; its sibling sortOrder IS marked not-authored, this one is not"
    ],
    "why_unbucketable": "all 7 are view keys with NO liveness verdict at any coordinate (direct or container-child) and no description marker that settles author-vs-platform. Five of them (scope/owner/isDefault/hidden/order) are one coherent question: which of the switcher/identity keys are authored and which are per-user state. That question is a ruling, and it is the one thing in this census nobody on this tree can answer by measurement."
    },
    "reading_can_the_zodonly_helper_serve_the_top_level_as_is": {
    "verdict": "Yes for 16 of the 17 types, no for view — and not at all until the ledger learns a top-level path. A reading only; I changed nothing.",
    "one": "The per-key EXPRESSION transfers unchanged. reconcileNestedLists computes zodOnly as subKeys.filter(k => !offered.includes(k) && !excused.includes(k) && !isRetiredAt(sub, k)); substituting sub := root and offered := topLevelFields(form) is exactly the top-level question, and retired tombstones stay excluded for free.",
    "two": "BLOCKER — the ledger has no top-level coordinate. Every LEDGER entry is keyed by a dotted path, and the "every ledger entry still resolves on both sides" test does lists.find(l => l.path === entry.path) against nestedLists(form), which only ever yields nested paths. A root entry (path "" or a sentinel) makes that test fail, so a top-level direction cannot record its FIRST deliberate omission without that test learning a root path. This is the concrete change the direction needs, and it is small.",
    "three": "BLOCKER for view — keysOf() on a union returns the UNION of the arms' keys, which is the SAFE direction for formOnly (the file says so in its own comment: an author may legally write any member's key) and the UNSAFE direction for zodOnly. Measured: view is the only union-rooted type of the 17 — 4 arms of 21/15/65/46 keys, union 85, INTERSECTION 11. Of its 56 zod-only keys, 9 are on every arm, 47 are on some arms only, and 35 are exclusive to exactly one arm. An as-is top-level zodOnly on view therefore demands one form simultaneously offer mutually exclusive arms (columns from the list arm and sections/drawerSide/modalSize from the form arm). Arm-aware resolution, or a per-arm form registry, is needed before view can be judged at all.",
    "four": "NOISE — 132 of the 274 (48%) are the ADR-0010 provenance/lock overlay. The liveness gate skips exactly this set as auto-live framework fields; the reconciliation gate has no equivalent skip, so wiring zodOnly at top level as-is makes half the red lines overlay noise. It needs the same FRAMEWORK_FIELDS-shaped skip (or 132 ledger rows, which is the wrong shape).",
    "five": "NOT a retirement problem: ZERO of the 274 carry a dead verdict. Cross-checked the other way — 27 dead rows exist across these 17 types; 5 are offered by forms and 22 are not in the authorable top-level shape at all (tombstoned or removed). So no bucket of this census is enforce-or-remove work."
    },
    "mcp_calls": "0 — no MCP GitHub tool was called, read or write",
    "api_writes": "1 — POST /repos//issues/19188/comments (this report). Reads were GET only: issues/19188, its comments, comment 5749440370, issues/19085/comments. No label write (no PR exists this round), no POST /issues.",
    "open_questions": [
    {
    "question": "Is view reconciled per metadata type or per union arm? B5 (16 keys) and most of B7 cannot be filed as cards until this is settled, because the registered view form authors ONE arm of four.",
    "options": [
    "A. per-arm: register a form per view kind (list/form/...) and reconcile each arm against its own form",
    "B. per-type with an arm-aware ledger: one form, and a ledger vocabulary that can say "this key belongs to an arm this form does not author"",
    "C. leave view out of the top-level direction entirely and gate only the 16 object-rooted types"
    ],
    "recommendation": "C first, then B. C buys the other 16 types their gate immediately (142 of the 274 minus view = the whole non-view population is object-rooted and needs no new vocabulary beyond the root path), and it makes view a single named exclusion rather than 56 unactionable red lines. B is the real answer but it is a design, not a card."
    },
    {
    "question": "Which view switcher/identity keys are authored and which are per-user state (view.scope, owner, isDefault, hidden, order)?",
    "options": [
    "A. a maintainer ruling recorded as ledger rows",
    "B. measure the writers in objectui and classify from the reads"
    ],
    "recommendation": "B then A — their siblings columnState/isPinned/sortOrder already say "written by the console, not authored" in their own describe() text, so the console is very likely the writer for some of these five; measuring is cheaper than ruling and produces the evidence the ruling needs."
    }
    ],
    "out_of_scope_findings": [
    "noted, not filed: the liveness ledger for view is anchored to the LEGACY nested arm — liveness/view.json carries 7 prop rows (name/label/object/list/form/listViews/formViews) while ViewMetadataSchema declares 85 authorable keys, and 33 of view's top-level keys resolve a verdict only through a container child coordinate (list.allowPrinting, form.drawerWidth). ⛔ Not filed: the gate DECLARES this rather than hiding it — it prints 'container coverage: 114 container entries carry a blanket verdict over 604 child keys that are classified NOWHERE … recorded, not asserted' and the baseline scripts/liveness/undrilled-containers.baseline.json is shrink-only. Successor: whoever takes the view bucket (B5/B6/B7) reads this file first.",
    "noted, not filed: app._unpublished is an underscore-prefixed, 'Machine-managed … Never authored' key that is NOT in the liveness gate's FRAMEWORK_FIELDS overlay set and instead carries an ordinary live row. It is the one key in this census whose machine-written status is spelled only in prose. ⛔ Not filed: nothing is broken by it (no gate reads that set for authorability), and its own B2 bucket already carries it. Successor: the B2 per-bucket card."
    ]
    }


    Generated by Claude Code

  3. os-litant commented on Sep 20, 2026

    @os-litant
    Collaborator

    Release: the split is delivered; card converted pm:dispatched → pm:blocked, assignee cleared. Seat domain:spec#1, session_01LvwGppdonww4zGLWZo5rho, seat post #6017. Cause: the round completed and delivered its census (report 5749550902); what remains is the WIRING, and that is now blocked on two of the cards filed off it. Destination: pm:blocked on #19329, #19330, body line added and read back.

    Verified before adopting anything — ⛔ the report was not taken on its own word

    Three load-bearing claims, re-measured by this seat at 2026-09-20T11:39Z on origin/main:

    1. Zero code, as designed. git ls-remote origin 'refs/heads/claude/issue-19188*' is empty. LIT control: the same command for issue-17508* returns 79ae7e0bf, so the instrument does see a pushed branch. ⇒ no PR, no changeset, no branch — and per this round's own order that is a good outcome, ⛔ not a shortfall.
    2. The 276 → 274 delta is real and explained. field.relatedListFilter now resolves in packages/spec/src/data/field.form.ts and object.validations in packages/spec/src/data/object.form.ts — finding(spec): field.relatedListFilter and object.validations are DECLARED by the served schema and omitted by METADATA_FORM_REGISTRY's forms — the generic metadata form never renders them, so an author's only door is the Source tab #19085 landed those two form rows, so the filer's 276 minus those two IS 274. DARK control: a fabricated key resolves in 0 form files. ⇒ the count moved because the tree moved, ⛔ not because the instrument changed.
    3. The defect shape holds. zodOnly is declared at :365, computed at :391 and consumed at :454 — all inside reconcileNestedLists. The top-level direction has no reader.

    ⭐ The arithmetic is internally consistent, checked by this seat rather than assumed: the 17 per-type counts sum to 274, and 132+4+4+5+16+20+47+39 = 267 bucketed, +7 unbucketable = 274.

    ⚠️ One discrepancy, recorded not fixed: the report's prose references a bucket 「B7」 that has no entry in its own bucket list, and the totals balance without one. ⇒ read B7 as a stray label, ⛔ not as a missing bucket — but whoever takes the view cards should notice it rather than hunt for 0 keys.

    The split — six cards, filed BEFORE this card was converted

    Triage's instruction at 5747751499 was 「the claiming seat's first deliverable is the split, not the fix: bucket the 276 by what they need, then file per-bucket cards」. That is done:

    card what keys
    #19329 ⭐ the prerequisite — the ledger has no top-level coordinate, and the gate has no FRAMEWORK_FIELDS-shaped skip. Nothing else can be recorded until this lands —
    #19330 ⭐ [Decision] is view reconciled per metadata type or per union arm? blocks 36
    #19331 scalar controls — one form row each, the cheapest bucket 47
    #19332 structured controls — a designed widget per key, ⛔ not a row 39
    #19333 a RECORDED REASON, never a form row (provenance overlay 132 · platform-written 4 · deprecated 4 · not-yet-enforced 5) 145
    #19334 7 view keys with no liveness verdict anywhere; five of them are one question 7

    47 + 39 + 145 + 36 (view buckets, in #19330) + 7 = 274. ⇒ every key is carded, ⛔ none is left in this thread.

    Why this card is pm:blocked and not closed

    Its own defect — the top-level direction has no reader — is not fixed. The split was the first deliverable, ⛔ not the whole card. The wiring is still this card's, and it cannot be done until #19329 (the ledger root path and the overlay skip) lands and #19330 (the view ruling) is answered, because without them the gate would record nothing and half its output would be overlay noise.

    ⛔ It is not needs-user-decision either: the decision that exists is #19330's, and it is on its own card, where the inbox can say which question is open.

    Two readings for whoever wires it, both from the round, ⛔ neither acted on here

    • The per-key expression transfers unchanged. subKeys.filter(k => !offered.includes(k) && !excused.includes(k) && !isRetiredAt(sub, k)) with sub := root and offered := topLevelFields(form) is exactly the top-level question, and retired tombstones stay excluded for free. The blockers are the ledger path and the overlay skip, ⛔ not the predicate.
    • This is not enforce-or-remove work. Zero of the 274 carry a dead verdict; cross-checked the other way, 27 dead rows exist across these types, 5 are offered by forms and 22 are not in the authorable top-level shape at all.

    Two out-of-scope findings, noted and carried, ⛔ not filed separately

    Both are recorded on the cards that will meet them: the view liveness ledger being anchored to the legacy nested arm (7 prop rows against 85 authorable keys) rides on #19330; app._unpublished being described as machine-managed while sitting outside the framework-field set rides on #19333.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    Unblock re-derivation (maintainer instruction 2026-09-23: 「上游已关,却还挂着阻塞,你帮我更新」) · 2026-09-23T15:55Z

    Upstream closed: #19329 closed completed 2026-09-22; its PR #19639 added the root coordinate and the ADR-0010 overlay skip. #19330 closed completed 2026-09-21 with ruling A: view is reconciled per arm, and the top-level direction covers the 16 object-rooted types for now.

    Re-derivation: PR #19639 and PR #19673 (the 45 scalar rows, #19331) both merged after the transition. Both leave the top-level zodOnly wiring undone, so this card's work is not done. Wiring it today would turn the 49 object-rooted keys that still have no offer or recorded reason into red lines, which triage refused in 5747751499. Those keys are carded on #19332 (structured controls) and #19333 (recorded reasons, including the 4 keys PR #19673 left out).

    Blocked-by: #19332
    Blocked-by: #19333

    New state: stays pm:blocked; the body's Blocked-by: #19329, #19330 line is replaced by the two lines above. Per #19330 A, view stays out of the direction until its first arm form is registered. Side note: #19333's own body still carries a stale Blocked-by: objectstack-ai/objectstack#19329 line.

    Session session_01X7HwfPLpQtCixDMrRGkSbe.


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Closing record — completed · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx, seat post #18549) · 2026-09-28T23:06Z

    The defect this card filed is fixed on main: the top-level zodOnly direction of the metadata-form ↔ Zod reconciliation is now wired.


    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

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions