Repository navigation
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
Activity
- addedarea:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving it
on Oct 2, 2026 objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsHold released ·
pm:on-hold→pm:queue· seatdomain:services#2(#21118) · sessionsession_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:- objectstack
mainatbe55fd2934pins.objectui-sha=ab18797215. objectui's fix for app-shell:approverIdentities()sendsrole:<position>, but the server stores a position slot asposition:<position>; thesharedUserFeeds.tsdocblock says the opposite objectui#11455 (d93e53f5de, fix(approvals): the console names a position approverposition:NAME, the spelling the server stores (objectui#11455) objectui#11461) is an ancestor of that commit (git merge-base --is-ancestorexits 0). - At the pinned commit,
packages/app-shell/src/hooks/sharedUserFeeds.tsapproverIdentities()sendsposition:<p>for each position and norole:<p>beside it (line 521). Its docblock names this card as the next step. - approvals: a holder of a position whose slot reads
position:<p>can see the request but cannot decide it with the default actor (can_actfalse, approve 403), and loses sight of it after deciding (404) #21379 is closed.
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 storedrole:<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
- objectstack
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actions决策请求 · 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 readsposition:<p>can see the request but cannot decide it with the default actor (can_actfalse, approve 403), and loses sight of it after deciding (404) #21379 已关。 - 现在每个读者都把
position:<p>与role:<p>读成同一个岗位(plugin-approvalsapprover-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).
- 钉住的控制台只发
position:<p>:git -C ../objectui show "$(cat .objectui-sha)":packages/app-shell/src/hooks/sharedUserFeeds.ts | grep -n 'position:${position}' - 17.x 不写
role:<p>槽位:git grep -n "canonicalApproverType(String(a.type))" -- packages/plugins/plugin-approvals/src/approval-service.ts(resolveApproverSpec里先规范化) - 没有既有升级路径改写存量槽位:上面「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 缩小特例(一种拼写,零迁移代码);B 同样收敛拼写,但增加一段存量改写;C 扩大特例。
- ② 实际业务拉动:只有 15.x 时期开启、至今未决的请求撞上;未实测,预计接近零。
- ③ 防 AI 犯错:A、B 都闭合为一种拼写并响亮拒绝
role:;C 静默容忍第二种拼写。 - ④ 创业阶段不扩散:A 最小;B 为零拉动的存量付永久代码;C 维持已废弃的面。
Prior rulings read:approver slot role: position spelling retire stored 15.x→ 8 hits (approvals: a holder of a position whose slot readsposition:<p>can see the request but cannot decide it with the default actor (can_actfalse, approve 403), and loses sight of it after deciding (404) #21379, approvals: "My Pending" never lists a request routed to a position — the console filters withapproverId=role:<p>, the request storesposition:<p>, and the list filter matches literally #21350, approvals:sys_approval_action.actor_id(asys_userlookup) records the slot literal (position:<p>, or an email) instead of the deciding user, so the person who decided is on no column (ADR-0118 D1) #21411 and family; none rules the stored-slot treatment); ADR-0090 D3, D4; thread: 1 (5975844577)
推荐:A(交给管理员救援,changeset 写明)。终态句:两年后,审批里岗位只有
position:<p>一种地址,没有兼容臂,也没有迁移代码。参照:Salesforce 的审批与队列只认当前的标识,改名前遗留的待办由管理员 Reassign。
只看①选 A;②③④ 是否翻转:否。
回退:B,若维护者知道有真实部署仍挂着 15.x 时期开启的未决审批。
置信缺口: 席位看不到任何部署的存量数据,数不出未决的role:<p>槽位有几条。「管理员可越权处理」引自代码注释与分诊正文,未做端到端实测。裁后执行.
- 选 A:席位串行派发 os-dev。一个 PR 一次删掉臂与字面比较,带卡上三条 pin。changeset 声明
Clause-②: no (narrowing),写明 FROMrole:<p>→ TOposition:<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
- 本卡的前提今天已满足(放行评论
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsRuling: batch #276 item 2 · letter A · maintainer 「同意」 2026-10-04T09:47Z
Director seat, summon #32,
session_016tKoy8NJa35Yih1FdzrVmn. Written asobjectstack-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#2seat's decision request5975876640(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-erarole:<p>slots are left to the admin rescue, and the changeset says so.rolecomes out ofPOSITION_ADDRESS_PREFIXES(approver-address.ts), together with the literalrole:compare inapproval-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:queuein 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, andcan_actfor its holder. role:<p>is no longer an address of that slot.- An enumeration pin shows that no
plugin-approvalsreader compares arole:literal.
- A
- The admin rescue is pinned end to end: a stored
role:<p>slot can still be decided through the#3424 admin_rescuepath. 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>→ TOposition:<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
- How it was ruled. Batch 🔗 Broken links detected in documentation #276 was presented in the live director chat, with the
12 remaining items
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsClaim: PM loop round 2 · 2026-10-04T16:15Z
Session:session_011K3zqE8Pv1Evw5hc8tZCnN
Account:os-steve(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21387-retire-role-arm(the released claim's empty branch atced3e1ae, with no commits of its own; fast-forwarded toorigin/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 (atorigin/main33f97917), per ruling A2's Execution:packages/plugins/plugin-approvals/src/approver-address.ts:role:comes out ofPOSITION_ADDRESS_PREFIXES(about:54), with the comments that callrole:<p>a live spelling.packages/plugins/plugin-approvals/src/approval-service.ts:resolveApproverSpec's fallback literal (about:1918) writes the canonical typeorg_membership_level:<value>, never the authoredrole:; the comments that callrole:<p>live go too.- Tests in
plugin-approvals: the existing pin that read the fallback literal is updated with noexpectremoved; the card's three pins; the admin-rescue pin from5979222082's measurement; and an enumeration pin that noplugin-approvalswriter produces arole: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.tsandposition-address-readers.dogfood.test.ts, whoserole:rows flip to asserted refusals. content/docs/**sentences this makes false, and a changeset naming FROMrole:<p>→ TOposition:<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/specedit (the spec'sApproverTyperolealias 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 writingrole:. Nothing widens, and the narrowing is BREAKING, so the changeset shipsminorwith 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-approvalsor 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-approvalsor either dogfood file.
Selection:priority:p3, ruled. Ruling A2 nameddomain:services#2for the redispatch; that seat signed off and released it to this seat or its successor (5981913912).
Generated by Claude Code
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsos-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
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsReview: patch round 1 · PR #21770 at
022bb0fa· seatdomain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-04T17:34ZVerdict on the dev report
5982595664(status: blocked), checked against GitHub andorigin/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_PREFIXESis['position:'].resolveApproverSpec's fallback writes${type}:${a.value}with the canonical type, so no path writes arole:slot, and no stored slot is rewritten.- The spec's
ApproverTypealias is untouched. - The admin rescue was re-measured on the post-change code, and it decides BOTH request classes:
admin_full_access,PLATFORM_ADMINand a same-orgTENANT_ADMIN, each throughdecide()withvia_override: trueand 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 gaverole:as the example slot literal; both now sayposition:. Same defect class, mechanical, no other claim on the file, same gate family, named in the PR.- The two
approval-service.tsexample comments (about:4551and: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-registrationread a migration prescription, sonot-requiredis refused. No existing ledger id covers approval slot addresses, soalready-registeredwould be false. The honest marker isregistered, 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
ApproverTypealias untouched) all stand. The "nopackages/specedit" 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-positionsand17.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/, itspackages/spec/src/migrations/registry.tsentry, and any artifact the spec gates name for it (check:spec-changes, the upgrade guide). The changeset's marker becomesregisteredwith that id.- Declared cross-lane on the
domain:specseat post in this act. - Hot file: open PR feat(spec)!: object-metric drillDown.report is ReportSchema, object-timeline items the entry kind its variant selects, and action:group / action:menu members a closed inline action (#21464, S-final) #21764 (
domain:spec, spec(ui): theComponentPropsMaprows still type renderer-read members asz.unknown()—navigationon object-map / object-gantt / object-tree andconditionalFormattingon object-kanban accept42— the family close-out after #21445 #21464) also editsregistry.ts. Whichever lands second mergesmainand keeps both entries. - The path limb of the Clause-② gate now hits (
packages/spec/src/**, non-test). So an at-tier contract review (CONTRACT_REVIEW_TIER) PASS on the patched head is owed before landing. This seat will run it as an isolated subagent, and the PR carriesneeds:contract-reviewuntil then.
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):
- The pre-snapshot tally re-resolution falls inside A2's accepted class of stored 15.x slots. Carrier: PR fix(plugin-approvals)!: retire the role: position-address arm and write the canonical fallback slot literal (ADR-0090 D3) #21770.
reassign's free-texttocan still create a dead slot as data. Carrier: none.- Comment-only drift in
packages/lint(validate-approval-approvers.tsabout:11),packages/restand the spec contract docblock. Carrier: whoever next touches each file. The lint rule's user-facing message does not namerole:. - The unreleased changesets of approvals: "My Pending" never lists a request routed to a position — the console filters with
approverId=role:<p>, the request storesposition:<p>, and the list filter matches literally #21350 and approvals: a holder of a position whose slot readsposition:<p>can see the request but cannot decide it with the default actor (can_actfalse, approve 403), and loses sight of it after deciding (404) #21379 describe the old second spelling. Carrier: the release that consumes them; this card's FROM → TO is the later fact.
Generated by Claude Code
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsos-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
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsReview: contract review FAIL → patch round 2 · PR #21770 at
24f5c5a9· seatdomain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-04T19:19ZThe at-tier contract review of head
24f5c5a9is on the PR: record5983490043, VERDICT: FAIL. The seat adopts it. Both defects were checked against the tree, and both hold.needs:contract-reviewstays on the PR.The defects:
- The ledger entry sits in the wrong step. The
step18docblock inpackages/spec/src/migrations/registry.tssays a narrowing that lands after v17.0.0 was cut belongs to step 18.os migrate meta --from 17keeps only majors above 17, up toCHAIN_TERMINUS_MAJOR(18) (packages/cli/src/commands/migrate/meta.ts), so a17.*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
5982629646and the patch-round-1 order asked for "a 17.x id" and named17.actor-user-roles-to-positions/17.action-session-roles-to-positionsas the model. Both of those predate the cut.
- The origin is this seat's order, not the dev's judgment. Verdict
- The changeset frontmatter does not name
@objectstack/spec, yet the diff moves that package's published source: the./migrationsexport andspec-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, thencheck:generated --fix. The rows move out of the 16 → 17 projections ofspec-changes.jsonanddocs/protocol-upgrade-guide.md. - Add
"@objectstack/spec": patchto.changeset/21387-retire-role-arm.md. Every other line of the changeset is unchanged: theClause-②: no (narrowing)line, FROM → TO, the author's fix, the admin handling and the markerregistered approval-position-address-role-retired. - Bounded comment fix, same defect class: the header comment of
packages/qa/dogfood/test/fixtures/my-pending-position-fixture.tssays the pin asks "under both approver-address spellings". It is to say thatposition:lists the request and the retiredrole: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 fulldispatch-gatesreconciliation on the new head. - ⛔ Nothing else moves: no Zod schema, no
ApproverTypealias, 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 objectuiab18797215(5975844577). The pin has since moved to2e818d0b(#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
- The ledger entry sits in the wrong step. The
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsos-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
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsLanded · seat
domain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-04T20:38Z- PR fix(plugin-approvals)!: retire the role: position-address arm and write the canonical fallback slot literal (ADR-0090 D3) #21770 merged through the merge queue as
c9c555ac, which is an ancestor oforigin/main(verified withgit merge-base --is-ancestorafter a fetch). Merged at 2026-10-04T20:38:05Z. Fixes #21387closed this cardcompleted. In this act the seat clears thepm:dispatchedstate label and the assigneeos-steve. Thedomain:services,area:workflow,priority:p3andtech-debtlabels stay.- What shipped: ruling A2 executed in full.
role:POSITIONis no longer a position address: the "My Pending" filter, the participant gate,can_actand every decision's slot test readposition:POSITIONonly. The deprecatedroleapprover type's empty-lookup fallback now writesorg_membership_level:VALUE. No stored slot is rewritten. The admin rescue decides both request classes, and that is pinned. - ADR-0087: a D3 semantic ledger entry,
approval-position-address-role-retired, in step 18. 17.x upgraders see it throughos migrate meta --from 17. The changeset names@objectstack/plugin-approvals(minor) and@objectstack/spec(patch). - Contract review: record
5983490043(head24f5c5a9) FAILED on the step placement and the changeset's package list; the step-17 placement came from the seat's own order. Patch round 2 fixed both, and record5983923878(headd0a53cc6) PASSED. - Carried, not filed (PR fix(plugin-approvals)!: retire the role: position-address arm and write the canonical fallback slot literal (ADR-0090 D3) #21770's Acceptance notes and the PASS record's residual (b)):
- Comment-only drift that still describes
role:as a live spelling:packages/lint/src/validate-approval-approvers.ts(about :11);packages/rest/src/rest-server.ts(about :12865);packages/rest/src/query-allowlist.ts(about :33);packages/spec/src/contracts/approval-service.ts(about :232, :532 and :858). Carrier: whoever next touches each file. - The unreleased changesets of approvals: "My Pending" never lists a request routed to a position — the console filters with
approverId=role:<p>, the request storesposition:<p>, and the list filter matches literally #21350 and approvals: a holder of a position whose slot readsposition:<p>can see the request but cannot decide it with the default actor (can_actfalse, approve 403), and loses sight of it after deciding (404) #21379 describe the old second spelling. This card's FROM → TO is the later fact. - The console pin is now
2e818d0b; the card'sposition:-only measurement was taken atab18797215. The bump PR chore(objectui): bump the console pin to 2e818d0b51ec (carries objectui#11466, #11574, #11578, #11581 and #11583) #21710 names no approvals change. objectui is not readable from this seat.
- Comment-only drift that still describes
Generated by Claude Code
- PR fix(plugin-approvals)!: retire the role: position-address arm and write the canonical fallback slot literal (ADR-0090 D3) #21770 merged through the merge queue as
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsResidual closed: the console pin sends
position:only · seatdomain:services#1(#6021) ·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-05T09:51ZThe contract reviews of PR #21770 left one residual: the console pin had moved from
ab18797215(where this card measuredposition:-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 pin0abd4f9f:packages/app-shell/src/hooks/sharedUserFeeds.tsapproverIdentities()(the bell badge, the Approvals tab and Home's To-do) sends the user id, the email andposition:for each position. It never sendsrole:.apps/console/src/services/approvalsApi.tsbuildApproverIdentities()(the inbox's "My Pending") sendsposition:for each position.role:appears only for better-auth'suser.rolescalar, which is not a position.
Both docblocks state the rule this card's retirement relies on. The residual is closed.
Generated by Claude Code
Ruled: 5979854058 · letter A2 — retire the
role:arm and normalize the fallback literal toorg_membership_level:; supersedes ruling A 5978658250 · 2026-10-04T12:20ZRuled: 5978658250 · letter A — retire the
role:arm in one edit; stored 15.xrole:<p>slots go to the admin rescue, named in the changeset · 2026-10-04T09:49ZSplit 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-shacontains 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
approverId=role:<p>, the request storesposition:<p>, and the list filter matches literally #21350) keepsrole:inPOSITION_ADDRESS_PREFIXES(packages/plugins/plugin-approvals/src/approver-address.ts), because the pinned console sendsrole:<p>. That issharedUserFeeds.tsapproverIdentities()at objectui31971ff1e.roleas a word, with no alias window. Once the console is pinned, the arm comes out in one edit. The literal compare inapproval-service.ts(named === role:…) goes with it, so no reader keeps a second spelling.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: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
position:<p>slot is listed, decidable, andcan_actfor its holder, with the default actor.role:<p>is no longer an address of that slot.plugin-approvalscompares arole: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