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
13 changes: 8 additions & 5 deletions examples/personal-workspace-browser/first-goal-home.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down
7 changes: 6 additions & 1 deletion examples/personal-workspace-browser/goal-showcase.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
6 changes: 5 additions & 1 deletion examples/personal-workspace-browser/research-results.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
12 changes: 12 additions & 0 deletions tests/architecture/test_content_digest_single_owner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions tests/capabilities/test_periodic_report_machine_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
19 changes: 17 additions & 2 deletions tests/control_plane/test_host_prompt_behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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:
Expand All @@ -52,14 +58,23 @@ def test_probe_uses_current_production_prompts_and_hidden_independent_oracle():
assert "--codex-app" in body
assert "LOOPX_TURN=<current_time_iso>" 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"]
assert client.count == report["provider_call_count"] == 10
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},
])
Expand Down
5 changes: 3 additions & 2 deletions tests/control_plane/test_prompt_upgrade_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion tests/control_plane/test_quota_should_run_parity.py
Original file line number Diff line number Diff line change
@@ -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,
)
Expand Down Expand Up @@ -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,
Expand Down
16 changes: 11 additions & 5 deletions tests/test_host_loop_activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
19 changes: 18 additions & 1 deletion tests/test_inbox_pagination.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading