Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
38497d8
feat(goals): add owner-configured acceptance authority
songoow Sep 18, 2026
344c84e
feat(dashboard): read owner-authorized Goal acceptance contracts
songoow Sep 18, 2026
4fa4986
docs(goals): explain acceptance setup and bounded follow-up
songoow Sep 18, 2026
a4dc703
fix(goals): canonicalize acceptance fingerprints and receipt readback
songoow Sep 18, 2026
6076b1c
feat(goals): bind acceptance criteria to explicit validator file hashes
songoow Sep 18, 2026
d49de06
docs(goals): clarify local trust and validator pinning
songoow Sep 18, 2026
9e6ece1
refactor(goals): retain one normalized acceptance digest
songoow Sep 18, 2026
37f7b31
feat(goals): execute owner-configured acceptance checks from the CLI
songoow Sep 18, 2026
5ed756f
feat(coordination): enforce opt-in Goal acceptance at runtime
songoow Sep 18, 2026
1a459b8
feat(coordination): run the acceptance checks a completion plan demands
songoow Sep 18, 2026
e972166
feat(goals): serve the acceptance basis the workspace panel reads
songoow Sep 18, 2026
cee7653
test(goals): prove the host runs the criteria a completion plan names
songoow Sep 18, 2026
b283488
fix(dashboard): make the acceptance browser smoke runnable
songoow Sep 18, 2026
4ff3b0f
docs(goals): name the acceptance smoke commands both mirrors now have
songoow Sep 18, 2026
35b58b7
test(goals): skip the SQLite acceptance rows the public minimum canno…
songoow Sep 18, 2026
f94ea5f
fix(goals): bind acceptance to the work and the transition, not to fi…
songoow Sep 18, 2026
704242a
fix(coordination): stop previewing a close the real call still gates
songoow Sep 18, 2026
5c7541c
Merge origin/main into codex/goal-acceptance-governance
songoow Sep 18, 2026
014459f
docs(coordination): say why the terminal replay needs no acceptance r…
songoow Sep 18, 2026
353f19d
Merge main and rebuild the acceptance dashboard bundle
huangruiteng Sep 18, 2026
0435a71
fix(docs): keep acceptance setup within the canonical timeout budget
huangruiteng Sep 18, 2026
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: 2 additions & 0 deletions apps/presentation/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
"smoke:pwa-bundle": "python3 ../../../examples/dashboard-pwa-bundle-smoke.py",
"smoke:goal-acceptance-browser": "node ../../../examples/dashboard-goal-acceptance-browser-smoke.mjs",
"smoke:goal-acceptance-packaged": "LOOPX_GOAL_ACCEPTANCE_PACKAGED=1 LOOPX_GOAL_ACCEPTANCE_PORT=5292 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node ../../../examples/dashboard-goal-acceptance-browser-smoke.mjs",
"smoke:goal-acceptance-contract-browser": "node smoke/goal-acceptance-contract-browser-smoke.mjs",
"smoke:goal-acceptance-contract-packaged": "LOOPX_ACCEPTANCE_CONTRACT_PACKAGED=1 LOOPX_ACCEPTANCE_CONTRACT_PORT=5297 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/goal-acceptance-contract-browser-smoke.mjs",
"smoke:status-projection-contract": "rm -rf /tmp/loopx-status-projection-contract-smoke && tsc --ignoreConfig --target ES2022 --module CommonJS --moduleResolution Node --ignoreDeprecations 6.0 --skipLibCheck --strict --resolveJsonModule --esModuleInterop --outDir /tmp/loopx-status-projection-contract-smoke smoke/status-projection-contract-smoke.ts src/data/status.ts src/data/status-merge.ts src/data/status-request-fence.ts && NODE_PATH=\"$PWD/node_modules\" node /tmp/loopx-status-projection-contract-smoke/apps/presentation/dashboard/smoke/status-projection-contract-smoke.js",
"smoke:team-plan-proposal": "tsc --ignoreConfig --target ES2022 --module ES2022 --moduleResolution Bundler --ignoreDeprecations 6.0 --jsx react-jsx --types node --skipLibCheck --strict --rootDir . --outDir node_modules/.cache/loopx-team-plan-smoke smoke/team-plan-proposal-smoke.ts src/data/chat.ts src/features/personal-workspace/team-plan-preview.ts src/vite-env.d.ts && node node_modules/.cache/loopx-team-plan-smoke/smoke/team-plan-proposal-smoke.js",
"smoke:status-source-switch-browser": "node ../../../examples/status-source-switch-browser-smoke.mjs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ for (const copy of Object.values(deliveryReviewCopy)) {
assert.ok(markdown.includes(`${copy.guards}: ${copy.unavailable}`), "Unavailable decisions must not export as zero pending");
}
console.log("delivery review: identity, scope, relationships, partial coverage, filtering, export and negative contracts passed");
await import("./goal-acceptance-contract-smoke.mjs");
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env node
// Exercise the shipped delivery view with synthetic status and read-only API fixtures.
import assert from "node:assert/strict";
import { execFileSync, spawn } from "node:child_process";
import { createRequire } from "node:module";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { cleanupBrowserSmoke, launchBrowser, startViteDashboardServer, waitForHttp } from "../../../../examples/dashboard-browser-smoke-support.mjs";
import { acceptance, contract, snapshot, verifiedContract } from "./goal-acceptance-contract-fixture.mjs";

const dashboardDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const root = resolve(dashboardDir, "../../..");
const python = process.env.LOOPX_PYTHON ?? "python3";
const port = Number(process.env.LOOPX_ACCEPTANCE_CONTRACT_PORT ?? 5297);
const packaged = process.env.LOOPX_ACCEPTANCE_CONTRACT_PACKAGED === "1";
const payload = JSON.parse(execFileSync(python, ["-c", `
import runpy, tempfile, json
from pathlib import Path
from loopx.status import collect_status
fixture = runpy.run_path('tests/test_delivery_review.py')
with tempfile.TemporaryDirectory() as directory:
registry, runtime = fixture['make_project'](Path(directory))
print(json.dumps(collect_status(registry_path=registry, runtime_root_override=str(runtime), scan_roots=[], limit=20, goal_id='release-demo', include_public_boundary_scan=False)))
`], { cwd: root, encoding: "utf8" }));
payload.run_history.goals[0].acceptance_observation = acceptance;
const server = packaged
? spawn(python, ["-m", "http.server", String(port), "--bind", "127.0.0.1", "--directory", resolve(root, "loopx/web")], { stdio: "ignore" })
: startViteDashboardServer({ dashboardDir, port });
let browser;
try {
await waitForHttp(`http://127.0.0.1:${port}/`);
browser = await launchBrowser(createRequire(import.meta.url)("playwright").chromium);
for (const [locale, viewport] of [["en", { width: 1440, height: 1000 }], ["zh-CN", { width: 390, height: 844 }]]) {
const page = await browser.newPage({ viewport });
const errors = [];
page.on("pageerror", error => errors.push(error.message));
const writes = [];
let response = structuredClone(snapshot);
let responseStatus = 200;
await page.addInitScript(language => localStorage.setItem("loopx-pw-locale", language), locale);
await page.route("**/api/**", route => {
if (route.request().method() !== "GET") writes.push(route.request().url());
return route.fulfill({ status: 404, json: { error: "Unavailable in read-only fixture" } });
});
await page.route(url => url.pathname === "/status.json", route => route.fulfill({ json: payload }));
await page.route("**/api/chat/delivery-review?*", route => {
assert.equal(route.request().method(), "GET");
assert.equal(new URL(route.request().url()).searchParams.get("goal_id"), "release-demo");
return route.fulfill({ status: responseStatus, json: response });
});
await page.goto(`http://127.0.0.1:${port}/${packaged ? "chat/" : ""}?statusUrl=/status.json`, { waitUntil: "networkidle" });
const navigation = page.getByRole("button", { name: locale === "en" ? "Open Goal navigation" : "打开 Goal 导航" });
if (await navigation.isVisible()) await navigation.click();
await page.locator(".personal-goal-link").first().click();
await page.getByRole("button", { name: locale === "en" ? "Overview" : "概览", exact: true }).click();
const review = page.locator(".delivery-review");
const section = review.locator(".delivery-acceptance-contract");
const refresh = review.getByRole("button", { name: locale === "en" ? "Refresh snapshot" : "刷新快照", exact: true });
const exportButton = review.getByRole("button", { name: locale === "en" ? "Export delivery snapshot" : "导出交付快照", exact: true });
const refreshSnapshot = async value => {
response = { ...snapshot, acceptance: { ...acceptance, goal_acceptance_contract: value } };
const read = page.waitForResponse(url => url.url().includes("/api/chat/delivery-review?"));
await refresh.click();
await read;
await page.waitForFunction(() => !document.querySelector(".delivery-review-toolbar button")?.disabled);
};
await review.locator(".delivery-chain").waitFor();
const baseline = await review.innerText();
assert.equal(await section.count(), 0);
for (const value of [{ enabled: false }, { ...verifiedContract("accepted"), enabled: false }, undefined]) {
await refreshSnapshot(value);
assert.equal(await section.count(), 0);
assert.equal(await review.innerText(), baseline, "Absent/off contract must preserve the existing view");
}
await refreshSnapshot(contract);
assert.equal(await section.getAttribute("open"), null, "Contract starts collapsed");
const summary = section.locator(":scope > summary");
await summary.focus();
await page.keyboard.press("Enter");
assert.notEqual(await section.getAttribute("open"), null, "Disclosure is keyboard accessible");
const text = await section.innerText();
for (const value of [contract.objective, contract.digest, "release-demo", "todo_confirmed", "todo_unbound", "todo_stale", "recovery"]) assert.ok(text.includes(value));
for (const value of locale === "en" ? ["Task association confirmed", "Task association missing", "Task association stale", "Task associations require confirmation", "Outside the current task gate"] : ["任务关联已确认", "任务关联缺失", "任务关联已过期", "任务关联需要确认", "不属于当前任务门禁范围"]) assert.ok(text.includes(value), value);
assert.equal(await section.locator("button,input,select,textarea").count(), 0, "Readback must offer no mutation controls");
await section.locator("details > summary").last().click();
await section.getByText(locale === "en" ? /The local Goal owner/ : /本地 Goal 所有者/).waitFor();
for (const [status, en, zh] of [["unverified", "Artifact checks not verified", "产物检查未验证"], ["failed", "Artifact checks failed", "产物检查失败"], ["stale", "Artifact checks stale", "产物检查已过期"], ["partial", "Task checks passed; Goal-wide verification unknown", "任务检查通过;Goal 整体验证未知"], ["accepted", "Artifact checks passed", "产物检查通过"]]) {
await refreshSnapshot(verifiedContract(status));
await section.getByText(locale === "en" ? en : zh, { exact: true }).waitFor();
assert.ok((await section.innerText()).includes(locale === "en" ? "neither automatically approves or completes the Goal" : "均不会自动批准或完成 Goal"));
}
const next = { ...verifiedContract("stale"), revision: 8, digest: "b".repeat(64) };
await refreshSnapshot(next);
await section.getByText(next.digest, { exact: true }).waitFor();
assert.equal(await section.getByText(contract.digest, { exact: true }).count(), 0);
assert.match(await section.locator("dl").first().innerText(), /8/);
await section.locator("details > summary").first().click();
await section.getByText("c".repeat(64), { exact: true }).waitFor();
assert.match(await section.locator("dl").last().innerText(), /6/);
assert.equal(await section.evaluate(element => element.scrollWidth > element.clientWidth + 2), false, "Long digests must wrap at narrow widths");
const downloadPromise = page.waitForEvent("download");
await exportButton.click();
const download = await downloadPromise;
const stream = await download.createReadStream();
let markdown = "";
for await (const chunk of stream) markdown += chunk.toString();
assert.ok(markdown.includes(next.digest) && markdown.includes("8") && markdown.includes(contract.objective));
responseStatus = 503;
await refreshSnapshot(next);
await section.getByText(locale === "en" ? /Retained snapshot/ : /当前保留旧快照/).waitFor();
assert.equal(await exportButton.isDisabled(), true, "Failed refresh must not export retained contract as current");
responseStatus = 200;
response = { ...snapshot, acceptance: { ...acceptance, goal_id: "other-goal", goal_acceptance_contract: next } };
const mismatch = page.waitForResponse(url => url.url().includes("/api/chat/delivery-review?"));
await refresh.click();
await mismatch;
await page.waitForFunction(() => !document.querySelector(".delivery-review-toolbar button")?.disabled);
await section.getByText(locale === "en" ? /Retained snapshot/ : /当前保留旧快照/).waitFor();
assert.equal(await section.getByText("other-goal", { exact: true }).count(), 0);
await refreshSnapshot({ enabled: false });
assert.equal(await section.count(), 0, "Disabling removes the previous enabled readback");
assert.deepEqual(writes, [], "Reading and exporting must not write state");
assert.deepEqual(errors, []);
await page.close();
}
console.log(`Goal acceptance contract browser (${packaged ? "packaged" : "development"}): off parity, states, keyboard, locales, mobile, revision refresh and read-only export passed`);
} finally {
await cleanupBrowserSmoke({ browser, server, fixturePaths: [] });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Synthetic server projection. No acceptance state is inferred by the client.
export const acceptance = {
schema_version: "goal_acceptance_observation_projection_v0", goal_id: "release-demo",
read_only: true, acceptance_assessed: false, coverage: "partial", missing_sources: [],
truncated: false, historical_progress: [], acceptance_gaps: [], guards: [], next_action: null, next_action_source: null,
};
export const contract = {
enabled: true, revision: 7, digest: "a".repeat(64), objective: "Deliver a recoverable release",
non_goals: ["Publishing the release"], status: "held", held_todo_ids: ["todo_unbound", "todo_stale"], verification: null,
criteria: [{ id: "recovery", description: "An independent recovery check passes." }],
tasks: [
{ todo_id: "todo_confirmed", state: "ready", criterion_ids: ["recovery"], applicable: true },
{ todo_id: "todo_unbound", state: "unbound", criterion_ids: [], reason: "No criterion is associated.", applicable: true },
{ todo_id: "todo_stale", state: "stale", criterion_ids: ["recovery"], reason: "Association refers to a prior contract revision.", applicable: true },
{ todo_id: "todo_retired", state: "stale", criterion_ids: ["recovery"], applicable: false },
],
};
export function verifiedContract(status) {
return {
...contract, status,
tasks: status === "held" ? contract.tasks : [contract.tasks[0], contract.tasks[3]],
held_todo_ids: status === "held" ? contract.held_todo_ids : [],
verification: status === "unverified" ? null : {
operation_id: "verify-release-7", contract_revision: status === "stale" ? 6 : 7,
contract_digest: status === "stale" ? "c".repeat(64) : contract.digest,
todo_id: status === "partial" ? "todo_confirmed" : null,
results: [{ criterion_id: "recovery", passed: status !== "failed", exit_code: status === "failed" ? 1 : 0 }],
},
};
}
export const snapshot = {
ok: true, goal_id: "release-demo", observed_at: "2026-09-01T00:00:00Z", acceptance,
graph: {
schema_version: "task_graph_projection_v0", mode: "read_only", goal_id: "release-demo", generated_at: null,
truth_contract: { projection_is_writable: false, write_api: false },
limits: { user_gate_node_limit: 2, user_gate_open_count: 0, user_gate_truncated_count: 0, topology_complete: true },
nodes: [{ node_id: "current", kind: "deliverable", title: "Integrate the verified release package", state: "open", refs: { todo_ids: ["todo_integrate"] } }],
edges: [],
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import assert from "node:assert/strict";
import { deliveryReviewMarkdown, parseDeliveryReview } from "../node_modules/.cache/delivery-review/data/delivery-review.js";
import { deliveryReviewCopy } from "../node_modules/.cache/delivery-review/features/personal-workspace/delivery-review-copy.js";
import { acceptance, contract, snapshot, verifiedContract } from "./goal-acceptance-contract-fixture.mjs";

const withContract = value => ({ ...snapshot, acceptance: { ...acceptance, goal_acceptance_contract: value } });
const before = structuredClone(contract);
for (const copy of Object.values(deliveryReviewCopy)) {
const baseline = deliveryReviewMarkdown(parseDeliveryReview(snapshot, snapshot.goal_id), copy);
for (const value of [undefined, { enabled: false }, { ...verifiedContract("accepted"), enabled: false }]) {
assert.equal(deliveryReviewMarkdown(parseDeliveryReview(withContract(value), snapshot.goal_id), copy), baseline,
"Missing/disabled contract must preserve baseline export even with retained success data");
}
for (const status of ["unverified", "failed", "stale", "accepted", "partial", "held"]) {
const source = withContract(verifiedContract(status));
const parsed = parseDeliveryReview(source, snapshot.goal_id);
assert.deepEqual(parsed.acceptance.goal_acceptance_contract, source.acceptance.goal_acceptance_contract);
const markdown = deliveryReviewMarkdown(parsed, copy);
for (const text of [copy.contract.boundary, contract.objective, contract.digest, `${copy.contract.revision}: 7`,
`${copy.contract.source}: release-demo`, copy.contract.taskState.ready, copy.contract.taskState.stale,
copy.contract.notApplicable, copy.contract.verificationState[status]]) {
assert.ok(markdown.includes(text), `Missing readback: ${text}`);
}
if (status !== "accepted") assert.ok(!markdown.includes(copy.contract.verificationState.accepted), "Association readiness must not imply passing checks");
if (status === "held") assert.ok(markdown.includes(copy.contract.taskState.unbound));
if (status === "stale") assert.ok(markdown.includes("c".repeat(64)) && markdown.includes(`${copy.contract.revision}: 6`), "Historical verification must retain its distinct basis");
if (status === "partial") assert.ok(markdown.includes(`${copy.contract.verificationScope}: todo\\_confirmed`));
}
const empty = parseDeliveryReview(withContract({ ...contract, criteria: [], tasks: [] }), snapshot.goal_id);
assert.ok(deliveryReviewMarkdown(empty, copy).includes(copy.contract.noTasks));
assert.ok(deliveryReviewMarkdown(empty, copy).includes(copy.contract.noCriteria));
}
for (const mutation of [
{ ...contract, enabled: "true" }, { ...contract, revision: "7" }, { ...contract, revision: 1.5 },
{ ...contract, digest: "" }, { ...contract, tasks: [{ ...contract.tasks[0], state: "approved" }] },
{ ...contract, status: "approved" }, { ...contract, verification: { status: "passed" } },
]) assert.throws(() => parseDeliveryReview(withContract(mutation), snapshot.goal_id));
assert.throws(() => parseDeliveryReview(withContract(contract), "other-goal"));
assert.throws(() => parseDeliveryReview({ ...withContract(contract), acceptance: { ...acceptance, goal_id: "other-goal", goal_acceptance_contract: contract } }, snapshot.goal_id));
const next = parseDeliveryReview(withContract({ ...contract, revision: 8, digest: "b".repeat(64) }), snapshot.goal_id);
assert.equal(next.acceptance.goal_acceptance_contract.revision, 8);
assert.notEqual(next.acceptance.goal_acceptance_contract.digest, contract.digest);
assert.deepEqual(contract, before, "Rendering must not mutate the supplied contract");
console.log("Goal acceptance contract API/export: off parity, association/verification distinction, source/revision and negative cases passed");
Loading
Loading