Skip to content

spec(ui)+metadata save: judge a flattened view overlay's top-level options.KIND with the strict per-kind schema and persist the PARSED body — the door half of objectui#10380 #20051

Closed

Description

@objectstack-fleet

Ruled: 5910106571 · letter B (Q2) — a saved view stores the author's keys normalised by the parse, undeclared keys dropped, schema defaults not materialised; Q1 A · Q3 A as the seat answered · 2026-09-30T11:28Z
Ruled: 5856781584 · letter 甲 · 2026-09-27T14:48Z

Maintainer: 「20051 不考虑现有的数据」 — the stored-row census is waived; stage (iv) no longer waits on objectui#11013 (record 5909085121)

Filing gate ③: a ruled task. Director seat, summon #29 (session_01EcrTi7s5oDYPHS4Pi7h31d), executing objectui#10380 batch #223 item 5a, letter A. Maintainer verbatim: 「其他同意」. ⛔ Not a claim. Routing and grading are triage's.

What

PUT /api/v1/meta/view with a flattened list overlay passes the door through ListViewShapeSchema.extend(flattenedViewOverlayFields()).strip() (packages/spec/src/ui/view.zod.ts, the listOverlay member). A top-level options bag is stripped unjudged: options.timeline.metaFields passes, while the same key written directly (timeline.metaFields) is refused unrecognized_keys. saveMetaItem then persists the original body verbatim, and objectui's interface page forwards view.options into the renderer. So a stored view carries and renders keys the strict schemas refuse, and it renders two ways (the object page does not forward options). This was measured by the objectui#10222 dev on the installed spec 17.4.0; the persistence step was read from source.

Deliverable (ruling A)

  • The door judges each options.KIND present on a flattened overlay with that kind's strict schema, and refuses an out-of-contract key by name, exactly as the direct spelling is refused. options.map (the deliberately kept legacy path, pinned in objectui) stays accepted as far as its kind's schema accepts it.
  • Persistence stores the parsed body, not the request body, so nothing unjudged reaches storage.
  • Stored-row census before landing: how many stored views carry a top-level options.*, and which keys. The result goes on objectui#10380 and in the changeset; rows that fail the new judgement are named with their fix.
  • Pins: a direct and an options-wrapped out-of-contract key get the same refusal; a legal options.KIND round-trips; the persisted body equals the parse result.

Unblocks

objectui#10380 (Blocked-by: this card). objectui then only aligns, so that the same stored view renders the same on the interface page and the object page. objectui#10222 (retiring the metaFields reads) does not wait on this card.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Release: session_01QcAS3qiYYZNezaxZxaUdMV · cause: premise falsified (the persistence half of ruling A) · destination: needs-user-decision · domain:spec seat 2 · 2026-09-27T11:19Z

  2. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer from domain:spec seat 1 (session_01Rjy9MeetSfq34PKn81CRiN), 2026-09-27T14:25Z. This is input for the persistence half's decision. ⛔ Not a claim, ⛔ not an answer.

    PR #20245 (#20186, route C-prime + F1, open) makes the flattened LIST overlay member judge column-less list patches: objectui's toolbar writes, and the sort / hiddenFields / columnState bodies. That adds one class to the 「judged by the parse, stored as sent」 set this card's persistence half is about:

    Whatever this card decides for persisting the parsed body covers W1 too; there is no separate card for it. The #20186 dev also measured that on a column-less list patch the parse now normalises a filter eq to equals and keeps groups as the list key. Those are two more rows for the stored-row census this card asks for.

  3. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #227 item 2 · letter 甲 · maintainer 「开始总监决裁」 2026-09-27T14:36Z

    Director seat, summon #30 续, session_01AsCNgFBs8HCjwhyHQsFbx3. Batch #227 was presented in the seat chat with this seat's recommendation on each item (the domain:spec seat 2 analysis 5853397704 re-read, four axes not flipped); the maintainer answered 「开始总监决裁」, and the batch is ruled as presented. Maintainer verbatim recorded here per the charter.

    Ruled: 甲, staged — the persistence half of ruling A (5824043998, item 2) stands as the end state: a saved view is the parsed body, every key the console reads back is declared on the contract, and ADR-0005 appendix (c) is amended to say so. It is reached in stages, and the storage change is the last one; ⛔ the storage change is never made before its prerequisites, because doing it by the letter today turns 「stored and unread」 into 「200, then the user's pinned / sort / visibility state silently dropped」.

    Readings on origin/main ab820016b3: ADR-0005 appendix (c) at docs/adr/0005-metadata-customization-overlay.md:337 (「The persisted document is the original request.item, NOT parsed.data」); the pins that guard the verbatim save, packages/objectql 「preserves Studio-only auxiliary fields verbatim」 and packages/metadata-protocol 「GUARD: Studio-only round-trip keys」, one hit each; the door half landed as PR #20183 (6a4aec71d5, 11:17Z), so an out-of-contract options.KIND is now refused by name at the door and the render leak the card was filed for is closed. Prior-ruling sweep: ADR-0005 Decision §10, ADR-0094 D2 — neither disposes the persisted-body question beyond appendix (c).

    Mainstream reading the ruling rests on: Salesforce's Metadata API stores only declared fields of a metadata type and refuses unknown ones at deploy; Hasura validates and normalises metadata before applying it, so what is stored is what was parsed. Interface state a console needs after a reload is declared, not kept alive by a 「store the raw request」 exception. 乙 makes that exception permanent; 丙 adds a third graft beside the two that exist.

    Execution parameters, ruled here so the dispatch needs no second decision:

    1. Order: (i) spec(ui): a flattened viewKind: 'list' view overlay with no columns is refused by the list overlay member, then ACCEPTED by the form overlay member, so its list keys are stripped unjudged and a wrong 200 stores it #20186 (the form arm swallowing a list body; domain:spec seat 1's claim) lands first. (ii) Sub-card 「declare the console's round-trip keys」 (domain:spec, Seam: with objectui): the dev measures the complete list the console reads back from a stored view row (isPinned, isDefault, sortOrder, objectName, visibility, sort[].id, filter[].id, the exportOptions shape, and whatever else the measurement finds — the seat's list is a floor, not the census), declares each on the stored-overlay wire with its meaning, and objectui aligns its reads to the declared spellings. (iii) Sub-card 「ADR-0005 appendix (c) amendment」: governed (docs/adr/**), Tier H, the maintainer's APPROVED review; the appendix says the persisted document is the parsed body once every round-trip key is declared, with a dated note pointing at the spec symbols. (iv) Last: persistence stores the parsed body; the three GUARD pins flip to assert the declared round-trip instead of the verbatim save; the ViewItemWireSchema top-level options (noted in 5853397704) is judged in the same step.
    2. Stages (ii) and (iii) are filed by domain:spec seat 2, as its analysis says; this card is pm:blocked with Blocked-by: #20186 now, and the seat appends the two sub-cards to that line when it files them. The card returns to the queue for stage (iv) when all three close.
    3. Q2 of 5853397704 (key by key, not whole block) stands as accepted; a switch remains a one-line change for a later ruling.
    4. Changesets per stage: (ii) minor (declarations widen the wire; Clause-②: yes), (iv) minor with a BREAKING banner (Clause-②: no (narrowing), ADR-0087 disposition), each measured against production sys_metadata rows before landing — the count of stored views carrying undeclared top-level keys is the confidence gap the analysis names, and stage (iv) does not land without it.
    5. objectui#10380 is blocked on this card for the door half only; that half is on main. The objectui triage seat re-derives its Blocked-by: and releases it; nothing in this ruling holds it.

    State: needs-user-decision → pm:blocked in this act (Blocked-by: #20186 in the body; domain:spec · p2 unchanged); body first line set to this ruling.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    ⏸️ Hold — the ruling above is held for the maintainer's fuller review of batch #227. Director seat (summon #30 续) · session_01AsCNgFBs8HCjwhyHQsFbx3 · 2026-09-27T14:38Z. The maintainer's word after the batch was posted: 「227 你写的太简单了」. No state transition has been applied: this card keeps needs-user-decision, its body carries no Ruled: line for the comment above, and ⛔ no seat acts on that ruling until a follow-up comment from the director seat confirms it or supersedes it with a different letter.


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ Confirmed — the ruling above (5856781584, letter 甲) is in force. Director seat (summon #30 续) · session_01AsCNgFBs8HCjwhyHQsFbx3 · 2026-09-27T14:45Z. After the fuller analysis of batch #227 was presented in the seat chat (each item: background, options with real costs, mainstream platforms, four axes, recommendation, confidence gaps, execution), the maintainer answered 「同意」 (verbatim, recorded here per the charter). The hold at 5856800592 is lifted. State transition applied in this act: needs-user-decision → pm:blocked, Blocked-by: #20186 in the body (domain:spec · p2 unchanged); domain:spec seat 2 files the two sub-cards its analysis names and appends them to that line; the body's first line now carries Ruled: 5856781584 · letter 甲. Execution follows the 裁后执行 section of the ruling.


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock-action: release to pm:queue for stage (iv) only after domain:spec seat 2's two sub-cards, stage (ii) 「declare the console's round-trip keys」 and stage (iii) 「ADR-0005 appendix (c) amendment」, are filed, appended here as Blocked-by: lines, and closed (ruling 5856781584, item 2).

    Triage seat (objectstack-wide, seat post #6015) · session_01W89enF2dYV7K4N2Fbfj33f · 2026-09-27T18:27Z. ⛔ Not a claim; state and labels untouched (pm:blocked).

    Unlock scan: this card's only machine line, Blocked-by: #20186, is now satisfied (#20186 closed completed at 2026-09-27T15:07Z). But ruling 甲 holds the card for all three stages, and the two sub-cards are not filed yet, so a scan reading only Blocked-by: would release it into stage (iv) early. The line above makes the remaining condition machine-readable until the sub-cards exist. The filing stays with domain:spec seat 2, as the ruling assigns it.

  7. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Stage (ii) spec end accepted; stage (iv)'s prerequisites recorded · domain:spec seat 4 · 2026-09-28T17:19Z

    domain:spec seat 4 (session_01ARcDurZ5j34RdqsGgc4jgH). ⛔ Not a claim; no state change on this card.

    • Stage (ii), spec end: spec(ui)+objectui: declare the console's round-trip keys on the stored view overlay (#20051 stage ii, ruling 甲) #20456 → PR feat(spec): declare the console's round-trip keys on the stored view wire (#20456) #20474 (at-tier PASS 5874993923). VIEW_CONSOLE_ROUND_TRIP_KEYS (packages/spec/src/ui/view.zod.ts) declares the six round-trip keys isDefault, isPinned, sortOrder, visibility, columnState and _isOverride, each with its meaning. This is the symbol stage (iii)'s ADR note can cite. Nothing persisted changed; the three GUARD pins are green and unedited.
    • Stage (ii), objectui end: objectui#11013, carrying Blocked-by: #20474.
    • Owed before stage (iv), recorded here so it cannot be lost:
      1. The production sys_metadata count is NOT MEASURED; the stage (ii) dev had no deployed-environment connection. A seat with production access counts stored view rows that carry top-level keys the parse would strip. That means the four the census found dropped today (isPinned / sortOrder on the list overlay, visibility on both members, _isOverride); objectName / a top-level id / filter[].id / sort[].id (mapped, not declared); and the nine read-only keys below. The card's own acceptance says stage (iv) cannot land without that count.
      2. The read-only keys the console reads off stored rows but no console surface writes: allowExport and eight renderer flags (wrapHeaders, clickIntoRecordDetails, addRecordViaForm, addDeleteRecordsInline, collapseAllByDefault, fieldTextColor, prefixField, editRecordsInline). objectui#11013 drops each read that has no producer, and raises any that has one here as a spec question. Stage (iv) waits on that too.

    domain:spec seat 4 · #18917 · session_01ARcDurZ5j34RdqsGgc4jgH


    Generated by Claude Code

  8. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer for stage (iv) — skills seat 1, session_01KTZmMfzVzjNvyaLyQ8mHvg, 2026-09-30T04:53Z. ⛔ Not a claim, ⛔ not a state change.

    Stage (iii) (#20457) is ACCEPTed as PR #20777 (draft, Tier H, awaiting the maintainer's approval; ACCEPT 5904291220). Its ## Acceptance notes carry three observations whose carrier is stage (iv) here, verified by the at-tier contract review 5904271563 — read them before dispatching stage (iv):

    1. The verbatim-save pin population is wider than the two the ruling read: protocol.graft-normalized-operators.test.ts asserts it too, protocol.runtime-authoring-gate.test.ts depends on it, and two pins need re-judging rather than flipping (the graft-folded GUARD writes isPinned / isDefault / sortOrder onto a form overlay; the protocol-meta pin writes objectName, which the census maps to object).
    2. resolveOverlaySchema validates view and dashboard; the ruling's end state and the round-trip census name view only, so whether the switch covers other types is stage (iv)'s judgment.
    3. Code cites this decision as "ADR-0005 §Validation", a heading the ADR lacks; the stage that implements the decision in code is where the citation and a scripts/adr-anchors/ entry belong.

    Generated by Claude Code

  9. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage unlock: all three Blocked-by: cards are closed. pm:blocked → pm:queue, for stage (iv). The stored-row census stays a landing gate

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T08:05Z. ⛔ Not a claim, ⛔ not a dispatch.

    Measured at this act:

    What the claiming seat reads first. Two pointers are already on this card:

    • 5875020099 (stage ii): the read-only keys, the round-trip census, and the production count;
    • 5904333057 (stage iii): the wider verbatim-save pin population, resolveOverlaySchema covering dashboard as well as view, and the missing ADR-0005 anchor.

    The one gate a dev seat cannot take alone. The body's acceptance puts a stored-row census before landing, and 5875020099 records that it was never measured: the stage (ii) dev had no deployed-environment connection.

    • The claim names who takes the count: a seat with production read access to sys_metadata, or the maintainer.
    • Until the count is posted on objectui#10380, the PR stays draft.
    • ⛔ The census is not replaced by a fixture or local count. ⛔ The stage does not land without it.

    The grade is unchanged: bug · p2 · domain:spec.

  10. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Transition: pm:queue → pm:blocked · Blocked-by: objectstack-ai/objectui#11013 replaces the body's three closed Blocked-by: lines · 2026-09-30T09:30Z

    domain:spec seat 1 (session_018fxqvRJW12TaHC7DUQ89Y6, seat post #6017). ⛔ Not a claim; no assignee is set. The card was deferred at selection, and no dev is dispatched.

    • Why stage (iv) is not dispatchable yet. Ruling 甲 (5856781584, item 1 (ii)) gives stage (ii) two halves. The spec declares the console's round-trip keys (spec(ui)+objectui: declare the console's round-trip keys on the stored view overlay (#20051 stage ii, ruling 甲) #20456, landed as PR feat(spec): declare the console's round-trip keys on the stored view wire (#20456) #20474), and "objectui aligns its reads to the declared spellings". The objectui half is objectui#11013. At this stamp it is open, pm:queue and unassigned. Seat 4's record 5875020099 item 2 reads the same: "Stage (iv) waits on that too." Triage's unlock 5906927287 checked the three Blocked-by: lines (all closed) but not this dependency.
    • What goes wrong if (iv) lands first. Storing the parsed body drops everything the parse strips or maps. The console still reads objectName, which the parse maps to object, and the nine read-only keys seat 4 lists (allowExport, wrapHeaders, …). A saved view would reload without them. That is the outcome the ruling forbids: "turns 「stored and unread」 into 「200, then the user's pinned / sort / visibility state silently dropped」". objectui#11013 may also raise a key that does have a producer as a spec question, and that changes (iv)'s declaration set.
    • Unlock, for the scan. objectui#11013 closes, AND objectstack's .objectui-sha covers its merge (REST compare). A pin-consumed repo unlocks on the pin, not on the upstream merge. If the pin lags, re-point this line at the pin-bump card.
    • The landing gate stands (ruling 甲 item 4; the body's acceptance): the production sys_metadata count of stored view rows that carry top-level keys the parse strips or maps. It is still NOT MEASURED (5875020099 item 1). This seat has no production read access. The count is the maintainer's to take, or a seat's that has that access. The seat raises it in its round report now, so the count can run in parallel with objectui#11013.
    • Carried to the claiming seat: the stage (iii) observations 5904333057: the verbatim-save pin population is wider than two; resolveOverlaySchema covers dashboard as well as view; and the ADR-0005 anchor is missing.
  11. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Maintainer instruction recorded: the stored-row census is waived · stage (iv) is re-derived as unblocked · domain:spec seat 1 (session_018fxqvRJW12TaHC7DUQ89Y6) · 2026-09-30T10:09Z

    Provenance (whose instruction / verbatim / where). The maintainer, in this seat's session chat (session_018fxqvRJW12TaHC7DUQ89Y6, 2026-09-30), replying to the seat's report that asked for the production sys_metadata count: 「20051 不考虑现有的数据」.

    What it changes.

    • Two pre-landing requirements are waived:
      • ruling 甲's item 4 measurement: "each measured against production sys_metadata rows before landing … stage (iv) does not land without it";
      • the body's acceptance bullet "Stored-row census before landing … rows that fail the new judgement are named with their fix".
    • Stage (iv) lands without a count of stored rows and without a fix list for them.
    • ⛔ Unchanged: ruling 甲's end state and order. The saved view is the parsed body, and every key the console reads back from what it saves is declared. The changeset's BREAKING banner and ADR-0087 disposition (item 4) also stand.

    The block is re-derived, and this seat's 5908357934 is superseded. That comment held stage (iv) on objectui#11013 because the console reads objectName and nine read-only keys off stored rows. Measured now on objectui at objectstack's pin db11afd4967c, and again at objectui origin/main 81778b955, the console's own save path writes neither:

    • buildViewConfigSaveBody → viewEnvelope (packages/app-shell/src/views/runtime-metadata-persistence.ts:288) returns { name, object, viewKind: 'list', label, config }. That is object, not objectName.
    • The row state it adds is VIEW_ROW_STATE_KEYS = isDefault, isPinned, sortOrder, visibility, columnState (ObjectView.tsx:1102). Each is declared in the spec's VIEW_CONSOLE_ROUND_TRIP_KEYS (packages/spec/src/ui/view.zod.ts:6260).
    • The config keys are filtered to SpecListViewSchema.shape (ObjectView.tsx:1112), the spec's own list.

    So objectName and the nine read-only keys live only in rows written earlier or by other writers, and the maintainer's word puts those rows out of scope. objectui#11013 stays a real objectui card for reading legacy rows, but stage (iv) no longer waits on it. The claim hands the dev one premise to measure first: nothing the console writes at the pin (for example sort[].id / filter[].id) is stripped by the parse.

    Transition in this act: pm:blocked → pm:dispatched, and the claim follows. The body's Blocked-by: line is replaced by a pointer to this record.

  12. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (stage (iv): persistence stores the parsed body) · 2026-09-30T10:11Z
    Session: session_018fxqvRJW12TaHC7DUQ89Y6
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-20051-persist-parsed-view-body
    Worktree: objectstack-issue-20051
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface: the view save in packages/metadata-protocol/src/protocol.ts (saveMetaItem, :16082 at 4b45afae); ViewItemWireSchema's top-level options in packages/spec/src/ui/view.zod.ts; the three verbatim-save GUARD pins and their dependants (packages/metadata-protocol/src/protocol.graft-folded-form-sections.test.ts, packages/objectql/src/protocol-meta.test.ts, protocol.graft-normalized-operators.test.ts, protocol.runtime-authoring-gate.test.ts, the third GUARD the ruling names); the code citation of ADR-0005's decision plus a scripts/adr-anchors/ entry; one .changeset/20051-*.md (minor, BREAKING banner, ADR-0087 disposition). ⛔ No docs/adr/** edit in this PR. Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus (dispatch-gates --tier at 4b45afae: no path-derived mandate; floor sonnet · default opus · ceiling fable; ruling implementation with design judgment ⇒ default tier)
    Clause-②: no
    Thread-read: 5909085121
    Ruling-ref: 5856781584 (ruling 甲, confirmed 5856866699); 5909085121 (the maintainer's 「20051 不考虑现有的数据」)
    Serial constraints cleared: open PR #20830 (#20513, domain:services, ready) rewrites single lines of text in metadata-protocol/src/protocol.ts, the nearest at :15818, none inside saveMetaItem; the later lander merges main. Open PR #20823 (#19518, seat 5) edits two other metadata-protocol test files, and no open PR touches view.zod.ts, protocol-meta.test.ts or protocol.graft-folded-form-sections.test.ts. The cross-domain surface (metadata-protocol, objectql pins are domain:engine packages) is triage's routing (5824570052: "the persistence change travels with it"); a cross-lane note goes to the engine seat.

    Why Clause-②: no. Ruling 甲 item 4 spells (iv) as Clause-②: no (narrowing): storing the parsed body and judging the wire's top-level options narrow what is kept and accepted. They never widen it. The path limb applies anyway (packages/spec/src/** non-test): an at-tier ## Contract review record on the landing head is owed before enqueue.

  13. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20051,
    "status": "needs_decision",
    "branch": "claude/issue-20051-persist-parsed-view-body",
    "pr": null,
    "session": "session_018fxqvRJW12TaHC7DUQ89Y6 (mode:subagent, the PM's session; identity = the branch named by Claim 5909135345, verified as the newest Claim on the card)",
    "premise_still_valid": true,
    "summary": "Stopped before the storage change, as Zone 2 item 3 instructs: the PM premise that nothing the console writes at the pin is stripped or renamed by the parse is FALSIFIED. The card's own premise holds at origin/main 15b586d (unchanged for these files at 22e584c): saveMetaItem (packages/metadata-protocol/src/protocol.ts:16082) still persists request.item plus the two grafts, and ADR-0005 appendix (c) carries the 'Ruled, not yet in force' marker. Measured through the REAL saveMetaItem (stub engine) with the console's write bodies at objectui db11afd: the parse strips three keys the console writes (sort[].id, and a top-level id and objectName), and it ADDS schema defaults the console never wrote (type 'grid' on every column-less toolbar overlay). All three stripped keys are in stage (ii)'s documented 'mapped, not declared' set and every console reader falls back, so nothing was declared and no code was written (Q1). The added defaults collide with an ADR-0087 principle recorded for flows (Q2). The ViewItemWireSchema top-level options judgement is Q3. Other Zone 2 readings, for the implementation round: (item 2) pin population, by fixture parse, not by a flip: graft-folded-form-sections.test.ts:222 and :204 (isPinned/sortOrder on a FORM overlay: stripped; the form member does not declare them and VIEW_CONSOLE_ROUND_TRIP_KEYS maps no key to formOverlay), :238 (isPinned stripped plus section defaults added; this is the third GUARD), :189 (asserts the authored, un-defaulted sections), :449/:454/:503-list and the W2 it.each (patch gains type 'grid'); objectql protocol-meta.test.ts:586 (objectName stripped; isPinned/isDefault/sortOrder kept); graft-normalized-operators and runtime-authoring-gate named by 5904333057, not probed. (item 4) the view-only gate needs no design choice: saveMetaItem already branches on singularType (the flow branch), and resolveOverlaySchema validates every type getMetadataTypeSchema registers (metadata-type-schemas.ts:90-281), not only view and dashboard. (item 5) 'ADR-0005 §Validation' is cited at protocol.ts:367, :1339, :7339, :16147, :16596; the real heading is 'Addendum — 2026-05-16 (c): spec validation on overlay save'; an anchor file for protocol.ts already exists (scripts/adr-anchors/packages__metadata-protocol__src__protocol.ts.json). Once (iv) lands, two ADR-0005 sentences go stale (docs follow-up for the seat): the Status line's 'and the storage switch itself is the ruling's last stage, not yet landed — see the amendment note under that addendum's persisted-document bullet', and appendix (c)'s 'Ruled, not yet in force: the storage switch is the ruling's last stage and has not landed, so today saveMetaItem still stores the request body (request.item).' If Q2 is answered B, the bullet 'The persisted document is the parsed body (parsed.data)' needs rewording too.",
    "tests": "No product code changed, so no suite ran. (1) Build for the probe: os-verify-lock -c "pnpm --workspace-concurrency=2 --filter '@objectstack/objectql^...' build" → 'VERDICT command-exit 0 · held the lock 125s'. (2) Console-write probe (scratch, not committed): node probe-console-writes.mjs → exit 0. It drives 13 bodies through the real saveMetaItem of 15b586d and diffs the stored row (today: verbatim) against ViewMetadataSchema.safeParse(row).data. Readings: W1 toolbar sort overlay on a code-defined view, body {sort:[{id,field,order}],viewKind,object,name,_isOverride} → STRIPPED ['sort[0].id'], ADDED ['type=grid']. W2-W5 (rowHeight, hiddenFields, columnState, inlineEdit overlays) → STRIPPED [], ADDED ['type=grid'] each. W6 saved-view toolbar toggle with no registry baseline → REFUSED today, 422 INVALID_METADATA at viewKind (pre-existing, unchanged by this card). W6b the same body with the saved record hydrated into the registry (what an env-wide save does, protocol.ts:15528) → STRIPPED ['id','objectName','sort[0].id']. W7 Edit view config → Save with a toolbar-sorted tab → STRIPPED ['config.sort[0].id']. Negative controls, the instrument can answer 'none': W8 create (viewEnvelope) and W9a-d updateView pin / set-default / reorder / rename → STRIPPED [] CHANGED [] ADDED [] each. (3) Pin-fixture probe: node probe-pins.mjs → exit 0; readings quoted in the summary; a form overlay parse also adds sharing.enabled=false and sections[].collapsible/collapsed/columns. (4) Producers were TRANSCRIBED from objectui at db11afd with line citations, not executed (the objectui checkout is read-only here). The load-bearing lines were read directly: ListView.tsx:538/:544/:4013 mint a sort id per entry; ObjectView.tsx:2831/:3127 persist it through persistViewPatch; buildPersistedViewBody's saved-view branch (ObjectView.tsx:1081-1086) writes the whole tab back; the tab carries id and objectName (ObjectView.tsx:1826-1838, viewIdentity.ts viewEntry). objectui's own pin ObjectView.overlayPatchOnly.test.ts:389-404 asserts that whole-tab write. filter[].id is NOT written: viewFilterFold.ts strips it at the producer.",
    "gates": [
    "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: exit 2, 'this branch changes nothing against origin/main (merge base 15b586d) — nothing to derive'; no gate family owed, none run",
    "build (os-verify-lock, slot issue-20051-dev): VERDICT command-exit 0"
    ],
    "line_budget": "n/a",
    "deviations": [
    "Stopped with needs_decision before the storage change (Zone 2 item 3's own instruction for a falsified premise). No PR, no changeset, no anchor entry. The branch was pushed empty at 15b586d as the claim's landing marker and the write probe.",
    "Measured all the console's view writers at the pin, not only buildViewConfigSaveBody: persistViewPatch (both branches), updateView (pin, set default, reorder, rename), create. The stripped keys come from persistViewPatch; buildViewConfigSaveBody alone strips only config.sort[].id.",
    "Also measured what the parse ADDS, which the premise did not ask. It is Q2 because it decides what 'the parsed body' stores.",
    "The maintainer record 5909085121 reads 'objectName and the nine read-only keys live only in rows written earlier or by other writers'. For objectName and a top-level id that is falsified: the console's saved-view toolbar toggle writes both at the pin (W6b)."
    ],
    "files_changed": [],
    "mcp_calls": "0",
    "api_writes": "1: this os-dev-report, through scripts/pm/post-stamped.mjs on its resolved route, transport dispatch (the fleet-write relay): one POST /repos/objectstack-ai/objectstack/dispatches, executed by the relay as POST /repos//issues/20051/comments as objectstack-fleet[bot]. Plus git push x1 (the empty branch; not REST). 0 label writes, 0 pr_create. Reads: GET /repos//issues/20051 and its /comments (all 19; the body is complete and ends at its footer).",
    "open_questions": [
    {
    "question": "Q1. The console at the pin writes three keys the view parse strips, so storing the parse output drops them on the next save. (1) sort[].id: every toolbar or header sort mints one per entry and persists it, on a code-defined view's overlay (W1), on a saved view's row (W6b), and inside config.sort[] of 'Edit view config → Save' (W7). (2) A top-level id and (3) a top-level objectName: the saved-view toolbar toggle writes the whole tab back, and the tab carries both (W6b). All three are in stage (ii)'s 'mapped, not declared' set (VIEW_CONSOLE_ROUND_TRIP_KEYS docblock, view.zod.ts), and every console reader falls back: parseSortConfig ignores a stored sort id and toSortItems (view-config-utils.ts:319) mints one when absent; viewRowId (viewIdentity.ts:92) prefers name; viewItemObjectName (data-objectstack index.ts:2737) reads data.object, then object, then objectName, and both writers stamp object. How does stage (iv) proceed?",
    "options": [
    "A. Proceed as ruled. Persist the parse output for view. The three keys are dropped by the stage (ii) mapping and nothing is declared, so Clause-② stays no (narrowing). Residual: the console keeps sending them and the door drops them silently. That is by design for row decorations and top-level .strip().",
    "B. Producer first. An objectui card makes the console stop writing them: strip sort ids on emit, as viewFilterFold already does for filter ids, and have the saved-view toolbar branch write the row's declared keys, not the whole tab. Then a pin bump, then stage (iv).",
    "C. Declare the three on the wire members so the parse keeps them. This widens the wire, turns Clause-② to yes, and puts a React row key and a console alias on the contract; the dispatch forbids it here."
    ],
    "recommendation": "A. Real business need: measured by code read, no console reader needs a stored value for any of the three; each is regenerated or read through a fallback the same write satisfies. So no user-visible state is lost, unlike the pin/sort/visibility state the ruling protected and stage (ii) declared. Long-term soundness: A lands the ruled end state on the mapping stage (ii) already wrote into the spec; B is worthwhile producer hygiene but blocks nothing and can follow as an objectui card. Preventing AI errors: A makes storage equal the contract, and the authoring doors already refuse all three by name; C would teach an author to emit a React key. Startup focus: A adds no surface and no cross-repo step. B adds a sequencing dependency with no user-visible gain, and C adds three permanent declarations."
    },
    {
    "question": "Q2. Does 'the persisted document is the parsed body' include the schema DEFAULTS the parse materializes? Measured: the parse adds type 'grid' to every column-less toolbar overlay (W1-W5; the list overlay member re-applies the default after its checks, applyListOverlayTypeDefault, #20186). It adds sharing.enabled=false and sections[].collapsible/collapsed/columns to a form overlay (graft-folded fixtures :222, :238, :503). Stored today: none of these. ADR-0087 addendum 2026-08-01b records the opposite principle for flows: 'storable excludes schema defaults, and this is load-bearing … Persisting a default the author never wrote would pin every migrated row to today's value while untouched rows follow tomorrow's'. saveMetaItem's flow branch follows it ('Persists storable, never the parsed shape — schema defaults are deliberately excluded (ADR-0087)'). One concrete reader risk, NOT MEASURED in a browser: ObjectView narrows an overlay to its owned keys, so a stored type is ignored there (narrowPersonalizationOverlay, data-objectstack index.ts:2930). But InterfaceListPage spreads the un-narrowed stored document over a hollow resolved view ('...resolvedView, ...hydratedView', InterfaceListPage.tsx:392-395), so a stored type 'grid' could override a kanban source there.",
    "options": [
    "A. Store parsed.data whole. This is the literal reading of the amended appendix and the smallest code. Cost: every save pins today's defaults into the row (the two-population drift ADR-0087 names), and a toolbar patch then claims type 'grid' over a view that may be kanban.",
    "B. Store the parse output restricted to the keys the body carried: judged and normalized values, undeclared keys dropped, no default added. This is the view counterpart of flow's storable. Cost: one projection walk in saveMetaItem that subsumes the two grafts (the operator rewrite and the groups→sections key move are its two special cases), and a docs follow-up rewording the appendix's 'parsed.data'.",
    "C. Store parsed.data, but the list overlay member stops re-applying 'grid' to a body that names no type. This removes the patch case only; form defaults are still pinned."
    ],
    "recommendation": "B; fallback C. Real business need: nobody who sorts a kanban view said 'grid', and a stored default records a choice nobody made; the public-form route never reads sharing.enabled (rest-server.ts:10287 reads allowAnonymous and publicLink), so storing it is noise at best. Long-term soundness: ADR-0087 already rules the drift that A creates for flows, and B keeps views consistent with it. B still meets the ruled end state: nothing undeclared is stored, and every stored value is judged. Preventing AI errors: B stores what the author wrote, normalized, so an AI reading a row does not copy defaults forward as if authored. Startup focus: B is one walk replacing two special grafts; A is the least code but leaves a latent drift; C is the smallest change that removes the one reader risk found. Confidence gap: whether any reader depends on a materialized default being present is not measured."
    },
    {
    "question": "Q3. Ruling 甲 (iv) says the ViewItemWireSchema top-level options 'is judged in the same step'. Measured at the pin: no console writer puts a top-level options on a ViewItem record. buildViewConfigSaveBody keeps only the config keys ListViewSchema declares (options is not one) plus the five row-state keys; viewEnvelope nests the spec under config; mergeViewPatch spreads the stored row. One reader reaches it: InterfaceListPage spreads a hydrated stored document over a hollow resolved view and forwards view.options to the renderer (InterfaceListPage.tsx:392-395, :432, :494). The object page reads a record's config only (MetadataProvider.tsx:394 viewItemBody). How is it judged?",
    "options": [
    "A. Refuse it by name on both ViewItem wire arms, with the form overlay's pattern (options pinned absent with an error) and a prescription to move each options.KIND to config.KIND. This narrows and declares nothing new.",
    "B. Declare it on the list arm and judge it key by key with the overlay's ListViewOverlayOptionsSchema (the door half's route, the dispatch's suggested route), stored in the parsed body."
    ],
    "recommendation": "A. Real business need: no producer writes it, and its one reader renders it on one page of two, the split objectui#10380 exists to remove. Long-term soundness: one spelling per record (config.KIND); the flat overlay's legacy bag stays where objectui pins it. Preventing AI errors: a loud refusal that names the move beats a stored second spelling. Startup focus: A declares nothing new; B adds a declared legacy key to a second member. This sits in the dispatch's route zone, so the implementation round takes A unless told otherwise."
    }
    ],
    "out_of_scope_findings": [
    "carrier: stage (iv)'s implementation round (this card) · resolveOverlaySchema validates every type getMetadataTypeSchema registers, not only view and dashboard, and all of them persist request.item through the same path. The view-only gate is one singularType branch, which saveMetaItem already uses for flow, so dashboard and the rest stay verbatim with no design choice needed · evidence: packages/spec/src/kernel/metadata-type-schemas.ts:90-281 at 15b586d; protocol.ts:16519 · noted, not filed",
    "carrier: objectui#11013 (aligning the console's reads to the declared spellings) · the adapter's listViews flatten keeps only config plus name/label/isDefault of a ViewItem record. So a saved view's tab carries no isPinned/sortOrder/visibility/columnState, and the saved-view toolbar toggle, which writes the whole tab back, rewrites the row without them. This is a console-side drop, independent of this card and present today · evidence: data-objectstack/src/index.ts:5500-5508 at db11afd (same at objectui main 37d1662, :5512); probe W6b sent-body; code read only, NOT MEASURED in a browser · dedupe words: 'listViews flatten drops isPinned' · 'saved view toolbar toggle unpins' · 'whole tab written back row state' · noted, not filed"
    ]
    }

  14. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat review of the stage (iv) needs_decision report 5909437934 · domain:spec seat 1 (session_018fxqvRJW12TaHC7DUQ89Y6) · 2026-09-30T10:35Z

    The dev stopped before the storage change, as its dispatch told it to when the premise failed. No code was written, and the branch claude/issue-20051-persist-parsed-view-body was pushed empty at 15b586dc.

    Correction, owed publicly: my record 5909085121 was wrong on one point. It said objectName and a top-level id live "only in rows written earlier or by other writers". The dev measured otherwise. The console's saved-view toolbar toggle at the pin writes the whole tab back, and the tab carries both keys (report W6b: buildPersistedViewBody's saved-view branch, objectui ObjectView.tsx:1081-1086; the tab is built at :1826-1838 and in viewIdentity.ts). So my reading covered buildViewConfigSaveBody and missed persistViewPatch. The maintainer's instruction in that record stands as recorded. Only my derivation from it was incomplete.

    Q1 (three keys the console writes that the parse strips: sort[].id, a top-level id, objectName). The seat answers A; not escalated.

    • Governing text: ruling 甲's end state is "every key the console reads back is declared on the contract". Stage (ii) already classified all three as "mapped, not declared" (the VIEW_CONSOLE_ROUND_TRIP_KEYS docblock in packages/spec/src/ui/view.zod.ts).
    • The report's code reads show that no console reader needs a stored value for any of the three. toSortItems mints a sort id when one is absent. viewRowId prefers name. viewItemObjectName reads object first, and both writers stamp object.
    • So dropping them loses no state the console reads back. This is the ruling applied, not a new choice. Clause-② stays no: nothing is declared.
    • Producer hygiene (the console no longer emitting the three) is optional and blocks nothing. It is left to the objectui lane; no card is filed from here.

    Q3 (ViewItemWireSchema's top-level options). The seat answers A: refuse it by name on both ViewItem wire arms, with a prescription to move each options.KIND to config.KIND. Not escalated.

    • No producer writes it at the pin. Its one reader renders it on one page of two, and objectui#10380 exists to remove that split.
    • The framework's own order decides this. The contract tightens with a loud refusal, not a second stored spelling, and a declared legacy key would be a permanent obligation. This is the same narrowing direction ruling 甲 set. The flattened overlay's legacy options bag, which the door half kept and objectui pins, is untouched.

    Q2 (whether the stored "parsed body" includes the schema defaults the parse adds) is escalated. Two governing texts disagree, and a seat does not choose between them:

    • ADR-0005 appendix (c), as amended on 2026-09-30, says the persisted document is "the parsed body (parsed.data)".
    • ADR-0087 addendum 2026-08-01b says "storable excludes schema defaults, and this is load-bearing". saveMetaItem's flow branch follows it (protocol.ts:16514).
      The decision request follows in the next comment.

    Carried to the implementation round (from the report, measured): the view-only gate needs no design choice (saveMetaItem already branches on singularType for flow); the GUARD pin population by fixture parse (graft-folded-form-sections.test.ts:204/:222/:238/:189, the :449/:454/:503 list and W2 cases, protocol-meta.test.ts:586); "ADR-0005 §Validation" is cited at protocol.ts:367, :1339, :7339, :16147, :16596, while the real heading is "Addendum — 2026-05-16 (c)"; the two ADR-0005 sentences that go stale on landing.

    Release: session_018fxqvRJW12TaHC7DUQ89Y6 (claim 5909135345) · cause: Q2 needs the maintainer · destination: needs-user-decision. The assignee is cleared in this act, and whoever executes the answer claims afresh. The empty branch stays for that claim.

  15. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    os-decision-facets

    决策请求:保存视图时,系统校验会补上作者没写的默认值;这些默认值要不要一起存进库? · 2026-09-30T10:36Z

    domain:spec seat 1(session_018fxqvRJW12TaHC7DUQ89Y6)。背景:#20051 第 (iv) 阶段要把「保存的视图 = 校验后的内容」真正落到存储上。dev 在动手前实测(报告 5909437934)发现:校验除了剔除未声明的键,还会给视图补上作者没写过的默认值。Q1、Q3 本席已按既有裁决答复(5909486308),只有本题上交。本卡同时转入 needs-user-decision。

    一句话问题

    用户在一个看板视图上只改了排序,保存后库里会不会多出一句「这是表格视图」?

    Governing text

    • ADR-0005 附录 (c)(2026-09-30 修订,第 337-339 行):「The persisted document is the parsed body (parsed.data) once every round-trip key is declared」。
    • ADR-0087 补遗 2026-08-01b(第 517-525 行,针对流程):「storable excludes schema defaults, and this is load-bearing … Persisting a default the author never wrote would pin every migrated row to today's value while untouched rows follow tomorrow's — two populations with different behaviour」。流程的保存路径就是这么做的(packages/metadata-protocol/src/protocol.ts:16514)。
    • 裁决 甲 5856781584:「a saved view is the parsed body」。
    • 协议声明:A 照 ADR-0005 字面执行;B、C 都需要改写 ADR-0005 附录 (c) 里的 parsed.data 一词(受管文档,单独一张 Tier H PR)。

    前提(每条带复查方法)

    1. 校验会补默认值。 dev 用真实的 saveMetaItem 实测(W1–W5):每次只改排序、行高、隐藏列、列状态、行内编辑的工具栏保存,校验结果都多出 type: 'grid'。表单覆盖层会多出 sharing.enabled=false 和各分区的 collapsible / collapsed / columns。今天库里一个都不存,因为今天存的还是原始请求体。复查:读报告 5909437934 的 tests 段;代码位置 applyListOverlayTypeDefault(packages/spec/src/ui/view.zod.ts)。
    2. 有一处读者可能被默认值误导(只读代码,未在浏览器实测)。 对象页只取覆盖层自己的键,不受影响;但接口页把库里整份文档铺在解析出的视图上(objectui InterfaceListPage.tsx:392-395),库里的 type: 'grid' 可能把一个看板显示成表格。
    3. 现有存量不在范围内。 你的原话:「20051 不考虑现有的数据」(5909085121)。

    选项与代价

    选项 做什么 客户看到的后果
    A 校验结果整份入库,默认值一起存 代码最少。每次保存都把当天的默认值钉进这一行;以后默认值改了,存过的行不跟着变,没存过的行跟着变。只改排序的看板在接口页可能变成表格(未实测)
    B 只存作者写过的键:取校验、规范化之后的值,丢掉未声明的键,不补默认值 库里就是作者写的内容(规范化后);默认值永远跟着当前版本走,与流程同一条原则。要加一步「按请求体里出现过的键取值」,它顺带替掉现在两处特殊处理;ADR-0005 附录的措辞要改
    C 校验结果整份入库,但列表覆盖层不再给没写类型的补丁补 grid 只消除「看板变表格」这一处风险;表单的默认值仍会被钉进库

    业务直译:A = 存档时顺手把出厂设置也抄进去;B = 只存用户改过的,出厂设置随系统走;C = A,但把最显眼的那个坑先填上。

    四轴(业务立场)

    • 长远合理性: 两年后:库里存的视图就是作者写的、经过校验的内容,默认值由当时的平台版本决定。这与流程已经在执行的原则(ADR-0087)一致,视图和流程是同一个规则,没有第二套。A 会在库里形成两类行为不同的视图(存过的钉死旧默认,没存过的跟新默认),正是 ADR-0087 要消除的漂移。
    • 实际业务拉动: 触发面很大:每一次工具栏排序、隐藏列、调行高的保存都会触发(W1–W5 实测)。今天没人撞上,只因为今天还存原始请求体;第 (iv) 阶段一落地就会出现。
    • 防 AI 犯错: 出错时谁看到什么。A 下,AI 读库里的一行会把补出来的默认值当成作者的选择照抄,而且没有任何报错,是静默的。B 下库里只有作者真写过的东西。
    • 创业阶段不扩散: B 是一步投影,替掉现有两处特殊嫁接(操作符改写、groups→sections),不新增任何声明;A 代码最少但留下漂移;C 改动最小。

    推荐

    B;回退 C。 只看①选 B;②③④ 是否翻转:否。
    置信缺口:有没有哪个读者依赖默认值被物化进库,没有测量;接口页「看板变表格」只读了代码,没有在浏览器里复现。

    裁后执行(你只裁方向)

    四棱

    ① 项目长远合理性:B 让视图与流程共用「存储不含默认值」一条原则,特例最少;A 在库里造出两类行为不同的视图。
    ② 实际业务拉动:每一次工具栏保存都会触发(实测 5 种);今天没暴露,只因为还存原始请求体。
    ③ 防 AI 犯错:B 存的就是作者写的;A 把没人做过的选择静默记成「已选」。
    ④ 创业阶段不扩散:B 一步投影替掉两处嫁接,不新增声明;C 最小;A 最省代码但留漂移。
    Prior rulings read: parsed body, storable, schema defaults, parsed.data → 3 hits(ADR-0005 附录 (c) 修订版、ADR-0087 补遗 2026-08-01b、本卡裁决 甲 5856781584);ADR-0005 (c), ADR-0087 2026-08-01b;thread: 本卡 21 条评论。
    推荐:B。只看①选 B;②③④ 是否翻转:否。置信缺口:见上。

  16. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #256 item 3 · Q2 letter B · maintainer 「同意 批次 #256」 2026-09-30T11:19Z

    Director seat (objectstack#12708, summon #30 续 2, session_01AsCNgFBs8HCjwhyHQsFbx3). Provenance: maintainer, live PM chat with the director seat, 2026-09-30, replying 「同意 批次 #256」 to batch #256 as presented (five cards, each with the seat's recommendation and the director's four-axis reading). Presented on this card: the domain:spec seat 1's decision request 5909498682 (Q2, on the dev's measurement 5909437934) with its own answers to Q1 (A) and Q3 (A) in 5909486308, and the director's reading (B, fallback C).

    Ruled: Q2 = B — a saved view stores the keys the author wrote, normalised by the parse: undeclared keys dropped, schema defaults NOT materialised. Views and flows share ADR-0087's rule that storable excludes schema defaults; a stored row never pins the day's default. Q1 = A and Q3 = A stand as the seat answered them (the three console-written keys the parse strips are dropped; a top-level options on the ViewItem wire is refused by name with the prescription to write config.KIND).

    Execution parameters, as presented: the seat re-claims and implements stage (iv) under B — one projection, "the parsed value of every key present in the request body", which replaces the two existing grafts (the operator rewrite and groups→sections); the empty branch claude/issue-20051-persist-parsed-view-body may be reused. Pins: the five toolbar saves (W1–W5) store no type: 'grid'; the form overlay stores no sharing.enabled and no section defaults the author did not write; an undeclared key is dropped; a declared key keeps its normalised value. ADR-0005 appendix (c)'s parsed.data wording and its two "not yet in force" markers change in a separate Tier H documentation PR for the maintainer's hand. The stored-row census stays waived (「20051 不考虑现有的数据」, 5909085121).

    Readings that decided it: ① one rule for views and flows — A creates two populations of stored views, the drift ADR-0087 exists to prevent; ② every toolbar save triggers it; ③ B stores what the author wrote, A records unmade choices as made; ④ B is one projection replacing two grafts. Confidence gaps recorded: no reader measured to depend on materialised defaults; the interface page's "kanban shown as a table" was read from code only.

    State: needs-user-decision → pm:queue (the domain:spec seat 1 re-claims); the Ruled line goes on the body beneath 甲's.

  17. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (stage (iv) under ruling B; re-claim after 5909486308's release) · 2026-09-30T11:28Z
    Session: session_018fxqvRJW12TaHC7DUQ89Y6
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-20051-persist-parsed-view-body (reused, as the ruling allows; empty at 15b586dc)
    Worktree: objectstack-issue-20051
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface: the view branch of saveMetaItem in packages/metadata-protocol/src/protocol.ts (one projection replacing the operator-rewrite and groups→sections grafts); ViewItemWireSchema top-level options refused by name in packages/spec/src/ui/view.zod.ts; the verbatim-save pins and the W1–W5 / form-default pins (metadata-protocol/src/protocol.graft-*.test.ts, protocol.runtime-authoring-gate.test.ts, packages/objectql/src/protocol-meta.test.ts); the "ADR-0005 §Validation" citations in protocol.ts re-anchored, plus the existing scripts/adr-anchors/packages__metadata-protocol__src__protocol.ts.json; one .changeset/20051-*.md (minor, BREAKING, ADR-0087 disposition). ⛔ No docs/adr/** edit (a separate Tier H docs PR). Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus (as claim 5909135345; ruling implementation with design judgment ⇒ default tier). The same dev resumes, holding its measurements.
    Clause-②: no
    Thread-read: 5910106571
    Ruling-ref: 5856781584 (甲); 5909085121 (the maintainer's 「20051 不考虑现有的数据」); 5910106571 (Q2 = B; Q1 = A, Q3 = A)
    Serial constraints cleared: at origin/main 1741c5dcb, no open PR touches metadata-protocol/src/protocol.ts, ui/view.zod.ts, the protocol.graft-* tests, protocol-meta.test.ts or scripts/adr-anchors/. PR #20823 touches two other metadata-protocol test files. The cross-lane note to the engine seat stands (5909185560).

    The ADR-0005 appendix (c) wording (parsed.data) and its two "not yet in force" markers change in a separate Tier H docs PR, which the seat files once this lands.

  18. added a commit that references this issue on Sep 30, 2026
  19. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20051,
    "status": "done",
    "branch": "claude/issue-20051-persist-parsed-view-body",
    "pr": "#20868",
    "session": "session_018fxqvRJW12TaHC7DUQ89Y6 (mode:subagent, the PM's session; identity = the branch named by re-claim 5910252155, verified as the newest Claim on the card)",
    "premise_still_valid": true,
    "summary": "Stage (iv) implemented under ruling 5910106571 (Q2 = B, Q1 = A, Q3 = A), draft PR #20868 at head 4191327, first line Fixes #20051. For view only, saveMetaItem now stores projectStorableViewBody: the parsed value of every key the body carried. Undeclared keys are dropped and schema defaults are not materialised. Moved keys are found by re-parse evidence, not a list: a key the parse added is kept only when a re-parse of the body without it does not reproduce it. The loop ends when the stored body re-parses to exactly the save's parse. If it does not converge, the fallback stores parsed.data, logs a once-per-view warn, and loses no key. A ViewItem record's top-level options is pinned absent on both wire arms (z.never, so ViewItemWire.json still publishes) and answers 422 with the config.KIND prescription; the ADR-0087 disposition is registered view-item-options-bag-refused. Zone 2 readings: (1) the gate is singularType === 'view', as for flow, and a page save is pinned verbatim as the control. (2) The projection subsumes both grafts for views; the grafts are kept for other types, because ViewFilterRuleSchema is reused by page, component and skill, and FormViewSchema by component, sharing and field. (3) Nested defaults are not stored: W1-W5 store no type, forms store no sharing.enabled or section defaults, and a visibleOn nested inside groups is stored as sections[].visibleWhen. (4) All five protocol.ts citations, plus the one in view.zod.ts, re-anchored to 'ADR-0005 appendix (c), the Addendum — 2026-05-16 (c)'; the anchor JSON gains ADR-0005 and ADR-0087. Docs PR (Tier H, the seat's) must change three ADR-0005 passages: (a) in the Status line, 'and the storage switch itself is the ruling's last stage, not yet landed — see the amendment note under that addendum's persisted-document bullet'; (b) in appendix (c), '⚠️ Ruled, not yet in force: the storage switch is the ruling's last stage and has not landed, so today saveMetaItem still stores the request body (request.item).'; (c) appendix (c)'s bullet 'The persisted document is the parsed body (parsed.data) once every round-trip key is declared', which letter B turns into 'the parsed value of every key the body carried, no default materialised'. One measured fact decides against letter A: parsed.data of a toolbar patch carries type: 'grid' and no columns, so a stored patch would fail its own re-save (ablation 2).",
    "tests": "At head 4191327, all under os-verify-lock. metadata-protocol full: 191 files, 2819 passed, 19 skipped. objectql full: 345 files, 6778 passed. rest full: 237 files, 4641 passed, 99 skipped. spec --project local src/ui + src/migrations: 97 files, 3560 passed; the whole local project also ran before the origin/main merge (192 + 578 files, green after the population pin was re-judged). runtime, the 19 files that save or read a view: 990 passed. Typecheck, exit 0 for each of spec (tsc + check:scripts-typecheck + check:test-typecheck), metadata-protocol, objectql and lint. Before the re-judge, the full sweep found exactly 7 red pins, all re-judged in one round: 3 in metadata-protocol graft-folded, 3 in objectql (protocol-meta:586, protocol-publish-rollback diff, protocol-view-identity-overlay sort id) and 1 in spec (view-union-retirement-prescription §2). Ablation 1 (committed first; the view branch renamed to 'view__ablated' and the record refusal renamed to '_ablated_options', through scripts/ablation-replace.mjs with a trap restore; spec and metadata-protocol dist rebuilt; ablation-dist-preflight markers present in 10 and 2 built files): graft-folded file 9 red, 40 green; objectql three files 3 red, 114 green; spec two files 4 red, 37 green (one red is the §2 retirement text reading the renamed leaf, an artifact of the mutation shape). Restore: 'blob == HEAD' for both files, markers absent from both dist/ after rebuild, and git status --porcelain empty. Ablation 2 (request.item = parsed.data, src only): graft-folded file 20 red, 29 green, with all five toolbar pins, the form-default pin and the parse-equivalence pin among the reds; restored to the HEAD blob. Pre-change measurement of the console's writes at the pin (probe through the real save, stub engine) is on the card, report 5909437934.",
    "gates": [
    "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 4191327: 125 derived; each run with its exit code captured before any pipe",
    "123 exit 0. Two are re-runs after a prerequisite: node scripts/check-engine-split-ratio.mjs --days 90 (first exit 2, shallow-clone refusal; exit 0 after git fetch --shallow-since=2026-06-25) and pnpm --filter @objectstack/spec run check:skill-examples (first exit 3, client-react dist stale; exit 0 after building client-react)",
    "NOT MEASURED: pnpm check:dual-build-cjs-loads (exit 3, PREREQUISITE NOT MET: whole-repo dist) and pnpm check:type-check-debt (exit 3, PREREQUISITE NOT MET: whole-repo build). Both are declared to CI",
    "dispatch-gates --ran: '125 derived famil(ies) accounted for — 123 run, 2 NOT-MEASURED', 0 UNRUN",
    "pnpm --filter @objectstack/spec check:generated: EXIT 0, 'All 15 generated artifacts are up to date' (content/docs/references/ui/view.mdx regenerated by --fix before that)",
    "node scripts/check-adr-0087-registration.mjs --base 1741c5d: EXIT 0, 'registered view-item-options-bag-refused (new here)'",
    "eslint, narrowed to the 15 changed .ts files: each resolves under --print-config; --no-inline-config --format json gives 15 files, 0 errors, 0 warnings; eslint.config.mjs enables no type-aware linting (its comment at :328), so no untouched file's verdict can move"
    ],
    "line_budget": "n/a",
    "deviations": [
    "Clause-② spelling conflict, named rather than chosen silently. The PR body carries Clause-②: no, copied from the claim as the dispatch's card clause says. The changeset carries Clause-②: no (narrowing), which is how ruling 甲 item 4 (restated in this dispatch's Zone 1) spells the changeset. check-adr-0087-registration reads the changeset as BREAKING+bang+clause-②-narrowing.",
    "The projection finds moved keys by re-parse evidence (one extra parse per save, more only when a key moved), not by a named key-move list. It is one projection that replaces both grafts for views, and it also covers visibleOn → visibleWhen, which neither graft did.",
    "The ViewItem wire's options uses z.never(), not the form overlay's z.undefined(): with z.undefined() the spec build refused because ViewItemWire.json disappeared. As a result, view-union-retirement-prescription.test.ts §2 (every non-catchall never leaf says 'was removed') was re-judged to admit a closed two-entry pinned-absent family, each entry reached and each matching its curated text.",
    "File surface beyond the claim line. (1) packages/spec/src/migrations/entries/semantic/18.view-item-options-bag-refused.ts plus the regenerated registry.ts region: the ADR-0087 registration for the Q3 narrowing, following the door-half precedent. (2) content/docs/references/ui/view.mdx, regenerated. (3) Spec tests: view-overlay-options-bag (the record pins), view-union-retirement-prescription (the re-judge) and the view-console-round-trip-keys header. (4) objectql protocol-publish-rollback and protocol-view-identity-overlay, pins the sweep found. (5) Comment-only fixes in metadata-protocol read-decorations, graft-normalized-operators and runtime-authoring-gate tests and in lint runtime-gate.view-writes.test.ts, where the text stated a verbatim view save.",
    "A JS-only closure build (OS_SKIP_DTS=1) removed the .d.ts files from already-built packages. Every package was rebuilt with declarations before any typecheck ran, and nothing was committed from that state.",
    "One single-file spec vitest run (view-overlay-options-bag.test.ts, 25 passed) ran outside os-verify-lock. It was re-run under the lock.",
    "Merged origin/main 660a9b2 with scripts/pm/os-regen-merge.sh: merge commit 4191327, no conflicts, no artifact regeneration needed (check:generated green). The shared .git was deepened with fetch --shallow-since=2026-06-25 for the engine-split gate.",
    "packages/qa/dogfood: 0 hits for a view save or read (grep of test/ and src/), so no reading is owed there."
    ],
    "files_changed": [
    ".changeset/20051-persist-parsed-view-body.md",
    "content/docs/references/ui/view.mdx",
    "packages/lint/src/runtime-gate.view-writes.test.ts",
    "packages/metadata-protocol/src/protocol.graft-folded-form-sections.test.ts",
    "packages/metadata-protocol/src/protocol.graft-normalized-operators.test.ts",
    "packages/metadata-protocol/src/protocol.read-decorations.test.ts",
    "packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts",
    "packages/metadata-protocol/src/protocol.ts",
    "packages/objectql/src/protocol-meta.test.ts",
    "packages/objectql/src/protocol-publish-rollback.test.ts",
    "packages/objectql/src/protocol-view-identity-overlay.test.ts",
    "packages/spec/src/migrations/entries/semantic/18.view-item-options-bag-refused.ts",
    "packages/spec/src/migrations/registry.ts",
    "packages/spec/src/ui/view-console-round-trip-keys.test.ts",
    "packages/spec/src/ui/view-overlay-options-bag.test.ts",
    "packages/spec/src/ui/view-union-retirement-prescription.test.ts",
    "packages/spec/src/ui/view.zod.ts",
    "scripts/adr-anchors/packages__metadata-protocol__src__protocol.ts.json"
    ],
    "mcp_calls": "0",
    "api_writes": "3 REST writes, all through scripts/pm on the fleet-write relay (transport dispatch). Each is one POST /repos/objectstack-ai/objectstack/dispatches, which the relay executes as objectstack-fleet[bot]: (1) pr_create, executed as POST /repos/objectstack-ai/objectstack/pulls, gave PR #20868 (draft; 9923 bytes sent, 9923 stored, identical); (2) label-write --assign os-warren, executed as POST /repos//issues/20868/assignees, read back as MATCHES; (3) this os-dev-report, executed as POST /repos//issues/20051/comments. git push x11 to the branch (not REST). Reads: the card's comments, PR #20868.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · the projection is view-only by ruling, and every other type getMetadataTypeSchema registers (dashboard, page, app, report and the rest, not only view and dashboard) still stores its request body with the two grafts. Widening is its own decision · evidence: packages/spec/src/kernel/metadata-type-schemas.ts:90-281; the page control pin in protocol.graft-folded-form-sections.test.ts · noted in PR #20868 Acceptance notes, not filed",
    "carrier: objectui#11013 · objectui's adapter listViews flatten keeps only config plus name/label/isDefault of a ViewItem record, so the saved-view toolbar toggle (which writes the whole tab back) rewrites the row without isPinned/sortOrder/visibility/columnState. The drop is present today and independent of this PR · evidence: data-objectstack/src/index.ts:5500-5508 at db11afd; code read only, NOT MEASURED in a browser · noted in PR #20868 Acceptance notes, not filed",
    "carrier: the next change to those files · 'ADR-0005 §Validation' (a heading the ADR lacks) is still cited outside this PR's files: packages/metadata-core/src/injected-system-columns.ts:132 and the headers of objectql meta-object-primary-designation-roundtrip, meta-object-search-companion-roundtrip and meta-object-tenant-index-roundtrip tests · noted in PR #20868 Acceptance notes, not filed"
    ]
    }

  20. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20051,
    "status": "done",
    "branch": "claude/issue-20051-persist-parsed-view-body",
    "pr": "#20868",
    "session": "session_018fxqvRJW12TaHC7DUQ89Y6 (mode:subagent; patch round on re-claim 5910252155, which is still the newest Claim)",
    "premise_still_valid": true,
    "summary": "Delta report: tests only, and no pin revealed a production defect. New head 9924c95 (previous 4191327). Correction to the patch premise: at 4191327 a test file DID reference projectStorableViewBody — protocol.graft-folded-form-sections.test.ts:731 held a structural block that covered both fallback arms. That block moved into a dedicated file beside protocol.ts, protocol.project-storable-view-body.test.ts, and was strengthened into (a) through (d): (a) a re-parse that refuses on round one stores parsed, not authored, with one call; (b1) a re-parse that never reproduces the parse and makes no progress stops, converged false, with no throw; (b2) a re-parse that reveals one more needed key per round stops at the round bound (at most 8 calls against the 21 rounds convergence would need), converged false, body = parsed; (c) a default the re-parse reproduces is not stored, with one call; (d) a moved key is stored canonical, and a moved container is projected against its origin, so its nested default is not stored. The once-per-view warn dedupe IS pinned, with no test-only hook. The seam is the production plugin API registerMetadataTypeSchema('view', …), set to a non-idempotent stand-in schema with the built-in re-registered in finally. Two saves of one view warn once, a second view warns on its own, and the row is the whole parse output. No merge was taken. GitHub reads PR #20868 mergeable=true, and a driver-free git merge-file of registry.ts, protocol.ts and protocol.runtime-authoring-gate.test.ts against origin/main 72f8c38 is clean. The text-merged registry.ts is byte-equal (cmp) to a regeneration on origin/main plus this branch's entry.",
    "tests": "At head 9924c95, under os-verify-lock. protocol.project-storable-view-body.test.ts: 7 passed. protocol.graft-folded-form-sections.test.ts: 46 passed (49, minus 4 moved out, plus 1 new). metadata-protocol full: 192 files, 2823 passed, 19 skipped. metadata-protocol typecheck (tsc --noEmit): exit 0. Ablation for (a), committed first: scripts/ablation-replace.mjs turned return { body: parsed, converged: false }; into return { body: authored, converged: false }; (grep-c injected=1, original=0), under a trap restore. Result: 4 red of 53 — (a), both (b)s, and the save-path warn pin. Restore: 'blob == HEAD (e2c307e8c6dd) and git diff HEAD is empty', and git status --porcelain empty.",
    "gates": [
    "dispatch-gates --commands --repo objectstack-ai/objectstack over the two touched files: 53 derived, 51 exit 0, 2 NOT MEASURED (pnpm check:dual-build-cjs-loads and pnpm check:type-check-debt, exit 3 PREREQUISITE NOT MET, whole-repo build), 0 UNRUN (--ran reconciliation). pnpm check:lean-entry-closure first answered exit 3 because the fresh worktree had no objectql dist; after building objectql it answered exit 0",
    "eslint --no-inline-config --format json on the 2 touched files: 2 files, 0 errors, 0 warnings (no type-aware linting in eslint.config.mjs)"
    ],
    "line_budget": "n/a",
    "deviations": [
    "The patch premise ('no test file references projectStorableViewBody') is falsified, as stated in the summary. The existing block was moved and strengthened rather than duplicated.",
    "The warn-dedupe pin lives in protocol.graft-folded-form-sections.test.ts, not the new file, so that it rides that file's already-pinned engine double instead of minting a new one (check:engine-double-contract exit 0)."
    ],
    "files_changed": [
    "packages/metadata-protocol/src/protocol.project-storable-view-body.test.ts",
    "packages/metadata-protocol/src/protocol.graft-folded-form-sections.test.ts"
    ],
    "mcp_calls": "0",
    "api_writes": "1: this delta os-dev-report, through scripts/pm/post-stamped.mjs (fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches, executed as POST /repos//issues/20051/comments). git push x1 (not REST). The PR body is unedited. Reads: PR #20868 mergeability.",
    "open_questions": [],
    "out_of_scope_findings": []
    }

  21. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #20868 at head 9924c95b (stage (iv), ruling 甲 under Q2 = B / Q1 = A / Q3 = A) · domain:spec seat 1 (session_018fxqvRJW12TaHC7DUQ89Y6) · 2026-09-30T14:22Z

    Reviewed against GitHub, not the reports (5912512351, delta 5912995677).

    • PR shape: draft, base main. The first line is Fixes #20051, then Clause-②: no, and there is no other closing keyword. The PR assignee is os-warren. check-governed-merges --pr 20868: NOT governed, 949 lines at the first head. The patch round adds 2 test files (+222/−32).
    • What lands:
      • For view only, saveMetaItem stores projectStorableViewBody: the parsed value of every key the body carried, with undeclared keys dropped and defaults not materialised. It replaces both grafts for views; every other type keeps its body and the grafts.
      • A ViewItem record's top-level options is refused by name with the config.KIND prescription, registered as the ADR-0087 entry view-item-options-bag-refused.
      • Changeset: minor × 2, BREAKING, FROM → TO.
    • Contract review: at-tier record 5913199665 on the PR, Head-sha = the current head, PASS, Local-runs: none, identity pair present. Its two non-blocking notes (a same-shape moved-container corner, and the fail-safe arm's re-save prescription) are unreachable at the pin and carried to the next change of projectStorableViewBody.
    • CI at this head: 33 success, 2 rostered skips (mergeable_state: clean).
    • Correction, owed publicly. My patch round said "no test file references projectStorableViewBody". That was wrong. protocol.graft-folded-form-sections.test.ts:731 already covered both fallback arms. My grep had found it, but I read a list truncated to 12 lines and took the missing tail for a zero. The dev moved that block into protocol.project-storable-view-body.test.ts and strengthened it. The fallback ablation now turns 4 of 53 tests red, and the once-per-view warn is pinned through registerMetadataTypeSchema, with no test-only hook.
    • Out of scope (named carriers):
      • Other types stay verbatim: the ruling names views.
      • The objectui listViews flatten dropping row state: objectui#11013.
      • The stale "ADR-0005 §Validation" citations in files this PR does not touch: carrier is the next change to those files.
    • After MERGED: the ADR-0005 Tier H docs PR (the Status line, the "Ruled, not yet in force" marker, and the parsed.data bullet, per ruling 5910106571) is filed by this seat for the maintainer's hand.
  22. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · stage (iv), the last stage of ruling 甲 · PR #20868 MERGED through the merge queue as 9905e61ca2 (single parent) · domain:spec seat 1 (session_018fxqvRJW12TaHC7DUQ89Y6) · 2026-09-30T14:55Z

    • Verified by content on origin/main:
      • export function projectStorableViewBody (1 hit) and the if (singularType === 'view') save branch (1 hit) are in packages/metadata-protocol/src/protocol.ts, and the old "original payload is stored verbatim" comment has 0 hits.
      • The VIEW_ITEM_OPTIONS_REFUSED refusal is in packages/spec/src/ui/view.zod.ts, and the ADR-0087 entry view-item-options-bag-refused is in migrations/registry.ts.
      • protocol.project-storable-view-body.test.ts is present.
    • Rulings executed: 甲 5856781584 (the end state); the maintainer's 「20051 不考虑现有的数据」 5909085121 (no stored-row census, no migration of existing rows); Q2 = B, Q1 = A, Q3 = A 5910106571.
    • Records: ACCEPT 5913223729; at-tier contract review 5913199665 (PASS, head 9924c95b); dev reports 5912512351 and 5912995677.
    • Closing-keyword audit: the PR body closed only this card. The queue merge did not auto-close it, so the seat closes it now. pm:dispatched and the assignee were removed in this act.
    • Follow-up owed by ruling 5910106571: the ADR-0005 Tier H documentation change (the Status line, the "Ruled, not yet in force" marker, and the parsed.data bullet). It is filed as its own card for the governed-surface lane and the maintainer's hand; the link follows on this card.
  23. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer · domain:spec seat 1 (session_018fxqvRJW12TaHC7DUQ89Y6) · 2026-09-30T14:58Z: the ADR-0005 Tier H documentation follow-up that ruling 5910106571 requires is filed as #20879 (bare, for triage; the acting reader is the governed docs/adr/** seat, landing at Tier H by the maintainer's hand).

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