Repository navigation
feat(metadata-core,metadata-protocol,objectql,plugin-security): the sys_metadata family goes tenant-less; the per-organization overlay axis retires; managed content is sealed (ADR-0131 D6/D7/D13) #15206
Description
Activity
- addedpriority:p1High: required for production / M2High: required for production / M2
on Sep 4, 2026 - added a commit that references this issue
on Sep 5, 2026 - added a commit that references this issue
on Sep 9, 2026 objectstack-fleet commented
on Oct 6, 2026 ContributorMore actionsv18 pre-opening re-verification (C5): DRIFTED badly. It is now XL and needs three maintainer rulings before it can be cut again
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-06T14:39Z. ⛔ Not a claim, ⛔ not a dispatch. Read-only re-verification on objectstackmain6befe19c6e. The cut base was 2026-09-04, 3,298 commits earlier. The maintainer asked for this pre-opening preparation in the triage seat's chat: 「现在就可以处理吧」. Classes, positions and functions only. The claiming seat applies these corrections before building. The body is not rewritten.Corrections:
- The four
sys_metadatatables do not "declaresystemFields.tenant: false".- Each declares its own
organization_id:metadata-core/src/objects/sys-metadata.object.ts:132,sys-metadata-audit.object.ts:113,sys-metadata-commit.object.ts:129,sys-metadata-history.object.ts:148. - Indexes key on it:
sys-metadata:226-231;history:183-188, withevent_seqper-organization;commit:146-148; and the runtimeOVERLAY_INDEX_COLUMNS(overlay-index.ts:137). - So the fields, the indexes and the runtime index all need re-keying.
- The physical column drop is C7's (D10: drops are the manual ceremony, last). It is not this card's.
- Each declares its own
- The
sys_view_definitionpositive control ("only the named files reference it") was already false at the cut, and still is: 35 files then, 37 now.- The non-test mentions are comments only, so "no reader or writer of its rows" holds.
- The "CLI migration allowlist entry" does not exist as a list entry. No ADR-0087 entry exists yet.
- Already done: the ADR-0005 and ADR-0017 amendment notes landed with the ADR merge.
- The managed-flow acceptance contradicts D6 as amended (Regime C). Flow disable has shipped (
flow-activation-store.ts,domains/activation-gate.ts). The permission-set clone has no linkage to refuse; its organization-owned copy is C3's.
New surface since the cut, which this card's retirement now has to remove (about 31 commits;
protocol.tsgrew from 21,613 to 27,853 lines, andorganizationIdForMetaReadcalls from 14 to 30):- the org-first served-row path (
servedOverlayRowCandidates:1994,mergePackageAwareOverlay:2122,findServedOverlayRow:9912); - the anonymous form intake's org-layer refusals (
:16163,:16232;metadata-core/src/anonymous-form-intake.ts). This is the 17.7 security layering; item-lock.ts:350resolveOverlayLockLayer;- the org gate on layered reads;
- the org fold in cached ETags;
- org overlays outranking packaged translations;
rest/src/meta-item-read-gate.ts([finding] class closure: six more places the runtime dispatcher's/metareads answer differently fromRestServer's, measured by #20320's census (unknown type,?preview=DRAFT, item translation and doc locale, the book tree, object?preview=draft) #20408).
Needs the maintainer before it is cut again:
- Split
allowOrgOverride. The same flag also decides whether an environment overlay of a packaged item is allowed (isOverlayAllowed:15886→refusePackagedBaseOverride:17067/refusePackagedBaseRemoval:17176). Turning off the five flags would also close the environment overlays D6 keeps. - What becomes of the shipped 17.x org-layer public-form withdrawal semantics (a security behaviour).
- The
singleDefault-Organization rows. fix(plugin-email): a metadata-door email template edit survives the next boot #21818 measured that undersingleevery Studio save of a view, dashboard, report, translation or email template is stored org-scoped, and new code relies on it (the email bootstrap, the anonymous form doors). "singleobserves no change" is false. Their migration to environment scope can collide by name: multi-organizationsingledeployments are reported at boot, not refused.
Generated by Claude Code
- The four
objectstack-fleet commented
on Oct 6, 2026 ContributorMore actionsRuling pointers: batch #282 items 3 and 4 (decision cards #22008 and #22011) · both A · maintainer 「同意」 2026-10-06T16:01Z
Director seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(via the relay). Records: 6020151485 on #22008 and 6020163868 on #22011, both closed. This card stayspm:blockedon #15193 and #15195. Thread-read: none newer than the body's blocked notice.- decision: ADR-0131 C5 — under
single, Studio saves are stored organization-scoped today. At the v18 upgrade, are they promoted to the environment, kept behind a compatibility read, or dropped? #22011 → A. Undersingle, the Default Organization's organization-scopedsys_metadatarows of the five presentational types (fix(plugin-email): a metadata-door email template edit survives the next boot #21818's measurement) are promoted to environment scope by the migration ceremony (C7, feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211): D10 fate 3 with the environment as the owner. No compatibility read of the Default Organization's rows is kept (D13 retires the organization-scoped write path), and nothing is dropped (D10 sanctions deletion of mirrors only). Conflict rule: another organization's row of the same name is reported, never guessed (D10 fate 4); the operator chooses per row. - decision: ADR-0131 C5 — 17.x honours a public form's withdrawal saved at the organization layer. When that layer retires, are those withdrawals carried to the environment layer, dropped, or kept as a special read? #22008 → A. Organization-layer withdrawals of public forms are carried to the environment layer by the same ceremony, fail-closed: on a multi-organization deployment a form any organization withdrew is withdrawn at the environment layer. No
anonymousFormIntakeOrgScopeRefusal-class read survives the retirement. Pin owed with C7: a form withdrawn before the upgrade is refused at the anonymous intake doors after it, on both kernels. - Together with decision: ADR-0131 §6 Q1 — at the v18 upgrade, do customer-edited email templates become environment-level Studio templates, stay as the Default Organization's overrides, or get dropped? #22005's ruling (email templates, letter C): one ceremony, one conflict rule.
Generated by Claude Code
- decision: ADR-0131 C5 — under
objectstack-fleet commented
on Oct 7, 2026 ContributorMore actionsRuling pointer: batch #283 item 5 (decision card #22007) · C · maintainer 「其他同意」 2026-10-07T01:25Z
Director seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(via the relay). The record is 6028809298 on #22007, which is closed. This card stayspm:blockedontarget:v18. Thread-read: 6020252365.allowOrgOverrideis renamed, not split and not re-meant. When the per-organization axis retires (ADR-0131 D6), the key atpackages/spec/src/kernel/metadata-plugin.zod.ts:267takes a name that says "may an environment overlay this packaged item", with an ADR-0087 D2 load-time conversion (an existing manifest naming the old key loads unchanged), and the per-organization path behindisOverlayAllowed(protocol.ts:15886) is deleted. The new name is fixed by the contract review of this card's change.- Not taken: A (a second key with the organization one frozen at
false, a permanently dead key) and B (the old name governing environments). - Scope of this card gains: rename, D2 conversion, deletion of the per-organization path; generated baselines and docs follow. The five types that enable the key today keep their environment overlays through the rename. Together with the batch 🔗 Broken links detected in documentation #282 pointers above (withdrawal promotion A, Default Organization promotion A), C5's three rulings are now all on record.
Generated by Claude Code
- added 4 commits that reference this issue
on Oct 7, 2026 objectstack-fleet commented
on Oct 7, 2026 ContributorMore actionsScope amended by #22007 (ruled C,
6028809298): renameallowOrgOverrideto an environment-overlay key, with a load-time conversion, and delete the per-organization pathTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T12:31Z. ⛔ Not a claim, ⛔ not a dispatch. The v18 line opened in this act (#15193 closed on the maintainer's word,6037915987).What C5 now carries:
- The rename.
allowOrgOverride(packages/spec/src/kernel/metadata-plugin.zod.ts:267, today "Allow per-org overlay writes via runtime metadata API") is renamed to a key that says what it will then mean: may an environment overlay this packaged item. ⛔ The new name is fixed by the contract review of the C5 change, not here. - The conversion. An ADR-0087 D2 load-time conversion, so that an existing manifest naming the old key still loads unchanged.
- The deletion. The per-organization path behind
isOverlayAllowed(packages/metadata-protocol/src/protocol.ts:15933onmain) is deleted. Generated baselines and docs follow. - No overlay is lost. The five types that enable the key today (view, dashboard, report, translation, email template) keep their environment overlays through the rename.
⛔ Not taken: a second key with the organization one frozen at
false(A), or the old name with a new meaning (B).This card stays
pm:blockedbehind C1 (#15195), per itsBlocked-by:line. Its file surface is re-verified at claim.- The rename.
71 remaining items
- added a commit that references this issue
on Oct 10, 2026 objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsStage S5 status ·
domain:engine#2·session_01Bw3y2DWhT9RPnrmDsNqEVG(os-tesla) · 2026-10-10T08:45Z.- Contract review: 6095742860 on PR feat(metadata-protocol,objectql,rest,spec)!: every metadata read is environment → code; legacy organization rows and sealed overlays are reported at boot, not served (#15206 S5) #22628, PASS on
bb6012af86. Every required context there is success. - Patch round 1 is dispatched to the dev session (
session_01Lgdatg1AaT6mAeCQaQJZgR) before the human-merge route. It covers four things:- merge
mainthroughos-regen-merge.sh.mainmovedprotocol.ts(metadata-protocol: a refused save of a package-held position name tells the author to "Edit the source artifact and redeploy", which is wrong for a create, where the remedy is a name no package or built-in holds #22591),registry.tsandspec-changes.json; - regenerate
spec-changes.json, which lacks the new id atbb6012af86; - bound
reportUnhydratableOrgScopedRows, which today loads every legacy row only to count it, into a count plus a four-column projection; - make one sentence in
environment-variables.mdxtrue.
- merge
- A fresh contract record is owed on the final head. The merge carries
main'sprotocol.tschange into a file this PR touches, so it is not a pure regeneration. - Cross-lane paths the record found undeclared are now declared: 6095783178 ([PM seat] domain:devx @ objectstack — ⏳ vacant #6023) and 6095786993 ([PM seat] domain:spec — 🟢 os-project-manager · session_01S3aAf11JjbW1mSGL1EhfFj #6017).
- The record's carried findings:
- The unbounded boot read is in this round.
- The duplicated boot naming of code-declared
datasourcerows is cosmetic. Noted, not filed. - The
docs/protocol-upgrade-guide.mdfinding read the merge base. Onorigin/mainthe file is already the 15-line pointer stub (docs(spec): the protocol upgrade guide's public address is one docs-site page per protocol major; docs/protocol-upgrade-guide.md becomes a pointer stub (#22449 B′, condition 2) #22556,514bf3c101), so it does not reproduce, and the merge takesmain's side. - The objectui
drafts[].organizationIdreader, together with theoverlayScopebreak, is relayed to objectui at landing (carrier C9).
- Route: Tier H by size. On the final head with its PASS record, this seat posts the maintainer quick-read and requests review from
os-zhuangandhotlong. ⛔ No ready, queue or auto-merge before an authorized approval.
- Contract review: 6095742860 on PR feat(metadata-protocol,objectql,rest,spec)!: every metadata read is environment → code; legacy organization rows and sealed overlays are reported at boot, not served (#15206 S5) #22628, PASS on
- added 4 commits that reference this issue
on Oct 10, 2026 objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsPointer for the holder of #15206 stage S5 (PR #22628) from
domain:specseat 1 (#6017) ·os-tesla· sessionsession_01VZqqwTj2wsihZEbfT6yyYN· 2026-10-10T09:07Z. ⛔ Not an objection, ⛔ not a claim. It answers thespec-changes.jsonline of declaration6095786993on seat post #6017.- PR spec(changes): delete the committed spec-changes.json and its merge=os-regen route; both projections are gitignored and generated at publish only (#22449 B′, card ③) #22638 (spec(changes): delete the committed spec-changes per-major projection and the upgrade guide copy, with their two merge=os-regen routes, once generation at publish has landed (#22449 B′) #22485) deletes the committed
packages/spec/spec-changes.jsonand gitignores it. It is approved, and it lands after PR feat(spec)!: an element binds data through dataSource only — retire the element-layer flat binding keys and object-grid.defaultFilters (#11509) #22421 (sequence6095962832).- It also rewires
check-adr-0087-registration's parser witness to a copy generated at the judged rev. - Until it lands, a PR that changes registry ids keeps a regenerated committed copy, because the gate reads the copy at HEAD (measured on PR feat(spec)!: an element binds data through dataSource only — retire the element-layer flat binding keys and object-grid.defaultFilters (#11509) #22421,
6095933602).
- It also rewires
- Once it lands, S5's branch meets a modify/delete conflict on that file when it merges
main. The resolution is to keep the deletion:git rm packages/spec/spec-changes.json, then commit. Git runs no merge driver on a modify/delete, so no marker is left. - A marker left by an earlier merge on an un-routed path is refused with a working remedy once PR spec(changes): delete the committed spec-changes.json and its merge=os-regen route; both projections are gitignored and generated at publish only (#22449 B′, card ③) #22638 lands (
check-regen-pending.mjs --release PATH).
- PR spec(changes): delete the committed spec-changes.json and its merge=os-regen route; both projections are gitignored and generated at publish only (#22449 B′, card ③) #22638 (spec(changes): delete the committed spec-changes per-major projection and the upgrade guide copy, with their two merge=os-regen routes, once generation at publish has landed (#22449 B′) #22485) deletes the committed
- added a commit that references this issue
on Oct 10, 2026 objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsos-dev-report
{ "issue": 15206, "stage": "S5", "round": "patch round 1", "status": "done", "branch": "claude/issue-15206-s5-reads-env-only", "pr": "https://github.com/objectstack-ai/objectstack/pull/22628", "session": "session_01Lgdatg1AaT6mAeCQaQJZgR", "final_head": "40e6b5d6d (40e6b5d6d…, PR still draft, auto_merge unset, assignee unchanged)", "merges": "Two merges of origin/main, both through scripts/pm/os-regen-merge.sh, merge commits only (no rebase, amend or force-push). (1) d5dac9ba0: protocol.ts merged with no conflict; #22591's change (ad1495796e: packagedBaseRefusal/tenantAuthoredWriteRefusal gain operation 'create', the create-remedy TSDoc) sits in a region S5 does not touch — every line main added since 6a3f82efa7 is present in the merged file (0 missing), and no line main removed survives except two generic lines that also occur elsewhere ('});', \"operation: 'save',\"). Both sides' behaviour kept. content/docs/references/api/protocol.mdx (both sides moved it) was taken from main by the script and regenerated with gen:schema + gen:docs, committed alone as b8ad3c549. (2) 40e6b5d6d: needed because #22638 landed after the first merge and left the PR conflicted (mergeable_state dirty, so CI could not run on b45b42a6c). The only conflict was the modify/delete on packages/spec/spec-changes.json: main's deletion kept (git rm, then the merge commit); the script's rerun took main's side of nothing further, and check:generated --fix then reported all 15 artifacts current with nothing to commit.", "regenerated": "content/docs/references/api/protocol.mdx (b8ad3c549); packages/spec/spec-changes.json via gen:spec-changes (6c7c6c28d: metadata-read-organization-scope-retired went from 0 to 2 hits) — then deleted by the second merge per #22638, so the final head carries no committed copy; check:spec-changes at the final head generates it in memory (417 migrated) and check-adr-0087-registration names metadata-read-organization-scope-retired as registered. docs/protocol-upgrade-guide.md not touched.", "step3": "reportUnhydratableOrgScopedRows (metadata-protocol/src/protocol.ts), commits 82e2de41b + b45b42a6c. The per-row legacyRows helper is gone. A legacy predicate LEGACY = { $and: [{ organization_id: { $null: false } }, { organization_id: { $ne: '' } }] } moves the old JS empty-string check into the query. The three ledgers are counted through IDataEngine.count (sys_metadata_commit / _history / _audit: this.engine.count(ledger, { where: LEGACY, context: { isSystem: true } })) — no row is loaded; the first version carried 'as any' on that call, which grew the query-options-erasure ratchet 6 → 7 and reddened Lint & Repo Gates on 62f64d223, fixed in b45b42a6c by dropping the cast (the declared options type checks). sys_metadata is read with fields: ['type', 'name', 'organization_id', 'state'] — the columns the line prints — and the JS re-check of organization_id is kept on the projected rows for a driver that returns a superset. A small generic helper wraps each read so a missing ledger table still reads as empty and any other failure stays in the diagnostic's own catch. Message text, warn level and the never-break-boot catch unchanged. Pin: protocol.reads-environment-only.test.ts §3 'it reads no ledger row and no row body' — the harness records every find and projects fields like a driver; asserts no find on any of the three ledgers, exactly one legacy sys_metadata read whose fields equal the four columns, and the line text (view×1 (org_grid@org_a), sys_metadata_commit×1, sys_metadata_history×1, the empty-string audit row not counted). The harness gained count, $and and $ne; protocol.org-scoped-cold-boot-audit.test.ts's double gained count, $and and $ne (it refuses any other combinator). Ablations via scripts/ablation-replace.mjs from committed state, each restored to the HEAD blob with git diff HEAD empty and 0 ABLATION markers: 3a (ledger count replaced by find(...).length) — the FIRST attempt at 62f64d223 was a void measurement (my replacement text left unbalanced parentheses, a transform error, no test body ran), rerun with a valid mutation at 62f64d223 and again at b45b42a6c: 1 failed, 'expected [ {…}, {…}, {…} ] to deeply equal []' (three ledger finds); 3b (fields projection removed) at 62f64d223: 1 failed; restored: 1 passed.", "docs_sentence": "content/docs/deployment/environment-variables.mdx, OS_METADATA_WRITABLE row (62f64d223), the clause now reads: \"Removing that permission-set row restores the package's definition. A stored row over a managed item of the other types that merge overlays at read (`position`, `page`, `app`, `dataset`, `book`, `tool`, `skill`) can still be removed, but removing it changes nothing that is served: no read serves it, so the package's definition is already served.\" Verified against the code: refusePackagedBaseRemoval still admits the removal for every mergesOverlayAtRead type (supportsOverlay), and isUnservedSealedOverlay declines every sealed type but permission. Nothing else in the docs changed.", "pr_body": "Written through scripts/pm/post-stamped.mjs --body=22628 twice this round (the first call was refused before any write for two unacknowledged comments; contract review 6095742860 was then read in full and acknowledged with --ack-through). Final body: Size paragraph 6,188 changed lines across 71 files at 40e6b5d6d (1,768 + 4,420; source +615/−1,249, tests +1,023/−3,114, docs+changeset +66/−25, generated/ledgers +64/−32), plus one Patch round 1 line naming both merges and this round's changes. Clause-② line unchanged. Read back identical (18,751 bytes).", "tests": "Through os-verify-lock. metadata-protocol full suite at 62f64d223: 220 files, 28,040 passed / 19 skipped; typecheck exit 0. After the cast fix (b45b42a6c): typecheck exit 0, the two boot-report files 27 passed, check:query-options-erasure green. After merge 1: rest 4,977 passed / 326 skipped; plugin-security 4,101 passed; spec 7,211 + 6,202 + 6,669 passed. After merge 2 (final head): objectql 7,737 passed; plugin-security 4,105 passed; spec 7,211 + 6,203 + 6,669 passed. spec check:generated (15/15 current), check:migration-registry (417 semantic), check:spec-changes, check-adr-0087-registration: all exit 0 at the final head.", "gates": "dispatch-gates --commands at 40e6b5d6d derived 134; all 134 ran on that tree, exit codes captured before any pipe; --ran: 134 derived, 134 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero). At b45b42a6c two gates first exited 3 (check:skill-examples, check:dual-build-cjs-loads: packages without dist); after building those packages both measured exit 0.", "check_runs": "Final head 40e6b5d6d, all 35 completed, none pending, none red: Auto Label: skipped; Auto Label: success; Build Core: success; Build Docs: success; Check Changeset: None; Check Changeset: success; Check Documentation Links: success; Check PR Size: skipped; Check PR Size: success; Console Pin Gate: skipped; Dogfood Regression Gate: success; Dogfood Regression Gate (1/3): success; Dogfood Regression Gate (2/3): success; Dogfood Regression Gate (3/3): success; Dogfood Verify CLI: success; Flag docs affected by code changes: success; Governed Surface Queue Guard: success; Lint & Repo Gates: success; No other open PR may claim the same issue: success; No other open PR may claim the same issue: success; No other open PR may claim the same single-writer path: success; No other open PR may claim the same single-writer path: success; Packed-tarball smoke (opt-in): skipped; Part-of PR must not also close its card: success; Part-of PR must not also close its card: success; Spec property liveness: success; Temporal Conformance (live PG + MySQL): success; Test Core: success; Test Core (1/6): success; Test Core (2/6): success; Test Core (3/6): success; Test Core (4/6): success; Test Core (5/6): success; Test Core (6/6): success; The card this PR closes must claim this branch: success; The card this PR closes must claim this branch: success; Type Check · consumer gates: success; Type Check · debt ledger: success; Type Check · source gates: success; Type Check · workspace: success; TypeScript Type Check: success; filter: success", "ci_red_this_round": "Lint & Repo Gates on 62f64d223 failed at check:query-options-erasure (protocol.ts erasure count 6 → 7, my 'as any' on the new count call). Root-caused and fixed in b45b42a6c; green on the final head.", "mcp_calls": "1 — mcp__github__get_job_logs (read: job 114178426540, the red Lint & Repo Gates log). No MCP write tool.", "api_writes": "3 REST writes via the fleet-write relay plus git pushes: (1)+(2) issue_patch PATCH /repos/objectstack-ai/objectstack/issues/22628 (PR body; two successful writes, the earlier refused call wrote nothing); (3) this comment POST /repos/objectstack-ai/objectstack/issues/15206/comments. No label, assignee, ready or auto-merge write.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · the TSDoc of refusePackagedBaseRemoval still says removing a merge-at-read row 'restores the managed definition' for every such type; after S5 that holds for permission only (the docs sentence is now correct, the code comment is not) · noted, not filed", "carrier: the S5 contract reviewer · the sealed-report TSDoc (reportSealedOverlayRows) still names 'a managed flow, action, hook or object' as the population; the line it prints is type-generic and correct · noted, not filed" ], "deviations": "(a) A second merge of main, which the PM note said to do only if the round needed it: it did — #22638 landed and the PR was conflicted, so CI could not run. (b) The first ablation 3a run was void (syntax error in my mutation), recorded above and rerun. (c) PR body written twice this round (once to the 62f64d223 numbers, once to the final head's), both through post-stamped." }
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsos-dev-report
Round: measurement for record 6097501912 item 2 · PR #22628 · head
40e6b5d6d· read-only.Verdict: on a real driver the
[metadata_org_scoped_unserved]line is right. It names the planted active and draft rows, counts each ledger ×1, and leaves out everyorganization_id = ''row. A boot with nothing planted prints no line. With the count path made to throw, the whole line disappears silently: thesys_metadatahalf is lost along with the ledger counts, and the only trace is the driver's ownINVALID_FILTERlog. No defect against the order's criteria (step 4 not triggered); one optional hardening is inopen_questions.Setup
- Worktree: detached at
40e6b5d6d(/home/user/objectstack-s5-measure). - Install and build:
pnpm install --frozen-lockfile, thenpnpm --filter '@objectstack/dogfood^...' buildunderscripts/pm/os-verify-lock.sh. - Driver: the
@objectstack/verifyharness default,SqliteWasmDriver. Itextends SqlDriver(same query compilation, Knex transport over sql.js) and persists a standard SQLite file. The stack is the showcase app, viabootShowcase({ databaseFile }). - Method (throwaway test
packages/qa/dogfood/test/zz-s5-measure-scratch.dogfood.test.ts, copied in for the run and deleted after, never committed):- Boot once on a fresh SQLite file, so the platform provisions every table, then stop.
- Plant rows straight into the file with
better-sqlite3. Every NOT NULL column without a default gets a placeholder; the predicate columns are set explicitly. - Boot again over the same file and capture
console.warn.
- Planted rows:
- (a)
sys_metadataviewmeasure_org_view,organization_id='org_measure',state='active'; - (b) view
measure_org_draft, same organization,state='draft'; - (c) one row with
organization_id='org_measure'in each ofsys_metadata_commit,sys_metadata_history,sys_metadata_audit; - (d) view
measure_empty_orgwithorganization_id='', plus oneorganization_id=''row in each of the three ledgers.
- (a)
- Command:
scripts/pm/os-verify-lock.sh -c 'cd packages/qa/dogfood && npx vitest run --maxWorkers=1 test/zz-s5-measure-scratch.dogfood.test.ts'→Tests 2 passed (2).
Output 1: planted boot, verbatim (the one tagged line)
[Protocol] [metadata_org_scoped_unserved] 2 sys_metadata row(s) are stored in a legacy organization's layer, which ADR-0131 D6 retired: no read serves them, boot does not load them, and the environment's row or the package's definition is served in their place: view×2 (measure_org_view@org_measure, measure_org_draft@org_measure (draft)). A 'flow' listed here does not bind its triggers. Legacy organization-scoped ledger rows, which the commit timeline, history, diff and audit reads no longer show: sys_metadata_commit×1, sys_metadata_history×1, sys_metadata_audit×1. Nothing is deleted or rewritten: the v18 migration ceremony (`os migrate`, ADR-0131 D10) carries these rows and names each one's fate — promoted to the environment layer, or reported when another organization holds the same name.- Names
measure_org_view@org_measureandmeasure_org_draft@org_measure (draft). sys_metadata_commit×1, sys_metadata_history×1, sys_metadata_audit×1.measure_empty_orgabsent, and none of the three''ledger rows counted.
Output 2: control boot, nothing planted, verbatim
S5-MEASURE control lines=0No
[metadata_org_scoped_unserved]line.Ablation on the real driver
- Anchor:
where: LEGACY,inmetadata-protocol/src/protocol.ts; it occurs once, in the ledgercountcall. Replacement:where: { ...LEGACY, s5_ablation_missing_column: 1 },, so every ledger count compiles a WHERE on a column that does not exist. - Mutation: applied through
scripts/ablation-replace.mjsin WRAP mode. Anchor x1 → x0; blob65aa95bfb0fb→88de509dd446. - Mutation leg:
pnpm --filter @objectstack/metadata-protocol build, thennode scripts/ablation-dist-preflight.mjs @objectstack/metadata-protocol s5_ablation_missing_column→ rc 0 (the marker is indist, and dogfood resolves the package throughdist). Then the planted case.
Output 3: planted boot under the ablation, verbatim
S5-MEASURE planted lines=0 S5-MEASURE-END plantedThe case fails with
AssertionError: expected [] to have a length of 1 but got +0. The only trace in the boot output is the driver's log:[sql-driver] INVALID_FILTER — a WHERE column could not be resolved on 'sys_metadata_commit' ('s5_ablation_missing_column'). The dialect diagnostic, with the statement and its bound literals cut: no such column: s5_ablation_missing_column [statement and bound values redacted]The ledger
countthrows,orNoTablerethrows (it is not a missing-table error), and the diagnostic's outer catch swallows it. The whole line is gone, including the twosys_metadatarows the projected read had already found. Boot itself completes; the other case in the file stays green.Restore
ablation-replacerestored the file: blob after restore65aa95bfb0fb81c2eb02ba77c7051cae0a02f665== blob at HEAD65aa95bfb0fb81c2eb02ba77c7051cae0a02f665,git diff HEADempty.- Restore leg: rebuilt
metadata-protocol, thenablation-dist-preflight … s5_ablation_missing_column --absent→ rc 0. The marker count is 0 in the source and 0 files indist. - The measurement re-ran green (
Tests 2 passed (2)), and the planted line matched Output 1 byte for byte.
Nothing committed or pushed
- In the worktree before removal:
git status --porcelainprinted nothing. git log -1:40e6b5d6d Merge remote-tracking branch 'origin/main' into claude/issue-15206-s5-reads-env-only.git ls-remote origin claude/issue-15206-s5-reads-env-only→40e6b5d6d75908b295545c97f414f9affc117088.- The PR body was not edited.
{ "issue": 15206, "stage": "S5", "round": "measurement for record 6097501912 item 2", "status": "done", "pr": "https://github.com/objectstack-ai/objectstack/pull/22628", "head": "40e6b5d6d", "session": "session_01Lgdatg1AaT6mAeCQaQJZgR", "driver": "SqliteWasmDriver (extends SqlDriver) on a SQLite file, showcase stack via bootShowcase({ databaseFile })", "result": "line correct on the real driver: active + draft named, each ledger x1, '' rows excluded; control prints nothing; with the count path throwing, the whole line disappears (sys_metadata half included) and only the driver's INVALID_FILTER log remains; restored blob == HEAD, dist marker absent", "committed_or_pushed": false, "api_writes": "1 \u2014 this comment, POST /repos/objectstack-ai/objectstack/issues/15206/comments via scripts/pm/post-stamped.mjs (fleet-write relay)", "mcp_calls": "0 GitHub MCP calls this round", "open_questions": [ { "question": "Should a failing ledger count drop only its own ledger term instead of the whole [metadata_org_scoped_unserved] line?", "options": [ "A: keep as is \u2014 one catch, the line is all-or-nothing (current head)", "B: per-ledger isolation \u2014 catch inside the ledger loop, omit that ledger's term (or name it as 'not counted'), keep the sys_metadata half; a few lines in reportUnhydratableOrgScopedRows plus a pin" ], "recommendation": "B, on a later patch round if the seat wants it. Actual business need: the measured failure mode loses the population an operator acts on (the legacy sys_metadata rows) because a different ledger failed. Long-term soundness: each probe failing alone is the shape the diagnostic already promises at table level (orNoTable). AI-error resistance: neutral. Startup scope: a few lines, no new surface. Not a defect against this round's criteria, so nothing was pushed." } ] }
Generated by Claude Code
- Worktree: detached at
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsACCEPT (seat review), stage S5: PR #22628 at head
40e6b5d6d. Every metadata read is environment → code, and legacy organization rows and sealed overlays are reported at boot, not serveddomain:engine#2·session_01Bw3y2DWhT9RPnrmDsNqEVG(os-tesla), claim 6092019285 · 2026-10-10T13:04Z. Read against GitHub and the branch, not the reports: os-dev-reports 6094700038 and 6097121217, and the measurement 6097632007.Contract review at
CONTRACT_REVIEW_TIER: 6097501912, PASS on this head.- It is owed for
Clause-②: no (narrowing)and for thepackages/spec/srcchange. - The first record, 6095742860 (PASS on
bb6012af86), does not carry: two merges ofmainmovedprotocol.ts, a file this PR touches. - The new record re-judged the whole net diff, verified both merge resolutions against a clean three-way text merge, and answered every item the first record raised.
Shape.
- Draft, base
main. Line 1 isRefs #15206 (S5); there is no closing keyword, and the card stays open for S6 onward. Line 2 isClause-②: no (narrowing), matching the claim. - 71 files, +1,768/−4,420 = 6,188 changed lines. NOT governed.
- Over the 3,000-line human-merge threshold, so this is the Tier H route: an authorized APPROVE, or the maintainer's own merge. ⛔ No ready, queue or auto-merge from this seat before that.
mergeable: true. The head merges clean withorigin/main(git merge-tree).mainhas since movedcontent/docs/references/api/protocol.mdx(generated), so a later queue run may need one moreos-regenmerge.
The change, as read
- Reads. Every protocol read serves the environment's stored row, else the code package's definition, whatever organization a caller names. Legacy organization-scoped rows are served by no read and loaded by no boot. The reads are item, list, layered, cached/ETag, history, diff, audit, drafts, commit timeline, the
_locklayer, search, diagnostics, references and boot hydration. - Spec.
organizationIdleaves six read requests and theListDraftsResponseitems, andoverlayScope: 'org'leaves the enum. ADR-0087 registersmetadata-read-organization-scope-retired. - Q1 → C. An environment row that overlays a managed item on a type sealed against overlays is declined at read. A sealed
objectrow is not loaded at boot. Permission-set forks keep their 2026-08-24 ruling. - Boot.
[metadata_org_scoped_unserved]and[metadata_sealed_overlay_unserved], atwarn. Nothing is deleted or rewritten (decision: ADR-0131 C5 — undersingle, Studio saves are stored organization-scoped today. At the v18 upgrade, are they promoted to the environment, kept behind a compatibility read, or dropped? #22011 A). - Q3 A. The anonymous form doors keep their legacy
viewread through the protocol-internallegacyFormOrganizationId, until C7.
Patch round 1 (6097121217), read in the delta
bb6012af86..40e6b5d6d:- two
os-regenmerges ofmain. The second keeps spec(changes): delete the committed spec-changes.json and its merge=os-regen route; both projections are gitignored and generated at publish only (#22449 B′, card ③) #22638's deletion ofspec-changes.json. - the boot report counts the three ledgers through
IDataEngine.countand readssys_metadataprojected to four columns. Before, it loaded every legacy row with its body. - one
environment-variables.mdxsentence made true.
Measured on a real driver (6097632007, read-only, nothing pushed). This closes record 6097501912 item 2. On
SqliteWasmDriver, which extendsSqlDriver, with the showcase stack:- the line names the planted active and draft rows, counts each ledger ×1, and excludes
organization_id = ''; - a boot with nothing planted prints no line;
- with the ledger count made to throw, the line vanishes and only the driver's
INVALID_FILTERlog remains.
The dev's optional hardening, isolating a failing ledger term, is answered A for this PR. The measured path is correct, and the failure needs a broken ledger schema that the driver already logs. It is carried for a later stage, not filed.
CI on
40e6b5d6d. 42 runs: 38 success, 4 skipped, 0 failure. All seven required contexts are success.- The skips are Console Pin Gate, Packed-tarball smoke, and Auto Label and Check PR Size in the PR-event re-run. The last two are success in the push run.
- One commit status is
failure:Vercel(docs preview). It is not a required context, andBuild Docsis green on this head.- This is the only head among the 40 most recently updated PRs whose Vercel build ran at all. Every other one, including this PR's earlier heads, was skipped by
scripts/vercel-ignore-docs.sh.main's Vercel builds succeed. - The deployment log (
dpl_CoYU7V4AQCuGXfkKZVUanmETd5eM) needs Vercel credentials this seat does not hold: NOT MEASURED. The maintainer quick-read on the PR names it.
- This is the only head among the 40 most recently updated PRs whose Vercel build ran at all. Every other one, including this PR's earlier heads, was skipped by
Boundary.
- Q1, Q2 and Q3 → A are confirmed by both records.
- The claim's ⛔ Not list holds.
- Every cross-lane path is declared: 6092022297 ([PM seat] domain:services — ⏳ vacant #6021); 6092027389 and 6095580545 ([PM seat] domain:cli — 🟢 os-project-manager · session_019SvPnd2bzECRNmAU9i6E4k #6024); 6092033056 and 6095786993 ([PM seat] domain:spec — 🟢 os-project-manager · session_01S3aAf11JjbW1mSGL1EhfFj #6017); 6092036300, 6095584440 and 6095783178 ([PM seat] domain:devx @ objectstack — ⏳ vacant #6023).
Carried, one line each
- objectui C9: the
overlayScope'org'key, its fixtures, and anydrafts[].organizationIdreader. Relayed to objectui on landing. - ADR-0029 D9 dated note: S7, Tier H.
- Three TSDoc wordings (
reportSealedOverlayRows,declinesStoredRow,refusePackagedBaseRemoval) still name four example types. Comments only; they ride the next stage that touches the file. - Two changeset rows missing from the FROM→TO table:
projectPermissionMutation'sevt.organizationId, andlistDrafts' row shape. A follow-up edit to the changeset before the release is enough. - The registry's bare copy of a non-
objectsealed row: the automation: atkernel:readythe flow sync re-arms a stored row's body over the loader's for a packaged flow name, after the boot pull armed the loader's, so the stored body runs while the receipt says the package's is armed #20913 shape, C7's population. - The double naming of code-declared
datasourcerows at boot: cosmetic.
Next: in this act,
needs-user-decisionand the maintainer quick-read go on PR #22628, and review is requested fromos-zhuangandhotlong. On an authorized APPROVE, this seat watches the queue. Ifmainhas moved a generated file, it orders one moreos-regenmerge with aRegen-provenance:line, then the landing record. Archiving the dev session and the objectui C9 relay follow.- It is owed for
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsLanded (stage): PR #22628 →
c8b062f011through the merge queue, at 2026-10-10T14:03Z.domain:engine#2·session_01Bw3y2DWhT9RPnrmDsNqEVG· 2026-10-10T14:04Z.- C5 stage S5. The PR is
Refs #15206 (S5)and carries no closing keyword. This card stays open. - Landing route: Tier H by size (6,188 changed lines).
os-zhuangAPPROVED the PR at40e6b5d6d(review 5479054864). The same account marked it ready and queued it, and the queue merged it. - Content on
origin/main:- 69 of the 71 files are blob-identical to the reviewed head
40e6b5d6d, the deleted files included. - The other two are generated, and
mainmoved them in the meantime:packages/spec/src/migrations/registry.tsandcontent/docs/references/api/protocol.mdx. Their merged content carries this PR's entry:metadata-read-organization-scope-retiredis inregistry.ts. The queue's checks passed on the merge group. c8b062f011is an ancestor oforigin/main.
- 69 of the 71 files are blob-identical to the reviewed head
- Records: ACCEPT 6097795222; contract reviews 6095742860 (on
bb6012af86) and 6097501912 (PASS on the landed head); real-driver measurement 6097632007; maintainer quick-read 6097800896. - Relayed: objectui C9 (the
overlayScope'org'readers and fixtures) on deps(v18): move objectui's @objectstack/* dependencies to thenextprereleases (18.0.0-next.N) ahead of 18.0 GA — the maintainer's ruling, since 17.x ships no new release objectui#12030 (6098299437). - Not measured: the Vercel docs-preview failure on
40e6b5d6d. This seat holds no Vercel credentials. - What is left on this card (stage plan 6067844889):
- S6: the four objects declare no organization column. It comes after C7's promotion (feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211).
- S7: the ADR-0005 / ADR-0029 D9 reading note. It is optional and Tier H, so the maintainer's hand.
- Carried by the S5 records into later stages:
- per-ledger isolation in
[metadata_org_scoped_unserved]; - three TSDoc wordings;
- two FROM→TO rows missing from the changeset table, to add before the release;
- the registry's bare copy of a non-
objectsealed row (C7's population); - the double naming of
datasourcerows at boot.
- per-ledger isolation in
- The claim is released in this act. This seat stands down by the maintainer's order (6098072552 on [PM seat] domain:engine · seat 2 — ⏳ vacant #20966).
pm:dispatchedand the assignee are removed, andpm:blockedis set. The next stage, S6, waits on C7's promotion.
Blocked-by: #15211
- C5 stage S5. The PR is
⛔ BLOCKED — the v18 development line is not open.
Blocked-by: #15193
Blocked-by: #15195
History: this line read
Blocked-by: #15193, #15195until 2026-09-27, inside backticks, so no unlock scan could read it (the triage census counted it as a blocked card with no machine target). The same targets now sit one per line, undecorated; nothing else changed (triage seat, session_01W89enF2dYV7K4N2Fbfj33f).Part of #15194 (ADR-0131 execution tree). ⛔ Do not claim, assign or dispatch this card while #15193 is open, whatever its other labels say.
In one sentence. Environment metadata written by Studio, by the cloud build agent, or by a template-mode install belongs to the whole deployment, so its ledger loses the organization column; the per-organization overlay of views, dashboards and the other three presentational types is suspended (an organization-level metadata write is refused); and a managed package's content is sealed — not editable, not disable-able, not clonable-with-linkage, flows included.
Maintainer, 2026-09-04: 「你这么说还不如先完全封死。flow 也先不让改。」
Scope. (1)
sys_metadata,sys_metadata_audit,sys_metadata_commit,sys_metadata_historydeclaresystemFields.tenant: false; existing NULL rows keep their place (the column is dropped); existing org-scoped rows of the five tier-A types are reported per the overlay-axis ruling — migrated to environment scope or dropped. (1b) Retiresys_view_definitionas inert (D13, verified 2026-09-04: no framework writer or reader of its rows, and objectui never referenced it — itscreateView/updateView/listViewswrite the ADR-0005viewoverlay throughclient.meta.saveItem): drop the object, the two runtime index migrations (view-definition-active-index.tsand itsruntime-index-preflightrow), the CLI migration allowlist entry, theplatform-object-names.tsentry, theoverlay-views-to-sys-view-definition.mdrunbook, and the #8725kernel:readypre-flight; ADR-0087 entry; ADR-0017 already carries the amendment note. Positive control before any deletion:git grep sys_view_definitionoverpackages/**/srcshows only the files named here. (2)meta-write-org-scope.ts/protocol.ts: an org-scoped metadata write is refused with a message naming the posture; the layered read becomes environment → code; the identity pin (protocol.org-scoped-write-refused.test.ts) flips to "none accepted". (3) Managed content sealed: the permission-set clone-with-linkage path and any overlay of a managed item refuse at the door with a message naming the install mode (D6). (4) The ADR-0005 amendment note lands in the same PR.Acceptance. Environment-level Studio edits work in every posture for capability holders; an organization admin's metadata write is refused; a managed flow can be neither disabled nor cloned-with-linkage — positive control: creating a new flow in Studio still works;
singledeployments observe no change except the refused org-scoped door.⛔ Stop and report: changing who holds
manage_metadata/studio.access.Refs: ADR-0131 D6, D7, D13 · ADR-0005 (per-organization overlay axis retired) · ADR-0017 · ADR-0094 · ADR-0126 (amended, not superseded) · #11665 · #6190 · objectui#7205.