docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487) - #14498

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum
Sep 3, 2026
Merged

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487)#14498
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#14487
Part of #14122

Governed surface — maintainer merge. This PR edits docs/adr/**, so it stays draft and is merged by hand by the maintainer; Governed Surface Queue Guard marks it. Nothing here authorises implementation work, and the record it amends is still Proposed.

What this is

One addendum section appended to docs/adr/0130-release-artifact-as-co-ownership-boundary.md, in that repository's existing addendum format (## Addendum (date, #card) — title, as in ADR-0006, ADR-0085, ADR-0087), recording the maintainer's 2026-09-02 ruling — option B on item 3 of #14454, the item carried onto decision card #14457: permission sets stay whole in the type: app package, and the permission matrix ADR-0130 §1.3(a) measures is not part of the payoff §4 claims for the package boundary.

⛔ No decision text moved. D1–D8, §1 and §3's non-goal on grouping keys are unchanged; no owner / publisher field; no new decision. The only edit outside the new section is a one-sentence pointer appended to the Status line — the ADR-0006 / ADR-0085 precedent for a record whose scope an addendum bounds.

The added text

Heading:

Addendum (2026-09-02, #14487) — the permission matrix is outside this boundary's payoff; permission sets stay whole in the app package

The bound it places on §1.3(a) and §4:

§1.3(a) counts, among the three measurable consequences of having no boundary, "a permission matrix of 30 rows × 9 CRUD columns × 6 permission sets that interleaves 客户/联系人/商机 with 运费标准/等级政策/工厂成本", and §4 draws the payoff from that section: "Studio's scope is the package, so package boundaries are the grouping Studio has never had (§1.3a)". The payoff does not extend to the permission matrix. Splitting a product into co-owning packages leaves that matrix exactly as flat as §1.3(a) found it.

The measurement it rests on:

A permission set grants across domains by nature: it is authored per role, not per module, so no module owns it. hotcrm#1449 measured the standard HotCRM product against its six planned modules — core, sales, cpq, service, marketing, activity, all inside the one crm namespace D1 makes co-ownable, with the type: app package declaring no objects at all. Of its six permission sets, four span five or six of the six modules (sales_rep, sales_manager and system_admin at six; service_agent at five); the remaining two span four (marketing_user) and two (guest_portal). Not one is confined to a single module.

Where the sets live:

The six sets stay whole in the type: app package. That is not a new rule but the standing one: ADR-0086 D3 gives a permission set exactly one owning packageId [...] In Studio's Access pillar — the matrix of ADR-0084, reached per package through ADR-0086 D7's package door — the sets therefore appear under the app package only, and their matrix stays as wide as the product. Modules group Data, Automation and Interface; they do not group Access.

What was not decided:

Per-package composition of grants — a module contributing its own objects' grants into a role the app package owns, by analogy with navigationContributions — is the other half of hotcrm#1449's question. It is filed, not decided: #14488, for the phase in which a module ships on its own (the §1.3(c) CPQ case, where a module's objects would otherwise arrive with no grants at all). It is out of this release and carries no commitment — neither that it will be built, nor that the contribution shape is the one it will take.

The section then records three constraints that phase inherits, so they are not rediscovered: ADR-0086 D4 stands until amended ("A package never writes into a shared/foreign record.", so #14488 is an amendment to D4, not an addition beside it); D4's conflict-freedom argument assumes one namespace per package, which D1 relaxes for co-owners inside one artifact; and which Access door edits a split product's app-owned sets is unmeasured (D7 scopes the package door to "this package's own object slice", and after a split the app package owns the sets but no objects). All three are marked as inputs, not decisions.

Status line, appended:

Scope bounded by the 2026-09-02 addendum (#14487): the permission matrix §1.3(a) measures is not part of this boundary's payoff — permission sets stay whole in the type: app package.

Evidence

Changeset — precedent followed

No changeset; the skip-changeset label carries it. Precedent, checked over the last 30 commits touching docs/adr: every ADR-only commit carries no changeset at all — including ADR-0130's own landing commit 682d03ba7 (#14151) and abeb5665e (#12867). scripts/check-empty-changeset.mjs rejects a newly added empty-frontmatter changeset, so in this repository the label is the mechanism and an empty file is not; the label is applied on this PR.

Gates

Family derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (1 path in the change set, 5 commands). All re-run at commit 018270546 under scripts/pm/os-verify-lock.sh, exit codes captured into a file before any pipe:

GateExitVerdict line
node scripts/check-adr-links.mjs0✅ check-adr-links: 600 relative link destination(s) under docs/adr/ resolve
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 427 files / 1398 TS blocks judged clean
pnpm check:adr-anchors0check-adr-anchors: OK (52 anchored file(s) [...] 31335 citation(s) across 4067 file(s) resolve)
pnpm check:doc-authoring0✓ doc authoring guard: 394 files clean — no bare metadata literals.
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm check:nul-bytes (not derived; run on every edit)0check-nul-bytes: OK (scanned 7914 text file(s) [...] no raw ASCII control bytes)

check:doc-formula-expressions first exited 3 — PREREQUISITE NOT MET (@objectstack/formula, then @objectstack/lint, not built), which is "nothing was measured", not a finding; both packages were built and the gate then ran green. The 9 further families dispatch-gates names apply only once a changeset path exists, which by the precedent above this PR does not create.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m


Generated by Claude Code

…ackage boundary's payoff (#14487)
Records the maintainer's 2026-09-02 ruling (option B on #14454 item 3): permission
sets stay whole in the `type: app` package, and the 30 x 9 x 6 permission matrix
§1.3(a) measures is NOT part of the payoff §4 claims for this boundary.
Measured, from the HotCRM split plan (objectstack-ai/hotcrm#1449): four of the six
permission sets span five or six of the six planned modules, and not one is confined
to a single module — a set grants per role, not per module, so no module owns it.
Per-package composition of grants is filed as #14488 for the phase in which a module
ships on its own, with no commitment; the section records the three constraints that
phase inherits (ADR-0086 D4 stands until amended; D4's conflict-freedom argument
assumes one namespace per package, which D1 relaxes; and which Access door edits a
split product's app-owned sets is unmeasured).
No decision text changes: D1-D8, §1 and §3's non-goal on grouping keys stand as
written. The Status line gains a pointer to the addendum, per the ADR-0006/0085
precedent for a record whose scope an addendum bounds.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
@hotlonghotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Sep 2, 2026
@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

PM 复审:PASS(内容与格式)—— 受管面,留 draft,请维护者手合

复审会话 session_01UHvF5hyiZjnCyExFnfQB8m(与派发 dev 同一会话身份、不同 agent 实例;维护者授权 PM 自审)。对象:全 diff(1 文件 +93)与它引用的每一处原文。

对树核实

实测
裁定表述✅ 与 #14454 评论 5507189677 及 #14457 关闭记录一致:本次 B;A 立 #14488 不承诺;来源标明是 PM 席的转述而非维护者原话
不动决定正文✅ D1–D8、§1、§3 未改;仅 Status 行追加一句范围限定 + 末尾一节补记
五处引文✅ 空白归一后逐字在源文件命中:ADR-0130 §1.3(a)「30 rows × 9 CRUD columns × 6 permission sets」、§4「the grouping Studio has never had」、ADR-0086 D4「A package never writes into a shared/foreign record.」与「because each set only grants」、D7「this package's own object slice」
[ADR-0086 D3]✅ 存在于 packages/spec/src/security/permission.zod.ts
测量数字✅ 与 hotcrm#1449 module-split-inventory.json 一致(6 套:6/6/6/5/4/2)
补记标题体例✅ 与 ADR-0085「## Addendum (2026-08-28): …」、ADR-0087「## Addendum (2026-07-15) — as-built true-up」同族
三条给 #14488 的前置✅ 都标为输入而非决定;「A 是对 ADR-0086 D4 的修订而非并列新增」这一句是本补记最有价值的发现,已同步进 #14457 的关闭记录
changeset✅ 遵循 ADR-only 提交零 changeset 的先例(含 ADR-0130 自己的落地提交 682d03b),挂 skip-changesetCheck Changeset 绿

CI(018270546

Governed Surface Queue Guard ✅(标记受管)、Check Changeset ✅、Check Documentation Links ✅、Part-of PR must not also close its card ✅;Lint & Repo Gates 与 Type Check 四项在跑(docs-only,预期绿)。

收口

按 Prime Directive #14,本 PR 不进队列、不 arm,保持 draft 由维护者手合。合并即关 #14487


Generated by Claude Code

@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 1, item 2 (this PR, option A: merge as-is). Verbatim: 「以上都同意,你帮我合并pr」.

Executing on that instruction: marking ready and merging directly (squash) from the maintainer's own account. No approving review is submitted by any seat; under the governed-surface regime the human merge is the review record (see the header of scripts/pm/check-governed-queue-guard.mjs, "the direct merge stays the fallback path"). Governed path in this PR: docs/adr/0130-release-artifact-as-co-ownership-boundary.md.


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@hotlong@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487) - #14498

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum
Sep 3, 2026
Merged

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487)#14498
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#14487
Part of #14122

Governed surface — maintainer merge. This PR edits docs/adr/**, so it stays draft and is merged by hand by the maintainer; Governed Surface Queue Guard marks it. Nothing here authorises implementation work, and the record it amends is still Proposed.

What this is

One addendum section appended to docs/adr/0130-release-artifact-as-co-ownership-boundary.md, in that repository's existing addendum format (## Addendum (date, #card) — title, as in ADR-0006, ADR-0085, ADR-0087), recording the maintainer's 2026-09-02 ruling — option B on item 3 of #14454, the item carried onto decision card #14457: permission sets stay whole in the type: app package, and the permission matrix ADR-0130 §1.3(a) measures is not part of the payoff §4 claims for the package boundary.

⛔ No decision text moved. D1–D8, §1 and §3's non-goal on grouping keys are unchanged; no owner / publisher field; no new decision. The only edit outside the new section is a one-sentence pointer appended to the Status line — the ADR-0006 / ADR-0085 precedent for a record whose scope an addendum bounds.

The added text

Heading:

Addendum (2026-09-02, #14487) — the permission matrix is outside this boundary's payoff; permission sets stay whole in the app package

The bound it places on §1.3(a) and §4:

§1.3(a) counts, among the three measurable consequences of having no boundary, "a permission matrix of 30 rows × 9 CRUD columns × 6 permission sets that interleaves 客户/联系人/商机 with 运费标准/等级政策/工厂成本", and §4 draws the payoff from that section: "Studio's scope is the package, so package boundaries are the grouping Studio has never had (§1.3a)". The payoff does not extend to the permission matrix. Splitting a product into co-owning packages leaves that matrix exactly as flat as §1.3(a) found it.

The measurement it rests on:

A permission set grants across domains by nature: it is authored per role, not per module, so no module owns it. hotcrm#1449 measured the standard HotCRM product against its six planned modules — core, sales, cpq, service, marketing, activity, all inside the one crm namespace D1 makes co-ownable, with the type: app package declaring no objects at all. Of its six permission sets, four span five or six of the six modules (sales_rep, sales_manager and system_admin at six; service_agent at five); the remaining two span four (marketing_user) and two (guest_portal). Not one is confined to a single module.

Where the sets live:

The six sets stay whole in the type: app package. That is not a new rule but the standing one: ADR-0086 D3 gives a permission set exactly one owning packageId [...] In Studio's Access pillar — the matrix of ADR-0084, reached per package through ADR-0086 D7's package door — the sets therefore appear under the app package only, and their matrix stays as wide as the product. Modules group Data, Automation and Interface; they do not group Access.

What was not decided:

Per-package composition of grants — a module contributing its own objects' grants into a role the app package owns, by analogy with navigationContributions — is the other half of hotcrm#1449's question. It is filed, not decided: #14488, for the phase in which a module ships on its own (the §1.3(c) CPQ case, where a module's objects would otherwise arrive with no grants at all). It is out of this release and carries no commitment — neither that it will be built, nor that the contribution shape is the one it will take.

The section then records three constraints that phase inherits, so they are not rediscovered: ADR-0086 D4 stands until amended ("A package never writes into a shared/foreign record.", so #14488 is an amendment to D4, not an addition beside it); D4's conflict-freedom argument assumes one namespace per package, which D1 relaxes for co-owners inside one artifact; and which Access door edits a split product's app-owned sets is unmeasured (D7 scopes the package door to "this package's own object slice", and after a split the app package owns the sets but no objects). All three are marked as inputs, not decisions.

Status line, appended:

Scope bounded by the 2026-09-02 addendum (#14487): the permission matrix §1.3(a) measures is not part of this boundary's payoff — permission sets stay whole in the type: app package.

Evidence

Changeset — precedent followed

No changeset; the skip-changeset label carries it. Precedent, checked over the last 30 commits touching docs/adr: every ADR-only commit carries no changeset at all — including ADR-0130's own landing commit 682d03ba7 (#14151) and abeb5665e (#12867). scripts/check-empty-changeset.mjs rejects a newly added empty-frontmatter changeset, so in this repository the label is the mechanism and an empty file is not; the label is applied on this PR.

Gates

Family derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (1 path in the change set, 5 commands). All re-run at commit 018270546 under scripts/pm/os-verify-lock.sh, exit codes captured into a file before any pipe:

GateExitVerdict line
node scripts/check-adr-links.mjs0✅ check-adr-links: 600 relative link destination(s) under docs/adr/ resolve
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 427 files / 1398 TS blocks judged clean
pnpm check:adr-anchors0check-adr-anchors: OK (52 anchored file(s) [...] 31335 citation(s) across 4067 file(s) resolve)
pnpm check:doc-authoring0✓ doc authoring guard: 394 files clean — no bare metadata literals.
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm check:nul-bytes (not derived; run on every edit)0check-nul-bytes: OK (scanned 7914 text file(s) [...] no raw ASCII control bytes)

check:doc-formula-expressions first exited 3 — PREREQUISITE NOT MET (@objectstack/formula, then @objectstack/lint, not built), which is "nothing was measured", not a finding; both packages were built and the gate then ran green. The 9 further families dispatch-gates names apply only once a changeset path exists, which by the precedent above this PR does not create.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m


Generated by Claude Code

…ackage boundary's payoff (#14487)
Records the maintainer's 2026-09-02 ruling (option B on #14454 item 3): permission
sets stay whole in the `type: app` package, and the 30 x 9 x 6 permission matrix
§1.3(a) measures is NOT part of the payoff §4 claims for this boundary.
Measured, from the HotCRM split plan (objectstack-ai/hotcrm#1449): four of the six
permission sets span five or six of the six planned modules, and not one is confined
to a single module — a set grants per role, not per module, so no module owns it.
Per-package composition of grants is filed as #14488 for the phase in which a module
ships on its own, with no commitment; the section records the three constraints that
phase inherits (ADR-0086 D4 stands until amended; D4's conflict-freedom argument
assumes one namespace per package, which D1 relaxes; and which Access door edits a
split product's app-owned sets is unmeasured).
No decision text changes: D1-D8, §1 and §3's non-goal on grouping keys stand as
written. The Status line gains a pointer to the addendum, per the ADR-0006/0085
precedent for a record whose scope an addendum bounds.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
@hotlonghotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Sep 2, 2026
@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

PM 复审:PASS(内容与格式)—— 受管面,留 draft,请维护者手合

复审会话 session_01UHvF5hyiZjnCyExFnfQB8m(与派发 dev 同一会话身份、不同 agent 实例;维护者授权 PM 自审)。对象:全 diff(1 文件 +93)与它引用的每一处原文。

对树核实

实测
裁定表述✅ 与 #14454 评论 5507189677 及 #14457 关闭记录一致:本次 B;A 立 #14488 不承诺;来源标明是 PM 席的转述而非维护者原话
不动决定正文✅ D1–D8、§1、§3 未改;仅 Status 行追加一句范围限定 + 末尾一节补记
五处引文✅ 空白归一后逐字在源文件命中:ADR-0130 §1.3(a)「30 rows × 9 CRUD columns × 6 permission sets」、§4「the grouping Studio has never had」、ADR-0086 D4「A package never writes into a shared/foreign record.」与「because each set only grants」、D7「this package's own object slice」
[ADR-0086 D3]✅ 存在于 packages/spec/src/security/permission.zod.ts
测量数字✅ 与 hotcrm#1449 module-split-inventory.json 一致(6 套:6/6/6/5/4/2)
补记标题体例✅ 与 ADR-0085「## Addendum (2026-08-28): …」、ADR-0087「## Addendum (2026-07-15) — as-built true-up」同族
三条给 #14488 的前置✅ 都标为输入而非决定;「A 是对 ADR-0086 D4 的修订而非并列新增」这一句是本补记最有价值的发现,已同步进 #14457 的关闭记录
changeset✅ 遵循 ADR-only 提交零 changeset 的先例(含 ADR-0130 自己的落地提交 682d03b),挂 skip-changesetCheck Changeset 绿

CI(018270546

Governed Surface Queue Guard ✅(标记受管)、Check Changeset ✅、Check Documentation Links ✅、Part-of PR must not also close its card ✅;Lint & Repo Gates 与 Type Check 四项在跑(docs-only,预期绿)。

收口

按 Prime Directive #14,本 PR 不进队列、不 arm,保持 draft 由维护者手合。合并即关 #14487


Generated by Claude Code

@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 1, item 2 (this PR, option A: merge as-is). Verbatim: 「以上都同意,你帮我合并pr」.

Executing on that instruction: marking ready and merging directly (squash) from the maintainer's own account. No approving review is submitted by any seat; under the governed-surface regime the human merge is the review record (see the header of scripts/pm/check-governed-queue-guard.mjs, "the direct merge stays the fallback path"). Governed path in this PR: docs/adr/0130-release-artifact-as-co-ownership-boundary.md.


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@hotlong@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487) - #14498

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum
Sep 3, 2026
Merged

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487)#14498
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#14487
Part of #14122

Governed surface — maintainer merge. This PR edits docs/adr/**, so it stays draft and is merged by hand by the maintainer; Governed Surface Queue Guard marks it. Nothing here authorises implementation work, and the record it amends is still Proposed.

What this is

One addendum section appended to docs/adr/0130-release-artifact-as-co-ownership-boundary.md, in that repository's existing addendum format (## Addendum (date, #card) — title, as in ADR-0006, ADR-0085, ADR-0087), recording the maintainer's 2026-09-02 ruling — option B on item 3 of #14454, the item carried onto decision card #14457: permission sets stay whole in the type: app package, and the permission matrix ADR-0130 §1.3(a) measures is not part of the payoff §4 claims for the package boundary.

⛔ No decision text moved. D1–D8, §1 and §3's non-goal on grouping keys are unchanged; no owner / publisher field; no new decision. The only edit outside the new section is a one-sentence pointer appended to the Status line — the ADR-0006 / ADR-0085 precedent for a record whose scope an addendum bounds.

The added text

Heading:

Addendum (2026-09-02, #14487) — the permission matrix is outside this boundary's payoff; permission sets stay whole in the app package

The bound it places on §1.3(a) and §4:

§1.3(a) counts, among the three measurable consequences of having no boundary, "a permission matrix of 30 rows × 9 CRUD columns × 6 permission sets that interleaves 客户/联系人/商机 with 运费标准/等级政策/工厂成本", and §4 draws the payoff from that section: "Studio's scope is the package, so package boundaries are the grouping Studio has never had (§1.3a)". The payoff does not extend to the permission matrix. Splitting a product into co-owning packages leaves that matrix exactly as flat as §1.3(a) found it.

The measurement it rests on:

A permission set grants across domains by nature: it is authored per role, not per module, so no module owns it. hotcrm#1449 measured the standard HotCRM product against its six planned modules — core, sales, cpq, service, marketing, activity, all inside the one crm namespace D1 makes co-ownable, with the type: app package declaring no objects at all. Of its six permission sets, four span five or six of the six modules (sales_rep, sales_manager and system_admin at six; service_agent at five); the remaining two span four (marketing_user) and two (guest_portal). Not one is confined to a single module.

Where the sets live:

The six sets stay whole in the type: app package. That is not a new rule but the standing one: ADR-0086 D3 gives a permission set exactly one owning packageId [...] In Studio's Access pillar — the matrix of ADR-0084, reached per package through ADR-0086 D7's package door — the sets therefore appear under the app package only, and their matrix stays as wide as the product. Modules group Data, Automation and Interface; they do not group Access.

What was not decided:

Per-package composition of grants — a module contributing its own objects' grants into a role the app package owns, by analogy with navigationContributions — is the other half of hotcrm#1449's question. It is filed, not decided: #14488, for the phase in which a module ships on its own (the §1.3(c) CPQ case, where a module's objects would otherwise arrive with no grants at all). It is out of this release and carries no commitment — neither that it will be built, nor that the contribution shape is the one it will take.

The section then records three constraints that phase inherits, so they are not rediscovered: ADR-0086 D4 stands until amended ("A package never writes into a shared/foreign record.", so #14488 is an amendment to D4, not an addition beside it); D4's conflict-freedom argument assumes one namespace per package, which D1 relaxes for co-owners inside one artifact; and which Access door edits a split product's app-owned sets is unmeasured (D7 scopes the package door to "this package's own object slice", and after a split the app package owns the sets but no objects). All three are marked as inputs, not decisions.

Status line, appended:

Scope bounded by the 2026-09-02 addendum (#14487): the permission matrix §1.3(a) measures is not part of this boundary's payoff — permission sets stay whole in the type: app package.

Evidence

Changeset — precedent followed

No changeset; the skip-changeset label carries it. Precedent, checked over the last 30 commits touching docs/adr: every ADR-only commit carries no changeset at all — including ADR-0130's own landing commit 682d03ba7 (#14151) and abeb5665e (#12867). scripts/check-empty-changeset.mjs rejects a newly added empty-frontmatter changeset, so in this repository the label is the mechanism and an empty file is not; the label is applied on this PR.

Gates

Family derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (1 path in the change set, 5 commands). All re-run at commit 018270546 under scripts/pm/os-verify-lock.sh, exit codes captured into a file before any pipe:

GateExitVerdict line
node scripts/check-adr-links.mjs0✅ check-adr-links: 600 relative link destination(s) under docs/adr/ resolve
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 427 files / 1398 TS blocks judged clean
pnpm check:adr-anchors0check-adr-anchors: OK (52 anchored file(s) [...] 31335 citation(s) across 4067 file(s) resolve)
pnpm check:doc-authoring0✓ doc authoring guard: 394 files clean — no bare metadata literals.
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm check:nul-bytes (not derived; run on every edit)0check-nul-bytes: OK (scanned 7914 text file(s) [...] no raw ASCII control bytes)

check:doc-formula-expressions first exited 3 — PREREQUISITE NOT MET (@objectstack/formula, then @objectstack/lint, not built), which is "nothing was measured", not a finding; both packages were built and the gate then ran green. The 9 further families dispatch-gates names apply only once a changeset path exists, which by the precedent above this PR does not create.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m


Generated by Claude Code

…ackage boundary's payoff (#14487)
Records the maintainer's 2026-09-02 ruling (option B on #14454 item 3): permission
sets stay whole in the `type: app` package, and the 30 x 9 x 6 permission matrix
§1.3(a) measures is NOT part of the payoff §4 claims for this boundary.
Measured, from the HotCRM split plan (objectstack-ai/hotcrm#1449): four of the six
permission sets span five or six of the six planned modules, and not one is confined
to a single module — a set grants per role, not per module, so no module owns it.
Per-package composition of grants is filed as #14488 for the phase in which a module
ships on its own, with no commitment; the section records the three constraints that
phase inherits (ADR-0086 D4 stands until amended; D4's conflict-freedom argument
assumes one namespace per package, which D1 relaxes; and which Access door edits a
split product's app-owned sets is unmeasured).
No decision text changes: D1-D8, §1 and §3's non-goal on grouping keys stand as
written. The Status line gains a pointer to the addendum, per the ADR-0006/0085
precedent for a record whose scope an addendum bounds.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
@hotlonghotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Sep 2, 2026
@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

PM 复审:PASS(内容与格式)—— 受管面,留 draft,请维护者手合

复审会话 session_01UHvF5hyiZjnCyExFnfQB8m(与派发 dev 同一会话身份、不同 agent 实例;维护者授权 PM 自审)。对象:全 diff(1 文件 +93)与它引用的每一处原文。

对树核实

实测
裁定表述✅ 与 #14454 评论 5507189677 及 #14457 关闭记录一致:本次 B;A 立 #14488 不承诺;来源标明是 PM 席的转述而非维护者原话
不动决定正文✅ D1–D8、§1、§3 未改;仅 Status 行追加一句范围限定 + 末尾一节补记
五处引文✅ 空白归一后逐字在源文件命中:ADR-0130 §1.3(a)「30 rows × 9 CRUD columns × 6 permission sets」、§4「the grouping Studio has never had」、ADR-0086 D4「A package never writes into a shared/foreign record.」与「because each set only grants」、D7「this package's own object slice」
[ADR-0086 D3]✅ 存在于 packages/spec/src/security/permission.zod.ts
测量数字✅ 与 hotcrm#1449 module-split-inventory.json 一致(6 套:6/6/6/5/4/2)
补记标题体例✅ 与 ADR-0085「## Addendum (2026-08-28): …」、ADR-0087「## Addendum (2026-07-15) — as-built true-up」同族
三条给 #14488 的前置✅ 都标为输入而非决定;「A 是对 ADR-0086 D4 的修订而非并列新增」这一句是本补记最有价值的发现,已同步进 #14457 的关闭记录
changeset✅ 遵循 ADR-only 提交零 changeset 的先例(含 ADR-0130 自己的落地提交 682d03b),挂 skip-changesetCheck Changeset 绿

CI(018270546

Governed Surface Queue Guard ✅(标记受管)、Check Changeset ✅、Check Documentation Links ✅、Part-of PR must not also close its card ✅;Lint & Repo Gates 与 Type Check 四项在跑(docs-only,预期绿)。

收口

按 Prime Directive #14,本 PR 不进队列、不 arm,保持 draft 由维护者手合。合并即关 #14487


Generated by Claude Code

@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 1, item 2 (this PR, option A: merge as-is). Verbatim: 「以上都同意,你帮我合并pr」.

Executing on that instruction: marking ready and merging directly (squash) from the maintainer's own account. No approving review is submitted by any seat; under the governed-surface regime the human merge is the review record (see the header of scripts/pm/check-governed-queue-guard.mjs, "the direct merge stays the fallback path"). Governed path in this PR: docs/adr/0130-release-artifact-as-co-ownership-boundary.md.


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@hotlong@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487) - #14498

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum
Sep 3, 2026
Merged

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487)#14498
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#14487
Part of #14122

Governed surface — maintainer merge. This PR edits docs/adr/**, so it stays draft and is merged by hand by the maintainer; Governed Surface Queue Guard marks it. Nothing here authorises implementation work, and the record it amends is still Proposed.

What this is

One addendum section appended to docs/adr/0130-release-artifact-as-co-ownership-boundary.md, in that repository's existing addendum format (## Addendum (date, #card) — title, as in ADR-0006, ADR-0085, ADR-0087), recording the maintainer's 2026-09-02 ruling — option B on item 3 of #14454, the item carried onto decision card #14457: permission sets stay whole in the type: app package, and the permission matrix ADR-0130 §1.3(a) measures is not part of the payoff §4 claims for the package boundary.

⛔ No decision text moved. D1–D8, §1 and §3's non-goal on grouping keys are unchanged; no owner / publisher field; no new decision. The only edit outside the new section is a one-sentence pointer appended to the Status line — the ADR-0006 / ADR-0085 precedent for a record whose scope an addendum bounds.

The added text

Heading:

Addendum (2026-09-02, #14487) — the permission matrix is outside this boundary's payoff; permission sets stay whole in the app package

The bound it places on §1.3(a) and §4:

§1.3(a) counts, among the three measurable consequences of having no boundary, "a permission matrix of 30 rows × 9 CRUD columns × 6 permission sets that interleaves 客户/联系人/商机 with 运费标准/等级政策/工厂成本", and §4 draws the payoff from that section: "Studio's scope is the package, so package boundaries are the grouping Studio has never had (§1.3a)". The payoff does not extend to the permission matrix. Splitting a product into co-owning packages leaves that matrix exactly as flat as §1.3(a) found it.

The measurement it rests on:

A permission set grants across domains by nature: it is authored per role, not per module, so no module owns it. hotcrm#1449 measured the standard HotCRM product against its six planned modules — core, sales, cpq, service, marketing, activity, all inside the one crm namespace D1 makes co-ownable, with the type: app package declaring no objects at all. Of its six permission sets, four span five or six of the six modules (sales_rep, sales_manager and system_admin at six; service_agent at five); the remaining two span four (marketing_user) and two (guest_portal). Not one is confined to a single module.

Where the sets live:

The six sets stay whole in the type: app package. That is not a new rule but the standing one: ADR-0086 D3 gives a permission set exactly one owning packageId [...] In Studio's Access pillar — the matrix of ADR-0084, reached per package through ADR-0086 D7's package door — the sets therefore appear under the app package only, and their matrix stays as wide as the product. Modules group Data, Automation and Interface; they do not group Access.

What was not decided:

Per-package composition of grants — a module contributing its own objects' grants into a role the app package owns, by analogy with navigationContributions — is the other half of hotcrm#1449's question. It is filed, not decided: #14488, for the phase in which a module ships on its own (the §1.3(c) CPQ case, where a module's objects would otherwise arrive with no grants at all). It is out of this release and carries no commitment — neither that it will be built, nor that the contribution shape is the one it will take.

The section then records three constraints that phase inherits, so they are not rediscovered: ADR-0086 D4 stands until amended ("A package never writes into a shared/foreign record.", so #14488 is an amendment to D4, not an addition beside it); D4's conflict-freedom argument assumes one namespace per package, which D1 relaxes for co-owners inside one artifact; and which Access door edits a split product's app-owned sets is unmeasured (D7 scopes the package door to "this package's own object slice", and after a split the app package owns the sets but no objects). All three are marked as inputs, not decisions.

Status line, appended:

Scope bounded by the 2026-09-02 addendum (#14487): the permission matrix §1.3(a) measures is not part of this boundary's payoff — permission sets stay whole in the type: app package.

Evidence

Changeset — precedent followed

No changeset; the skip-changeset label carries it. Precedent, checked over the last 30 commits touching docs/adr: every ADR-only commit carries no changeset at all — including ADR-0130's own landing commit 682d03ba7 (#14151) and abeb5665e (#12867). scripts/check-empty-changeset.mjs rejects a newly added empty-frontmatter changeset, so in this repository the label is the mechanism and an empty file is not; the label is applied on this PR.

Gates

Family derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (1 path in the change set, 5 commands). All re-run at commit 018270546 under scripts/pm/os-verify-lock.sh, exit codes captured into a file before any pipe:

GateExitVerdict line
node scripts/check-adr-links.mjs0✅ check-adr-links: 600 relative link destination(s) under docs/adr/ resolve
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 427 files / 1398 TS blocks judged clean
pnpm check:adr-anchors0check-adr-anchors: OK (52 anchored file(s) [...] 31335 citation(s) across 4067 file(s) resolve)
pnpm check:doc-authoring0✓ doc authoring guard: 394 files clean — no bare metadata literals.
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm check:nul-bytes (not derived; run on every edit)0check-nul-bytes: OK (scanned 7914 text file(s) [...] no raw ASCII control bytes)

check:doc-formula-expressions first exited 3 — PREREQUISITE NOT MET (@objectstack/formula, then @objectstack/lint, not built), which is "nothing was measured", not a finding; both packages were built and the gate then ran green. The 9 further families dispatch-gates names apply only once a changeset path exists, which by the precedent above this PR does not create.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m


Generated by Claude Code

…ackage boundary's payoff (#14487)
Records the maintainer's 2026-09-02 ruling (option B on #14454 item 3): permission
sets stay whole in the `type: app` package, and the 30 x 9 x 6 permission matrix
§1.3(a) measures is NOT part of the payoff §4 claims for this boundary.
Measured, from the HotCRM split plan (objectstack-ai/hotcrm#1449): four of the six
permission sets span five or six of the six planned modules, and not one is confined
to a single module — a set grants per role, not per module, so no module owns it.
Per-package composition of grants is filed as #14488 for the phase in which a module
ships on its own, with no commitment; the section records the three constraints that
phase inherits (ADR-0086 D4 stands until amended; D4's conflict-freedom argument
assumes one namespace per package, which D1 relaxes; and which Access door edits a
split product's app-owned sets is unmeasured).
No decision text changes: D1-D8, §1 and §3's non-goal on grouping keys stand as
written. The Status line gains a pointer to the addendum, per the ADR-0006/0085
precedent for a record whose scope an addendum bounds.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
@hotlonghotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Sep 2, 2026
@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

PM 复审:PASS(内容与格式)—— 受管面,留 draft,请维护者手合

复审会话 session_01UHvF5hyiZjnCyExFnfQB8m(与派发 dev 同一会话身份、不同 agent 实例;维护者授权 PM 自审)。对象:全 diff(1 文件 +93)与它引用的每一处原文。

对树核实

实测
裁定表述✅ 与 #14454 评论 5507189677 及 #14457 关闭记录一致:本次 B;A 立 #14488 不承诺;来源标明是 PM 席的转述而非维护者原话
不动决定正文✅ D1–D8、§1、§3 未改;仅 Status 行追加一句范围限定 + 末尾一节补记
五处引文✅ 空白归一后逐字在源文件命中:ADR-0130 §1.3(a)「30 rows × 9 CRUD columns × 6 permission sets」、§4「the grouping Studio has never had」、ADR-0086 D4「A package never writes into a shared/foreign record.」与「because each set only grants」、D7「this package's own object slice」
[ADR-0086 D3]✅ 存在于 packages/spec/src/security/permission.zod.ts
测量数字✅ 与 hotcrm#1449 module-split-inventory.json 一致(6 套:6/6/6/5/4/2)
补记标题体例✅ 与 ADR-0085「## Addendum (2026-08-28): …」、ADR-0087「## Addendum (2026-07-15) — as-built true-up」同族
三条给 #14488 的前置✅ 都标为输入而非决定;「A 是对 ADR-0086 D4 的修订而非并列新增」这一句是本补记最有价值的发现,已同步进 #14457 的关闭记录
changeset✅ 遵循 ADR-only 提交零 changeset 的先例(含 ADR-0130 自己的落地提交 682d03b),挂 skip-changesetCheck Changeset 绿

CI(018270546

Governed Surface Queue Guard ✅(标记受管)、Check Changeset ✅、Check Documentation Links ✅、Part-of PR must not also close its card ✅;Lint & Repo Gates 与 Type Check 四项在跑(docs-only,预期绿)。

收口

按 Prime Directive #14,本 PR 不进队列、不 arm,保持 draft 由维护者手合。合并即关 #14487


Generated by Claude Code

@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 1, item 2 (this PR, option A: merge as-is). Verbatim: 「以上都同意,你帮我合并pr」.

Executing on that instruction: marking ready and merging directly (squash) from the maintainer's own account. No approving review is submitted by any seat; under the governed-surface regime the human merge is the review record (see the header of scripts/pm/check-governed-queue-guard.mjs, "the direct merge stays the fallback path"). Governed path in this PR: docs/adr/0130-release-artifact-as-co-ownership-boundary.md.


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@hotlong@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487) - #14498

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum
Sep 3, 2026
Merged

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487)#14498
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#14487
Part of #14122

Governed surface — maintainer merge. This PR edits docs/adr/**, so it stays draft and is merged by hand by the maintainer; Governed Surface Queue Guard marks it. Nothing here authorises implementation work, and the record it amends is still Proposed.

What this is

One addendum section appended to docs/adr/0130-release-artifact-as-co-ownership-boundary.md, in that repository's existing addendum format (## Addendum (date, #card) — title, as in ADR-0006, ADR-0085, ADR-0087), recording the maintainer's 2026-09-02 ruling — option B on item 3 of #14454, the item carried onto decision card #14457: permission sets stay whole in the type: app package, and the permission matrix ADR-0130 §1.3(a) measures is not part of the payoff §4 claims for the package boundary.

⛔ No decision text moved. D1–D8, §1 and §3's non-goal on grouping keys are unchanged; no owner / publisher field; no new decision. The only edit outside the new section is a one-sentence pointer appended to the Status line — the ADR-0006 / ADR-0085 precedent for a record whose scope an addendum bounds.

The added text

Heading:

Addendum (2026-09-02, #14487) — the permission matrix is outside this boundary's payoff; permission sets stay whole in the app package

The bound it places on §1.3(a) and §4:

§1.3(a) counts, among the three measurable consequences of having no boundary, "a permission matrix of 30 rows × 9 CRUD columns × 6 permission sets that interleaves 客户/联系人/商机 with 运费标准/等级政策/工厂成本", and §4 draws the payoff from that section: "Studio's scope is the package, so package boundaries are the grouping Studio has never had (§1.3a)". The payoff does not extend to the permission matrix. Splitting a product into co-owning packages leaves that matrix exactly as flat as §1.3(a) found it.

The measurement it rests on:

A permission set grants across domains by nature: it is authored per role, not per module, so no module owns it. hotcrm#1449 measured the standard HotCRM product against its six planned modules — core, sales, cpq, service, marketing, activity, all inside the one crm namespace D1 makes co-ownable, with the type: app package declaring no objects at all. Of its six permission sets, four span five or six of the six modules (sales_rep, sales_manager and system_admin at six; service_agent at five); the remaining two span four (marketing_user) and two (guest_portal). Not one is confined to a single module.

Where the sets live:

The six sets stay whole in the type: app package. That is not a new rule but the standing one: ADR-0086 D3 gives a permission set exactly one owning packageId [...] In Studio's Access pillar — the matrix of ADR-0084, reached per package through ADR-0086 D7's package door — the sets therefore appear under the app package only, and their matrix stays as wide as the product. Modules group Data, Automation and Interface; they do not group Access.

What was not decided:

Per-package composition of grants — a module contributing its own objects' grants into a role the app package owns, by analogy with navigationContributions — is the other half of hotcrm#1449's question. It is filed, not decided: #14488, for the phase in which a module ships on its own (the §1.3(c) CPQ case, where a module's objects would otherwise arrive with no grants at all). It is out of this release and carries no commitment — neither that it will be built, nor that the contribution shape is the one it will take.

The section then records three constraints that phase inherits, so they are not rediscovered: ADR-0086 D4 stands until amended ("A package never writes into a shared/foreign record.", so #14488 is an amendment to D4, not an addition beside it); D4's conflict-freedom argument assumes one namespace per package, which D1 relaxes for co-owners inside one artifact; and which Access door edits a split product's app-owned sets is unmeasured (D7 scopes the package door to "this package's own object slice", and after a split the app package owns the sets but no objects). All three are marked as inputs, not decisions.

Status line, appended:

Scope bounded by the 2026-09-02 addendum (#14487): the permission matrix §1.3(a) measures is not part of this boundary's payoff — permission sets stay whole in the type: app package.

Evidence

Changeset — precedent followed

No changeset; the skip-changeset label carries it. Precedent, checked over the last 30 commits touching docs/adr: every ADR-only commit carries no changeset at all — including ADR-0130's own landing commit 682d03ba7 (#14151) and abeb5665e (#12867). scripts/check-empty-changeset.mjs rejects a newly added empty-frontmatter changeset, so in this repository the label is the mechanism and an empty file is not; the label is applied on this PR.

Gates

Family derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (1 path in the change set, 5 commands). All re-run at commit 018270546 under scripts/pm/os-verify-lock.sh, exit codes captured into a file before any pipe:

GateExitVerdict line
node scripts/check-adr-links.mjs0✅ check-adr-links: 600 relative link destination(s) under docs/adr/ resolve
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 427 files / 1398 TS blocks judged clean
pnpm check:adr-anchors0check-adr-anchors: OK (52 anchored file(s) [...] 31335 citation(s) across 4067 file(s) resolve)
pnpm check:doc-authoring0✓ doc authoring guard: 394 files clean — no bare metadata literals.
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm check:nul-bytes (not derived; run on every edit)0check-nul-bytes: OK (scanned 7914 text file(s) [...] no raw ASCII control bytes)

check:doc-formula-expressions first exited 3 — PREREQUISITE NOT MET (@objectstack/formula, then @objectstack/lint, not built), which is "nothing was measured", not a finding; both packages were built and the gate then ran green. The 9 further families dispatch-gates names apply only once a changeset path exists, which by the precedent above this PR does not create.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m


Generated by Claude Code

…ackage boundary's payoff (#14487)
Records the maintainer's 2026-09-02 ruling (option B on #14454 item 3): permission
sets stay whole in the `type: app` package, and the 30 x 9 x 6 permission matrix
§1.3(a) measures is NOT part of the payoff §4 claims for this boundary.
Measured, from the HotCRM split plan (objectstack-ai/hotcrm#1449): four of the six
permission sets span five or six of the six planned modules, and not one is confined
to a single module — a set grants per role, not per module, so no module owns it.
Per-package composition of grants is filed as #14488 for the phase in which a module
ships on its own, with no commitment; the section records the three constraints that
phase inherits (ADR-0086 D4 stands until amended; D4's conflict-freedom argument
assumes one namespace per package, which D1 relaxes; and which Access door edits a
split product's app-owned sets is unmeasured).
No decision text changes: D1-D8, §1 and §3's non-goal on grouping keys stand as
written. The Status line gains a pointer to the addendum, per the ADR-0006/0085
precedent for a record whose scope an addendum bounds.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
@hotlonghotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Sep 2, 2026
@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

PM 复审:PASS(内容与格式)—— 受管面,留 draft,请维护者手合

复审会话 session_01UHvF5hyiZjnCyExFnfQB8m(与派发 dev 同一会话身份、不同 agent 实例;维护者授权 PM 自审)。对象:全 diff(1 文件 +93)与它引用的每一处原文。

对树核实

实测
裁定表述✅ 与 #14454 评论 5507189677 及 #14457 关闭记录一致:本次 B;A 立 #14488 不承诺;来源标明是 PM 席的转述而非维护者原话
不动决定正文✅ D1–D8、§1、§3 未改;仅 Status 行追加一句范围限定 + 末尾一节补记
五处引文✅ 空白归一后逐字在源文件命中:ADR-0130 §1.3(a)「30 rows × 9 CRUD columns × 6 permission sets」、§4「the grouping Studio has never had」、ADR-0086 D4「A package never writes into a shared/foreign record.」与「because each set only grants」、D7「this package's own object slice」
[ADR-0086 D3]✅ 存在于 packages/spec/src/security/permission.zod.ts
测量数字✅ 与 hotcrm#1449 module-split-inventory.json 一致(6 套:6/6/6/5/4/2)
补记标题体例✅ 与 ADR-0085「## Addendum (2026-08-28): …」、ADR-0087「## Addendum (2026-07-15) — as-built true-up」同族
三条给 #14488 的前置✅ 都标为输入而非决定;「A 是对 ADR-0086 D4 的修订而非并列新增」这一句是本补记最有价值的发现,已同步进 #14457 的关闭记录
changeset✅ 遵循 ADR-only 提交零 changeset 的先例(含 ADR-0130 自己的落地提交 682d03b),挂 skip-changesetCheck Changeset 绿

CI(018270546

Governed Surface Queue Guard ✅(标记受管)、Check Changeset ✅、Check Documentation Links ✅、Part-of PR must not also close its card ✅;Lint & Repo Gates 与 Type Check 四项在跑(docs-only,预期绿)。

收口

按 Prime Directive #14,本 PR 不进队列、不 arm,保持 draft 由维护者手合。合并即关 #14487


Generated by Claude Code

@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 1, item 2 (this PR, option A: merge as-is). Verbatim: 「以上都同意,你帮我合并pr」.

Executing on that instruction: marking ready and merging directly (squash) from the maintainer's own account. No approving review is submitted by any seat; under the governed-surface regime the human merge is the review record (see the header of scripts/pm/check-governed-queue-guard.mjs, "the direct merge stays the fallback path"). Governed path in this PR: docs/adr/0130-release-artifact-as-co-ownership-boundary.md.


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@hotlong@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487) - #14498

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum
Sep 3, 2026
Merged

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487)#14498
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#14487
Part of #14122

Governed surface — maintainer merge. This PR edits docs/adr/**, so it stays draft and is merged by hand by the maintainer; Governed Surface Queue Guard marks it. Nothing here authorises implementation work, and the record it amends is still Proposed.

What this is

One addendum section appended to docs/adr/0130-release-artifact-as-co-ownership-boundary.md, in that repository's existing addendum format (## Addendum (date, #card) — title, as in ADR-0006, ADR-0085, ADR-0087), recording the maintainer's 2026-09-02 ruling — option B on item 3 of #14454, the item carried onto decision card #14457: permission sets stay whole in the type: app package, and the permission matrix ADR-0130 §1.3(a) measures is not part of the payoff §4 claims for the package boundary.

⛔ No decision text moved. D1–D8, §1 and §3's non-goal on grouping keys are unchanged; no owner / publisher field; no new decision. The only edit outside the new section is a one-sentence pointer appended to the Status line — the ADR-0006 / ADR-0085 precedent for a record whose scope an addendum bounds.

The added text

Heading:

Addendum (2026-09-02, #14487) — the permission matrix is outside this boundary's payoff; permission sets stay whole in the app package

The bound it places on §1.3(a) and §4:

§1.3(a) counts, among the three measurable consequences of having no boundary, "a permission matrix of 30 rows × 9 CRUD columns × 6 permission sets that interleaves 客户/联系人/商机 with 运费标准/等级政策/工厂成本", and §4 draws the payoff from that section: "Studio's scope is the package, so package boundaries are the grouping Studio has never had (§1.3a)". The payoff does not extend to the permission matrix. Splitting a product into co-owning packages leaves that matrix exactly as flat as §1.3(a) found it.

The measurement it rests on:

A permission set grants across domains by nature: it is authored per role, not per module, so no module owns it. hotcrm#1449 measured the standard HotCRM product against its six planned modules — core, sales, cpq, service, marketing, activity, all inside the one crm namespace D1 makes co-ownable, with the type: app package declaring no objects at all. Of its six permission sets, four span five or six of the six modules (sales_rep, sales_manager and system_admin at six; service_agent at five); the remaining two span four (marketing_user) and two (guest_portal). Not one is confined to a single module.

Where the sets live:

The six sets stay whole in the type: app package. That is not a new rule but the standing one: ADR-0086 D3 gives a permission set exactly one owning packageId [...] In Studio's Access pillar — the matrix of ADR-0084, reached per package through ADR-0086 D7's package door — the sets therefore appear under the app package only, and their matrix stays as wide as the product. Modules group Data, Automation and Interface; they do not group Access.

What was not decided:

Per-package composition of grants — a module contributing its own objects' grants into a role the app package owns, by analogy with navigationContributions — is the other half of hotcrm#1449's question. It is filed, not decided: #14488, for the phase in which a module ships on its own (the §1.3(c) CPQ case, where a module's objects would otherwise arrive with no grants at all). It is out of this release and carries no commitment — neither that it will be built, nor that the contribution shape is the one it will take.

The section then records three constraints that phase inherits, so they are not rediscovered: ADR-0086 D4 stands until amended ("A package never writes into a shared/foreign record.", so #14488 is an amendment to D4, not an addition beside it); D4's conflict-freedom argument assumes one namespace per package, which D1 relaxes for co-owners inside one artifact; and which Access door edits a split product's app-owned sets is unmeasured (D7 scopes the package door to "this package's own object slice", and after a split the app package owns the sets but no objects). All three are marked as inputs, not decisions.

Status line, appended:

Scope bounded by the 2026-09-02 addendum (#14487): the permission matrix §1.3(a) measures is not part of this boundary's payoff — permission sets stay whole in the type: app package.

Evidence

Changeset — precedent followed

No changeset; the skip-changeset label carries it. Precedent, checked over the last 30 commits touching docs/adr: every ADR-only commit carries no changeset at all — including ADR-0130's own landing commit 682d03ba7 (#14151) and abeb5665e (#12867). scripts/check-empty-changeset.mjs rejects a newly added empty-frontmatter changeset, so in this repository the label is the mechanism and an empty file is not; the label is applied on this PR.

Gates

Family derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (1 path in the change set, 5 commands). All re-run at commit 018270546 under scripts/pm/os-verify-lock.sh, exit codes captured into a file before any pipe:

GateExitVerdict line
node scripts/check-adr-links.mjs0✅ check-adr-links: 600 relative link destination(s) under docs/adr/ resolve
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 427 files / 1398 TS blocks judged clean
pnpm check:adr-anchors0check-adr-anchors: OK (52 anchored file(s) [...] 31335 citation(s) across 4067 file(s) resolve)
pnpm check:doc-authoring0✓ doc authoring guard: 394 files clean — no bare metadata literals.
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm check:nul-bytes (not derived; run on every edit)0check-nul-bytes: OK (scanned 7914 text file(s) [...] no raw ASCII control bytes)

check:doc-formula-expressions first exited 3 — PREREQUISITE NOT MET (@objectstack/formula, then @objectstack/lint, not built), which is "nothing was measured", not a finding; both packages were built and the gate then ran green. The 9 further families dispatch-gates names apply only once a changeset path exists, which by the precedent above this PR does not create.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m


Generated by Claude Code

…ackage boundary's payoff (#14487)
Records the maintainer's 2026-09-02 ruling (option B on #14454 item 3): permission
sets stay whole in the `type: app` package, and the 30 x 9 x 6 permission matrix
§1.3(a) measures is NOT part of the payoff §4 claims for this boundary.
Measured, from the HotCRM split plan (objectstack-ai/hotcrm#1449): four of the six
permission sets span five or six of the six planned modules, and not one is confined
to a single module — a set grants per role, not per module, so no module owns it.
Per-package composition of grants is filed as #14488 for the phase in which a module
ships on its own, with no commitment; the section records the three constraints that
phase inherits (ADR-0086 D4 stands until amended; D4's conflict-freedom argument
assumes one namespace per package, which D1 relaxes; and which Access door edits a
split product's app-owned sets is unmeasured).
No decision text changes: D1-D8, §1 and §3's non-goal on grouping keys stand as
written. The Status line gains a pointer to the addendum, per the ADR-0006/0085
precedent for a record whose scope an addendum bounds.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
@hotlonghotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Sep 2, 2026
@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

PM 复审:PASS(内容与格式)—— 受管面,留 draft,请维护者手合

复审会话 session_01UHvF5hyiZjnCyExFnfQB8m(与派发 dev 同一会话身份、不同 agent 实例;维护者授权 PM 自审)。对象:全 diff(1 文件 +93)与它引用的每一处原文。

对树核实

实测
裁定表述✅ 与 #14454 评论 5507189677 及 #14457 关闭记录一致:本次 B;A 立 #14488 不承诺;来源标明是 PM 席的转述而非维护者原话
不动决定正文✅ D1–D8、§1、§3 未改;仅 Status 行追加一句范围限定 + 末尾一节补记
五处引文✅ 空白归一后逐字在源文件命中:ADR-0130 §1.3(a)「30 rows × 9 CRUD columns × 6 permission sets」、§4「the grouping Studio has never had」、ADR-0086 D4「A package never writes into a shared/foreign record.」与「because each set only grants」、D7「this package's own object slice」
[ADR-0086 D3]✅ 存在于 packages/spec/src/security/permission.zod.ts
测量数字✅ 与 hotcrm#1449 module-split-inventory.json 一致(6 套:6/6/6/5/4/2)
补记标题体例✅ 与 ADR-0085「## Addendum (2026-08-28): …」、ADR-0087「## Addendum (2026-07-15) — as-built true-up」同族
三条给 #14488 的前置✅ 都标为输入而非决定;「A 是对 ADR-0086 D4 的修订而非并列新增」这一句是本补记最有价值的发现,已同步进 #14457 的关闭记录
changeset✅ 遵循 ADR-only 提交零 changeset 的先例(含 ADR-0130 自己的落地提交 682d03b),挂 skip-changesetCheck Changeset 绿

CI(018270546

Governed Surface Queue Guard ✅(标记受管)、Check Changeset ✅、Check Documentation Links ✅、Part-of PR must not also close its card ✅;Lint & Repo Gates 与 Type Check 四项在跑(docs-only,预期绿)。

收口

按 Prime Directive #14,本 PR 不进队列、不 arm,保持 draft 由维护者手合。合并即关 #14487


Generated by Claude Code

@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 1, item 2 (this PR, option A: merge as-is). Verbatim: 「以上都同意,你帮我合并pr」.

Executing on that instruction: marking ready and merging directly (squash) from the maintainer's own account. No approving review is submitted by any seat; under the governed-surface regime the human merge is the review record (see the header of scripts/pm/check-governed-queue-guard.mjs, "the direct merge stays the fallback path"). Governed path in this PR: docs/adr/0130-release-artifact-as-co-ownership-boundary.md.


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@hotlong@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487) - #14498

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum
Sep 3, 2026
Merged

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487)#14498
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#14487
Part of #14122

Governed surface — maintainer merge. This PR edits docs/adr/**, so it stays draft and is merged by hand by the maintainer; Governed Surface Queue Guard marks it. Nothing here authorises implementation work, and the record it amends is still Proposed.

What this is

One addendum section appended to docs/adr/0130-release-artifact-as-co-ownership-boundary.md, in that repository's existing addendum format (## Addendum (date, #card) — title, as in ADR-0006, ADR-0085, ADR-0087), recording the maintainer's 2026-09-02 ruling — option B on item 3 of #14454, the item carried onto decision card #14457: permission sets stay whole in the type: app package, and the permission matrix ADR-0130 §1.3(a) measures is not part of the payoff §4 claims for the package boundary.

⛔ No decision text moved. D1–D8, §1 and §3's non-goal on grouping keys are unchanged; no owner / publisher field; no new decision. The only edit outside the new section is a one-sentence pointer appended to the Status line — the ADR-0006 / ADR-0085 precedent for a record whose scope an addendum bounds.

The added text

Heading:

Addendum (2026-09-02, #14487) — the permission matrix is outside this boundary's payoff; permission sets stay whole in the app package

The bound it places on §1.3(a) and §4:

§1.3(a) counts, among the three measurable consequences of having no boundary, "a permission matrix of 30 rows × 9 CRUD columns × 6 permission sets that interleaves 客户/联系人/商机 with 运费标准/等级政策/工厂成本", and §4 draws the payoff from that section: "Studio's scope is the package, so package boundaries are the grouping Studio has never had (§1.3a)". The payoff does not extend to the permission matrix. Splitting a product into co-owning packages leaves that matrix exactly as flat as §1.3(a) found it.

The measurement it rests on:

A permission set grants across domains by nature: it is authored per role, not per module, so no module owns it. hotcrm#1449 measured the standard HotCRM product against its six planned modules — core, sales, cpq, service, marketing, activity, all inside the one crm namespace D1 makes co-ownable, with the type: app package declaring no objects at all. Of its six permission sets, four span five or six of the six modules (sales_rep, sales_manager and system_admin at six; service_agent at five); the remaining two span four (marketing_user) and two (guest_portal). Not one is confined to a single module.

Where the sets live:

The six sets stay whole in the type: app package. That is not a new rule but the standing one: ADR-0086 D3 gives a permission set exactly one owning packageId [...] In Studio's Access pillar — the matrix of ADR-0084, reached per package through ADR-0086 D7's package door — the sets therefore appear under the app package only, and their matrix stays as wide as the product. Modules group Data, Automation and Interface; they do not group Access.

What was not decided:

Per-package composition of grants — a module contributing its own objects' grants into a role the app package owns, by analogy with navigationContributions — is the other half of hotcrm#1449's question. It is filed, not decided: #14488, for the phase in which a module ships on its own (the §1.3(c) CPQ case, where a module's objects would otherwise arrive with no grants at all). It is out of this release and carries no commitment — neither that it will be built, nor that the contribution shape is the one it will take.

The section then records three constraints that phase inherits, so they are not rediscovered: ADR-0086 D4 stands until amended ("A package never writes into a shared/foreign record.", so #14488 is an amendment to D4, not an addition beside it); D4's conflict-freedom argument assumes one namespace per package, which D1 relaxes for co-owners inside one artifact; and which Access door edits a split product's app-owned sets is unmeasured (D7 scopes the package door to "this package's own object slice", and after a split the app package owns the sets but no objects). All three are marked as inputs, not decisions.

Status line, appended:

Scope bounded by the 2026-09-02 addendum (#14487): the permission matrix §1.3(a) measures is not part of this boundary's payoff — permission sets stay whole in the type: app package.

Evidence

Changeset — precedent followed

No changeset; the skip-changeset label carries it. Precedent, checked over the last 30 commits touching docs/adr: every ADR-only commit carries no changeset at all — including ADR-0130's own landing commit 682d03ba7 (#14151) and abeb5665e (#12867). scripts/check-empty-changeset.mjs rejects a newly added empty-frontmatter changeset, so in this repository the label is the mechanism and an empty file is not; the label is applied on this PR.

Gates

Family derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (1 path in the change set, 5 commands). All re-run at commit 018270546 under scripts/pm/os-verify-lock.sh, exit codes captured into a file before any pipe:

GateExitVerdict line
node scripts/check-adr-links.mjs0✅ check-adr-links: 600 relative link destination(s) under docs/adr/ resolve
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 427 files / 1398 TS blocks judged clean
pnpm check:adr-anchors0check-adr-anchors: OK (52 anchored file(s) [...] 31335 citation(s) across 4067 file(s) resolve)
pnpm check:doc-authoring0✓ doc authoring guard: 394 files clean — no bare metadata literals.
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm check:nul-bytes (not derived; run on every edit)0check-nul-bytes: OK (scanned 7914 text file(s) [...] no raw ASCII control bytes)

check:doc-formula-expressions first exited 3 — PREREQUISITE NOT MET (@objectstack/formula, then @objectstack/lint, not built), which is "nothing was measured", not a finding; both packages were built and the gate then ran green. The 9 further families dispatch-gates names apply only once a changeset path exists, which by the precedent above this PR does not create.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m


Generated by Claude Code

…ackage boundary's payoff (#14487)
Records the maintainer's 2026-09-02 ruling (option B on #14454 item 3): permission
sets stay whole in the `type: app` package, and the 30 x 9 x 6 permission matrix
§1.3(a) measures is NOT part of the payoff §4 claims for this boundary.
Measured, from the HotCRM split plan (objectstack-ai/hotcrm#1449): four of the six
permission sets span five or six of the six planned modules, and not one is confined
to a single module — a set grants per role, not per module, so no module owns it.
Per-package composition of grants is filed as #14488 for the phase in which a module
ships on its own, with no commitment; the section records the three constraints that
phase inherits (ADR-0086 D4 stands until amended; D4's conflict-freedom argument
assumes one namespace per package, which D1 relaxes; and which Access door edits a
split product's app-owned sets is unmeasured).
No decision text changes: D1-D8, §1 and §3's non-goal on grouping keys stand as
written. The Status line gains a pointer to the addendum, per the ADR-0006/0085
precedent for a record whose scope an addendum bounds.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
@hotlonghotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Sep 2, 2026
@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

PM 复审:PASS(内容与格式)—— 受管面,留 draft,请维护者手合

复审会话 session_01UHvF5hyiZjnCyExFnfQB8m(与派发 dev 同一会话身份、不同 agent 实例;维护者授权 PM 自审)。对象:全 diff(1 文件 +93)与它引用的每一处原文。

对树核实

实测
裁定表述✅ 与 #14454 评论 5507189677 及 #14457 关闭记录一致:本次 B;A 立 #14488 不承诺;来源标明是 PM 席的转述而非维护者原话
不动决定正文✅ D1–D8、§1、§3 未改;仅 Status 行追加一句范围限定 + 末尾一节补记
五处引文✅ 空白归一后逐字在源文件命中:ADR-0130 §1.3(a)「30 rows × 9 CRUD columns × 6 permission sets」、§4「the grouping Studio has never had」、ADR-0086 D4「A package never writes into a shared/foreign record.」与「because each set only grants」、D7「this package's own object slice」
[ADR-0086 D3]✅ 存在于 packages/spec/src/security/permission.zod.ts
测量数字✅ 与 hotcrm#1449 module-split-inventory.json 一致(6 套:6/6/6/5/4/2)
补记标题体例✅ 与 ADR-0085「## Addendum (2026-08-28): …」、ADR-0087「## Addendum (2026-07-15) — as-built true-up」同族
三条给 #14488 的前置✅ 都标为输入而非决定;「A 是对 ADR-0086 D4 的修订而非并列新增」这一句是本补记最有价值的发现,已同步进 #14457 的关闭记录
changeset✅ 遵循 ADR-only 提交零 changeset 的先例(含 ADR-0130 自己的落地提交 682d03b),挂 skip-changesetCheck Changeset 绿

CI(018270546

Governed Surface Queue Guard ✅(标记受管)、Check Changeset ✅、Check Documentation Links ✅、Part-of PR must not also close its card ✅;Lint & Repo Gates 与 Type Check 四项在跑(docs-only,预期绿)。

收口

按 Prime Directive #14,本 PR 不进队列、不 arm,保持 draft 由维护者手合。合并即关 #14487


Generated by Claude Code

@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 1, item 2 (this PR, option A: merge as-is). Verbatim: 「以上都同意,你帮我合并pr」.

Executing on that instruction: marking ready and merging directly (squash) from the maintainer's own account. No approving review is submitted by any seat; under the governed-surface regime the human merge is the review record (see the header of scripts/pm/check-governed-queue-guard.mjs, "the direct merge stays the fallback path"). Governed path in this PR: docs/adr/0130-release-artifact-as-co-ownership-boundary.md.


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@hotlong@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487) - #14498

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum
Sep 3, 2026
Merged

docs(adr): ADR-0130 addendum — the permission matrix is outside the package boundary's payoff (#14487)#14498
hotlong merged 1 commit into
mainfrom
claude/issue-14487-adr-0130-permission-matrix-addendum

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#14487
Part of #14122

Governed surface — maintainer merge. This PR edits docs/adr/**, so it stays draft and is merged by hand by the maintainer; Governed Surface Queue Guard marks it. Nothing here authorises implementation work, and the record it amends is still Proposed.

What this is

One addendum section appended to docs/adr/0130-release-artifact-as-co-ownership-boundary.md, in that repository's existing addendum format (## Addendum (date, #card) — title, as in ADR-0006, ADR-0085, ADR-0087), recording the maintainer's 2026-09-02 ruling — option B on item 3 of #14454, the item carried onto decision card #14457: permission sets stay whole in the type: app package, and the permission matrix ADR-0130 §1.3(a) measures is not part of the payoff §4 claims for the package boundary.

⛔ No decision text moved. D1–D8, §1 and §3's non-goal on grouping keys are unchanged; no owner / publisher field; no new decision. The only edit outside the new section is a one-sentence pointer appended to the Status line — the ADR-0006 / ADR-0085 precedent for a record whose scope an addendum bounds.

The added text

Heading:

Addendum (2026-09-02, #14487) — the permission matrix is outside this boundary's payoff; permission sets stay whole in the app package

The bound it places on §1.3(a) and §4:

§1.3(a) counts, among the three measurable consequences of having no boundary, "a permission matrix of 30 rows × 9 CRUD columns × 6 permission sets that interleaves 客户/联系人/商机 with 运费标准/等级政策/工厂成本", and §4 draws the payoff from that section: "Studio's scope is the package, so package boundaries are the grouping Studio has never had (§1.3a)". The payoff does not extend to the permission matrix. Splitting a product into co-owning packages leaves that matrix exactly as flat as §1.3(a) found it.

The measurement it rests on:

A permission set grants across domains by nature: it is authored per role, not per module, so no module owns it. hotcrm#1449 measured the standard HotCRM product against its six planned modules — core, sales, cpq, service, marketing, activity, all inside the one crm namespace D1 makes co-ownable, with the type: app package declaring no objects at all. Of its six permission sets, four span five or six of the six modules (sales_rep, sales_manager and system_admin at six; service_agent at five); the remaining two span four (marketing_user) and two (guest_portal). Not one is confined to a single module.

Where the sets live:

The six sets stay whole in the type: app package. That is not a new rule but the standing one: ADR-0086 D3 gives a permission set exactly one owning packageId [...] In Studio's Access pillar — the matrix of ADR-0084, reached per package through ADR-0086 D7's package door — the sets therefore appear under the app package only, and their matrix stays as wide as the product. Modules group Data, Automation and Interface; they do not group Access.

What was not decided:

Per-package composition of grants — a module contributing its own objects' grants into a role the app package owns, by analogy with navigationContributions — is the other half of hotcrm#1449's question. It is filed, not decided: #14488, for the phase in which a module ships on its own (the §1.3(c) CPQ case, where a module's objects would otherwise arrive with no grants at all). It is out of this release and carries no commitment — neither that it will be built, nor that the contribution shape is the one it will take.

The section then records three constraints that phase inherits, so they are not rediscovered: ADR-0086 D4 stands until amended ("A package never writes into a shared/foreign record.", so #14488 is an amendment to D4, not an addition beside it); D4's conflict-freedom argument assumes one namespace per package, which D1 relaxes for co-owners inside one artifact; and which Access door edits a split product's app-owned sets is unmeasured (D7 scopes the package door to "this package's own object slice", and after a split the app package owns the sets but no objects). All three are marked as inputs, not decisions.

Status line, appended:

Scope bounded by the 2026-09-02 addendum (#14487): the permission matrix §1.3(a) measures is not part of this boundary's payoff — permission sets stay whole in the type: app package.

Evidence

Changeset — precedent followed

No changeset; the skip-changeset label carries it. Precedent, checked over the last 30 commits touching docs/adr: every ADR-only commit carries no changeset at all — including ADR-0130's own landing commit 682d03ba7 (#14151) and abeb5665e (#12867). scripts/check-empty-changeset.mjs rejects a newly added empty-frontmatter changeset, so in this repository the label is the mechanism and an empty file is not; the label is applied on this PR.

Gates

Family derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (1 path in the change set, 5 commands). All re-run at commit 018270546 under scripts/pm/os-verify-lock.sh, exit codes captured into a file before any pipe:

GateExitVerdict line
node scripts/check-adr-links.mjs0✅ check-adr-links: 600 relative link destination(s) under docs/adr/ resolve
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 427 files / 1398 TS blocks judged clean
pnpm check:adr-anchors0check-adr-anchors: OK (52 anchored file(s) [...] 31335 citation(s) across 4067 file(s) resolve)
pnpm check:doc-authoring0✓ doc authoring guard: 394 files clean — no bare metadata literals.
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm check:nul-bytes (not derived; run on every edit)0check-nul-bytes: OK (scanned 7914 text file(s) [...] no raw ASCII control bytes)

check:doc-formula-expressions first exited 3 — PREREQUISITE NOT MET (@objectstack/formula, then @objectstack/lint, not built), which is "nothing was measured", not a finding; both packages were built and the gate then ran green. The 9 further families dispatch-gates names apply only once a changeset path exists, which by the precedent above this PR does not create.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m


Generated by Claude Code

…ackage boundary's payoff (#14487)
Records the maintainer's 2026-09-02 ruling (option B on #14454 item 3): permission
sets stay whole in the `type: app` package, and the 30 x 9 x 6 permission matrix
§1.3(a) measures is NOT part of the payoff §4 claims for this boundary.
Measured, from the HotCRM split plan (objectstack-ai/hotcrm#1449): four of the six
permission sets span five or six of the six planned modules, and not one is confined
to a single module — a set grants per role, not per module, so no module owns it.
Per-package composition of grants is filed as #14488 for the phase in which a module
ships on its own, with no commitment; the section records the three constraints that
phase inherits (ADR-0086 D4 stands until amended; D4's conflict-freedom argument
assumes one namespace per package, which D1 relaxes; and which Access door edits a
split product's app-owned sets is unmeasured).
No decision text changes: D1-D8, §1 and §3's non-goal on grouping keys stand as
written. The Status line gains a pointer to the addendum, per the ADR-0006/0085
precedent for a record whose scope an addendum bounds.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
@hotlonghotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Sep 2, 2026
@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

PM 复审:PASS(内容与格式)—— 受管面,留 draft,请维护者手合

复审会话 session_01UHvF5hyiZjnCyExFnfQB8m(与派发 dev 同一会话身份、不同 agent 实例;维护者授权 PM 自审)。对象:全 diff(1 文件 +93)与它引用的每一处原文。

对树核实

实测
裁定表述✅ 与 #14454 评论 5507189677 及 #14457 关闭记录一致:本次 B;A 立 #14488 不承诺;来源标明是 PM 席的转述而非维护者原话
不动决定正文✅ D1–D8、§1、§3 未改;仅 Status 行追加一句范围限定 + 末尾一节补记
五处引文✅ 空白归一后逐字在源文件命中:ADR-0130 §1.3(a)「30 rows × 9 CRUD columns × 6 permission sets」、§4「the grouping Studio has never had」、ADR-0086 D4「A package never writes into a shared/foreign record.」与「because each set only grants」、D7「this package's own object slice」
[ADR-0086 D3]✅ 存在于 packages/spec/src/security/permission.zod.ts
测量数字✅ 与 hotcrm#1449 module-split-inventory.json 一致(6 套:6/6/6/5/4/2)
补记标题体例✅ 与 ADR-0085「## Addendum (2026-08-28): …」、ADR-0087「## Addendum (2026-07-15) — as-built true-up」同族
三条给 #14488 的前置✅ 都标为输入而非决定;「A 是对 ADR-0086 D4 的修订而非并列新增」这一句是本补记最有价值的发现,已同步进 #14457 的关闭记录
changeset✅ 遵循 ADR-only 提交零 changeset 的先例(含 ADR-0130 自己的落地提交 682d03b),挂 skip-changesetCheck Changeset 绿

CI(018270546

Governed Surface Queue Guard ✅(标记受管)、Check Changeset ✅、Check Documentation Links ✅、Part-of PR must not also close its card ✅;Lint & Repo Gates 与 Type Check 四项在跑(docs-only,预期绿)。

收口

按 Prime Directive #14,本 PR 不进队列、不 arm,保持 draft 由维护者手合。合并即关 #14487


Generated by Claude Code

@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 1, item 2 (this PR, option A: merge as-is). Verbatim: 「以上都同意,你帮我合并pr」.

Executing on that instruction: marking ready and merging directly (squash) from the maintainer's own account. No approving review is submitted by any seat; under the governed-surface regime the human merge is the review record (see the header of scripts/pm/check-governed-queue-guard.mjs, "the direct merge stays the fallback path"). Governed path in this PR: docs/adr/0130-release-artifact-as-co-ownership-boundary.md.


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@hotlong@claude