feat(issue-5): single CAS transition boundary for every job-status write
engine.transition is now the only writer of job status. It implements the target table in state-transitions.md §3: claim, non-terminal metadata, explicit terminals, idempotent re-receipt, refusal of conflicting or late signals with an audit entry, stale-attempt rejection, and deadline timeout. Two write paths become one. update_job_status merged fields unconditionally; pick_pending hand-rolled a correct pending->running swap plus its own audit mirror. Neither was wrong alone, but a rule added to one silently missed the other, which is the shape that produced the phase 1 transport bugs. Both keep their signatures and their preserved legacy bodies behind MAM_ENGINE_CAS=0. A merged `status` value is routed to the request that owns it, so a raw field write can no longer slip past the table. Getting that wrong first was caught by the characterization tests: a metadata merge was overwriting the caller's status with the pre-merge value. Lock discipline is now enforced, not described. The lock covers a decision and one record write; audit mirroring is queued and flushed after release, carrying a monotonic transition_seq so ordering survives the move. A test watches for subprocess, socket and sleep calls while the lock is held and asserts none. Structural correction to my own plan, caught by a phase 1 guard: the plan put the engine in lib_py/loop, but delegation would then have had to import a sibling, which is exactly what mixes trees under a frozen loop. The engine belongs with the registry writers, so it lives in lib_py/delegation and loop consumes it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrDNJkHmm18uu7n7JwV5Qd
This commit is contained in:
1 parent
9389d7ebe2
commit
2532e78453
8 files changed
+794
-3
No files matched your search
@@ -0,0 +1,16 @@
|
||||
"""Loop core: the single state-transition authority and the shadow watcher.
|
||||
|
||||
Issue #5 phase 2a. Two things live here and they are deliberately separated:
|
||||
|
||||
* `lib_py.delegation.engine` decides and commits state. It is the only writer
|
||||
of job status, and it lives with the other registry writers rather than here
|
||||
-- see that module's docstring for why the package boundary matters under a
|
||||
frozen loop.
|
||||
* `watcher` observes and never decides; `policy` decides and never writes;
|
||||
`shadow` runs the two together and, in this phase, only logs what it *would*
|
||||
have committed.
|
||||
|
||||
Package rule, enforced by test: nothing here imports a `lib_py` module other
|
||||
than `lib_py.delegation`. Under a frozen loop the parent package may resolve to
|
||||
a different tree than a bound sibling, so a stray import silently mixes trees.
|
||||
"""
|
||||
Reference in new issue
Block a user