Skip to content

metadata-protocol: a same-named item in a dependent package replaces the base package's item for package-less reads and in the base app #28238

Closed

Description

@objectstack-fleet

epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848

Filing gate: ① reproducible defect · reach: public HTTP reads and the console app, measured once with the wrong result (below). ⛔ Not a claim.

Part of #28236 (P0, child N-03) · lane: domain:server (packages/metadata-protocol) · size: M.

Governing text — read before ADR-0146. ADR-0146 on main (cdbf08d1) predates this epic's re-plan and is being rewritten in place by #28237. Its D2 (in-place overlay editing of a read-only package's item in Studio, the "customized" chip, Reset to package default), its D3 (<base>.customize; New and Clone landing there) and its Phase 2 rollout are superseded — ⛔ do not implement them on this card. Still standing: D1 (the catalog and the item tools belong to Studio), D4's ledger switch (#28148), D5, D7. Where the ADR and this card or epic #28236 disagree, the epic's rulings win.

Scope note (2026-10-31) — unbound overlays keep working. A stored row with no package (the ADR-0005 overlay Setup writes today) still wins over the code package on a package-less read; only a row bound to a different package is excluded. ⛔ Do not break Setup's existing customizations; pin both cases.

Business reason. Any installed package that depends on another can silently change the other's views by shipping the same name, with no declaration and no marker, and the vendor's own app renders it. Authors and users then see different things: Studio, which reads with the base package, shows the base; users see the dependent package's copy.

Measured on a local examples/app-showcase boot at d6c0a75f1a (2026-10-30), single posture:

  1. Writable package com.default.acme-custom with dependencies: { "com.example.showcase": "*" } (without the dependency the publish is refused, list-view-object-outside-closure).
  2. PUT /api/v1/meta/view/showcase_task.default?mode=draft&package=com.default.acme-custom with the base body, config.columns = title, status; POST …/publish?package=com.default.acme-custom → 200.
  3. ?package=com.example.showcase → 6 columns · ?package=com.default.acme-custom → 2 · no package → 2 (_packageId: com.example.showcase) · /_console/apps/com.example.showcase/showcase_task renders 2.
    Cleanup DELETE …?package=com.default.acme-custom restores 6.

Where. protocol-environment-rows.ts#packagelessAddress: a package-less read whose stored rows bind exactly one package serves that package's row; the code package that ships the item is not a candidate. Reached through protocol.ts packagelessReadAddress → getMetaItem / getMetaItemLayered; the list side merges through servedOverlayRowCandidates (list path not re-measured).

Wanted.

  1. The code package that ships the name is a candidate of the package-less address; a stored row bound to another package never wins over it by being the only stored row.
  2. The package-less read, the read naming the base, the layered read and the base app's list agree (pin test across the three reads + the list).
  3. No change to declared overrides — that rule arrives with the epic's override core; this card only stops the undeclared leak.

Acceptance. The three steps above answer 6 / 2 / 6 and the app renders 6; existing protocol.item-read-overlay-cache.test.ts and meta-overlay-cache suites stay green.

Dedupe. Related, not duplicates: #22057 (closed; a read naming a package served another package's row on an unscoped kernel), #23291 (closed; a package-less read kept serving the unbound row). Searched open+closed package-less read overlay another package: 74 hits, none with this shape.

Links back: #28236, ADR-0048 §3.3/§3.4.


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Oct 30, 2026
  2. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

    Claim: PM loop round 1 · card thread · 2026-10-31T03:04Z
    Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01FEkQ5nQHuymasPwjX3mAr2
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-boxqo3 (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#2
    Ruling-ref: none. The card's own "Wanted" 1–3 and "Acceptance" are carried verbatim in the order. It is a sub-issue of #28236 (N-03) and inherits the parent's first touch; "No change to declared overrides" is in force.
    File surface: packages/metadata-protocol/src/protocol-environment-rows.ts (packagelessAddress) and its read callers (packagelessReadAddress, servedOverlayRowCandidates on the list side), tests, and changesets. protocol.ts only if unavoidable, re-reading the #25467 split's open PRs first. ⛔ No package-less write address change: a fork if the read needs one.
    Container & model: M, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: high
    Clause-②: no (expected: a package-less read serves the base package's row instead of an undeclared dependent's; nothing is refused or newly accepted; the dev measures and declares)
    Metadata-protocol: none (expected semantic or none; the dev measures against the AGENTS.md level table and declares)
    Responsibility: the code is packagelessAddress's candidate set (metadata-protocol) · no platform path covers it today (#22057 and #23291 closed other shapes) · reached by public HTTP package-less reads and the console app of any installed package with a dependent writable package (measured on the card)
    Thread-read: none
    Premise: holds (read 2026-10-31T03:04Z at origin/main 91fa06df8: protocol-environment-rows.ts L133 packagelessAddress present; the card's repro is dated 10-30 at d6c0a75f1a); the dev re-measures first
    Serial constraints cleared: open PR #28188 touches only protocol.meta-types-degenerate-derivation.test.ts, disjoint. The #25467 protocol.ts split has no open PR at this read. The sibling claim #27884 is on metadata-redaction.ts and protocol-datasource-credentials-ref.ts, a disjoint file set in the same package; the later lander merges main
    Resume-from: none — fresh from origin/main

    State: pm:queue → pm:dispatched, assigned to os-project-manager (label write at 02:59Z), recorded by this claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-project-manager · session_01FEkQ5nQHuymasPwjX3mAr2

    os-dev-report
    {
    "issue": 28238,
    "status": "needs_decision",
    "branch": "claude/project-thread-boxqo3",
    "pr": "#28304",
    "session": "session_01FEkQ5nQHuymasPwjX3mAr2",
    "premise_still_valid": true,
    "summary": "The by-name half is fixed and pinned: a read naming no package (getMetaItem) and the layered read's effective now take their stored content from packagelessContentPackage (protocol-environment-rows.ts), where the code package that ships the name is a candidate, so a dependent package's same-named row is not served for it; the package-unbound row still wins over the code item, and packagelessAddress (the write address, the read's version) does not move. Live on app-showcase the card's steps answer 7/2/7 (base 7/2/2; the showcase list has 7 columns now, not 6) and ?layers=true effective 7 (base 2). The app still renders 2 (Title, Status) on both base and head: the console reads views only from the list naming no package and keys them by name, last wins, and that list keeps one slot per package that holds the name (#21934), the dependent slot wearing the base's _packageId. Making the app render the base needs a list rule or a console merge rule, which is the open question; the PR therefore says Part of #28238, not Fixes.",
    "tests": "New packages/objectql/src/protocol-packageless-read-dependent-package.test.ts (real ObjectQL + SchemaRegistry + in-memory driver; dependent package installed through installPackage with dependencies declared; its row written by draft save + per-item publish naming it): 5 cases. Red on base 69ce01d (production files at base blobs, metadata-protocol rebuilt): 3 failed | 1 passed of the 4 cases then, AssertionError expected [ 'title', 'status' ] to deeply equal [ Array(6) ]. Head: Tests 5 passed (5). Ablation via scripts/ablation-replace.mjs on protocol-environment-rows.ts (one-shipper answer back to address.packageId, marker ABL28238): anchor 1 to 0, blob 1a530197d936 to 992e059d588a, ablation-dist-preflight hit dist/index.js and dist/index.cjs, Tests 3 failed | 2 passed (5); restored blob == HEAD, git diff HEAD empty; restore leg rebuilt, preflight --absent (24 files), Tests 5 passed (5). Suites at head: metadata-protocol Test Files 284 passed | 4 skipped (288), Tests 21860 passed (includes protocol.item-read-overlay-cache, meta-overlay-cache, protocol.packageless-save-address); objectql --project local Test Files 501 passed (501), Tests 9755 passed; typecheck objectql + metadata-protocol exit 0; eslint --no-inline-config on packages/metadata-protocol and the new test exit 0. Live showcase (pnpm dev -- --fresh, HTTP + Playwright on /_console/apps/com.example.showcase/showcase_task): base 7/2/2, layered effective 2, render [Title, Status]; head 7/2/7, layered 7, render [Title, Status]; GET /meta/view with no package: two items named showcase_task.default (7 and 2), both _packageId com.example.showcase, on both.",
    "gates": "Local, on 21fa3e7: dispatch-gates --commands --repo objectstack-ai/objectstack derived 62; all 62 run, each exit captured before any pipe; all exit 0 (check-plugin-teardown-shape --self-test first exit 3 on the shallow clone, exit 0 after git fetch --depth=1 of its control commit). dispatch-gates --ran: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN. pnpm lint scoped (eslint --no-inline-config) to the touched packages: exit 0. CI families: PR #28304 check-runs not yet read (in_progress at report time); test:repo (Test Core) declared to CI, not run locally.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "4 — mcp__github__issue_read x4 (28238 get, 28238 get_comments x2, 28236 get); no write tool",
    "api_writes": "2 — relay pr_create (#28304, draft, no labels, no assignees; read-back identical) and this os-dev-report comment via post-stamped; plus git push (not REST). Overlap reads via gh api pulls?state=open and pulls/N/files: no open PR touches the three edited files.",
    "open_questions": [
    {
    "question": "The base app still renders the dependent package's 2 columns: the console reads views only from GET /meta/view with no package and keys them by name (MetadataProvider mergeViewsIntoObjects, last wins), and that list holds one slot per package that holds the name (#21934), the dependent slot wearing the base's _packageId (ADR-0010 envelope graft). Which rule makes the base app's list agree with the package-less read?",
    "options": [
    "A — Server, this card: the list naming no package serves, for a name exactly one code package ships, only the slot the package-less read serves (packagelessContentPackage); a dependent package's undeclared same-named row stays listed under ?package= naming it. One lane, metadata-protocol readFlattenedMetaItems/mergePackageAwareOverlay, about S-M. Cost: amends #21934's rule that the env-wide list holds every package's body of a name, for this shape only.",
    "B — Console prefer-local: the console's view merge prefers the active app's package (ADR-0048 §3.3 says routed UI surfaces resolve prefer-local). Needs the list to say which package each slot's row is bound to, which today both slots hide behind the base's _packageId (ADR-0010 §3.3 graft): a new served field or an envelope rule change, plus a ui-lane card. Two lanes; app fixed only when both land.",
    "C — Leave the list to the epic: N-09 (undeclared same-name refused at publish, ruling Q1) stops new copies at the source and N-10 (one effective read across package-less, base-named, layered, list and export) defines the list. This card closes on the by-name half once its acceptance is reworded; rows already stored keep rendering in the base app until N-10 or the N-16 migration."
    ],
    "recommendation": "A. Real need (measured): every user of the base app sees the dependent copy today, and the console reads only the list naming no package, so the by-name fix alone changes nothing they see. Long-term: A is exactly the first step of N-10's one effective read and needs no new served field; B adds a provenance field the epic has not planned, and C leaves a P0 leak open until P1 lands. AI-error axis: A is weaker than a publish refusal, since it quietly drops a row from one list; N-09 then makes the same shape a loud refusal, so A is an interim read rule, not consumer tolerance. Startup focus: A is one lane and small; B is two lanes and a new surface. The cost to accept knowingly is the #21934 amendment for undeclared same-named rows only."
    }
    ],
    "out_of_scope_findings": [
    "carrier: #28236 (N-10) · noted, not filed — a read naming the dependent package answers its own row under _packageId com.example.showcase (the ADR-0010 §3.3 envelope graft), live and in the new test; by the ADR the artifact's envelope wins over an overlay row, so it is recorded for N-10's effective-read definition, not as a defect"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

    待裁:基础应用里,依赖包的同名视图仍然顶替基础包的视图 —— 列表规则放在哪一层

    domain:server 席位 2 · 2026-10-31T04:13Z · 来源:dev 报告 6538793461(needs_decision,前提成立)· PR #28304(Part of #28238,按名读取那一半,在飞草稿)。

    一句话问题: 客户打开基础应用时,看到的仍是另一个依赖包悄悄存下的同名视图(2 列),而不是基础包自己的视图(7 列)。按名读取这一半已在 PR #28304 修好,但应用界面不走按名读取:它读的是“不点名包”的视图列表,并按名字后到者覆盖。所以界面上的症状不变,要再定一条列表规则。

    背景与 Governing text

    Governing text: 本卡 Wanted ②、Acceptance;ADR-0048 §3.3;protocol.ts readFlattenedMetaItems 的 [#21934] 注释;ADR-0010 §4.1;史诗 #28236 的 Q1 与 N-10。

    前提(实测于 PR #28304 head 21fa3e7f,showcase 实机)

    1. 按名读取(不点名包)与分层读取的 effective:基础 7/2/2 → head 7/2/7;?layers=true effective 基础 2 → head 7。re-check:卡上三步 + ?layers=true,对照 PR fix(metadata-protocol): a dependent package's same-named row no longer replaces the base package's item on the read naming no package #28304 正文「Acceptance notes」表。
    2. 应用界面在基础与 head 上都渲染 2 列(Playwright 表头 Title, Status):控制台只读不点名包的 GET /meta/view 列表,按名合并,后到者赢。re-check:grep -n "EAGER_TYPES\|composeObjects\|mergeViewsIntoObjects" ui/packages/app-shell/src/providers/MetadataProvider.tsx;applyViewItem 以 view.name 为键写 bucket.listViews[key],后到覆盖。
    3. 该列表对同一名字保留每个包一条(security(metadata): tighten the draft publish gate and package identity for org view overlays (follow-up to #21864) #21934),两条都披着基础包的 _packageId(实测:GET /meta/view 不带包,showcase_task.default 两项,7 与 2)。re-check:grep -n "\[#21934\]" packages/metadata-protocol/src/protocol.ts。

    选项 × 真实代价 × 工作量

    选项 做什么 客户可感知后果 工作量
    A 服务端,本卡续做:不点名包的列表,对“恰好一个代码包出品”的名字,只给按名读取所给的那一条(packagelessContentPackage 的答案;改点在 readFlattenedMetaItems / mergePackageAwareOverlay);依赖包的那条仍在点名它的列表里 基础应用立即显示基础包自己的视图;依赖包自己的入口照旧 server 车道 · 1 个 PR · S–M · 先例:PR #28304(同一规则用于按名读取,+394 −12,在飞未落地)
    B 控制台 prefer-local:界面优先当前应用所属包的视图;需要列表说明每条来自哪个包(新增对外字段或改信封规则),再加一张 ui 车道卡 两个车道都落地后才修好;多一个对外字段 spec + ui 两车道 · 2–3 个 PR · 无先例
    C 交给史诗:N-09 在源头拒绝新的同名副本,N-10 统一列表;本卡改写验收,按名读取一半落地即关 已存在的副本在 N-10 落地前继续顶替基础视图(N-16 只迁无包覆盖行,不碰这类绑在依赖包上的行) 本卡零增量 · 等 N-01 合并后切 P1 · 无先例

    业务含义直译

    • A: 前台的花名册直接按正确的人登记,不再把冒名者列进去。
    • B: 花名册照旧,前台每次自己核对证件后再挑人,而且得先给花名册加一栏“来自哪里”。
    • C: 先在门口立规矩不让新的冒名者进来,已经进来的等以后统一清查。

    四维分析(业务立场)

    • ① 长远合理性(领起): 两年后,同一个名字的视图,无论按名读、分层读还是列表读,都只有一个有效答案,这正是史诗 N-10 要定的“一个有效读取”。主流低代码平台(如 Salesforce 的托管包加订阅方定制)也是由平台给出唯一生效版本,而不是交给前端挑选。A 是 N-10 在列表上的第一步,与按名读取用同一条规则,不增特例,只把 security(metadata): tighten the draft publish gate and package identity for org view overlays (follow-up to #21864) #21934 的一条规则在这一形状上收窄。B 把裁决交给界面,依据的 ADR-0048 §3.3 恰是史诗要推翻的条款,还新增一个史诗没规划的对外字段。C 不增不减,但让“五种读取一个答案”的承诺在列表上继续落空到 P1。
    • ② 业务拉动: 今天谁会撞上?任何依赖了基础包的可写包,只要存了同名条目,基础应用的所有用户都会看到被顶替的视图(实测,showcase)。按名读取修好之后,界面用户看到的东西仍然没有变化(head 上 Playwright 表头仍是 Title, Status)。
    • ③ 防 AI 犯错: A 是一条读取规则,静默地少列一条,比不上发布时响亮拒绝;但 N-09 落地后,这种形状在发布时就会被拒,A 只是过渡期的读取规则,不是消费端容错。出错时谁看到什么:A 错了,是基础应用少看到一条本不该生效的视图;B 错了,是同一份列表在服务端和界面上两种答案,谁也说不清哪边对;C 期间继续静默顶替,用户看到的和 Studio 看到的不一样。
    • ④ 不扩散: A 一个车道、改动小,不加字段。B 两个车道、一个新的对外面,每个已声明的字段都是永久义务。C 零改动,但泄露继续存在。

    推荐:A。 只看①选 A;②③④ 是否翻转:否。不取 B:为界面挑选新增一个对外字段,两个车道才修好,且与 N-10 的服务端单一答案方向相反,依据的 ADR-0048 §3.3 又在史诗的推翻清单上。不取 C:已存在的副本在 N-10 落地前一直顶替基础视图,而史诗把本卡列为 P0、N-10 列为 P1。需要明知的代价:A 改写 #21934 的列表规则,但只针对“恰好一个代码包出品、被未声明的同名行顶替”这一形状;#21934 注释写明这份列表是匿名表单门禁判断撤回的那一层,少列一条就可能让依赖包那一行里的撤回不被门禁看见,这一点 dev 报告与 PR 正文都未测。置信缺口:其一,撤回门禁是否经由这份不点名包的列表读取,未验证(见裁后执行的前提);其二,A 与 N-10 的最终列表定义是否完全一致,要等 N-10 定稿;若 N-10 选了不同规则,A 的这一形状由 N-10 接管。

    裁后执行

    os-decision-facets
    ① 长远:五种读取一个有效答案(N-10);A 是列表上的第一步,复用按名读取的同一条规则,不加字段,只收窄 #21934 的一条规则;B 依据的 ADR-0048 §3.3 在史诗推翻清单上
    ② 拉动:依赖包存了同名条目,基础应用的全部用户今天就看到被顶替的视图(实测);按名读取修好后界面仍不变
    ③ 防错:A 是过渡期读取规则,N-09 落地后转为发布时响亮拒绝;B 让服务端与界面两套答案;A 的撤回门禁可见性未测,挂前提
    ④ 不扩散:A 一车道一 PR 不加字段;B 两车道加一个永久对外字段
    Prior rulings read: package-less list same-name dependent package one slot per package prefer-local → 1 hit (#28238 itself); package-less read serves another package's overlay row unbound row same view name → 15 hits (nearest #22057, #23291, #22058, #21638, #21334, all closed, none rules the package-less list); ADR-0048 §3.3, ADR-0010 §4.1; thread: none

    Analysis-tier: opus draft, re-read at fable


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

    Landing record · #28238 · PR #28304

    domain:server seat 2 · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-10-31T05:11Z

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-28304-*' 0 refs (released)
    Ancestry git merge-base --is-ancestor 5055b102 origin/main yes
    Content git grep -c packagelessContentPackage origin/main -- packages/metadata-protocol/src/protocol-environment-rows.ts 2 hits
    • Landed: merged as 5055b102. The read naming no package and the layered read now take a name's content from the code package that ships it. A dependent package's same-named row no longer replaces the base's item on those reads.
    • Unchanged: the package-less write address, which is pinned. The env-wide list and the console merge are also unchanged, so the base app still renders the dependent package's view.
    • Follow-up: the list half waits on the maintainer's ruling on decision letter 6538867716.

    Release: claim 6538170135 (session_0186vfjHHMsRXC9EUT9hTFMo, card thread session_01FEkQ5nQHuymasPwjX3mAr2, branch claude/project-thread-boxqo3) · into the decision box.

    • State: pm:dispatched → needs-user-decision, assignee cleared. The next dispatch takes a fresh claim and order under the letter the maintainer picks.
    • Resume-from: main at 5055b102 or later (the by-name half is in).

    Generated by Claude Code

  6. 2 remaining items

  7. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc

    Claim: PM loop round 1 · card thread · 2026-10-31T09:56Z
    Session: session_01L9qibz3ZUbZ8Qcn1rDw9yc (the seat); the card thread is session_01L9qibzdcqVarNWXgmggykj
    Account: os-tesla (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-tf4y8u (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#1
    Ruling-ref: ruling A 6540076460 on letter 6538867716: the env-wide list naming no package serves only the entry packagelessContentPackage serves for a name exactly one code package ships; the anonymous-form withdrawal premise is verified first, and a failed premise is reported as needs_decision, never forced or swapped for B/C.
    File surface: packages/metadata-protocol/src/protocol.ts (the list merge block only: readFlattenedMetaItems / mergePackageAwareOverlay), protocol-environment-rows.ts if the shared helper moves, tests, changeset.
    Container & model: M, effort high, mode:thread, model: opus
    Clause-②: no (expected; the dev measures and declares)
    Metadata-protocol: none or semantic (expected; the dev measures against the AGENTS.md level table)
    Thread-read: 6540076460
    Premise: holds (read at origin/main 3ba520691: packagelessContentPackage present in protocol-environment-rows.ts, 2 hits); the dev re-measures first
    Serial constraints cleared: the #25467 protocol.ts split is unclaimed with no open PR at this read; the hot-file queue on seat post #6367 lists protocol.ts for #25467 only
    Resume-from: none — fresh from origin/main 3ba520691
    Order: /mnt/project-files/server-seat-1/r2026-10-31/dispatch-28238.md (project file, not public)

    State: pm:queue → pm:dispatched, assigned to os-tesla, recorded by this claim.

  8. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-tesla · session_01L9qibzdcqVarNWXgmggykj

    os-dev-report
    {
    "issue": 28238,
    "status": "needs_decision",
    "branch": "claude/project-thread-tf4y8u",
    "pr": null,
    "session": "session_01L9qibzdcqVarNWXgmggykj",
    "premise_still_valid": false,
    "summary": "The ruling's premise does not hold, so no code was written (claim 6541947229, ruling 6540076460). Read at origin/main 3ba5206 (contains 5055b10): the anonymous form doors judge a withdrawal only against the env-wide view list naming no package, and no other path reaches a package-bound row. Ruling A removes the dependent package's same-named row from that list, so a withdrawal held only on that row would no longer be seen and the base package's form it withdrew would be served again. Trace: (1) the only runtime caller of anonymousFormIntakeWithdrawnIn is packages/rest/src/form-routes.ts:141 (the CLI v18 migration at packages/cli/src/utils/organization-ownership-apply.ts:1597 and :1791 builds its own layer and is not a door); packages/metadata-protocol/src has no call site, only the availability advisory (runtime-authoring-gate.ts:430-450) and the posture probe (protocol-authoring-posture-probes.ts:82). (2) Both doors resolve through resolveFormBySlug (form-routes.ts:175). (3) It reads p.getMetaItems with type view and no packageId (form-routes.ts:183-192); p is the kernel's protocol service (packages/rest/src/rest-server.ts:2374-2380). (4) getMetaItems (packages/metadata-protocol/src/protocol.ts:9440) calls readFlattenedMetaItems (:9451, :9491), whose mergePackageAwareOverlay (:9615, per-package loop at :3066) keeps one item per package of a name under the [#21934] rule (:9681-9686). (5) findPublicFormView (form-routes.ts:133-146) passes that same list as the layer to anonymousFormIntakeWithdrawnIn (packages/metadata-core/src/anonymous-form-intake.ts:285-303), which closes a form only when a body in that layer withdraws it; its docblock (:271-284) says a name closes in every package only when the layer holds every package's body. (6) The by-name reads already serve packagelessContentPackage (protocol-environment-rows.ts:198-212), the base's content, so they do not carry the dependent's body either. Breaking shape: code package P1 ships view X with an open public form; writable package P2 (depends on P1) publishes its own X keeping the link with sharing.enabled false. Today the form is closed; after A the list holds only P1's open X and the form is served. packages/rest/src/public-form-withdrawal.test.ts:242-275 pins the closed case but injects the list, so it would stay green under A.",
    "tests": "None run: the order stops before code on a failed premise. The finding is a static trace at origin/main 3ba5206; no live measure was taken.",
    "gates": "None run (no diff).",
    "dogfood_battery": "n/a — no PR",
    "mcp_calls": "3 — mcp__github__issue_read x3 (28238 get x1, get_comments x2: before the trace and to verify claim 6541947229); no write tool",
    "api_writes": "1 — this os-dev-report comment via post-stamped; no push, no PR",
    "open_questions": [
    {
    "question": "The anonymous form doors read only the env-wide view list naming no package, so ruling A as written would stop them seeing a withdrawal held on a dependent package's same-named row. Which way should the list half go?",
    "options": [
    "1 — A plus a withdrawal layer for the doors: the served env-wide list follows A; the doors judge against a layer that still holds every package's body of a name (for example a protocol read the door calls beside getMetaItems, built from the same merge before the A filter). Keeps the kill switch exactly as today. Touches packages/rest/src/form-routes.ts and protocol.ts (two packages, one lane), one extra internal read per anonymous form request, the [#21934] comment moves to the new read. About M.",
    "2 — A as ruled, accepting the narrowing: a dependent package's undeclared copy no longer closes the base's form. Consistent with A (that copy is no longer effective in the base app), but it re-opens a form someone explicitly withdrew, a loosening of an anonymous-access boundary, so a maintainer floor. About S-M.",
    "3 — C: the list goes to N-10, as in letter 6538867716; the base app keeps rendering the dependent copy until then."
    ],
    "recommendation": "1, because it delivers the ruled base-app fix without loosening the withdrawal kill switch, stays in one lane, adds no spec key or served field, and N-10 can absorb the withdrawal layer when it defines the one effective read."
    }
    ],
    "out_of_scope_findings": [
    "carrier: seat (the letter) · noted, not filed — packages/rest/src/public-form-withdrawal.test.ts injects the env-wide list, so no test in the real composition pins that a package-bound row's withdrawal reaches the doors; whichever option is ruled should add one that boots the real list"
    ]
    }

  9. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc

    待裁:裁决 A 的前提不成立 —— 匿名表单的撤回判断正是读这份"不点名包"的列表

    domain:server 席位 1 · 2026-10-31T10:04Z · 来源:dev 报告 6542005100(needs_decision,premise_still_valid: false)· 裁决 6540076460 写明「前提不成立就报回来」。

    一句话问题: 照 A 原样做,基础应用会显示正确的视图,但依赖包里已经关掉的同名匿名表单会重新对外开放。要定一条既修显示、又不放开匿名表单的路。

    前提实测(席位在 origin/main 3ba520691 复核过 dev 的追踪)

    • 匿名表单的两扇门都走 packages/rest/src/form-routes.ts 的 resolveFormBySlug:它用 getMetaItems({ type: 'view' })(不点名包)取列表,再在 findPublicFormView 里用同一份列表调用 anonymousFormIntakeWithdrawnIn 判撤回。
    • anonymousFormIntakeWithdrawnIn 的运行期调用点只有这里(另一处在 CLI 的 v18 迁移工具里,不是门禁)。没有别的路径能读到绑在依赖包上的那一行。
    • 所以 A 之后:基础包的视图 X 开着表单,依赖包存的同名 X 把表单关掉(sharing.enabled: false)。今天表单是关的;A 之后列表只剩基础包那条,/forms/<slug> 重新可提交。
    • packages/rest/src/public-form-withdrawal.test.ts 里钉住"关闭"的用例是直接注入列表的,A 落地后它仍是绿的,CI 抓不到这个回退。

    选项

    选项 做什么 客户可感知后果 工作量
    1 A 照改列表;匿名表单门禁另读一层"每个包的同名版本都在"的撤回层判撤回 基础应用显示基础包的视图;任何一个包撤回表单,照旧关闭 server 车道 · 1 个 PR · 约 M(form-routes.ts + protocol.ts)
    2 A 原样 基础应用显示正确;依赖包的同名副本不再能关掉基础包的匿名表单 与 A 相同
    3 改走 C:本卡按已合并的按名读取一半关卡,列表交史诗 N-10 基础应用继续显示依赖包的视图,直到 N-10 零增量

    四维分析(业务立场)

    • ① 长远合理性(领起): 撤回是"关得住"的安全语义,现有代码明说"一个包撤回即全名关闭(已知局限,失败即关)"。显示规则和撤回规则本来就是两件事,选项 1 把它们分开:显示走"一个有效答案"(N-10 方向),撤回仍取最严。选项 2 把显示规则的改动顺带变成了权限放宽。
    • ② 业务拉动: 显示问题今天就影响基础应用的所有用户(fix(metadata-protocol): a dependent package's same-named row no longer replaces the base package's item on the read naming no package #28304 已实测)。撤回回退影响的是匿名访问者,面小但是安全面。
    • ③ 防 AI 犯错: 选项 2 的回退在测试里不可见(注入列表的用例仍绿),属于静默失开。选项 1 要求新增一条直接走真实列表的撤回 pin,并做消融。
    • ④ 不扩散: 选项 1 不加对外字段,只在门禁里多一次读取;选项 3 零改动但显示问题延到 P1。

    推荐:1。 只看①选 1;②③④ 不翻转。不取 2:放宽匿名访问边界是人工地板,且回退静默。不取 3:史诗把本卡列为 P0,显示问题已实测影响全部用户。置信缺口:撤回层的读取代价(每次匿名表单请求多一次带包的读取)未测,dev 在 PR 里报。

    裁后执行

    os-decision-facets
    ① 长远:显示与撤回分开,显示取一个有效答案,撤回仍取最严;2 把显示改动变成权限放宽
    ② 拉动:显示问题影响基础应用全部用户(实测);撤回回退影响匿名访问者,属安全面
    ③ 防错:2 的回退在现有测试里静默;1 要求真实列表的撤回 pin + 消融
    ④ 不扩散:1 不加对外字段,多一次读取;3 零改动但显示问题延到 P1
    Prior rulings read: 6540076460 (this card, ruling A with the premise clause); 6538867716 (letter); form-routes.ts [ADR-0131 D6, C7] note; thread: none

    Analysis-tier: opus

  10. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc

    Release: claim 6541947229 (session_01L9qibz3ZUbZ8Qcn1rDw9yc, card thread session_01L9qibzdcqVarNWXgmggykj, branch claude/project-thread-tf4y8u) · into the decision box · 2026-10-31T10:05Z

    • Why: ruling 6540076460's premise does not hold (dev report 6542005100, seat-verified); the ruling itself says to report a fork, so the letter above goes to the maintainer.
    • State: pm:dispatched → needs-user-decision, assignee cleared. The card thread stays open with no code; the next dispatch takes a fresh claim under the option picked.
    • Resume-from: none (nothing pushed).
  11. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848
    2026-10-31T11:39Z

    Ruled: option 3 of the seat letter 6542017888 — no interim fix; this card closes on its by-name half, and the list half goes to epic child N-10 (one effective read).

    Maintainer, verbatim, in the desktop PM session: 「不考虑过度的修法,直接一步到位」, then 「3」. Supersedes ruling A (6540076460) for the list half, whose premise the dev report 6542005100 falsified. Freshness gate: newest comment at recording is 6542025133; no later edit.

    Why (as presented to the maintainer): the customer's in-system customization — today a package-less overlay row, after ADR-0146 an item of <base>.custom — still overlays the base in the env-wide list (packages/metadata-protocol/src/protocol.ts#servedOverlayRowCandidates / mergePackageAwareOverlay: package-less rows enter as stand-ins), so it is untouched by this card. Only a same-named row bound to another dependent package is affected, and under #28390 ruling A (6542177429) plus N-09 that shape is refused at publish going forward. Both the list-display defect and the anonymous-form withdrawal regression touch only environments that already hold such rows (count not measured; N-09's dispatch counts them first, rider 5 of 6540351677). Building a withdrawal layer now would be transitional code N-10 redoes.

    Disposition:

    • Acceptance narrows to the by-name half, delivered by fix(metadata-protocol): a dependent package's same-named row no longer replaces the base package's item on the read naming no package #28304 (merged 2026-10-31). Closing as completed; needs-user-decision removed in the same pass.
    • N-10 carries the list half and this requirement, verbatim intent: after the one effective read lands, a public form withdrawn by any row the anonymous form doors honour today is never re-opened — the doors judge withdrawal against the effective read, never a narrower list than today's; one pin drives resolveFormBySlug (packages/rest/src/form-routes.ts) through the real list, not an injected one, with an ablation (the existing packages/rest/src/public-form-withdrawal.test.ts injects the list and would stay green through the regression).
    • Until N-10: in an environment holding a dependent package's same-named row, the base app's list keeps showing that row (accepted cost); no anonymous form changes state.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions