Execute runtime credential invalidation for declared suppression #19

Closed
opened 2026-08-26 10:24:04 +00:00 by agent.odin · 3 comments
Owner

Proposal

Vedanta's optional runtime executor for a suppression that flake-ops has already declared and a NixOps4 provider has already applied: invalidate the workflow-time credentials the suppressed identity still holds.

Delivery class

agent-unit — a gated runtime path with capture doubles, over src/kanidm.rs's exact token-ID invalidation.

Design

A duplicate, and the narrower of the two runtime endpoints. The provider (nixops4-providers#26) performs the terminal authority withdrawal and the UUID tombstone. Vedanta adds only immediacy for credentials already in flight: a suppressed identity holding a live workflow JWT is a window the next apply does not close fast enough.

Gated on the observed terminal projection, not on the declaration. Acting on a declaration the provider has not applied would invalidate credentials for an identity the directory still considers authorized — a partial suppression that looks complete from one side.

Bounded hard, and this is where Vedanta's law bites. It invalidates active workflow JWTs and API credentials by exact token ID. It does not delete, suppress, disable, or retire a principal: Vedanta's own law forbids principal deletion outright and assigns rotation, disabling and retirement to Jostoph. A suppression endpoint that reached past credentials would be doing the provider's job and Jostoph's at once.

Idempotent, with the audit distinction kept. Invalidating an already-invalid or unknown credential returns success — a suppression that ran once must be able to finish twice — while the record retains whether the credential was observed before. Same discipline as kanidm.rs and forgejo.rs.

Tasks

  • Verify the declaration and the provider-observed terminal projection; refuse otherwise.
  • Invalidate active workflow JWTs and API credentials by exact token ID.
  • Return success for already-invalid or unknown credentials, retaining the observed-before distinction.
  • Prove no principal is deleted, disabled, retired, or rotated on this path.
  • Prove no sweep is possible — invalidation names exact token IDs.
  • Emit the runtime audit outcome without releasing or recreating directory state.

Specification Delta

Requirement: runtime invalidation follows the terminal projection

Scenario: suppression is declared but not yet applied

  • GIVEN a declared suppression whose projection is not terminal
  • WHEN the runtime endpoint is called
  • THEN it refuses, and invalidates nothing

Requirement: the runtime path never touches a principal

Scenario: a runtime suppression succeeds

  • GIVEN a declared and observed-terminal suppression
  • WHEN the runtime endpoint acts
  • THEN the named workflow credentials are invalidated by exact token ID
  • AND the principal is neither deleted, disabled, retired, nor rotated

Scenario: it runs a second time

  • GIVEN credentials already invalidated
  • WHEN the endpoint runs again
  • THEN it succeeds
  • AND the audit retains that they were not observed live this time

OpenSpec

OpenSpec 85

Structural parent

Ting/Ting#87 — Epic: definitive suppression.

## Proposal Vedanta's optional runtime executor for a suppression that flake-ops has already declared and a NixOps4 provider has already applied: invalidate the workflow-time credentials the suppressed identity still holds. ## Delivery class `agent-unit` — a gated runtime path with capture doubles, over `src/kanidm.rs`'s exact token-ID invalidation. ## Design **A duplicate, and the narrower of the two runtime endpoints.** The provider ([nixops4-providers#26](https://jo.et0.pw/lar.ad/nixops4-providers/issues/26)) performs the terminal authority withdrawal and the UUID tombstone. Vedanta adds only immediacy for credentials already in flight: a suppressed identity holding a live workflow JWT is a window the next apply does not close fast enough. **Gated on the observed terminal projection**, not on the declaration. Acting on a declaration the provider has not applied would invalidate credentials for an identity the directory still considers authorized — a partial suppression that looks complete from one side. **Bounded hard, and this is where Vedanta's law bites.** It invalidates active workflow JWTs and API credentials by **exact token ID**. It does not delete, suppress, disable, or retire a principal: [Vedanta's own law](https://jo.et0.pw/Ting/Vedanta/src/branch/fastlane/AGENTS.md) forbids principal deletion outright and assigns rotation, disabling and retirement to Jostoph. A suppression endpoint that reached past credentials would be doing the provider's job and Jostoph's at once. **Idempotent, with the audit distinction kept.** Invalidating an already-invalid or unknown credential returns success — a suppression that ran once must be able to finish twice — while the record retains whether the credential was observed before. Same discipline as `kanidm.rs` and `forgejo.rs`. ## Tasks - [ ] Verify the declaration and the provider-observed **terminal** projection; refuse otherwise. - [ ] Invalidate active workflow JWTs and API credentials by exact token ID. - [ ] Return success for already-invalid or unknown credentials, retaining the observed-before distinction. - [ ] Prove no principal is deleted, disabled, retired, or rotated on this path. - [ ] Prove no sweep is possible — invalidation names exact token IDs. - [ ] Emit the runtime audit outcome without releasing or recreating directory state. ## Specification Delta ### Requirement: runtime invalidation follows the terminal projection #### Scenario: suppression is declared but not yet applied - **GIVEN** a declared suppression whose projection is not terminal - **WHEN** the runtime endpoint is called - **THEN** it refuses, and invalidates nothing ### Requirement: the runtime path never touches a principal #### Scenario: a runtime suppression succeeds - **GIVEN** a declared and observed-terminal suppression - **WHEN** the runtime endpoint acts - **THEN** the named workflow credentials are invalidated by exact token ID - **AND** the principal is neither deleted, disabled, retired, nor rotated #### Scenario: it runs a second time - **GIVEN** credentials already invalidated - **WHEN** the endpoint runs again - **THEN** it succeeds - **AND** the audit retains that they were not observed live this time ## OpenSpec [OpenSpec 85](https://jo.et0.pw/Ting/Ting/wiki/OpenSpec-85-stable-identity-promotion-and-suppression) ## Structural parent [Ting/Ting#87](https://jo.et0.pw/Ting/Ting/issues/87) — Epic: definitive suppression.
Owner

Re-pointed by the 2026-09-04 restructure of the suppression path.

This record is now a Deliverable under Ting/Ting#87, not a Session. The Meta/Session label is removed.

Three freeholder rulings moved it here:

  • Q1 — the Campaign left Ting/Vedanta for the estate tracker (Ting/Ting#85). It spans three repositories and Vedanta holds the optional half.
  • Q2 — one OpenSpec package covers both promotion and suppression: OpenSpec 85. They share one identity spine and two packages would let the copies drift.
  • Q3 — the Session tier is dissolved. Every node here carried two or three tasks and no Specification Delta, so none of them was a coherent bundle of two or more Deliverables. A Session re-forms only where OpenSpec 85's derived leaves show one within a single repository.

What to build against: OpenSpec 85, not this body. This record predates the package and has not yet been refined to project it — that refinement is the next pass, and until it runs the body here is provenance rather than instruction.

**Re-pointed by the 2026-09-04 restructure of the suppression path.** This record is now a **Deliverable** under [Ting/Ting#87](https://jo.et0.pw/Ting/Ting/issues/87), not a Session. The `Meta/Session` label is removed. Three freeholder rulings moved it here: - **Q1** — the Campaign left `Ting/Vedanta` for the estate tracker ([Ting/Ting#85](https://jo.et0.pw/Ting/Ting/issues/85)). It spans three repositories and Vedanta holds the optional half. - **Q2** — one OpenSpec package covers both promotion and suppression: [OpenSpec 85](https://jo.et0.pw/Ting/Ting/wiki/OpenSpec-85-stable-identity-promotion-and-suppression). They share one identity spine and two packages would let the copies drift. - **Q3** — the Session tier is dissolved. Every node here carried two or three tasks and no Specification Delta, so none of them was a coherent bundle of two or more Deliverables. A Session re-forms only where OpenSpec 85's derived leaves show one **within a single repository**. **What to build against:** OpenSpec 85, not this body. This record predates the package and has not yet been refined to project it — that refinement is the next pass, and until it runs the body here is provenance rather than instruction.
Owner

Ruling 2026-09-04 — the tombstone is inert, so this record's scope narrows further.

The provider now renders a suppressed principal unable to authenticate (accountexpire to a past instant) and leaves its memberships in place, inert. See OpenSpec 85 v0.1.1.

What that means here: the identity itself is already dead by the time this endpoint runs. What remains is genuinely only credentials already in flight — workflow JWTs and API credentials minted before the suppression converged, which outlive the principal that was issued them until they are invalidated by exact token ID.

That is a smaller job than the body implies, and a sharper one: this endpoint closes a window measured in a credential's remaining lifetime, not a hole in the suppression itself. If it never ran, the suppression would still be terminal — those credentials would simply expire on their own schedule instead of immediately.

Worth stating in the record when it is next refined, because a reader could otherwise take this endpoint for the thing that makes suppression work, and widen it accordingly.

**Ruling 2026-09-04 — the tombstone is inert, so this record's scope narrows further.** The provider now renders a suppressed principal unable to authenticate (`accountexpire` to a past instant) and leaves its memberships in place, inert. See [OpenSpec 85](https://jo.et0.pw/Ting/Ting/wiki/OpenSpec-85-stable-identity-promotion-and-suppression) v0.1.1. **What that means here:** the identity itself is already dead by the time this endpoint runs. What remains is genuinely only credentials already in flight — workflow JWTs and API credentials minted before the suppression converged, which outlive the principal that was issued them until they are invalidated by exact token ID. That is a smaller job than the body implies, and a sharper one: this endpoint closes a window measured in a credential's remaining lifetime, not a hole in the suppression itself. If it never ran, the suppression would still be terminal — those credentials would simply expire on their own schedule instead of immediately. Worth stating in the record when it is next refined, because a reader could otherwise take this endpoint for the thing that makes suppression work, and widen it accordingly.
Owner

Refinement against OpenSpec 85 — the pass this record was told to wait for

Projecting the package, plus one correction the 2026-09-04 vocabulary ruling forces on this body.

The gate

Same answer as #18's Q1: the observed projection is what the directory shows, read by Vedanta at call time, not nixops4 state Vedanta cannot reach. The provider's outputs are themselves read back from kanidm, so the two are one fact.

What "terminal projection" means now, and it is not what this body says

The body says the provider performs "the terminal authority withdrawal and the UUID tombstone", and asks this endpoint to gate on that. There is no tombstone attribute. The rendering emits attributes.accountexpire and nothing else (flake-ops#473) — a second, estate-named marker would have carried this estate's vocabulary into a provider anyone may consume, and would have collided with kanidm's recycled, which names deleted entries.

So the gate cannot look for a marker. It must judge inertness itself: accountexpire parses as an instant, and that instant is in the past at the moment of the check.

That distinction is load-bearing rather than pedantic. An accountexpire set to 2099 is a converged declaration and a fully live principal. A gate that asked "is the attribute set" would admit it and invalidate the credentials of an identity the directory still authorizes — a suppression that looks complete from one side, which is the exact failure this record's own gating paragraph names.

What remains, restated from the 2026-09-04 ruling

Credentials already in flight, and nothing else. The identity is dead before this runs. If it never ran, the suppression would still be terminal — those credentials would expire on their own schedule instead of immediately.

The absence that is the proof

This path gets no delete, disable, retire, or rotate method — not a guarded one, none. Vedanta's law assigns all four to Jostoph, and a method that exists can be called by the next contributor who has a reason. Same discipline as checkout.rs's Occupancy, where the missing method is the assertion.

Delivery

Building with #18 in one PR, per Vedanta's one-PR-per-session law; the two share the gate.

## Refinement against OpenSpec 85 — the pass this record was told to wait for Projecting the package, plus one correction the 2026-09-04 vocabulary ruling forces on this body. ### The gate Same answer as [#18](https://jo.et0.pw/Ting/Vedanta/issues/18)'s Q1: the observed projection is **what the directory shows, read by Vedanta at call time**, not nixops4 state Vedanta cannot reach. The provider's outputs are themselves read back from kanidm, so the two are one fact. ### What "terminal projection" means now, and it is not what this body says The body says the provider performs "the terminal authority withdrawal and the UUID tombstone", and asks this endpoint to gate on that. **There is no tombstone attribute.** The rendering emits `attributes.accountexpire` and nothing else ([flake-ops#473](https://jo.et0.pw/lar.ad/flake-ops/pulls/473)) — a second, estate-named marker would have carried this estate's vocabulary into a provider anyone may consume, and would have collided with kanidm's `recycled`, which names *deleted* entries. So the gate cannot look for a marker. **It must judge inertness itself:** `accountexpire` parses as an instant, and that instant is in the past *at the moment of the check*. That distinction is load-bearing rather than pedantic. An `accountexpire` set to 2099 is a converged declaration and a fully live principal. A gate that asked "is the attribute set" would admit it and invalidate the credentials of an identity the directory still authorizes — a suppression that looks complete from one side, which is the exact failure this record's own gating paragraph names. ### What remains, restated from the 2026-09-04 ruling Credentials already in flight, and nothing else. The identity is dead before this runs. If it never ran, the suppression would still be terminal — those credentials would expire on their own schedule instead of immediately. ### The absence that is the proof This path gets **no** delete, disable, retire, or rotate method — not a guarded one, none. Vedanta's law assigns all four to Jostoph, and a method that exists can be called by the next contributor who has a reason. Same discipline as `checkout.rs`'s `Occupancy`, where the missing method *is* the assertion. ### Delivery Building with #18 in **one PR**, per Vedanta's one-PR-per-session law; the two share the gate.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Blocks Depends on
You do not have permission to read 1 dependency
Reference
Ting/Vedanta#19
No description provided.