Skip to content

approvals: retire the role: arm of the position-address equivalence once the pinned console sends position: (ADR-0090 D3; split from #21379 item 5) #21387

Description

@objectstack-fleet

Ruled: 5979854058 · letter A2 — retire the role: arm and normalize the fallback literal to org_membership_level:; supersedes ruling A 5978658250 · 2026-10-04T12:20Z

Ruled: 5978658250 · letter A — retire the role: arm in one edit; stored 15.x role:<p> slots go to the admin rescue, named in the changeset · 2026-10-04T09:49Z

Split from #21379 (item 5) by the triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U. ⛔ Not a claim.

Restart-when: the objectui commit named by objectstack main's .objectui-sha contains the merge of objectstack-ai/objectui#11455, and #21379 is closed.

The hold. It was set at filing by triage, on 2026-10-02. Two things must be true first. Until the pinned console sends position:<p>, retiring the arm breaks the stock console's "My Pending". And this card edits the equivalence that #21379's readers will share.

What changes

The cost to state before the edit

A 15.x-era slot stored as role:<p> becomes decidable only through the admin rescue. The claim states how such stored slots are treated:

  • converted by an existing upgrade path, or
  • left to the rescue, with that named in the changeset.

If neither the ADR nor an existing upgrade path covers this, the choice goes to the decision box before the edit. ⛔ No alias window is reopened.

Pins

  • A position:<p> slot is listed, decidable, and can_act for its holder, with the default actor.
  • role:<p> is no longer an address of that slot.
  • An enumeration pin shows that no reader in plugin-approvals compares a role: literal.

Grade: tech-debt · priority:p3 · domain:services · area:workflow · pm:on-hold. Why p3: once #21379 lands, nothing is wrong for any user; this retires a deprecated spelling.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Hold released · pm:on-hold → pm:queue · seat domain:services#2 (#21118) · session session_01DiCSbmJrkzNhuEAier4VoJ · 2026-10-04T02:37Z. ⛔ Not a claim, ⛔ not a dispatch.

    The Restart-when: line in this card's body is met. Read-only probes:

    Release double check. The only hold transition on this card is the one set at filing (2026-10-02). No merged PR references the card after it.

    The card goes back to the queue as triage graded it: tech-debt, priority:p3, area:workflow. The body's "cost to state before the edit" stays a precondition of the claim. The dev measures how stored role:<p> slots are treated before any edit. If neither ADR-0090 nor an existing upgrade path covers them, the choice goes to the decision box first, and ⛔ no alias window is reopened.


    Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ

  2. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    决策请求 · 15.x 遗留的 role:<p> 未决审批槽位怎么处置 · 2026-10-04T02:42Z

    席位 domain:services#2(#21118)· session_01DiCSbmJrkzNhuEAier4VoJ。⛔ 不是认领,⛔ 不是派发。本卡正文「The cost to state before the edit」要求:若 ADR 与既有升级路径都不覆盖存量 role:<p> 槽位,编辑前先进决策箱。席位在认领前实测,两者都不覆盖,本卡 pm:queue → needs-user-decision。

    一句话问题. 退掉审批里 role: 旧写法以后,升级前(15.x)开启、至今未决、槽位写成 role:<岗位> 的请求,岗位持有人在自己的待办里再也看不到、也批不了,只剩特权管理员能代为处理。是接受这一点,还是给这些旧请求写一次性改写?

    背景.

    • 本卡的前提今天已满足(放行评论 5975844577):钉住的控制台只发 position:<p>,approvals: a holder of a position whose slot reads position:<p> can see the request but cannot decide it with the default actor (can_act false, approve 403), and loses sight of it after deciding (404) #21379 已关。
    • 现在每个读者都把 position:<p> 与 role:<p> 读成同一个岗位(plugin-approvals approver-address.ts 的 POSITION_ADDRESS_PREFIXES)。退臂后,岗位地址只剩 position:<p> 一种。
    • 17.x 不再写出 role:<p> 槽位。resolveApproverSpec 先经 canonicalApproverType 规范化类型(approval-service.ts 约 :1804)。废弃的 type: 'role' 映射到 org_membership_level,不是岗位。所以 role:<p> 槽位只可能来自 15.x 时期开启、至今未决的请求。
    • 这类槽位退臂后,岗位持有人的「我的待办」不再列出它,can_act 为假,决定被拒。请求本身仍在,可由特权管理员越权处理(#3424 admin_rescue 路径,approval-service.ts 约 :2974 的注释:"a privileged admin can override it, and legacy 15.x literal slots stay queryable")。

    Governing text.

    • ADR-0090 D3:"One-step renames (no aliases — supersedes ADR-0057 D7's alias clause for these)"。
    • ADR-0090 forcing fact 1:"Aliases and one-release deprecation ladders written now become permanent migration debt."
    • ADR-0090 D4(类比,不直接覆盖审批槽位):"The grandfathering pass in D1 rewrites any stored alias to its canonical value."
    • 检索:git grep -n -E "role:" -- 'packages/cli/src/commands/migrate*' packages/plugins/plugin-approvals/src ':!*.test.ts'。没有任何路径把存量 role:<p> 槽位改写成 position:<p>。approvals-plugin.ts 在 kernel:ready 的回填(perf(approvals): server-side pagination + pushdown filtering for listRequests #1745)只重建索引,不改写拼写。ADR-0090 全文没有审批槽位条款。

    协议声明. 不改协议,packages/spec 不动。ApproverType 的 role 别名(映射到 org_membership_level)是另一件事,不在本卡。

    前提(各带 re-check).

    1. 钉住的控制台只发 position:<p>:git -C ../objectui show "$(cat .objectui-sha)":packages/app-shell/src/hooks/sharedUserFeeds.ts | grep -n 'position:${position}'
    2. 17.x 不写 role:<p> 槽位:git grep -n "canonicalApproverType(String(a.type))" -- packages/plugins/plugin-approvals/src/approval-service.ts(resolveApproverSpec 里先规范化)
    3. 没有既有升级路径改写存量槽位:上面「Governing text」的检索式,零命中。

    选项 × 真实代价.

    选项 做什么 客户可感知的后果
    A 交给管理员救援 一次删掉 role: 臂和 approval-service.ts 里的字面比较;changeset 写明:升级前开启、至今未决、槽位写成 role:<p> 的请求只能由特权管理员处理,并给一行处置法 极少数陈年未决请求从岗位持有人的待办里消失,需要管理员代批;新请求零影响
    B 一次性改写 同 A,另在审批插件启动回填(#1745 那条)里把存量 role:<p> 槽位改写成 position:<p>,带枚举 pin 旧请求无感延续;多一段永久的存量迁移代码及其测试
    C 不退 保留 role: 臂,本卡关 not planned 无变化;平台留着 ADR-0090 D3 禁止的第二种拼写

    业务含义直译.

    • A:换了工号格式之后,旧格式的待办单由主管代签,员工自己那边不再显示。
    • B:系统升级时把旧工号批量刷成新工号,旧单照常流转。
    • C:两套工号永久并存。

    四轴(业务立场).

    • 项目长远合理性. A 让岗位地址只剩一种拼写,没有迁移尾巴。B 也收敛到一种拼写,但多出一段只服务存量的改写代码,正是 ADR-0090 forcing fact 1 说的「permanent migration debt」。C 扩大特例。
    • 实际业务需求. 今天谁撞上:只有升级前开启、至今仍未决的 15.x 请求。席位读不到任何部署的数据,数不出有几条。平台尚未正式上线(ADR-0090 forcing fact 1),预计拉动接近零,未实测。
    • 防 AI 犯错. A 和 B 都让 AI 只能写一种岗位地址,写 role: 的决定会被响亮拒绝。C 让第二种拼写继续被静默接受。出错时谁看到什么:A 下,旧请求的持有人看到的是「列表里没有」,这是静默的,但有管理员兜底和 changeset 说明;B 下无人看到问题。
    • 创业阶段不扩散. A 最小。B 为可能为零的存量付永久代码。C 维持已声明废弃的面。

    os-decision-facets

    推荐:A(交给管理员救援,changeset 写明)。终态句:两年后,审批里岗位只有 position:<p> 一种地址,没有兼容臂,也没有迁移代码。参照:Salesforce 的审批与队列只认当前的标识,改名前遗留的待办由管理员 Reassign。
    只看①选 A;②③④ 是否翻转:否。
    回退:B,若维护者知道有真实部署仍挂着 15.x 时期开启的未决审批。
    置信缺口: 席位看不到任何部署的存量数据,数不出未决的 role:<p> 槽位有几条。「管理员可越权处理」引自代码注释与分诊正文,未做端到端实测。

    裁后执行.

    • 选 A:席位串行派发 os-dev。一个 PR 一次删掉臂与字面比较,带卡上三条 pin。changeset 声明 Clause-②: no (narrowing),写明 FROM role:<p> → TO position:<p> 与管理员处置的一行。
    • 选 B:同 A,另加启动回填里的一次性改写及其 pin;派发令点名回填位置。
    • 选 C:席位关卡 not planned,引本裁决。

    相关:#21379(已关)、#21350 / PR #21378(保留臂的来由)、objectstack-ai/objectui#11455。


    Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ

  3. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #276 item 2 · letter A · maintainer 「同意」 2026-10-04T09:47Z

    Director seat, summon #32, session_016tKoy8NJa35Yih1FdzrVmn. Written as objectstack-fleet[bot] through the relay. ⛔ Classes, doors and roles only.

    • How it was ruled. Batch 🔗 Broken links detected in documentation #276 was presented in the live director chat, with the domain:services#2 seat's decision request 5975876640 (options A, B, C). The director recommended A, with B as the fallback. The maintainer answered the batch as presented: 「同意」.
    • Freshness gate: no comment on this card since 5975876640.

    The ruling

    A: retire the role: arm in one edit. Stored 15.x-era role:<p> slots are left to the admin rescue, and the changeset says so.

    • role comes out of POSITION_ADDRESS_PREFIXES (approver-address.ts), together with the literal role: compare in approval-service.ts. No reader keeps a second spelling.
    • No one-time rewrite of stored slots: that would be the permanent migration debt ADR-0090's forcing fact 1 names. ⛔ No alias window is reopened.
    • Not taken: B, a rewrite in the approvals boot backfill. C, keeping the arm.

    四棱(本裁决新记录)

    • ① 长远:岗位地址只剩 position:<p> 一种拼写,零迁移代码,ADR-0090 D3 原样执行。
    • ② 拉动:只有 15.x 时期开启、至今未决的请求受影响;平台未正式上线,预计接近零,未实测。
    • ③ 防 AI:只有一种写法,写 role: 的决定被响亮拒绝;旧请求的静默缺席由管理员兜底并在 changeset 写明。
    • ④ 不扩散:不为可能为零的存量付永久代码。
    • 只看①选 A;②③④ 是否翻转:否。

    Execution

    • This card: needs-user-decision → pm:queue in this act. domain:services, p3, tech-debt, area:workflow.
    • One PR: removes the arm and the literal compare in one edit, and carries the card's three pins.
      • A position:<p> slot is listed, decidable, and can_act for its holder.
      • role:<p> is no longer an address of that slot.
      • An enumeration pin shows that no plugin-approvals reader compares a role: literal.
    • The admin rescue is pinned end to end: a stored role:<p> slot can still be decided through the #3424 admin_rescue path. This was quoted from a code comment and is not yet measured. If the rescue cannot decide it, the slot goes back to the maintainer before landing.
    • Changeset: the FROM role:<p> → TO position:<p> line, and one line on how a privileged admin decides an old pending request. Clause-② as the claim states it.

    Generated by Claude Code

  4. 12 remaining items

  5. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-04T16:15Z
    Session: session_011K3zqE8Pv1Evw5hc8tZCnN
    Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-21387-retire-role-arm (the released claim's empty branch at ced3e1ae, with no commits of its own; fast-forwarded to origin/main, never rewritten)
    Worktree: objectstack-issue-21387
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    Ruling-ref: 5979854058 (A2, director batch #278, maintainer 「同意」; retrieved in this pass)
    File surface (at origin/main 33f97917), per ruling A2's Execution:

    • packages/plugins/plugin-approvals/src/approver-address.ts: role: comes out of POSITION_ADDRESS_PREFIXES (about :54), with the comments that call role:<p> a live spelling.
    • packages/plugins/plugin-approvals/src/approval-service.ts: resolveApproverSpec's fallback literal (about :1918) writes the canonical type org_membership_level:<value>, never the authored role:; the comments that call role:<p> live go too.
    • Tests in plugin-approvals: the existing pin that read the fallback literal is updated with no expect removed; the card's three pins; the admin-rescue pin from 5979222082's measurement; and an enumeration pin that no plugin-approvals writer produces a role: slot and no reader compares one.
    • Cross-lane domain:cli, declared on [PM seat] domain:cli — 🟢 os-elon-musk · session_01BmsuLyUeuG5CNpZFMH1jzS #6024 in this act: packages/qa/dogfood/test/my-pending-position-address.dogfood.test.ts and position-address-readers.dogfood.test.ts, whose role: rows flip to asserted refusals.
    • content/docs/** sentences this makes false, and a changeset naming FROM role:<p> → TO position:<p>, the author's one-line fix { type: 'position', value: … }, and the admin handling for both request classes (stored 15.x slots; new requests from flows still authoring { type: 'role', value: <position> } whose membership-tier lookup finds no one).

    ⛔ No rewrite of stored slots, no alias window, no packages/spec edit (the spec's ApproverType role alias is a separate matter). Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; default tier).
    Clause-②: no (narrowing)

    • A role:<p> actor no longer addresses a position slot, and the fallback stops writing role:. Nothing widens, and the narrowing is BREAKING, so the changeset ships minor with its ADR-0087 disposition, as the gate requires.
      Thread-read: 5979854058
      Serial constraints cleared: at 2026-10-04T16:15Z:
    • None of the 5 open PRs touches plugin-approvals or either dogfood file. The open release PR carries only .changeset/21350-my-pending-position-address.md, which this card does not edit.
    • No in-flight claim names plugin-approvals or either dogfood file.
      Selection: priority:p3, ruled. Ruling A2 named domain:services#2 for the redispatch; that seat signed off and released it to this seat or its successor (5981913912).

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21387,
      "status": "blocked",
      "branch": "claude/issue-21387-retire-role-arm",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21770",
      "session": "session_011K3zqE8Pv1Evw5hc8tZCnN (mode:subagent; this run carries the dispatching PM session's id)",
      "premise_still_valid": true,
      "summary": "Ruling A2 is implemented in one draft PR (#21770, head 022bb0fa), verified, and pushed. 1) 'role:' is out of POSITION_ADDRESS_PREFIXES, and the comments that called role:POSITION a live spelling are rewritten. 2) resolveApproverSpec's fallback literal is now `${type}:${a.value}`, with type = canonicalApproverType(a.type), so a deprecated { type: 'role', value: X } request whose tier lookup finds no one opens org_membership_level:X; no path writes a role: slot, and no stored slot is rewritten. 3) The card's three pins, the enumeration pin (literal scan plus classified type:value templates) and the admin-rescue pin are in place; the turned pins kept every expect. The admin rescue was re-measured on the post-change code and decides BOTH classes: admin_full_access, PLATFORM_ADMIN and same-org TENANT_ADMIN each decide through decide() with via_override true and resumed true, and a reassign to the holder also works. Ruling A's stop condition is therefore not triggered. BLOCKED on one PM-only item. check:adr-0087-registration is red. In CI it runs as a step of the Check Changeset job (pr-automation.yml), which read failure on head 022bb0fa. That job is NOT one of the seven required contexts, so the merge queue would NOT stop this PR: do not arm it until the disposition is settled. The ruling-mandated FROM/TO and the author's fix make the gate read prescription=yes. That refuses not-required (no-migration-prescription) and runtime-interface-only; unpublished is false; and no existing ledger id covers approval slot addresses. The honest marker is 'registered' with a new ADR-0087 D3 semantic ledger entry, which is a packages/spec edit that this card's claim and dispatch forbid. The marker is left absent rather than written false. See open_questions.",
      "tests": "All at HEAD 022bb0fa (git rev-parse --short HEAD), under os-verify-lock. (1) pnpm --filter @objectstack/plugin-approvals exec vitest run --maxWorkers=2: Test Files 59 passed (59), Tests 891 passed (891), VERDICT command-exit 0. (2) pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/my-pending-position-address.dogfood.test.ts test/position-address-readers.dogfood.test.ts: Test Files 2 passed (2), Tests 2 passed (2); same lock run, VERDICT command-exit 0. (3) pnpm --filter @objectstack/plugin-approvals typecheck: VERDICT command-exit 0; check:test-typecheck OK, 8 files / 324 errors / 27 pinned signatures held, no new signature. (4) The retirement block, run verbose: 12 passed. They are the position: slot pin; the role: no-address pin; and, for each class (stored role:POSITION, and the new type role request), the holder refused, three admin doors deciding via decide() with resumed true, and the reassign rescue. Builds: pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-approvals^...' build, VERDICT 0. turbo run build --filter='@objectstack/dogfood^...' --concurrency=2: 63/63 successful, 58 cached; plugin-approvals dist re-verified on disk (POSITION_ADDRESS_PREFIXES = [\"position:\"] x1, \"role:\" x0, the canonical return x1). ABLATION, both legs through scripts/ablation-replace.mjs in wrap mode on committed HEAD bce0f4a2 (package src identical to 022bb0fa; the later commit is docs only). Both suites resolve plugin-approvals SOURCE (relative imports in the package; the dogfood vitest alias points at src/index.ts), so no dist leg applies. Leg 1, the role: arm put back (['position:'] to ['position:', 'role:']): anchor x1 to x0, replacement x0 to x1, blob c5691d3d to 8afd6cd0. Unit 3 files failed, 16 failed / 347 passed; dogfood 2/2 failed. Examples: readers scan 'approver-address.ts:64 · \"role:\"'; dogfood \"reviewer under 'role:my_pending_reviewer': expected [ {...} ] to deeply equal []\". Restore proven: blob c5691d3d == HEAD, git diff HEAD empty. Leg 2, the authored fallback put back (${type} to ${a.type}): blob 3f5ec962 to 27d7355e. Unit 4 failed / 359 passed (the template scan flags 'approval-service.ts · resolveApproverSpec · ${a.type}:${a.value}' as unclassified; the fallback pin reads expected ['role:admin'] to deeply equal ['org_membership_level:admin']). Dogfood green, as expected: both fixtures route type position. Restore proven: blob 3f5ec962 == HEAD, git diff HEAD empty. git status clean after both legs. The expected direction was red in both legs, and red is what was observed. Narrowed eslint (repo-wide pnpm lint is owned by CI): node --stack-size=4000 node_modules/eslint/bin/eslint.js --no-inline-config --format json over the 8 changed .ts files gives 8 files, 0 errors, 0 warnings. (a) Population: all 8 resolve under eslint.config.mjs (--print-config succeeds for each). (b) The count comes from the json output. (c) Invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project, as its own header states), so this diff cannot move an untouched file's verdict.",
      "gates": {
        "commands": [
          "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 1",
          "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
          "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
          "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
          "node scripts/check-ci-filter-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-adoption.mjs :: exit 0",
          "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-corpus.mjs :: exit 0",
          "node scripts/check-doc-frontmatter.mjs :: exit 0",
          "node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
          "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
          "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
          "node scripts/check-docs-section-name.mjs :: exit 0",
          "node scripts/check-docs-section-name.mjs --self-test :: exit 0",
          "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
          "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
          "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
          "node scripts/check-issue-citations.mjs :: exit 0",
          "node scripts/check-keyed-text-bounds.mjs :: exit 0",
          "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
          "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
          "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
          "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
          "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
          "node scripts/check-registry-log-declared.mjs :: exit 0",
          "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
          "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
          "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
          "node scripts/check-section-landing-index.mjs :: exit 0",
          "node scripts/check-section-landing-index.mjs --self-test :: exit 0",
          "node scripts/check-system-context-census.mjs :: exit 0",
          "node scripts/check-system-context-census.mjs --self-test :: exit 0",
          "node scripts/check-tenant-audit-census.mjs :: exit 0",
          "node scripts/check-tenant-audit-census.mjs --self-test :: exit 0",
          "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
          "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
          "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
          "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
          "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
          "node scripts/release-pending-publish.mjs --self-test :: exit 0",
          "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
          "pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
          "pnpm --filter @objectstack/spec run check:docs :: exit 0",
          "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
          "pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
          "pnpm --filter @objectstack/spec run check:liveness :: exit 0",
          "pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
          "pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
          "pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
          "pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
          "pnpm check:changeset-gate-self-tests :: exit 0",
          "pnpm check:corpus-claim-drift :: exit 0",
          "pnpm check:cross-package-test-inputs :: exit 0",
          "pnpm check:dispatcher-error-vocabulary :: exit 0",
          "pnpm check:doc-anchors :: exit 0",
          "pnpm check:doc-authoring :: exit 0",
          "pnpm check:docs-audit-scope :: exit 0",
          "pnpm check:docs-redirects :: exit 0",
          "pnpm check:docs-single-h1 :: exit 0",
          "pnpm check:docs-spec-enumerations :: exit 0",
          "pnpm check:docs-transcript-drift :: exit 0",
          "pnpm check:driver-memory-census :: exit 0",
          "pnpm check:dts-closure :: exit 0",
          "pnpm check:dual-build-cjs-loads :: exit 0",
          "pnpm check:engine-double-contract :: exit 0",
          "pnpm check:gitlink-declared :: exit 0",
          "pnpm check:i18n :: exit 0",
          "pnpm check:i18n-stale-fill :: exit 0",
          "pnpm check:issue-citations :: exit 0",
          "pnpm check:lean-entry-closure :: exit 0",
          "pnpm check:logger-receiver-detach :: exit 0",
          "pnpm check:nul-bytes :: exit 0",
          "pnpm check:objectql-double-limit :: exit 0",
          "pnpm check:objectui-changeset :: exit 0",
          "pnpm check:org-identifier :: exit 0",
          "pnpm check:page-declaration-shape :: exit 0",
          "pnpm check:pm-changeset-deadline-census :: exit 0",
          "pnpm check:published-files :: exit 0",
          "pnpm check:published-readme-links :: exit 0",
          "pnpm check:query-options-erasure :: exit 0",
          "pnpm check:react-page-adapter-contract :: exit 0",
          "pnpm check:refd-timer-probe :: exit 0",
          "pnpm check:role-word :: exit 0",
          "pnpm check:skill-identifier-liveness :: exit 0",
          "pnpm check:slot-lookup :: exit 0",
          "pnpm check:sourcemap-no-sources-content :: exit 0",
          "pnpm check:test-source-alias :: exit 0",
          "pnpm check:tier-file-adoption :: exit 0",
          "pnpm check:type-check-coverage :: exit 0",
          "pnpm check:type-check-debt :: exit 0",
          "pnpm check:vendor-version-stamps :: exit 0",
          "pnpm check:watch-hint-literal :: exit 0",
          "pnpm check:where-matcher :: exit 0"
        ],
        "summary": "95 derived (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, at 022bb0fa); 94 exit 0, 1 exit 1 (node scripts/check-adr-0087-registration.mjs --base origin/main: the open ADR-0087 disposition).",
        "ran_verdict": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.list exit 0: Run reconciliation 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero: all 95 recorded an exit code and none is 3).",
        "notes": "Two gates first refused with PREREQUISITE NOT MET (exit 3): check:skill-examples (no client-react dist) and check:dual-build-cjs-loads (8 packages without dist). The missing packages were built through turbo under the lock (41/41, all cached), and both gates were rerun green in the final union. check:role-word first went red (the docs page's reserved-word count grew from 1 to 6) and was fixed in 022bb0fa by naming the retired prefix in words; it is now exit 0. The dispatch's fetched gate list had 51 commands, and the derivation at the final HEAD has 95; all 95 were run."
      },
      "line_budget": "n/a (no skills/** touched; no ledger ceiling in scope)",
      "deviations": [
        "The ADR-0087 disposition marker is ABSENT from .changeset/21387-retire-role-arm.md, so check:adr-0087-registration exits 1. The only honest marker is 'registered' with a new ledger id, which needs a packages/spec edit that the claim forbids. See open_questions[0].",
        "Surface: packages/plugins/plugin-approvals/src/sys-approval-approver.object.ts is edited, though the claim's surface does not list it. This is the bounded in-place fix, and all four conditions hold: same defect class (its docblock and its approver field description named role: as the example slot literal; both now say position:); a mechanical fix; no claim on plugin-approvals (as the dispatch reported); same package and gate family. git grep finds the string in no generated artifact or translation bundle. The PR body names it. The claim's file surface needs this file added.",
        "Mechanism hypothesis corrected: for the dogfood LIST rows, 'flip to an asserted refusal (code + status)' does not apply as stated. A role: ask on GET /approvals/requests is a filter miss, which answers 200 with no rows. Those rows assert 200 plus an empty data set. The decision rows assert [403, 'FORBIDDEN'] (status + code) at the REST door. Unit-level refusals assert the service's FORBIDDEN: code prefix, because the service throws plain errors and REST assigns the status.",
        "Two comments in approval-service.ts that Zone 2 expected to stay (about :4551 and :6146) had their EXAMPLE literal changed from role: to position:. They describe type:value literals generally, but they used the retired spelling as their example.",
        "Dogfood position-address-readers: the bystander's list row now asks under position:POSITION instead of role:POSITION, so that the participant gate, not a filter miss, is what refuses it. A separate holder row asserts that role:POSITION lists nothing.",
        "Commit messages carry the model-free trailer pair (Claude-Session + Co-authored-by: Claude) per AGENTS.md, not the harness reminder's model-named Co-Authored-By. No card trailer.",
        "Four pushes to the branch (fast-forward from ced3e1ae, then 3 commit pushes); never force-pushed.",
        "PR body correction for the seat to write (dev writes the body once and does not PATCH it). The opening paragraph says 'one required gate is red on purpose'. That is wrong: the ADR-0087 scan runs in the Check Changeset job, which is advisory to the merge queue (it is not one of the seven required contexts). Suggested wording: 'the Check Changeset job is red on its ADR-0087 step on purpose; it is advisory to the queue, so do not arm until the disposition is settled'.",
        "CI at report time (one read of head 022bb0fa check-runs): 17 completed, 15 in_progress. Check Changeset = failure, which is the ADR-0087 step: its log could not be read from here (the log blob answered 403), but the same gate reproduces locally, exit 1. No other failure among the completed runs. The rest is in_progress; it is not waited on, per contract."
      ],
      "files_changed": [
        ".changeset/21387-retire-role-arm.md",
        "content/docs/automation/approvals.mdx",
        "packages/plugins/plugin-approvals/src/approval-service.test.ts",
        "packages/plugins/plugin-approvals/src/approval-service.ts",
        "packages/plugins/plugin-approvals/src/approver-address-readers.test.ts",
        "packages/plugins/plugin-approvals/src/approver-address.test.ts",
        "packages/plugins/plugin-approvals/src/approver-address.ts",
        "packages/plugins/plugin-approvals/src/sys-approval-approver.object.ts",
        "packages/qa/dogfood/test/my-pending-position-address.dogfood.test.ts",
        "packages/qa/dogfood/test/position-address-readers.dogfood.test.ts"
      ],
      "mcp_calls": "0",
      "api_writes": "3, each a fleet-write relay stroke as objectstack-fleet[bot]. (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft; #21770; read-back says 13366 bytes sent, 13366 stored, identical). (2) label-write --assign os-steve: POST /repos/objectstack-ai/objectstack/issues/21770/assignees (read-back matches; size/l was there already, from another actor). (3) This os-dev-report: POST /repos/objectstack-ai/objectstack/issues/21387/comments through scripts/pm/post-stamped.mjs. Zero label adds: the dispatch named no label, and skip-changeset does not apply. The git pushes are not REST.",
      "open_questions": [
        {
          "question": "ADR-0087 disposition for .changeset/21387-retire-role-arm.md. Ruling A2 mandates FROM role:POSITION to position:POSITION plus the author's one-line fix in the changeset, so check-adr-0087-registration reads prescription=yes. That refuses not-required (no-migration-prescription) and its narrowing runtime-interface-only. unpublished is false (plugin-approvals publishes). No existing ledger id covers approval slot addresses, so already-registered would be a dishonest claim. The honest marker is 'registered NEW-ID', which needs an ADR-0087 D3 semantic ledger entry: packages/spec/src/migrations/entries/semantic/17.NEW-ID.ts plus its registry line. That follows the precedent of the ADR-0090 runtime faces actor-user-roles-to-positions and action-session-roles-to-positions. The claim and the dispatch both say no packages/spec edit. May the surface admit that one ledger entry?",
          "options": [
            "A: Amend the claim's surface to admit ONE D3 semantic ledger entry (for example approval-position-address-role-retired: surface = the approvals approverId filter / actorId / stored slot spelling role:POSITION; replacement = position:POSITION; plus the deprecated-type fallback now writing org_membership_level:VALUE), plus its registry line and any artifact check:spec-changes / check:upgrade-guide then names. The changeset then carries 'registered NEW-ID'. No Zod schema, no ApproverType alias and no stored slot is touched. Cost: about two spec files, possibly a regenerated artifact, and the spec gate family added to the verification surface. Redispatch onto this same branch.",
            "B: Move the FROM/TO and the author's fix out of the changeset into the PR body, and claim not-required (no-migration-prescription). This contradicts ruling A2's changeset clause, and the gate would go green only because the prescription was hidden, while the rewrite it describes is real.",
            "C: Leave it as is: the PR cannot land (a required gate is red)."
          ],
          "recommendation": "A. Real business need: the upgrading readers are any deployment whose clients send role:POSITION or whose flows author { type: 'role', value: POSITION }. In-repo producers measure 0 (the sibling report 5979222082); deployments cannot be measured from here. The ledger's generated spec-changes.json and upgrade guide are, besides the CHANGELOG, the channel such an upgrader greps. Long-term soundness: ADR-0087 makes the ledger the single source of upgrade artifacts, and A follows the established ADR-0090 runtime-face precedent rather than a one-off exemption. AI-safety: A keeps the gate meaning what it says. B would teach that a declared-breaking change greens the gate by hiding its prescription, which is exactly the omission class #6148 exists to close. Startup focus: A adds one ledger file and no gate, code or migration rewrite. On the surface question: decision request 5975876640's 'packages/spec 不动' was a statement about the PROTOCOL (no schema, no alias change), and a ledger entry changes neither. So the PM may judge A within the ruling; if the PM reads that statement as covering the ledger too, the question goes to the maintainer."
        }
      ],
      "out_of_scope_findings": [
        "carrier: PR #21770 (Acceptance notes) · noted, not filed. A pre-snapshot unanimous/quorum/per_group request (opened before the 17.0 open-time snapshot) re-resolves its slate at every tally (decideNode's back-compat path). With a deprecated role approver whose lookup was empty, the re-resolved slot now spells org_membership_level:X while the stored one is role:X. An approval a holder recorded under role:X before the upgrade then no longer satisfies it, so the admin override finalizes such a request. This is within ruling A2's accepted class of stored 15.x role: slots, and is the pre-existing re-resolution, not a new rewrite. Dedupe words: pre-snapshot tally re-resolution, legacy role slot, unanimous back-compat.",
        "carrier: none (承接者:无) · noted in PR #21770 Acceptance notes only. reassign's 'to' and a user approver's 'value' are free text, so a caller can still create a role:X dead slot as data. This is the same pre-existing class as any type:value literal handed there, and the admin rescue covers it. The enumeration pin scans code, not data. Dedupe words: reassign free text, dead slot, approver address validation.",
        "carrier: whoever next touches each file · noted, not filed. These comments in other packages describe the old spelling: packages/lint/src/validate-approval-approvers.ts about :11 (the deprecated type 'falls back to the role:sales_manager literal', now org_membership_level:sales_manager); packages/rest/src/rest-server.ts about :12865 (lists role: among the console identities); packages/spec/src/contracts/approval-service.ts about :532 (acted_as may hold role:, still true for historical rows). Comment-only drift. Dedupe words: role prefix comment drift, fallback literal docblock.",
        "carrier: the release that consumes them · noted, not filed. The unreleased .changeset/21350-my-pending-position-address.md and .changeset/21379-position-address-readers.md describe role: as a second spelling. If they ship in the same version as .changeset/21387-retire-role-arm.md, both statements reach the CHANGELOG; this card's FROM/TO is the later fact. They are other cards' release inputs and were not edited. Dedupe words: changeset contradiction, role spelling, same release."
      ]
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: patch round 1 · PR #21770 at 022bb0fa · seat domain:services#1 · session_011K3zqE8Pv1Evw5hc8tZCnN · 2026-10-04T17:34Z

    Verdict on the dev report 5982595664 (status: blocked), checked against GitHub and origin/main. The code half implements ruling A2 (5979854058) and is acceptable as built. The patch round adds the ADR-0087 disposition the gate requires.

    Ruling A2, checked in the diff:

    • POSITION_ADDRESS_PREFIXES is ['position:'].
    • resolveApproverSpec's fallback writes ${type}:${a.value} with the canonical type, so no path writes a role: slot, and no stored slot is rewritten.
    • The spec's ApproverType alias is untouched.
    • The admin rescue was re-measured on the post-change code, and it decides BOTH request classes: admin_full_access, PLATFORM_ADMIN and a same-org TENANT_ADMIN, each through decide() with via_override: true and the run resumed, plus a reassign to the holder. Ruling A's stop condition is not triggered.
    • The turned pins keep every expect. The enumeration pin and the card's three pins are in.
    • Both ablation legs went red and were restored, with blob equal to HEAD.
    • Suites: plugin-approvals 891 passed; both dogfood files pass.
    • The changeset is minor, Clause-②: no (narrowing), with FROM → TO, the author's fix and the admin handling for both classes.

    Surface revision 1 (claim 5981987845): accepted in place.

    • packages/plugins/plugin-approvals/src/sys-approval-approver.object.ts: its docblock and its approver field description gave role: as the example slot literal; both now say position:. Same defect class, mechanical, no other claim on the file, same gate family, named in the PR.
    • The two approval-service.ts example comments (about :4551 and :6146) now use the canonical spelling.
    • The dogfood list rows assert 200 with no rows, because a role: ask on the list is a filter miss and not a refusal. The decision rows assert [403, 'FORBIDDEN']. The dev corrected the PM's hypothesis here, and the correction is accepted.

    Open question (the ADR-0087 disposition) → A, decided by the seat.

    • Ruling A2's mandated FROM → TO makes check-adr-0087-registration read a migration prescription, so not-required is refused. No existing ledger id covers approval slot addresses, so already-registered would be false. The honest marker is registered, with one new ADR-0087 D3 semantic ledger entry.
    • This is ledger bookkeeping with no product-visible behaviour change, which is a named no-escalation class.
    • Ruling A2's ⛔ lines (no stored-slot rewrite, no alias window, the spec's ApproverType alias untouched) all stand. The "no packages/spec edit" fence was this seat's claim wording, not the ruling's.
    • The precedent is ADR-0090's own runtime faces: 17.actor-user-roles-to-positions and 17.action-session-roles-to-positions.
    • Not taken: B (moving FROM → TO out of the changeset) greens the gate by hiding a real prescription. C (leaving it red) blocks a finished build.

    Surface revision 2, in this act: ONE semantic ledger entry under packages/spec/src/migrations/entries/semantic/, its packages/spec/src/migrations/registry.ts entry, and any artifact the spec gates name for it (check:spec-changes, the upgrade guide). The changeset's marker becomes registered with that id.

    PR body: after the patch, the seat corrects the opening paragraph. "one required gate is red on purpose" was wrong: the ADR-0087 step runs in Check Changeset, which is not a required context. The patch-round section is then appended.

    Out-of-scope findings (all Acceptance notes, none filed):


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21387,
      "status": "done",
      "branch": "claude/issue-21387-retire-role-arm",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21770",
      "session": "session_011K3zqE8Pv1Evw5hc8tZCnN (mode:subagent; this run carries the dispatching PM session's id)",
      "premise_still_valid": true,
      "summary": "Patch round 1 is done on the same branch and PR, with no new claim and no body PATCH. ONE ADR-0087 D3 semantic ledger entry, approval-position-address-role-retired, is added, and its registry region, spec-changes.json and upgrade-guide rows were generated with the repo's tooling. The changeset's marker is now 'registered approval-position-address-role-retired', and check-adr-0087-registration --base origin/main exits 0. Nothing else in packages/spec changed. Head 3f4333ef: all 117 derived gates exit 0, and CI has 35/35 completed with Check Changeset success. #21764 landed afterwards, so origin/main was merged again at 24f5c5a9 (clean, both sides kept). At 24f5c5a9 the three ledger checks, check-adr-0087-registration and check-changeset-no-major all exit 0. 24f5c5a9 is pushed. The code half is unchanged from 022bb0fa.",
      "tests": "At 3f4333ef, under os-verify-lock: pnpm --filter @objectstack/spec build, VERDICT 0, tree clean afterwards. pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 over the 58 spec test files that git grep finds reading the ledger (migrations/registry, spec-changes.json, protocol-upgrade-guide, MIGRATIONS_BY_MAJOR): Test Files 58 passed (58), Tests 2219 passed (2219), VERDICT 0. pnpm --filter @objectstack/cli exec vitest run --project unit src/commands/migrate/meta.report-order.test.ts src/utils/spec-release-changes.test.ts: 2 passed, Tests 22 passed. Its first try refused with a MODULE-resolution error (no @objectstack/core dist in the recreated worktree), which is read as NOT MEASURED, not as red; turbo built the cli closure (58/58) and the rerun is green. pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-query-signature.test.ts: 15 passed. NOT MEASURED locally, declared to CI: the full spec suite (a first full run was stopped by my 580s timeout before printing any result; the lock was then free and no process was left), and the CLI integration-tier ledger readers test/migrate-meta-default-range and test/migrate-meta-engine-guidance (cli local layer is unit only). Code half: reused from round 0 at 022bb0fa as existing facts (plugin-approvals 891 passed, dogfood 2/2, typecheck 0, both ablation legs red then restored). The patch does not touch it.",
      "gates": {
        "commands": [
          "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
          "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
          "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
          "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
          "node scripts/check-ci-filter-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-adoption.mjs :: exit 0",
          "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-corpus.mjs :: exit 0",
          "node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
          "node scripts/check-doc-frontmatter.mjs :: exit 0",
          "node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
          "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
          "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
          "node scripts/check-docs-section-name.mjs :: exit 0",
          "node scripts/check-docs-section-name.mjs --self-test :: exit 0",
          "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
          "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
          "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
          "node scripts/check-issue-citations.mjs :: exit 0",
          "node scripts/check-keyed-text-bounds.mjs :: exit 0",
          "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
          "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
          "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
          "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
          "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
          "node scripts/check-registry-log-declared.mjs :: exit 0",
          "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
          "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
          "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
          "node scripts/check-section-landing-index.mjs :: exit 0",
          "node scripts/check-section-landing-index.mjs --self-test :: exit 0",
          "node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0",
          "node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0",
          "node scripts/check-system-context-census.mjs :: exit 0",
          "node scripts/check-system-context-census.mjs --self-test :: exit 0",
          "node scripts/check-tenant-audit-census.mjs :: exit 0",
          "node scripts/check-tenant-audit-census.mjs --self-test :: exit 0",
          "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
          "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
          "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
          "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
          "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
          "node scripts/release-pending-publish.mjs --self-test :: exit 0",
          "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
          "pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
          "pnpm --filter @objectstack/spec run check:api-surface :: exit 0",
          "pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0",
          "pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0",
          "pnpm --filter @objectstack/spec run check:docs :: exit 0",
          "pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0",
          "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
          "pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
          "pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0",
          "pnpm --filter @objectstack/spec run check:export-origins :: exit 0",
          "pnpm --filter @objectstack/spec run check:exported-any :: exit 0",
          "pnpm --filter @objectstack/spec run check:generated :: exit 0",
          "pnpm --filter @objectstack/spec run check:liveness :: exit 0",
          "pnpm --filter @objectstack/spec run check:llms-txt :: exit 0",
          "pnpm --filter @objectstack/spec run check:migration-registry :: exit 0",
          "pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0",
          "pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
          "pnpm --filter @objectstack/spec run check:skill-refs :: exit 0",
          "pnpm --filter @objectstack/spec run check:spec-changes :: exit 0",
          "pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
          "pnpm --filter @objectstack/spec run check:upgrade-guide :: exit 0",
          "pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
          "pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
          "pnpm check:changeset-gate-self-tests :: exit 0",
          "pnpm check:corpus-claim-drift :: exit 0",
          "pnpm check:cross-package-test-inputs :: exit 0",
          "pnpm check:dispatcher-error-vocabulary :: exit 0",
          "pnpm check:doc-anchors :: exit 0",
          "pnpm check:doc-authoring :: exit 0",
          "pnpm check:docs-audit-scope :: exit 0",
          "pnpm check:docs-redirects :: exit 0",
          "pnpm check:docs-single-h1 :: exit 0",
          "pnpm check:docs-spec-enumerations :: exit 0",
          "pnpm check:docs-transcript-drift :: exit 0",
          "pnpm check:driver-memory-census :: exit 0",
          "pnpm check:dts-closure :: exit 0",
          "pnpm check:dual-build-cjs-loads :: exit 0",
          "pnpm check:engine-double-contract :: exit 0",
          "pnpm check:future-spec-major :: exit 0",
          "pnpm check:gitlink-declared :: exit 0",
          "pnpm check:i18n :: exit 0",
          "pnpm check:i18n-stale-fill :: exit 0",
          "pnpm check:issue-citations :: exit 0",
          "pnpm check:lean-entry-closure :: exit 0",
          "pnpm check:logger-receiver-detach :: exit 0",
          "pnpm check:merge-driver :: exit 0",
          "pnpm check:nul-bytes :: exit 0",
          "pnpm check:objectql-double-limit :: exit 0",
          "pnpm check:objectui-changeset :: exit 0",
          "pnpm check:org-identifier :: exit 0",
          "pnpm check:page-declaration-shape :: exit 0",
          "pnpm check:pm-changeset-deadline-census :: exit 0",
          "pnpm check:pm-prior-rulings :: exit 0",
          "pnpm check:pm-widening-tells :: exit 0",
          "pnpm check:published-files :: exit 0",
          "pnpm check:published-readme-links :: exit 0",
          "pnpm check:query-options-erasure :: exit 0",
          "pnpm check:react-page-adapter-contract :: exit 0",
          "pnpm check:refd-timer-probe :: exit 0",
          "pnpm check:role-word :: exit 0",
          "pnpm check:skill-identifier-liveness :: exit 0",
          "pnpm check:slot-lookup :: exit 0",
          "pnpm check:sourcemap-no-sources-content :: exit 0",
          "pnpm check:spec-parsed-alias :: exit 0",
          "pnpm check:test-source-alias :: exit 0",
          "pnpm check:tier-file-adoption :: exit 0",
          "pnpm check:type-check-coverage :: exit 0",
          "pnpm check:type-check-debt :: exit 0",
          "pnpm check:vendor-version-stamps :: exit 0",
          "pnpm check:watch-hint-literal :: exit 0",
          "pnpm check:where-matcher :: exit 0"
        ],
        "summary": "117 derived at 3f4333ef (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths; 22 new versus round 0, all spec families). All 117 exit 0, including check-adr-0087-registration --base origin/main and check-changeset-no-major --base origin/main. Three first refused with exit 3 (check:skill-examples, check:dual-build-cjs-loads, check:i18n: no dist in the recreated worktree); turbo built the packages they named (68/68, all cached) and all three were rerun to exit 0. At 24f5c5a9 (the #21764 merge): check:migration-registry, check:spec-changes, check:upgrade-guide, check-adr-0087-registration and check-changeset-no-major each exit 0.",
        "ran_verdict": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran-p1.list exit 0: Run reconciliation 117 derived, 117 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero), at 3f4333ef.",
        "ci": "On 3f4333ef: 35/35 check runs completed, none failed, Check Changeset success. 24f5c5a9 was not read (pushed at report time)."
      },
      "line_budget": "n/a",
      "deviations": [
        "The PR head moved to 24f5c5a9 after the gate union: #21764 landed after 3f4333ef was pushed, and the dispatch says to merge main and keep both entries. The merge was clean and touched none of this card's files. On 24f5c5a9 only the ledger checks, check-adr-0087-registration and check-changeset-no-major were rerun; the full 117-command union is from 3f4333ef.",
        "The changeset frontmatter still names only @objectstack/plugin-approvals; @objectstack/spec is not added. All public packages share one Changesets fixed group, so spec releases in lockstep, and the ledger rows reach upgraders through spec-changes.json and the upgrade guide.",
        "The entry's surface string carries no backticks, because the upgrade-guide generator wraps the surface in a code span (a first draft with nested backticks was regenerated before the commit)."
      ],
      "files_changed": [
        "packages/spec/src/migrations/entries/semantic/17.approval-position-address-role-retired.ts",
        "packages/spec/src/migrations/registry.ts",
        "packages/spec/spec-changes.json",
        "docs/protocol-upgrade-guide.md",
        ".changeset/21387-retire-role-arm.md"
      ],
      "pr_body": {
        "opening_paragraph_corrected": "Executes ruling A2 (record 5979854058, director batch #278, maintainer 「同意」) as one PR. Draft. The ADR-0087 disposition step runs in the `Check Changeset` job, which is NOT one of the seven required contexts. It read red at `022bb0fa` because the honest disposition needed a ledger entry outside the card's first surface. Patch round 1 (below, surface revision 2 in seat verdict 5982629646) adds that entry, and the step is now green: `Check Changeset` read success on `3f4333ef`, and `check-adr-0087-registration --base origin/main` exits 0 at `24f5c5a9`. The patch reaches `packages/spec/src/**`, so an at-tier contract review PASS on the patched head is owed before landing.",
        "patch_round_1_section": "## Patch round 1 (head 24f5c5a9)\n\nSeat verdict 5982629646 answered the ADR-0087 question with A (surface revision 2). The cross-lane declaration is on #6017 (5982635753).\n\n- **One D3 semantic ledger entry**, `approval-position-address-role-retired`, in `packages/spec/src/migrations/entries/semantic/17.approval-position-address-role-retired.ts`. It is modelled on `actor-user-roles-to-positions` and `action-session-roles-to-positions`.\n  - Surface: the approvals position address `role:POSITION`, meaning the `approverId` filter, a decision's `actorId`, and a stored `pending_approvers` slot.\n  - Replacement: `position:POSITION`. An author's `{ type: 'role', value: POSITION }` becomes `{ type: 'position', value: POSITION }`.\n  - Reason: the ADR-0090 D3 retirement; the deprecated `role` type's fallback now writing `org_membership_level:VALUE`; the two request classes that become admin-decided; no stored-slot rewrite; and why this is a D3 entry and not D2 (the address is runtime data, and which type the author meant is the author's judgment).\n  - Acceptance criteria: what an upgrader verifies.\n- **Generated with the repo's own tooling, never by hand:** `gen:migration-registry` (the `registry.ts` region), `gen:spec-changes` (`packages/spec/spec-changes.json`) and `gen:upgrade-guide` (`docs/protocol-upgrade-guide.md`). `check:migration-registry`, `check:spec-changes` and `check:upgrade-guide` exit 0.\n- **The changeset's ADR-0087 marker** now reads `registered approval-position-address-role-retired`, spelled from the gate's own output. `check-adr-0087-registration --base origin/main` exits 0 with: 1 declared-breaking changeset, carrying a disposition, new here: approval-position-address-role-retired.\n- **Nothing else changes in `packages/spec`:** no Zod schema, no `ApproverType` alias, no contract docblock, no stored slot. The changeset frontmatter still names only `@objectstack/plugin-approvals`. `@objectstack/spec` is in the same Changesets `fixed` group, so it releases in lockstep, and the new ledger rows reach upgraders through `spec-changes.json` and the upgrade guide.\n- **Hot file.** `origin/main` was merged at `3f4333ef`. #21764 landed after that, and `24f5c5a9` merges it: a clean merge that keeps both sides. Its three `18.*` semantic entries and this `17.*` entry are all in the generated region, and the three ledger checks plus `check-adr-0087-registration` and `check-changeset-no-major` exit 0 at `24f5c5a9`.\n- **Tests at `3f4333ef`:**\n  - the spec build;\n  - the 58 spec test files that read the ledger: 2219 passed;\n  - the CLI unit-tier ledger readers (`meta.report-order`, `spec-release-changes`): 22 passed;\n  - driver-sql `sql-driver-query-signature`: 15 passed.\n\n  Declared to CI: the full spec suite, which runs past this container's 10-minute foreground cap, and the CLI integration-tier ledger readers (`test/migrate-meta-*`).\n- **Gates at `3f4333ef`:** `dispatch-gates` derives 117 commands (22 new spec families) and all 117 exit 0. `--ran` reads 117 derived, 117 run, 0 NOT-MEASURED, 0 UNRUN. CI on `3f4333ef`: 35 of 35 check runs completed, `Check Changeset` success.\n- **The Clause-② gate's path limb now hits `packages/spec/src/**`**, so a contract review is owed. The seat runs it."
      },
      "mcp_calls": "0",
      "api_writes": "1, a fleet-write relay stroke: POST /repos/objectstack-ai/objectstack/issues/21387/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). There were no pr_create, assignee or label writes and no body PATCH. The git pushes (3f4333ef, 24f5c5a9) are not REST.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: contract review FAIL → patch round 2 · PR #21770 at 24f5c5a9 · seat domain:services#1 · session_011K3zqE8Pv1Evw5hc8tZCnN · 2026-10-04T19:19Z

    The at-tier contract review of head 24f5c5a9 is on the PR: record 5983490043, VERDICT: FAIL. The seat adopts it. Both defects were checked against the tree, and both hold. needs:contract-review stays on the PR.

    The defects:

    1. The ledger entry sits in the wrong step. The step18 docblock in packages/spec/src/migrations/registry.ts says a narrowing that lands after v17.0.0 was cut belongs to step 18. os migrate meta --from 17 keeps only majors above 17, up to CHAIN_TERMINUS_MAJOR (18) (packages/cli/src/commands/migrate/meta.ts), so a 17.* entry is never listed to the 17.x upgraders it exists for.
      • The origin is this seat's order, not the dev's judgment. Verdict 5982629646 and the patch-round-1 order asked for "a 17.x id" and named 17.actor-user-roles-to-positions / 17.action-session-roles-to-positions as the model. Both of those predate the cut.
    2. The changeset frontmatter does not name @objectstack/spec, yet the diff moves that package's published source: the ./migrations export and spec-changes.json.

    Patch round 2 (same claim 5981987845, same branch, same PR). Surface: the files already in the PR, plus one comment line.

    • Rename packages/spec/src/migrations/entries/semantic/17.approval-position-address-role-retired.ts → 18.approval-position-address-role-retired.ts. The id is unchanged.
    • Regenerate with the repo's own tooling, never by hand: gen:migration-registry, then check:generated --fix. The rows move out of the 16 → 17 projections of spec-changes.json and docs/protocol-upgrade-guide.md.
    • Add "@objectstack/spec": patch to .changeset/21387-retire-role-arm.md. Every other line of the changeset is unchanged: the Clause-②: no (narrowing) line, FROM → TO, the author's fix, the admin handling and the marker registered approval-position-address-role-retired.
    • Bounded comment fix, same defect class: the header comment of packages/qa/dogfood/test/fixtures/my-pending-position-fixture.ts says the pin asks "under both approver-address spellings". It is to say that position: lists the request and the retired role: spelling does not. The cross-lane addendum is posted on [PM seat] domain:cli — 🟢 os-elon-musk · session_01BmsuLyUeuG5CNpZFMH1jzS #6024 in this act.
    • Gates that must exit 0: check:migration-registry, check:spec-changes, check:upgrade-guide, check-adr-0087-registration --base origin/main, check-changeset-no-major --base origin/main, then the full dispatch-gates reconciliation on the new head.
    • ⛔ Nothing else moves: no Zod schema, no ApproverType alias, no contract docblock, no stored slot.

    After the patch:

    • The seat corrects the PR body's reach sentence. The channel is os migrate meta --from 17 (step 18). The 16 → 17 projections no longer carry the entry.
    • The seat appends a "Patch round 2" section to the PR body.
    • A fresh at-tier contract review runs on the patched head. Landing waits for its PASS.

    The reviewer's residual (a), the console pin: the card measured position:-only sending at objectui ab18797215 (5975844577). The pin has since moved to 2e818d0b (#21710). Its bump PR names the objectui PRs it carries, and none of them is an approvals change. objectui itself is not readable from this session, so the card's measurement stands as declared, and the maintainer's round report names it.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21387,
      "status": "done",
      "branch": "claude/issue-21387-retire-role-arm",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21770",
      "session": "session_011K3zqE8Pv1Evw5hc8tZCnN (mode:subagent; this run carries the dispatching PM session's id)",
      "premise_still_valid": true,
      "summary": "Patch round 2 is done on the same branch and PR, with no new claim and no body PATCH. The ledger entry moved to step 18 (renamed to 18.approval-position-address-role-retired.ts, same id and content) and the registry was regenerated: it now sits in the step18 block, +59 lines against origin/main. check:generated --fix regenerated spec-changes.json and docs/protocol-upgrade-guide.md, and both are now byte-identical to origin/main and gone from the PR diff. Neither generator projects step 18; both loop to PROTOCOL_MAJOR 17. Measured on the built dist: composeMigrationChain(17, 18), the --from 17 chain, lists the entry, and (16, 17) no longer does. The changeset names \"@objectstack/spec\": patch, and the gate accepts patch. The fixture header comment is fixed. origin/main 025008ae is merged at d0a53cc6, which is pushed. All five named gates and all 116 derived gates exit 0.",
      "tests": "At d0a53cc6, under os-verify-lock. Spec build plus check:generated --fix: the --fix leg ran with VERDICT 0 after the check leg had named two stale artifacts (VERDICT 1, as expected). pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 over 57 of round 1's 58 ledger-reading spec test files: Test Files 57 passed (57), Tests 2134 passed (2134), VERDICT 0. pnpm --filter @objectstack/cli exec vitest run --project unit src/commands/migrate/meta.report-order.test.ts src/utils/spec-release-changes.test.ts: 22 passed. pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-query-signature.test.ts: 15 passed. VERDICT 0. Channel probe (read-only, node on packages/spec/dist/migrations/index.mjs): MIGRATION_MAJORS [17,18], terminus 18, the entry in step 18; composeMigrationChain(17, 18) lists it = true; composeMigrationChain(16, 17) lists it = false; control ui-action-group-menu-members-typed in (17, 18) = true. Declared to CI, not run here: scripts/build-schemas-check-mode.test.ts (85 tests; it spawns full schema builds, the one file that pushed round 1's 58-file run to 12m10s past the foreground cap; green there at 3f4333ef), the full spec suite, and the CLI integration-tier ledger readers (test/migrate-meta-default-range, test/migrate-meta-engine-guidance). The plugin-approvals code half is reused from round 0 at 022bb0fa as existing facts (891 passed, dogfood 2/2, typecheck 0, ablation red then restored); this round changes no code there.",
      "gates": {
        "named": [
          "pnpm --filter @objectstack/spec run check:migration-registry :: exit 0",
          "pnpm --filter @objectstack/spec run check:spec-changes :: exit 0",
          "pnpm --filter @objectstack/spec run check:upgrade-guide :: exit 0",
          "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
          "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0"
        ],
        "commands": [
          "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
          "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
          "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
          "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
          "node scripts/check-ci-filter-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-adoption.mjs :: exit 0",
          "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-corpus.mjs :: exit 0",
          "node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
          "node scripts/check-doc-frontmatter.mjs :: exit 0",
          "node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
          "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
          "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
          "node scripts/check-docs-section-name.mjs :: exit 0",
          "node scripts/check-docs-section-name.mjs --self-test :: exit 0",
          "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
          "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
          "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
          "node scripts/check-issue-citations.mjs :: exit 0",
          "node scripts/check-keyed-text-bounds.mjs :: exit 0",
          "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
          "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
          "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
          "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
          "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
          "node scripts/check-registry-log-declared.mjs :: exit 0",
          "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
          "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
          "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
          "node scripts/check-section-landing-index.mjs :: exit 0",
          "node scripts/check-section-landing-index.mjs --self-test :: exit 0",
          "node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0",
          "node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0",
          "node scripts/check-system-context-census.mjs :: exit 0",
          "node scripts/check-system-context-census.mjs --self-test :: exit 0",
          "node scripts/check-tenant-audit-census.mjs :: exit 0",
          "node scripts/check-tenant-audit-census.mjs --self-test :: exit 0",
          "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
          "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
          "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
          "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
          "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
          "node scripts/release-pending-publish.mjs --self-test :: exit 0",
          "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
          "pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
          "pnpm --filter @objectstack/spec run check:api-surface :: exit 0",
          "pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0",
          "pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0",
          "pnpm --filter @objectstack/spec run check:docs :: exit 0",
          "pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0",
          "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
          "pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
          "pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0",
          "pnpm --filter @objectstack/spec run check:export-origins :: exit 0",
          "pnpm --filter @objectstack/spec run check:exported-any :: exit 0",
          "pnpm --filter @objectstack/spec run check:liveness :: exit 0",
          "pnpm --filter @objectstack/spec run check:llms-txt :: exit 0",
          "pnpm --filter @objectstack/spec run check:migration-registry :: exit 0",
          "pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0",
          "pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
          "pnpm --filter @objectstack/spec run check:skill-refs :: exit 0",
          "pnpm --filter @objectstack/spec run check:spec-changes :: exit 0",
          "pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
          "pnpm --filter @objectstack/spec run check:upgrade-guide :: exit 0",
          "pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
          "pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
          "pnpm check:changeset-gate-self-tests :: exit 0",
          "pnpm check:corpus-claim-drift :: exit 0",
          "pnpm check:cross-package-test-inputs :: exit 0",
          "pnpm check:dispatcher-error-vocabulary :: exit 0",
          "pnpm check:doc-anchors :: exit 0",
          "pnpm check:doc-authoring :: exit 0",
          "pnpm check:docs-audit-scope :: exit 0",
          "pnpm check:docs-redirects :: exit 0",
          "pnpm check:docs-single-h1 :: exit 0",
          "pnpm check:docs-spec-enumerations :: exit 0",
          "pnpm check:docs-transcript-drift :: exit 0",
          "pnpm check:driver-memory-census :: exit 0",
          "pnpm check:dts-closure :: exit 0",
          "pnpm check:dual-build-cjs-loads :: exit 0",
          "pnpm check:engine-double-contract :: exit 0",
          "pnpm check:future-spec-major :: exit 0",
          "pnpm check:gitlink-declared :: exit 0",
          "pnpm check:i18n :: exit 0",
          "pnpm check:i18n-stale-fill :: exit 0",
          "pnpm check:issue-citations :: exit 0",
          "pnpm check:lean-entry-closure :: exit 0",
          "pnpm check:logger-receiver-detach :: exit 0",
          "pnpm check:merge-driver :: exit 0",
          "pnpm check:nul-bytes :: exit 0",
          "pnpm check:objectql-double-limit :: exit 0",
          "pnpm check:objectui-changeset :: exit 0",
          "pnpm check:org-identifier :: exit 0",
          "pnpm check:page-declaration-shape :: exit 0",
          "pnpm check:pm-changeset-deadline-census :: exit 0",
          "pnpm check:pm-prior-rulings :: exit 0",
          "pnpm check:pm-widening-tells :: exit 0",
          "pnpm check:published-files :: exit 0",
          "pnpm check:published-readme-links :: exit 0",
          "pnpm check:query-options-erasure :: exit 0",
          "pnpm check:react-page-adapter-contract :: exit 0",
          "pnpm check:refd-timer-probe :: exit 0",
          "pnpm check:role-word :: exit 0",
          "pnpm check:skill-identifier-liveness :: exit 0",
          "pnpm check:slot-lookup :: exit 0",
          "pnpm check:sourcemap-no-sources-content :: exit 0",
          "pnpm check:spec-parsed-alias :: exit 0",
          "pnpm check:test-source-alias :: exit 0",
          "pnpm check:tier-file-adoption :: exit 0",
          "pnpm check:type-check-coverage :: exit 0",
          "pnpm check:type-check-debt :: exit 0",
          "pnpm check:vendor-version-stamps :: exit 0",
          "pnpm check:watch-hint-literal :: exit 0",
          "pnpm check:where-matcher :: exit 0"
        ],
        "summary": "116 derived at d0a53cc6 (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, 13 paths against merge base 025008ae). check:generated dropped out compared with round 1, because no generated artifact remains in the diff. All 116 exit 0, none refused with exit 3: the dists they read were prebuilt through turbo under the lock (68/68). The five named gates (check:migration-registry, check:spec-changes, check:upgrade-guide, check-adr-0087-registration --base origin/main, check-changeset-no-major --base origin/main) all exit 0.",
        "ran_verdict": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran-p2.list exit 0: Run reconciliation 116 derived, 116 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero), at d0a53cc6.",
        "ci": "On d0a53cc6 at report time: 33 completed, 1 in_progress, none failed, Check Changeset success. Not waited on."
      },
      "line_budget": "n/a",
      "deviations": [
        "The entry's prose needed no step correction: it names no step 17, protocol 17 or 16 to 17 boundary (grep found none). It mentions only the 15.x era of the stored slots, and that is still true. Its content is byte-unchanged; only the file was renamed.",
        "One of the 58 ledger-reading spec test files (scripts/build-schemas-check-mode.test.ts) is declared to CI instead of being run here. It does not fit the foreground cap; it was green in round 1; and the files it reads in this diff are now byte-identical to main.",
        "check:generated is no longer in the derived set at d0a53cc6, because no generated artifact remains in the diff; it was run in this round with --fix (VERDICT 0)."
      ],
      "files_changed": [
        "packages/spec/src/migrations/entries/semantic/17.approval-position-address-role-retired.ts renamed to 18.approval-position-address-role-retired.ts (content unchanged)",
        "packages/spec/src/migrations/registry.ts (regenerated: the entry moves from step17 to step18; against origin/main, +59)",
        "packages/spec/spec-changes.json (regenerated; now identical to origin/main)",
        "docs/protocol-upgrade-guide.md (regenerated; now identical to origin/main)",
        ".changeset/21387-retire-role-arm.md (+ \"@objectstack/spec\": patch)",
        "packages/qa/dogfood/test/fixtures/my-pending-position-fixture.ts (header comment only)"
      ],
      "pr_body": {
        "round_1_reach_sentence_replacement": "The entry reaches 17.x upgraders through `os migrate meta --from 17`, which composes step 18; measured on the built spec at `d0a53cc6`, `composeMigrationChain(17, 18)` lists `approval-position-address-role-retired`. `spec-changes.json` and `docs/protocol-upgrade-guide.md` project only up to `PROTOCOL_MAJOR` (17), so after patch round 2 they carry no row for it and are byte-identical to `main`.",
        "patch_round_2_section": "## Patch round 2 (head d0a53cc6)\n\nThe contract review of `24f5c5a9` (record 5983490043) returned FAIL on two defects. The seat adopted it in 5983502467 and ordered this round. Cross-lane addenda: #6017 5983506965 and #6024 5983511854.\n\n- **The ledger entry moves to step 18.** `git mv` renames `entries/semantic/17.approval-position-address-role-retired.ts` to `18.approval-position-address-role-retired.ts`. The id and the content are unchanged; the prose names no step-17 boundary, so it needed no edit.\n  - The `step18` docblock says that a narrowing which lands after the v17.0.0 cut is told \"where `migrate meta` users are told, at the major boundary where they look\".\n  - `pnpm --filter @objectstack/spec gen:migration-registry` regenerated `registry.ts`, and the entry now sits in the `step18` block. Against `origin/main` (`025008ae`), `registry.ts` reads `1 file changed, 59 insertions(+)`.\n- **The projections, regenerated and measured, not predicted.** `check:generated` named two stale artifacts, `spec-changes.json` and `docs/protocol-upgrade-guide.md`, and `check:generated --fix` regenerated them from a fresh spec build. Both are now **byte-identical to `origin/main`**: `git diff --shortstat origin/main HEAD` prints nothing for either, and both have left the PR's file list.\n  - Step 18 is projected by **neither** generator. Both loop up to and including `PROTOCOL_MAJOR` (17): `build-spec-changes.ts:254` and `build-upgrade-guide.ts:78`. Neither file carries any `18.*` id; for example, `ui-action-group-menu-members-typed` appears 0 times in each.\n- **The channel, measured on the built `packages/spec` dist.**\n  - `composeMigrationChain(17, 18)` lists the entry. That is the chain `os migrate meta --from 17` composes, because `CHAIN_TERMINUS_MAJOR` = max(`PROTOCOL_MAJOR`, `MIGRATION_MAJORS`) = 18 and `MIGRATION_MAJORS` = [17, 18].\n  - `composeMigrationChain(16, 17)` no longer lists it.\n  - Control: the step-18 entry `ui-action-group-menu-members-typed` is listed by `(17, 18)`.\n- **The changeset now names `\"@objectstack/spec\": patch`** next to `\"@objectstack/plugin-approvals\": minor`. Every other line is unchanged: the `Clause-②: no (narrowing)` line, FROM → TO, the author's fix, the admin handling, and the marker `registered approval-position-address-role-retired`. `check-changeset-no-major --base origin/main` exits 0 with `patch`. `check-adr-0087-registration --base origin/main` exits 0: the id resolves at HEAD and is new in the diff.\n- **Bounded comment fix.** The header of `packages/qa/dogfood/test/fixtures/my-pending-position-fixture.ts` (about :10) no longer says \"under both approver-address spellings\". It now says that `position:POSITION` lists the request and the retired `role:POSITION` spelling (ADR-0090 D3) does not. The change is comment only.\n- Nothing else changed: no Zod schema, no `ApproverType` alias, no contract docblock, no stored-slot rewrite, and no plugin-approvals code.\n- **Hot file.** `origin/main` `025008ae` was merged at `d0a53cc6`. The merge was clean, and main brought no change to `packages/spec`.\n- **Gates at `d0a53cc6`:**\n  - `check:migration-registry`, `check:spec-changes`, `check:upgrade-guide`, `check-adr-0087-registration --base origin/main` and `check-changeset-no-major --base origin/main` all exit 0.\n  - `dispatch-gates` derives 116 commands. `check:generated` dropped out, because no generated artifact is in the diff any more. All 116 exit 0, and `--ran` reads 116 derived, 116 run, 0 NOT-MEASURED, 0 UNRUN.\n- **Tests at `d0a53cc6`:**\n  - 57 of round 1's 58 ledger-reading spec test files: 2134 passed.\n  - The CLI unit-tier ledger readers: 22 passed.\n  - driver-sql `sql-driver-query-signature`: 15 passed.\n  - Declared to CI: `scripts/build-schemas-check-mode.test.ts` (it spawns full schema builds and runs past the 10-minute foreground cap; it was green in round 1's run at `3f4333ef`), the full spec suite, and the CLI integration-tier ledger readers.\n  - The plugin-approvals code half is unchanged and reused from round 0.\n- **CI on `d0a53cc6` at report time:** 33 check runs completed and 1 in progress, none failed, `Check Changeset` success."
      },
      "mcp_calls": "0",
      "api_writes": "1, a fleet-write relay stroke: POST /repos/objectstack-ai/objectstack/issues/21387/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). There were no pr_create, assignee or label writes and no body PATCH. The git push (24f5c5a9 to d0a53cc6) is not REST.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · seat domain:services#1 · session_011K3zqE8Pv1Evw5hc8tZCnN · 2026-10-04T20:38Z


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Residual closed: the console pin sends position: only · seat domain:services#1 (#6021) · session_011K3zqE8Pv1Evw5hc8tZCnN · 2026-10-05T09:51Z

    The contract reviews of PR #21770 left one residual: the console pin had moved from ab18797215 (where this card measured position:-only sending) to a later pin, and objectui was then believed unreadable from this seat. It is readable read-only, so the seat measured it at the current pin 0abd4f9f:

    • packages/app-shell/src/hooks/sharedUserFeeds.ts approverIdentities() (the bell badge, the Approvals tab and Home's To-do) sends the user id, the email and position: for each position. It never sends role:.
    • apps/console/src/services/approvalsApi.ts buildApproverIdentities() (the inbox's "My Pending") sends position: for each position. role: appears only for better-auth's user.role scalar, which is not a position.

    Both docblocks state the rule this card's retirement relies on. The residual is closed.


    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

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions