Skip to content

feat(spec,security,lint): ADR-0066 authoring surface — per-operation requiredPermissions (⑤) + capability-reference lint (⑨)#2690

Merged
os-zhuang merged 2 commits into
mainfrom
claude/auth-lifecycle-gaps-248nvb
Jul 8, 2026
Merged

feat(spec,security,lint): ADR-0066 authoring surface — per-operation requiredPermissions (⑤) + capability-reference lint (⑨)#2690
os-zhuang merged 2 commits into
mainfrom
claude/auth-lifecycle-gaps-248nvb

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

What & why

Closes two P2 model-completeness items on the authorization gap map (umbrella #2561), both on the ADR-0066 authoring surface. They ship together because ⑨'s lint understands ⑤'s per-operation shape.


⑤ Per-operation object requiredPermissions

Today an object's requiredPermissions gates all of CRUD on one capability set; ERP routinely needs "read-open / write-gated" (Salesforce & Dataverse separate capability by operation).

Object.requiredPermissions now accepts either string[] (all operations, original shape) or { read?, create?, update?, delete? } (gates each operation class independently).

  • spec (data/object.zod.ts): ObjectRequiredPermissionsSchema union + PerOperationRequiredPermissionsSchema (.strict(), so a mistyped key is rejected at author time).
  • plugin-security: normalized into per-CRUD buckets; the AND-gate (before the CRUD grant, fail-closed) checks the caps for the request's operation. crudBucketForOperation (derived from OPERATION_TO_PERMISSION) folds transfer/restoreupdate, purgedelete.
  • Backward-compatible: the string[] form normalizes into an all bucket that unions with every operation, preserving gate-every-operation semantics.

⑨ Author-time capability-reference lint

requiredPermissions is a free string, so a typo (mange_users) is schema-valid and fails closed at runtime — safe but undiscoverable. New validateCapabilityReferences (@objectstack/lint) warns at author time when a requiredPermissions (object / field / app / action) names a capability registered nowhere.

  • Author-time "known" set = built-in platform capabilities ∪ capabilities the stack grants via a permission set's systemPermissionssys_capability seed rows.
  • Warning, not error: a package can't see other installed packages' capabilities, and the reference fails closed anyway. systemPermissions (the declaration side) is never flagged. Understands the ⑤ per-operation map and points at the exact operation slice.
  • spec now owns the canonical PLATFORM_CAPABILITIES / PLATFORM_CAPABILITY_NAMES (security/capabilities.ts) as the single source of truth; plugin-security's bootstrapSystemCapabilities seeds from that same list (no drift between seeder and lint).
  • cli: wired into os validate and os lint.

Tests / verification

  • plugin-security: 192 passed (⑤ middleware cases + crudBucketForOperation unit tests).
  • lint: 139 passed (8 new ⑨ rule cases).
  • spec: 6715 passed; API-surface snapshot regenerated (0 breaking, 7 added exports); liveness ✓.
  • Full workspace turbo build green (no downstream typecheck breakage).
  • End-to-end: os validate runs the new step clean on a real example app, and emits the expected warning on a seeded capability typo (validation still passes — non-blocking).
  • Docs (content/docs/permissions/authorization.mdx) + ADR-0066 ⑤/⑨ updated to "landed".

Refs #2561

🤖 Generated with Claude Code

https://claude.ai/code/session_0187GeNqezxV6g5jiLiggfbt

…66 ⑤)

Object `requiredPermissions` may now be either a `string[]` (gates all CRUD,
the original shape) or a `{ read, create, update, delete }` map that gates each
operation class independently — enabling read-open / write-gated objects
(Salesforce & Dataverse separate capability by operation).

- spec: `ObjectRequiredPermissionsSchema` union + `PerOperationRequiredPermissions`
  (`.strict()` so a mistyped key fails at author time).
- plugin-security: normalize into per-CRUD buckets and enforce the caps for the
  request's operation as the same D3 AND-gate (fail-closed, checked before the
  CRUD grant). `transfer`/`restore` fold into `update`, `purge` into `delete`
  via `crudBucketForOperation`, derived from the CRUD permission bits.
- Backward-compatible: the array form is normalized into an `all` bucket that
  unions with the per-op bucket, preserving gate-every-operation semantics.
- Tests: per-operation middleware cases (read-open / create-gated / array-form
  backward compat) + `crudBucketForOperation` unit tests. Docs + ADR-0066 ⑤
  marked landed; api-surface snapshot regenerated.

Refs #2561

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

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 8, 2026 12:37pm

Request Review

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:data tests tooling and removed size/m labels Jul 8, 2026
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/cli, @objectstack/lint, @objectstack/plugin-security, @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/cli, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/cli, @objectstack/plugin-security, @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/access-recipes.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint, packages/plugins/plugin-security, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via packages/plugins/plugin-security, @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via packages/plugins/plugin-security, @objectstack/spec)
  • content/docs/permissions/profiles.mdx (via @objectstack/spec)
  • content/docs/permissions/roles.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/plugin-security, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/objectos/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/protocol/objectos/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli, @objectstack/plugin-security, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/role-based-interfaces.mdx (via packages/plugins/plugin-security)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)

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.

`requiredPermissions` is a free string, so a typo (`mange_users`) is schema-valid
and fails closed at runtime — safe but undiscoverable. The new
`validateCapabilityReferences` rule (@objectstack/lint) warns at author time
(`os validate` / `os lint`) when a `requiredPermissions` on an object, field, app,
or action names a capability registered nowhere.

Author-time "known" set = built-in platform capabilities ∪ capabilities the
stack grants via a permission set's `systemPermissions` ∪ `sys_capability` seed
rows. Warning (not error): a package can't see other installed packages'
capabilities, and the reference fails closed anyway. `systemPermissions` (the
declaration side) is never flagged. Understands the per-operation
`requiredPermissions` map form (⑤) and points at the exact operation slice.

- spec: canonical `PLATFORM_CAPABILITIES` / `PLATFORM_CAPABILITY_NAMES`
  (security/capabilities.ts) as the single source of truth.
- plugin-security: `bootstrapSystemCapabilities` now seeds from that list
  (KNOWN_CAPABILITIES re-exports it — no drift between seeder and lint).
- cli: wired into `os validate` and `os lint`.
- Tests: 8 rule cases; end-to-end verified via `os validate`. Docs + ADR-0066 ⑨
  marked landed; api-surface snapshot regenerated.

Refs #2561

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0187GeNqezxV6g5jiLiggfbt
@github-actions github-actions Bot added the size/l label Jul 8, 2026
@os-zhuang os-zhuang changed the title feat(spec,security): per-operation object requiredPermissions (ADR-0066 ⑤) feat(spec,security,lint): ADR-0066 authoring surface — per-operation requiredPermissions (⑤) + capability-reference lint (⑨) Jul 8, 2026
@os-zhuang os-zhuang marked this pull request as ready for review July 8, 2026 15:28
@os-zhuang os-zhuang merged commit 466adf6 into main Jul 8, 2026
17 checks passed
@os-zhuang os-zhuang deleted the claude/auth-lifecycle-gaps-248nvb branch July 8, 2026 15:28
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 protocol:data size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants