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

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