Skip to content

feat(security): ADR-0091 L1 — grant validity windows with resolution-time filtering#2795

Merged
os-zhuang merged 1 commit into
mainfrom
feat/adr-0091-l1-grant-validity
Jul 10, 2026
Merged

feat(security): ADR-0091 L1 — grant validity windows with resolution-time filtering#2795
os-zhuang merged 1 commit into
mainfrom
feat/adr-0091-l1-grant-validity

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

What

ADR-0091 L1 (accepted 2026-07-10): time becomes a first-class axis of authorization. Grant rows gain validity windows, and correctness lives in resolution-time filtering, fail-closed — never in a background cleanup job (ADR-0049).

D1 — columns (both user-grant tables)

sys_user_position + sys_user_permission_set gain valid_from / valid_until (half-open [from, until), UTC; null = unbounded → existing rows unchanged, zero migration) plus the lifecycle-audit columns reason, delegated_from, last_certified_at, certified_by.

D2 — the shared predicate, applied symmetrically

New isGrantActive / isGrantExpired in @objectstack/core (grant-validity.ts), consumed by:

  • resolveAuthzContext — both grant tables filtered before any derivation; an expired unscoped admin_full_access grant no longer derives platform_admin.
  • explain buildContextForUser — same filter; expired-but-present rows come back as expiredGrants and the principal layer reports the dedicated "held until … — expired" contributor state (spec: ExplainLayer contributors gain optional state: 'active' | 'expired'), so "why did access disappear" is self-answering.
  • PositionGraphService.expandPositionUsers — expired holders stop receiving position-recipient shares (valid_* columns ride the projection).
  • D12 delegated-admin gate — covered transitively: its held-scope resolution consumes the already-filtered set resolution (an expired sub-admin grant is an expired admin).

Present-but-unparseable bounds fail closed (deliberately stricter than API-key isExpired — a grant row is standing authority, not a single credential).

D7 lint rules (both error, over seed data)

  • security-grant-expired-at-authoring — a valid_until already past (or unparseable) at authoring time is a grant that can never resolve: dead on arrival.
  • security-delegation-missing-reason — a delegated_from row without reason breaks the D3 dual audit.
  • Drive-by: re-export the missing SECURITY_MASTER_DETAIL_UNGRANTED constant.

Docs

authorization.mdx grant-lifecycle section + ADR index row; ADR-0091 Phasing L1 marked landed.

Tests

Suite Result
core (incl. new grant-validity.test.ts, 5 new resolver cases) 94/94
plugin-security (explain expired state) 265/265
plugin-sharing (expired-holder exclusion) 76/76
lint (4 new fixtures) 188/188
spec 6684/6684

Liveness gate passes. Boundary semantics covered: valid_until exactly at now = inactive (half-open); future valid_from = pending, filtered but not reported "expired".

Not in this PR (L2, per ADR phasing)

delegatable flag + D12 gate self-service branch, break-glass workflow, certification-stamp write path, dogfood proof.

🤖 Generated with Claude Code

https://claude.ai/code/session_012oLzaP8n7A3YKFmgaHWC8H


Generated by Claude Code

…time filtering

Effective-dated grants land as data + filtering, never as a cleanup job
(ADR-0049): a sys_user_position / sys_user_permission_set row outside its
half-open [valid_from, valid_until) window stops resolving, fail-closed,
symmetrically in every resolver.

- objects: both user-grant tables gain valid_from / valid_until / reason /
  delegated_from / last_certified_at / certified_by (D1; null = unbounded,
  existing rows unchanged — zero migration)
- core: shared isGrantActive/isGrantExpired predicate; resolveAuthzContext
  filters both tables (an expired unscoped admin_full_access grant no longer
  derives platform_admin); unparseable bounds fail closed
- explain: buildContextForUser filters + returns expiredGrants; the principal
  layer reports the dedicated 'held until … — expired' contributor state
  (spec: ExplainLayer contributors gain optional state: active|expired)
- sharing: expandPositionUsers drops expired holders from position-recipient
  expansion (valid_* columns ride the projection)
- lint (D7): security-grant-expired-at-authoring +
  security-delegation-missing-reason (both error) over seed grant rows;
  re-export the missing SECURITY_MASTER_DETAIL_UNGRANTED
- docs: authorization.mdx lifecycle section + ADR index row; ADR-0091 L1
  marked landed

Tests: core 94, plugin-security 265, plugin-sharing 76, lint 188, spec 6684 —
all green; liveness gate passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012oLzaP8n7A3YKFmgaHWC8H
@vercel

vercel Bot commented Jul 10, 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 10, 2026 2:38pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Jul 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): @objectstack/core, @objectstack/lint, @objectstack/plugin-security, @objectstack/plugin-sharing, @objectstack/spec.

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

  • content/docs/ai/actions-as-tools.mdx (via @objectstack/core)
  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/knowledge-rag.mdx (via @objectstack/core)
  • content/docs/ai/natural-language-queries.mdx (via @objectstack/core)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @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/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/core, @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/core, 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/migration-from-objectql.mdx (via @objectstack/core)
  • 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/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/core, @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/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/core)
  • content/docs/kernel/runtime-services/index.mdx (via 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/core, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/core)
  • content/docs/permissions/access-recipes.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/authentication.mdx (via @objectstack/core)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint, packages/plugins/plugin-security, packages/plugins/plugin-sharing, @objectstack/spec)
  • content/docs/permissions/explain.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via packages/plugins/plugin-security, packages/plugins/plugin-sharing, @objectstack/spec)
  • content/docs/permissions/positions.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/anatomy.mdx (via @objectstack/core)
  • content/docs/plugins/development.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/core, @objectstack/plugin-security, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/core, @objectstack/plugin-security, @objectstack/plugin-sharing, @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/core, @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/core)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/core, @objectstack/spec)
  • 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/plugins/plugin-sharing, 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/core, @objectstack/plugin-security, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/audience-based-interfaces.mdx (via packages/plugins/plugin-security)
  • 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/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.

@os-zhuang
os-zhuang marked this pull request as ready for review July 10, 2026 15:03
@os-zhuang
os-zhuang merged commit 82e745e into main Jul 10, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the feat/adr-0091-l1-grant-validity branch July 10, 2026 15:03
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants