Skip to content

Freeze released workflows and read them with the current readers (#271) - #287

Merged
memtomem merged 4 commits into
mainfrom
test/frozen-workflows-271
Nov 9, 2026
Merged

memtomem merged 4 commits into
mainfrom
test/frozen-workflows-271

Conversation

@memtomem

@memtomem memtomem commented Nov 9, 2026

Copy link
Copy Markdown
Owner

Refs #271. This is the Workflows family, part 1 of 2. It follows the convention set by the artifacts (#274) and rooms (#279, #284) families. The issue stays open.

What

  • tests/fixtures/persisted/workflows/<release>/ holds workflows written by v0.2.0, the first release with workflows, and v0.3.0.
    • Each is its own project: the workflow database, the workflow directories (spec, planner output, node bundles, stream logs), and any workflow-origin run artifact.
    • freeze.py writes them with the release's own CLI against fake codex and kimi.
    • Workflow agents run with an environment allowlist, so the fakes read their behaviour from a file under HOME.
  • Scenarios (10 per release):
    • planned, completed
    • no-changes: gate off, empty final patch
    • gate-failed, failed, cancelled
    • running: the durable job's worker SIGKILLed during the final node
    • recovery-required: the release's own status after that kill
    • unapproved-completed: exits inside approve_workflow_run, after the completion commit
    • unapproved-running: exits right after the final artifact is published, before the completion commit
    • v0.3.0 only: created (workflow create --file) and revised
  • Hooks during generation. The two exits are hooks loaded into the release venv for the run only. A third hook pins the host name the release records in a lease owner to frozen-host, so no real machine name is frozen.
  • Correction: no release wrote a manifest v1 or v2; both normalize to v3 before the insert. The D-table row said otherwise. It is corrected and names the synthetic tests that cover those readers.
  • Rebinding: only the repository root, fingerprint (in its stored v1 version) and lease owner of each row, and the final artifact's root and fingerprint. Every other cell is asserted unchanged. spec_json, node bundles and commit.json are never touched, because digests cover their bytes.
  • Readers:
    • workflow list / show (--prompts) / status / logs / lineage / export / cancel / prune
    • WorkflowStore.events and read_node_bundle, including refusal of damaged bundles
    • a dead released lease is recovered by show and status, and a live owner keeps the lease despite a stale heartbeat (clock fixed)
    • for the final artifact: diff, runs show, apply (approved; after a prune; approval healed from the database; refused for a workflow that never completed; no_changes), and the standalone recover skip
  • docs/architecture/persisted-contracts.md: the Workflows row names this test and what is still missing.

No source under src/ changes. No reader misread a released workflow.

Part 2 (not here, listed in the D table):

  • a current run or resume of a released workflow, which needs a fixed clock because resume keeps the stored deadline
  • apply --verify environment narrowing
  • control-plan (toolgraph) workflows
  • recovery of an interrupted workflow-origin apply

Known gap: every frozen workflow recorded a cost of 0, so a reader that dropped total_cost_usd would pass. Covering it needs a fixture whose fake reports a cost.

Verification

  • suite-run CLEAN.
  • tests/test_frozen_workflows.py passes; the skips are v0.3.0-only scenarios for v0.2.0.
  • Thirteen injected reader regressions each fail an intended assertion. They include:
    • a bundle's commit version, patch digest, commit digest and database digest checks removed;
    • the dead-owner host check inverted, and the live-owner pid answer ignored;
    • the approval heal's state and run-id checks broken;
    • the spec digest encoding changed;
    • a legacy row's lineage, event payloads, usage and runs show output dropped.
  • Two pitfalls were fixed in the mutation runs themselves:
    • a same-size swap left a stale .pyc in use after the restore, so runs disable bytecode;
    • a lease decision depended on the fixture's age, so the clock is fixed per test.
  • Codex reviewed the design in two rounds, then the implementation:
    • Round 1: NEEDS-FIX with 3 Major (bundle damage, runs show and usage unchecked), fixed in the second commit.
    • Round 2: SHIP. Its optional database-digest case is the third commit.

🤖 Generated with Claude Code

memtomem and others added 4 commits November 9, 2026 19:41
The Workflows family of the persisted-contracts D rows, part 1 of 2.

tests/fixtures/persisted/workflows/<release>/ holds ten workflows each from
v0.2.0 (the first release with workflows) and v0.3.0, plus v0.3.0's
document-created and revised plans. Each is its own project: the workflow
database, the workflow directories (spec, planner output, node bundles,
stream logs) and any workflow-origin run artifact. freeze.py writes them
with the release's own CLI against fake codex and kimi that take their
behaviour from a file under HOME (workflow agents run with an environment
allowlist):
- planned, completed, no-changes (gate off, empty final patch),
  gate-failed, failed, cancelled;
- running (the durable job's worker SIGKILLed during the final node),
  recovery-required (the release's own status after that);
- unapproved-completed (exit inside approve_workflow_run, after the
  completion commit) and unapproved-running (exit right after the final
  artifact is published, before it).
The two exits, and a fixed host name so no real machine name is frozen in a
lease owner, are hooks loaded into the release venv for the run only.

No release wrote a v1 or v2 manifest: both normalize to v3 before the
insert. The doc row said otherwise; it is corrected and names the synthetic
tests that cover those readers.

The test rebinds only the repository root, fingerprint and lease owner of
each row and the final artifact's root and fingerprint, and asserts every
other cell is unchanged. It reads the workflows through workflow list,
show (--prompts), status, logs, lineage, export, cancel and prune, the
events and node-bundle readers, recovery of a dead released lease (and a
live owner kept with a stale heartbeat, clock fixed), and the final
artifact through diff, runs show, apply (approved, after a prune, approval
healed, refused for a workflow that never completed, no_changes) and the
recover skip. Eight injected reader regressions each fail an intended
assertion.

Part 2 will cover a current run or resume of a released workflow, apply
--verify environment narrowing and control-plan workflows.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…#271)

Review round 1 found checks that passed with the guarded code removed:
- bundle verification: damaged patches, results and commit records
  (version and digest) are refused, and a patch re-sealed in commit.json
  and the database still fails result.json's own patch digest;
- runs show: its JSON is compared with the released manifest;
- usage: the show line and the loaded record match the released manifest.

Twelve injected regressions now each fail an intended assertion.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Review round 2: only the database record of the bundle digest is changed,
the bundle files are intact, and read_node_bundle must still refuse it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
An f-string without placeholders and a one-letter loop name failed the
lint job.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@memtomem
memtomem merged commit 02d13b8 into main Nov 9, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant