From b13a288451619e5cbb55086de6ddd8e4ad90bed3 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 18 Oct 2026 02:25:56 +0800 Subject: [PATCH 1/3] test(workspace): align Goal and refresh browser acceptance Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../personal-workspace-browser/first-goal-home.mjs | 13 ++++++++----- .../personal-workspace-browser/goal-showcase.mjs | 7 ++++++- .../goal-suggestions-live.mjs | 3 ++- .../goal-workspace-m2.mjs | 2 +- .../showcase-workspace.mjs | 4 +++- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/examples/personal-workspace-browser/first-goal-home.mjs b/examples/personal-workspace-browser/first-goal-home.mjs index 4d621688c6..59acb9e05c 100644 --- a/examples/personal-workspace-browser/first-goal-home.mjs +++ b/examples/personal-workspace-browser/first-goal-home.mjs @@ -88,16 +88,19 @@ export const firstGoalHomeScenario = { const failedBanner = page.locator(".personal-system-health-banner"); assert.match(await failedBanner.innerText(), /状态来源读取失败/, "The failure is shown in owner language"); assert.match(await failedBanner.innerText(), /最近一次读取失败:/); - const headerStatus = page.locator(".personal-channel-title, header").first(); + const refreshFeedback = page.locator(".personal-refresh-control").getByRole("status"); await page.locator("small", { hasText: "刷新失败" }).first().waitFor({ state: "visible", timeout: 10_000 }); - assert.equal(await page.locator("small", { hasText: "刚刚更新" }).count(), 0, "A failed refresh never reports an update"); - void headerStatus; + assert.equal(await refreshFeedback.textContent(), "", "A failed refresh never announces an update"); await page.screenshot({ path: resolve(outputDir, "first-goal-home-source-failed.png"), animations: "disabled" }); api.statusFailure = null; - await sourceRetry.click(); + // Success is announced in the accessible status region. Observe the + // transient announcement from the retry, alongside the restored board. + await Promise.all([ + refreshFeedback.filter({ hasText: "刚刚更新" }).waitFor({ state: "attached", timeout: 10_000 }), + sourceRetry.click(), + ]); await firstGoal.waitFor({ state: "visible", timeout: 15_000 }); assert.equal(await page.locator(".personal-system-health-banner").count(), 0, "A repaired source clears the failure"); - await page.locator("small", { hasText: "刚刚更新" }).first().waitFor({ state: "visible", timeout: 10_000 }); // English copy and the phone composition. await page.evaluate(() => localStorage.setItem("loopx-pw-locale", "en")); diff --git a/examples/personal-workspace-browser/goal-showcase.mjs b/examples/personal-workspace-browser/goal-showcase.mjs index ef5d70cdc4..76bf1575bb 100644 --- a/examples/personal-workspace-browser/goal-showcase.mjs +++ b/examples/personal-workspace-browser/goal-showcase.mjs @@ -25,7 +25,10 @@ export const goalShowcaseScenario = { await openShowcaseGoal(page); const overview = page.locator(".goal-overview"); assert.equal(await overview.getByRole("alert").count(), 0, "The showcase state has no region failure"); - assert.match(await page.locator(".goal-overview-objective").innerText(), /ledger API/); + const declaredObjective = overview.getByTestId("goal-declared-objective"); + assert.equal(await declaredObjective.innerText(), "Move legacy billing exports onto the ledger service."); + assert.match(await overview.locator(".delivery-acceptance-contract .goal-overview-objective").innerText(), /ledger API/, + "Optional acceptance keeps its own objective beside the Goal's recorded direction"); assert.match(await page.locator(".goal-overview-progress").innerText(), /2\/4/); assert.ok(await progressIsReadable(page), "The short criterion count fits on one line, rather than being squeezed by its meter"); assert.equal(await page.locator(".goal-criteria-meter i").count(), 4, "The meter has one segment per criterion"); @@ -66,6 +69,8 @@ export const goalShowcaseScenario = { for (const value of observed) { assert.ok(["23/28", "24/29"].includes(value), `The refresh invented an intermediate count: ${value}`); } + assert.equal(await declaredObjective.innerText(), "Move legacy billing exports onto the ledger service.", + "Progress refresh does not replace the Goal's recorded objective"); await page.waitForTimeout(700); const cost = await page.getByTestId("goal-vitals").locator(".goal-vital").nth(4).locator(".goal-vital-value").innerText(); assert.equal(cost, "$25.40", "Cost displays the exact observation"); diff --git a/examples/personal-workspace-browser/goal-suggestions-live.mjs b/examples/personal-workspace-browser/goal-suggestions-live.mjs index 11d80b8ebc..e80b15b663 100644 --- a/examples/personal-workspace-browser/goal-suggestions-live.mjs +++ b/examples/personal-workspace-browser/goal-suggestions-live.mjs @@ -83,7 +83,8 @@ export const goalSuggestionsLiveScenario = { await page.screenshot({ path: resolve(outputDir, "goal-suggestions-live.png"), animations: "disabled" }); await row.locator('button[data-suggestion="open_decisions"]').click(); await page.getByTestId("goal-vitals").waitFor({ state: "visible", timeout: 10_000 }); - await page.locator(".goal-overview").getByText(gateText).first().waitFor({ state: "visible", timeout: 10_000 }); + await page.locator(".goal-overview-attention").getByRole("button").filter({ hasText: gateText }) + .waitFor({ state: "visible", timeout: 10_000 }); return { coverageEntries: await session.close(), note: "real Todo owner and status; CLI user_action stays optional, user_gate becomes the decision suggestion" }; } catch (error) { if (diagnostics) process.stderr.write(diagnostics); diff --git a/examples/personal-workspace-browser/goal-workspace-m2.mjs b/examples/personal-workspace-browser/goal-workspace-m2.mjs index d27767df92..9366047454 100644 --- a/examples/personal-workspace-browser/goal-workspace-m2.mjs +++ b/examples/personal-workspace-browser/goal-workspace-m2.mjs @@ -98,7 +98,7 @@ export const goalWorkspaceM2Scenario = { // fallback direction button only exists without a contract or plan. const inspection = page.locator(".goal-overview .delivery-check-inspection"); const criteria = inspection.locator(".goal-overview-criteria"); - await page.locator(".goal-overview-objective").waitFor({state:"visible"}); + await page.locator(".goal-overview .delivery-acceptance-contract .goal-overview-objective").waitFor({state:"visible"}); await criteria.waitFor({state:"hidden"}); const summary = inspection.locator(":scope > summary"); await summary.focus(); await page.keyboard.press("Enter"); diff --git a/examples/personal-workspace-browser/showcase-workspace.mjs b/examples/personal-workspace-browser/showcase-workspace.mjs index b21061b77f..d989315d0a 100644 --- a/examples/personal-workspace-browser/showcase-workspace.mjs +++ b/examples/personal-workspace-browser/showcase-workspace.mjs @@ -139,7 +139,9 @@ export async function installShowcaseWorkspace(api, page, state) { if (workspaceNames[goal.id]) goal.display_name = workspaceNames[goal.id]; } const goal = fixture.run_history.goals.find((item) => item.id === goalId); - if (goal) Object.assign(goal, { display_name: title, acceptance_observation: acceptance }); + if (goal) Object.assign(goal, { display_name: title, + goal_objective: "Move legacy billing exports onto the ledger service.", + acceptance_observation: acceptance }); const queueItem = fixture.attention_queue.items.find((item) => item.goal_id === goalId); if (queueItem) { const items = [...openTodos, deferredTodo, ...recentDone]; From c814382499fd104474cc551170cf783437e9f71a Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 18 Oct 2026 05:14:51 +0800 Subject: [PATCH 2/3] test(ci): preserve current consumer semantics and observation clocks Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../test_content_digest_single_owner.py | 12 ++++++++++++ .../test_periodic_report_machine_store.py | 1 + .../test_host_prompt_behavior.py | 19 +++++++++++++++++-- .../control_plane/test_prompt_upgrade_hook.py | 5 +++-- .../test_quota_should_run_parity.py | 8 +++++++- tests/test_host_loop_activation.py | 16 +++++++++++----- tests/test_inbox_pagination.py | 19 ++++++++++++++++++- 7 files changed, 69 insertions(+), 11 deletions(-) diff --git a/tests/architecture/test_content_digest_single_owner.py b/tests/architecture/test_content_digest_single_owner.py index 7c6a8d20ad..755a2fef82 100644 --- a/tests/architecture/test_content_digest_single_owner.py +++ b/tests/architecture/test_content_digest_single_owner.py @@ -185,6 +185,7 @@ "loopx.control_plane.collaboration.peers", "loopx.control_plane.collaboration.request_control_source", "loopx.control_plane.collaboration.result_files", + "loopx.control_plane.collaboration.turn_context_observation", "loopx.control_plane.coordination.local_authority_shadow_history", "loopx.control_plane.coordination.shadow_management", "loopx.control_plane.digest_envelope", @@ -1595,6 +1596,17 @@ def read(value: dict[str, Any]) -> tuple[dict, str | None]: ) +def test_turn_agenda_addresses_keep_the_bare_digest_contract(tmp_path: Path) -> None: + from loopx.control_plane.collaboration.turn_context_observation import _paths + + rejected = (HEX64.upper(), HEX64[:-1], HEX64 + "0", "g" * 64, + "sha256-" + HEX64, HEX64 + ".stop") + for stem in (*BARE_ACCEPTS, *rejected): + (tmp_path / f"{stem}.json").write_text("{}", encoding="utf-8") + (tmp_path / f"{HEX64}.txt").write_text("{}", encoding="utf-8") + assert {path.stem for path in _paths(tmp_path)} == set(BARE_ACCEPTS) + + def test_outcome_projection_fingerprint_requires_the_envelope() -> None: from loopx.capabilities.issue_fix import outcome_projection diff --git a/tests/capabilities/test_periodic_report_machine_store.py b/tests/capabilities/test_periodic_report_machine_store.py index d4392bc1a0..f66921eb66 100644 --- a/tests/capabilities/test_periodic_report_machine_store.py +++ b/tests/capabilities/test_periodic_report_machine_store.py @@ -403,6 +403,7 @@ def test_canonical_machine_config_cli_uses_the_same_store_and_projection( assert catalog["schema_version"] == "machine_configuration_catalog_v0" assert [item["namespace"] for item in catalog["namespaces"]] == [ "change_quality_qualification", + "codex_chat", "goal_storage", "manager_runtime", "periodic_report", diff --git a/tests/control_plane/test_host_prompt_behavior.py b/tests/control_plane/test_host_prompt_behavior.py index 83af1d9aa1..97fb0e2334 100644 --- a/tests/control_plane/test_host_prompt_behavior.py +++ b/tests/control_plane/test_host_prompt_behavior.py @@ -17,6 +17,12 @@ ] +def sent_packet(messages): + heading, payload = messages[-1]["content"].split("\n", 1) + assert heading == "Current quota result:" + return json.loads(payload) + + class ScriptedClient: actor_ref = "scripted-not-model-evidence" @@ -26,7 +32,7 @@ def __init__(self, mutation=None, repeats=1): self.repeats = repeats def next_final_content(self, messages): - assert "expected" not in json.dumps(messages) + assert "expected" not in sent_packet(messages) answer = dict(ANSWERS[(self.count // self.repeats) % len(ANSWERS)]) self.count += 1 if self.mutation: @@ -52,7 +58,7 @@ def test_probe_uses_current_production_prompts_and_hidden_independent_oracle(): assert "--codex-app" in body assert "LOOPX_TURN=" in body assert case["id"] not in json.dumps(messages) - assert "expected" not in json.dumps(messages) + assert sent_packet(messages) == case["packet"] client = ScriptedClient() report = run_probe(client, repeats=1) assert report["qualification_passed"] @@ -60,6 +66,15 @@ def test_probe_uses_current_production_prompts_and_hidden_independent_oracle(): assert not report["host_execution_qualified"] +def test_oracle_boundary_allows_version_preconditions_but_rejects_answer_leakage(): + packet = {"lease": {"expected_version": 7}} + messages = probe_messages("thin", packet) + assert sent_packet(messages) == packet + assert json.loads(ScriptedClient().next_final_content(messages)) == ANSWERS[0] + with pytest.raises(AssertionError): + ScriptedClient().next_final_content(probe_messages("thin", cases()[0])) + + @pytest.mark.parametrize("mutation", [ {"action": "wait"}, {"notify": True}, {"finish_goal": True}, {"notify": 0}, ]) diff --git a/tests/control_plane/test_prompt_upgrade_hook.py b/tests/control_plane/test_prompt_upgrade_hook.py index 7415fae57e..7469e49572 100644 --- a/tests/control_plane/test_prompt_upgrade_hook.py +++ b/tests/control_plane/test_prompt_upgrade_hook.py @@ -140,8 +140,9 @@ def test_partial_reconciliation_preserves_other_pending_entries(tmp_path, monkey @pytest.mark.parametrize("route_source", ["quota_cli_invocation", "loopx_turn_run_once"]) def test_upgrade_read_projection_preserves_work_authority(tmp_path, monkeypatch, route_source): home, path, database, registry, root, _, desired, _ = _deferred(tmp_path, monkeypatch) - monkeypatch.setattr("loopx.control_plane.scheduler.scheduler_hint.now_utc", - lambda: datetime(2026, 1, 1, tzinfo=timezone.utc)) + now = datetime(2026, 1, 1, tzinfo=timezone.utc) + monkeypatch.setattr("loopx.control_plane.scheduler.scheduler_hint.now_utc", lambda: now) + monkeypatch.setattr("loopx.control_plane.runtime.time.now_utc", lambda: now) status = quota_status_payload(goal_id="fixture-goal", status="active", recommended_action="Advance the selected work", coordination={"registered_agents": ["agent-a"]}, agent_todo_items=[{"todo_id": "todo_ordinary_work", "index": 1, diff --git a/tests/control_plane/test_quota_should_run_parity.py b/tests/control_plane/test_quota_should_run_parity.py index 9dfacf9d5b..915e875911 100644 --- a/tests/control_plane/test_quota_should_run_parity.py +++ b/tests/control_plane/test_quota_should_run_parity.py @@ -1,5 +1,7 @@ from __future__ import annotations +from datetime import datetime, timezone + from loopx.control_plane.quota.should_run import ( build_quota_should_run as bounded_build_quota_should_run, ) @@ -40,7 +42,11 @@ def test_absent_boundary_keeps_delivery_and_capability_decisions() -> None: assert not (packet.get("delivery_workspace") or {}).get("allowed_write_scopes") -def test_facade_builds_through_bounded_should_run_module() -> None: +def test_facade_builds_through_bounded_should_run_module(monkeypatch) -> None: + # Compare both entrypoints at one observation instant; all freshness and + # authority fields remain part of the exact parity assertion below. + monkeypatch.setattr("loopx.control_plane.runtime.time.now_utc", + lambda: datetime(2026, 1, 1, tzinfo=timezone.utc)) todo_text = "[P1] Advance the bounded slice." payload = quota_status_payload( goal_id=GOAL_ID, diff --git a/tests/test_host_loop_activation.py b/tests/test_host_loop_activation.py index 5d82573f9b..4696cc6f35 100644 --- a/tests/test_host_loop_activation.py +++ b/tests/test_host_loop_activation.py @@ -439,11 +439,17 @@ def test_goal_hosts_reuse_thin_dispatch_and_stay_compact() -> None: for payload in goal_hosts: assert "selection_command" in payload["task_body"] assert "No learning queue unless asked." in payload["task_body"] - assert ( - "do/verify authorized work, then follow next_cli_actions for writeback/spend" - in payload["task_body"] - ) - assert payload["interface_budget"]["budget_char_count"] <= 2_800 + for obligation in ( + "do/verify authorized work", + "settlement_plan.ordered_steps, else next_cli_actions", + "preserve exact identities/effect ids", + ): + assert obligation in payload["task_body"] + # Current authority/continuation clauses measure 2,862–3,166 normalized + # chars on this fixed fixture. Keep a tight regression guard and the + # native host's separate hard cap, without shortening those obligations. + assert payload["interface_budget"]["budget_char_count"] <= 3_200 + assert payload["interface_budget"]["max_chars"] == 4_000 assert payload["interface_budget"]["within_budget"] is True diff --git a/tests/test_inbox_pagination.py b/tests/test_inbox_pagination.py index 1a6427851e..0b5b2a7296 100644 --- a/tests/test_inbox_pagination.py +++ b/tests/test_inbox_pagination.py @@ -1,6 +1,7 @@ """Receiver recovery through real CLI processes and identity-bound MCP stdio.""" import asyncio +from datetime import datetime, timezone import hashlib import json import subprocess @@ -77,12 +78,28 @@ def test_cli_recovers_later_context_without_concluding_deferred_requests(inbox): assert read_ids(root) == set(expected[:20]) assert first.get("next_cursor"), "pending request 21 has no recovery cursor" + second_started = datetime.now(timezone.utc) second = cli(root, registry, "read", "--cursor", first["next_cursor"]) + second_finished = datetime.now(timezone.utc) assert ids(second) == expected[20:40] and second["has_more"] assert second["items"][0]["brief"] == brief assert read_ids(root) == set(expected[:40]) # Every CLI invocation starts a fresh process; replay never consumes a page. - assert cli(root, registry, "read", "--cursor", first["next_cursor"]) == second + replay_started = datetime.now(timezone.utc) + replay = cli(root, registry, "read", "--cursor", first["next_cursor"]) + replay_finished = datetime.now(timezone.utc) + stable_pages = [] + for page, started, finished in ( + (second, second_started, second_finished), + (replay, replay_started, replay_finished), + ): + agenda = dict(page["unfinished_delegations"]) + observed_at = datetime.fromisoformat(agenda.pop("observed_at")) + assert started <= observed_at <= finished, "Each agenda observes the current read" + stable_pages.append({**page, "unfinished_delegations": agenda}) + # The agenda is freshly observed; every request, cursor and authority field + # still has to match. Replaying the page must not consume its requests. + assert stable_pages[0] == stable_pages[1] third = cli(root, registry, "read", "--cursor", second["next_cursor"]) assert ids(third) == expected[40:] assert not third["has_more"] and third["next_cursor"] is None From 3e25193a7ec520171cc2363f84c65f3026f343e2 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 18 Oct 2026 05:38:20 +0800 Subject: [PATCH 3/3] test(ci): verify localized research refresh after copy cleanup Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- examples/personal-workspace-browser/research-results.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/personal-workspace-browser/research-results.mjs b/examples/personal-workspace-browser/research-results.mjs index 43e0d5e8cf..df6acac750 100644 --- a/examples/personal-workspace-browser/research-results.mjs +++ b/examples/personal-workspace-browser/research-results.mjs @@ -120,7 +120,11 @@ export const researchResultsScenario = { await page.getByRole("button", {name: "Files", exact: true}).click(); const english = page.getByRole("region", {name: "Research results", exact: true}); await english.getByText("123456789.1234567891 SYN", {exact: true}).waitFor(); - assert.match(await english.textContent(), /counterevidence and open questions/); + mode = "disabled"; + await english.getByRole("button", {name: "Refresh", exact: true}).click(); + await english.getByText("No readable public research results for this Goal yet.", {exact: true}).waitFor(); + assert.equal(await english.getByText("123456789.1234567891 SYN", {exact: true}).count(), 0); + assert.equal(api.turnRequests.length, 0, "Localized research refresh must not invoke a model"); return {note: `Packaged results consume Goal-scoped public research, preserve uncertainty, and clear stale/disabled/wrong-Goal contents; ${nativeUrl ? "real isolated Core publication/HTTP, scripted Goal directory and corruption cases" : "scripted API fixture"}`, projectionSha256: projection.payload_sha256, coverageEntries: await context.close()}; } finally {if (!page.isClosed()) await context.close();}