Launch grants, version 2.0
SV2-IDN-04 binds a worker’s identity to authority the controller issued, so an agent cannot claim another project, task, account or brain by changing request parameters. The interface is scripts.swarm_v2.auth_context.LaunchAuthority; the schema is urn:swarm-v2:launch-grant.
Issue
issue(slug, execution_id, project_ids=..., brain_id=..., account=...) returns a signed token for an execution admitted through RedisStore.start_execution (SV2-IDN-02).
- The subject comes from admitted state, never from the caller:
execution_id,generation,seat_idandtask_idare read from the execution registry. An execution that is no longer the current attempt of its seat is refused (stale_generation). - The controller selects the corpus: a non-empty list of canonical project IDs (SV2-IDN-01, never
unknown), onebrain_idand oneaccount, each validated before anything is written (invalid_request). The grant carries the projects sorted and unique. - The grant carries
issuer,audienceandkey_id, issued and expiry times, and a randomgrant_id. Lifetime defaults to 300 seconds and is at most 900. - The token is
v2.<claims>.<HMAC-SHA256>, with canonical JSON claims and unpadded base64url. The signing key is aLaunchKeyof at least 32 bytes held by the controller; workers never verify grants. - Each issue records a nonsecret audit row (
launch-grants): grant, issuer, audience, key ID, execution, generation, times and stateissued. Neither the token nor the key is stored. - An execution whose task or seat falls outside the identifier grammar gets no grant (
invalid_request). - The audit row is written in one watched transaction with the disable switch and the execution history: a grant is refused while grants are disabled (
forbidden_scope) or once its execution is no longer the current attempt (stale_generation), even when either changes during the issue.
Register
register(slug, token, body) validates the grant before trusting any worker metadata, in this order:
| Check | Error class |
|---|---|
| Token shape, signature, claim set and claim types | unauthenticated |
Schema version other than 2.0 | invalid_request |
| Signing key ID, issuer, audience | unauthenticated |
| Not yet valid, or at or past expiry | unauthenticated |
| Grant for another swarm | forbidden_scope |
Body not an object, names a field outside the bound set, or omits execution_id or generation | invalid_request |
A body field (swarm_id, execution_id, generation, seat_id, task_id, account, brain_id, project_ids) whose type or value differs from the grant; project_ids must match the grant’s sorted list | forbidden_scope |
| Grant not issued by this controller, or revoked | unauthenticated |
| Grant execution no longer the seat’s current attempt | stale_generation |
| Execution already registered under another grant | forbidden_scope |
| Grants disabled for the swarm | forbidden_scope |
Every refusal happens before any registry write. A GrantRefused carries error_class, the operation_id of the request (register(..., operation_id)), echoed only when it is an identifier and unknown otherwise, as the shared contract refusals do; issue and disable are controller calls and report unknown. It also carries a retry class: new_request for every refusal, since correcting the input takes a new valid request and never an implicit fallback, and same_request for dependency_unavailable, raised when a watched transaction kept conflicting and nothing was written; for issue the retry is a fresh issue. detail() is the sanitized form; it never contains the token, the key or another caller’s resource. launch_grant_rejections(store, slug) counts refusals per error class, and launch_grant_rejections_total sums them.
The last four checks and the write run in one watched transaction over the execution history, the registrations, the grant audit rows and the disable switch. The transaction writes the registration (launch-registrations) and moves the grant’s audit row to registered; it never writes execution records. A retry with the same valid grant, from the same or a restarted controller, returns the stored Registration unchanged and writes nothing. Losing the transport before the commit leaves nothing written; losing it after the commit leaves the registration, and the retry returns it. A stale attempt cannot replace a newer generation’s registration. Registration.session_grant() returns the SV2-IDN-03 SessionGrant for the registered corpus.
Rollback
disable(slug) sets the swarm’s disable switch and, in the same transaction, moves every grant still issued to revoked, returning their IDs. While disabled, issue and every new registration are refused; a retry of an existing registration still returns it. Registrations, audit rows and execution records are authoritative history: this module never deletes them and never writes execution records. enable(slug) clears the switch. The preceding local launch path does not call this module, so it is unaffected either way.
Limitations
- No live launch path issues or registers grants yet. The registration endpoint and the distributed launch that carries the token to a worker belong to the runtime packages; until they land, live hooks still pass no grant to SV2-IDN-03 attribution.
- Key provisioning and rotation are not part of this package. A verifier knows one key ID; a rotation needs a key set.
- Registrations and audit rows have no retention sweep.