Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions docs/architecture/rfcs/automatic-execution-admission-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,12 @@ the interval. The local CLI remains a same-UID trust boundary.
A managed start is two-phase in the same store: admission reserves the interval
slot, and the Turn executor confirms that reservation only after the host
attempt is durable in its journal. A crash between the two leaves the
reservation resumable by the same Turn identity once the floor is reached, so a
reserved-but-unstarted start never strands a Turn; a confirmed start stays
fail-closed for the same identity, and an explicit manual reason cannot bypass
that. A store record written without the phase field is read as an attempted
start, so an older or hand-edited file fails closed rather than resuming.
reservation immediately resumable by the same Turn identity without moving the
original interval anchor, so a reserved-but-unstarted start never strands a
Turn. A confirmed start stays fail-closed for the same identity, and an explicit
manual reason cannot bypass that. A store record written without the phase field
is read as an attempted start, so an older or hand-edited file fails closed
rather than resuming.

The M3 settings companion presents the quota-owned Goal/agent/automation policy
through one explicit Save backed by revision-locked preview, apply and readback,
Expand Down
6 changes: 2 additions & 4 deletions loopx/control_plane/quota/automation_cadence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,8 @@ export async function admitAutomationStart(p: JsonObject): Promise<JsonObject> {
}
if (held !== undefined) {
// Same identity, no durable host attempt: keep the original interval anchor.
return manual === null && current.eligible_now !== true
? {...current, admitted: false, reserved: false, reason: "minimum_interval_wait"}
: {...current, admitted: true, reserved: true, resumed: true,
reason: "resumed_unstarted_reservation"};
return {...current, admitted: true, reserved: true, resumed: true,
reason: "resumed_unstarted_reservation"};
}
if (manual === null && current.eligible_now !== true) {
return {...current, admitted: false, reserved: false, reason: "minimum_interval_wait"};
Expand Down
4 changes: 2 additions & 2 deletions tests/control_plane_ts/automation_cadence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ test("an unconfirmed reservation resumes while a confirmed start fails closed",
await manage({...base, operation: "configure", expected_revision: 0, min_interval_minutes: 60,
owner_reference: "owner-request", execute: true});
assert.equal((await start("turn:1", 1000)).reserved, true);
assert.equal((await start("turn:1", 1000 + 3_600_000 - 1)).reason, "minimum_interval_wait");
const resumed = await start("turn:1", 1000 + 3_600_000);
assert.equal((await start("turn:2", 1001)).reason, "minimum_interval_wait");
const resumed = await start("turn:1", 1001, {trigger_at_ms: 1000});
assert.equal(resumed.admitted, true);
assert.equal(resumed.resumed, true);
assert.equal(resumed.reason, "resumed_unstarted_reservation");
Expand Down
4 changes: 2 additions & 2 deletions tests/test_loopx_turn_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1322,7 +1322,7 @@ def die_after_reservation(identity: Mapping[str, object]) -> dict[str, object]:
assert calls == {"host": 0, "writeback": 0, "spend": 0, "scheduler": 0}

started_at_ms = int(_cadence_starts(runtime_root)[0]["started_at_ms"])
monkeypatch.setattr(turn_cadence, "time", _FrozenTurnClock(started_at_ms + 120_000))
monkeypatch.setattr(turn_cadence, "time", _FrozenTurnClock(started_at_ms + 1))
restart = _managed_cadence(runtime_root)
recovered = run_loopx_turn_once(
plan, admit_start=restart.admit, confirm_start=restart.confirm, **common
Expand Down Expand Up @@ -1368,7 +1368,7 @@ def die_before_attempt_record(path: Path, journal: dict[str, object]) -> None:
assert calls == {"host": 0, "writeback": 0, "spend": 0, "scheduler": 0}

started_at_ms = int(_cadence_starts(runtime_root)[0]["started_at_ms"])
monkeypatch.setattr(turn_cadence, "time", _FrozenTurnClock(started_at_ms + 120_000))
monkeypatch.setattr(turn_cadence, "time", _FrozenTurnClock(started_at_ms + 1))
restart = _managed_cadence(runtime_root)
recovered = run_loopx_turn_once(
plan, admit_start=restart.admit, confirm_start=restart.confirm, **common
Expand Down
Loading