Files
poimen/tasks/T3.6-version-pinning-at-spawn.md
T

97 lines
3.8 KiB
Markdown

# T3.6 — Version pinning at spawn
| Field | Value |
|---|---|
| Phase | P3 — Workflow as data |
| Size | S — under 1 day |
| Status | Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | — |
## Goal
`VersionSelector::Latest` resolves exactly once, at run spawn, and the resolved
hash is recorded.
## Facts (inlined — no spec read needed)
- `SubWorkflow` pins by `VersionSelector`: `Exact(hash)` or `Latest`.
- **`Latest` resolves once, at run spawn**, and the resolved hash is recorded. A
run whose sub-workflow version can change mid-execution is a run whose results
attribute to nothing.
- The same rule applies to the top-level workflow: the run records the exact
`WorkflowVersion` it executed, and a version published mid-run does not affect
it.
- Related failure the type system prevents: a placeholder that type-checks is
invisible. A hardcoded `"current"` version hash compiled, passed tests, and
made every result unattributable — which is why `WorkflowVersion` has no
`Default` (T0.1).
- Versions are never edited in place; a new version is a new hash with a parent
pointer.
## Steps
1. At spawn, resolve the top-level workflow and every reachable `SubWorkflow`
selector to concrete hashes.
2. Record the resolved set on the run's spawn event — the top-level version plus
a map of `(WorkflowId → WorkflowVersion)` for sub-workflows.
3. Make the interpreter (T3.5) read only from that recorded map. It must have no
path back to the registry's current `Latest`.
4. Resolve `Latest` against the registry once; if a new version is published
afterwards, nothing re-reads it.
5. Test: start a run, publish a new version of the same workflow mid-run, assert
the running run's recorded version is unchanged and it finishes on the pinned
one.
## Acceptance
- Publishing a new version mid-run does not change the running run's recorded
version.
## Phase gate
P3 closes when a user-authored YAML workflow runs with no framework recompile.
## Verify
**Harness:** a workflow registry that can publish a new version while a run is
in flight, plus a run long enough to straddle the publish (stub latency on a
mid-run step).
**Integration test**`tests/it_version_pinning.rs`:
1. Register workflow `W` v1, containing a `SubWorkflow` pinned `Latest`.
2. Spawn a run; park it mid-execution **before** the sub-workflow step.
3. Publish `W` v2 and a new version of the sub-workflow.
4. Release the run.
5. Assert the run's recorded top-level `WorkflowVersion` is v1.
6. Assert the sub-workflow executed is the version resolved **at spawn**, not the
newly published one — check the recorded resolution map, then check which
version actually ran (they must agree).
7. Assert the spawn event carries the full `(WorkflowId → WorkflowVersion)` map,
not a selector.
8. Registry-access audit: assert the interpreter makes **zero** registry lookups
after spawn — instrument the registry with a call counter.
**Command:** `cargo test -p executor version_pinning`
**False pass:**
- Parking the run **after** the sub-workflow step, so nothing could have
re-resolved. The park point must precede the step.
- Asserting only step 5. The top-level version is usually recorded correctly; the
lazy-resolution bug lives in the sub-workflow path. Steps 6 and 8 are the test.
- Step 6 checking the recorded map only. A map that is recorded and then ignored
passes — assert what actually ran.
## Traps
- Resolving `Latest` lazily at the point the sub-workflow is entered. It is the
natural implementation and it makes long runs non-attributable.
- Storing the selector rather than the resolved hash on the run record.
---
Background (not required to do this task):
[rust-agentic-sys.md](../../../rust-agentic-sys.md) §4.4, §12.2, §19 ·
[rust-agentic-task.md](../../../rust-agentic-task.md)