[P1] Add persistent project-scoped authorization rules to the authority boundary #76

Open
opened 2026-08-17 07:41:18 +00:00 by lost-rob0t · 0 comments
lost-rob0t commented 2026-08-17 07:41:18 +00:00 (Migrated from github.com)

Parent: #74

Depends on: #75, #53

Goal

Add persistent project-scoped authorization rules that let a trusted host/operator remember a bounded approval across restarts while continuing to use the existing #53 authority boundary.

This is the core semantic equivalent of a downstream UI action such as:

Allow this operation for this project persistently.

It is not a fifth authority tier, not persistent allow_session, and not an alias for dangerous.

Problem / motivation

#53 correctly implements runtime authority modes and exact pending-operation approval:

  • approve_diff;
  • allow_once;
  • allow_session;
  • dangerous.

allow_session is intentionally tied to a concrete runtime/session and cleared on teardown. That is the right behavior and must remain unchanged.

What is missing is a separate trusted policy layer that can remember an earlier operator decision such as:

Permit this project's configured test operation without prompting me every run.

That remembered decision must be narrower than broad unattended authority and must remain subordinate to all current validation, capability, confinement, host-policy, effect-accounting, and cancellation boundaries.

Architectural boundary

Reuse rlm_authority; do not build authority_v2.

The intended flow is conceptually:

operation exists / tool loaded
  -> capability check
  -> schema + hard execution/confinement policy
  -> normalized executable operation
  -> current trusted host ceiling / persistent-policy eligibility
  -> eligible project-scoped persisted rule lookup/match
  -> existing authority mediation / pending lifecycle as required
  -> execution

The exact insertion point must preserve #53's invariant that authority mediates an already-valid normalized operation. Persisted policy cannot be consulted early as a way to skip validation.

The persistent rule store comes from #75. This issue owns matching/resolution and integration with authority decisions, not generic storage.

Persistent authorization model

Support two deliberately distinct authorization classes.

1. Exact operation grant

An exact grant matches one canonical normalized operation identity including all security-relevant executable fields.

Examples of changes that must invalidate an exact grant when relevant to that operation:

  • executable or tool name;
  • arguments/payload;
  • target path;
  • normalized cwd;
  • server/profile/package/version;
  • environment/config reference identities that affect the normalized operation;
  • effect class;
  • required capability;
  • other confinement-relevant normalized details.

Rendered strings are not the security identity when structured normalized fields are available.

2. Explicit bounded policy profile/pattern

A trusted host/provider may deliberately declare a closed, schema-validated bounded matcher/profile such as “the project's configured test operation family.”

This is not arbitrary regex/string matching over shell text and not a callable supplied by model/project data.

Bounded policies must have inspectable identity/version and closed matcher semantics so changes do not silently widen their match set.

Examples of safe directions to evaluate:

  • exact tool + effect + fixed argument fields with enumerated variable fields;
  • trusted named operation profiles with validated structured parameters;
  • another closed matcher algebra implemented by trusted core/provider code.

Do not expose arbitrary call/1 or repository-provided predicates as the match engine.

Relationship to #53 operation fingerprints

#53's rlm_operation_fingerprint/3 intentionally fingerprints the normalized operation with its runtime authority context. That is correct for exact in-process pending approval, but an ephemeral runtime/session context cannot simply be persisted as the project grant identity and expected to mean the same thing after restart.

This issue must explicitly design the composition. Acceptable directions include:

  • introduce/reuse a canonical context-independent normalized operation identity for persistent policy and keep #53's context-bound fingerprint for pending execution;
  • or derive a project-policy fingerprint from the same canonical operation data plus stable project identity while preserving #53's existing approval fingerprint semantics.

Do not weaken #53's exact fingerprint behavior merely to make persistence convenient.

The same normalized operation data should drive both systems so they cannot disagree about security-relevant payload changes.

Project scope

Use #75's canonical ProjectIdentity; do not use raw cwd as the authorization key.

The authorization contract must define deterministic behavior for:

  • symlinked access to the same project;
  • Git worktrees;
  • relocated repositories;
  • multiple clones;
  • non-Git projects;
  • stale/ambiguous project identities.

The first slice need not solve universal distributed repo identity. It must fail closed when project identity cannot be resolved unambiguously for a persistent security decision.

Trusted grant creation

Only trusted host/operator code can create or revoke a persistent grant.

A model/agent may request persistent authorization and may provide the normalized proposed operation as data, but the final persistent mutation requires an explicit host/operator-mediated action.

There must be no API path where:

model says "remember this"
  -> durable grant created automatically

Likewise, discovering a project policy/config file must not itself create a persistent grant.

Core exposes state/decision APIs only. Downstream clients decide whether the action is labeled “Always allow,” “Allow for project,” etc.

Host ceiling and precedence

Persisted project authorization is eligible only when current trusted host/runtime policy permits it.

Required invariant:

hard runtime restrictions
        >
current trusted host ceiling/policy
        >
eligible project-scoped persisted authorization
        >
session / operation decision

The current four #53 modes are not automatically a complete representation of this new “persistent-grant eligibility” ceiling. The implementation must deliberately define how a host enables/forbids use of persisted grants without turning the runtime into allow_session or dangerous.

For example, a default approve_diff runtime may still be allowed by trusted host configuration to honor an explicitly stored project grant for one exact operation. Conversely a stricter host/run policy must be able to ignore/reject that grant and require approval/deny execution.

Do not silently infer “stored grant exists, therefore it wins.”

Capability and hard-policy separation

A persistent authorization only answers the human-mediation question for an otherwise valid operation.

It never grants or bypasses:

  • tool(Name) or any other capability;
  • schema/argument validation;
  • path/root confinement;
  • MCP installer/stdio execution-profile policy from #52/#72;
  • network policy;
  • process policy;
  • budgets/timeouts/output limits;
  • ownership/lifecycle rules;
  • cancellation;
  • effect identity/accounting.

An operation that fails any earlier hard boundary remains denied even if a matching persistent grant exists.

dangerous remains an explicit separate mode and cannot be synthesized from one or many project grants.

Revocation and supersession

Persistent grants require stable inspectable rule IDs and first-class revocation.

Revocation must:

  • take effect for new decisions immediately;
  • survive restart;
  • make the revoked rule ineligible;
  • retain bounded non-executable provenance/audit information where appropriate;
  • never retain pending trusted continuations as history.

If a bounded policy profile changes version/shape, existing grants must either bind to the old exact profile identity or fail closed pending explicit migration/re-authorization. Do not reinterpret an old grant under a broader new matcher silently.

Retries, concurrency, effect identity, cancellation

Persistent permission answers whether mediation can be skipped, not whether an effect should execute again.

Compose with #57 where applicable so retries/replay do not use a persistent grant to duplicate an already-claimed side effect.

Preserve #63 cancellation linearization and #64 active-vs-terminal state rules. A stored grant cannot resurrect an operation whose owner/run has already been cancelled.

Concurrent grant creation/revocation/decision must have deterministic semantics. In particular, revocation racing with a not-yet-executing operation must have a documented linearization point and fail safe.

Public API direction

Exact predicates must follow repository conventions. Provide trusted host-facing semantics equivalent to:

project_authorization_grant_exact(+Store, +Project, +Operation, +Meta, -Outcome).
project_authorization_grant_profile(+Store, +Project, +ProfileRule, +Meta, -Outcome).
project_authorization_revoke(+Store, +GrantId, +Reason, -Outcome).
project_authorization_list(+Store, +Project, +Options, -Outcome).

project_authorization_match(+Store,
                            +Project,
                            +NormalizedOperation,
                            +HostPolicy,
                            -Outcome).

The authority integration should consume a sanitized decision equivalent to:

no_persisted_match
eligible_exact_grant(GrantRef)
eligible_bounded_grant(GrantRef, ProfileRef)
stored_but_ineligible(Reason)
invalid_policy(Reason)

Do not expose trusted matcher callables or persistence backend internals in model-facing operation data.

Immediate grant lookup/mutation over local state does not need fake async APIs. If the selected #75 backend is genuinely latency-bearing, use #54's canonical execute -> async Future -> sync-await direction rather than duplicating authorization logic.

Security invariants

  • The model cannot create, widen, or revoke trusted durable authorization by itself.
  • Project files cannot auto-grant permission merely by existing or being loaded.
  • allow_session remains non-persistent.
  • Persistent grants never change authority mode to dangerous.
  • Exact grants match normalized structured operation identity, not approximate rendered command text.
  • Any security-relevant exact-operation change requires a new grant.
  • Bounded rules are explicit, closed, inspectable, trusted declarations; no arbitrary predicate/callable supplied by model/project input.
  • Project A grants never authorize project B.
  • Ambiguous/stale project identity fails closed.
  • A stricter current host/runtime ceiling defeats a stored grant.
  • Capability denial still wins.
  • Schema/confinement/process/network/budget/effect/cancellation denial still wins.
  • Malformed/unknown-version grant records fail closed.
  • Secrets are not copied into grant records; normalized operations retain only safe reference metadata as existing authority/MCP boundaries require.

Interaction with existing modules/issues

#53 / rlm_authority

This issue extends policy resolution into the existing authority decision boundary. It must not duplicate pending approval, approve/deny/edit, allow-once consumption, session authority, authority events, or child narrowing.

#75

Owns durable scopes, project identity, records, persistence, provenance, and revocation substrate. This issue defines authorization-specific record schemas/matching and authority integration.

#16

An artifact may describe a proposed command or prior decision as knowledge, but artifact publication never creates a grant.

#52 / #72

An MCP server/profile can be configured and still require mediation. A stored project grant cannot bypass the closed MCP profile/package/config/cwd hard policy.

#57

Reuse canonical effect identity/once-only accounting when that issue establishes it. Persistent human authorization and effect deduplication are separate axes.

#68-#71

Compiled workflows may carry effective project policy as explicit operator state, but model-produced workflow/context data cannot mutate the grant store.

Acceptance criteria

  • Trusted host API can create an exact project-scoped grant for an already normalized operation.
  • The grant survives process restart through #75 and is eligible again only for the same resolved project.
  • Restart does not set dangerous and does not persist/restore allow_session.
  • The same exact operation in project B is not authorized by project A's grant.
  • A changed security-relevant payload/argument/path/cwd/profile/etc. does not inherit an exact grant.
  • Edit/re-normalization producing a new #53 fingerprint also cannot silently inherit an exact grant unless its persistent operation identity still exactly matches by defined semantics.
  • At least one deliberately bounded trusted policy-profile fixture can authorize a documented narrow operation family without permitting an unrelated operation.
  • Widening/changing that profile's schema/version does not silently widen an existing grant.
  • Capability denial happens even when a persistent grant matches.
  • Hard confinement/process/network/MCP policy denial happens even when a persistent grant matches.
  • A stricter current host/runtime policy can suppress a stored grant and force the appropriate existing #53 decision path.
  • Revocation prevents subsequent authorization immediately and after restart.
  • Malformed/unknown-version grants fail closed.
  • Model-facing plans/tools cannot create persistent grants directly.
  • Loading project config alone cannot create a grant.
  • Adversarial tests cover project isolation, changed payload, stale/ambiguous identity, profile widening, revoke/decision race, retry/effect interaction, and cancellation before execution claim.
  • Existing #53 tests remain valid, especially exact fingerprint, allow-once, non-persistent allow-session, child narrowing, approval edit invalidation, and dangerous hard-boundary behavior.

Non-goals

  • No UI/TUI.
  • No persistent allow_session.
  • No new broad “allow forever” authority mode.
  • No machine-global permission created from a project decision.
  • No generic process/shell allowlist implementation here.
  • No arbitrary regex/shell-string policy engine.
  • No arbitrary Prolog matcher loaded from a repository.
  • No replacement for capabilities or #53.
  • No effect ledger implementation beyond integration with #57 when available.

Dependencies / references

  • Parent #74
  • Depends on #75 scoped durable-state substrate
  • Composes with completed #53 authority boundary
  • #52/#72 MCP hard policy
  • #54 async contract
  • #57 effect identity / once-only observations
  • #63 cancellation-linearizable approval execution
  • #64 bounded terminal authority state
  • #68-#71 context/workflow pipeline
  • docs/authority-runtime.md
  • prolog/rlm_authority.pl
  • research/RLM-RESEARCH-003-typed-symbolic-execution.org
  • research/RLM-RESEARCH-009-durable-artifact-context.org

RLM-RESEARCH-010 remains on draft PR #58 rather than canonical main; do not depend on unmerged details.

Parent: #74 Depends on: #75, #53 ## Goal Add **persistent project-scoped authorization rules** that let a trusted host/operator remember a bounded approval across restarts while continuing to use the existing #53 authority boundary. This is the core semantic equivalent of a downstream UI action such as: > Allow this operation for this project persistently. It is not a fifth authority tier, not persistent `allow_session`, and not an alias for `dangerous`. ## Problem / motivation #53 correctly implements runtime authority modes and exact pending-operation approval: - `approve_diff`; - `allow_once`; - `allow_session`; - `dangerous`. `allow_session` is intentionally tied to a concrete runtime/session and cleared on teardown. That is the right behavior and must remain unchanged. What is missing is a separate trusted policy layer that can remember an earlier operator decision such as: > Permit this project's configured test operation without prompting me every run. That remembered decision must be narrower than broad unattended authority and must remain subordinate to all current validation, capability, confinement, host-policy, effect-accounting, and cancellation boundaries. ## Architectural boundary Reuse `rlm_authority`; do not build `authority_v2`. The intended flow is conceptually: ```text operation exists / tool loaded -> capability check -> schema + hard execution/confinement policy -> normalized executable operation -> current trusted host ceiling / persistent-policy eligibility -> eligible project-scoped persisted rule lookup/match -> existing authority mediation / pending lifecycle as required -> execution ``` The exact insertion point must preserve #53's invariant that authority mediates an already-valid normalized operation. Persisted policy cannot be consulted early as a way to skip validation. The persistent rule store comes from #75. This issue owns matching/resolution and integration with authority decisions, not generic storage. ## Persistent authorization model Support two deliberately distinct authorization classes. ### 1. Exact operation grant An exact grant matches one canonical normalized operation identity including all security-relevant executable fields. Examples of changes that must invalidate an exact grant when relevant to that operation: - executable or tool name; - arguments/payload; - target path; - normalized cwd; - server/profile/package/version; - environment/config reference identities that affect the normalized operation; - effect class; - required capability; - other confinement-relevant normalized details. Rendered strings are not the security identity when structured normalized fields are available. ### 2. Explicit bounded policy profile/pattern A trusted host/provider may deliberately declare a closed, schema-validated bounded matcher/profile such as “the project's configured test operation family.” This is **not** arbitrary regex/string matching over shell text and not a callable supplied by model/project data. Bounded policies must have inspectable identity/version and closed matcher semantics so changes do not silently widen their match set. Examples of safe directions to evaluate: - exact tool + effect + fixed argument fields with enumerated variable fields; - trusted named operation profiles with validated structured parameters; - another closed matcher algebra implemented by trusted core/provider code. Do not expose arbitrary `call/1` or repository-provided predicates as the match engine. ## Relationship to #53 operation fingerprints #53's `rlm_operation_fingerprint/3` intentionally fingerprints the normalized operation **with its runtime authority context**. That is correct for exact in-process pending approval, but an ephemeral runtime/session context cannot simply be persisted as the project grant identity and expected to mean the same thing after restart. This issue must explicitly design the composition. Acceptable directions include: - introduce/reuse a canonical **context-independent normalized operation identity** for persistent policy and keep #53's context-bound fingerprint for pending execution; - or derive a project-policy fingerprint from the same canonical operation data plus stable project identity while preserving #53's existing approval fingerprint semantics. Do not weaken #53's exact fingerprint behavior merely to make persistence convenient. The same normalized operation data should drive both systems so they cannot disagree about security-relevant payload changes. ## Project scope Use #75's canonical `ProjectIdentity`; do not use raw `cwd` as the authorization key. The authorization contract must define deterministic behavior for: - symlinked access to the same project; - Git worktrees; - relocated repositories; - multiple clones; - non-Git projects; - stale/ambiguous project identities. The first slice need not solve universal distributed repo identity. It **must** fail closed when project identity cannot be resolved unambiguously for a persistent security decision. ## Trusted grant creation Only trusted host/operator code can create or revoke a persistent grant. A model/agent may request persistent authorization and may provide the normalized proposed operation as data, but the final persistent mutation requires an explicit host/operator-mediated action. There must be no API path where: ```text model says "remember this" -> durable grant created automatically ``` Likewise, discovering a project policy/config file must not itself create a persistent grant. Core exposes state/decision APIs only. Downstream clients decide whether the action is labeled “Always allow,” “Allow for project,” etc. ## Host ceiling and precedence Persisted project authorization is eligible only when current trusted host/runtime policy permits it. Required invariant: ```text hard runtime restrictions > current trusted host ceiling/policy > eligible project-scoped persisted authorization > session / operation decision ``` The current four #53 modes are not automatically a complete representation of this new “persistent-grant eligibility” ceiling. The implementation must deliberately define how a host enables/forbids use of persisted grants **without** turning the runtime into `allow_session` or `dangerous`. For example, a default `approve_diff` runtime may still be allowed by trusted host configuration to honor an explicitly stored project grant for one exact operation. Conversely a stricter host/run policy must be able to ignore/reject that grant and require approval/deny execution. Do not silently infer “stored grant exists, therefore it wins.” ## Capability and hard-policy separation A persistent authorization only answers the human-mediation question for an otherwise valid operation. It never grants or bypasses: - `tool(Name)` or any other capability; - schema/argument validation; - path/root confinement; - MCP installer/stdio execution-profile policy from #52/#72; - network policy; - process policy; - budgets/timeouts/output limits; - ownership/lifecycle rules; - cancellation; - effect identity/accounting. An operation that fails any earlier hard boundary remains denied even if a matching persistent grant exists. `dangerous` remains an explicit separate mode and cannot be synthesized from one or many project grants. ## Revocation and supersession Persistent grants require stable inspectable rule IDs and first-class revocation. Revocation must: - take effect for new decisions immediately; - survive restart; - make the revoked rule ineligible; - retain bounded non-executable provenance/audit information where appropriate; - never retain pending trusted continuations as history. If a bounded policy profile changes version/shape, existing grants must either bind to the old exact profile identity or fail closed pending explicit migration/re-authorization. Do not reinterpret an old grant under a broader new matcher silently. ## Retries, concurrency, effect identity, cancellation Persistent permission answers **whether mediation can be skipped**, not whether an effect should execute again. Compose with #57 where applicable so retries/replay do not use a persistent grant to duplicate an already-claimed side effect. Preserve #63 cancellation linearization and #64 active-vs-terminal state rules. A stored grant cannot resurrect an operation whose owner/run has already been cancelled. Concurrent grant creation/revocation/decision must have deterministic semantics. In particular, revocation racing with a not-yet-executing operation must have a documented linearization point and fail safe. ## Public API direction Exact predicates must follow repository conventions. Provide trusted host-facing semantics equivalent to: ```prolog project_authorization_grant_exact(+Store, +Project, +Operation, +Meta, -Outcome). project_authorization_grant_profile(+Store, +Project, +ProfileRule, +Meta, -Outcome). project_authorization_revoke(+Store, +GrantId, +Reason, -Outcome). project_authorization_list(+Store, +Project, +Options, -Outcome). project_authorization_match(+Store, +Project, +NormalizedOperation, +HostPolicy, -Outcome). ``` The authority integration should consume a sanitized decision equivalent to: ```text no_persisted_match eligible_exact_grant(GrantRef) eligible_bounded_grant(GrantRef, ProfileRef) stored_but_ineligible(Reason) invalid_policy(Reason) ``` Do not expose trusted matcher callables or persistence backend internals in model-facing operation data. Immediate grant lookup/mutation over local state does not need fake async APIs. If the selected #75 backend is genuinely latency-bearing, use #54's canonical execute -> async Future -> sync-await direction rather than duplicating authorization logic. ## Security invariants - The model cannot create, widen, or revoke trusted durable authorization by itself. - Project files cannot auto-grant permission merely by existing or being loaded. - `allow_session` remains non-persistent. - Persistent grants never change authority mode to `dangerous`. - Exact grants match normalized structured operation identity, not approximate rendered command text. - Any security-relevant exact-operation change requires a new grant. - Bounded rules are explicit, closed, inspectable, trusted declarations; no arbitrary predicate/callable supplied by model/project input. - Project A grants never authorize project B. - Ambiguous/stale project identity fails closed. - A stricter current host/runtime ceiling defeats a stored grant. - Capability denial still wins. - Schema/confinement/process/network/budget/effect/cancellation denial still wins. - Malformed/unknown-version grant records fail closed. - Secrets are not copied into grant records; normalized operations retain only safe reference metadata as existing authority/MCP boundaries require. ## Interaction with existing modules/issues ### #53 / `rlm_authority` This issue extends policy resolution **into** the existing authority decision boundary. It must not duplicate pending approval, approve/deny/edit, allow-once consumption, session authority, authority events, or child narrowing. ### #75 Owns durable scopes, project identity, records, persistence, provenance, and revocation substrate. This issue defines authorization-specific record schemas/matching and authority integration. ### #16 An artifact may describe a proposed command or prior decision as knowledge, but artifact publication never creates a grant. ### #52 / #72 An MCP server/profile can be configured and still require mediation. A stored project grant cannot bypass the closed MCP profile/package/config/cwd hard policy. ### #57 Reuse canonical effect identity/once-only accounting when that issue establishes it. Persistent human authorization and effect deduplication are separate axes. ### #68-#71 Compiled workflows may carry effective project policy as explicit operator state, but model-produced workflow/context data cannot mutate the grant store. ## Acceptance criteria - [ ] Trusted host API can create an exact project-scoped grant for an already normalized operation. - [ ] The grant survives process restart through #75 and is eligible again only for the same resolved project. - [ ] Restart does not set `dangerous` and does not persist/restore `allow_session`. - [ ] The same exact operation in project B is **not** authorized by project A's grant. - [ ] A changed security-relevant payload/argument/path/cwd/profile/etc. does **not** inherit an exact grant. - [ ] Edit/re-normalization producing a new #53 fingerprint also cannot silently inherit an exact grant unless its persistent operation identity still exactly matches by defined semantics. - [ ] At least one deliberately bounded trusted policy-profile fixture can authorize a documented narrow operation family without permitting an unrelated operation. - [ ] Widening/changing that profile's schema/version does not silently widen an existing grant. - [ ] Capability denial happens even when a persistent grant matches. - [ ] Hard confinement/process/network/MCP policy denial happens even when a persistent grant matches. - [ ] A stricter current host/runtime policy can suppress a stored grant and force the appropriate existing #53 decision path. - [ ] Revocation prevents subsequent authorization immediately and after restart. - [ ] Malformed/unknown-version grants fail closed. - [ ] Model-facing plans/tools cannot create persistent grants directly. - [ ] Loading project config alone cannot create a grant. - [ ] Adversarial tests cover project isolation, changed payload, stale/ambiguous identity, profile widening, revoke/decision race, retry/effect interaction, and cancellation before execution claim. - [ ] Existing #53 tests remain valid, especially exact fingerprint, allow-once, non-persistent allow-session, child narrowing, approval edit invalidation, and dangerous hard-boundary behavior. ## Non-goals - No UI/TUI. - No persistent `allow_session`. - No new broad “allow forever” authority mode. - No machine-global permission created from a project decision. - No generic process/shell allowlist implementation here. - No arbitrary regex/shell-string policy engine. - No arbitrary Prolog matcher loaded from a repository. - No replacement for capabilities or #53. - No effect ledger implementation beyond integration with #57 when available. ## Dependencies / references - Parent #74 - Depends on #75 scoped durable-state substrate - Composes with completed #53 authority boundary - #52/#72 MCP hard policy - #54 async contract - #57 effect identity / once-only observations - #63 cancellation-linearizable approval execution - #64 bounded terminal authority state - #68-#71 context/workflow pipeline - `docs/authority-runtime.md` - `prolog/rlm_authority.pl` - `research/RLM-RESEARCH-003-typed-symbolic-execution.org` - `research/RLM-RESEARCH-009-durable-artifact-context.org` `RLM-RESEARCH-010` remains on draft PR #58 rather than canonical `main`; do not depend on unmerged details.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nsaspy/prolog-rlm#76
No description provided.