feat(release): persist resumable run state
This commit is contained in:
@@ -18,6 +18,7 @@ candidate/publish actions:
|
||||
- configure target versions per release unit in the web UI
|
||||
- select the repositories that should advance through checkboxes
|
||||
- generate dry-run selective release plans for independently versioned packages
|
||||
- freeze a selective plan as a durable, resumable local release run
|
||||
- create annotated source release tags for selected clean, version-aligned
|
||||
repositories and publish each branch/tag pair atomically
|
||||
- generate signed catalog candidates for the selected release units
|
||||
@@ -49,6 +50,70 @@ tag. `source_preflight_ready` means that the plan-visible source gates pass; the
|
||||
non-mutating `Preview Tag + Publish` remains mandatory for remote, manifest, and
|
||||
immutable-tag checks.
|
||||
|
||||
## Durable release runs
|
||||
|
||||
The **Durable Run State** card turns the current repository/version selection
|
||||
into a versioned local run record. The server rebuilds the selective plan and
|
||||
requires the plan to resolve exactly the requested repositories and target
|
||||
versions; the browser cannot submit or replace the plan snapshot. The input and
|
||||
plan are then immutable and covered by a canonical SHA-256 integrity digest.
|
||||
The complete record also has a checksum so a valid-looking manual edit to its
|
||||
mutable state fails closed. File permissions remain the authority boundary;
|
||||
these digests detect accidental or manual corruption, not an attacker who can
|
||||
replace the private record and recompute its checksums. Changing a target,
|
||||
channel, or gate input requires a new run.
|
||||
|
||||
Run records live below `runtime/release-runs/`. They survive console restarts,
|
||||
but remain local operator state rather than a signed release artifact or the
|
||||
system audit log. The directory is restricted to mode `0700` and records to
|
||||
`0600`. Writes use a cross-process lock, a same-directory temporary file,
|
||||
`fsync`, atomic replacement, and directory `fsync`. Symbolic-link storage paths,
|
||||
overly broad record modes, malformed schemas, unknown fields, invalid state
|
||||
combinations, oversized files, and digest mismatches fail closed. A bad record
|
||||
is neither rewritten nor automatically quarantined.
|
||||
|
||||
The default store belongs to the selected workspace: the console uses that
|
||||
workspace's meta checkout when it is available, or a stable workspace-digest
|
||||
namespace below this checkout's runtime directory otherwise. The same digest is
|
||||
part of the immutable input snapshot. An alternate workspace therefore cannot
|
||||
silently list or resume another workspace's runs. `run_state_root` remains an
|
||||
explicit embedding/test override.
|
||||
|
||||
Each frozen plan step has an explicit `pending`, `running`, `succeeded`,
|
||||
`failed`, or `interrupted` state. Plan order remains a prerequisite: a later
|
||||
step is unavailable until earlier steps have succeeded. Exact attempt and
|
||||
resume/retry request identifiers are fingerprinted so repeated state commands
|
||||
are idempotent. The record keeps at most 256 server-generated state events and
|
||||
128 request fingerprints. Events contain only an enum event type, timestamp,
|
||||
step identifier, and bounded result code—never commands, process output,
|
||||
confirmation text, credentials, bearer tokens, or signing material.
|
||||
|
||||
An explicit resume after a process restart converts every persisted `running`
|
||||
step to `interrupted`; it never guesses whether an external effect happened.
|
||||
An interrupted read-only step can be prepared for retry. An interrupted
|
||||
mutating step remains unavailable until the operator has reconciled local and
|
||||
remote state and explicitly records that reconciliation when preparing the
|
||||
retry. A known failed attempt can likewise be prepared for retry. The UI keeps
|
||||
the retry control visible and explains why it is disabled when retry is unsafe.
|
||||
|
||||
The run API is covered by the same local console token middleware as every
|
||||
other `/api/` route:
|
||||
|
||||
- `POST /api/release-runs` rebuilds and freezes a selective plan.
|
||||
- `GET /api/release-runs` lists bounded summaries; unreadable entries are
|
||||
reported as unavailable instead of being silently omitted.
|
||||
- `GET /api/release-runs/{run_id}` reads and verifies one exact record.
|
||||
- `POST /api/release-runs/{run_id}/resume` records explicit recovery.
|
||||
- `POST /api/release-runs/{run_id}/steps/{step_id}/retry` prepares a failed or
|
||||
safely reconciled interrupted step for another attempt.
|
||||
|
||||
This foundation tracks state only. It deliberately does not run a plan step or
|
||||
infer success from a button click. Existing preview and executor controls stay
|
||||
separate and continue to require `COMMIT`, `TAG`, `PUBLISH`, `APPLY`, or `PUSH`
|
||||
at their existing narrow boundaries. Wiring confirmed executors to claim and
|
||||
finish run steps, plus package/install verification, remains follow-up work;
|
||||
until then the console must not present a run as execution evidence.
|
||||
|
||||
Dashboard collection is also fail closed. Unreadable Core version metadata or a
|
||||
malformed module contract is returned as a bounded `collection_errors` entry
|
||||
with a remediation, marks the dashboard blocked, and becomes a structured
|
||||
|
||||
Reference in New Issue
Block a user