Skip to content
Merged
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
2 changes: 1 addition & 1 deletion loopx/control_plane/goals/global_registry_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def _multi_goal_source_entry_unchanged(
except Exception: # noqa: BLE001
source_goals_cache[cache_key] = None
source_goals = source_goals_cache[cache_key]
if not source_goals or len(source_goals) <= 1:
if not source_goals:
return False
candidates = [
row for row in source_goals if str(row.get("id") or "") == goal_id
Expand Down
9 changes: 8 additions & 1 deletion loopx/control_plane/quota/effect_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
from ..work_items.delivery_batch_scale import DELIVERY_BATCH_SCALE_INPUT_CHOICES
from ..turn_driver.native_context_profile import NATIVE_CONTEXT_PAGE_OUTPUT_BYTES

_PLATFORM_IS_POSIX = os.name == "posix"

__all__ = [
"SETTLEMENT_IDENTITY_SCHEMA_VERSION",
"SETTLEMENT_PLAN_SCHEMA_VERSION",
Expand Down Expand Up @@ -265,10 +267,15 @@ def writeback_command(boundary: str) -> str:
writeback_command(" --delivery-boundary semantic_closeout")
+ " --agent-vision-json <file>"
)
max_output_arg = (
f" --max-page-output-bytes {NATIVE_CONTEXT_PAGE_OUTPUT_BYTES}"
if _PLATFORM_IS_POSIX
else ""
)
spend = (
f"{cli_prefix} quota spend-slot --goal-id {shlex.quote(goal_id)} --slots 1 "
f"--source {quota_spend_source} --execute{binding_arg}{turn_arg}{goal_ref_arg}"
f"{scoped_cli_args} --max-page-output-bytes {NATIVE_CONTEXT_PAGE_OUTPUT_BYTES}"
f"{scoped_cli_args}{max_output_arg}"
)
payload = effect_runtime_result("settlement.turn_scoped_cli_plan", {
"identity": identity.as_dict(), "delivery_boundary": delivery_boundary,
Expand Down
3 changes: 2 additions & 1 deletion loopx/control_plane/todos/read_projection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,13 @@ export async function projectCanonicalTodoFields(input: JsonObject): Promise<Jso
const checksConfigured = configuredGoalChecks(acceptance) !== null;
const lineage = Array.isArray(input.lineage) ? input.lineage.map(value => requireJsonObject(value, "Todo lineage")) : all;
const lineageFacts = input.lineage_facts ?? lineage.map(successionFacts);
const includeArchived = input.include_archived === true || input.retained === true;
const query = input.kind === "query" && input.selection == null ? object(input.query) : {};
const detailId = query.agent_id == null && query.limit == null ? query.todo_id : null;
const fields: JsonObject = {}, evaluations: JsonObject = {};
for (const role of ["user", "agent"]) {
const section = role === "agent" ? "Agent Todo" : "User Todo / Owner Review Reading Queue";
const items = all.filter(row => (row.role === "user" ? "user" : "agent") === role && (input.retained === true || !archived.has(row.todo_id)))
const items = all.filter(row => (row.role === "user" ? "user" : "agent") === role && (includeArchived || !archived.has(row.todo_id)))
.map(row => structureCanonicalTodo({...row,
...(input.retained !== true && checksConfigured && guards[text(row.todo_id)] ? {goal_acceptance_guard: guards[text(row.todo_id)]} : {})}, role, section,
input.retained === true ? null : 500, input.retained !== true));
Expand Down
10 changes: 8 additions & 2 deletions loopx/control_plane/todos/read_transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ import {compactTodoReadSummary, compactThinTodoRead, compactTodoQueueSummary, to
import {projectTodoContextPage} from "./context_projection.ts";
import {sealProjectionEnvelope, PROJECTION_ENVELOPE_SEAL_REQUEST} from "../projection_envelope.ts";

const includesArchivedRows = (query: JsonObject): boolean =>
query.read_scope === "completed_history" || query.include_archived === true;

export async function readCanonicalTodoProjection(input: JsonObject): Promise<JsonObject> {
const canonical = await collectCanonicalTodoSource(requireJsonObject(input.source, "canonical Todo read source"));
if (canonical.status !== "loaded") return {schema_version: TODO_READ_RESULT, canonical};
const todos = canonical.todos;
const guards = canonical.goal_acceptance_work_guards ?? {};

const projected = await projectCanonicalTodoFields({...input, todos, retained: requireJsonObject(input.query ?? {}, "Todo query").read_scope === "completed_history",
const query = requireJsonObject(input.query ?? {}, "Todo query");
const includeArchived = includesArchivedRows(query);
const retained = query.read_scope === "completed_history";
const projected = await projectCanonicalTodoFields({...input, todos, retained, include_archived: includeArchived,
goal_acceptance_contract: canonical.goal_acceptance_contract ?? null,
goal_acceptance_work_guards: guards});
if (input.query == null || projected.rejection) return {...projected, canonical};
Expand Down Expand Up @@ -65,6 +71,7 @@ export async function readHistoricalTodoProjection(input: JsonObject): Promise<J

async function todoListPayload(input: JsonObject, original: JsonObject[], fields: JsonObject, canonical: JsonObject): Promise<JsonObject> {
const query = requireJsonObject(input.query, "Todo list query");
const includeArchived = includesArchivedRows(query);
const fullText = query.full_text === undefined ? false : requireBoolean(query.full_text, "full_text");
const summary = query.summary === undefined ? false : requireBoolean(query.summary, "Todo summary view");
if (fullText && (query.thin === true || query.json_references === true || summary))
Expand Down Expand Up @@ -118,7 +125,6 @@ async function todoListPayload(input: JsonObject, original: JsonObject[], fields
item.text = String(matching[0]!.text ?? "");
}
const matchedCount = todos.length;
const includeArchived = query.read_scope === "completed_history" || query.include_archived === true;
const hot = !fullText && !includeArchived && query.agent_id && query.limit == null &&
query.role == null && query.status == null && query.todo_id == null;
if (hot && !summary) {
Expand Down
22 changes: 22 additions & 0 deletions tests/control_plane/test_quota_settlement.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,28 @@ def quote(value: object) -> str:
assert '"${LOOPX_TURN:?}"' not in command


def test_windows_settlement_command_omits_unsupported_max_page_output_bytes() -> None:
# Non-POSIX platforms fail closed on private capture validation; settlement
# spend recipes must omit --max-page-output-bytes on Windows.
with patch.object(quota_effect_program, "_PLATFORM_IS_POSIX", False):
plan = quota_effect_program.build_turn_scoped_cli_settlement_plan(
goal_id=GOAL_ID, agent_id=AGENT_ID, todo_id=TODO_ID,
turn_instance_id=TURN_ID, scoped_cli_args="", lifecycle_actor_args="",
).as_dict()
command = settlement_step_command(plan, SettlementStepKind.QUOTA_SPEND)
assert command is not None
assert "--max-page-output-bytes" not in command

with patch.object(quota_effect_program, "_PLATFORM_IS_POSIX", True):
plan = quota_effect_program.build_turn_scoped_cli_settlement_plan(
goal_id=GOAL_ID, agent_id=AGENT_ID, todo_id=TODO_ID,
turn_instance_id=TURN_ID, scoped_cli_args="", lifecycle_actor_args="",
).as_dict()
command = settlement_step_command(plan, SettlementStepKind.QUOTA_SPEND)
assert command is not None
assert "--max-page-output-bytes" in command


def test_cli_validation_projects_ordinary_completion_not_terminal_intent() -> None:
plan = build_codex_app_settlement_plan(
goal_id=GOAL_ID, agent_id=AGENT_ID, todo_id=TODO_ID,
Expand Down
85 changes: 85 additions & 0 deletions tests/control_plane/test_sqlite_archive_and_registry_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,91 @@ def test_multi_goal_source_registry_sync_and_health_prevent_sibling_false_positi
assert stale_goal_ids == ["goal-drifted"]


def test_single_goal_source_registry_unchanged_prevents_false_positive_stale_warning(
tmp_path: Path,
) -> None:
runtime = tmp_path / "runtime"
project = tmp_path / "project"
source_registry_path = project / ".loopx" / "registry.json"
source_registry_path.parent.mkdir(parents=True)

state_rel = ".codex/goals/goal-single/ACTIVE_GOAL_STATE.md"
state_path = project / state_rel
state_path.parent.mkdir(parents=True)
state_path.write_text("# Goal\n", encoding="utf-8")
single_goal = {
"id": "goal-single",
"status": "active",
"repo": str(project),
"state_file": state_rel,
}

old_synced_at = "2026-01-01T00:00:00+00:00"
global_goals = [
sanitize_goal_for_global(
single_goal,
source_registry=source_registry_path,
synced_at=old_synced_at,
)
]
global_path = global_registry_path(runtime)
global_path.parent.mkdir(parents=True, exist_ok=True)
global_path.write_text(
json.dumps(
{
"schema_version": 1,
"common_runtime_root": str(runtime),
"goals": global_goals,
},
indent=2,
)
+ "\n",
encoding="utf-8",
)

source_registry = {
"schema_version": 1,
"common_runtime_root": str(runtime),
"goals": [single_goal],
}
source_registry_path.write_text(
json.dumps(source_registry, indent=2) + "\n", encoding="utf-8"
)

now = time.time() + 100
os.utime(source_registry_path, (now, now))

health = status_module.collect_global_registry_health(
registry_path=source_registry_path,
runtime_root=runtime,
current_registry=source_registry,
)
stale_goal_ids = [
finding.get("goal_id")
for finding in health["findings"]
if finding.get("kind") == "stale_source_registry"
]
assert stale_goal_ids == []

source_registry["goals"][0]["status"] = "paused"
source_registry_path.write_text(
json.dumps(source_registry, indent=2) + "\n", encoding="utf-8"
)
os.utime(source_registry_path, (now + 10, now + 10))

health_mutated = status_module.collect_global_registry_health(
registry_path=source_registry_path,
runtime_root=runtime,
current_registry=source_registry,
)
stale_mutated_ids = [
finding.get("goal_id")
for finding in health_mutated["findings"]
if finding.get("kind") == "stale_source_registry"
]
assert stale_mutated_ids == ["goal-single"]


@pytest.mark.parametrize(
("scenario", "todo_payload", "expected_omitted_fields", "expect_inline_note"),
[
Expand Down
99 changes: 99 additions & 0 deletions tests/control_plane_ts/todo_read.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {engageLegacyCoordinationWriterFence} from "../../loopx/control_plane/coo
import {canonicalAuthoritySha256} from "../../loopx/control_plane/coordination/authority_store_codec.ts";
import {coordinationTodoReadModel} from "../../loopx/control_plane/coordination/coordination_projection.ts";
import {TODO_CANONICAL_READ_RECORD_SCHEMA} from "../../loopx/control_plane/coordination/coordination_state_contract.ts";
import {authorityProjectionFixture} from "./authority_projection_fixture.ts";
import {goalAcceptanceTodoDigest, normalizeGoalAcceptanceDocument} from "../../loopx/control_plane/goals/acceptance_contract.ts";

const now = "2026-10-19T07:00:00Z";
const row = (todo_id: string, extra: JsonObject = {}): JsonObject => ({schema_version: "todo_item_v0", todo_id,
Expand Down Expand Up @@ -117,6 +119,103 @@ for (const provider of ["file", "sqlite"] as const) {
});
}

for (const provider of ["file", "sqlite"] as const) {
test(`${provider} include-archived keeps current acceptance limits on active Todos`, async t => {
const root = await mkdtemp(join(tmpdir(), "loopx-todo-mixed-archive-"));
t.after(() => rm(root, {recursive: true, force: true}));
const state = join(root, "state.md"), goal = "goal-a";
await writeFile(state, "# Synthetic Goal\n");
if (provider === "sqlite") await selectLocalSqliteAuthority(root, goal, true);
const store = await openLocalAuthorityStore(root, goal);
const bound = row("todo_bound", {text: "Original bound requirement"});
const unbound = row("todo_unbound", {index: 2, text: "Unbound active requirement"});
const archived = row("todo_archived", {index: 3, status: "done", done: true,
task_class: "advancement_task", archive_state: "archive", source_section: "Completed Work Archive"});
const document = normalizeGoalAcceptanceDocument({
objective: "Preserve current acceptance limits while reading history",
non_goals: [],
criteria: [{id: "criterion-a", description: "The bound work remains guarded",
validation_argv: ["true"]}],
bindings: [{todo_id: "todo_bound", criterion_ids: ["criterion-a"]}],
});
const acceptance = {
schema_version: "loopx_goal_acceptance_v0",
enabled: true,
revision: 1,
digest: canonicalAuthoritySha256(document),
document,
verification: null,
bindings: [{todo_id: "todo_bound", todo_semantic_digest: goalAcceptanceTodoDigest(bound),
revision: 1, criterion_ids: ["criterion-a"], confirmed_by: "owner"}],
};
const projection = authorityProjectionFixture(goal, [
{...bound, text: "Changed bound requirement"},
unbound,
archived,
], [], "native", {goal_acceptance: acceptance});
const committed = await store.commitAuthority({
expected_provider_revision: null,
operation_id: "seed-mixed-archive",
events: [],
next_projection: projection,
receipts: [],
});
assert.equal(committed.status, "applied");
if (committed.status !== "applied") return;
const fence = await engageLegacyCoordinationWriterFence({
schema_version: "loopx_legacy_coordination_writer_fence_engage_request_v0",
runtime_root: root,
goal_id: goal,
state_path: state,
fence: {
schema_version: "loopx_legacy_coordination_writer_fence_v0",
state: "engaged",
goal_id: goal,
fence_id: "mixed-archive-fixture",
source_version: "fixture",
source_projection_sha256: canonicalAuthoritySha256(projection),
expected_shadow_provider_revision: committed.provider_revision,
},
});
assert.equal(fence.status, "applied");

const query = (include_archived: boolean, status: string | null = null) => readTodo({
schema_version: TODO_READ_REQUEST,
kind: "query",
source: {runtime_root: root, goal_id: goal},
observed_at: Date.parse(now) / 1000,
evaluated_at: now,
query: {goal_id: goal, role: "agent", status, todo_id: null, agent_id: null, limit: null,
read_scope: "active", include_archived, json_references: false, thin: false,
state_file: state, project: root},
});

const normalResult = await query(false);
const mixedResult = await query(true);
const openOnlyResult = await query(true, "open");
assert.ok(normalResult.payload, JSON.stringify(normalResult));
assert.ok(mixedResult.payload, JSON.stringify(mixedResult));
assert.ok(openOnlyResult.payload, JSON.stringify(openOnlyResult));
const normal = normalResult.payload as JsonObject;
const mixed = mixedResult.payload as JsonObject;
const openOnly = openOnlyResult.payload as JsonObject;
assert.deepEqual((normal.todos as JsonObject[]).map(item => item.todo_id), ["todo_bound", "todo_unbound"]);
assert.deepEqual(new Set((mixed.todos as JsonObject[]).map(item => item.todo_id)),
new Set(["todo_bound", "todo_unbound", "todo_archived"]));
assert.deepEqual(new Set((openOnly.todos as JsonObject[]).map(item => item.todo_id)),
new Set(["todo_bound", "todo_unbound"]));
for (const payload of [normal, mixed, openOnly]) {
const summary = payload.agent_todos as JsonObject;
const contract = summary.goal_acceptance_contract as JsonObject;
assert.equal(contract.status, "held");
assert.deepEqual(contract.held_todo_ids, ["todo_bound", "todo_unbound"]);
assert.deepEqual(summary.first_executable_items, []);
const active = (summary.items as JsonObject[]).filter(item => item.todo_id !== "todo_archived");
assert.ok(active.every(item => (item.goal_acceptance_guard as JsonObject).allowed === false));
}
});
}

test("display limits retain full-source closure and reject altered evaluation evidence", async () => {
const items = Array.from({length: 4096}, (_, i) => row(`todo_completed_${i}`, {index: i + 1,
status: "done", done: true, no_followup: true}));
Expand Down
Loading