docs(adr-0094): retire D5's env-overlay customization direction (D5-R, #6609 ruling A) - #6962
Conversation
…#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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 13 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
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
|
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 Why this PR stops here
What this seat verified independently, rather than taking on report
What the maintainer is actually being asked to approveRuling A permitted either re-routing the environment-customization intent to the Disposition of the 4 write points (this card's design work): left to 403 loudly at the producer. 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 Reverse verification, as reported and acceptedRV-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): 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 Generated by Claude Code |
|
Queue steward audit — this PR landed with its governance gate 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 |
… 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>
…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>
…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>
…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>
…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>
…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>
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::713/:752/:794/:928verbatim — they did not drift across the fix(spec): ADR-0005 白名单强制 —— 九类未获批 allowOrgOverride:true 回滚为 false (#6483) #6608 merge;permission-set-projection.ts's file header;docs/adr/0094-*.mdnorpermission-set-projection.tsis among them, so there was no overlap to report and no zero-work outcome.Mechanism, measured
permissionis nowsupportsOverlay: true,allowOrgOverride: false,allowRuntimeCreate: true. Two gates enforce that, and they split on artifact provenance, not on the record'smanaged_bycolumn:saveMetaItemNOT_OVERRIDABLEenvironmentIdis set;SysMetadataRepository.assertAllowedwithoverride-artifactintent otherwise — so the refusal is topology-independentsys_metadata-only (data-door created, or a package set authored + published through the METADATA door)runtime-onlyintent ridesallowRuntimeCreate, untouched by the rollbackThe dogfood suite already pins that split the counter-intuitive way round:
member_default's row ismanaged_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
allowRuntimeCreatematerialized 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
allowRuntimeCreatetier 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
D5-Rsection retiring the direction;Statusand aRevisedline; the TL;DR paragraph rewritten; D3 gains a tier-gate table and the note that its artifact-backeddeleterow 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 forpermission-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-securitydoes not re-derive artifact-backing to pre-empt the refusal:isArtifactBackedis 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 — themanaged_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.:752needs an unmaterialized declaration to reach the gate at all.:713and:928cannot 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 inpermission-set-projection.test.tswere 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
codeandstatusenvelope — a baretoThrow()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:794refusal, 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
:713pin failing by not loggingADR-0005frompermission-set-projection.tscheck:adr-anchorsred naming that fileHonest delta: the
:928backfill 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
saveMetaItemonly.deleteMetaItem's artifact-backed refusal sits insideenvironmentId !== undefinedwith 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-anchorsOK (42 anchored files, +1) ·check:adr-linksOK ·check:nul-bytesOK (plus a manual control-byte self-scan over all five touched files, no hits) ·check:doc-authoringOK ·check:docs-audit-scopeOK ·check:role-wordOK ·check:error-code-casingOK ·check:durability-log-levelOK ·check:spec-parsed-aliasOK ·check:meta-type-normalizedOK ·@objectstack/plugin-security866/866 tests pass ·tsc --noEmitclean · eslint clean on both touched sources.Deliberately NOT done
No behaviour change to the rollback — option C stays closed, no admission pair, no promote,
allowOrgOverrideuntouched. Nothing undercontent/docs/releases/. No second copy ofisArtifactBackedin 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 onlyOS_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