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-zhuangos-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

vercelBot commented Jul 8, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 8, 2026 12:37pm

Request Review

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

github-actionsBot 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
@os-zhuangos-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 mainJul 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:datasize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude