From cbcd27b4a8788ce194b62d6532009c4ce124ab82 Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:02:03 +0100 Subject: [PATCH] Govern deploy enrollment durability Record the bootstrap-to-runtime credential replacement as one durable transition across API, agent lifecycle, security, and storage contracts. Document rollback, replay, and no-secret-on-failure guarantees proved by the deploy handler suite.\n\nChange-source: pulse-maintainer --- .../v6/internal/subsystems/agent-lifecycle.md | 19 +++++++++++++++++++ .../v6/internal/subsystems/api-contracts.md | 18 ++++++++++++++++++ .../internal/subsystems/security-privacy.md | 12 ++++++++++++ .../internal/subsystems/storage-recovery.md | 13 +++++++++++++ 4 files changed, 62 insertions(+) diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index c90f97b0c..80c56fa1b 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -6674,3 +6674,22 @@ or re-enrollment authority. Its liveness marker observes only whether the monitor select loop is progressing; agent report success or failure cannot independently assert that Pulse is healthy. Agent-lifecycle behavior and proof routes remain unchanged. + +### Deploy enrollment swaps credentials as one durable transition + +A deploy bootstrap token remains the live credential until Pulse can durably +replace it with the long-lived, host-bound runtime token. Enrollment prepares +the runtime credential first, then removes the single-use bootstrap token and +adds the runtime token under one configuration lock and one persistence write. +Concurrent reuse loses the locked removal and returns `409`; persistence +failure restores the complete prior token inventory and primary-token +projection, returns `500`, discloses no runtime credential, and leaves the +target enrolling so the same bootstrap credential can be retried safely. + +Bootstrap minting follows the same commit boundary: a token is returned only +after its updated inventory is durably stored, and a failed write restores the +prior live inventory. `TestHandleEnroll_Success`, +`TestHandleEnroll_RollsBackBootstrapConsumptionWhenPersistenceFails`, and +`TestMintBootstrapTokenForTarget_RollsBackWhenPersistenceFails` in +`internal/api/deploy_handlers_test.go` pin the durable replacement and rollback +paths. diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index 9172f3c70..e4e54a2e9 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -9965,3 +9965,21 @@ failure count, sanitized error, and last interruption. It never returns the URL or endpoint fingerprint. A saved configuration that cannot be decrypted is `configuration_unavailable`, while explicit removal is immediately `disabled` even if cancellation of an older request is still unwinding. + +### Deploy enrollment reports only committed credentials + +`POST /api/agents/agent/enroll` returns a long-lived runtime token only after +the authenticated single-use bootstrap token has been replaced in the durable +API-token inventory. The replacement is one locked persistence transition; +another request that already consumed the bootstrap token receives the stable +`409 token_already_consumed` response. If persistence fails, the API restores +the complete prior inventory and legacy primary-token projection, returns +`500 token_persistence_error`, emits no runtime token, and does not advance the +deployment target from `ENROLLING` to `VERIFYING`. + +The internal bootstrap-mint boundary likewise returns no raw token or token ID +when persistence fails. `TestHandleEnroll_Success`, +`TestHandleEnroll_RollsBackBootstrapConsumptionWhenPersistenceFails`, and +`TestMintBootstrapTokenForTarget_RollsBackWhenPersistenceFails` in +`internal/api/deploy_handlers_test.go` prove the persisted success state and +both failed-commit responses. diff --git a/docs/release-control/v6/internal/subsystems/security-privacy.md b/docs/release-control/v6/internal/subsystems/security-privacy.md index 19a1111a8..cb7fa0bc8 100644 --- a/docs/release-control/v6/internal/subsystems/security-privacy.md +++ b/docs/release-control/v6/internal/subsystems/security-privacy.md @@ -2356,3 +2356,15 @@ tokens and primary-token projection, emits only a failed `token_deleted` audit event, and returns an error; a successful response identifies a deletion that will survive restart. Exact multi-token removal and persistence-failure rollback are exercised in `internal/api/security_tokens_lifecycle_test.go`. + +### Deploy enrollment never exposes an uncommitted credential + +The deploy bootstrap secret is single-use only at the durable API-token commit +boundary. Pulse generates the host-bound runtime credential without admitting +or returning it, then replaces the bootstrap record under one lock and persists +the resulting inventory once. A concurrent replay cannot pass the locked +removal. A failed persistence write restores the complete prior inventory and +primary-token projection, returns an error, and discloses no runtime secret; +failed bootstrap minting likewise returns no credential material. The success, +replay, and forced-write-failure paths are exercised in +`internal/api/deploy_handlers_test.go`. diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index 8296dbd55..6fbe25857 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -5642,3 +5642,16 @@ whose atomic/fsync discipline ensures a later process can report a Pulse monitoring gap; it contains endpoint fingerprint and timing only. Configuration export/import carries the watchdog URL solely inside the existing passphrase-encrypted bundle and advances that bundle contract to version 4.4. + +### Deploy credential replacement preserves restart-time truth + +The shared deploy enrollment path treats the persisted API-token inventory as +the commit boundary when exchanging a bootstrap token for a runtime token. One +locked write records the replacement; a failed write restores the complete +prior inventory and primary-token projection, returns no new credential, and +leaves the deployment target retryable. Bootstrap minting also restores live +state and returns no token if its inventory write fails. These transitions add +no backup, restore, retention, snapshot, or recovery authority; they prevent a +successful enrollment response from describing credential state that a restart +would undo. Their success and forced-write-failure proofs live in +`internal/api/deploy_handlers_test.go`.