Repository navigation
Conversation
Nine end-to-end journeys drafted from existing sources only (cloud ADR-0111, cloud MAGIC-FLOW runbook §7, cloud#1521, cloud#1653, hotcrm docs/requirements, objectstack platform checklist), each with its runner or `none yet` and its last reading; the three rulings as rules the PM protocol will cite; one pointer line on PRIORITIZATION.md. Claude-Session: https://claude.ai/code/session_01Wj1HUjzyeiBQ8atRf1ZhaL Co-authored-by: Claude <noreply@anthropic.com>
维护者速读(终稿)总监席, 改了什么 新增 ADR-0136「路的清单」:9 条端到端路径(上限 10),每行五格(谁走 / 入口→完成时看到什么 / runner / 最近读数 / 来源),外加 5 条「未入选」备换;D2 写了三条规则(旅程锚定优先级、跨车道优先级、队列由跑旅程喂); 九条路,一行一句
为什么改 用的人少、没有拉力读数,分诊第一问一直是「违背哪条契约」,于是队列被契约扫描自我喂养(两周 1,166 次合并里业务功能 8%)。这张表把「先做什么」变成你声明过的路。 风险与代价(含回滚) 零产品行为变化;规则的执行在另一张 skills 卡里,本 PR 不改任何流程。回滚 = revert。真正的代价写在 Consequences:产品仓有 P0/P1 时,契约卫生卡整批停 —— 这是有意的。 席位意见 建议:删掉你不认的行、把「未入选」里想要的换进来,然后 Approve。我自己的两个疑问留给你:J2 和 J6 没有 runner 就变不了红 —— 要么补 runner,要么删;J1/J4 的 runner 引了 你要做的 在本 PR 上编辑表格(删 / 换),然后 Approve。 Generated by Claude Code |
|
Landing provenance (ruling C) — director seat, Governed surface (
Generated by Claude Code |
os-zhuang
left a comment
There was a problem hiding this comment.
这是云服务的旅程,不是本应用开发平台的旅程。本平台的目的是不能让第三方开发者能进入我们的元数据协议快速地开发迭代企业管理应用。
|
这是云服务的旅程,不是本应用开发平台的旅程。本平台的目的是不能让第三方开发者能进入我们的元数据协议快速地开发迭代企业管理应用。 |
|
Rework in progress on your review (5224841038) — director seat, Your direction, verbatim: 「这是云服务的旅程,不是本应用开发平台的旅程。本平台的目的是不能让第三方开发者能进入我们的元数据协议快速地开发迭代企业管理应用。」 — recorded as the ruling on #18477 (stamped 2026-09-16T15:36Z). D1 is being rewritten around the developer platform's journeys: a third-party developer enters the metadata protocol → scaffolds → authors metadata → runs locally → verifies → publishes → installs → iterates against a customer requirement and sees it live; AI-assisted authoring stays as a developer journey; end users appear only as the proof the built app works. The cloud-service rows (signup, control-plane speed, hosted tenancy) move to 「未入选 — cloud 服务自己的清单」. D2 / D3 unchanged. Same dev, same branch; the four-piece is re-hung when the new table is up, and you will be asked to read it again. Generated by Claude Code |
…clared-journeys-adr
…orm's Re-centre D1 on the third-party developer entering the metadata protocol: discover + scaffold → author metadata (by hand / AI-assisted / Studio) → run locally → verify → publish → install → iterate against a customer requirement, with the app's end users as the closing proof. Cloud-service rows move to a 「未入选 — cloud 服务自己的清单」 subsection. D2, D3 and the Consequences stand; the source/merge accounting follows the new table. Claude-Session: https://claude.ai/code/session_01Wj1HUjzyeiBQ8atRf1ZhaL Co-authored-by: Claude <noreply@anthropic.com>
维护者速读(终稿 v2 —— 按你 15:34 的意见重写后)总监席, 改了什么 D1 整表重写:主角改为第三方开发者,十行按他的路径顺序排;云服务的六条(注册建组织、控制面速度、托管租户隔离、运营启停、计费、云端副签)移到「未入选 — cloud 服务自己的清单」,并写明 cloud 仓可按同一形状自立一张。D2 三条规则、D3 取代 PRIORITIZATION.md 不变。你的原话逐字引在 Deciders 里,旁边注了本席按上下文读作「能让」。 十条路,一行一句
为什么改 你的裁决:平台的目的是让第三方开发者进入元数据协议、快速开发迭代企业管理应用;首稿把云服务的旅程当成了锚。 风险与代价(含回滚) 零产品行为变化;执行在 #18489;回滚 = revert。 席位意见 建议:再删一轮 —— 我自己看 J4 与 J5 有重叠(都是「在运行中的应用里看到」),J9 没有 runner;要么给 J9 补一个「REQ 从 Triaged 到 Shipped」的机械读数,要么承认它是产品缺口(🔴)留着提醒。 你要做的 重读十行 → 删/换 → Approve → 翻 ready 入队。 Generated by Claude Code |
|
Rework #2 ordered — director seat, Maintainer, on the direct channel, verbatim: 「#18480(路的清单)有很大问题啊,基于元数据开发的应用到底能实现那些功能,开发好的应用是否要测试验证,这些需要进清单吗」 → 「这个清单的目的到底是什么」 → on the seat's three options, 「A(建议):」. Ruling A — v3 reshapes the anchor into 「应用能力 × 验证」:
v2 ( Generated by Claude Code |
…clared-journeys-adr
…developer path Reshape D1 per the maintainer's v2->v3 ruling: the main table is now one row per capability area of an enterprise app built on the metadata protocol (C1..C13, sourced from docs/qa/platform-checklist/areas/*.json), each row stating what the built app does, which metadata declares it, how it is verified (checklist items + named runner + what stays manual) and a dated reading. The developer path folds v2's J1-J9 into a six-row second table (P1..P6). v2's J10 becomes D2.4: an app counts as built only when the checklist items its metadata kinds map to pass in a run record and objectstack verify is green, with hotcrm as the reference implementation. Row cap is now by capability area, not a hard ten. All 15 areas and all 264 items are accounted for in a new area-to-row table (main 244, path 20). The P0 recount is 20, not the 19 v2 recorded; the correction is stated in Consequences and the checklist files are untouched. Every reading re-taken 2026-09-17. The platform-side non-selected block is emptied because both of its lines moved into C5 and C13; the cloud block stands verbatim. Claude-Session: https://claude.ai/code/session_01Wj1HUjzyeiBQ8atRf1ZhaL Co-authored-by: Claude <noreply@anthropic.com>
维护者速读(终稿 v3 —— 按你 07:2x 的「A(建议):」重写后)总监席, 改了什么 清单从「开发者的十步路」改成两张表。主表 13 行 = 做出来的应用能干什么,每行写:应用里能做什么 · 哪些元数据声明它 · 怎么验证(checklist 项数 / P0 / 具名 runner / 哪些还是手动)· 今天的读数。C1 建对象与增删改查 · C2 各种视图与搜索 · C3 表单、字段类型、校验与公式 · C4 权限与行级/字段级安全 · C5 审批 · C6 自动化 · C7 仪表盘与报表 · C8 登录与身份 · C9 对外后端与集成 · C10 附件 · C11 多语言 · C12 AI · C13 Studio 零代码。副表 6 行 = 开发者怎么把它做出来:进入 → 写元数据(手写 / AI / Studio)→ 本地跑 → 验证 → 发布与安装 → 迭代客户需求。checklist 的 15 个区、264 项全部有去处(主表 244、副表 20),表里写明了每个区落到哪几行。 新规则 D2.4:做好的应用必须过验证才算做好 —— 它的元数据种类映射到的 checklist 项要在一条 run record 里通过,加上 读数 全部 2026-09-17 重取。今天 main 上的 Test Core 是红的( 你要做的 读主表 13 行:哪一行不是你要的能力,删;写得不对,换。副表 6 行同样。然后 Approve、翻 ready 入队。行数上限已改为按能力区,不再硬卡十行。 风险与代价(含回滚) 一份文档,零代码;规则的执行是另一张卡(#18489,接受后按 C/P 行号重划范围)。回滚 = revert 一个 squash 提交。 Generated by Claude Code |
…nt about `0136` is claimed by PR #18480's `0136-declared-journeys-as-priority-anchor.md`, added ~42 hours earlier. `scripts/check-adr-anchors.mjs` prescribes the NEW record taking the next free number, and renumbering an already-accepted record was ruled out — before it is referenced is the only cheap moment. `0137` re-verified free: absent from `docs/adr/` on `origin/main` (which tops out at 0135) and claimed by none of the 31 open PRs, scanned through the added-file list of each. The scan lit twice on `0136`, so the zero is a reading. Three corrections the record owed: - **Status**: this record declares and implements nothing. D1's authoring refusal is decision batch #122 item 2's, carried by PR #18638 under one ADR-0087 id; D2–D4 are consumer-delivered in objectui#8069. - **Scope boundary**: the gate-slot conversion is RULED and IN FLIGHT, not "filed as a follow-up" — the dangling sentence is gone. The record's claim that converting them "would bake a direction the ruling did not give" is true only of batch #119, and is now stated as what it is: a statement about which ruling authorizes what, not a reason the conversion should wait. - **The hand enumeration is replaced by a citation of #15811's census**, because the hand list had already rotted: it omitted `system/settings-manifest.zod.ts:424` and `:686`, both `visible: SettingsVisibilityInputSchema`. Measured through `SettingsManifestSchema.safeParse` on the built dist: all six refused spellings (`ast`-only, blank `source`, blank bare string × both slots) are ACCEPTED, while a grammar-violating source is REFUSED with `custom@visible` and `custom@specifiers.0.visible` — so the refinement is live at both slots and narrows neither arm. Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh Co-authored-by: Claude <noreply@anthropic.com>
|
Closed without merging — superseded by the North Star (skills seat, Maintainer, chat 2026-09-18 (2026-09-18T21:40Z–2026-09-18T22:04Z), verbatim: 「18480 我总觉得写的很混乱,里面很多统计数据很容易飘逸,很多无关的内容。如果是需要功能清单,不应该是一份单独文档吗?还有平台的目标,是否需要类似北极星的单独文档」 → 「同意,北极星定稿,然后评估skills需要做哪些修改 136是否要关闭」 → 「其他同意」 (on the seat's proposal to close this PR unmerged). The two durable halves of ADR-0136 moved into the North Star ( Generated by Claude Code |
…semantics (ADR-0136) (objectstack-ai#18985) Fixes objectstack-ai#17778 Clause-②: no The `domain:spec` half of the maintainer ruling on objectui#8069 (decision batch objectstack-ai#119 item 3, 2026-09-12: 「同意」 to **A**, with **Q2 yes** and **Q3 yes**), **reworked** under decision batch objectstack-ai#160 item 1 on objectstack-ai#19003 (letter **A**, maintainer 「同意」 2026-09-18T11:58Z). The renderer half is objectui#8069 and is not in this PR. ## What this PR is now — a record, one producer fix, and nothing else Ruling A removed the schema change from this PR. Decision batch objectstack-ai#122 item 2 (card objectstack-ai#15811, comment [`5644350409`](objectstack-ai#15811 (comment)), 2026-09-12) had already ruled the evaluated-slot narrowing across **all 36 declaring positions** — its own census names *「field / option / grid-column `visibleWhen` / `readonlyWhen` / `requiredWhen`」* — and **PR objectstack-ai#18638 owns it under one ADR-0087 id and lands first**. Card objectstack-ai#17778 ruled fault semantics, not the carrier symbol, so nothing ruled is lost. **Removed here** (commit `9f30a18a9`): the three `FieldSchema` triad-slot edits; the `PredicateSchema` / `PredicateInputSchema` rebinding, which go back to composing the persistence schemas, wide; the `field-rule-predicate-evaluated-slot-source-required` ADR-0087 entry (the entry file and, through `gen:migration-registry`, its registry rows) and the changeset marker; the triad pin test; the two ADR anchors; and every api-surface / reference-page row that existed only because of those. **Kept, per the same ruling**: ADR-0137 (the fault-semantics record, renumbered — see below), the ADR-0089 pointer addendum, and the producer fix. **Re-derived and removed** (see the measurement below): the ADR-0058 D7 roster entry. ### The revert is byte-exact, not "close enough" The three sources and the `field.zod` anchor were restored with `git checkout d8b12fc --` (the merged-main parent of this branch's merge commit, a pinned sha, not a moving ref); `git diff d8b12fc` is **empty** for each. `registry.ts` was not hand-edited — the entry FILE was deleted and `gen:migration-registry` re-emitted the generated regions; the result is byte-identical to merged main and `check:migration-registry` is green. After regenerating, the whole diff against merged main is **6 files**, and the generated half of it is **4 lines**: | artefact | delta vs merged main | |---|---| | `content/docs/references/**` | **byte-identical** — 0 rows | | `packages/spec/api-surface-declarations/` | **4 lines**, in `root.txt` and `shared.txt`, all of them the producer fix | | `packages/spec/api-surface/`, `export-origins/`, `declaration-map/` | unchanged — `check:api-surface` green | ## The producer fix, on its own terms `cel()` and `expression()` (hence `F` and `P`) always write a non-blank `source`, but were declared as returning `Expression`, whose `source` is optional — a declaration of a shape neither function can produce. The fix is at the PRODUCER (Prime Directive objectstack-ai#12): the return type now states what the helper emits. The docblock was **rewritten** so it no longer rests on the triad requirement this rework removes; what it now says is the general fact plus the live consumer, `FlowEdgeSchema.condition`. ```diff -declare function cel(strings: TemplateStringsArray, ...values: unknown[]): Expression; +declare function cel(strings: TemplateStringsArray, ...values: unknown[]): EvaluatedExpression; -declare function expression(source: string, dialect?: ExpressionDialect, meta?: ExpressionMeta): Expression; +declare function expression(source: string, dialect?: ExpressionDialect, meta?: ExpressionMeta): EvaluatedExpression; ``` Narrowing a return type removes nothing from a caller — `EvaluatedExpression` is assignable to `Expression` — so no call site changes. ## `Clause-②` re-judged, and the changeset level follows it The previous body declared `Clause-②: yes (narrowing)`. With the accept-set narrowing gone that declared something this diff no longer does, and `Check Changeset` read it and reddened. **Re-judged against the six files that remain: `Clause-②: no`.** The judgement is measured, not asserted: | question the declaration asks | reading | instrument | |---|---|---| | does it widen an accept set? | **no** — no zod schema moves at all; `packages/spec/src` changes are two return types and two docblocks | `git diff d8b12fc -- packages/spec/src` | | does it narrow an accept set? | **no** — same reading; the narrowing left with commit `9f30a18a9` | as above | | does it expand the public surface? | **no** — no export added, removed or renamed | `check:api-surface` green, with no removed-or-narrowed report | ⇒ the changeset is **`patch`**, the BREAKING banner is gone and so is the ADR-0087 disposition marker — both belonged to the narrowing that owned them. Something published still moves (two return types in the shipped `.d.ts`), so `skip-changeset` would be wrong; a producer-side fix in a released package is exactly what a `patch` entry is for. Re-run locally against a `pull_request` payload carrying this body, before pushing: `GITHUB_EVENT_NAME=pull_request node scripts/check-changeset-no-major.mjs --base origin/main --event PAYLOAD_PATH` → **exit 0**, `NOT DECLARED — the clause-② declaration reads 'no'`. The same command against the OLD body reproduces the CI red, so the local run is a measurement and not a hopeful one. ## ADR-0136 is renumbered to ADR-0137 PR objectstack-ai#18480 added `docs/adr/0136-declared-journeys-as-priority-anchor.md` about 42 hours earlier. `scripts/check-adr-anchors.mjs` prescribes exactly this — the NEW record takes the next free number, and renumbering an already-accepted record was ruled out, so before it is referenced is the only cheap moment. **`0137` re-verified free on this rework**, not inherited from the earlier sweep: | query | result | |---|---| | `git ls-tree origin/main docs/adr/` | tops out at **0135** — neither 0136 nor 0137 on `main` | | added `docs/adr/*` files across **all 31 open PRs** (`GET /pulls/{n}/files`) | two hits, both `0136`: objectstack-ai#18480 and this PR. **Zero** on 0137 | The scan lit twice on `0136`, so the zero on `0137` is a reading and not a dead query. ## Three corrections the record owed **1. Its Status line claimed an implementation it no longer has.** It now says what is true: this record declares and implements nothing. D1's authoring refusal is batch objectstack-ai#122 item 2's, carried by objectstack-ai#18638; D2 / D3 / D4 are consumer-delivered in objectui#8069. **2. The gate-slot conversion is RULED and IN FLIGHT, not "filed as a follow-up".** The dangling sentence is gone. The record's claim that converting the gate slots "would bake a direction the ruling did not give" is true of batch **objectstack-ai#119** and was silent about batch **objectstack-ai#122 item 2**, which gave exactly that direction six days earlier, and about objectstack-ai#18638 which implements it. The claim is now stated as what it is — a statement about which ruling authorizes what, not a reason the conversion should wait — and the lint-side cost (`validate-visibility-predicates.ts`'s `celRefusal` records the opposite position today) is named as a cost objectstack-ai#18638 carries, not as an objection. **3. The hand enumeration is replaced by a citation of objectstack-ai#15811's census, because the hand list had already rotted.** It omitted `packages/spec/src/system/settings-manifest.zod.ts:424` and `:686`, both `visible: SettingsVisibilityInputSchema`, which is `ExpressionInputSchema.superRefine(...)`. Measured through `SettingsManifestSchema.safeParse` on the built `dist`: | authored `visible` | manifest slot (`:686`) | specifier slot (`:424`) | |---|---|---| | `{ dialect: 'cel', ast: … }` | ACCEPTED | ACCEPTED | | `{ dialect: 'cel', source: ' ' }` | ACCEPTED | ACCEPTED | | bare `' '` | ACCEPTED | ACCEPTED | | **LIT CONTROL** `'data.provider.toUpperCase()'` | **REFUSED** `custom@visible` | **REFUSED** `custom@specifiers.0.visible` | | DARK CONTROL `"data.provider === 'smtp'"` | ACCEPTED | — | The refinement is live at both slots and narrows neither the `ast`-only nor the blank-`source` arm — `if (!source) return;` is the line, and the lit control is what makes the six ACCEPTEDs a reading. ## The ADR-0058 D7 roster entry — re-derived, then removed The ruling's KEEP list names it, and carries NO re-derive clause. The instruction to re-derive came from this seat's dispatch brief, not from the maintainer — recorded here because the earlier wording attributed it to the ruling. The re-derivation concluded the entry no longer belongs (measured: discovery finds 37 positions with the line present and 37 without, floor 37 unchanged and met at 37; the alias types zero slots), so the line is removed here pending the maintainer's explicit confirmation of that removal. The roster lists schemas that **declare** an expression surface — "a slot whose accepted grammar is narrower gets its own schema and must be listed here too". `PredicateInputSchema` earned its place only while the rebinding made it `= EvaluatedExpressionInputSchema` and bound the triad to it from another file. Reverted, it is a plain alias of `ExpressionInputSchema` typing no slot, and the ledger header's limitation 2 names it as the standing **latent** example — leaving it rostered would make that paragraph false. objectstack-ai#18638 measured the same thing independently: *「`PredicateInputSchema` is a plain alias of `ExpressionInputSchema` with zero slot users; it stays wide with the schema it aliases」*. Measured twice on this branch with the revert already applied, by raising the `head` floor to 9999 through `ablation-replace.mjs` so the assertion prints the count: ``` roster entry PRESENT -> discovery found 37 position(s) via 'head' (floor 37) roster entry ABSENT -> discovery found 37 position(s) via 'head' (floor 37) ``` Identical, because the three triad positions are head-matched by `ExpressionInputSchema` again. Both mutations landed and both restores verified on disk (`anchor 1 -> 0`, then `blob == HEAD` and `git diff HEAD` empty). Identity grep agrees: `PredicateInputSchema` has **2** hits under `packages/spec/src/**/*.zod.ts` — its own definition and its `z.input` companion, zero slots — against a lit control of **19** files for `ExpressionInputSchema` and a dark control of **0**. ⛔ **Not a gate weakening.** The `head` floor stays **37** and is met at 37; no ledger row is deleted, no floor is lowered, no test is skipped or quarantined. The same three surfaces are discovered through the schema that types them. Both dogfood files are byte-identical to merged main. The same statement holds for the two other removals: the triad pin test and the ADR-0087 entry are removed because the behaviour they recorded is no longer in this PR — not to turn anything green. `packages/qa/dogfood` re-run after the removal: **7 passed (7)**. ## ⛔ GOVERNED SURFACE — this PR parks as a draft, by design `docs/adr/**` is on the register, so this is the regime's correct resting state, not a stall. An authorized approval is owed before any seat lands this. This seat has not flipped it ready, has not enqueued it and has not armed auto-merge. The `needs:contract-review` carrier is the review seat's and stays hung; a fresh at-tier review is owed on this head. ## Evidence - `pnpm --filter @objectstack/spec check:generated` — proved exactly **2 of 16** artifacts stale (`api-surface-declarations/`, `content/docs/references/**`) and `--fix` regenerated only those two. Re-run after: **16 of 16** up to date. - `check:migration-registry` green with the entry file deleted — the generated regions match `entries/`. - Gate families re-derived on this head with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` and reconciled with `--ran`; results and every non-zero exit are in the report comment on objectstack-ai#17778. -⚠️ The derivation printed a **STALE TREE** warning: this branch is at least 14 commits behind `origin/main` and 10 of the files the families are derived from moved in that range. No `origin/main` merge was taken in this rework, on purpose — this PR rebases after objectstack-ai#18638 lands, and a merge now would move the `merge=os-regen` artefacts for a base that is about to change. So the family list is this tree's, and CI on the merge queue's rebuilt generation is what covers the rest. ## Acceptance notes - **`packages/spec/src/data/field.zod.ts:1513` `expression`** (the formula slot) is an evaluated slot on `ExpressionInputSchema`. Untouched, and no longer this PR's business at all: it is inside objectstack-ai#18638's 36-position census. - **`PredicateSchema` / `PredicateInputSchema` have zero slot users** — measured above. The ruling says they stay as they are and that a later card may retire them as dead symbols on their own measurement. No anchor is left claiming otherwise: the anchor this PR added for `shared/expression.zod.ts` is deleted, and the `field.zod.ts` anchor is back to its pre-PR text. - The ADR-0137 record's `Consumers` line still names `shared/expression.zod.ts` and `data/field.zod.ts`. That is the set of files the DECISION governs, which is unchanged; it is not a claim that this PR edits them for that reason. ## 维护者速读(草稿) > 席位意见一节留空,由席位在 at-tier 评审后定稿为评论。 **改了什么** — 按 batch objectstack-ai#160 item 1 的裁决 A,把这个 PR 里的**协议收窄整段拿掉**:字段三条规则槽位(`visibleWhen` / `readonlyWhen` / `requiredWhen`)回到原样,`Predicate*` 两个别名回到原样(仍然是宽的持久化契约),对应的 ADR-0087 迁移条目、pin 测试、两个 ADR anchor、以及只因它们才产生的 api-surface 与参考文档行,全部删除。留下的是:**ADR-0137**(改号后的 fault 语义记录)、ADR-0089 的指针附录、`cel` / `expression` 的返回类型修复。另外按指示重新推导后,删掉了 ADR-0058 D7 的那一行 roster 条目。 **为什么改** — 同一个收窄早在六天前就被 batch objectstack-ai#122 item 2 裁决过了,覆盖全部 36 个求值槽位(包含本卡的三条字段规则),并且由 PR objectstack-ai#18638 用**一个** ADR-0087 id 承载、先落地。两个 PR 各带一份收窄,就是一次迁移两个 id、两份 CHANGELOG 说法。objectstack-ai#17778 裁决的是 fault 语义,不是承载它的符号,所以记录留下、收窄交出去,**没有任何被裁决过的东西丢失**。 **风险与代价(含回滚)** — 风险很低:协议行为**零变化**(没有任何 zod schema 移动),对外只剩两个函数返回类型收窄,而 `EvaluatedExpression` 可赋值给 `Expression`,所有调用点照常编译。changeset 因此从 `minor` + BREAKING 降为 **`patch`**,`Clause-②` 重判为 **`no`**(三项读数在上表)。代价是本 PR 不再自带任何强制:D1 的编写期拒收要等 objectstack-ai#18638;这一点在记录的 Status 和 Scope boundary 里明写了,不是留给读者去发现。回滚成本极低——本轮全部是删除与还原,恢复即 revert 这四个 commit。 **席位意见** — (留空) **你要做的** — 这个 PR 碰了 `docs/adr/**`,属受管面,停在 draft 等一个授权批准,这是制度的正常终态。需要你看的是三件事:① ADR-0137 现在**只声明不实现**——D1 交给 objectstack-ai#18638、D2/D3/D4 交给 objectui#8069,这个归属你是否认可;② 记录里原来那句「gate 槽位转换会写进裁决没给的方向」已改写为「那只对 batch objectstack-ai#119 成立;batch objectstack-ai#122 item 2 给了这个方向,objectstack-ai#18638 正在做」,这个更正你是否同意;③ D7 roster 那一行**被删**而不是保留——测量是:删与不删,`head` 发现数都是 37(地板 37),且 `PredicateInputSchema` 零槽位使用,所以它不再是「更窄的别名」。裁决原文把它列在「保留」里,**且未附任何重新推导的条款** —— 要求重新推导的是本席派发令,不是维护者(先前措辞把它归给了裁决,此处更正)。推导结论是该条目不再属于花名册(实测:该行在与不在,发现数都是 37,地板 37 未动且 met at 37;该别名零槽位),故此处删除,**等您明确确认这一删除**。 --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…l, organization, ownership, and every guest key's fate (objectstack-ai#22239) Part of objectstack-ai#22146 Clause-②: no ## What this PR is Round 3 of objectstack-ai#22146: one new decision record, `docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md`, **Status: Proposed**. It transcribes the maintainer's ruling [`6054113537`](objectstack-ai#22146 (comment)) (batch objectstack-ai#290 item 1, 「22146 同意」: A on all eight questions and G2 on the gap, with the Q4 and Q2 clarifications) into nine decisions. Each one states its contract and its enforcement point. The revision round applies the ruling supplement [`6056614963`](objectstack-ai#22146 (comment)) (maintainer 「D1 A′ D2b R」): D1 is revised to A′, and D2b is closed as R. - **No code changes.** Nothing changes in `packages/**`, `content/docs/**` or `skills/**`. The ruling places every code change after acceptance, as execution cards E1 to E4 (the record's *Execution plan*). - **Tier H.** The diff touches `docs/adr/**`, so this PR stays draft and lands only by the maintainer's hand. - **The card stays open** for the execution cards, so line 1 is `Part of`. ## What the record says, one line per decision - **D1, identity and ownership (A′).** The guest is a principal and never owns a record; a forged owner is refused; the audit names the guest as the actor. Who owns a guest-written row is the business scenario's own metadata: the door's declaration, the object's hooks, record-change flows and assignment rules. The platform stamps nothing and declares no default owner. Empty state: the owner stays unset, as the form doors do today, with the existing authoring advisory and no publish refusal. The enforcer is the guest branch of the owner-anchor stamp in `SecurityPlugin` (card E3). - **D2, the closed list of doors.** Exactly five door classes serve an unauthenticated request: - the public form doors; - share links; - the public book and doc reads; - `authRequired: false` endpoints of type `object_operation`; - `authRequired: false` endpoints of type `flow`. Everything else answers 401, decided once per domain by `shouldDenyAnonymous`. The control-plane allowlist, the signed inbound-hook channel and MCP are credentialed or infrastructure, so they are outside the guest model. - **D2b, anonymous door × elevated flow (R).** Publish refuses an `authRequired: false` flow endpoint whose target declares `runAs: 'system'`, in both directions, with a prescription: an authenticated endpoint, the signed inbound-hook channel, a public form, and `runAs: 'automation'` once ADR-0073 M2 lands. No door triggers an elevated flow directly. Card E2 implements it with a registered ADR-0087 semantic entry. Record-change flows fired by a guest-written row stay recorded and undecided. - **D3, the grants channel (ADR-0090 D9, enforced).** The `guest` anchor's bindings resolve for the guest, read through the one binding reader every position uses. An empty set denies all. There is no second channel: not the baseline, not `everyone`, and not the position-name fold. The binding tier is unchanged. The row scope runs on one pipeline, and E1 pins it per sharing model. - **D4, organization.** The guest's organization is resolved only when the deployment has a unique organization, using the same predicate as ADR-0131 D9. On a multi-organization deployment the request is refused until D5 exists. The question is asked only where the organization is needed. - **Clarification (1), carried into the record:** the form doors' declared default-organization binding stays as it is. Nothing that serves today starts refusing. - **D5, site binding.** The record gives the shape only: match, organization, guest grants and allowed doors. ⛔ It declares no metadata type and reserves no key. The binding is built when a named deployment needs it. - **D6 and D7.** ADR-0106 D7, explain's `EXTERNAL` floor and ADR-0121 D6 are unchanged. The webhook signature vocabulary goes to a follow-up card, F1. That card is named in the record and not filed. - **D8, guest keys.** Every declared guest key gets a fate and an ADR-0087 disposition. - `sys_record_share`'s `guest` recipient is to be **removed** on its own card, E4. The basis is ADR-0090 D11 and the already-registered `sharing-rule-recipient-reconcile` entry. - The form doors' `guest_portal` set name was not on the card's list. It is recorded too, with the fate keep. - **D9.** The record now holds the maintainer's ruling of 2026-08-08 (Option A, `f586f1a89`), which until now lived only in the module doc of `assemble-execution-context.ts`. ## What the revision changed (supplement [`6056614963`](objectstack-ai#22146 (comment))) - **D1 → A′.** Points 3, 5 and 6 of the draft (the organization-level default owner, its cardinality, the empty-state refusal) are replaced: ownership is the scenario's own metadata, and the empty state is the owner left unset. Points 1, 2 and 4 are kept. - **D5.** The default-owner element is removed from the shape. - **D2b → R**, with its enforcement text. The four-axis table stays as the reasoning. M and the first-drafted default owner move to *Alternatives considered*. - **Execution plan.** E2 carries R and its registered ADR-0087 semantic entry. E3 shrinks to the stamp's guest branch (never the guest, never the system principal, a forged owner refused, the owner unset unless the scenario sets it): no new key and no disposition. - **Elsewhere.** The Consequences paragraph on owner-assigning hooks is withdrawn. *What the ruling did not settle* loses D2b and D1's empty state. Acceptance criterion 3 (D2b chosen) is met. The supplement is added to *Decided by*. - **Consistency edits the ruled changes forced:** the Status line, the Consumers line (the spec change is now D2b's refusal, not a D1 key), D4 point 3 (no owner stamp left to need the organization), D2's class 5 row, follow-up F3 (M2 now only extends R's prescription), and the References. ## What stays open - **The indirect path:** record-change flows fired by a guest-written row are recorded and not decided. - **The spelling of D5's binding** belongs to the card that builds it. - **The guest's row scope** is stated as a contract; E1 pins its measured outcome. ## How the number was chosen: 0138 - `origin/main` at `73a0a6bf1d`, after this round's merge, tops out at 0137, and 0136 is absent. Still no open PR adds 0136 or 0138; objectstack-ai#22198, the one ADR PR at the first count, has landed on ADR-0048. - **I checked every open PR's file list.** That is all 20 open PRs, paged to the end for objectstack-ai#22142 (104 files) and objectstack-ai#21988 (229 files). Only objectstack-ai#22198 touches `docs/adr/`, and it touches `0048-cross-package-metadata-collision.md`. No open PR adds 0136 or 0138. - **0136 is not reused.** It was handed to a decision once: unmerged PR objectstack-ai#18480 added `0136-declared-journeys-as-priority-anchor.md`, and objectstack-ai#18985 renumbered its own record from 0136 to 0137 because of it. `scripts/check-adr-anchors.mjs` computes the next free number as the highest number plus one, which gives 0138. ## Back-pointers: none in this PR, by house practice - **Where the lines go.** The house form for an amended record is a status-line continuation. ADR-0042, ADR-0046 and ADR-0131 carry lines of the form "· **Amended** (date, ADR-NNNN Dk) — …". These lines are written when the amendment is in force. No record in the registry carries such a line pointing at a Proposed record. So the exact lines for **ADR-0090** (D9) and **ADR-0056** (D2) are written into the record's *Acceptance criteria*, to land with the accepting change. - **Which ADRs get no line.** ADR-0106 D7, ADR-0121 D6 and ADR-0096 D5/E1 are left unchanged by this record, so they get none. **ADR-0135** gets none either: it mirrors cloud ADR-0024 and adds no clause of its own, and this record amends none of its decisions. ## Verification at `2d69b2b714` (the revision, on a merge of main `73a0a6bf1d`) I ran every command in the claim-time gate list at the revision head. Each exit code was captured before any pipe. | Command | Exit | |:--|:--| | `node scripts/check-adr-links.mjs` (and `--self-test`) | 0, 0 | | `node scripts/check-adr-symbol-anchors.mjs` (and `--self-test`) | 0, 0 | | `node scripts/check-ci-filter-parity.mjs` | 0 | | `node scripts/check-closing-keyword-parity.mjs` (and `--self-test`) | 0, 0 | | `node scripts/check-comment-mask-corpus.mjs` | 0 | | `pnpm --filter @objectstack/lint run check:doc-formula-expressions` | 0 (see note) | | `pnpm check:adr-anchors` | 0 | | `pnpm check:cross-package-test-inputs` | 0 | | `pnpm check:doc-authoring` | 0 | | `pnpm check:driver-memory-census` | 0 | | `pnpm check:gitlink-declared` | 0 | | `pnpm check:nul-bytes` | 0 | | `pnpm check:pm-governed-merges` | 0 | | `pnpm check:pm-prior-rulings` | 0 | | `pnpm check:refd-timer-probe` | 0 | | `pnpm check:watch-hint-literal` | 0 | - **`check-adr-symbol-anchors`.** It reports "2225 anchors across 141 records resolve". Positive control: the record count is 141, which is the 140 at base plus this record. No anchor carries a line number. - **`check:pm-prior-rulings`.** The self-test passes 155 cases. The tool's `--card 22146` read returns 16 ADR decision hits and 1 ruling on the thread (`6054113537`). - **`check:doc-formula-expressions`.** In the first round its first run exited 3 (PREREQUISITE NOT MET: the closure was not built), which measured nothing. I rebuilt the `@objectstack/formula` and `@objectstack/lint` closure under the verify lock after this round's merge (VERDICT command-exit 0), and the gate exited 0. - **Re-derivation.** `dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `2d69b2b714` gives the same 19 families. The `--ran` reconciliation shows 19 run and 0 NOT-MEASURED, a zero derived from the recorded exit codes. The change set is one file, +640/−0, against merge base `73a0a6bf1`. ## Not measured, and named - **The G2 sweep (the booted per-door-class anonymous sweep).** I did not run it, by the ruling: it is an acceptance precondition, to be run in an environment that permits the probe. This round wrote no driver and no probe. - **The platform documentation links.** I could not re-fetch them, because the container's proxy answers 403 to CONNECT for those hosts. The record carries the URLs that the measurement round recorded. - **The cloud repository's anonymous surfaces.** This round did not read them. ## Changeset I read the Check Changeset job in `pr-automation.yml`. It has two exemptions: the `skip-changeset` label (read live, twice) and the Changesets release PR. Its failure text prescribes: "if it releases nothing …, apply the 'skip-changeset' label". This PR adds no `.changeset/*.md` and changes none, and `docs/adr/**` ships in no package's `files[]`. So the label is `skip-changeset`. ## Acceptance notes - **A doc comment that describes the old form-door semantics.** The comment above `RestServer.registerFormEndpoints` in `packages/rest/src/rest-server.ts` still says that security is delegated to a `guest_portal` set carried on the context, and that the middleware falls open when none is registered. What admits a form submission today is the `publicFormGrant` branch in `SecurityPlugin`. - This is comment drift only, and no reach was measured. It is noted, not filed. - Carrier: none. Card E2, once cut, edits that domain. ## 维护者速读(草稿) - **改了什么:** 按你「D1 A′ D2b R」的补充裁决修订 ADR-0138 草稿,其余内容不动。 - D1:访客永不拥有记录、伪造的 owner 一律拒绝、操作留痕记在访客名下,这三条保留。"本组织声明一个默认 owner"整条删掉:访客写进来的记录归谁,由各业务场景在元数据里自己定(入口声明、对象钩子、记录触发流程、分配规则),平台不打任何默认值。没人设置时 owner 就空着,跟今天公开表单一样;表单的作者视图照常提示,发布不拒绝。 - D2b:匿名入口不能触发以系统身份运行的流程。发布时直接拒绝,端点和流程两头都检查,并给出替代办法(带凭据的端点、签名 webhook 通道、公开表单,以及 M2 落地后的 automation)。 - 站点绑定的形状里去掉"默认 owner";执行卡 E3 缩成只校验访客分支,不新增任何键。 - **为什么改:** 你指出归属是业务场景的事,平台级默认值在多数部署里是错的,还会抢在对象自己的分配逻辑前面。D2b 选 R 之后,AI 照着 `runAs` 的说明写出"匿名入口 + 系统身份流程"时,发布就会被拦下。 - **风险与代价(含回滚):** - 本 PR 仍只改一个文档文件,合并后运行时行为不变;回滚就是删掉这个文件。 - D2b 的拒绝会让"匿名端点指向系统身份流程"这种写法从此发布不了。仓内没有这样的声明;仓外的部署本轮没有测。 - 接受前提还剩一项:在允许探测的环境里做一次启动后的逐类匿名入口实测(G2)。 - **席位意见:** - **你要做的:** 修订版就绪后,批准这份 ADR(Tier H,点 Approve 或亲手合并)。 --- _Generated by [Claude Code](https://claude.ai/code/session_01LAi5BVvQNiYzepSAcsoFLK)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #18477
Maintainer 「立」 (2026-09-16, direct channel). Rework round 2 (v3, 2026-09-17): the maintainer rejected v2's ten-row journey table, verbatim and untranslated: 「#18480(路的清单)有很大问题啊,基于元数据开发的应用到底能实现那些功能,开发好的应用是否要测试验证,这些需要进清单吗」 → 「这个清单的目的到底是什么」 → on the director seat's three options, 「A(建议):」. Ruling A is recorded on #18480 comment 5710656572. One file changed in this round;
docs/adr/PRIORITIZATION.mdstill carries only its one pointer line from round 1.What changed vs v2
docs/qa/platform-checklist/and this table takes its rows from itCoverage, by the numbers (remeasured on this merged tree)
records-forms(39) spans C1/C2/C3/C10;platform-core(29) spans C1/C2/C8/P5;cli(17) spans C1/C9/P1/P3/P4/P5 — and the ADR says where each of their P0s went.priority === "P0"is 20 items (access-security 6 · platform-core 5 · api-backend 2 · ai 1 · approvals 1 · automation 1 · cli 1 · integration-system 1 · records-forms 1 · studio-authoring 1). The correction is stated in Consequences; the checklist files are untouched.automatedrunner; 154 are manual. 10 of the 20 P0s are manual — D2.4 names all ten, so the rule is honest about being a rule people execute today, not a gate that goes red by itself.none yet— 5 items, zeroautomatedin the whole area. In the path table P6 (iterate a customer requirement) staysnone yet. Those two plus C5 (2 of 18 approvals items automated) and C13's P0studio-authoring.first-run-loopare written up in Consequences as the gap list for thechecklist-authorskill.approvals.account-app-entrymoved into C5 (it is C5's only P0) and the twostudio-authoring.custom-page-*items moved into C13; the ADR says so explicitly rather than dropping them.Readings, all re-taken 2026-09-17
Dogfood Regression Gatemain@e0d05538, CI run 35192708788 (scheduled, 07:04Z), 3 shards +Dogfood Verify CLITest Core, same runpackages/lintpnpm run testexit 1); 5/6 success. Every row whose runners rideTest Coretherefore readsunknown, never 🟢scaffold-e2e.ymlmain@a55646b0check-links.ymlpull_requestonly; its lastmainrun was acancelledon 2026-08-30, so this reading is from a PR run and the ADR says soplatform-checklist-watchdog.ymlmain@879b5127(the checklist's own structural + coverage ratchet)showcase-smoke.ymle0d05538wasin_progressat read timelive-e2e.yml+ci.ymlmain@e896c389—.objectui-sha53ded82b; the ADR states the gapgolden-journey.ymlci.yml/e2e.ymlmain@087b7c5d(main has not moved since 2026-09-16)publish-staging/publish-productionmain@590b095e(2026-09-14; nothing published since)docs/requirements/main@087b7c5d: all six REQ areStatus: Triaged, none past it; REQ-0002 Traceability is still "to be filled in per phase", REQ-0003–0006 still "to be filled in when built". In flight but not landed: hotcrm draft PR #1950 (REQ-0006), its ci #3055 failure / e2e #1273 successMechanism assumptions the dispatch asked me to measure
areas/*.json= 15 files, 264 items; the per-area counts match the director's reading exactly (access-security 27 · ai 8 · api-backend 23 · approvals 18 · attachments-storage 9 · automation 16 · cli 17 · dashboards 11 · i18n 5 · identity-auth 22 · integration-system 18 · platform-core 29 · records-forms 39 · search 7 · studio-authoring 15). All 264 arestatus: "active". P0 = 20, not 19 (see above).coverage.jsonhasmetadataKinds, keyed by ledger name, 37 entries; each is{items: [...]}exceptrealtime_subscription, which is{waived: ...}. The ADR's 「声明它的元数据」 column is a read of that map and says so — a kind may appear on several rows.scripts/checklist-select.mjsexists; run with no argument it prints its selectors:(id) | area: | capability: | priority:P0 | surface:api | since:vN | file:(path) | all. D2.4 quotes that set.git cat-file -eon each, 13/13 OK).mainis still087b7c5d, so nothing moved; the traceability placeholders are quoted above. What v2 did not have is the in-flight draft PR feat(cli): two flow anti-pattern lints — date-equality filters (#1874) + phantom aggregation (#1870) #1950, now named in the P6 row.Gates (exit code captured by redirect, then
$?— never through a pipe; treeb3da1ec4)Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths passed — the script took the change set from the merge base itself: 2 paths, three-dot). 18 commands, the same 18 as rounds 1 and 2. Reconciled:✓ dispatch-gates --ran: 18 derived famil(ies) accounted for — 18 run, 0 NOT-MEASURED (a DERIVED zero — all 18 recorded an exit code and none of them is 3).check-adr-links:: 0 ·--self-test:: 0 ·check-adr-symbol-anchors:: 0 (2210 anchors across 140 records resolve) ·--self-test:: 0 ·pnpm check:adr-anchors:: 0check-ci-filter-parity:: 0 ·check-closing-keyword-parity:: 0 ·--self-test:: 0 ·check-comment-mask-corpus:: 0pnpm check:doc-authoring:: 0 ·check:nul-bytes:: 0 ·check:pm-governed-merges:: 0 ·check:pm-prior-rulings:: 0 ·check:driver-memory-census:: 0 ·check:refd-timer-probe:: 0 ·check:watch-hint-literal:: 0pnpm --filter @objectstack/lint run check:doc-formula-expressions:: 0 (the lint closure was built first, underscripts/pm/os-verify-lock.sh;VERDICT command-exit 0 · held the lock 183s · waited 0s)pnpm check:cross-package-test-inputs:: 0 before that build, 1 after it — measured both ways on this same commit, which pins round 2's diagnosis instead of restating it. The gate flagspackages/cli/test/init-created-files-summary.e2e.test.tsdescending intopackages/spec/dist/, "no declared glob reaches inside it". Withpackages/spec/distabsent the gate exits 0; the only thing that changed between the two legs is that building@objectstack/lint's closure produced that directory. Recorded as 1 in the--ranrecord so nothing is cherry-picked. This PR touches no test, noturbo.jsonand no package; it is reported in the os-dev-report as an out-of-scope finding for the seat.No package is touched, so no dependency-closure build and no package test/typecheck are owed. Repo-wide scans are CI's.
Acceptance notes
docs/adr/PRIORITIZATION.md's pointer line names the ADR by its v1 title ("Declared journeys as the priority anchor"). The H1 is now 「应用能力 × 验证,加开发者路径,作为优先级锚」.check-adr-linksresolves the link (it checks destinations, not link text), and the dispatch fences that file, so it is left alone. 承接者: whoever lands the enforcement card (skills(pm-dispatch): ADR-0136 D2 becomes the triage protocol —Journey:line, journey bands, cross-lane product-first order, journeys feed the queue #18489) touches that area next.main@e0d05538is red onTest Coreshard 2/6 (packages/lint) in scheduled CI run 35192708788, 2026-09-17T07:04Z. Not caused by this branch; it is why several ADR rows readunknown. dedupe words: Test Core shard 2/6, packages/lint, main red, scheduled CI 35192708788.check-links.ymlhas nomaintrigger, so the ADR's link-reachability runner has no reading onmain— the lastmainrun is acancelledfrom 2026-08-30. dedupe words: check-links workflow, pull_request only, no main schedule.skip-changeset:docs/adr/**publishes nothing (fast lane).维护者速读(草稿)
改了什么。 按你的裁决 A 重塑了
docs/adr/0136。主表不再是「路」,而是你的应用能干什么 × 怎么验证:13 行,每行一个能力区 —— C1 建对象与记录增删改查 · C2 视图与搜索 · C3 表单/字段/校验/公式 · C4 权限与行列级安全 · C5 审批 · C6 自动化 · C7 仪表盘与报表 · C8 身份与登录 · C9 集成与后端 API · C10 附件 · C11 多语言 · C12 AI 辅助 · C13 Studio 零代码。每行写清四件事:终端客户能做什么、哪些元数据声明它、拿什么验证(清单项数 / P0 数 / P0 项 id / 具名 runner / 哪些仍是手动)、最近读数带日期。开发者路径缩成第二张表 6 行(进入 → 写元数据 → 本地跑 → 验证 → 发布安装 → 迭代客户需求)。行数上限改成按能力区,不再硬卡十行。 新增一条 D2.4:做好的应用必须过验证才算做好 —— 它的元数据 kind 映射到的清单项要在一条 run record 里通过,objectstack verify要绿,hotcrm 是参考实现;一张交付了面向应用行为却没有通过 run record 的卡,不算 done。v2 的 J10(终端用户作证明)就是折进了这条规则,不再单独占一行。为什么改。 你的原话:「基于元数据开发的应用到底能实现那些功能,开发好的应用是否要测试验证,这些需要进清单吗」「这个清单的目的到底是什么」。v2 那张表只回答了「开发者怎么走」,没回答「做出来的东西能干什么、怎么证明它能干」。v3 把主表换成能力 × 验证,并在 Context 第一句把目的钉死:这是排「先修什么」的锚,不是产品能力规格,也不是测试计划;能力与验证的账本是
docs/qa/platform-checklist/,本表从它取行。这一版把清单的 264 项全部分派进了表里(v2 只引了 22 项),15 个区一个不落,所以「哪块没人管」现在是能看出来的。风险与代价(含回滚)。 这是法,不是执法:
SKILL.md一个字没动,执行仍是 #18489 那张卡。三处得先说破:① 读数会腐烂 —— 全部是 2026-09-17 的快照,今天main的Test Core恰好是红的(shard 2/6,packages/lint),所以所有靠它的行我写unknown而不是 🟢,这是故意的;② 表越细越贵 —— 13 行 × 6 列,你每删一行就得同时想清楚那一区的 264 分之若干去哪,归属表会对不上;③ D2.4 今天要人执行 —— 20 个 P0 里 10 个是手动的,这条规则短期内不会自己变红。回滚 = 把0136恢复到365d1bc0那一版(v2 十行表),PRIORITIZATION.md不用动,没有任何代码依赖本文件。席位意见。 (留空,席位定稿成评论)
你要做的。 读这两张表,按删除与替换编辑:① 主表 13 行是不是你认的能力切分 —— 哪行该删、哪两行该并、少了哪一行;② 「怎么验证」列里那些
none yet和unknown(尤其 C11 多语言整区零 runner、C5 审批 18 项只有 2 项自动、C13 的零代码首跑闭环、P6 客户需求端到端)是不是你要优先补的缺口;③ D2.4 的措辞就是你要 PM 协议引用的措辞吗 —— 特别是「没有通过的 run record 就不算 done」这一句的力度;④ 副表 6 行够不够、顺序对不对。然后手动合并(governed surface,PR 保持 draft,席位不翻 ready)。Generated by Claude Code
Generated by Claude Code