Skip to content

[decision] Does the v5.0 project → environment rename extend to the API surface — client.projects.* and the res.project / res.projects response fields? #12473

Description

@os-litant

Filed unassigned and unlabelled by the domain:cli seat (#6024), session session_01UjujZN219uFzBhSYfMykCd. ⛔ Not graded, not routed — and ⛔ not a card an execution seat can answer: it is a breaking public-API decision.

⚠️ This seat owed this card at #12153's acceptance and did not file it. Triage flagged the omission on #12432 ("expected from the cli seat at #12153 acceptance per the cross-repo follow-up rule"). Filed now, late, and recorded as late so the gap is visible rather than tidied away.

What is settled, and what is not

Settled. ADR-0006 renamed project → environment at v5.0 with no aliases, and AGENTS.md states "Project now only means the npm/monorepo sense." Three PRs have carried that through the CLI:

Not settled, and deliberately fenced out of all three:

surface where
client.projects.* @objectstack/client SDK method names
res.project / res.projects control-plane response fields
packages/client/src/index.ts:1814 Provision a new project — the SDK JSDoc that travels with the above

Each of the three cards named these as out of scope for the same reason: they are API surface in other packages, not CLI wording.

Why this needs a human, ⛔ not a seat

Renaming any of them is a breaking change to a published contract with a migration story:

  • client.projects.* is a published SDK method namespace — renaming breaks every caller, and an alias is exactly what ADR-0006 says the rename does not get.
  • res.project / res.projects are control-plane wire fields. The producer is the cloud control plane, not this repo, so this is also a cross-repo coordination question (objectstack-ai/cloud), not a local rename.

⇒ Squarely on the maintainer floor: protocol / public-contract change, plus a cross-repo producer. ⛔ No execution seat should decide it, and ⛔ no code PR should quietly start it.

The options, as this seat understands them

  1. Rename with a breaking major — the SDK namespace and the wire fields both move to environments / environment. Consistent with ADR-0006's no-alias rule; costs a coordinated cloud-side change and a migration entry.
  2. Rename the SDK only, leave the wire fields — the CLI already maps between them (res?.project feeding os environments show). Cheaper, but leaves the contract half-renamed and the mapping permanent.
  3. Leave both, and record the boundary — declare that the rename was scoped to the CLI's user-facing vocabulary and that project survives deliberately on the API surface. Costs nothing today; requires the boundary to be written down where the next reader of ADR-0006 will find it, or this question returns every few months.

⛔ This seat states no recommendation — with a cross-repo producer and a published namespace in play, a recommendation from the lane that keeps tripping over the drift is not worth much. What the seat can say is that option 3 is not free: the cost is a documented boundary, and its absence is what has produced three cards in three days.

Why it is worth a card rather than leaving it implicit

The drift is generative: #10967, #12153, #12432 and #12464 are four cards in the same week, each fixing one axis of one rename, each correctly fencing out the API surface, and none able to close the question. Until it is answered, every future reader of a project-spelled identifier next to an environment-spelled one files another.

Refs

Activity

  1. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    Maintainer ruling recorded — Option 3: the boundary is written down; project survives on the API surface deliberately

    Provenance: maintainer, 2026-08-27, PM chat decision-inbox batch 6 (adjudication session session_01DKWDdUJ2XNRESVVWUvcpnh), verbatim: 「其他同意」 — accepting the triage recommendation 3 (2026-08-26T05:48Z facet block).

    Ruled: the v5.0 project → environment rename is scoped to the CLI's user-facing vocabulary. The API surface keeps project deliberately: client.projects.* (SDK namespace), res.project / res.projects (control-plane wire fields, produced by the cloud repo), and the SDK JSDoc at packages/client/src/index.ts:1814. The deliverable is an ADR-0006 addendum naming those three surfaces as intentionally retained, so the next reader — human or agent — finds a written boundary instead of filing the fifth card on this seam. Pre-registered in the same addendum: option 1 (full rename, SDK + wire, breaking major with cloud coordination) reopens at the next planned SDK/protocol breaking major — the rename is deferred, not abandoned. ⛔ Option 2 (SDK-only half-rename) is permanently declined — the worst shape for AI authors.

    Note on the standing startup-stage immediate-retirement rule (2026-08-27, objectstack#12668): it covers deprecated-alias retirements; it does not force a zero-pull cross-repo breaking major — the boundary-addendum is the lean move here, as ruled.

    Dispatch: doc-only S; ADR face is governed — draft PR, human merge. No code changes ride it.

    State: needs-user-decision → pm:queue.


    Generated by Claude Code

  2. self-assigned this
    on Aug 27, 2026
  3. os-litant commented on Aug 27, 2026

    @os-litant
    CollaboratorAuthor

    🔒 Claimed — domain:cli seat (#6024), R45。⚠️ 治理面:draft PR,人工合并

    Session session_01UjujZN219uFzBhSYfMykCd,identity os-litant。Branch claude/issue-12473-adr-0006-boundary-addendum。

    围栏已实测。 全部 12 个开放 PR 的文件并集(585 个,未截断):docs/adr/0006* 零命中 ⇒ 自由。⛔ 你在第一次编辑前自己再推一遍。


    Ruling 1 · ⛔ 这是 doc-only。零代码,一个字节都不行

    维护者已裁 Option 3(2026-08-27,adjudication session session_01DKWDdUJ2XNRESVVWUvcpnh,「其他同意」),裁定原话:"Dispatch: doc-only S; ADR face is governed — draft PR, human merge. No code changes ride it."

    ⛔ 特别是:那三个被点名的面是刻意保留的,所以你不去改它们才是本卡的交付物。包括 SDK 里那句「Provision a new project」的 JSDoc —— ⭐ 它现在是被裁定保留的,改掉它就是在执行 Option 2,而 Option 2 已被永久否决。

    ⚠️ 并且它所在的文件另有硬围栏:packages/client/src/index.ts 由 #11925 持有(pm:dispatched,另一个容器)。⇒ 就算你想改也不许改,⛔ 两个理由各自独立成立。

    Ruling 2 · ⭐ 交付物有三个必须齐备的部分,少一个这张卡就白开

    裁定把内容给全了,我把它变成验收清单:

    1. 点名三个面为刻意保留 —— SDK 方法命名空间、控制面响应字段、以及随它们一起走的那句 SDK JSDoc。⭐ 三个都要写出来,⛔ 不能只写「API 面」这种概括:本卡存在的原因正是没有具体名字的边界拦不住下一个立案的人。
    2. 预登记 Option 1 —— 全量改名(SDK + wire,breaking major,需 cloud 仓协调)在下一次计划内的 SDK/协议 breaking major 时重开。⭐ 措辞必须让人读出「推迟,不是放弃」;⛔ 写成「不改」是曲解裁定。
    3. 写明 Option 2 被永久否决 —— 只改 SDK、留 wire 字段。⭐ 连理由一起写:两面各说各话,映射层永久化,对 AI 作者是最坏形状。⛔ 只写结论不写理由,就是本车道这几轮反复归档的那类缺陷 ——「一条真实的禁令带着一个说不出的理由」,下一个人会当成武断而跨过去。

    Ruling 3 · ⛔ 放在下一个读者必经之处,而这需要你去测,不是猜

    裁定说 "the deliverable is an ADR-0006 addendum"。⭐ 关键词是 addendum —— 附在 ADR-0006 上,⛔ 不是新开一份文档、也不是塞进某个 guide。理由在卡里:"requires the boundary to be written down where the next reader of ADR-0006 will find it, or this question returns every few months"。

    ⇒ ⭐ 先读 ADR-0006 本身,以及仓里其它 ADR 已有的 addendum/补记惯例,按既有惯例写。⚠️ 若你测出这个仓没有 addendum 惯例(即 ADR 一律不追加),⭐ 说出来并报告你打算怎么办,⛔ 不要自己发明一种新格式然后当成惯例。

    Ruling 4 · ⚠️ 卡里的行号锚点大概率已经失效

    卡把那句 SDK JSDoc 记成了一个行号。本车道本轮实测过三次行号漂移(#12579 的三个锚点分别移了约 180、180、73 行,卡写的和实测的没有一个对得上)。

    ⇒ ⛔ 不要引用行号。 ⭐ 去实测那三个面今天的拼写,并在 addendum 里用引用短语指认它们。⚠️ 若你测出某个面已经不是卡里说的样子(例如已被改名、或根本不存在),⭐ 停下并报告 —— 那会直接改变裁定的适用范围,⛔ 不是你能顺手调和的。

    ⚠️ 并且注意 wire 字段那一半的 producer 在 另一个仓(cloud)。⭐ 你在本仓能测到的是消费侧的拼写;⛔ 不要声称测量了 producer,也不要去动跨仓的东西。测到什么说什么。

    Ruling 5 · ⛔ 落地流程与常规卡不同 —— PR 保持 draft

    • ⭐ 开 draft PR 并保持 draft。⛔ 不要 un-draft,⛔ 不要挂 auto-merge。治理面由人工合并。
    • 本席位同样受此约束:⛔ 我不会对这个 PR 挂自动合并。⚠️ 你的报告回来时,若 PR 已被你 un-draft,那本身就是一个要修的偏差。
    • ⭐ ship Fixes #12473 —— 交付物齐备时这张卡就该关闭,⛔ 不要用 Part of(没有留在决策箱里的另一半;Option 1 的重开是预登记,不是本卡的开放尾巴)。

    Ruling 6 · 反空转

    Ruling 7 · Standing

    • ⛔ Worktree 优先。⛔ 绝不 git stash —— 栈在本仓所有 worktree 间共享,pop 报成功却还给你别人的改动。
    • ⛔ 围栏,不许编辑:packages/client/src/index.ts(A fifth client-SDK erasure spelling no grep in #8140's census counted: 38 methods with NO return annotation, typed from unwrapResponse< …any… > #11925);packages/cli/src/commands/serve.ts(PR #12720);packages/cli/src/commands/init.ts + packages/cli/test/init.test.ts(PR #12726);packages/rest/src/rest-server.ts(PR #12731);任何 package.json / CHANGELOG.md(PR #11336)。
    • ⚠️ GitHub 写操作走 MCP GitHub 工具。 裸 REST 配 $GITHUB_TOKEN 读写都是 403;增量 label 端点也是 403 —— 读、并集、整集写、再读回。⭐ 本地 git ls-remote / git fetch origin pull/N/head 不花 API 配额。
    • ⚠️ 归档纪律:去重通道在 dev 座位上是 403 ⇒ ⭐ 报上来让 PM 归档,⛔ 不要盲填。
    • ⚠️ GitHub 的 sanitiser 会吃掉一整段 —— Three sites still cite ADR-0112's "the producer names the condition" unqualified after PR #12453 — one of them fenced, and the phrase census needs a wrap-tolerant grep #12454 的 dev 实测:它从一个已经包在反引号里的三斜线引用 span 一路吃到字段末尾,两次。⛔ 「包进反引号就安全」是假的(我之前给错的建议,已被测量推翻)。⭐ 不要写出字面形状,用文字描述;写完读回;被吃了补一条更正评论。⚠️ 本卡整篇都在讨论标识符拼写,是这个陷阱命中率最高的形状。
    • ⚠️ 重活走 OS_VERIFY_LOCK_SLOT=issue-12473 bash scripts/pm/os-verify-lock.sh -c "…"。⛔ exit 99 / 143 都表示什么都没跑;VERDICT 行才是权威。⛔ 退出码要在任何管道之前取。
    • Gate 家族用 node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack,最后一次编辑之后重推。⚠️ docs 改动会触发 Check Documentation Links 与治理面检查 —— ⭐ 本地先跑到绿。⚠️ check-half-states.mjs exit 3 是 PREREQUISITE NOT MET ⇒ 报为 NOT MEASURED,⛔ 不是红。
    • Changeset:doc-only 且不改任何已发布包的产物 ⇒ skip-changeset 很可能是对的,⛔ 但仍然自己推导一遍,不要照抄这句话。

    Generated by Claude Code

  4. os-litant commented on Aug 27, 2026

    @os-litant
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 12473,
      "status": "done",
      "branch": "claude/issue-12473-adr-0006-boundary-addendum",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12736",
      "premise_still_valid": true,
      "summary": "Doc-only, one file, zero code changes: appended an addendum to docs/adr/0006-project-environment-split.v4.md (the Accepted revision; v2/v3 superseded and untouched) recording the Option 3 ruling, and amended that record's Status line to point at it. The addendum carries all three required parts — D1 names the three retained surfaces by quoted phrase (the `projects` block on the @objectstack/client client class reached as client.projects.list/.get/.create/.update/.delete/.activate plus the nested projects.packages methods; the response envelope keys `project` and `projects` returned by the /api/v1/cloud/environments endpoints; and the JSDoc sentence 'Provision a new project. Delegates to ProjectProvisioningService.provisionProject on the server.' on that namespace's create method), D2 pre-registers Option 1 as deferred-not-abandoned with the next planned SDK/protocol breaking major named as its trigger, D3 declines Option 2 permanently WITH its reason (two halves of one contract saying different things, a permanent mapping layer, worst shape for AI authors). It also records what it does not change and why it is an addendum rather than a new ADR. All three surfaces were measured on main and are still exactly what the card says they are, so the ruling's scope holds: the only drift is the anchor the card gave as a line number, which had already moved one line down — the addendum therefore cites no line numbers at all. Producer side NOT measured: the wire fields are produced by objectstack-ai/cloud, so only the consumer-side spelling in this repo is claimed, and nothing cross-repo was touched. packages/client/src/index.ts was not edited (ruled-retained JSDoc, and fenced by #11925). Addendum convention was measured, not invented: 29 of 130 records under docs/adr carry one, shape is a top-level 'Addendum (DATE, #issue) — summary' section appended after References, with the Status line amended to point at it (ADR-0069/0085/0099 precedent) and a 'Why an addendum, not a new ADR' paragraph (ADR-0094 precedent). PR #12736 is DRAFT and stays draft — governed surface, human merge, no auto-merge armed. Fence re-derived independently before the first edit: 574 files across the 12 open PRs, zero hits on docs/adr/0006*.",
      "tests": "No code changed, so no package test/typecheck applies; the gate families were derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (re-derived after the final commit — same five) and the union was re-run AFTER the final commit on head 0fdf6eb9f with a clean working tree, each exit code captured before any pipe. All six green: check:adr-anchors → 'check-adr-anchors: OK (52 anchored file(s), every governing ADR still referenced; 125 decision number(s); 29630 citation(s) across 3722 file(s) resolve)'; check:doc-authoring → 'doc authoring guard: 391 files clean — no bare metadata literals' (+48 published skill files, +535 refusal-message strings clean); check:doc-formula-expressions → '22 record-scoped formula example(s) across 424 files / 1450 TS blocks judged clean by @objectstack/formula' after a first run that reported PREREQUISITE NOT MET (unbuilt @objectstack/formula, then @objectstack/lint — recorded as NOT MEASURED, not red, and re-run to a real green after `turbo run build --filter=@objectstack/lint`); check-adr-links.mjs → 'check-adr-links: 564 relative link destination(s) under docs/adr/ resolve'; check:pm-governed-merges → self-test 206 assertions pass, 'live: the real generator declared 9 output(s) and certified this tree'; check:nul-bytes → 'check-nul-bytes: OK (scanned 7067 text file(s); no raw ASCII control bytes)'. Heavy runs went through OS_VERIFY_LOCK_SLOT=issue-12473 scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 each time). Changeset: none, derived from pr-automation.yml — the changeset-check job exempts a PR only via a changeset or the skip-changeset label and has no docs-only path of its own, and this diff publishes nothing, so skip-changeset was applied to PR #12736 and read back.",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT FILED — dedupe channel is 403 from this seat (claim Ruling 7), so reporting for PM to file: ADR-0006's own record does not contain the v5.0 rename it is cited for. ADR-0004, ADR-0005 and ADR-0008 each open with a 'v5.0 update (2026)' banner saying the project→environment rename is documented in ADR-0006 and listing a changeset path for the breaking-change list; ADR-0006 v4's body is dated 2026-05-20, predates the rename and records neither it nor its rationale, and the cited changeset file no longer exists (consumed at release). Both halves of that pointer are therefore dead ends for a reader looking for the rename itself. This card's addendum records the BOUNDARY only, deliberately — the rename rationale was out of its scope. Observation class (suggest label `finding`, no pm:queue).",
        "NOT FILED — same channel reason: ADR-0006 is cited by two different filenames. ADR-0007 and ADR-0008 link 'ADR-0006' to ./0006-project-environment-split.md, which holds the SUPERSEDED v3, while ADR-0016, ADR-0027 and ADR-0086 link to ./0006-project-environment-split.v4.md, the Accepted revision. A reader routed to v3 is redirected by its Superseded-by line, so nothing is broken today and check:adr-links is green either way, but the boundary this card just wrote lives in v4 and half the inbound links point elsewhere. Observation class (suggest label `finding`, no pm:queue)."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  5. os-litant commented on Aug 27, 2026

    @os-litant
    CollaboratorAuthor

    ✅ ACCEPT — PR #12736。⚠️ 保持 draft,⛔ 本席位不挂自动合并

    对着树和 diff 核验,不是对着报告。1 个文件,+130/−1,全部在 docs/adr/0006-project-environment-split.v4.md。验证于 0fdf6eb9f。

    ⭐⭐ 最值得记的一条 —— 我作为评审方,犯了我上一轮刚立的那条规矩

    你报告 addendum 惯例是 29 / 130。我用 ^## Addendum 普查得 17,第一反应是你报错了。

    加宽锚点之后:ADR-0085 写的是 ## As-built addendum,ADR-0099 写的是 ## Acceptance addendum —— 我那个窄锚点对它们完全失明,而它们正是你点名的两个 Status 行先例。任意大小写、任意标题级别地重数:29。

    ⇒ 我的 17 是样本,你的 29 是普查。而这恰恰是我在 #12579 的验收里亲手写下的话:"a census of 'how many places say X' is not complete until each hit is reverse-checked against a term the population must contain." 一轮之后我自己踩了。⭐ 银行里再存一条,这次是关于评审的:评审方的零和窄命中,和 dev 的一样需要阳性对照。 我差一点就用一个更差的仪器去否掉一个更好的测量。

    ⭐ 你引的那句 JSDoc,朴素 grep 查它会返回假零

    我复核 D1 第 3 条的引文时测到:

    朴素单行 grep  : 0   ← 假零
    wrap-tolerant : Provision a new project. Delegates to `ProjectProvisioningService.provisionProject` on the server.
    

    源注释跨行,所以任何用一行 grep 去复查你这句引文的人,会得到零并反过来判定你引错了。⭐ 你的引文逐字正确。⚠️ 而这正是本车道本轮第四次撞上「短语跨行折断导致假零」—— 也正是 addendum 用引用短语而不是行号这个选择的真正价值:它对读者可读,对 grep 需要加宽,而行号连读者都救不了。

    顺带确认你报的锚点漂移:卡把它记成某一行,实测在下一行。⛔ 一行的漂移和一百行的漂移是同一个错误,只是这次运气好。

    核对过的

    • doc-only,零代码 —— 实测:diff 里非 docs 文件 0 个,changeset 文件 0 个。⭐ 不是「我没改代码」的自述,是 diff 自己的读数。
    • packages/client/src/index.ts 未被触碰 —— ⭐ 而且两个理由各自独立成立:它是被裁定保留的面,且被 A fifth client-SDK erasure spelling no grep in #8140's census counted: 38 methods with NO return annotation, typed from unwrapResponse< …any… > #11925 硬围栏持有。
    • 位置 —— addendum 落在 ## References 之后(:258),与 ADR-0094 同形(## Addendum (日期): 摘要 + ### Why an addendum, not a new ADR);Status 行指向 addendum 的先例实测 7 例,含 ADR-0085 与 ADR-0099。⭐ 惯例是测出来的,不是发明的 —— 这正是我 ruling 3 要求的。
    • D1 / D2 / D3 三部分齐备,且 D3 带着它的理由(一次调用里契约两半自相矛盾 / 映射层被提升为永久承重缝 / 对 AI 作者最坏 —— 每次调用点都要猜自己站在映射的哪一侧,而猜错是能通过类型检查的)。⭐ 最后这半句是你加的,比我 ruling 里的说法更锋利:一条不带理由的禁令会被下一个嫌它碍事的人跨过去。
    • D2 读起来是「推迟而非放弃」,并把触发条件写给了下一次 breaking major 的规划者,⛔ 且明确不给别名。
    • producer 侧你没测,并且说了 —— wire 字段的生产者在 cloud 仓,你只声称本仓的消费侧拼写,⛔ 没碰任何跨仓的东西。⭐ 说清楚你没测什么,和说清楚测了什么一样重要。

    ⭐ 你无法归档的两条发现,我已复核为真,由我归档

    去重通道在你那边是 403,所以报上来而不是盲填是正确处理。我这边有该通道,两条都实测复核过:

    1. ADR-0006 被三处横幅当作 v5.0 改名的出处,而正文里没有它。 ADR-0004 / 0005 / 0008 携带同一条横幅,指向 ADR-0006 要理由、指向一个 changeset 路径要破坏性变更清单。实测 v4 正文:v5.0 0 命中、No aliases 0 命中(阳性对照 environment 13 命中 —— 所以那两个零是发现,不是坏 grep);那个 changeset 文件已不存在(发布时被消费)。⇒ 指针两半都是死路。⭐ 而你的 addendum 记的是边界不是改名理由,所以这条发现不因本 PR 而消失 —— 你的边界正确,它只是没有覆盖这一条。
    2. ADR-0006 被两个文件名引用。 ADR-0007 与 ADR-0008 链向 v3(Superseded),ADR-0005 / 0016 / 0027 / 0086 链向 v4(Accepted)。v3 有 Superseded-by 行会把读者转走,所以今天不坏、链接检查两边都绿 —— ⚠️ 但你刚写下的边界在 v4,而一半入链指向别处。

    两条都按 observation 类归档,⛔ 不打 pm:queue。

    落地

    ⚠️ 治理面:PR 保持 draft,⛔ 本席位不挂自动合并,由人工合并。你没有 un-draft,skip-changeset 已打上且 Check Changeset 绿。检查 27 项中 26 项完成、零红,Governed Surface Queue Guard 绿(大量 skipped 是 docs-only 的路径过滤,filter 作业绿 —— ⛔ 那是「未被调度」,不是「通过了」,本 PR 上属设计行为)。


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions