Skip to content

docs(adr-0094): retire D5's env-overlay customization direction (D5-R, #6609 ruling A) - #6962

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-6858-adr0094-revision
Aug 9, 2026
Merged

docs(adr-0094): retire D5's env-overlay customization direction (D5-R, #6609 ruling A)#6962
os-zhuang merged 2 commits into
mainfrom
claude/issue-6858-adr0094-revision

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #6858

#6609 maintainer ruling A follow-through, written against the post-merge world PR #6608 created (merged 2026-08-09T05:05:06Z).

Premise re-verified before writing

All three of the card's readings still hold on origin/main @ 68feaadd6, and I re-measured rather than trusting the anchors:

Mechanism, measured

permission is now supportsOverlay: true, allowOrgOverride: false, allowRuntimeCreate: true. Two gates enforce that, and they split on artifact provenance, not on the record's managed_by column:

Target's definition saveMetaItem Where enforced
artifact-backed (ships in a code package) 403 NOT_OVERRIDABLE protocol's tier gate when environmentId is set; SysMetadataRepository.assertAllowed with override-artifact intent otherwise — so the refusal is topology-independent
sys_metadata-only (data-door created, or a package set authored + published through the METADATA door) accepted runtime-only intent rides allowRuntimeCreate, untouched by the rollback

The dogfood suite already pins that split the counter-intuitive way round: member_default's row is managed_by:'admin' and its edit is refused (its definition is a platform artifact), while a data-door-authored set edits fine. The record's provenance column was never what the gate reads.

The ruled choice, and why this PR picked it

The ruling permitted either re-routing the environment-customization intent to the allowRuntimeCreate materialized layer or retiring the direction. This PR retires it, and names the surviving layer narrowly instead of dressing it up as a re-route.

The re-route reading was rejected from D5's own text. D5's whole argument against "clone to customize" was that a clone forks — it stops receiving the vendor's later baseline changes and loses the layered code-vs-overlay diff. For a code-declared set the allowRuntimeCreate tier has exactly that property: it can only author a separate definition, never customize the declared one. Presenting it as D5's successor would re-introduce the fork D5 rejected under a new name. So D5-R retires the direction for artifact-backed sets, and records the surviving tier as a neighbour — it edits the one stored definition in place, with no code-vs-overlay layering and no "delete = reset to the shipped declaration".

Three axes, since the ruling weighed them: business — the direction was confirmed 2026-07-14 and never exercised (zero live org-scoped overlay rows in-repo at rollback, no example app customizes a packaged set from the environment), so there is no measured pull to preserve; long-term — ADR-0005 excludes the authorization surface definitionally and the admission pair cannot be written for it; authoring safety — an ADR pointing an author, human or agent, at a write path that 403s is the most expensive kind of documentation error, because the code it produces is confident and wrong.

Conclusion per authority face

  • ADR-0094 — new D5-R section retiring the direction; Status and a Revised line; the TL;DR paragraph rewritten; D3 gains a tier-gate table and the note that its artifact-backed delete row now describes a normally-empty path; two Consequences bullets corrected; References extended.
  • permission-set-projection.ts — the header's direction confirmation replaced with the retirement plus a per-write-point statement of what is refused and what still lands. The single-store branch's comment is corrected (its cause is no longer the only one); its thrown message is left verbatim per { field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240 (one condition, one wording) — the remedy it names, "edit the package and re-publish", is right on every kernel.
  • scripts/adr-anchors.json — new anchor for permission-set-projection.ts (ADR-0094 / ADR-0005 / ADR-0086) carrying the two rules that must not be quietly undone.

Disposition of the four write points — this card's design work

Left to 403 loudly, at the producer. plugin-security does not re-derive artifact-backing to pre-empt the refusal: isArtifactBacked is the metadata protocol's rule (it excludes the 'sys_metadata' rehydration sentinel), and a second copy in a consumer is the parallel-allowlist failure Prime Directive #8 exists to prevent — the managed_by-keyed heuristic the single-store branch uses is provably not the same fact.

Measured per point: only :794 (the update translation) is actually closed by the rollback. :752 needs an unmaterialized declaration to reach the gate at all. :713 and :928 cannot target an artifact-backed name — a packaged definition cannot be trashed through the data door, and the backfill only runs for names with no metadata presence — and both deliberately catch, because they run after the record already exists; they report on the durability channel (#4632) and the backfill counts the failure. That is the degradation report, not a swallow, and it stays.

Tests

PR #6608's own body recorded the blind spot: "its own suite stubs saveMetaItem, so this file is where that behaviour is actually pinned against the real gate". Four cases in permission-set-projection.test.ts were pinning the retired direction and staying green for exactly that reason — the stub could not refuse.

The stub now models ADR-0005's tier gate, and each affected fixture was triaged individually rather than re-spelled in a batch: two replaced with the refusal (asserting the code and status envelope — a bare toThrow() would stay green on any unrelated throw), two re-shaped to seed the legacy overlay directly, since the write that used to mint it is now refused. Five cases added: the :794 refusal, the materialized-row control that keeps the refusal meaningful, :752, :713's durability-channel disposition, and :928's inability to reach the gate.

Reverse verification — directions predicted before running

Mutation Predicted Measured
remove the tier gate from the test stub 4 red / 45 green: the three 403-envelope pins, plus the :713 pin failing by not logging exactly that — 4 failed / 45 passed, the four named ones
strip ADR-0005 from permission-set-projection.ts check:adr-anchors red naming that file exit 1, invariant text printed; restored exit 0

Honest delta: the :928 backfill pin stays green under the first mutation. That is predicted and correct rather than a gap — the case claims the backfill branch is never entered for a declared name, which is true independently of whether the gate exists, so it asserts on the write ledger (protocol.saves.length) instead of on the absence of a throw. Reported as a guard working, not tidied into a red.

Also deliberately not modelled: the stub gates saveMetaItem only. deleteMetaItem's artifact-backed refusal sits inside environmentId !== undefined with no repository-level twin, so its answer is topology-dependent and a single modelled verdict would be a fabrication; it stays pinned where the real gate is reachable.

Gates

check:adr-anchors OK (42 anchored files, +1) · check:adr-links OK · check:nul-bytes OK (plus a manual control-byte self-scan over all five touched files, no hits) · check:doc-authoring OK · check:docs-audit-scope OK · check:role-word OK · check:error-code-casing OK · check:durability-log-level OK · check:spec-parsed-alias OK · check:meta-type-normalized OK · @objectstack/plugin-security 866/866 tests pass · tsc --noEmit clean · eslint clean on both touched sources.

Deliberately NOT done

No behaviour change to the rollback — option C stays closed, no admission pair, no promote, allowOrgOverride untouched. Nothing under content/docs/releases/. No second copy of isArtifactBacked in the consumer. No change to the thrown single-store message.

Out of scope, filed

#6960 (finding, unassigned): a pre-rollback overlay row on an artifact-backed item of a rolled-back type can no longer be removed through the ordinary delete path — 403 on every kernel, leaving only OS_METADATA_WRITABLE. Dormant (zero such rows in-repo), and it spans all rolled-back overlayable types rather than ADR-0094, so D5-R records the carry-over in prose and the fix is left to triage.


Generated by Claude Code

…#6858)

#6609 maintainer ruling A follow-through, written against the post-merge world
PR #6608 created (merged 2026-08-09).

Premise re-verified on origin/main @ 68feaad before writing: the four
production write points are still at :713/:752/:794/:928 verbatim, the
2026-07-14 direction confirmation is still in the projection file's header,
and PR #6608 touched neither the ADR nor that file — the card's work was all
still outstanding.

Direction chosen: RETIRE, with the surviving layer named narrowly. The
ruling permitted re-routing the intent to the allowRuntimeCreate materialized
layer instead, and that reading was rejected from code: D5's own argument
against "clone to customize" was that a clone FORKS, and for a code-declared
set the allowRuntimeCreate tier has exactly that property — it can only
author a separate definition, never customize the declared one. Calling it a
re-route would re-introduce the fork D5 rejected under a new name. D5-R
therefore retires the direction for artifact-backed sets and records the
surviving tier as a neighbour, not a successor.

Disposition of the four write points: LEFT TO 403 LOUDLY at the producer.
plugin-security does not re-derive artifact-backing to pre-empt the refusal —
isArtifactBacked is the protocol's rule (it excludes the 'sys_metadata'
rehydration sentinel) and a second copy in a consumer is the parallel-allowlist
failure PD #8 exists to prevent. Measured: only :794 (update) is closed by the
rollback; :752 needs an unmaterialized declaration to reach the gate, and
:713/:928 cannot target an artifact-backed name at all.

Tests: the suite's protocol stub now models ADR-0005's tier gate. PR #6608's
own body recorded that this suite stubs saveMetaItem and therefore could not
see the gate; four cases here were pinning the RETIRED direction and staying
green for exactly that reason. Triaged individually — two replaced with the
403 envelope (code AND status, not a bare throw), two re-shaped to seed the
legacy overlay directly since the write that used to mint it is refused — and
five cases added covering each write point's disposition plus the surviving
tier as the control.

Reverse verification, direction predicted before running:
  - remove the stub's tier gate => predicted 4 red / 45 green, measured
    4 red / 45 green, the four named ones. The :928 backfill pin stays GREEN
    under the same mutation, as predicted and reported rather than tidied: it
    claims the branch is never entered, which is true independently of the gate.
  - strip ADR-0005 from the projection file => predicted check:adr-anchors red,
    measured exit 1 naming the file; restored exit 0.

Gates: check:adr-anchors OK (42 anchored files, +1 new entry for
permission-set-projection.ts), check:adr-links OK, check:nul-bytes OK,
check:doc-authoring OK, check:docs-audit-scope OK, check:role-word OK,
check:error-code-casing OK, check:durability-log-level OK,
check:spec-parsed-alias OK, check:meta-type-normalized OK.
plugin-security: 866/866 tests pass, tsc --noEmit clean, eslint clean.

Deliberately NOT done: no behaviour change to the rollback (option C stays
closed), no admission pair, no promote, no edit under content/docs/releases/,
and the single-store branch's thrown message is left verbatim (#5240 — one
condition, one wording); only its comment is corrected.

Out of scope, filed as #6960: a pre-rollback overlay row on an artifact-backed
item can no longer be REMOVED through the ordinary delete path (403 on every
kernel), only via OS_METADATA_WRITABLE. Dormant (zero such rows in-repo).

Fixes #6858

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BM1tNf5U3nEbHKR4fo5qVQ
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 9, 2026 6:40am

Request Review

@github-actions github-actions Bot added the size/m label Aug 9, 2026
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-security.

13 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/deployment/cli.mdx (via @objectstack/plugin-security)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/plugin-security)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/access-recipes.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/authorization.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/explain.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/permissions-matrix.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/plugin-security)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-security)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-security)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-security)
  • content/docs/ui/audience-based-interfaces.mdx (via packages/plugins/plugin-security)
  • content/docs/ui/dashboards.mdx (via @objectstack/plugin-security)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 9, 2026
Only conflict: scripts/adr-anchors.json — the union-append hot spot. Both
sides appended new anchor entries to the same array tail. Resolved as a TRUE
union by taking origin/main's file and re-appending this branch's entry, so
neither side's rows are dropped: main's two new entries (rest-server.ts and
runtime/src/domains/packages.ts, both ADR-0045 #4829) plus this branch's
permission-set-projection.ts entry. 44 anchors, 44 unique files.

Verified on the merged tree: check-adr-anchors --self-test OK, the gate itself
OK, check-nul-bytes OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BM1tNf5U3nEbHKR4fo5qVQ
@os-zhuang
os-zhuang marked this pull request as ready for review August 9, 2026 07:02
@os-zhuang
os-zhuang requested a review from hotlong August 9, 2026 07:02
@os-zhuang
os-zhuang requested a review from hotlong as a code owner August 9, 2026 07:02

Copy link
Copy Markdown
Contributor Author

PM review — PASS on every axis this seat may judge. Marked ready for review, and deliberately NOT armed for auto-merge.

Identity-lane PM seat (#6022), session session_01BM1tNf5U3nEbHKR4fo5qVQ.

Why this PR stops here

ADR maintainer approval is red by design, not by defect. #6741 / #6785 reserve the merge of any docs/adr/** PR to the maintainer in person; the job's own message states the green path is @hotlong's approving review, which re-runs the check. No agent seat may land this PR, this one included — so it is left ready-for-review with the review requested, and auto-merge is not armed. Every other check on head 3a3ad2f97 is completed: success (25 runs, ADR maintainer approval the only non-green: ESLint ✅, TypeScript Type Check ✅, Test Core ✅, Dogfood Regression Gate ✅, Check Changeset ✅).

What this seat verified independently, rather than taking on report

What the maintainer is actually being asked to approve

Ruling A permitted either re-routing the environment-customization intent to the allowRuntimeCreate tier or retiring the 2026-07-14 direction confirmation. This PR takes RETIRE, recorded as ADR-0094 D5-R, and argues the choice from code rather than preference: D5's own case against "clone to customize" was that a clone forks, and the allowRuntimeCreate tier has exactly that property for a code-declared set — it can only author a separate definition, never customize the declared one. Presenting it as D5's successor would have re-introduced, under a new name, the fork D5 rejected. The tier is therefore recorded as a surviving neighbour, stated narrowly.

Disposition of the 4 write points (this card's design work): left to 403 loudly at the producer. plugin-security does not re-derive artifact-backing to pre-empt the refusal, because isArtifactBacked is the protocol's rule and a second copy in a consumer is the parallel-allowlist failure PD #8 exists to prevent. Measured per point: only :794 is actually closed by the rollback; :752 needs an unmaterialized declaration to reach the gate; :713 and :928 cannot target an artifact-backed name at all and catch deliberately as durability channels (#4632).

One correction the maintainer should see, carried forward by the dev: the card and #6609 both describe the affected layer as "code-declared vs materialized", which is directionally right, but the gate keys on artifact provenance, not on the record's managed_by column — and the two disagree in production in both directions. member_default's row is managed_by: 'admin' and its edit is still refused. The ADR now states this with both measured specimens.

Reverse verification, as reported and accepted

RV-1 (remove the tier gate from the test stub): predicted 4 red / 45 green, measured exactly those four. RV-2 (strip ADR-0005 from the projection file): check:adr-anchors red naming that file, restored green. The :928 backfill pin stayed green under RV-1 — predicted, and correct: that case asserts on the write ledger (protocol.saves.length === 0), which holds whether or not the gate exists. Reported as a guard working rather than tidied into a red. deleteMetaItem's artifact-backed refusal was deliberately not modelled in the stub because its verdict is topology-dependent and modelling one answer would have been a fabrication.

Follow-up filed by the dev, unassigned, routing left to triage: #6960 — a pre-rollback env overlay on an artifact-backed item of a rolled-back type can no longer be removed through the ordinary delete path, which diverges from saveMetaItem's own stated carve-out that "removing a row that predates this refusal is repair". Dormant (zero such rows in-repo), spans all rolled-back overlayable types rather than ADR-0094 alone.


Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit 63f3b87 Aug 9, 2026
25 of 26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-6858-adr0094-revision branch August 9, 2026 08:37
@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Queue steward audit — this PR landed with its governance gate red.

ADR Merge Approval concluded failure on the merge_group ref at 08:22:15Z (it touches docs/adr/0094-sys-permission-set-pure-projection.md), this PR carries no APPROVED review from the maintainer account, and it merged at 08:37:15Z regardless. Direct evidence that the queue never kicked it: the next queue entry was built on top of this PR's result commit 63f3b87 at 08:26:00Z, three and a half minutes after the gate went red.

The gate is not in the merge queue's required-check set — evidence and suggested remedy in #7022. ⛔ No action is requested of this lane; whether ADR-0094 is ratified retroactively or reverted and re-landed through the approval path is the maintainer's call. This seat does not merge, revert, or change configuration.


Generated by Claude Code

os-zhuang added a commit that referenced this pull request Aug 13, 2026
… pointing at D5-R (#7351) (#8291)

D2 restated D5's 2026-07-14 direction as a live parenthetical — "see D5 — an env
overlay is the standard customization of a packaged set" — with no retirement
marker, in a section a reader reaches before D5 itself. D5's own heading has said
"RETIRED 2026-08-09 — see D5-R" since #6858 / PR #6962, so the document
contradicted itself in one file.

The pointer now names D5-R, marks the direction retired, and states what follows
for the projector: an artifact-backed set normally has no overlay layer left to
contribute to the effective body. This follows the idiom D3's table row already
uses ("Since D5-R this row describes a path that is normally empty").

D5's preserved body and the surrounding historical record are untouched — the
retirement is deliberately recorded as history, not rewritten.


Claude-Session: https://claude.ai/code/session_016pY4Xb2iDecfDtT3CWoiTW

Co-authored-by: Claude <noreply@anthropic.com>
hotlong pushed a commit that referenced this pull request Aug 13, 2026
…nting at D5-R (#8292) (#8385)

ADR-0086 carried three live-voiced, present-tense pointers at ADR-0094 D5's
2026-07-14 direction (an env overlay of a package-owned permission set is a
first-class ADR-0005 customization), which was RETIRED on 2026-08-09 by D5-R
(#6858 / PR #6962) after #6483 / PR #6608 rolled `permission` back to
`allowOrgOverride: false`:

- D7 status row: "an env edit of a package set is now a first-class overlay,
  not a flat 403" — directly contradicted by the current 403 not_overridable.
- P2 status row: "Subtract layer landed as ADR-0005 first-class overlay".
- The superseded-mechanisms block, item 2 (the site named in #8292).

Each now names D5-R and marks the direction retired, following the idiom
PR #8291 used inside ADR-0094 D2. The upshot is recorded where it matters:
this ADR's own two-doors answer stands unsuperseded — edit the package and
re-publish. The narrow carry-overs D5-R preserves (data-door delete still
degrades to a reset; supportsOverlay unchanged, so a pre-rollback overlay row
still merges at read time) are stated so they are not mistaken for a re-route.

The historical record is deliberately kept as history, not rewritten.

A fourth D5 citation (D4 status row, composition anchored at the position) is
left untouched: D5-R does not withdraw that bullet.

Part of #8292


Claude-Session: https://claude.ai/code/session_01Jqe56GnYFddggeAyfkZFVz

Co-authored-by: Claude <noreply@anthropic.com>
hotlong pushed a commit that referenced this pull request Aug 13, 2026
…066 D1 (#8440)

ADR-0094 D5 ("Env-scope overlays of package-owned sets are FIRST-CLASS
customizations") never discusses capabilities, placeholder derivation,
or managed_by back-doors, and is now RETIRED (#6858 / PR #6962) — the
cite sent readers to a section headed RETIRED 2026-08-09. The claim
being attributed ("retire implicit managed_by-guessing") is ADR-0066
D1's own direction; this corrects the cross-reference.

Part of #8386


Claude-Session: https://claude.ai/code/session_01Jqe56GnYFddggeAyfkZFVz

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…DR-0094 D5-R (objectstack-ai#7082) (objectstack-ai#7346)

PR objectstack-ai#6962 retired the 2026-07-14 env-overlay direction and fixed this file's
header; the function-level JSDoc of `upsertEnvPermissionSet` kept the same two
claims D5-R retired -- "an env overlay is the platform's standard customization
of a packaged definition" and "deleting the overlay resets the row to the
shipped declaration". objectstack-ai#6483 / PR objectstack-ai#6608 rolled `permission` back to
`allowOrgOverride: false` (the first is 403 NOT_OVERRIDABLE) and objectstack-ai#6960 measures
the delete path refusing to lift even a legacy overlay (the second).

Prose only, no behaviour change. Three neighbouring copies in the same package
are corrected with it so the package no longer states the direction in two
voices.


Claude-Session: https://claude.ai/code/session_01BM1tNf5U3nEbHKR4fo5qVQ

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…ired 2026-07-14 overlay direction (objectstack-ai#7351) (objectstack-ai#8290)

Both suites already pin the post-objectstack-ai#6483 refusal correctly in their test bodies,
which carry the objectstack-ai#6483 annotations. Only the file headers still described the
2026-07-14 direction — "a data-plane edit is TRANSLATED into an env-scope
ADR-0005 overlay" and "delete RESETS to the declared body" — as current. That
direction was retired on 2026-08-09 by ADR-0094 D5-R (objectstack-ai#6858 / PR objectstack-ai#6962), after
objectstack-ai#6483 / PR objectstack-ai#6608 rolled `permission` back to `allowOrgOverride: false`.

Prose only: the diff touches comment lines exclusively, no assertion and no
behaviour changes.


Claude-Session: https://claude.ai/code/session_016pY4Xb2iDecfDtT3CWoiTW

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…or edits of packaged permission sets (objectstack-ai#8292) (objectstack-ai#8384)

ADR-0094 D5's 2026-07-14 direction (translate an admin-door edit of a
package-owned permission set into a first-class ADR-0005 env-scope overlay)
was retired on 2026-08-09 by D5-R (objectstack-ai#6858 / PR objectstack-ai#6962), after objectstack-ai#6483 / PR objectstack-ai#6608
rolled `permission` back to `allowOrgOverride: false`. The published page
still asserted the retired direction in the present tense — and inverted:
it told a reader the edit succeeds, when the platform answers 403
not_overridable.

Rewrites the data-layer gate bullet to state current behavior and name the
supported channel (ADR-0086 two-doors: edit the package and re-publish),
in retired-voice consistent with metadata-plugin.zod.ts:921.

The sibling bullet ("deleting an artifact-backed set resets it to its
declared body") is left unchanged: it is still correct, pinned end-to-end by
packages/qa/dogfood/test/two-doors-permission.dogfood.test.ts.

Part of objectstack-ai#8292


Claude-Session: https://claude.ai/code/session_01Jqe56GnYFddggeAyfkZFVz

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants