Repository navigation
[IMPL] T3 — Fence artifact writes and reads by ExecutionGeneration #21
Description
Parent: #17 · Depends on #20 · Baseline
1beffac68efd2b5bf3599911cb2500a1a4f2f186(T2 / PR #26 merged).
0. Traceability / dependency
- Task: T3 / 6.
- Blocked by: none — #20 completed via PR #26.
- Blocks: #22 and therefore [IMPL] T5 — Demote retry cleanup to generation-scoped retention/GC #23–[IMPL] T6 — Enforce coordinated generation-aware cutover and writer capability gate #24.
- Serialization rule: complete this before T4 because both touch the authoritative write set.
1. Required contract
Add explicit execution_generation to authoritative artifacts and make every authoritative Create consume the active-owner predicate established by T2.
Minimum scope:
- AgentRun;
- Evidence;
- Claim;
- Vote;
- ToolCall;
- DebateRound;
- Reflection lineage/generation;
- generation-aware checkpoint treatment.
Artifact IDs move from -a<ExecutionAttempt>- to -g<ExecutionGeneration>-. The generation column is authoritative; ID text is diagnostic/stable identity only.
A write must atomically prove the supplied generation is the Case's current active RUNNING owner before the row becomes authoritative. A Go-side preflight Get + INSERT is invalid.
2. Read semantics
Split broad Case reads into explicit semantics:
- current/authoritative: requires generation and returns only that generation;
- history/audit: may return many generations and exposes them as current/superseded.
Consensus, report generation, terminal reference validation and current API projections may not consume unfiltered cross-generation rows.
Checkpoint RunID may remain logical/stable only if the stored checkpoint also carries generation and a superseded writer cannot overwrite the active generation's checkpoint.
3. #11 integration
Extend terminal/reference semantics so same-Case is not enough: a Resolution at generation G may cite only Vote/Evidence/Claim rows from generation G.
Legacy generation-0 rows are history and cannot satisfy a positive-generation terminal commit without an explicit validated import.
4. RED → GREEN
Mandatory real MySQL gated:
- late artifact writer gen1 resumes after gen2 claim → zero authoritative insert / ownership error;
- same-Case cross-generation Vote/Evidence/Claim reference → terminal verification refuses it;
- authoritative Create ownership check and INSERT are one transaction/linearization boundary.
Deterministic in-memory/unit first, plus MySQL integration:
- current reads return gen2 while gen1 rows remain;
- no-cleanup retry succeeds without mixed-generation consensus/report inputs;
- ID/remap tests prove
-g<generation>-namespace; - history reads preserve legacy generation 0 and expose generation metadata;
- superseded checkpoint cannot be restored as active authority.
5. Non-goals
No status/terminal generation CAS (T4), no GC behavior change (T5), no rollout gate (T6), no Egress/Audit Witness redesign.
Start status
- T2 durable owner predicate merged to
mainas1beffac68efd2b5bf3599911cb2500a1a4f2f186. - T3 implementation and RED→GREEN verification completed in PR #27.
- T4/T5/T6 remained untouched during T3; #22 stays blocked until PR feat(runtime): generation-fence authoritative artifacts #27 merges.
T3 implementation refinement
Read-only review against main 1beffac68efd2b5bf3599911cb2500a1a4f2f186 freezes these implementation details before RED→GREEN:
Explicit owner propagation
T2's durable ownership predicate requires more than generation equality. Introduce an explicit runtime-only ExecutionOwner value containing at least JobID, CaseID, WorkerID, ExecutionGeneration. RunManager constructs it from the active Claim and passes it explicitly through the Orchestrator call path. Do not hide it in context.Value, and do not persist worker/job identity on DecisionCase.
For a positive generation, authoritative artifact writes require a complete owner and must fail closed when it is absent or stale. Generation 0 remains legacy/non-durable compatibility only.
Artifact schema
Add execution_generation BIGINT NOT NULL DEFAULT 0 to AgentRun, Evidence, Claim, Vote, DebateRound and ToolCall. ToolCall must also persist case_id directly rather than relying only on AgentRun joins. Reflection must have unambiguous Case/generation provenance directly (preferred: case_id + execution_generation) rather than inheriting ownership only through a mutable/filtered join.
Add generation to the corresponding domain entities so history/audit callers can observe provenance.
Write linearization
Every authoritative Create must verify the full T2 owner predicate and insert the row inside one DB transaction. A preflight Case/Job read followed by an independent INSERT is forbidden. The common repository helper should enforce:
job_id + case_id + RUNNING + worker_id + execution_generation + unexpired lease
and matching decision_case.execution_generation.
Read split
Do not silently redefine ListByCase. Introduce explicit generation-scoped authoritative reads (for example ListByCaseGeneration) and retain clearly named history/all-generation reads for UI/audit. Decision-making, terminal reference validation and current projections must use the generation-scoped path.
ID namespace
Replace persisted artifact execution IDs from -a<ExecutionAttempt>- to -g<ExecutionGeneration>-. ExecutionAttempt remains compatibility/metrics only. ID strings are never parsed for ownership proof.
Checkpoint
Keep the logical checkpoint RunID stable, but change durable checkpoint identity to (run_id, execution_generation). Save/Load must explicitly carry generation. A checkpoint from generation N must never be returned to generation N+1 implicitly. Cross-generation import/handoff, if ever required, must be an explicit operation; ordinary resume does not do it.
#11 terminal reference integration
The existing comment/assumption that an attempt embedded in the ID scopes terminal references is obsolete. Both atomic and fallback verification must require case_id + execution_generation for Vote/Evidence/Claim. Resolution itself needs the committing generation available to verification, while the general status/terminal generation CAS remains T4.
T3/T5 boundary
CleanupCaseArtifacts may remain physically case-wide until T5 only if T3 correctness no longer depends on cleanup and no T3 read/write path can mix generations. T3 must add a no-cleanup retry proof. Do not redesign GC in this PR.
Owner value-object placement
The existing runtime.ExecutionContext is the single execution-owner value to reuse, but repository ports cannot import domain/runtime because runtime already depends on port. Therefore T3 will move the value-object definition to the bottom-layer domain/entity package and keep runtime.ExecutionContext as a type alias for compatibility. This is not a second owner identity.
The owner fields are frozen as CaseID, JobID, WorkerID, JobAttempt, ExecutionGeneration plus logical RunID where runtime identity needs it. Repository authorization ignores RunID/JobAttempt and consumes the T2 predicate fields only.
Implementation status
- Implementation: PR #27
- Final head:
cb014244cd926762702a22576006b7b5d30d937a - History reduced to 4 logical commits; final tree is byte-identical to the previously validated content tree.
- Exact-head CI run 37589099246 completed success.
- Backend: formatting +
go test -race ./...+go vet ./...passed. - Real MySQL 8.4: S27 migration + stale writer + current/history read isolation + checkpoint generation isolation + cross-generation terminal references + owner-check/INSERT linearization passed.
- Runtime / frontend / operational gates passed.
- Merge PR feat(runtime): generation-fence authoritative artifacts #27; only after merge may #22 / T4 become unblocked.
Activity