Session: pool checkout and return — the steady-state slot cycle #31

Closed
opened 2026-09-03 18:15:16 +00:00 by larandar · 1 comment
Owner

Proposal

The steady-state loop over an already-provisioned pool: allocate a slot to a session, and return it afterwards carrying nothing forward.

Session review boundary

One PR, one verdict over the cycle: allocation, the assignment stamp, ordinary completion, and the guarantee that a returned slot inherits no prior authority. Bring-up is #30 — a one-time act with a different reviewer question and a different blast radius.

Design

Checkout binds a slot to a session under a new immutable grant. It inherits no prior lease, token, or authority. It stamps the occupancy onto the slot's OpenBao entity alias custom_metadata, which is what makes the swarm KV path per-assignment rather than per-slot — R4's requirement, whose consumer is already written in flake-ops#471. That path shape is this Session's acceptance for the stamp.

custom_metadata, not a claim, and the reason is structural rather than stylistic: a kanidm claim map is valuesByGroup, so a claim carries only what the directory asserts about an account, and occupancy has no group to hang on. Alias metadata is rewritten from claim mappings on every login; custom_metadata is written out of band and a login does not touch it, which is the lifetime this value needs.

Return is ordinary completion: revoke the session capability, rotate the checkout credential, advance the generation, and return the same worker-<UUID> slot to the pool. The principal survives — deletion, suppression, tombstoning and recreation are reserved for compromise or irrecoverable retirement, and are not this Session's.

A slot is freed by an explicit release and by nothing else. Per the freeholder direction on Ting/Ting#2 Q2 (2026-09-04), the liveness mechanism stays undefined until incidents show what evidence is actually available. Rather than leave a gap, the cycle takes the conservative shape: nothing infers liveness, so no timeout, staleness rule, or unreachable runtime frees a slot. #37 owns that rule and is specifiable today — the deferral costs no acceptance.

The chosen failure is pool exhaustion: a drone that dies without releasing holds its slot, and ten such deaths stop the swarm. Loud, bounded, recoverable, and it produces the incident record a real liveness mechanism will be designed against.

Do not reach for the contract's assignment_id for the stamp. That names the queen's tenure assignment that authorized a request — one value shared by every drone under a queen. It cannot separate two occupants of one slot, which is the stamp's entire job, and a path built on it looks isolated and is not.

Tasks

  • Bind a slot to a session under a new immutable grant inheriting nothing.
  • Stamp occupancy onto the slot's alias custom_metadata, against flake-ops#471's path shape.
  • Ordinary completion: revoke, rotate, advance generation, return the UUID.
  • Prove a returned slot's next session inherits no prior lease, token, or authority.
  • Prove the principal survives return, and that tombstoning is not on this path.
  • Free a slot only on explicit release; prove no elapsed time or failed check frees one.

Specification Delta

Requirement: a returned slot carries nothing forward

Scenario: a session completes and the slot is reused

  • GIVEN a slot returned by ordinary completion
  • WHEN it is allocated to a later session
  • THEN that session holds a new immutable grant
  • AND no lease, token, or authority from the previous session is valid

Requirement: occupancy is what isolates, not the slot

Scenario: two successive occupants of one slot

  • GIVEN a slot allocated, returned, and allocated again
  • WHEN the second occupant reads its workspace and report paths
  • THEN it cannot reach the first occupant's workspace or reports

Requirement: the principal survives ordinary completion

Scenario: a session ends normally

  • GIVEN a drone session completing without compromise
  • WHEN return runs
  • THEN the session capability, leases, and resource tokens are revoked
  • AND the durable principal is neither deleted, suppressed, nor tombstoned

Requirement: only an explicit release frees a slot

Scenario: a holder disappears without releasing

  • GIVEN a slot whose holder stopped without an explicit release
  • WHEN allocation is attempted, at any later time
  • THEN the slot is not reissued

OpenSpec

OpenSpec 30 — agent identity pool lifecycle, which projects from ratified OpenSpec 61 v0.1.4, whose amendment 0.1.2 is the return path. This Session is its steady-state half; #30 is the other.

Structural parent

Ting/Ting#2 — Epic: agent identity pool lifecycle. Session split ratified there as Q3 (2026-09-03).

Absorbs

#9 — R4's per-assignment isolation. The stamp has no caller until checkout exists, so it is a requirement of this Session rather than a Deliverable of its own.

## Proposal The steady-state loop over an already-provisioned pool: allocate a slot to a session, and return it afterwards carrying nothing forward. ## Session review boundary One PR, one verdict over the cycle: allocation, the assignment stamp, ordinary completion, and the guarantee that a returned slot inherits no prior authority. Bring-up is [#30](https://jo.et0.pw/Ting/Vedanta/issues/30) — a one-time act with a different reviewer question and a different blast radius. ## Design **Checkout** binds a slot to a session under a **new** immutable grant. It inherits no prior lease, token, or authority. It stamps the occupancy onto the slot's OpenBao entity alias `custom_metadata`, which is what makes the swarm KV path per-assignment rather than per-slot — R4's requirement, whose consumer is already written in [flake-ops#471](https://jo.et0.pw/lar.ad/flake-ops/pulls/471). That path shape is this Session's acceptance for the stamp. **`custom_metadata`, not a claim**, and the reason is structural rather than stylistic: a kanidm claim map is `valuesByGroup`, so a claim carries only what the directory asserts about an account, and occupancy has no group to hang on. Alias `metadata` is rewritten from claim mappings on every login; `custom_metadata` is written out of band and a login does not touch it, which is the lifetime this value needs. **Return** is ordinary completion: revoke the session capability, rotate the checkout credential, advance the generation, and return the same `worker-<UUID>` slot to the pool. The principal survives — deletion, suppression, tombstoning and recreation are reserved for compromise or irrecoverable retirement, and are not this Session's. **A slot is freed by an explicit release and by nothing else.** Per the freeholder direction on [Ting/Ting#2](https://jo.et0.pw/Ting/Ting/issues/2#issuecomment-15474) Q2 (2026-09-04), the liveness mechanism stays undefined until incidents show what evidence is actually available. Rather than leave a gap, the cycle takes the conservative shape: nothing infers liveness, so no timeout, staleness rule, or unreachable runtime frees a slot. [#37](https://jo.et0.pw/Ting/Vedanta/issues/37) owns that rule and is specifiable today — the deferral costs no acceptance. The chosen failure is pool exhaustion: a drone that dies without releasing holds its slot, and ten such deaths stop the swarm. Loud, bounded, recoverable, and it produces the incident record a real liveness mechanism will be designed against. **Do not reach for the contract's `assignment_id` for the stamp.** That names the queen's tenure assignment that authorized a request — one value shared by every drone under a queen. It cannot separate two occupants of one slot, which is the stamp's entire job, and a path built on it looks isolated and is not. ## Tasks - [ ] Bind a slot to a session under a new immutable grant inheriting nothing. - [ ] Stamp occupancy onto the slot's alias `custom_metadata`, against flake-ops#471's path shape. - [ ] Ordinary completion: revoke, rotate, advance generation, return the UUID. - [ ] Prove a returned slot's next session inherits no prior lease, token, or authority. - [ ] Prove the principal survives return, and that tombstoning is not on this path. - [ ] Free a slot only on explicit release; prove no elapsed time or failed check frees one. ## Specification Delta ### Requirement: a returned slot carries nothing forward #### Scenario: a session completes and the slot is reused - **GIVEN** a slot returned by ordinary completion - **WHEN** it is allocated to a later session - **THEN** that session holds a new immutable grant - **AND** no lease, token, or authority from the previous session is valid ### Requirement: occupancy is what isolates, not the slot #### Scenario: two successive occupants of one slot - **GIVEN** a slot allocated, returned, and allocated again - **WHEN** the second occupant reads its workspace and report paths - **THEN** it cannot reach the first occupant's workspace or reports ### Requirement: the principal survives ordinary completion #### Scenario: a session ends normally - **GIVEN** a drone session completing without compromise - **WHEN** return runs - **THEN** the session capability, leases, and resource tokens are revoked - **AND** the durable principal is neither deleted, suppressed, nor tombstoned ### Requirement: only an explicit release frees a slot #### Scenario: a holder disappears without releasing - **GIVEN** a slot whose holder stopped without an explicit release - **WHEN** allocation is attempted, at any later time - **THEN** the slot is not reissued ## OpenSpec [OpenSpec 30 — agent identity pool lifecycle](https://jo.et0.pw/Ting/Vedanta/wiki/OpenSpec-30-agent-identity-pool-lifecycle), which projects from ratified [OpenSpec 61](https://jo.et0.pw/Ting/Ting/wiki/OpenSpec-61-forge-identity-lifecycle-contract) v0.1.4, whose amendment 0.1.2 is the return path. This Session is its steady-state half; [#30](https://jo.et0.pw/Ting/Vedanta/issues/30) is the other. ## Structural parent [Ting/Ting#2](https://jo.et0.pw/Ting/Ting/issues/2) — Epic: agent identity pool lifecycle. Session split ratified there as Q3 (2026-09-03). ## Absorbs [#9](https://jo.et0.pw/Ting/Vedanta/issues/9) — R4's per-assignment isolation. The stamp has no caller until checkout exists, so it is a requirement of this Session rather than a Deliverable of its own.
Author
Owner

Delivered by PR #39, merged at 7b9b5ba. All three leaves closed: #35, #36, #37.

src/checkout.rs carries binding under a new grant with the custom_metadata occupancy stamp, ordinary completion that preserves the worker, and the explicit-release rule. 96 lib tests, gate green.

Two guarantees are structural rather than asserted, which is worth knowing before anyone edits the module:

  • The principal survives because Occupancy has no delete, suppress, or tombstone method. Adding one would silently remove the proof.
  • No elapsed time frees a slot because the type reads no clock. Introducing one re-opens the question #37 closed.

One gap this Session did not cover, now filed as #40. Cycle::new starts every slot available — correct for a pool that has never run, wrong for every restart after the first. A process that restarts while drones hold slots comes back believing the pool is empty. That is #37's failure arriving through a door #37 does not watch: the safety rule is stated per-transition, and the gap is in the initial state.

Not a scope miss by this Session — OpenSpec 30 does not specify restart recovery at all, so the package wants an amendment alongside #40. src/checkout.rs must not run against a real pool until #40 lands.

Delivered by [PR #39](https://jo.et0.pw/Ting/Vedanta/pulls/39), merged at `7b9b5ba`. All three leaves closed: [#35](https://jo.et0.pw/Ting/Vedanta/issues/35), [#36](https://jo.et0.pw/Ting/Vedanta/issues/36), [#37](https://jo.et0.pw/Ting/Vedanta/issues/37). `src/checkout.rs` carries binding under a new grant with the `custom_metadata` occupancy stamp, ordinary completion that preserves the worker, and the explicit-release rule. 96 lib tests, gate green. Two guarantees are structural rather than asserted, which is worth knowing before anyone edits the module: - **The principal survives** because `Occupancy` has no delete, suppress, or tombstone method. Adding one would silently remove the proof. - **No elapsed time frees a slot** because the type reads no clock. Introducing one re-opens the question #37 closed. **One gap this Session did not cover, now filed as [#40](https://jo.et0.pw/Ting/Vedanta/issues/40).** `Cycle::new` starts every slot available — correct for a pool that has never run, wrong for every restart after the first. A process that restarts while drones hold slots comes back believing the pool is empty. That is #37's failure arriving through a door #37 does not watch: the safety rule is stated per-transition, and the gap is in the initial state. Not a scope miss by this Session — [OpenSpec 30](https://jo.et0.pw/Ting/Vedanta/wiki/OpenSpec-30-agent-identity-pool-lifecycle) does not specify restart recovery at all, so the package wants an amendment alongside #40. **`src/checkout.rs` must not run against a real pool until #40 lands.**
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.

Reference
Ting/Vedanta#31
No description provided.