Skip to content

P1 F5.9b: expire approval grants after an immutable 10-minute lifetime #299

Description

@gnanirahulnutakki

Goal

Complete the next durable F5.9 security slice by giving every newly created approval grant one immutable, server-enforced absolute lifetime of exactly 10 minutes.

The usable interval is half-open:

approved_at <= consumed_at < expires_at

This remains foundational approval infrastructure only. It must not introduce MCP transport, credentials, connector dispatch, or an execution path.

Decision

The owner selected the fixed-lifetime option proposed on #23:

  • lifetime: exactly 10 minutes
  • source of time: PostgreSQL statement time at creation and consumption
  • mutability: expires_at is immutable at the application-role boundary
  • expiry response: the existing indistinguishable ErrApprovalGrantUnavailable
  • expiry evidence: no successful lifecycle event is appended
  • retention: the expired row remains for forensic inspection
  • renewal: outside this slice; a fresh action intent is required after expiry

Why this slice is safe and independently valuable

F5.9a (#250 / #251) already persists exact, single-use grants and consumes them with one conditional UPDATE ... RETURNING. The remaining gap is that an unconsumed grant has no expiry and can otherwise remain usable indefinitely.

The expiry predicate belongs in that same atomic update. Checking expiry before the update would create a time-of-check/time-of-use race; trusting a caller-supplied timestamp would make the lifetime bypassable.

Scope

  • Add an additive 0013 migration for approval-grant expiry.
  • Add immutable expires_at timestamptz NOT NULL with a database constraint requiring exactly approved_at + interval '10 minutes'.
  • Add an immutable evidence/lifecycle version marker so legacy rows remain distinguishable.
  • Backfill legacy rows for retention, but make every legacy unconsumed grant fail closed after migration; do not manufacture a false consumption event.
  • Mint approved_at and expires_at from one PostgreSQL statement timestamp.
  • Consume using one conditional UPDATE ... RETURNING whose predicate includes approved_at <= statement_timestamp() AND statement_timestamp() < expires_at.
  • Remove caller control over approval and consumption timestamps from the public approval-grant API.
  • Introduce a new lifecycle audit format and evidence-hash domain that binds the immutable expiry.
  • Preserve offline verification of existing policy format 1 and approval format 2 records.
  • Keep the application role limited to updating consumed_at; it must not update expiry or version metadata.
  • Update the operator/developer contract and session evidence.

Acceptance criteria

  • Every new grant has expires_at = approved_at + 10 minutes, enforced by PostgreSQL.
  • Consumption succeeds before expiry and atomically records the database statement time.
  • Consumption at or after expiry fails with ErrApprovalGrantUnavailable.
  • Unknown, foreign-workspace, mismatched, replayed, expired, and legacy-unconsumed grants remain indistinguishable to callers.
  • An expired refusal leaves consumed_at IS NULL, retains the row, and appends no successful approval-consumed event.
  • Two concurrent consumers before expiry still produce exactly one success.
  • Expiry and evidence-version columns cannot be updated by the application role.
  • New creation and consumption lifecycle entries share the same evidence digest, and mutating expires_at changes that digest.
  • Existing format-1 and format-2 audit exports remain independently rehashable; mixed format-1/2/3 chains verify.
  • Migration, unit, PostgreSQL integration, race, fuzz/adversarial, repository CI, release, and real two-cluster gates pass.

Non-goals

  • MCP elicitation/create transport, URL-mode elicitation, or trusting elicitation content.
  • Configurable/PDP-selected lifetimes.
  • Renewal or multiple active grants for one immutable intent.
  • Ardur PDP integration, multi-approver counting, credentials, connector execution, signed dispatch, shell/filesystem access, or production mutation.

Security, operability, and cost

  • PostgreSQL is the authoritative clock at the durable compare-and-set boundary.
  • Expiry is checked in the same write that consumes the grant, so there is no TOCTOU gap.
  • Legacy grants are retained for forensics but cannot cross the new evidence contract.
  • The added storage is one timestamp plus a small version field per grant; compute and storage cost are negligible at approval volumes.
  • Database/app clock skew no longer widens the authorization window.

Primary references

Parent: #23
Depends on: #250

Metadata

Metadata

Assignees

No one assigned

    Labels

    governancePDP / audit / decision-ledger / tenancyphase-3Phase 3: policy federation + MCP writepolicy-federationFan-out policy / approvals / abstention

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions