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 definitionsaveMetaItemWhere enforced
artifact-backed (ships in a code package)403 NOT_OVERRIDABLEprotocol'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)acceptedruntime-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 codeandstatus 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

MutationPredictedMeasured
remove the tier gate from the test stub4 red / 45 green: the three 403-envelope pins, plus the :713 pin failing by not loggingexactly that — 4 failed / 45 passed, the four named ones
strip ADR-0005 from permission-set-projection.tscheck:adr-anchors red naming that fileexit 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-security866/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

vercelBot commented Aug 9, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 9, 2026 6:40am

Request Review

@github-actions

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-actionsgithub-actionsBot 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 hotlongAugust 9, 2026 07:02
@os-zhuang
os-zhuang requested a review from hotlong as a code ownerAugust 9, 2026 07:02
@os-zhuangClaude

Copy link
Copy Markdown
ContributorAuthor

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 queueAug 9, 2026
Merged via the queue into main with commit 63f3b87Aug 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

claudeBot 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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-zhuang@claude