Skip to content

[finding] email_template: a Studio save of the same template in a second locale replaces the first locale's overlay at the next boot (sys_metadata keeps one overlay row per type and name) #22877

Closed

Description

@objectstack-fleet

Ruled: 6116165299 · letter A (Q2 i) · 2026-10-12T01:32Z
Re-presented: analysis 6115923019 · the deferral 6115691537 is withdrawn on the maintainer's word 「所以 22877 要和我重新讨论分析新的开发方案」 · 2026-10-12T01:04Z
Ruled: 6115691537 · letter deferred (option E recorded, analysed on restart) · 2026-10-12T00:38Z
Restart-when: closed #15205 (left pm:on-hold, see the line above)
Restart-touch: packages/metadata-protocol/src/migrations/overlay-index.ts

Filing gate, class (a): a reproducible defect, reach: a public door (PUT /meta/email_template/...), measured once on the showcase by the #15205 dev (report 6111776721). Filed by the domain:services seat 2 (seat post #21118, session_013LbZ9MhPp1iriZEtgJqioA). ⛔ Not a claim.

Reader: triage (grade and lane).

What was measured

Related, closed

Done when

  • Two locales of one email template, each saved through the metadata door, both survive a restart and each serves its own wording; CONTROL: a single-locale save is unchanged.

Duplicate check

MCP search_issues on this repo, closed cards included: "email_template overlay locale reverts after restart sys_metadata one row per type name" → 8 hits, the related closed cards above; none is this storage face.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:engine · area:i18n, into pm:queue. Direction: the overlay keeps the identity the spec and the registry already use

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-11T17:53Z. ⛔ Not a claim, ⛔ not a dispatch.

  2. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-11T20:47Z
    Session: session_01ADzJtzYTLUfgrRZHxkagkX
    Account: os-steve (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-22877-locale-bundle-overlay
    Worktree: objectstack-issue-22877
    Domain: domain:engine
    Seat: domain:engine#2 (seat post #20966); the lane's only seated PM
    File surface (read on origin/main 2e11d9006c; stop on a breach and explain it in the report):

  3. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22877,
    "status": "needs_decision",
    "branch": "claude/issue-22877-locale-bundle-overlay",
    "pr": null,
    "session": "session_01ADzJtzYTLUfgrRZHxkagkX (subagent of the domain:engine seat 2 PM; parent's id)",
    "premise_still_valid": true,
    "summary": "Premise holds, reproduced on origin/main 45e206e with the real ObjectQL engine, an in-memory driver and the real protocol (scratch test, never committed): saveMetaItem email_template acme.welcome en-US then zh-CN leaves ONE sys_metadata row (the zh-CN body). A reboot over the same store (new engine and protocol, loadMetaFromDb loaded=1) serves the package wording for en-US. Same family: getMetaItem by the bare name then serves the zh-CN overlay, not the canonical en-US member that the registry's bare-name read resolves to. Measured mechanism: the overlay unique key is (type, name, organization_id, COALESCE(package_id,'')) WHERE state='active', plus a draft twin (migrations/overlay-index.ts:137 OVERLAY_INDEX_COLUMNS; declared idx_sys_metadata_overlay_active in metadata-core sys-metadata.object.ts:226-230), with no locale column. saveMetaItem writes ref={type,name:request.name,org:'env'} (protocol.ts:20782), and SysMetadataRepository.put resolves the row through resolveWriteHead/whereFor (sys-metadata-repository.ts:655, :2092) by that key, so the second locale updates the first locale's row in place. Boot hydration (loadMetaFromDb, protocol.ts:26850, through hydrateOverlayIntoRegistry) and the list merge (mergePackageAwareOverlay slotOf, protocol.ts:2187) are already body-keyed by (name, locale). They would carry two rows correctly. The store's write address, and every by-name door that reads rows by (type,name), is the one layer that disagrees. STOP CONDITION HIT (triage 6111942095). Inside the existing unique key, only the name column can carry the locale. The registry's bundle-key spelling there (name@locale) is refused by the item-name grammar METADATA_ITEM_NAME_PATTERN (spec shared/identifiers.zod.ts:166). refuseUngrammaticalMetaItemName (protocol.ts:19437) enforces that grammar on saveMetaItem, on publishMetaItem, and on migrateStoredMetadata, which re-saves each row under its stored name. Measured: saveMetaItem name 'acme.welcome@zh-CN' returns 400 INVALID_REQUEST, 'is not a legal metadata item name ... express containment structurally, never with a separator inside the identity string'. That is the maintainer-ruled retirement of compound addressing (commit 311433f). Deriving that row name inside the store, past the door check, would mint rows the grammar calls illegal. os migrate meta --stored would fail each of them. A non-canonical member's draft could never be promoted through publishMetaItem. GET and DELETE of a member would need an ungrammatical name. A grammatical spelling such as an extra dot segment (auth.welcome.zh_cn) collides with real dotted template names and folds BCP-47 case lossily. So no route fits within the existing row: what is left is a new column with a changed unique key (the triage's migration shape) or a new stored body shape. Every route also needs a by-name door address for a non-canonical member: deleteMetaItem, getMetaItem and publishMetaItem take only (type, name), and ?locale= on /meta is already the UI translation locale (rest meta-item-read-gate.ts:2294-2321). Without that address, a second-locale overlay could not be deleted. Other locale-bundled types: ITEM_KEY_DISCRIMINATORS (metadata-core item-key-discriminators.ts:68) lists only email_template. translation is overlay-enabled and has a locale key, but its identity is the name alone, so it does not share this problem. PR #22875 compatibility: its loader (template-loader.ts on head 4aa656e) reads getMetaItemsForExecution and filters by the body's name and locale, and every option below keeps the list bodies' name bare. No code written, no PR. The empty branch was pushed as the claim's landing marker.",
    "tests": "Measurement only, no fix and therefore no ablation. (1) bash scripts/pm/os-verify-lock.sh -c "pnpm --workspace-concurrency=2 --filter '@objectstack/objectql^...' build" -> 'os-verify-lock: VERDICT command-exit 0 · held the lock 222s'. (2) Scratch packages/objectql/src/zz-scratch-22877.test.ts (copied the harness of environment-overlay-five-types.test.ts: real ObjectQL, in-memory driver, real ObjectStackProtocolImplementation, env_prod; package com.acme.mail ships acme.welcome en-US 'PKG en' and zh-CN 'PKG zh'), run via pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/zz-scratch-22877.test.ts, exit 0, 'Tests 2 passed (2)'. Printed readings: SAVE1/SAVE2 success:true; ROWS [{name:acme.welcome,locale:zh-CN,subject:ADMIN zh}]; LOAD {loaded:1,errors:0}; LIST-AFTER [[en-US,PKG en],[zh-CN,ADMIN zh]]; GET-BY-NAME [zh-CN,ADMIN zh]. Second case: saveMetaItem name 'acme.welcome@zh-CN' -> {err:INVALID_REQUEST, msg:'"acme.welcome@zh-CN" is not a legal metadata item name. ...'}; deleteMetaItem name 'acme.welcome@zh-CN' -> success:true 'nothing to delete' (the delete door does not apply the grammar). Scratch file deleted afterwards; git status --porcelain empty, git diff HEAD empty. (3) node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> exit 2, 'this branch changes nothing against origin/main (merge base 45e206e) — nothing to derive'.",
    "mcp_calls": "0 — no MCP GitHub tool called",
    "api_writes": "1 — POST /repos//issues/22877/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). Not REST: git push -u origin claude/issue-22877-locale-bundle-overlay (empty branch, the claim's landing marker and write probe). REST reads with the session's gh: GET issues/22877, GET issues/22877/comments, GET pulls/22875, GET pulls/22875/files; git fetch of PR 22875's head into a private ref, deleted at cleanup.",
    "open_questions": [
    {
    "question": "Q1: How does a sys_metadata overlay row carry the bundle member (locale) of a locale-bundled type, so that two locales of one email_template are two overlays? Measured: no route fits inside the existing row. The name column is the only in-key carrier, and the item-name grammar (ruling commit 311433f) refuses a separator inside the identity string.",
    "options": [
    "A — Structural member column. Add one nullable discriminator column to sys_metadata. NULL means the canonical member, so every existing row and every undiscriminated type stays byte-identical. Add COALESCE(column,'') to the active and draft partial unique indexes and to the declared index, through the existing ADR-0120 D4 probe-then-replace ceremony (partial-index-probe.ts, os migrate duplicates). Backfill pre-fix rows from their body's locale. The repository's write head, delete, history lineage and version counter gain the member; the write door takes it from the body through itemDiscriminator. Cost L: metadata-core object, overlay-index migration, repository, protocol doors, objectql restart pins, a dogfood case. Business need: real and measured. Built-in auth templates ship 6 names x 4 locales (24 members, plugin-email templates/auth-templates.ts at 45e206e). PR #22875 makes sends resolve by (name, locale) from the layered list. The defect was measured on the showcase. Long-term: meets the declared identity structurally, which is what the grammar ruling prescribes; one key across registry, list and store. AI-proofing: the identity is declared once (ITEM_KEY_DISCRIMINATORS) and the store's unique key enforces it; no ungrammatical names; a duplicate is refused loudly by the index. Startup focus: delivers an already-declared capability and adds none; no alias, no staging, no new gate; the cost is a real index migration.",
    "B — Encode the member into the name column (the registry's bundle key, name@locale) and widen METADATA_ITEM_NAME_PATTERN for discriminated types. Cost M, plus a spec change. Business need: met. Long-term: reverses the compound-addressing retirement (commit 311433f, 'never with a separator inside the identity string'). It also persists a key format that metadata-core's docblock explicitly declined to publish even across packages. AI-proofing: worse, because it re-admits separator-bearing identity strings at the door. Startup focus: smaller now, but it is a ruling reversal and leaves debt. Not recommended.",
    "C — Keep one overlay per (type, name) and make it loud. The save door refuses (409, naming the stored member and the remedy, which is to delete that overlay first) a save whose member differs from the stored overlay's, instead of silently replacing it. GET by the bare name serves the canonical member. Cost S. Business need: NOT met, because an environment can customize only one language per template, against the measured pull above. Long-term: narrows the store below what spec declares (a bundle resolved by (name, locale)): a declared capability left unimplemented. AI-proofing: loud, but the spec would still advertise bundles. Startup focus: smallest. Acceptable only as an explicit interim.",
    "D — One overlay row per bundle: the stored body holds every customized member, a stored container like the view containers' expansion. Cost L+: a spec'd container schema, stored-row conversions, and expansion in every row reader (the precedent stored view containers carried known complexity). Business need: met. Long-term: two stored shapes for one type. AI-proofing: a second shape for AI to learn. Startup focus: the largest expansion."
    ],
    "recommendation": "A. It is the only option that meets the measured business need without reversing a ruling: the store comes to meet the declared identity, structurally, which is what the grammar ruling prescribes. It also adds no authorable surface, keeps undiscriminated types byte-identical, and pays for itself with one index migration through an existing ceremony. Pins carried over from the triage: two locales saved, then a restart, each serves its own wording; CONTROL a single-locale save is unchanged; CONTROL a package-shipped bundle is unchanged."
    },
    {
    "question": "Q2 (needed by A, B and D): how do the by-name doors (GET, DELETE, publish, history and revert of /meta/email_template/NAME) address a non-canonical member? Today they take only (type, name), and ?locale= on /meta is already the UI translation locale. Without an address, a second-locale overlay could not be deleted.",
    "options": [
    "i — PUT stays body-addressed (the member comes from the body's locale, as today). The by-name doors accept one member selector in their closed query-parameter set (AGENTS.md route rule 5), named after the discriminator so it cannot be read as the UI locale. Absent, it means the canonical member, which is also what the registry's bare-name read answers. Cost M.",
    "ii — No per-member address. GET by the bare name serves the canonical member; DELETE by the bare name resets the whole bundle's overlays. Cost S. A single member can no longer be reverted on its own.",
    "iii — A path segment per member (/meta/email_template/NAME/locales/TAG). Cost M-L. A new route shape that every door and the route ledger must learn."
    ],
    "recommendation": "i. It keeps the door shape, makes each member individually readable and revertable, and stays a closed, declared parameter with a located 400 when it is wrong. The canonical default keeps the by-name read consistent with the registry."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: public door (getMetaItem / GET /meta/email_template/NAME after en-US then zh-CN saves serves the zh-CN overlay instead of the canonical en-US member, measured in the scratch run at 45e206e) · same family as #22877 — not filed separately: fold into #22877's decision card (Q2 decides the by-name read) · dedupe words: email_template getMetaItem bare name canonical member overlay locale",
    "carrier: the decision card the seat files for #22877 · noted, not filed — translation is overlay-enabled and has a locale key, but its identity is the name alone (not in ITEM_KEY_DISCRIMINATORS), so it does not share this problem; no finding"
    ],
    "gates": [
    {
    "command": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack",
    "exit": 2,
    "note": "empty diff: 'this branch changes nothing … nothing to derive'"
    },
    {
    "command": "dispatch gate list (69 commands, dispatch-gates.txt)",
    "exit": null,
    "note": "NOT RUN, reason: no diff — needs_decision returned before any code"
    },
    {
    "command": "bash scripts/pm/os-verify-lock.sh -c "pnpm --workspace-concurrency=2 --filter '@objectstack/objectql^...' build"",
    "exit": 0,
    "note": "measurement prerequisite, VERDICT command-exit 0"
    },
    {
    "command": "pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/zz-scratch-22877.test.ts",
    "exit": 0,
    "note": "scratch measurement, file deleted, not committed"
    }
    ],
    "line_budget": "0 — no PR, no commit (additions 0 + deletions 0)",
    "deviations": [
    "Returned needs_decision although triage 6111942095 said 'If it fits within the existing row (for example, the registry's own bundle key), build it'. Measured: the registry's bundle key in the row's name is refused by the item-name grammar (ruling commit 311433f), so it does not fit. That leaves the triage's own stop condition. Conflict named here, not resolved silently.",
    "Worktree based on origin/main 45e206e. origin/main had advanced from the PM's 2e11d90 by the time of the fetch, so every reading is cited against 45e206e.",
    "The dispatch gate list (69 commands) was not run, because there is no diff (dispatch-gates --commands on the branch exits 2, 'nothing to derive').",
    "One scratch test file was created in the worktree for the measurement and deleted uncommitted. The objectql dependency closure was built under the verify lock for it."
    ],
    "files_changed": []
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Decision: how an environment overlay keeps two languages of one email template

    domain:engine seat 2 (#20966) · os-steve · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-11T21:09Z. Raised by this card's dev (os-dev-report 6113736478, needs_decision), which hit the triage stop condition (6111942095: "If the only way is a new sys_metadata column or a changed unique key, that is a migration shape. Stop, and file it as a decision with the measurement"). Anchored on this card; the card leaves the active queue until answered. ⛔ Classes, positions and functions only.

    Reader who acts: the maintainer (through the director's batch) picks one letter for Q1 and one numeral for Q2; then the domain:engine lane re-claims this card.

    维护者速读

    • 一句话问题: 管理员在 Studio 里把同一个邮件模板分别存成英文版和中文版,重启后英文版丢了,回到出厂文案。原因是环境覆盖层每个模板名只存一行,第二种语言的保存把第一种覆盖掉了。
    • 为什么要你定: 修法只有两类:要么给覆盖层表加一列并改唯一索引(要走一次索引迁移),要么把语言塞进模板名(而 8 月 25 日你裁过「身份串里不许夹分隔符」,这条路等于推翻那次裁决)。triage 已写明:走到「加列 / 改唯一键」就停下请你裁。
    • 席位意见: Q1 荐 A(加一列存语言,唯一索引带上它;老数据和其它类型逐字节不变),回退 C(先改成响亮拒绝,不再静默覆盖)。Q2 荐 i(按名读、删、发布时可多带一个语言参数,不带即默认英文版),回退 ii。
    • 你要做的: 回一行,例如「[finding] email_template: a Studio save of the same template in a second locale replaces the first locale's overlay at the next boot (sys_metadata keeps one overlay row per type and name) #22877 A + i」。

    Background (measured on origin/main 45e206e498)

    Governing text

    • packages/spec/src/system/email-template.zod.ts (locale): "IETF BCP-47 locale tag — the second half of the bundle key. Multiple rows sharing one name form an i18n bundle".
    • packages/spec/src/shared/identifiers.zod.ts (METADATA_ITEM_NAME_PATTERN): "stage 1 of the maintainer-ruled retirement of compound <section>/<name> addressing, 2026-08-25"; enforced at saveMetaItem / publishMetaItem.
    • packages/metadata-core/src/item-key-discriminators.ts: "Every layer that keys metadata by name therefore has to agree on ONE answer to 'what else is part of this type's identity?'". The same file declined to publish the registry's key encoding across packages.
    • AGENTS.md route rule 5 (a door's query parameters are a closed, declared set) for Q2.
    • Protocol position: the spec already declares (name, locale); the store is the layer that falls short, so no option changes the protocol except B.

    Premises (each with a re-check)

    • P1 (holds): the overlay key has no locale. Re-check: git grep -n "OVERLAY_INDEX_COLUMNS =" origin/main -- packages/metadata-protocol/src/migrations/overlay-index.ts → ['type', 'name', 'organization_id', 'package_id'].
    • P2 (holds): the item-name grammar refuses a separator. Re-check: git grep -n "METADATA_ITEM_NAME_PATTERN = new RegExp" origin/main -- packages/spec/src/shared/identifiers.zod.ts, then read the segment [a-z][a-z0-9_]* above it.
    • P3 (holds): email_template is the only discriminated type. Re-check: git grep -n -A2 "ITEM_KEY_DISCRIMINATORS:" origin/main -- packages/metadata-core/src/item-key-discriminators.ts.
    • P4 (holds): 24 built-in members. Re-check: git show origin/main:packages/plugins/plugin-email/src/templates/auth-templates.ts | grep -c "locale: '" → 24.
    • P5 (holds): ?locale= on /meta already means the UI translation locale. Re-check: git grep -n "?locale=" origin/main -- packages/rest/src/meta-item-read-gate.ts (the [#20320] docblock: Accept-Language, then ?locale=).
    • P6 (the dev's measurement, not re-runnable as committed): the repro readings above.

    Q1 — how the overlay row carries the language

    Option What it does What a customer sees Workload
    A One nullable member column on sys_metadata (NULL = the canonical member, so every existing row and every undiscriminated type stays byte-identical); COALESCE(column,'') joins the active and draft unique indexes through the existing probe-then-replace index ceremony (ADR-0120 D4, os migrate duplicates); pre-fix rows backfilled from their body's locale; the repository's write head, delete, history lineage and version counter carry it; the save door takes it from the body. Each language an admin customizes survives a restart and serves its own wording. A duplicate is refused loudly by the index. domain:engine · 1–2 PRs (metadata-core object, overlay-index migration, repository, protocol doors, restart pins, a dogfood case) · precedent PR #6770 (+1391 / −164, the probe-then-replace ceremony) and PR #11031 (+1071 / −42, the duplicates report)
    B Store the registry's bundle key (name@locale) in name, and widen the item-name grammar for discriminated types. Same survival as A. domain:engine + domain:spec · 1–2 PRs · no precedent; reverses the 2026-08-25 grammar ruling
    C Keep one overlay per (type, name) and make it loud: a save whose language differs from the stored overlay's is refused 409, naming the stored language and the remedy (delete that overlay first); a bare-name read serves the canonical member. An environment can customize only one language per template, but is told so instead of silently losing the first. domain:engine · 1 PR, small · no precedent
    D One overlay row per bundle: the stored body holds every customized member, expanded by every row reader. Same survival as A. domain:engine + domain:spec · 2+ PRs · no precedent (the stored view containers carried known expansion complexity)

    Q2 — how a by-name door addresses one language (needed by A, B and D)

    Option What it does What a customer sees Workload
    i PUT stays body-addressed. GET, DELETE, publish, history and revert by name accept one member selector in their closed query-parameter set, named after the discriminator so it cannot be read as the UI locale; absent = the canonical member, which is what the registry's bare-name read answers; a wrong value is a located 400. Each language can be read, reverted or deleted on its own. domain:engine · rides the Q1 PR or one more · no precedent; widens the doors' accept set, so Clause-②: yes and a contract review
    ii No per-member address: a bare-name GET serves the canonical member; a bare-name DELETE resets the whole bundle's overlays. One language cannot be reverted alone. rides the Q1 PR · no precedent
    iii A path segment per member (/meta/email_template/NAME/locales/TAG). Same as i. domain:engine + domain:cli · a new route shape every door and the route ledger must learn

    业务含义直译: A = 「同一份合同模板,中文版、英文版各存一份,柜子上按『模板名 + 语种』编号」;B = 「把语种写进模板名里」,正是 8 月份明令禁止的编号方式;C = 「一个模板只准改一种语言,改第二种时当面拒绝」;D = 「把所有语种塞进同一个文件夹」。Q2 的 i = 「调某一语种的档案时报上语种」,ii = 「只能整套退回出厂版」。

    四轴(业务立场):

    • 长远合理性: A 让覆盖层和 spec、注册表、列表合并四层用同一个身份 (name, locale),终态即主流平台的建模:Zendesk Guide 的文章译本、Shopify 的 Translations 都以「资源 + 语种」为结构化键。B 推翻已裁的语法;C 让 spec 声明的多语种包在存储层落空,是声明未兑现;D 让一种类型有两种存储形状。
    • 实际业务拉动: 真实且已实测:内置 24 个认证邮件成员;showcase 上实测丢失;PR feat(plugin-email)!: email templates resolve from the registry; the built-in seed, the boot sweep and the live projector retire (ADR-0131 D6/D10, C4 stage 2) #22875 上线后,丢一个语种就是那个语种的邮件回落出厂文案。C 不满足这个拉动。
    • 防 AI 犯错: A 下身份只声明一次(ITEM_KEY_DISCRIMINATORS),唯一索引强制执行,重复保存会被响亮拒绝。B 重新放行身份串里的分隔符,而 8 月份收紧语法,正是因为 /meta 曾接受 'Views/All Leads' 这类拼写。C 拒绝得响亮,但 spec 仍对 AI 宣称支持多语种包。今天的现状最糟:静默覆盖,管理员看不到。
    • 创业阶段不扩散: A 不新增任何可写的元数据键,只兑现已声明的能力,代价是一次走既有流程的索引迁移;Q2-i 新增一个查询参数(放宽接受集),ii 不新增。

    推荐 Q1 = A,回退 C;Q2 = i,回退 ii。 A 与 C 工作量差 ≥3×,但①读数不相近(C 让已声明的能力在存储层落空),所以仍荐 A。置信缺口:按语种逐个撤销(Q2-i 的用途)今天没有实测调用方,Studio 的语种编辑界面是否会用到未核;生产部署里覆盖层 email_template 行数未实测,迁移回填的规模不明。自检:只看① 选 A / i;②③④ 是否翻转:否。

    裁后执行

    • A + i: the lane re-claims this card and dispatches one dev in two commits or two PRs: (1) the member column, the index replacement through the probe-then-replace ceremony, backfill, repository and save door, with the triage's pins (two languages saved, restart, each serves its own wording; CONTROL single-language save unchanged; CONTROL package-shipped bundle unchanged); (2) the by-name member selector, Clause-②: yes, with a contract review before landing. Contract-tier review for the migration either way.
    • A + ii: step (1) only; DELETE by bare name resets every member, pinned.
    • B: a domain:spec card for the grammar widening first (it reverses a ruling, so the maintainer's words are quoted in it); this card then follows A's pins.
    • C: one small PR on this card (the 409 and the canonical bare-name read). The bundle stays declared but unstored, so the lane also files the enforce-or-remove follow-up for the store.
    • D: a domain:spec card for the stored container schema first.
    • PR feat(plugin-email)!: email templates resolve from the registry; the built-in seed, the boot sweep and the live projector retire (ADR-0131 D6/D10, C4 stage 2) #22875 is unaffected under every letter.

    os-decision-facets

    Prior rulings read: email_template overlay locale, sys_metadata unique key discriminator, item-name grammar compound addressing → 8 hits (#7730, #7774, #7932, #9135, #8195, #22005 closed; this card; #16612 off-topic); the 2026-08-25 grammar ruling as cited in identifiers.zod.ts (commit 311433f6b1); ADR-0120 D4; thread: this card (3 comments read).

  5. 26 remaining items

  6. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-12T12:08Z · stage 2 of 2
    Session: session_01ADzJtzYTLUfgrRZHxkagkX
    Account: os-steve (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-22877-member-selector (new: stage 1's branch merged as 5677add93f and is not reused)
    Worktree: objectstack-issue-22877-s2
    Domain: domain:engine
    Seat: domain:engine#2 (seat post #20966); the lane's only seated PM
    Ruling-ref: 6116165299 (letter A, Q2 i). Its workload line places both stages in domain:engine. Seat answers 6118224899; carried items in landing record 6121513183.
    File surface (read on origin/main 0ede0f8ab3; stop on a breach and explain it in the report). ⛔ Classes, positions and functions only, in every artefact.

  7. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22877,
    "status": "done",
    "branch": "claude/issue-22877-member-selector",
    "pr": "#23070",
    "session": "session_01ADzJtzYTLUfgrRZHxkagkX (subagent of the domain:engine seat 2 PM; the parent's id)",
    "premise_still_valid": true,
    "summary": "Stage 2 of ruling 6116165299 (A, Q2 i) delivered as draft PR #23070 (Fixes #22877, Clause-② yes, head 2b882e6, +1375/-105, 14 files). The five by-name /meta doors (GET, DELETE, /history, /publish, /rollback) accept ONE bundle member through member (the stored column's concept, never locale). The protocol reads it once (resolveBundleMemberSelector: absent = canonical; a selector on a type with no discriminator, a non-string, a blank or a value over the column's 100 characters is a located 400 VALIDATION_ERROR). Each REST route now opens with a measured closed query set, and the four spec requests (Get/Delete/Publish/HistoryMetaItemRequest) gain an optional member; rollback has no declared request. The carried items are built: SchemaRegistry.getItem's bare-name read answers the canonical member across tiers; removeRuntimeShadow/removeOverlayEntry and the protocol heal address one member; publishPackageDrafts, discardPackageDrafts, revertCommit and the cluster fan-out (MetadataMutationEvent.member) carry it. The showcase dogfood found one defect in the first cut — a hydrated overlay of a member the package does not ship wears the bundle's artifact envelope, so the heal left it serving until restart — fixed (a member's shadow is lifted while the bundle ships any artifact) and pinned in the objectql harness with a versioned package. Measured absent-member answers: GET 404 (item undefined), DELETE reset:false, publish 404 NO_DRAFT, history events [], revert 404 VERSION_NOT_FOUND.",
    "tests": "All heavy runs under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 each); exit codes captured before any pipe. objectql full: 'Test Files 407 passed (407)', 'Tests 7955 passed' (before the last registry commit 2b882e6); after it, the 13 objectql files naming removeRuntimeShadow/removeOverlayEntry/email_template '246 passed' and restart harness + registry bundle file '40 passed'. metadata-protocol full at 12b4b50: 'Test Files 228 passed | 3 skipped (231)', 'Tests 28196 passed | 19 skipped'. rest full at 00886e1 (the main merge touched no rest file): 'Test Files 279 passed (279)', 'Tests 5960 passed | 327 skipped'; after the merge 6 targeted rest files '141 passed'. spec protocol.test.ts + type-alias-convention.pin.test.ts '187 passed'. typecheck exit 0: spec, metadata-protocol, objectql (+check:test-typecheck), rest (+check:test-typecheck), dogfood. dogfood email-template-two-locales-survive-boot.dogfood.test.ts '4 passed (4)' after objectql rebuilt at 2b882e6 (also measures that the showcase's /meta item read is RestServer's route). eslint --no-inline-config --format json over the 11 changed .ts files: 11 linted, 0 errors, 0 warnings; population = eslint.config.mjs files ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] minus NEVER_LINTED; no type-aware linting (no parserOptions.project), so untouched files' verdicts cannot move. Ablations (scripts/ablation-replace.mjs wrap mode on committed HEAD fdf01eb: anchor 1->0, blob changed, restored blob == HEAD, git diff HEAD empty; metadata-protocol rebuilt with ablation-dist-preflight present then --absent): (1) rollbackMetaItem member dropped (marker __ABLATION_22877_ROLLBACK_MEMBER_DROPPED): harness '1 failed | 17 passed', only the member-revert pin red ('No history row at version 2 ... (its canonical member)'), bare-name revert CONTROL green; restored '18 passed'. (2) registry tier order reverted (__ABLATION_22877_REGISTRY_TIER_ORDER, objectql src): '1 failed | 38 passed', only the bare-name canonical registry pin red (the harness's protocol-level bare-name pins stay green because getMetaItem answers the canonical artifact before the registry); restored '39 passed'. (3) heal member dropped: '3 failed | 15 passed' (DELETE by member, DELETE reverse, cluster member reset); restored '18 passed'. (4) batch publish member dropped: batch answered refused / NO_DRAFT 'No pending draft exists for email_template/acme.welcome — nothing to publish.'; batch discard member dropped: discard answered success while the zh-CN draft stayed; both restored, dist absent.",
    "mcp_calls": "0 — no MCP GitHub tool called",
    "api_writes": "3 REST writes in 2 relay dispatches (objectstack-fleet[bot]): POST /repos/objectstack-ai/objectstack/pulls (draft #23070) + POST /repos//issues/23070/assignees (os-steve) as one pr_create action, run 38273402759, body read back byte-identical (19409 sent = 19409 stored); POST /repos//issues/22877/comments (this os-dev-report, scripts/pm/post-stamped.mjs). Not REST: git push of the branch (the empty landing marker, then commits and one merge of main). Zero label writes (the dispatch forbade them). REST reads with the session's gh: issues/22877 and its comments, pulls/23070.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: 承接者:无 (the runtime lane) · noted in the PR's acceptance notes, not filed — packages/runtime/src/domains/meta.ts (the runtime dispatcher's /meta item read) does not read ?member=: a host served by the dispatcher alone would answer GET ...?member=zh-CN with the canonical member, since that transport has no closed query set at all. Outside the claim's surface; not measured on a dispatcher-only host (the showcase is served by RestServer, measured by the dogfood case). dedupe words: dispatcher meta member selector; runtime domains meta query; member selector transport parity",
    "carrier: 承接者:无 · noted, not filed — GET /meta/_drafts and GET /packages/:id/commits keep their declared shapes and do not serve member (the ruling widened the doors' accept set, not these responses), so two drafts or plan items of one template in two languages read as two entries with the same type and name; Studio's DraftChangesPanel would fetch the canonical body for both (not measured in a browser). dedupe words: drafts list bundle member; commit timeline member; DraftChangesPanel locale",
    "carrier: 承接者:无 · noted — the by-name doors outside the ruling's list (/diff, /layers, /audit, /published, /references) stay canonical-only; the deprecated ?layers= spelling refuses ?member= with a 400 rather than answering the canonical member's layers.",
    "carrier: 承接者:无 · noted — the MetadataService (MetadataManager) keys a type's items by name alone, so for a bundle it holds one member (the last registered); getMetaItem and the registry heal now accept its answer only when it is the addressed member. Pre-existing; the service is unchanged.",
    "carrier: 承接者:无 · noted — the selector's VALUE refusal is raised by the protocol and rendered by the REST door's one error door (handleRouteError) in the flat position (body.code VALIDATION_ERROR, body.error the sentence), while the route's own recognition refusal of a misspelled name is nested (body.error.code); the existing split check:route-envelope ratchets repo-wide, same code on both."
    ],
    "gates": [
    "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
    "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
    "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
    "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
    "node scripts/check-ci-filter-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-adoption.mjs :: exit 0",
    "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-corpus.mjs :: exit 0",
    "node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
    "node scripts/check-doc-frontmatter.mjs :: exit 0",
    "node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
    "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
    "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
    "node scripts/check-docs-section-name.mjs :: exit 0",
    "node scripts/check-docs-section-name.mjs --self-test :: exit 0",
    "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
    "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
    "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
    "node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
    "node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
    "node scripts/check-issue-citations.mjs :: exit 0",
    "node scripts/check-keyed-text-bounds.mjs :: exit 0",
    "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
    "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
    "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
    "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
    "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
    "node scripts/check-registry-log-declared.mjs :: exit 0",
    "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
    "node scripts/check-rest-log-declared.mjs :: exit 0",
    "node scripts/check-rest-log-declared.mjs --self-test :: exit 0",
    "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
    "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
    "node scripts/check-section-landing-index.mjs :: exit 0",
    "node scripts/check-section-landing-index.mjs --self-test :: exit 0",
    "node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0",
    "node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0",
    "node scripts/check-system-context-census.mjs :: exit 0",
    "node scripts/check-system-context-census.mjs --self-test :: exit 0",
    "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
    "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
    "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
    "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
    "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
    "node scripts/release-pending-publish.mjs --self-test :: exit 0",
    "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
    "pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
    "pnpm --filter @objectstack/spec run check:api-surface :: exit 0",
    "pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0",
    "pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0",
    "pnpm --filter @objectstack/spec run check:docs :: exit 0",
    "pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0",
    "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
    "pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
    "pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0",
    "pnpm --filter @objectstack/spec run check:export-origins :: exit 0",
    "pnpm --filter @objectstack/spec run check:exported-any :: exit 0",
    "pnpm --filter @objectstack/spec run check:generated :: exit 0",
    "pnpm --filter @objectstack/spec run check:liveness :: exit 0",
    "pnpm --filter @objectstack/spec run check:llms-txt :: exit 0",
    "pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0",
    "pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
    "pnpm --filter @objectstack/spec run check:skill-refs :: exit 0",
    "pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
    "pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
    "pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
    "pnpm check:authz-resolver :: exit 0",
    "pnpm check:changeset-gate-self-tests :: exit 0",
    "pnpm check:corpus-claim-drift :: exit 0",
    "pnpm check:cross-package-test-inputs :: exit 0",
    "pnpm check:dispatcher-error-vocabulary :: exit 0",
    "pnpm check:doc-anchors :: exit 0",
    "pnpm check:doc-authoring :: exit 0",
    "pnpm check:docs-audit-scope :: exit 0",
    "pnpm check:docs-redirects :: exit 0",
    "pnpm check:docs-single-h1 :: exit 0",
    "pnpm check:docs-spec-enumerations :: exit 0",
    "pnpm check:docs-transcript-drift :: exit 0",
    "pnpm check:driver-memory-census :: exit 0",
    "pnpm check:dts-closure :: exit 0",
    "pnpm check:dual-build-cjs-loads :: exit 0",
    "pnpm check:durability-log-level :: exit 0",
    "pnpm check:engine-double-contract :: exit 0",
    "pnpm check:error-status-conformance :: exit 0",
    "pnpm check:filter-alias-parity :: exit 0",
    "pnpm check:gitlink-declared :: exit 0",
    "pnpm check:issue-citations :: exit 0",
    "pnpm check:lean-entry-closure :: exit 0",
    "pnpm check:logger-receiver-detach :: exit 0",
    "pnpm check:merge-driver :: exit 0",
    "pnpm check:nul-bytes :: exit 0",
    "pnpm check:objectql-double-limit :: exit 0",
    "pnpm check:objectui-changeset :: exit 0",
    "pnpm check:org-identifier :: exit 0",
    "pnpm check:page-declaration-shape :: exit 0",
    "pnpm check:pm-changeset-deadline-census :: exit 0",
    "pnpm check:pm-prior-rulings :: exit 0",
    "pnpm check:published-files :: exit 0",
    "pnpm check:published-readme-links :: exit 0",
    "pnpm check:query-options-erasure :: exit 0",
    "pnpm check:quick-reference-counts :: exit 0",
    "pnpm check:react-page-adapter-contract :: exit 0",
    "pnpm check:refd-timer-probe :: exit 0",
    "pnpm check:role-word :: exit 0",
    "pnpm check:route-envelope :: exit 0",
    "pnpm check:skill-identifier-liveness :: exit 0",
    "pnpm check:slot-lookup :: exit 0",
    "pnpm check:sourcemap-no-sources-content :: exit 0",
    "pnpm check:spec-parsed-alias :: exit 0",
    "pnpm check:test-source-alias :: exit 0",
    "pnpm check:tier-file-adoption :: exit 0",
    "pnpm check:type-check-coverage :: exit 0",
    "pnpm check:type-check-debt :: exit 0",
    "pnpm check:vendor-version-stamps :: exit 0",
    "pnpm check:watch-hint-literal :: exit 0",
    "pnpm check:where-matcher :: exit 0"
    ],
    "gates_reconciliation": "node scripts/pm/dispatch-gates.mjs --ran (exit codes recorded) at 2b882e6: 118 derived, 118 run, 0 NOT-MEASURED, 0 UNRUN; the lead list (86 at 0ede0f8) is a subset. An earlier round at 12b4b50 had check:skill-examples and check:dual-build-cjs-loads at exit 3 (PREREQUISITE NOT MET); after a full packages build (turbo 72 tasks, exit 0) both pass and the final round has them at exit 0.",
    "line_budget": "+1375 / -105 = 1480 changed lines in 14 files (PR #23070 as GitHub counts it); within the ruled 1-1.5k estimate (precedents PR #6770, PR #11031); under the 3,000-line Tier H threshold, no split.",
    "deviations": [
    "File surface: all edits are inside the claim's listed files, plus protocol.lookup-artifact-item-call-sites.test.ts (its census records the re-scoped artifact-lookup site and the definition's new member parameter) and two regenerated artifacts (packages/spec/authorable-surface/api.json, content/docs/references/api/protocol.mdx).",
    "removeRuntimeShadow with a named member keeps the artifact test at the level of the NAME (any member's artifact), not the member — found by the showcase dogfood (a hydrated overlay of an unshipped member wears the bundle's artifact envelope, so removeOverlayEntry refuses it as code); pinned in the objectql harness, whose package is now versioned so its artifacts carry the envelope.",
    "Response contracts unchanged by choice: the repository's draft headers and the stored commit plan carry member, and the protocol's listDrafts/listCommits serve their declared keys only (see out_of_scope_findings 2).",
    "No RollbackMetaItemRequestSchema was added: none exists in packages/spec, and the REST door calls rollbackMetaItem through a cast; the field was added only where the protocol request is declared.",
    "Test-suite heads: objectql full ran before the last registry commit (then targeted reruns at 2b882e6); rest full ran pre-merge at 00886e1 (the merge touched no rest file; targeted rest reruns after); metadata-protocol full at the merge 12b4b50. CI runs the full set on the PR.",
    "Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer; AGENTS.md (the user's instruction, enforced by the pre-push hook) requires the model-free trailer pair and the session-URL footer, so those were used.",
    "PR body line 3 states what widens (the dispatch asked for a line saying what widens after the Clause-② line)."
    ],
    "files_changed": [
    ".changeset/22877-member-selector.md",
    "content/docs/references/api/protocol.mdx",
    "packages/metadata-protocol/src/protocol.bundle-member-selector.test.ts",
    "packages/metadata-protocol/src/protocol.lookup-artifact-item-call-sites.test.ts",
    "packages/metadata-protocol/src/protocol.ts",
    "packages/metadata-protocol/src/sys-metadata-repository.ts",
    "packages/objectql/src/environment-overlay-email-locale-restart.test.ts",
    "packages/objectql/src/registry-i18n-bundle-key.test.ts",
    "packages/objectql/src/registry.ts",
    "packages/qa/dogfood/test/email-template-two-locales-survive-boot.dogfood.test.ts",
    "packages/rest/src/rest-server-meta-member-selector.test.ts",
    "packages/rest/src/rest-server.ts",
    "packages/spec/authorable-surface/api.json",
    "packages/spec/src/api/protocol.zod.ts"
    ]
    }

  8. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat note: PR #23070's contract review is FAIL on 2b882e6774, and patch round 1 is asked · domain:engine seat 2 · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T16:13Z

    This concerns report 6127796890 (stage 2). The record is 6128093700 on the PR. The seat re-read its blocking finding.

    Blocking, in ②. @objectstack/objectql is graded patch. But the exported SchemaRegistry gains an optional member argument on public methods, and the WHICH LEVEL rule (batch #35 on #15294) grades an additive widening at least minor. The PR body's Widens: line also omits three additive items:

    • MetadataMutationEvent.member;
    • listDrafts' member header;
    • the registry methods' argument.

    The dev is asked to fix the changeset level and the PR body. No code change is asked. A delta contract review then runs on the new head.

    Holding at this head, per the record's ①:

    • the name member (the discriminator field is locale, the forbidden one);
    • the five closed query sets, re-measured from each handler's read points;
    • the four located 400s;
    • the four spec request fields, with the regenerated surface;
    • the registry's canonical bare read;
    • removeRuntimeShadow at name level;
    • the package-batch doors and the cluster event;
    • the answers for an absent member.

    Follow-ups, filed at this PR's ACCEPT (the out-of-scope findings, two families):

    1. The member selector's transport parity:
      • the runtime dispatcher's /meta item GET neither declares a closed set nor forwards ?member=;
      • PUT /meta/:type/:name declares no closed set, so ?member= there is dropped silently;
      • the remaining by-name doors (/diff, /layers, /audit, /published, /references) have no closed sets either.
    2. The drafts and commits lists, and Studio and client member addressing: two drafts of one name in two languages read as identical entries.
  9. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22877,
    "status": "done",
    "round": "patch round 1",
    "branch": "claude/issue-22877-member-selector",
    "pr": "#23070",
    "new_head": "2c055c088f",
    "session": "session_01ADzJtzYTLUfgrRZHxkagkX (subagent of the domain:engine seat 2 PM; the parent's id)",
    "premise_still_valid": true,
    "summary": "Patch round 1 on PR #23070 answers contract review 6128093700 (FAIL on 2b882e6). The new head is 2c055c0: one fast-forward commit on 2b882e6, with no rebase, amend, force-push or main merge. The PR is not conflicting: GitHub answered mergeable true, state blocked, on this head's first read, and unknown on a later read while it recomputed. (1) The blocking ② finding is fixed in .changeset/22877-member-selector.md. @objectstack/objectql goes from patch to minor. The changeset gains a section, 'For code that uses these packages directly', which says what a consumer can now pass or read: SchemaRegistry's optional last argument member on getItem, getArtifactItem, removeRuntimeShadow and removeOverlayEntry, including what each answers and getItem's bare-name canonical answer; the optional MetadataMutationEvent.member and the ClusterMetadataMutationPayload that carries it; and the optional member on SysMetadataRepository.listDrafts entries, which GET /meta/_drafts does not serve. (2) The PR body's Widens: line was NOT patched by this dev. .claude/agents/os-dev.md says the dev writes the PR body once, at pr_create, never PATCHes it, and names later edits in its report for the seat to write. That conflicts with the round's instruction ('the PR body update through the relay'), and the role file wins. The replacement body is ready, see deviations. (3) CI: no shard has gone red on either head. On 2b882e6, Test Core 1/6 and 5/6 concluded success; 2/6, 3/6, 4/6 and 6/6 were cancelled by this round's push, none red. On 2c055c0, at this report: Test Core 1/6, 5/6 and 6/6 success; 2/6, 3/6 and 4/6 in_progress; every other check success or skipped (Lint & Repo Gates, TypeScript Type Check, Build Core, Check Changeset, Governed Surface Queue Guard included). Code is unchanged, so no test was re-run.",
    "fix_chosen": {
    "changeset_level": "@objectstack/objectql: patch -> minor (an additive widening of SchemaRegistry, which the package index exports; the WHICH LEVEL rule grades it at least minor)",
    "changeset_text": "a section naming each additive public surface and what a consumer can pass or read: SchemaRegistry's member argument (four methods), MetadataMutationEvent.member, and the SysMetadataRepository.listDrafts member header",
    "pr_body": "prepared, not written (role-file rule); see deviations"
    },
    "tests": "No code changed this round, so no suite or ablation was re-run; round 0's records stand (os-dev-report 6127796890). Gate union re-derived on the new head: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 2c055c0 gives 118 commands, the same set as round 0; all 118 exit 0. Reconciled with --ran (exit codes recorded): '118 derived, 118 run, 0 NOT-MEASURED, 0 UNRUN'. The changeset gates are included: check-changeset-no-major --base origin/main and --self-test, check-empty-changeset, check-adr-0087-registration, check:pm-changeset-deadline-census, check:changeset-gate-self-tests. The worktree was recreated, with pnpm install --frozen-lockfile and a full packages build under the lock (turbo, 72 tasks, exit 0, cache-replayed), so the dist-reading gates read built output. CI 'Check Changeset' is success on 2c055c0.",
    "ci": {
    "head_2c055c088f": "Test Core (1/6), (5/6) and (6/6) success; (2/6), (3/6) and (4/6) in_progress at this report; every other check success, or skipped (Console Pin Gate, Temporal Conformance, Dogfood Verify CLI, Packed-tarball smoke, the Dogfood Regression Gate matrix shard)",
    "head_2b882e6774": "Test Core (1/6) and (5/6) success; (2/6), (3/6), (4/6) and (6/6) cancelled by this round's push; nothing red"
    },
    "mcp_calls": "0 — no MCP GitHub tool called",
    "api_writes": "1 REST write: POST /repos//issues/22877/comments (this os-dev-report, scripts/pm/post-stamped.mjs, through the relay). Not REST: git push of one fast-forward commit (2b882e6..2c055c0). No PR-body PATCH (see deviations), no label writes. REST reads with the session's gh: comment 6128093700, check-runs on both heads, pulls/23070.",
    "open_questions": [],
    "out_of_scope_findings": [
    "The review's escalations are the seat's to file, as the round said, and none was built here: the runtime dispatcher's ?member=, the remaining by-name doors' closed sets (PUT included), the drafts and commits lists with Studio and the client SDK, and the envelope graft that makes isCodeArtifactBody misread an unshipped member's overlay."
    ],
    "gates": [
    "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
    "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
    "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
    "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
    "node scripts/check-ci-filter-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-adoption.mjs :: exit 0",
    "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-corpus.mjs :: exit 0",
    "node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
    "node scripts/check-doc-frontmatter.mjs :: exit 0",
    "node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
    "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
    "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
    "node scripts/check-docs-section-name.mjs :: exit 0",
    "node scripts/check-docs-section-name.mjs --self-test :: exit 0",
    "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
    "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
    "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
    "node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
    "node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
    "node scripts/check-issue-citations.mjs :: exit 0",
    "node scripts/check-keyed-text-bounds.mjs :: exit 0",
    "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
    "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
    "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
    "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
    "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
    "node scripts/check-registry-log-declared.mjs :: exit 0",
    "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
    "node scripts/check-rest-log-declared.mjs :: exit 0",
    "node scripts/check-rest-log-declared.mjs --self-test :: exit 0",
    "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
    "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
    "node scripts/check-section-landing-index.mjs :: exit 0",
    "node scripts/check-section-landing-index.mjs --self-test :: exit 0",
    "node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0",
    "node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0",
    "node scripts/check-system-context-census.mjs :: exit 0",
    "node scripts/check-system-context-census.mjs --self-test :: exit 0",
    "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
    "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
    "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
    "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
    "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
    "node scripts/release-pending-publish.mjs --self-test :: exit 0",
    "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
    "pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
    "pnpm --filter @objectstack/spec run check:api-surface :: exit 0",
    "pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0",
    "pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0",
    "pnpm --filter @objectstack/spec run check:docs :: exit 0",
    "pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0",
    "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
    "pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
    "pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0",
    "pnpm --filter @objectstack/spec run check:export-origins :: exit 0",
    "pnpm --filter @objectstack/spec run check:exported-any :: exit 0",
    "pnpm --filter @objectstack/spec run check:generated :: exit 0",
    "pnpm --filter @objectstack/spec run check:liveness :: exit 0",
    "pnpm --filter @objectstack/spec run check:llms-txt :: exit 0",
    "pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0",
    "pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
    "pnpm --filter @objectstack/spec run check:skill-refs :: exit 0",
    "pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
    "pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
    "pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
    "pnpm check:authz-resolver :: exit 0",
    "pnpm check:changeset-gate-self-tests :: exit 0",
    "pnpm check:corpus-claim-drift :: exit 0",
    "pnpm check:cross-package-test-inputs :: exit 0",
    "pnpm check:dispatcher-error-vocabulary :: exit 0",
    "pnpm check:doc-anchors :: exit 0",
    "pnpm check:doc-authoring :: exit 0",
    "pnpm check:docs-audit-scope :: exit 0",
    "pnpm check:docs-redirects :: exit 0",
    "pnpm check:docs-single-h1 :: exit 0",
    "pnpm check:docs-spec-enumerations :: exit 0",
    "pnpm check:docs-transcript-drift :: exit 0",
    "pnpm check:driver-memory-census :: exit 0",
    "pnpm check:dts-closure :: exit 0",
    "pnpm check:dual-build-cjs-loads :: exit 0",
    "pnpm check:durability-log-level :: exit 0",
    "pnpm check:engine-double-contract :: exit 0",
    "pnpm check:error-status-conformance :: exit 0",
    "pnpm check:filter-alias-parity :: exit 0",
    "pnpm check:gitlink-declared :: exit 0",
    "pnpm check:issue-citations :: exit 0",
    "pnpm check:lean-entry-closure :: exit 0",
    "pnpm check:logger-receiver-detach :: exit 0",
    "pnpm check:merge-driver :: exit 0",
    "pnpm check:nul-bytes :: exit 0",
    "pnpm check:objectql-double-limit :: exit 0",
    "pnpm check:objectui-changeset :: exit 0",
    "pnpm check:org-identifier :: exit 0",
    "pnpm check:page-declaration-shape :: exit 0",
    "pnpm check:pm-changeset-deadline-census :: exit 0",
    "pnpm check:pm-prior-rulings :: exit 0",
    "pnpm check:published-files :: exit 0",
    "pnpm check:published-readme-links :: exit 0",
    "pnpm check:query-options-erasure :: exit 0",
    "pnpm check:quick-reference-counts :: exit 0",
    "pnpm check:react-page-adapter-contract :: exit 0",
    "pnpm check:refd-timer-probe :: exit 0",
    "pnpm check:role-word :: exit 0",
    "pnpm check:route-envelope :: exit 0",
    "pnpm check:skill-identifier-liveness :: exit 0",
    "pnpm check:slot-lookup :: exit 0",
    "pnpm check:sourcemap-no-sources-content :: exit 0",
    "pnpm check:spec-parsed-alias :: exit 0",
    "pnpm check:test-source-alias :: exit 0",
    "pnpm check:tier-file-adoption :: exit 0",
    "pnpm check:type-check-coverage :: exit 0",
    "pnpm check:type-check-debt :: exit 0",
    "pnpm check:vendor-version-stamps :: exit 0",
    "pnpm check:watch-hint-literal :: exit 0",
    "pnpm check:where-matcher :: exit 0"
    ],
    "line_budget": "This round: 1 file, +11/-1 against 2b882e6 (.changeset/22877-member-selector.md). The PR on 2c055c0 against its merge-base: 14 files, +1385/-105 = 1490 changed lines, still under the 3,000-line Tier H threshold.",
    "deviations": [
    "PR body not edited by the dev. .claude/agents/os-dev.md: 'PR 正文 dev 只写一次,在开 PR 那一笔,⛔ 不 PATCH;事后要改的报告点名改法,席位代写。' The round asked for 'the PR body update through the relay', and the role file wins on a conflict. For the seat to write, two edits, prepared in full at the scratch path below. (a) Replace line 3 with: 'Widens: the accept set of the five by-name /meta doors (GET, DELETE, /history, /publish, /rollback on /meta/:type/:name) by one query parameter, member, and the four protocol requests behind them (GetMetaItemRequest, DeleteMetaItemRequest, PublishMetaItemRequest, HistoryMetaItemRequest) by one optional field, member; and three exported surfaces, each by one optional member: the last argument of SchemaRegistry.getItem, getArtifactItem, removeRuntimeShadow and removeOverlayEntry (@objectstack/objectql), the MetadataMutationEvent field (@objectstack/metadata-protocol, also carried on the ClusterMetadataMutationPayload a peer accepts), and the header on each SysMetadataRepository.listDrafts entry.' Lines 1 and 2 are unchanged. (b) Optional, from the review's ① 3: under 'The value refusal's envelope', add the bullet 'The ?layers= plus ?member= refusal is raised by the GET handler itself, not by the protocol. It is thrown into the same handleRouteError, so it is flat too; that is the handler's only pre-existing shape for a 400 of its own, so it mints no new dialect.' The full body with both edits, built from the stored body (contains no less-than character), is at /tmp/claude-0/-home-user-objectstack/99494cdf-a0fd-56b5-adae-98900c5dd071/scratchpad/issue-22877b/s2/r1/pr-body-r1.md. Its evidence section still describes head 2b882e6, where every code-bearing measurement was taken.",
    "The worktree removed after round 0 was recreated at ../objectstack-issue-22877-s2 on the existing local branch, which matched origin at 2b882e6."
    ],
    "files_changed": [
    ".changeset/22877-member-selector.md"
    ]
    }

  10. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #23070 at 2c055c088f (stage 2 of 2) · domain:engine seat 2 · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T16:55Z

    Basis:

    • the maintainer's ruling 6116165299 (A, Q2 i) binds;
    • the seat answers 6118224899;
    • stage 1's landing record 6121513183;
    • claim 6123966682;
    • reports 6127796890 and 6128562834 (patch round 1).

    Everything below was checked on GitHub, not taken from the reports. Classes, positions and functions only.

    Landing: prechecks pass:

    • every check is green;
    • merge-tree is clean against origin/main;
    • not governed;
    • 1,490 lines.

    The PR is readied and armed through the relay in this act. When it merges, Fixes #22877 closes the card.

  11. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: stage 2, PR #23070 → 038d4c9588 · domain:engine seat 2 · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T17:25Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:i18nThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingdomain:enginepriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions