feat(spec)!: book audience gates by permission set — retire the { profile } arm (ADR-0090)#2732
Merged
Merged
Conversation
…file } arm (ADR-0090)
ADR-0090 D2 removed the Profile concept, but BookAudienceSchema still
modelled its gated arm as { profile: string } (with pre-D3 vocabulary in
the comments). Books ship in packages, and packages own permission sets
but never positions (D9 — the same environment-ownership argument that
removed profiles), so the gate is a capability reference:
- `{ profile: string }` → `{ permissionSet: string }` — one-step rename,
no alias; the zod union now rejects `{ profile }` at parse time.
- `'public'` comment now points at the built-in `guest` position (D9)
instead of the removed "guest profile".
- Regenerated content/docs/references (book.mdx), which also catches up
security/* with the AdminScope (D12) and explain (D6) shapes that
landed without a docs regen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ph4jEAfWDh6taMbZweWhom
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 93 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 10, 2026 04:43
os-zhuang
added a commit
to objectstack-ai/objectui
that referenced
this pull request
Jul 10, 2026
…pec's { permissionSet } shape (ADR-0090) (#2371)
The framework retired BookAudience's { profile } arm (ADR-0090 D2 removed
the Profile concept; D9 makes the gate a capability reference). Update the
three UI mirrors: the metadata-admin default JSON schema oneOf, the
BookPreview audience chip, and the book list-column renderer. The "Role-
gated" form title also violated the D3 word ban and is now
"Permission-set gated".
Companion to objectstack-ai/objectstack#2732.
Claude-Session: https://claude.ai/code/session_01Ph4jEAfWDh6taMbZweWhom
Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang
added a commit
that referenced
this pull request
Jul 10, 2026
… the ADR-0090 D2/D3 cleanup the P1 wave missed (#2774) * feat(security)!: enforce book audience at the REST read layer; finish the ADR-0090 D2/D3 cleanup the P1 wave missed Follow-through on the { permissionSet } book-audience rename (#2732), in ADR-0049 discipline — the gated arm existed but nothing enforced it: - rest: /meta/book, /meta/doc, and /meta/book/:name/tree now enforce the ADR-0046 §6.7 audience model. Anonymous callers see only public books/docs; { permissionSet }-gated books require holding the named set; a doc's effective audience is the union over the books that CLAIM it (unclaimed → org; orphan rendering never inherits public). Fails CLOSED when holdings cannot be resolved. doc/book item reads bypass the shared meta cache (per-caller gate vs shared ETag). Nine new route tests. - spec: pure helpers powering the gate (audienceAllows, resolveDocAudiences, docAudienceAllows, resolveBookClaimedDocs) with unit tests; the REST layer and any future portal share ONE semantics. - plugin-security: security service exposes resolvePermissionSetNames — the same resolution as data-plane enforcement. - D2/D3 leftovers: METADATA_FORM_REGISTRY role→position (the position type had LOST its form layout in the P1 rename) and profile removed; artifact ingestion maps positions→'position' (stale roles→'role' matched nothing and silently dropped compiled positions); EnvironmentArtifactMetadataSchema declares positions; metadata-form translations regain position and drop role/profile in all four locales (+ vocabulary regression test); position.test/audit.zod/security-plugin identifiers and comments de-role'd; eslint + doc-authoring domain lists Role→Position; content/docs/ui/role-based-interfaces.mdx renamed to audience-based-interfaces.mdx with stale permission-vocabulary copy fixed across five docs. - lint: books join the D3 role-word scan; new advisory rule security-book-audience-unknown-set flags a gated audience naming a set the stack does not declare (runtime fails closed — surface the typo at author time). - scripts/check-role-word.mjs: ADR-0090 D3 vocabulary RATCHET over content/docs + skills (baseline freezes the 45 current files; new occurrences fail CI; improvements ratchet the baseline down). Wired into the lint workflow. Verified: spec/objectql(808)/cli(464)/rest(227+9)/metadata(260)/ platform-objects(76)/plugin-security/lint(32) suites green; eslint, check:role-word, check:doc-authoring, check:api-surface (regenerated), check:liveness, check:skill-docs, check-changeset-fixed all pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ph4jEAfWDh6taMbZweWhom * test(lint): de-flake lazy-deps in-process gate test — explicit 30s timeout The in-process variant cold-loads sucrase + typescript and took 5.6s on a loaded CI runner, tripping vitest's default 5s per-test timeout (Test Core failure on this PR's first run). The test asserts a loading CONTRACT, not latency — give it an explicit generous timeout like the sibling dist-based variants effectively have via their spawn overhead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ph4jEAfWDh6taMbZweWhom --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
BookAudienceSchema(packages/spec/src/system/book.zod.ts) still modelled its gated arm as{ profile: string }— a reference to the Profile concept ADR-0090 D2 deleted — with pre-D3 vocabulary ("role-gated", "guest profile") in the comments. This PR retires it with a launch-window one-step rename, no aliases.Decision: permission set, not position
The gated arm becomes
{ permissionSet: string }, a capability reference, for three reasons grounded in ADR-0090:crm_admin) the customer binds to a position of their own choosing" — so a CRM package's Admin Guide gates to{ permissionSet: 'crm_admin' }, keeping ADR-0086 provenance and uninstall semantics intact.permission_set. Positions are distribution. The two literals already map to the D9 anchors:'org'inherits the package grant (authenticated members),'public'≡ the built-inguestposition.ADR-0046 §6.7 itself glossed the arm as "gates a book to a profile / permission set", so this lands on the reading that survived ADR-0090.
Changes
book.zod.ts— union arm renamed; comments rewritten to D3 vocabulary (guestposition, capability-gated); doc comment records the D9 rationale. The zod union now rejects{ profile }at parse time (contract-first, no lenient dialect, per the D4 discipline).book.test.ts— audience variants updated; new test asserting{ profile }is rejected.content/docs/references/**— regenerated (gen:schema+gen:docs). This also catches upsecurity/*with theAdminScope(D12) andexplain(D6) shapes that landed in feat(security)!: ADR-0090 P3 — security publish linter (D7), delegated administration (D12), BU assignment anchor #2711/feat(security): ADR-0090 P4 — explain engine (D6), access-matrix snapshot gate, recalibrated benchmark #2716 without a docs regen.@objectstack/spec.The REST read layer only evaluates
isPublicAudience(anonymous gate), so no runtime evaluation code changes; enforcement of the gated arm remains the ADR-0046 Phase D item it already was.Companion PR
ObjectUI mirrors this shape in three places (metadata-admin JSON schema
oneOf,BookPreviewaudience chip, book list-column renderer) — companion PR in objectstack-ai/objectui updates them to{ permissionSet }.Why the D3 forbidden-word lint didn't catch this
The D3 lint (
security-role-word,packages/lint/src/validate-security-posture.ts, shipped in P3 #2711) validates published stack metadata — names/labels of objects, fields, actions, permission sets, positions, and apps. It could not have caught this because:sys_role,isProfile, …) and missed this file because the arm was spelledprofile, notrole.profile(unlikerole) is not a banned word, so a book authored with{ profile: ... }drew no finding. After this PR the schema itself rejects the shape, which is the stronger, contract-first gate.Possible follow-ups (not in this PR): a source/docs-level
\brole\bsweep check (e.g.content/docs/ui/role-based-interfaces.mdxstill violates the D3 docs ban), and adding book name/label to the lint's role-word scan now that book audience is a permission-model reference. Two more D2 leftovers found in objectui'sdefault-schemas.ts(roleandprofilemetadata-type entries mirroring the framework'smetadata-form-registry.tsprofile: permissionFormrow) are flagged in the companion PR rather than fixed, since removing them needs a coordinated registry change.🤖 Generated with Claude Code
https://claude.ai/code/session_01Ph4jEAfWDh6taMbZweWhom
Generated by Claude Code