From 9950a5f6f6b5fed66d5619f88eb6fe6d28fc4a34 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 01:06:26 +0530
Subject: [PATCH 001/143] docs(progress): reconcile release candidate state
---
docs/PROGRESS.md | 67 ++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 56 insertions(+), 11 deletions(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index a07aa816..a6a3986b 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -13,17 +13,23 @@ pass is never presented as a browser or production pass.
- **Branch:** `feat/learner-ux-v6.1`
- **Current product checkpoint:**
- `d6c4aec6c3f32b264aef8a90e1f9c8fa148b77f4`. At
- `2026-07-20T19:17:12Z`, 28 hours, 42 minutes, and 48 seconds remained. The
- newest independent commits are `b9dd722` for retry-safe notebook intake and
- `d6c4aec` for source-bound recovery from terminal learner responses. The
- exact staged restart snapshot passed App **55/55**, client API **32/32**,
- Worker API **90/90**, and session core **29/29** tests; repository, web, and
- Worker strict TypeScript; scoped Prettier; Git whitespace; and an eight-file
- secret scan. Two independent review axes found no source blocker. These are
- local source results only. No current CloakBrowser journey, exact image,
- qualification receipt, deployment, production smoke, learner observation,
- Devpost publication, submission receipt, or merge is inferred.
+ `134c22ff21afefb82c4e1bf0a51357254ca9a306`. The owner partitioned the
+ preserved integration delta into nine commits and fast-forwarded both
+ `feat/learner-ux-v6.1` and `main` to this candidate before qualification. The
+ lead switched back to the feature branch for small independent fix-forward
+ commits; the existing `main` ref is an unqualified candidate, not release
+ evidence. At `2026-07-20T19:33:40Z`, 28 hours, 26 minutes, and 20 seconds
+ remained. Current local source verification passes web Vitest **605/605**,
+ repository/web/Worker strict TypeScript, and the expanded Python matrix
+ **323/324**; the sole Python failure is denied localhost socket creation in
+ the managed sandbox. Root Vitest passes **619/646**; 24 failures are denied
+ process/socket/sandbox capabilities and three are stale scientific-evidence
+ expectations. The source-bound SBOM and scientific manifests still name
+ source `6f551367`, not this checkpoint, so qualification and deployment are
+ prohibited until the final source freezes and all evidence is regenerated.
+ No current CloakBrowser journey, exact image, qualification receipt,
+ deployment, production smoke, learner observation, Devpost publication, or
+ submission receipt is inferred.
- **Finish-command starting checkpoint:** committed `HEAD`
`24262ea6f40e988d7be054bdba6f531531b1679f` plus the explicitly preserved
dirty slices listed by `git status`. At `2026-07-19T15:18:04Z`, the
@@ -2806,3 +2812,42 @@ browser proof, submission, and the final merge remain serialized.
8. Merge the reviewed feature branch into `main` only after the release and
submission state is truthfully recorded. No deployment, submission, or
merge is claimed at this checkpoint.
+
+## Finish Commander reconciliation — 2026-07-20T19:33:40Z
+
+### Completed and frozen
+
+- `0509833 fix(api): bound stalled client requests`
+- `51d989f fix(admission): fence retry lease releases`
+- `b9dd722 fix(upload): reconcile retryable notebook intake`
+- `d6c4aec fix(session): reconcile terminal investigation restart`
+- The owner partitioned the remaining preserved integration delta into nine
+ commits ending at `134c22f`; web Vitest passes **77/77 files, 605/605 tests**,
+ repository/web/Worker TypeScript passes, and the expanded kernel, runner,
+ secret, smoke, and OCI-normalization Python matrix passes **323/324**. The
+ one Python failure is `test_kernel_service_exposes_health_and_only_the_fixed_public_run`
+ because this managed sandbox rejects an IPv4 socket with `EPERM`.
+
+### Open release-critical graph
+
+```text
+truthful live-readiness gate + accessibility/publication hygiene
+ -> focused tests and small independent commits
+ -> complete deterministic suites, build, format, whitespace, secrets
+ -> clean final source freeze
+ -> one exact runner image and real aggregate containment sentinel
+ -> regenerate source/image-bound SBOM, VEX, registry, and negative controls
+ -> issue exact qualification and release-check receipts
+ -> verify contained Wrangler identity and deploy only that tuple
+ -> public identity/smoke plus full CloakBrowser desktop/mobile matrix
+ -> truthful learner evidence or NO_DATA, video, Devpost, and receipt
+ -> fast-forward the verified feature tip into main
+```
+
+Current blockers are explicit: `PARTIAL` generation-filesystem isolation is
+still treated as live-ready in two web surfaces; generated scientific evidence
+is stale; one untracked FUSE placeholder prevents a clean tree; no aggregate
+cgroup-v2 enforcement receipt exists; and public browser, impact, video, and
+submission evidence have not occurred. Audit-agent attribution and a residual
+`Ctrl K` accessible-name defect are publication/accessibility repairs, not
+release proof. No blocker is closed by source code alone.
From 6d8d9c6f58ab44e1317118b484c70b710465b7f3 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 01:17:25 +0530
Subject: [PATCH 002/143] fix(learner): require enforced live isolation
---
apps/web/e2e/judged-flow.spec.ts | 38 +++++++++++++++++--
apps/web/src/App.test.tsx | 32 +++++++++++++---
apps/web/src/App.tsx | 6 ++-
apps/web/src/api.test.ts | 25 ++++++++++++
apps/web/src/api.ts | 2 +-
.../src/features/judge/JudgeModeView.test.tsx | 33 ++++++++++++++--
apps/web/src/features/judge/JudgeModeView.tsx | 10 +++--
7 files changed, 128 insertions(+), 18 deletions(-)
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index 114c8d2f..39fdc06b 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -1511,7 +1511,7 @@ test("Judge Mode distinguishes every authority path", async ({ page }) => {
health.data.liveCodex === "configured" &&
health.data.liveKernel === "configured" &&
health.data.sandbox === "credential-and-privilege-boundary" &&
- health.data.generationFilesystemReadIsolation === "PARTIAL" &&
+ health.data.generationFilesystemReadIsolation === "OS_ENFORCED" &&
health.data.release?.status === "bound";
await page.goto("/judge");
await expect(page).toHaveURL(/\/judge$/);
@@ -2191,6 +2191,38 @@ test("configured reasoning cannot start without a qualified hosted runner", asyn
);
});
+test("partial generation isolation cannot expose live notebook upload", async ({
+ page,
+}) => {
+ await page.route("**/api/health", async (route) => {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify({
+ ok: true,
+ data: {
+ platform: "cloudflare-workers",
+ sample: "available",
+ replay: "available",
+ liveGpt: "configured",
+ liveCodex: "configured",
+ liveKernel: "configured",
+ readiness: "ready",
+ sandbox: "credential-and-privilege-boundary",
+ generationFilesystemReadIsolation: "PARTIAL",
+ requestId: "e2e-health-partial-isolation",
+ },
+ }),
+ });
+ });
+
+ await openLiveSetup(page);
+ await expect(
+ page.getByText(/generation filesystem read isolation is partial/i),
+ ).toBeVisible();
+ await expect(page.locator('input[type="file"]')).toHaveCount(0);
+});
+
test("unsupported notebooks are parsed without execution and cannot advance", async ({
page,
}) => {
@@ -2382,7 +2414,7 @@ test("a configured hosted runner completes an untouched leakage notebook", async
liveCodex: "configured",
liveKernel: "configured",
sandbox: "credential-and-privilege-boundary",
- generationFilesystemReadIsolation: "PARTIAL",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
release: { status: "bound" },
});
@@ -2621,7 +2653,7 @@ test("a configured hosted runner completes an untouched class-imbalance notebook
liveCodex: "configured",
liveKernel: "configured",
sandbox: "credential-and-privilege-boundary",
- generationFilesystemReadIsolation: "PARTIAL",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
release: { status: "bound" },
});
diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
index b54ed4a1..467fde5c 100644
--- a/apps/web/src/App.test.tsx
+++ b/apps/web/src/App.test.tsx
@@ -418,6 +418,7 @@ function installApi(
options: {
liveGpt?: "configured" | "server-key-required";
runner?: "configured" | "local-runner-required";
+ generationIsolation?: "PARTIAL" | "OS_ENFORCED";
rejectLiveBelief?: boolean;
cancelOffline?: boolean;
beliefTest?: typeof liveBeliefTest | typeof imbalanceBeliefTest;
@@ -491,7 +492,9 @@ function installApi(
options.runner === "configured"
? "credential-and-privilege-boundary"
: "local-runner-required",
- generationFilesystemReadIsolation: "PARTIAL",
+ generationFilesystemReadIsolation:
+ options.generationIsolation ??
+ (options.runner === "configured" ? "OS_ENFORCED" : "PARTIAL"),
...(options.runner === "configured"
? {
release: {
@@ -1275,6 +1278,28 @@ describe("CounterLab judged flow", () => {
expect(document.body).not.toHaveTextContent(/local runner required/i);
});
+ it("keeps live notebook upload disabled when generation read isolation is partial", async () => {
+ const user = userEvent.setup();
+ installApi({
+ liveGpt: "configured",
+ runner: "configured",
+ generationIsolation: "PARTIAL",
+ });
+ render( );
+
+ await openLiveSetup(user);
+
+ expect(
+ await screen.findByText(/qualified hosted runner is needed/i),
+ ).toBeInTheDocument();
+ expect(document.body).toHaveTextContent(
+ /generation filesystem read isolation is partial.*not accepted as live authority/i,
+ );
+ expect(
+ screen.queryByLabelText(/attach a supported notebook/i),
+ ).not.toBeInTheDocument();
+ });
+
it("does not continue live setup until a supported notebook has been uploaded", async () => {
const user = userEvent.setup();
const fetcher = installApi({ liveGpt: "configured", runner: "configured" });
@@ -2176,10 +2201,7 @@ describe("CounterLab judged flow", () => {
};
window.localStorage.setItem("counterlab.sessionId", "session_ui");
window.localStorage.setItem("counterlab.claim", learnerClaim);
- window.localStorage.setItem(
- "counterlab.claimSessionId",
- "session_ui",
- );
+ window.localStorage.setItem("counterlab.claimSessionId", "session_ui");
window.localStorage.setItem("counterlab.mode", "live");
window.history.replaceState({}, "", "/session/session_ui");
const fetcher = installApi({
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 4b736c9d..822aa502 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -4107,7 +4107,7 @@ function LiveSetup({
health?.liveCodex === "configured" &&
health.liveKernel === "configured" &&
health.sandbox === "credential-and-privilege-boundary" &&
- health.generationFilesystemReadIsolation === "PARTIAL" &&
+ health.generationFilesystemReadIsolation === "OS_ENFORCED" &&
health.release?.status === "bound";
return (
@@ -4168,7 +4168,9 @@ function LiveSetup({
{runnerConfigured
? "Plans, verification, and fixed-kernel results can complete in this hosted session."
- : "You can review the notebook and make a prediction here. The checked experiment runs only on a separately protected device."}
+ : health?.generationFilesystemReadIsolation === "PARTIAL"
+ ? "Generation filesystem read isolation is partial and is not accepted as live authority. Use the verified sample or replay."
+ : "You can review the notebook and make a prediction here. The checked experiment runs only on a separately protected device."}
diff --git a/apps/web/src/api.test.ts b/apps/web/src/api.test.ts
index ea2b3119..bbb2b29d 100644
--- a/apps/web/src/api.test.ts
+++ b/apps/web/src/api.test.ts
@@ -662,6 +662,31 @@ describe("CounterLabApiClient", () => {
);
});
+ it("accepts an explicitly OS-enforced generation isolation claim", async () => {
+ const fetcher = vi.fn(async () =>
+ jsonResponse({
+ ok: true,
+ data: {
+ platform: "cloudflare-workers",
+ sample: "available",
+ replay: "available",
+ liveGpt: "configured",
+ liveCodex: "configured",
+ liveKernel: "configured",
+ readiness: "ready",
+ sandbox: "credential-and-privilege-boundary",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ requestId: "request_isolated",
+ },
+ }),
+ );
+ const client = new CounterLabApiClient({ fetch: fetcher });
+
+ await expect(client.getHealth()).resolves.toMatchObject({
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ });
+ });
+
it("rejects health responses that claim configured means available", async () => {
const fetcher = vi.fn(async () =>
jsonResponse({
diff --git a/apps/web/src/api.ts b/apps/web/src/api.ts
index 076b411e..738fa3d3 100644
--- a/apps/web/src/api.ts
+++ b/apps/web/src/api.ts
@@ -137,7 +137,7 @@ export const CapabilityHealthSchema = z
"credential-and-privilege-boundary",
"local-runner-required",
]),
- generationFilesystemReadIsolation: z.literal("PARTIAL"),
+ generationFilesystemReadIsolation: z.enum(["PARTIAL", "OS_ENFORCED"]),
requestId: NonEmptyString,
})
.strict();
diff --git a/apps/web/src/features/judge/JudgeModeView.test.tsx b/apps/web/src/features/judge/JudgeModeView.test.tsx
index b9304ab4..0dda267b 100644
--- a/apps/web/src/features/judge/JudgeModeView.test.tsx
+++ b/apps/web/src/features/judge/JudgeModeView.test.tsx
@@ -39,11 +39,16 @@ const configuredHealth: CapabilityHealth = {
requestId: "request_judge_1",
};
+const isolatedHealth: CapabilityHealth = {
+ ...configuredHealth,
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+};
+
describe("JudgeModeView", () => {
it("distinguishes sample, live, and legacy replay authority", async () => {
render(
{
);
});
+ it("does not offer live authority for partial generation read isolation", () => {
+ render(
+ ,
+ );
+
+ expect(
+ screen.queryByRole("link", { name: /run an unprimed live test/i }),
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByRole("link", { name: /run live/i }),
+ ).not.toBeInTheDocument();
+ expect(document.body).toHaveTextContent(
+ /generation filesystem read isolation is partial.*live authority remains unavailable/i,
+ );
+ });
+
it("labels the primed sample as a disclosed walkthrough", async () => {
const user = userEvent.setup();
const onStartSample = vi.fn();
@@ -229,7 +256,7 @@ describe("JudgeModeView", () => {
it("does not equate configured services with observed readiness", () => {
render(
{
it("does not deep-probe or expose live entry before readiness is checked", () => {
render(
{liveReady ? (
From 5259a495948a68024702ba5239bf8057d1785f08 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 01:19:57 +0530
Subject: [PATCH 003/143] fix(a11y): simplify command trigger name
---
.../components/studio/ProjectSidebar.test.tsx | 40 +++++++++++++++++++
.../src/components/studio/ProjectSidebar.tsx | 4 +-
2 files changed, 43 insertions(+), 1 deletion(-)
create mode 100644 apps/web/src/components/studio/ProjectSidebar.test.tsx
diff --git a/apps/web/src/components/studio/ProjectSidebar.test.tsx b/apps/web/src/components/studio/ProjectSidebar.test.tsx
new file mode 100644
index 00000000..9f82c1de
--- /dev/null
+++ b/apps/web/src/components/studio/ProjectSidebar.test.tsx
@@ -0,0 +1,40 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it, vi } from "vitest";
+
+import { ProjectSidebar } from "./ProjectSidebar";
+import type { StudioContext } from "./types";
+
+const context: StudioContext = {
+ mode: "instant",
+ stage: "question-path",
+ artifact: null,
+ session: null,
+ events: [],
+};
+
+describe("ProjectSidebar", () => {
+ it("keeps the keyboard shortcut visible without adding it to the command name", async () => {
+ const user = userEvent.setup();
+ const onOpenCommands = vi.fn();
+
+ render(
+ ,
+ );
+
+ const trigger = screen.getByRole("button", { name: "Open commands" });
+ expect(trigger).toHaveTextContent("Ctrl K");
+ expect(trigger).not.toHaveAccessibleName(/ctrl k/i);
+
+ await user.click(trigger);
+ expect(onOpenCommands).toHaveBeenCalledOnce();
+ });
+});
diff --git a/apps/web/src/components/studio/ProjectSidebar.tsx b/apps/web/src/components/studio/ProjectSidebar.tsx
index 2af714cd..753ad5c7 100644
--- a/apps/web/src/components/studio/ProjectSidebar.tsx
+++ b/apps/web/src/components/studio/ProjectSidebar.tsx
@@ -111,9 +111,11 @@ export function ProjectSidebar({
- ⌘ Commands Ctrl K
+ ⌘ Commands{" "}
+ Ctrl K
);
From 22d48a527dfb4bc256416c5a6d861dc7dcad0c44 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 01:30:20 +0530
Subject: [PATCH 004/143] docs(progress): record browser design review
---
docs/PROGRESS.md | 77 ++++++++++++++++++++++++++++++++++++++----------
1 file changed, 61 insertions(+), 16 deletions(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index a6a3986b..9e8facbb 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -13,23 +13,23 @@ pass is never presented as a browser or production pass.
- **Branch:** `feat/learner-ux-v6.1`
- **Current product checkpoint:**
- `134c22ff21afefb82c4e1bf0a51357254ca9a306`. The owner partitioned the
+ `5259a495948a68024702ba5239bf8057d1785f08`. The owner partitioned the
preserved integration delta into nine commits and fast-forwarded both
- `feat/learner-ux-v6.1` and `main` to this candidate before qualification. The
- lead switched back to the feature branch for small independent fix-forward
- commits; the existing `main` ref is an unqualified candidate, not release
- evidence. At `2026-07-20T19:33:40Z`, 28 hours, 26 minutes, and 20 seconds
- remained. Current local source verification passes web Vitest **605/605**,
- repository/web/Worker strict TypeScript, and the expanded Python matrix
- **323/324**; the sole Python failure is denied localhost socket creation in
- the managed sandbox. Root Vitest passes **619/646**; 24 failures are denied
- process/socket/sandbox capabilities and three are stale scientific-evidence
- expectations. The source-bound SBOM and scientific manifests still name
- source `6f551367`, not this checkpoint, so qualification and deployment are
- prohibited until the final source freezes and all evidence is regenerated.
- No current CloakBrowser journey, exact image, qualification receipt,
- deployment, production smoke, learner observation, Devpost publication, or
- submission receipt is inferred.
+ `feat/learner-ux-v6.1` and `main` to `134c22f` before qualification. The lead
+ switched back to the feature branch and added seven independent fix-forward
+ commits through `5259a49`; the existing `main` ref remains an unqualified
+ earlier candidate, not release evidence. At `2026-07-20T19:33:40Z`, 28
+ hours, 26 minutes, and 20 seconds remained. Current local source verification
+ passes web Vitest **608/608**, repository/web/Worker/E2E strict TypeScript,
+ and the expanded Python product matrix **324/324** when the one localhost
+ socket test runs with the required socket permission. Elevated root Vitest
+ passes **643/646**; its remaining three failures are stale
+ scientific-evidence expectations. The source-bound SBOM and scientific
+ manifests still name source `6f551367`, not this checkpoint, so
+ qualification and deployment are prohibited until the final source freezes
+ and all evidence is regenerated. No current CloakBrowser journey, exact
+ image, qualification receipt, deployment, production smoke, learner
+ observation, Devpost publication, or submission receipt is inferred.
- **Finish-command starting checkpoint:** committed `HEAD`
`24262ea6f40e988d7be054bdba6f531531b1679f` plus the explicitly preserved
dirty slices listed by `git status`. At `2026-07-19T15:18:04Z`, the
@@ -2851,3 +2851,48 @@ cgroup-v2 enforcement receipt exists; and public browser, impact, video, and
submission evidence have not occurred. Audit-agent attribution and a residual
`Ctrl K` accessible-name defect are publication/accessibility repairs, not
release proof. No blocker is closed by source code alone.
+
+## Finish Commander browser and authority checkpoint — 2026-07-20
+
+### Newly frozen independent commits
+
+- `6d8d9c6 fix(learner): require enforced live isolation` makes Live visible
+ and selectable only when health reports `OS_ENFORCED` generation-filesystem
+ isolation. The current Worker truthfully reports `PARTIAL`, so its Live path
+ is intentionally unavailable instead of being advertised as production
+ ready. Focused App/API/Judge coverage passed **117/117** and the complete web
+ suite passed **608/608**.
+- `5259a49 fix(a11y): simplify command trigger name` keeps the visible
+ `Ctrl K` shortcut while exposing the stable accessible name `Open commands`.
+ Its focused component test passed **1/1**.
+
+### Labelled design-review evidence
+
+The checked-in Playwright authority guard was used with the explicit
+`stock-chromium-design-review` mode because this session has no
+`CLOAK_CDP_ENDPOINT`. Desktop `1440x900` and mobile `390x844` Landing and Judge
+first-fold checks passed **4/4** in **16.0 seconds**, including first-viewport
+visibility, pre-Prediction value hiding, horizontal-overflow checks, and zero
+console, page, request, or HTTP-response failures. Captures are retained under
+`apps/web/test-results/runtime/design-review-20260720/evidence/belief-break/`.
+They are design-review artifacts only and are not CloakBrowser or release
+qualification evidence.
+
+### Current remaining gates
+
+1. Finish the deduplicated P0/P1 source review and any small independent
+ repairs, while keeping Live disabled unless a genuine OS-enforced boundary
+ is proved.
+2. Resolve the untracked in-repository FUSE placeholder through the required
+ immediate deletion approval so the source can become clean.
+3. Freeze one exact source, rebuild once, regenerate all source/image-bound
+ scientific and supply-chain evidence, and pass the real containment and
+ negative-control gates.
+4. Deploy only a qualified tuple, then run the public sample/replay and any
+ genuinely ready Live journey through CloakBrowser. The labelled Chrome
+ captures above cannot close this gate.
+5. Keep learner impact `NO_DATA` until consented observations exist; align the
+ exact app, repository, video, Devpost entry, feedback identifier, and
+ submission receipt before final publication.
+6. Push the finished feature branch and fast-forward its verified tip into
+ `main` only after the preceding gates are truthfully recorded.
From 2b94c9d0d0aa2b34c5a7bbf1263b30cb1001a27d Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 02:19:21 +0530
Subject: [PATCH 005/143] feat(runner): enforce Bubblewrap generation boundary
---
Dockerfile.runner | 6 +-
apps/web/worker/runner-container-env.test.ts | 2 +
apps/web/worker/runner-container-env.ts | 2 +
packages/codex-client/src/app-server.ts | 52 ++-
packages/codex-client/src/index.test.ts | 21 +-
.../hosted-runner/src/launch-boundary.test.ts | 285 ++++++++++-
services/hosted-runner/src/launch-boundary.ts | 442 +++++++++++++++---
services/hosted-runner/src/server.test.ts | 2 +
services/hosted-runner/src/server.ts | 11 +
.../hosted-runner/src/startup-probe.test.ts | 39 +-
services/hosted-runner/src/startup-probe.ts | 34 +-
11 files changed, 779 insertions(+), 117 deletions(-)
diff --git a/Dockerfile.runner b/Dockerfile.runner
index 9d97bbc1..d4faff0e 100644
--- a/Dockerfile.runner
+++ b/Dockerfile.runner
@@ -69,8 +69,12 @@ ENV DEBIAN_FRONTEND=noninteractive \
COPY --from=node-builder /usr/local/bin/node /usr/local/bin/node
-RUN chmod 0555 /usr/local/bin/node \
+RUN apt-get update \
+ && apt-get install --yes --no-install-recommends bubblewrap=0.11.0-2+deb13u1 \
+ && chmod 0555 /usr/local/bin/node \
&& test -x /usr/local/bin/node \
+ && test "$(/usr/bin/bwrap --version)" = "bubblewrap 0.11.0" \
+ && test -x /usr/bin/bwrap \
&& test -x /usr/sbin/groupadd \
&& test -x /usr/sbin/useradd \
&& test -x /usr/bin/setpriv \
diff --git a/apps/web/worker/runner-container-env.test.ts b/apps/web/worker/runner-container-env.test.ts
index eab5d9ff..278ea4b1 100644
--- a/apps/web/worker/runner-container-env.test.ts
+++ b/apps/web/worker/runner-container-env.test.ts
@@ -27,7 +27,9 @@ describe("runner Container environment", () => {
PORT: "8080",
COUNTERLAB_RUNNER_WORK_ROOT: "/tmp/counterlab-jobs",
COUNTERLAB_CODEX_HOME_ROOT: "/tmp/counterlab-codex",
+ COUNTERLAB_CODEX_ROOT: "/opt/codex",
COUNTERLAB_CODEX_EXECUTABLE: "/usr/local/bin/codex",
+ COUNTERLAB_BWRAP_EXECUTABLE: "/usr/bin/bwrap",
COUNTERLAB_SETPRIV_EXECUTABLE: "/usr/bin/setpriv",
COUNTERLAB_PYTHON_EXECUTABLE: "/opt/counterlab-venv/bin/python",
COUNTERLAB_CODEX_UID: "10001",
diff --git a/apps/web/worker/runner-container-env.ts b/apps/web/worker/runner-container-env.ts
index a1d7bea8..5d4731fe 100644
--- a/apps/web/worker/runner-container-env.ts
+++ b/apps/web/worker/runner-container-env.ts
@@ -25,7 +25,9 @@ export function createRunnerContainerEnvVars(
PORT: "8080",
COUNTERLAB_RUNNER_WORK_ROOT: "/tmp/counterlab-jobs",
COUNTERLAB_CODEX_HOME_ROOT: "/tmp/counterlab-codex",
+ COUNTERLAB_CODEX_ROOT: "/opt/codex",
COUNTERLAB_CODEX_EXECUTABLE: "/usr/local/bin/codex",
+ COUNTERLAB_BWRAP_EXECUTABLE: "/usr/bin/bwrap",
COUNTERLAB_SETPRIV_EXECUTABLE: "/usr/bin/setpriv",
COUNTERLAB_PYTHON_EXECUTABLE: "/opt/counterlab-venv/bin/python",
COUNTERLAB_CODEX_UID: "10001",
diff --git a/packages/codex-client/src/app-server.ts b/packages/codex-client/src/app-server.ts
index e523e7b0..d718684e 100644
--- a/packages/codex-client/src/app-server.ts
+++ b/packages/codex-client/src/app-server.ts
@@ -709,6 +709,7 @@ function parseInput(schema: z.ZodType, value: unknown): T {
class AppServerConnection {
private readonly process: ChildProcessWithoutNullStreams;
+ private readonly terminated: Promise;
private readonly pending = new Map();
private readonly events = new AsyncEventQueue();
private stdoutBuffer = "";
@@ -737,6 +738,9 @@ class AppServerConnection {
} catch (error) {
throw asSetupError(error);
}
+ this.terminated = new Promise((resolve) => {
+ this.process.once("close", () => resolve());
+ });
this.process.stdout.setEncoding("utf8");
this.process.stderr.setEncoding("utf8");
@@ -794,20 +798,22 @@ class AppServerConnection {
: undefined;
}
- close(): void {
- if (this.closed) return;
- this.closed = true;
- for (const request of this.pending.values()) {
- clearTimeout(request.timer);
- request.reject(
- new CompilerSetupError(
- "CODEX_PROCESS_EXITED",
- "Codex App Server connection closed.",
- ),
- );
+ async close(): Promise {
+ if (!this.closed) {
+ this.closed = true;
+ for (const request of this.pending.values()) {
+ clearTimeout(request.timer);
+ request.reject(
+ new CompilerSetupError(
+ "CODEX_PROCESS_EXITED",
+ "Codex App Server connection closed.",
+ ),
+ );
+ }
+ this.pending.clear();
+ this.terminateProcess();
}
- this.pending.clear();
- this.terminateProcess();
+ await this.awaitTermination();
}
cancel(): void {
@@ -964,6 +970,24 @@ class AppServerConnection {
timer.unref();
}
+ private async awaitTermination(): Promise {
+ await new Promise((resolve, reject) => {
+ const timer = setTimeout(() => {
+ reject(
+ new CompilerSetupError(
+ "CODEX_PROCESS_EXITED",
+ "Codex App Server did not terminate after forced shutdown.",
+ ),
+ );
+ }, 5_000);
+ timer.unref();
+ void this.terminated.then(() => {
+ clearTimeout(timer);
+ resolve();
+ });
+ });
+ }
+
private killProcessTree(signal: NodeJS.Signals): void {
const pid = this.process.pid;
if (process.platform !== "win32" && pid !== undefined) {
@@ -1470,7 +1494,7 @@ export class AppServerCodexCompiler implements CodexCompiler {
throw asSetupError(error);
} finally {
signal?.removeEventListener("abort", cancel);
- connection.close();
+ await connection.close();
await launch.dispose?.();
}
}
diff --git a/packages/codex-client/src/index.test.ts b/packages/codex-client/src/index.test.ts
index 56fec952..2d1f5e75 100644
--- a/packages/codex-client/src/index.test.ts
+++ b/packages/codex-client/src/index.test.ts
@@ -1102,6 +1102,7 @@ describe("AppServerCodexCompiler stdio transport", () => {
const work = await mkdtemp(join(tmpdir(), "counterlab-codex-cancel-"));
const pidFile = join(work, "app-server.pid");
let processId: number | undefined;
+ let disposedAfterExit = false;
try {
const compiler = new AppServerCodexCompiler({
command: process.execPath,
@@ -1111,7 +1112,23 @@ describe("AppServerCodexCompiler stdio transport", () => {
`--ignore-sigterm=${pidFile}`,
],
timeoutMs: 5_000,
- ...unisolatedTestProcess,
+ launchBoundary: {
+ async health() {
+ return { available: true as const };
+ },
+ async prepare(request) {
+ return {
+ command: request.command,
+ args: request.args,
+ environment: request.environment,
+ protocolCwd: request.hostCwd,
+ async dispose() {
+ expect(() => process.kill(processId!, 0)).toThrow();
+ disposedAfterExit = true;
+ },
+ };
+ },
+ },
});
const controller = new AbortController();
const compilation = collect(
@@ -1125,8 +1142,8 @@ describe("AppServerCodexCompiler stdio transport", () => {
name: "CompilerSetupError",
code: "CODEX_CANCELLED",
});
- await new Promise((resolve) => setTimeout(resolve, 350));
expect(() => process.kill(processId!, 0)).toThrow();
+ expect(disposedAfterExit).toBe(true);
} finally {
if (processId !== undefined) {
try {
diff --git a/services/hosted-runner/src/launch-boundary.test.ts b/services/hosted-runner/src/launch-boundary.test.ts
index 4de1c9f6..ee2dc215 100644
--- a/services/hosted-runner/src/launch-boundary.test.ts
+++ b/services/hosted-runner/src/launch-boundary.test.ts
@@ -1,12 +1,28 @@
-import { mkdtemp, readFile, rm, stat } from "node:fs/promises";
+import { execFile } from "node:child_process";
+import {
+ mkdir,
+ mkdtemp,
+ readFile,
+ readdir,
+ rm,
+ stat,
+ symlink,
+ writeFile,
+} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { promisify } from "node:util";
import { afterEach, describe, expect, it } from "vitest";
-import { ContainerCodexLaunchBoundary } from "./launch-boundary.js";
+import {
+ buildContainerBubblewrapLaunch,
+ buildContainerBubblewrapProbe,
+ ContainerCodexLaunchBoundary,
+} from "./launch-boundary.js";
const roots: string[] = [];
+const execFileAsync = promisify(execFile);
afterEach(async () => {
await Promise.all(
@@ -25,24 +41,28 @@ describe("ContainerCodexLaunchBoundary", () => {
const workspaceRoot = await root();
const workspace = join(workspaceRoot, "job_1");
const codexHomeRoot = await root();
+ const codexRoot = await root();
+ const codexExecutable = join(codexRoot, "bin", "codex");
+ await mkdir(join(codexRoot, "bin"), { recursive: true });
+ await writeFile(codexExecutable, "#!/bin/sh\n", { mode: 0o700 });
const boundary = new ContainerCodexLaunchBoundary({
authJson: JSON.stringify({
tokens: { access_token: "access-token-long-enough-for-test" },
}),
workspaceRoot,
codexHomeRoot,
- codexExecutable: process.execPath,
+ codexRoot,
+ codexExecutable,
+ bwrapExecutable: "/usr/bin/bwrap",
setprivExecutable: "/usr/bin/setpriv",
uid: process.getuid?.() ?? 1000,
gid: process.getgid?.() ?? 1000,
});
- await import("node:fs/promises").then(({ mkdir }) =>
- mkdir(workspace, { mode: 0o700 }),
- );
+ await mkdir(workspace, { mode: 0o700 });
await expect(boundary.health()).resolves.toEqual({ available: true });
const prepared = await boundary.prepare({
- command: process.execPath,
+ command: codexExecutable,
args: ["app-server", "--stdio"],
environment: {
PATH: process.env.PATH,
@@ -60,45 +80,86 @@ describe("ContainerCodexLaunchBoundary", () => {
}
expect((await stat(workspace)).mode & 0o777).toBe(0o700);
- expect(prepared.command).toBe("/usr/bin/setpriv");
+ expect(prepared.command).toBe("/usr/bin/bwrap");
+ expect(prepared.args).toContain("--unshare-user");
+ expect(prepared.args).toContain("--unshare-pid");
+ expect(prepared.args).toContain("--unshare-ipc");
+ expect(prepared.args).toContain("--unshare-uts");
+ expect(prepared.args).toContain("--clearenv");
+ expect(prepared.args).toContain("--cap-drop");
expect(prepared.args).toContain("--no-new-privs");
- expect(prepared.args).not.toContain(
- `--reuid=${process.getuid?.() ?? 1000}`,
- );
- expect(prepared.args).not.toContain(
- `--regid=${process.getgid?.() ?? 1000}`,
- );
- expect(prepared.args).not.toContain("--clear-groups");
- expect(prepared.args).toContain(process.execPath);
+ expect(prepared.args).toContain("/opt/codex/bin/codex");
expect(prepared.environment).not.toHaveProperty("CODEX_AUTH_JSON");
expect(prepared.environment).not.toHaveProperty("OPENAI_API_KEY");
expect(prepared.environment).not.toHaveProperty("CODEX_ACCESS_TOKEN");
- expect(prepared.environment.CODEX_HOME).toMatch(/counterlab-codex-/u);
- expect(prepared.environment.TMPDIR).toMatch(/counterlab-codex-.*\/tmp$/u);
- expect(prepared.environment.TMPDIR).not.toBe(workspace);
- expect(
- (await stat(prepared.environment.TMPDIR ?? "missing")).mode & 0o777,
- ).toBe(0o700);
- const authPath = join(prepared.environment.CODEX_HOME ?? "", "auth.json");
+ expect(prepared.environment.CODEX_HOME).toBe("/home/counterlab");
+ expect(prepared.environment.TMPDIR).toBe("/tmp");
+ const guestAuthIndex = prepared.args.indexOf("/home/counterlab/auth.json");
+ const authPath = prepared.args[guestAuthIndex - 1] ?? "missing";
+ expect(authPath).toMatch(/counterlab-codex-.*\.auth\.json$/u);
expect(await readFile(authPath, "utf8")).toContain("access_token");
+ const workspaceIndex = prepared.args.indexOf(workspace);
+ expect(prepared.args.slice(workspaceIndex - 1, workspaceIndex + 2)).toEqual(
+ ["--bind", workspace, "/workspace"],
+ );
expect(prepared.args).toContain("--strict-config");
expect(prepared.args).toContain("shell_environment_policy.inherit=none");
expect(prepared.args).toContain(
'shell_environment_policy.exclude=["CODEX_ACCESS_TOKEN","OPENAI_API_KEY"]',
);
await prepared.revokeCredentials?.();
+ await expect(readFile(authPath, "utf8")).resolves.toBe("");
+ await prepared.revokeCredentials?.();
+ await prepared.dispose?.();
await expect(readFile(authPath, "utf8")).rejects.toThrow();
+ expect(await readdir(codexHomeRoot)).toEqual([]);
await prepared.dispose?.();
});
+ it("rejects a requested executable that differs from the configured Codex binary", async () => {
+ const workspaceRoot = await root();
+ const workspace = join(workspaceRoot, "job_1");
+ const codexRoot = await root();
+ const codexExecutable = join(codexRoot, "codex");
+ await mkdir(workspace);
+ await writeFile(codexExecutable, "#!/bin/sh\n", { mode: 0o700 });
+ const boundary = new ContainerCodexLaunchBoundary({
+ authJson: JSON.stringify({
+ tokens: { access_token: "access-token-long-enough-for-test" },
+ }),
+ workspaceRoot,
+ codexHomeRoot: await root(),
+ codexRoot,
+ codexExecutable,
+ bwrapExecutable: "/usr/bin/bwrap",
+ setprivExecutable: "/usr/bin/setpriv",
+ uid: process.getuid?.() ?? 1000,
+ gid: process.getgid?.() ?? 1000,
+ });
+
+ await expect(
+ boundary.prepare({
+ command: process.execPath,
+ args: ["app-server", "--stdio"],
+ environment: { PATH: process.env.PATH },
+ hostCwd: workspace,
+ }),
+ ).rejects.toMatchObject({ code: "CODEX_ISOLATION_UNAVAILABLE" });
+ });
+
it("rejects a generation directory outside the configured workspace root", async () => {
+ const codexRoot = await root();
+ const codexExecutable = join(codexRoot, "codex");
+ await writeFile(codexExecutable, "#!/bin/sh\n", { mode: 0o700 });
const boundary = new ContainerCodexLaunchBoundary({
authJson: JSON.stringify({
tokens: { access_token: "access-token-long-enough-for-test" },
}),
workspaceRoot: await root(),
codexHomeRoot: await root(),
- codexExecutable: process.execPath,
+ codexRoot,
+ codexExecutable,
+ bwrapExecutable: "/usr/bin/bwrap",
setprivExecutable: "/usr/bin/setpriv",
uid: process.getuid?.() ?? 1000,
gid: process.getgid?.() ?? 1000,
@@ -113,4 +174,180 @@ describe("ContainerCodexLaunchBoundary", () => {
}),
).rejects.toMatchObject({ code: "CODEX_ISOLATION_UNAVAILABLE" });
});
+
+ it("rejects a symlinked generation directory", async () => {
+ const workspaceRoot = await root();
+ const workspace = join(workspaceRoot, "job_real");
+ const linkedWorkspace = join(workspaceRoot, "job_link");
+ const codexRoot = await root();
+ const codexExecutable = join(codexRoot, "codex");
+ await mkdir(workspace);
+ await symlink(workspace, linkedWorkspace);
+ await writeFile(codexExecutable, "#!/bin/sh\n", { mode: 0o700 });
+ const boundary = new ContainerCodexLaunchBoundary({
+ authJson: JSON.stringify({
+ tokens: { access_token: "access-token-long-enough-for-test" },
+ }),
+ workspaceRoot,
+ codexHomeRoot: await root(),
+ codexRoot,
+ codexExecutable,
+ bwrapExecutable: "/usr/bin/bwrap",
+ setprivExecutable: "/usr/bin/setpriv",
+ uid: process.getuid?.() ?? 1000,
+ gid: process.getgid?.() ?? 1000,
+ });
+
+ await expect(
+ boundary.prepare({
+ command: codexExecutable,
+ args: ["app-server", "--stdio"],
+ environment: { PATH: process.env.PATH },
+ hostCwd: linkedWorkspace,
+ }),
+ ).rejects.toMatchObject({ code: "CODEX_ISOLATION_UNAVAILABLE" });
+ });
+
+ it("rejects overlapping writable roots and executables outside the mounted system root", async () => {
+ const workspaceRoot = await root();
+ const codexHomeRoot = join(workspaceRoot, "codex-home");
+ const codexRoot = await root();
+ const codexExecutable = join(codexRoot, "codex");
+ await mkdir(codexHomeRoot);
+ await writeFile(codexExecutable, "#!/bin/sh\n", { mode: 0o700 });
+ const common = {
+ authJson: JSON.stringify({
+ tokens: { access_token: "access-token-long-enough-for-test" },
+ }),
+ workspaceRoot,
+ codexHomeRoot,
+ codexRoot,
+ codexExecutable,
+ bwrapExecutable: "/usr/bin/bwrap",
+ uid: process.getuid?.() ?? 1000,
+ gid: process.getgid?.() ?? 1000,
+ };
+
+ await expect(
+ new ContainerCodexLaunchBoundary({
+ ...common,
+ setprivExecutable: "/usr/bin/setpriv",
+ }).health(),
+ ).resolves.toMatchObject({ available: false });
+ await expect(
+ new ContainerCodexLaunchBoundary({
+ ...common,
+ codexHomeRoot: await root(),
+ setprivExecutable: codexExecutable,
+ }).health(),
+ ).resolves.toMatchObject({ available: false });
+ });
+
+ it("builds a fixed mount allowlist with no inherited secret environment", () => {
+ const plan = buildContainerBubblewrapLaunch({
+ appServerArgs: ["app-server", "--stdio"],
+ bwrapExecutable: "/usr/bin/bwrap",
+ codexExecutable: "/opt/counterlab-codex/bin/codex",
+ codexRoot: "/opt/counterlab-codex",
+ setprivExecutable: "/usr/bin/setpriv",
+ stagedAuthFile: "/run/counterlab-codex/job_1.auth.json",
+ workspace: "/work/jobs/job_1",
+ });
+
+ expect(plan.mountedHostPaths).toEqual([
+ "/usr",
+ "/etc/ca-certificates",
+ "/etc/hosts",
+ "/etc/nsswitch.conf",
+ "/etc/resolv.conf",
+ "/etc/ssl",
+ "/opt/counterlab-codex",
+ "/run/counterlab-codex/job_1.auth.json",
+ "/work/jobs/job_1",
+ ]);
+ expect(plan.mountedHostPaths).not.toContain("/app");
+ expect(plan.mountedHostPaths).not.toContain("/repo");
+ expect(plan.mountedHostPaths).not.toContain("/opt/counterlab-venv");
+ expect(plan.environment).toEqual({
+ CODEX_HOME: "/home/counterlab",
+ HOME: "/home/counterlab",
+ LANG: "C.UTF-8",
+ PATH: "/usr/bin",
+ TMPDIR: "/tmp",
+ });
+ expect(plan.environment).not.toHaveProperty("CODEX_AUTH_JSON");
+ expect(plan.environment).not.toHaveProperty("CODEX_ACCESS_TOKEN");
+ expect(plan.environment).not.toHaveProperty("OPENAI_API_KEY");
+ });
+
+ it.runIf(process.env.COUNTERLAB_REAL_BWRAP_PROBE === "1")(
+ "proves the real Bubblewrap boundary hides host roots and permits bounded workspace writes",
+ async () => {
+ const workspace = await root();
+ const codexRoot = await root();
+ await mkdir(join(codexRoot, "bin"), { recursive: true });
+ await writeFile(join(codexRoot, "bin", "codex"), "#!/bin/sh\n", {
+ mode: 0o700,
+ });
+ await writeFile(join(workspace, "approved.txt"), "approved\n", {
+ mode: 0o600,
+ });
+ const plan = buildContainerBubblewrapProbe({
+ bwrapExecutable: "/usr/bin/bwrap",
+ codexRoot,
+ workspace,
+ });
+
+ const { stdout } = await execFileAsync(plan.command, plan.args, {
+ env: plan.environment,
+ timeout: 30_000,
+ });
+ expect(JSON.parse(stdout)).toEqual({
+ forbiddenHostPathsHidden: true,
+ parentEnvironmentHidden: true,
+ workspaceVisible: true,
+ workspaceWritable: true,
+ });
+ },
+ );
+
+ it.runIf(process.env.COUNTERLAB_REAL_BWRAP_PROBE === "1")(
+ "proves the real Bubblewrap launch invokes setpriv and the mounted Codex binary",
+ async () => {
+ const workspace = await root();
+ const codexRoot = await root();
+ const authRoot = await root();
+ const stagedAuthFile = join(authRoot, "staged-auth.json");
+ await mkdir(join(codexRoot, "bin"), { recursive: true });
+ await writeFile(
+ join(codexRoot, "bin", "codex"),
+ "#!/bin/sh\nset -eu\ntest -s /home/counterlab/auth.json\nprintf 'generated\\n' > /workspace/generated.txt\nprintf '{\"authVisibleToAppServer\":true,\"workspaceWritable\":true}\\n'\n",
+ { mode: 0o700 },
+ );
+ await writeFile(stagedAuthFile, '{"tokens":{"access_token":"test"}}', {
+ mode: 0o600,
+ });
+ const plan = buildContainerBubblewrapLaunch({
+ appServerArgs: ["app-server", "--stdio"],
+ bwrapExecutable: "/usr/bin/bwrap",
+ codexExecutable: join(codexRoot, "bin", "codex"),
+ codexRoot,
+ setprivExecutable: "/usr/bin/setpriv",
+ stagedAuthFile,
+ workspace,
+ });
+
+ const { stdout } = await execFileAsync(plan.command, plan.args, {
+ env: plan.environment,
+ timeout: 30_000,
+ });
+ expect(JSON.parse(stdout)).toEqual({
+ authVisibleToAppServer: true,
+ workspaceWritable: true,
+ });
+ expect(await readFile(join(workspace, "generated.txt"), "utf8")).toBe(
+ "generated\n",
+ );
+ },
+ );
});
diff --git a/services/hosted-runner/src/launch-boundary.ts b/services/hosted-runner/src/launch-boundary.ts
index 6cf0cf49..cadc7952 100644
--- a/services/hosted-runner/src/launch-boundary.ts
+++ b/services/hosted-runner/src/launch-boundary.ts
@@ -1,13 +1,14 @@
import { constants } from "node:fs";
+import { randomUUID } from "node:crypto";
import {
access,
chmod,
- chown,
- mkdir,
- mkdtemp,
+ lstat,
+ open,
+ readFile,
realpath,
- rm,
stat,
+ unlink,
writeFile,
} from "node:fs/promises";
import { isAbsolute, join, relative, resolve } from "node:path";
@@ -31,17 +32,57 @@ const CodexAuthSchema = z
const CREDENTIAL_EXCLUSIONS =
'shell_environment_policy.exclude=["CODEX_ACCESS_TOKEN","OPENAI_API_KEY"]';
+const GUEST_CODEX_ROOT = "/opt/codex";
+const GUEST_CODEX_HOME = "/home/counterlab";
+const GUEST_WORKSPACE = "/workspace";
+const NETWORK_RUNTIME_MOUNTS = [
+ "/etc/ca-certificates",
+ "/etc/hosts",
+ "/etc/nsswitch.conf",
+ "/etc/resolv.conf",
+ "/etc/ssl",
+] as const;
export type ContainerCodexLaunchBoundaryOptions = {
authJson: string;
workspaceRoot: string;
codexHomeRoot: string;
+ codexRoot: string;
codexExecutable: string;
+ bwrapExecutable: string;
setprivExecutable: string;
+ ptraceScopePath?: string;
uid: number;
gid: number;
};
+export type ContainerBubblewrapLaunchPlan = PreparedAppServerLaunch & {
+ mountedHostPaths: string[];
+};
+
+type BuildContainerBubblewrapLaunchOptions = {
+ appServerArgs: string[];
+ bwrapExecutable: string;
+ codexExecutable: string;
+ codexRoot: string;
+ setprivExecutable: string;
+ stagedAuthFile: string;
+ workspace: string;
+};
+
+export type BuildContainerBubblewrapProbeOptions = {
+ bwrapExecutable: string;
+ codexRoot: string;
+ workspace: string;
+};
+
+export type ContainerBubblewrapProbePlan = {
+ command: string;
+ args: string[];
+ environment: NodeJS.ProcessEnv;
+ mountedHostPaths: string[];
+};
+
function isolationError(message: string, cause?: unknown): CompilerSetupError {
return new CompilerSetupError("CODEX_ISOLATION_UNAVAILABLE", message, {
...(cause === undefined ? {} : { cause }),
@@ -57,17 +98,217 @@ function isContained(root: string, candidate: string): boolean {
);
}
-export class ContainerCodexLaunchBoundary implements AppServerLaunchBoundary {
- private readonly parsedAuth: string;
+function pathsOverlap(left: string, right: string): boolean {
+ return left === right || isContained(left, right) || isContained(right, left);
+}
- private get requiresPrivilegeDrop(): boolean {
- return (
- process.getuid?.() !== this.options.uid ||
- process.getgid?.() !== this.options.gid
+function requireAbsolutePath(label: string, value: string): void {
+ if (!isAbsolute(value) || value.includes("\0")) {
+ throw isolationError(`${label} must be an absolute path.`);
+ }
+}
+
+function guestCodexExecutable(codexRoot: string, codexExecutable: string) {
+ const fromRoot = relative(codexRoot, codexExecutable);
+ if (
+ fromRoot === "" ||
+ fromRoot.startsWith("..") ||
+ isAbsolute(fromRoot) ||
+ fromRoot.includes("\0")
+ ) {
+ throw isolationError(
+ "The Codex executable must resolve beneath the mounted Codex root.",
);
}
+ return `${GUEST_CODEX_ROOT}/${fromRoot}`;
+}
+
+function buildContainerBubblewrapBase(options: {
+ bwrapExecutable: string;
+ codexRoot: string;
+ stagedAuthFile?: string;
+ workspace: string;
+}): ContainerBubblewrapProbePlan {
+ for (const [label, value] of [
+ ["Bubblewrap executable", options.bwrapExecutable],
+ ["Codex root", options.codexRoot],
+ ["Generation workspace", options.workspace],
+ ] as const) {
+ requireAbsolutePath(label, value);
+ }
+ if (options.stagedAuthFile !== undefined) {
+ requireAbsolutePath("Staged Codex auth file", options.stagedAuthFile);
+ }
+ const mountedHostPaths = [
+ "/usr",
+ ...NETWORK_RUNTIME_MOUNTS,
+ options.codexRoot,
+ ...(options.stagedAuthFile === undefined ? [] : [options.stagedAuthFile]),
+ options.workspace,
+ ];
+ const environment: NodeJS.ProcessEnv = {
+ HOME: GUEST_CODEX_HOME,
+ CODEX_HOME: GUEST_CODEX_HOME,
+ LANG: "C.UTF-8",
+ PATH: "/usr/bin",
+ TMPDIR: "/tmp",
+ };
+ return {
+ command: options.bwrapExecutable,
+ args: [
+ "--die-with-parent",
+ "--new-session",
+ "--unshare-user",
+ "--unshare-pid",
+ "--unshare-ipc",
+ "--unshare-uts",
+ "--unshare-cgroup-try",
+ "--cap-drop",
+ "ALL",
+ "--clearenv",
+ "--ro-bind",
+ "/usr",
+ "/usr",
+ "--symlink",
+ "usr/bin",
+ "/bin",
+ "--symlink",
+ "usr/lib",
+ "/lib",
+ "--symlink",
+ "usr/lib",
+ "/lib64",
+ "--dir",
+ "/etc",
+ ...NETWORK_RUNTIME_MOUNTS.flatMap((path) => ["--ro-bind", path, path]),
+ "--dev",
+ "/dev",
+ "--proc",
+ "/proc",
+ "--ro-bind",
+ "/dev/null",
+ "/proc/1/environ",
+ "--dir",
+ "/opt",
+ "--ro-bind",
+ options.codexRoot,
+ GUEST_CODEX_ROOT,
+ "--tmpfs",
+ "/tmp",
+ "--dir",
+ "/home",
+ "--tmpfs",
+ GUEST_CODEX_HOME,
+ ...(options.stagedAuthFile === undefined
+ ? []
+ : [
+ "--ro-bind",
+ options.stagedAuthFile,
+ `${GUEST_CODEX_HOME}/auth.json`,
+ ]),
+ "--dir",
+ GUEST_WORKSPACE,
+ "--bind",
+ options.workspace,
+ GUEST_WORKSPACE,
+ "--setenv",
+ "HOME",
+ GUEST_CODEX_HOME,
+ "--setenv",
+ "CODEX_HOME",
+ GUEST_CODEX_HOME,
+ "--setenv",
+ "LANG",
+ "C.UTF-8",
+ "--setenv",
+ "PATH",
+ "/usr/bin",
+ "--setenv",
+ "TMPDIR",
+ "/tmp",
+ "--chdir",
+ GUEST_WORKSPACE,
+ ],
+ environment,
+ mountedHostPaths,
+ };
+}
+
+export function buildContainerBubblewrapLaunch(
+ options: BuildContainerBubblewrapLaunchOptions,
+): ContainerBubblewrapLaunchPlan {
+ for (const [label, value] of [
+ ["Codex executable", options.codexExecutable],
+ ["setpriv executable", options.setprivExecutable],
+ ] as const) {
+ requireAbsolutePath(label, value);
+ }
+ if (options.appServerArgs.some((argument) => argument.includes("\0"))) {
+ throw isolationError("Codex App Server arguments contain an invalid byte.");
+ }
+ const codexCommand = guestCodexExecutable(
+ options.codexRoot,
+ options.codexExecutable,
+ );
+ const base = buildContainerBubblewrapBase(options);
+
+ return {
+ ...base,
+ args: [
+ ...base.args,
+ options.setprivExecutable,
+ "--no-new-privs",
+ codexCommand,
+ ...options.appServerArgs,
+ "--strict-config",
+ "-c",
+ "shell_environment_policy.inherit=none",
+ "-c",
+ CREDENTIAL_EXCLUSIONS,
+ ],
+ protocolCwd: GUEST_WORKSPACE,
+ };
+}
+
+export function buildContainerBubblewrapProbe(
+ options: BuildContainerBubblewrapProbeOptions,
+): ContainerBubblewrapProbePlan {
+ const base = buildContainerBubblewrapBase(options);
+ const probeScript = String.raw`
+set -euo pipefail
+test -x /opt/codex/bin/codex
+test ! -s /proc/1/environ
+test ! -e /app
+test ! -e /repo
+test ! -e /opt/counterlab-venv
+test ! -e /work/jobs
+test ! -e /run/counterlab-codex
+test -f /workspace/approved.txt
+/usr/bin/setpriv --no-new-privs /opt/codex/bin/codex --version >/dev/null
+printf 'bounded output\n' > /workspace/probe-output.txt
+test "$(cat /workspace/probe-output.txt)" = 'bounded output'
+printf '{"forbiddenHostPathsHidden":true,"parentEnvironmentHidden":true,"workspaceVisible":true,"workspaceWritable":true}\n'
+`;
+ return {
+ ...base,
+ args: [
+ ...base.args,
+ "/usr/bin/bash",
+ "--noprofile",
+ "--norc",
+ "-c",
+ probeScript,
+ ],
+ };
+}
+
+export class ContainerCodexLaunchBoundary implements AppServerLaunchBoundary {
+ private readonly parsedAuth: string;
+ private readonly ptraceScopePath: string;
constructor(private readonly options: ContainerCodexLaunchBoundaryOptions) {
+ this.ptraceScopePath =
+ options.ptraceScopePath ?? "/proc/sys/kernel/yama/ptrace_scope";
try {
this.parsedAuth = JSON.stringify(
CodexAuthSchema.parse(JSON.parse(options.authJson) as unknown),
@@ -83,7 +324,9 @@ export class ContainerCodexLaunchBoundary implements AppServerLaunchBoundary {
this.options.gid <= 0 ||
!isAbsolute(this.options.workspaceRoot) ||
!isAbsolute(this.options.codexHomeRoot) ||
+ !isAbsolute(this.options.codexRoot) ||
!isAbsolute(this.options.codexExecutable) ||
+ !isAbsolute(this.options.bwrapExecutable) ||
!isAbsolute(this.options.setprivExecutable)
) {
return {
@@ -92,18 +335,67 @@ export class ContainerCodexLaunchBoundary implements AppServerLaunchBoundary {
};
}
try {
+ if (
+ process.getuid?.() !== this.options.uid ||
+ process.getgid?.() !== this.options.gid
+ ) {
+ return {
+ available: false,
+ reason:
+ "Container Codex must start as the declared non-root process identity.",
+ };
+ }
+ const [workspaceRoot, codexHomeRoot, codexRoot, codexExecutable] =
+ await Promise.all([
+ realpath(this.options.workspaceRoot),
+ realpath(this.options.codexHomeRoot),
+ realpath(this.options.codexRoot),
+ realpath(this.options.codexExecutable),
+ ]);
+ const [bwrapExecutable, setprivExecutable, systemRoot] =
+ await Promise.all([
+ realpath(this.options.bwrapExecutable),
+ realpath(this.options.setprivExecutable),
+ realpath("/usr"),
+ ]);
+ if (
+ !isContained(codexRoot, codexExecutable) ||
+ !isContained(systemRoot, bwrapExecutable) ||
+ !isContained(systemRoot, setprivExecutable) ||
+ pathsOverlap(workspaceRoot, codexHomeRoot) ||
+ pathsOverlap(workspaceRoot, codexRoot) ||
+ pathsOverlap(codexHomeRoot, codexRoot)
+ ) {
+ return {
+ available: false,
+ reason: "Container Codex mount roots are not independently bounded.",
+ };
+ }
+ const ptraceScope = Number.parseInt(
+ (await readFile(this.ptraceScopePath, "utf8")).trim(),
+ 10,
+ );
+ if (!Number.isInteger(ptraceScope) || ptraceScope < 1) {
+ return {
+ available: false,
+ reason: "Container Codex requires child-to-parent ptrace protection.",
+ };
+ }
await Promise.all([
access(this.options.codexExecutable, constants.X_OK),
+ access(this.options.bwrapExecutable, constants.X_OK),
access(this.options.setprivExecutable, constants.X_OK),
+ access(this.options.codexRoot, constants.R_OK | constants.X_OK),
access(this.options.workspaceRoot, constants.R_OK | constants.W_OK),
access(this.options.codexHomeRoot, constants.R_OK | constants.W_OK),
+ ...NETWORK_RUNTIME_MOUNTS.map((path) => access(path, constants.R_OK)),
]);
return { available: true };
} catch {
return {
available: false,
reason:
- "Container Codex executables or private directories are unavailable.",
+ "Container Codex isolation executables or private roots are unavailable.",
};
}
}
@@ -115,8 +407,8 @@ export class ContainerCodexLaunchBoundary implements AppServerLaunchBoundary {
if (!health.available) throw isolationError(health.reason);
try {
await Promise.all([
- chmod(this.options.workspaceRoot, 0o711),
- chmod(this.options.codexHomeRoot, 0o711),
+ chmod(this.options.workspaceRoot, 0o700),
+ chmod(this.options.codexHomeRoot, 0o700),
]);
const rootModes = await Promise.all([
stat(this.options.workspaceRoot),
@@ -125,101 +417,103 @@ export class ContainerCodexLaunchBoundary implements AppServerLaunchBoundary {
for (const metadata of rootModes) {
const mode = metadata.mode & 0o777;
const ownerIsPrivate = (mode & 0o700) === 0o700 && (mode & 0o066) === 0;
- const alternateUidCanTraverse =
- !this.requiresPrivilegeDrop || (mode & 0o011) === 0o011;
- if (!ownerIsPrivate || !alternateUidCanTraverse) {
+ if (!ownerIsPrivate) {
throw isolationError(
- "Container Codex private roots did not retain the required traverse-only permissions.",
+ "Container Codex private roots did not retain private permissions.",
);
}
}
} catch (error) {
if (error instanceof CompilerSetupError) throw error;
throw isolationError(
- "Container Codex private roots cannot grant traverse-only access.",
+ "Container Codex private roots cannot enforce private access.",
error,
);
}
const workspaceRoot = await realpath(resolve(this.options.workspaceRoot));
+ const requestedWorkspaceMetadata = await lstat(resolve(request.hostCwd));
+ if (requestedWorkspaceMetadata.isSymbolicLink()) {
+ throw isolationError("Generation directory must not be a symbolic link.");
+ }
const workspace = await realpath(resolve(request.hostCwd));
if (!isContained(workspaceRoot, workspace)) {
throw isolationError(
"Generation directory is outside the hosted runner workspace.",
);
}
- const codexHome = await mkdtemp(
- join(resolve(this.options.codexHomeRoot), "counterlab-codex-"),
+ if (!isAbsolute(request.command)) {
+ throw isolationError("The requested Codex executable must be absolute.");
+ }
+ const [requestedCommand, codexRoot, codexExecutable] = await Promise.all([
+ realpath(request.command),
+ realpath(this.options.codexRoot),
+ realpath(this.options.codexExecutable),
+ ]);
+ if (requestedCommand !== codexExecutable) {
+ throw isolationError(
+ "The requested Codex executable does not match the configured executable.",
+ );
+ }
+ const authPath = join(
+ resolve(this.options.codexHomeRoot),
+ `counterlab-codex-${randomUUID()}.auth.json`,
);
- const authPath = join(codexHome, "auth.json");
- const runtimeTemp = join(codexHome, "tmp");
let disposed = false;
let revoked = false;
+ let authHandle: Awaited> | undefined;
try {
- await chmod(codexHome, 0o700);
- await mkdir(runtimeTemp, { mode: 0o700 });
- await chmod(runtimeTemp, 0o700);
- await writeFile(authPath, this.parsedAuth, {
- encoding: "utf8",
- mode: 0o600,
- flag: "wx",
- });
- await chmod(authPath, 0o600);
- if (this.requiresPrivilegeDrop) {
- await Promise.all([
- chown(codexHome, this.options.uid, this.options.gid),
- chown(runtimeTemp, this.options.uid, this.options.gid),
- chown(authPath, this.options.uid, this.options.gid),
- chown(workspace, this.options.uid, this.options.gid),
- ]);
- }
+ authHandle = await open(authPath, "wx", 0o600);
+ await authHandle.writeFile(this.parsedAuth, "utf8");
+ await authHandle.close();
+ authHandle = undefined;
await chmod(workspace, 0o700);
} catch (error) {
- await rm(codexHome, { force: true, recursive: true });
+ await authHandle?.close().catch(() => undefined);
+ await writeFile(authPath, "", { flag: "w" }).catch(() => undefined);
+ await unlink(authPath).catch(() => undefined);
throw isolationError("Codex credential staging failed.", error);
}
- const environment: NodeJS.ProcessEnv = {
- ...request.environment,
- HOME: codexHome,
- CODEX_HOME: codexHome,
- TMPDIR: runtimeTemp,
- };
- delete environment.CODEX_AUTH_JSON;
- delete environment.OPENAI_API_KEY;
- delete environment.CODEX_ACCESS_TOKEN;
+ const plan = buildContainerBubblewrapLaunch({
+ appServerArgs: request.args,
+ bwrapExecutable: this.options.bwrapExecutable,
+ codexExecutable,
+ codexRoot,
+ setprivExecutable: this.options.setprivExecutable,
+ stagedAuthFile: authPath,
+ workspace,
+ });
return {
- command: this.options.setprivExecutable,
- args: [
- ...(this.requiresPrivilegeDrop
- ? [
- `--reuid=${this.options.uid}`,
- `--regid=${this.options.gid}`,
- "--clear-groups",
- ]
- : []),
- "--no-new-privs",
- request.command,
- ...request.args,
- "--strict-config",
- "-c",
- "shell_environment_policy.inherit=none",
- "-c",
- CREDENTIAL_EXCLUSIONS,
- ],
- environment,
- protocolCwd: workspace,
- spawnCwd: workspace,
+ ...plan,
async revokeCredentials() {
if (revoked) return;
- revoked = true;
- await rm(authPath, { force: true });
+ try {
+ await writeFile(authPath, "", { encoding: "utf8", flag: "w" });
+ if ((await stat(authPath)).size !== 0) {
+ throw isolationError(
+ "Codex authentication remained readable after revocation.",
+ );
+ }
+ revoked = true;
+ } catch (error) {
+ if (error instanceof CompilerSetupError) throw error;
+ throw isolationError("Codex credential revocation failed.", error);
+ }
},
async dispose() {
if (disposed) return;
+ if (!revoked) {
+ await writeFile(authPath, "", { encoding: "utf8", flag: "w" });
+ if ((await stat(authPath)).size !== 0) {
+ throw isolationError(
+ "Codex authentication remained readable during disposal.",
+ );
+ }
+ revoked = true;
+ }
+ await unlink(authPath);
disposed = true;
- await chmod(codexHome, 0o700).catch(() => undefined);
- await rm(codexHome, { force: true, recursive: true });
},
};
}
diff --git a/services/hosted-runner/src/server.test.ts b/services/hosted-runner/src/server.test.ts
index 92101e45..1d6046cc 100644
--- a/services/hosted-runner/src/server.test.ts
+++ b/services/hosted-runner/src/server.test.ts
@@ -36,6 +36,7 @@ async function start(
releaseIdentity?: HostedRunnerServerOptions["releaseIdentity"],
) {
const server = createHostedRunnerServer({
+ generationFilesystemReadIsolation: "OS_ENFORCED",
authorizeToken,
processJob,
...(onJobSettled === undefined ? {} : { onJobSettled }),
@@ -117,6 +118,7 @@ describe("hosted runner HTTP service", () => {
await expect(ready.json()).resolves.toEqual({
status: "ready",
service: "counterlab-hosted-runner",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
runnerSourceCommit,
runnerImageDigest,
});
diff --git a/services/hosted-runner/src/server.ts b/services/hosted-runner/src/server.ts
index e84e3ca1..aaa27cba 100644
--- a/services/hosted-runner/src/server.ts
+++ b/services/hosted-runner/src/server.ts
@@ -30,6 +30,7 @@ const DispatchSchema = z
.strict();
export type HostedRunnerServerOptions = {
+ generationFilesystemReadIsolation: "OS_ENFORCED";
releaseIdentity?: {
runnerSourceCommit: string;
runnerImageDigest: string;
@@ -104,6 +105,8 @@ export function createHostedRunnerServer(options: HostedRunnerServerOptions) {
respond(response, 200, {
status: "ready",
service: "counterlab-hosted-runner",
+ generationFilesystemReadIsolation:
+ options.generationFilesystemReadIsolation,
...(options.releaseIdentity ?? {}),
});
return;
@@ -219,6 +222,7 @@ async function startProductionServer(): Promise {
console.log(JSON.stringify(await runHostedRunnerStartupProbe()));
return;
}
+ const startupProbe = await runHostedRunnerStartupProbe();
const authJson = process.env.CODEX_AUTH_JSON;
const runnerVerifyingPublicKey =
process.env.COUNTERLAB_RUNNER_VERIFYING_PUBLIC_KEY;
@@ -255,6 +259,9 @@ async function startProductionServer(): Promise {
process.env.COUNTERLAB_CODEX_HOME_ROOT ?? "/run/counterlab-codex";
const codexExecutable =
process.env.COUNTERLAB_CODEX_EXECUTABLE ?? "/usr/local/bin/codex";
+ const codexRoot = process.env.COUNTERLAB_CODEX_ROOT ?? "/opt/codex";
+ const bwrapExecutable =
+ process.env.COUNTERLAB_BWRAP_EXECUTABLE ?? "/usr/bin/bwrap";
const setprivExecutable =
process.env.COUNTERLAB_SETPRIV_EXECUTABLE ?? "/usr/bin/setpriv";
const uid = Number(process.env.COUNTERLAB_CODEX_UID ?? "10001");
@@ -267,7 +274,9 @@ async function startProductionServer(): Promise {
authJson,
workspaceRoot: resolve(workspaceRoot),
codexHomeRoot: resolve(codexHomeRoot),
+ codexRoot: resolve(codexRoot),
codexExecutable: resolve(codexExecutable),
+ bwrapExecutable: resolve(bwrapExecutable),
setprivExecutable: resolve(setprivExecutable),
uid,
gid,
@@ -280,6 +289,8 @@ async function startProductionServer(): Promise {
current?: ReturnType;
} = {};
const server = createHostedRunnerServer({
+ generationFilesystemReadIsolation:
+ startupProbe.generationFilesystemReadIsolation,
...(releaseIdentityMissing
? {}
: { releaseIdentity: { runnerSourceCommit, runnerImageDigest } }),
diff --git a/services/hosted-runner/src/startup-probe.test.ts b/services/hosted-runner/src/startup-probe.test.ts
index 50ffc76a..b035039b 100644
--- a/services/hosted-runner/src/startup-probe.test.ts
+++ b/services/hosted-runner/src/startup-probe.test.ts
@@ -16,6 +16,7 @@ describe("hosted runner startup probe", () => {
_options: { env: NodeJS.ProcessEnv; timeout: number },
) => undefined,
);
+ const writeFile = vi.fn(async () => undefined);
const stat = vi.fn(async (path: string) => ({
uid: 0,
gid: 0,
@@ -32,6 +33,8 @@ describe("hosted runner startup probe", () => {
COUNTERLAB_RUNNER_WORK_ROOT: "/runtime/jobs",
COUNTERLAB_CODEX_HOME_ROOT: "/runtime/codex",
COUNTERLAB_CODEX_EXECUTABLE: "/runtime/codex-bin",
+ COUNTERLAB_CODEX_ROOT: "/runtime/codex-package",
+ COUNTERLAB_BWRAP_EXECUTABLE: "/runtime/bwrap",
COUNTERLAB_SETPRIV_EXECUTABLE: "/runtime/setpriv",
COUNTERLAB_PYTHON_EXECUTABLE: "/runtime/python",
},
@@ -41,6 +44,8 @@ describe("hosted runner startup probe", () => {
access: access as unknown as typeof import("node:fs/promises").access,
mkdir: mkdir as unknown as typeof import("node:fs/promises").mkdir,
stat: stat as unknown as typeof import("node:fs/promises").stat,
+ writeFile:
+ writeFile as unknown as typeof import("node:fs/promises").writeFile,
getUid: () => 10001,
getGid: () => 10001,
execute,
@@ -56,9 +61,12 @@ describe("hosted runner startup probe", () => {
"immutable-paths",
"codex",
"python",
+ "bubblewrap",
+ "bubblewrap-read-isolation",
"setpriv",
"writable-roots",
],
+ generationFilesystemReadIsolation: "OS_ENFORCED",
});
expect(stat.mock.calls.map(([path]) => path)).toEqual([
"/runtime/app",
@@ -68,6 +76,8 @@ describe("hosted runner startup probe", () => {
"/runtime/node",
"/runtime/runner.mjs",
"/runtime/codex-bin",
+ "/runtime/codex-package",
+ "/runtime/bwrap",
"/runtime/setpriv",
"/runtime/python",
]);
@@ -79,11 +89,37 @@ describe("hosted runner startup probe", () => {
recursive: true,
mode: 0o700,
});
- expect(execute).toHaveBeenCalledTimes(3);
+ expect(mkdir).toHaveBeenCalledWith("/runtime/jobs/.isolation-probe", {
+ recursive: true,
+ mode: 0o700,
+ });
+ expect(writeFile).toHaveBeenCalledWith(
+ "/runtime/jobs/.isolation-probe/approved.txt",
+ "approved\n",
+ { encoding: "utf8", flag: "w", mode: 0o600 },
+ );
+ expect(execute).toHaveBeenCalledTimes(4);
for (const [, , options] of execute.mock.calls) {
expect(options.env).not.toHaveProperty("CODEX_AUTH_JSON");
expect(options.env).not.toHaveProperty("OPENAI_API_KEY");
}
+ const [probeExecutable, probeArgs, probeOptions] =
+ execute.mock.calls[3] ?? [];
+ expect(probeExecutable).toBe("/runtime/bwrap");
+ expect(probeArgs).toContain("--unshare-user");
+ expect(probeArgs).toContain("--unshare-pid");
+ expect(probeArgs).toContain("--clearenv");
+ expect(probeArgs?.join(" ")).toContain("/usr/bin/setpriv");
+ expect(probeArgs?.join(" ")).toContain("/opt/codex/bin/codex");
+ expect(probeArgs).toContain("/runtime/jobs/.isolation-probe");
+ expect(probeArgs).not.toContain("/runtime/jobs");
+ expect(probeOptions?.env).toEqual({
+ CODEX_HOME: "/home/counterlab",
+ HOME: "/home/counterlab",
+ LANG: "C.UTF-8",
+ PATH: "/usr/bin",
+ TMPDIR: "/tmp",
+ });
});
it("fails closed when a runtime executable cannot be launched", async () => {
@@ -92,6 +128,7 @@ describe("hosted runner startup probe", () => {
bundlePath: "/app/runner.mjs",
access: async () => undefined,
mkdir: async () => undefined as never,
+ writeFile: async () => undefined,
stat: (async (
path: Parameters[0],
) => ({
diff --git a/services/hosted-runner/src/startup-probe.ts b/services/hosted-runner/src/startup-probe.ts
index f1b9e7bd..a9a3f96b 100644
--- a/services/hosted-runner/src/startup-probe.ts
+++ b/services/hosted-runner/src/startup-probe.ts
@@ -1,8 +1,11 @@
import { execFile } from "node:child_process";
import { constants } from "node:fs";
-import { access, mkdir, stat } from "node:fs/promises";
+import { access, mkdir, stat, writeFile } from "node:fs/promises";
+import { join } from "node:path";
import { promisify } from "node:util";
+import { buildContainerBubblewrapProbe } from "./launch-boundary.js";
+
const execFileAsync = promisify(execFile);
type Execute = (
@@ -19,6 +22,7 @@ export type HostedRunnerStartupProbeOptions = {
access?: typeof access;
mkdir?: typeof mkdir;
stat?: typeof stat;
+ writeFile?: typeof writeFile;
getUid?: () => number | undefined;
getGid?: () => number | undefined;
execute?: Execute;
@@ -34,9 +38,12 @@ export type HostedRunnerStartupProbeResult = {
"immutable-paths",
"codex",
"python",
+ "bubblewrap",
+ "bubblewrap-read-isolation",
"setpriv",
"writable-roots",
];
+ generationFilesystemReadIsolation: "OS_ENFORCED";
};
export async function runHostedRunnerStartupProbe(
@@ -46,6 +53,7 @@ export async function runHostedRunnerStartupProbe(
const accessFile = options.access ?? access;
const makeDirectory = options.mkdir ?? mkdir;
const readMetadata = options.stat ?? stat;
+ const writeProbeFile = options.writeFile ?? writeFile;
const uid = (options.getUid ?? process.getuid)?.();
const gid = (options.getGid ?? process.getgid)?.();
const execute =
@@ -61,11 +69,15 @@ export async function runHostedRunnerStartupProbe(
environment.COUNTERLAB_CODEX_HOME_ROOT ?? "/run/counterlab-codex";
const codexExecutable =
environment.COUNTERLAB_CODEX_EXECUTABLE ?? "/usr/local/bin/codex";
+ const codexRoot = environment.COUNTERLAB_CODEX_ROOT ?? "/opt/codex";
+ const bwrapExecutable =
+ environment.COUNTERLAB_BWRAP_EXECUTABLE ?? "/usr/bin/bwrap";
const setprivExecutable =
environment.COUNTERLAB_SETPRIV_EXECUTABLE ?? "/usr/bin/setpriv";
const pythonExecutable =
environment.COUNTERLAB_PYTHON_EXECUTABLE ??
"/opt/counterlab-venv/bin/python";
+ const probeWorkspace = join(workspaceRoot, ".isolation-probe");
if (uid !== 10001 || gid !== 10001) {
throw new Error(
@@ -95,11 +107,19 @@ export async function runHostedRunnerStartupProbe(
accessFile(nodeExecutable, constants.X_OK),
accessFile(bundlePath, constants.R_OK),
accessFile(codexExecutable, constants.X_OK),
+ accessFile(codexRoot, constants.R_OK | constants.X_OK),
+ accessFile(bwrapExecutable, constants.X_OK),
accessFile(setprivExecutable, constants.X_OK),
accessFile(pythonExecutable, constants.X_OK),
makeDirectory(workspaceRoot, { recursive: true, mode: 0o700 }),
makeDirectory(codexHomeRoot, { recursive: true, mode: 0o700 }),
+ makeDirectory(probeWorkspace, { recursive: true, mode: 0o700 }),
]);
+ await writeProbeFile(join(probeWorkspace, "approved.txt"), "approved\n", {
+ encoding: "utf8",
+ flag: "w",
+ mode: 0o600,
+ });
const childEnvironment: NodeJS.ProcessEnv = {
HOME: codexHomeRoot,
@@ -133,6 +153,15 @@ export async function runHostedRunnerStartupProbe(
env: childEnvironment,
timeout: 30_000,
});
+ const isolationProbe = buildContainerBubblewrapProbe({
+ bwrapExecutable,
+ codexRoot,
+ workspace: probeWorkspace,
+ });
+ await execute(isolationProbe.command, isolationProbe.args, {
+ env: isolationProbe.environment,
+ timeout: 30_000,
+ });
return {
status: "ready",
@@ -144,8 +173,11 @@ export async function runHostedRunnerStartupProbe(
"immutable-paths",
"codex",
"python",
+ "bubblewrap",
+ "bubblewrap-read-isolation",
"setpriv",
"writable-roots",
],
+ generationFilesystemReadIsolation: "OS_ENFORCED",
};
}
From f3651328593018ee90395d3cdfddc2ee56397b05 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 02:35:31 +0530
Subject: [PATCH 006/143] fix(release): version enforced isolation receipts
---
apps/web/worker/api.test.ts | 6 +-
apps/web/worker/api.ts | 5 +-
apps/web/worker/deployment-config.test.ts | 25 +-
apps/web/worker/runner-control-plane.test.ts | 6 +
apps/web/worker/runner-control-plane.ts | 10 +-
.../scientific-engine-registry/src/schema.ts | 87 ++++++-
.../scientific-engine-registry/src/types.ts | 8 +
.../test/release-check-receipt.test.ts | 83 ++++++-
.../test/release-schema.test.ts | 166 +++++++++++--
.../schemas/deployment-receipt-v5.schema.json | 229 +++++++++++++++++
.../qualified-runner-release-v5.schema.json | 235 ++++++++++++++++++
.../release-check-receipt-v3.schema.json | 144 +++++++++++
scripts/create-deployment-receipt.test.ts | 3 +
scripts/create-deployment-receipt.ts | 13 +-
scripts/generate-contract-schemas.ts | 45 +++-
scripts/prepare-qualified-deploy.test.ts | 1 +
scripts/prepare-qualified-deploy.ts | 9 +-
scripts/production-smoke.sh | 8 +-
scripts/qualify-runner-release.ts | 6 +-
scripts/release-check-receipt.ts | 6 +-
scripts/test_production_smoke.py | 10 +-
scripts/verify-scientific-engines.sh | 3 +-
22 files changed, 1043 insertions(+), 65 deletions(-)
create mode 100644 scientific-engines/schemas/deployment-receipt-v5.schema.json
create mode 100644 scientific-engines/schemas/qualified-runner-release-v5.schema.json
create mode 100644 scientific-engines/schemas/release-check-receipt-v3.schema.json
diff --git a/apps/web/worker/api.test.ts b/apps/web/worker/api.test.ts
index 31d21843..44c36db3 100644
--- a/apps/web/worker/api.test.ts
+++ b/apps/web/worker/api.test.ts
@@ -10412,7 +10412,10 @@ describe("Cloudflare Worker API", () => {
);
await expect(probedHealth.json()).resolves.toMatchObject({
ok: true,
- data: { readiness: "ready" },
+ data: {
+ readiness: "ready",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ },
});
});
@@ -10443,6 +10446,7 @@ describe("Cloudflare Worker API", () => {
liveCodex: "configured",
liveKernel: "configured",
readiness: "not-ready",
+ generationFilesystemReadIsolation: "PARTIAL",
},
});
});
diff --git a/apps/web/worker/api.ts b/apps/web/worker/api.ts
index 249cda2b..87726907 100644
--- a/apps/web/worker/api.ts
+++ b/apps/web/worker/api.ts
@@ -4254,7 +4254,10 @@ export function createApi(options: ApiOptions = {}) {
runner === "configured"
? ("credential-and-privilege-boundary" as const)
: ("local-runner-required" as const),
- generationFilesystemReadIsolation: "PARTIAL" as const,
+ generationFilesystemReadIsolation:
+ readiness?.checks.runner === true
+ ? ("OS_ENFORCED" as const)
+ : ("PARTIAL" as const),
requestId: context.get("requestId"),
}),
);
diff --git a/apps/web/worker/deployment-config.test.ts b/apps/web/worker/deployment-config.test.ts
index 04216c76..ccbc49d6 100644
--- a/apps/web/worker/deployment-config.test.ts
+++ b/apps/web/worker/deployment-config.test.ts
@@ -515,6 +515,13 @@ describe("Cloudflare static asset routing", () => {
expect(dockerfile).toContain("/repo/requirements.runner.lock.txt");
expect(dockerfile).toContain("chmod -R a=rX /repo");
expect(dockerfile).toContain("chmod 0555 /usr/local/bin/node");
+ expect(dockerfile).toContain("bubblewrap=0.11.0-2+deb13u1");
+ expect(dockerfile).toContain(
+ 'test "$(/usr/bin/bwrap --version)" = "bubblewrap 0.11.0"',
+ );
+ expect(dockerfile.indexOf("bubblewrap=0.11.0-2+deb13u1")).toBeLessThan(
+ dockerfile.indexOf("USER 10001:10001"),
+ );
expect(dockerfile.indexOf("chmod 0555 /usr/local/bin/node")).toBeLessThan(
dockerfile.indexOf("USER 10001:10001"),
);
@@ -547,8 +554,9 @@ describe("Cloudflare static asset routing", () => {
const evidenceCommit = "2".repeat(40);
const image = `registry.cloudflare.com/account-1/counterlab-runner:git-${sourceCommit}`;
const receipt = {
- schemaVersion: "4",
+ schemaVersion: "5",
status: "VERIFIED",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
sourceCommit,
sourceArchiveSha256: "b".repeat(64),
sourceTreeSha256: "c".repeat(64),
@@ -580,9 +588,10 @@ describe("Cloudflare static asset routing", () => {
registryDigest: `sha256:${"3".repeat(64)}`,
registryResolvedAt: "2026-07-16T16:20:00.000Z",
qualifiedAt: "2026-07-16T16:30:00.000Z",
- verifierVersion: "counterlab-release-v4",
+ verifierVersion: "counterlab-release-v5",
};
const observation = {
+ generationFilesystemReadIsolation: "OS_ENFORCED" as const,
sourceCommit,
sourceArchiveSha256: receipt.sourceArchiveSha256,
sourceTreeSha256: receipt.sourceTreeSha256,
@@ -789,8 +798,9 @@ describe("Cloudflare static asset routing", () => {
const evidenceCommit = "2".repeat(40);
const image = `registry.cloudflare.com/account-1/counterlab-runner:git-${sourceCommit}`;
const receipt = {
- schemaVersion: "4",
+ schemaVersion: "5",
status: "VERIFIED",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
sourceCommit,
sourceArchiveSha256: "b".repeat(64),
sourceTreeSha256: "c".repeat(64),
@@ -822,7 +832,7 @@ describe("Cloudflare static asset routing", () => {
registryDigest: `sha256:${"3".repeat(64)}`,
registryResolvedAt: "2026-07-16T16:20:00.000Z",
qualifiedAt: "2026-07-16T16:30:00.000Z",
- verifierVersion: "counterlab-release-v4",
+ verifierVersion: "counterlab-release-v5",
};
const config = productionDeployConfig();
config.durable_objects.bindings = [
@@ -835,6 +845,7 @@ describe("Cloudflare static asset routing", () => {
receipt,
image,
observation: {
+ generationFilesystemReadIsolation: "OS_ENFORCED",
sourceCommit,
sourceArchiveSha256: receipt.sourceArchiveSha256,
sourceTreeSha256: receipt.sourceTreeSha256,
@@ -877,8 +888,9 @@ describe("Cloudflare static asset routing", () => {
const sourceCommit = "a".repeat(40);
const image = `registry.cloudflare.com/account-1/counterlab-runner:git-${sourceCommit}`;
const receipt = {
- schemaVersion: "4",
+ schemaVersion: "5",
status: "VERIFIED",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
sourceCommit,
sourceArchiveSha256: "b".repeat(64),
sourceTreeSha256: "c".repeat(64),
@@ -910,7 +922,7 @@ describe("Cloudflare static asset routing", () => {
registryDigest: `sha256:${"3".repeat(64)}`,
registryResolvedAt: "2026-07-16T15:55:00.000Z",
qualifiedAt: "2026-07-16T16:30:00.000Z",
- verifierVersion: "counterlab-release-v4",
+ verifierVersion: "counterlab-release-v5",
};
expect(() =>
@@ -922,6 +934,7 @@ describe("Cloudflare static asset routing", () => {
receipt,
image,
observation: {
+ generationFilesystemReadIsolation: "OS_ENFORCED",
sourceCommit,
sourceArchiveSha256: "9".repeat(64),
sourceTreeSha256: receipt.sourceTreeSha256,
diff --git a/apps/web/worker/runner-control-plane.test.ts b/apps/web/worker/runner-control-plane.test.ts
index 922b4eb0..20a17ed7 100644
--- a/apps/web/worker/runner-control-plane.test.ts
+++ b/apps/web/worker/runner-control-plane.test.ts
@@ -41,6 +41,7 @@ describe("HttpRunnerDispatcher", () => {
JSON.stringify({
status: "ready",
service: "counterlab-hosted-runner",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
runnerSourceCommit,
runnerImageDigest,
}),
@@ -83,6 +84,7 @@ describe("HttpRunnerDispatcher", () => {
["invalid response", "schema"],
["stale source identity", "source"],
["stale image identity", "image"],
+ ["partial filesystem isolation", "isolation"],
])("fails Container readiness closed on %s", async (_label, failure) => {
const startAndWaitForPorts = vi.fn(async () => {
if (failure === "startup") throw new Error("image did not start");
@@ -97,6 +99,8 @@ describe("HttpRunnerDispatcher", () => {
status: "ready",
service:
failure === "schema" ? "wrong-runner" : "counterlab-hosted-runner",
+ generationFilesystemReadIsolation:
+ failure === "isolation" ? "PARTIAL" : "OS_ENFORCED",
runnerSourceCommit:
failure === "source" ? "d".repeat(40) : runnerSourceCommit,
runnerImageDigest:
@@ -321,6 +325,7 @@ describe("HttpRunnerDispatcher", () => {
JSON.stringify({
status: "ready",
service: "counterlab-hosted-runner",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
runnerSourceCommit,
runnerImageDigest,
}),
@@ -407,6 +412,7 @@ describe("HttpRunnerDispatcher", () => {
JSON.stringify({
status: "ready",
service: "counterlab-hosted-runner",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
runnerSourceCommit: source,
runnerImageDigest: image,
}),
diff --git a/apps/web/worker/runner-control-plane.ts b/apps/web/worker/runner-control-plane.ts
index 529e5feb..1042e9df 100644
--- a/apps/web/worker/runner-control-plane.ts
+++ b/apps/web/worker/runner-control-plane.ts
@@ -105,12 +105,20 @@ async function runnerReadinessMatches(
!Array.isArray(payload) &&
JSON.stringify(Object.keys(payload).sort()) ===
JSON.stringify(
- ["runnerImageDigest", "runnerSourceCommit", "service", "status"].sort(),
+ [
+ "generationFilesystemReadIsolation",
+ "runnerImageDigest",
+ "runnerSourceCommit",
+ "service",
+ "status",
+ ].sort(),
) &&
"status" in payload &&
payload.status === "ready" &&
"service" in payload &&
payload.service === "counterlab-hosted-runner" &&
+ "generationFilesystemReadIsolation" in payload &&
+ payload.generationFilesystemReadIsolation === "OS_ENFORCED" &&
"runnerSourceCommit" in payload &&
payload.runnerSourceCommit === releaseIdentity.runnerSourceCommit &&
"runnerImageDigest" in payload &&
diff --git a/packages/scientific-engine-registry/src/schema.ts b/packages/scientific-engine-registry/src/schema.ts
index 03a66c8d..245bebff 100644
--- a/packages/scientific-engine-registry/src/schema.ts
+++ b/packages/scientific-engine-registry/src/schema.ts
@@ -838,7 +838,7 @@ export const QualifiedRunnerReleaseV3Schema = z
export const QUALIFIED_AGGREGATE_LIMIT_MODE =
"container-cgroup-and-process-rlimit" as const;
-export const QualifiedRunnerReleaseSchema = z
+export const QualifiedRunnerReleaseV4Schema = z
.strictObject({
schemaVersion: z.literal("4"),
status: z.literal("VERIFIED"),
@@ -966,6 +966,34 @@ export const QualifiedRunnerReleaseSchema = z
}
});
+export const QualifiedRunnerReleaseSchema = z
+ .strictObject({
+ ...QualifiedRunnerReleaseV4Schema.shape,
+ schemaVersion: z.literal("5"),
+ generationFilesystemReadIsolation: z.literal("OS_ENFORCED"),
+ verifierVersion: z.literal("counterlab-release-v5"),
+ })
+ .superRefine((release, context) => {
+ const {
+ generationFilesystemReadIsolation: _generationFilesystemReadIsolation,
+ ...legacyRelease
+ } = release;
+ const result = QualifiedRunnerReleaseV4Schema.safeParse({
+ ...legacyRelease,
+ schemaVersion: "4",
+ verifierVersion: "counterlab-release-v4",
+ });
+ if (!result.success) {
+ for (const issue of result.error.issues) {
+ context.addIssue({
+ code: "custom",
+ path: issue.path,
+ message: issue.message,
+ });
+ }
+ }
+ });
+
export const RELEASE_CHECK_IDS = [
"test-all",
"leakage-mutations",
@@ -1045,7 +1073,7 @@ export const ReleaseCheckReceiptV1Schema = z
}
});
-export const ReleaseCheckReceiptSchema = z
+export const ReleaseCheckReceiptV2Schema = z
.strictObject({
schemaVersion: z.literal("2"),
status: z.literal("PASSED"),
@@ -1113,6 +1141,34 @@ export const ReleaseCheckReceiptSchema = z
}
});
+export const ReleaseCheckReceiptSchema = z
+ .strictObject({
+ ...ReleaseCheckReceiptV2Schema.shape,
+ schemaVersion: z.literal("3"),
+ generationFilesystemReadIsolation: z.literal("OS_ENFORCED"),
+ verifierVersion: z.literal("counterlab-release-check-v3"),
+ })
+ .superRefine((receipt, context) => {
+ const {
+ generationFilesystemReadIsolation: _generationFilesystemReadIsolation,
+ ...legacyReceipt
+ } = receipt;
+ const result = ReleaseCheckReceiptV2Schema.safeParse({
+ ...legacyReceipt,
+ schemaVersion: "2",
+ verifierVersion: "counterlab-release-check-v2",
+ });
+ if (!result.success) {
+ for (const issue of result.error.issues) {
+ context.addIssue({
+ code: "custom",
+ path: issue.path,
+ message: issue.message,
+ });
+ }
+ }
+ });
+
export const DeploymentReceiptV3Schema = z
.strictObject({
schemaVersion: z.literal("3"),
@@ -1203,7 +1259,7 @@ export const DeploymentReceiptV3Schema = z
}
});
-export const DeploymentReceiptSchema = z
+export const DeploymentReceiptV4Schema = z
.strictObject({
schemaVersion: z.literal("4"),
status: z.literal("DEPLOYED"),
@@ -1306,3 +1362,28 @@ export const DeploymentReceiptSchema = z
});
}
});
+
+export const DeploymentReceiptSchema = z
+ .strictObject({
+ ...DeploymentReceiptV4Schema.shape,
+ schemaVersion: z.literal("5"),
+ generationFilesystemReadIsolation: z.literal("OS_ENFORCED"),
+ verifierVersion: z.literal("counterlab-deployment-v5"),
+ })
+ .superRefine((receipt, context) => {
+ const result = DeploymentReceiptV4Schema.safeParse({
+ ...receipt,
+ schemaVersion: "4",
+ generationFilesystemReadIsolation: "PARTIAL",
+ verifierVersion: "counterlab-deployment-v4",
+ });
+ if (!result.success) {
+ for (const issue of result.error.issues) {
+ context.addIssue({
+ code: "custom",
+ path: issue.path,
+ message: issue.message,
+ });
+ }
+ }
+ });
diff --git a/packages/scientific-engine-registry/src/types.ts b/packages/scientific-engine-registry/src/types.ts
index 8c5345bc..d062c5ac 100644
--- a/packages/scientific-engine-registry/src/types.ts
+++ b/packages/scientific-engine-registry/src/types.ts
@@ -5,6 +5,7 @@ import {
ContainedRuntimeAttestationV1Schema,
DeploymentReceiptSchema,
DeploymentReceiptV3Schema,
+ DeploymentReceiptV4Schema,
ScientificEngineDescriptorSchema,
ScientificEngineEvidenceCatalogSchema,
ScientificEngineEvidenceKindSchema,
@@ -17,8 +18,10 @@ import {
QualifiedRunnerReleaseV1Schema,
QualifiedRunnerReleaseV2Schema,
QualifiedRunnerReleaseV3Schema,
+ QualifiedRunnerReleaseV4Schema,
ReleaseCheckReceiptSchema,
ReleaseCheckReceiptV1Schema,
+ ReleaseCheckReceiptV2Schema,
SubjectPackAuthorityBindingSchema,
SubjectPackEngineBindingSchema,
SubjectPackEngineBindingsSchema,
@@ -33,6 +36,7 @@ export type ContainedRuntimeAttestationV1 = z.infer<
>;
export type DeploymentReceipt = z.infer;
export type DeploymentReceiptV3 = z.infer;
+export type DeploymentReceiptV4 = z.infer;
export type ScientificEngineRole = z.infer;
export type ScientificEngineDescriptor = z.infer<
@@ -77,6 +81,10 @@ export type QualifiedRunnerReleaseV2 = z.infer<
export type QualifiedRunnerReleaseV3 = z.infer<
typeof QualifiedRunnerReleaseV3Schema
>;
+export type QualifiedRunnerReleaseV4 = z.infer<
+ typeof QualifiedRunnerReleaseV4Schema
+>;
export type TimeoutCleanupReceipt = z.infer;
export type ReleaseCheckReceipt = z.infer;
export type ReleaseCheckReceiptV1 = z.infer;
+export type ReleaseCheckReceiptV2 = z.infer;
diff --git a/packages/scientific-engine-registry/test/release-check-receipt.test.ts b/packages/scientific-engine-registry/test/release-check-receipt.test.ts
index e62c6db6..76ffe9b7 100644
--- a/packages/scientific-engine-registry/test/release-check-receipt.test.ts
+++ b/packages/scientific-engine-registry/test/release-check-receipt.test.ts
@@ -1,8 +1,14 @@
import { createHash } from "node:crypto";
-import { readFileSync } from "node:fs";
+import { existsSync, readFileSync } from "node:fs";
+import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
+import {
+ ReleaseCheckReceiptSchema,
+ ReleaseCheckReceiptV2Schema,
+} from "../src/index";
+
import {
assertReleaseCheckBinding,
createDeploymentReleaseIdentity,
@@ -15,8 +21,9 @@ const evidenceCommit = "b".repeat(40);
function qualifiedReceipt() {
return {
- schemaVersion: "4",
+ schemaVersion: "5",
status: "VERIFIED",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
sourceCommit,
sourceArchiveSha256: "1".repeat(64),
sourceTreeSha256: "2".repeat(64),
@@ -57,7 +64,7 @@ function qualifiedReceipt() {
registryDigest: `sha256:${"1".repeat(64)}`,
registryResolvedAt: "2026-07-19T00:00:00.000Z",
qualifiedAt: "2026-07-19T00:01:00.000Z",
- verifierVersion: "counterlab-release-v4",
+ verifierVersion: "counterlab-release-v5",
} as const;
}
@@ -103,11 +110,11 @@ function deploymentReceipt() {
const runner = "d".repeat(40);
const digest = `sha256:${"a".repeat(64)}`;
return {
- schemaVersion: "4",
+ schemaVersion: "5",
status: "DEPLOYED",
workerName: "counterlab",
productionOrigin: "https://counterlab.cserules.workers.dev",
- generationFilesystemReadIsolation: "PARTIAL",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
workerEvidenceCommit: worker,
runnerSourceCommit: runner,
qualifiedRunnerReceiptSha256: "1".repeat(64),
@@ -144,7 +151,7 @@ function deploymentReceipt() {
workerVersionSha256: "c".repeat(64),
containerStatusSha256: "d".repeat(64),
deployedAt: "2026-07-19T00:00:00.000Z",
- verifierVersion: "counterlab-deployment-v4",
+ verifierVersion: "counterlab-deployment-v5",
} as const;
}
@@ -263,13 +270,75 @@ describe("release-check receipt", () => {
});
expect(receipt).toMatchObject({
+ schemaVersion: "3",
status: "PASSED",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
evidenceCommit,
- verifierVersion: "counterlab-release-check-v2",
+ verifierVersion: "counterlab-release-check-v3",
});
expect(receipt.checks).toHaveLength(11);
});
+ it("preserves v2 receipts while requiring OS isolation in v3", () => {
+ const current = createReleaseCheckReceipt({
+ qualifiedReceipt: qualifiedReceipt(),
+ qualifiedReceiptBytes: qualifiedBytes(),
+ runtimeAttestation: runtimeAttestation(),
+ currentCommit: evidenceCommit,
+ worktreeClean: true,
+ runnerImageDigest: qualifiedReceipt().localImageDigest,
+ adapterImageDigest: qualifiedReceipt().adapterImageDigest,
+ checkedAt: "2026-07-19T00:02:00.000Z",
+ });
+ const {
+ generationFilesystemReadIsolation: _generationFilesystemReadIsolation,
+ ...legacy
+ } = current;
+
+ expect(
+ ReleaseCheckReceiptV2Schema.parse({
+ ...legacy,
+ schemaVersion: "2",
+ verifierVersion: "counterlab-release-check-v2",
+ }),
+ ).toMatchObject({ schemaVersion: "2" });
+ expect(() =>
+ ReleaseCheckReceiptSchema.parse({
+ ...current,
+ generationFilesystemReadIsolation: "PARTIAL",
+ }),
+ ).toThrow();
+ });
+
+ it("publishes distinct historical v2 and isolation-enforced v3 schemas", () => {
+ const v2Path = resolve(
+ process.cwd(),
+ "scientific-engines/schemas/release-check-receipt-v2.schema.json",
+ );
+ const v3Path = resolve(
+ process.cwd(),
+ "scientific-engines/schemas/release-check-receipt-v3.schema.json",
+ );
+ expect(existsSync(v2Path)).toBe(true);
+ expect(existsSync(v3Path)).toBe(true);
+ const v2 = JSON.parse(readFileSync(v2Path, "utf8")) as {
+ properties?: Record;
+ required?: string[];
+ };
+ const v3 = JSON.parse(readFileSync(v3Path, "utf8")) as typeof v2;
+
+ expect(v2.properties?.schemaVersion?.const).toBe("2");
+ expect(v2.properties).not.toHaveProperty(
+ "generationFilesystemReadIsolation",
+ );
+ expect(v2.required).not.toContain("generationFilesystemReadIsolation");
+ expect(v3.properties?.schemaVersion?.const).toBe("3");
+ expect(v3.properties?.generationFilesystemReadIsolation?.const).toBe(
+ "OS_ENFORCED",
+ );
+ expect(v3.required).toContain("generationFilesystemReadIsolation");
+ });
+
it("rejects a different qualified receipt or live runtime", () => {
const receipt = createReleaseCheckReceipt({
qualifiedReceipt: qualifiedReceipt(),
diff --git a/packages/scientific-engine-registry/test/release-schema.test.ts b/packages/scientific-engine-registry/test/release-schema.test.ts
index 903a7a4c..e134cfaa 100644
--- a/packages/scientific-engine-registry/test/release-schema.test.ts
+++ b/packages/scientific-engine-registry/test/release-schema.test.ts
@@ -5,9 +5,11 @@ import { describe, expect, it } from "vitest";
import {
DeploymentReceiptSchema,
DeploymentReceiptV3Schema,
+ DeploymentReceiptV4Schema,
QualifiedRunnerReleaseSchema,
QualifiedRunnerReleaseV2Schema,
QualifiedRunnerReleaseV3Schema,
+ QualifiedRunnerReleaseV4Schema,
} from "../src/index";
import * as releaseTools from "../../../scripts/prepare-qualified-deploy";
@@ -37,7 +39,7 @@ describe("qualified runner release schema", () => {
expect(() => QualifiedRunnerReleaseSchema.parse(legacyReceipt())).toThrow();
});
- it("creates a runtime-bound promoted v4 receipt from recomputed evidence", () => {
+ it("creates a runtime-bound promoted v5 receipt from recomputed evidence", () => {
const sourceCommit = "a".repeat(40);
const evidenceCommit = "2".repeat(40);
const registryImage = `registry.cloudflare.com/account-1/counterlab-runner:git-${sourceCommit}`;
@@ -88,6 +90,7 @@ describe("qualified runner release schema", () => {
timeoutRuntimeSessionId: "rt-v61-test1",
timeoutVerifiedAt: "2026-07-16T16:24:00.000Z",
currentCommit: evidenceCommit,
+ generationFilesystemReadIsolation: "OS_ENFORCED",
sourceIsAncestor: true,
changedPaths: ["scientific-engines/snapshot.json"],
registryImage,
@@ -99,7 +102,8 @@ describe("qualified runner release schema", () => {
);
expect(QualifiedRunnerReleaseSchema.parse(receipt)).toMatchObject({
- schemaVersion: "4",
+ schemaVersion: "5",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
evidenceCommit,
registryImage,
registryDigest: `sha256:${"3".repeat(64)}`,
@@ -115,6 +119,79 @@ describe("qualified runner release schema", () => {
).toThrow();
});
+ it("retains the exact historical v4 receipt contract", () => {
+ const sourceCommit = "a".repeat(40);
+ const evidenceCommit = "2".repeat(40);
+ const current = (
+ releaseTools as unknown as {
+ createQualifiedRunnerRelease: (
+ observation: Record,
+ qualifiedAt: string,
+ ) => Record;
+ }
+ ).createQualifiedRunnerRelease(
+ {
+ sourceCommit,
+ sourceArchiveSha256: "b".repeat(64),
+ sourceTreeSha256: "c".repeat(64),
+ dockerfileSha256: "d".repeat(64),
+ localImageTag: `counterlab-runner:git-${sourceCommit}`,
+ localImageDigest: `sha256:${"e".repeat(64)}`,
+ ociRevision: sourceCommit,
+ ociSourceTreeSha256: "c".repeat(64),
+ engineAuthorityHash: "f".repeat(64),
+ runtimeManifestHash: "1".repeat(64),
+ runtimeToolchainSha256: "4".repeat(64),
+ runtimePolicySha256: "0".repeat(64),
+ proofDependencyManifestSha256: "1".repeat(64),
+ toolchainLockSha256: "5".repeat(64),
+ runtimeAdapterSha256: "6".repeat(64),
+ buildctlSha256: "7".repeat(64),
+ buildkitdSha256: "8".repeat(64),
+ buildkitConfigSha256: "9".repeat(64),
+ adapterDockerfileSha256: "a".repeat(64),
+ adapterImageTag: `counterlab-adapter:git-${sourceCommit}`,
+ adapterImageDigest: `sha256:${"b".repeat(64)}`,
+ adapterManifestDigest: `sha256:${"c".repeat(64)}`,
+ adapterOciArchiveSha256: "d".repeat(64),
+ adapterOciRevision: sourceCommit,
+ adapterOciSourceTreeSha256: "c".repeat(64),
+ limitMode: "container-cgroup-and-process-rlimit",
+ aggregateLimitIntentEnforced: true,
+ aggregateLimitEvidenceSha256: "e".repeat(64),
+ timeoutCleanupReceipt: `node_modules/.cache/counterlab-v6.1/releases/timeout-cleanup-${sourceCommit}.json`,
+ timeoutCleanupReceiptSha256: "e".repeat(64),
+ timeoutCleanupPayloadSha256: "f".repeat(64),
+ timeoutRunControlReceiptSha256: "0".repeat(64),
+ timeoutRootlessReceiptSha256: "1".repeat(64),
+ timeoutRuntimeSessionId: "rt-v61-test1",
+ timeoutVerifiedAt: "2026-07-16T16:24:00.000Z",
+ currentCommit: evidenceCommit,
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ sourceIsAncestor: true,
+ changedPaths: ["scientific-engines/snapshot.json"],
+ registryImage: `registry.cloudflare.com/account-1/counterlab-runner:git-${sourceCommit}`,
+ registryDigest: `sha256:${"3".repeat(64)}`,
+ registryResolvedAt: "2026-07-16T16:25:00.000Z",
+ observedAt: "2026-07-16T16:30:00.000Z",
+ },
+ "2026-07-16T16:30:00.000Z",
+ );
+ const {
+ generationFilesystemReadIsolation: _generationFilesystemReadIsolation,
+ ...legacyV4
+ } = current;
+
+ expect(
+ QualifiedRunnerReleaseV4Schema.parse({
+ ...legacyV4,
+ schemaVersion: "4",
+ verifierVersion: "counterlab-release-v4",
+ }),
+ ).toMatchObject({ schemaVersion: "4" });
+ expect(() => QualifiedRunnerReleaseSchema.parse(legacyV4)).toThrow();
+ });
+
it("retains strict parsing for historical v3 receipts", () => {
const sourceCommit = "a".repeat(40);
expect(
@@ -179,18 +256,31 @@ describe("qualified runner release schema", () => {
);
});
- it("publishes the timeout-bound v4 receipt as generated JSON Schema", () => {
- const path = resolve(
+ it("publishes distinct historical v4 and isolation-enforced v5 schemas", () => {
+ const v4Path = resolve(
process.cwd(),
"scientific-engines/schemas/qualified-runner-release-v4.schema.json",
);
- expect(existsSync(path)).toBe(true);
- const schema = JSON.parse(readFileSync(path, "utf8")) as {
- properties?: { schemaVersion?: { const?: string } };
+ const v5Path = resolve(
+ process.cwd(),
+ "scientific-engines/schemas/qualified-runner-release-v5.schema.json",
+ );
+ expect(existsSync(v4Path)).toBe(true);
+ expect(existsSync(v5Path)).toBe(true);
+ const v4 = JSON.parse(readFileSync(v4Path, "utf8")) as {
+ properties?: Record;
required?: string[];
};
- expect(schema.properties?.schemaVersion?.const).toBe("4");
- expect(schema.required).toEqual(
+ const v5 = JSON.parse(readFileSync(v5Path, "utf8")) as {
+ properties?: Record;
+ required?: string[];
+ };
+ expect(v4.properties?.schemaVersion?.const).toBe("4");
+ expect(v4.properties).not.toHaveProperty(
+ "generationFilesystemReadIsolation",
+ );
+ expect(v4.required).not.toContain("generationFilesystemReadIsolation");
+ expect(v4.required).toEqual(
expect.arrayContaining([
"timeoutCleanupReceipt",
"timeoutCleanupReceiptSha256",
@@ -203,6 +293,11 @@ describe("qualified runner release schema", () => {
"proofDependencyManifestSha256",
]),
);
+ expect(v5.properties?.schemaVersion?.const).toBe("5");
+ expect(v5.properties?.generationFilesystemReadIsolation?.const).toBe(
+ "OS_ENFORCED",
+ );
+ expect(v5.required).toContain("generationFilesystemReadIsolation");
});
});
@@ -212,11 +307,11 @@ describe("deployment receipt schema", () => {
const runner = "b".repeat(40);
const digest = `sha256:${"c".repeat(64)}`;
return {
- schemaVersion: "4",
+ schemaVersion: "5",
status: "DEPLOYED",
workerName: "counterlab",
productionOrigin: "https://counterlab.cserules.workers.dev",
- generationFilesystemReadIsolation: "PARTIAL",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
workerEvidenceCommit: worker,
runnerSourceCommit: runner,
qualifiedRunnerReceiptSha256: "1".repeat(64),
@@ -253,13 +348,13 @@ describe("deployment receipt schema", () => {
workerVersionSha256: "b".repeat(64),
containerStatusSha256: "c".repeat(64),
deployedAt: "2026-07-19T00:03:00.000Z",
- verifierVersion: "counterlab-deployment-v4",
+ verifierVersion: "counterlab-deployment-v5",
} as const;
}
it("binds the deployed Worker, Container, release checks, and exact artifacts", () => {
expect(DeploymentReceiptSchema.parse(receipt())).toMatchObject({
- schemaVersion: "4",
+ schemaVersion: "5",
status: "DEPLOYED",
workerName: "counterlab",
});
@@ -278,11 +373,25 @@ describe("deployment receipt schema", () => {
expect(() =>
DeploymentReceiptSchema.parse({
...receipt(),
- generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationFilesystemReadIsolation: "PARTIAL",
}),
).toThrow();
});
+ it("retains the historical v4 PARTIAL isolation contract", () => {
+ expect(
+ DeploymentReceiptV4Schema.parse({
+ ...receipt(),
+ schemaVersion: "4",
+ generationFilesystemReadIsolation: "PARTIAL",
+ verifierVersion: "counterlab-deployment-v4",
+ }),
+ ).toMatchObject({
+ schemaVersion: "4",
+ generationFilesystemReadIsolation: "PARTIAL",
+ });
+ });
+
it("rejects a dry-run projection that changes bytes or contains another file", () => {
expect(() =>
DeploymentReceiptSchema.parse({
@@ -316,6 +425,7 @@ describe("deployment receipt schema", () => {
DeploymentReceiptV3Schema.parse({
...historical,
schemaVersion: "3",
+ generationFilesystemReadIsolation: "PARTIAL",
verifierVersion: "counterlab-deployment-v3",
}),
).toMatchObject({ schemaVersion: "3" });
@@ -347,22 +457,32 @@ describe("deployment receipt schema", () => {
);
});
- it("publishes the timeout-bound v4 deployment receipt as generated JSON Schema", () => {
- const path = resolve(
+ it("publishes distinct historical v4 and isolation-enforced v5 deployment schemas", () => {
+ const v4Path = resolve(
process.cwd(),
"scientific-engines/schemas/deployment-receipt-v4.schema.json",
);
- expect(existsSync(path)).toBe(true);
- const schema = JSON.parse(readFileSync(path, "utf8")) as {
+ const v5Path = resolve(
+ process.cwd(),
+ "scientific-engines/schemas/deployment-receipt-v5.schema.json",
+ );
+ expect(existsSync(v4Path)).toBe(true);
+ expect(existsSync(v5Path)).toBe(true);
+ const v4 = JSON.parse(readFileSync(v4Path, "utf8")) as {
properties?: {
schemaVersion?: { const?: string };
+ generationFilesystemReadIsolation?: { const?: string };
dryRunFileCount?: { const?: number };
};
required?: string[];
};
- expect(schema.properties?.schemaVersion?.const).toBe("4");
- expect(schema.properties?.dryRunFileCount?.const).toBe(1);
- expect(schema.required).toEqual(
+ const v5 = JSON.parse(readFileSync(v5Path, "utf8")) as typeof v4;
+ expect(v4.properties?.schemaVersion?.const).toBe("4");
+ expect(v4.properties?.generationFilesystemReadIsolation?.const).toBe(
+ "PARTIAL",
+ );
+ expect(v4.properties?.dryRunFileCount?.const).toBe(1);
+ expect(v4.required).toEqual(
expect.arrayContaining([
"timeoutCleanupReceiptSha256",
"aggregateLimitEvidenceSha256",
@@ -376,5 +496,9 @@ describe("deployment receipt schema", () => {
"wranglerVersion",
]),
);
+ expect(v5.properties?.schemaVersion?.const).toBe("5");
+ expect(v5.properties?.generationFilesystemReadIsolation?.const).toBe(
+ "OS_ENFORCED",
+ );
});
});
diff --git a/scientific-engines/schemas/deployment-receipt-v5.schema.json b/scientific-engines/schemas/deployment-receipt-v5.schema.json
new file mode 100644
index 00000000..33ab8cf1
--- /dev/null
+++ b/scientific-engines/schemas/deployment-receipt-v5.schema.json
@@ -0,0 +1,229 @@
+{
+ "$id": "https://counterlab.dev/schemas/deployment-receipt-v5.schema.json",
+ "title": "CounterLab deployment receipt v5",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "5"
+ },
+ "status": {
+ "type": "string",
+ "const": "DEPLOYED"
+ },
+ "workerName": {
+ "type": "string",
+ "const": "counterlab"
+ },
+ "productionOrigin": {
+ "type": "string",
+ "const": "https://counterlab.cserules.workers.dev"
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ },
+ "workerEvidenceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "runnerSourceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "qualifiedRunnerReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "releaseCheckReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "releaseCheckCheckedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "timeoutCleanupReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "aggregateLimitEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeToolchainSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimePolicySha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "proofDependencyManifestSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeAdapterSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "adapterImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "workerVersionId": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
+ },
+ "workerTag": {
+ "type": "string",
+ "pattern": "^git-[a-f0-9]{40}$"
+ },
+ "workerMessage": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 256
+ },
+ "containerApplicationId": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_-]{1,128}$"
+ },
+ "containerApplicationVersion": {
+ "type": "string",
+ "pattern": "^[1-9][0-9]*$"
+ },
+ "containerImage": {
+ "type": "string",
+ "pattern": "^registry\\.cloudflare\\.com\\/[A-Za-z0-9_-]{3,64}\\/counterlab-runner@sha256:[a-f0-9]{64}$"
+ },
+ "containerState": {
+ "type": "string",
+ "enum": [
+ "active",
+ "ready"
+ ]
+ },
+ "containerImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "workerArtifactClassification": {
+ "type": "string",
+ "const": "PROCESS_BOUND_PARTIAL"
+ },
+ "workerArtifactManifestSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "deployConfigSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "workerBundleSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "clientAssetsSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "clientAssetCount": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "maximum": 9007199254740991
+ },
+ "clientPublicAssetsSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "clientPublicAssetCount": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "maximum": 9007199254740991
+ },
+ "viteVersion": {
+ "type": "string",
+ "const": "8.1.4"
+ },
+ "wranglerVersion": {
+ "type": "string",
+ "const": "4.110.0"
+ },
+ "dryRunSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "dryRunFileCount": {
+ "type": "number",
+ "const": 1
+ },
+ "deploymentStatusSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "workerVersionSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "containerStatusSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "deployedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "verifierVersion": {
+ "type": "string",
+ "const": "counterlab-deployment-v5"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "status",
+ "workerName",
+ "productionOrigin",
+ "generationFilesystemReadIsolation",
+ "workerEvidenceCommit",
+ "runnerSourceCommit",
+ "qualifiedRunnerReceiptSha256",
+ "releaseCheckReceiptSha256",
+ "releaseCheckCheckedAt",
+ "timeoutCleanupReceiptSha256",
+ "aggregateLimitEvidenceSha256",
+ "runtimeToolchainSha256",
+ "runtimePolicySha256",
+ "proofDependencyManifestSha256",
+ "runtimeAdapterSha256",
+ "adapterImageDigest",
+ "workerVersionId",
+ "workerTag",
+ "workerMessage",
+ "containerApplicationId",
+ "containerApplicationVersion",
+ "containerImage",
+ "containerState",
+ "containerImageDigest",
+ "workerArtifactClassification",
+ "workerArtifactManifestSha256",
+ "deployConfigSha256",
+ "workerBundleSha256",
+ "clientAssetsSha256",
+ "clientAssetCount",
+ "clientPublicAssetsSha256",
+ "clientPublicAssetCount",
+ "viteVersion",
+ "wranglerVersion",
+ "dryRunSha256",
+ "dryRunFileCount",
+ "deploymentStatusSha256",
+ "workerVersionSha256",
+ "containerStatusSha256",
+ "deployedAt",
+ "verifierVersion"
+ ],
+ "additionalProperties": false
+}
diff --git a/scientific-engines/schemas/qualified-runner-release-v5.schema.json b/scientific-engines/schemas/qualified-runner-release-v5.schema.json
new file mode 100644
index 00000000..db58db8a
--- /dev/null
+++ b/scientific-engines/schemas/qualified-runner-release-v5.schema.json
@@ -0,0 +1,235 @@
+{
+ "$id": "https://counterlab.dev/schemas/qualified-runner-release-v5.schema.json",
+ "title": "CounterLab qualified runner release v5",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "5"
+ },
+ "status": {
+ "type": "string",
+ "const": "VERIFIED"
+ },
+ "sourceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "sourceArchiveSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "sourceTreeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "dockerfileSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "localImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-runner:git-[a-f0-9]{40}$"
+ },
+ "localImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "ociRevision": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "ociSourceTreeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "engineAuthorityHash": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeManifestHash": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeToolchainSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimePolicySha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "proofDependencyManifestSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "toolchainLockSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeAdapterSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "buildctlSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "buildkitdSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "buildkitConfigSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "adapterDockerfileSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "adapterImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-adapter:git-[a-f0-9]{40}$"
+ },
+ "adapterImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "adapterManifestDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "adapterOciArchiveSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "adapterOciRevision": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "adapterOciSourceTreeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "limitMode": {
+ "type": "string",
+ "const": "container-cgroup-and-process-rlimit"
+ },
+ "aggregateLimitIntentEnforced": {
+ "type": "boolean",
+ "const": true
+ },
+ "aggregateLimitEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "timeoutCleanupReceipt": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 300
+ },
+ "timeoutCleanupReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "timeoutCleanupPayloadSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "timeoutRunControlReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "timeoutRootlessReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "timeoutRuntimeSessionId": {
+ "type": "string",
+ "pattern": "^rt-[a-z0-9][a-z0-9-]{7,13}$"
+ },
+ "timeoutVerifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "evidenceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "registryImage": {
+ "type": "string",
+ "pattern": "^registry\\.cloudflare\\.com\\/[A-Za-z0-9_-]{3,64}\\/counterlab-runner:git-[a-f0-9]{40}$"
+ },
+ "registryDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "registryResolvedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "qualifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "verifierVersion": {
+ "type": "string",
+ "const": "counterlab-release-v5"
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "status",
+ "sourceCommit",
+ "sourceArchiveSha256",
+ "sourceTreeSha256",
+ "dockerfileSha256",
+ "localImageTag",
+ "localImageDigest",
+ "ociRevision",
+ "ociSourceTreeSha256",
+ "engineAuthorityHash",
+ "runtimeManifestHash",
+ "runtimeToolchainSha256",
+ "runtimePolicySha256",
+ "proofDependencyManifestSha256",
+ "toolchainLockSha256",
+ "runtimeAdapterSha256",
+ "buildctlSha256",
+ "buildkitdSha256",
+ "buildkitConfigSha256",
+ "adapterDockerfileSha256",
+ "adapterImageTag",
+ "adapterImageDigest",
+ "adapterManifestDigest",
+ "adapterOciArchiveSha256",
+ "adapterOciRevision",
+ "adapterOciSourceTreeSha256",
+ "limitMode",
+ "aggregateLimitIntentEnforced",
+ "aggregateLimitEvidenceSha256",
+ "timeoutCleanupReceipt",
+ "timeoutCleanupReceiptSha256",
+ "timeoutCleanupPayloadSha256",
+ "timeoutRunControlReceiptSha256",
+ "timeoutRootlessReceiptSha256",
+ "timeoutRuntimeSessionId",
+ "timeoutVerifiedAt",
+ "evidenceCommit",
+ "registryImage",
+ "registryDigest",
+ "registryResolvedAt",
+ "qualifiedAt",
+ "verifierVersion",
+ "generationFilesystemReadIsolation"
+ ],
+ "additionalProperties": false
+}
diff --git a/scientific-engines/schemas/release-check-receipt-v3.schema.json b/scientific-engines/schemas/release-check-receipt-v3.schema.json
new file mode 100644
index 00000000..17da26c8
--- /dev/null
+++ b/scientific-engines/schemas/release-check-receipt-v3.schema.json
@@ -0,0 +1,144 @@
+{
+ "$id": "https://counterlab.dev/schemas/release-check-receipt-v3.schema.json",
+ "title": "CounterLab release-check receipt v3",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "3"
+ },
+ "status": {
+ "type": "string",
+ "const": "PASSED"
+ },
+ "evidenceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "sourceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "qualifiedRunnerReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "qualifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "runnerImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-runner:git-[a-f0-9]{40}$"
+ },
+ "runnerImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "adapterImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-adapter:git-[a-f0-9]{40}$"
+ },
+ "adapterImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "registryDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "runtimeToolchainSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimePolicySha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "proofDependencyManifestSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "aggregateLimitEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeAdapterSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "checks": {
+ "minItems": 11,
+ "maxItems": 11,
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "enum": [
+ "test-all",
+ "leakage-mutations",
+ "imbalance-mutations",
+ "held-out",
+ "adapter-sandbox-smoke",
+ "scientific-engines",
+ "web-build",
+ "achieved-metrics",
+ "reproduce-leakage",
+ "replay-patch",
+ "secret-scan"
+ ]
+ },
+ "status": {
+ "type": "string",
+ "const": "PASSED"
+ }
+ },
+ "required": [
+ "id",
+ "status"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "checkedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "verifierVersion": {
+ "type": "string",
+ "const": "counterlab-release-check-v3"
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "status",
+ "evidenceCommit",
+ "sourceCommit",
+ "qualifiedRunnerReceiptSha256",
+ "qualifiedAt",
+ "runnerImageTag",
+ "runnerImageDigest",
+ "adapterImageTag",
+ "adapterImageDigest",
+ "registryDigest",
+ "runtimeToolchainSha256",
+ "runtimePolicySha256",
+ "proofDependencyManifestSha256",
+ "aggregateLimitEvidenceSha256",
+ "runtimeAdapterSha256",
+ "checks",
+ "checkedAt",
+ "verifierVersion",
+ "generationFilesystemReadIsolation"
+ ],
+ "additionalProperties": false
+}
diff --git a/scripts/create-deployment-receipt.test.ts b/scripts/create-deployment-receipt.test.ts
index 32dca2b3..f3779437 100644
--- a/scripts/create-deployment-receipt.test.ts
+++ b/scripts/create-deployment-receipt.test.ts
@@ -194,6 +194,7 @@ describe("deployment receipt Worker identity", () => {
describe("deployment receipt qualification binding", () => {
const qualifiedReceiptBytes = Buffer.from("qualified-receipt\n");
const qualified = {
+ generationFilesystemReadIsolation: "OS_ENFORCED",
evidenceCommit: "a".repeat(40),
sourceCommit: "b".repeat(40),
registryDigest: `sha256:${"c".repeat(64)}`,
@@ -209,6 +210,8 @@ describe("deployment receipt qualification binding", () => {
aggregateLimitEvidenceSha256: "5".repeat(64),
} as const;
const releaseCheck = {
+ generationFilesystemReadIsolation:
+ qualified.generationFilesystemReadIsolation,
evidenceCommit: qualified.evidenceCommit,
sourceCommit: qualified.sourceCommit,
qualifiedRunnerReceiptSha256: createHash("sha256")
diff --git a/scripts/create-deployment-receipt.ts b/scripts/create-deployment-receipt.ts
index da660664..615591df 100644
--- a/scripts/create-deployment-receipt.ts
+++ b/scripts/create-deployment-receipt.ts
@@ -482,8 +482,7 @@ async function main(): Promise {
runnerSourceCommit: args["--source-commit"],
runnerImageDigest: args["--registry-digest"],
timeoutCleanupReceiptSha256: qualified.timeoutCleanupReceiptSha256,
- aggregateLimitEvidenceSha256:
- qualified.aggregateLimitEvidenceSha256,
+ aggregateLimitEvidenceSha256: qualified.aggregateLimitEvidenceSha256,
runtimePolicySha256: qualified.runtimePolicySha256,
proofDependencyManifestSha256: qualified.proofDependencyManifestSha256,
workerArtifactClassification: frozenWorkerRelease.identity.classification,
@@ -560,19 +559,19 @@ async function main(): Promise {
const dryRun = dryRunWorkerHash(root, paths.dryRun, workerBundleSha256);
const configBytes = readFileSync(paths.config);
const receipt = DeploymentReceiptSchema.parse({
- schemaVersion: "4",
+ schemaVersion: "5",
status: "DEPLOYED",
workerName: "counterlab",
productionOrigin: "https://counterlab.cserules.workers.dev",
- generationFilesystemReadIsolation: "PARTIAL",
+ generationFilesystemReadIsolation:
+ releaseCheck.generationFilesystemReadIsolation,
workerEvidenceCommit: args["--evidence-commit"],
runnerSourceCommit: args["--source-commit"],
qualifiedRunnerReceiptSha256: sha256(qualifiedBytes),
releaseCheckReceiptSha256: sha256(releaseCheckBytes),
releaseCheckCheckedAt: releaseCheck.checkedAt,
timeoutCleanupReceiptSha256: qualified.timeoutCleanupReceiptSha256,
- aggregateLimitEvidenceSha256:
- qualified.aggregateLimitEvidenceSha256,
+ aggregateLimitEvidenceSha256: qualified.aggregateLimitEvidenceSha256,
runtimeToolchainSha256: releaseCheck.runtimeToolchainSha256,
runtimePolicySha256: releaseCheck.runtimePolicySha256,
proofDependencyManifestSha256: releaseCheck.proofDependencyManifestSha256,
@@ -602,7 +601,7 @@ async function main(): Promise {
workerVersionSha256: sha256(versionBytes),
containerStatusSha256: sha256(containersBytes),
deployedAt: new Date().toISOString(),
- verifierVersion: "counterlab-deployment-v4",
+ verifierVersion: "counterlab-deployment-v5",
});
await writeFile(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`, {
encoding: "utf8",
diff --git a/scripts/generate-contract-schemas.ts b/scripts/generate-contract-schemas.ts
index 953b2238..02cac6ec 100644
--- a/scripts/generate-contract-schemas.ts
+++ b/scripts/generate-contract-schemas.ts
@@ -20,11 +20,14 @@ import {
ContainedRuntimeAttestationV1Schema,
DeploymentReceiptSchema,
DeploymentReceiptV3Schema,
+ DeploymentReceiptV4Schema,
QualifiedRunnerReleaseSchema,
QualifiedRunnerReleaseV2Schema,
QualifiedRunnerReleaseV3Schema,
+ QualifiedRunnerReleaseV4Schema,
ReleaseCheckReceiptSchema,
ReleaseCheckReceiptV1Schema,
+ ReleaseCheckReceiptV2Schema,
ScientificEngineEvidenceCatalogSchema,
ScientificEngineRegistrySchema,
ScientificEngineRuntimeManifestSchema,
@@ -237,7 +240,7 @@ const schemas = [
fileName: "qualified-runner-release-v4.schema.json",
id: "https://counterlab.dev/schemas/qualified-runner-release-v4.schema.json",
title: "CounterLab qualified runner release v4",
- schema: QualifiedRunnerReleaseSchema,
+ schema: QualifiedRunnerReleaseV4Schema,
destinations: [
resolve(
root,
@@ -245,6 +248,18 @@ const schemas = [
),
],
},
+ {
+ fileName: "qualified-runner-release-v5.schema.json",
+ id: "https://counterlab.dev/schemas/qualified-runner-release-v5.schema.json",
+ title: "CounterLab qualified runner release v5",
+ schema: QualifiedRunnerReleaseSchema,
+ destinations: [
+ resolve(
+ root,
+ "scientific-engines/schemas/qualified-runner-release-v5.schema.json",
+ ),
+ ],
+ },
{
fileName: "timeout-cleanup-receipt-v1.schema.json",
id: "https://counterlab.dev/schemas/timeout-cleanup-receipt-v1.schema.json",
@@ -297,7 +312,7 @@ const schemas = [
fileName: "release-check-receipt-v2.schema.json",
id: "https://counterlab.dev/schemas/release-check-receipt-v2.schema.json",
title: "CounterLab release-check receipt v2",
- schema: ReleaseCheckReceiptSchema,
+ schema: ReleaseCheckReceiptV2Schema,
destinations: [
resolve(
root,
@@ -305,6 +320,18 @@ const schemas = [
),
],
},
+ {
+ fileName: "release-check-receipt-v3.schema.json",
+ id: "https://counterlab.dev/schemas/release-check-receipt-v3.schema.json",
+ title: "CounterLab release-check receipt v3",
+ schema: ReleaseCheckReceiptSchema,
+ destinations: [
+ resolve(
+ root,
+ "scientific-engines/schemas/release-check-receipt-v3.schema.json",
+ ),
+ ],
+ },
{
fileName: "deployment-receipt-v3.schema.json",
id: "https://counterlab.dev/schemas/deployment-receipt-v3.schema.json",
@@ -321,7 +348,7 @@ const schemas = [
fileName: "deployment-receipt-v4.schema.json",
id: "https://counterlab.dev/schemas/deployment-receipt-v4.schema.json",
title: "CounterLab deployment receipt v4",
- schema: DeploymentReceiptSchema,
+ schema: DeploymentReceiptV4Schema,
destinations: [
resolve(
root,
@@ -329,6 +356,18 @@ const schemas = [
),
],
},
+ {
+ fileName: "deployment-receipt-v5.schema.json",
+ id: "https://counterlab.dev/schemas/deployment-receipt-v5.schema.json",
+ title: "CounterLab deployment receipt v5",
+ schema: DeploymentReceiptSchema,
+ destinations: [
+ resolve(
+ root,
+ "scientific-engines/schemas/deployment-receipt-v5.schema.json",
+ ),
+ ],
+ },
];
for (const definition of schemas) {
diff --git a/scripts/prepare-qualified-deploy.test.ts b/scripts/prepare-qualified-deploy.test.ts
index 4dd1d93e..1105bb4d 100644
--- a/scripts/prepare-qualified-deploy.test.ts
+++ b/scripts/prepare-qualified-deploy.test.ts
@@ -22,6 +22,7 @@ describe("qualified release evidence-only delta", () => {
it("rejects authority schemas, runtime policy, and undeclared evidence files", () => {
for (const path of [
"scientific-engines/schemas/qualified-runner-release-v4.schema.json",
+ "scientific-engines/schemas/qualified-runner-release-v5.schema.json",
"services/runner/src/counterlab_runner/contained-runtime-policy.json",
"scientific-engines/fixtures/validation/unreviewed.json",
"scripts/qualify-runner-release.ts",
diff --git a/scripts/prepare-qualified-deploy.ts b/scripts/prepare-qualified-deploy.ts
index 89d6601f..9c7be94a 100644
--- a/scripts/prepare-qualified-deploy.ts
+++ b/scripts/prepare-qualified-deploy.ts
@@ -68,6 +68,7 @@ export type RunnerReleaseObservation = {
};
export type QualifiedReleaseObservation = RunnerReleaseObservation & {
+ generationFilesystemReadIsolation: "OS_ENFORCED";
limitMode: "container-cgroup-and-process-rlimit";
aggregateLimitIntentEnforced: true;
aggregateLimitEvidenceSha256: string;
@@ -648,6 +649,8 @@ export async function collectQualifiedReleaseObservation(input: {
return {
...observation,
...timeout,
+ generationFilesystemReadIsolation:
+ receipt.generationFilesystemReadIsolation,
};
}
@@ -657,8 +660,10 @@ export function createQualifiedRunnerRelease(
): unknown {
assertQualifiedObservation(observation);
return QualifiedRunnerReleaseSchema.parse({
- schemaVersion: "4",
+ schemaVersion: "5",
status: "VERIFIED",
+ generationFilesystemReadIsolation:
+ observation.generationFilesystemReadIsolation,
sourceCommit: observation.sourceCommit,
sourceArchiveSha256: observation.sourceArchiveSha256,
sourceTreeSha256: observation.sourceTreeSha256,
@@ -699,7 +704,7 @@ export function createQualifiedRunnerRelease(
registryDigest: observation.registryDigest,
registryResolvedAt: observation.registryResolvedAt,
qualifiedAt,
- verifierVersion: "counterlab-release-v4",
+ verifierVersion: "counterlab-release-v5",
});
}
diff --git a/scripts/production-smoke.sh b/scripts/production-smoke.sh
index c8a19f2c..05fa148b 100755
--- a/scripts/production-smoke.sh
+++ b/scripts/production-smoke.sh
@@ -481,7 +481,7 @@ stage_started="$(timestamp)"
"${CURL_BIN}" --fail --silent --show-error \
--proto '=https' --proto-redir '=https' --max-redirs 0 \
--max-time 30 \
- "${BASE_URL}/api/health" \
+ "${BASE_URL}/api/health?readiness=probe" \
>"${WORK_DIR}/health.json"
python3 - "${WORK_DIR}/health.json" "${WORKER_VERSION_ID}" "${WORKER_EVIDENCE_COMMIT}" "${RUNNER_SOURCE_COMMIT}" "${CONTAINER_IMAGE_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" <<'PY'
import json
@@ -500,7 +500,7 @@ required = {
"liveCodex": "configured",
"liveKernel": "configured",
"sandbox": "credential-and-privilege-boundary",
- "generationFilesystemReadIsolation": "PARTIAL",
+ "generationFilesystemReadIsolation": "OS_ENFORCED",
}
for key, expected in required.items():
if health.get(key) != expected:
@@ -509,8 +509,8 @@ for key, expected in required.items():
)
if health.get("maintenance") is not False:
raise SystemExit("production health reports maintenance mode")
-if health.get("readiness") != "not-checked":
- raise SystemExit("ordinary production health must not perform a deep readiness probe")
+if health.get("readiness") != "ready":
+ raise SystemExit("production health deep readiness probe did not pass")
expected_release = {
"status": "bound",
"workerVersionId": sys.argv[2],
diff --git a/scripts/qualify-runner-release.ts b/scripts/qualify-runner-release.ts
index 44e8b312..b4d7a1ca 100644
--- a/scripts/qualify-runner-release.ts
+++ b/scripts/qualify-runner-release.ts
@@ -733,7 +733,11 @@ async function main(): Promise {
adapterManifestDigest: buildReceipt.adapterManifestDigest,
adapterOciArchiveSha256: buildReceipt.adapterOciArchiveSha256,
});
- const observation = { ...releaseObservation, ...timeoutProof };
+ const observation = {
+ ...releaseObservation,
+ ...timeoutProof,
+ generationFilesystemReadIsolation: "OS_ENFORCED" as const,
+ };
for (const [label, built, observed] of [
[
"source archive",
diff --git a/scripts/release-check-receipt.ts b/scripts/release-check-receipt.ts
index bb2ee067..30f5daa3 100644
--- a/scripts/release-check-receipt.ts
+++ b/scripts/release-check-receipt.ts
@@ -188,8 +188,10 @@ export function createReleaseCheckReceipt(input: {
);
const checkedAt = input.checkedAt ?? new Date().toISOString();
const receipt = ReleaseCheckReceiptSchema.parse({
- schemaVersion: "2",
+ schemaVersion: "3",
status: "PASSED",
+ generationFilesystemReadIsolation:
+ qualified.generationFilesystemReadIsolation,
evidenceCommit: qualified.evidenceCommit,
sourceCommit: qualified.sourceCommit,
qualifiedRunnerReceiptSha256: sha256(input.qualifiedReceiptBytes),
@@ -206,7 +208,7 @@ export function createReleaseCheckReceipt(input: {
runtimeAdapterSha256: qualified.runtimeAdapterSha256,
checks: RELEASE_CHECK_IDS.map((id) => ({ id, status: "PASSED" })),
checkedAt,
- verifierVersion: "counterlab-release-check-v2",
+ verifierVersion: "counterlab-release-check-v3",
});
return assertReleaseCheckBinding({
qualifiedReceipt: qualified,
diff --git a/scripts/test_production_smoke.py b/scripts/test_production_smoke.py
index 61b4bdde..8d2c7232 100644
--- a/scripts/test_production_smoke.py
+++ b/scripts/test_production_smoke.py
@@ -146,11 +146,11 @@ def _deployment_receipt() -> dict[str, object]:
runner = "b" * 40
digest = "sha256:" + "c" * 64
return {
- "schemaVersion": "4",
+ "schemaVersion": "5",
"status": "DEPLOYED",
"workerName": "counterlab",
"productionOrigin": "https://counterlab.cserules.workers.dev",
- "generationFilesystemReadIsolation": "PARTIAL",
+ "generationFilesystemReadIsolation": "OS_ENFORCED",
"workerEvidenceCommit": worker,
"runnerSourceCommit": runner,
"qualifiedRunnerReceiptSha256": "1" * 64,
@@ -189,7 +189,7 @@ def _deployment_receipt() -> dict[str, object]:
"workerVersionSha256": "a" * 64,
"containerStatusSha256": "b" * 64,
"deployedAt": "2026-07-19T00:00:00.000Z",
- "verifierVersion": "counterlab-deployment-v4",
+ "verifierVersion": "counterlab-deployment-v5",
}
@@ -314,8 +314,8 @@ def test_control_plane_validators_bind_exact_release_and_maintenance_state() ->
"liveCodex": "configured",
"liveKernel": "configured",
"sandbox": "credential-and-privilege-boundary",
- "generationFilesystemReadIsolation": "PARTIAL",
- "readiness": "not-checked",
+ "generationFilesystemReadIsolation": "OS_ENFORCED",
+ "readiness": "ready",
"maintenance": False,
"release": release,
},
diff --git a/scripts/verify-scientific-engines.sh b/scripts/verify-scientific-engines.sh
index 91b4eff4..cf8ae3f0 100755
--- a/scripts/verify-scientific-engines.sh
+++ b/scripts/verify-scientific-engines.sh
@@ -196,8 +196,9 @@ node -e '
value.status !== "ready" ||
value.service !== "counterlab-hosted-runner" ||
value.probe !== "non-root-startup" ||
+ value.generationFilesystemReadIsolation !== "OS_ENFORCED" ||
JSON.stringify(value.checks) !==
- JSON.stringify(["entrypoint", "non-root-user", "immutable-paths", "codex", "python", "setpriv", "writable-roots"])
+ JSON.stringify(["entrypoint", "non-root-user", "immutable-paths", "codex", "python", "bubblewrap", "bubblewrap-read-isolation", "setpriv", "writable-roots"])
) {
throw new Error("Runner non-root startup probe returned an invalid sentinel");
}
From ce2da766a48b818bff6a3998ca776ae342db5451 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 02:40:34 +0530
Subject: [PATCH 007/143] docs(progress): record isolation release state
---
docs/DECISIONS.md | 13 ++++++++
docs/PROGRESS.md | 64 +++++++++++++++++++++++++++++++++++++++
docs/RELEASE_CHECKLIST.md | 6 ++++
3 files changed, 83 insertions(+)
diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md
index 654c8cd2..cc603b04 100644
--- a/docs/DECISIONS.md
+++ b/docs/DECISIONS.md
@@ -771,3 +771,16 @@ New sessions receive a separate 256-bit owner key.
- Rehash the manifest, qualification receipt, and release-check receipt before
every state-changing deployment step. Any byte drift stops before maintenance
mode, migrations, Container rollout, or final Worker upload.
+
+## 2026-07-20 — Version stronger isolation evidence instead of rewriting receipts
+
+- Preserve qualified-runner v4 and release-check v2 without a generation
+ isolation field, and preserve deployment v4 with its exact `PARTIAL`
+ isolation meaning.
+- Use qualified-runner v5, release-check v3, and deployment v5 only for the
+ strict `OS_ENFORCED` status contract. Never reinterpret historical bytes.
+- Treat the status field as necessary but insufficient release proof. A later
+ version must bind a persisted canonical exact-image isolation sentinel and
+ its hash through qualification, deployment, production readiness, and smoke.
+- Keep `PROCESS_BOUND_PARTIAL` separate: it describes Worker/client artifact
+ attestation, not the hosted generation filesystem boundary.
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 9e8facbb..fded4d12 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -2896,3 +2896,67 @@ qualification evidence.
submission receipt before final publication.
6. Push the finished feature branch and fast-forward its verified tip into
`main` only after the preceding gates are truthfully recorded.
+
+## Finish Commander isolation checkpoint — 2026-07-20T21:08:25Z
+
+Deadline: `2026-07-22T00:00:00Z`; **26 hours 51 minutes 35 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD `f365132`; the only
+worktree entry is the preserved, untracked
+`scripts/.fuse_hidden0000a9600000801f`, which was not opened, staged, or
+deleted.
+
+### Newly frozen independent commits
+
+- `2b94c9d feat(runner): enforce Bubblewrap generation boundary` pins and
+ verifies Bubblewrap in the runner image, hides non-allowlisted host roots,
+ keeps only the job workspace writable, validates command/root containment,
+ and waits for the Codex child to exit before final credential cleanup.
+- `f365132 fix(release): version enforced isolation receipts` makes deep runner
+ readiness require `OS_ENFORCED`, requires that state in production smoke,
+ preserves historical qualified v4/release-check v2/deployment v4 receipt
+ semantics byte-for-byte, and introduces strict v5/v3/v5 receipts for the new
+ isolation claim.
+
+### Exact local verification
+
+- Release/schema/contained-runtime focused Vitest: **65/65 passed**.
+- Worker/API/client focused Vitest: **186/186 passed**.
+- Production-smoke Pytest: **19/19 passed**.
+- Reproduction tooling Pytest: **12/12 passed** with both repository Python
+ source roots supplied explicitly.
+- Repository, web, Worker, and hosted-runner TypeScript: **passed**.
+- Scoped Prettier, Git whitespace, generated-schema byte comparison, and the
+ explicit 21-file secret scan: **passed**.
+- Broader scientific release-verifier run: **76/79 passed**. The three failures
+ are expected stale source-bound integrity evidence and remain an explicit
+ regeneration gate after the final source freeze; they are not relabelled as
+ passing.
+- The first isolated Python commands omitted repository import roots and
+ failed collection; the correctly scoped invocations above are the recorded
+ results.
+
+### Current release truth and ordered queue
+
+1. Persist a canonical source/tree/image-bound generation-isolation sentinel,
+ carry its evidence and probe hashes through new receipt versions, and
+ require the exact probe hash from production runner readiness. The current
+ v5/v3/v5 status literal is necessary but not yet independently auditable.
+2. Obtain immediate owner approval before removing the named FUSE artifact, or
+ keep release qualification blocked on a non-clean tree.
+3. Freeze source, build one exact image, and prove both the new isolation
+ sentinel and aggregate cgroup-v2 containment. This host still has no
+ delegated writable aggregate-cgroup subtree, so that gate remains
+ fail-closed.
+4. Regenerate source/image-bound scientific, SBOM, VEX, health, held-out, and
+ negative-control evidence; issue qualification and release-check receipts
+ only from that tuple.
+5. Verify repository-contained Wrangler identity, deploy only the qualified
+ tuple, and run public production smoke. No deployment is claimed here.
+6. Run the full desktop/mobile/accessibility journey matrix through
+ CloakBrowser. The current session still has no `CLOAK_CDP_ENDPOINT`; prior
+ stock-Chromium captures remain design-review evidence only.
+7. Keep learner impact `NO_DATA` without consented observations, then align the
+ exact app, repository, video, Devpost entry, feedback ID, and submission
+ receipt before publication.
+8. Push the completed feature branch, review its exact tip, fast-forward it
+ into `main`, and push `main` only after the release state is truthful.
diff --git a/docs/RELEASE_CHECKLIST.md b/docs/RELEASE_CHECKLIST.md
index 0d91baec..9e8c32a9 100644
--- a/docs/RELEASE_CHECKLIST.md
+++ b/docs/RELEASE_CHECKLIST.md
@@ -357,3 +357,9 @@ after the final source freeze.
the qualified deployed tuple.
- [ ] Review and commit each logical slice with the configured user identity,
then fast-forward merge `feat/learner-ux-v6.1` into `main`.
+- [x] Preserve historical qualified v4/release-check v2/deployment v4 receipt
+ semantics and introduce strict v5/v3/v5 contracts for `OS_ENFORCED`
+ generation-filesystem isolation.
+- [ ] Persist and hash-bind the exact-image generation-isolation sentinel,
+ require its exact probe hash from the production runner, and propagate it
+ through new receipt versions without rewriting v5/v3/v5.
From b5d23597708ba67067191fc31a6fbefc4c8351db Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 02:47:12 +0530
Subject: [PATCH 008/143] fix(runner): hash Bubblewrap startup evidence
---
.../hosted-runner/src/startup-probe.test.ts | 91 ++++++++++++-
services/hosted-runner/src/startup-probe.ts | 127 ++++++++++++++----
2 files changed, 189 insertions(+), 29 deletions(-)
diff --git a/services/hosted-runner/src/startup-probe.test.ts b/services/hosted-runner/src/startup-probe.test.ts
index b035039b..700211c5 100644
--- a/services/hosted-runner/src/startup-probe.test.ts
+++ b/services/hosted-runner/src/startup-probe.test.ts
@@ -1,6 +1,16 @@
import { describe, expect, it, vi } from "vitest";
-import { runHostedRunnerStartupProbe } from "./startup-probe.js";
+import {
+ hashGenerationIsolationProbe,
+ runHostedRunnerStartupProbe,
+} from "./startup-probe.js";
+
+const BUBBLEWRAP_OUTPUT = {
+ forbiddenHostPathsHidden: true,
+ parentEnvironmentHidden: true,
+ workspaceVisible: true,
+ workspaceWritable: true,
+} as const;
describe("hosted runner startup probe", () => {
it("checks the real runtime chain without propagating credentials", async () => {
@@ -14,7 +24,10 @@ describe("hosted runner startup probe", () => {
_executable: string,
_args: string[],
_options: { env: NodeJS.ProcessEnv; timeout: number },
- ) => undefined,
+ ) =>
+ _executable === "/runtime/bwrap"
+ ? { stdout: `${JSON.stringify(BUBBLEWRAP_OUTPUT)}\n` }
+ : { stdout: "" },
);
const writeFile = vi.fn(async () => undefined);
const stat = vi.fn(async (path: string) => ({
@@ -67,7 +80,33 @@ describe("hosted runner startup probe", () => {
"writable-roots",
],
generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationProbe: {
+ schemaVersion: "1",
+ probeVersion: "counterlab-generation-isolation-v1",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: [
+ "entrypoint",
+ "non-root-user",
+ "immutable-paths",
+ "codex",
+ "python",
+ "bubblewrap",
+ "bubblewrap-read-isolation",
+ "setpriv",
+ "writable-roots",
+ ],
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ bubblewrap: BUBBLEWRAP_OUTPUT,
+ },
+ generationIsolationProbeSha256: expect.stringMatching(/^[a-f0-9]{64}$/u),
});
+ expect(result.generationIsolationProbeSha256).toBe(
+ hashGenerationIsolationProbe(result.generationIsolationProbe),
+ );
+ expect(result.generationIsolationProbeSha256).toBe(
+ "cc950c368771f715d7b8807b20c91a04b53065d0ad64ba51024b638f7da329ea",
+ );
expect(stat.mock.calls.map(([path]) => path)).toEqual([
"/runtime/app",
"/runtime/runner.mjs",
@@ -147,6 +186,54 @@ describe("hosted runner startup probe", () => {
).rejects.toThrow("not executable");
});
+ it.each([
+ ["missing stdout", undefined],
+ ["invalid JSON", { stdout: "not-json" }],
+ [
+ "a false isolation check",
+ {
+ stdout: JSON.stringify({
+ ...BUBBLEWRAP_OUTPUT,
+ parentEnvironmentHidden: false,
+ }),
+ },
+ ],
+ [
+ "an unknown isolation field",
+ {
+ stdout: JSON.stringify({
+ ...BUBBLEWRAP_OUTPUT,
+ unverifiedClaim: true,
+ }),
+ },
+ ],
+ ])("fails closed when Bubblewrap returns %s", async (_label, probeResult) => {
+ let invocation = 0;
+ await expect(
+ runHostedRunnerStartupProbe({
+ bundlePath: "/app/runner.mjs",
+ access: async () => undefined,
+ mkdir: async () => undefined as never,
+ writeFile: async () => undefined,
+ stat: (async (
+ path: Parameters[0],
+ ) => ({
+ uid: 0,
+ gid: 0,
+ mode: 0o555,
+ isDirectory: () => path === "/app",
+ isFile: () => path === "/app/runner.mjs",
+ })) as unknown as typeof import("node:fs/promises").stat,
+ getUid: () => 10001,
+ getGid: () => 10001,
+ execute: async () => {
+ invocation += 1;
+ return invocation === 4 ? probeResult : { stdout: "" };
+ },
+ }),
+ ).rejects.toThrow();
+ });
+
it("fails closed when PID 1 is not the declared non-root identity", async () => {
await expect(
runHostedRunnerStartupProbe({
diff --git a/services/hosted-runner/src/startup-probe.ts b/services/hosted-runner/src/startup-probe.ts
index a9a3f96b..6c2805fc 100644
--- a/services/hosted-runner/src/startup-probe.ts
+++ b/services/hosted-runner/src/startup-probe.ts
@@ -1,9 +1,13 @@
import { execFile } from "node:child_process";
+import { createHash } from "node:crypto";
import { constants } from "node:fs";
import { access, mkdir, stat, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { promisify } from "node:util";
+import { canonicalJson } from "@counterlab/session-core";
+import { z } from "zod";
+
import { buildContainerBubblewrapProbe } from "./launch-boundary.js";
const execFileAsync = promisify(execFile);
@@ -14,6 +18,72 @@ type Execute = (
options: { env: NodeJS.ProcessEnv; timeout: number },
) => Promise;
+const STARTUP_CHECKS = [
+ "entrypoint",
+ "non-root-user",
+ "immutable-paths",
+ "codex",
+ "python",
+ "bubblewrap",
+ "bubblewrap-read-isolation",
+ "setpriv",
+ "writable-roots",
+] as const;
+
+const BubblewrapReadIsolationOutputSchema = z.strictObject({
+ forbiddenHostPathsHidden: z.literal(true),
+ parentEnvironmentHidden: z.literal(true),
+ workspaceVisible: z.literal(true),
+ workspaceWritable: z.literal(true),
+});
+
+export const GENERATION_ISOLATION_PROBE_VERSION =
+ "counterlab-generation-isolation-v1" as const;
+
+export type GenerationIsolationProbePayload = {
+ schemaVersion: "1";
+ probeVersion: typeof GENERATION_ISOLATION_PROBE_VERSION;
+ service: "counterlab-hosted-runner";
+ probe: "non-root-startup";
+ checks: typeof STARTUP_CHECKS;
+ generationFilesystemReadIsolation: "OS_ENFORCED";
+ bubblewrap: z.infer;
+};
+
+function readExecutionStdout(result: unknown): string {
+ if (typeof result !== "object" || result === null || !("stdout" in result)) {
+ throw new Error(
+ "Hosted runner Bubblewrap probe did not return inspectable stdout",
+ );
+ }
+ const stdout = result.stdout;
+ if (typeof stdout === "string") return stdout;
+ if (Buffer.isBuffer(stdout)) return stdout.toString("utf8");
+ throw new Error(
+ "Hosted runner Bubblewrap probe returned an invalid stdout payload",
+ );
+}
+
+export function createGenerationIsolationProbePayload(
+ bubblewrapOutput: unknown,
+): GenerationIsolationProbePayload {
+ return {
+ schemaVersion: "1",
+ probeVersion: GENERATION_ISOLATION_PROBE_VERSION,
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: STARTUP_CHECKS,
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ bubblewrap: BubblewrapReadIsolationOutputSchema.parse(bubblewrapOutput),
+ };
+}
+
+export function hashGenerationIsolationProbe(
+ payload: GenerationIsolationProbePayload,
+): string {
+ return createHash("sha256").update(canonicalJson(payload)).digest("hex");
+}
+
export type HostedRunnerStartupProbeOptions = {
environment?: NodeJS.ProcessEnv;
nodeExecutable?: string;
@@ -32,18 +102,10 @@ export type HostedRunnerStartupProbeResult = {
status: "ready";
service: "counterlab-hosted-runner";
probe: "non-root-startup";
- checks: [
- "entrypoint",
- "non-root-user",
- "immutable-paths",
- "codex",
- "python",
- "bubblewrap",
- "bubblewrap-read-isolation",
- "setpriv",
- "writable-roots",
- ];
+ checks: typeof STARTUP_CHECKS;
generationFilesystemReadIsolation: "OS_ENFORCED";
+ generationIsolationProbe: GenerationIsolationProbePayload;
+ generationIsolationProbeSha256: string;
};
export async function runHostedRunnerStartupProbe(
@@ -59,7 +121,7 @@ export async function runHostedRunnerStartupProbe(
const execute =
options.execute ??
(async (executable, args, executionOptions) => {
- await execFileAsync(executable, args, executionOptions);
+ return execFileAsync(executable, args, executionOptions);
});
const nodeExecutable = options.nodeExecutable ?? process.execPath;
const bundlePath = options.bundlePath ?? new URL(import.meta.url).pathname;
@@ -158,26 +220,37 @@ export async function runHostedRunnerStartupProbe(
codexRoot,
workspace: probeWorkspace,
});
- await execute(isolationProbe.command, isolationProbe.args, {
- env: isolationProbe.environment,
- timeout: 30_000,
- });
+ const isolationProbeExecution = await execute(
+ isolationProbe.command,
+ isolationProbe.args,
+ {
+ env: isolationProbe.environment,
+ timeout: 30_000,
+ },
+ );
+ let bubblewrapOutput: unknown;
+ try {
+ bubblewrapOutput = JSON.parse(
+ readExecutionStdout(isolationProbeExecution).trim(),
+ ) as unknown;
+ } catch (error) {
+ throw new Error(
+ "Hosted runner Bubblewrap probe returned invalid JSON evidence",
+ { cause: error },
+ );
+ }
+ const generationIsolationProbe =
+ createGenerationIsolationProbePayload(bubblewrapOutput);
return {
status: "ready",
service: "counterlab-hosted-runner",
probe: "non-root-startup",
- checks: [
- "entrypoint",
- "non-root-user",
- "immutable-paths",
- "codex",
- "python",
- "bubblewrap",
- "bubblewrap-read-isolation",
- "setpriv",
- "writable-roots",
- ],
+ checks: STARTUP_CHECKS,
generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationProbe,
+ generationIsolationProbeSha256: hashGenerationIsolationProbe(
+ generationIsolationProbe,
+ ),
};
}
From fc1c58101e66ecb17346dbf9862bd7d86e9f6834 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 02:50:58 +0530
Subject: [PATCH 009/143] fix(runner): bind pinned Bubblewrap version
---
.../hosted-runner/src/startup-probe.test.ts | 32 +++++++++++++++----
services/hosted-runner/src/startup-probe.ts | 24 ++++++++++++--
2 files changed, 47 insertions(+), 9 deletions(-)
diff --git a/services/hosted-runner/src/startup-probe.test.ts b/services/hosted-runner/src/startup-probe.test.ts
index 700211c5..a9068cb6 100644
--- a/services/hosted-runner/src/startup-probe.test.ts
+++ b/services/hosted-runner/src/startup-probe.test.ts
@@ -1,6 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import {
+ createGenerationIsolationProbePayload,
hashGenerationIsolationProbe,
runHostedRunnerStartupProbe,
} from "./startup-probe.js";
@@ -25,9 +26,11 @@ describe("hosted runner startup probe", () => {
_args: string[],
_options: { env: NodeJS.ProcessEnv; timeout: number },
) =>
- _executable === "/runtime/bwrap"
- ? { stdout: `${JSON.stringify(BUBBLEWRAP_OUTPUT)}\n` }
- : { stdout: "" },
+ _executable === "/runtime/bwrap" && _args[0] === "--version"
+ ? { stdout: "bubblewrap 0.11.0\n" }
+ : _executable === "/runtime/bwrap"
+ ? { stdout: `${JSON.stringify(BUBBLEWRAP_OUTPUT)}\n` }
+ : { stdout: "" },
);
const writeFile = vi.fn(async () => undefined);
const stat = vi.fn(async (path: string) => ({
@@ -97,6 +100,7 @@ describe("hosted runner startup probe", () => {
"writable-roots",
],
generationFilesystemReadIsolation: "OS_ENFORCED",
+ bubblewrapVersion: "0.11.0",
bubblewrap: BUBBLEWRAP_OUTPUT,
},
generationIsolationProbeSha256: expect.stringMatching(/^[a-f0-9]{64}$/u),
@@ -105,7 +109,7 @@ describe("hosted runner startup probe", () => {
hashGenerationIsolationProbe(result.generationIsolationProbe),
);
expect(result.generationIsolationProbeSha256).toBe(
- "cc950c368771f715d7b8807b20c91a04b53065d0ad64ba51024b638f7da329ea",
+ "700cc58bedc163846e3854415170f49f55da9fd3ba316cc4967747d5268199dc",
);
expect(stat.mock.calls.map(([path]) => path)).toEqual([
"/runtime/app",
@@ -137,13 +141,17 @@ describe("hosted runner startup probe", () => {
"approved\n",
{ encoding: "utf8", flag: "w", mode: 0o600 },
);
- expect(execute).toHaveBeenCalledTimes(4);
+ expect(execute).toHaveBeenCalledTimes(5);
for (const [, , options] of execute.mock.calls) {
expect(options.env).not.toHaveProperty("CODEX_AUTH_JSON");
expect(options.env).not.toHaveProperty("OPENAI_API_KEY");
}
+ expect(execute.mock.calls[3]?.slice(0, 2)).toEqual([
+ "/runtime/bwrap",
+ ["--version"],
+ ]);
const [probeExecutable, probeArgs, probeOptions] =
- execute.mock.calls[3] ?? [];
+ execute.mock.calls[4] ?? [];
expect(probeExecutable).toBe("/runtime/bwrap");
expect(probeArgs).toContain("--unshare-user");
expect(probeArgs).toContain("--unshare-pid");
@@ -228,12 +236,22 @@ describe("hosted runner startup probe", () => {
getGid: () => 10001,
execute: async () => {
invocation += 1;
- return invocation === 4 ? probeResult : { stdout: "" };
+ if (invocation === 4) return { stdout: "bubblewrap 0.11.0\n" };
+ return invocation === 5 ? probeResult : { stdout: "" };
},
}),
).rejects.toThrow();
});
+ it("rejects a Bubblewrap version that differs from the pinned image", () => {
+ expect(() =>
+ createGenerationIsolationProbePayload(
+ BUBBLEWRAP_OUTPUT,
+ "bubblewrap 0.11.1",
+ ),
+ ).toThrow("requires bubblewrap 0.11.0; observed bubblewrap 0.11.1");
+ });
+
it("fails closed when PID 1 is not the declared non-root identity", async () => {
await expect(
runHostedRunnerStartupProbe({
diff --git a/services/hosted-runner/src/startup-probe.ts b/services/hosted-runner/src/startup-probe.ts
index 6c2805fc..c29a8dda 100644
--- a/services/hosted-runner/src/startup-probe.ts
+++ b/services/hosted-runner/src/startup-probe.ts
@@ -37,6 +37,8 @@ const BubblewrapReadIsolationOutputSchema = z.strictObject({
workspaceWritable: z.literal(true),
});
+const EXPECTED_BUBBLEWRAP_VERSION = "bubblewrap 0.11.0" as const;
+
export const GENERATION_ISOLATION_PROBE_VERSION =
"counterlab-generation-isolation-v1" as const;
@@ -47,6 +49,7 @@ export type GenerationIsolationProbePayload = {
probe: "non-root-startup";
checks: typeof STARTUP_CHECKS;
generationFilesystemReadIsolation: "OS_ENFORCED";
+ bubblewrapVersion: "0.11.0";
bubblewrap: z.infer;
};
@@ -66,7 +69,13 @@ function readExecutionStdout(result: unknown): string {
export function createGenerationIsolationProbePayload(
bubblewrapOutput: unknown,
+ bubblewrapVersionOutput: string,
): GenerationIsolationProbePayload {
+ if (bubblewrapVersionOutput.trim() !== EXPECTED_BUBBLEWRAP_VERSION) {
+ throw new Error(
+ `Hosted runner requires ${EXPECTED_BUBBLEWRAP_VERSION}; observed ${bubblewrapVersionOutput.trim() || "no version"}`,
+ );
+ }
return {
schemaVersion: "1",
probeVersion: GENERATION_ISOLATION_PROBE_VERSION,
@@ -74,6 +83,7 @@ export function createGenerationIsolationProbePayload(
probe: "non-root-startup",
checks: STARTUP_CHECKS,
generationFilesystemReadIsolation: "OS_ENFORCED",
+ bubblewrapVersion: "0.11.0",
bubblewrap: BubblewrapReadIsolationOutputSchema.parse(bubblewrapOutput),
};
}
@@ -215,6 +225,14 @@ export async function runHostedRunnerStartupProbe(
env: childEnvironment,
timeout: 30_000,
});
+ const bubblewrapVersionExecution = await execute(
+ bwrapExecutable,
+ ["--version"],
+ {
+ env: childEnvironment,
+ timeout: 30_000,
+ },
+ );
const isolationProbe = buildContainerBubblewrapProbe({
bwrapExecutable,
codexRoot,
@@ -239,8 +257,10 @@ export async function runHostedRunnerStartupProbe(
{ cause: error },
);
}
- const generationIsolationProbe =
- createGenerationIsolationProbePayload(bubblewrapOutput);
+ const generationIsolationProbe = createGenerationIsolationProbePayload(
+ bubblewrapOutput,
+ readExecutionStdout(bubblewrapVersionExecution),
+ );
return {
status: "ready",
From 9a15ec4cca27f7bd840bbc4855f0965964cccb67 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 03:03:43 +0530
Subject: [PATCH 010/143] feat(release): define image isolation evidence
---
.../scientific-engine-registry/src/schema.ts | 229 ++++++++-
.../scientific-engine-registry/src/types.ts | 28 ++
.../test/generation-isolation-schema.test.ts | 169 +++++++
.../schemas/deployment-receipt-v6.schema.json | 245 ++++++++++
...neration-isolation-evidence-v1.schema.json | 185 ++++++++
.../qualified-runner-release-v6.schema.json | 434 ++++++++++++++++++
.../release-check-receipt-v4.schema.json | 160 +++++++
scripts/generate-contract-schemas.ts | 85 +++-
scripts/generation-isolation-evidence.test.ts | 164 +++++++
scripts/generation-isolation-evidence.ts | 182 ++++++++
10 files changed, 1872 insertions(+), 9 deletions(-)
create mode 100644 packages/scientific-engine-registry/test/generation-isolation-schema.test.ts
create mode 100644 scientific-engines/schemas/deployment-receipt-v6.schema.json
create mode 100644 scientific-engines/schemas/generation-isolation-evidence-v1.schema.json
create mode 100644 scientific-engines/schemas/qualified-runner-release-v6.schema.json
create mode 100644 scientific-engines/schemas/release-check-receipt-v4.schema.json
create mode 100644 scripts/generation-isolation-evidence.test.ts
create mode 100644 scripts/generation-isolation-evidence.ts
diff --git a/packages/scientific-engine-registry/src/schema.ts b/packages/scientific-engine-registry/src/schema.ts
index 245bebff..07b071d6 100644
--- a/packages/scientific-engine-registry/src/schema.ts
+++ b/packages/scientific-engine-registry/src/schema.ts
@@ -616,6 +616,70 @@ export const TimeoutCleanupReceiptSchema = z
}
});
+export const GENERATION_ISOLATION_PROBE_VERSION =
+ "counterlab-generation-isolation-v1" as const;
+
+export const GENERATION_ISOLATION_MOUNT_POLICY_VERSION =
+ "counterlab-bwrap-mount-policy-v1" as const;
+
+export const GenerationIsolationProbePayloadSchema = z.strictObject({
+ schemaVersion: z.literal("1"),
+ probeVersion: z.literal(GENERATION_ISOLATION_PROBE_VERSION),
+ service: z.literal("counterlab-hosted-runner"),
+ probe: z.literal("non-root-startup"),
+ checks: z.tuple([
+ z.literal("entrypoint"),
+ z.literal("non-root-user"),
+ z.literal("immutable-paths"),
+ z.literal("codex"),
+ z.literal("python"),
+ z.literal("bubblewrap"),
+ z.literal("bubblewrap-read-isolation"),
+ z.literal("setpriv"),
+ z.literal("writable-roots"),
+ ]),
+ generationFilesystemReadIsolation: z.literal("OS_ENFORCED"),
+ bubblewrapVersion: z.literal("0.11.0"),
+ bubblewrap: z.strictObject({
+ forbiddenHostPathsHidden: z.literal(true),
+ parentEnvironmentHidden: z.literal(true),
+ workspaceVisible: z.literal(true),
+ workspaceWritable: z.literal(true),
+ }),
+});
+
+export const GenerationIsolationEvidenceV1Schema = z
+ .strictObject({
+ schemaVersion: z.literal("1"),
+ status: z.literal("VERIFIED"),
+ generationFilesystemReadIsolation: z.literal("OS_ENFORCED"),
+ sourceCommit: GitCommitSchema,
+ sourceTreeSha256: Sha256Schema,
+ localImageTag: z.string().regex(/^counterlab-runner:git-[a-f0-9]{40}$/),
+ localImageDigest: OciDigestSchema,
+ imageUser: z.literal("10001:10001"),
+ mountPolicyVersion: z.literal(GENERATION_ISOLATION_MOUNT_POLICY_VERSION),
+ probePayload: GenerationIsolationProbePayloadSchema,
+ probePayloadSha256: Sha256Schema,
+ verifiedAt: z.iso.datetime({ offset: true }),
+ verifierVersion: z.literal("counterlab-generation-isolation-evidence-v1"),
+ })
+ .superRefine((evidence, context) => {
+ if (
+ evidence.localImageTag !==
+ `counterlab-runner:git-${evidence.sourceCommit}`
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["localImageTag"],
+ message: "generation-isolation image tag must bind the source commit",
+ });
+ }
+ });
+
+export const GenerationIsolationEvidenceSchema =
+ GenerationIsolationEvidenceV1Schema;
+
export const QualifiedRunnerReleaseV1Schema = z
.strictObject({
schemaVersion: z.literal("1"),
@@ -966,7 +1030,7 @@ export const QualifiedRunnerReleaseV4Schema = z
}
});
-export const QualifiedRunnerReleaseSchema = z
+export const QualifiedRunnerReleaseV5Schema = z
.strictObject({
...QualifiedRunnerReleaseV4Schema.shape,
schemaVersion: z.literal("5"),
@@ -994,6 +1058,77 @@ export const QualifiedRunnerReleaseSchema = z
}
});
+export const QualifiedRunnerReleaseV6Schema = z
+ .strictObject({
+ ...QualifiedRunnerReleaseV5Schema.shape,
+ schemaVersion: z.literal("6"),
+ generationIsolationEvidence: GenerationIsolationEvidenceV1Schema,
+ generationIsolationEvidenceSha256: Sha256Schema,
+ generationIsolationProbeSha256: Sha256Schema,
+ generationIsolationVerifiedAt: z.iso.datetime({ offset: true }),
+ verifierVersion: z.literal("counterlab-release-v6"),
+ })
+ .superRefine((release, context) => {
+ const {
+ generationIsolationEvidence: _generationIsolationEvidence,
+ generationIsolationEvidenceSha256: _generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: _generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: _generationIsolationVerifiedAt,
+ ...legacyRelease
+ } = release;
+ const result = QualifiedRunnerReleaseV5Schema.safeParse({
+ ...legacyRelease,
+ schemaVersion: "5",
+ verifierVersion: "counterlab-release-v5",
+ });
+ if (!result.success) {
+ for (const issue of result.error.issues) {
+ context.addIssue({
+ code: "custom",
+ path: issue.path,
+ message: issue.message,
+ });
+ }
+ }
+ if (
+ Date.parse(release.generationIsolationVerifiedAt) >
+ Date.parse(release.qualifiedAt)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["generationIsolationVerifiedAt"],
+ message: "generation-isolation proof must precede qualification",
+ });
+ }
+ const evidence = release.generationIsolationEvidence;
+ for (const [field, expected, observed] of [
+ ["sourceCommit", release.sourceCommit, evidence.sourceCommit],
+ ["sourceTreeSha256", release.sourceTreeSha256, evidence.sourceTreeSha256],
+ ["localImageTag", release.localImageTag, evidence.localImageTag],
+ ["localImageDigest", release.localImageDigest, evidence.localImageDigest],
+ [
+ "generationIsolationProbeSha256",
+ release.generationIsolationProbeSha256,
+ evidence.probePayloadSha256,
+ ],
+ [
+ "generationIsolationVerifiedAt",
+ release.generationIsolationVerifiedAt,
+ evidence.verifiedAt,
+ ],
+ ] as const) {
+ if (expected !== observed) {
+ context.addIssue({
+ code: "custom",
+ path: [field],
+ message: `${field} must match embedded generation-isolation evidence`,
+ });
+ }
+ }
+ });
+
+export const QualifiedRunnerReleaseSchema = QualifiedRunnerReleaseV5Schema;
+
export const RELEASE_CHECK_IDS = [
"test-all",
"leakage-mutations",
@@ -1141,7 +1276,7 @@ export const ReleaseCheckReceiptV2Schema = z
}
});
-export const ReleaseCheckReceiptSchema = z
+export const ReleaseCheckReceiptV3Schema = z
.strictObject({
...ReleaseCheckReceiptV2Schema.shape,
schemaVersion: z.literal("3"),
@@ -1169,6 +1304,50 @@ export const ReleaseCheckReceiptSchema = z
}
});
+export const ReleaseCheckReceiptV4Schema = z
+ .strictObject({
+ ...ReleaseCheckReceiptV3Schema.shape,
+ schemaVersion: z.literal("4"),
+ generationIsolationEvidenceSha256: Sha256Schema,
+ generationIsolationProbeSha256: Sha256Schema,
+ generationIsolationVerifiedAt: z.iso.datetime({ offset: true }),
+ verifierVersion: z.literal("counterlab-release-check-v4"),
+ })
+ .superRefine((receipt, context) => {
+ const {
+ generationIsolationEvidenceSha256: _generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: _generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: _generationIsolationVerifiedAt,
+ ...legacyReceipt
+ } = receipt;
+ const result = ReleaseCheckReceiptV3Schema.safeParse({
+ ...legacyReceipt,
+ schemaVersion: "3",
+ verifierVersion: "counterlab-release-check-v3",
+ });
+ if (!result.success) {
+ for (const issue of result.error.issues) {
+ context.addIssue({
+ code: "custom",
+ path: issue.path,
+ message: issue.message,
+ });
+ }
+ }
+ if (
+ Date.parse(receipt.generationIsolationVerifiedAt) >
+ Date.parse(receipt.checkedAt)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["generationIsolationVerifiedAt"],
+ message: "generation-isolation proof must precede release checks",
+ });
+ }
+ });
+
+export const ReleaseCheckReceiptSchema = ReleaseCheckReceiptV3Schema;
+
export const DeploymentReceiptV3Schema = z
.strictObject({
schemaVersion: z.literal("3"),
@@ -1363,7 +1542,7 @@ export const DeploymentReceiptV4Schema = z
}
});
-export const DeploymentReceiptSchema = z
+export const DeploymentReceiptV5Schema = z
.strictObject({
...DeploymentReceiptV4Schema.shape,
schemaVersion: z.literal("5"),
@@ -1387,3 +1566,47 @@ export const DeploymentReceiptSchema = z
}
}
});
+
+export const DeploymentReceiptV6Schema = z
+ .strictObject({
+ ...DeploymentReceiptV5Schema.shape,
+ schemaVersion: z.literal("6"),
+ generationIsolationEvidenceSha256: Sha256Schema,
+ generationIsolationProbeSha256: Sha256Schema,
+ generationIsolationVerifiedAt: z.iso.datetime({ offset: true }),
+ verifierVersion: z.literal("counterlab-deployment-v6"),
+ })
+ .superRefine((receipt, context) => {
+ const {
+ generationIsolationEvidenceSha256: _generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: _generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: _generationIsolationVerifiedAt,
+ ...legacyReceipt
+ } = receipt;
+ const result = DeploymentReceiptV5Schema.safeParse({
+ ...legacyReceipt,
+ schemaVersion: "5",
+ verifierVersion: "counterlab-deployment-v5",
+ });
+ if (!result.success) {
+ for (const issue of result.error.issues) {
+ context.addIssue({
+ code: "custom",
+ path: issue.path,
+ message: issue.message,
+ });
+ }
+ }
+ if (
+ Date.parse(receipt.generationIsolationVerifiedAt) >
+ Date.parse(receipt.deployedAt)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["generationIsolationVerifiedAt"],
+ message: "generation-isolation proof must precede deployment",
+ });
+ }
+ });
+
+export const DeploymentReceiptSchema = DeploymentReceiptV5Schema;
diff --git a/packages/scientific-engine-registry/src/types.ts b/packages/scientific-engine-registry/src/types.ts
index d062c5ac..8a801a6a 100644
--- a/packages/scientific-engine-registry/src/types.ts
+++ b/packages/scientific-engine-registry/src/types.ts
@@ -6,6 +6,11 @@ import {
DeploymentReceiptSchema,
DeploymentReceiptV3Schema,
DeploymentReceiptV4Schema,
+ DeploymentReceiptV5Schema,
+ DeploymentReceiptV6Schema,
+ GenerationIsolationEvidenceSchema,
+ GenerationIsolationEvidenceV1Schema,
+ GenerationIsolationProbePayloadSchema,
ScientificEngineDescriptorSchema,
ScientificEngineEvidenceCatalogSchema,
ScientificEngineEvidenceKindSchema,
@@ -19,9 +24,13 @@ import {
QualifiedRunnerReleaseV2Schema,
QualifiedRunnerReleaseV3Schema,
QualifiedRunnerReleaseV4Schema,
+ QualifiedRunnerReleaseV5Schema,
+ QualifiedRunnerReleaseV6Schema,
ReleaseCheckReceiptSchema,
ReleaseCheckReceiptV1Schema,
ReleaseCheckReceiptV2Schema,
+ ReleaseCheckReceiptV3Schema,
+ ReleaseCheckReceiptV4Schema,
SubjectPackAuthorityBindingSchema,
SubjectPackEngineBindingSchema,
SubjectPackEngineBindingsSchema,
@@ -37,6 +46,17 @@ export type ContainedRuntimeAttestationV1 = z.infer<
export type DeploymentReceipt = z.infer;
export type DeploymentReceiptV3 = z.infer;
export type DeploymentReceiptV4 = z.infer;
+export type DeploymentReceiptV5 = z.infer;
+export type DeploymentReceiptV6 = z.infer;
+export type GenerationIsolationEvidence = z.infer<
+ typeof GenerationIsolationEvidenceSchema
+>;
+export type GenerationIsolationEvidenceV1 = z.infer<
+ typeof GenerationIsolationEvidenceV1Schema
+>;
+export type GenerationIsolationProbePayload = z.infer<
+ typeof GenerationIsolationProbePayloadSchema
+>;
export type ScientificEngineRole = z.infer;
export type ScientificEngineDescriptor = z.infer<
@@ -84,7 +104,15 @@ export type QualifiedRunnerReleaseV3 = z.infer<
export type QualifiedRunnerReleaseV4 = z.infer<
typeof QualifiedRunnerReleaseV4Schema
>;
+export type QualifiedRunnerReleaseV5 = z.infer<
+ typeof QualifiedRunnerReleaseV5Schema
+>;
+export type QualifiedRunnerReleaseV6 = z.infer<
+ typeof QualifiedRunnerReleaseV6Schema
+>;
export type TimeoutCleanupReceipt = z.infer;
export type ReleaseCheckReceipt = z.infer;
export type ReleaseCheckReceiptV1 = z.infer;
export type ReleaseCheckReceiptV2 = z.infer;
+export type ReleaseCheckReceiptV3 = z.infer;
+export type ReleaseCheckReceiptV4 = z.infer;
diff --git a/packages/scientific-engine-registry/test/generation-isolation-schema.test.ts b/packages/scientific-engine-registry/test/generation-isolation-schema.test.ts
new file mode 100644
index 00000000..33b0170a
--- /dev/null
+++ b/packages/scientific-engine-registry/test/generation-isolation-schema.test.ts
@@ -0,0 +1,169 @@
+import { existsSync, readFileSync } from "node:fs";
+import { resolve } from "node:path";
+
+import { describe, expect, it } from "vitest";
+import { z } from "zod";
+
+import {
+ DeploymentReceiptV6Schema,
+ GenerationIsolationEvidenceV1Schema,
+ QualifiedRunnerReleaseV6Schema,
+ ReleaseCheckReceiptV4Schema,
+} from "../src/index.js";
+
+const sourceCommit = "a".repeat(40);
+
+function probePayload() {
+ return {
+ schemaVersion: "1",
+ probeVersion: "counterlab-generation-isolation-v1",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: [
+ "entrypoint",
+ "non-root-user",
+ "immutable-paths",
+ "codex",
+ "python",
+ "bubblewrap",
+ "bubblewrap-read-isolation",
+ "setpriv",
+ "writable-roots",
+ ],
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ bubblewrapVersion: "0.11.0",
+ bubblewrap: {
+ forbiddenHostPathsHidden: true,
+ parentEnvironmentHidden: true,
+ workspaceVisible: true,
+ workspaceWritable: true,
+ },
+ } as const;
+}
+
+function evidence() {
+ return {
+ schemaVersion: "1",
+ status: "VERIFIED",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ sourceCommit,
+ sourceTreeSha256: "b".repeat(64),
+ localImageTag: `counterlab-runner:git-${sourceCommit}`,
+ localImageDigest: `sha256:${"c".repeat(64)}`,
+ imageUser: "10001:10001",
+ mountPolicyVersion: "counterlab-bwrap-mount-policy-v1",
+ probePayload: probePayload(),
+ probePayloadSha256: "d".repeat(64),
+ verifiedAt: "2026-07-20T21:30:00.000Z",
+ verifierVersion: "counterlab-generation-isolation-evidence-v1",
+ } as const;
+}
+
+describe("generation isolation evidence schema", () => {
+ it("accepts only the pinned, source-bound, all-true probe envelope", () => {
+ expect(GenerationIsolationEvidenceV1Schema.parse(evidence())).toEqual(
+ evidence(),
+ );
+ expect(() =>
+ GenerationIsolationEvidenceV1Schema.parse({
+ ...evidence(),
+ localImageTag: `counterlab-runner:git-${"e".repeat(40)}`,
+ }),
+ ).toThrow(/bind the source commit/u);
+ expect(() =>
+ GenerationIsolationEvidenceV1Schema.parse({
+ ...evidence(),
+ probePayload: {
+ ...probePayload(),
+ bubblewrap: {
+ ...probePayload().bubblewrap,
+ forbiddenHostPathsHidden: false,
+ },
+ },
+ }),
+ ).toThrow();
+ expect(() =>
+ GenerationIsolationEvidenceV1Schema.parse({
+ ...evidence(),
+ unverifiedClaim: true,
+ }),
+ ).toThrow();
+ });
+
+ it("adds evidence bindings only in the next receipt versions", () => {
+ for (const [schema, version] of [
+ [QualifiedRunnerReleaseV6Schema, "6"],
+ [ReleaseCheckReceiptV4Schema, "4"],
+ [DeploymentReceiptV6Schema, "6"],
+ ] as const) {
+ const jsonSchema = z.toJSONSchema(schema) as {
+ properties?: Record;
+ required?: string[];
+ };
+ expect(jsonSchema.properties?.schemaVersion?.const).toBe(version);
+ expect(jsonSchema.required).toEqual(
+ expect.arrayContaining([
+ ...(version === "6" && schema === QualifiedRunnerReleaseV6Schema
+ ? ["generationIsolationEvidence"]
+ : []),
+ "generationIsolationEvidenceSha256",
+ "generationIsolationProbeSha256",
+ "generationIsolationVerifiedAt",
+ ]),
+ );
+ }
+ });
+
+ it("publishes the evidence and next-version receipt schemas", () => {
+ for (const path of [
+ "generation-isolation-evidence-v1.schema.json",
+ "qualified-runner-release-v6.schema.json",
+ "release-check-receipt-v4.schema.json",
+ "deployment-receipt-v6.schema.json",
+ ]) {
+ const absolute = resolve(
+ process.cwd(),
+ "scientific-engines/schemas",
+ path,
+ );
+ expect(existsSync(absolute)).toBe(true);
+ const schema = JSON.parse(readFileSync(absolute, "utf8")) as {
+ additionalProperties?: boolean;
+ };
+ expect(schema.additionalProperties).toBe(false);
+ }
+
+ const evidenceSchema = JSON.parse(
+ readFileSync(
+ resolve(
+ process.cwd(),
+ "scientific-engines/schemas/generation-isolation-evidence-v1.schema.json",
+ ),
+ "utf8",
+ ),
+ ) as {
+ properties: {
+ probePayload: {
+ properties: {
+ checks: {
+ minItems?: number;
+ maxItems?: number;
+ items?: boolean;
+ prefixItems?: unknown[];
+ };
+ };
+ };
+ };
+ };
+ expect(
+ evidenceSchema.properties.probePayload.properties.checks,
+ ).toMatchObject({
+ minItems: 9,
+ maxItems: 9,
+ items: false,
+ });
+ expect(
+ evidenceSchema.properties.probePayload.properties.checks.prefixItems,
+ ).toHaveLength(9);
+ });
+});
diff --git a/scientific-engines/schemas/deployment-receipt-v6.schema.json b/scientific-engines/schemas/deployment-receipt-v6.schema.json
new file mode 100644
index 00000000..6ec0f0f8
--- /dev/null
+++ b/scientific-engines/schemas/deployment-receipt-v6.schema.json
@@ -0,0 +1,245 @@
+{
+ "$id": "https://counterlab.dev/schemas/deployment-receipt-v6.schema.json",
+ "title": "CounterLab deployment receipt v6",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "6"
+ },
+ "status": {
+ "type": "string",
+ "const": "DEPLOYED"
+ },
+ "workerName": {
+ "type": "string",
+ "const": "counterlab"
+ },
+ "productionOrigin": {
+ "type": "string",
+ "const": "https://counterlab.cserules.workers.dev"
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ },
+ "workerEvidenceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "runnerSourceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "qualifiedRunnerReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "releaseCheckReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "releaseCheckCheckedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "timeoutCleanupReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "aggregateLimitEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeToolchainSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimePolicySha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "proofDependencyManifestSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeAdapterSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "adapterImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "workerVersionId": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
+ },
+ "workerTag": {
+ "type": "string",
+ "pattern": "^git-[a-f0-9]{40}$"
+ },
+ "workerMessage": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 256
+ },
+ "containerApplicationId": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_-]{1,128}$"
+ },
+ "containerApplicationVersion": {
+ "type": "string",
+ "pattern": "^[1-9][0-9]*$"
+ },
+ "containerImage": {
+ "type": "string",
+ "pattern": "^registry\\.cloudflare\\.com\\/[A-Za-z0-9_-]{3,64}\\/counterlab-runner@sha256:[a-f0-9]{64}$"
+ },
+ "containerState": {
+ "type": "string",
+ "enum": [
+ "active",
+ "ready"
+ ]
+ },
+ "containerImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "workerArtifactClassification": {
+ "type": "string",
+ "const": "PROCESS_BOUND_PARTIAL"
+ },
+ "workerArtifactManifestSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "deployConfigSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "workerBundleSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "clientAssetsSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "clientAssetCount": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "maximum": 9007199254740991
+ },
+ "clientPublicAssetsSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "clientPublicAssetCount": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "maximum": 9007199254740991
+ },
+ "viteVersion": {
+ "type": "string",
+ "const": "8.1.4"
+ },
+ "wranglerVersion": {
+ "type": "string",
+ "const": "4.110.0"
+ },
+ "dryRunSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "dryRunFileCount": {
+ "type": "number",
+ "const": 1
+ },
+ "deploymentStatusSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "workerVersionSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "containerStatusSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "deployedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "verifierVersion": {
+ "type": "string",
+ "const": "counterlab-deployment-v6"
+ },
+ "generationIsolationEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "generationIsolationProbeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "generationIsolationVerifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "status",
+ "workerName",
+ "productionOrigin",
+ "generationFilesystemReadIsolation",
+ "workerEvidenceCommit",
+ "runnerSourceCommit",
+ "qualifiedRunnerReceiptSha256",
+ "releaseCheckReceiptSha256",
+ "releaseCheckCheckedAt",
+ "timeoutCleanupReceiptSha256",
+ "aggregateLimitEvidenceSha256",
+ "runtimeToolchainSha256",
+ "runtimePolicySha256",
+ "proofDependencyManifestSha256",
+ "runtimeAdapterSha256",
+ "adapterImageDigest",
+ "workerVersionId",
+ "workerTag",
+ "workerMessage",
+ "containerApplicationId",
+ "containerApplicationVersion",
+ "containerImage",
+ "containerState",
+ "containerImageDigest",
+ "workerArtifactClassification",
+ "workerArtifactManifestSha256",
+ "deployConfigSha256",
+ "workerBundleSha256",
+ "clientAssetsSha256",
+ "clientAssetCount",
+ "clientPublicAssetsSha256",
+ "clientPublicAssetCount",
+ "viteVersion",
+ "wranglerVersion",
+ "dryRunSha256",
+ "dryRunFileCount",
+ "deploymentStatusSha256",
+ "workerVersionSha256",
+ "containerStatusSha256",
+ "deployedAt",
+ "verifierVersion",
+ "generationIsolationEvidenceSha256",
+ "generationIsolationProbeSha256",
+ "generationIsolationVerifiedAt"
+ ],
+ "additionalProperties": false
+}
diff --git a/scientific-engines/schemas/generation-isolation-evidence-v1.schema.json b/scientific-engines/schemas/generation-isolation-evidence-v1.schema.json
new file mode 100644
index 00000000..8e3d0ddc
--- /dev/null
+++ b/scientific-engines/schemas/generation-isolation-evidence-v1.schema.json
@@ -0,0 +1,185 @@
+{
+ "$id": "https://counterlab.dev/schemas/generation-isolation-evidence-v1.schema.json",
+ "title": "CounterLab generation isolation evidence v1",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "1"
+ },
+ "status": {
+ "type": "string",
+ "const": "VERIFIED"
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ },
+ "sourceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "sourceTreeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "localImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-runner:git-[a-f0-9]{40}$"
+ },
+ "localImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "imageUser": {
+ "type": "string",
+ "const": "10001:10001"
+ },
+ "mountPolicyVersion": {
+ "type": "string",
+ "const": "counterlab-bwrap-mount-policy-v1"
+ },
+ "probePayload": {
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "1"
+ },
+ "probeVersion": {
+ "type": "string",
+ "const": "counterlab-generation-isolation-v1"
+ },
+ "service": {
+ "type": "string",
+ "const": "counterlab-hosted-runner"
+ },
+ "probe": {
+ "type": "string",
+ "const": "non-root-startup"
+ },
+ "checks": {
+ "type": "array",
+ "prefixItems": [
+ {
+ "type": "string",
+ "const": "entrypoint"
+ },
+ {
+ "type": "string",
+ "const": "non-root-user"
+ },
+ {
+ "type": "string",
+ "const": "immutable-paths"
+ },
+ {
+ "type": "string",
+ "const": "codex"
+ },
+ {
+ "type": "string",
+ "const": "python"
+ },
+ {
+ "type": "string",
+ "const": "bubblewrap"
+ },
+ {
+ "type": "string",
+ "const": "bubblewrap-read-isolation"
+ },
+ {
+ "type": "string",
+ "const": "setpriv"
+ },
+ {
+ "type": "string",
+ "const": "writable-roots"
+ }
+ ],
+ "minItems": 9,
+ "maxItems": 9,
+ "items": false
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ },
+ "bubblewrapVersion": {
+ "type": "string",
+ "const": "0.11.0"
+ },
+ "bubblewrap": {
+ "type": "object",
+ "properties": {
+ "forbiddenHostPathsHidden": {
+ "type": "boolean",
+ "const": true
+ },
+ "parentEnvironmentHidden": {
+ "type": "boolean",
+ "const": true
+ },
+ "workspaceVisible": {
+ "type": "boolean",
+ "const": true
+ },
+ "workspaceWritable": {
+ "type": "boolean",
+ "const": true
+ }
+ },
+ "required": [
+ "forbiddenHostPathsHidden",
+ "parentEnvironmentHidden",
+ "workspaceVisible",
+ "workspaceWritable"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "probeVersion",
+ "service",
+ "probe",
+ "checks",
+ "generationFilesystemReadIsolation",
+ "bubblewrapVersion",
+ "bubblewrap"
+ ],
+ "additionalProperties": false
+ },
+ "probePayloadSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "verifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "verifierVersion": {
+ "type": "string",
+ "const": "counterlab-generation-isolation-evidence-v1"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "status",
+ "generationFilesystemReadIsolation",
+ "sourceCommit",
+ "sourceTreeSha256",
+ "localImageTag",
+ "localImageDigest",
+ "imageUser",
+ "mountPolicyVersion",
+ "probePayload",
+ "probePayloadSha256",
+ "verifiedAt",
+ "verifierVersion"
+ ],
+ "additionalProperties": false
+}
diff --git a/scientific-engines/schemas/qualified-runner-release-v6.schema.json b/scientific-engines/schemas/qualified-runner-release-v6.schema.json
new file mode 100644
index 00000000..b3a79472
--- /dev/null
+++ b/scientific-engines/schemas/qualified-runner-release-v6.schema.json
@@ -0,0 +1,434 @@
+{
+ "$id": "https://counterlab.dev/schemas/qualified-runner-release-v6.schema.json",
+ "title": "CounterLab qualified runner release v6",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "6"
+ },
+ "status": {
+ "type": "string",
+ "const": "VERIFIED"
+ },
+ "sourceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "sourceArchiveSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "sourceTreeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "dockerfileSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "localImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-runner:git-[a-f0-9]{40}$"
+ },
+ "localImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "ociRevision": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "ociSourceTreeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "engineAuthorityHash": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeManifestHash": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeToolchainSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimePolicySha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "proofDependencyManifestSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "toolchainLockSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeAdapterSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "buildctlSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "buildkitdSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "buildkitConfigSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "adapterDockerfileSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "adapterImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-adapter:git-[a-f0-9]{40}$"
+ },
+ "adapterImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "adapterManifestDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "adapterOciArchiveSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "adapterOciRevision": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "adapterOciSourceTreeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "limitMode": {
+ "type": "string",
+ "const": "container-cgroup-and-process-rlimit"
+ },
+ "aggregateLimitIntentEnforced": {
+ "type": "boolean",
+ "const": true
+ },
+ "aggregateLimitEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "timeoutCleanupReceipt": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 300
+ },
+ "timeoutCleanupReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "timeoutCleanupPayloadSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "timeoutRunControlReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "timeoutRootlessReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "timeoutRuntimeSessionId": {
+ "type": "string",
+ "pattern": "^rt-[a-z0-9][a-z0-9-]{7,13}$"
+ },
+ "timeoutVerifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "evidenceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "registryImage": {
+ "type": "string",
+ "pattern": "^registry\\.cloudflare\\.com\\/[A-Za-z0-9_-]{3,64}\\/counterlab-runner:git-[a-f0-9]{40}$"
+ },
+ "registryDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "registryResolvedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "qualifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "verifierVersion": {
+ "type": "string",
+ "const": "counterlab-release-v6"
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ },
+ "generationIsolationEvidence": {
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "1"
+ },
+ "status": {
+ "type": "string",
+ "const": "VERIFIED"
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ },
+ "sourceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "sourceTreeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "localImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-runner:git-[a-f0-9]{40}$"
+ },
+ "localImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "imageUser": {
+ "type": "string",
+ "const": "10001:10001"
+ },
+ "mountPolicyVersion": {
+ "type": "string",
+ "const": "counterlab-bwrap-mount-policy-v1"
+ },
+ "probePayload": {
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "1"
+ },
+ "probeVersion": {
+ "type": "string",
+ "const": "counterlab-generation-isolation-v1"
+ },
+ "service": {
+ "type": "string",
+ "const": "counterlab-hosted-runner"
+ },
+ "probe": {
+ "type": "string",
+ "const": "non-root-startup"
+ },
+ "checks": {
+ "type": "array",
+ "prefixItems": [
+ {
+ "type": "string",
+ "const": "entrypoint"
+ },
+ {
+ "type": "string",
+ "const": "non-root-user"
+ },
+ {
+ "type": "string",
+ "const": "immutable-paths"
+ },
+ {
+ "type": "string",
+ "const": "codex"
+ },
+ {
+ "type": "string",
+ "const": "python"
+ },
+ {
+ "type": "string",
+ "const": "bubblewrap"
+ },
+ {
+ "type": "string",
+ "const": "bubblewrap-read-isolation"
+ },
+ {
+ "type": "string",
+ "const": "setpriv"
+ },
+ {
+ "type": "string",
+ "const": "writable-roots"
+ }
+ ],
+ "minItems": 9,
+ "maxItems": 9,
+ "items": false
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ },
+ "bubblewrapVersion": {
+ "type": "string",
+ "const": "0.11.0"
+ },
+ "bubblewrap": {
+ "type": "object",
+ "properties": {
+ "forbiddenHostPathsHidden": {
+ "type": "boolean",
+ "const": true
+ },
+ "parentEnvironmentHidden": {
+ "type": "boolean",
+ "const": true
+ },
+ "workspaceVisible": {
+ "type": "boolean",
+ "const": true
+ },
+ "workspaceWritable": {
+ "type": "boolean",
+ "const": true
+ }
+ },
+ "required": [
+ "forbiddenHostPathsHidden",
+ "parentEnvironmentHidden",
+ "workspaceVisible",
+ "workspaceWritable"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "probeVersion",
+ "service",
+ "probe",
+ "checks",
+ "generationFilesystemReadIsolation",
+ "bubblewrapVersion",
+ "bubblewrap"
+ ],
+ "additionalProperties": false
+ },
+ "probePayloadSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "verifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "verifierVersion": {
+ "type": "string",
+ "const": "counterlab-generation-isolation-evidence-v1"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "status",
+ "generationFilesystemReadIsolation",
+ "sourceCommit",
+ "sourceTreeSha256",
+ "localImageTag",
+ "localImageDigest",
+ "imageUser",
+ "mountPolicyVersion",
+ "probePayload",
+ "probePayloadSha256",
+ "verifiedAt",
+ "verifierVersion"
+ ],
+ "additionalProperties": false
+ },
+ "generationIsolationEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "generationIsolationProbeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "generationIsolationVerifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "status",
+ "sourceCommit",
+ "sourceArchiveSha256",
+ "sourceTreeSha256",
+ "dockerfileSha256",
+ "localImageTag",
+ "localImageDigest",
+ "ociRevision",
+ "ociSourceTreeSha256",
+ "engineAuthorityHash",
+ "runtimeManifestHash",
+ "runtimeToolchainSha256",
+ "runtimePolicySha256",
+ "proofDependencyManifestSha256",
+ "toolchainLockSha256",
+ "runtimeAdapterSha256",
+ "buildctlSha256",
+ "buildkitdSha256",
+ "buildkitConfigSha256",
+ "adapterDockerfileSha256",
+ "adapterImageTag",
+ "adapterImageDigest",
+ "adapterManifestDigest",
+ "adapterOciArchiveSha256",
+ "adapterOciRevision",
+ "adapterOciSourceTreeSha256",
+ "limitMode",
+ "aggregateLimitIntentEnforced",
+ "aggregateLimitEvidenceSha256",
+ "timeoutCleanupReceipt",
+ "timeoutCleanupReceiptSha256",
+ "timeoutCleanupPayloadSha256",
+ "timeoutRunControlReceiptSha256",
+ "timeoutRootlessReceiptSha256",
+ "timeoutRuntimeSessionId",
+ "timeoutVerifiedAt",
+ "evidenceCommit",
+ "registryImage",
+ "registryDigest",
+ "registryResolvedAt",
+ "qualifiedAt",
+ "verifierVersion",
+ "generationFilesystemReadIsolation",
+ "generationIsolationEvidence",
+ "generationIsolationEvidenceSha256",
+ "generationIsolationProbeSha256",
+ "generationIsolationVerifiedAt"
+ ],
+ "additionalProperties": false
+}
diff --git a/scientific-engines/schemas/release-check-receipt-v4.schema.json b/scientific-engines/schemas/release-check-receipt-v4.schema.json
new file mode 100644
index 00000000..ef0894f5
--- /dev/null
+++ b/scientific-engines/schemas/release-check-receipt-v4.schema.json
@@ -0,0 +1,160 @@
+{
+ "$id": "https://counterlab.dev/schemas/release-check-receipt-v4.schema.json",
+ "title": "CounterLab release-check receipt v4",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "4"
+ },
+ "status": {
+ "type": "string",
+ "const": "PASSED"
+ },
+ "evidenceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "sourceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "qualifiedRunnerReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "qualifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "runnerImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-runner:git-[a-f0-9]{40}$"
+ },
+ "runnerImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "adapterImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-adapter:git-[a-f0-9]{40}$"
+ },
+ "adapterImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "registryDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "runtimeToolchainSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimePolicySha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "proofDependencyManifestSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "aggregateLimitEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeAdapterSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "checks": {
+ "minItems": 11,
+ "maxItems": 11,
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "enum": [
+ "test-all",
+ "leakage-mutations",
+ "imbalance-mutations",
+ "held-out",
+ "adapter-sandbox-smoke",
+ "scientific-engines",
+ "web-build",
+ "achieved-metrics",
+ "reproduce-leakage",
+ "replay-patch",
+ "secret-scan"
+ ]
+ },
+ "status": {
+ "type": "string",
+ "const": "PASSED"
+ }
+ },
+ "required": [
+ "id",
+ "status"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "checkedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "verifierVersion": {
+ "type": "string",
+ "const": "counterlab-release-check-v4"
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ },
+ "generationIsolationEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "generationIsolationProbeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "generationIsolationVerifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "status",
+ "evidenceCommit",
+ "sourceCommit",
+ "qualifiedRunnerReceiptSha256",
+ "qualifiedAt",
+ "runnerImageTag",
+ "runnerImageDigest",
+ "adapterImageTag",
+ "adapterImageDigest",
+ "registryDigest",
+ "runtimeToolchainSha256",
+ "runtimePolicySha256",
+ "proofDependencyManifestSha256",
+ "aggregateLimitEvidenceSha256",
+ "runtimeAdapterSha256",
+ "checks",
+ "checkedAt",
+ "verifierVersion",
+ "generationFilesystemReadIsolation",
+ "generationIsolationEvidenceSha256",
+ "generationIsolationProbeSha256",
+ "generationIsolationVerifiedAt"
+ ],
+ "additionalProperties": false
+}
diff --git a/scripts/generate-contract-schemas.ts b/scripts/generate-contract-schemas.ts
index 02cac6ec..1d3bd40f 100644
--- a/scripts/generate-contract-schemas.ts
+++ b/scripts/generate-contract-schemas.ts
@@ -18,16 +18,20 @@ import {
import {
ContainedRuntimeAttestationSchema,
ContainedRuntimeAttestationV1Schema,
- DeploymentReceiptSchema,
DeploymentReceiptV3Schema,
DeploymentReceiptV4Schema,
- QualifiedRunnerReleaseSchema,
+ DeploymentReceiptV5Schema,
+ DeploymentReceiptV6Schema,
+ GenerationIsolationEvidenceV1Schema,
QualifiedRunnerReleaseV2Schema,
QualifiedRunnerReleaseV3Schema,
QualifiedRunnerReleaseV4Schema,
- ReleaseCheckReceiptSchema,
+ QualifiedRunnerReleaseV5Schema,
+ QualifiedRunnerReleaseV6Schema,
ReleaseCheckReceiptV1Schema,
ReleaseCheckReceiptV2Schema,
+ ReleaseCheckReceiptV3Schema,
+ ReleaseCheckReceiptV4Schema,
ScientificEngineEvidenceCatalogSchema,
ScientificEngineRegistrySchema,
ScientificEngineRuntimeManifestSchema,
@@ -42,7 +46,36 @@ import {
} from "../packages/generative-ui-contracts/src/index.js";
const root = resolve(import.meta.dirname, "..");
+
+function closeTupleArrays(value: unknown): void {
+ if (Array.isArray(value)) {
+ for (const item of value) closeTupleArrays(item);
+ return;
+ }
+ if (typeof value !== "object" || value === null) return;
+ const record = value as Record;
+ if (Array.isArray(record.prefixItems)) {
+ record.minItems = record.prefixItems.length;
+ record.maxItems = record.prefixItems.length;
+ record.items = false;
+ }
+ for (const item of Object.values(record)) closeTupleArrays(item);
+}
+
const schemas = [
+ {
+ fileName: "generation-isolation-evidence-v1.schema.json",
+ id: "https://counterlab.dev/schemas/generation-isolation-evidence-v1.schema.json",
+ title: "CounterLab generation isolation evidence v1",
+ schema: GenerationIsolationEvidenceV1Schema,
+ closeTupleArrays: true,
+ destinations: [
+ resolve(
+ root,
+ "scientific-engines/schemas/generation-isolation-evidence-v1.schema.json",
+ ),
+ ],
+ },
{
fileName: "boundary-map-result-v1.schema.json",
id: "https://counterlab.dev/schemas/boundary-map-result-v1.schema.json",
@@ -252,7 +285,7 @@ const schemas = [
fileName: "qualified-runner-release-v5.schema.json",
id: "https://counterlab.dev/schemas/qualified-runner-release-v5.schema.json",
title: "CounterLab qualified runner release v5",
- schema: QualifiedRunnerReleaseSchema,
+ schema: QualifiedRunnerReleaseV5Schema,
destinations: [
resolve(
root,
@@ -260,6 +293,19 @@ const schemas = [
),
],
},
+ {
+ fileName: "qualified-runner-release-v6.schema.json",
+ id: "https://counterlab.dev/schemas/qualified-runner-release-v6.schema.json",
+ title: "CounterLab qualified runner release v6",
+ schema: QualifiedRunnerReleaseV6Schema,
+ closeTupleArrays: true,
+ destinations: [
+ resolve(
+ root,
+ "scientific-engines/schemas/qualified-runner-release-v6.schema.json",
+ ),
+ ],
+ },
{
fileName: "timeout-cleanup-receipt-v1.schema.json",
id: "https://counterlab.dev/schemas/timeout-cleanup-receipt-v1.schema.json",
@@ -324,7 +370,7 @@ const schemas = [
fileName: "release-check-receipt-v3.schema.json",
id: "https://counterlab.dev/schemas/release-check-receipt-v3.schema.json",
title: "CounterLab release-check receipt v3",
- schema: ReleaseCheckReceiptSchema,
+ schema: ReleaseCheckReceiptV3Schema,
destinations: [
resolve(
root,
@@ -332,6 +378,18 @@ const schemas = [
),
],
},
+ {
+ fileName: "release-check-receipt-v4.schema.json",
+ id: "https://counterlab.dev/schemas/release-check-receipt-v4.schema.json",
+ title: "CounterLab release-check receipt v4",
+ schema: ReleaseCheckReceiptV4Schema,
+ destinations: [
+ resolve(
+ root,
+ "scientific-engines/schemas/release-check-receipt-v4.schema.json",
+ ),
+ ],
+ },
{
fileName: "deployment-receipt-v3.schema.json",
id: "https://counterlab.dev/schemas/deployment-receipt-v3.schema.json",
@@ -360,7 +418,7 @@ const schemas = [
fileName: "deployment-receipt-v5.schema.json",
id: "https://counterlab.dev/schemas/deployment-receipt-v5.schema.json",
title: "CounterLab deployment receipt v5",
- schema: DeploymentReceiptSchema,
+ schema: DeploymentReceiptV5Schema,
destinations: [
resolve(
root,
@@ -368,10 +426,25 @@ const schemas = [
),
],
},
+ {
+ fileName: "deployment-receipt-v6.schema.json",
+ id: "https://counterlab.dev/schemas/deployment-receipt-v6.schema.json",
+ title: "CounterLab deployment receipt v6",
+ schema: DeploymentReceiptV6Schema,
+ destinations: [
+ resolve(
+ root,
+ "scientific-engines/schemas/deployment-receipt-v6.schema.json",
+ ),
+ ],
+ },
];
for (const definition of schemas) {
const generated = z.toJSONSchema(definition.schema);
+ if ("closeTupleArrays" in definition && definition.closeTupleArrays) {
+ closeTupleArrays(generated);
+ }
const serialized = `${JSON.stringify(
{ $id: definition.id, title: definition.title, ...generated },
null,
diff --git a/scripts/generation-isolation-evidence.test.ts b/scripts/generation-isolation-evidence.test.ts
new file mode 100644
index 00000000..b325d9e4
--- /dev/null
+++ b/scripts/generation-isolation-evidence.test.ts
@@ -0,0 +1,164 @@
+import { describe, expect, it } from "vitest";
+
+import {
+ createGenerationIsolationEvidence,
+ verifyGenerationIsolationEvidence,
+} from "./generation-isolation-evidence.js";
+
+const sourceCommit = "a".repeat(40);
+const sourceTreeSha256 = "b".repeat(64);
+const localImageTag = `counterlab-runner:git-${sourceCommit}`;
+const localImageDigest = `sha256:${"c".repeat(64)}`;
+const probePayload = {
+ schemaVersion: "1",
+ probeVersion: "counterlab-generation-isolation-v1",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: [
+ "entrypoint",
+ "non-root-user",
+ "immutable-paths",
+ "codex",
+ "python",
+ "bubblewrap",
+ "bubblewrap-read-isolation",
+ "setpriv",
+ "writable-roots",
+ ],
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ bubblewrapVersion: "0.11.0",
+ bubblewrap: {
+ forbiddenHostPathsHidden: true,
+ parentEnvironmentHidden: true,
+ workspaceVisible: true,
+ workspaceWritable: true,
+ },
+} as const;
+const probeSha256 =
+ "700cc58bedc163846e3854415170f49f55da9fd3ba316cc4967747d5268199dc";
+
+function startupProbe() {
+ return {
+ status: "ready",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: probePayload.checks,
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationProbe: probePayload,
+ generationIsolationProbeSha256: probeSha256,
+ } as const;
+}
+
+function createEvidence() {
+ return createGenerationIsolationEvidence({
+ sourceCommit,
+ sourceTreeSha256,
+ localImageTag,
+ localImageDigest,
+ imageUser: "10001:10001",
+ startupProbe: startupProbe(),
+ verifiedAt: "2026-07-20T21:30:00.000Z",
+ });
+}
+
+describe("generation-isolation evidence", () => {
+ it("binds canonical exact-image probe evidence to source and image", () => {
+ const result = createEvidence();
+ expect(result.probeSha256).toBe(probeSha256);
+ expect(result.evidence).toMatchObject({
+ sourceCommit,
+ sourceTreeSha256,
+ localImageTag,
+ localImageDigest,
+ imageUser: "10001:10001",
+ probePayloadSha256: probeSha256,
+ });
+ expect(
+ verifyGenerationIsolationEvidence({
+ evidence: result.evidence,
+ evidenceSha256: result.evidenceSha256,
+ expected: {
+ sourceCommit,
+ sourceTreeSha256,
+ localImageTag,
+ localImageDigest,
+ probeSha256,
+ verifiedAt: "2026-07-20T21:30:00.000Z",
+ },
+ }),
+ ).toEqual(result);
+ });
+
+ it("rejects self-reported, evidence, source, and image hash mutations", () => {
+ expect(() =>
+ createGenerationIsolationEvidence({
+ sourceCommit,
+ sourceTreeSha256,
+ localImageTag,
+ localImageDigest,
+ imageUser: "10001:10001",
+ startupProbe: {
+ ...startupProbe(),
+ generationIsolationProbeSha256: "d".repeat(64),
+ },
+ }),
+ ).toThrow(/probe hash/u);
+
+ const result = createEvidence();
+ for (const mutation of [
+ { evidenceSha256: "d".repeat(64), expected: {} },
+ {
+ evidenceSha256: result.evidenceSha256,
+ expected: { sourceCommit: "e".repeat(40) },
+ },
+ {
+ evidenceSha256: result.evidenceSha256,
+ expected: { sourceTreeSha256: "f".repeat(64) },
+ },
+ {
+ evidenceSha256: result.evidenceSha256,
+ expected: { localImageDigest: `sha256:${"0".repeat(64)}` },
+ },
+ {
+ evidenceSha256: result.evidenceSha256,
+ expected: { probeSha256: "1".repeat(64) },
+ },
+ ]) {
+ expect(() =>
+ verifyGenerationIsolationEvidence({
+ evidence: result.evidence,
+ evidenceSha256: mutation.evidenceSha256,
+ expected: {
+ sourceCommit,
+ sourceTreeSha256,
+ localImageTag,
+ localImageDigest,
+ ...mutation.expected,
+ },
+ }),
+ ).toThrow();
+ }
+ });
+
+ it("rejects a modified probe payload even when its outer envelope is intact", () => {
+ const result = createEvidence();
+ expect(() =>
+ verifyGenerationIsolationEvidence({
+ evidence: {
+ ...result.evidence,
+ probePayload: {
+ ...result.evidence.probePayload,
+ bubblewrapVersion: "0.11.1",
+ },
+ },
+ evidenceSha256: result.evidenceSha256,
+ expected: {
+ sourceCommit,
+ sourceTreeSha256,
+ localImageTag,
+ localImageDigest,
+ },
+ }),
+ ).toThrow();
+ });
+});
diff --git a/scripts/generation-isolation-evidence.ts b/scripts/generation-isolation-evidence.ts
new file mode 100644
index 00000000..53bbce0a
--- /dev/null
+++ b/scripts/generation-isolation-evidence.ts
@@ -0,0 +1,182 @@
+import { createHash } from "node:crypto";
+
+import {
+ GENERATION_ISOLATION_MOUNT_POLICY_VERSION,
+ GenerationIsolationEvidenceV1Schema,
+ GenerationIsolationProbePayloadSchema,
+ QualifiedRunnerReleaseV6Schema,
+ type GenerationIsolationEvidenceV1,
+ type QualifiedRunnerReleaseV6,
+} from "../packages/scientific-engine-registry/src/index.js";
+import { canonicalJson } from "../packages/session-core/src/index.js";
+import { z } from "zod";
+
+const Sha256Schema = z.string().regex(/^[a-f0-9]{64}$/u);
+
+const StartupProbeResultSchema = z
+ .strictObject({
+ status: z.literal("ready"),
+ service: z.literal("counterlab-hosted-runner"),
+ probe: z.literal("non-root-startup"),
+ checks: GenerationIsolationProbePayloadSchema.shape.checks,
+ generationFilesystemReadIsolation: z.literal("OS_ENFORCED"),
+ generationIsolationProbe: GenerationIsolationProbePayloadSchema,
+ generationIsolationProbeSha256: Sha256Schema,
+ })
+ .superRefine((result, context) => {
+ for (const [field, outer, inner] of [
+ ["service", result.service, result.generationIsolationProbe.service],
+ ["probe", result.probe, result.generationIsolationProbe.probe],
+ [
+ "checks",
+ canonicalJson(result.checks),
+ canonicalJson(result.generationIsolationProbe.checks),
+ ],
+ [
+ "generationFilesystemReadIsolation",
+ result.generationFilesystemReadIsolation,
+ result.generationIsolationProbe.generationFilesystemReadIsolation,
+ ],
+ ] as const) {
+ if (outer !== inner) {
+ context.addIssue({
+ code: "custom",
+ path: [field],
+ message: `${field} must match the nested isolation probe`,
+ });
+ }
+ }
+ });
+
+function sha256Canonical(value: unknown): string {
+ return createHash("sha256").update(canonicalJson(value)).digest("hex");
+}
+
+export function hashGenerationIsolationEvidence(
+ evidence: GenerationIsolationEvidenceV1,
+): string {
+ return sha256Canonical(GenerationIsolationEvidenceV1Schema.parse(evidence));
+}
+
+export function createGenerationIsolationEvidence(input: {
+ sourceCommit: string;
+ sourceTreeSha256: string;
+ localImageTag: string;
+ localImageDigest: string;
+ imageUser: string;
+ startupProbe: unknown;
+ verifiedAt?: string;
+}): {
+ evidence: GenerationIsolationEvidenceV1;
+ evidenceSha256: string;
+ probeSha256: string;
+} {
+ const startupProbe = StartupProbeResultSchema.parse(input.startupProbe);
+ const probeSha256 = sha256Canonical(startupProbe.generationIsolationProbe);
+ if (probeSha256 !== startupProbe.generationIsolationProbeSha256) {
+ throw new Error(
+ "Generation-isolation probe hash does not match the exact-image payload",
+ );
+ }
+ const evidence = GenerationIsolationEvidenceV1Schema.parse({
+ schemaVersion: "1",
+ status: "VERIFIED",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ sourceCommit: input.sourceCommit,
+ sourceTreeSha256: input.sourceTreeSha256,
+ localImageTag: input.localImageTag,
+ localImageDigest: input.localImageDigest,
+ imageUser: input.imageUser,
+ mountPolicyVersion: GENERATION_ISOLATION_MOUNT_POLICY_VERSION,
+ probePayload: startupProbe.generationIsolationProbe,
+ probePayloadSha256: probeSha256,
+ verifiedAt: input.verifiedAt ?? new Date().toISOString(),
+ verifierVersion: "counterlab-generation-isolation-evidence-v1",
+ });
+ return {
+ evidence,
+ evidenceSha256: hashGenerationIsolationEvidence(evidence),
+ probeSha256,
+ };
+}
+
+export function verifyGenerationIsolationEvidence(input: {
+ evidence: unknown;
+ evidenceSha256: string;
+ expected: {
+ sourceCommit: string;
+ sourceTreeSha256: string;
+ localImageTag: string;
+ localImageDigest: string;
+ imageUser?: "10001:10001";
+ probeSha256?: string;
+ verifiedAt?: string;
+ };
+}): {
+ evidence: GenerationIsolationEvidenceV1;
+ evidenceSha256: string;
+ probeSha256: string;
+} {
+ const evidence = GenerationIsolationEvidenceV1Schema.parse(input.evidence);
+ const evidenceSha256 = hashGenerationIsolationEvidence(evidence);
+ if (evidenceSha256 !== Sha256Schema.parse(input.evidenceSha256)) {
+ throw new Error("Generation-isolation evidence hash mismatch");
+ }
+ const probeSha256 = sha256Canonical(evidence.probePayload);
+ if (probeSha256 !== evidence.probePayloadSha256) {
+ throw new Error("Generation-isolation probe payload hash mismatch");
+ }
+ for (const [field, expected, observed] of [
+ ["sourceCommit", input.expected.sourceCommit, evidence.sourceCommit],
+ [
+ "sourceTreeSha256",
+ input.expected.sourceTreeSha256,
+ evidence.sourceTreeSha256,
+ ],
+ ["localImageTag", input.expected.localImageTag, evidence.localImageTag],
+ [
+ "localImageDigest",
+ input.expected.localImageDigest,
+ evidence.localImageDigest,
+ ],
+ [
+ "imageUser",
+ input.expected.imageUser ?? "10001:10001",
+ evidence.imageUser,
+ ],
+ [
+ "probeSha256",
+ input.expected.probeSha256 ?? evidence.probePayloadSha256,
+ evidence.probePayloadSha256,
+ ],
+ [
+ "verifiedAt",
+ input.expected.verifiedAt ?? evidence.verifiedAt,
+ evidence.verifiedAt,
+ ],
+ ] as const) {
+ if (expected !== observed) {
+ throw new Error(`Generation-isolation ${field} binding mismatch`);
+ }
+ }
+ return { evidence, evidenceSha256, probeSha256 };
+}
+
+export function parseQualifiedRunnerReleaseV6(
+ value: unknown,
+): QualifiedRunnerReleaseV6 {
+ const release = QualifiedRunnerReleaseV6Schema.parse(value);
+ verifyGenerationIsolationEvidence({
+ evidence: release.generationIsolationEvidence,
+ evidenceSha256: release.generationIsolationEvidenceSha256,
+ expected: {
+ sourceCommit: release.sourceCommit,
+ sourceTreeSha256: release.sourceTreeSha256,
+ localImageTag: release.localImageTag,
+ localImageDigest: release.localImageDigest,
+ probeSha256: release.generationIsolationProbeSha256,
+ verifiedAt: release.generationIsolationVerifiedAt,
+ },
+ });
+ return release;
+}
From 56638e9748f8a6b2996bf61c882594aa27fdeb70 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 03:06:51 +0530
Subject: [PATCH 011/143] feat(release): persist exact-image isolation evidence
---
apps/web/worker/deployment-config.test.ts | 7 ++
scripts/generation-isolation-evidence.ts | 113 ++++++++++++++++++++++
scripts/verify-scientific-engines.sh | 36 +++++++
3 files changed, 156 insertions(+)
diff --git a/apps/web/worker/deployment-config.test.ts b/apps/web/worker/deployment-config.test.ts
index ccbc49d6..157b258c 100644
--- a/apps/web/worker/deployment-config.test.ts
+++ b/apps/web/worker/deployment-config.test.ts
@@ -547,6 +547,13 @@ describe("Cloudflare static asset routing", () => {
expect(probe).not.toContain("--user");
expect(probe).not.toContain("--entrypoint");
expect(probe).toContain('"${IMAGE}"');
+ expect(verifier).toContain("--generation-isolation-report");
+ expect(verifier).toContain(
+ '{{index .Config.Labels "io.counterlab.source-tree-sha256"}}',
+ );
+ expect(verifier).toContain(
+ "node --import tsx scripts/generation-isolation-evidence.ts",
+ );
});
it("generates a deploy config from a source-bound qualified image receipt", () => {
diff --git a/scripts/generation-isolation-evidence.ts b/scripts/generation-isolation-evidence.ts
index 53bbce0a..431c4433 100644
--- a/scripts/generation-isolation-evidence.ts
+++ b/scripts/generation-isolation-evidence.ts
@@ -1,4 +1,7 @@
import { createHash } from "node:crypto";
+import { lstat, realpath, stat, writeFile } from "node:fs/promises";
+import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
+import { fileURLToPath } from "node:url";
import {
GENERATION_ISOLATION_MOUNT_POLICY_VERSION,
@@ -180,3 +183,113 @@ export function parseQualifiedRunnerReleaseV6(
});
return release;
}
+
+function isContained(root: string, candidate: string): boolean {
+ const fromRoot = relative(root, candidate);
+ return (
+ fromRoot === "" || (!fromRoot.startsWith("..") && !isAbsolute(fromRoot))
+ );
+}
+
+async function newContainedOutput(root: string, requested: string) {
+ const output = resolve(root, requested);
+ if (!isContained(root, output) || output === root) {
+ throw new Error(
+ "Generation-isolation output must remain in the repository",
+ );
+ }
+ let current = root;
+ for (const component of relative(root, output).split(sep)) {
+ current = resolve(current, component);
+ try {
+ const metadata = await lstat(current);
+ if (metadata.isSymbolicLink()) {
+ throw new Error("Generation-isolation output path contains a symlink");
+ }
+ if (!isContained(root, await realpath(current))) {
+ throw new Error(
+ "Generation-isolation output path escapes the repository",
+ );
+ }
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") break;
+ throw error;
+ }
+ }
+ const parent = await realpath(dirname(output));
+ if (!isContained(root, parent) || !(await stat(parent)).isDirectory()) {
+ throw new Error("Generation-isolation output parent is not contained");
+ }
+ try {
+ await lstat(output);
+ throw new Error("Generation-isolation output already exists");
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
+ }
+ return output;
+}
+
+function cliArguments(argv: string[]): Record {
+ const allowed = new Set([
+ "--source-commit",
+ "--source-tree-sha256",
+ "--local-image-tag",
+ "--local-image-digest",
+ "--image-user",
+ "--startup-probe-json",
+ "--verified-at",
+ "--output",
+ ]);
+ const values = new Map();
+ for (let index = 0; index < argv.length; index += 2) {
+ const flag = argv[index];
+ const value = argv[index + 1];
+ if (
+ flag === undefined ||
+ value === undefined ||
+ !allowed.has(flag) ||
+ values.has(flag)
+ ) {
+ throw new Error("Invalid generation-isolation evidence arguments");
+ }
+ values.set(flag, value);
+ }
+ if (values.size !== allowed.size) {
+ throw new Error("Incomplete generation-isolation evidence arguments");
+ }
+ return Object.fromEntries(values);
+}
+
+async function main(): Promise {
+ const root = await realpath(resolve(import.meta.dirname, ".."));
+ const values = cliArguments(process.argv.slice(2));
+ const output = await newContainedOutput(root, values["--output"] ?? "");
+ const result = createGenerationIsolationEvidence({
+ sourceCommit: values["--source-commit"] ?? "",
+ sourceTreeSha256: values["--source-tree-sha256"] ?? "",
+ localImageTag: values["--local-image-tag"] ?? "",
+ localImageDigest: values["--local-image-digest"] ?? "",
+ imageUser: values["--image-user"] ?? "",
+ startupProbe: JSON.parse(values["--startup-probe-json"] ?? "") as unknown,
+ verifiedAt: values["--verified-at"] ?? "",
+ });
+ await writeFile(output, `${JSON.stringify(result.evidence, null, 2)}\n`, {
+ encoding: "utf8",
+ flag: "wx",
+ mode: 0o600,
+ });
+ console.log(
+ JSON.stringify({
+ evidenceSha256: result.evidenceSha256,
+ probeSha256: result.probeSha256,
+ output: relative(root, output),
+ }),
+ );
+}
+
+if (
+ process.argv[1] !== undefined &&
+ fileURLToPath(import.meta.url) === resolve(process.argv[1])
+) {
+ await main();
+}
diff --git a/scripts/verify-scientific-engines.sh b/scripts/verify-scientific-engines.sh
index cf8ae3f0..afa379ce 100755
--- a/scripts/verify-scientific-engines.sh
+++ b/scripts/verify-scientific-engines.sh
@@ -6,6 +6,7 @@ IMAGE=""
REGISTRY_ONLY=0
REQUIRE_PRODUCTION=0
RUNTIME_REPORT=""
+GENERATION_ISOLATION_REPORT=""
ENVIRONMENT_HELPER="${ROOT_DIR}/scripts/prepare-contained-shell-environment.sh"
[[ -f "${ENVIRONMENT_HELPER}" && ! -L "${ENVIRONMENT_HELPER}" ]] || {
@@ -24,6 +25,8 @@ Options:
--registry-only Skip Proof Capsule linkage and permit an omitted runtime image.
--require-production Reject a local-candidate runtime manifest.
--runtime-report PATH Persist the exact runtime report to a new contained file.
+ --generation-isolation-report PATH
+ Persist source/image-bound generation isolation evidence.
--help Show this help.
The default release gate fails closed unless an image is supplied and Proof Capsule
@@ -51,6 +54,11 @@ while [[ $# -gt 0 ]]; do
RUNTIME_REPORT="$2"
shift 2
;;
+ --generation-isolation-report)
+ [[ $# -ge 2 ]] || { echo "--generation-isolation-report requires a value" >&2; exit 2; }
+ GENERATION_ISOLATION_REPORT="$2"
+ shift 2
+ ;;
--help|-h)
usage
exit 0
@@ -75,6 +83,16 @@ if [[ -n "${RUNTIME_REPORT}" ]]; then
exit 2
}
fi
+if [[ -n "${GENERATION_ISOLATION_REPORT}" ]]; then
+ if [[ "${GENERATION_ISOLATION_REPORT}" != /* ]]; then
+ GENERATION_ISOLATION_REPORT="${ROOT_DIR}/${GENERATION_ISOLATION_REPORT#./}"
+ fi
+ node scripts/assert-contained-path.mjs "${GENERATION_ISOLATION_REPORT}"
+ [[ ! -e "${GENERATION_ISOLATION_REPORT}" && ! -L "${GENERATION_ISOLATION_REPORT}" ]] || {
+ echo "Generation-isolation output must be a new repository-contained file." >&2
+ exit 2
+ }
+fi
[[ -x "./node_modules/.bin/tsx" ]] || {
echo "Missing local TypeScript runtime. Install the pinned workspace dependencies first." >&2
@@ -137,6 +155,7 @@ DOCKER_COMMAND=(
IMAGE_DIGEST="$("${DOCKER_COMMAND[@]}" image inspect "${IMAGE}" --format '{{.Id}}')"
SOURCE_COMMIT="$("${DOCKER_COMMAND[@]}" image inspect "${IMAGE}" --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')"
+SOURCE_TREE_SHA256="$("${DOCKER_COMMAND[@]}" image inspect "${IMAGE}" --format '{{index .Config.Labels "io.counterlab.source-tree-sha256"}}')"
IMAGE_USER="$("${DOCKER_COMMAND[@]}" image inspect "${IMAGE}" --format '{{.Config.User}}')"
if [[ ! "${IMAGE_DIGEST}" =~ ^sha256:[a-f0-9]{64}$ ]]; then
echo "Runner image does not expose a valid sha256 image ID." >&2
@@ -146,6 +165,10 @@ if [[ ! "${SOURCE_COMMIT}" =~ ^[a-f0-9]{40}$ ]]; then
echo "Runner image has an unbound or invalid OCI source revision." >&2
exit 1
fi
+if [[ ! "${SOURCE_TREE_SHA256}" =~ ^[a-f0-9]{64}$ ]]; then
+ echo "Runner image has an unbound or invalid OCI source-tree hash." >&2
+ exit 1
+fi
if [[ "${IMAGE_USER}" != "10001:10001" ]]; then
echo "Runner image must declare the non-root user 10001:10001; observed '${IMAGE_USER}'." >&2
exit 1
@@ -204,6 +227,19 @@ node -e '
}
' "${STARTUP_PROBE_OUTPUT}"
+if [[ -n "${GENERATION_ISOLATION_REPORT}" ]]; then
+ VERIFIED_AT="$(date -u +%Y-%m-%dT%H:%M:%S.000Z)"
+ node --import tsx scripts/generation-isolation-evidence.ts \
+ --source-commit "${SOURCE_COMMIT}" \
+ --source-tree-sha256 "${SOURCE_TREE_SHA256}" \
+ --local-image-tag "${IMAGE}" \
+ --local-image-digest "${IMAGE_DIGEST}" \
+ --image-user "${IMAGE_USER}" \
+ --startup-probe-json "${STARTUP_PROBE_OUTPUT}" \
+ --verified-at "${VERIFIED_AT}" \
+ --output "${GENERATION_ISOLATION_REPORT}"
+fi
+
# The preceding probe executes the real OCI entrypoint as Config.User. This
# second run adopts the host identity only so the exact-image verifier can read
# the repository evidence mounted read-only.
From 151854bb8bef3cc4f58f368dc3be213f1284d183 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 03:08:10 +0530
Subject: [PATCH 012/143] docs(progress): record image isolation evidence
---
docs/PROGRESS.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 74 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index fded4d12..33880daa 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -2960,3 +2960,77 @@ deleted.
receipt before publication.
8. Push the completed feature branch, review its exact tip, fast-forward it
into `main`, and push `main` only after the release state is truthful.
+
+## Finish Commander image-isolation evidence checkpoint — 2026-07-20T21:37:26Z
+
+Deadline: `2026-07-22T00:00:00Z`; **26 hours 22 minutes 34 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD `56638e9`; its only
+worktree entry is the preserved untracked
+`scripts/.fuse_hidden0000a9600000801f`. It remains unopened, unstaged, and
+undeleted.
+
+### Newly frozen independent commits
+
+- `b5d2359 fix(runner): hash Bubblewrap startup evidence` parses the actual
+ Bubblewrap stdout, rejects missing, malformed, false, or unknown evidence,
+ and hashes a canonical startup-probe payload.
+- `fc1c581 fix(runner): bind pinned Bubblewrap version` observes and binds the
+ exact image-pinned Bubblewrap `0.11.0` version into that payload.
+- `9a15ec4 feat(release): define image isolation evidence` preserves historical
+ receipt schemas, adds strict evidence plus qualified v6/release-check
+ v4/deployment v6 contracts, closes generated tuple semantics, and adds a
+ canonical hash verifier with mutation coverage.
+- `56638e9 feat(release): persist exact-image isolation evidence` lets the
+ exact-image scientific verifier create a contained, `0600`, non-overwriting
+ evidence record bound to OCI source, source-tree, image config digest, image
+ user, and recomputed probe hash.
+
+### Exact local verification
+
+- Hosted-runner startup probe: **8/8 passed**; full hosted-runner suite:
+ **45 passed, 2 skipped** after rerunning the five socket tests with loopback
+ permission.
+- Isolation contracts and canonical verifier: **4 files, 27/27 passed**.
+- Worker deployment configuration: **17/17 passed**.
+- Contained-runtime source validation: **35/35 passed**.
+- Root, Worker, and hosted-runner TypeScript at the affected checkpoints:
+ **passed**.
+- Shell syntax, scoped Prettier, Git whitespace, historical v5/v3/v5 schema
+ byte preservation, and scoped secret scans: **passed**.
+- The image evidence writer has not yet been exercised against a newly built
+ exact image. Source coverage is not represented as image or production
+ evidence.
+
+### Reconciled issue and external-gate truth
+
+- Internal P0/P1 reconciliation is now five source-pass, four partial or
+ mitigated, and three open; **zero are production-closed**.
+- Black-box product issues are 17 source-covered and one partial; **none are
+ verified against the current public tuple**. All 11 submission requirements
+ remain externally unverified.
+- This active session exposes no `CLOAK_CDP_ENDPOINT` or Playwright-safe tool
+ surface. Stock Chromium remains design-review-only under the checked-in
+ browser policy and cannot close release QA.
+- Repository-contained Wrangler `4.110.0` currently reports
+ `loggedIn:false`; normal-home credentials are not inspected or copied.
+- The host still lacks the delegated aggregate cgroup-v2 authority required by
+ the current qualification contract. This is a genuine external release
+ blocker and must not be converted into a synthetic receipt.
+
+### Ordered next work
+
+1. Consume the exact-image evidence in qualified v6, release-check v4, and
+ deployment v6; require the same probe hash from Container `/ready`, Worker
+ deep readiness, public release identity, and production smoke.
+2. Resolve the named FUSE artifact only with immediate destructive-action
+ approval, then freeze source and regenerate exact source/image-bound
+ scientific and supply-chain evidence.
+3. Obtain a real aggregate-containment receipt on a capable authorized host,
+ authenticate Wrangler inside the repository boundary, qualify, deploy, and
+ smoke exactly one tuple.
+4. Run CloakBrowser desktop/mobile/accessibility journeys in a session that
+ exposes the required endpoint. Keep learner evidence `NO_DATA` unless real
+ consented observations occur.
+5. Align and verify Devpost, public video, repository, feedback ID, and
+ submission receipt; push the feature branch and fast-forward its verified
+ tip into `main` only after the recorded gates are truthful.
From e8ae61f3973a71549d6d1341985709d50cc4ef11 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 03:45:06 +0530
Subject: [PATCH 013/143] fix(release): bind qualified isolation identity
---
apps/web/worker/api.test.ts | 16 ++
apps/web/worker/api.ts | 34 +++-
apps/web/worker/deployment-config.test.ts | 138 +++++++++++++++-
apps/web/worker/runner-container-env.test.ts | 4 +
apps/web/worker/runner-container-env.ts | 6 +
apps/web/worker/runner-control-plane.test.ts | 117 ++++++++++---
apps/web/worker/runner-control-plane.ts | 24 ++-
.../scientific-engine-registry/src/schema.ts | 6 +-
.../test/release-check-receipt.test.ts | 154 +++++++++++++++++-
.../test/release-schema.test.ts | 121 +++++++++++++-
scripts/create-deployment-receipt.test.ts | 22 +++
scripts/create-deployment-receipt.ts | 58 +++++--
scripts/deploy-qualified.sh | 19 ++-
scripts/prepare-qualified-deploy.ts | 61 ++++++-
scripts/production-smoke.sh | 16 +-
scripts/production_smoke_report.py | 58 ++++++-
scripts/qualify-runner-release.ts | 66 +++++++-
scripts/release-check-receipt.ts | 122 +++++++++++---
scripts/release-check.sh | 17 +-
scripts/test_production_smoke.py | 63 +++++--
scripts/verify-scientific-engines.sh | 16 +-
services/hosted-runner/src/server.test.ts | 39 +++++
services/hosted-runner/src/server.ts | 64 ++++++--
23 files changed, 1090 insertions(+), 151 deletions(-)
diff --git a/apps/web/worker/api.test.ts b/apps/web/worker/api.test.ts
index 44c36db3..1fd56a5f 100644
--- a/apps/web/worker/api.test.ts
+++ b/apps/web/worker/api.test.ts
@@ -10493,6 +10493,8 @@ describe("Cloudflare Worker API", () => {
COUNTERLAB_WORKER_EVIDENCE_COMMIT: workerEvidenceCommit,
COUNTERLAB_RUNNER_SOURCE_COMMIT: "b".repeat(40),
COUNTERLAB_RUNNER_IMAGE_DIGEST: `sha256:${"c".repeat(64)}`,
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "5".repeat(64),
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(64),
COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256: "d".repeat(64),
COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256: "9".repeat(64),
COUNTERLAB_RUNTIME_POLICY_SHA256: "e".repeat(64),
@@ -10522,6 +10524,8 @@ describe("Cloudflare Worker API", () => {
workerEvidenceCommit,
runnerSourceCommit: "b".repeat(40),
runnerImageDigest: `sha256:${"c".repeat(64)}`,
+ generationIsolationEvidenceSha256: "5".repeat(64),
+ generationIsolationProbeSha256: "6".repeat(64),
timeoutCleanupReceiptSha256: "d".repeat(64),
aggregateLimitEvidenceSha256: "9".repeat(64),
runtimePolicySha256: "e".repeat(64),
@@ -10543,6 +10547,8 @@ describe("Cloudflare Worker API", () => {
COUNTERLAB_WORKER_EVIDENCE_COMMIT: workerEvidenceCommit,
COUNTERLAB_RUNNER_SOURCE_COMMIT: "b".repeat(40),
COUNTERLAB_RUNNER_IMAGE_DIGEST: `sha256:${"c".repeat(64)}`,
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "5".repeat(64),
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(64),
COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256: "d".repeat(64),
COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256: "9".repeat(64),
COUNTERLAB_RUNTIME_POLICY_SHA256: "e".repeat(64),
@@ -10583,6 +10589,10 @@ describe("Cloudflare Worker API", () => {
for (const invalidQualificationBinding of [
{ COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256: undefined },
{ COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256: "0".repeat(63) },
+ { COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: undefined },
+ { COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "0".repeat(63) },
+ { COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: undefined },
+ { COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "0".repeat(63) },
{ COUNTERLAB_RUNTIME_POLICY_SHA256: undefined },
{ COUNTERLAB_RUNTIME_POLICY_SHA256: "0".repeat(63) },
{ COUNTERLAB_PROOF_DEPENDENCY_MANIFEST_SHA256: undefined },
@@ -10610,6 +10620,8 @@ describe("Cloudflare Worker API", () => {
COUNTERLAB_WORKER_EVIDENCE_COMMIT: workerEvidenceCommit,
COUNTERLAB_RUNNER_SOURCE_COMMIT: "b".repeat(40),
COUNTERLAB_RUNNER_IMAGE_DIGEST: `sha256:${"c".repeat(64)}`,
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "5".repeat(64),
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(64),
COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256: "d".repeat(64),
COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256: "9".repeat(64),
COUNTERLAB_RUNTIME_POLICY_SHA256: "e".repeat(64),
@@ -10930,6 +10942,8 @@ describe("Cloudflare Worker API", () => {
COUNTERLAB_RUNNER_SIGNING_PRIVATE_KEY: TEST_RUNNER_SIGNING_PRIVATE_KEY,
COUNTERLAB_RUNNER_SOURCE_COMMIT: "b".repeat(40),
COUNTERLAB_RUNNER_IMAGE_DIGEST: `sha256:${"c".repeat(64)}`,
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "5".repeat(64),
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(64),
} as unknown as Env & Record);
expect(response.status).toBe(200);
@@ -10947,6 +10961,8 @@ describe("Cloudflare Worker API", () => {
COUNTERLAB_RUNNER_BASE_URL: "http://127.0.0.1:8788",
COUNTERLAB_RUNNER_SOURCE_COMMIT: "b".repeat(40),
COUNTERLAB_RUNNER_IMAGE_DIGEST: `sha256:${"c".repeat(64)}`,
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "5".repeat(64),
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(64),
} as unknown as Env & Record);
await expect(unsigned.json()).resolves.toMatchObject({
data: { liveCodex: "local-runner-required" },
diff --git a/apps/web/worker/api.ts b/apps/web/worker/api.ts
index 87726907..4c944c40 100644
--- a/apps/web/worker/api.ts
+++ b/apps/web/worker/api.ts
@@ -243,6 +243,8 @@ type WorkerBindings = Omit & {
COUNTERLAB_WORKER_EVIDENCE_COMMIT?: string;
COUNTERLAB_RUNNER_SOURCE_COMMIT?: string;
COUNTERLAB_RUNNER_IMAGE_DIGEST?: string;
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256?: string;
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256?: string;
COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256?: string;
COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256?: string;
COUNTERLAB_RUNTIME_POLICY_SHA256?: string;
@@ -972,9 +974,15 @@ function runnerDispatcher(
context.env?.COUNTERLAB_RUNNER_SOURCE_COMMIT?.trim() ?? "";
const runnerImageDigest =
context.env?.COUNTERLAB_RUNNER_IMAGE_DIGEST?.trim() ?? "";
+ const generationIsolationEvidenceSha256 =
+ context.env?.COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256?.trim() ?? "";
+ const generationIsolationProbeSha256 =
+ context.env?.COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256?.trim() ?? "";
const exactRunnerIdentity =
/^[a-f0-9]{40}$/u.test(runnerSourceCommit) &&
- /^sha256:[a-f0-9]{64}$/u.test(runnerImageDigest);
+ /^sha256:[a-f0-9]{64}$/u.test(runnerImageDigest) &&
+ /^[a-f0-9]{64}$/u.test(generationIsolationEvidenceSha256) &&
+ /^[a-f0-9]{64}$/u.test(generationIsolationProbeSha256);
const processRunnerURL = context.env?.COUNTERLAB_RUNNER_BASE_URL?.trim();
if (
processRunnerURL !== undefined &&
@@ -984,7 +992,12 @@ function runnerDispatcher(
try {
return new HttpRunnerDispatcher({
baseURL: processRunnerURL,
- releaseIdentity: { runnerSourceCommit, runnerImageDigest },
+ releaseIdentity: {
+ runnerSourceCommit,
+ runnerImageDigest,
+ generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256,
+ },
});
} catch {
return undefined;
@@ -996,7 +1009,12 @@ function runnerDispatcher(
? new CloudflareContainerRunnerDispatcher(
context.env.RUNNER,
createRunnerContainerEnvVars(context.env),
- { runnerSourceCommit, runnerImageDigest },
+ {
+ runnerSourceCommit,
+ runnerImageDigest,
+ generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256,
+ },
)
: undefined;
}
@@ -1048,6 +1066,8 @@ function releaseIdentity(context: Context):
workerEvidenceCommit: string;
runnerSourceCommit: string;
runnerImageDigest: string;
+ generationIsolationEvidenceSha256: string;
+ generationIsolationProbeSha256: string;
timeoutCleanupReceiptSha256: string;
aggregateLimitEvidenceSha256: string;
runtimePolicySha256: string;
@@ -1067,6 +1087,10 @@ function releaseIdentity(context: Context):
context.env?.COUNTERLAB_WORKER_EVIDENCE_COMMIT ?? "";
const runnerSourceCommit = context.env?.COUNTERLAB_RUNNER_SOURCE_COMMIT ?? "";
const runnerImageDigest = context.env?.COUNTERLAB_RUNNER_IMAGE_DIGEST ?? "";
+ const generationIsolationEvidenceSha256 =
+ context.env?.COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256 ?? "";
+ const generationIsolationProbeSha256 =
+ context.env?.COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256 ?? "";
const timeoutCleanupReceiptSha256 =
context.env?.COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256 ?? "";
const aggregateLimitEvidenceSha256 =
@@ -1099,6 +1123,8 @@ function releaseIdentity(context: Context):
!/^[a-f0-9]{40}$/u.test(workerEvidenceCommit) ||
!/^[a-f0-9]{40}$/u.test(runnerSourceCommit) ||
!/^sha256:[a-f0-9]{64}$/u.test(runnerImageDigest) ||
+ !/^[a-f0-9]{64}$/u.test(generationIsolationEvidenceSha256) ||
+ !/^[a-f0-9]{64}$/u.test(generationIsolationProbeSha256) ||
!/^[a-f0-9]{64}$/u.test(timeoutCleanupReceiptSha256) ||
!/^[a-f0-9]{64}$/u.test(aggregateLimitEvidenceSha256) ||
!/^[a-f0-9]{64}$/u.test(runtimePolicySha256) ||
@@ -1126,6 +1152,8 @@ function releaseIdentity(context: Context):
workerEvidenceCommit,
runnerSourceCommit,
runnerImageDigest,
+ generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256,
timeoutCleanupReceiptSha256,
aggregateLimitEvidenceSha256,
runtimePolicySha256,
diff --git a/apps/web/worker/deployment-config.test.ts b/apps/web/worker/deployment-config.test.ts
index 157b258c..81865abf 100644
--- a/apps/web/worker/deployment-config.test.ts
+++ b/apps/web/worker/deployment-config.test.ts
@@ -8,6 +8,7 @@ import {
bindFrozenWorkerRelease,
qualifiedDeployConfig,
} from "../../../scripts/prepare-qualified-deploy";
+import { createGenerationIsolationEvidence } from "../../../scripts/generation-isolation-evidence";
function productionDeployConfig() {
return {
@@ -141,6 +142,62 @@ function timeoutQualification(sourceCommit: string) {
} as const;
}
+function generationIsolationQualification(input: {
+ sourceCommit: string;
+ sourceTreeSha256: string;
+ localImageTag: string;
+ localImageDigest: string;
+ verifiedAt?: string;
+}) {
+ const probePayload = {
+ schemaVersion: "1",
+ probeVersion: "counterlab-generation-isolation-v1",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: [
+ "entrypoint",
+ "non-root-user",
+ "immutable-paths",
+ "codex",
+ "python",
+ "bubblewrap",
+ "bubblewrap-read-isolation",
+ "setpriv",
+ "writable-roots",
+ ],
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ bubblewrapVersion: "0.11.0",
+ bubblewrap: {
+ forbiddenHostPathsHidden: true,
+ parentEnvironmentHidden: true,
+ workspaceVisible: true,
+ workspaceWritable: true,
+ },
+ } as const;
+ const probeSha256 =
+ "700cc58bedc163846e3854415170f49f55da9fd3ba316cc4967747d5268199dc";
+ const result = createGenerationIsolationEvidence({
+ ...input,
+ imageUser: "10001:10001",
+ verifiedAt: input.verifiedAt ?? "2026-07-16T16:18:00.000Z",
+ startupProbe: {
+ status: "ready",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: probePayload.checks,
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationProbe: probePayload,
+ generationIsolationProbeSha256: probeSha256,
+ },
+ });
+ return {
+ generationIsolationEvidence: result.evidence,
+ generationIsolationEvidenceSha256: result.evidenceSha256,
+ generationIsolationProbeSha256: result.probeSha256,
+ generationIsolationVerifiedAt: result.evidence.verifiedAt,
+ } as const;
+}
+
function luminance(color: string): number {
const channels = [1, 3, 5].map((offset) =>
Number.parseInt(color.slice(offset, offset + 2), 16),
@@ -219,6 +276,7 @@ describe("Cloudflare static asset routing", () => {
expect(verifyIndex).toBeGreaterThan(0);
expect(buildIndex).toBeGreaterThan(verifyIndex);
+ expect(script).toContain('--expected-image-digest "${LOCAL_IMAGE_DIGEST}"');
});
it("gates deployment on immutable authority, migrations, secrets, and 100 percent traffic", () => {
@@ -401,6 +459,12 @@ describe("Cloudflare static asset routing", () => {
"qualification output already exists; refusing to replace it",
);
expect(script).toContain("assertCurrentGrypeReleaseEvidenceBinding");
+ expect(script).toContain("input.expectedConfigDigest");
+ expect(script).toContain('"--expected-image-digest"');
+ expect(script).toContain("buildReceipt.localImageDigest");
+ expect(script).toContain(
+ '"qualified local image digest is unavailable for promotion"',
+ );
expect(script).toContain('flag: "wx"');
expect(
script.match(
@@ -546,8 +610,15 @@ describe("Cloudflare static asset routing", () => {
expect(probe).toContain("COUNTERLAB_RUNNER_STARTUP_PROBE=1");
expect(probe).not.toContain("--user");
expect(probe).not.toContain("--entrypoint");
- expect(probe).toContain('"${IMAGE}"');
+ expect(probe).toContain('"${IMAGE_DIGEST}"');
+ expect(
+ verifier.match(/"\$\{IMAGE_DIGEST\}"/gu)?.length,
+ ).toBeGreaterThanOrEqual(2);
expect(verifier).toContain("--generation-isolation-report");
+ expect(verifier).toContain("--expected-image-digest");
+ expect(verifier).toContain(
+ '"${IMAGE_DIGEST}" != "${EXPECTED_IMAGE_DIGEST}"',
+ );
expect(verifier).toContain(
'{{index .Config.Labels "io.counterlab.source-tree-sha256"}}',
);
@@ -561,7 +632,7 @@ describe("Cloudflare static asset routing", () => {
const evidenceCommit = "2".repeat(40);
const image = `registry.cloudflare.com/account-1/counterlab-runner:git-${sourceCommit}`;
const receipt = {
- schemaVersion: "5",
+ schemaVersion: "6",
status: "VERIFIED",
generationFilesystemReadIsolation: "OS_ENFORCED",
sourceCommit,
@@ -570,6 +641,12 @@ describe("Cloudflare static asset routing", () => {
dockerfileSha256: "d".repeat(64),
localImageTag: `counterlab-runner:git-${sourceCommit}`,
localImageDigest: `sha256:${"e".repeat(64)}`,
+ ...generationIsolationQualification({
+ sourceCommit,
+ sourceTreeSha256: "c".repeat(64),
+ localImageTag: `counterlab-runner:git-${sourceCommit}`,
+ localImageDigest: `sha256:${"e".repeat(64)}`,
+ }),
ociRevision: sourceCommit,
ociSourceTreeSha256: "c".repeat(64),
engineAuthorityHash: "f".repeat(64),
@@ -595,10 +672,16 @@ describe("Cloudflare static asset routing", () => {
registryDigest: `sha256:${"3".repeat(64)}`,
registryResolvedAt: "2026-07-16T16:20:00.000Z",
qualifiedAt: "2026-07-16T16:30:00.000Z",
- verifierVersion: "counterlab-release-v5",
+ verifierVersion: "counterlab-release-v6",
};
const observation = {
generationFilesystemReadIsolation: "OS_ENFORCED" as const,
+ ...generationIsolationQualification({
+ sourceCommit,
+ sourceTreeSha256: receipt.sourceTreeSha256,
+ localImageTag: receipt.localImageTag,
+ localImageDigest: receipt.localImageDigest,
+ }),
sourceCommit,
sourceArchiveSha256: receipt.sourceArchiveSha256,
sourceTreeSha256: receipt.sourceTreeSha256,
@@ -663,6 +746,10 @@ describe("Cloudflare static asset routing", () => {
COUNTERLAB_RUNTIME_POLICY_SHA256: receipt.runtimePolicySha256,
COUNTERLAB_PROOF_DEPENDENCY_MANIFEST_SHA256:
receipt.proofDependencyManifestSha256,
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256:
+ receipt.generationIsolationEvidenceSha256,
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256:
+ receipt.generationIsolationProbeSha256,
}),
);
const generatedFromVite = qualifiedDeployConfig({
@@ -776,6 +863,19 @@ describe("Cloudflare static asset routing", () => {
observation,
}),
).toThrow(/runtime policy/u);
+ for (const [field, label] of [
+ ["generationIsolationEvidenceSha256", /generation isolation evidence/u],
+ ["generationIsolationProbeSha256", /generation isolation probe/u],
+ ] as const) {
+ expect(() =>
+ qualifiedDeployConfig({
+ config: productionDeployConfig(),
+ receipt,
+ image,
+ observation: { ...observation, [field]: "0".repeat(64) },
+ }),
+ ).toThrow(label);
+ }
expect(() =>
qualifiedDeployConfig({
config: productionDeployConfig(),
@@ -805,7 +905,7 @@ describe("Cloudflare static asset routing", () => {
const evidenceCommit = "2".repeat(40);
const image = `registry.cloudflare.com/account-1/counterlab-runner:git-${sourceCommit}`;
const receipt = {
- schemaVersion: "5",
+ schemaVersion: "6",
status: "VERIFIED",
generationFilesystemReadIsolation: "OS_ENFORCED",
sourceCommit,
@@ -814,6 +914,12 @@ describe("Cloudflare static asset routing", () => {
dockerfileSha256: "d".repeat(64),
localImageTag: `counterlab-runner:git-${sourceCommit}`,
localImageDigest: `sha256:${"e".repeat(64)}`,
+ ...generationIsolationQualification({
+ sourceCommit,
+ sourceTreeSha256: "c".repeat(64),
+ localImageTag: `counterlab-runner:git-${sourceCommit}`,
+ localImageDigest: `sha256:${"e".repeat(64)}`,
+ }),
ociRevision: sourceCommit,
ociSourceTreeSha256: "c".repeat(64),
engineAuthorityHash: "f".repeat(64),
@@ -839,7 +945,7 @@ describe("Cloudflare static asset routing", () => {
registryDigest: `sha256:${"3".repeat(64)}`,
registryResolvedAt: "2026-07-16T16:20:00.000Z",
qualifiedAt: "2026-07-16T16:30:00.000Z",
- verifierVersion: "counterlab-release-v5",
+ verifierVersion: "counterlab-release-v6",
};
const config = productionDeployConfig();
config.durable_objects.bindings = [
@@ -853,6 +959,12 @@ describe("Cloudflare static asset routing", () => {
image,
observation: {
generationFilesystemReadIsolation: "OS_ENFORCED",
+ ...generationIsolationQualification({
+ sourceCommit,
+ sourceTreeSha256: receipt.sourceTreeSha256,
+ localImageTag: receipt.localImageTag,
+ localImageDigest: receipt.localImageDigest,
+ }),
sourceCommit,
sourceArchiveSha256: receipt.sourceArchiveSha256,
sourceTreeSha256: receipt.sourceTreeSha256,
@@ -895,7 +1007,7 @@ describe("Cloudflare static asset routing", () => {
const sourceCommit = "a".repeat(40);
const image = `registry.cloudflare.com/account-1/counterlab-runner:git-${sourceCommit}`;
const receipt = {
- schemaVersion: "5",
+ schemaVersion: "6",
status: "VERIFIED",
generationFilesystemReadIsolation: "OS_ENFORCED",
sourceCommit,
@@ -904,6 +1016,12 @@ describe("Cloudflare static asset routing", () => {
dockerfileSha256: "d".repeat(64),
localImageTag: `counterlab-runner:git-${sourceCommit}`,
localImageDigest: `sha256:${"e".repeat(64)}`,
+ ...generationIsolationQualification({
+ sourceCommit,
+ sourceTreeSha256: "c".repeat(64),
+ localImageTag: `counterlab-runner:git-${sourceCommit}`,
+ localImageDigest: `sha256:${"e".repeat(64)}`,
+ }),
ociRevision: sourceCommit,
ociSourceTreeSha256: "c".repeat(64),
engineAuthorityHash: "f".repeat(64),
@@ -929,7 +1047,7 @@ describe("Cloudflare static asset routing", () => {
registryDigest: `sha256:${"3".repeat(64)}`,
registryResolvedAt: "2026-07-16T15:55:00.000Z",
qualifiedAt: "2026-07-16T16:30:00.000Z",
- verifierVersion: "counterlab-release-v5",
+ verifierVersion: "counterlab-release-v6",
};
expect(() =>
@@ -942,6 +1060,12 @@ describe("Cloudflare static asset routing", () => {
image,
observation: {
generationFilesystemReadIsolation: "OS_ENFORCED",
+ ...generationIsolationQualification({
+ sourceCommit,
+ sourceTreeSha256: receipt.sourceTreeSha256,
+ localImageTag: receipt.localImageTag,
+ localImageDigest: receipt.localImageDigest,
+ }),
sourceCommit,
sourceArchiveSha256: "9".repeat(64),
sourceTreeSha256: receipt.sourceTreeSha256,
diff --git a/apps/web/worker/runner-container-env.test.ts b/apps/web/worker/runner-container-env.test.ts
index 278ea4b1..652fdb18 100644
--- a/apps/web/worker/runner-container-env.test.ts
+++ b/apps/web/worker/runner-container-env.test.ts
@@ -17,6 +17,8 @@ describe("runner Container environment", () => {
COUNTERLAB_RUNNER_SIGNING_PRIVATE_KEY: signingKeys.privateKey,
COUNTERLAB_RUNNER_SOURCE_COMMIT: "b".repeat(40),
COUNTERLAB_RUNNER_IMAGE_DIGEST: `sha256:${"c".repeat(64)}`,
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "d".repeat(64),
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "e".repeat(64),
});
expect(environment).toEqual({
@@ -37,6 +39,8 @@ describe("runner Container environment", () => {
COUNTERLAB_RUNNER_ONE_SHOT: "1",
COUNTERLAB_RUNNER_SOURCE_COMMIT: "b".repeat(40),
COUNTERLAB_RUNNER_IMAGE_DIGEST: `sha256:${"c".repeat(64)}`,
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "d".repeat(64),
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "e".repeat(64),
});
});
diff --git a/apps/web/worker/runner-container-env.ts b/apps/web/worker/runner-container-env.ts
index 5d4731fe..22c33d27 100644
--- a/apps/web/worker/runner-container-env.ts
+++ b/apps/web/worker/runner-container-env.ts
@@ -6,6 +6,8 @@ export type RunnerContainerSecretBindings = {
COUNTERLAB_RUNNER_SIGNING_PRIVATE_KEY?: string;
COUNTERLAB_RUNNER_SOURCE_COMMIT?: string;
COUNTERLAB_RUNNER_IMAGE_DIGEST?: string;
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256?: string;
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256?: string;
};
export function createRunnerContainerEnvVars(
@@ -37,5 +39,9 @@ export function createRunnerContainerEnvVars(
bindings.COUNTERLAB_RUNNER_SOURCE_COMMIT ?? "",
COUNTERLAB_RUNNER_IMAGE_DIGEST:
bindings.COUNTERLAB_RUNNER_IMAGE_DIGEST ?? "",
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256:
+ bindings.COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256 ?? "",
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256:
+ bindings.COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256 ?? "",
};
}
diff --git a/apps/web/worker/runner-control-plane.test.ts b/apps/web/worker/runner-control-plane.test.ts
index 20a17ed7..8fc315c4 100644
--- a/apps/web/worker/runner-control-plane.test.ts
+++ b/apps/web/worker/runner-control-plane.test.ts
@@ -32,7 +32,14 @@ const job: RunnerJob = {
};
const runnerImageDigest = `sha256:${"c".repeat(64)}`;
const runnerSourceCommit = "b".repeat(40);
-const runnerReleaseIdentity = { runnerSourceCommit, runnerImageDigest };
+const generationIsolationEvidenceSha256 = "d".repeat(64);
+const generationIsolationProbeSha256 = "e".repeat(64);
+const runnerReleaseIdentity = {
+ runnerSourceCommit,
+ runnerImageDigest,
+ generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256,
+};
describe("HttpRunnerDispatcher", () => {
it("starts and probes the digest-bound Container readiness instance", async () => {
@@ -44,6 +51,8 @@ describe("HttpRunnerDispatcher", () => {
generationFilesystemReadIsolation: "OS_ENFORCED",
runnerSourceCommit,
runnerImageDigest,
+ generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256,
}),
{ status: 200, headers: { "content-type": "application/json" } },
);
@@ -57,7 +66,7 @@ describe("HttpRunnerDispatcher", () => {
await expect(dispatcher.ready()).resolves.toBe(true);
expect(getByName).toHaveBeenCalledWith(
- `counterlab-readiness-${"c".repeat(64)}`,
+ `counterlab-readiness-${generationIsolationEvidenceSha256}`,
);
expect(startAndWaitForPorts).toHaveBeenCalledWith({
ports: [8080],
@@ -84,6 +93,8 @@ describe("HttpRunnerDispatcher", () => {
["invalid response", "schema"],
["stale source identity", "source"],
["stale image identity", "image"],
+ ["stale isolation evidence identity", "evidence"],
+ ["stale isolation probe identity", "probe"],
["partial filesystem isolation", "isolation"],
])("fails Container readiness closed on %s", async (_label, failure) => {
const startAndWaitForPorts = vi.fn(async () => {
@@ -105,6 +116,12 @@ describe("HttpRunnerDispatcher", () => {
failure === "source" ? "d".repeat(40) : runnerSourceCommit,
runnerImageDigest:
failure === "image" ? `sha256:${"e".repeat(64)}` : runnerImageDigest,
+ generationIsolationEvidenceSha256:
+ failure === "evidence"
+ ? "f".repeat(64)
+ : generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256:
+ failure === "probe" ? "f".repeat(64) : generationIsolationProbeSha256,
};
return new Response(JSON.stringify(payload), { status: 200 });
});
@@ -120,11 +137,22 @@ describe("HttpRunnerDispatcher", () => {
it.each([
[
"invalid source commit",
- { runnerSourceCommit: "main", runnerImageDigest },
+ { ...runnerReleaseIdentity, runnerSourceCommit: "main" },
],
[
"invalid image digest",
- { runnerSourceCommit, runnerImageDigest: "counterlab-runner:latest" },
+ {
+ ...runnerReleaseIdentity,
+ runnerImageDigest: "counterlab-runner:latest",
+ },
+ ],
+ [
+ "invalid isolation evidence hash",
+ { ...runnerReleaseIdentity, generationIsolationEvidenceSha256: "short" },
+ ],
+ [
+ "invalid isolation probe hash",
+ { ...runnerReleaseIdentity, generationIsolationProbeSha256: "short" },
],
])("rejects %s before acquiring a Container", (_label, identity) => {
const getByName = vi.fn();
@@ -328,6 +356,8 @@ describe("HttpRunnerDispatcher", () => {
generationFilesystemReadIsolation: "OS_ENFORCED",
runnerSourceCommit,
runnerImageDigest,
+ generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256,
}),
{ status: 200 },
),
@@ -400,29 +430,60 @@ describe("HttpRunnerDispatcher", () => {
});
it.each([
- ["stale source", "d".repeat(40), runnerImageDigest],
- ["stale image", runnerSourceCommit, `sha256:${"e".repeat(64)}`],
- ])("fails HTTP readiness closed for %s", async (_label, source, image) => {
- const dispatcher = new HttpRunnerDispatcher({
- baseURL: "https://runner.example.test",
- releaseIdentity: runnerReleaseIdentity,
- fetch: vi.fn(
- async () =>
- new Response(
- JSON.stringify({
- status: "ready",
- service: "counterlab-hosted-runner",
- generationFilesystemReadIsolation: "OS_ENFORCED",
- runnerSourceCommit: source,
- runnerImageDigest: image,
- }),
- { status: 200 },
- ),
- ),
- });
-
- await expect(dispatcher.ready()).resolves.toBe(false);
- });
+ [
+ "stale source",
+ "d".repeat(40),
+ runnerImageDigest,
+ generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256,
+ ],
+ [
+ "stale image",
+ runnerSourceCommit,
+ `sha256:${"e".repeat(64)}`,
+ generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256,
+ ],
+ [
+ "stale isolation evidence",
+ runnerSourceCommit,
+ runnerImageDigest,
+ "f".repeat(64),
+ generationIsolationProbeSha256,
+ ],
+ [
+ "stale isolation probe",
+ runnerSourceCommit,
+ runnerImageDigest,
+ generationIsolationEvidenceSha256,
+ "f".repeat(64),
+ ],
+ ])(
+ "fails HTTP readiness closed for %s",
+ async (_label, source, image, evidence, probe) => {
+ const dispatcher = new HttpRunnerDispatcher({
+ baseURL: "https://runner.example.test",
+ releaseIdentity: runnerReleaseIdentity,
+ fetch: vi.fn(
+ async () =>
+ new Response(
+ JSON.stringify({
+ status: "ready",
+ service: "counterlab-hosted-runner",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ runnerSourceCommit: source,
+ runnerImageDigest: image,
+ generationIsolationEvidenceSha256: evidence,
+ generationIsolationProbeSha256: probe,
+ }),
+ { status: 200 },
+ ),
+ ),
+ });
+
+ await expect(dispatcher.ready()).resolves.toBe(false);
+ },
+ );
it("requires an exact expected identity for an HTTP runner", () => {
expect(
@@ -432,6 +493,8 @@ describe("HttpRunnerDispatcher", () => {
releaseIdentity: {
runnerSourceCommit: "main",
runnerImageDigest,
+ generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256,
},
}),
).toThrow(/source commit must be exact/i);
diff --git a/apps/web/worker/runner-control-plane.ts b/apps/web/worker/runner-control-plane.ts
index 1042e9df..b2d4db3a 100644
--- a/apps/web/worker/runner-control-plane.ts
+++ b/apps/web/worker/runner-control-plane.ts
@@ -40,6 +40,8 @@ export type RunnerContainerBinding = {
export type RunnerReleaseIdentity = {
runnerSourceCommit: string;
runnerImageDigest: string;
+ generationIsolationEvidenceSha256: string;
+ generationIsolationProbeSha256: string;
};
export type HttpRunnerDispatcherOptions = {
@@ -90,6 +92,18 @@ function assertExactRunnerReleaseIdentity(
if (!/^sha256:[a-f0-9]{64}$/u.test(releaseIdentity.runnerImageDigest)) {
throw new Error("Runner image digest must be exact for readiness");
}
+ if (!/^[a-f0-9]{64}$/u.test(releaseIdentity.generationIsolationProbeSha256)) {
+ throw new Error(
+ "Generation-isolation probe hash must be exact for readiness",
+ );
+ }
+ if (
+ !/^[a-f0-9]{64}$/u.test(releaseIdentity.generationIsolationEvidenceSha256)
+ ) {
+ throw new Error(
+ "Generation-isolation evidence hash must be exact for readiness",
+ );
+ }
}
async function runnerReadinessMatches(
@@ -107,6 +121,8 @@ async function runnerReadinessMatches(
JSON.stringify(
[
"generationFilesystemReadIsolation",
+ "generationIsolationEvidenceSha256",
+ "generationIsolationProbeSha256",
"runnerImageDigest",
"runnerSourceCommit",
"service",
@@ -119,6 +135,12 @@ async function runnerReadinessMatches(
payload.service === "counterlab-hosted-runner" &&
"generationFilesystemReadIsolation" in payload &&
payload.generationFilesystemReadIsolation === "OS_ENFORCED" &&
+ "generationIsolationEvidenceSha256" in payload &&
+ payload.generationIsolationEvidenceSha256 ===
+ releaseIdentity.generationIsolationEvidenceSha256 &&
+ "generationIsolationProbeSha256" in payload &&
+ payload.generationIsolationProbeSha256 ===
+ releaseIdentity.generationIsolationProbeSha256 &&
"runnerSourceCommit" in payload &&
payload.runnerSourceCommit === releaseIdentity.runnerSourceCommit &&
"runnerImageDigest" in payload &&
@@ -209,7 +231,7 @@ export class CloudflareContainerRunnerDispatcher implements RunnerDispatcher {
private readonly releaseIdentity: RunnerReleaseIdentity,
) {
assertExactRunnerReleaseIdentity(releaseIdentity);
- this.readinessInstanceName = `counterlab-readiness-${releaseIdentity.runnerImageDigest.slice("sha256:".length)}`;
+ this.readinessInstanceName = `counterlab-readiness-${releaseIdentity.generationIsolationEvidenceSha256}`;
}
async ready(): Promise {
diff --git a/packages/scientific-engine-registry/src/schema.ts b/packages/scientific-engine-registry/src/schema.ts
index 07b071d6..4efaaa9d 100644
--- a/packages/scientific-engine-registry/src/schema.ts
+++ b/packages/scientific-engine-registry/src/schema.ts
@@ -1127,7 +1127,7 @@ export const QualifiedRunnerReleaseV6Schema = z
}
});
-export const QualifiedRunnerReleaseSchema = QualifiedRunnerReleaseV5Schema;
+export const QualifiedRunnerReleaseSchema = QualifiedRunnerReleaseV6Schema;
export const RELEASE_CHECK_IDS = [
"test-all",
@@ -1346,7 +1346,7 @@ export const ReleaseCheckReceiptV4Schema = z
}
});
-export const ReleaseCheckReceiptSchema = ReleaseCheckReceiptV3Schema;
+export const ReleaseCheckReceiptSchema = ReleaseCheckReceiptV4Schema;
export const DeploymentReceiptV3Schema = z
.strictObject({
@@ -1609,4 +1609,4 @@ export const DeploymentReceiptV6Schema = z
}
});
-export const DeploymentReceiptSchema = DeploymentReceiptV5Schema;
+export const DeploymentReceiptSchema = DeploymentReceiptV6Schema;
diff --git a/packages/scientific-engine-registry/test/release-check-receipt.test.ts b/packages/scientific-engine-registry/test/release-check-receipt.test.ts
index 76ffe9b7..3abd9ae4 100644
--- a/packages/scientific-engine-registry/test/release-check-receipt.test.ts
+++ b/packages/scientific-engine-registry/test/release-check-receipt.test.ts
@@ -7,7 +7,9 @@ import { describe, expect, it } from "vitest";
import {
ReleaseCheckReceiptSchema,
ReleaseCheckReceiptV2Schema,
+ ReleaseCheckReceiptV3Schema,
} from "../src/index";
+import { createGenerationIsolationEvidence } from "../../../scripts/generation-isolation-evidence";
import {
assertReleaseCheckBinding,
@@ -19,11 +21,72 @@ import {
const sourceCommit = "a".repeat(40);
const evidenceCommit = "b".repeat(40);
+function generationIsolation(verifiedAt = "2026-07-19T00:00:30.000Z") {
+ const probePayload = {
+ schemaVersion: "1",
+ probeVersion: "counterlab-generation-isolation-v1",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: [
+ "entrypoint",
+ "non-root-user",
+ "immutable-paths",
+ "codex",
+ "python",
+ "bubblewrap",
+ "bubblewrap-read-isolation",
+ "setpriv",
+ "writable-roots",
+ ],
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ bubblewrapVersion: "0.11.0",
+ bubblewrap: {
+ forbiddenHostPathsHidden: true,
+ parentEnvironmentHidden: true,
+ workspaceVisible: true,
+ workspaceWritable: true,
+ },
+ } as const;
+ return createGenerationIsolationEvidence({
+ sourceCommit,
+ sourceTreeSha256: "2".repeat(64),
+ localImageTag: `counterlab-runner:git-${sourceCommit}`,
+ localImageDigest: `sha256:${"4".repeat(64)}`,
+ imageUser: "10001:10001",
+ verifiedAt,
+ startupProbe: {
+ status: "ready",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: probePayload.checks,
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationProbe: probePayload,
+ generationIsolationProbeSha256:
+ "700cc58bedc163846e3854415170f49f55da9fd3ba316cc4967747d5268199dc",
+ },
+ });
+}
+
+function generationIsolationFields() {
+ const result = generationIsolation();
+ return {
+ generationIsolationEvidence: result.evidence,
+ generationIsolationEvidenceSha256: result.evidenceSha256,
+ generationIsolationProbeSha256: result.probeSha256,
+ generationIsolationVerifiedAt: result.evidence.verifiedAt,
+ } as const;
+}
+
+function freshGenerationIsolationEvidence() {
+ return generationIsolation("2026-07-19T00:01:30.000Z").evidence;
+}
+
function qualifiedReceipt() {
return {
- schemaVersion: "5",
+ schemaVersion: "6",
status: "VERIFIED",
generationFilesystemReadIsolation: "OS_ENFORCED",
+ ...generationIsolationFields(),
sourceCommit,
sourceArchiveSha256: "1".repeat(64),
sourceTreeSha256: "2".repeat(64),
@@ -64,7 +127,7 @@ function qualifiedReceipt() {
registryDigest: `sha256:${"1".repeat(64)}`,
registryResolvedAt: "2026-07-19T00:00:00.000Z",
qualifiedAt: "2026-07-19T00:01:00.000Z",
- verifierVersion: "counterlab-release-v5",
+ verifierVersion: "counterlab-release-v6",
} as const;
}
@@ -110,11 +173,14 @@ function deploymentReceipt() {
const runner = "d".repeat(40);
const digest = `sha256:${"a".repeat(64)}`;
return {
- schemaVersion: "5",
+ schemaVersion: "6",
status: "DEPLOYED",
workerName: "counterlab",
productionOrigin: "https://counterlab.cserules.workers.dev",
generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationEvidenceSha256: "0".repeat(64),
+ generationIsolationProbeSha256: "1".repeat(64),
+ generationIsolationVerifiedAt: "2026-07-18T23:58:00.000Z",
workerEvidenceCommit: worker,
runnerSourceCommit: runner,
qualifiedRunnerReceiptSha256: "1".repeat(64),
@@ -151,7 +217,7 @@ function deploymentReceipt() {
workerVersionSha256: "c".repeat(64),
containerStatusSha256: "d".repeat(64),
deployedAt: "2026-07-19T00:00:00.000Z",
- verifierVersion: "counterlab-deployment-v5",
+ verifierVersion: "counterlab-deployment-v6",
} as const;
}
@@ -266,20 +332,51 @@ describe("release-check receipt", () => {
worktreeClean: true,
runnerImageDigest: qualifiedReceipt().localImageDigest,
adapterImageDigest: qualifiedReceipt().adapterImageDigest,
+ generationIsolationEvidence: freshGenerationIsolationEvidence(),
checkedAt: "2026-07-19T00:02:00.000Z",
});
expect(receipt).toMatchObject({
- schemaVersion: "3",
+ schemaVersion: "4",
status: "PASSED",
generationFilesystemReadIsolation: "OS_ENFORCED",
evidenceCommit,
- verifierVersion: "counterlab-release-check-v3",
+ verifierVersion: "counterlab-release-check-v4",
});
expect(receipt.checks).toHaveLength(11);
});
- it("preserves v2 receipts while requiring OS isolation in v3", () => {
+ it("requires a fresh exact-image probe that matches qualification", () => {
+ const input = {
+ qualifiedReceipt: qualifiedReceipt(),
+ qualifiedReceiptBytes: qualifiedBytes(),
+ runtimeAttestation: runtimeAttestation(),
+ currentCommit: evidenceCommit,
+ worktreeClean: true,
+ runnerImageDigest: qualifiedReceipt().localImageDigest,
+ adapterImageDigest: qualifiedReceipt().adapterImageDigest,
+ checkedAt: "2026-07-19T00:02:00.000Z",
+ } as const;
+ expect(() =>
+ createReleaseCheckReceipt({
+ ...input,
+ generationIsolationEvidence: {
+ ...freshGenerationIsolationEvidence(),
+ sourceCommit: "0".repeat(40),
+ },
+ }),
+ ).toThrow(/source commit|sourceCommit/iu);
+ expect(() =>
+ createReleaseCheckReceipt({
+ ...input,
+ generationIsolationEvidence: generationIsolation(
+ "2026-07-18T22:00:00.000Z",
+ ).evidence,
+ }),
+ ).toThrow(/not fresh/u);
+ });
+
+ it("preserves v3 and v2 receipts while requiring hash binding in v4", () => {
const current = createReleaseCheckReceipt({
qualifiedReceipt: qualifiedReceipt(),
qualifiedReceiptBytes: qualifiedBytes(),
@@ -288,12 +385,26 @@ describe("release-check receipt", () => {
worktreeClean: true,
runnerImageDigest: qualifiedReceipt().localImageDigest,
adapterImageDigest: qualifiedReceipt().adapterImageDigest,
+ generationIsolationEvidence: freshGenerationIsolationEvidence(),
checkedAt: "2026-07-19T00:02:00.000Z",
});
+ const {
+ generationIsolationEvidenceSha256: _generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: _generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: _generationIsolationVerifiedAt,
+ ...legacyV3
+ } = current;
+ expect(
+ ReleaseCheckReceiptV3Schema.parse({
+ ...legacyV3,
+ schemaVersion: "3",
+ verifierVersion: "counterlab-release-check-v3",
+ }),
+ ).toMatchObject({ schemaVersion: "3" });
const {
generationFilesystemReadIsolation: _generationFilesystemReadIsolation,
...legacy
- } = current;
+ } = legacyV3;
expect(
ReleaseCheckReceiptV2Schema.parse({
@@ -348,6 +459,7 @@ describe("release-check receipt", () => {
worktreeClean: true,
runnerImageDigest: qualifiedReceipt().localImageDigest,
adapterImageDigest: qualifiedReceipt().adapterImageDigest,
+ generationIsolationEvidence: freshGenerationIsolationEvidence(),
checkedAt: "2026-07-19T00:02:00.000Z",
});
const common = {
@@ -405,6 +517,31 @@ describe("release-check receipt", () => {
}),
).toThrow(new RegExp(`live ${label.source}`, "u"));
}
+ for (const [field, value, label] of [
+ [
+ "generationIsolationEvidenceSha256",
+ "0".repeat(64),
+ /generation isolation evidence/u,
+ ],
+ [
+ "generationIsolationProbeSha256",
+ "0".repeat(64),
+ /generation isolation probe/u,
+ ],
+ [
+ "generationIsolationVerifiedAt",
+ "2026-07-19T00:00:31.000Z",
+ /generation isolation verification/u,
+ ],
+ ] as const) {
+ expect(() =>
+ assertReleaseCheckBinding({
+ ...common,
+ releaseCheckReceipt: { ...receipt, [field]: value },
+ runtimeAttestation: runtimeAttestation(),
+ }),
+ ).toThrow(label);
+ }
});
it("rejects stale receipts and incomplete check sets", () => {
@@ -416,6 +553,7 @@ describe("release-check receipt", () => {
worktreeClean: true,
runnerImageDigest: qualifiedReceipt().localImageDigest,
adapterImageDigest: qualifiedReceipt().adapterImageDigest,
+ generationIsolationEvidence: freshGenerationIsolationEvidence(),
checkedAt: "2026-07-19T00:02:00.000Z",
});
const common = {
diff --git a/packages/scientific-engine-registry/test/release-schema.test.ts b/packages/scientific-engine-registry/test/release-schema.test.ts
index e134cfaa..626af6e2 100644
--- a/packages/scientific-engine-registry/test/release-schema.test.ts
+++ b/packages/scientific-engine-registry/test/release-schema.test.ts
@@ -6,11 +6,14 @@ import {
DeploymentReceiptSchema,
DeploymentReceiptV3Schema,
DeploymentReceiptV4Schema,
+ DeploymentReceiptV5Schema,
QualifiedRunnerReleaseSchema,
QualifiedRunnerReleaseV2Schema,
QualifiedRunnerReleaseV3Schema,
QualifiedRunnerReleaseV4Schema,
+ QualifiedRunnerReleaseV5Schema,
} from "../src/index";
+import { createGenerationIsolationEvidence } from "../../../scripts/generation-isolation-evidence";
import * as releaseTools from "../../../scripts/prepare-qualified-deploy";
function legacyReceipt() {
@@ -34,12 +37,67 @@ function legacyReceipt() {
};
}
+function generationIsolationQualification(input: {
+ sourceCommit: string;
+ sourceTreeSha256: string;
+ localImageTag: string;
+ localImageDigest: string;
+ verifiedAt?: string;
+}) {
+ const probePayload = {
+ schemaVersion: "1",
+ probeVersion: "counterlab-generation-isolation-v1",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: [
+ "entrypoint",
+ "non-root-user",
+ "immutable-paths",
+ "codex",
+ "python",
+ "bubblewrap",
+ "bubblewrap-read-isolation",
+ "setpriv",
+ "writable-roots",
+ ],
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ bubblewrapVersion: "0.11.0",
+ bubblewrap: {
+ forbiddenHostPathsHidden: true,
+ parentEnvironmentHidden: true,
+ workspaceVisible: true,
+ workspaceWritable: true,
+ },
+ } as const;
+ const result = createGenerationIsolationEvidence({
+ ...input,
+ imageUser: "10001:10001",
+ verifiedAt: input.verifiedAt ?? "2026-07-16T16:23:00.000Z",
+ startupProbe: {
+ status: "ready",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: probePayload.checks,
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationProbe: probePayload,
+ generationIsolationProbeSha256:
+ "700cc58bedc163846e3854415170f49f55da9fd3ba316cc4967747d5268199dc",
+ },
+ });
+ return {
+ generationIsolationEvidence: result.evidence,
+ generationIsolationEvidenceSha256: result.evidenceSha256,
+ generationIsolationProbeSha256: result.probeSha256,
+ generationIsolationVerifiedAt: result.evidence.verifiedAt,
+ } as const;
+}
+
describe("qualified runner release schema", () => {
it("requires an exact registry promotion digest", () => {
expect(() => QualifiedRunnerReleaseSchema.parse(legacyReceipt())).toThrow();
});
- it("creates a runtime-bound promoted v5 receipt from recomputed evidence", () => {
+ it("creates a runtime-bound promoted v6 receipt from recomputed evidence", () => {
const sourceCommit = "a".repeat(40);
const evidenceCommit = "2".repeat(40);
const registryImage = `registry.cloudflare.com/account-1/counterlab-runner:git-${sourceCommit}`;
@@ -91,6 +149,12 @@ describe("qualified runner release schema", () => {
timeoutVerifiedAt: "2026-07-16T16:24:00.000Z",
currentCommit: evidenceCommit,
generationFilesystemReadIsolation: "OS_ENFORCED",
+ ...generationIsolationQualification({
+ sourceCommit,
+ sourceTreeSha256: "c".repeat(64),
+ localImageTag: `counterlab-runner:git-${sourceCommit}`,
+ localImageDigest: `sha256:${"e".repeat(64)}`,
+ }),
sourceIsAncestor: true,
changedPaths: ["scientific-engines/snapshot.json"],
registryImage,
@@ -102,7 +166,7 @@ describe("qualified runner release schema", () => {
);
expect(QualifiedRunnerReleaseSchema.parse(receipt)).toMatchObject({
- schemaVersion: "5",
+ schemaVersion: "6",
generationFilesystemReadIsolation: "OS_ENFORCED",
evidenceCommit,
registryImage,
@@ -119,7 +183,7 @@ describe("qualified runner release schema", () => {
).toThrow();
});
- it("retains the exact historical v4 receipt contract", () => {
+ it("retains the exact historical v5 and v4 receipt contracts", () => {
const sourceCommit = "a".repeat(40);
const evidenceCommit = "2".repeat(40);
const current = (
@@ -168,6 +232,12 @@ describe("qualified runner release schema", () => {
timeoutVerifiedAt: "2026-07-16T16:24:00.000Z",
currentCommit: evidenceCommit,
generationFilesystemReadIsolation: "OS_ENFORCED",
+ ...generationIsolationQualification({
+ sourceCommit,
+ sourceTreeSha256: "c".repeat(64),
+ localImageTag: `counterlab-runner:git-${sourceCommit}`,
+ localImageDigest: `sha256:${"e".repeat(64)}`,
+ }),
sourceIsAncestor: true,
changedPaths: ["scientific-engines/snapshot.json"],
registryImage: `registry.cloudflare.com/account-1/counterlab-runner:git-${sourceCommit}`,
@@ -177,10 +247,24 @@ describe("qualified runner release schema", () => {
},
"2026-07-16T16:30:00.000Z",
);
+ const {
+ generationIsolationEvidence: _generationIsolationEvidence,
+ generationIsolationEvidenceSha256: _generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: _generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: _generationIsolationVerifiedAt,
+ ...legacyV5
+ } = current;
+ expect(
+ QualifiedRunnerReleaseV5Schema.parse({
+ ...legacyV5,
+ schemaVersion: "5",
+ verifierVersion: "counterlab-release-v5",
+ }),
+ ).toMatchObject({ schemaVersion: "5" });
const {
generationFilesystemReadIsolation: _generationFilesystemReadIsolation,
...legacyV4
- } = current;
+ } = legacyV5;
expect(
QualifiedRunnerReleaseV4Schema.parse({
@@ -307,11 +391,14 @@ describe("deployment receipt schema", () => {
const runner = "b".repeat(40);
const digest = `sha256:${"c".repeat(64)}`;
return {
- schemaVersion: "5",
+ schemaVersion: "6",
status: "DEPLOYED",
workerName: "counterlab",
productionOrigin: "https://counterlab.cserules.workers.dev",
generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationEvidenceSha256: "0".repeat(64),
+ generationIsolationProbeSha256: "1".repeat(64),
+ generationIsolationVerifiedAt: "2026-07-19T00:01:00.000Z",
workerEvidenceCommit: worker,
runnerSourceCommit: runner,
qualifiedRunnerReceiptSha256: "1".repeat(64),
@@ -348,13 +435,13 @@ describe("deployment receipt schema", () => {
workerVersionSha256: "b".repeat(64),
containerStatusSha256: "c".repeat(64),
deployedAt: "2026-07-19T00:03:00.000Z",
- verifierVersion: "counterlab-deployment-v5",
+ verifierVersion: "counterlab-deployment-v6",
} as const;
}
it("binds the deployed Worker, Container, release checks, and exact artifacts", () => {
expect(DeploymentReceiptSchema.parse(receipt())).toMatchObject({
- schemaVersion: "5",
+ schemaVersion: "6",
status: "DEPLOYED",
workerName: "counterlab",
});
@@ -378,10 +465,23 @@ describe("deployment receipt schema", () => {
).toThrow();
});
- it("retains the historical v4 PARTIAL isolation contract", () => {
+ it("retains the historical v5 and v4 isolation contracts", () => {
+ const {
+ generationIsolationEvidenceSha256: _generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: _generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: _generationIsolationVerifiedAt,
+ ...legacyV5
+ } = receipt();
+ expect(
+ DeploymentReceiptV5Schema.parse({
+ ...legacyV5,
+ schemaVersion: "5",
+ verifierVersion: "counterlab-deployment-v5",
+ }),
+ ).toMatchObject({ schemaVersion: "5" });
expect(
DeploymentReceiptV4Schema.parse({
- ...receipt(),
+ ...legacyV5,
schemaVersion: "4",
generationFilesystemReadIsolation: "PARTIAL",
verifierVersion: "counterlab-deployment-v4",
@@ -409,6 +509,9 @@ describe("deployment receipt schema", () => {
it("retains strict parsing for historical v3 deployment receipts", () => {
const {
+ generationIsolationEvidenceSha256: _generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: _generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: _generationIsolationVerifiedAt,
timeoutCleanupReceiptSha256: _timeoutCleanupReceiptSha256,
aggregateLimitEvidenceSha256: _aggregateLimitEvidenceSha256,
runtimePolicySha256: _runtimePolicySha256,
diff --git a/scripts/create-deployment-receipt.test.ts b/scripts/create-deployment-receipt.test.ts
index f3779437..07ca88cd 100644
--- a/scripts/create-deployment-receipt.test.ts
+++ b/scripts/create-deployment-receipt.test.ts
@@ -97,6 +97,8 @@ describe("deployment receipt Worker identity", () => {
workerEvidenceCommit: "a".repeat(40),
runnerSourceCommit: "b".repeat(40),
runnerImageDigest: `sha256:${"c".repeat(64)}`,
+ generationIsolationEvidenceSha256: "5".repeat(64),
+ generationIsolationProbeSha256: "6".repeat(64),
timeoutCleanupReceiptSha256: "d".repeat(64),
aggregateLimitEvidenceSha256: "9".repeat(64),
runtimePolicySha256: "e".repeat(64),
@@ -115,6 +117,14 @@ describe("deployment receipt Worker identity", () => {
["COUNTERLAB_WORKER_EVIDENCE_COMMIT", expected.workerEvidenceCommit],
["COUNTERLAB_RUNNER_SOURCE_COMMIT", expected.runnerSourceCommit],
["COUNTERLAB_RUNNER_IMAGE_DIGEST", expected.runnerImageDigest],
+ [
+ "COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256",
+ expected.generationIsolationEvidenceSha256,
+ ],
+ [
+ "COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256",
+ expected.generationIsolationProbeSha256,
+ ],
[
"COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256",
expected.timeoutCleanupReceiptSha256,
@@ -159,6 +169,8 @@ describe("deployment receipt Worker identity", () => {
for (const name of [
"COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256",
+ "COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256",
+ "COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256",
"COUNTERLAB_RUNTIME_POLICY_SHA256",
"COUNTERLAB_PROOF_DEPENDENCY_MANIFEST_SHA256",
"COUNTERLAB_WORKER_ARTIFACT_MANIFEST_SHA256",
@@ -208,6 +220,9 @@ describe("deployment receipt qualification binding", () => {
proofDependencyManifestSha256: "3".repeat(64),
runtimeAdapterSha256: "4".repeat(64),
aggregateLimitEvidenceSha256: "5".repeat(64),
+ generationIsolationEvidenceSha256: "6".repeat(64),
+ generationIsolationProbeSha256: "7".repeat(64),
+ generationIsolationVerifiedAt: "2026-07-19T00:00:30.000Z",
} as const;
const releaseCheck = {
generationFilesystemReadIsolation:
@@ -228,6 +243,10 @@ describe("deployment receipt qualification binding", () => {
proofDependencyManifestSha256: qualified.proofDependencyManifestSha256,
runtimeAdapterSha256: qualified.runtimeAdapterSha256,
aggregateLimitEvidenceSha256: qualified.aggregateLimitEvidenceSha256,
+ generationIsolationEvidenceSha256:
+ qualified.generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: qualified.generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: qualified.generationIsolationVerifiedAt,
} as const;
const common = {
qualified,
@@ -247,6 +266,9 @@ describe("deployment receipt qualification binding", () => {
["aggregateLimitEvidenceSha256", /aggregate limit evidence/u],
["runtimePolicySha256", /runtime policy/u],
["proofDependencyManifestSha256", /proof dependency manifest/u],
+ ["generationIsolationEvidenceSha256", /generation isolation evidence/u],
+ ["generationIsolationProbeSha256", /generation isolation probe/u],
+ ["generationIsolationVerifiedAt", /generation isolation verification/u],
] as const) {
expect(() =>
assertDeploymentReceiptBindings({
diff --git a/scripts/create-deployment-receipt.ts b/scripts/create-deployment-receipt.ts
index 615591df..d1281e4f 100644
--- a/scripts/create-deployment-receipt.ts
+++ b/scripts/create-deployment-receipt.ts
@@ -5,14 +5,14 @@ import { dirname, isAbsolute, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
- DeploymentReceiptSchema,
- QualifiedRunnerReleaseSchema,
- ReleaseCheckReceiptSchema,
+ DeploymentReceiptV6Schema,
+ ReleaseCheckReceiptV4Schema,
} from "../packages/scientific-engine-registry/src/index.js";
import type {
- QualifiedRunnerRelease,
- ReleaseCheckReceipt,
+ QualifiedRunnerReleaseV6,
+ ReleaseCheckReceiptV4,
} from "../packages/scientific-engine-registry/src/index.js";
+import { parseQualifiedRunnerReleaseV6 } from "./generation-isolation-evidence.js";
import {
collectFrozenClientAssets,
verifyFrozenWorkerReleaseManifest,
@@ -222,6 +222,8 @@ export function assertActiveWorkerReleaseBindings(
workerEvidenceCommit: string;
runnerSourceCommit: string;
runnerImageDigest: string;
+ generationIsolationEvidenceSha256: string;
+ generationIsolationProbeSha256: string;
timeoutCleanupReceiptSha256: string;
aggregateLimitEvidenceSha256: string;
runtimePolicySha256: string;
@@ -244,6 +246,10 @@ export function assertActiveWorkerReleaseBindings(
COUNTERLAB_WORKER_EVIDENCE_COMMIT: expected.workerEvidenceCommit,
COUNTERLAB_RUNNER_SOURCE_COMMIT: expected.runnerSourceCommit,
COUNTERLAB_RUNNER_IMAGE_DIGEST: expected.runnerImageDigest,
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256:
+ expected.generationIsolationEvidenceSha256,
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256:
+ expected.generationIsolationProbeSha256,
COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256:
expected.timeoutCleanupReceiptSha256,
COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256:
@@ -277,7 +283,7 @@ export function assertActiveWorkerReleaseBindings(
}
type QualifiedDeploymentIdentity = Pick<
- QualifiedRunnerRelease,
+ QualifiedRunnerReleaseV6,
| "evidenceCommit"
| "sourceCommit"
| "registryDigest"
@@ -291,10 +297,13 @@ type QualifiedDeploymentIdentity = Pick<
| "proofDependencyManifestSha256"
| "aggregateLimitEvidenceSha256"
| "runtimeAdapterSha256"
+ | "generationIsolationEvidenceSha256"
+ | "generationIsolationProbeSha256"
+ | "generationIsolationVerifiedAt"
>;
type ReleaseCheckDeploymentIdentity = Pick<
- ReleaseCheckReceipt,
+ ReleaseCheckReceiptV4,
| "evidenceCommit"
| "sourceCommit"
| "qualifiedRunnerReceiptSha256"
@@ -309,6 +318,9 @@ type ReleaseCheckDeploymentIdentity = Pick<
| "proofDependencyManifestSha256"
| "aggregateLimitEvidenceSha256"
| "runtimeAdapterSha256"
+ | "generationIsolationEvidenceSha256"
+ | "generationIsolationProbeSha256"
+ | "generationIsolationVerifiedAt"
>;
export function assertDeploymentReceiptBindings(input: {
@@ -355,6 +367,21 @@ export function assertDeploymentReceiptBindings(input: {
input.releaseCheck.qualifiedAt,
input.qualified.qualifiedAt,
],
+ [
+ "generation isolation evidence",
+ input.releaseCheck.generationIsolationEvidenceSha256,
+ input.qualified.generationIsolationEvidenceSha256,
+ ],
+ [
+ "generation isolation probe",
+ input.releaseCheck.generationIsolationProbeSha256,
+ input.qualified.generationIsolationProbeSha256,
+ ],
+ [
+ "generation isolation verification time",
+ input.releaseCheck.generationIsolationVerifiedAt,
+ input.qualified.generationIsolationVerifiedAt,
+ ],
[
"runner image tag",
input.releaseCheck.runnerImageTag,
@@ -474,13 +501,16 @@ async function main(): Promise {
}
const bindings = version.resources?.bindings;
const qualifiedBytes = readFileSync(paths.qualified);
- const qualified = QualifiedRunnerReleaseSchema.parse(
+ const qualified = parseQualifiedRunnerReleaseV6(
JSON.parse(qualifiedBytes.toString("utf8")) as unknown,
);
assertActiveWorkerReleaseBindings(bindings, {
workerEvidenceCommit: args["--evidence-commit"],
runnerSourceCommit: args["--source-commit"],
runnerImageDigest: args["--registry-digest"],
+ generationIsolationEvidenceSha256:
+ qualified.generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: qualified.generationIsolationProbeSha256,
timeoutCleanupReceiptSha256: qualified.timeoutCleanupReceiptSha256,
aggregateLimitEvidenceSha256: qualified.aggregateLimitEvidenceSha256,
runtimePolicySha256: qualified.runtimePolicySha256,
@@ -507,7 +537,7 @@ async function main(): Promise {
}
const releaseCheckBytes = readFileSync(paths.releaseCheck);
- const releaseCheck = ReleaseCheckReceiptSchema.parse(
+ const releaseCheck = ReleaseCheckReceiptV4Schema.parse(
JSON.parse(releaseCheckBytes.toString("utf8")) as unknown,
);
assertDeploymentReceiptBindings({
@@ -558,13 +588,17 @@ async function main(): Promise {
}
const dryRun = dryRunWorkerHash(root, paths.dryRun, workerBundleSha256);
const configBytes = readFileSync(paths.config);
- const receipt = DeploymentReceiptSchema.parse({
- schemaVersion: "5",
+ const receipt = DeploymentReceiptV6Schema.parse({
+ schemaVersion: "6",
status: "DEPLOYED",
workerName: "counterlab",
productionOrigin: "https://counterlab.cserules.workers.dev",
generationFilesystemReadIsolation:
releaseCheck.generationFilesystemReadIsolation,
+ generationIsolationEvidenceSha256:
+ releaseCheck.generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: releaseCheck.generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: releaseCheck.generationIsolationVerifiedAt,
workerEvidenceCommit: args["--evidence-commit"],
runnerSourceCommit: args["--source-commit"],
qualifiedRunnerReceiptSha256: sha256(qualifiedBytes),
@@ -601,7 +635,7 @@ async function main(): Promise {
workerVersionSha256: sha256(versionBytes),
containerStatusSha256: sha256(containersBytes),
deployedAt: new Date().toISOString(),
- verifierVersion: "counterlab-deployment-v5",
+ verifierVersion: "counterlab-deployment-v6",
});
await writeFile(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`, {
encoding: "utf8",
diff --git a/scripts/deploy-qualified.sh b/scripts/deploy-qualified.sh
index 51a0a12a..710e9227 100755
--- a/scripts/deploy-qualified.sh
+++ b/scripts/deploy-qualified.sh
@@ -189,11 +189,14 @@ process.stdout.write(identity.receipt[field]);
EVIDENCE_COMMIT="$(qualified_identity_value evidenceCommit)"
SOURCE_COMMIT="$(qualified_identity_value sourceCommit)"
LOCAL_IMAGE="$(qualified_identity_value localImageTag)"
+LOCAL_IMAGE_DIGEST="$(qualified_identity_value localImageDigest)"
REGISTRY_DIGEST="$(qualified_identity_value registryDigest)"
TIMEOUT_CLEANUP_RECEIPT_SHA256="$(qualified_identity_value timeoutCleanupReceiptSha256)"
AGGREGATE_LIMIT_EVIDENCE_SHA256="$(qualified_identity_value aggregateLimitEvidenceSha256)"
RUNTIME_POLICY_SHA256="$(qualified_identity_value runtimePolicySha256)"
PROOF_DEPENDENCY_MANIFEST_SHA256="$(qualified_identity_value proofDependencyManifestSha256)"
+GENERATION_ISOLATION_EVIDENCE_SHA256="$(qualified_identity_value generationIsolationEvidenceSha256)"
+GENERATION_ISOLATION_PROBE_SHA256="$(qualified_identity_value generationIsolationProbeSha256)"
QUALIFIED_RECEIPT_SHA256="$(
node -e '
const identity = JSON.parse(process.argv[1]);
@@ -228,7 +231,9 @@ CLIENT_DIR="$(repo_path "apps/web/dist/client")"
WORKER_ARTIFACT_MANIFEST="${RELEASE_DIR}/frozen-worker-release.json"
mkdir -p "${RELEASE_DIR}"
-./scripts/verify-scientific-engines.sh --image "${LOCAL_IMAGE}"
+./scripts/verify-scientific-engines.sh \
+ --image "${LOCAL_IMAGE}" \
+ --expected-image-digest "${LOCAL_IMAGE_DIGEST}"
"${PNPM}" --filter @counterlab/web build
[[ -f "apps/web/dist/client/_headers" ]] || {
@@ -409,9 +414,9 @@ wait_for_maintenance_health() {
if "${CURL_BIN}" --silent --show-error --fail-with-body \
--connect-timeout 10 --max-time 20 \
--output "${candidate}" "${PRODUCTION_ORIGIN}/api/health" &&
- node - "${candidate}" "${EVIDENCE_COMMIT}" "${SOURCE_COMMIT}" "${REGISTRY_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${expected_version}" <<'NODE'
+ node - "${candidate}" "${EVIDENCE_COMMIT}" "${SOURCE_COMMIT}" "${REGISTRY_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${expected_version}" "${GENERATION_ISOLATION_EVIDENCE_SHA256}" "${GENERATION_ISOLATION_PROBE_SHA256}" <<'NODE'
const fs = require("node:fs");
-const [path, evidenceCommit, sourceCommit, digest, timeoutReceipt, aggregateEvidence, runtimePolicy, proofManifest, artifactClassification, artifactManifest, workerBundle, clientAssets, clientAssetCount, publicAssets, publicAssetCount, viteVersion, wranglerVersion, expectedVersion] = process.argv.slice(2);
+const [path, evidenceCommit, sourceCommit, digest, timeoutReceipt, aggregateEvidence, runtimePolicy, proofManifest, artifactClassification, artifactManifest, workerBundle, clientAssets, clientAssetCount, publicAssets, publicAssetCount, viteVersion, wranglerVersion, expectedVersion, isolationEvidence, isolationProbe] = process.argv.slice(2);
const payload = JSON.parse(fs.readFileSync(path, "utf8"));
const data = payload?.ok === true ? payload.data : undefined;
if (
@@ -421,6 +426,8 @@ if (
data.release.workerEvidenceCommit !== evidenceCommit ||
data.release.runnerSourceCommit !== sourceCommit ||
data.release.runnerImageDigest !== digest ||
+ data.release.generationIsolationEvidenceSha256 !== isolationEvidence ||
+ data.release.generationIsolationProbeSha256 !== isolationProbe ||
data.release.timeoutCleanupReceiptSha256 !== timeoutReceipt ||
data.release.aggregateLimitEvidenceSha256 !== aggregateEvidence ||
data.release.runtimePolicySha256 !== runtimePolicy ||
@@ -459,9 +466,9 @@ wait_for_final_readiness() {
if "${CURL_BIN}" --silent --show-error --fail-with-body \
--connect-timeout 10 --max-time 20 \
--output "${candidate}" "${PRODUCTION_ORIGIN}/ready" &&
- node - "${candidate}" "${EVIDENCE_COMMIT}" "${SOURCE_COMMIT}" "${REGISTRY_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${expected_version}" <<'NODE'
+ node - "${candidate}" "${EVIDENCE_COMMIT}" "${SOURCE_COMMIT}" "${REGISTRY_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${expected_version}" "${GENERATION_ISOLATION_EVIDENCE_SHA256}" "${GENERATION_ISOLATION_PROBE_SHA256}" <<'NODE'
const fs = require("node:fs");
-const [path, evidenceCommit, sourceCommit, digest, timeoutReceipt, aggregateEvidence, runtimePolicy, proofManifest, artifactClassification, artifactManifest, workerBundle, clientAssets, clientAssetCount, publicAssets, publicAssetCount, viteVersion, wranglerVersion, expectedVersion] = process.argv.slice(2);
+const [path, evidenceCommit, sourceCommit, digest, timeoutReceipt, aggregateEvidence, runtimePolicy, proofManifest, artifactClassification, artifactManifest, workerBundle, clientAssets, clientAssetCount, publicAssets, publicAssetCount, viteVersion, wranglerVersion, expectedVersion, isolationEvidence, isolationProbe] = process.argv.slice(2);
const payload = JSON.parse(fs.readFileSync(path, "utf8"));
if (
payload?.status !== "ready" ||
@@ -471,6 +478,8 @@ if (
payload.release.workerEvidenceCommit !== evidenceCommit ||
payload.release.runnerSourceCommit !== sourceCommit ||
payload.release.runnerImageDigest !== digest ||
+ payload.release.generationIsolationEvidenceSha256 !== isolationEvidence ||
+ payload.release.generationIsolationProbeSha256 !== isolationProbe ||
payload.release.timeoutCleanupReceiptSha256 !== timeoutReceipt ||
payload.release.aggregateLimitEvidenceSha256 !== aggregateEvidence ||
payload.release.runtimePolicySha256 !== runtimePolicy ||
diff --git a/scripts/prepare-qualified-deploy.ts b/scripts/prepare-qualified-deploy.ts
index 9c7be94a..9a22e277 100644
--- a/scripts/prepare-qualified-deploy.ts
+++ b/scripts/prepare-qualified-deploy.ts
@@ -9,9 +9,13 @@ import { z } from "zod";
import {
ContainedRuntimeAttestationSchema,
- QualifiedRunnerReleaseSchema,
+ type GenerationIsolationEvidenceV1,
} from "../packages/scientific-engine-registry/src/index.js";
import { canonicalJson } from "../packages/session-core/src/index.js";
+import {
+ parseQualifiedRunnerReleaseV6,
+ verifyGenerationIsolationEvidence,
+} from "./generation-isolation-evidence.js";
import { assertReleaseCheckBinding } from "./release-check-receipt.js";
import {
containedRuntimeAdapterArguments,
@@ -69,6 +73,10 @@ export type RunnerReleaseObservation = {
export type QualifiedReleaseObservation = RunnerReleaseObservation & {
generationFilesystemReadIsolation: "OS_ENFORCED";
+ generationIsolationEvidence: GenerationIsolationEvidenceV1;
+ generationIsolationEvidenceSha256: string;
+ generationIsolationProbeSha256: string;
+ generationIsolationVerifiedAt: string;
limitMode: "container-cgroup-and-process-rlimit";
aggregateLimitIntentEnforced: true;
aggregateLimitEvidenceSha256: string;
@@ -267,6 +275,18 @@ function assertQualifiedObservation(
"qualified source commit is not an ancestor of the evidence commit",
);
}
+ verifyGenerationIsolationEvidence({
+ evidence: observation.generationIsolationEvidence,
+ evidenceSha256: observation.generationIsolationEvidenceSha256,
+ expected: {
+ sourceCommit: observation.sourceCommit,
+ sourceTreeSha256: observation.sourceTreeSha256,
+ localImageTag: observation.localImageTag,
+ localImageDigest: observation.localImageDigest,
+ probeSha256: observation.generationIsolationProbeSha256,
+ verifiedAt: observation.generationIsolationVerifiedAt,
+ },
+ });
assertEvidenceOnlyReleaseDelta(observation.changedPaths);
}
@@ -607,7 +627,7 @@ export async function collectQualifiedReleaseObservation(input: {
root: string;
receipt: unknown;
}): Promise {
- const receipt = QualifiedRunnerReleaseSchema.parse(input.receipt);
+ const receipt = parseQualifiedRunnerReleaseV6(input.receipt);
const runtimeAdapter = process.env.COUNTERLAB_DOCKER_BIN;
if (runtimeAdapter === undefined || runtimeAdapter.trim().length === 0) {
throw new Error(
@@ -651,6 +671,11 @@ export async function collectQualifiedReleaseObservation(input: {
...timeout,
generationFilesystemReadIsolation:
receipt.generationFilesystemReadIsolation,
+ generationIsolationEvidence: receipt.generationIsolationEvidence,
+ generationIsolationEvidenceSha256:
+ receipt.generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: receipt.generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: receipt.generationIsolationVerifiedAt,
};
}
@@ -659,11 +684,16 @@ export function createQualifiedRunnerRelease(
qualifiedAt = new Date().toISOString(),
): unknown {
assertQualifiedObservation(observation);
- return QualifiedRunnerReleaseSchema.parse({
- schemaVersion: "5",
+ return parseQualifiedRunnerReleaseV6({
+ schemaVersion: "6",
status: "VERIFIED",
generationFilesystemReadIsolation:
observation.generationFilesystemReadIsolation,
+ generationIsolationEvidence: observation.generationIsolationEvidence,
+ generationIsolationEvidenceSha256:
+ observation.generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: observation.generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: observation.generationIsolationVerifiedAt,
sourceCommit: observation.sourceCommit,
sourceArchiveSha256: observation.sourceArchiveSha256,
sourceTreeSha256: observation.sourceTreeSha256,
@@ -704,7 +734,7 @@ export function createQualifiedRunnerRelease(
registryDigest: observation.registryDigest,
registryResolvedAt: observation.registryResolvedAt,
qualifiedAt,
- verifierVersion: "counterlab-release-v5",
+ verifierVersion: "counterlab-release-v6",
});
}
@@ -1083,7 +1113,7 @@ export function qualifiedDeployConfig(input: {
observation: QualifiedReleaseObservation;
}): Record {
const config = canonicalReleaseConfig(input.config);
- const receipt = QualifiedRunnerReleaseSchema.parse(input.receipt);
+ const receipt = parseQualifiedRunnerReleaseV6(input.receipt);
const comparisons: Array<[string, string, string]> = [
["source commit", receipt.sourceCommit, input.observation.sourceCommit],
[
@@ -1231,6 +1261,21 @@ export function qualifiedDeployConfig(input: {
receipt.timeoutVerifiedAt,
input.observation.timeoutVerifiedAt,
],
+ [
+ "generation isolation evidence",
+ receipt.generationIsolationEvidenceSha256,
+ input.observation.generationIsolationEvidenceSha256,
+ ],
+ [
+ "generation isolation probe",
+ receipt.generationIsolationProbeSha256,
+ input.observation.generationIsolationProbeSha256,
+ ],
+ [
+ "generation isolation verification time",
+ receipt.generationIsolationVerifiedAt,
+ input.observation.generationIsolationVerifiedAt,
+ ],
["registry image", receipt.registryImage, input.observation.registryImage],
[
"registry digest",
@@ -1338,6 +1383,10 @@ export function qualifiedDeployConfig(input: {
COUNTERLAB_WORKER_EVIDENCE_COMMIT: receipt.evidenceCommit,
COUNTERLAB_RUNNER_SOURCE_COMMIT: receipt.sourceCommit,
COUNTERLAB_RUNNER_IMAGE_DIGEST: receipt.registryDigest,
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256:
+ receipt.generationIsolationEvidenceSha256,
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256:
+ receipt.generationIsolationProbeSha256,
COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256:
receipt.timeoutCleanupReceiptSha256,
COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256:
diff --git a/scripts/production-smoke.sh b/scripts/production-smoke.sh
index 05fa148b..32496fbc 100755
--- a/scripts/production-smoke.sh
+++ b/scripts/production-smoke.sh
@@ -98,7 +98,7 @@ esac
if [[ -z "${COUNTERLAB_DEPLOYMENT_RECEIPT:-}" ]]; then
echo "Production smoke requires COUNTERLAB_DEPLOYMENT_RECEIPT." >&2
- echo "Use the schema-v4 receipt written by scripts/deploy-qualified.sh." >&2
+ echo "Use the schema-v6 receipt written by scripts/deploy-qualified.sh." >&2
exit 2
fi
DEPLOYMENT_RECEIPT="$(repo_path "${COUNTERLAB_DEPLOYMENT_RECEIPT}")"
@@ -156,6 +156,9 @@ TIMEOUT_CLEANUP_RECEIPT_SHA256="$(deployment_identity_value timeoutCleanupReceip
AGGREGATE_LIMIT_EVIDENCE_SHA256="$(deployment_identity_value aggregateLimitEvidenceSha256)"
RUNTIME_POLICY_SHA256="$(deployment_identity_value runtimePolicySha256)"
PROOF_DEPENDENCY_MANIFEST_SHA256="$(deployment_identity_value proofDependencyManifestSha256)"
+GENERATION_ISOLATION_EVIDENCE_SHA256="$(deployment_identity_value generationIsolationEvidenceSha256)"
+GENERATION_ISOLATION_PROBE_SHA256="$(deployment_identity_value generationIsolationProbeSha256)"
+GENERATION_ISOLATION_VERIFIED_AT="$(deployment_identity_value generationIsolationVerifiedAt)"
WORKER_ARTIFACT_CLASSIFICATION="$(deployment_identity_value workerArtifactClassification)"
WORKER_ARTIFACT_MANIFEST_SHA256="$(deployment_identity_value workerArtifactManifestSha256)"
WORKER_BUNDLE_SHA256="$(deployment_identity_value workerBundleSha256)"
@@ -401,6 +404,9 @@ init_args+=(--timeout-cleanup-receipt-sha256 "${TIMEOUT_CLEANUP_RECEIPT_SHA256}"
init_args+=(--aggregate-limit-evidence-sha256 "${AGGREGATE_LIMIT_EVIDENCE_SHA256}")
init_args+=(--runtime-policy-sha256 "${RUNTIME_POLICY_SHA256}")
init_args+=(--proof-dependency-manifest-sha256 "${PROOF_DEPENDENCY_MANIFEST_SHA256}")
+init_args+=(--generation-isolation-evidence-sha256 "${GENERATION_ISOLATION_EVIDENCE_SHA256}")
+init_args+=(--generation-isolation-probe-sha256 "${GENERATION_ISOLATION_PROBE_SHA256}")
+init_args+=(--generation-isolation-verified-at "${GENERATION_ISOLATION_VERIFIED_AT}")
init_args+=(--worker-artifact-classification "${WORKER_ARTIFACT_CLASSIFICATION}")
init_args+=(--worker-artifact-manifest-sha256 "${WORKER_ARTIFACT_MANIFEST_SHA256}")
init_args+=(--worker-bundle-sha256 "${WORKER_BUNDLE_SHA256}")
@@ -423,7 +429,7 @@ stage_started="$(timestamp)"
--max-time 30 \
"${BASE_URL}/ready" \
>"${WORK_DIR}/ready.json"
-python3 - "${WORK_DIR}/ready.json" "${WORKER_VERSION_ID}" "${WORKER_EVIDENCE_COMMIT}" "${RUNNER_SOURCE_COMMIT}" "${CONTAINER_IMAGE_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" <<'PY'
+python3 - "${WORK_DIR}/ready.json" "${WORKER_VERSION_ID}" "${WORKER_EVIDENCE_COMMIT}" "${RUNNER_SOURCE_COMMIT}" "${CONTAINER_IMAGE_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${GENERATION_ISOLATION_EVIDENCE_SHA256}" "${GENERATION_ISOLATION_PROBE_SHA256}" <<'PY'
import json
import pathlib
import sys
@@ -454,6 +460,8 @@ expected_release = {
"workerEvidenceCommit": sys.argv[3],
"runnerSourceCommit": sys.argv[4],
"runnerImageDigest": sys.argv[5],
+ "generationIsolationEvidenceSha256": sys.argv[19],
+ "generationIsolationProbeSha256": sys.argv[20],
"timeoutCleanupReceiptSha256": sys.argv[6],
"aggregateLimitEvidenceSha256": sys.argv[7],
"runtimePolicySha256": sys.argv[8],
@@ -483,7 +491,7 @@ stage_started="$(timestamp)"
--max-time 30 \
"${BASE_URL}/api/health?readiness=probe" \
>"${WORK_DIR}/health.json"
-python3 - "${WORK_DIR}/health.json" "${WORKER_VERSION_ID}" "${WORKER_EVIDENCE_COMMIT}" "${RUNNER_SOURCE_COMMIT}" "${CONTAINER_IMAGE_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" <<'PY'
+python3 - "${WORK_DIR}/health.json" "${WORKER_VERSION_ID}" "${WORKER_EVIDENCE_COMMIT}" "${RUNNER_SOURCE_COMMIT}" "${CONTAINER_IMAGE_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${GENERATION_ISOLATION_EVIDENCE_SHA256}" "${GENERATION_ISOLATION_PROBE_SHA256}" <<'PY'
import json
import pathlib
import sys
@@ -518,6 +526,8 @@ expected_release = {
"workerEvidenceCommit": sys.argv[3],
"runnerSourceCommit": sys.argv[4],
"runnerImageDigest": sys.argv[5],
+ "generationIsolationEvidenceSha256": sys.argv[19],
+ "generationIsolationProbeSha256": sys.argv[20],
"timeoutCleanupReceiptSha256": sys.argv[6],
"aggregateLimitEvidenceSha256": sys.argv[7],
"runtimePolicySha256": sys.argv[8],
diff --git a/scripts/production_smoke_report.py b/scripts/production_smoke_report.py
index 893f088e..c9c45142 100644
--- a/scripts/production_smoke_report.py
+++ b/scripts/production_smoke_report.py
@@ -167,7 +167,7 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"privacy",
}:
raise ValueError("smoke report has unknown or missing fields")
- if report["schemaVersion"] not in {"2", "3", "4"} or report[
+ if report["schemaVersion"] not in {"2", "3", "4", "5"} or report[
"status"
] not in _REPORT_STATUSES:
raise ValueError("smoke report schema or status is invalid")
@@ -186,7 +186,7 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"containerImageDigest",
"deploymentReceiptSha256",
}
- if report["schemaVersion"] in {"3", "4"}:
+ if report["schemaVersion"] in {"3", "4", "5"}:
deployment_fields.update(
{
"timeoutCleanupReceiptSha256",
@@ -194,7 +194,7 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"proofDependencyManifestSha256",
}
)
- if report["schemaVersion"] == "4":
+ if report["schemaVersion"] in {"4", "5"}:
deployment_fields.update(
{
"aggregateLimitEvidenceSha256",
@@ -209,6 +209,14 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"wranglerVersion",
}
)
+ if report["schemaVersion"] == "5":
+ deployment_fields.update(
+ {
+ "generationIsolationEvidenceSha256",
+ "generationIsolationProbeSha256",
+ "generationIsolationVerifiedAt",
+ }
+ )
if not isinstance(deployment, dict) or set(deployment) != deployment_fields:
raise ValueError("smoke deployment metadata is invalid")
worker_version = deployment["workerVersion"]
@@ -222,7 +230,7 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
if commit is not None and not _COMMIT.fullmatch(commit):
raise ValueError(f"{field} is invalid")
hash_fields = ["deploymentReceiptSha256"]
- if report["schemaVersion"] in {"3", "4"}:
+ if report["schemaVersion"] in {"3", "4", "5"}:
hash_fields.extend(
[
"timeoutCleanupReceiptSha256",
@@ -230,7 +238,7 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"proofDependencyManifestSha256",
]
)
- if report["schemaVersion"] == "4":
+ if report["schemaVersion"] in {"4", "5"}:
hash_fields.extend(
[
"aggregateLimitEvidenceSha256",
@@ -240,11 +248,18 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"clientPublicAssetsSha256",
]
)
+ if report["schemaVersion"] == "5":
+ hash_fields.extend(
+ [
+ "generationIsolationEvidenceSha256",
+ "generationIsolationProbeSha256",
+ ]
+ )
for field in hash_fields:
value = deployment[field]
if value is not None and not _SHA256.fullmatch(value):
raise ValueError(f"{field} is invalid")
- if report["schemaVersion"] == "4":
+ if report["schemaVersion"] in {"4", "5"}:
artifact_values = [
deployment[field]
for field in (
@@ -283,6 +298,13 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
or public_count > client_count
):
raise ValueError("client asset counts are invalid")
+ if report["schemaVersion"] == "5":
+ isolation_verified_at = deployment["generationIsolationVerifiedAt"]
+ if isolation_verified_at is not None:
+ _timestamp(
+ isolation_verified_at,
+ "generationIsolationVerifiedAt",
+ )
if report["privacy"] != {
"containsSecrets": False,
"containsRawNotebookBytes": False,
@@ -359,6 +381,9 @@ def initialize_report(
aggregate_limit_evidence_sha256: str | None = None,
runtime_policy_sha256: str | None = None,
proof_dependency_manifest_sha256: str | None = None,
+ generation_isolation_evidence_sha256: str | None = None,
+ generation_isolation_probe_sha256: str | None = None,
+ generation_isolation_verified_at: str | None = None,
worker_artifact_classification: str | None = None,
worker_artifact_manifest_sha256: str | None = None,
worker_bundle_sha256: str | None = None,
@@ -372,7 +397,7 @@ def initialize_report(
) -> dict[str, Any]:
report = validate_report(
{
- "schemaVersion": "4",
+ "schemaVersion": "5",
"status": "RUNNING",
"baseUrl": _base_url(base_url),
"startedAt": _timestamp(started_at, "startedAt"),
@@ -390,6 +415,13 @@ def initialize_report(
"proofDependencyManifestSha256": (
proof_dependency_manifest_sha256
),
+ "generationIsolationEvidenceSha256": (
+ generation_isolation_evidence_sha256
+ ),
+ "generationIsolationProbeSha256": (
+ generation_isolation_probe_sha256
+ ),
+ "generationIsolationVerifiedAt": generation_isolation_verified_at,
"workerArtifactClassification": worker_artifact_classification,
"workerArtifactManifestSha256": worker_artifact_manifest_sha256,
"workerBundleSha256": worker_bundle_sha256,
@@ -481,6 +513,9 @@ def _parser() -> argparse.ArgumentParser:
initialize.add_argument("--aggregate-limit-evidence-sha256")
initialize.add_argument("--runtime-policy-sha256")
initialize.add_argument("--proof-dependency-manifest-sha256")
+ initialize.add_argument("--generation-isolation-evidence-sha256")
+ initialize.add_argument("--generation-isolation-probe-sha256")
+ initialize.add_argument("--generation-isolation-verified-at")
initialize.add_argument("--worker-artifact-classification")
initialize.add_argument("--worker-artifact-manifest-sha256")
initialize.add_argument("--worker-bundle-sha256")
@@ -522,6 +557,15 @@ def main() -> int:
proof_dependency_manifest_sha256=(
arguments.proof_dependency_manifest_sha256
),
+ generation_isolation_evidence_sha256=(
+ arguments.generation_isolation_evidence_sha256
+ ),
+ generation_isolation_probe_sha256=(
+ arguments.generation_isolation_probe_sha256
+ ),
+ generation_isolation_verified_at=(
+ arguments.generation_isolation_verified_at
+ ),
worker_artifact_classification=(
arguments.worker_artifact_classification
),
diff --git a/scripts/qualify-runner-release.ts b/scripts/qualify-runner-release.ts
index b4d7a1ca..c5b7df18 100644
--- a/scripts/qualify-runner-release.ts
+++ b/scripts/qualify-runner-release.ts
@@ -18,6 +18,7 @@ import {
} from "./contained-runtime-attestation.mjs";
import {
ContainedRuntimeAttestationSchema,
+ GenerationIsolationEvidenceV1Schema,
assertCurrentGrypeReleaseEvidenceBinding,
} from "../packages/scientific-engine-registry/src/index.js";
import {
@@ -25,6 +26,10 @@ import {
createQualifiedRunnerRelease,
} from "./prepare-qualified-deploy.js";
import { validateTimeoutCleanupProof } from "./timeout-cleanup-receipt.js";
+import {
+ hashGenerationIsolationEvidence,
+ verifyGenerationIsolationEvidence,
+} from "./generation-isolation-evidence.js";
import {
SourceBoundBuildReceiptSchema,
type SourceBoundBuildReceipt,
@@ -322,6 +327,23 @@ async function promoteImage(input: {
"registry image tag must match the built runner source commit",
);
}
+ if (
+ commandText(
+ input.root,
+ input.runtimeAdapter,
+ runtimeAdapterArguments([
+ "image",
+ "inspect",
+ input.expectedConfigDigest,
+ "--format",
+ "{{.Id}}",
+ ]),
+ ) !== input.expectedConfigDigest
+ ) {
+ throw new Error(
+ "qualified local image digest is unavailable for promotion",
+ );
+ }
const wrangler = resolve(input.root, "node_modules/.bin/wrangler");
const credentials = RegistryCredentialsSchema.parse(
JSON.parse(
@@ -395,7 +417,11 @@ async function promoteImage(input: {
);
execFileSync(
input.runtimeAdapter,
- runtimeAdapterArguments(["tag", input.localImage, input.registryImage]),
+ runtimeAdapterArguments([
+ "tag",
+ input.expectedConfigDigest,
+ input.registryImage,
+ ]),
{ cwd: input.root, stdio: "inherit", timeout: 30_000 },
);
execFileSync(
@@ -453,6 +479,10 @@ async function main(): Promise {
throw new Error("runner qualification requires the verified Git root");
}
const output = await repositoryOutputPath(root, args.output);
+ const isolationOutput = await repositoryOutputPath(
+ root,
+ `${output}.generation-isolation-${Date.now()}-${process.pid}.json`,
+ );
const buildReceiptPath = await existingRepositoryFile(
root,
args.buildReceipt,
@@ -637,6 +667,10 @@ async function main(): Promise {
"scripts/verify-scientific-engines.sh",
"--image",
buildReceipt.localImageTag,
+ "--expected-image-digest",
+ buildReceipt.localImageDigest,
+ "--generation-isolation-report",
+ isolationOutput,
],
{
cwd: root,
@@ -644,6 +678,22 @@ async function main(): Promise {
stdio: "inherit",
},
);
+ const readGenerationIsolation = async () => {
+ const evidence = GenerationIsolationEvidenceV1Schema.parse(
+ JSON.parse(await readFile(isolationOutput, "utf8")) as unknown,
+ );
+ return verifyGenerationIsolationEvidence({
+ evidence,
+ evidenceSha256: hashGenerationIsolationEvidence(evidence),
+ expected: {
+ sourceCommit: buildReceipt.sourceCommit,
+ sourceTreeSha256: buildReceipt.sourceTreeSha256,
+ localImageTag: buildReceipt.localImageTag,
+ localImageDigest: buildReceipt.localImageDigest,
+ },
+ });
+ };
+ const initialGenerationIsolation = await readGenerationIsolation();
assertCleanWorktree(root);
runtimeAttestation = observeRuntimeAttestation(root, runtimeAdapter);
@@ -733,10 +783,24 @@ async function main(): Promise {
adapterManifestDigest: buildReceipt.adapterManifestDigest,
adapterOciArchiveSha256: buildReceipt.adapterOciArchiveSha256,
});
+ const generationIsolation = await readGenerationIsolation();
+ if (
+ generationIsolation.evidenceSha256 !==
+ initialGenerationIsolation.evidenceSha256 ||
+ generationIsolation.probeSha256 !== initialGenerationIsolation.probeSha256
+ ) {
+ throw new Error(
+ "generation-isolation evidence changed during qualification",
+ );
+ }
const observation = {
...releaseObservation,
...timeoutProof,
generationFilesystemReadIsolation: "OS_ENFORCED" as const,
+ generationIsolationEvidence: generationIsolation.evidence,
+ generationIsolationEvidenceSha256: generationIsolation.evidenceSha256,
+ generationIsolationProbeSha256: generationIsolation.probeSha256,
+ generationIsolationVerifiedAt: generationIsolation.evidence.verifiedAt,
};
for (const [label, built, observed] of [
[
diff --git a/scripts/release-check-receipt.ts b/scripts/release-check-receipt.ts
index 30f5daa3..8ab77291 100644
--- a/scripts/release-check-receipt.ts
+++ b/scripts/release-check-receipt.ts
@@ -7,11 +7,18 @@ import { fileURLToPath } from "node:url";
import {
ContainedRuntimeAttestationSchema,
- DeploymentReceiptSchema,
- QualifiedRunnerReleaseSchema,
+ DeploymentReceiptV5Schema,
+ DeploymentReceiptV6Schema,
+ GenerationIsolationEvidenceV1Schema,
RELEASE_CHECK_IDS,
- ReleaseCheckReceiptSchema,
+ QualifiedRunnerReleaseV5Schema,
+ ReleaseCheckReceiptV4Schema,
} from "../packages/scientific-engine-registry/src/index.js";
+import {
+ hashGenerationIsolationEvidence,
+ parseQualifiedRunnerReleaseV6,
+ verifyGenerationIsolationEvidence,
+} from "./generation-isolation-evidence.js";
import {
containedRuntimeAdapterArguments,
requireContainedRuntimeSessionId,
@@ -34,9 +41,14 @@ function sha256(value: Buffer): string {
}
export function createQualifiedReleaseIdentity(receiptBytes: Buffer) {
- const receipt = QualifiedRunnerReleaseSchema.parse(
- JSON.parse(receiptBytes.toString("utf8")) as unknown,
- );
+ const value = JSON.parse(receiptBytes.toString("utf8")) as unknown;
+ const receipt =
+ typeof value === "object" &&
+ value !== null &&
+ "schemaVersion" in value &&
+ value.schemaVersion === "6"
+ ? parseQualifiedRunnerReleaseV6(value)
+ : QualifiedRunnerReleaseV5Schema.parse(value);
return {
identitySchemaVersion: "1",
receiptType: "qualified-runner-release",
@@ -46,9 +58,14 @@ export function createQualifiedReleaseIdentity(receiptBytes: Buffer) {
}
export function createDeploymentReleaseIdentity(receiptBytes: Buffer) {
- const receipt = DeploymentReceiptSchema.parse(
- JSON.parse(receiptBytes.toString("utf8")) as unknown,
- );
+ const value = JSON.parse(receiptBytes.toString("utf8")) as unknown;
+ const receipt =
+ typeof value === "object" &&
+ value !== null &&
+ "schemaVersion" in value &&
+ value.schemaVersion === "6"
+ ? DeploymentReceiptV6Schema.parse(value)
+ : DeploymentReceiptV5Schema.parse(value);
return {
identitySchemaVersion: "1",
receiptType: "deployment-receipt",
@@ -58,8 +75,8 @@ export function createDeploymentReleaseIdentity(receiptBytes: Buffer) {
}
export function assertReleaseCheckBinding(input: BindingInput) {
- const qualified = QualifiedRunnerReleaseSchema.parse(input.qualifiedReceipt);
- const releaseCheck = ReleaseCheckReceiptSchema.parse(
+ const qualified = parseQualifiedRunnerReleaseV6(input.qualifiedReceipt);
+ const releaseCheck = ReleaseCheckReceiptV4Schema.parse(
input.releaseCheckReceipt,
);
const runtime = ContainedRuntimeAttestationSchema.parse(
@@ -74,6 +91,21 @@ export function assertReleaseCheckBinding(input: BindingInput) {
sha256(input.qualifiedReceiptBytes),
],
["qualification time", releaseCheck.qualifiedAt, qualified.qualifiedAt],
+ [
+ "generation isolation evidence",
+ releaseCheck.generationIsolationEvidenceSha256,
+ qualified.generationIsolationEvidenceSha256,
+ ],
+ [
+ "generation isolation probe",
+ releaseCheck.generationIsolationProbeSha256,
+ qualified.generationIsolationProbeSha256,
+ ],
+ [
+ "generation isolation verification time",
+ releaseCheck.generationIsolationVerifiedAt,
+ qualified.generationIsolationVerifiedAt,
+ ],
["runner image tag", releaseCheck.runnerImageTag, qualified.localImageTag],
[
"runner image digest",
@@ -180,18 +212,47 @@ export function createReleaseCheckReceipt(input: {
worktreeClean: boolean;
runnerImageDigest: string;
adapterImageDigest: string;
+ generationIsolationEvidence: unknown;
checkedAt?: string;
}) {
- const qualified = QualifiedRunnerReleaseSchema.parse(input.qualifiedReceipt);
+ const qualified = parseQualifiedRunnerReleaseV6(input.qualifiedReceipt);
const runtime = ContainedRuntimeAttestationSchema.parse(
input.runtimeAttestation,
);
const checkedAt = input.checkedAt ?? new Date().toISOString();
- const receipt = ReleaseCheckReceiptSchema.parse({
- schemaVersion: "3",
+ const freshEvidence = GenerationIsolationEvidenceV1Schema.parse(
+ input.generationIsolationEvidence,
+ );
+ verifyGenerationIsolationEvidence({
+ evidence: freshEvidence,
+ evidenceSha256: hashGenerationIsolationEvidence(freshEvidence),
+ expected: {
+ sourceCommit: qualified.sourceCommit,
+ sourceTreeSha256: qualified.sourceTreeSha256,
+ localImageTag: qualified.localImageTag,
+ localImageDigest: qualified.localImageDigest,
+ probeSha256: qualified.generationIsolationProbeSha256,
+ },
+ });
+ const freshVerifiedAt = Date.parse(freshEvidence.verifiedAt);
+ const checkedAtMilliseconds = Date.parse(checkedAt);
+ if (
+ !Number.isFinite(freshVerifiedAt) ||
+ !Number.isFinite(checkedAtMilliseconds) ||
+ freshVerifiedAt > checkedAtMilliseconds + 5 * 60_000 ||
+ checkedAtMilliseconds - freshVerifiedAt > 30 * 60_000
+ ) {
+ throw new Error("release-check generation-isolation evidence is not fresh");
+ }
+ const receipt = ReleaseCheckReceiptV4Schema.parse({
+ schemaVersion: "4",
status: "PASSED",
generationFilesystemReadIsolation:
qualified.generationFilesystemReadIsolation,
+ generationIsolationEvidenceSha256:
+ qualified.generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: qualified.generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: qualified.generationIsolationVerifiedAt,
evidenceCommit: qualified.evidenceCommit,
sourceCommit: qualified.sourceCommit,
qualifiedRunnerReceiptSha256: sha256(input.qualifiedReceiptBytes),
@@ -208,7 +269,7 @@ export function createReleaseCheckReceipt(input: {
runtimeAdapterSha256: qualified.runtimeAdapterSha256,
checks: RELEASE_CHECK_IDS.map((id) => ({ id, status: "PASSED" })),
checkedAt,
- verifierVersion: "counterlab-release-check-v3",
+ verifierVersion: "counterlab-release-check-v4",
});
return assertReleaseCheckBinding({
qualifiedReceipt: qualified,
@@ -288,23 +349,29 @@ function argumentsFrom(argv: string[]) {
if (
flag === undefined ||
value === undefined ||
- !["--qualified", "--runtime-adapter", "--output"].includes(flag) ||
+ ![
+ "--qualified",
+ "--runtime-adapter",
+ "--generation-isolation-report",
+ "--output",
+ ].includes(flag) ||
values.has(flag)
) {
throw new Error(
- "Usage: release-check-receipt --qualified FILE --runtime-adapter FILE --output FILE",
+ "Usage: release-check-receipt --qualified FILE --runtime-adapter FILE --generation-isolation-report FILE --output FILE",
);
}
values.set(flag, value);
}
- if (values.size !== 3) {
+ if (values.size !== 4) {
throw new Error(
- "Usage: release-check-receipt --qualified FILE --runtime-adapter FILE --output FILE",
+ "Usage: release-check-receipt --qualified FILE --runtime-adapter FILE --generation-isolation-report FILE --output FILE",
);
}
return {
qualified: values.get("--qualified")!,
runtimeAdapter: values.get("--runtime-adapter")!,
+ generationIsolationReport: values.get("--generation-isolation-report")!,
output: values.get("--output")!,
};
}
@@ -336,16 +403,18 @@ async function main(): Promise {
return;
}
const args = argumentsFrom(process.argv.slice(2));
- const [qualifiedPath, runtimeAdapter, output] = await Promise.all([
- existingRepositoryFile(root, args.qualified),
- existingRepositoryFile(root, args.runtimeAdapter),
- repositoryOutput(root, args.output),
- ]);
+ const [qualifiedPath, runtimeAdapter, generationIsolationReport, output] =
+ await Promise.all([
+ existingRepositoryFile(root, args.qualified),
+ existingRepositoryFile(root, args.runtimeAdapter),
+ existingRepositoryFile(root, args.generationIsolationReport),
+ repositoryOutput(root, args.output),
+ ]);
if (((await stat(runtimeAdapter)).mode & 0o111) === 0) {
throw new Error("release-check runtime adapter is not executable");
}
const qualifiedReceiptBytes = readFileSync(qualifiedPath);
- const qualifiedReceipt = QualifiedRunnerReleaseSchema.parse(
+ const qualifiedReceipt = parseQualifiedRunnerReleaseV6(
JSON.parse(qualifiedReceiptBytes.toString("utf8")) as unknown,
);
const runtimeAttestation = JSON.parse(
@@ -390,6 +459,9 @@ async function main(): Promise {
]).length === 0,
runnerImageDigest,
adapterImageDigest,
+ generationIsolationEvidence: JSON.parse(
+ readFileSync(generationIsolationReport, "utf8"),
+ ) as unknown,
});
await writeFile(output, `${JSON.stringify(receipt, null, 2)}\n`, {
encoding: "utf8",
diff --git a/scripts/release-check.sh b/scripts/release-check.sh
index 86acfa6c..94f68659 100755
--- a/scripts/release-check.sh
+++ b/scripts/release-check.sh
@@ -134,12 +134,18 @@ process.stdout.write(identity.receiptSha256);
RELEASE_RECEIPT_DIR="${CACHE_ROOT}/releases"
RELEASE_RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)-$$"
RELEASE_CHECK_RECEIPT="${COUNTERLAB_RELEASE_CHECK_RECEIPT_OUTPUT:-${RELEASE_RECEIPT_DIR}/release-check-${EVIDENCE_COMMIT}-${RELEASE_RUN_ID}.json}"
+GENERATION_ISOLATION_REPORT="${RELEASE_RECEIPT_DIR}/generation-isolation-release-check-${SOURCE_COMMIT}-${RELEASE_RUN_ID}.json"
+mkdir -p "${RELEASE_RECEIPT_DIR}"
case "${RELEASE_CHECK_RECEIPT}" in
/*) ;;
*) RELEASE_CHECK_RECEIPT="${ROOT_DIR}/${RELEASE_CHECK_RECEIPT#./}" ;;
esac
-node scripts/assert-contained-path.mjs "${RELEASE_RECEIPT_DIR}" "${RELEASE_CHECK_RECEIPT}"
-[[ ! -e "${RELEASE_CHECK_RECEIPT}" && ! -L "${RELEASE_CHECK_RECEIPT}" ]] || {
+node scripts/assert-contained-path.mjs \
+ "${RELEASE_RECEIPT_DIR}" \
+ "${RELEASE_CHECK_RECEIPT}" \
+ "${GENERATION_ISOLATION_REPORT}"
+[[ ! -e "${RELEASE_CHECK_RECEIPT}" && ! -L "${RELEASE_CHECK_RECEIPT}" && \
+ ! -e "${GENERATION_ISOLATION_REPORT}" && ! -L "${GENERATION_ISOLATION_REPORT}" ]] || {
echo "Release-check receipt output already exists; refusing to overwrite it." >&2
exit 2
}
@@ -160,7 +166,10 @@ bash scripts/run-mutations.sh leakage
bash scripts/run-mutations.sh imbalance
"${PNPM}" run held-out:check
COUNTERLAB_SANDBOX_IMAGE="${ADAPTER_IMAGE}" bash scripts/sandbox-smoke.sh
-bash scripts/verify-scientific-engines.sh --image "${ENGINE_IMAGE}"
+bash scripts/verify-scientific-engines.sh \
+ --image "${ENGINE_IMAGE}" \
+ --expected-image-digest "${EXPECTED_IMAGE_DIGEST}" \
+ --generation-isolation-report "${GENERATION_ISOLATION_REPORT}"
"${PNPM}" --filter @counterlab/web build
.venv/bin/python scripts/secret-scan.py \
apps/web/dist/counterlab/index.js \
@@ -176,11 +185,11 @@ if [[ -n "$(git status --porcelain=v1 --untracked-files=all)" ]]; then
exit 2
fi
-mkdir -p "${RELEASE_RECEIPT_DIR}"
node scripts/assert-contained-path.mjs "${RELEASE_RECEIPT_DIR}" "${RELEASE_CHECK_RECEIPT}"
node --import tsx scripts/release-check-receipt.ts \
--qualified "${RECEIPT}" \
--runtime-adapter "${RUNTIME_ADAPTER}" \
+ --generation-isolation-report "${GENERATION_ISOLATION_REPORT}" \
--output "${RELEASE_CHECK_RECEIPT}"
echo "Release checks passed. No repository secret pattern was detected."
diff --git a/scripts/test_production_smoke.py b/scripts/test_production_smoke.py
index 8d2c7232..3d73338f 100644
--- a/scripts/test_production_smoke.py
+++ b/scripts/test_production_smoke.py
@@ -41,6 +41,9 @@ def _deployment_identity(**overrides: object) -> dict[str, object]:
"aggregate_limit_evidence_sha256": "9" * 64,
"runtime_policy_sha256": "c" * 64,
"proof_dependency_manifest_sha256": "d" * 64,
+ "generation_isolation_evidence_sha256": "5" * 64,
+ "generation_isolation_probe_sha256": "6" * 64,
+ "generation_isolation_verified_at": "2026-07-15T11:59:00Z",
"worker_artifact_classification": "PROCESS_BOUND_PARTIAL",
"worker_artifact_manifest_sha256": "1" * 64,
"worker_bundle_sha256": "2" * 64,
@@ -118,7 +121,9 @@ def _control_plane_validator_source(kind: str) -> str:
'"${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" '
'"${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" '
'"${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" '
- '"${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" <<\'PY\'\n'
+ '"${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" '
+ '"${GENERATION_ISOLATION_EVIDENCE_SHA256}" '
+ '"${GENERATION_ISOLATION_PROBE_SHA256}" <<\'PY\'\n'
)
start = script.index(marker) + len(marker)
return script[start : script.index("\nPY\n", start)]
@@ -146,11 +151,14 @@ def _deployment_receipt() -> dict[str, object]:
runner = "b" * 40
digest = "sha256:" + "c" * 64
return {
- "schemaVersion": "5",
+ "schemaVersion": "6",
"status": "DEPLOYED",
"workerName": "counterlab",
"productionOrigin": "https://counterlab.cserules.workers.dev",
"generationFilesystemReadIsolation": "OS_ENFORCED",
+ "generationIsolationEvidenceSha256": "6" * 64,
+ "generationIsolationProbeSha256": "7" * 64,
+ "generationIsolationVerifiedAt": "2026-07-18T23:58:00.000Z",
"workerEvidenceCommit": worker,
"runnerSourceCommit": runner,
"qualifiedRunnerReceiptSha256": "1" * 64,
@@ -189,7 +197,7 @@ def _deployment_receipt() -> dict[str, object]:
"workerVersionSha256": "a" * 64,
"containerStatusSha256": "b" * 64,
"deployedAt": "2026-07-19T00:00:00.000Z",
- "verifierVersion": "counterlab-deployment-v5",
+ "verifierVersion": "counterlab-deployment-v6",
}
@@ -265,6 +273,8 @@ def test_control_plane_validators_bind_exact_release_and_maintenance_state() ->
public_asset_count = 25
vite_version = "8.1.4"
wrangler_version = "4.110.0"
+ generation_isolation_evidence = "5" * 64
+ generation_isolation_probe = "6" * 64
release = {
"status": "bound",
"workerVersionId": version,
@@ -272,6 +282,8 @@ def test_control_plane_validators_bind_exact_release_and_maintenance_state() ->
"workerEvidenceCommit": worker,
"runnerSourceCommit": runner,
"runnerImageDigest": digest,
+ "generationIsolationEvidenceSha256": generation_isolation_evidence,
+ "generationIsolationProbeSha256": generation_isolation_probe,
"timeoutCleanupReceiptSha256": timeout_receipt,
"aggregateLimitEvidenceSha256": aggregate_limit_evidence,
"runtimePolicySha256": runtime_policy,
@@ -347,6 +359,8 @@ def run_validator(
str(public_asset_count),
vite_version,
wrangler_version,
+ generation_isolation_evidence,
+ generation_isolation_probe,
],
check=False,
capture_output=True,
@@ -385,6 +399,8 @@ def run_validator(
"clientPublicAssetCount",
"viteVersion",
"wranglerVersion",
+ "generationIsolationEvidenceSha256",
+ "generationIsolationProbeSha256",
):
tampered = json.loads(json.dumps(payload))
tampered_release = (
@@ -503,7 +519,7 @@ def test_smoke_report_is_atomic_idempotent_and_secret_free() -> None:
**_deployment_identity(),
)
assert initialized["status"] == "RUNNING"
- assert initialized["schemaVersion"] == "4"
+ assert initialized["schemaVersion"] == "5"
assert initialized["baseUrl"] == "https://counterlab.example.test"
assert initialized["stages"] == []
@@ -528,7 +544,7 @@ def test_smoke_report_is_atomic_idempotent_and_secret_free() -> None:
}
-def test_smoke_report_versions_preserve_v2_v3_and_require_v4_artifact_bindings() -> None:
+def test_smoke_report_versions_preserve_v2_v3_v4_and_require_v5_isolation_bindings() -> None:
with tempfile.TemporaryDirectory() as destination:
path = pathlib.Path(destination) / "production-smoke.json"
current = initialize_report(
@@ -537,7 +553,13 @@ def test_smoke_report_versions_preserve_v2_v3_and_require_v4_artifact_bindings()
started_at="2026-07-15T12:00:00Z",
**_deployment_identity(),
)
- assert current["schemaVersion"] == "4"
+ assert current["schemaVersion"] == "5"
+
+ v5_fields = (
+ "generationIsolationEvidenceSha256",
+ "generationIsolationProbeSha256",
+ "generationIsolationVerifiedAt",
+ )
v4_fields = (
"aggregateLimitEvidenceSha256",
@@ -552,7 +574,13 @@ def test_smoke_report_versions_preserve_v2_v3_and_require_v4_artifact_bindings()
"wranglerVersion",
)
- historical_v3 = json.loads(json.dumps(current))
+ historical_v4 = json.loads(json.dumps(current))
+ historical_v4["schemaVersion"] = "4"
+ for field in v5_fields:
+ historical_v4["deployment"].pop(field)
+ assert validate_report(historical_v4)["schemaVersion"] == "4"
+
+ historical_v3 = json.loads(json.dumps(historical_v4))
historical_v3["schemaVersion"] = "3"
for field in v4_fields:
historical_v3["deployment"].pop(field)
@@ -573,6 +601,11 @@ def test_smoke_report_versions_preserve_v2_v3_and_require_v4_artifact_bindings()
with pytest.raises(ValueError, match="deployment metadata"):
validate_report(expanded_v2)
+ expanded_v4 = json.loads(json.dumps(current))
+ expanded_v4["schemaVersion"] = "4"
+ with pytest.raises(ValueError, match="deployment metadata"):
+ validate_report(expanded_v4)
+
incomplete_v3 = json.loads(json.dumps(historical_v3))
incomplete_v3["deployment"].pop("runtimePolicySha256")
with pytest.raises(ValueError, match="deployment metadata"):
@@ -583,10 +616,20 @@ def test_smoke_report_versions_preserve_v2_v3_and_require_v4_artifact_bindings()
with pytest.raises(ValueError, match="proofDependencyManifestSha256"):
validate_report(malformed_v3)
- invalid_v4 = json.loads(json.dumps(current))
- invalid_v4["deployment"]["clientPublicAssetCount"] = 28
+ malformed_v5_hash = json.loads(json.dumps(current))
+ malformed_v5_hash["deployment"]["generationIsolationProbeSha256"] = "short"
+ with pytest.raises(ValueError, match="generationIsolationProbeSha256"):
+ validate_report(malformed_v5_hash)
+
+ malformed_v5_time = json.loads(json.dumps(current))
+ malformed_v5_time["deployment"]["generationIsolationVerifiedAt"] = "yesterday"
+ with pytest.raises(ValueError, match="generationIsolationVerifiedAt"):
+ validate_report(malformed_v5_time)
+
+ invalid_v5 = json.loads(json.dumps(current))
+ invalid_v5["deployment"]["clientPublicAssetCount"] = 28
with pytest.raises(ValueError, match="asset counts"):
- validate_report(invalid_v4)
+ validate_report(invalid_v5)
def test_smoke_report_rejects_conflicts_secrets_and_credential_urls() -> None:
diff --git a/scripts/verify-scientific-engines.sh b/scripts/verify-scientific-engines.sh
index afa379ce..e0f6d799 100755
--- a/scripts/verify-scientific-engines.sh
+++ b/scripts/verify-scientific-engines.sh
@@ -3,6 +3,7 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
IMAGE=""
+EXPECTED_IMAGE_DIGEST=""
REGISTRY_ONLY=0
REQUIRE_PRODUCTION=0
RUNTIME_REPORT=""
@@ -22,6 +23,8 @@ Usage: ./scripts/verify-scientific-engines.sh [options]
Options:
--image IMAGE Verify the exact local runner image from inside its runtime.
+ --expected-image-digest SHA256
+ Refuse to start unless IMAGE resolves to this immutable ID.
--registry-only Skip Proof Capsule linkage and permit an omitted runtime image.
--require-production Reject a local-candidate runtime manifest.
--runtime-report PATH Persist the exact runtime report to a new contained file.
@@ -41,6 +44,11 @@ while [[ $# -gt 0 ]]; do
IMAGE="$2"
shift 2
;;
+ --expected-image-digest)
+ [[ $# -ge 2 ]] || { echo "--expected-image-digest requires a value" >&2; exit 2; }
+ EXPECTED_IMAGE_DIGEST="$2"
+ shift 2
+ ;;
--registry-only)
REGISTRY_ONLY=1
shift
@@ -161,6 +169,10 @@ if [[ ! "${IMAGE_DIGEST}" =~ ^sha256:[a-f0-9]{64}$ ]]; then
echo "Runner image does not expose a valid sha256 image ID." >&2
exit 1
fi
+if [[ -n "${EXPECTED_IMAGE_DIGEST}" && "${IMAGE_DIGEST}" != "${EXPECTED_IMAGE_DIGEST}" ]]; then
+ echo "Runner image does not match the required immutable image ID." >&2
+ exit 1
+fi
if [[ ! "${SOURCE_COMMIT}" =~ ^[a-f0-9]{40}$ ]]; then
echo "Runner image has an unbound or invalid OCI source revision." >&2
exit 1
@@ -212,7 +224,7 @@ STARTUP_PROBE_OUTPUT="$("${DOCKER_COMMAND[@]}" run --rm --name "${STARTUP_CONTAI
-e COUNTERLAB_RUNNER_STARTUP_PROBE=1 \
-e COUNTERLAB_RUNNER_WORK_ROOT=/counterlab-runtime/jobs \
-e COUNTERLAB_CODEX_HOME_ROOT=/counterlab-runtime/codex \
- "${IMAGE}")"
+ "${IMAGE_DIGEST}")"
node -e '
const value = JSON.parse(process.argv[1]);
if (
@@ -267,7 +279,7 @@ RUNTIME_VERIFICATION_OUTPUT="$("${DOCKER_COMMAND[@]}" run --rm --name "${RUNTIME
--mount "type=bind,src=${ROOT_DIR}/scientific-engines,dst=/repo/scientific-engines,readonly" \
--workdir=/repo \
--entrypoint python \
- "${IMAGE}" \
+ "${IMAGE_DIGEST}" \
/repo/scripts/verify_scientific_runtime.py \
--root /repo \
--image-digest "${IMAGE_DIGEST}" \
diff --git a/services/hosted-runner/src/server.test.ts b/services/hosted-runner/src/server.test.ts
index 1d6046cc..f49e069b 100644
--- a/services/hosted-runner/src/server.test.ts
+++ b/services/hosted-runner/src/server.test.ts
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import {
CODEX_ATTEMPT_TIMEOUT_MS,
createHostedRunnerServer,
+ verifiedHostedRunnerReleaseIdentity,
type HostedRunnerServerOptions,
} from "./server.js";
@@ -108,9 +109,13 @@ describe("hosted runner HTTP service", () => {
const processJob = vi.fn(async () => undefined);
const runnerSourceCommit = "b".repeat(40);
const runnerImageDigest = `sha256:${"c".repeat(64)}`;
+ const generationIsolationEvidenceSha256 = "d".repeat(64);
+ const generationIsolationProbeSha256 = "e".repeat(64);
const baseUrl = await start(processJob, undefined, undefined, {
runnerSourceCommit,
runnerImageDigest,
+ generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256,
});
const ready = await fetch(`${baseUrl}/ready`);
@@ -121,6 +126,8 @@ describe("hosted runner HTTP service", () => {
generationFilesystemReadIsolation: "OS_ENFORCED",
runnerSourceCommit,
runnerImageDigest,
+ generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256,
});
const accepted = await fetch(`${baseUrl}/jobs`, {
@@ -149,6 +156,38 @@ describe("hosted runner HTTP service", () => {
});
});
+ it("fails release identity closed on partial or stale isolation evidence", () => {
+ const exact = {
+ runnerSourceCommit: "b".repeat(40),
+ runnerImageDigest: `sha256:${"c".repeat(64)}`,
+ generationIsolationEvidenceSha256: "d".repeat(64),
+ generationIsolationProbeSha256: "e".repeat(64),
+ };
+ expect(verifiedHostedRunnerReleaseIdentity(exact, "e".repeat(64))).toEqual(
+ exact,
+ );
+ expect(
+ verifiedHostedRunnerReleaseIdentity(
+ {
+ runnerSourceCommit: "",
+ runnerImageDigest: "",
+ generationIsolationEvidenceSha256: "",
+ generationIsolationProbeSha256: "",
+ },
+ "e".repeat(64),
+ ),
+ ).toBeUndefined();
+ expect(() =>
+ verifiedHostedRunnerReleaseIdentity(
+ { ...exact, generationIsolationEvidenceSha256: "" },
+ "e".repeat(64),
+ ),
+ ).toThrow(/exact source commit/u);
+ expect(() =>
+ verifiedHostedRunnerReleaseIdentity(exact, "f".repeat(64)),
+ ).toThrow(/startup probe/u);
+ });
+
it("signals one-shot lifecycle cleanup only after the accepted job settles", async () => {
let finishJob: (() => void) | undefined;
const processJob = vi.fn(
diff --git a/services/hosted-runner/src/server.ts b/services/hosted-runner/src/server.ts
index aaa27cba..7b7b69fa 100644
--- a/services/hosted-runner/src/server.ts
+++ b/services/hosted-runner/src/server.ts
@@ -29,12 +29,16 @@ const DispatchSchema = z
})
.strict();
+export type HostedRunnerReleaseIdentity = {
+ runnerSourceCommit: string;
+ runnerImageDigest: string;
+ generationIsolationEvidenceSha256: string;
+ generationIsolationProbeSha256: string;
+};
+
export type HostedRunnerServerOptions = {
generationFilesystemReadIsolation: "OS_ENFORCED";
- releaseIdentity?: {
- runnerSourceCommit: string;
- runnerImageDigest: string;
- };
+ releaseIdentity?: HostedRunnerReleaseIdentity;
authorizeToken(
token: string,
jobId: string,
@@ -50,6 +54,30 @@ export type HostedRunnerServerOptions = {
onJobSettled?(input: { jobId: string }): Promise | void;
};
+export function verifiedHostedRunnerReleaseIdentity(
+ candidate: HostedRunnerReleaseIdentity,
+ observedProbeSha256: string,
+): HostedRunnerReleaseIdentity | undefined {
+ const values = Object.values(candidate);
+ if (values.every((value) => value.length === 0)) return undefined;
+ if (
+ !/^[a-f0-9]{40}$/u.test(candidate.runnerSourceCommit) ||
+ !/^sha256:[a-f0-9]{64}$/u.test(candidate.runnerImageDigest) ||
+ !/^[a-f0-9]{64}$/u.test(candidate.generationIsolationEvidenceSha256) ||
+ !/^[a-f0-9]{64}$/u.test(candidate.generationIsolationProbeSha256)
+ ) {
+ throw new Error(
+ "Runner release identity must contain an exact source commit, image digest, generation-isolation evidence hash, and probe hash",
+ );
+ }
+ if (candidate.generationIsolationProbeSha256 !== observedProbeSha256) {
+ throw new Error(
+ "Runner startup probe does not match the qualified generation-isolation evidence",
+ );
+ }
+ return candidate;
+}
+
type ActiveJob = {
controller: AbortController;
};
@@ -230,6 +258,10 @@ async function startProductionServer(): Promise {
process.env.COUNTERLAB_RUNNER_SOURCE_COMMIT?.trim() ?? "";
const runnerImageDigest =
process.env.COUNTERLAB_RUNNER_IMAGE_DIGEST?.trim() ?? "";
+ const generationIsolationEvidenceSha256 =
+ process.env.COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256?.trim() ?? "";
+ const generationIsolationProbeSha256 =
+ process.env.COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256?.trim() ?? "";
delete process.env.CODEX_AUTH_JSON;
delete process.env.COUNTERLAB_RUNNER_VERIFYING_PUBLIC_KEY;
if (authJson === undefined || authJson.trim().length === 0) {
@@ -243,17 +275,15 @@ async function startProductionServer(): Promise {
"COUNTERLAB_RUNNER_VERIFYING_PUBLIC_KEY is required by the hosted runner",
);
}
- const releaseIdentityMissing =
- runnerSourceCommit.length === 0 && runnerImageDigest.length === 0;
- if (
- !releaseIdentityMissing &&
- (!/^[a-f0-9]{40}$/u.test(runnerSourceCommit) ||
- !/^sha256:[a-f0-9]{64}$/u.test(runnerImageDigest))
- ) {
- throw new Error(
- "Runner release identity must contain an exact source commit and image digest",
- );
- }
+ const releaseIdentity = verifiedHostedRunnerReleaseIdentity(
+ {
+ runnerSourceCommit,
+ runnerImageDigest,
+ generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256,
+ },
+ startupProbe.generationIsolationProbeSha256,
+ );
const workspaceRoot = process.env.COUNTERLAB_RUNNER_WORK_ROOT ?? "/work/jobs";
const codexHomeRoot =
process.env.COUNTERLAB_CODEX_HOME_ROOT ?? "/run/counterlab-codex";
@@ -291,9 +321,7 @@ async function startProductionServer(): Promise {
const server = createHostedRunnerServer({
generationFilesystemReadIsolation:
startupProbe.generationFilesystemReadIsolation,
- ...(releaseIdentityMissing
- ? {}
- : { releaseIdentity: { runnerSourceCommit, runnerImageDigest } }),
+ ...(releaseIdentity === undefined ? {} : { releaseIdentity }),
async authorizeToken(scopedToken, jobId, purpose, controlPlaneOrigin) {
try {
await verifyRunnerJobToken(scopedToken, runnerVerifyingPublicKey, {
From 9348093a15942e7fb59c2755d3e080a20785b5c0 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 04:19:20 +0530
Subject: [PATCH 014/143] fix(release): bind fresh isolation recheck
---
apps/web/worker/api.test.ts | 101 +++--
apps/web/worker/api.ts | 25 ++
apps/web/worker/deployment-config.test.ts | 131 ++++++-
.../scientific-engine-registry/src/schema.ts | 178 ++++++++-
.../scientific-engine-registry/src/types.ts | 4 +
.../test/generation-isolation-schema.test.ts | 24 ++
.../test/release-check-receipt.test.ts | 168 ++++++++-
.../test/release-schema.test.ts | 74 +++-
.../schemas/deployment-receipt-v7.schema.json | 261 +++++++++++++
.../release-check-receipt-v5.schema.json | 356 ++++++++++++++++++
scripts/create-deployment-receipt.test.ts | 104 ++++-
scripts/create-deployment-receipt.ts | 79 +++-
scripts/deploy-qualified.sh | 190 +++++++---
scripts/generate-contract-schemas.ts | 26 ++
scripts/prepare-qualified-deploy.ts | 51 ++-
scripts/production-smoke.sh | 42 ++-
scripts/production_smoke_report.py | 102 ++++-
scripts/release-check-receipt.ts | 96 ++++-
scripts/test_production_smoke.py | 155 +++++++-
19 files changed, 2015 insertions(+), 152 deletions(-)
create mode 100644 scientific-engines/schemas/deployment-receipt-v7.schema.json
create mode 100644 scientific-engines/schemas/release-check-receipt-v5.schema.json
diff --git a/apps/web/worker/api.test.ts b/apps/web/worker/api.test.ts
index 1fd56a5f..d45c96f8 100644
--- a/apps/web/worker/api.test.ts
+++ b/apps/web/worker/api.test.ts
@@ -10495,6 +10495,14 @@ describe("Cloudflare Worker API", () => {
COUNTERLAB_RUNNER_IMAGE_DIGEST: `sha256:${"c".repeat(64)}`,
COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "5".repeat(64),
COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(64),
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256: "7".repeat(
+ 64,
+ ),
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(
+ 64,
+ ),
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT:
+ "2026-07-19T05:31:00.000+05:30",
COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256: "d".repeat(64),
COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256: "9".repeat(64),
COUNTERLAB_RUNTIME_POLICY_SHA256: "e".repeat(64),
@@ -10515,32 +10523,35 @@ describe("Cloudflare Worker API", () => {
},
} as unknown as Env);
- await expect(response.json()).resolves.toMatchObject({
- data: {
- release: {
- status: "bound",
- workerVersionId,
- workerVersionTag: `git-${workerEvidenceCommit}`,
- workerEvidenceCommit,
- runnerSourceCommit: "b".repeat(40),
- runnerImageDigest: `sha256:${"c".repeat(64)}`,
- generationIsolationEvidenceSha256: "5".repeat(64),
- generationIsolationProbeSha256: "6".repeat(64),
- timeoutCleanupReceiptSha256: "d".repeat(64),
- aggregateLimitEvidenceSha256: "9".repeat(64),
- runtimePolicySha256: "e".repeat(64),
- proofDependencyManifestSha256: "f".repeat(64),
- workerArtifactClassification: "PROCESS_BOUND_PARTIAL",
- workerArtifactManifestSha256: "1".repeat(64),
- workerBundleSha256: "2".repeat(64),
- clientAssetsSha256: "3".repeat(64),
- clientAssetCount: 27,
- clientPublicAssetsSha256: "4".repeat(64),
- clientPublicAssetCount: 25,
- viteVersion: "8.1.4",
- wranglerVersion: "4.110.0",
- },
- },
+ const releaseHealth = (await response.json()) as {
+ data: { release: unknown };
+ };
+ expect(releaseHealth.data.release).toEqual({
+ status: "bound",
+ workerVersionId,
+ workerVersionTag: `git-${workerEvidenceCommit}`,
+ workerEvidenceCommit,
+ runnerSourceCommit: "b".repeat(40),
+ runnerImageDigest: `sha256:${"c".repeat(64)}`,
+ generationIsolationEvidenceSha256: "5".repeat(64),
+ generationIsolationProbeSha256: "6".repeat(64),
+ releaseCheckGenerationIsolationEvidenceSha256: "7".repeat(64),
+ releaseCheckGenerationIsolationProbeSha256: "6".repeat(64),
+ releaseCheckGenerationIsolationVerifiedAt:
+ "2026-07-19T05:31:00.000+05:30",
+ timeoutCleanupReceiptSha256: "d".repeat(64),
+ aggregateLimitEvidenceSha256: "9".repeat(64),
+ runtimePolicySha256: "e".repeat(64),
+ proofDependencyManifestSha256: "f".repeat(64),
+ workerArtifactClassification: "PROCESS_BOUND_PARTIAL",
+ workerArtifactManifestSha256: "1".repeat(64),
+ workerBundleSha256: "2".repeat(64),
+ clientAssetsSha256: "3".repeat(64),
+ clientAssetCount: 27,
+ clientPublicAssetsSha256: "4".repeat(64),
+ clientPublicAssetCount: 25,
+ viteVersion: "8.1.4",
+ wranglerVersion: "4.110.0",
});
const mismatchedTag = await api.request("/api/health", undefined, {
@@ -10549,6 +10560,14 @@ describe("Cloudflare Worker API", () => {
COUNTERLAB_RUNNER_IMAGE_DIGEST: `sha256:${"c".repeat(64)}`,
COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "5".repeat(64),
COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(64),
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256: "7".repeat(
+ 64,
+ ),
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(
+ 64,
+ ),
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT:
+ "2026-07-19T05:31:00.000+05:30",
COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256: "d".repeat(64),
COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256: "9".repeat(64),
COUNTERLAB_RUNTIME_POLICY_SHA256: "e".repeat(64),
@@ -10593,6 +10612,29 @@ describe("Cloudflare Worker API", () => {
{ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "0".repeat(63) },
{ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: undefined },
{ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "0".repeat(63) },
+ {
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256:
+ undefined,
+ },
+ {
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256:
+ "0".repeat(63),
+ },
+ {
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256: undefined,
+ },
+ {
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256: "0".repeat(
+ 64,
+ ),
+ },
+ {
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT: undefined,
+ },
+ {
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT:
+ "not-a-timestamp",
+ },
{ COUNTERLAB_RUNTIME_POLICY_SHA256: undefined },
{ COUNTERLAB_RUNTIME_POLICY_SHA256: "0".repeat(63) },
{ COUNTERLAB_PROOF_DEPENDENCY_MANIFEST_SHA256: undefined },
@@ -10622,6 +10664,13 @@ describe("Cloudflare Worker API", () => {
COUNTERLAB_RUNNER_IMAGE_DIGEST: `sha256:${"c".repeat(64)}`,
COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "5".repeat(64),
COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(64),
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256:
+ "7".repeat(64),
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(
+ 64,
+ ),
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT:
+ "2026-07-19T05:31:00.000+05:30",
COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256: "d".repeat(64),
COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256: "9".repeat(64),
COUNTERLAB_RUNTIME_POLICY_SHA256: "e".repeat(64),
diff --git a/apps/web/worker/api.ts b/apps/web/worker/api.ts
index 4c944c40..98715402 100644
--- a/apps/web/worker/api.ts
+++ b/apps/web/worker/api.ts
@@ -245,6 +245,9 @@ type WorkerBindings = Omit & {
COUNTERLAB_RUNNER_IMAGE_DIGEST?: string;
COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256?: string;
COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256?: string;
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256?: string;
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256?: string;
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT?: string;
COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256?: string;
COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256?: string;
COUNTERLAB_RUNTIME_POLICY_SHA256?: string;
@@ -1068,6 +1071,9 @@ function releaseIdentity(context: Context):
runnerImageDigest: string;
generationIsolationEvidenceSha256: string;
generationIsolationProbeSha256: string;
+ releaseCheckGenerationIsolationEvidenceSha256: string;
+ releaseCheckGenerationIsolationProbeSha256: string;
+ releaseCheckGenerationIsolationVerifiedAt: string;
timeoutCleanupReceiptSha256: string;
aggregateLimitEvidenceSha256: string;
runtimePolicySha256: string;
@@ -1091,6 +1097,15 @@ function releaseIdentity(context: Context):
context.env?.COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256 ?? "";
const generationIsolationProbeSha256 =
context.env?.COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256 ?? "";
+ const releaseCheckGenerationIsolationEvidenceSha256 =
+ context.env
+ ?.COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256 ?? "";
+ const releaseCheckGenerationIsolationProbeSha256 =
+ context.env?.COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256 ??
+ "";
+ const releaseCheckGenerationIsolationVerifiedAt =
+ context.env?.COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT ??
+ "";
const timeoutCleanupReceiptSha256 =
context.env?.COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256 ?? "";
const aggregateLimitEvidenceSha256 =
@@ -1125,6 +1140,13 @@ function releaseIdentity(context: Context):
!/^sha256:[a-f0-9]{64}$/u.test(runnerImageDigest) ||
!/^[a-f0-9]{64}$/u.test(generationIsolationEvidenceSha256) ||
!/^[a-f0-9]{64}$/u.test(generationIsolationProbeSha256) ||
+ !/^[a-f0-9]{64}$/u.test(releaseCheckGenerationIsolationEvidenceSha256) ||
+ releaseCheckGenerationIsolationProbeSha256 !==
+ generationIsolationProbeSha256 ||
+ !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/u.test(
+ releaseCheckGenerationIsolationVerifiedAt,
+ ) ||
+ !Number.isFinite(Date.parse(releaseCheckGenerationIsolationVerifiedAt)) ||
!/^[a-f0-9]{64}$/u.test(timeoutCleanupReceiptSha256) ||
!/^[a-f0-9]{64}$/u.test(aggregateLimitEvidenceSha256) ||
!/^[a-f0-9]{64}$/u.test(runtimePolicySha256) ||
@@ -1154,6 +1176,9 @@ function releaseIdentity(context: Context):
runnerImageDigest,
generationIsolationEvidenceSha256,
generationIsolationProbeSha256,
+ releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt,
timeoutCleanupReceiptSha256,
aggregateLimitEvidenceSha256,
runtimePolicySha256,
diff --git a/apps/web/worker/deployment-config.test.ts b/apps/web/worker/deployment-config.test.ts
index 81865abf..7bd632d6 100644
--- a/apps/web/worker/deployment-config.test.ts
+++ b/apps/web/worker/deployment-config.test.ts
@@ -3,10 +3,11 @@ import { existsSync, readdirSync } from "node:fs";
import { relative, resolve } from "node:path";
import { describe, expect, it } from "vitest";
+import { RELEASE_CHECK_IDS } from "../../../packages/scientific-engine-registry/src/index";
import {
bindFrozenWorkerRelease,
- qualifiedDeployConfig,
+ qualifiedDeployConfig as generateQualifiedDeployConfig,
} from "../../../scripts/prepare-qualified-deploy";
import { createGenerationIsolationEvidence } from "../../../scripts/generation-isolation-evidence";
@@ -198,6 +199,85 @@ function generationIsolationQualification(input: {
} as const;
}
+function releaseCheckReceiptFor(qualified: {
+ evidenceCommit: string;
+ sourceCommit: string;
+ sourceTreeSha256: string;
+ qualifiedAt: string;
+ localImageTag: string;
+ localImageDigest: string;
+ adapterImageTag: string;
+ adapterImageDigest: string;
+ registryDigest: string;
+ runtimeToolchainSha256: string;
+ runtimePolicySha256: string;
+ proofDependencyManifestSha256: string;
+ aggregateLimitEvidenceSha256: string;
+ runtimeAdapterSha256: string;
+ generationIsolationEvidenceSha256: string;
+ generationIsolationProbeSha256: string;
+ generationIsolationVerifiedAt: string;
+}) {
+ const fresh = generationIsolationQualification({
+ sourceCommit: qualified.sourceCommit,
+ sourceTreeSha256: qualified.sourceTreeSha256,
+ localImageTag: qualified.localImageTag,
+ localImageDigest: qualified.localImageDigest,
+ verifiedAt: "2026-07-16T16:30:30.000Z",
+ });
+ return {
+ schemaVersion: "5",
+ status: "PASSED",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationEvidenceSha256:
+ qualified.generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: qualified.generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: qualified.generationIsolationVerifiedAt,
+ releaseCheckGenerationIsolationEvidence: fresh.generationIsolationEvidence,
+ releaseCheckGenerationIsolationEvidenceSha256:
+ fresh.generationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256:
+ fresh.generationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ fresh.generationIsolationVerifiedAt,
+ evidenceCommit: qualified.evidenceCommit,
+ sourceCommit: qualified.sourceCommit,
+ qualifiedRunnerReceiptSha256: "2".repeat(64),
+ qualifiedAt: qualified.qualifiedAt,
+ runnerImageTag: qualified.localImageTag,
+ runnerImageDigest: qualified.localImageDigest,
+ adapterImageTag: qualified.adapterImageTag,
+ adapterImageDigest: qualified.adapterImageDigest,
+ registryDigest: qualified.registryDigest,
+ runtimeToolchainSha256: qualified.runtimeToolchainSha256,
+ runtimePolicySha256: qualified.runtimePolicySha256,
+ proofDependencyManifestSha256: qualified.proofDependencyManifestSha256,
+ aggregateLimitEvidenceSha256: qualified.aggregateLimitEvidenceSha256,
+ runtimeAdapterSha256: qualified.runtimeAdapterSha256,
+ checks: RELEASE_CHECK_IDS.map((id) => ({ id, status: "PASSED" as const })),
+ checkedAt: "2026-07-16T16:31:00.000Z",
+ verifierVersion: "counterlab-release-check-v5",
+ } as const;
+}
+
+function qualifiedDeployConfig(
+ input: Omit<
+ Parameters[0],
+ "releaseCheckReceipt"
+ > & {
+ releaseCheckReceipt?: unknown;
+ },
+) {
+ return generateQualifiedDeployConfig({
+ ...input,
+ releaseCheckReceipt:
+ input.releaseCheckReceipt ??
+ releaseCheckReceiptFor(
+ input.receipt as Parameters[0],
+ ),
+ });
+}
+
function luminance(color: string): number {
const channels = [1, 3, 5].map((offset) =>
Number.parseInt(color.slice(offset, offset + 2), 16),
@@ -310,6 +390,18 @@ describe("Cloudflare static asset routing", () => {
expect(script).toContain("--containers-rollout immediate");
expect(script).toContain("deployments status");
expect(script).toContain("scripts/create-deployment-receipt.ts");
+ expect(script).toContain("release-check-identity");
+ expect(script).toContain(
+ "Release-check receipt bytes changed after identity validation.",
+ );
+ expect(script).toContain(
+ "RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256",
+ );
+ expect(script).toContain("RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT");
+ expect(script).toContain(
+ 'exactKeys(payload, ["status", "service", "checks", "maintenance", "release"])',
+ );
+ expect(script).toContain('exactKeys(payload, ["ok", "data"])');
expect(receiptVerifier).toContain("active.percentage !== 100");
expect(script).toContain("deployment-receipt.json");
expect(script).not.toContain("--containers-rollout gradual");
@@ -357,12 +449,10 @@ describe("Cloudflare static asset routing", () => {
expect(script.slice(finalDeploy, finalReadiness)).toContain(
"--containers-rollout none",
);
- expect(script).toContain(
- "data.release.workerVersionId !== expectedVersion",
- );
- expect(script).toContain(
- "payload.release.workerVersionId !== expectedVersion",
- );
+ expect(script.match(/workerVersionId: expectedVersion/gu)).toHaveLength(2);
+ expect(
+ script.match(/releaseCheckGenerationIsolationEvidenceSha256:/gu),
+ ).toHaveLength(2);
expect(script).toContain(
'rollback "${recovery_version}" \\\n --config "${RECOVERY_CONFIG}"',
);
@@ -674,6 +764,7 @@ describe("Cloudflare static asset routing", () => {
qualifiedAt: "2026-07-16T16:30:00.000Z",
verifierVersion: "counterlab-release-v6",
};
+ const releaseCheckReceipt = releaseCheckReceiptFor(receipt);
const observation = {
generationFilesystemReadIsolation: "OS_ENFORCED" as const,
...generationIsolationQualification({
@@ -719,6 +810,7 @@ describe("Cloudflare static asset routing", () => {
const generated = qualifiedDeployConfig({
config: productionDeployConfig(),
receipt,
+ releaseCheckReceipt,
image,
observation,
});
@@ -750,8 +842,29 @@ describe("Cloudflare static asset routing", () => {
receipt.generationIsolationEvidenceSha256,
COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256:
receipt.generationIsolationProbeSha256,
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256:
+ releaseCheckReceipt.releaseCheckGenerationIsolationEvidenceSha256,
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256:
+ releaseCheckReceipt.releaseCheckGenerationIsolationProbeSha256,
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT:
+ releaseCheckReceipt.releaseCheckGenerationIsolationVerifiedAt,
}),
);
+ expect(() =>
+ qualifiedDeployConfig({
+ config: productionDeployConfig(),
+ receipt,
+ releaseCheckReceipt: {
+ ...releaseCheckReceipt,
+ releaseCheckGenerationIsolationEvidence: {
+ ...releaseCheckReceipt.releaseCheckGenerationIsolationEvidence,
+ sourceTreeSha256: "0".repeat(64),
+ },
+ },
+ image,
+ observation,
+ }),
+ ).toThrow(/evidence hash mismatch/u);
const generatedFromVite = qualifiedDeployConfig({
config: viteGeneratedDeployConfig(),
receipt,
@@ -947,6 +1060,7 @@ describe("Cloudflare static asset routing", () => {
qualifiedAt: "2026-07-16T16:30:00.000Z",
verifierVersion: "counterlab-release-v6",
};
+ const releaseCheckReceipt = releaseCheckReceiptFor(receipt);
const config = productionDeployConfig();
config.durable_objects.bindings = [
{ name: "RUNNER", class_name: "CounterLabRunner" },
@@ -956,6 +1070,7 @@ describe("Cloudflare static asset routing", () => {
qualifiedDeployConfig({
config,
receipt,
+ releaseCheckReceipt,
image,
observation: {
generationFilesystemReadIsolation: "OS_ENFORCED",
@@ -1049,6 +1164,7 @@ describe("Cloudflare static asset routing", () => {
qualifiedAt: "2026-07-16T16:30:00.000Z",
verifierVersion: "counterlab-release-v6",
};
+ const releaseCheckReceipt = releaseCheckReceiptFor(receipt);
expect(() =>
qualifiedDeployConfig({
@@ -1057,6 +1173,7 @@ describe("Cloudflare static asset routing", () => {
containers: [{ class_name: "CounterLabRunner" }],
},
receipt,
+ releaseCheckReceipt,
image,
observation: {
generationFilesystemReadIsolation: "OS_ENFORCED",
diff --git a/packages/scientific-engine-registry/src/schema.ts b/packages/scientific-engine-registry/src/schema.ts
index 4efaaa9d..3be20819 100644
--- a/packages/scientific-engine-registry/src/schema.ts
+++ b/packages/scientific-engine-registry/src/schema.ts
@@ -1346,7 +1346,106 @@ export const ReleaseCheckReceiptV4Schema = z
}
});
-export const ReleaseCheckReceiptSchema = ReleaseCheckReceiptV4Schema;
+export const ReleaseCheckReceiptV5Schema = z
+ .strictObject({
+ ...ReleaseCheckReceiptV4Schema.shape,
+ schemaVersion: z.literal("5"),
+ releaseCheckGenerationIsolationEvidence:
+ GenerationIsolationEvidenceV1Schema,
+ releaseCheckGenerationIsolationEvidenceSha256: Sha256Schema,
+ releaseCheckGenerationIsolationProbeSha256: Sha256Schema,
+ releaseCheckGenerationIsolationVerifiedAt: z.iso.datetime({ offset: true }),
+ verifierVersion: z.literal("counterlab-release-check-v5"),
+ })
+ .superRefine((receipt, context) => {
+ const {
+ releaseCheckGenerationIsolationEvidence:
+ _releaseCheckGenerationIsolationEvidence,
+ releaseCheckGenerationIsolationEvidenceSha256:
+ _releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256:
+ _releaseCheckGenerationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ _releaseCheckGenerationIsolationVerifiedAt,
+ ...legacyReceipt
+ } = receipt;
+ const result = ReleaseCheckReceiptV4Schema.safeParse({
+ ...legacyReceipt,
+ schemaVersion: "4",
+ verifierVersion: "counterlab-release-check-v4",
+ });
+ if (!result.success) {
+ for (const issue of result.error.issues) {
+ context.addIssue({
+ code: "custom",
+ path: issue.path,
+ message: issue.message,
+ });
+ }
+ }
+ const evidence = receipt.releaseCheckGenerationIsolationEvidence;
+ for (const [field, expected, observed] of [
+ ["sourceCommit", receipt.sourceCommit, evidence.sourceCommit],
+ ["runnerImageTag", receipt.runnerImageTag, evidence.localImageTag],
+ [
+ "runnerImageDigest",
+ receipt.runnerImageDigest,
+ evidence.localImageDigest,
+ ],
+ [
+ "releaseCheckGenerationIsolationProbeSha256",
+ receipt.releaseCheckGenerationIsolationProbeSha256,
+ evidence.probePayloadSha256,
+ ],
+ [
+ "releaseCheckGenerationIsolationVerifiedAt",
+ receipt.releaseCheckGenerationIsolationVerifiedAt,
+ evidence.verifiedAt,
+ ],
+ ] as const) {
+ if (expected !== observed) {
+ context.addIssue({
+ code: "custom",
+ path: [field],
+ message: `${field} must match release-check isolation evidence`,
+ });
+ }
+ }
+ if (
+ Date.parse(receipt.releaseCheckGenerationIsolationVerifiedAt) >
+ Date.parse(receipt.checkedAt)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["releaseCheckGenerationIsolationVerifiedAt"],
+ message: "release-check isolation proof must precede receipt issuance",
+ });
+ }
+ if (
+ Date.parse(receipt.releaseCheckGenerationIsolationVerifiedAt) <
+ Date.parse(receipt.qualifiedAt)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["releaseCheckGenerationIsolationVerifiedAt"],
+ message: "release-check isolation proof must follow qualification",
+ });
+ }
+ if (
+ Date.parse(receipt.checkedAt) -
+ Date.parse(receipt.releaseCheckGenerationIsolationVerifiedAt) >
+ 30 * 60_000
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["releaseCheckGenerationIsolationVerifiedAt"],
+ message:
+ "release-check isolation proof must be within 30 minutes of receipt issuance",
+ });
+ }
+ });
+
+export const ReleaseCheckReceiptSchema = ReleaseCheckReceiptV5Schema;
export const DeploymentReceiptV3Schema = z
.strictObject({
@@ -1609,4 +1708,79 @@ export const DeploymentReceiptV6Schema = z
}
});
-export const DeploymentReceiptSchema = DeploymentReceiptV6Schema;
+export const DeploymentReceiptV7Schema = z
+ .strictObject({
+ ...DeploymentReceiptV6Schema.shape,
+ schemaVersion: z.literal("7"),
+ releaseCheckGenerationIsolationEvidenceSha256: Sha256Schema,
+ releaseCheckGenerationIsolationProbeSha256: Sha256Schema,
+ releaseCheckGenerationIsolationVerifiedAt: z.iso.datetime({ offset: true }),
+ verifierVersion: z.literal("counterlab-deployment-v7"),
+ })
+ .superRefine((receipt, context) => {
+ const {
+ releaseCheckGenerationIsolationEvidenceSha256:
+ _releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256:
+ _releaseCheckGenerationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ _releaseCheckGenerationIsolationVerifiedAt,
+ ...legacyReceipt
+ } = receipt;
+ const result = DeploymentReceiptV6Schema.safeParse({
+ ...legacyReceipt,
+ schemaVersion: "6",
+ verifierVersion: "counterlab-deployment-v6",
+ });
+ if (!result.success) {
+ for (const issue of result.error.issues) {
+ context.addIssue({
+ code: "custom",
+ path: issue.path,
+ message: issue.message,
+ });
+ }
+ }
+ if (
+ receipt.releaseCheckGenerationIsolationProbeSha256 !==
+ receipt.generationIsolationProbeSha256
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["releaseCheckGenerationIsolationProbeSha256"],
+ message: "release-check isolation probe must match the qualified probe",
+ });
+ }
+ if (
+ Date.parse(receipt.releaseCheckGenerationIsolationVerifiedAt) >
+ Date.parse(receipt.deployedAt)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["releaseCheckGenerationIsolationVerifiedAt"],
+ message: "release-check isolation proof must precede deployment",
+ });
+ }
+ if (
+ Date.parse(receipt.releaseCheckGenerationIsolationVerifiedAt) >
+ Date.parse(receipt.releaseCheckCheckedAt)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["releaseCheckGenerationIsolationVerifiedAt"],
+ message: "release-check isolation proof must precede receipt issuance",
+ });
+ }
+ if (
+ Date.parse(receipt.releaseCheckGenerationIsolationVerifiedAt) <
+ Date.parse(receipt.generationIsolationVerifiedAt)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["releaseCheckGenerationIsolationVerifiedAt"],
+ message: "release-check isolation proof must follow qualification",
+ });
+ }
+ });
+
+export const DeploymentReceiptSchema = DeploymentReceiptV7Schema;
diff --git a/packages/scientific-engine-registry/src/types.ts b/packages/scientific-engine-registry/src/types.ts
index 8a801a6a..c6d33d54 100644
--- a/packages/scientific-engine-registry/src/types.ts
+++ b/packages/scientific-engine-registry/src/types.ts
@@ -8,6 +8,7 @@ import {
DeploymentReceiptV4Schema,
DeploymentReceiptV5Schema,
DeploymentReceiptV6Schema,
+ DeploymentReceiptV7Schema,
GenerationIsolationEvidenceSchema,
GenerationIsolationEvidenceV1Schema,
GenerationIsolationProbePayloadSchema,
@@ -31,6 +32,7 @@ import {
ReleaseCheckReceiptV2Schema,
ReleaseCheckReceiptV3Schema,
ReleaseCheckReceiptV4Schema,
+ ReleaseCheckReceiptV5Schema,
SubjectPackAuthorityBindingSchema,
SubjectPackEngineBindingSchema,
SubjectPackEngineBindingsSchema,
@@ -48,6 +50,7 @@ export type DeploymentReceiptV3 = z.infer;
export type DeploymentReceiptV4 = z.infer;
export type DeploymentReceiptV5 = z.infer;
export type DeploymentReceiptV6 = z.infer;
+export type DeploymentReceiptV7 = z.infer;
export type GenerationIsolationEvidence = z.infer<
typeof GenerationIsolationEvidenceSchema
>;
@@ -116,3 +119,4 @@ export type ReleaseCheckReceiptV1 = z.infer;
export type ReleaseCheckReceiptV2 = z.infer;
export type ReleaseCheckReceiptV3 = z.infer;
export type ReleaseCheckReceiptV4 = z.infer;
+export type ReleaseCheckReceiptV5 = z.infer;
diff --git a/packages/scientific-engine-registry/test/generation-isolation-schema.test.ts b/packages/scientific-engine-registry/test/generation-isolation-schema.test.ts
index 33b0170a..4f72c0f8 100644
--- a/packages/scientific-engine-registry/test/generation-isolation-schema.test.ts
+++ b/packages/scientific-engine-registry/test/generation-isolation-schema.test.ts
@@ -6,9 +6,11 @@ import { z } from "zod";
import {
DeploymentReceiptV6Schema,
+ DeploymentReceiptV7Schema,
GenerationIsolationEvidenceV1Schema,
QualifiedRunnerReleaseV6Schema,
ReleaseCheckReceiptV4Schema,
+ ReleaseCheckReceiptV5Schema,
} from "../src/index.js";
const sourceCommit = "a".repeat(40);
@@ -112,6 +114,26 @@ describe("generation isolation evidence schema", () => {
]),
);
}
+ for (const [schema, version] of [
+ [ReleaseCheckReceiptV5Schema, "5"],
+ [DeploymentReceiptV7Schema, "7"],
+ ] as const) {
+ const jsonSchema = z.toJSONSchema(schema) as {
+ properties?: Record;
+ required?: string[];
+ };
+ expect(jsonSchema.properties?.schemaVersion?.const).toBe(version);
+ expect(jsonSchema.required).toEqual(
+ expect.arrayContaining([
+ "releaseCheckGenerationIsolationEvidenceSha256",
+ "releaseCheckGenerationIsolationProbeSha256",
+ "releaseCheckGenerationIsolationVerifiedAt",
+ ...(version === "5"
+ ? ["releaseCheckGenerationIsolationEvidence"]
+ : []),
+ ]),
+ );
+ }
});
it("publishes the evidence and next-version receipt schemas", () => {
@@ -119,7 +141,9 @@ describe("generation isolation evidence schema", () => {
"generation-isolation-evidence-v1.schema.json",
"qualified-runner-release-v6.schema.json",
"release-check-receipt-v4.schema.json",
+ "release-check-receipt-v5.schema.json",
"deployment-receipt-v6.schema.json",
+ "deployment-receipt-v7.schema.json",
]) {
const absolute = resolve(
process.cwd(),
diff --git a/packages/scientific-engine-registry/test/release-check-receipt.test.ts b/packages/scientific-engine-registry/test/release-check-receipt.test.ts
index 3abd9ae4..049b5e79 100644
--- a/packages/scientific-engine-registry/test/release-check-receipt.test.ts
+++ b/packages/scientific-engine-registry/test/release-check-receipt.test.ts
@@ -8,6 +8,7 @@ import {
ReleaseCheckReceiptSchema,
ReleaseCheckReceiptV2Schema,
ReleaseCheckReceiptV3Schema,
+ ReleaseCheckReceiptV4Schema,
} from "../src/index";
import { createGenerationIsolationEvidence } from "../../../scripts/generation-isolation-evidence";
@@ -15,6 +16,7 @@ import {
assertReleaseCheckBinding,
createDeploymentReleaseIdentity,
createQualifiedReleaseIdentity,
+ createReleaseCheckReleaseIdentity,
createReleaseCheckReceipt,
} from "../../../scripts/release-check-receipt";
@@ -173,7 +175,7 @@ function deploymentReceipt() {
const runner = "d".repeat(40);
const digest = `sha256:${"a".repeat(64)}`;
return {
- schemaVersion: "6",
+ schemaVersion: "7",
status: "DEPLOYED",
workerName: "counterlab",
productionOrigin: "https://counterlab.cserules.workers.dev",
@@ -181,11 +183,14 @@ function deploymentReceipt() {
generationIsolationEvidenceSha256: "0".repeat(64),
generationIsolationProbeSha256: "1".repeat(64),
generationIsolationVerifiedAt: "2026-07-18T23:58:00.000Z",
+ releaseCheckGenerationIsolationEvidenceSha256: "2".repeat(64),
+ releaseCheckGenerationIsolationProbeSha256: "1".repeat(64),
+ releaseCheckGenerationIsolationVerifiedAt: "2026-07-18T23:59:30.000Z",
workerEvidenceCommit: worker,
runnerSourceCommit: runner,
qualifiedRunnerReceiptSha256: "1".repeat(64),
releaseCheckReceiptSha256: "2".repeat(64),
- releaseCheckCheckedAt: "2026-07-18T23:59:00.000Z",
+ releaseCheckCheckedAt: "2026-07-18T23:59:45.000Z",
timeoutCleanupReceiptSha256: "b".repeat(64),
aggregateLimitEvidenceSha256: "6".repeat(64),
runtimeToolchainSha256: "3".repeat(64),
@@ -217,7 +222,7 @@ function deploymentReceipt() {
workerVersionSha256: "c".repeat(64),
containerStatusSha256: "d".repeat(64),
deployedAt: "2026-07-19T00:00:00.000Z",
- verifierVersion: "counterlab-deployment-v6",
+ verifierVersion: "counterlab-deployment-v7",
} as const;
}
@@ -308,6 +313,103 @@ describe("release-check receipt", () => {
).toThrow(/Worker tag/u);
});
+ it("reads historical deployment v6 and v5 identities without changing them", () => {
+ const {
+ releaseCheckGenerationIsolationEvidenceSha256:
+ _releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256:
+ _releaseCheckGenerationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ _releaseCheckGenerationIsolationVerifiedAt,
+ ...withoutFreshReleaseCheck
+ } = deploymentReceipt();
+ const v6 = {
+ ...withoutFreshReleaseCheck,
+ schemaVersion: "6",
+ verifierVersion: "counterlab-deployment-v6",
+ } as const;
+ expect(
+ createDeploymentReleaseIdentity(Buffer.from(JSON.stringify(v6))).receipt,
+ ).toMatchObject({ schemaVersion: "6" });
+
+ const {
+ generationIsolationEvidenceSha256: _generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: _generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: _generationIsolationVerifiedAt,
+ ...withoutQualificationIsolation
+ } = v6;
+ const v5 = {
+ ...withoutQualificationIsolation,
+ schemaVersion: "5",
+ verifierVersion: "counterlab-deployment-v5",
+ } as const;
+ expect(
+ createDeploymentReleaseIdentity(Buffer.from(JSON.stringify(v5))).receipt,
+ ).toMatchObject({ schemaVersion: "5" });
+ });
+
+ it("hands current and historical release-check identities across without rewriting bytes", () => {
+ const current = createReleaseCheckReceipt({
+ qualifiedReceipt: qualifiedReceipt(),
+ qualifiedReceiptBytes: qualifiedBytes(),
+ runtimeAttestation: runtimeAttestation(),
+ currentCommit: evidenceCommit,
+ worktreeClean: true,
+ runnerImageDigest: qualifiedReceipt().localImageDigest,
+ adapterImageDigest: qualifiedReceipt().adapterImageDigest,
+ generationIsolationEvidence: freshGenerationIsolationEvidence(),
+ checkedAt: "2026-07-19T00:02:00.000Z",
+ });
+ const currentBytes = Buffer.from(`${JSON.stringify(current, null, 2)}\n`);
+ expect(createReleaseCheckReleaseIdentity(currentBytes)).toMatchObject({
+ receiptType: "release-check-receipt",
+ receiptSha256: createHash("sha256").update(currentBytes).digest("hex"),
+ receipt: { schemaVersion: "5" },
+ });
+
+ const {
+ releaseCheckGenerationIsolationEvidence:
+ _releaseCheckGenerationIsolationEvidence,
+ releaseCheckGenerationIsolationEvidenceSha256:
+ _releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256:
+ _releaseCheckGenerationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ _releaseCheckGenerationIsolationVerifiedAt,
+ ...currentWithoutFreshEvidence
+ } = current;
+ const historical = ReleaseCheckReceiptV4Schema.parse({
+ ...currentWithoutFreshEvidence,
+ schemaVersion: "4",
+ verifierVersion: "counterlab-release-check-v4",
+ });
+ expect(() =>
+ ReleaseCheckReceiptSchema.parse(currentWithoutFreshEvidence),
+ ).toThrow();
+ expect(() =>
+ assertReleaseCheckBinding({
+ qualifiedReceipt: qualifiedReceipt(),
+ qualifiedReceiptBytes: qualifiedBytes(),
+ releaseCheckReceipt: {
+ ...current,
+ releaseCheckGenerationIsolationEvidenceSha256: "0".repeat(64),
+ },
+ runtimeAttestation: runtimeAttestation(),
+ currentCommit: evidenceCommit,
+ worktreeClean: true,
+ runnerImageDigest: qualifiedReceipt().localImageDigest,
+ adapterImageDigest: qualifiedReceipt().adapterImageDigest,
+ observedAt: "2026-07-19T00:03:00.000Z",
+ }),
+ ).toThrow(/evidence hash mismatch/u);
+ const historicalBytes = Buffer.from(JSON.stringify(historical));
+ expect(createReleaseCheckReleaseIdentity(historicalBytes)).toMatchObject({
+ receiptType: "release-check-receipt",
+ receiptSha256: createHash("sha256").update(historicalBytes).digest("hex"),
+ receipt: { schemaVersion: "4" },
+ });
+ });
+
it("uses named receipt accessors instead of positional release tuples", () => {
const releaseCheck = readFileSync("scripts/release-check.sh", "utf8");
const deploy = readFileSync("scripts/deploy-qualified.sh", "utf8");
@@ -337,11 +439,14 @@ describe("release-check receipt", () => {
});
expect(receipt).toMatchObject({
- schemaVersion: "4",
+ schemaVersion: "5",
status: "PASSED",
generationFilesystemReadIsolation: "OS_ENFORCED",
+ releaseCheckGenerationIsolationProbeSha256:
+ generationIsolation().probeSha256,
+ releaseCheckGenerationIsolationVerifiedAt: "2026-07-19T00:01:30.000Z",
evidenceCommit,
- verifierVersion: "counterlab-release-check-v4",
+ verifierVersion: "counterlab-release-check-v5",
});
expect(receipt.checks).toHaveLength(11);
});
@@ -374,9 +479,17 @@ describe("release-check receipt", () => {
).evidence,
}),
).toThrow(/not fresh/u);
+ expect(() =>
+ createReleaseCheckReceipt({
+ ...input,
+ generationIsolationEvidence: generationIsolation(
+ "2026-07-19T00:00:45.000Z",
+ ).evidence,
+ }),
+ ).toThrow(/follow qualification/u);
});
- it("preserves v3 and v2 receipts while requiring hash binding in v4", () => {
+ it("preserves v4, v3, and v2 receipts while requiring fresh evidence in v5", () => {
const current = createReleaseCheckReceipt({
qualifiedReceipt: qualifiedReceipt(),
qualifiedReceiptBytes: qualifiedBytes(),
@@ -388,12 +501,30 @@ describe("release-check receipt", () => {
generationIsolationEvidence: freshGenerationIsolationEvidence(),
checkedAt: "2026-07-19T00:02:00.000Z",
});
+ const {
+ releaseCheckGenerationIsolationEvidence:
+ _releaseCheckGenerationIsolationEvidence,
+ releaseCheckGenerationIsolationEvidenceSha256:
+ _releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256:
+ _releaseCheckGenerationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ _releaseCheckGenerationIsolationVerifiedAt,
+ ...legacyV4
+ } = current;
+ expect(
+ ReleaseCheckReceiptV4Schema.parse({
+ ...legacyV4,
+ schemaVersion: "4",
+ verifierVersion: "counterlab-release-check-v4",
+ }),
+ ).toMatchObject({ schemaVersion: "4" });
const {
generationIsolationEvidenceSha256: _generationIsolationEvidenceSha256,
generationIsolationProbeSha256: _generationIsolationProbeSha256,
generationIsolationVerifiedAt: _generationIsolationVerifiedAt,
...legacyV3
- } = current;
+ } = legacyV4;
expect(
ReleaseCheckReceiptV3Schema.parse({
...legacyV3,
@@ -421,7 +552,7 @@ describe("release-check receipt", () => {
).toThrow();
});
- it("publishes distinct historical v2 and isolation-enforced v3 schemas", () => {
+ it("publishes historical schemas and the fresh-evidence v5 schema", () => {
const v2Path = resolve(
process.cwd(),
"scientific-engines/schemas/release-check-receipt-v2.schema.json",
@@ -430,13 +561,19 @@ describe("release-check receipt", () => {
process.cwd(),
"scientific-engines/schemas/release-check-receipt-v3.schema.json",
);
+ const v5Path = resolve(
+ process.cwd(),
+ "scientific-engines/schemas/release-check-receipt-v5.schema.json",
+ );
expect(existsSync(v2Path)).toBe(true);
expect(existsSync(v3Path)).toBe(true);
+ expect(existsSync(v5Path)).toBe(true);
const v2 = JSON.parse(readFileSync(v2Path, "utf8")) as {
properties?: Record;
required?: string[];
};
const v3 = JSON.parse(readFileSync(v3Path, "utf8")) as typeof v2;
+ const v5 = JSON.parse(readFileSync(v5Path, "utf8")) as typeof v2;
expect(v2.properties?.schemaVersion?.const).toBe("2");
expect(v2.properties).not.toHaveProperty(
@@ -448,6 +585,11 @@ describe("release-check receipt", () => {
"OS_ENFORCED",
);
expect(v3.required).toContain("generationFilesystemReadIsolation");
+ expect(v5.properties?.schemaVersion?.const).toBe("5");
+ expect(v5.required).toContain(
+ "releaseCheckGenerationIsolationEvidenceSha256",
+ );
+ expect(v5.required).toContain("releaseCheckGenerationIsolationVerifiedAt");
});
it("rejects a different qualified receipt or live runtime", () => {
@@ -573,6 +715,16 @@ describe("release-check receipt", () => {
observedAt: "2026-07-20T00:03:01.000Z",
}),
).toThrow(/stale/u);
+ expect(() =>
+ assertReleaseCheckBinding({
+ ...common,
+ releaseCheckReceipt: {
+ ...receipt,
+ checkedAt: "2026-07-19T00:32:00.000Z",
+ },
+ observedAt: "2026-07-19T00:33:00.000Z",
+ }),
+ ).toThrow(/within 30 minutes/u);
expect(() =>
assertReleaseCheckBinding({
...common,
diff --git a/packages/scientific-engine-registry/test/release-schema.test.ts b/packages/scientific-engine-registry/test/release-schema.test.ts
index 626af6e2..5670ee1a 100644
--- a/packages/scientific-engine-registry/test/release-schema.test.ts
+++ b/packages/scientific-engine-registry/test/release-schema.test.ts
@@ -7,6 +7,7 @@ import {
DeploymentReceiptV3Schema,
DeploymentReceiptV4Schema,
DeploymentReceiptV5Schema,
+ DeploymentReceiptV6Schema,
QualifiedRunnerReleaseSchema,
QualifiedRunnerReleaseV2Schema,
QualifiedRunnerReleaseV3Schema,
@@ -391,7 +392,7 @@ describe("deployment receipt schema", () => {
const runner = "b".repeat(40);
const digest = `sha256:${"c".repeat(64)}`;
return {
- schemaVersion: "6",
+ schemaVersion: "7",
status: "DEPLOYED",
workerName: "counterlab",
productionOrigin: "https://counterlab.cserules.workers.dev",
@@ -399,6 +400,9 @@ describe("deployment receipt schema", () => {
generationIsolationEvidenceSha256: "0".repeat(64),
generationIsolationProbeSha256: "1".repeat(64),
generationIsolationVerifiedAt: "2026-07-19T00:01:00.000Z",
+ releaseCheckGenerationIsolationEvidenceSha256: "2".repeat(64),
+ releaseCheckGenerationIsolationProbeSha256: "1".repeat(64),
+ releaseCheckGenerationIsolationVerifiedAt: "2026-07-19T00:01:30.000Z",
workerEvidenceCommit: worker,
runnerSourceCommit: runner,
qualifiedRunnerReceiptSha256: "1".repeat(64),
@@ -435,13 +439,13 @@ describe("deployment receipt schema", () => {
workerVersionSha256: "b".repeat(64),
containerStatusSha256: "c".repeat(64),
deployedAt: "2026-07-19T00:03:00.000Z",
- verifierVersion: "counterlab-deployment-v6",
+ verifierVersion: "counterlab-deployment-v7",
} as const;
}
it("binds the deployed Worker, Container, release checks, and exact artifacts", () => {
expect(DeploymentReceiptSchema.parse(receipt())).toMatchObject({
- schemaVersion: "6",
+ schemaVersion: "7",
status: "DEPLOYED",
workerName: "counterlab",
});
@@ -463,15 +467,55 @@ describe("deployment receipt schema", () => {
generationFilesystemReadIsolation: "PARTIAL",
}),
).toThrow();
+ expect(() =>
+ DeploymentReceiptSchema.parse({
+ ...receipt(),
+ releaseCheckGenerationIsolationProbeSha256: "0".repeat(64),
+ }),
+ ).toThrow(/must match the qualified probe/u);
+ expect(() =>
+ DeploymentReceiptSchema.parse({
+ ...receipt(),
+ releaseCheckGenerationIsolationVerifiedAt: "2026-07-19T00:04:00.000Z",
+ }),
+ ).toThrow(/precede deployment/u);
+ expect(() =>
+ DeploymentReceiptSchema.parse({
+ ...receipt(),
+ releaseCheckGenerationIsolationVerifiedAt: "2026-07-19T00:00:30.000Z",
+ }),
+ ).toThrow(/follow qualification/u);
+ expect(() =>
+ DeploymentReceiptSchema.parse({
+ ...receipt(),
+ releaseCheckGenerationIsolationVerifiedAt: "2026-07-19T00:02:30.000Z",
+ }),
+ ).toThrow(/precede receipt issuance/u);
});
- it("retains the historical v5 and v4 isolation contracts", () => {
+ it("retains the historical v6, v5, and v4 isolation contracts", () => {
+ const {
+ releaseCheckGenerationIsolationEvidenceSha256:
+ _releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256:
+ _releaseCheckGenerationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ _releaseCheckGenerationIsolationVerifiedAt,
+ ...legacyV6
+ } = receipt();
+ expect(
+ DeploymentReceiptV6Schema.parse({
+ ...legacyV6,
+ schemaVersion: "6",
+ verifierVersion: "counterlab-deployment-v6",
+ }),
+ ).toMatchObject({ schemaVersion: "6" });
const {
generationIsolationEvidenceSha256: _generationIsolationEvidenceSha256,
generationIsolationProbeSha256: _generationIsolationProbeSha256,
generationIsolationVerifiedAt: _generationIsolationVerifiedAt,
...legacyV5
- } = receipt();
+ } = legacyV6;
expect(
DeploymentReceiptV5Schema.parse({
...legacyV5,
@@ -509,6 +553,12 @@ describe("deployment receipt schema", () => {
it("retains strict parsing for historical v3 deployment receipts", () => {
const {
+ releaseCheckGenerationIsolationEvidenceSha256:
+ _releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256:
+ _releaseCheckGenerationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ _releaseCheckGenerationIsolationVerifiedAt,
generationIsolationEvidenceSha256: _generationIsolationEvidenceSha256,
generationIsolationProbeSha256: _generationIsolationProbeSha256,
generationIsolationVerifiedAt: _generationIsolationVerifiedAt,
@@ -569,8 +619,13 @@ describe("deployment receipt schema", () => {
process.cwd(),
"scientific-engines/schemas/deployment-receipt-v5.schema.json",
);
+ const v7Path = resolve(
+ process.cwd(),
+ "scientific-engines/schemas/deployment-receipt-v7.schema.json",
+ );
expect(existsSync(v4Path)).toBe(true);
expect(existsSync(v5Path)).toBe(true);
+ expect(existsSync(v7Path)).toBe(true);
const v4 = JSON.parse(readFileSync(v4Path, "utf8")) as {
properties?: {
schemaVersion?: { const?: string };
@@ -580,6 +635,7 @@ describe("deployment receipt schema", () => {
required?: string[];
};
const v5 = JSON.parse(readFileSync(v5Path, "utf8")) as typeof v4;
+ const v7 = JSON.parse(readFileSync(v7Path, "utf8")) as typeof v4;
expect(v4.properties?.schemaVersion?.const).toBe("4");
expect(v4.properties?.generationFilesystemReadIsolation?.const).toBe(
"PARTIAL",
@@ -603,5 +659,13 @@ describe("deployment receipt schema", () => {
expect(v5.properties?.generationFilesystemReadIsolation?.const).toBe(
"OS_ENFORCED",
);
+ expect(v7.properties?.schemaVersion?.const).toBe("7");
+ expect(v7.required).toEqual(
+ expect.arrayContaining([
+ "releaseCheckGenerationIsolationEvidenceSha256",
+ "releaseCheckGenerationIsolationProbeSha256",
+ "releaseCheckGenerationIsolationVerifiedAt",
+ ]),
+ );
});
});
diff --git a/scientific-engines/schemas/deployment-receipt-v7.schema.json b/scientific-engines/schemas/deployment-receipt-v7.schema.json
new file mode 100644
index 00000000..eb886c58
--- /dev/null
+++ b/scientific-engines/schemas/deployment-receipt-v7.schema.json
@@ -0,0 +1,261 @@
+{
+ "$id": "https://counterlab.dev/schemas/deployment-receipt-v7.schema.json",
+ "title": "CounterLab deployment receipt v7",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "7"
+ },
+ "status": {
+ "type": "string",
+ "const": "DEPLOYED"
+ },
+ "workerName": {
+ "type": "string",
+ "const": "counterlab"
+ },
+ "productionOrigin": {
+ "type": "string",
+ "const": "https://counterlab.cserules.workers.dev"
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ },
+ "workerEvidenceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "runnerSourceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "qualifiedRunnerReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "releaseCheckReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "releaseCheckCheckedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "timeoutCleanupReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "aggregateLimitEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeToolchainSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimePolicySha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "proofDependencyManifestSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeAdapterSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "adapterImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "workerVersionId": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
+ },
+ "workerTag": {
+ "type": "string",
+ "pattern": "^git-[a-f0-9]{40}$"
+ },
+ "workerMessage": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 256
+ },
+ "containerApplicationId": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_-]{1,128}$"
+ },
+ "containerApplicationVersion": {
+ "type": "string",
+ "pattern": "^[1-9][0-9]*$"
+ },
+ "containerImage": {
+ "type": "string",
+ "pattern": "^registry\\.cloudflare\\.com\\/[A-Za-z0-9_-]{3,64}\\/counterlab-runner@sha256:[a-f0-9]{64}$"
+ },
+ "containerState": {
+ "type": "string",
+ "enum": [
+ "active",
+ "ready"
+ ]
+ },
+ "containerImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "workerArtifactClassification": {
+ "type": "string",
+ "const": "PROCESS_BOUND_PARTIAL"
+ },
+ "workerArtifactManifestSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "deployConfigSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "workerBundleSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "clientAssetsSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "clientAssetCount": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "maximum": 9007199254740991
+ },
+ "clientPublicAssetsSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "clientPublicAssetCount": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "maximum": 9007199254740991
+ },
+ "viteVersion": {
+ "type": "string",
+ "const": "8.1.4"
+ },
+ "wranglerVersion": {
+ "type": "string",
+ "const": "4.110.0"
+ },
+ "dryRunSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "dryRunFileCount": {
+ "type": "number",
+ "const": 1
+ },
+ "deploymentStatusSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "workerVersionSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "containerStatusSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "deployedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "verifierVersion": {
+ "type": "string",
+ "const": "counterlab-deployment-v7"
+ },
+ "generationIsolationEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "generationIsolationProbeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "generationIsolationVerifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "releaseCheckGenerationIsolationEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "releaseCheckGenerationIsolationProbeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "releaseCheckGenerationIsolationVerifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "status",
+ "workerName",
+ "productionOrigin",
+ "generationFilesystemReadIsolation",
+ "workerEvidenceCommit",
+ "runnerSourceCommit",
+ "qualifiedRunnerReceiptSha256",
+ "releaseCheckReceiptSha256",
+ "releaseCheckCheckedAt",
+ "timeoutCleanupReceiptSha256",
+ "aggregateLimitEvidenceSha256",
+ "runtimeToolchainSha256",
+ "runtimePolicySha256",
+ "proofDependencyManifestSha256",
+ "runtimeAdapterSha256",
+ "adapterImageDigest",
+ "workerVersionId",
+ "workerTag",
+ "workerMessage",
+ "containerApplicationId",
+ "containerApplicationVersion",
+ "containerImage",
+ "containerState",
+ "containerImageDigest",
+ "workerArtifactClassification",
+ "workerArtifactManifestSha256",
+ "deployConfigSha256",
+ "workerBundleSha256",
+ "clientAssetsSha256",
+ "clientAssetCount",
+ "clientPublicAssetsSha256",
+ "clientPublicAssetCount",
+ "viteVersion",
+ "wranglerVersion",
+ "dryRunSha256",
+ "dryRunFileCount",
+ "deploymentStatusSha256",
+ "workerVersionSha256",
+ "containerStatusSha256",
+ "deployedAt",
+ "verifierVersion",
+ "generationIsolationEvidenceSha256",
+ "generationIsolationProbeSha256",
+ "generationIsolationVerifiedAt",
+ "releaseCheckGenerationIsolationEvidenceSha256",
+ "releaseCheckGenerationIsolationProbeSha256",
+ "releaseCheckGenerationIsolationVerifiedAt"
+ ],
+ "additionalProperties": false
+}
diff --git a/scientific-engines/schemas/release-check-receipt-v5.schema.json b/scientific-engines/schemas/release-check-receipt-v5.schema.json
new file mode 100644
index 00000000..42c52466
--- /dev/null
+++ b/scientific-engines/schemas/release-check-receipt-v5.schema.json
@@ -0,0 +1,356 @@
+{
+ "$id": "https://counterlab.dev/schemas/release-check-receipt-v5.schema.json",
+ "title": "CounterLab release-check receipt v5",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "5"
+ },
+ "status": {
+ "type": "string",
+ "const": "PASSED"
+ },
+ "evidenceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "sourceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "qualifiedRunnerReceiptSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "qualifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "runnerImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-runner:git-[a-f0-9]{40}$"
+ },
+ "runnerImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "adapterImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-adapter:git-[a-f0-9]{40}$"
+ },
+ "adapterImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "registryDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "runtimeToolchainSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimePolicySha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "proofDependencyManifestSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "aggregateLimitEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "runtimeAdapterSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "checks": {
+ "minItems": 11,
+ "maxItems": 11,
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "enum": [
+ "test-all",
+ "leakage-mutations",
+ "imbalance-mutations",
+ "held-out",
+ "adapter-sandbox-smoke",
+ "scientific-engines",
+ "web-build",
+ "achieved-metrics",
+ "reproduce-leakage",
+ "replay-patch",
+ "secret-scan"
+ ]
+ },
+ "status": {
+ "type": "string",
+ "const": "PASSED"
+ }
+ },
+ "required": [
+ "id",
+ "status"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "checkedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "verifierVersion": {
+ "type": "string",
+ "const": "counterlab-release-check-v5"
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ },
+ "generationIsolationEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "generationIsolationProbeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "generationIsolationVerifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "releaseCheckGenerationIsolationEvidence": {
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "1"
+ },
+ "status": {
+ "type": "string",
+ "const": "VERIFIED"
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ },
+ "sourceCommit": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{40}$"
+ },
+ "sourceTreeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "localImageTag": {
+ "type": "string",
+ "pattern": "^counterlab-runner:git-[a-f0-9]{40}$"
+ },
+ "localImageDigest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "imageUser": {
+ "type": "string",
+ "const": "10001:10001"
+ },
+ "mountPolicyVersion": {
+ "type": "string",
+ "const": "counterlab-bwrap-mount-policy-v1"
+ },
+ "probePayload": {
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "const": "1"
+ },
+ "probeVersion": {
+ "type": "string",
+ "const": "counterlab-generation-isolation-v1"
+ },
+ "service": {
+ "type": "string",
+ "const": "counterlab-hosted-runner"
+ },
+ "probe": {
+ "type": "string",
+ "const": "non-root-startup"
+ },
+ "checks": {
+ "type": "array",
+ "prefixItems": [
+ {
+ "type": "string",
+ "const": "entrypoint"
+ },
+ {
+ "type": "string",
+ "const": "non-root-user"
+ },
+ {
+ "type": "string",
+ "const": "immutable-paths"
+ },
+ {
+ "type": "string",
+ "const": "codex"
+ },
+ {
+ "type": "string",
+ "const": "python"
+ },
+ {
+ "type": "string",
+ "const": "bubblewrap"
+ },
+ {
+ "type": "string",
+ "const": "bubblewrap-read-isolation"
+ },
+ {
+ "type": "string",
+ "const": "setpriv"
+ },
+ {
+ "type": "string",
+ "const": "writable-roots"
+ }
+ ]
+ },
+ "generationFilesystemReadIsolation": {
+ "type": "string",
+ "const": "OS_ENFORCED"
+ },
+ "bubblewrapVersion": {
+ "type": "string",
+ "const": "0.11.0"
+ },
+ "bubblewrap": {
+ "type": "object",
+ "properties": {
+ "forbiddenHostPathsHidden": {
+ "type": "boolean",
+ "const": true
+ },
+ "parentEnvironmentHidden": {
+ "type": "boolean",
+ "const": true
+ },
+ "workspaceVisible": {
+ "type": "boolean",
+ "const": true
+ },
+ "workspaceWritable": {
+ "type": "boolean",
+ "const": true
+ }
+ },
+ "required": [
+ "forbiddenHostPathsHidden",
+ "parentEnvironmentHidden",
+ "workspaceVisible",
+ "workspaceWritable"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "probeVersion",
+ "service",
+ "probe",
+ "checks",
+ "generationFilesystemReadIsolation",
+ "bubblewrapVersion",
+ "bubblewrap"
+ ],
+ "additionalProperties": false
+ },
+ "probePayloadSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "verifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ },
+ "verifierVersion": {
+ "type": "string",
+ "const": "counterlab-generation-isolation-evidence-v1"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "status",
+ "generationFilesystemReadIsolation",
+ "sourceCommit",
+ "sourceTreeSha256",
+ "localImageTag",
+ "localImageDigest",
+ "imageUser",
+ "mountPolicyVersion",
+ "probePayload",
+ "probePayloadSha256",
+ "verifiedAt",
+ "verifierVersion"
+ ],
+ "additionalProperties": false
+ },
+ "releaseCheckGenerationIsolationEvidenceSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "releaseCheckGenerationIsolationProbeSha256": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "releaseCheckGenerationIsolationVerifiedAt": {
+ "type": "string",
+ "format": "date-time",
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "status",
+ "evidenceCommit",
+ "sourceCommit",
+ "qualifiedRunnerReceiptSha256",
+ "qualifiedAt",
+ "runnerImageTag",
+ "runnerImageDigest",
+ "adapterImageTag",
+ "adapterImageDigest",
+ "registryDigest",
+ "runtimeToolchainSha256",
+ "runtimePolicySha256",
+ "proofDependencyManifestSha256",
+ "aggregateLimitEvidenceSha256",
+ "runtimeAdapterSha256",
+ "checks",
+ "checkedAt",
+ "verifierVersion",
+ "generationFilesystemReadIsolation",
+ "generationIsolationEvidenceSha256",
+ "generationIsolationProbeSha256",
+ "generationIsolationVerifiedAt",
+ "releaseCheckGenerationIsolationEvidence",
+ "releaseCheckGenerationIsolationEvidenceSha256",
+ "releaseCheckGenerationIsolationProbeSha256",
+ "releaseCheckGenerationIsolationVerifiedAt"
+ ],
+ "additionalProperties": false
+}
diff --git a/scripts/create-deployment-receipt.test.ts b/scripts/create-deployment-receipt.test.ts
index 07ca88cd..e621af21 100644
--- a/scripts/create-deployment-receipt.test.ts
+++ b/scripts/create-deployment-receipt.test.ts
@@ -9,6 +9,36 @@ import {
qualifiedContainerImage,
selectQualifiedContainer,
} from "./create-deployment-receipt";
+import { createGenerationIsolationEvidence } from "./generation-isolation-evidence";
+
+const generationIsolationProbePayload = {
+ schemaVersion: "1",
+ probeVersion: "counterlab-generation-isolation-v1",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: [
+ "entrypoint",
+ "non-root-user",
+ "immutable-paths",
+ "codex",
+ "python",
+ "bubblewrap",
+ "bubblewrap-read-isolation",
+ "setpriv",
+ "writable-roots",
+ ],
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ bubblewrapVersion: "0.11.0",
+ bubblewrap: {
+ forbiddenHostPathsHidden: true,
+ parentEnvironmentHidden: true,
+ workspaceVisible: true,
+ workspaceWritable: true,
+ },
+} as const;
+
+const generationIsolationProbeSha256 =
+ "700cc58bedc163846e3854415170f49f55da9fd3ba316cc4967747d5268199dc";
describe("deployment receipt frozen dry-run projection", () => {
const workerBundleSha256 = "a".repeat(64);
@@ -99,6 +129,9 @@ describe("deployment receipt Worker identity", () => {
runnerImageDigest: `sha256:${"c".repeat(64)}`,
generationIsolationEvidenceSha256: "5".repeat(64),
generationIsolationProbeSha256: "6".repeat(64),
+ releaseCheckGenerationIsolationEvidenceSha256: "7".repeat(64),
+ releaseCheckGenerationIsolationProbeSha256: "6".repeat(64),
+ releaseCheckGenerationIsolationVerifiedAt: "2026-07-19T00:02:00.000Z",
timeoutCleanupReceiptSha256: "d".repeat(64),
aggregateLimitEvidenceSha256: "9".repeat(64),
runtimePolicySha256: "e".repeat(64),
@@ -125,6 +158,18 @@ describe("deployment receipt Worker identity", () => {
"COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256",
expected.generationIsolationProbeSha256,
],
+ [
+ "COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256",
+ expected.releaseCheckGenerationIsolationEvidenceSha256,
+ ],
+ [
+ "COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256",
+ expected.releaseCheckGenerationIsolationProbeSha256,
+ ],
+ [
+ "COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT",
+ expected.releaseCheckGenerationIsolationVerifiedAt,
+ ],
[
"COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256",
expected.timeoutCleanupReceiptSha256,
@@ -162,7 +207,7 @@ describe("deployment receipt Worker identity", () => {
["COUNTERLAB_MAINTENANCE_MODE", "false"],
].map(([name, text]) => ({ name, text, type: "plain_text" }));
- it("requires direct active bindings for both qualification hashes", () => {
+ it("requires direct active bindings for qualification and release-check evidence", () => {
expect(() =>
assertActiveWorkerReleaseBindings(bindings, expected),
).not.toThrow();
@@ -171,6 +216,9 @@ describe("deployment receipt Worker identity", () => {
"COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256",
"COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256",
"COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256",
+ "COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256",
+ "COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256",
+ "COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT",
"COUNTERLAB_RUNTIME_POLICY_SHA256",
"COUNTERLAB_PROOF_DEPENDENCY_MANIFEST_SHA256",
"COUNTERLAB_WORKER_ARTIFACT_MANIFEST_SHA256",
@@ -201,6 +249,22 @@ describe("deployment receipt Worker identity", () => {
).toThrow(new RegExp(name, "u"));
}
});
+
+ it("rejects duplicate release bindings even when one value matches", () => {
+ expect(() =>
+ assertActiveWorkerReleaseBindings(
+ [
+ ...bindings,
+ {
+ name: "COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256",
+ text: expected.releaseCheckGenerationIsolationEvidenceSha256,
+ type: "plain_text",
+ },
+ ],
+ expected,
+ ),
+ ).toThrow(/COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256/u);
+ });
});
describe("deployment receipt qualification binding", () => {
@@ -209,6 +273,7 @@ describe("deployment receipt qualification binding", () => {
generationFilesystemReadIsolation: "OS_ENFORCED",
evidenceCommit: "a".repeat(40),
sourceCommit: "b".repeat(40),
+ sourceTreeSha256: "8".repeat(64),
registryDigest: `sha256:${"c".repeat(64)}`,
qualifiedAt: "2026-07-19T00:01:00.000Z",
localImageTag: `counterlab-runner:git-${"b".repeat(40)}`,
@@ -221,9 +286,26 @@ describe("deployment receipt qualification binding", () => {
runtimeAdapterSha256: "4".repeat(64),
aggregateLimitEvidenceSha256: "5".repeat(64),
generationIsolationEvidenceSha256: "6".repeat(64),
- generationIsolationProbeSha256: "7".repeat(64),
+ generationIsolationProbeSha256,
generationIsolationVerifiedAt: "2026-07-19T00:00:30.000Z",
} as const;
+ const freshIsolation = createGenerationIsolationEvidence({
+ sourceCommit: qualified.sourceCommit,
+ sourceTreeSha256: qualified.sourceTreeSha256,
+ localImageTag: qualified.localImageTag,
+ localImageDigest: qualified.localImageDigest,
+ imageUser: "10001:10001",
+ verifiedAt: "2026-07-19T00:02:00.000Z",
+ startupProbe: {
+ status: "ready",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: generationIsolationProbePayload.checks,
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationProbe: generationIsolationProbePayload,
+ generationIsolationProbeSha256,
+ },
+ });
const releaseCheck = {
generationFilesystemReadIsolation:
qualified.generationFilesystemReadIsolation,
@@ -247,6 +329,12 @@ describe("deployment receipt qualification binding", () => {
qualified.generationIsolationEvidenceSha256,
generationIsolationProbeSha256: qualified.generationIsolationProbeSha256,
generationIsolationVerifiedAt: qualified.generationIsolationVerifiedAt,
+ releaseCheckGenerationIsolationEvidence: freshIsolation.evidence,
+ releaseCheckGenerationIsolationEvidenceSha256:
+ freshIsolation.evidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256: freshIsolation.probeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ freshIsolation.evidence.verifiedAt,
} as const;
const common = {
qualified,
@@ -269,6 +357,18 @@ describe("deployment receipt qualification binding", () => {
["generationIsolationEvidenceSha256", /generation isolation evidence/u],
["generationIsolationProbeSha256", /generation isolation probe/u],
["generationIsolationVerifiedAt", /generation isolation verification/u],
+ [
+ "releaseCheckGenerationIsolationEvidenceSha256",
+ /Generation-isolation evidence hash mismatch/u,
+ ],
+ [
+ "releaseCheckGenerationIsolationProbeSha256",
+ /release-check generation isolation probe|probeSha256 binding mismatch/u,
+ ],
+ [
+ "releaseCheckGenerationIsolationVerifiedAt",
+ /Generation-isolation verifiedAt binding mismatch/u,
+ ],
] as const) {
expect(() =>
assertDeploymentReceiptBindings({
diff --git a/scripts/create-deployment-receipt.ts b/scripts/create-deployment-receipt.ts
index d1281e4f..2fdee7f8 100644
--- a/scripts/create-deployment-receipt.ts
+++ b/scripts/create-deployment-receipt.ts
@@ -4,15 +4,16 @@ import { realpath, stat, writeFile } from "node:fs/promises";
import { dirname, isAbsolute, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
-import {
- DeploymentReceiptV6Schema,
- ReleaseCheckReceiptV4Schema,
-} from "../packages/scientific-engine-registry/src/index.js";
+import { DeploymentReceiptV7Schema } from "../packages/scientific-engine-registry/src/index.js";
import type {
QualifiedRunnerReleaseV6,
- ReleaseCheckReceiptV4,
+ ReleaseCheckReceiptV5,
} from "../packages/scientific-engine-registry/src/index.js";
-import { parseQualifiedRunnerReleaseV6 } from "./generation-isolation-evidence.js";
+import {
+ parseQualifiedRunnerReleaseV6,
+ verifyGenerationIsolationEvidence,
+} from "./generation-isolation-evidence.js";
+import { parseReleaseCheckReceiptV5 } from "./release-check-receipt.js";
import {
collectFrozenClientAssets,
verifyFrozenWorkerReleaseManifest,
@@ -224,6 +225,9 @@ export function assertActiveWorkerReleaseBindings(
runnerImageDigest: string;
generationIsolationEvidenceSha256: string;
generationIsolationProbeSha256: string;
+ releaseCheckGenerationIsolationEvidenceSha256: string;
+ releaseCheckGenerationIsolationProbeSha256: string;
+ releaseCheckGenerationIsolationVerifiedAt: string;
timeoutCleanupReceiptSha256: string;
aggregateLimitEvidenceSha256: string;
runtimePolicySha256: string;
@@ -250,6 +254,12 @@ export function assertActiveWorkerReleaseBindings(
expected.generationIsolationEvidenceSha256,
COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256:
expected.generationIsolationProbeSha256,
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256:
+ expected.releaseCheckGenerationIsolationEvidenceSha256,
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256:
+ expected.releaseCheckGenerationIsolationProbeSha256,
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT:
+ expected.releaseCheckGenerationIsolationVerifiedAt,
COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256:
expected.timeoutCleanupReceiptSha256,
COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256:
@@ -273,10 +283,12 @@ export function assertActiveWorkerReleaseBindings(
COUNTERLAB_MAINTENANCE_MODE: "false",
} as const;
for (const [name, value] of Object.entries(expectedBindings)) {
- const binding = (bindings as Array>).find(
+ const matchingBindings = (
+ bindings as Array>
+ ).filter(
(candidate) => candidate.name === name && candidate.type === "plain_text",
);
- if (binding?.text !== value) {
+ if (matchingBindings.length !== 1 || matchingBindings[0]?.text !== value) {
throw new Error(`active Worker binding ${name} is not release-bound`);
}
}
@@ -286,6 +298,7 @@ type QualifiedDeploymentIdentity = Pick<
QualifiedRunnerReleaseV6,
| "evidenceCommit"
| "sourceCommit"
+ | "sourceTreeSha256"
| "registryDigest"
| "qualifiedAt"
| "localImageTag"
@@ -303,7 +316,7 @@ type QualifiedDeploymentIdentity = Pick<
>;
type ReleaseCheckDeploymentIdentity = Pick<
- ReleaseCheckReceiptV4,
+ ReleaseCheckReceiptV5,
| "evidenceCommit"
| "sourceCommit"
| "qualifiedRunnerReceiptSha256"
@@ -321,6 +334,10 @@ type ReleaseCheckDeploymentIdentity = Pick<
| "generationIsolationEvidenceSha256"
| "generationIsolationProbeSha256"
| "generationIsolationVerifiedAt"
+ | "releaseCheckGenerationIsolationEvidence"
+ | "releaseCheckGenerationIsolationEvidenceSha256"
+ | "releaseCheckGenerationIsolationProbeSha256"
+ | "releaseCheckGenerationIsolationVerifiedAt"
>;
export function assertDeploymentReceiptBindings(input: {
@@ -331,6 +348,19 @@ export function assertDeploymentReceiptBindings(input: {
sourceCommit: string;
registryDigest: string;
}): void {
+ verifyGenerationIsolationEvidence({
+ evidence: input.releaseCheck.releaseCheckGenerationIsolationEvidence,
+ evidenceSha256:
+ input.releaseCheck.releaseCheckGenerationIsolationEvidenceSha256,
+ expected: {
+ sourceCommit: input.qualified.sourceCommit,
+ sourceTreeSha256: input.qualified.sourceTreeSha256,
+ localImageTag: input.qualified.localImageTag,
+ localImageDigest: input.qualified.localImageDigest,
+ probeSha256: input.qualified.generationIsolationProbeSha256,
+ verifiedAt: input.releaseCheck.releaseCheckGenerationIsolationVerifiedAt,
+ },
+ });
const comparisons: ReadonlyArray = [
[
"qualified evidence commit",
@@ -382,6 +412,11 @@ export function assertDeploymentReceiptBindings(input: {
input.releaseCheck.generationIsolationVerifiedAt,
input.qualified.generationIsolationVerifiedAt,
],
+ [
+ "release-check generation isolation probe",
+ input.releaseCheck.releaseCheckGenerationIsolationProbeSha256,
+ input.qualified.generationIsolationProbeSha256,
+ ],
[
"runner image tag",
input.releaseCheck.runnerImageTag,
@@ -504,6 +539,10 @@ async function main(): Promise {
const qualified = parseQualifiedRunnerReleaseV6(
JSON.parse(qualifiedBytes.toString("utf8")) as unknown,
);
+ const releaseCheckBytes = readFileSync(paths.releaseCheck);
+ const releaseCheck = parseReleaseCheckReceiptV5(
+ JSON.parse(releaseCheckBytes.toString("utf8")) as unknown,
+ );
assertActiveWorkerReleaseBindings(bindings, {
workerEvidenceCommit: args["--evidence-commit"],
runnerSourceCommit: args["--source-commit"],
@@ -511,6 +550,12 @@ async function main(): Promise {
generationIsolationEvidenceSha256:
qualified.generationIsolationEvidenceSha256,
generationIsolationProbeSha256: qualified.generationIsolationProbeSha256,
+ releaseCheckGenerationIsolationEvidenceSha256:
+ releaseCheck.releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256:
+ releaseCheck.releaseCheckGenerationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ releaseCheck.releaseCheckGenerationIsolationVerifiedAt,
timeoutCleanupReceiptSha256: qualified.timeoutCleanupReceiptSha256,
aggregateLimitEvidenceSha256: qualified.aggregateLimitEvidenceSha256,
runtimePolicySha256: qualified.runtimePolicySha256,
@@ -536,10 +581,6 @@ async function main(): Promise {
throw new Error("active Worker lacks version metadata");
}
- const releaseCheckBytes = readFileSync(paths.releaseCheck);
- const releaseCheck = ReleaseCheckReceiptV4Schema.parse(
- JSON.parse(releaseCheckBytes.toString("utf8")) as unknown,
- );
assertDeploymentReceiptBindings({
qualified,
qualifiedReceiptBytes: qualifiedBytes,
@@ -588,8 +629,8 @@ async function main(): Promise {
}
const dryRun = dryRunWorkerHash(root, paths.dryRun, workerBundleSha256);
const configBytes = readFileSync(paths.config);
- const receipt = DeploymentReceiptV6Schema.parse({
- schemaVersion: "6",
+ const receipt = DeploymentReceiptV7Schema.parse({
+ schemaVersion: "7",
status: "DEPLOYED",
workerName: "counterlab",
productionOrigin: "https://counterlab.cserules.workers.dev",
@@ -599,6 +640,12 @@ async function main(): Promise {
releaseCheck.generationIsolationEvidenceSha256,
generationIsolationProbeSha256: releaseCheck.generationIsolationProbeSha256,
generationIsolationVerifiedAt: releaseCheck.generationIsolationVerifiedAt,
+ releaseCheckGenerationIsolationEvidenceSha256:
+ releaseCheck.releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256:
+ releaseCheck.releaseCheckGenerationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ releaseCheck.releaseCheckGenerationIsolationVerifiedAt,
workerEvidenceCommit: args["--evidence-commit"],
runnerSourceCommit: args["--source-commit"],
qualifiedRunnerReceiptSha256: sha256(qualifiedBytes),
@@ -635,7 +682,7 @@ async function main(): Promise {
workerVersionSha256: sha256(versionBytes),
containerStatusSha256: sha256(containersBytes),
deployedAt: new Date().toISOString(),
- verifierVersion: "counterlab-deployment-v6",
+ verifierVersion: "counterlab-deployment-v7",
});
await writeFile(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`, {
encoding: "utf8",
diff --git a/scripts/deploy-qualified.sh b/scripts/deploy-qualified.sh
index 710e9227..08c1d6c5 100755
--- a/scripts/deploy-qualified.sh
+++ b/scripts/deploy-qualified.sh
@@ -210,7 +210,45 @@ process.stdout.write(identity.receiptSha256);
echo "Qualified receipt bytes changed after identity validation." >&2
exit 2
}
-RELEASE_CHECK_RECEIPT_SHA256="$(sha256sum "${RELEASE_CHECK_RECEIPT}" | cut -d ' ' -f 1)"
+RELEASE_CHECK_IDENTITY_JSON="$(
+ node --import tsx scripts/release-check-receipt.ts \
+ release-check-identity \
+ --release-check "${RELEASE_CHECK_RECEIPT}"
+)"
+release_check_identity_value() {
+ local field="$1"
+ node -e '
+const [raw, field] = process.argv.slice(1);
+const identity = JSON.parse(raw);
+const outerFields = ["identitySchemaVersion", "receipt", "receiptSha256", "receiptType"];
+if (
+ Object.keys(identity).sort().join("\n") !== outerFields.sort().join("\n") ||
+ identity.identitySchemaVersion !== "1" ||
+ identity.receiptType !== "release-check-receipt" ||
+ !/^[a-f0-9]{64}$/.test(identity.receiptSha256)
+) throw new Error("release-check identity envelope is invalid");
+if (!Object.hasOwn(identity.receipt, field) || typeof identity.receipt[field] !== "string") {
+ throw new Error(`release-check identity field is unavailable: ${field}`);
+}
+process.stdout.write(identity.receipt[field]);
+' "${RELEASE_CHECK_IDENTITY_JSON}" "${field}"
+}
+RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256="$(release_check_identity_value releaseCheckGenerationIsolationEvidenceSha256)"
+RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256="$(release_check_identity_value releaseCheckGenerationIsolationProbeSha256)"
+RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT="$(release_check_identity_value releaseCheckGenerationIsolationVerifiedAt)"
+RELEASE_CHECK_RECEIPT_SHA256="$(
+ node -e '
+const identity = JSON.parse(process.argv[1]);
+if (identity?.identitySchemaVersion !== "1" || !/^[a-f0-9]{64}$/.test(identity?.receiptSha256)) {
+ throw new Error("release-check receipt byte hash is unavailable");
+}
+process.stdout.write(identity.receiptSha256);
+' "${RELEASE_CHECK_IDENTITY_JSON}"
+)"
+[[ "$(sha256sum "${RELEASE_CHECK_RECEIPT}" | cut -d ' ' -f 1)" == "${RELEASE_CHECK_RECEIPT_SHA256}" ]] || {
+ echo "Release-check receipt bytes changed after identity validation." >&2
+ exit 2
+}
CONTAINER_APPLICATION_NAME="counterlab-counterlabrunner"
QUALIFIED_CONTAINER_IMAGE="${IMAGE%:git-*}@${REGISTRY_DIGEST}"
WORKER_TAG="git-${EVIDENCE_COMMIT}"
@@ -414,33 +452,64 @@ wait_for_maintenance_health() {
if "${CURL_BIN}" --silent --show-error --fail-with-body \
--connect-timeout 10 --max-time 20 \
--output "${candidate}" "${PRODUCTION_ORIGIN}/api/health" &&
- node - "${candidate}" "${EVIDENCE_COMMIT}" "${SOURCE_COMMIT}" "${REGISTRY_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${expected_version}" "${GENERATION_ISOLATION_EVIDENCE_SHA256}" "${GENERATION_ISOLATION_PROBE_SHA256}" <<'NODE'
+ node - "${candidate}" "${EVIDENCE_COMMIT}" "${SOURCE_COMMIT}" "${REGISTRY_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${expected_version}" "${GENERATION_ISOLATION_EVIDENCE_SHA256}" "${GENERATION_ISOLATION_PROBE_SHA256}" "${RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256}" "${RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256}" "${RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT}" <<'NODE'
const fs = require("node:fs");
-const [path, evidenceCommit, sourceCommit, digest, timeoutReceipt, aggregateEvidence, runtimePolicy, proofManifest, artifactClassification, artifactManifest, workerBundle, clientAssets, clientAssetCount, publicAssets, publicAssetCount, viteVersion, wranglerVersion, expectedVersion, isolationEvidence, isolationProbe] = process.argv.slice(2);
+const [path, evidenceCommit, sourceCommit, digest, timeoutReceipt, aggregateEvidence, runtimePolicy, proofManifest, artifactClassification, artifactManifest, workerBundle, clientAssets, clientAssetCount, publicAssets, publicAssetCount, viteVersion, wranglerVersion, expectedVersion, isolationEvidence, isolationProbe, releaseCheckIsolationEvidence, releaseCheckIsolationProbe, releaseCheckIsolationVerifiedAt] = process.argv.slice(2);
const payload = JSON.parse(fs.readFileSync(path, "utf8"));
const data = payload?.ok === true ? payload.data : undefined;
+const exactKeys = (value, keys) =>
+ value !== null && typeof value === "object" && !Array.isArray(value) &&
+ JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...keys].sort());
+const expectedRelease = {
+ status: "bound",
+ workerVersionId: expectedVersion,
+ workerVersionTag: `git-${evidenceCommit}`,
+ workerEvidenceCommit: evidenceCommit,
+ runnerSourceCommit: sourceCommit,
+ runnerImageDigest: digest,
+ generationIsolationEvidenceSha256: isolationEvidence,
+ generationIsolationProbeSha256: isolationProbe,
+ releaseCheckGenerationIsolationEvidenceSha256: releaseCheckIsolationEvidence,
+ releaseCheckGenerationIsolationProbeSha256: releaseCheckIsolationProbe,
+ releaseCheckGenerationIsolationVerifiedAt: releaseCheckIsolationVerifiedAt,
+ timeoutCleanupReceiptSha256: timeoutReceipt,
+ aggregateLimitEvidenceSha256: aggregateEvidence,
+ runtimePolicySha256: runtimePolicy,
+ proofDependencyManifestSha256: proofManifest,
+ workerArtifactClassification: artifactClassification,
+ workerArtifactManifestSha256: artifactManifest,
+ workerBundleSha256: workerBundle,
+ clientAssetsSha256: clientAssets,
+ clientAssetCount: Number(clientAssetCount),
+ clientPublicAssetsSha256: publicAssets,
+ clientPublicAssetCount: Number(publicAssetCount),
+ viteVersion,
+ wranglerVersion,
+};
+const expectedData = {
+ platform: "cloudflare-workers",
+ sample: "available",
+ replay: "available",
+ liveGpt: "configured",
+ liveCodex: "configured",
+ liveKernel: "configured",
+ maintenance: true,
+ readiness: "not-checked",
+ release: expectedRelease,
+ sandbox: "credential-and-privilege-boundary",
+ generationFilesystemReadIsolation: "PARTIAL",
+};
if (
- data?.maintenance !== true ||
- data?.release?.status !== "bound" ||
- data.release.workerVersionId !== expectedVersion ||
- data.release.workerEvidenceCommit !== evidenceCommit ||
- data.release.runnerSourceCommit !== sourceCommit ||
- data.release.runnerImageDigest !== digest ||
- data.release.generationIsolationEvidenceSha256 !== isolationEvidence ||
- data.release.generationIsolationProbeSha256 !== isolationProbe ||
- data.release.timeoutCleanupReceiptSha256 !== timeoutReceipt ||
- data.release.aggregateLimitEvidenceSha256 !== aggregateEvidence ||
- data.release.runtimePolicySha256 !== runtimePolicy ||
- data.release.proofDependencyManifestSha256 !== proofManifest ||
- data.release.workerArtifactClassification !== artifactClassification ||
- data.release.workerArtifactManifestSha256 !== artifactManifest ||
- data.release.workerBundleSha256 !== workerBundle ||
- data.release.clientAssetsSha256 !== clientAssets ||
- data.release.clientAssetCount !== Number(clientAssetCount) ||
- data.release.clientPublicAssetsSha256 !== publicAssets ||
- data.release.clientPublicAssetCount !== Number(publicAssetCount) ||
- data.release.viteVersion !== viteVersion ||
- data.release.wranglerVersion !== wranglerVersion
+ !exactKeys(payload, ["ok", "data"]) ||
+ !exactKeys(data, [...Object.keys(expectedData), "requestId"]) ||
+ !exactKeys(data?.release, Object.keys(expectedRelease)) ||
+ typeof data?.requestId !== "string" ||
+ data.requestId.length === 0 ||
+ Object.entries(expectedData).some(([key, value]) =>
+ key === "release"
+ ? Object.entries(expectedRelease).some(([releaseKey, releaseValue]) => data.release?.[releaseKey] !== releaseValue)
+ : data?.[key] !== value,
+ )
) process.exit(1);
NODE
then
@@ -466,33 +535,58 @@ wait_for_final_readiness() {
if "${CURL_BIN}" --silent --show-error --fail-with-body \
--connect-timeout 10 --max-time 20 \
--output "${candidate}" "${PRODUCTION_ORIGIN}/ready" &&
- node - "${candidate}" "${EVIDENCE_COMMIT}" "${SOURCE_COMMIT}" "${REGISTRY_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${expected_version}" "${GENERATION_ISOLATION_EVIDENCE_SHA256}" "${GENERATION_ISOLATION_PROBE_SHA256}" <<'NODE'
+ node - "${candidate}" "${EVIDENCE_COMMIT}" "${SOURCE_COMMIT}" "${REGISTRY_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${expected_version}" "${GENERATION_ISOLATION_EVIDENCE_SHA256}" "${GENERATION_ISOLATION_PROBE_SHA256}" "${RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256}" "${RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256}" "${RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT}" <<'NODE'
const fs = require("node:fs");
-const [path, evidenceCommit, sourceCommit, digest, timeoutReceipt, aggregateEvidence, runtimePolicy, proofManifest, artifactClassification, artifactManifest, workerBundle, clientAssets, clientAssetCount, publicAssets, publicAssetCount, viteVersion, wranglerVersion, expectedVersion, isolationEvidence, isolationProbe] = process.argv.slice(2);
+const [path, evidenceCommit, sourceCommit, digest, timeoutReceipt, aggregateEvidence, runtimePolicy, proofManifest, artifactClassification, artifactManifest, workerBundle, clientAssets, clientAssetCount, publicAssets, publicAssetCount, viteVersion, wranglerVersion, expectedVersion, isolationEvidence, isolationProbe, releaseCheckIsolationEvidence, releaseCheckIsolationProbe, releaseCheckIsolationVerifiedAt] = process.argv.slice(2);
const payload = JSON.parse(fs.readFileSync(path, "utf8"));
+const exactKeys = (value, keys) =>
+ value !== null && typeof value === "object" && !Array.isArray(value) &&
+ JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...keys].sort());
+const expectedChecks = {
+ admission: true,
+ analyst: true,
+ maintenance: true,
+ persistence: true,
+ privateStorage: true,
+ releaseIdentity: true,
+ runner: true,
+ signing: true,
+};
+const expectedRelease = {
+ status: "bound",
+ workerVersionId: expectedVersion,
+ workerVersionTag: `git-${evidenceCommit}`,
+ workerEvidenceCommit: evidenceCommit,
+ runnerSourceCommit: sourceCommit,
+ runnerImageDigest: digest,
+ generationIsolationEvidenceSha256: isolationEvidence,
+ generationIsolationProbeSha256: isolationProbe,
+ releaseCheckGenerationIsolationEvidenceSha256: releaseCheckIsolationEvidence,
+ releaseCheckGenerationIsolationProbeSha256: releaseCheckIsolationProbe,
+ releaseCheckGenerationIsolationVerifiedAt: releaseCheckIsolationVerifiedAt,
+ timeoutCleanupReceiptSha256: timeoutReceipt,
+ aggregateLimitEvidenceSha256: aggregateEvidence,
+ runtimePolicySha256: runtimePolicy,
+ proofDependencyManifestSha256: proofManifest,
+ workerArtifactClassification: artifactClassification,
+ workerArtifactManifestSha256: artifactManifest,
+ workerBundleSha256: workerBundle,
+ clientAssetsSha256: clientAssets,
+ clientAssetCount: Number(clientAssetCount),
+ clientPublicAssetsSha256: publicAssets,
+ clientPublicAssetCount: Number(publicAssetCount),
+ viteVersion,
+ wranglerVersion,
+};
if (
- payload?.status !== "ready" ||
- payload?.maintenance !== false ||
- payload?.release?.status !== "bound" ||
- payload.release.workerVersionId !== expectedVersion ||
- payload.release.workerEvidenceCommit !== evidenceCommit ||
- payload.release.runnerSourceCommit !== sourceCommit ||
- payload.release.runnerImageDigest !== digest ||
- payload.release.generationIsolationEvidenceSha256 !== isolationEvidence ||
- payload.release.generationIsolationProbeSha256 !== isolationProbe ||
- payload.release.timeoutCleanupReceiptSha256 !== timeoutReceipt ||
- payload.release.aggregateLimitEvidenceSha256 !== aggregateEvidence ||
- payload.release.runtimePolicySha256 !== runtimePolicy ||
- payload.release.proofDependencyManifestSha256 !== proofManifest ||
- payload.release.workerArtifactClassification !== artifactClassification ||
- payload.release.workerArtifactManifestSha256 !== artifactManifest ||
- payload.release.workerBundleSha256 !== workerBundle ||
- payload.release.clientAssetsSha256 !== clientAssets ||
- payload.release.clientAssetCount !== Number(clientAssetCount) ||
- payload.release.clientPublicAssetsSha256 !== publicAssets ||
- payload.release.clientPublicAssetCount !== Number(publicAssetCount) ||
- payload.release.viteVersion !== viteVersion ||
- payload.release.wranglerVersion !== wranglerVersion
+ !exactKeys(payload, ["status", "service", "checks", "maintenance", "release"]) ||
+ payload.status !== "ready" ||
+ payload.service !== "counterlab-control-plane" ||
+ payload.maintenance !== false ||
+ !exactKeys(payload.checks, Object.keys(expectedChecks)) ||
+ Object.entries(expectedChecks).some(([key, value]) => payload.checks?.[key] !== value) ||
+ !exactKeys(payload.release, Object.keys(expectedRelease)) ||
+ Object.entries(expectedRelease).some(([key, value]) => payload.release?.[key] !== value)
) process.exit(1);
NODE
then
diff --git a/scripts/generate-contract-schemas.ts b/scripts/generate-contract-schemas.ts
index 1d3bd40f..39d2d7a3 100644
--- a/scripts/generate-contract-schemas.ts
+++ b/scripts/generate-contract-schemas.ts
@@ -22,6 +22,7 @@ import {
DeploymentReceiptV4Schema,
DeploymentReceiptV5Schema,
DeploymentReceiptV6Schema,
+ DeploymentReceiptV7Schema,
GenerationIsolationEvidenceV1Schema,
QualifiedRunnerReleaseV2Schema,
QualifiedRunnerReleaseV3Schema,
@@ -32,6 +33,7 @@ import {
ReleaseCheckReceiptV2Schema,
ReleaseCheckReceiptV3Schema,
ReleaseCheckReceiptV4Schema,
+ ReleaseCheckReceiptV5Schema,
ScientificEngineEvidenceCatalogSchema,
ScientificEngineRegistrySchema,
ScientificEngineRuntimeManifestSchema,
@@ -390,6 +392,18 @@ const schemas = [
),
],
},
+ {
+ fileName: "release-check-receipt-v5.schema.json",
+ id: "https://counterlab.dev/schemas/release-check-receipt-v5.schema.json",
+ title: "CounterLab release-check receipt v5",
+ schema: ReleaseCheckReceiptV5Schema,
+ destinations: [
+ resolve(
+ root,
+ "scientific-engines/schemas/release-check-receipt-v5.schema.json",
+ ),
+ ],
+ },
{
fileName: "deployment-receipt-v3.schema.json",
id: "https://counterlab.dev/schemas/deployment-receipt-v3.schema.json",
@@ -438,6 +452,18 @@ const schemas = [
),
],
},
+ {
+ fileName: "deployment-receipt-v7.schema.json",
+ id: "https://counterlab.dev/schemas/deployment-receipt-v7.schema.json",
+ title: "CounterLab deployment receipt v7",
+ schema: DeploymentReceiptV7Schema,
+ destinations: [
+ resolve(
+ root,
+ "scientific-engines/schemas/deployment-receipt-v7.schema.json",
+ ),
+ ],
+ },
];
for (const definition of schemas) {
diff --git a/scripts/prepare-qualified-deploy.ts b/scripts/prepare-qualified-deploy.ts
index 9a22e277..4878ff4d 100644
--- a/scripts/prepare-qualified-deploy.ts
+++ b/scripts/prepare-qualified-deploy.ts
@@ -16,7 +16,10 @@ import {
parseQualifiedRunnerReleaseV6,
verifyGenerationIsolationEvidence,
} from "./generation-isolation-evidence.js";
-import { assertReleaseCheckBinding } from "./release-check-receipt.js";
+import {
+ assertReleaseCheckBinding,
+ parseReleaseCheckReceiptV5,
+} from "./release-check-receipt.js";
import {
containedRuntimeAdapterArguments,
requireContainedRuntimeSessionId,
@@ -1109,11 +1112,25 @@ function assertProductionBindings(config: Record): void {
export function qualifiedDeployConfig(input: {
config: unknown;
receipt: unknown;
+ releaseCheckReceipt: unknown;
image: string;
observation: QualifiedReleaseObservation;
}): Record {
const config = canonicalReleaseConfig(input.config);
const receipt = parseQualifiedRunnerReleaseV6(input.receipt);
+ const releaseCheck = parseReleaseCheckReceiptV5(input.releaseCheckReceipt);
+ verifyGenerationIsolationEvidence({
+ evidence: releaseCheck.releaseCheckGenerationIsolationEvidence,
+ evidenceSha256: releaseCheck.releaseCheckGenerationIsolationEvidenceSha256,
+ expected: {
+ sourceCommit: receipt.sourceCommit,
+ sourceTreeSha256: receipt.sourceTreeSha256,
+ localImageTag: receipt.localImageTag,
+ localImageDigest: receipt.localImageDigest,
+ probeSha256: receipt.generationIsolationProbeSha256,
+ verifiedAt: releaseCheck.releaseCheckGenerationIsolationVerifiedAt,
+ },
+ });
const comparisons: Array<[string, string, string]> = [
["source commit", receipt.sourceCommit, input.observation.sourceCommit],
[
@@ -1276,6 +1293,31 @@ export function qualifiedDeployConfig(input: {
receipt.generationIsolationVerifiedAt,
input.observation.generationIsolationVerifiedAt,
],
+ [
+ "release-check evidence commit",
+ releaseCheck.evidenceCommit,
+ receipt.evidenceCommit,
+ ],
+ [
+ "release-check source commit",
+ releaseCheck.sourceCommit,
+ receipt.sourceCommit,
+ ],
+ [
+ "release-check qualification isolation evidence",
+ releaseCheck.generationIsolationEvidenceSha256,
+ receipt.generationIsolationEvidenceSha256,
+ ],
+ [
+ "release-check qualification isolation probe",
+ releaseCheck.generationIsolationProbeSha256,
+ receipt.generationIsolationProbeSha256,
+ ],
+ [
+ "release-check isolation probe",
+ releaseCheck.releaseCheckGenerationIsolationProbeSha256,
+ receipt.generationIsolationProbeSha256,
+ ],
["registry image", receipt.registryImage, input.observation.registryImage],
[
"registry digest",
@@ -1387,6 +1429,12 @@ export function qualifiedDeployConfig(input: {
receipt.generationIsolationEvidenceSha256,
COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256:
receipt.generationIsolationProbeSha256,
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256:
+ releaseCheck.releaseCheckGenerationIsolationEvidenceSha256,
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256:
+ releaseCheck.releaseCheckGenerationIsolationProbeSha256,
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT:
+ releaseCheck.releaseCheckGenerationIsolationVerifiedAt,
COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256:
receipt.timeoutCleanupReceiptSha256,
COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256:
@@ -1493,6 +1541,7 @@ async function main(): Promise {
qualifiedDeployConfig({
config: JSON.parse(configText) as unknown,
receipt,
+ releaseCheckReceipt: JSON.parse(releaseCheckReceiptText) as unknown,
image: args.image,
observation,
}),
diff --git a/scripts/production-smoke.sh b/scripts/production-smoke.sh
index 32496fbc..d95098dc 100755
--- a/scripts/production-smoke.sh
+++ b/scripts/production-smoke.sh
@@ -98,7 +98,7 @@ esac
if [[ -z "${COUNTERLAB_DEPLOYMENT_RECEIPT:-}" ]]; then
echo "Production smoke requires COUNTERLAB_DEPLOYMENT_RECEIPT." >&2
- echo "Use the schema-v6 receipt written by scripts/deploy-qualified.sh." >&2
+ echo "Use the schema-v7 receipt written by scripts/deploy-qualified.sh." >&2
exit 2
fi
DEPLOYMENT_RECEIPT="$(repo_path "${COUNTERLAB_DEPLOYMENT_RECEIPT}")"
@@ -159,6 +159,9 @@ PROOF_DEPENDENCY_MANIFEST_SHA256="$(deployment_identity_value proofDependencyMan
GENERATION_ISOLATION_EVIDENCE_SHA256="$(deployment_identity_value generationIsolationEvidenceSha256)"
GENERATION_ISOLATION_PROBE_SHA256="$(deployment_identity_value generationIsolationProbeSha256)"
GENERATION_ISOLATION_VERIFIED_AT="$(deployment_identity_value generationIsolationVerifiedAt)"
+RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256="$(deployment_identity_value releaseCheckGenerationIsolationEvidenceSha256)"
+RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256="$(deployment_identity_value releaseCheckGenerationIsolationProbeSha256)"
+RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT="$(deployment_identity_value releaseCheckGenerationIsolationVerifiedAt)"
WORKER_ARTIFACT_CLASSIFICATION="$(deployment_identity_value workerArtifactClassification)"
WORKER_ARTIFACT_MANIFEST_SHA256="$(deployment_identity_value workerArtifactManifestSha256)"
WORKER_BUNDLE_SHA256="$(deployment_identity_value workerBundleSha256)"
@@ -407,6 +410,9 @@ init_args+=(--proof-dependency-manifest-sha256 "${PROOF_DEPENDENCY_MANIFEST_SHA2
init_args+=(--generation-isolation-evidence-sha256 "${GENERATION_ISOLATION_EVIDENCE_SHA256}")
init_args+=(--generation-isolation-probe-sha256 "${GENERATION_ISOLATION_PROBE_SHA256}")
init_args+=(--generation-isolation-verified-at "${GENERATION_ISOLATION_VERIFIED_AT}")
+init_args+=(--release-check-generation-isolation-evidence-sha256 "${RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256}")
+init_args+=(--release-check-generation-isolation-probe-sha256 "${RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256}")
+init_args+=(--release-check-generation-isolation-verified-at "${RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT}")
init_args+=(--worker-artifact-classification "${WORKER_ARTIFACT_CLASSIFICATION}")
init_args+=(--worker-artifact-manifest-sha256 "${WORKER_ARTIFACT_MANIFEST_SHA256}")
init_args+=(--worker-bundle-sha256 "${WORKER_BUNDLE_SHA256}")
@@ -429,13 +435,17 @@ stage_started="$(timestamp)"
--max-time 30 \
"${BASE_URL}/ready" \
>"${WORK_DIR}/ready.json"
-python3 - "${WORK_DIR}/ready.json" "${WORKER_VERSION_ID}" "${WORKER_EVIDENCE_COMMIT}" "${RUNNER_SOURCE_COMMIT}" "${CONTAINER_IMAGE_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${GENERATION_ISOLATION_EVIDENCE_SHA256}" "${GENERATION_ISOLATION_PROBE_SHA256}" <<'PY'
+python3 - "${WORK_DIR}/ready.json" "${WORKER_VERSION_ID}" "${WORKER_EVIDENCE_COMMIT}" "${RUNNER_SOURCE_COMMIT}" "${CONTAINER_IMAGE_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${GENERATION_ISOLATION_EVIDENCE_SHA256}" "${GENERATION_ISOLATION_PROBE_SHA256}" "${RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256}" "${RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256}" "${RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT}" <<'PY'
import json
import pathlib
import sys
payload = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"))
+if set(payload) != {"status", "service", "checks", "maintenance", "release"}:
+ raise SystemExit("public readiness response has unknown or missing fields")
if payload.get("status") != "ready":
raise SystemExit("public readiness response is not ready")
+if payload.get("service") != "counterlab-control-plane":
+ raise SystemExit("public readiness service identity is invalid")
checks = payload.get("checks")
expected_checks = {
"admission",
@@ -462,6 +472,9 @@ expected_release = {
"runnerImageDigest": sys.argv[5],
"generationIsolationEvidenceSha256": sys.argv[19],
"generationIsolationProbeSha256": sys.argv[20],
+ "releaseCheckGenerationIsolationEvidenceSha256": sys.argv[21],
+ "releaseCheckGenerationIsolationProbeSha256": sys.argv[22],
+ "releaseCheckGenerationIsolationVerifiedAt": sys.argv[23],
"timeoutCleanupReceiptSha256": sys.argv[6],
"aggregateLimitEvidenceSha256": sys.argv[7],
"runtimePolicySha256": sys.argv[8],
@@ -491,15 +504,33 @@ stage_started="$(timestamp)"
--max-time 30 \
"${BASE_URL}/api/health?readiness=probe" \
>"${WORK_DIR}/health.json"
-python3 - "${WORK_DIR}/health.json" "${WORKER_VERSION_ID}" "${WORKER_EVIDENCE_COMMIT}" "${RUNNER_SOURCE_COMMIT}" "${CONTAINER_IMAGE_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${GENERATION_ISOLATION_EVIDENCE_SHA256}" "${GENERATION_ISOLATION_PROBE_SHA256}" <<'PY'
+python3 - "${WORK_DIR}/health.json" "${WORKER_VERSION_ID}" "${WORKER_EVIDENCE_COMMIT}" "${RUNNER_SOURCE_COMMIT}" "${CONTAINER_IMAGE_DIGEST}" "${TIMEOUT_CLEANUP_RECEIPT_SHA256}" "${AGGREGATE_LIMIT_EVIDENCE_SHA256}" "${RUNTIME_POLICY_SHA256}" "${PROOF_DEPENDENCY_MANIFEST_SHA256}" "${WORKER_ARTIFACT_CLASSIFICATION}" "${WORKER_ARTIFACT_MANIFEST_SHA256}" "${WORKER_BUNDLE_SHA256}" "${CLIENT_ASSETS_SHA256}" "${CLIENT_ASSET_COUNT}" "${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" "${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" "${GENERATION_ISOLATION_EVIDENCE_SHA256}" "${GENERATION_ISOLATION_PROBE_SHA256}" "${RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256}" "${RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256}" "${RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT}" <<'PY'
import json
import pathlib
import sys
payload = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"))
-if payload.get("ok") is not True:
+if set(payload) != {"ok", "data"} or payload.get("ok") is not True:
raise SystemExit("health response is not a success envelope")
health = payload.get("data", {})
+expected_health_fields = {
+ "platform",
+ "sample",
+ "replay",
+ "liveGpt",
+ "liveCodex",
+ "liveKernel",
+ "maintenance",
+ "readiness",
+ "release",
+ "sandbox",
+ "generationFilesystemReadIsolation",
+ "requestId",
+}
+if not isinstance(health, dict) or set(health) != expected_health_fields:
+ raise SystemExit("health data has unknown or missing fields")
+if not isinstance(health.get("requestId"), str) or not health["requestId"]:
+ raise SystemExit("health request ID is unavailable")
required = {
"platform": "cloudflare-workers",
"sample": "available",
@@ -528,6 +559,9 @@ expected_release = {
"runnerImageDigest": sys.argv[5],
"generationIsolationEvidenceSha256": sys.argv[19],
"generationIsolationProbeSha256": sys.argv[20],
+ "releaseCheckGenerationIsolationEvidenceSha256": sys.argv[21],
+ "releaseCheckGenerationIsolationProbeSha256": sys.argv[22],
+ "releaseCheckGenerationIsolationVerifiedAt": sys.argv[23],
"timeoutCleanupReceiptSha256": sys.argv[6],
"aggregateLimitEvidenceSha256": sys.argv[7],
"runtimePolicySha256": sys.argv[8],
diff --git a/scripts/production_smoke_report.py b/scripts/production_smoke_report.py
index c9c45142..da224064 100644
--- a/scripts/production_smoke_report.py
+++ b/scripts/production_smoke_report.py
@@ -7,7 +7,7 @@
import re
import secrets
import urllib.parse
-from datetime import datetime
+from datetime import datetime, timedelta
from typing import Any
@@ -167,7 +167,7 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"privacy",
}:
raise ValueError("smoke report has unknown or missing fields")
- if report["schemaVersion"] not in {"2", "3", "4", "5"} or report[
+ if report["schemaVersion"] not in {"2", "3", "4", "5", "6"} or report[
"status"
] not in _REPORT_STATUSES:
raise ValueError("smoke report schema or status is invalid")
@@ -186,7 +186,7 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"containerImageDigest",
"deploymentReceiptSha256",
}
- if report["schemaVersion"] in {"3", "4", "5"}:
+ if report["schemaVersion"] in {"3", "4", "5", "6"}:
deployment_fields.update(
{
"timeoutCleanupReceiptSha256",
@@ -194,7 +194,7 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"proofDependencyManifestSha256",
}
)
- if report["schemaVersion"] in {"4", "5"}:
+ if report["schemaVersion"] in {"4", "5", "6"}:
deployment_fields.update(
{
"aggregateLimitEvidenceSha256",
@@ -209,7 +209,7 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"wranglerVersion",
}
)
- if report["schemaVersion"] == "5":
+ if report["schemaVersion"] in {"5", "6"}:
deployment_fields.update(
{
"generationIsolationEvidenceSha256",
@@ -217,6 +217,14 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"generationIsolationVerifiedAt",
}
)
+ if report["schemaVersion"] == "6":
+ deployment_fields.update(
+ {
+ "releaseCheckGenerationIsolationEvidenceSha256",
+ "releaseCheckGenerationIsolationProbeSha256",
+ "releaseCheckGenerationIsolationVerifiedAt",
+ }
+ )
if not isinstance(deployment, dict) or set(deployment) != deployment_fields:
raise ValueError("smoke deployment metadata is invalid")
worker_version = deployment["workerVersion"]
@@ -230,7 +238,7 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
if commit is not None and not _COMMIT.fullmatch(commit):
raise ValueError(f"{field} is invalid")
hash_fields = ["deploymentReceiptSha256"]
- if report["schemaVersion"] in {"3", "4", "5"}:
+ if report["schemaVersion"] in {"3", "4", "5", "6"}:
hash_fields.extend(
[
"timeoutCleanupReceiptSha256",
@@ -238,7 +246,7 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"proofDependencyManifestSha256",
]
)
- if report["schemaVersion"] in {"4", "5"}:
+ if report["schemaVersion"] in {"4", "5", "6"}:
hash_fields.extend(
[
"aggregateLimitEvidenceSha256",
@@ -248,18 +256,25 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
"clientPublicAssetsSha256",
]
)
- if report["schemaVersion"] == "5":
+ if report["schemaVersion"] in {"5", "6"}:
hash_fields.extend(
[
"generationIsolationEvidenceSha256",
"generationIsolationProbeSha256",
]
)
+ if report["schemaVersion"] == "6":
+ hash_fields.extend(
+ [
+ "releaseCheckGenerationIsolationEvidenceSha256",
+ "releaseCheckGenerationIsolationProbeSha256",
+ ]
+ )
for field in hash_fields:
value = deployment[field]
if value is not None and not _SHA256.fullmatch(value):
raise ValueError(f"{field} is invalid")
- if report["schemaVersion"] in {"4", "5"}:
+ if report["schemaVersion"] in {"4", "5", "6"}:
artifact_values = [
deployment[field]
for field in (
@@ -298,13 +313,50 @@ def validate_report(report: dict[str, Any]) -> dict[str, Any]:
or public_count > client_count
):
raise ValueError("client asset counts are invalid")
- if report["schemaVersion"] == "5":
+ if report["schemaVersion"] in {"5", "6"}:
isolation_verified_at = deployment["generationIsolationVerifiedAt"]
if isolation_verified_at is not None:
- _timestamp(
+ qualification_time = _parsed_timestamp(
isolation_verified_at,
"generationIsolationVerifiedAt",
)
+ if qualification_time > report_started:
+ raise ValueError(
+ "generationIsolationVerifiedAt follows smoke startedAt"
+ )
+ if report["schemaVersion"] == "6":
+ fresh_probe = deployment[
+ "releaseCheckGenerationIsolationProbeSha256"
+ ]
+ qualified_probe = deployment["generationIsolationProbeSha256"]
+ if (
+ fresh_probe is not None
+ and qualified_probe is not None
+ and fresh_probe != qualified_probe
+ ):
+ raise ValueError(
+ "release-check isolation probe does not match qualification"
+ )
+ fresh_verified_at = deployment[
+ "releaseCheckGenerationIsolationVerifiedAt"
+ ]
+ if fresh_verified_at is not None:
+ fresh_time = _parsed_timestamp(
+ fresh_verified_at,
+ "releaseCheckGenerationIsolationVerifiedAt",
+ )
+ if fresh_time > report_started:
+ raise ValueError(
+ "releaseCheckGenerationIsolationVerifiedAt follows smoke startedAt"
+ )
+ if report_started - fresh_time > timedelta(hours=24):
+ raise ValueError(
+ "releaseCheckGenerationIsolationVerifiedAt is stale"
+ )
+ if isolation_verified_at is not None and fresh_time < qualification_time:
+ raise ValueError(
+ "release-check isolation verification precedes qualification"
+ )
if report["privacy"] != {
"containsSecrets": False,
"containsRawNotebookBytes": False,
@@ -384,6 +436,9 @@ def initialize_report(
generation_isolation_evidence_sha256: str | None = None,
generation_isolation_probe_sha256: str | None = None,
generation_isolation_verified_at: str | None = None,
+ release_check_generation_isolation_evidence_sha256: str | None = None,
+ release_check_generation_isolation_probe_sha256: str | None = None,
+ release_check_generation_isolation_verified_at: str | None = None,
worker_artifact_classification: str | None = None,
worker_artifact_manifest_sha256: str | None = None,
worker_bundle_sha256: str | None = None,
@@ -397,7 +452,7 @@ def initialize_report(
) -> dict[str, Any]:
report = validate_report(
{
- "schemaVersion": "5",
+ "schemaVersion": "6",
"status": "RUNNING",
"baseUrl": _base_url(base_url),
"startedAt": _timestamp(started_at, "startedAt"),
@@ -422,6 +477,15 @@ def initialize_report(
generation_isolation_probe_sha256
),
"generationIsolationVerifiedAt": generation_isolation_verified_at,
+ "releaseCheckGenerationIsolationEvidenceSha256": (
+ release_check_generation_isolation_evidence_sha256
+ ),
+ "releaseCheckGenerationIsolationProbeSha256": (
+ release_check_generation_isolation_probe_sha256
+ ),
+ "releaseCheckGenerationIsolationVerifiedAt": (
+ release_check_generation_isolation_verified_at
+ ),
"workerArtifactClassification": worker_artifact_classification,
"workerArtifactManifestSha256": worker_artifact_manifest_sha256,
"workerBundleSha256": worker_bundle_sha256,
@@ -516,6 +580,11 @@ def _parser() -> argparse.ArgumentParser:
initialize.add_argument("--generation-isolation-evidence-sha256")
initialize.add_argument("--generation-isolation-probe-sha256")
initialize.add_argument("--generation-isolation-verified-at")
+ initialize.add_argument(
+ "--release-check-generation-isolation-evidence-sha256"
+ )
+ initialize.add_argument("--release-check-generation-isolation-probe-sha256")
+ initialize.add_argument("--release-check-generation-isolation-verified-at")
initialize.add_argument("--worker-artifact-classification")
initialize.add_argument("--worker-artifact-manifest-sha256")
initialize.add_argument("--worker-bundle-sha256")
@@ -566,6 +635,15 @@ def main() -> int:
generation_isolation_verified_at=(
arguments.generation_isolation_verified_at
),
+ release_check_generation_isolation_evidence_sha256=(
+ arguments.release_check_generation_isolation_evidence_sha256
+ ),
+ release_check_generation_isolation_probe_sha256=(
+ arguments.release_check_generation_isolation_probe_sha256
+ ),
+ release_check_generation_isolation_verified_at=(
+ arguments.release_check_generation_isolation_verified_at
+ ),
worker_artifact_classification=(
arguments.worker_artifact_classification
),
diff --git a/scripts/release-check-receipt.ts b/scripts/release-check-receipt.ts
index 8ab77291..735b75f8 100644
--- a/scripts/release-check-receipt.ts
+++ b/scripts/release-check-receipt.ts
@@ -9,10 +9,13 @@ import {
ContainedRuntimeAttestationSchema,
DeploymentReceiptV5Schema,
DeploymentReceiptV6Schema,
+ DeploymentReceiptV7Schema,
GenerationIsolationEvidenceV1Schema,
RELEASE_CHECK_IDS,
QualifiedRunnerReleaseV5Schema,
ReleaseCheckReceiptV4Schema,
+ ReleaseCheckReceiptV5Schema,
+ type ReleaseCheckReceiptV5,
} from "../packages/scientific-engine-registry/src/index.js";
import {
hashGenerationIsolationEvidence,
@@ -58,17 +61,57 @@ export function createQualifiedReleaseIdentity(receiptBytes: Buffer) {
}
export function createDeploymentReleaseIdentity(receiptBytes: Buffer) {
+ const value = JSON.parse(receiptBytes.toString("utf8")) as unknown;
+ const schemaVersion =
+ typeof value === "object" && value !== null && "schemaVersion" in value
+ ? value.schemaVersion
+ : undefined;
+ const receipt =
+ schemaVersion === "7"
+ ? DeploymentReceiptV7Schema.parse(value)
+ : schemaVersion === "6"
+ ? DeploymentReceiptV6Schema.parse(value)
+ : DeploymentReceiptV5Schema.parse(value);
+ return {
+ identitySchemaVersion: "1",
+ receiptType: "deployment-receipt",
+ receiptSha256: sha256(receiptBytes),
+ receipt,
+ } as const;
+}
+
+export function parseReleaseCheckReceiptV5(
+ value: unknown,
+): ReleaseCheckReceiptV5 {
+ const receipt = ReleaseCheckReceiptV5Schema.parse(value);
+ verifyGenerationIsolationEvidence({
+ evidence: receipt.releaseCheckGenerationIsolationEvidence,
+ evidenceSha256: receipt.releaseCheckGenerationIsolationEvidenceSha256,
+ expected: {
+ sourceCommit: receipt.sourceCommit,
+ sourceTreeSha256:
+ receipt.releaseCheckGenerationIsolationEvidence.sourceTreeSha256,
+ localImageTag: receipt.runnerImageTag,
+ localImageDigest: receipt.runnerImageDigest,
+ probeSha256: receipt.releaseCheckGenerationIsolationProbeSha256,
+ verifiedAt: receipt.releaseCheckGenerationIsolationVerifiedAt,
+ },
+ });
+ return receipt;
+}
+
+export function createReleaseCheckReleaseIdentity(receiptBytes: Buffer) {
const value = JSON.parse(receiptBytes.toString("utf8")) as unknown;
const receipt =
typeof value === "object" &&
value !== null &&
"schemaVersion" in value &&
- value.schemaVersion === "6"
- ? DeploymentReceiptV6Schema.parse(value)
- : DeploymentReceiptV5Schema.parse(value);
+ value.schemaVersion === "5"
+ ? parseReleaseCheckReceiptV5(value)
+ : ReleaseCheckReceiptV4Schema.parse(value);
return {
identitySchemaVersion: "1",
- receiptType: "deployment-receipt",
+ receiptType: "release-check-receipt",
receiptSha256: sha256(receiptBytes),
receipt,
} as const;
@@ -76,12 +119,22 @@ export function createDeploymentReleaseIdentity(receiptBytes: Buffer) {
export function assertReleaseCheckBinding(input: BindingInput) {
const qualified = parseQualifiedRunnerReleaseV6(input.qualifiedReceipt);
- const releaseCheck = ReleaseCheckReceiptV4Schema.parse(
- input.releaseCheckReceipt,
- );
+ const releaseCheck = parseReleaseCheckReceiptV5(input.releaseCheckReceipt);
const runtime = ContainedRuntimeAttestationSchema.parse(
input.runtimeAttestation,
);
+ verifyGenerationIsolationEvidence({
+ evidence: releaseCheck.releaseCheckGenerationIsolationEvidence,
+ evidenceSha256: releaseCheck.releaseCheckGenerationIsolationEvidenceSha256,
+ expected: {
+ sourceCommit: qualified.sourceCommit,
+ sourceTreeSha256: qualified.sourceTreeSha256,
+ localImageTag: qualified.localImageTag,
+ localImageDigest: qualified.localImageDigest,
+ probeSha256: qualified.generationIsolationProbeSha256,
+ verifiedAt: releaseCheck.releaseCheckGenerationIsolationVerifiedAt,
+ },
+ });
const comparisons: Array<[string, string, string]> = [
["evidence commit", releaseCheck.evidenceCommit, qualified.evidenceCommit],
["source commit", releaseCheck.sourceCommit, qualified.sourceCommit],
@@ -223,7 +276,7 @@ export function createReleaseCheckReceipt(input: {
const freshEvidence = GenerationIsolationEvidenceV1Schema.parse(
input.generationIsolationEvidence,
);
- verifyGenerationIsolationEvidence({
+ const freshVerification = verifyGenerationIsolationEvidence({
evidence: freshEvidence,
evidenceSha256: hashGenerationIsolationEvidence(freshEvidence),
expected: {
@@ -244,8 +297,8 @@ export function createReleaseCheckReceipt(input: {
) {
throw new Error("release-check generation-isolation evidence is not fresh");
}
- const receipt = ReleaseCheckReceiptV4Schema.parse({
- schemaVersion: "4",
+ const receipt = parseReleaseCheckReceiptV5({
+ schemaVersion: "5",
status: "PASSED",
generationFilesystemReadIsolation:
qualified.generationFilesystemReadIsolation,
@@ -253,6 +306,12 @@ export function createReleaseCheckReceipt(input: {
qualified.generationIsolationEvidenceSha256,
generationIsolationProbeSha256: qualified.generationIsolationProbeSha256,
generationIsolationVerifiedAt: qualified.generationIsolationVerifiedAt,
+ releaseCheckGenerationIsolationEvidence: freshVerification.evidence,
+ releaseCheckGenerationIsolationEvidenceSha256:
+ freshVerification.evidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256: freshVerification.probeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ freshVerification.evidence.verifiedAt,
evidenceCommit: qualified.evidenceCommit,
sourceCommit: qualified.sourceCommit,
qualifiedRunnerReceiptSha256: sha256(input.qualifiedReceiptBytes),
@@ -269,7 +328,7 @@ export function createReleaseCheckReceipt(input: {
runtimeAdapterSha256: qualified.runtimeAdapterSha256,
checks: RELEASE_CHECK_IDS.map((id) => ({ id, status: "PASSED" })),
checkedAt,
- verifierVersion: "counterlab-release-check-v4",
+ verifierVersion: "counterlab-release-check-v5",
});
return assertReleaseCheckBinding({
qualifiedReceipt: qualified,
@@ -402,6 +461,21 @@ async function main(): Promise {
);
return;
}
+ if (process.argv[2] === "release-check-identity") {
+ if (process.argv.length !== 5 || process.argv[3] !== "--release-check") {
+ throw new Error(
+ "Usage: release-check-receipt release-check-identity --release-check FILE",
+ );
+ }
+ const releaseCheckPath = await existingRepositoryFile(
+ root,
+ process.argv[4]!,
+ );
+ process.stdout.write(
+ `${JSON.stringify(createReleaseCheckReleaseIdentity(readFileSync(releaseCheckPath)))}\n`,
+ );
+ return;
+ }
const args = argumentsFrom(process.argv.slice(2));
const [qualifiedPath, runtimeAdapter, generationIsolationReport, output] =
await Promise.all([
diff --git a/scripts/test_production_smoke.py b/scripts/test_production_smoke.py
index 3d73338f..ff2ec167 100644
--- a/scripts/test_production_smoke.py
+++ b/scripts/test_production_smoke.py
@@ -44,6 +44,11 @@ def _deployment_identity(**overrides: object) -> dict[str, object]:
"generation_isolation_evidence_sha256": "5" * 64,
"generation_isolation_probe_sha256": "6" * 64,
"generation_isolation_verified_at": "2026-07-15T11:59:00Z",
+ "release_check_generation_isolation_evidence_sha256": "7" * 64,
+ "release_check_generation_isolation_probe_sha256": "6" * 64,
+ "release_check_generation_isolation_verified_at": (
+ "2026-07-15T11:59:30Z"
+ ),
"worker_artifact_classification": "PROCESS_BOUND_PARTIAL",
"worker_artifact_manifest_sha256": "1" * 64,
"worker_bundle_sha256": "2" * 64,
@@ -123,7 +128,10 @@ def _control_plane_validator_source(kind: str) -> str:
'"${CLIENT_PUBLIC_ASSETS_SHA256}" "${CLIENT_PUBLIC_ASSET_COUNT}" '
'"${FROZEN_VITE_VERSION}" "${FROZEN_WRANGLER_VERSION}" '
'"${GENERATION_ISOLATION_EVIDENCE_SHA256}" '
- '"${GENERATION_ISOLATION_PROBE_SHA256}" <<\'PY\'\n'
+ '"${GENERATION_ISOLATION_PROBE_SHA256}" '
+ '"${RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256}" '
+ '"${RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256}" '
+ '"${RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT}" <<\'PY\'\n'
)
start = script.index(marker) + len(marker)
return script[start : script.index("\nPY\n", start)]
@@ -151,7 +159,7 @@ def _deployment_receipt() -> dict[str, object]:
runner = "b" * 40
digest = "sha256:" + "c" * 64
return {
- "schemaVersion": "6",
+ "schemaVersion": "7",
"status": "DEPLOYED",
"workerName": "counterlab",
"productionOrigin": "https://counterlab.cserules.workers.dev",
@@ -159,6 +167,11 @@ def _deployment_receipt() -> dict[str, object]:
"generationIsolationEvidenceSha256": "6" * 64,
"generationIsolationProbeSha256": "7" * 64,
"generationIsolationVerifiedAt": "2026-07-18T23:58:00.000Z",
+ "releaseCheckGenerationIsolationEvidenceSha256": "8" * 64,
+ "releaseCheckGenerationIsolationProbeSha256": "7" * 64,
+ "releaseCheckGenerationIsolationVerifiedAt": (
+ "2026-07-18T23:58:30.000Z"
+ ),
"workerEvidenceCommit": worker,
"runnerSourceCommit": runner,
"qualifiedRunnerReceiptSha256": "1" * 64,
@@ -197,7 +210,7 @@ def _deployment_receipt() -> dict[str, object]:
"workerVersionSha256": "a" * 64,
"containerStatusSha256": "b" * 64,
"deployedAt": "2026-07-19T00:00:00.000Z",
- "verifierVersion": "counterlab-deployment-v6",
+ "verifierVersion": "counterlab-deployment-v7",
}
@@ -223,6 +236,34 @@ def test_deployment_receipt_parser_accepts_only_the_exact_release_tuple() -> Non
assert identity["receipt"] == receipt
assert identity["receiptSha256"] == hashlib.sha256(path.read_bytes()).hexdigest()
+ historical_v6 = json.loads(json.dumps(receipt))
+ historical_v6["schemaVersion"] = "6"
+ historical_v6["verifierVersion"] = "counterlab-deployment-v6"
+ for field in (
+ "releaseCheckGenerationIsolationEvidenceSha256",
+ "releaseCheckGenerationIsolationProbeSha256",
+ "releaseCheckGenerationIsolationVerifiedAt",
+ ):
+ historical_v6.pop(field)
+ path.write_text(json.dumps(historical_v6), encoding="utf-8")
+ parsed_v6 = _parse_deployment_identity(path)
+ assert parsed_v6.returncode == 0, parsed_v6.stderr
+ assert json.loads(parsed_v6.stdout)["receipt"]["schemaVersion"] == "6"
+
+ historical_v5 = json.loads(json.dumps(historical_v6))
+ historical_v5["schemaVersion"] = "5"
+ historical_v5["verifierVersion"] = "counterlab-deployment-v5"
+ for field in (
+ "generationIsolationEvidenceSha256",
+ "generationIsolationProbeSha256",
+ "generationIsolationVerifiedAt",
+ ):
+ historical_v5.pop(field)
+ path.write_text(json.dumps(historical_v5), encoding="utf-8")
+ parsed_v5 = _parse_deployment_identity(path)
+ assert parsed_v5.returncode == 0, parsed_v5.stderr
+ assert json.loads(parsed_v5.stdout)["receipt"]["schemaVersion"] == "5"
+
receipt["unexpected"] = True
path.write_text(json.dumps(receipt), encoding="utf-8")
unknown = _parse_deployment_identity(path)
@@ -275,6 +316,9 @@ def test_control_plane_validators_bind_exact_release_and_maintenance_state() ->
wrangler_version = "4.110.0"
generation_isolation_evidence = "5" * 64
generation_isolation_probe = "6" * 64
+ release_check_isolation_evidence = "7" * 64
+ release_check_isolation_probe = generation_isolation_probe
+ release_check_isolation_verified_at = "2026-07-15T11:59:30Z"
release = {
"status": "bound",
"workerVersionId": version,
@@ -284,6 +328,15 @@ def test_control_plane_validators_bind_exact_release_and_maintenance_state() ->
"runnerImageDigest": digest,
"generationIsolationEvidenceSha256": generation_isolation_evidence,
"generationIsolationProbeSha256": generation_isolation_probe,
+ "releaseCheckGenerationIsolationEvidenceSha256": (
+ release_check_isolation_evidence
+ ),
+ "releaseCheckGenerationIsolationProbeSha256": (
+ release_check_isolation_probe
+ ),
+ "releaseCheckGenerationIsolationVerifiedAt": (
+ release_check_isolation_verified_at
+ ),
"timeoutCleanupReceiptSha256": timeout_receipt,
"aggregateLimitEvidenceSha256": aggregate_limit_evidence,
"runtimePolicySha256": runtime_policy,
@@ -300,6 +353,7 @@ def test_control_plane_validators_bind_exact_release_and_maintenance_state() ->
}
ready = {
"status": "ready",
+ "service": "counterlab-control-plane",
"checks": {
key: True
for key in (
@@ -330,6 +384,7 @@ def test_control_plane_validators_bind_exact_release_and_maintenance_state() ->
"readiness": "ready",
"maintenance": False,
"release": release,
+ "requestId": "request-test-1",
},
}
@@ -361,6 +416,9 @@ def run_validator(
wrangler_version,
generation_isolation_evidence,
generation_isolation_probe,
+ release_check_isolation_evidence,
+ release_check_isolation_probe,
+ release_check_isolation_verified_at,
],
check=False,
capture_output=True,
@@ -401,6 +459,9 @@ def run_validator(
"wranglerVersion",
"generationIsolationEvidenceSha256",
"generationIsolationProbeSha256",
+ "releaseCheckGenerationIsolationEvidenceSha256",
+ "releaseCheckGenerationIsolationProbeSha256",
+ "releaseCheckGenerationIsolationVerifiedAt",
):
tampered = json.loads(json.dumps(payload))
tampered_release = (
@@ -416,6 +477,31 @@ def run_validator(
assert rejected.returncode != 0
assert "release identity does not match" in rejected.stderr
+ for scope in ("top", "release"):
+ expanded = json.loads(json.dumps(payload))
+ if scope == "top":
+ expanded["unexpected"] = True
+ elif kind == "ready":
+ expanded["release"]["unexpected"] = True
+ else:
+ expanded["data"]["release"]["unexpected"] = True
+ path.write_text(json.dumps(expanded), encoding="utf-8")
+ rejected = run_validator(kind, path)
+ assert rejected.returncode != 0
+
+ if kind == "ready":
+ expanded = json.loads(json.dumps(payload))
+ expanded["checks"]["unexpected"] = True
+ path.write_text(json.dumps(expanded), encoding="utf-8")
+ rejected = run_validator(kind, path)
+ assert rejected.returncode != 0
+ else:
+ expanded = json.loads(json.dumps(payload))
+ expanded["data"]["unexpected"] = True
+ path.write_text(json.dumps(expanded), encoding="utf-8")
+ rejected = run_validator(kind, path)
+ assert rejected.returncode != 0
+
maintenance = json.loads(json.dumps(payload))
if kind == "ready":
maintenance["maintenance"] = True
@@ -519,7 +605,7 @@ def test_smoke_report_is_atomic_idempotent_and_secret_free() -> None:
**_deployment_identity(),
)
assert initialized["status"] == "RUNNING"
- assert initialized["schemaVersion"] == "5"
+ assert initialized["schemaVersion"] == "6"
assert initialized["baseUrl"] == "https://counterlab.example.test"
assert initialized["stages"] == []
@@ -544,7 +630,7 @@ def test_smoke_report_is_atomic_idempotent_and_secret_free() -> None:
}
-def test_smoke_report_versions_preserve_v2_v3_v4_and_require_v5_isolation_bindings() -> None:
+def test_smoke_report_versions_preserve_v2_through_v5_and_require_v6_fresh_isolation() -> None:
with tempfile.TemporaryDirectory() as destination:
path = pathlib.Path(destination) / "production-smoke.json"
current = initialize_report(
@@ -553,7 +639,13 @@ def test_smoke_report_versions_preserve_v2_v3_v4_and_require_v5_isolation_bindin
started_at="2026-07-15T12:00:00Z",
**_deployment_identity(),
)
- assert current["schemaVersion"] == "5"
+ assert current["schemaVersion"] == "6"
+
+ v6_fields = (
+ "releaseCheckGenerationIsolationEvidenceSha256",
+ "releaseCheckGenerationIsolationProbeSha256",
+ "releaseCheckGenerationIsolationVerifiedAt",
+ )
v5_fields = (
"generationIsolationEvidenceSha256",
@@ -574,7 +666,13 @@ def test_smoke_report_versions_preserve_v2_v3_v4_and_require_v5_isolation_bindin
"wranglerVersion",
)
- historical_v4 = json.loads(json.dumps(current))
+ historical_v5 = json.loads(json.dumps(current))
+ historical_v5["schemaVersion"] = "5"
+ for field in v6_fields:
+ historical_v5["deployment"].pop(field)
+ assert validate_report(historical_v5)["schemaVersion"] == "5"
+
+ historical_v4 = json.loads(json.dumps(historical_v5))
historical_v4["schemaVersion"] = "4"
for field in v5_fields:
historical_v4["deployment"].pop(field)
@@ -616,21 +714,58 @@ def test_smoke_report_versions_preserve_v2_v3_v4_and_require_v5_isolation_bindin
with pytest.raises(ValueError, match="proofDependencyManifestSha256"):
validate_report(malformed_v3)
- malformed_v5_hash = json.loads(json.dumps(current))
+ malformed_v5_hash = json.loads(json.dumps(historical_v5))
malformed_v5_hash["deployment"]["generationIsolationProbeSha256"] = "short"
with pytest.raises(ValueError, match="generationIsolationProbeSha256"):
validate_report(malformed_v5_hash)
- malformed_v5_time = json.loads(json.dumps(current))
+ malformed_v5_time = json.loads(json.dumps(historical_v5))
malformed_v5_time["deployment"]["generationIsolationVerifiedAt"] = "yesterday"
with pytest.raises(ValueError, match="generationIsolationVerifiedAt"):
validate_report(malformed_v5_time)
- invalid_v5 = json.loads(json.dumps(current))
+ invalid_v5 = json.loads(json.dumps(historical_v5))
invalid_v5["deployment"]["clientPublicAssetCount"] = 28
with pytest.raises(ValueError, match="asset counts"):
validate_report(invalid_v5)
+ malformed_v6_hash = json.loads(json.dumps(current))
+ malformed_v6_hash["deployment"][
+ "releaseCheckGenerationIsolationEvidenceSha256"
+ ] = "short"
+ with pytest.raises(
+ ValueError,
+ match="releaseCheckGenerationIsolationEvidenceSha256",
+ ):
+ validate_report(malformed_v6_hash)
+
+ mismatched_v6_probe = json.loads(json.dumps(current))
+ mismatched_v6_probe["deployment"][
+ "releaseCheckGenerationIsolationProbeSha256"
+ ] = "0" * 64
+ with pytest.raises(ValueError, match="does not match qualification"):
+ validate_report(mismatched_v6_probe)
+
+ for verified_at, message in (
+ ("2026-07-15T12:00:01Z", "follows smoke startedAt"),
+ ("2026-07-14T11:59:59Z", "stale"),
+ ("2026-07-15T11:58:59Z", "precedes qualification"),
+ ("yesterday", "releaseCheckGenerationIsolationVerifiedAt"),
+ ):
+ invalid_time = json.loads(json.dumps(current))
+ invalid_time["deployment"][
+ "releaseCheckGenerationIsolationVerifiedAt"
+ ] = verified_at
+ with pytest.raises(ValueError, match=message):
+ validate_report(invalid_time)
+
+ future_qualification = json.loads(json.dumps(current))
+ future_qualification["deployment"][
+ "generationIsolationVerifiedAt"
+ ] = "2026-07-15T12:00:01Z"
+ with pytest.raises(ValueError, match="follows smoke startedAt"):
+ validate_report(future_qualification)
+
def test_smoke_report_rejects_conflicts_secrets_and_credential_urls() -> None:
with tempfile.TemporaryDirectory() as destination:
From ea01ca7efa2a47911713e2f3ea8cf4644f4de154 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 04:22:13 +0530
Subject: [PATCH 015/143] docs(progress): record fresh release chain
---
docs/PROGRESS.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 70 insertions(+), 1 deletion(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 33880daa..e0f936f8 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -12,7 +12,18 @@ pass is never presented as a browser or production pass.
### Current checkpoint
- **Branch:** `feat/learner-ux-v6.1`
-- **Current product checkpoint:**
+- **Latest committed checkpoint:** `9348093a15942e7fb59c2755d3e080a20785b5c0`.
+ The fresh exact-image isolation rerun is independently hash-bound through
+ release-check receipt v5, deployment receipt v7, Worker release identity,
+ exact public response validation, and production-smoke report v6. Historical
+ receipt readers remain compatible. The worktree contains only the preserved,
+ untracked `scripts/.fuse_hidden0000a9600000801f`, which remains unopened,
+ unstaged, and undeleted. The complete web suite passes **616/616**; the
+ correctly contained and socket-enabled root suite passes **667/670** with
+ **2 skipped**, leaving exactly three stale scientific-evidence failures.
+ Qualification, deployment, browser qualification, learner evidence, and
+ submission remain unclaimed.
+- **Earlier product checkpoint:**
`5259a495948a68024702ba5239bf8057d1785f08`. The owner partitioned the
preserved integration delta into nine commits and fast-forwarded both
`feat/learner-ux-v6.1` and `main` to `134c22f` before qualification. The lead
@@ -3034,3 +3045,61 @@ undeleted.
5. Align and verify Devpost, public video, repository, feedback ID, and
submission receipt; push the feature branch and fast-forward its verified
tip into `main` only after the recorded gates are truthful.
+
+## Finish Commander fresh release-chain checkpoint — 2026-07-20T22:50:08Z
+
+Deadline: `2026-07-22T00:00:00Z`; **25 hours 9 minutes 52 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD
+`9348093a15942e7fb59c2755d3e080a20785b5c0`; the only worktree entry besides
+this progress update is the preserved untracked
+`scripts/.fuse_hidden0000a9600000801f`.
+
+### Newly frozen independent commits
+
+- `e8ae61f fix(release): bind qualified isolation identity` carries the
+ qualification-time isolation evidence and exact probe through qualified
+ receipts, runtime readiness, Worker bindings, deployment receipts, and
+ public release identity. Active Worker bindings now reject duplicates.
+- `9348093 fix(release): bind fresh isolation recheck` separately embeds and
+ hashes the release-time rerun, introduces release-check receipt v5,
+ deployment receipt v7, and smoke report v6, validates exact public response
+ shapes, preserves historical readers, rejects future/reversed/stale proof,
+ and requires the rerun within 30 minutes of receipt issuance.
+
+### Exact verification at this checkpoint
+
+- Fresh receipt/deployment contract Vitest: **4 files, 35/35 passed**.
+- Worker API/deployment-config Vitest: **2 files, 123/123 passed**.
+- Production-smoke Pytest: **19/19 passed**.
+- Complete web Vitest: **78 files, 616/616 passed**.
+- Complete root Vitest with an absolute repository-contained temp root and
+ loopback permission: **58/60 files passed; 667 passed, 3 failed, 2 skipped**.
+ All three failures are current source-bound scientific-evidence drift in the
+ release verifier; the earlier relative-temp and socket-denied run is not
+ treated as product evidence.
+- Repository, web, and Worker strict TypeScript: **passed**.
+- Production Vite/Worker build: **passed**. The main client remains
+ **552.03 kB** minified and above the 500 kB warning threshold; Worker output
+ is **1,862.23 kB**.
+- Scoped Prettier, Bash syntax, Git whitespace, generated contract schemas,
+ and a 19-file secret scan: **passed**.
+
+### Ordered remaining gates
+
+1. Refresh the exact source-bound scientific integrity, SBOM tool-lock, VEX,
+ and vulnerability evidence without weakening the three failing tests.
+2. Resolve the named FUSE artifact only under the repository destructive-action
+ rule; until then, clean-tree release checks remain blocked.
+3. Freeze one source commit, build and exercise one exact runner image, prove
+ the generation-isolation sentinel and real aggregate containment, regenerate
+ all source/image-bound evidence, and issue qualification/release receipts.
+4. Verify repository-contained Wrangler authentication, deploy only the
+ qualified tuple, and run production smoke against the exact public identity.
+5. Run the full desktop/mobile/accessibility/error-path matrix through
+ CloakBrowser. Stock Chromium remains design-review evidence only.
+6. Keep learner evidence `NO_DATA` unless real consented participants are
+ observed; align Devpost, video, public repository, feedback ID, and every
+ claim before authorized submission.
+7. Push the completed feature branch, verify its remote tip, fast-forward the
+ exact verified commit into `main`, and push `main` only after the release and
+ submission records are truthful.
From de21f14536302d24f6dc40843632ee3c3b6bfd8d Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 04:29:26 +0530
Subject: [PATCH 016/143] fix(release): verify refreshed image digest
---
scripts/refresh-source-bound-scientific-evidence.sh | 1 +
scripts/source-bound-evidence.test.ts | 1 +
2 files changed, 2 insertions(+)
diff --git a/scripts/refresh-source-bound-scientific-evidence.sh b/scripts/refresh-source-bound-scientific-evidence.sh
index fe4eb8b5..89dac7fb 100644
--- a/scripts/refresh-source-bound-scientific-evidence.sh
+++ b/scripts/refresh-source-bound-scientific-evidence.sh
@@ -500,6 +500,7 @@ node --import tsx scripts/refresh-scientific-engine-bindings.ts --check
COUNTERLAB_DOCKER_BIN="${RUNTIME_ADAPTER}" \
./scripts/verify-scientific-engines.sh \
--image "${IMAGE}" \
+ --expected-image-digest "${IMAGE_DIGEST}" \
--runtime-report "${WORK}/runtime-verification.json"
RECEIPT="${WORK}/evidence-refresh-receipt.json"
diff --git a/scripts/source-bound-evidence.test.ts b/scripts/source-bound-evidence.test.ts
index 6080967e..b0ff8e13 100644
--- a/scripts/source-bound-evidence.test.ts
+++ b/scripts/source-bound-evidence.test.ts
@@ -591,6 +591,7 @@ describe("source-bound release evidence helpers", () => {
expect(refresh).toContain(
'--runtime-report "${WORK}/runtime-verification.json"',
);
+ expect(refresh).toContain('--expected-image-digest "${IMAGE_DIGEST}"');
expect(
refresh.match(/--manifest-digest "\$\{MANIFEST_DIGEST\}"/gu),
).toHaveLength(2);
From 85c5652afc75230a28209883153814ca6cb64c36 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 04:34:48 +0530
Subject: [PATCH 017/143] fix(runtime): preserve supervisor response channel
---
scripts/contained-runtime-supervisor.mjs | 2 +-
scripts/contained-runtime-supervisor.test.ts | 11 +++++++++++
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/scripts/contained-runtime-supervisor.mjs b/scripts/contained-runtime-supervisor.mjs
index 05abc985..c9e4e034 100644
--- a/scripts/contained-runtime-supervisor.mjs
+++ b/scripts/contained-runtime-supervisor.mjs
@@ -332,7 +332,7 @@ async function handleRequest(request) {
throw new Error("runtime supervisor action is invalid");
}
-const controlServer = createServer((socket) => {
+const controlServer = createServer({ allowHalfOpen: true }, (socket) => {
const chunks = [];
let total = 0;
socket.setTimeout(35_000);
diff --git a/scripts/contained-runtime-supervisor.test.ts b/scripts/contained-runtime-supervisor.test.ts
index 5c14317f..ef4bee79 100644
--- a/scripts/contained-runtime-supervisor.test.ts
+++ b/scripts/contained-runtime-supervisor.test.ts
@@ -55,6 +55,17 @@ function drainReceipt() {
}
describe("contained runtime supervisor protocol", () => {
+ it("keeps the response side open after a client finishes its request", () => {
+ const source = readFileSync(
+ resolve(process.cwd(), "scripts/contained-runtime-supervisor.mjs"),
+ "utf8",
+ );
+
+ expect(source).toContain(
+ "const controlServer = createServer({ allowHalfOpen: true }, (socket) => {",
+ );
+ });
+
it("keeps a failed owned-child shutdown retryable", () => {
const source = readFileSync(
resolve(process.cwd(), "scripts/contained-runtime-supervisor.mjs"),
From 9ac208d64a13232c76fd4bba8c0a562cee196c4e Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 04:37:18 +0530
Subject: [PATCH 018/143] docs(progress): record runtime startup repair
---
docs/PROGRESS.md | 42 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index e0f936f8..2142b831 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -3103,3 +3103,45 @@ this progress update is the preserved untracked
7. Push the completed feature branch, verify its remote tip, fast-forward the
exact verified commit into `main`, and push `main` only after the release and
submission records are truthful.
+
+## Finish Commander runtime-start checkpoint — 2026-07-20T23:06:21Z
+
+Deadline: `2026-07-22T00:00:00Z`; **24 hours 53 minutes 39 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD
+`85c5652afc75230a28209883153814ca6cb64c36`; the only visible worktree entry is
+the preserved untracked `scripts/.fuse_hidden0000a9600000801f`.
+
+### Newly frozen independent commits
+
+- `de21f14 fix(release): verify refreshed image digest` passes the build
+ receipt's exact image digest into the final scientific verifier instead of
+ relying on the mutable local tag until later receipt validation. The focused
+ source-bound evidence suite passed **11/11**; shell syntax, formatting,
+ whitespace, and the two-file secret scan passed.
+- `85c5652 fix(runtime): preserve supervisor response channel` configures the
+ Unix control server for request half-close semantics. Before the repair, a
+ real fresh runtime reached its sockets but the attestation request received
+ an empty response; the added characterization failed **1/5**. After repair,
+ supervisor, attestation, entry, and contained-runtime suites passed **52/52**,
+ and a second fresh session reached `Contained runtime session ready`.
+
+### Current external and release truth
+
+- The repository-contained Wrangler `4.110.0` identity check failed closed
+ because its isolated profile reported `loggedIn: false`. A fresh manual OAuth
+ flow was started with `--browser=false` and timed out without a callback. No
+ normal-home credential was read or copied, and no Cloudflare resource was
+ mutated.
+- The current shell exposes no `CLOAK_CDP_ENDPOINT`; no CloakBrowser result is
+ claimed.
+- Read-only source audit confirms that the contained rootless OCI path still
+ removes cgroup resources and records aggregate enforcement as false. The
+ strict timeout and qualification contracts correctly require genuine,
+ source-bound Linux cgroup v2 negative-control evidence, so no qualified
+ receipt can be issued from the current producer. This gate will not be
+ weakened or replaced with a fabricated receipt.
+- The FUSE artifact still blocks the exact build's clean-tree gate. Its removal
+ remains pending the constitution's immediate explicit approval.
+- The three scientific release-verifier failures remain the expected stale
+ exact-image evidence and can be resolved only after a clean source build and
+ authoritative full refresh.
From 773e8087402713c0aa26587e6b3f982428189f30 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 04:59:25 +0530
Subject: [PATCH 019/143] fix(judge): open verified sample proof directly
---
.../src/features/judge/JudgeModeView.test.tsx | 50 ++++++++++++++++++-
apps/web/src/features/judge/JudgeModeView.tsx | 16 ++++--
.../src/features/judge/SampleEvidencePack.tsx | 15 +++++-
3 files changed, 74 insertions(+), 7 deletions(-)
diff --git a/apps/web/src/features/judge/JudgeModeView.test.tsx b/apps/web/src/features/judge/JudgeModeView.test.tsx
index 0dda267b..bd9f9b30 100644
--- a/apps/web/src/features/judge/JudgeModeView.test.tsx
+++ b/apps/web/src/features/judge/JudgeModeView.test.tsx
@@ -1,10 +1,15 @@
+import { readFile } from "node:fs/promises";
+import { resolve } from "node:path";
+
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import { describe, expect, it, vi } from "vitest";
+import { afterEach, describe, expect, it, vi } from "vitest";
import type { CapabilityHealth } from "../../api";
import { JudgeModeView } from "./JudgeModeView";
+const repositoryRoot = resolve(import.meta.dirname, "../../../../..");
+
const configuredHealth: CapabilityHealth = {
platform: "cloudflare-workers",
sample: "available",
@@ -45,6 +50,10 @@ const isolatedHealth: CapabilityHealth = {
};
describe("JudgeModeView", () => {
+ afterEach(() => {
+ vi.unstubAllGlobals();
+ });
+
it("distinguishes sample, live, and legacy replay authority", async () => {
render(
{
);
expect(
screen.getByRole("link", {
- name: /inspect exact values and integrity/i,
+ name: /inspect verified sample proof/i,
}),
).toHaveAttribute("href", "#sample-evidence");
expect(screen.getByText("Sample lesson")).toBeInTheDocument();
@@ -203,6 +212,43 @@ describe("JudgeModeView", () => {
expect(onStartSample).toHaveBeenCalledTimes(1);
});
+ it("opens the verified Sample Proof Capsule from the first fold in one action", async () => {
+ const capsuleBytes = await readFile(
+ resolve(
+ repositoryRoot,
+ "fixtures/public/leakage_sample_proof_capsule_v1.counterlab",
+ ),
+ );
+ vi.stubGlobal(
+ "fetch",
+ vi.fn(async () => new Response(capsuleBytes, { status: 200 })),
+ );
+ const user = userEvent.setup();
+
+ render(
+ ,
+ );
+
+ await screen.findByText(/capsule bytes match the checked-in reference/iu);
+ await user.click(
+ screen.getByRole("link", {
+ name: /inspect verified sample proof/iu,
+ }),
+ );
+
+ const summary = screen.getByText("Inspect Sample Proof Capsule", {
+ selector: "summary",
+ });
+ expect(summary.closest("details")).toHaveAttribute("open");
+ expect(summary).toHaveFocus();
+ });
+
it("does not offer a live link when deployed authority is unavailable", async () => {
const user = userEvent.setup();
const retry = vi.fn();
diff --git a/apps/web/src/features/judge/JudgeModeView.tsx b/apps/web/src/features/judge/JudgeModeView.tsx
index 08c11c8f..b0a598f2 100644
--- a/apps/web/src/features/judge/JudgeModeView.tsx
+++ b/apps/web/src/features/judge/JudgeModeView.tsx
@@ -1,3 +1,5 @@
+import { useState } from "react";
+
import type { CapabilityHealth } from "../../api";
import { VerifiedBeliefBreakMechanism } from "../../components/learner/VerifiedBeliefBreakTheater";
import { verifiedReplay } from "../../sampleReplayMetadata";
@@ -40,6 +42,8 @@ export function JudgeModeView({
}) {
const liveReady = liveAuthorityReady(health);
const release = health?.release?.status === "bound" ? health.release : null;
+ const [sampleEvidenceInspectionRequest, setSampleEvidenceInspectionRequest] =
+ useState(0);
return (
@@ -92,8 +96,14 @@ export function JudgeModeView({
occurs in this preview.
-
- Inspect exact values and integrity
+
+ setSampleEvidenceInspectionRequest((request) => request + 1)
+ }
+ >
+ Inspect verified sample proof
@@ -341,7 +351,7 @@ export function JudgeModeView({
-
+
diff --git a/apps/web/src/features/judge/SampleEvidencePack.tsx b/apps/web/src/features/judge/SampleEvidencePack.tsx
index aadb8210..0629f08b 100644
--- a/apps/web/src/features/judge/SampleEvidencePack.tsx
+++ b/apps/web/src/features/judge/SampleEvidencePack.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from "react";
+import { useEffect, useRef, useState } from "react";
import {
type ValidatedSampleProofCapsuleV1,
@@ -69,11 +69,14 @@ function HashValue({ value }: { value: string }) {
export function SampleEvidencePack({
verifyCapsule = verifyCheckedInSampleProofCapsule,
createDownload = createVerifiedDownload,
+ inspectionRequest = 0,
}: {
verifyCapsule?: () => Promise
;
createDownload?: (bytes: Uint8Array) => VerifiedDownload;
+ inspectionRequest?: number;
}) {
const [state, setState] = useState({ status: "checking" });
+ const inspectionRef = useRef(null);
useEffect(() => {
let active = true;
@@ -102,6 +105,14 @@ export function SampleEvidencePack({
};
}, [createDownload, verifyCapsule]);
+ useEffect(() => {
+ if (inspectionRequest < 1 || state.status !== "ready") return;
+ const inspection = inspectionRef.current;
+ if (inspection === null) return;
+ inspection.open = true;
+ inspection.querySelector("summary")?.focus({ preventScroll: true });
+ }, [inspectionRequest, state.status]);
+
return (
✓ Capsule bytes match the checked-in
reference; fixed sample authority bindings resolve
-
+
Inspect Sample Proof Capsule
Authority
From c7a3b9e9261d29480830f16835bdb3f72a26575f Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 05:00:17 +0530
Subject: [PATCH 020/143] docs(progress): record proof access and release
blockers
---
docs/PROGRESS.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 2142b831..e7dc7284 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -3145,3 +3145,66 @@ the preserved untracked `scripts/.fuse_hidden0000a9600000801f`.
- The three scientific release-verifier failures remain the expected stale
exact-image evidence and can be resolved only after a clean source build and
authoritative full refresh.
+
+## Finish Commander proof-access checkpoint — 2026-07-20T23:29:33Z
+
+Deadline: `2026-07-22T00:00:00Z`; **24 hours 30 minutes 27 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD `773e808`; the only
+visible worktree entry is the already-unlinked FUSE tombstone
+`scripts/.fuse_hidden0000a9600000801f` (`nlink=0`). It remains unstaged and was
+not removed.
+
+### Newly frozen independent commit
+
+- `773e808 fix(judge): open verified sample proof directly` closes the strict
+ source-level MB-015 interaction gap. The first-fold Judge action now opens
+ the checked-in Sample Proof Capsule disclosure after its bytes and authority
+ bindings verify, then places keyboard focus on the disclosure summary. A
+ failed integrity check still withholds both inspection and download.
+
+### Exact verification
+
+- The new Judge regression failed before implementation and passed afterward.
+- Judge Mode plus SampleEvidencePack Vitest: **2 files, 11/11 passed**.
+- Web strict TypeScript: **passed**.
+- Scoped Prettier, Git whitespace, and explicit three-file secret scan:
+ **passed**.
+
+### Reconciled source and external truth
+
+- Black-box product P0/P1 source status is now **17 source-covered and one
+ partial**. MB-014 remains partial because real cold-user comprehension has
+ not been measured. No product issue is production-closed yet.
+- Internal status remains **five source-pass, four partial or mitigated, and
+ three open**. CL-001 submission, CL-006 learner evidence, and optional
+ CL-024 continuing Learning Director remain open.
+- Aggregate containment cannot be produced by the current runtime: it removes
+ OCI cgroup resources and explicitly disables cgroups. A genuine fix requires
+ a delegated cgroup-v2 subtree plus owned-process inspection outside the
+ repository, which the current filesystem constitution forbids. The strict
+ gate remains unchanged and no evidence is synthesized.
+- `playwright_safe` is registered but its tools were not injected into this
+ session, and no `CLOAK_CDP_ENDPOINT` exists. Direct CloakBrowser launch
+ failed with `SIGTRAP`; stock Chromium is not substituted. A fresh Codex
+ session with the registered safe-browser namespace is required for current
+ browser qualification.
+- Repository-contained Wrangler remains unauthenticated after the earlier
+ manual OAuth callback timed out. No Worker, Container, D1, R2, secret, or
+ submission state was mutated.
+
+### Ordered remaining gates
+
+1. Run the consolidated local P0/P1 acceptance matrix and full deterministic
+ source gates; repair only evidence-backed regressions in independent
+ commits.
+2. Resolve the already-unlinked FUSE tombstone by closing its owning temporary
+ process, without deleting or sweeping repository content.
+3. Obtain an explicit, narrow authority decision for real delegated cgroup-v2
+ observation, or keep exact-image qualification fail-closed.
+4. Freeze one source, build one image, regenerate the exact SBOM/VEX/scientific
+ evidence, qualify, release-check, and deploy only that tuple.
+5. Start a fresh safe-browser-capable session and run the complete public
+ desktop/mobile/accessibility/error-path matrix.
+6. Keep learner evidence `NO_DATA` without consented participants; align and
+ verify the exact public app, repository, video, Devpost, feedback ID, and
+ submission receipt before the final feature push and fast-forward merge.
From fac223f18199d2c8dca17b67670c42b4b03fca81 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 05:05:16 +0530
Subject: [PATCH 021/143] docs(release): align isolation and completion claims
---
README.md | 13 +++++++-----
docs/AUTHORITY_BOUNDARIES.md | 15 +++++++-------
docs/DEMO_SCRIPT.md | 9 +++++----
docs/DEVPOST_COPY.md | 11 +++++-----
docs/THREAT_MODEL.md | 39 ++++++++++++++++++------------------
5 files changed, 47 insertions(+), 40 deletions(-)
diff --git a/README.md b/README.md
index 95cb0782..8cfc73d5 100644
--- a/README.md
+++ b/README.md
@@ -310,11 +310,14 @@ The recorded local host App Server replay could inspect global skill files
outside its generation directory, so that replay remains labelled `PARTIAL`.
The hosted source-free Plan path is separate: credentials are staged only for
App Server initialization and revoked before the generated turn; generated
-child commands receive neither model credentials nor signing authority. The
-Container launch uses a fixed non-root UID, `setpriv --no-new-privs`, and write
-constraints, but it has no
-mount namespace or filesystem read allowlist. Hosted filesystem generation
-read isolation is explicitly `PARTIAL`; formal sandbox proof is not claimed.
+child commands receive neither model credentials nor signing authority. Current
+source refuses a live launch unless the exact runner reports a pinned
+Bubblewrap boundary with an allowlisted runtime mount set, a writable
+generation workspace, missing repository/held-out/verifier paths, a fixed
+non-root UID, and `no-new-privs`. That is source-level admission logic, not production evidence:
+until a newly built exact image passes the sentinel and release qualification,
+hosted live authority remains unavailable and no `OS_ENFORCED` deployment claim
+is made.
See [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md).
## How Codex accelerated the build
diff --git a/docs/AUTHORITY_BOUNDARIES.md b/docs/AUTHORITY_BOUNDARIES.md
index ab0b5916..7987328e 100644
--- a/docs/AUTHORITY_BOUNDARIES.md
+++ b/docs/AUTHORITY_BOUNDARIES.md
@@ -75,13 +75,14 @@ The live implementation is `AppServerCodexCompiler` in `packages/codex-client`.
Launching additionally requires a trusted `AppServerLaunchBoundary`. Without
one, the compiler fails with `CODEX_ISOLATION_UNAVAILABLE`; direct spawn is
-available only to fake unit-test processes. The hosted Container boundary
-stages and revokes credentials, uses a fixed non-root UID plus
-`setpriv --no-new-privs`, and constrains writes, but
-does not provide a mount namespace or filesystem read allowlist. Filesystem
-generation read isolation is therefore `PARTIAL`. The included Bubblewrap
-probe proves a stronger target mount shape but is not the authenticated hosted
-launcher.
+available only to fake unit-test processes. Current hosted source stages and
+revokes credentials, uses a fixed non-root UID plus `no-new-privs`, and requires
+the exact runner to report a pinned Bubblewrap boundary with an allowlisted
+runtime mount set and a writable generation workspace. The startup sentinel
+must also prove that repository, held-out, verifier, and unrelated-job paths are
+missing. This is not yet a production claim: a newly built exact image must
+exercise and bind the sentinel before health may report `OS_ENFORCED`; otherwise
+live authority fails closed.
The hosted lab turn is limited to approved belief evidence, public
schema/documentation, redacted fixture structure, resource limits, and permitted
diff --git a/docs/DEMO_SCRIPT.md b/docs/DEMO_SCRIPT.md
index 62afe107..72b72a5a 100644
--- a/docs/DEMO_SCRIPT.md
+++ b/docs/DEMO_SCRIPT.md
@@ -68,10 +68,11 @@ and use the primary download. Emphasize that the upload was never overwritten.
## 2:32–2:45 — Completion and proof
-Show: “You can now distinguish ‘good on familiar rows’ from ‘generalizes to new
-entities.’” Then open Evidence & proof for the Reasoning Diff and Proof Capsule.
-Close: “Chatbots explain. CounterLab lets reality answer—and only then helps you
-fix the code.”
+Show: “You completed one verified entity-leakage loop: this fixed task separated
+performance on familiar rows from performance on new entities. It does not
+establish mastery.” Then open Evidence & proof for the Reasoning Diff and Proof
+Capsule. Close: “Chatbots explain. CounterLab lets reality answer—and only then
+helps you fix the code.”
## Backup disclosure
diff --git a/docs/DEVPOST_COPY.md b/docs/DEVPOST_COPY.md
index a9b3011d..eeea2a71 100644
--- a/docs/DEVPOST_COPY.md
+++ b/docs/DEVPOST_COPY.md
@@ -114,10 +114,11 @@ never executes uploaded cells, and sends only the learner-approved sanitized
packet to the reasoning analyst. Raw rows, the notebook file, secrets, and
local paths are excluded.
-Hosted Runtime Codex uses a credential-and-privilege launch boundary with a
-fixed non-root UID, `setpriv --no-new-privs`, and source-free typed plans. It
-has no mount namespace or filesystem read allowlist,
-so filesystem generation read isolation is explicitly `PARTIAL`. Fixed scoring,
+Hosted Runtime Codex is admitted only when the exact runner proves a pinned
+Bubblewrap boundary with a fixed non-root UID, `no-new-privs`, an allowlisted
+runtime mount set, a writable generation workspace, and missing repository,
+held-out, verifier, and unrelated-job paths. If that exact-image sentinel or
+release binding is absent, Judge Mode keeps Live unavailable. Fixed scoring,
execution, and verification remain independent of generated output.
## Evidence modes
@@ -149,7 +150,7 @@ claim should be copied into the submission until a measured study exists.
Upload a supported notebook, approve the exact outbound packet, and follow
reconnect, transfer, repaired-copy download, and Proof Capsule export.
5. Inspect `docs/RELEASE_CHECKLIST.md`, `docs/PRODUCTION_SMOKE.json`, and the
- schema-v4 deployment receipt for the exact commit, Worker version, Container
+ schema-v7 deployment receipt for the exact commit, Worker version, Container
digest, asset hashes, and smoke outcomes. If those identifiers do not agree,
treat live qualification as not proven.
6. Run the repository-contained verification commands documented in `README.md`
diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md
index 6b6a75e0..a838b717 100644
--- a/docs/THREAT_MODEL.md
+++ b/docs/THREAT_MODEL.md
@@ -65,20 +65,20 @@ isolation. Therefore generation-time hidden-verifier unreadability is `PARTIAL`.
Post-generation candidate execution is separately OS-constrained and verified;
that does not retroactively prove generation isolation.
-Current code prevents another unisolated launch. A real Bubblewrap probe creates
-a root containing only `/usr` and the exact generation workspace and verifies
-that repository, held-out, and hidden-verifier host paths resolve as missing.
-This proves the mount shape, not authenticated end-to-end generation: stable
-Codex authentication is file-backed and would be readable to model-invoked
-commands if mounted into that namespace.
-
-The current hosted Container launch is a separate credential-and-privilege
-boundary. It runs Codex at a fixed non-root UID with
-`setpriv --no-new-privs`, stages and revokes authentication, and constrains
-writes. It does not provide a mount namespace or filesystem read allowlist, so
-hosted filesystem generation read isolation is also `PARTIAL`. This is a claim
-limitation, not evidence that generated output gained scoring, kernel, or
-verifier authority.
+Current source prevents another unisolated hosted launch. Its exact-image
+startup probe requires pinned Bubblewrap, an allowlisted runtime mount set, a
+writable generation workspace, a fixed non-root UID, `no-new-privs`, and
+missing repository, held-out, hidden-verifier, and unrelated-job paths. The
+probe output is parsed, canonicalized, hashed, and carried through the strict
+release contracts.
+
+That source mechanism is not itself authenticated end-to-end or production
+evidence. Stable Codex authentication would be readable to model-invoked
+commands if mounted into the namespace, so the hosted path stages credentials
+only for initialization and revokes them before the generated turn. A newly
+built exact image must pass the sentinel and qualification before health may
+report `OS_ENFORCED`; otherwise live authority fails closed. The historical
+local App Server replay remains explicitly `PARTIAL`.
Hosted source-free Plans remove arbitrary model-authored Python from the public
critical path, but the Container runtime, Codex CLI, configured model endpoint,
@@ -107,10 +107,11 @@ honest patch refusal even when intake and the fixed lab are supported.
## Highest-priority hardening
Keep the historical local replay labelled `PARTIAL`; do not spend the release
-critical path pretending it is the hosted runner. For hosted execution, add and
-test phase-specific outbound filtering after model compilation, non-root fixed
+critical path pretending it is the hosted runner. For hosted execution, retain
+phase-specific outbound filtering after model compilation, non-root fixed
children with process/file/CPU limits, deterministic single-thread numeric
settings, pinned base images and hash-locked Python artifacts, and scientific
-engine/SBOM/license drift gates. Requalify staged-auth revocation after every
-Codex CLI protocol change. Until those checks execute, no formal sandbox or
-fully no-network hosted-process claim is made.
+engine/SBOM/license drift gates. Requalify the Bubblewrap sentinel and staged
+authentication revocation after every Codex CLI or runner-image change. Until
+those checks execute on the exact deployed tuple, no formal sandbox or fully
+no-network hosted-process claim is made.
From cd58f3c39eac35736abc991fab88ba5d5561e4ef Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 05:08:58 +0530
Subject: [PATCH 022/143] test(release): accept wrapped engine verification
command
---
services/runner/tests/test_reproduction_tooling.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/services/runner/tests/test_reproduction_tooling.py b/services/runner/tests/test_reproduction_tooling.py
index 62d1646b..3e4c0b4f 100644
--- a/services/runner/tests/test_reproduction_tooling.py
+++ b/services/runner/tests/test_reproduction_tooling.py
@@ -2,6 +2,7 @@
import importlib.util
import json
+import re
import shutil
from pathlib import Path
from types import ModuleType
@@ -91,7 +92,10 @@ def test_release_check_keeps_engine_and_adapter_images_separate() -> None:
'COUNTERLAB_SANDBOX_IMAGE="${ADAPTER_IMAGE}" '
"bash scripts/reproduce-session.sh leakage-01" in release_check
)
- assert 'verify-scientific-engines.sh --image "${ENGINE_IMAGE}"' in release_check
+ assert re.search(
+ r'verify-scientific-engines\.sh\s+\\\s*\n\s*--image "\$\{ENGINE_IMAGE\}"',
+ release_check,
+ )
def test_release_check_uses_read_only_evidence_checks() -> None:
From 889a12d3d69a779bcb674be9b04c0006122989a0 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 05:14:30 +0530
Subject: [PATCH 023/143] docs(progress): record consolidated source gates
---
docs/PROGRESS.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index e7dc7284..554258ea 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -3208,3 +3208,76 @@ not removed.
6. Keep learner evidence `NO_DATA` without consented participants; align and
verify the exact public app, repository, video, Devpost, feedback ID, and
submission receipt before the final feature push and fast-forward merge.
+
+## Finish Commander consolidated source-gate checkpoint — 2026-07-20T23:43:12Z
+
+Deadline: `2026-07-22T00:00:00Z`; **24 hours 16 minutes 48 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD `cd58f3c`; the only
+visible worktree entry besides this progress update is the preserved,
+already-unlinked FUSE tombstone `scripts/.fuse_hidden0000a9600000801f`. It was
+not staged or modified.
+
+### Newly frozen independent commits
+
+- `fac223f docs(release): align isolation and completion claims` aligns the
+ public documentation with the exact-image isolation contract and bounded
+ learner-completion language without claiming production qualification.
+- `cd58f3c test(release): accept wrapped engine verification command` repairs a
+ brittle reproduction-tooling guard so it verifies the exact engine-image
+ argument across ordinary shell line wrapping. The focused file passes
+ **12/12**.
+
+### Exact verification at this checkpoint
+
+- Complete web Vitest: **78 files, 617/617 tests passed**.
+- Web client TypeScript, generated Worker types, and Worker TypeScript:
+ **passed**.
+- Production Vite/Worker build: **passed**. Worker output is **1,863.39 kB /
+ 359.59 kB gzip**; main client JavaScript is **552.03 kB / 148.06 kB gzip**;
+ main CSS is **200.01 kB / 34.61 kB gzip**. The main JavaScript warning and
+ mixed static/dynamic `VerifiedBeliefBreakTheater` import remain open
+ performance observations.
+- Complete kernel and runner Pytest: **297/297 passed**.
+- Focused scientific-engine, reachability, and held-out-generator Pytest:
+ **12/12 passed**.
+- Held-out evidence check: **10/10 intake/routing**, **7/8 fixed full-loop
+ completion**, tracked evidence matched, exit `0`. The 7/8 is the existing
+ bounded result, not a new regression.
+- Complete root Vitest: **58/60 files passed; 668 passed, 3 failed, 2 skipped**.
+ The three failures are the intentional fail-closed response to stale
+ source/image-bound scientific evidence. No test was weakened.
+- Focused Git whitespace and secret scans for the release-test change:
+ **passed**.
+
+### Current production and external truth
+
+- The public `/api/health` endpoint returned HTTP success and reported the
+ configured sample, replay, GPT, Codex, kernel, and sandbox capabilities, but
+ it did **not** expose an exact source, image, or release identity. It is not
+ treated as the branch tip or as a qualified tuple.
+- Repository-contained Wrangler still reported `loggedIn: false` immediately
+ before a new manual OAuth flow was opened. No deployment mutation has
+ occurred in this checkpoint.
+- The aggregate cgroup-v2 producer and CloakBrowser endpoint gates remain
+ unresolved. The scientific-evidence failures cannot be repaired honestly
+ until one exact image is built and exercised under the required containment
+ contract.
+
+### Ordered remaining gates
+
+1. Complete repository-contained Wrangler OAuth and verify the intended
+ account without printing credentials.
+2. Produce genuine aggregate cgroup-v2 evidence on an authorized capable host;
+ keep qualification fail-closed if the host cannot provide it.
+3. Use an exact clean committed source checkout for one runner build, refresh
+ the complete source/image-bound scientific and supply-chain evidence, and
+ rerun the three currently stale verifier cases.
+4. Run the complete release gate, deploy only the qualified tuple, and prove
+ the public identity plus supported live and failure-path journeys.
+5. Run the full desktop/mobile/accessibility/performance matrix through the
+ injected safe CloakBrowser surface. No stock-browser result closes this
+ gate.
+6. Keep learner impact `NO_DATA` without consented participants; align and
+ verify Devpost, public video, repository, feedback ID, and submission receipt
+ before pushing the feature branch and fast-forwarding its verified tip into
+ `main`.
From 1f3067ce185679c0a42992667afde7a42973c737 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 05:19:28 +0530
Subject: [PATCH 024/143] perf(learner): defer verified theater payload
---
apps/web/src/App.tsx | 7 +-
.../learner/LockedBeliefBreakPreview.tsx | 88 +++++++++++++++++++
.../learner/VerifiedBeliefBreakTheater.tsx | 72 +--------------
3 files changed, 92 insertions(+), 75 deletions(-)
create mode 100644 apps/web/src/components/learner/LockedBeliefBreakPreview.tsx
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 822aa502..30646c11 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -82,7 +82,7 @@ import {
} from "./components/learner/PredictionSeal";
import { PrivacyPacketSummary } from "./components/learner/PrivacyPacketSummary";
import { QuestionComposer } from "./components/learner/QuestionComposer";
-import { VerifiedBeliefBreakMechanism } from "./components/learner/VerifiedBeliefBreakTheater";
+import { LockedBeliefBreakPreview } from "./components/learner/LockedBeliefBreakPreview";
import {
currentLearnerStage,
learnerSessionStatusLabel,
@@ -1142,10 +1142,7 @@ function Landing({
let fixed evidence answer.
-
+
+
+
+ Fair-test mechanism
+ Result locked until Prediction
+
+
+
+ Familiar rows · identities may repeat
+ Evaluation unit
+ Unseen entities · identities stay apart
+
+
+
+
+ Familiar-row test
+ —
+ identity may cross the split
+
+
+ →
+ same model
+
+
+ Unseen-entity test
+ —
+ test identities stay new
+
+
+
+
+ ✓
+ Model, features, preprocessing, sample sizes, metric, and seed stay
+ fixed.
+
+
+
+
+ Seal what you expect before CounterLab reveals whether the conclusion
+ stays similar when only the evaluation unit changes.
+
+
+
+
+
Boundary question
+ Where does the conclusion change or stop applying?
+
+
+
Learner action
+ Predict first, then let verified evidence answer.
+
+
+
+ );
+}
+
+export function LockedBeliefBreakPreview() {
+ return (
+
+ );
+}
diff --git a/apps/web/src/components/learner/VerifiedBeliefBreakTheater.tsx b/apps/web/src/components/learner/VerifiedBeliefBreakTheater.tsx
index 2321f9d7..dfc15789 100644
--- a/apps/web/src/components/learner/VerifiedBeliefBreakTheater.tsx
+++ b/apps/web/src/components/learner/VerifiedBeliefBreakTheater.tsx
@@ -6,6 +6,7 @@ import {
} from "../../features/boundary/sampleBoundaryFixture";
import { bundledSampleEvidence, type VerifiedRun } from "../../sample";
import sampleResultBytes from "../../../../../fixtures/public/leakage_verified_result.json?raw";
+import { LockedBeliefBreakEvidence } from "./LockedBeliefBreakPreview";
import styles from "./VerifiedBeliefBreakTheater.module.css";
const MODE_LABEL = "Verified sample exploration";
@@ -410,75 +411,6 @@ function FoldEvidence({ evidence }: { evidence: VerifiedBeliefBreakEvidence }) {
);
}
-function LockedFoldEvidence() {
- return (
-
-
-
- Fair-test mechanism
- Result locked until Prediction
-
-
-
- Familiar rows · identities may repeat
- Evaluation unit
- Unseen entities · identities stay apart
-
-
-
-
- Familiar-row test
- —
- identity may cross the split
-
-
- →
- same model
-
-
- Unseen-entity test
- —
- test identities stay new
-
-
-
-
- ✓
- Model, features, preprocessing, sample sizes, metric, and seed stay
- fixed.
-
-
-
-
- Seal what you expect before CounterLab reveals whether the conclusion
- stays similar when only the evaluation unit changes.
-
-
-
-
-
Boundary question
- Where does the conclusion change or stop applying?
-
-
-
Learner action
- Predict first, then let verified evidence answer.
-
-
-
- );
-}
-
function VerifiedEvidence({
evidence,
presentation,
@@ -752,7 +684,7 @@ export function VerifiedBeliefBreakMechanism({
className={`${styles.mechanismBody} ${presentation !== "full" ? styles.previewBody : ""} ${presentation === "compact" ? styles.compactBody : ""}`}
data-layout={presentation !== "full" ? "stable-preview" : "flow"}
>
-
+
) : (
Date: Tue, 21 Jul 2026 05:30:48 +0530
Subject: [PATCH 025/143] fix(judge): bind isolation copy to release health
---
apps/web/src/features/judge/JudgeModeView.test.tsx | 12 ++++++++++++
apps/web/src/features/judge/JudgeModeView.tsx | 12 ++++++++++--
2 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/apps/web/src/features/judge/JudgeModeView.test.tsx b/apps/web/src/features/judge/JudgeModeView.test.tsx
index bd9f9b30..6296fb4a 100644
--- a/apps/web/src/features/judge/JudgeModeView.test.tsx
+++ b/apps/web/src/features/judge/JudgeModeView.test.tsx
@@ -125,6 +125,12 @@ describe("JudgeModeView", () => {
expect(screen.getByText("Runtime Codex")).toBeInTheDocument();
expect(screen.getByText("Fixed kernel")).toBeInTheDocument();
expect(screen.getByText("Frozen verifier")).toBeInTheDocument();
+ expect(document.body).toHaveTextContent(
+ /filesystem generation read isolation is OS-enforced/i,
+ );
+ expect(document.body).not.toHaveTextContent(
+ /filesystem generation read isolation is explicitly PARTIAL/i,
+ );
expect(document.body).toHaveTextContent(
/evidence-first learning for notebook users/i,
);
@@ -297,6 +303,12 @@ describe("JudgeModeView", () => {
expect(document.body).toHaveTextContent(
/release identity is unbound.*live qualification as unproven/i,
);
+ expect(document.body).toHaveTextContent(
+ /no exact released filesystem generation read-isolation status is available/i,
+ );
+ expect(document.body).not.toHaveTextContent(
+ /filesystem generation read isolation is OS-enforced/i,
+ );
});
it("does not equate configured services with observed readiness", () => {
diff --git a/apps/web/src/features/judge/JudgeModeView.tsx b/apps/web/src/features/judge/JudgeModeView.tsx
index b0a598f2..48497be8 100644
--- a/apps/web/src/features/judge/JudgeModeView.tsx
+++ b/apps/web/src/features/judge/JudgeModeView.tsx
@@ -42,6 +42,14 @@ export function JudgeModeView({
}) {
const liveReady = liveAuthorityReady(health);
const release = health?.release?.status === "bound" ? health.release : null;
+ const generationIsolationBoundary =
+ release !== null &&
+ health?.generationFilesystemReadIsolation === "OS_ENFORCED"
+ ? "Filesystem generation read isolation is OS-enforced for the exact released live runtime."
+ : release !== null &&
+ health?.generationFilesystemReadIsolation === "PARTIAL"
+ ? "Filesystem generation read isolation is explicitly PARTIAL, so live authority remains unavailable."
+ : "No exact released filesystem generation read-isolation status is available, so live authority remains unproven.";
const [sampleEvidenceInspectionRequest, setSampleEvidenceInspectionRequest] =
useState(0);
@@ -401,8 +409,8 @@ export function JudgeModeView({
packages, arbitrary code execution, unsupported patch shapes, and
insufficient evidence. A Proof Capsule proves integrity and scoped
verification—not global mastery or formal sandbox security. The
- hosted Codex launch has a credential-and-privilege boundary;
- filesystem generation read isolation is explicitly PARTIAL.
+ hosted Codex launch has a credential-and-privilege boundary.{" "}
+ {generationIsolationBoundary}
Date: Tue, 21 Jul 2026 05:32:38 +0530
Subject: [PATCH 026/143] docs(submission): make Devpost fields paste-ready
---
docs/DEVPOST_COPY.md | 55 ++++++++++++++++++++++++++++++++++++++++----
1 file changed, 51 insertions(+), 4 deletions(-)
diff --git a/docs/DEVPOST_COPY.md b/docs/DEVPOST_COPY.md
index eeea2a71..7a23e9bc 100644
--- a/docs/DEVPOST_COPY.md
+++ b/docs/DEVPOST_COPY.md
@@ -12,12 +12,31 @@ CounterLab — A scientific debugger for beliefs
Ask like chat. Prove it like science.
+## Submission fields
+
+- **Category:** Education
+- **Primary audience:** ML learners and instructors who need to test what a
+ notebook result actually supports
+- **Live judge URL:**
+- **License:** MIT
+- **Repository URL:** **PENDING** anonymous-access verification; do not paste a
+ private or 404 URL
+- **Public video URL:** **PENDING** capture from the final qualified release
+- **Codex `/feedback` Session ID:** **PENDING** owner-supplied primary build
+ thread ID
+- **Exact release receipt:** **PENDING** final source/Container/Worker
+ qualification
+- **Ready-to-use notebook fixtures:**
+ `fixtures/notebooks/customer_churn_leakage.ipynb` and
+ `fixtures/notebooks/fraud_class_imbalance.ipynb`
+
## Short description
-CounterLab turns a learner's supported notebook claim into two competing
-models, an immutable Prediction, the smallest verified discriminating Test, a
-signed or integrity-hashed Boundary Map, deterministic transfer, and a minimal
-repair unlocked only after transfer passes.
+CounterLab helps ML learners and instructors test what a supported notebook
+result actually means. It turns the learner's claim into two competing models,
+an immutable Prediction, the smallest verified discriminating Test, a signed
+or integrity-hashed Boundary Map, deterministic transfer, and a minimal repair
+unlocked only after transfer passes.
CounterLab is evidence-first learning for notebook users: seal a Prediction,
change one condition, and let fixed evidence—not AI prose—release the bounded
@@ -51,6 +70,34 @@ The Evidence Verdict is exactly `SUPPORTS`, `INCONCLUSIVE`, or `REJECTED`.
CounterLab never labels the learner wrong and releases no result from a rejected
test.
+## What we built during Build Week
+
+The build-week implementation turned the earlier evidence engine into one
+learner-first, source-bound release candidate:
+
+- a chat-first Question composer and one six-stage progress model;
+- immutable Prediction, evidence story, Model Duel, Fair Test Builder,
+ Experiment Theater, Boundary Hunt, visual transfer, Repair preview, and
+ bounded completion;
+- a ten-second Judge preview bound to checked-in fixed sample evidence and a
+ one-click integrity-verified Sample Proof Capsule;
+- recoverable reject/insufficient-evidence paths, supported-artifact gating,
+ pre-Prediction result-leak regressions, stored proof-event hydration, and
+ capability-link lifecycle handling;
+- source/image/release identity contracts, exact-image isolation sentinels,
+ negative release controls, and fail-closed public readiness; and
+- keyboard, responsive, reduced-motion, interaction-evidence, replay, and
+ download coverage without adding learner names, raw free text, or notebook
+ content to analytics.
+
+Codex accelerated repository exploration, test construction, bounded repairs,
+release-safety review, and repeated regression runs during the build. Those
+engineering contributions are separate from **Runtime Codex inside the
+product**, which may propose only schema-valid plans and repairs. Human
+decisions fixed the product thesis, learner authority, supported Subject Packs,
+scientific invariants, privacy boundary, release claims, and every final
+integration decision.
+
## What is technically distinct
CounterLab separates authority instead of asking one model to propose, compute,
From 55788ef1cd2ddfaaee0e7f83d6ce1fbddfb6058b Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 05:34:13 +0530
Subject: [PATCH 027/143] docs(demo): lead with the verified belief break
---
docs/DEMO_CAPTIONS.vtt | 31 +++++++++++++++++++
docs/DEMO_SCRIPT.md | 69 +++++++++++++++++++++++++-----------------
2 files changed, 72 insertions(+), 28 deletions(-)
create mode 100644 docs/DEMO_CAPTIONS.vtt
diff --git a/docs/DEMO_CAPTIONS.vtt b/docs/DEMO_CAPTIONS.vtt
new file mode 100644
index 00000000..80315b52
--- /dev/null
+++ b/docs/DEMO_CAPTIONS.vtt
@@ -0,0 +1,31 @@
+WEBVTT
+
+00:00.000 --> 00:12.000
+CounterLab is for ML learners and instructors. This fixed sample changes one variable—who is new—and a 98.5% score becomes 59.4%. No model call produced these values.
+
+00:12.000 --> 00:27.000
+CounterLab begins with the Question. It treats notebook evidence as untrusted data, never runs a cell, and sends no raw rows, notebook file, or local path.
+
+00:27.000 --> 00:43.000
+GPT-5.6 proposes these competing explanations from only the approved sanitized packet. It cannot run the notebook or decide the result. The learner seals an immutable Prediction before any experimental value appears.
+
+00:43.000 --> 00:58.000
+Runtime Codex proposes only bounded operation IDs. The fixed scorer selects the test, and the frozen verifier decides whether the fixed kernel may release evidence.
+
+00:58.000 --> 01:18.000
+The numbers come from the fixed kernel. GPT-5.6 and Codex may explain or propose, but neither computes or verifies them.
+
+01:18.000 --> 01:34.000
+The learner explores already verified cells; the browser and the model calculate nothing new.
+
+01:34.000 --> 01:52.000
+No model grades the sentence. The fixed evaluator checks transfer on a different surface.
+
+01:52.000 --> 02:10.000
+Repair stays locked until deterministic transfer passes, and CounterLab patches only a copy.
+
+02:10.000 --> 02:30.000
+The Reasoning Diff is for the learner. The Proof Capsule binds the machine-readable evidence, versions, limitations, and event chain.
+
+02:30.000 --> 02:45.000
+Codex also accelerated the build through repository exploration, tests, and bounded repairs; humans fixed the scope and authority rules. Learner-impact status is NO_DATA, so we claim no measured learning gain. Chatbots explain. CounterLab lets reality answer.
diff --git a/docs/DEMO_SCRIPT.md b/docs/DEMO_SCRIPT.md
index 72b72a5a..909da168 100644
--- a/docs/DEMO_SCRIPT.md
+++ b/docs/DEMO_SCRIPT.md
@@ -1,57 +1,62 @@
-# 2:45 demo voiceover
+# 2:45 demo voiceover and shot list
Record **Live notebook analysis** only after the exact source has passed the
Container smoke. A bundled sample is frozen, not a substitute live run. Keep
**Verified replay** open as a disclosed backup and never present it as a new
-model call.
+model call. Use [DEMO_CAPTIONS.vtt](DEMO_CAPTIONS.vtt) word for word so the
+spoken roles, on-screen labels, and captions agree.
-## 0:00–0:15 — Question
+## 0:00–0:12 — The belief break
-Start on “What result are you trying to understand?” State that the notebook's
-98.5% score is claimed to generalize to unseen customers, then attach the
-untouched notebook. Say: “CounterLab begins with the question, not a mode or a
-dashboard.”
+Open Judge Mode on the disclosed fixed sample. Keep its Sample label visible
+and show 98.5% familiar rows beside 59.4% unseen customers. Say: “CounterLab is
+for ML learners and instructors. This fixed sample changes one variable—who is
+new—and a 98.5% score becomes 59.4%. No model call produced these values.”
-## 0:15–0:35 — Evidence and privacy
+## 0:12–0:27 — Question, evidence, and privacy
-Show the Notebook Evidence Story: the stored metric, exact cell/output
-reference, and why it matters. Open the short privacy summary and approve the
-exact sanitized packet. Say: “CounterLab reads this evidence as untrusted data;
-it never runs a notebook cell, and the model receives no raw rows, file, or
-local path.”
+Move to an exact-qualified **Live notebook analysis**. Start on “What result are
+you trying to understand?”, attach the untouched supported notebook, and show
+the exact cell/output reference plus privacy summary. Say: “CounterLab begins
+with the Question. It treats notebook evidence as untrusted data, never runs a
+cell, and sends no raw rows, notebook file, or local path.”
-## 0:35–0:55 — Model Duel and Prediction Seal
+## 0:27–0:43 — GPT-5.6 and Prediction
Show the learner's current explanation and the alternative with equal visual
weight. Confirm that the first captures the learner's meaning. Choose “Remain
-near 98%,” set confidence, and seal it. Say: “This is a commitment, not a quiz.
-No experimental result is released before the Prediction is immutable.”
+near 98%,” set confidence, and seal it. Say: “GPT-5.6 proposes these competing
+explanations from only the approved sanitized packet. It cannot run the
+notebook or decide the result. The learner seals an immutable Prediction before
+any experimental value appears.”
-## 0:55–1:15 — Fair Test Builder
+## 0:43–0:58 — Runtime Codex and Fair Test Builder
Show what changes, what stays fixed, and why whole-customer holdout matches
deployment. If this exact live compile is genuinely rejected and repaired, show
the learner-language explanation and optionally open Evidence & proof for the
sanitized counterexample. If it passes first try, say so. Never stage a failure.
-Say: “Codex proposes bounded artifacts; the fixed scorer selects, and the
-verifier decides whether the fixed kernel may release evidence.”
+Say: “Runtime Codex proposes only bounded operation IDs. The fixed scorer
+selects the test, and the frozen verifier decides whether the fixed kernel may
+release evidence.”
-## 1:15–1:40 — Experiment Theater
+## 0:58–1:18 — Experiment Theater
After the verified payload arrives, show the pinned Prediction and one dominant
comparison: 98.5% random rows versus 59.4% unseen customers, with controlled
model and preprocessing. Use the exact table for 67.4% identity ablation, zero
group overlap, sample sizes, seed, and result hash. Say: “The numbers come from
-the fixed kernel and stay hidden until verification.”
+the fixed kernel. GPT-5.6 and Codex may explain or propose, but neither computes
+or verifies them.”
-## 1:40–1:55 — Boundary
+## 1:18–1:34 — Boundary
Try one verified Boundary Hunt point, then reveal the full map. Show its
non-color symbols, exact-value table, assumptions, and integrity or signature
wording. Say: “The learner explores already verified cells; the browser and the
model calculate nothing new.”
-## 1:55–2:15 — Reflect and apply
+## 1:34–1:52 — Reflect and Apply
Build: “When rows repeat the same entity, I should hold out whole entities,
because random rows can share identity across train and test.” Then use the
@@ -59,20 +64,28 @@ timeline to select a time-ordered holdout and identify the feature that crosses
NOW. Say: “No model grades the sentence. The fixed evaluator checks transfer on
a different surface.”
-## 2:15–2:32 — Repair
+## 1:52–2:10 — Repair
Show the repair preview: random rows become whole-customer holdout, identity is
removed, and overlap is reported; target, model family, unrelated cells, and the
original notebook remain preserved. Review the verified copied-notebook patch
-and use the primary download. Emphasize that the upload was never overwritten.
+and use the primary download. Say: “Repair stays locked until deterministic
+transfer passes, and CounterLab patches only a copy.”
-## 2:32–2:45 — Completion and proof
+## 2:10–2:30 — Completion and proof
Show: “You completed one verified entity-leakage loop: this fixed task separated
performance on familiar rows from performance on new entities. It does not
establish mastery.” Then open Evidence & proof for the Reasoning Diff and Proof
-Capsule. Close: “Chatbots explain. CounterLab lets reality answer—and only then
-helps you fix the code.”
+Capsule. Say: “The Reasoning Diff is for the learner. The Proof Capsule binds
+the machine-readable evidence, versions, limitations, and event chain.”
+
+## 2:30–2:45 — Build truth and close
+
+Show the exact release identity. Say: “Codex also accelerated the build through
+repository exploration, tests, and bounded repairs; humans fixed the scope and
+authority rules. Learner-impact status is NO_DATA, so we claim no measured
+learning gain. Chatbots explain. CounterLab lets reality answer.”
## Backup disclosure
From eafd8677a11ba0898ae9fbb9a1918d3515dbc206 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 05:35:45 +0530
Subject: [PATCH 028/143] docs(codex): separate build and runtime authority
---
docs/CODEX_USAGE.md | 32 +++++++++++++++++++++++---------
1 file changed, 23 insertions(+), 9 deletions(-)
diff --git a/docs/CODEX_USAGE.md b/docs/CODEX_USAGE.md
index cfce5b5e..eea02fb4 100644
--- a/docs/CODEX_USAGE.md
+++ b/docs/CODEX_USAGE.md
@@ -1,9 +1,19 @@
-# Runtime Codex usage
+# Codex usage: build-time and runtime
CounterLab uses Codex as a bounded Plan compiler. Codex proposes how to compose
registered experiments or repairs; it never owns numeric truth, learner transfer
scoring, or final verification.
+## Build-time Codex
+
+Codex also accelerated the build as an engineering collaborator: repository
+mapping, focused test construction, bounded implementation slices, regression
+triage, release-safety review, and documentation reconciliation. Those
+build-time contributions do not grant Runtime Codex any additional product
+authority. Humans fixed the product thesis, learner and model authority,
+supported Subject Packs, scientific invariants, privacy boundary, acceptance
+criteria, and final integration decisions.
+
## Product modes
| Mode | Model calls | Meaning |
@@ -37,9 +47,11 @@ Codex receives only the approved Belief Spec, sanitized Artifact Manifest,
resolved evidence, selected Subject Pack capabilities/schema, resource limits,
and permitted outputs in its request packet. That packet excludes notebook
bytes, raw rows, secrets, hidden verifier source, mutation implementations,
-held-out fixtures, R2/D1 credentials, and unrelated files. This input-minimizing
-contract does not claim the hosted process is OS-unable to read every runtime
-file; hosted filesystem generation read isolation is `PARTIAL`.
+held-out fixtures, R2/D1 credentials, and unrelated files. The historical
+recorded replay used an input-minimizing process boundary with `PARTIAL`
+filesystem read isolation. Current source admits hosted Live only when the
+exact runner proves `OS_ENFORCED` generation read isolation; source logic is not
+production evidence until that exact image is qualified and deployed.
### Outputs
@@ -92,11 +104,13 @@ model-authored Python, and it does not require nested Docker. Missing binding,
credential, timeout, or process capability is a typed failure—not replay or
sample success.
-The hosted Codex launcher stages and revokes credentials, uses a fixed non-root
-UID plus `setpriv --no-new-privs`, and constrains writes. It does not provide a
-mount namespace or filesystem read allowlist, so hosted filesystem generation
-read isolation is `PARTIAL`.
-Source-free plans still have no numerical or verification authority.
+The hosted Codex launcher stages and revokes credentials and requires an exact
+image sentinel for a fixed non-root UID, `no-new-privs`, a pinned Bubblewrap
+boundary, an allowlisted runtime mount set, a writable generation workspace,
+and missing repository, held-out, verifier, and unrelated-job paths. Judge Mode
+keeps Live unavailable unless that exact `OS_ENFORCED` result is bound to the
+public release. Source-free plans still have no numerical or verification
+authority.
## Advanced local adapter proof
From 45038ce552ce80298af8d5840f6b2c16e27aa917 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 05:36:44 +0530
Subject: [PATCH 029/143] docs(readme): document samples and Codex decisions
---
README.md | 25 ++++++++++++++++++++-----
1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 8cfc73d5..7241ef6f 100644
--- a/README.md
+++ b/README.md
@@ -190,6 +190,14 @@ Required for local reproduction: Node 22+, pnpm 11.13.1, Python 3.12+, and the
locked dependencies. A Docker-compatible engine is required for local
Cloudflare Container development and for the advanced local adapter proof.
+The two ready-to-use supported notebook fixtures are:
+
+- `fixtures/notebooks/customer_churn_leakage.ipynb`
+- `fixtures/notebooks/fraud_class_imbalance.ipynb`
+
+Use them for the entity-leakage and class-imbalance live paths respectively;
+do not treat them as evidence that arbitrary notebooks are supported.
+
```bash
cp .env.example .env.local
./scripts/clean-demo.sh
@@ -322,11 +330,18 @@ See [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md).
## How Codex accelerated the build
-Codex helped implement and test the monorepo, then CounterLab used Codex itself
-as a bounded runtime compiler. The real rejected trace exposed two product
-contract defects: incomplete public SDK documentation and transient bytecode in
-an exact-file workspace. The public contract was strengthened; a separate later
-run verified successfully.
+Build-time Codex helped map the monorepo, turn audit findings into focused
+regressions, implement bounded UI and release slices, triage cross-language
+failures, review authority drift, and keep the evidence documents synchronized
+with executed checks. Changes were integrated as small independently reviewed
+commits; a source test, deployment receipt, model call, or learner result was
+never treated as successful without direct evidence.
+
+That engineering role is separate from Runtime Codex inside CounterLab. Runtime
+Codex acts only as a bounded Plan compiler. The real rejected trace exposed two
+product-contract defects: incomplete public SDK documentation and transient
+bytecode in an exact-file workspace. The public contract was strengthened; a
+separate later run verified successfully.
Human decisions remained the product thesis, learner authority, concept scope,
fixture parameters, evidence contracts, verifier invariants, transfer design,
From 94a5d51ad33c1672ae32485d9011a70179546129 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 05:39:42 +0530
Subject: [PATCH 030/143] fix(runtime): align timeout rlimit authority
---
scripts/timeout-cleanup-receipt.test.ts | 12 +++++++++
scripts/timeout-cleanup-receipt.ts | 16 ++++++------
.../src/counterlab_runner/timeout_proof.py | 2 +-
services/runner/tests/test_timeout_proof.py | 25 ++++++++++++++++++-
4 files changed, 46 insertions(+), 9 deletions(-)
diff --git a/scripts/timeout-cleanup-receipt.test.ts b/scripts/timeout-cleanup-receipt.test.ts
index fd5f637e..e4b2e375 100644
--- a/scripts/timeout-cleanup-receipt.test.ts
+++ b/scripts/timeout-cleanup-receipt.test.ts
@@ -6,6 +6,7 @@ import { canonicalJson } from "../packages/session-core/src/index.js";
import {
QUALIFIED_AGGREGATE_LIMIT_MODE,
+ TIMEOUT_ROOTLESS_RLIMIT_TYPES,
assertQualifiedAggregateRuntimeLimits,
} from "./timeout-cleanup-receipt.js";
@@ -98,6 +99,17 @@ function aggregateInput() {
}
describe("timeout cleanup aggregate resource authority", () => {
+ it("matches the five rlimits emitted by the contained runtime", () => {
+ expect(TIMEOUT_ROOTLESS_RLIMIT_TYPES).toEqual([
+ "RLIMIT_AS",
+ "RLIMIT_CPU",
+ "RLIMIT_FSIZE",
+ "RLIMIT_NOFILE",
+ "RLIMIT_NPROC",
+ ]);
+ expect(TIMEOUT_ROOTLESS_RLIMIT_TYPES).not.toContain("RLIMIT_CORE");
+ });
+
it("rejects process-only or merely declared aggregate limits", () => {
expect(() =>
assertQualifiedAggregateRuntimeLimits({
diff --git a/scripts/timeout-cleanup-receipt.ts b/scripts/timeout-cleanup-receipt.ts
index c0af94d4..1fa18dfa 100644
--- a/scripts/timeout-cleanup-receipt.ts
+++ b/scripts/timeout-cleanup-receipt.ts
@@ -164,15 +164,17 @@ const AggregateLimitEvidenceSchema = z
}
});
+export const TIMEOUT_ROOTLESS_RLIMIT_TYPES = [
+ "RLIMIT_AS",
+ "RLIMIT_CPU",
+ "RLIMIT_FSIZE",
+ "RLIMIT_NOFILE",
+ "RLIMIT_NPROC",
+] as const;
+
const RlimitSchema = z
.strictObject({
- type: z.enum([
- "RLIMIT_AS",
- "RLIMIT_CORE",
- "RLIMIT_CPU",
- "RLIMIT_FSIZE",
- "RLIMIT_NPROC",
- ]),
+ type: z.enum(TIMEOUT_ROOTLESS_RLIMIT_TYPES),
soft: PositiveSafeIntegerSchema,
hard: PositiveSafeIntegerSchema,
})
diff --git a/services/runner/src/counterlab_runner/timeout_proof.py b/services/runner/src/counterlab_runner/timeout_proof.py
index e67d2edf..e23c099b 100644
--- a/services/runner/src/counterlab_runner/timeout_proof.py
+++ b/services/runner/src/counterlab_runner/timeout_proof.py
@@ -408,9 +408,9 @@ def validate_rootless_receipt(
def _validate_enforced_rlimits(value: object) -> bool:
required = {
"RLIMIT_AS",
- "RLIMIT_CORE",
"RLIMIT_CPU",
"RLIMIT_FSIZE",
+ "RLIMIT_NOFILE",
"RLIMIT_NPROC",
}
if not isinstance(value, list) or len(value) != len(required):
diff --git a/services/runner/tests/test_timeout_proof.py b/services/runner/tests/test_timeout_proof.py
index 156a359f..5d30b655 100644
--- a/services/runner/tests/test_timeout_proof.py
+++ b/services/runner/tests/test_timeout_proof.py
@@ -171,9 +171,9 @@ def _rootless(control: dict[str, object], build: dict[str, object]) -> dict[str,
{"type": name, "soft": 1, "hard": 1}
for name in (
"RLIMIT_AS",
- "RLIMIT_CORE",
"RLIMIT_CPU",
"RLIMIT_FSIZE",
+ "RLIMIT_NOFILE",
"RLIMIT_NPROC",
)
],
@@ -221,6 +221,29 @@ def test_control_receipt_rejects_result_release_and_hash_mutation() -> None:
validate_control_receipt({**control, "receiptPayloadSha256": "f" * 64})
+def test_rootless_rlimit_validator_matches_the_runtime_contract() -> None:
+ runtime_limits = [
+ {"type": name, "soft": 1, "hard": 1}
+ for name in (
+ "RLIMIT_AS",
+ "RLIMIT_CPU",
+ "RLIMIT_FSIZE",
+ "RLIMIT_NOFILE",
+ "RLIMIT_NPROC",
+ )
+ ]
+
+ assert timeout_proof_module._validate_enforced_rlimits(runtime_limits)
+ assert not timeout_proof_module._validate_enforced_rlimits(
+ [
+ {**entry, "type": "RLIMIT_CORE"}
+ if entry["type"] == "RLIMIT_NOFILE"
+ else entry
+ for entry in runtime_limits
+ ]
+ )
+
+
def test_rootless_receipt_binds_control_and_exact_adapter_authority() -> None:
control = _control()
build = {
From b7c54fe11f4d365ea93e55841e23027b205a1ce4 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 05:49:23 +0530
Subject: [PATCH 031/143] fix(runtime): retain deterministic cgroup limits
---
scripts/contained-rootless-spec.mjs | 12 +++++-------
scripts/contained-runtime-run.mjs | 2 +-
scripts/contained-runtime.test.ts | 14 +++++++-------
3 files changed, 13 insertions(+), 15 deletions(-)
diff --git a/scripts/contained-rootless-spec.mjs b/scripts/contained-rootless-spec.mjs
index 5db554d1..e6887295 100644
--- a/scripts/contained-rootless-spec.mjs
+++ b/scripts/contained-rootless-spec.mjs
@@ -1447,14 +1447,13 @@ export function sanitizeContainedRootlessSpec({
);
if (hasNerdctlHooks) delete parsed.hooks;
delete parsed.annotations;
- delete linux.cgroupsPath;
- delete linux.resources;
+ linux.cgroupsPath = `${namespace}/${expected.invocationId}`;
delete linux.sysctl;
if (
- Object.hasOwn(linux, "cgroupsPath") ||
- Object.hasOwn(linux, "resources")
+ linux.cgroupsPath !== `${namespace}/${expected.invocationId}` ||
+ !Object.hasOwn(linux, "resources")
) {
- throw new Error("contained rootless OCI spec cgroup removal failed");
+ throw new Error("contained rootless OCI spec cgroup normalization failed");
}
const baseSpecSha256 = sha256(canonicalJson(parsed));
const runtimeAnnotations = {
@@ -1527,13 +1526,12 @@ export function sanitizeContainedRootlessSpec({
removedFields: [
...(hasNerdctlHooks ? ["hooks"] : []),
"annotations",
- "linux.cgroupsPath",
- "linux.resources",
"linux.sysctl",
"linux.seccomp.restrictedTraceRule",
],
normalizedFields: [
"annotations",
+ "linux.cgroupsPath",
"process.terminal",
"process.user.additionalGids",
"process.env.HOSTNAME",
diff --git a/scripts/contained-runtime-run.mjs b/scripts/contained-runtime-run.mjs
index 19628956..f414fc01 100644
--- a/scripts/contained-runtime-run.mjs
+++ b/scripts/contained-runtime-run.mjs
@@ -521,7 +521,7 @@ export function containedRunPlan({
"--runc-root",
resolve(sessionRoot, "run/runc"),
"--cgroup",
- "",
+ `${namespace}/${invocationId}`,
"--platform",
"linux/amd64",
],
diff --git a/scripts/contained-runtime.test.ts b/scripts/contained-runtime.test.ts
index b83dbd10..6a9d9ceb 100644
--- a/scripts/contained-runtime.test.ts
+++ b/scripts/contained-runtime.test.ts
@@ -1111,7 +1111,7 @@ describe("contained runtime command policy", () => {
"--fifo-dir",
clientFifoRoot,
"--cgroup",
- "",
+ `counterlab-v6.1/${invocationId}`,
"--platform",
"linux/amd64",
]),
@@ -1181,7 +1181,7 @@ describe("contained runtime command policy", () => {
expect(containedRuntimeResourceAbsent(missing())).toBe(true);
});
- it("removes only unsupported cgroup fields from a bounded OCI spec", () => {
+ it("retains bounded cgroup intent without claiming observed enforcement", () => {
const sessionRoot = resolve(root, ".rt/rt-validator-spec");
const installRoot = resolve(
root,
@@ -1213,9 +1213,10 @@ describe("contained runtime command policy", () => {
source,
});
const sanitized = JSON.parse(prepared.config);
+ const original = JSON.parse(source);
- expect(sanitized.linux).not.toHaveProperty("cgroupsPath");
- expect(sanitized.linux).not.toHaveProperty("resources");
+ expect(sanitized.linux.cgroupsPath).toBe(`counterlab-v6.1/${invocationId}`);
+ expect(sanitized.linux.resources).toEqual(original.linux.resources);
expect(sanitized.process.rlimits).toEqual(plan.expected.rlimits);
expect(sanitized.process.noNewPrivileges).toBe(true);
expect(sanitized.process.terminal).toBe(false);
@@ -1286,8 +1287,6 @@ describe("contained runtime command policy", () => {
removedFields: [
"hooks",
"annotations",
- "linux.cgroupsPath",
- "linux.resources",
"linux.sysctl",
"linux.seccomp.restrictedTraceRule",
],
@@ -1295,6 +1294,7 @@ describe("contained runtime command policy", () => {
finalContainerId: prepared.finalContainerId,
removedMounts: ["/etc/hostname", "/etc/hosts", "/etc/resolv.conf"],
});
+ expect(prepared.receipt.normalizedFields).toContain("linux.cgroupsPath");
const missingHookSource = JSON.parse(source);
missingHookSource.hooks = null;
@@ -2279,7 +2279,7 @@ describe("contained runtime command policy", () => {
expect(startCall).toEqual(
expect.arrayContaining([
"--cgroup",
- "",
+ `counterlab-v6.1/${invocationId}`,
"--label",
`io.counterlab.runtime.invocation=${invocationId}`,
]),
From fa4261dbd0fb1ebc10655eadb6badbff4000ce4c Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 05:53:02 +0530
Subject: [PATCH 032/143] test(runtime): harden cgroup intent bindings
---
scripts/contained-runtime.test.ts | 51 +++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/scripts/contained-runtime.test.ts b/scripts/contained-runtime.test.ts
index 6a9d9ceb..e9bb9308 100644
--- a/scripts/contained-runtime.test.ts
+++ b/scripts/contained-runtime.test.ts
@@ -1116,6 +1116,10 @@ describe("contained runtime command policy", () => {
"linux/amd64",
]),
);
+ expect(
+ plan.start.args.filter((argument) => argument === "--cgroup"),
+ ).toHaveLength(1);
+ expect(plan.start.args).not.toContain("");
expect(plan.containerName).toBe("counterlab-startup-validator");
expect(plan.start.args).not.toContain("counterlab-startup-validator");
expect(plan.mountImageRootfs).toEqual({
@@ -1217,6 +1221,17 @@ describe("contained runtime command policy", () => {
expect(sanitized.linux.cgroupsPath).toBe(`counterlab-v6.1/${invocationId}`);
expect(sanitized.linux.resources).toEqual(original.linux.resources);
+ const canonicalBase = structuredClone(sanitized);
+ delete canonicalBase.annotations;
+ expect(prepared.receipt.baseSpecSha256).toBe(
+ createHash("sha256").update(canonicalJson(canonicalBase)).digest("hex"),
+ );
+ expect(prepared.receipt.sanitizedSpecSha256).toBe(
+ createHash("sha256").update(canonicalJson(sanitized)).digest("hex"),
+ );
+ expect(prepared.receipt.configFileSha256).toBe(
+ createHash("sha256").update(prepared.config).digest("hex"),
+ );
expect(sanitized.process.rlimits).toEqual(plan.expected.rlimits);
expect(sanitized.process.noNewPrivileges).toBe(true);
expect(sanitized.process.terminal).toBe(false);
@@ -1296,6 +1311,42 @@ describe("contained runtime command policy", () => {
});
expect(prepared.receipt.normalizedFields).toContain("linux.cgroupsPath");
+ const resourceMutations: Array<(spec: typeof original) => void> = [
+ (spec) => {
+ spec.linux.resources.memory.limit += 1;
+ },
+ (spec) => {
+ spec.linux.resources.memory.swap = -1;
+ },
+ (spec) => {
+ spec.linux.resources.pids.limit += 1;
+ },
+ (spec) => {
+ spec.linux.resources.cpu.quota -= 1;
+ },
+ (spec) => {
+ spec.linux.resources.devices[1].major = 2;
+ },
+ (spec) => {
+ spec.linux.resources.io = {};
+ },
+ (spec) => {
+ spec.linux.cgroupsPath = "/escaped";
+ },
+ ];
+ for (const mutate of resourceMutations) {
+ const changed = structuredClone(original);
+ mutate(changed);
+ expect(() =>
+ sanitizeContainedRootlessSpec({
+ containerId,
+ expected,
+ metadataSha256: "3".repeat(64),
+ source: JSON.stringify(changed),
+ }),
+ ).toThrow(/cgroup path|resource/u);
+ }
+
const missingHookSource = JSON.parse(source);
missingHookSource.hooks = null;
expect(() =>
From 22e489ff3d25457de37b95a7b5f8639a18e4e770 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 05:55:10 +0530
Subject: [PATCH 033/143] fix(runtime): request delegated cgroup namespace
---
scripts/contained-runtime-supervisor.mjs | 3 +++
scripts/contained-runtime-supervisor.test.ts | 24 ++++++++++++++++++++
2 files changed, 27 insertions(+)
diff --git a/scripts/contained-runtime-supervisor.mjs b/scripts/contained-runtime-supervisor.mjs
index c9e4e034..61c6f096 100644
--- a/scripts/contained-runtime-supervisor.mjs
+++ b/scripts/contained-runtime-supervisor.mjs
@@ -91,6 +91,9 @@ const containerdRootlesskit = spawn(
[
`--state-dir=${resolve(runRoot, "containerd-rootless")}`,
"--net=host",
+ "--pidns",
+ "--cgroupns",
+ "--evacuate-cgroup2=containerd",
process.execPath,
resolve(root, "scripts/contained-runtime-server.mjs"),
"--session-id",
diff --git a/scripts/contained-runtime-supervisor.test.ts b/scripts/contained-runtime-supervisor.test.ts
index ef4bee79..abdc0e30 100644
--- a/scripts/contained-runtime-supervisor.test.ts
+++ b/scripts/contained-runtime-supervisor.test.ts
@@ -84,6 +84,30 @@ describe("contained runtime supervisor protocol", () => {
);
});
+ it("requests delegated PID and cgroup namespaces for containerd", () => {
+ const source = readFileSync(
+ resolve(process.cwd(), "scripts/contained-runtime-supervisor.mjs"),
+ "utf8",
+ );
+ const launch = source.slice(
+ source.indexOf("const containerdRootlesskit = spawn("),
+ source.indexOf("closeSync(containerdLog)"),
+ );
+
+ expect(launch).toContain('"--pidns"');
+ expect(launch).toContain('"--cgroupns"');
+ expect(launch).toContain('"--evacuate-cgroup2=containerd"');
+ expect(launch.indexOf('"--pidns"')).toBeLessThan(
+ launch.indexOf("scripts/contained-runtime-server.mjs"),
+ );
+ expect(launch.indexOf('"--cgroupns"')).toBeLessThan(
+ launch.indexOf("scripts/contained-runtime-server.mjs"),
+ );
+ expect(launch.indexOf('"--evacuate-cgroup2=containerd"')).toBeLessThan(
+ launch.indexOf("scripts/contained-runtime-server.mjs"),
+ );
+ });
+
it("accepts only exact hash-bound status, drain, and shutdown requests", () => {
expect(
parseSupervisorRequest(
From d797b4c5245cb7b1d95710fc576932dff2b29c20 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 06:00:58 +0530
Subject: [PATCH 034/143] feat(runtime): bind aggregate qualification requests
---
scripts/contained-runtime-attestation.mjs | 1 +
scripts/contained-runtime-client.mjs | 19 ++++--
scripts/contained-runtime-request.d.mts | 27 ++++++++
scripts/contained-runtime-request.mjs | 82 +++++++++++++++++++++++
scripts/contained-runtime-request.test.ts | 74 ++++++++++++++++++++
scripts/contained-runtime-server.mjs | 28 ++------
6 files changed, 204 insertions(+), 27 deletions(-)
create mode 100644 scripts/contained-runtime-request.d.mts
create mode 100644 scripts/contained-runtime-request.mjs
create mode 100644 scripts/contained-runtime-request.test.ts
diff --git a/scripts/contained-runtime-attestation.mjs b/scripts/contained-runtime-attestation.mjs
index 4a437924..8beb9da0 100644
--- a/scripts/contained-runtime-attestation.mjs
+++ b/scripts/contained-runtime-attestation.mjs
@@ -13,6 +13,7 @@ export const RUNTIME_POLICY_PATH =
export const RUNTIME_HELPER_PATHS = Object.freeze({
runtimeClient: "scripts/contained-runtime-client.mjs",
+ runtimeRequest: "scripts/contained-runtime-request.mjs",
runtimeRun: "scripts/contained-runtime-run.mjs",
rootlessSpec: "scripts/contained-rootless-spec.mjs",
imageAuthority: "scripts/contained-image-authority.mjs",
diff --git a/scripts/contained-runtime-client.mjs b/scripts/contained-runtime-client.mjs
index dcaa8ace..809ab305 100644
--- a/scripts/contained-runtime-client.mjs
+++ b/scripts/contained-runtime-client.mjs
@@ -13,6 +13,10 @@ import { isAbsolute, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { validateContainedRunControlReceipt } from "./contained-runtime-run.mjs";
+import {
+ AGGREGATE_TIMEOUT_QUALIFICATION_MODE,
+ createContainedRuntimeRequest,
+} from "./contained-runtime-request.mjs";
const root = realpathSync(resolve(fileURLToPath(import.meta.url), "../.."));
const argv = process.argv.slice(2);
@@ -91,11 +95,16 @@ if (command[0] === "login") {
}
}
-const request = `${JSON.stringify({
- schemaVersion: "1",
- args: command,
- stdinBase64: stdin.toString("base64"),
-})}\n`;
+const request = `${JSON.stringify(
+ createContainedRuntimeRequest({
+ args: command,
+ qualificationMode:
+ controlReceiptPath === undefined
+ ? null
+ : AGGREGATE_TIMEOUT_QUALIFICATION_MODE,
+ stdin,
+ }),
+)}\n`;
const response = await new Promise((accept, reject) => {
const chunks = [];
diff --git a/scripts/contained-runtime-request.d.mts b/scripts/contained-runtime-request.d.mts
new file mode 100644
index 00000000..2501285d
--- /dev/null
+++ b/scripts/contained-runtime-request.d.mts
@@ -0,0 +1,27 @@
+export const AGGREGATE_TIMEOUT_QUALIFICATION_MODE: "aggregate-timeout-proof-v1";
+
+export type ContainedRuntimeQualificationMode =
+ typeof AGGREGATE_TIMEOUT_QUALIFICATION_MODE | null;
+
+export interface ContainedRuntimeWireRequest {
+ schemaVersion: "2";
+ args: string[];
+ qualificationMode: ContainedRuntimeQualificationMode;
+ stdinBase64: string;
+}
+
+export interface ContainedRuntimeRequest {
+ args: string[];
+ qualificationMode: ContainedRuntimeQualificationMode;
+ stdin: Buffer;
+}
+
+export function parseContainedRuntimeRequest(
+ value: unknown,
+): ContainedRuntimeRequest;
+
+export function createContainedRuntimeRequest(input: {
+ args: string[];
+ qualificationMode: ContainedRuntimeQualificationMode;
+ stdin: Buffer;
+}): ContainedRuntimeWireRequest;
diff --git a/scripts/contained-runtime-request.mjs b/scripts/contained-runtime-request.mjs
new file mode 100644
index 00000000..27f1e26e
--- /dev/null
+++ b/scripts/contained-runtime-request.mjs
@@ -0,0 +1,82 @@
+export const AGGREGATE_TIMEOUT_QUALIFICATION_MODE =
+ "aggregate-timeout-proof-v1";
+
+const requestKeys = [
+ "args",
+ "qualificationMode",
+ "schemaVersion",
+ "stdinBase64",
+];
+
+function validateArguments(value) {
+ if (
+ !Array.isArray(value) ||
+ value.length === 0 ||
+ value.length > 128 ||
+ value.some(
+ (argument) =>
+ typeof argument !== "string" ||
+ argument.length === 0 ||
+ argument.length > 8_192,
+ )
+ ) {
+ throw new Error("contained runtime request argument is invalid");
+ }
+}
+
+function validateQualificationMode(value, args) {
+ if (value !== null && value !== AGGREGATE_TIMEOUT_QUALIFICATION_MODE) {
+ throw new Error("contained runtime request qualification mode is invalid");
+ }
+ if (value !== null && args[0] !== "run") {
+ throw new Error("contained runtime qualification requires a run command");
+ }
+}
+
+export function parseContainedRuntimeRequest(value) {
+ if (
+ value === null ||
+ typeof value !== "object" ||
+ Array.isArray(value) ||
+ JSON.stringify(Object.keys(value).sort()) !==
+ JSON.stringify([...requestKeys].sort()) ||
+ value.schemaVersion !== "2"
+ ) {
+ throw new Error("contained runtime request shape is invalid");
+ }
+ validateArguments(value.args);
+ validateQualificationMode(value.qualificationMode, value.args);
+ if (typeof value.stdinBase64 !== "string") {
+ throw new Error("contained runtime request input is invalid");
+ }
+ const stdin = Buffer.from(value.stdinBase64, "base64");
+ if (
+ stdin.byteLength > 8_192 ||
+ stdin.toString("base64") !== value.stdinBase64
+ ) {
+ throw new Error("contained runtime request input is invalid");
+ }
+ return {
+ args: [...value.args],
+ qualificationMode: value.qualificationMode,
+ stdin,
+ };
+}
+
+export function createContainedRuntimeRequest({
+ args,
+ qualificationMode,
+ stdin,
+}) {
+ if (!Buffer.isBuffer(stdin)) {
+ throw new Error("contained runtime request input is invalid");
+ }
+ const request = {
+ schemaVersion: "2",
+ args: Array.isArray(args) ? [...args] : args,
+ qualificationMode,
+ stdinBase64: stdin.toString("base64"),
+ };
+ parseContainedRuntimeRequest(request);
+ return request;
+}
diff --git a/scripts/contained-runtime-request.test.ts b/scripts/contained-runtime-request.test.ts
new file mode 100644
index 00000000..adacd0f3
--- /dev/null
+++ b/scripts/contained-runtime-request.test.ts
@@ -0,0 +1,74 @@
+import { describe, expect, it } from "vitest";
+
+import {
+ AGGREGATE_TIMEOUT_QUALIFICATION_MODE,
+ createContainedRuntimeRequest,
+ parseContainedRuntimeRequest,
+} from "./contained-runtime-request.mjs";
+
+describe("contained runtime request protocol", () => {
+ it("keeps ordinary commands outside qualification mode", () => {
+ const request = createContainedRuntimeRequest({
+ args: ["version"],
+ qualificationMode: null,
+ stdin: Buffer.alloc(0),
+ });
+
+ expect(request).toEqual({
+ schemaVersion: "2",
+ args: ["version"],
+ qualificationMode: null,
+ stdinBase64: "",
+ });
+ expect(parseContainedRuntimeRequest(request)).toEqual({
+ args: ["version"],
+ qualificationMode: null,
+ stdin: Buffer.alloc(0),
+ });
+ });
+
+ it("allows aggregate qualification only for an explicit run", () => {
+ const request = createContainedRuntimeRequest({
+ args: ["run", "bounded-image"],
+ qualificationMode: AGGREGATE_TIMEOUT_QUALIFICATION_MODE,
+ stdin: Buffer.from("bounded input"),
+ });
+
+ expect(parseContainedRuntimeRequest(request)).toEqual({
+ args: ["run", "bounded-image"],
+ qualificationMode: AGGREGATE_TIMEOUT_QUALIFICATION_MODE,
+ stdin: Buffer.from("bounded input"),
+ });
+ expect(() =>
+ createContainedRuntimeRequest({
+ args: ["version"],
+ qualificationMode: AGGREGATE_TIMEOUT_QUALIFICATION_MODE,
+ stdin: Buffer.alloc(0),
+ }),
+ ).toThrow(/qualification/u);
+ });
+
+ it("rejects unknown, malformed, and non-canonical requests", () => {
+ const ordinary = createContainedRuntimeRequest({
+ args: ["version"],
+ qualificationMode: null,
+ stdin: Buffer.alloc(0),
+ });
+
+ expect(() =>
+ parseContainedRuntimeRequest({ ...ordinary, extra: true }),
+ ).toThrow(/shape/u);
+ expect(() =>
+ parseContainedRuntimeRequest({
+ ...ordinary,
+ qualificationMode: "unknown",
+ }),
+ ).toThrow(/qualification/u);
+ expect(() =>
+ parseContainedRuntimeRequest({ ...ordinary, stdinBase64: "%%%" }),
+ ).toThrow(/input/u);
+ expect(() =>
+ parseContainedRuntimeRequest({ ...ordinary, args: ["version", ""] }),
+ ).toThrow(/argument/u);
+ });
+});
diff --git a/scripts/contained-runtime-server.mjs b/scripts/contained-runtime-server.mjs
index b46a1e42..8c50187d 100644
--- a/scripts/contained-runtime-server.mjs
+++ b/scripts/contained-runtime-server.mjs
@@ -24,6 +24,7 @@ import {
probeContainedShimSocketDirectory,
} from "./contained-containerd-config.mjs";
import { createContainedRuntimeEnvironment } from "./contained-runtime-environment.mjs";
+import { parseContainedRuntimeRequest } from "./contained-runtime-request.mjs";
import { verifyPersistedContainedRootlessSpec } from "./contained-rootless-spec.mjs";
const root = realpathSync(resolve(fileURLToPath(import.meta.url), "../.."));
@@ -404,28 +405,10 @@ const server = createServer((socket) => {
socket.on("end", () => {
let response;
try {
- const request = JSON.parse(Buffer.concat(chunks).toString("utf8"));
- if (
- request === null ||
- typeof request !== "object" ||
- request.schemaVersion !== "1" ||
- !Array.isArray(request.args) ||
- request.args.length === 0 ||
- request.args.length > 128 ||
- request.args.some(
- (argument) =>
- typeof argument !== "string" ||
- argument.length === 0 ||
- argument.length > 8_192,
- ) ||
- typeof request.stdinBase64 !== "string"
- ) {
- throw new Error("contained runtime request is invalid");
- }
- const stdin = Buffer.from(request.stdinBase64, "base64");
- if (stdin.byteLength > 8_192) {
- throw new Error("contained runtime input exceeded its bound");
- }
+ const request = parseContainedRuntimeRequest(
+ JSON.parse(Buffer.concat(chunks).toString("utf8")),
+ );
+ const stdin = request.stdin;
if (request.args[0] === "counterlab-drain") {
const receipt = createDrainReceipt(request.args, stdin);
response = {
@@ -474,6 +457,7 @@ const server = createServer((socket) => {
cwd: root,
environment,
installRoot,
+ qualificationMode: request.qualificationMode,
sessionRoot,
stdin,
})
From 79d25babb638dc7726f2031f8a6f6b07b84f989e Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 06:04:40 +0530
Subject: [PATCH 035/143] feat(runtime): validate aggregate cgroup evidence
---
scripts/contained-cgroup-evidence.d.mts | 26 +++
scripts/contained-cgroup-evidence.mjs | 272 ++++++++++++++++++++++
scripts/contained-cgroup-evidence.test.ts | 165 +++++++++++++
scripts/contained-runtime-attestation.mjs | 1 +
4 files changed, 464 insertions(+)
create mode 100644 scripts/contained-cgroup-evidence.d.mts
create mode 100644 scripts/contained-cgroup-evidence.mjs
create mode 100644 scripts/contained-cgroup-evidence.test.ts
diff --git a/scripts/contained-cgroup-evidence.d.mts b/scripts/contained-cgroup-evidence.d.mts
new file mode 100644
index 00000000..f43f0877
--- /dev/null
+++ b/scripts/contained-cgroup-evidence.d.mts
@@ -0,0 +1,26 @@
+export const QUALIFIED_AGGREGATE_LIMIT_MODE: "container-cgroup-and-process-rlimit";
+
+export function canonicalCgroupJson(value: unknown): string;
+export function sha256CgroupBytes(value: Buffer | string): string;
+export function createContainedCgroupIdentity(input: {
+ invocationId: string;
+ finalContainerId: string;
+ sanitizedSpecSha256: string;
+}): string;
+export function validateContainedCgroupEvidence(
+ value: T,
+ expected: {
+ invocationId: string;
+ finalContainerId: string;
+ sanitizedSpecSha256: string;
+ intendedAggregateLimits: {
+ cpuCount: number;
+ maxProcesses: number;
+ memoryBytes: number;
+ };
+ runtimeAttestationSha256: string;
+ runtimeSessionId: string;
+ driverCliSha256: string;
+ driverModuleSha256: string;
+ },
+): T;
diff --git a/scripts/contained-cgroup-evidence.mjs b/scripts/contained-cgroup-evidence.mjs
new file mode 100644
index 00000000..6849e8d3
--- /dev/null
+++ b/scripts/contained-cgroup-evidence.mjs
@@ -0,0 +1,272 @@
+import { createHash } from "node:crypto";
+
+export const QUALIFIED_AGGREGATE_LIMIT_MODE =
+ "container-cgroup-and-process-rlimit";
+
+const sha256Pattern = /^[a-f0-9]{64}$/u;
+const invocationPattern = /^[a-f0-9]{64}$/u;
+const sessionPattern = /^rt-[a-z0-9][a-z0-9-]{7,13}$/u;
+const isoTimestampPattern =
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})$/u;
+
+const topLevelKeys = [
+ "authority",
+ "cgroupId",
+ "cgroupIdentity",
+ "cgroupPath",
+ "cgroupVersion",
+ "cleanup",
+ "finalContainerId",
+ "invocationId",
+ "membership",
+ "negativeControls",
+ "observedAt",
+ "observedLimits",
+ "observer",
+ "receiptPayloadSha256",
+ "runtimeAttestationSha256",
+ "sanitizedSpecSha256",
+ "schemaVersion",
+ "status",
+];
+
+export function canonicalCgroupJson(value) {
+ if (Array.isArray(value)) {
+ return `[${value.map(canonicalCgroupJson).join(",")}]`;
+ }
+ if (value !== null && typeof value === "object") {
+ return `{${Object.keys(value)
+ .sort()
+ .map((key) => `${JSON.stringify(key)}:${canonicalCgroupJson(value[key])}`)
+ .join(",")}}`;
+ }
+ return JSON.stringify(value);
+}
+
+export function sha256CgroupBytes(value) {
+ return createHash("sha256").update(value).digest("hex");
+}
+
+function object(value, label) {
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
+ throw new Error(`aggregate cgroup evidence ${label} shape is invalid`);
+ }
+ return value;
+}
+
+function exactKeys(value, keys, label) {
+ if (
+ JSON.stringify(Object.keys(value).sort()) !==
+ JSON.stringify([...keys].sort())
+ ) {
+ throw new Error(`aggregate cgroup evidence ${label} shape is invalid`);
+ }
+}
+
+function safeInteger(value, { positive = false } = {}) {
+ return Number.isSafeInteger(value) && value >= (positive ? 1 : 0);
+}
+
+function sha256(value) {
+ return typeof value === "string" && sha256Pattern.test(value);
+}
+
+function validateExpected(value) {
+ const expected = object(value, "expected binding");
+ exactKeys(
+ expected,
+ [
+ "driverCliSha256",
+ "driverModuleSha256",
+ "finalContainerId",
+ "intendedAggregateLimits",
+ "invocationId",
+ "runtimeAttestationSha256",
+ "runtimeSessionId",
+ "sanitizedSpecSha256",
+ ],
+ "expected binding",
+ );
+ const intended = object(expected.intendedAggregateLimits, "intended limits");
+ exactKeys(
+ intended,
+ ["cpuCount", "maxProcesses", "memoryBytes"],
+ "intended limits",
+ );
+ if (
+ !invocationPattern.test(expected.invocationId ?? "") ||
+ !invocationPattern.test(expected.finalContainerId ?? "") ||
+ !sha256(expected.sanitizedSpecSha256) ||
+ !sha256(expected.runtimeAttestationSha256) ||
+ !sessionPattern.test(expected.runtimeSessionId ?? "") ||
+ !sha256(expected.driverCliSha256) ||
+ !sha256(expected.driverModuleSha256) ||
+ typeof intended.cpuCount !== "number" ||
+ !Number.isFinite(intended.cpuCount) ||
+ intended.cpuCount < 0.25 ||
+ intended.cpuCount > 2 ||
+ !safeInteger(intended.maxProcesses, { positive: true }) ||
+ intended.maxProcesses > 32 ||
+ !safeInteger(intended.memoryBytes, { positive: true }) ||
+ intended.memoryBytes < 64 * 1024 * 1024 ||
+ intended.memoryBytes > 1024 * 1024 * 1024
+ ) {
+ throw new Error("aggregate cgroup evidence expected binding is invalid");
+ }
+ return { expected, intended };
+}
+
+export function createContainedCgroupIdentity({
+ invocationId,
+ finalContainerId,
+ sanitizedSpecSha256,
+}) {
+ if (
+ !invocationPattern.test(invocationId ?? "") ||
+ !invocationPattern.test(finalContainerId ?? "") ||
+ !sha256(sanitizedSpecSha256)
+ ) {
+ throw new Error("aggregate cgroup evidence identity input is invalid");
+ }
+ return sha256CgroupBytes(
+ `counterlab-cgroup-v2\0${invocationId}\0${finalContainerId}\0${sanitizedSpecSha256}`,
+ );
+}
+
+export function validateContainedCgroupEvidence(value, expectedValue) {
+ const evidence = object(value, "top-level");
+ exactKeys(evidence, topLevelKeys, "top-level");
+ const { expected, intended } = validateExpected(expectedValue);
+ const observed = object(evidence.observedLimits, "observed limits");
+ const membership = object(evidence.membership, "membership");
+ const controls = object(evidence.negativeControls, "negative controls");
+ const memoryControl = object(controls.memory, "memory control");
+ const processControl = object(controls.processes, "process control");
+ const cpuControl = object(controls.cpu, "CPU control");
+ const cleanup = object(evidence.cleanup, "cleanup");
+ const observer = object(evidence.observer, "observer");
+ exactKeys(
+ observed,
+ [
+ "cpuPeriodMicros",
+ "cpuQuotaMicros",
+ "memoryMaxBytes",
+ "memorySwapMaxBytes",
+ "pidsMax",
+ ],
+ "observed limits",
+ );
+ exactKeys(
+ membership,
+ [
+ "descendantsObserved",
+ "leaderPid",
+ "leaderStartTimeTicks",
+ "memberPids",
+ "memberSetSha256",
+ ],
+ "membership",
+ );
+ exactKeys(controls, ["cpu", "memory", "processes"], "negative controls");
+ exactKeys(
+ memoryControl,
+ ["enforced", "oomKillAfter", "oomKillBefore", "requestedBytes"],
+ "memory control",
+ );
+ exactKeys(
+ processControl,
+ ["attemptedProcesses", "enforced", "maxEventsAfter", "maxEventsBefore"],
+ "process control",
+ );
+ exactKeys(
+ cpuControl,
+ [
+ "busyWindowMs",
+ "enforced",
+ "nrThrottledAfter",
+ "nrThrottledBefore",
+ "throttledUsecAfter",
+ "throttledUsecBefore",
+ ],
+ "CPU control",
+ );
+ exactKeys(cleanup, ["cgroupAbsentAfterTimeout"], "cleanup");
+ exactKeys(
+ observer,
+ ["driverCliSha256", "driverModuleSha256", "runtimeSessionId"],
+ "observer",
+ );
+
+ const memberPids = membership.memberPids;
+ const uniqueMembers = Array.isArray(memberPids)
+ ? new Set(memberPids)
+ : new Set();
+ const { receiptPayloadSha256, ...payload } = evidence;
+ if (
+ evidence.schemaVersion !== "1" ||
+ evidence.status !== "OBSERVED" ||
+ evidence.authority !== "linux-cgroup-v2" ||
+ evidence.cgroupVersion !== 2 ||
+ evidence.invocationId !== expected.invocationId ||
+ evidence.finalContainerId !== expected.finalContainerId ||
+ evidence.sanitizedSpecSha256 !== expected.sanitizedSpecSha256 ||
+ evidence.runtimeAttestationSha256 !== expected.runtimeAttestationSha256 ||
+ evidence.cgroupId !== `counterlab-v6.1-${expected.invocationId}` ||
+ evidence.cgroupPath !== `counterlab-v6.1/${expected.invocationId}` ||
+ evidence.cgroupIdentity !== createContainedCgroupIdentity(expected) ||
+ observed.memoryMaxBytes !== intended.memoryBytes ||
+ ![0, intended.memoryBytes].includes(observed.memorySwapMaxBytes) ||
+ observed.pidsMax !== intended.maxProcesses ||
+ !safeInteger(observed.cpuQuotaMicros, { positive: true }) ||
+ !safeInteger(observed.cpuPeriodMicros, { positive: true }) ||
+ observed.cpuQuotaMicros / observed.cpuPeriodMicros !== intended.cpuCount ||
+ !safeInteger(membership.leaderPid, { positive: true }) ||
+ typeof membership.leaderStartTimeTicks !== "string" ||
+ !/^[1-9][0-9]*$/u.test(membership.leaderStartTimeTicks) ||
+ !Array.isArray(memberPids) ||
+ memberPids.length < 2 ||
+ memberPids.length > 64 ||
+ memberPids.some((pid) => !safeInteger(pid, { positive: true })) ||
+ uniqueMembers.size !== memberPids.length ||
+ !uniqueMembers.has(membership.leaderPid) ||
+ membership.memberSetSha256 !==
+ sha256CgroupBytes(
+ canonicalCgroupJson(
+ [...memberPids].sort((left, right) => left - right),
+ ),
+ ) ||
+ membership.descendantsObserved !== true ||
+ !safeInteger(memoryControl.requestedBytes, { positive: true }) ||
+ memoryControl.requestedBytes <= intended.memoryBytes ||
+ !safeInteger(memoryControl.oomKillBefore) ||
+ !safeInteger(memoryControl.oomKillAfter, { positive: true }) ||
+ memoryControl.oomKillAfter <= memoryControl.oomKillBefore ||
+ memoryControl.enforced !== true ||
+ !safeInteger(processControl.attemptedProcesses, { positive: true }) ||
+ processControl.attemptedProcesses <= intended.maxProcesses ||
+ !safeInteger(processControl.maxEventsBefore) ||
+ !safeInteger(processControl.maxEventsAfter, { positive: true }) ||
+ processControl.maxEventsAfter <= processControl.maxEventsBefore ||
+ processControl.enforced !== true ||
+ !safeInteger(cpuControl.busyWindowMs, { positive: true }) ||
+ !safeInteger(cpuControl.nrThrottledBefore) ||
+ !safeInteger(cpuControl.nrThrottledAfter, { positive: true }) ||
+ cpuControl.nrThrottledAfter <= cpuControl.nrThrottledBefore ||
+ !safeInteger(cpuControl.throttledUsecBefore) ||
+ !safeInteger(cpuControl.throttledUsecAfter, { positive: true }) ||
+ cpuControl.throttledUsecAfter <= cpuControl.throttledUsecBefore ||
+ cpuControl.enforced !== true ||
+ cleanup.cgroupAbsentAfterTimeout !== true ||
+ observer.runtimeSessionId !== expected.runtimeSessionId ||
+ observer.driverCliSha256 !== expected.driverCliSha256 ||
+ observer.driverModuleSha256 !== expected.driverModuleSha256 ||
+ typeof evidence.observedAt !== "string" ||
+ !isoTimestampPattern.test(evidence.observedAt) ||
+ !Number.isFinite(Date.parse(evidence.observedAt)) ||
+ !sha256(receiptPayloadSha256) ||
+ sha256CgroupBytes(canonicalCgroupJson(payload)) !== receiptPayloadSha256
+ ) {
+ throw new Error("aggregate cgroup evidence binding is invalid");
+ }
+ return evidence;
+}
diff --git a/scripts/contained-cgroup-evidence.test.ts b/scripts/contained-cgroup-evidence.test.ts
new file mode 100644
index 00000000..cfc12ee4
--- /dev/null
+++ b/scripts/contained-cgroup-evidence.test.ts
@@ -0,0 +1,165 @@
+import { describe, expect, it } from "vitest";
+
+import {
+ QUALIFIED_AGGREGATE_LIMIT_MODE,
+ canonicalCgroupJson,
+ createContainedCgroupIdentity,
+ sha256CgroupBytes,
+ validateContainedCgroupEvidence,
+} from "./contained-cgroup-evidence.mjs";
+
+const invocationId = "1".repeat(64);
+const finalContainerId = "2".repeat(64);
+const sanitizedSpecSha256 = "3".repeat(64);
+const intendedAggregateLimits = {
+ cpuCount: 1,
+ maxProcesses: 16,
+ memoryBytes: 512 * 1024 * 1024,
+};
+
+function evidence() {
+ const memberPids = [100, 101];
+ const payload = {
+ schemaVersion: "1",
+ status: "OBSERVED",
+ authority: "linux-cgroup-v2",
+ cgroupVersion: 2,
+ cgroupId: `counterlab-v6.1-${invocationId}`,
+ cgroupPath: `counterlab-v6.1/${invocationId}`,
+ cgroupIdentity: createContainedCgroupIdentity({
+ invocationId,
+ finalContainerId,
+ sanitizedSpecSha256,
+ }),
+ invocationId,
+ finalContainerId,
+ sanitizedSpecSha256,
+ runtimeAttestationSha256: "4".repeat(64),
+ observedLimits: {
+ memoryMaxBytes: intendedAggregateLimits.memoryBytes,
+ memorySwapMaxBytes: intendedAggregateLimits.memoryBytes,
+ pidsMax: intendedAggregateLimits.maxProcesses,
+ cpuQuotaMicros: 100_000,
+ cpuPeriodMicros: 100_000,
+ },
+ membership: {
+ leaderPid: 100,
+ leaderStartTimeTicks: "12345",
+ memberPids,
+ memberSetSha256: sha256CgroupBytes(canonicalCgroupJson(memberPids)),
+ descendantsObserved: true,
+ },
+ negativeControls: {
+ memory: {
+ requestedBytes: intendedAggregateLimits.memoryBytes + 64 * 1024 * 1024,
+ oomKillBefore: 0,
+ oomKillAfter: 1,
+ enforced: true,
+ },
+ processes: {
+ attemptedProcesses: intendedAggregateLimits.maxProcesses + 1,
+ maxEventsBefore: 0,
+ maxEventsAfter: 1,
+ enforced: true,
+ },
+ cpu: {
+ busyWindowMs: 500,
+ nrThrottledBefore: 0,
+ nrThrottledAfter: 1,
+ throttledUsecBefore: 0,
+ throttledUsecAfter: 1,
+ enforced: true,
+ },
+ },
+ cleanup: { cgroupAbsentAfterTimeout: true },
+ observer: {
+ runtimeSessionId: "rt-v61-test1",
+ driverCliSha256: "5".repeat(64),
+ driverModuleSha256: "6".repeat(64),
+ },
+ observedAt: "2026-07-20T12:00:00.000Z",
+ };
+ return {
+ ...payload,
+ receiptPayloadSha256: sha256CgroupBytes(canonicalCgroupJson(payload)),
+ };
+}
+
+function expected() {
+ return {
+ invocationId,
+ finalContainerId,
+ sanitizedSpecSha256,
+ intendedAggregateLimits,
+ runtimeAttestationSha256: "4".repeat(64),
+ runtimeSessionId: "rt-v61-test1",
+ driverCliSha256: "5".repeat(64),
+ driverModuleSha256: "6".repeat(64),
+ };
+}
+
+describe("contained cgroup evidence", () => {
+ it("accepts only the qualified aggregate limit mode and complete evidence", () => {
+ expect(QUALIFIED_AGGREGATE_LIMIT_MODE).toBe(
+ "container-cgroup-and-process-rlimit",
+ );
+ expect(validateContainedCgroupEvidence(evidence(), expected())).toEqual(
+ evidence(),
+ );
+ });
+
+ it("rejects identity, limit, membership, counter, cleanup, and hash drift", () => {
+ const mutations: Array<(value: ReturnType) => void> = [
+ (value) => {
+ value.cgroupPath = "counterlab-v6.1/escaped";
+ },
+ (value) => {
+ value.observedLimits.memoryMaxBytes += 1;
+ },
+ (value) => {
+ value.membership.memberPids = [100, 100];
+ },
+ (value) => {
+ value.negativeControls.memory.oomKillAfter = 0;
+ },
+ (value) => {
+ value.negativeControls.processes.maxEventsAfter = 0;
+ },
+ (value) => {
+ value.negativeControls.cpu.throttledUsecAfter = 0;
+ },
+ (value) => {
+ value.cleanup.cgroupAbsentAfterTimeout = false;
+ },
+ (value) => {
+ value.observer.driverCliSha256 = "7".repeat(64);
+ },
+ (value) => {
+ value.receiptPayloadSha256 = "8".repeat(64);
+ },
+ ];
+
+ for (const mutate of mutations) {
+ const changed = structuredClone(evidence());
+ mutate(changed);
+ expect(() =>
+ validateContainedCgroupEvidence(changed, expected()),
+ ).toThrow(/aggregate cgroup evidence/u);
+ }
+ });
+
+ it("rejects unknown fields and stale bindings", () => {
+ expect(() =>
+ validateContainedCgroupEvidence(
+ { ...evidence(), unknown: true },
+ expected(),
+ ),
+ ).toThrow(/shape/u);
+ expect(() =>
+ validateContainedCgroupEvidence(evidence(), {
+ ...expected(),
+ invocationId: "9".repeat(64),
+ }),
+ ).toThrow(/binding/u);
+ });
+});
diff --git a/scripts/contained-runtime-attestation.mjs b/scripts/contained-runtime-attestation.mjs
index 8beb9da0..6f64e212 100644
--- a/scripts/contained-runtime-attestation.mjs
+++ b/scripts/contained-runtime-attestation.mjs
@@ -12,6 +12,7 @@ export const RUNTIME_POLICY_PATH =
"services/runner/src/counterlab_runner/contained-runtime-policy.json";
export const RUNTIME_HELPER_PATHS = Object.freeze({
+ cgroupEvidence: "scripts/contained-cgroup-evidence.mjs",
runtimeClient: "scripts/contained-runtime-client.mjs",
runtimeRequest: "scripts/contained-runtime-request.mjs",
runtimeRun: "scripts/contained-runtime-run.mjs",
From 275dd9ee6ba57c0204a16e42ed2d7977ece21451 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 06:06:16 +0530
Subject: [PATCH 036/143] docs(progress): record cgroup source checkpoint
---
docs/PROGRESS.md | 100 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 100 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 554258ea..13ce8b1e 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -3281,3 +3281,103 @@ not staged or modified.
verify Devpost, public video, repository, feedback ID, and submission receipt
before pushing the feature branch and fast-forwarding its verified tip into
`main`.
+
+## Finish Commander cgroup-source checkpoint — 2026-07-21T00:34:56Z
+
+Deadline: `2026-07-22T00:00:00Z`; **23 hours 25 minutes 04 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD `79d25ba`; the only
+worktree entry besides this progress update is the preserved, already-unlinked
+FUSE tombstone `scripts/.fuse_hidden0000a9600000801f`. It was inspected
+read-only during ownership diagnosis, remains byte-identical to
+`scripts/start-contained-runtime.sh`, and remains unchanged and unstaged.
+
+### Independently committed work since the consolidated checkpoint
+
+- `1f3067c perf(learner): defer verified theater payload` replaces the eager
+ landing import with a locked lightweight preview and a genuine lazy verified
+ Theater. The main client fell from **552.03 kB to 446.77 kB** minified; the
+ lazy Theater chunk is **20.78 kB**, and the mixed-import build warning is
+ gone.
+- `6b0bf01 fix(judge): bind isolation copy to release health` makes Judge copy
+ distinguish release-bound `OS_ENFORCED`, release-bound `PARTIAL`, and
+ unavailable evidence instead of presenting a fixed isolation claim.
+- `0621b79`, `55788ef`, `eafd867`, and `45038ce` make the Devpost fields
+ paste-ready, move the verified belief break into the demo's opening seconds,
+ add word-for-word captions, separate build-time Codex from Runtime Codex,
+ and document both sample notebooks without inventing repository, video,
+ feedback, release, learner, or submission evidence.
+- `94a5d51 fix(runtime): align timeout rlimit authority` aligns TypeScript and
+ Python on the five limits actually emitted: `RLIMIT_AS`, `RLIMIT_CPU`,
+ `RLIMIT_FSIZE`, `RLIMIT_NOFILE`, and `RLIMIT_NPROC`.
+- `b7c54fe fix(runtime): retain deterministic cgroup limits` preserves the
+ validated OCI CPU, memory, swap, and PID intent, normalizes the cgroup to
+ `counterlab-v6.1/`, and stops passing `--cgroup ""`. The
+ receipt deliberately remains `aggregateLimitIntentEnforced: false` with
+ `aggregateLimitEvidence: null`.
+- `fa4261d test(runtime): harden cgroup intent bindings` adds seven fail-closed
+ resource mutations, exact non-empty cgroup argument assertions, and explicit
+ config/base/sanitized hash bindings.
+- `22e489f fix(runtime): request delegated cgroup namespace` launches the
+ pinned RootlessKit with `--pidns`, `--cgroupns`, and
+ `--evacuate-cgroup2=containerd` before the runtime server.
+- `d797b4c feat(runtime): bind aggregate qualification requests` introduces a
+ strict source-bound request protocol. Only an explicit, already path-checked
+ timeout control-receipt request may select `aggregate-timeout-proof-v1`;
+ normal learner calls carry `null`, unknown fields fail, and non-run commands
+ cannot select qualification.
+- `79d25ba feat(runtime): validate aggregate cgroup evidence` adds a pure
+ runtime validator for exact limits, member identity, memory/PID/CPU counter
+ deltas, cleanup absence, observer/source identity, timestamp, and self-hash.
+ It does not observe the host or issue a qualified receipt.
+
+### Exact verification in these slices
+
+- Complete web Vitest after the learner performance change: **78 files,
+ 617/617 passed**.
+- Cgroup/runtime adjacent suites: **52/52**, then **39/39**, **46/46**,
+ **50/50**, and the validator cross-boundary suite **12/12**, all passed at
+ their respective committed slices.
+- Runtime/Python rlimit parity: TypeScript **4/4** and Python **7/7**, passed.
+- Repository, web client, generated Worker, and Worker TypeScript: **passed**
+ after each current runtime slice.
+- Production Vite/Worker build: **passed** at the learner-performance slice;
+ main client **446.77 kB / 128.55 kB gzip**, CSS **200.01 kB / 34.61 kB
+ gzip**, Worker **1,863.39 kB / 359.59 kB gzip**.
+- Scoped Prettier, Git whitespace, and explicit secret scans: **passed** for
+ every committed slice.
+
+### Live issue matrix
+
+| Gate | Source mechanism | Acceptance evidence | Current production proof |
+| --- | --- | --- | --- |
+| Aggregate containment | Retained OCI limits, delegated RootlessKit request, strict qualification protocol and evidence validator | Real exact-image memory OOM, PID denial, CPU throttling, genuine membership, and post-timeout cgroup absence | **not run**; observer/final receipt and real host proof remain open |
+| Clean exact source | Nested ignored worktree under `.rt/` avoids the unlinked FUSE tombstone without deleting it | Completely clean physical checkout at frozen source `S` | **not run** |
+| Exact image/evidence | Build receipt, timeout proof, SBOM/VEX/scientific refresh, evidence commit `E` | All source/image/hash-bound receipts verify | **not run** |
+| Cloudflare release | Repository-contained Wrangler identity, qualified deploy, public identity, smoke | Exact account, Worker version, Container digest, source `S`, evidence `E` | **not run**; contained Wrangler is not authenticated |
+| Browser qualification | CloakBrowser Playwright harness | 32 current tests plus required manual semantic/visual matrix on the exact public tuple | **0 current rendered journeys**; this session has no injected `playwright_safe` tools or `CLOAK_CDP_ENDPOINT` |
+| Submission/impact | Paste-ready copy, first-12-second demo script, captions, `NO_DATA` guard | Public repo/video/feedback links, Devpost submission receipt, real consented learner rows or honest `NO_DATA` | **externally unverified** |
+
+### Ordered remaining work
+
+1. Add the independent cgroup observer, release-only negative controls, and a
+ create-once post-cleanup qualified receipt without changing normal learner
+ execution.
+2. Obtain a constitution-compatible way to execute the real cgroup proof. The
+ runtime necessarily reads its delegated `/sys/fs/cgroup` subtree and owned
+ `/proc//stat`; the current repository-only filesystem rule does not
+ authorize the lead to execute that proof, so this gate remains fail-closed.
+3. Freeze source `S` in a clean repository-contained nested worktree, build the
+ runner once, run the timeout and scientific evidence proofs while
+ `HEAD == S`, commit only allowlisted evidence as `E`, then qualify.
+4. Complete repository-contained Wrangler authentication, deploy only the
+ qualified tuple, and run public identity/smoke without overstating the
+ currently deployed Worker.
+5. Start a fresh Codex session with injected `playwright_safe`; registration
+ alone is insufficient. Run all **32** current tests and the missing
+ responsive, semantic, screen-reader, network, console, and Web Vitals
+ review. Historical stock-Chromium and outside-root runtime metadata do not
+ qualify this release.
+6. Keep learner impact `NO_DATA` without real consented participants; verify
+ and align the public repository, video, `/feedback` ID, Devpost fields, and
+ submission receipt before the authorized feature push and fast-forward
+ merge to `main`.
From cb8705a166ecdcea7c5944cf95bb99df94495077 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 06:09:46 +0530
Subject: [PATCH 037/143] feat(runtime): derive qualified rootless receipts
---
...contained-qualified-rootless-receipt.d.mts | 26 +++
.../contained-qualified-rootless-receipt.mjs | 102 ++++++++++
...ntained-qualified-rootless-receipt.test.ts | 186 ++++++++++++++++++
scripts/contained-runtime-attestation.mjs | 1 +
4 files changed, 315 insertions(+)
create mode 100644 scripts/contained-qualified-rootless-receipt.d.mts
create mode 100644 scripts/contained-qualified-rootless-receipt.mjs
create mode 100644 scripts/contained-qualified-rootless-receipt.test.ts
diff --git a/scripts/contained-qualified-rootless-receipt.d.mts b/scripts/contained-qualified-rootless-receipt.d.mts
new file mode 100644
index 00000000..52f73ce4
--- /dev/null
+++ b/scripts/contained-qualified-rootless-receipt.d.mts
@@ -0,0 +1,26 @@
+export const UNQUALIFIED_AGGREGATE_LIMIT_MODE: "process-address-space-rlimit-with-unenforced-cgroup-intent";
+
+export function createQualifiedContainedRootlessReceipt<
+ TReceipt extends Record,
+ TEvidence,
+>(input: {
+ aggregateLimitEvidence: TEvidence;
+ baseReceipt: TReceipt;
+ observerBindings: {
+ runtimeAttestationSha256: string;
+ runtimeSessionId: string;
+ driverCliSha256: string;
+ driverModuleSha256: string;
+ };
+}): Omit<
+ TReceipt,
+ | "limitMode"
+ | "aggregateLimitIntentEnforced"
+ | "aggregateLimitEvidence"
+ | "receiptPayloadSha256"
+> & {
+ limitMode: "container-cgroup-and-process-rlimit";
+ aggregateLimitIntentEnforced: true;
+ aggregateLimitEvidence: TEvidence;
+ receiptPayloadSha256: string;
+};
diff --git a/scripts/contained-qualified-rootless-receipt.mjs b/scripts/contained-qualified-rootless-receipt.mjs
new file mode 100644
index 00000000..865f2cf4
--- /dev/null
+++ b/scripts/contained-qualified-rootless-receipt.mjs
@@ -0,0 +1,102 @@
+import {
+ QUALIFIED_AGGREGATE_LIMIT_MODE,
+ canonicalCgroupJson,
+ sha256CgroupBytes,
+ validateContainedCgroupEvidence,
+} from "./contained-cgroup-evidence.mjs";
+
+export const UNQUALIFIED_AGGREGATE_LIMIT_MODE =
+ "process-address-space-rlimit-with-unenforced-cgroup-intent";
+
+const rootlessReceiptKeys = [
+ "aggregateLimitEvidence",
+ "aggregateLimitIntentEnforced",
+ "baseSpecSha256",
+ "commandSha256",
+ "configFileSha256",
+ "enforcedRlimits",
+ "finalContainerId",
+ "imageAuthority",
+ "imageRootfs",
+ "intendedAggregateLimits",
+ "internalMountManifest",
+ "internalMountManifestSha256",
+ "invocationId",
+ "limitMode",
+ "metadataSha256",
+ "normalizedFields",
+ "originalSpecSha256",
+ "readOnlyMountManifest",
+ "readOnlyMountManifestSha256",
+ "receiptPayloadSha256",
+ "removedFields",
+ "removedMounts",
+ "sanitizedSpecSha256",
+ "schemaVersion",
+ "stagingContainerId",
+ "status",
+];
+
+const observerBindingKeys = [
+ "driverCliSha256",
+ "driverModuleSha256",
+ "runtimeAttestationSha256",
+ "runtimeSessionId",
+];
+
+function object(value, label) {
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
+ throw new Error(`qualified rootless receipt ${label} shape is invalid`);
+ }
+ return value;
+}
+
+function exactKeys(value, keys, label) {
+ if (
+ JSON.stringify(Object.keys(value).sort()) !==
+ JSON.stringify([...keys].sort())
+ ) {
+ throw new Error(`qualified rootless receipt ${label} shape is invalid`);
+ }
+}
+
+export function createQualifiedContainedRootlessReceipt({
+ aggregateLimitEvidence,
+ baseReceipt: baseValue,
+ observerBindings: observerValue,
+}) {
+ const baseReceipt = object(baseValue, "base");
+ const observerBindings = object(observerValue, "observer binding");
+ exactKeys(baseReceipt, rootlessReceiptKeys, "base");
+ exactKeys(observerBindings, observerBindingKeys, "observer binding");
+ const { receiptPayloadSha256, ...basePayload } = baseReceipt;
+ if (
+ baseReceipt.schemaVersion !== "4" ||
+ baseReceipt.status !== "VALIDATED" ||
+ baseReceipt.limitMode !== UNQUALIFIED_AGGREGATE_LIMIT_MODE ||
+ baseReceipt.aggregateLimitIntentEnforced !== false ||
+ baseReceipt.aggregateLimitEvidence !== null ||
+ typeof receiptPayloadSha256 !== "string" ||
+ !/^[a-f0-9]{64}$/u.test(receiptPayloadSha256) ||
+ sha256CgroupBytes(canonicalCgroupJson(basePayload)) !== receiptPayloadSha256
+ ) {
+ throw new Error("qualified rootless receipt base binding is invalid");
+ }
+ const evidence = validateContainedCgroupEvidence(aggregateLimitEvidence, {
+ invocationId: baseReceipt.invocationId,
+ finalContainerId: baseReceipt.finalContainerId,
+ sanitizedSpecSha256: baseReceipt.sanitizedSpecSha256,
+ intendedAggregateLimits: baseReceipt.intendedAggregateLimits,
+ ...observerBindings,
+ });
+ const payload = {
+ ...basePayload,
+ limitMode: QUALIFIED_AGGREGATE_LIMIT_MODE,
+ aggregateLimitIntentEnforced: true,
+ aggregateLimitEvidence: evidence,
+ };
+ return {
+ ...payload,
+ receiptPayloadSha256: sha256CgroupBytes(canonicalCgroupJson(payload)),
+ };
+}
diff --git a/scripts/contained-qualified-rootless-receipt.test.ts b/scripts/contained-qualified-rootless-receipt.test.ts
new file mode 100644
index 00000000..e4526b19
--- /dev/null
+++ b/scripts/contained-qualified-rootless-receipt.test.ts
@@ -0,0 +1,186 @@
+import { describe, expect, it } from "vitest";
+
+import {
+ QUALIFIED_AGGREGATE_LIMIT_MODE,
+ canonicalCgroupJson,
+ createContainedCgroupIdentity,
+ sha256CgroupBytes,
+} from "./contained-cgroup-evidence.mjs";
+import {
+ UNQUALIFIED_AGGREGATE_LIMIT_MODE,
+ createQualifiedContainedRootlessReceipt,
+} from "./contained-qualified-rootless-receipt.mjs";
+
+const invocationId = "1".repeat(64);
+const finalContainerId = "2".repeat(64);
+const sanitizedSpecSha256 = "3".repeat(64);
+const intendedAggregateLimits = {
+ cpuCount: 1,
+ maxProcesses: 16,
+ memoryBytes: 512 * 1024 * 1024,
+};
+const observerBindings = {
+ runtimeAttestationSha256: "4".repeat(64),
+ runtimeSessionId: "rt-v61-test1",
+ driverCliSha256: "5".repeat(64),
+ driverModuleSha256: "6".repeat(64),
+};
+
+function hashed>(payload: T) {
+ return {
+ ...payload,
+ receiptPayloadSha256: sha256CgroupBytes(canonicalCgroupJson(payload)),
+ };
+}
+
+function baseReceipt() {
+ return hashed({
+ schemaVersion: "4",
+ status: "VALIDATED",
+ limitMode: UNQUALIFIED_AGGREGATE_LIMIT_MODE,
+ aggregateLimitIntentEnforced: false,
+ invocationId,
+ stagingContainerId: "7".repeat(64),
+ finalContainerId,
+ metadataSha256: "8".repeat(64),
+ originalSpecSha256: "9".repeat(64),
+ baseSpecSha256: "a".repeat(64),
+ sanitizedSpecSha256,
+ configFileSha256: "b".repeat(64),
+ internalMountManifest: [],
+ internalMountManifestSha256: "c".repeat(64),
+ readOnlyMountManifest: [],
+ readOnlyMountManifestSha256: "d".repeat(64),
+ commandSha256: "e".repeat(64),
+ imageAuthority: {},
+ imageRootfs: {},
+ removedFields: [],
+ normalizedFields: [],
+ removedMounts: [],
+ intendedAggregateLimits,
+ enforcedRlimits: [],
+ aggregateLimitEvidence: null,
+ });
+}
+
+function aggregateEvidence() {
+ const memberPids = [100, 101];
+ return hashed({
+ schemaVersion: "1",
+ status: "OBSERVED",
+ authority: "linux-cgroup-v2",
+ cgroupVersion: 2,
+ cgroupId: `counterlab-v6.1-${invocationId}`,
+ cgroupPath: `counterlab-v6.1/${invocationId}`,
+ cgroupIdentity: createContainedCgroupIdentity({
+ invocationId,
+ finalContainerId,
+ sanitizedSpecSha256,
+ }),
+ invocationId,
+ finalContainerId,
+ sanitizedSpecSha256,
+ runtimeAttestationSha256: observerBindings.runtimeAttestationSha256,
+ observedLimits: {
+ memoryMaxBytes: intendedAggregateLimits.memoryBytes,
+ memorySwapMaxBytes: intendedAggregateLimits.memoryBytes,
+ pidsMax: intendedAggregateLimits.maxProcesses,
+ cpuQuotaMicros: 100_000,
+ cpuPeriodMicros: 100_000,
+ },
+ membership: {
+ leaderPid: 100,
+ leaderStartTimeTicks: "12345",
+ memberPids,
+ memberSetSha256: sha256CgroupBytes(canonicalCgroupJson(memberPids)),
+ descendantsObserved: true,
+ },
+ negativeControls: {
+ memory: {
+ requestedBytes: intendedAggregateLimits.memoryBytes + 1,
+ oomKillBefore: 0,
+ oomKillAfter: 1,
+ enforced: true,
+ },
+ processes: {
+ attemptedProcesses: intendedAggregateLimits.maxProcesses + 1,
+ maxEventsBefore: 0,
+ maxEventsAfter: 1,
+ enforced: true,
+ },
+ cpu: {
+ busyWindowMs: 500,
+ nrThrottledBefore: 0,
+ nrThrottledAfter: 1,
+ throttledUsecBefore: 0,
+ throttledUsecAfter: 1,
+ enforced: true,
+ },
+ },
+ cleanup: { cgroupAbsentAfterTimeout: true },
+ observer: {
+ runtimeSessionId: observerBindings.runtimeSessionId,
+ driverCliSha256: observerBindings.driverCliSha256,
+ driverModuleSha256: observerBindings.driverModuleSha256,
+ },
+ observedAt: "2026-07-20T12:00:00.000Z",
+ });
+}
+
+describe("qualified contained rootless receipt", () => {
+ it("derives a new qualified receipt without mutating the conservative base", () => {
+ const base = baseReceipt();
+ const qualified = createQualifiedContainedRootlessReceipt({
+ aggregateLimitEvidence: aggregateEvidence(),
+ baseReceipt: base,
+ observerBindings,
+ });
+
+ expect(base.limitMode).toBe(UNQUALIFIED_AGGREGATE_LIMIT_MODE);
+ expect(base.aggregateLimitIntentEnforced).toBe(false);
+ expect(base.aggregateLimitEvidence).toBeNull();
+ expect(qualified.limitMode).toBe(QUALIFIED_AGGREGATE_LIMIT_MODE);
+ expect(qualified.aggregateLimitIntentEnforced).toBe(true);
+ expect(qualified.aggregateLimitEvidence).toEqual(aggregateEvidence());
+ const { receiptPayloadSha256, ...payload } = qualified;
+ expect(receiptPayloadSha256).toBe(
+ sha256CgroupBytes(canonicalCgroupJson(payload)),
+ );
+ });
+
+ it("rejects stale, already asserted, malformed, or unknown base receipts", () => {
+ const mutations: Array<(value: ReturnType) => void> = [
+ (value) => {
+ value.aggregateLimitIntentEnforced = true;
+ },
+ (value) => {
+ (value as { limitMode: string }).limitMode =
+ QUALIFIED_AGGREGATE_LIMIT_MODE;
+ },
+ (value) => {
+ value.invocationId = "f".repeat(64);
+ },
+ (value) => {
+ value.receiptPayloadSha256 = "0".repeat(64);
+ },
+ ];
+ for (const mutate of mutations) {
+ const changed = structuredClone(baseReceipt());
+ mutate(changed);
+ expect(() =>
+ createQualifiedContainedRootlessReceipt({
+ aggregateLimitEvidence: aggregateEvidence(),
+ baseReceipt: changed,
+ observerBindings,
+ }),
+ ).toThrow(/rootless receipt/u);
+ }
+ expect(() =>
+ createQualifiedContainedRootlessReceipt({
+ aggregateLimitEvidence: aggregateEvidence(),
+ baseReceipt: { ...baseReceipt(), unknown: true },
+ observerBindings,
+ }),
+ ).toThrow(/shape/u);
+ });
+});
diff --git a/scripts/contained-runtime-attestation.mjs b/scripts/contained-runtime-attestation.mjs
index 6f64e212..c286ec73 100644
--- a/scripts/contained-runtime-attestation.mjs
+++ b/scripts/contained-runtime-attestation.mjs
@@ -13,6 +13,7 @@ export const RUNTIME_POLICY_PATH =
export const RUNTIME_HELPER_PATHS = Object.freeze({
cgroupEvidence: "scripts/contained-cgroup-evidence.mjs",
+ qualifiedRootlessReceipt: "scripts/contained-qualified-rootless-receipt.mjs",
runtimeClient: "scripts/contained-runtime-client.mjs",
runtimeRequest: "scripts/contained-runtime-request.mjs",
runtimeRun: "scripts/contained-runtime-run.mjs",
From 3b7796c909967fddb39eb8ae70b0ce7fd895072f Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 06:14:19 +0530
Subject: [PATCH 038/143] feat(runtime): persist qualified receipts once
---
.../contained-qualified-receipt-store.d.mts | 30 +++
scripts/contained-qualified-receipt-store.mjs | 175 ++++++++++++++
.../contained-qualified-receipt-store.test.ts | 226 ++++++++++++++++++
scripts/contained-runtime-attestation.mjs | 2 +
4 files changed, 433 insertions(+)
create mode 100644 scripts/contained-qualified-receipt-store.d.mts
create mode 100644 scripts/contained-qualified-receipt-store.mjs
create mode 100644 scripts/contained-qualified-receipt-store.test.ts
diff --git a/scripts/contained-qualified-receipt-store.d.mts b/scripts/contained-qualified-receipt-store.d.mts
new file mode 100644
index 00000000..2613ce0e
--- /dev/null
+++ b/scripts/contained-qualified-receipt-store.d.mts
@@ -0,0 +1,30 @@
+export interface QualifiedRootlessReceiptStoreBinding {
+ baseReceiptFileSha256: string;
+ baseReceiptPath: string;
+ finalContainerId: string;
+ sessionRoot: string;
+ observerBindings: {
+ runtimeAttestationSha256: string;
+ runtimeSessionId: string;
+ driverCliSha256: string;
+ driverModuleSha256: string;
+ };
+}
+
+export function persistQualifiedContainedRootlessReceipt(
+ input: {
+ aggregateLimitEvidence: unknown;
+ } & QualifiedRootlessReceiptStoreBinding,
+): {
+ qualifiedReceipt: Record;
+ qualifiedReceiptFileSha256: string;
+ qualifiedReceiptPath: string;
+ qualifiedReceiptPayloadSha256: string;
+};
+
+export function verifyQualifiedContainedRootlessReceipt(
+ input: QualifiedRootlessReceiptStoreBinding & {
+ qualifiedReceiptFileSha256: string;
+ qualifiedReceiptPath: string;
+ },
+): Record;
diff --git a/scripts/contained-qualified-receipt-store.mjs b/scripts/contained-qualified-receipt-store.mjs
new file mode 100644
index 00000000..f038772a
--- /dev/null
+++ b/scripts/contained-qualified-receipt-store.mjs
@@ -0,0 +1,175 @@
+import { lstatSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
+import { isAbsolute, relative, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+import { sha256CgroupBytes } from "./contained-cgroup-evidence.mjs";
+import { createQualifiedContainedRootlessReceipt } from "./contained-qualified-rootless-receipt.mjs";
+
+const repositoryRoot = realpathSync(
+ resolve(fileURLToPath(import.meta.url), "../.."),
+);
+const containerIdPattern = /^[a-f0-9]{64}$/u;
+const sha256Pattern = /^[a-f0-9]{64}$/u;
+const maximumReceiptBytes = 1_048_576;
+
+function contained(root, candidate) {
+ const fromRoot = relative(root, candidate);
+ return (
+ fromRoot === "" || (!fromRoot.startsWith("..") && !isAbsolute(fromRoot))
+ );
+}
+
+function privateDirectory(path, label) {
+ const metadata = lstatSync(path);
+ if (
+ metadata.isSymbolicLink() ||
+ !metadata.isDirectory() ||
+ realpathSync(path) !== path ||
+ metadata.uid !== process.getuid() ||
+ (metadata.mode & 0o077) !== 0
+ ) {
+ throw new Error(`qualified rootless receipt ${label} is invalid`);
+ }
+}
+
+function privateFile(path, label) {
+ const metadata = lstatSync(path);
+ if (
+ metadata.isSymbolicLink() ||
+ !metadata.isFile() ||
+ realpathSync(path) !== path ||
+ metadata.uid !== process.getuid() ||
+ metadata.nlink !== 1 ||
+ (metadata.mode & 0o600) !== 0o600 ||
+ (metadata.mode & 0o077) !== 0 ||
+ metadata.size < 1 ||
+ metadata.size > maximumReceiptBytes
+ ) {
+ throw new Error(`qualified rootless receipt ${label} is invalid`);
+ }
+}
+
+function receiptPaths({ sessionRoot, finalContainerId }) {
+ if (
+ !isAbsolute(sessionRoot) ||
+ !contained(repositoryRoot, sessionRoot) ||
+ !containerIdPattern.test(finalContainerId ?? "")
+ ) {
+ throw new Error("qualified rootless receipt path input is invalid");
+ }
+ privateDirectory(sessionRoot, "session root");
+ const runRoot = resolve(sessionRoot, "run");
+ const specRoot = resolve(runRoot, "rootless-specs");
+ if (
+ !contained(sessionRoot, runRoot) ||
+ !contained(runRoot, specRoot) ||
+ !contained(repositoryRoot, specRoot)
+ ) {
+ throw new Error("qualified rootless receipt path escaped the repository");
+ }
+ privateDirectory(runRoot, "run root");
+ privateDirectory(specRoot, "spec root");
+ return {
+ baseReceiptPath: resolve(specRoot, `${finalContainerId}.receipt.json`),
+ qualifiedReceiptPath: resolve(
+ specRoot,
+ `${finalContainerId}.qualified-receipt.json`,
+ ),
+ };
+}
+
+function readBaseReceipt({
+ baseReceiptFileSha256,
+ baseReceiptPath,
+ finalContainerId,
+ sessionRoot,
+}) {
+ if (!sha256Pattern.test(baseReceiptFileSha256 ?? "")) {
+ throw new Error("qualified rootless base receipt hash is invalid");
+ }
+ const expected = receiptPaths({ sessionRoot, finalContainerId });
+ if (baseReceiptPath !== expected.baseReceiptPath) {
+ throw new Error("qualified rootless receipt base path changed");
+ }
+ privateFile(baseReceiptPath, "base receipt");
+ const source = readFileSync(baseReceiptPath);
+ if (sha256CgroupBytes(source) !== baseReceiptFileSha256) {
+ throw new Error("qualified rootless base receipt changed");
+ }
+ let receipt;
+ try {
+ receipt = JSON.parse(source.toString("utf8"));
+ } catch (error) {
+ throw new Error("qualified rootless base receipt JSON is invalid", {
+ cause: error,
+ });
+ }
+ if (receipt?.finalContainerId !== finalContainerId) {
+ throw new Error("qualified rootless base receipt identity changed");
+ }
+ return { expected, receipt };
+}
+
+export function persistQualifiedContainedRootlessReceipt(input) {
+ const { expected, receipt: baseReceipt } = readBaseReceipt(input);
+ const qualifiedReceipt = createQualifiedContainedRootlessReceipt({
+ aggregateLimitEvidence: input.aggregateLimitEvidence,
+ baseReceipt,
+ observerBindings: input.observerBindings,
+ });
+ const source = `${JSON.stringify(qualifiedReceipt, null, 2)}\n`;
+ try {
+ writeFileSync(expected.qualifiedReceiptPath, source, {
+ encoding: "utf8",
+ flag: "wx",
+ mode: 0o600,
+ });
+ } catch (error) {
+ if (error?.code === "EEXIST") {
+ throw new Error("qualified rootless receipt already exists", {
+ cause: error,
+ });
+ }
+ throw error;
+ }
+ privateFile(expected.qualifiedReceiptPath, "qualified receipt");
+ return {
+ qualifiedReceipt,
+ qualifiedReceiptFileSha256: sha256CgroupBytes(source),
+ qualifiedReceiptPath: expected.qualifiedReceiptPath,
+ qualifiedReceiptPayloadSha256: qualifiedReceipt.receiptPayloadSha256,
+ };
+}
+
+export function verifyQualifiedContainedRootlessReceipt(input) {
+ const { expected, receipt: baseReceipt } = readBaseReceipt(input);
+ if (
+ input.qualifiedReceiptPath !== expected.qualifiedReceiptPath ||
+ !sha256Pattern.test(input.qualifiedReceiptFileSha256 ?? "")
+ ) {
+ throw new Error("qualified rootless receipt path or hash changed");
+ }
+ privateFile(input.qualifiedReceiptPath, "qualified receipt");
+ const source = readFileSync(input.qualifiedReceiptPath);
+ if (sha256CgroupBytes(source) !== input.qualifiedReceiptFileSha256) {
+ throw new Error("qualified rootless receipt file changed");
+ }
+ let qualifiedReceipt;
+ try {
+ qualifiedReceipt = JSON.parse(source.toString("utf8"));
+ } catch (error) {
+ throw new Error("qualified rootless receipt JSON is invalid", {
+ cause: error,
+ });
+ }
+ const expectedReceipt = createQualifiedContainedRootlessReceipt({
+ aggregateLimitEvidence: qualifiedReceipt.aggregateLimitEvidence,
+ baseReceipt,
+ observerBindings: input.observerBindings,
+ });
+ const expectedSource = `${JSON.stringify(expectedReceipt, null, 2)}\n`;
+ if (!source.equals(Buffer.from(expectedSource))) {
+ throw new Error("qualified rootless receipt binding changed");
+ }
+ return qualifiedReceipt;
+}
diff --git a/scripts/contained-qualified-receipt-store.test.ts b/scripts/contained-qualified-receipt-store.test.ts
new file mode 100644
index 00000000..b0070980
--- /dev/null
+++ b/scripts/contained-qualified-receipt-store.test.ts
@@ -0,0 +1,226 @@
+import {
+ mkdirSync,
+ mkdtempSync,
+ readFileSync,
+ symlinkSync,
+ writeFileSync,
+} from "node:fs";
+import { resolve } from "node:path";
+
+import { describe, expect, it } from "vitest";
+
+import {
+ canonicalCgroupJson,
+ createContainedCgroupIdentity,
+ sha256CgroupBytes,
+} from "./contained-cgroup-evidence.mjs";
+import {
+ persistQualifiedContainedRootlessReceipt,
+ verifyQualifiedContainedRootlessReceipt,
+} from "./contained-qualified-receipt-store.mjs";
+import { UNQUALIFIED_AGGREGATE_LIMIT_MODE } from "./contained-qualified-rootless-receipt.mjs";
+
+const root = process.cwd();
+const invocationId = "1".repeat(64);
+const finalContainerId = "2".repeat(64);
+const sanitizedSpecSha256 = "3".repeat(64);
+const intendedAggregateLimits = {
+ cpuCount: 1,
+ maxProcesses: 16,
+ memoryBytes: 512 * 1024 * 1024,
+};
+const observerBindings = {
+ runtimeAttestationSha256: "4".repeat(64),
+ runtimeSessionId: "rt-v61-test1",
+ driverCliSha256: "5".repeat(64),
+ driverModuleSha256: "6".repeat(64),
+};
+
+function hashed>(payload: T) {
+ return {
+ ...payload,
+ receiptPayloadSha256: sha256CgroupBytes(canonicalCgroupJson(payload)),
+ };
+}
+
+function baseReceipt() {
+ return hashed({
+ schemaVersion: "4",
+ status: "VALIDATED",
+ limitMode: UNQUALIFIED_AGGREGATE_LIMIT_MODE,
+ aggregateLimitIntentEnforced: false,
+ invocationId,
+ stagingContainerId: "7".repeat(64),
+ finalContainerId,
+ metadataSha256: "8".repeat(64),
+ originalSpecSha256: "9".repeat(64),
+ baseSpecSha256: "a".repeat(64),
+ sanitizedSpecSha256,
+ configFileSha256: "b".repeat(64),
+ internalMountManifest: [],
+ internalMountManifestSha256: "c".repeat(64),
+ readOnlyMountManifest: [],
+ readOnlyMountManifestSha256: "d".repeat(64),
+ commandSha256: "e".repeat(64),
+ imageAuthority: {},
+ imageRootfs: {},
+ removedFields: [],
+ normalizedFields: [],
+ removedMounts: [],
+ intendedAggregateLimits,
+ enforcedRlimits: [],
+ aggregateLimitEvidence: null,
+ });
+}
+
+function aggregateEvidence() {
+ const memberPids = [100, 101];
+ return hashed({
+ schemaVersion: "1",
+ status: "OBSERVED",
+ authority: "linux-cgroup-v2",
+ cgroupVersion: 2,
+ cgroupId: `counterlab-v6.1-${invocationId}`,
+ cgroupPath: `counterlab-v6.1/${invocationId}`,
+ cgroupIdentity: createContainedCgroupIdentity({
+ invocationId,
+ finalContainerId,
+ sanitizedSpecSha256,
+ }),
+ invocationId,
+ finalContainerId,
+ sanitizedSpecSha256,
+ runtimeAttestationSha256: observerBindings.runtimeAttestationSha256,
+ observedLimits: {
+ memoryMaxBytes: intendedAggregateLimits.memoryBytes,
+ memorySwapMaxBytes: intendedAggregateLimits.memoryBytes,
+ pidsMax: intendedAggregateLimits.maxProcesses,
+ cpuQuotaMicros: 100_000,
+ cpuPeriodMicros: 100_000,
+ },
+ membership: {
+ leaderPid: 100,
+ leaderStartTimeTicks: "12345",
+ memberPids,
+ memberSetSha256: sha256CgroupBytes(canonicalCgroupJson(memberPids)),
+ descendantsObserved: true,
+ },
+ negativeControls: {
+ memory: {
+ requestedBytes: intendedAggregateLimits.memoryBytes + 1,
+ oomKillBefore: 0,
+ oomKillAfter: 1,
+ enforced: true,
+ },
+ processes: {
+ attemptedProcesses: intendedAggregateLimits.maxProcesses + 1,
+ maxEventsBefore: 0,
+ maxEventsAfter: 1,
+ enforced: true,
+ },
+ cpu: {
+ busyWindowMs: 500,
+ nrThrottledBefore: 0,
+ nrThrottledAfter: 1,
+ throttledUsecBefore: 0,
+ throttledUsecAfter: 1,
+ enforced: true,
+ },
+ },
+ cleanup: { cgroupAbsentAfterTimeout: true },
+ observer: {
+ runtimeSessionId: observerBindings.runtimeSessionId,
+ driverCliSha256: observerBindings.driverCliSha256,
+ driverModuleSha256: observerBindings.driverModuleSha256,
+ },
+ observedAt: "2026-07-20T12:00:00.000Z",
+ });
+}
+
+function fixture() {
+ const parent = resolve(
+ root,
+ "node_modules/.cache/counterlab-v6.1/tmp/qualified-receipt-store",
+ );
+ mkdirSync(parent, { recursive: true, mode: 0o700 });
+ const sessionRoot = mkdtempSync(resolve(parent, "rt-store-"));
+ const runRoot = resolve(sessionRoot, "run");
+ const specRoot = resolve(runRoot, "rootless-specs");
+ mkdirSync(specRoot, { recursive: true, mode: 0o700 });
+ const baseReceiptPath = resolve(specRoot, `${finalContainerId}.receipt.json`);
+ const source = `${JSON.stringify(baseReceipt(), null, 2)}\n`;
+ writeFileSync(baseReceiptPath, source, { mode: 0o600, flag: "wx" });
+ return {
+ baseReceiptFileSha256: sha256CgroupBytes(source),
+ baseReceiptPath,
+ sessionRoot,
+ };
+}
+
+describe("qualified rootless receipt store", () => {
+ it("creates and verifies one distinct qualified receipt", () => {
+ const input = fixture();
+ const persisted = persistQualifiedContainedRootlessReceipt({
+ ...input,
+ aggregateLimitEvidence: aggregateEvidence(),
+ finalContainerId,
+ observerBindings,
+ });
+
+ expect(persisted.qualifiedReceiptPath).toBe(
+ resolve(
+ input.sessionRoot,
+ `run/rootless-specs/${finalContainerId}.qualified-receipt.json`,
+ ),
+ );
+ expect(readFileSync(input.baseReceiptPath, "utf8")).toBe(
+ `${JSON.stringify(baseReceipt(), null, 2)}\n`,
+ );
+ expect(() =>
+ verifyQualifiedContainedRootlessReceipt({
+ ...input,
+ finalContainerId,
+ observerBindings,
+ qualifiedReceiptFileSha256: persisted.qualifiedReceiptFileSha256,
+ qualifiedReceiptPath: persisted.qualifiedReceiptPath,
+ }),
+ ).not.toThrow();
+ expect(() =>
+ persistQualifiedContainedRootlessReceipt({
+ ...input,
+ aggregateLimitEvidence: aggregateEvidence(),
+ finalContainerId,
+ observerBindings,
+ }),
+ ).toThrow(/already exists/u);
+ });
+
+ it("rejects path, hash, and symbolic-link drift", () => {
+ const wrongHash = fixture();
+ expect(() =>
+ persistQualifiedContainedRootlessReceipt({
+ ...wrongHash,
+ aggregateLimitEvidence: aggregateEvidence(),
+ baseReceiptFileSha256: "0".repeat(64),
+ finalContainerId,
+ observerBindings,
+ }),
+ ).toThrow(/base receipt/u);
+
+ const linked = fixture();
+ const linkedPath = resolve(
+ linked.sessionRoot,
+ "run/rootless-specs/link.json",
+ );
+ symlinkSync(linked.baseReceiptPath, linkedPath);
+ expect(() =>
+ persistQualifiedContainedRootlessReceipt({
+ ...linked,
+ aggregateLimitEvidence: aggregateEvidence(),
+ baseReceiptPath: linkedPath,
+ finalContainerId,
+ observerBindings,
+ }),
+ ).toThrow(/path/u);
+ });
+});
diff --git a/scripts/contained-runtime-attestation.mjs b/scripts/contained-runtime-attestation.mjs
index c286ec73..de4de1af 100644
--- a/scripts/contained-runtime-attestation.mjs
+++ b/scripts/contained-runtime-attestation.mjs
@@ -13,6 +13,8 @@ export const RUNTIME_POLICY_PATH =
export const RUNTIME_HELPER_PATHS = Object.freeze({
cgroupEvidence: "scripts/contained-cgroup-evidence.mjs",
+ qualifiedRootlessReceiptStore:
+ "scripts/contained-qualified-receipt-store.mjs",
qualifiedRootlessReceipt: "scripts/contained-qualified-rootless-receipt.mjs",
runtimeClient: "scripts/contained-runtime-client.mjs",
runtimeRequest: "scripts/contained-runtime-request.mjs",
From 0077f1a7af707cee6f3b502277738f5d04392aca Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 06:21:28 +0530
Subject: [PATCH 039/143] feat(runtime): add fixed cgroup controls
---
scripts/contained-cgroup-control-helper.d.mts | 8 +
scripts/contained-cgroup-control-helper.mjs | 217 ++++++++++++++++++
.../contained-cgroup-control-helper.test.ts | 50 ++++
scripts/contained-runtime-attestation.mjs | 1 +
4 files changed, 276 insertions(+)
create mode 100644 scripts/contained-cgroup-control-helper.d.mts
create mode 100644 scripts/contained-cgroup-control-helper.mjs
create mode 100644 scripts/contained-cgroup-control-helper.test.ts
diff --git a/scripts/contained-cgroup-control-helper.d.mts b/scripts/contained-cgroup-control-helper.d.mts
new file mode 100644
index 00000000..01bdd1ea
--- /dev/null
+++ b/scripts/contained-cgroup-control-helper.d.mts
@@ -0,0 +1,8 @@
+export type ContainedCgroupControl =
+ | { mode: "memory"; requestedBytes: number }
+ | { mode: "processes"; attemptedProcesses: number }
+ | { mode: "cpu"; busyWindowMs: number; workers: number };
+
+export function parseContainedCgroupControl(
+ args: string[],
+): ContainedCgroupControl;
diff --git a/scripts/contained-cgroup-control-helper.mjs b/scripts/contained-cgroup-control-helper.mjs
new file mode 100644
index 00000000..e9092499
--- /dev/null
+++ b/scripts/contained-cgroup-control-helper.mjs
@@ -0,0 +1,217 @@
+#!/usr/bin/env node
+
+import { spawn } from "node:child_process";
+import { realpathSync, writeFileSync } from "node:fs";
+import { performance } from "node:perf_hooks";
+import { fileURLToPath } from "node:url";
+
+const minimumMemoryBytes = 64 * 1024 * 1024;
+const maximumMemoryBytes = 1280 * 1024 * 1024;
+
+function integer(value) {
+ if (typeof value !== "string" || !/^[1-9][0-9]*$/u.test(value)) {
+ throw new Error("contained cgroup control integer is invalid");
+ }
+ const parsed = Number.parseInt(value, 10);
+ if (!Number.isSafeInteger(parsed)) {
+ throw new Error("contained cgroup control integer is invalid");
+ }
+ return parsed;
+}
+
+export function parseContainedCgroupControl(args) {
+ if (!Array.isArray(args) || args[0] !== "--mode") {
+ throw new Error("contained cgroup control arguments are invalid");
+ }
+ if (
+ args[1] === "memory" &&
+ args.length === 4 &&
+ args[2] === "--requested-bytes"
+ ) {
+ const requestedBytes = integer(args[3]);
+ if (
+ requestedBytes < minimumMemoryBytes ||
+ requestedBytes > maximumMemoryBytes
+ ) {
+ throw new Error("contained cgroup control memory bound is invalid");
+ }
+ return { mode: "memory", requestedBytes };
+ }
+ if (
+ args[1] === "processes" &&
+ args.length === 4 &&
+ args[2] === "--attempted-processes"
+ ) {
+ const attemptedProcesses = integer(args[3]);
+ if (attemptedProcesses < 2 || attemptedProcesses > 64) {
+ throw new Error("contained cgroup control process bound is invalid");
+ }
+ return { mode: "processes", attemptedProcesses };
+ }
+ if (
+ args[1] === "cpu" &&
+ args.length === 6 &&
+ args[2] === "--busy-window-ms" &&
+ args[4] === "--workers"
+ ) {
+ const busyWindowMs = integer(args[3]);
+ const workers = integer(args[5]);
+ if (
+ busyWindowMs < 100 ||
+ busyWindowMs > 5_000 ||
+ workers < 2 ||
+ workers > 8
+ ) {
+ throw new Error("contained cgroup control CPU bound is invalid");
+ }
+ return { mode: "cpu", busyWindowMs, workers };
+ }
+ throw new Error("contained cgroup control mode or fields are invalid");
+}
+
+async function waitForGo() {
+ process.stdout.write("READY\n");
+ const chunks = [];
+ let length = 0;
+ for await (const chunk of process.stdin) {
+ length += chunk.byteLength;
+ if (length > 16) {
+ throw new Error("contained cgroup control handshake is oversized");
+ }
+ chunks.push(chunk);
+ }
+ if (Buffer.concat(chunks).toString("utf8") !== "GO\n") {
+ throw new Error("contained cgroup control handshake is invalid");
+ }
+}
+
+function childCompletion(child) {
+ return new Promise((accept) => {
+ let started = false;
+ child.once("spawn", () => {
+ started = true;
+ });
+ child.once("error", () => accept({ started: false }));
+ child.once("close", (code, signal) => accept({ started, code, signal }));
+ });
+}
+
+async function memoryControl(requestedBytes) {
+ writeFileSync("/proc/self/oom_score_adj", "1000\n", "utf8");
+ const retained = [];
+ let allocated = 0;
+ const chunkBytes = 8 * 1024 * 1024;
+ while (allocated < requestedBytes) {
+ const chunk = Buffer.allocUnsafe(
+ Math.min(chunkBytes, requestedBytes - allocated),
+ );
+ for (let offset = 0; offset < chunk.byteLength; offset += 4_096) {
+ chunk[offset] = 1;
+ }
+ retained.push(chunk);
+ allocated += chunk.byteLength;
+ }
+ await new Promise((accept) => setTimeout(accept, 2_000));
+ throw new Error(
+ `contained cgroup memory control escaped its limit at ${retained.length} chunks`,
+ );
+}
+
+async function processControl(attemptedProcesses, scriptPath) {
+ const children = [];
+ const completions = [];
+ let denied = false;
+ for (let index = 0; index < attemptedProcesses; index += 1) {
+ const child = spawn(
+ process.execPath,
+ [scriptPath, "--internal-hold", "5000"],
+ { stdio: "ignore" },
+ );
+ children.push(child);
+ const completion = childCompletion(child);
+ completions.push(completion);
+ const state = await Promise.race([
+ completion,
+ new Promise((accept) =>
+ child.once("spawn", () => accept({ started: true })),
+ ),
+ ]);
+ if (!state.started) {
+ denied = true;
+ break;
+ }
+ }
+ for (const child of children) {
+ if (child.exitCode === null && child.signalCode === null) {
+ child.kill("SIGTERM");
+ }
+ }
+ await Promise.all(completions);
+ if (!denied) {
+ throw new Error("contained cgroup process control escaped its limit");
+ }
+ process.stdout.write(
+ `${JSON.stringify({ attemptedProcesses, denied: true })}\n`,
+ );
+}
+
+async function cpuControl(busyWindowMs, workers, scriptPath) {
+ const children = Array.from({ length: workers }, () =>
+ spawn(
+ process.execPath,
+ [scriptPath, "--internal-busy", String(busyWindowMs)],
+ { stdio: "ignore" },
+ ),
+ );
+ const results = await Promise.all(children.map(childCompletion));
+ if (results.some((result) => !result.started || result.code !== 0)) {
+ throw new Error("contained cgroup CPU control worker failed");
+ }
+ process.stdout.write(`${JSON.stringify({ busyWindowMs, workers })}\n`);
+}
+
+async function main() {
+ const scriptPath = realpathSync(fileURLToPath(import.meta.url));
+ if (process.argv[2] === "--internal-hold" && process.argv.length === 4) {
+ const holdMs = integer(process.argv[3]);
+ if (holdMs > 10_000) {
+ throw new Error("contained cgroup internal hold bound is invalid");
+ }
+ await new Promise((accept) => setTimeout(accept, holdMs));
+ return;
+ }
+ if (process.argv[2] === "--internal-busy" && process.argv.length === 4) {
+ const busyMs = integer(process.argv[3]);
+ if (busyMs > 5_000) {
+ throw new Error("contained cgroup internal CPU bound is invalid");
+ }
+ const deadline = performance.now() + busyMs;
+ while (performance.now() < deadline) {
+ // Fixed release-only CPU load. Kernel counters are authoritative.
+ }
+ return;
+ }
+ const control = parseContainedCgroupControl(process.argv.slice(2));
+ await waitForGo();
+ if (control.mode === "memory") {
+ await memoryControl(control.requestedBytes);
+ return;
+ }
+ if (control.mode === "processes") {
+ await processControl(control.attemptedProcesses, scriptPath);
+ return;
+ }
+ await cpuControl(control.busyWindowMs, control.workers, scriptPath);
+}
+
+if (
+ process.argv[1] !== undefined &&
+ realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))
+) {
+ main().catch((error) => {
+ process.stderr.write(
+ `${error instanceof Error ? error.message : "contained cgroup control failed"}\n`,
+ );
+ process.exitCode = 70;
+ });
+}
diff --git a/scripts/contained-cgroup-control-helper.test.ts b/scripts/contained-cgroup-control-helper.test.ts
new file mode 100644
index 00000000..d5e04954
--- /dev/null
+++ b/scripts/contained-cgroup-control-helper.test.ts
@@ -0,0 +1,50 @@
+import { describe, expect, it } from "vitest";
+
+import { parseContainedCgroupControl } from "./contained-cgroup-control-helper.mjs";
+
+describe("contained cgroup negative-control helper", () => {
+ it("accepts only bounded release-control modes", () => {
+ expect(
+ parseContainedCgroupControl([
+ "--mode",
+ "memory",
+ "--requested-bytes",
+ String(576 * 1024 * 1024),
+ ]),
+ ).toEqual({ mode: "memory", requestedBytes: 576 * 1024 * 1024 });
+ expect(
+ parseContainedCgroupControl([
+ "--mode",
+ "processes",
+ "--attempted-processes",
+ "17",
+ ]),
+ ).toEqual({ mode: "processes", attemptedProcesses: 17 });
+ expect(
+ parseContainedCgroupControl([
+ "--mode",
+ "cpu",
+ "--busy-window-ms",
+ "500",
+ "--workers",
+ "4",
+ ]),
+ ).toEqual({ mode: "cpu", busyWindowMs: 500, workers: 4 });
+ });
+
+ it("rejects unknown fields, unsafe bounds, and non-integers", () => {
+ for (const args of [
+ ["--mode", "unknown"],
+ ["--mode", "memory", "--requested-bytes", "1"],
+ ["--mode", "processes", "--attempted-processes", "65"],
+ ["--mode", "cpu", "--busy-window-ms", "99", "--workers", "4"],
+ ["--mode", "cpu", "--busy-window-ms", "500", "--workers", "1"],
+ ["--mode", "memory", "--requested-bytes", "100.5"],
+ ["--mode", "memory", "--requested-bytes", "67108864", "extra"],
+ ]) {
+ expect(() => parseContainedCgroupControl(args), args.join(" ")).toThrow(
+ /cgroup control/u,
+ );
+ }
+ });
+});
diff --git a/scripts/contained-runtime-attestation.mjs b/scripts/contained-runtime-attestation.mjs
index de4de1af..6494dcdb 100644
--- a/scripts/contained-runtime-attestation.mjs
+++ b/scripts/contained-runtime-attestation.mjs
@@ -12,6 +12,7 @@ export const RUNTIME_POLICY_PATH =
"services/runner/src/counterlab_runner/contained-runtime-policy.json";
export const RUNTIME_HELPER_PATHS = Object.freeze({
+ cgroupControlHelper: "scripts/contained-cgroup-control-helper.mjs",
cgroupEvidence: "scripts/contained-cgroup-evidence.mjs",
qualifiedRootlessReceiptStore:
"scripts/contained-qualified-receipt-store.mjs",
From fbc5ba9cbe1659b3e18ab230ef690cede3dd4bd5 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 06:31:59 +0530
Subject: [PATCH 040/143] feat(runtime): bind cgroup observer identity
---
.../contained-cgroup-observer-bindings.d.mts | 10 +
.../contained-cgroup-observer-bindings.mjs | 174 ++++++++++++++++++
...contained-cgroup-observer-bindings.test.ts | 139 ++++++++++++++
scripts/contained-runtime-attestation.mjs | 1 +
4 files changed, 324 insertions(+)
create mode 100644 scripts/contained-cgroup-observer-bindings.d.mts
create mode 100644 scripts/contained-cgroup-observer-bindings.mjs
create mode 100644 scripts/contained-cgroup-observer-bindings.test.ts
diff --git a/scripts/contained-cgroup-observer-bindings.d.mts b/scripts/contained-cgroup-observer-bindings.d.mts
new file mode 100644
index 00000000..91616f90
--- /dev/null
+++ b/scripts/contained-cgroup-observer-bindings.d.mts
@@ -0,0 +1,10 @@
+export interface ContainedCgroupObserverBindings {
+ driverCliSha256: string;
+ driverModuleSha256: string;
+ runtimeAttestationSha256: string;
+ runtimeSessionId: string;
+}
+
+export function resolveContainedCgroupObserverBindings(input: {
+ sessionRoot: string;
+}): Readonly;
diff --git a/scripts/contained-cgroup-observer-bindings.mjs b/scripts/contained-cgroup-observer-bindings.mjs
new file mode 100644
index 00000000..fb235661
--- /dev/null
+++ b/scripts/contained-cgroup-observer-bindings.mjs
@@ -0,0 +1,174 @@
+import { lstatSync, readFileSync, realpathSync } from "node:fs";
+import { basename, isAbsolute, relative, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+import {
+ canonicalRuntimeJson,
+ createPublicContainedRuntimeAttestation,
+ createRuntimeToolchainFingerprint,
+ requireContainedRuntimeSessionId,
+ sha256RuntimeBytes,
+} from "./contained-runtime-attestation.mjs";
+
+const repositoryRoot = realpathSync(
+ resolve(fileURLToPath(import.meta.url), "../.."),
+);
+const maximumAttestationBytes = 1_048_576;
+const sha256Pattern = /^[a-f0-9]{64}$/u;
+
+function contained(parent, candidate) {
+ const fromParent = relative(parent, candidate);
+ return (
+ fromParent === "" ||
+ (!fromParent.startsWith("..") && !isAbsolute(fromParent))
+ );
+}
+
+function privateDirectory(path, label) {
+ const metadata = lstatSync(path);
+ if (
+ metadata.isSymbolicLink() ||
+ !metadata.isDirectory() ||
+ realpathSync(path) !== path ||
+ metadata.uid !== process.getuid() ||
+ (metadata.mode & 0o077) !== 0
+ ) {
+ throw new Error(`contained cgroup observer ${label} is invalid`);
+ }
+}
+
+function privateFile(path, label) {
+ const metadata = lstatSync(path);
+ if (
+ metadata.isSymbolicLink() ||
+ !metadata.isFile() ||
+ realpathSync(path) !== path ||
+ metadata.uid !== process.getuid() ||
+ metadata.nlink !== 1 ||
+ (metadata.mode & 0o600) !== 0o600 ||
+ (metadata.mode & 0o077) !== 0 ||
+ metadata.size < 1 ||
+ metadata.size > maximumAttestationBytes
+ ) {
+ throw new Error(`contained cgroup observer ${label} is invalid`);
+ }
+}
+
+function exactRuntimeSessionRoot(sessionRoot) {
+ if (!isAbsolute(sessionRoot) || !contained(repositoryRoot, sessionRoot)) {
+ throw new Error("contained cgroup observer session root is invalid");
+ }
+ const runtimeSessionId = requireContainedRuntimeSessionId(
+ basename(sessionRoot),
+ );
+ const expected = resolve(repositoryRoot, ".rt", runtimeSessionId);
+ if (sessionRoot !== expected) {
+ throw new Error("contained cgroup observer session root is not exact");
+ }
+ privateDirectory(resolve(repositoryRoot, ".rt"), "runtime root");
+ privateDirectory(sessionRoot, "session root");
+ return runtimeSessionId;
+}
+
+function equalJson(left, right) {
+ return canonicalRuntimeJson(left) === canonicalRuntimeJson(right);
+}
+
+function repositoryFile(path, label) {
+ const metadata = lstatSync(path);
+ if (
+ !contained(repositoryRoot, path) ||
+ metadata.isSymbolicLink() ||
+ !metadata.isFile() ||
+ realpathSync(path) !== path ||
+ metadata.uid !== process.getuid()
+ ) {
+ throw new Error(`contained cgroup observer ${label} is invalid`);
+ }
+}
+
+export function resolveContainedCgroupObserverBindings({ sessionRoot }) {
+ const runtimeSessionId = exactRuntimeSessionRoot(sessionRoot);
+ const attestationPath = resolve(sessionRoot, "attestation.json");
+ privateFile(attestationPath, "runtime attestation");
+ let attestation;
+ try {
+ attestation = JSON.parse(readFileSync(attestationPath, "utf8"));
+ } catch (error) {
+ throw new Error("contained cgroup observer attestation JSON is invalid", {
+ cause: error,
+ });
+ }
+ const sessionPrefix = `.rt/${runtimeSessionId}`;
+ const expectedPublicPaths = {
+ containerdRootlesskitApiSocket: `${sessionPrefix}/run/containerd-rootless/api.sock`,
+ containerdSocket: `${sessionPrefix}/run/containerd.sock`,
+ runtimeCommandSocket: `${sessionPrefix}/run/runtime-command.sock`,
+ buildkitSocket: `${sessionPrefix}/run/buildkitd.sock`,
+ };
+ const material = createRuntimeToolchainFingerprint({
+ root: repositoryRoot,
+ adapterPath: resolve(
+ repositoryRoot,
+ "scripts/contained-runtime-adapter.sh",
+ ),
+ containerdConfigPath: resolve(sessionRoot, "config/containerd.toml"),
+ buildkitConfigPath: resolve(sessionRoot, "config/buildkitd.toml"),
+ });
+ if (
+ attestation?.schemaVersion !== "3" ||
+ attestation?.status !== "READY" ||
+ attestation?.sessionId !== runtimeSessionId ||
+ attestation?.namespace !== "counterlab-v6.1" ||
+ attestation?.paths?.sessionRoot !== sessionPrefix ||
+ Object.entries(expectedPublicPaths).some(
+ ([name, path]) => attestation?.paths?.[name] !== path,
+ ) ||
+ attestation?.runtimeToolchainSha256 !== material.runtimeToolchainSha256 ||
+ attestation?.toolchainLockSha256 !== material.toolchainLockSha256 ||
+ attestation?.adapterSha256 !== material.adapterSha256 ||
+ attestation?.runtimePolicySha256 !== material.runtimePolicySha256 ||
+ attestation?.proofDependencyManifestSha256 !==
+ material.proofDependencyManifestSha256 ||
+ !equalJson(attestation?.helperSha256, material.helperSha256) ||
+ !equalJson(attestation?.fileSha256, {
+ ...material.fileSha256,
+ supervisorReady: attestation?.fileSha256?.supervisorReady,
+ }) ||
+ !sha256Pattern.test(attestation?.fileSha256?.supervisorReady ?? "")
+ ) {
+ throw new Error("contained cgroup observer attestation binding changed");
+ }
+ const componentSha256 = Object.fromEntries(
+ Object.entries(material.lock.components).map(([name, component]) => [
+ name,
+ component.sha256,
+ ]),
+ );
+ const publicAttestation = createPublicContainedRuntimeAttestation({
+ attestation,
+ componentSha256,
+ });
+ const driverCliPath = resolve(
+ repositoryRoot,
+ "scripts/verify-contained-runtime-timeout.py",
+ );
+ const driverModulePath = resolve(
+ repositoryRoot,
+ "services/runner/src/counterlab_runner/timeout_proof.py",
+ );
+ for (const [path, label] of [
+ [driverCliPath, "proof CLI"],
+ [driverModulePath, "proof module"],
+ ]) {
+ repositoryFile(path, label);
+ }
+ return Object.freeze({
+ driverCliSha256: sha256RuntimeBytes(readFileSync(driverCliPath)),
+ driverModuleSha256: sha256RuntimeBytes(readFileSync(driverModulePath)),
+ runtimeAttestationSha256: sha256RuntimeBytes(
+ canonicalRuntimeJson(publicAttestation),
+ ),
+ runtimeSessionId,
+ });
+}
diff --git a/scripts/contained-cgroup-observer-bindings.test.ts b/scripts/contained-cgroup-observer-bindings.test.ts
new file mode 100644
index 00000000..ac6f9499
--- /dev/null
+++ b/scripts/contained-cgroup-observer-bindings.test.ts
@@ -0,0 +1,139 @@
+import { randomBytes } from "node:crypto";
+import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
+import { resolve } from "node:path";
+
+import { describe, expect, it } from "vitest";
+
+import {
+ canonicalRuntimeJson,
+ createPublicContainedRuntimeAttestation,
+ createRuntimeToolchainFingerprint,
+ sha256RuntimeBytes,
+} from "./contained-runtime-attestation.mjs";
+import { resolveContainedCgroupObserverBindings } from "./contained-cgroup-observer-bindings.mjs";
+
+const root = process.cwd();
+
+function fixture() {
+ const runtimeSessionId = `rt-bind-${randomBytes(4).toString("hex")}`;
+ const sessionRoot = resolve(root, ".rt", runtimeSessionId);
+ mkdirSync(resolve(sessionRoot, "config"), {
+ recursive: true,
+ mode: 0o700,
+ });
+ writeFileSync(resolve(sessionRoot, "config/containerd.toml"), "version=3\n", {
+ flag: "wx",
+ mode: 0o600,
+ });
+ writeFileSync(
+ resolve(sessionRoot, "config/buildkitd.toml"),
+ "debug=false\n",
+ {
+ flag: "wx",
+ mode: 0o600,
+ },
+ );
+ const material = createRuntimeToolchainFingerprint({
+ root,
+ adapterPath: resolve(root, "scripts/contained-runtime-adapter.sh"),
+ containerdConfigPath: resolve(sessionRoot, "config/containerd.toml"),
+ buildkitConfigPath: resolve(sessionRoot, "config/buildkitd.toml"),
+ });
+ const sessionPrefix = `.rt/${runtimeSessionId}`;
+ const attestation = {
+ schemaVersion: "3",
+ status: "READY",
+ sessionId: runtimeSessionId,
+ namespace: "counterlab-v6.1",
+ runtimeToolchainSha256: material.runtimeToolchainSha256,
+ toolchainLockSha256: material.toolchainLockSha256,
+ adapterSha256: material.adapterSha256,
+ helperSha256: material.helperSha256,
+ runtimePolicySha256: material.runtimePolicySha256,
+ proofDependencyManifest: material.proofDependencyManifest,
+ proofDependencyManifestSha256: material.proofDependencyManifestSha256,
+ createdAt: new Date().toISOString(),
+ paths: {
+ sessionRoot: sessionPrefix,
+ containerdRootlesskitApiSocket: `${sessionPrefix}/run/containerd-rootless/api.sock`,
+ containerdSocket: `${sessionPrefix}/run/containerd.sock`,
+ runtimeCommandSocket: `${sessionPrefix}/run/runtime-command.sock`,
+ buildkitSocket: `${sessionPrefix}/run/buildkitd.sock`,
+ },
+ pids: {},
+ fileSha256: {
+ ...material.fileSha256,
+ supervisorReady: "f".repeat(64),
+ },
+ };
+ writeFileSync(
+ resolve(sessionRoot, "attestation.json"),
+ `${JSON.stringify(attestation)}\n`,
+ { flag: "wx", mode: 0o600 },
+ );
+ return { attestation, material, runtimeSessionId, sessionRoot };
+}
+
+describe("contained cgroup observer bindings", () => {
+ it("derives proof-driver and public-attestation hashes from exact live bytes", () => {
+ const input = fixture();
+ const componentSha256 = Object.fromEntries(
+ Object.entries(input.material.lock.components).map(
+ ([name, component]) => [name, component.sha256],
+ ),
+ );
+ const publicAttestation = createPublicContainedRuntimeAttestation({
+ attestation: input.attestation,
+ componentSha256,
+ });
+
+ expect(
+ resolveContainedCgroupObserverBindings({
+ sessionRoot: input.sessionRoot,
+ }),
+ ).toEqual({
+ driverCliSha256: sha256RuntimeBytes(
+ readFileSync(
+ resolve(root, "scripts/verify-contained-runtime-timeout.py"),
+ ),
+ ),
+ driverModuleSha256: sha256RuntimeBytes(
+ readFileSync(
+ resolve(
+ root,
+ "services/runner/src/counterlab_runner/timeout_proof.py",
+ ),
+ ),
+ ),
+ runtimeAttestationSha256: sha256RuntimeBytes(
+ canonicalRuntimeJson(publicAttestation),
+ ),
+ runtimeSessionId: input.runtimeSessionId,
+ });
+ });
+
+ it("rejects a noncanonical session root and stale helper attestation", () => {
+ const wrongRoot = fixture();
+ expect(() =>
+ resolveContainedCgroupObserverBindings({
+ sessionRoot: resolve(wrongRoot.sessionRoot, "config"),
+ }),
+ ).toThrow(/session/u);
+
+ const stale = fixture();
+ stale.attestation.helperSha256 = {
+ ...stale.attestation.helperSha256,
+ runtimeRun: "0".repeat(64),
+ };
+ writeFileSync(
+ resolve(stale.sessionRoot, "attestation.json"),
+ `${JSON.stringify(stale.attestation)}\n`,
+ { mode: 0o600 },
+ );
+ expect(() =>
+ resolveContainedCgroupObserverBindings({
+ sessionRoot: stale.sessionRoot,
+ }),
+ ).toThrow(/attestation binding/u);
+ });
+});
diff --git a/scripts/contained-runtime-attestation.mjs b/scripts/contained-runtime-attestation.mjs
index 6494dcdb..faafc247 100644
--- a/scripts/contained-runtime-attestation.mjs
+++ b/scripts/contained-runtime-attestation.mjs
@@ -14,6 +14,7 @@ export const RUNTIME_POLICY_PATH =
export const RUNTIME_HELPER_PATHS = Object.freeze({
cgroupControlHelper: "scripts/contained-cgroup-control-helper.mjs",
cgroupEvidence: "scripts/contained-cgroup-evidence.mjs",
+ cgroupObserverBindings: "scripts/contained-cgroup-observer-bindings.mjs",
qualifiedRootlessReceiptStore:
"scripts/contained-qualified-receipt-store.mjs",
qualifiedRootlessReceipt: "scripts/contained-qualified-rootless-receipt.mjs",
From 37684dd2424bdf09def57d05cbbad40f2f3d75b3 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 06:35:18 +0530
Subject: [PATCH 041/143] feat(runtime): define cgroup observer protocol
---
.../contained-cgroup-observer-protocol.d.mts | 61 ++++++
.../contained-cgroup-observer-protocol.mjs | 201 ++++++++++++++++++
...contained-cgroup-observer-protocol.test.ts | 167 +++++++++++++++
scripts/contained-runtime-attestation.mjs | 1 +
4 files changed, 430 insertions(+)
create mode 100644 scripts/contained-cgroup-observer-protocol.d.mts
create mode 100644 scripts/contained-cgroup-observer-protocol.mjs
create mode 100644 scripts/contained-cgroup-observer-protocol.test.ts
diff --git a/scripts/contained-cgroup-observer-protocol.d.mts b/scripts/contained-cgroup-observer-protocol.d.mts
new file mode 100644
index 00000000..4c76be22
--- /dev/null
+++ b/scripts/contained-cgroup-observer-protocol.d.mts
@@ -0,0 +1,61 @@
+export interface ContainedCgroupObserverManifest {
+ schemaVersion: "1";
+ status: "REQUESTED";
+ qualificationMode: "aggregate-timeout-proof-v1";
+ runtimeSessionId: string;
+ invocationId: string;
+ finalContainerId: string;
+ cgroupId: string;
+ cgroupPath: string;
+ cgroupIdentity: string;
+ sanitizedSpecSha256: string;
+ intendedAggregateLimits: {
+ cpuCount: number;
+ maxProcesses: number;
+ memoryBytes: number;
+ };
+ baseReceiptPath: string;
+ baseReceiptFileSha256: string;
+ baseReceiptPayloadSha256: string;
+ runtimeAttestationSha256: string;
+ driverCliSha256: string;
+ driverModuleSha256: string;
+ requestedAt: string;
+ receiptPayloadSha256: string;
+}
+
+export function validateContainedCgroupObserverManifest(
+ value: unknown,
+ options?: { observedAtMs?: number },
+): ContainedCgroupObserverManifest;
+
+export function createContainedCgroupObserverManifest(input: {
+ baseReceiptFileSha256: string;
+ baseReceiptPayloadSha256: string;
+ finalContainerId: string;
+ intendedAggregateLimits: ContainedCgroupObserverManifest["intendedAggregateLimits"];
+ invocationId: string;
+ observerBindings: {
+ runtimeSessionId: string;
+ runtimeAttestationSha256: string;
+ driverCliSha256: string;
+ driverModuleSha256: string;
+ };
+ requestedAt?: Date;
+ sanitizedSpecSha256: string;
+}): ContainedCgroupObserverManifest;
+
+export function containedCgroupQualificationPaths(input: {
+ repositoryRoot: string;
+ runtimeSessionId: string;
+ invocationId: string;
+}): Readonly<{
+ evidencePath: string;
+ failurePath: string;
+ invocationRoot: string;
+ manifestPath: string;
+ observerDraftPath: string;
+ observerReadyPath: string;
+ qualificationRoot: string;
+ sessionRoot: string;
+}>;
diff --git a/scripts/contained-cgroup-observer-protocol.mjs b/scripts/contained-cgroup-observer-protocol.mjs
new file mode 100644
index 00000000..0988cbfa
--- /dev/null
+++ b/scripts/contained-cgroup-observer-protocol.mjs
@@ -0,0 +1,201 @@
+import { isAbsolute, relative, resolve } from "node:path";
+
+import { AGGREGATE_TIMEOUT_QUALIFICATION_MODE } from "./contained-runtime-request.mjs";
+import {
+ canonicalCgroupJson,
+ createContainedCgroupIdentity,
+ sha256CgroupBytes,
+} from "./contained-cgroup-evidence.mjs";
+
+const sha256Pattern = /^[a-f0-9]{64}$/u;
+const sessionPattern = /^rt-[a-z0-9][a-z0-9-]{7,13}$/u;
+const maximumAgeMs = 5 * 60_000;
+
+const manifestKeys = [
+ "baseReceiptFileSha256",
+ "baseReceiptPath",
+ "baseReceiptPayloadSha256",
+ "cgroupId",
+ "cgroupIdentity",
+ "cgroupPath",
+ "driverCliSha256",
+ "driverModuleSha256",
+ "finalContainerId",
+ "intendedAggregateLimits",
+ "invocationId",
+ "qualificationMode",
+ "receiptPayloadSha256",
+ "requestedAt",
+ "runtimeAttestationSha256",
+ "runtimeSessionId",
+ "sanitizedSpecSha256",
+ "schemaVersion",
+ "status",
+];
+
+function object(value, label) {
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
+ throw new Error(`contained cgroup observer ${label} shape is invalid`);
+ }
+ return value;
+}
+
+function exactKeys(value, keys, label) {
+ if (
+ JSON.stringify(Object.keys(value).sort()) !==
+ JSON.stringify([...keys].sort())
+ ) {
+ throw new Error(`contained cgroup observer ${label} shape is invalid`);
+ }
+}
+
+function validTimestamp(value, observedAtMs) {
+ if (typeof value !== "string") return false;
+ const parsed = Date.parse(value);
+ return (
+ Number.isFinite(parsed) &&
+ new Date(parsed).toISOString() === value &&
+ Math.abs(observedAtMs - parsed) <= maximumAgeMs
+ );
+}
+
+function validIntent(value) {
+ const intent = object(value, "aggregate intent");
+ exactKeys(
+ intent,
+ ["cpuCount", "maxProcesses", "memoryBytes"],
+ "aggregate intent",
+ );
+ if (
+ typeof intent.cpuCount !== "number" ||
+ !Number.isFinite(intent.cpuCount) ||
+ intent.cpuCount < 0.25 ||
+ intent.cpuCount > 2 ||
+ !Number.isSafeInteger(intent.maxProcesses) ||
+ intent.maxProcesses < 1 ||
+ intent.maxProcesses > 32 ||
+ !Number.isSafeInteger(intent.memoryBytes) ||
+ intent.memoryBytes < 64 * 1024 * 1024 ||
+ intent.memoryBytes > 1024 * 1024 * 1024
+ ) {
+ throw new Error("contained cgroup observer aggregate intent is invalid");
+ }
+ return intent;
+}
+
+export function validateContainedCgroupObserverManifest(
+ value,
+ { observedAtMs = Date.now() } = {},
+) {
+ const manifest = object(value, "manifest");
+ exactKeys(manifest, manifestKeys, "manifest");
+ const intent = validIntent(manifest.intendedAggregateLimits);
+ const expectedReceiptPath = `.rt/${manifest.runtimeSessionId}/run/rootless-specs/${manifest.finalContainerId}.receipt.json`;
+ const { receiptPayloadSha256, ...payload } = manifest;
+ if (
+ manifest.schemaVersion !== "1" ||
+ manifest.status !== "REQUESTED" ||
+ manifest.qualificationMode !== AGGREGATE_TIMEOUT_QUALIFICATION_MODE ||
+ !sessionPattern.test(manifest.runtimeSessionId ?? "") ||
+ !sha256Pattern.test(manifest.invocationId ?? "") ||
+ !sha256Pattern.test(manifest.finalContainerId ?? "") ||
+ !sha256Pattern.test(manifest.sanitizedSpecSha256 ?? "") ||
+ !sha256Pattern.test(manifest.baseReceiptFileSha256 ?? "") ||
+ !sha256Pattern.test(manifest.baseReceiptPayloadSha256 ?? "") ||
+ !sha256Pattern.test(manifest.runtimeAttestationSha256 ?? "") ||
+ !sha256Pattern.test(manifest.driverCliSha256 ?? "") ||
+ !sha256Pattern.test(manifest.driverModuleSha256 ?? "") ||
+ manifest.baseReceiptPath !== expectedReceiptPath ||
+ manifest.cgroupId !== `counterlab-v6.1-${manifest.invocationId}` ||
+ manifest.cgroupPath !== `counterlab-v6.1/${manifest.invocationId}` ||
+ manifest.cgroupIdentity !== createContainedCgroupIdentity(manifest) ||
+ !validTimestamp(manifest.requestedAt, observedAtMs) ||
+ !sha256Pattern.test(receiptPayloadSha256 ?? "") ||
+ sha256CgroupBytes(canonicalCgroupJson(payload)) !== receiptPayloadSha256
+ ) {
+ throw new Error("contained cgroup observer manifest binding is invalid");
+ }
+ return manifest;
+}
+
+export function createContainedCgroupObserverManifest({
+ baseReceiptFileSha256,
+ baseReceiptPayloadSha256,
+ finalContainerId,
+ intendedAggregateLimits,
+ invocationId,
+ observerBindings,
+ requestedAt = new Date(),
+ sanitizedSpecSha256,
+}) {
+ if (
+ !(requestedAt instanceof Date) ||
+ !Number.isFinite(requestedAt.getTime())
+ ) {
+ throw new Error("contained cgroup observer request time is invalid");
+ }
+ const payload = {
+ schemaVersion: "1",
+ status: "REQUESTED",
+ qualificationMode: AGGREGATE_TIMEOUT_QUALIFICATION_MODE,
+ runtimeSessionId: observerBindings?.runtimeSessionId,
+ invocationId,
+ finalContainerId,
+ cgroupId: `counterlab-v6.1-${invocationId}`,
+ cgroupPath: `counterlab-v6.1/${invocationId}`,
+ cgroupIdentity: createContainedCgroupIdentity({
+ invocationId,
+ finalContainerId,
+ sanitizedSpecSha256,
+ }),
+ sanitizedSpecSha256,
+ intendedAggregateLimits,
+ baseReceiptPath: `.rt/${observerBindings?.runtimeSessionId}/run/rootless-specs/${finalContainerId}.receipt.json`,
+ baseReceiptFileSha256,
+ baseReceiptPayloadSha256,
+ runtimeAttestationSha256: observerBindings?.runtimeAttestationSha256,
+ driverCliSha256: observerBindings?.driverCliSha256,
+ driverModuleSha256: observerBindings?.driverModuleSha256,
+ requestedAt: requestedAt.toISOString(),
+ };
+ return validateContainedCgroupObserverManifest(
+ {
+ ...payload,
+ receiptPayloadSha256: sha256CgroupBytes(canonicalCgroupJson(payload)),
+ },
+ { observedAtMs: requestedAt.getTime() },
+ );
+}
+
+export function containedCgroupQualificationPaths({
+ repositoryRoot,
+ runtimeSessionId,
+ invocationId,
+}) {
+ if (
+ !isAbsolute(repositoryRoot) ||
+ !sessionPattern.test(runtimeSessionId ?? "") ||
+ !sha256Pattern.test(invocationId ?? "")
+ ) {
+ throw new Error("contained cgroup observer path input is invalid");
+ }
+ const sessionRoot = resolve(repositoryRoot, ".rt", runtimeSessionId);
+ const qualificationRoot = resolve(sessionRoot, "run/cgroup-qualification");
+ const invocationRoot = resolve(qualificationRoot, invocationId);
+ for (const candidate of [sessionRoot, qualificationRoot, invocationRoot]) {
+ const fromRoot = relative(repositoryRoot, candidate);
+ if (fromRoot.startsWith("..") || isAbsolute(fromRoot)) {
+ throw new Error("contained cgroup observer path escaped the repository");
+ }
+ }
+ return Object.freeze({
+ evidencePath: resolve(invocationRoot, "aggregate-evidence.json"),
+ failurePath: resolve(invocationRoot, "failure.json"),
+ invocationRoot,
+ manifestPath: resolve(invocationRoot, "manifest.json"),
+ observerDraftPath: resolve(invocationRoot, "observer-draft.json"),
+ observerReadyPath: resolve(invocationRoot, "observer-ready.json"),
+ qualificationRoot,
+ sessionRoot,
+ });
+}
diff --git a/scripts/contained-cgroup-observer-protocol.test.ts b/scripts/contained-cgroup-observer-protocol.test.ts
new file mode 100644
index 00000000..718cc4af
--- /dev/null
+++ b/scripts/contained-cgroup-observer-protocol.test.ts
@@ -0,0 +1,167 @@
+import { resolve } from "node:path";
+
+import { describe, expect, it } from "vitest";
+
+import {
+ containedCgroupQualificationPaths,
+ createContainedCgroupObserverManifest,
+ validateContainedCgroupObserverManifest,
+} from "./contained-cgroup-observer-protocol.mjs";
+
+const root = process.cwd();
+const runtimeSessionId = "rt-v61-test1";
+const invocationId = "1".repeat(64);
+const finalContainerId = "2".repeat(64);
+const sanitizedSpecSha256 = "3".repeat(64);
+const requestedAt = new Date("2026-07-20T01:02:03.000Z");
+
+function manifest() {
+ return createContainedCgroupObserverManifest({
+ baseReceiptFileSha256: "4".repeat(64),
+ baseReceiptPayloadSha256: "5".repeat(64),
+ finalContainerId,
+ intendedAggregateLimits: {
+ cpuCount: 1,
+ maxProcesses: 16,
+ memoryBytes: 512 * 1024 * 1024,
+ },
+ invocationId,
+ observerBindings: {
+ runtimeSessionId,
+ runtimeAttestationSha256: "6".repeat(64),
+ driverCliSha256: "7".repeat(64),
+ driverModuleSha256: "8".repeat(64),
+ },
+ requestedAt,
+ sanitizedSpecSha256,
+ });
+}
+
+describe("contained cgroup observer protocol", () => {
+ it("creates a source-, session-, base-receipt-, and cgroup-bound request", () => {
+ const value = manifest();
+
+ expect(
+ validateContainedCgroupObserverManifest(value, {
+ observedAtMs: requestedAt.getTime(),
+ }),
+ ).toEqual(value);
+ expect(value.baseReceiptPath).toBe(
+ `.rt/${runtimeSessionId}/run/rootless-specs/${finalContainerId}.receipt.json`,
+ );
+ expect(value.cgroupPath).toBe(`counterlab-v6.1/${invocationId}`);
+ expect(value.receiptPayloadSha256).toMatch(/^[a-f0-9]{64}$/u);
+ });
+
+ it("rejects unknown, stale, and drifted requests", () => {
+ const mutations: Array<(value: ReturnType) => void> = [
+ (value) => {
+ value.finalContainerId = "9".repeat(64);
+ },
+ (value) => {
+ value.baseReceiptPath = ".rt/escaped/receipt.json";
+ },
+ (value) => {
+ value.intendedAggregateLimits.memoryBytes += 1;
+ },
+ (value) => {
+ value.driverCliSha256 = "a".repeat(64);
+ },
+ (value) => {
+ value.receiptPayloadSha256 = "b".repeat(64);
+ },
+ ];
+ for (const mutate of mutations) {
+ const changed = structuredClone(manifest());
+ mutate(changed);
+ expect(() =>
+ validateContainedCgroupObserverManifest(changed, {
+ observedAtMs: requestedAt.getTime(),
+ }),
+ ).toThrow(/observer manifest/u);
+ }
+ expect(() =>
+ validateContainedCgroupObserverManifest(
+ { ...manifest(), unknown: true },
+ { observedAtMs: requestedAt.getTime() },
+ ),
+ ).toThrow(/shape/u);
+ expect(() =>
+ validateContainedCgroupObserverManifest(manifest(), {
+ observedAtMs: requestedAt.getTime() + 5 * 60_000 + 1,
+ }),
+ ).toThrow(/binding/u);
+ });
+
+ it("derives only exact repository-contained handshake paths", () => {
+ expect(
+ containedCgroupQualificationPaths({
+ repositoryRoot: root,
+ runtimeSessionId,
+ invocationId,
+ }),
+ ).toEqual({
+ sessionRoot: resolve(root, ".rt", runtimeSessionId),
+ qualificationRoot: resolve(
+ root,
+ ".rt",
+ runtimeSessionId,
+ "run/cgroup-qualification",
+ ),
+ invocationRoot: resolve(
+ root,
+ ".rt",
+ runtimeSessionId,
+ "run/cgroup-qualification",
+ invocationId,
+ ),
+ manifestPath: resolve(
+ root,
+ ".rt",
+ runtimeSessionId,
+ "run/cgroup-qualification",
+ invocationId,
+ "manifest.json",
+ ),
+ observerReadyPath: resolve(
+ root,
+ ".rt",
+ runtimeSessionId,
+ "run/cgroup-qualification",
+ invocationId,
+ "observer-ready.json",
+ ),
+ observerDraftPath: resolve(
+ root,
+ ".rt",
+ runtimeSessionId,
+ "run/cgroup-qualification",
+ invocationId,
+ "observer-draft.json",
+ ),
+ evidencePath: resolve(
+ root,
+ ".rt",
+ runtimeSessionId,
+ "run/cgroup-qualification",
+ invocationId,
+ "aggregate-evidence.json",
+ ),
+ failurePath: resolve(
+ root,
+ ".rt",
+ runtimeSessionId,
+ "run/cgroup-qualification",
+ invocationId,
+ "failure.json",
+ ),
+ });
+ expect(() =>
+ containedCgroupQualificationPaths({
+ repositoryRoot: resolve(root, ".rt", ".."),
+ runtimeSessionId: "bad",
+ invocationId,
+ }),
+ ).toThrow(/path input/u);
+ });
+});
diff --git a/scripts/contained-runtime-attestation.mjs b/scripts/contained-runtime-attestation.mjs
index faafc247..03a0801f 100644
--- a/scripts/contained-runtime-attestation.mjs
+++ b/scripts/contained-runtime-attestation.mjs
@@ -15,6 +15,7 @@ export const RUNTIME_HELPER_PATHS = Object.freeze({
cgroupControlHelper: "scripts/contained-cgroup-control-helper.mjs",
cgroupEvidence: "scripts/contained-cgroup-evidence.mjs",
cgroupObserverBindings: "scripts/contained-cgroup-observer-bindings.mjs",
+ cgroupObserverProtocol: "scripts/contained-cgroup-observer-protocol.mjs",
qualifiedRootlessReceiptStore:
"scripts/contained-qualified-receipt-store.mjs",
qualifiedRootlessReceipt: "scripts/contained-qualified-rootless-receipt.mjs",
From 771d8bf8f8a33d4c35433d1db1ea4f288ba5e3fc Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 06:50:01 +0530
Subject: [PATCH 042/143] feat(runtime): observe aggregate cgroup controls
---
scripts/contained-cgroup-observer.d.mts | 60 ++
scripts/contained-cgroup-observer.mjs | 942 ++++++++++++++++++++++
scripts/contained-cgroup-observer.test.ts | 267 ++++++
scripts/contained-runtime-attestation.mjs | 1 +
4 files changed, 1270 insertions(+)
create mode 100644 scripts/contained-cgroup-observer.d.mts
create mode 100644 scripts/contained-cgroup-observer.mjs
create mode 100644 scripts/contained-cgroup-observer.test.ts
diff --git a/scripts/contained-cgroup-observer.d.mts b/scripts/contained-cgroup-observer.d.mts
new file mode 100644
index 00000000..0577ca25
--- /dev/null
+++ b/scripts/contained-cgroup-observer.d.mts
@@ -0,0 +1,60 @@
+import type { ContainedCgroupObserverManifest } from "./contained-cgroup-observer-protocol.mjs";
+
+export function createContainedCgroupObserverReady(
+ manifest: ContainedCgroupObserverManifest,
+ options?: { armedAt?: Date; observerPid?: number },
+): Record;
+
+export function validateContainedCgroupObserverReady(
+ value: T,
+ manifest: ContainedCgroupObserverManifest,
+): T;
+
+export function createContainedCgroupObserverFailure(
+ manifest: ContainedCgroupObserverManifest,
+ options?: { failedAt?: Date },
+): Record;
+
+export function validateContainedCgroupObserverFailure(
+ value: T,
+ manifest: ContainedCgroupObserverManifest,
+): T;
+
+export function createContainedCgroupObserverDraft(
+ manifest: ContainedCgroupObserverManifest,
+ observation: Record,
+): Record;
+
+export function validateContainedCgroupObserverDraft(
+ value: T,
+ manifest: ContainedCgroupObserverManifest,
+): T;
+
+export function parseContainedCgroupKeyValues(
+ source: string,
+): Record;
+export function parseContainedCgroupMembers(source: string): number[];
+export function parseContainedProcessStat(
+ source: string,
+ expectedPid: number,
+): { pid: number; parentPid: number; startTimeTicks: string };
+export function selectContainedCgroupMembership(
+ memberPids: number[],
+ processStats: Map<
+ number,
+ { pid: number; parentPid: number; startTimeTicks: string }
+ >,
+): Record;
+
+export function observeContainedCgroup(
+ manifest: ContainedCgroupObserverManifest,
+ adapter: {
+ now(): Date;
+ waitForCgroup(): Promise;
+ readCgroupFile(name: string): string | Promise;
+ readProcessStat(pid: number): string | Promise;
+ runControl(control: Record): Promise;
+ publishDraft(draft: Record): Promise;
+ waitForCgroupAbsent(): Promise;
+ },
+): Promise>;
diff --git a/scripts/contained-cgroup-observer.mjs b/scripts/contained-cgroup-observer.mjs
new file mode 100644
index 00000000..ccbd3ab0
--- /dev/null
+++ b/scripts/contained-cgroup-observer.mjs
@@ -0,0 +1,942 @@
+#!/usr/bin/env node
+
+import { spawn } from "node:child_process";
+import {
+ existsSync,
+ lstatSync,
+ readFileSync,
+ realpathSync,
+ statfsSync,
+ writeFileSync,
+} from "node:fs";
+import { isAbsolute, relative, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+import {
+ canonicalCgroupJson,
+ sha256CgroupBytes,
+ validateContainedCgroupEvidence,
+} from "./contained-cgroup-evidence.mjs";
+import { resolveContainedCgroupObserverBindings } from "./contained-cgroup-observer-bindings.mjs";
+import {
+ containedCgroupQualificationPaths,
+ validateContainedCgroupObserverManifest,
+} from "./contained-cgroup-observer-protocol.mjs";
+
+const repositoryRoot = realpathSync(
+ resolve(fileURLToPath(import.meta.url), "../.."),
+);
+const cgroupFilesystemRoot = "/sys/fs/cgroup";
+const cgroup2Magic = 0x63677270;
+const maximumArtifactBytes = 1_048_576;
+const allowedCgroupFiles = new Set([
+ "cgroup.procs",
+ "cpu.max",
+ "cpu.stat",
+ "memory.events",
+ "memory.max",
+ "memory.swap.max",
+ "pids.events",
+ "pids.max",
+]);
+
+function object(value, label) {
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
+ throw new Error(`contained cgroup observer ${label} shape is invalid`);
+ }
+ return value;
+}
+
+function exactKeys(value, keys, label) {
+ if (
+ JSON.stringify(Object.keys(value).sort()) !==
+ JSON.stringify([...keys].sort())
+ ) {
+ throw new Error(`contained cgroup observer ${label} shape is invalid`);
+ }
+}
+
+function hashedArtifact(payload) {
+ return {
+ ...payload,
+ receiptPayloadSha256: sha256CgroupBytes(canonicalCgroupJson(payload)),
+ };
+}
+
+function validTimestamp(value) {
+ if (typeof value !== "string") return false;
+ const parsed = Date.parse(value);
+ return Number.isFinite(parsed) && new Date(parsed).toISOString() === value;
+}
+
+export function createContainedCgroupObserverReady(
+ manifest,
+ { armedAt = new Date(), observerPid = process.pid } = {},
+) {
+ if (
+ !(armedAt instanceof Date) ||
+ !Number.isFinite(armedAt.getTime()) ||
+ !Number.isSafeInteger(observerPid) ||
+ observerPid < 1
+ ) {
+ throw new Error("contained cgroup observer ready input is invalid");
+ }
+ const payload = {
+ schemaVersion: "1",
+ status: "ARMED",
+ manifestPayloadSha256: manifest.receiptPayloadSha256,
+ invocationId: manifest.invocationId,
+ finalContainerId: manifest.finalContainerId,
+ observerPid,
+ armedAt: armedAt.toISOString(),
+ };
+ return validateContainedCgroupObserverReady(
+ hashedArtifact(payload),
+ manifest,
+ );
+}
+
+export function validateContainedCgroupObserverReady(value, manifest) {
+ const ready = object(value, "ready receipt");
+ exactKeys(
+ ready,
+ [
+ "armedAt",
+ "finalContainerId",
+ "invocationId",
+ "manifestPayloadSha256",
+ "observerPid",
+ "receiptPayloadSha256",
+ "schemaVersion",
+ "status",
+ ],
+ "ready receipt",
+ );
+ const { receiptPayloadSha256, ...payload } = ready;
+ if (
+ ready.schemaVersion !== "1" ||
+ ready.status !== "ARMED" ||
+ ready.manifestPayloadSha256 !== manifest.receiptPayloadSha256 ||
+ ready.invocationId !== manifest.invocationId ||
+ ready.finalContainerId !== manifest.finalContainerId ||
+ !Number.isSafeInteger(ready.observerPid) ||
+ ready.observerPid < 1 ||
+ !validTimestamp(ready.armedAt) ||
+ !/^[a-f0-9]{64}$/u.test(receiptPayloadSha256 ?? "") ||
+ sha256CgroupBytes(canonicalCgroupJson(payload)) !== receiptPayloadSha256
+ ) {
+ throw new Error("contained cgroup observer ready binding is invalid");
+ }
+ return ready;
+}
+
+export function createContainedCgroupObserverFailure(
+ manifest,
+ { failedAt = new Date() } = {},
+) {
+ if (!(failedAt instanceof Date) || !Number.isFinite(failedAt.getTime())) {
+ throw new Error("contained cgroup observer failure input is invalid");
+ }
+ const payload = {
+ schemaVersion: "1",
+ status: "FAILED",
+ code: "OBSERVATION_FAILED",
+ manifestPayloadSha256: manifest.receiptPayloadSha256,
+ invocationId: manifest.invocationId,
+ finalContainerId: manifest.finalContainerId,
+ failedAt: failedAt.toISOString(),
+ };
+ return validateContainedCgroupObserverFailure(
+ hashedArtifact(payload),
+ manifest,
+ );
+}
+
+export function validateContainedCgroupObserverFailure(value, manifest) {
+ const failure = object(value, "failure receipt");
+ exactKeys(
+ failure,
+ [
+ "code",
+ "failedAt",
+ "finalContainerId",
+ "invocationId",
+ "manifestPayloadSha256",
+ "receiptPayloadSha256",
+ "schemaVersion",
+ "status",
+ ],
+ "failure receipt",
+ );
+ const { receiptPayloadSha256, ...payload } = failure;
+ if (
+ failure.schemaVersion !== "1" ||
+ failure.status !== "FAILED" ||
+ failure.code !== "OBSERVATION_FAILED" ||
+ failure.manifestPayloadSha256 !== manifest.receiptPayloadSha256 ||
+ failure.invocationId !== manifest.invocationId ||
+ failure.finalContainerId !== manifest.finalContainerId ||
+ !validTimestamp(failure.failedAt) ||
+ !/^[a-f0-9]{64}$/u.test(receiptPayloadSha256 ?? "") ||
+ sha256CgroupBytes(canonicalCgroupJson(payload)) !== receiptPayloadSha256
+ ) {
+ throw new Error("contained cgroup observer failure binding is invalid");
+ }
+ return failure;
+}
+
+function expectedEvidence(manifest) {
+ return {
+ invocationId: manifest.invocationId,
+ finalContainerId: manifest.finalContainerId,
+ sanitizedSpecSha256: manifest.sanitizedSpecSha256,
+ intendedAggregateLimits: manifest.intendedAggregateLimits,
+ runtimeAttestationSha256: manifest.runtimeAttestationSha256,
+ runtimeSessionId: manifest.runtimeSessionId,
+ driverCliSha256: manifest.driverCliSha256,
+ driverModuleSha256: manifest.driverModuleSha256,
+ };
+}
+
+function evidencePayload(manifest, observation) {
+ return {
+ schemaVersion: "1",
+ status: "OBSERVED",
+ authority: "linux-cgroup-v2",
+ cgroupVersion: 2,
+ cgroupId: manifest.cgroupId,
+ cgroupPath: manifest.cgroupPath,
+ cgroupIdentity: manifest.cgroupIdentity,
+ invocationId: manifest.invocationId,
+ finalContainerId: manifest.finalContainerId,
+ sanitizedSpecSha256: manifest.sanitizedSpecSha256,
+ runtimeAttestationSha256: manifest.runtimeAttestationSha256,
+ observedLimits: observation.observedLimits,
+ membership: observation.membership,
+ negativeControls: observation.negativeControls,
+ cleanup: { cgroupAbsentAfterTimeout: true },
+ observer: {
+ runtimeSessionId: manifest.runtimeSessionId,
+ driverCliSha256: manifest.driverCliSha256,
+ driverModuleSha256: manifest.driverModuleSha256,
+ },
+ observedAt: observation.observedAt,
+ };
+}
+
+export function createContainedCgroupObserverDraft(manifest, observation) {
+ const finalEvidence = validateContainedCgroupEvidence(
+ hashedArtifact(evidencePayload(manifest, observation)),
+ expectedEvidence(manifest),
+ );
+ const payload = {
+ schemaVersion: "1",
+ status: "OBSERVED_ACTIVE",
+ manifestPayloadSha256: manifest.receiptPayloadSha256,
+ invocationId: manifest.invocationId,
+ finalContainerId: manifest.finalContainerId,
+ candidateLeaderPresentAfterControls: true,
+ observedLimits: finalEvidence.observedLimits,
+ membership: finalEvidence.membership,
+ negativeControls: finalEvidence.negativeControls,
+ observedAt: finalEvidence.observedAt,
+ };
+ return validateContainedCgroupObserverDraft(
+ hashedArtifact(payload),
+ manifest,
+ );
+}
+
+export function validateContainedCgroupObserverDraft(value, manifest) {
+ const draft = object(value, "draft receipt");
+ exactKeys(
+ draft,
+ [
+ "candidateLeaderPresentAfterControls",
+ "finalContainerId",
+ "invocationId",
+ "manifestPayloadSha256",
+ "membership",
+ "negativeControls",
+ "observedAt",
+ "observedLimits",
+ "receiptPayloadSha256",
+ "schemaVersion",
+ "status",
+ ],
+ "draft receipt",
+ );
+ const { receiptPayloadSha256, ...payload } = draft;
+ if (
+ draft.schemaVersion !== "1" ||
+ draft.status !== "OBSERVED_ACTIVE" ||
+ draft.manifestPayloadSha256 !== manifest.receiptPayloadSha256 ||
+ draft.invocationId !== manifest.invocationId ||
+ draft.finalContainerId !== manifest.finalContainerId ||
+ draft.candidateLeaderPresentAfterControls !== true ||
+ !validTimestamp(draft.observedAt) ||
+ !/^[a-f0-9]{64}$/u.test(receiptPayloadSha256 ?? "") ||
+ sha256CgroupBytes(canonicalCgroupJson(payload)) !== receiptPayloadSha256
+ ) {
+ throw new Error("contained cgroup observer draft binding is invalid");
+ }
+ validateContainedCgroupEvidence(
+ hashedArtifact(evidencePayload(manifest, draft)),
+ expectedEvidence(manifest),
+ );
+ return draft;
+}
+
+function positiveInteger(value, label) {
+ if (!/^[1-9][0-9]*$/u.test(value)) {
+ throw new Error(`contained cgroup observer ${label} is invalid`);
+ }
+ const parsed = Number.parseInt(value, 10);
+ if (!Number.isSafeInteger(parsed)) {
+ throw new Error(`contained cgroup observer ${label} is invalid`);
+ }
+ return parsed;
+}
+
+function nonnegativeInteger(value, label) {
+ if (!/^(?:0|[1-9][0-9]*)$/u.test(value)) {
+ throw new Error(`contained cgroup observer ${label} is invalid`);
+ }
+ const parsed = Number.parseInt(value, 10);
+ if (!Number.isSafeInteger(parsed)) {
+ throw new Error(`contained cgroup observer ${label} is invalid`);
+ }
+ return parsed;
+}
+
+export function parseContainedCgroupKeyValues(source) {
+ if (
+ typeof source !== "string" ||
+ source.length === 0 ||
+ source.length > 8_192
+ ) {
+ throw new Error("contained cgroup observer counter source is invalid");
+ }
+ const values = {};
+ for (const line of source.trim().split("\n")) {
+ const match = line.match(/^([a-z_]+) ([0-9]+)$/u);
+ if (match === null || Object.hasOwn(values, match[1])) {
+ throw new Error("contained cgroup observer counter source is invalid");
+ }
+ values[match[1]] = nonnegativeInteger(match[2], "counter value");
+ }
+ return values;
+}
+
+export function parseContainedCgroupMembers(source) {
+ if (
+ typeof source !== "string" ||
+ source.length === 0 ||
+ source.length > 8_192
+ ) {
+ throw new Error("contained cgroup observer membership source is invalid");
+ }
+ const members = source
+ .trim()
+ .split("\n")
+ .map((entry) => positiveInteger(entry, "member PID"))
+ .sort((left, right) => left - right);
+ if (
+ members.length < 2 ||
+ members.length > 64 ||
+ new Set(members).size !== members.length
+ ) {
+ throw new Error("contained cgroup observer membership is invalid");
+ }
+ return members;
+}
+
+export function parseContainedProcessStat(source, expectedPid) {
+ if (
+ typeof source !== "string" ||
+ source.length === 0 ||
+ source.length > 8_192
+ ) {
+ throw new Error("contained cgroup observer process stat is invalid");
+ }
+ const separator = source.lastIndexOf(") ");
+ const opening = source.indexOf(" (");
+ if (opening < 1 || separator <= opening) {
+ throw new Error("contained cgroup observer process stat is invalid");
+ }
+ const pid = positiveInteger(source.slice(0, opening), "process PID");
+ const fields = source
+ .slice(separator + 2)
+ .trim()
+ .split(/\s+/u);
+ if (
+ pid !== expectedPid ||
+ fields.length < 20 ||
+ !/^[A-Z]$/u.test(fields[0])
+ ) {
+ throw new Error("contained cgroup observer process stat is invalid");
+ }
+ return {
+ pid,
+ parentPid: nonnegativeInteger(fields[1], "parent PID"),
+ startTimeTicks: String(positiveInteger(fields[19], "process start time")),
+ };
+}
+
+export function selectContainedCgroupMembership(memberPids, processStats) {
+ const members = new Set(memberPids);
+ if (
+ !(processStats instanceof Map) ||
+ processStats.size !== members.size ||
+ [...members].some((pid) => processStats.get(pid)?.pid !== pid)
+ ) {
+ throw new Error("contained cgroup observer process membership is invalid");
+ }
+ const roots = memberPids.filter(
+ (pid) => !members.has(processStats.get(pid).parentPid),
+ );
+ if (roots.length !== 1) {
+ throw new Error("contained cgroup observer ancestry is ambiguous");
+ }
+ const leaderPid = roots[0];
+ const descendantsObserved = memberPids.some((pid) => {
+ if (pid === leaderPid) return false;
+ const visited = new Set();
+ let current = pid;
+ while (members.has(current) && !visited.has(current)) {
+ if (current === leaderPid) return true;
+ visited.add(current);
+ current = processStats.get(current).parentPid;
+ }
+ return false;
+ });
+ if (!descendantsObserved) {
+ throw new Error("contained cgroup observer found no candidate descendant");
+ }
+ return {
+ leaderPid,
+ leaderStartTimeTicks: processStats.get(leaderPid).startTimeTicks,
+ memberPids,
+ memberSetSha256: sha256CgroupBytes(canonicalCgroupJson(memberPids)),
+ descendantsObserved: true,
+ };
+}
+
+function scalar(source, label) {
+ return positiveInteger(String(source).trim(), label);
+}
+
+function swapScalar(source) {
+ const value = String(source).trim();
+ return value === "0" ? 0 : positiveInteger(value, "memory swap limit");
+}
+
+function cpuLimit(source) {
+ const match = String(source)
+ .trim()
+ .match(/^([1-9][0-9]*) ([1-9][0-9]*)$/u);
+ if (match === null) {
+ throw new Error("contained cgroup observer CPU limit is invalid");
+ }
+ return {
+ cpuQuotaMicros: positiveInteger(match[1], "CPU quota"),
+ cpuPeriodMicros: positiveInteger(match[2], "CPU period"),
+ };
+}
+
+async function stableMembership(adapter) {
+ const memberPids = parseContainedCgroupMembers(
+ await adapter.readCgroupFile("cgroup.procs"),
+ );
+ const processStats = new Map();
+ for (const pid of memberPids) {
+ processStats.set(
+ pid,
+ parseContainedProcessStat(await adapter.readProcessStat(pid), pid),
+ );
+ }
+ const membership = selectContainedCgroupMembership(memberPids, processStats);
+ return { memberPids, membership, processStats };
+}
+
+async function assertCandidateSurvived(adapter, initial) {
+ const current = new Set(
+ parseContainedCgroupMembers(await adapter.readCgroupFile("cgroup.procs")),
+ );
+ for (const pid of initial.memberPids) {
+ if (!current.has(pid)) {
+ throw new Error("contained cgroup observer candidate membership changed");
+ }
+ const observed = parseContainedProcessStat(
+ await adapter.readProcessStat(pid),
+ pid,
+ );
+ if (
+ observed.startTimeTicks !== initial.processStats.get(pid).startTimeTicks
+ ) {
+ throw new Error("contained cgroup observer candidate PID was reused");
+ }
+ }
+}
+
+export async function observeContainedCgroup(manifest, adapter) {
+ await adapter.waitForCgroup();
+ const observedLimits = {
+ memoryMaxBytes: scalar(
+ await adapter.readCgroupFile("memory.max"),
+ "memory limit",
+ ),
+ memorySwapMaxBytes: swapScalar(
+ await adapter.readCgroupFile("memory.swap.max"),
+ ),
+ pidsMax: scalar(await adapter.readCgroupFile("pids.max"), "PID limit"),
+ ...cpuLimit(await adapter.readCgroupFile("cpu.max")),
+ };
+ const initial = await stableMembership(adapter);
+
+ const cpuBefore = parseContainedCgroupKeyValues(
+ await adapter.readCgroupFile("cpu.stat"),
+ );
+ await adapter.runControl({ mode: "cpu", busyWindowMs: 500, workers: 4 });
+ const cpuAfter = parseContainedCgroupKeyValues(
+ await adapter.readCgroupFile("cpu.stat"),
+ );
+
+ const processesBefore = parseContainedCgroupKeyValues(
+ await adapter.readCgroupFile("pids.events"),
+ );
+ const attemptedProcesses = observedLimits.pidsMax + 1;
+ await adapter.runControl({ mode: "processes", attemptedProcesses });
+ const processesAfter = parseContainedCgroupKeyValues(
+ await adapter.readCgroupFile("pids.events"),
+ );
+
+ const memoryBefore = parseContainedCgroupKeyValues(
+ await adapter.readCgroupFile("memory.events"),
+ );
+ const requestedBytes = observedLimits.memoryMaxBytes + 64 * 1024 * 1024;
+ await adapter.runControl({ mode: "memory", requestedBytes });
+ const memoryAfter = parseContainedCgroupKeyValues(
+ await adapter.readCgroupFile("memory.events"),
+ );
+
+ await assertCandidateSurvived(adapter, initial);
+ const observedAt = adapter.now().toISOString();
+ const observation = {
+ observedLimits,
+ membership: initial.membership,
+ negativeControls: {
+ memory: {
+ requestedBytes,
+ oomKillBefore: memoryBefore.oom_kill,
+ oomKillAfter: memoryAfter.oom_kill,
+ enforced: memoryAfter.oom_kill > memoryBefore.oom_kill,
+ },
+ processes: {
+ attemptedProcesses,
+ maxEventsBefore: processesBefore.max,
+ maxEventsAfter: processesAfter.max,
+ enforced: processesAfter.max > processesBefore.max,
+ },
+ cpu: {
+ busyWindowMs: 500,
+ nrThrottledBefore: cpuBefore.nr_throttled,
+ nrThrottledAfter: cpuAfter.nr_throttled,
+ throttledUsecBefore: cpuBefore.throttled_usec,
+ throttledUsecAfter: cpuAfter.throttled_usec,
+ enforced:
+ cpuAfter.nr_throttled > cpuBefore.nr_throttled &&
+ cpuAfter.throttled_usec > cpuBefore.throttled_usec,
+ },
+ },
+ observedAt,
+ };
+ const draft = createContainedCgroupObserverDraft(manifest, observation);
+ await adapter.publishDraft(draft);
+ await adapter.waitForCgroupAbsent();
+ return validateContainedCgroupEvidence(
+ hashedArtifact(evidencePayload(manifest, observation)),
+ expectedEvidence(manifest),
+ );
+}
+
+function privateDirectory(path, label) {
+ const metadata = lstatSync(path);
+ if (
+ metadata.isSymbolicLink() ||
+ !metadata.isDirectory() ||
+ realpathSync(path) !== path ||
+ metadata.uid !== process.getuid() ||
+ (metadata.mode & 0o777) !== 0o700
+ ) {
+ throw new Error(`contained cgroup observer ${label} is invalid`);
+ }
+}
+
+function privateFile(path, label) {
+ const metadata = lstatSync(path);
+ if (
+ metadata.isSymbolicLink() ||
+ !metadata.isFile() ||
+ realpathSync(path) !== path ||
+ metadata.uid !== process.getuid() ||
+ metadata.nlink !== 1 ||
+ (metadata.mode & 0o777) !== 0o600 ||
+ metadata.size < 1 ||
+ metadata.size > maximumArtifactBytes
+ ) {
+ throw new Error(`contained cgroup observer ${label} is invalid`);
+ }
+}
+
+function writeArtifact(path, value) {
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, {
+ encoding: "utf8",
+ flag: "wx",
+ mode: 0o600,
+ });
+ privateFile(path, "artifact");
+}
+
+function sleep(milliseconds) {
+ return new Promise((accept) => setTimeout(accept, milliseconds));
+}
+
+async function waitUntil(predicate, label, timeoutMs) {
+ const deadline = Date.now() + timeoutMs;
+ while (Date.now() <= deadline) {
+ if (predicate()) return;
+ await sleep(10);
+ }
+ throw new Error(`contained cgroup observer ${label} timed out`);
+}
+
+function actualCgroupAdapter(manifest, paths) {
+ const cgroupRoot = resolve(cgroupFilesystemRoot, manifest.cgroupPath);
+ if (
+ resolve(
+ cgroupFilesystemRoot,
+ relative(cgroupFilesystemRoot, cgroupRoot),
+ ) !== cgroupRoot ||
+ statfsSync(cgroupFilesystemRoot).type !== cgroup2Magic
+ ) {
+ throw new Error("contained cgroup observer cgroup v2 root is invalid");
+ }
+ const helperPath = resolve(
+ repositoryRoot,
+ "scripts/contained-cgroup-control-helper.mjs",
+ );
+
+ function cgroupFile(name) {
+ if (!allowedCgroupFiles.has(name)) {
+ throw new Error("contained cgroup observer file is not allowlisted");
+ }
+ const path = resolve(cgroupRoot, name);
+ if (resolve(cgroupRoot, name) !== path) {
+ throw new Error("contained cgroup observer file path is invalid");
+ }
+ const metadata = lstatSync(path);
+ if (
+ metadata.isSymbolicLink() ||
+ !metadata.isFile() ||
+ realpathSync(path) !== path
+ ) {
+ throw new Error("contained cgroup observer file is invalid");
+ }
+ return path;
+ }
+
+ async function runControl(control) {
+ const args =
+ control.mode === "memory"
+ ? [
+ "--mode",
+ "memory",
+ "--requested-bytes",
+ String(control.requestedBytes),
+ ]
+ : control.mode === "processes"
+ ? [
+ "--mode",
+ "processes",
+ "--attempted-processes",
+ String(control.attemptedProcesses),
+ ]
+ : [
+ "--mode",
+ "cpu",
+ "--busy-window-ms",
+ String(control.busyWindowMs),
+ "--workers",
+ String(control.workers),
+ ];
+ const child = spawn(process.execPath, [helperPath, ...args], {
+ cwd: repositoryRoot,
+ env: process.env,
+ stdio: ["pipe", "pipe", "pipe"],
+ });
+ let stdout = "";
+ let stderr = "";
+ let outputInvalid = false;
+ const completion = new Promise((accept) => {
+ child.once("error", (error) =>
+ accept({ code: null, signal: null, error }),
+ );
+ child.once("close", (code, signal) => accept({ code, signal }));
+ });
+ const ready = new Promise((accept, reject) => {
+ child.stdout.on("data", (chunk) => {
+ stdout += chunk.toString("utf8");
+ if (stdout.length > 8_192) {
+ outputInvalid = true;
+ reject(new Error("helper output exceeded bound"));
+ }
+ if (stdout.startsWith("READY\n")) accept();
+ });
+ child.stderr.on("data", (chunk) => {
+ stderr += chunk.toString("utf8");
+ if (stderr.length > 8_192) {
+ outputInvalid = true;
+ reject(new Error("helper error exceeded bound"));
+ }
+ });
+ child.once("close", () =>
+ reject(new Error("helper exited before ready")),
+ );
+ child.once("error", reject);
+ });
+ const bounded = async (promise, timeoutMs, label) => {
+ let timer;
+ try {
+ return await Promise.race([
+ promise,
+ new Promise((_, reject) => {
+ timer = setTimeout(
+ () =>
+ reject(
+ new Error(`contained cgroup observer ${label} timed out`),
+ ),
+ timeoutMs,
+ );
+ }),
+ ]);
+ } finally {
+ clearTimeout(timer);
+ }
+ };
+ try {
+ await bounded(ready, 5_000, "helper readiness");
+ if (!Number.isSafeInteger(child.pid) || child.pid < 1 || outputInvalid) {
+ throw new Error("contained cgroup observer helper PID is invalid");
+ }
+ writeFileSync(cgroupFile("cgroup.procs"), `${child.pid}\n`, "utf8");
+ child.stdin.end("GO\n");
+ const result = await bounded(completion, 15_000, "helper completion");
+ if (
+ result.error !== undefined ||
+ outputInvalid ||
+ (control.mode !== "memory" && result.code !== 0) ||
+ (control.mode === "memory" && result.code === 0)
+ ) {
+ throw new Error("contained cgroup observer helper result is invalid");
+ }
+ if (control.mode === "processes") {
+ const lines = stdout.trim().split("\n");
+ let report;
+ try {
+ report = JSON.parse(lines.at(-1));
+ } catch (error) {
+ throw new Error(
+ "contained cgroup observer process report is invalid",
+ {
+ cause: error,
+ },
+ );
+ }
+ if (
+ report?.attemptedProcesses !== control.attemptedProcesses ||
+ report?.denied !== true
+ ) {
+ throw new Error(
+ "contained cgroup observer process report is invalid",
+ );
+ }
+ }
+ } finally {
+ if (child.exitCode === null && child.signalCode === null) {
+ child.kill("SIGTERM");
+ }
+ child.stdin.destroy();
+ try {
+ await bounded(completion, 2_000, "helper reap");
+ } catch {
+ if (child.exitCode === null && child.signalCode === null) {
+ child.kill("SIGKILL");
+ }
+ await bounded(completion, 2_000, "forced helper reap");
+ }
+ }
+ }
+
+ return {
+ now: () => new Date(),
+ async waitForCgroup() {
+ await waitUntil(() => existsSync(cgroupRoot), "cgroup creation", 10_000);
+ if (
+ lstatSync(cgroupRoot).isSymbolicLink() ||
+ !lstatSync(cgroupRoot).isDirectory() ||
+ realpathSync(cgroupRoot) !== cgroupRoot
+ ) {
+ throw new Error("contained cgroup observer cgroup path is invalid");
+ }
+ await waitUntil(
+ () => {
+ try {
+ return (
+ parseContainedCgroupMembers(
+ readFileSync(cgroupFile("cgroup.procs"), "utf8"),
+ ).length >= 2
+ );
+ } catch {
+ return false;
+ }
+ },
+ "candidate membership",
+ 10_000,
+ );
+ },
+ readCgroupFile(name) {
+ const path = cgroupFile(name);
+ const source = readFileSync(path, "utf8");
+ if (source.length === 0 || source.length > 8_192) {
+ throw new Error("contained cgroup observer cgroup file is invalid");
+ }
+ return source;
+ },
+ readProcessStat(pid) {
+ return readFileSync(`/proc/${pid}/stat`, "utf8");
+ },
+ runControl,
+ publishDraft(draft) {
+ writeArtifact(paths.observerDraftPath, draft);
+ },
+ async waitForCgroupAbsent() {
+ await waitUntil(() => !existsSync(cgroupRoot), "cgroup cleanup", 30_000);
+ },
+ };
+}
+
+function parseMainArguments(args) {
+ if (
+ args.length !== 4 ||
+ args[0] !== "--session-id" ||
+ args[2] !== "--manifest" ||
+ !/^rt-[a-z0-9][a-z0-9-]{7,13}$/u.test(args[1] ?? "") ||
+ !isAbsolute(args[3] ?? "")
+ ) {
+ throw new Error("contained cgroup observer arguments are invalid");
+ }
+ return { runtimeSessionId: args[1], manifestPath: args[3] };
+}
+
+async function main() {
+ const args = parseMainArguments(process.argv.slice(2));
+ privateDirectory(resolve(repositoryRoot, ".rt"), "runtime root");
+ const sessionRoot = resolve(repositoryRoot, ".rt", args.runtimeSessionId);
+ const manifestRelative = relative(repositoryRoot, args.manifestPath);
+ const manifestMatch = manifestRelative.match(
+ new RegExp(
+ `^\\.rt/${args.runtimeSessionId}/run/cgroup-qualification/([a-f0-9]{64})/manifest\\.json$`,
+ "u",
+ ),
+ );
+ if (manifestMatch === null || isAbsolute(manifestRelative)) {
+ throw new Error("contained cgroup observer manifest path is not exact");
+ }
+ const paths = containedCgroupQualificationPaths({
+ repositoryRoot,
+ runtimeSessionId: args.runtimeSessionId,
+ invocationId: manifestMatch[1],
+ });
+ if (args.manifestPath !== paths.manifestPath) {
+ throw new Error("contained cgroup observer manifest path is not exact");
+ }
+ const observerBindings = resolveContainedCgroupObserverBindings({
+ sessionRoot,
+ });
+ privateFile(args.manifestPath, "manifest");
+ const manifest = validateContainedCgroupObserverManifest(
+ JSON.parse(readFileSync(args.manifestPath, "utf8")),
+ );
+ try {
+ if (
+ manifest.invocationId !== manifestMatch[1] ||
+ manifest.runtimeSessionId !== args.runtimeSessionId ||
+ Object.entries(observerBindings).some(
+ ([name, value]) => manifest[name] !== value,
+ )
+ ) {
+ throw new Error("contained cgroup observer live binding changed");
+ }
+ privateDirectory(paths.qualificationRoot, "qualification root");
+ privateDirectory(paths.invocationRoot, "invocation root");
+ const baseReceiptPath = resolve(repositoryRoot, manifest.baseReceiptPath);
+ privateFile(baseReceiptPath, "base receipt");
+ const baseReceiptSource = readFileSync(baseReceiptPath);
+ const baseReceipt = JSON.parse(baseReceiptSource.toString("utf8"));
+ const {
+ receiptPayloadSha256: baseReceiptPayloadSha256,
+ ...baseReceiptPayload
+ } = baseReceipt;
+ if (
+ sha256CgroupBytes(baseReceiptSource) !== manifest.baseReceiptFileSha256 ||
+ baseReceiptPayloadSha256 !== manifest.baseReceiptPayloadSha256 ||
+ sha256CgroupBytes(canonicalCgroupJson(baseReceiptPayload)) !==
+ baseReceiptPayloadSha256 ||
+ baseReceipt?.schemaVersion !== "4" ||
+ baseReceipt?.status !== "VALIDATED" ||
+ baseReceipt?.limitMode !==
+ "process-address-space-rlimit-with-unenforced-cgroup-intent" ||
+ baseReceipt?.aggregateLimitIntentEnforced !== false ||
+ baseReceipt?.aggregateLimitEvidence !== null ||
+ baseReceipt?.finalContainerId !== manifest.finalContainerId ||
+ baseReceipt?.invocationId !== manifest.invocationId ||
+ baseReceipt?.sanitizedSpecSha256 !== manifest.sanitizedSpecSha256 ||
+ canonicalCgroupJson(baseReceipt?.intendedAggregateLimits) !==
+ canonicalCgroupJson(manifest.intendedAggregateLimits)
+ ) {
+ throw new Error("contained cgroup observer base receipt changed");
+ }
+ const adapter = actualCgroupAdapter(manifest, paths);
+ writeArtifact(
+ paths.observerReadyPath,
+ createContainedCgroupObserverReady(manifest),
+ );
+ const evidence = await observeContainedCgroup(manifest, adapter);
+ writeArtifact(paths.evidencePath, evidence);
+ } catch (error) {
+ if (!existsSync(paths.failurePath)) {
+ try {
+ writeArtifact(
+ paths.failurePath,
+ createContainedCgroupObserverFailure(manifest),
+ );
+ } catch {
+ // Preserve the original fail-closed observation error.
+ }
+ }
+ throw error;
+ }
+}
+
+if (
+ process.argv[1] !== undefined &&
+ realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))
+) {
+ main().catch((error) => {
+ process.stderr.write(
+ `${error instanceof Error ? error.message : "contained cgroup observer failed"}\n`,
+ );
+ process.exitCode = 70;
+ });
+}
diff --git a/scripts/contained-cgroup-observer.test.ts b/scripts/contained-cgroup-observer.test.ts
new file mode 100644
index 00000000..0486970d
--- /dev/null
+++ b/scripts/contained-cgroup-observer.test.ts
@@ -0,0 +1,267 @@
+import { describe, expect, it } from "vitest";
+
+import {
+ canonicalCgroupJson,
+ sha256CgroupBytes,
+ validateContainedCgroupEvidence,
+} from "./contained-cgroup-evidence.mjs";
+import {
+ createContainedCgroupObserverDraft,
+ createContainedCgroupObserverFailure,
+ createContainedCgroupObserverReady,
+ observeContainedCgroup,
+ parseContainedCgroupKeyValues,
+ parseContainedCgroupMembers,
+ parseContainedProcessStat,
+ selectContainedCgroupMembership,
+ validateContainedCgroupObserverDraft,
+ validateContainedCgroupObserverFailure,
+ validateContainedCgroupObserverReady,
+} from "./contained-cgroup-observer.mjs";
+import { createContainedCgroupObserverManifest } from "./contained-cgroup-observer-protocol.mjs";
+
+const runtimeSessionId = "rt-v61-test1";
+const invocationId = "1".repeat(64);
+const finalContainerId = "2".repeat(64);
+const sanitizedSpecSha256 = "3".repeat(64);
+const now = new Date("2026-07-20T01:02:03.000Z");
+
+function manifest() {
+ return createContainedCgroupObserverManifest({
+ baseReceiptFileSha256: "4".repeat(64),
+ baseReceiptPayloadSha256: "5".repeat(64),
+ finalContainerId,
+ intendedAggregateLimits: {
+ cpuCount: 1,
+ maxProcesses: 16,
+ memoryBytes: 512 * 1024 * 1024,
+ },
+ invocationId,
+ observerBindings: {
+ runtimeSessionId,
+ runtimeAttestationSha256: "6".repeat(64),
+ driverCliSha256: "7".repeat(64),
+ driverModuleSha256: "8".repeat(64),
+ },
+ requestedAt: now,
+ sanitizedSpecSha256,
+ });
+}
+
+function processStat(pid: number, parentPid: number, startTime: number) {
+ const fields = [
+ "S",
+ String(parentPid),
+ ...Array.from({ length: 17 }, () => "0"),
+ String(startTime),
+ ];
+ return `${pid} (counterlab worker) ${fields.join(" ")}\n`;
+}
+
+function adapter(overrides: Record = {}) {
+ const counters = {
+ cpu: { nr_throttled: 0, throttled_usec: 0 },
+ processes: { max: 0 },
+ memory: { oom_kill: 0 },
+ };
+ const controls: Array> = [];
+ const drafts: Array> = [];
+ return {
+ controls,
+ drafts,
+ now: () => now,
+ waitForCgroup: async () => undefined,
+ readCgroupFile(name: string) {
+ const values: Record = {
+ "memory.max": String(512 * 1024 * 1024),
+ "memory.swap.max": "0",
+ "pids.max": "16",
+ "cpu.max": "100000 100000",
+ "cgroup.procs": "100\n101\n",
+ "cpu.stat": `usage_usec 10\nnr_throttled ${counters.cpu.nr_throttled}\nthrottled_usec ${counters.cpu.throttled_usec}\n`,
+ "pids.events": `max ${counters.processes.max}\n`,
+ "memory.events": `low 0\noom_kill ${counters.memory.oom_kill}\n`,
+ };
+ const value = values[name];
+ if (value === undefined)
+ throw new Error(`unexpected cgroup file: ${name}`);
+ return value;
+ },
+ readProcessStat(pid: number) {
+ return pid === 100
+ ? processStat(100, 1, 12345)
+ : processStat(101, 100, 12346);
+ },
+ async runControl(control: Record) {
+ controls.push(control);
+ if (control.mode === "cpu") {
+ counters.cpu.nr_throttled += 1;
+ counters.cpu.throttled_usec += 100;
+ } else if (control.mode === "processes") {
+ counters.processes.max += 1;
+ } else if (control.mode === "memory") {
+ counters.memory.oom_kill += 1;
+ }
+ },
+ async publishDraft(draft: Record) {
+ drafts.push(draft);
+ },
+ waitForCgroupAbsent: async () => undefined,
+ ...overrides,
+ };
+}
+
+describe("contained cgroup observer", () => {
+ it("parses counters, candidate-only ancestry, and process identity", () => {
+ expect(parseContainedCgroupKeyValues("max 0\noom_kill 2\n")).toEqual({
+ max: 0,
+ oom_kill: 2,
+ });
+ expect(parseContainedCgroupMembers("101\n100\n")).toEqual([100, 101]);
+ expect(
+ parseContainedProcessStat(processStat(101, 100, 12346), 101),
+ ).toEqual({ pid: 101, parentPid: 100, startTimeTicks: "12346" });
+ expect(
+ selectContainedCgroupMembership(
+ [100, 101],
+ new Map([
+ [100, { pid: 100, parentPid: 1, startTimeTicks: "12345" }],
+ [101, { pid: 101, parentPid: 100, startTimeTicks: "12346" }],
+ ]),
+ ),
+ ).toMatchObject({ leaderPid: 100, descendantsObserved: true });
+ });
+
+ it("runs fixed controls in order and emits only complete evidence after cleanup", async () => {
+ const input = manifest();
+ const fake = adapter();
+ const evidence = await observeContainedCgroup(input, fake);
+
+ expect(fake.controls).toEqual([
+ { mode: "cpu", busyWindowMs: 500, workers: 4 },
+ { mode: "processes", attemptedProcesses: 17 },
+ { mode: "memory", requestedBytes: 576 * 1024 * 1024 },
+ ]);
+ expect(fake.drafts).toHaveLength(1);
+ expect(() =>
+ validateContainedCgroupObserverDraft(fake.drafts[0], input),
+ ).not.toThrow();
+ expect(() =>
+ validateContainedCgroupEvidence(evidence, {
+ invocationId,
+ finalContainerId,
+ sanitizedSpecSha256,
+ intendedAggregateLimits: input.intendedAggregateLimits,
+ runtimeAttestationSha256: input.runtimeAttestationSha256,
+ runtimeSessionId,
+ driverCliSha256: input.driverCliSha256,
+ driverModuleSha256: input.driverModuleSha256,
+ }),
+ ).not.toThrow();
+ });
+
+ it("rejects missing counter enforcement, ambiguous ancestry, and PID reuse", async () => {
+ const noCpu = adapter({
+ runControl: async (control: Record) => {
+ if (control.mode !== "cpu") return;
+ },
+ });
+ await expect(observeContainedCgroup(manifest(), noCpu)).rejects.toThrow(
+ /evidence binding/u,
+ );
+
+ const ambiguous = adapter({
+ readProcessStat: (pid: number) => processStat(pid, 1, 12000 + pid),
+ });
+ await expect(observeContainedCgroup(manifest(), ambiguous)).rejects.toThrow(
+ /ancestry/u,
+ );
+
+ let reads = 0;
+ const reused = adapter({
+ readProcessStat(pid: number) {
+ reads += 1;
+ return processStat(pid, pid === 100 ? 1 : 100, 12345 + pid + reads);
+ },
+ });
+ await expect(observeContainedCgroup(manifest(), reused)).rejects.toThrow(
+ /reused/u,
+ );
+ });
+
+ it("binds ready and draft receipts and rejects mutation", () => {
+ const input = manifest();
+ const ready = createContainedCgroupObserverReady(input, {
+ armedAt: now,
+ observerPid: 123,
+ });
+ expect(validateContainedCgroupObserverReady(ready, input)).toEqual(ready);
+ expect(() =>
+ validateContainedCgroupObserverReady(
+ { ...ready, observerPid: 124 },
+ input,
+ ),
+ ).toThrow(/ready binding/u);
+
+ const failure = createContainedCgroupObserverFailure(input, {
+ failedAt: now,
+ });
+ expect(validateContainedCgroupObserverFailure(failure, input)).toEqual(
+ failure,
+ );
+ expect(() =>
+ validateContainedCgroupObserverFailure(
+ { ...failure, code: "PRIVATE_ERROR" },
+ input,
+ ),
+ ).toThrow(/failure binding/u);
+
+ const observation = {
+ observedLimits: {
+ memoryMaxBytes: 512 * 1024 * 1024,
+ memorySwapMaxBytes: 0,
+ pidsMax: 16,
+ cpuQuotaMicros: 100_000,
+ cpuPeriodMicros: 100_000,
+ },
+ membership: {
+ leaderPid: 100,
+ leaderStartTimeTicks: "12345",
+ memberPids: [100, 101],
+ memberSetSha256: sha256CgroupBytes(canonicalCgroupJson([100, 101])),
+ descendantsObserved: true,
+ },
+ negativeControls: {
+ memory: {
+ requestedBytes: 576 * 1024 * 1024,
+ oomKillBefore: 0,
+ oomKillAfter: 1,
+ enforced: true,
+ },
+ processes: {
+ attemptedProcesses: 17,
+ maxEventsBefore: 0,
+ maxEventsAfter: 1,
+ enforced: true,
+ },
+ cpu: {
+ busyWindowMs: 500,
+ nrThrottledBefore: 0,
+ nrThrottledAfter: 1,
+ throttledUsecBefore: 0,
+ throttledUsecAfter: 1,
+ enforced: true,
+ },
+ },
+ observedAt: now.toISOString(),
+ };
+ const draft = createContainedCgroupObserverDraft(input, observation);
+ expect(validateContainedCgroupObserverDraft(draft, input)).toEqual(draft);
+ expect(() =>
+ validateContainedCgroupObserverDraft(
+ { ...draft, candidateLeaderPresentAfterControls: false },
+ input,
+ ),
+ ).toThrow(/draft binding/u);
+ });
+});
diff --git a/scripts/contained-runtime-attestation.mjs b/scripts/contained-runtime-attestation.mjs
index 03a0801f..a84184a5 100644
--- a/scripts/contained-runtime-attestation.mjs
+++ b/scripts/contained-runtime-attestation.mjs
@@ -15,6 +15,7 @@ export const RUNTIME_HELPER_PATHS = Object.freeze({
cgroupControlHelper: "scripts/contained-cgroup-control-helper.mjs",
cgroupEvidence: "scripts/contained-cgroup-evidence.mjs",
cgroupObserverBindings: "scripts/contained-cgroup-observer-bindings.mjs",
+ cgroupObserver: "scripts/contained-cgroup-observer.mjs",
cgroupObserverProtocol: "scripts/contained-cgroup-observer-protocol.mjs",
qualifiedRootlessReceiptStore:
"scripts/contained-qualified-receipt-store.mjs",
From 317ba44d9f86491b114662e5c557d5aa9de037bf Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 06:58:06 +0530
Subject: [PATCH 043/143] fix(runtime): bind qualified receipts to live
sessions
---
.../contained-qualified-receipt-store.d.mts | 8 +-
scripts/contained-qualified-receipt-store.mjs | 62 +++++--
.../contained-qualified-receipt-store.test.ts | 161 ++++++++++++------
3 files changed, 170 insertions(+), 61 deletions(-)
diff --git a/scripts/contained-qualified-receipt-store.d.mts b/scripts/contained-qualified-receipt-store.d.mts
index 2613ce0e..591a2738 100644
--- a/scripts/contained-qualified-receipt-store.d.mts
+++ b/scripts/contained-qualified-receipt-store.d.mts
@@ -3,7 +3,11 @@ export interface QualifiedRootlessReceiptStoreBinding {
baseReceiptPath: string;
finalContainerId: string;
sessionRoot: string;
- observerBindings: {
+}
+
+interface QualifiedReceiptStoreDependencies {
+ now?: () => number;
+ resolveObserverBindings?: (input: { sessionRoot: string }) => {
runtimeAttestationSha256: string;
runtimeSessionId: string;
driverCliSha256: string;
@@ -15,6 +19,7 @@ export function persistQualifiedContainedRootlessReceipt(
input: {
aggregateLimitEvidence: unknown;
} & QualifiedRootlessReceiptStoreBinding,
+ dependencies?: QualifiedReceiptStoreDependencies,
): {
qualifiedReceipt: Record;
qualifiedReceiptFileSha256: string;
@@ -27,4 +32,5 @@ export function verifyQualifiedContainedRootlessReceipt(
qualifiedReceiptFileSha256: string;
qualifiedReceiptPath: string;
},
+ dependencies?: QualifiedReceiptStoreDependencies,
): Record;
diff --git a/scripts/contained-qualified-receipt-store.mjs b/scripts/contained-qualified-receipt-store.mjs
index f038772a..9b911998 100644
--- a/scripts/contained-qualified-receipt-store.mjs
+++ b/scripts/contained-qualified-receipt-store.mjs
@@ -3,6 +3,7 @@ import { isAbsolute, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { sha256CgroupBytes } from "./contained-cgroup-evidence.mjs";
+import { resolveContainedCgroupObserverBindings } from "./contained-cgroup-observer-bindings.mjs";
import { createQualifiedContainedRootlessReceipt } from "./contained-qualified-rootless-receipt.mjs";
const repositoryRoot = realpathSync(
@@ -11,6 +12,7 @@ const repositoryRoot = realpathSync(
const containerIdPattern = /^[a-f0-9]{64}$/u;
const sha256Pattern = /^[a-f0-9]{64}$/u;
const maximumReceiptBytes = 1_048_576;
+const maximumObservationAgeMs = 5 * 60_000;
function contained(root, candidate) {
const fromRoot = relative(root, candidate);
@@ -26,7 +28,7 @@ function privateDirectory(path, label) {
!metadata.isDirectory() ||
realpathSync(path) !== path ||
metadata.uid !== process.getuid() ||
- (metadata.mode & 0o077) !== 0
+ (metadata.mode & 0o777) !== 0o700
) {
throw new Error(`qualified rootless receipt ${label} is invalid`);
}
@@ -49,14 +51,17 @@ function privateFile(path, label) {
}
}
-function receiptPaths({ sessionRoot, finalContainerId }) {
+function receiptPaths({ sessionRoot, finalContainerId, runtimeSessionId }) {
if (
!isAbsolute(sessionRoot) ||
!contained(repositoryRoot, sessionRoot) ||
- !containerIdPattern.test(finalContainerId ?? "")
+ !containerIdPattern.test(finalContainerId ?? "") ||
+ !/^rt-[a-z0-9][a-z0-9-]{7,13}$/u.test(runtimeSessionId ?? "") ||
+ sessionRoot !== resolve(repositoryRoot, ".rt", runtimeSessionId)
) {
throw new Error("qualified rootless receipt path input is invalid");
}
+ privateDirectory(resolve(repositoryRoot, ".rt"), "runtime root");
privateDirectory(sessionRoot, "session root");
const runRoot = resolve(sessionRoot, "run");
const specRoot = resolve(runRoot, "rootless-specs");
@@ -82,12 +87,17 @@ function readBaseReceipt({
baseReceiptFileSha256,
baseReceiptPath,
finalContainerId,
+ observerBindings,
sessionRoot,
}) {
if (!sha256Pattern.test(baseReceiptFileSha256 ?? "")) {
throw new Error("qualified rootless base receipt hash is invalid");
}
- const expected = receiptPaths({ sessionRoot, finalContainerId });
+ const expected = receiptPaths({
+ sessionRoot,
+ finalContainerId,
+ runtimeSessionId: observerBindings.runtimeSessionId,
+ });
if (baseReceiptPath !== expected.baseReceiptPath) {
throw new Error("qualified rootless receipt base path changed");
}
@@ -110,12 +120,35 @@ function readBaseReceipt({
return { expected, receipt };
}
-export function persistQualifiedContainedRootlessReceipt(input) {
- const { expected, receipt: baseReceipt } = readBaseReceipt(input);
+export function persistQualifiedContainedRootlessReceipt(
+ input,
+ {
+ now = () => Date.now(),
+ resolveObserverBindings = resolveContainedCgroupObserverBindings,
+ } = {},
+) {
+ const observerBindings = resolveObserverBindings({
+ sessionRoot: input.sessionRoot,
+ });
+ const observedAt = Date.parse(input.aggregateLimitEvidence?.observedAt);
+ const currentTime = now();
+ if (
+ !Number.isFinite(currentTime) ||
+ !Number.isFinite(observedAt) ||
+ new Date(observedAt).toISOString() !==
+ input.aggregateLimitEvidence?.observedAt ||
+ Math.abs(currentTime - observedAt) > maximumObservationAgeMs
+ ) {
+ throw new Error("qualified rootless receipt observation is stale");
+ }
+ const { expected, receipt: baseReceipt } = readBaseReceipt({
+ ...input,
+ observerBindings,
+ });
const qualifiedReceipt = createQualifiedContainedRootlessReceipt({
aggregateLimitEvidence: input.aggregateLimitEvidence,
baseReceipt,
- observerBindings: input.observerBindings,
+ observerBindings,
});
const source = `${JSON.stringify(qualifiedReceipt, null, 2)}\n`;
try {
@@ -141,8 +174,17 @@ export function persistQualifiedContainedRootlessReceipt(input) {
};
}
-export function verifyQualifiedContainedRootlessReceipt(input) {
- const { expected, receipt: baseReceipt } = readBaseReceipt(input);
+export function verifyQualifiedContainedRootlessReceipt(
+ input,
+ { resolveObserverBindings = resolveContainedCgroupObserverBindings } = {},
+) {
+ const observerBindings = resolveObserverBindings({
+ sessionRoot: input.sessionRoot,
+ });
+ const { expected, receipt: baseReceipt } = readBaseReceipt({
+ ...input,
+ observerBindings,
+ });
if (
input.qualifiedReceiptPath !== expected.qualifiedReceiptPath ||
!sha256Pattern.test(input.qualifiedReceiptFileSha256 ?? "")
@@ -165,7 +207,7 @@ export function verifyQualifiedContainedRootlessReceipt(input) {
const expectedReceipt = createQualifiedContainedRootlessReceipt({
aggregateLimitEvidence: qualifiedReceipt.aggregateLimitEvidence,
baseReceipt,
- observerBindings: input.observerBindings,
+ observerBindings,
});
const expectedSource = `${JSON.stringify(expectedReceipt, null, 2)}\n`;
if (!source.equals(Buffer.from(expectedSource))) {
diff --git a/scripts/contained-qualified-receipt-store.test.ts b/scripts/contained-qualified-receipt-store.test.ts
index b0070980..5f7bbbf5 100644
--- a/scripts/contained-qualified-receipt-store.test.ts
+++ b/scripts/contained-qualified-receipt-store.test.ts
@@ -1,10 +1,5 @@
-import {
- mkdirSync,
- mkdtempSync,
- readFileSync,
- symlinkSync,
- writeFileSync,
-} from "node:fs";
+import { randomBytes } from "node:crypto";
+import { mkdirSync, readFileSync, symlinkSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
@@ -73,7 +68,7 @@ function baseReceipt() {
});
}
-function aggregateEvidence() {
+function aggregateEvidence(bindings = observerBindings) {
const memberPids = [100, 101];
return hashed({
schemaVersion: "1",
@@ -90,7 +85,7 @@ function aggregateEvidence() {
invocationId,
finalContainerId,
sanitizedSpecSha256,
- runtimeAttestationSha256: observerBindings.runtimeAttestationSha256,
+ runtimeAttestationSha256: bindings.runtimeAttestationSha256,
observedLimits: {
memoryMaxBytes: intendedAggregateLimits.memoryBytes,
memorySwapMaxBytes: intendedAggregateLimits.memoryBytes,
@@ -129,21 +124,20 @@ function aggregateEvidence() {
},
cleanup: { cgroupAbsentAfterTimeout: true },
observer: {
- runtimeSessionId: observerBindings.runtimeSessionId,
- driverCliSha256: observerBindings.driverCliSha256,
- driverModuleSha256: observerBindings.driverModuleSha256,
+ runtimeSessionId: bindings.runtimeSessionId,
+ driverCliSha256: bindings.driverCliSha256,
+ driverModuleSha256: bindings.driverModuleSha256,
},
observedAt: "2026-07-20T12:00:00.000Z",
});
}
function fixture() {
- const parent = resolve(
- root,
- "node_modules/.cache/counterlab-v6.1/tmp/qualified-receipt-store",
- );
+ const runtimeSessionId = `rt-store-${randomBytes(4).toString("hex")}`;
+ const parent = resolve(root, ".rt");
mkdirSync(parent, { recursive: true, mode: 0o700 });
- const sessionRoot = mkdtempSync(resolve(parent, "rt-store-"));
+ const sessionRoot = resolve(parent, runtimeSessionId);
+ mkdirSync(sessionRoot, { mode: 0o700 });
const runRoot = resolve(sessionRoot, "run");
const specRoot = resolve(runRoot, "rootless-specs");
mkdirSync(specRoot, { recursive: true, mode: 0o700 });
@@ -153,19 +147,31 @@ function fixture() {
return {
baseReceiptFileSha256: sha256CgroupBytes(source),
baseReceiptPath,
+ observerBindings: { ...observerBindings, runtimeSessionId },
sessionRoot,
};
}
+function dependencies(input: ReturnType) {
+ return {
+ now: () => Date.parse("2026-07-20T12:01:00.000Z"),
+ resolveObserverBindings: () => input.observerBindings,
+ };
+}
+
describe("qualified rootless receipt store", () => {
it("creates and verifies one distinct qualified receipt", () => {
const input = fixture();
- const persisted = persistQualifiedContainedRootlessReceipt({
- ...input,
- aggregateLimitEvidence: aggregateEvidence(),
- finalContainerId,
- observerBindings,
- });
+ const persisted = persistQualifiedContainedRootlessReceipt(
+ {
+ baseReceiptFileSha256: input.baseReceiptFileSha256,
+ baseReceiptPath: input.baseReceiptPath,
+ sessionRoot: input.sessionRoot,
+ aggregateLimitEvidence: aggregateEvidence(input.observerBindings),
+ finalContainerId,
+ },
+ dependencies(input),
+ );
expect(persisted.qualifiedReceiptPath).toBe(
resolve(
@@ -177,34 +183,45 @@ describe("qualified rootless receipt store", () => {
`${JSON.stringify(baseReceipt(), null, 2)}\n`,
);
expect(() =>
- verifyQualifiedContainedRootlessReceipt({
- ...input,
- finalContainerId,
- observerBindings,
- qualifiedReceiptFileSha256: persisted.qualifiedReceiptFileSha256,
- qualifiedReceiptPath: persisted.qualifiedReceiptPath,
- }),
+ verifyQualifiedContainedRootlessReceipt(
+ {
+ baseReceiptFileSha256: input.baseReceiptFileSha256,
+ baseReceiptPath: input.baseReceiptPath,
+ sessionRoot: input.sessionRoot,
+ finalContainerId,
+ qualifiedReceiptFileSha256: persisted.qualifiedReceiptFileSha256,
+ qualifiedReceiptPath: persisted.qualifiedReceiptPath,
+ },
+ dependencies(input),
+ ),
).not.toThrow();
expect(() =>
- persistQualifiedContainedRootlessReceipt({
- ...input,
- aggregateLimitEvidence: aggregateEvidence(),
- finalContainerId,
- observerBindings,
- }),
+ persistQualifiedContainedRootlessReceipt(
+ {
+ baseReceiptFileSha256: input.baseReceiptFileSha256,
+ baseReceiptPath: input.baseReceiptPath,
+ sessionRoot: input.sessionRoot,
+ aggregateLimitEvidence: aggregateEvidence(input.observerBindings),
+ finalContainerId,
+ },
+ dependencies(input),
+ ),
).toThrow(/already exists/u);
});
it("rejects path, hash, and symbolic-link drift", () => {
const wrongHash = fixture();
expect(() =>
- persistQualifiedContainedRootlessReceipt({
- ...wrongHash,
- aggregateLimitEvidence: aggregateEvidence(),
- baseReceiptFileSha256: "0".repeat(64),
- finalContainerId,
- observerBindings,
- }),
+ persistQualifiedContainedRootlessReceipt(
+ {
+ baseReceiptPath: wrongHash.baseReceiptPath,
+ sessionRoot: wrongHash.sessionRoot,
+ aggregateLimitEvidence: aggregateEvidence(wrongHash.observerBindings),
+ baseReceiptFileSha256: "0".repeat(64),
+ finalContainerId,
+ },
+ dependencies(wrongHash),
+ ),
).toThrow(/base receipt/u);
const linked = fixture();
@@ -214,13 +231,57 @@ describe("qualified rootless receipt store", () => {
);
symlinkSync(linked.baseReceiptPath, linkedPath);
expect(() =>
- persistQualifiedContainedRootlessReceipt({
- ...linked,
- aggregateLimitEvidence: aggregateEvidence(),
- baseReceiptPath: linkedPath,
- finalContainerId,
- observerBindings,
- }),
+ persistQualifiedContainedRootlessReceipt(
+ {
+ baseReceiptFileSha256: linked.baseReceiptFileSha256,
+ sessionRoot: linked.sessionRoot,
+ aggregateLimitEvidence: aggregateEvidence(linked.observerBindings),
+ baseReceiptPath: linkedPath,
+ finalContainerId,
+ },
+ dependencies(linked),
+ ),
).toThrow(/path/u);
});
+
+ it("rejects stale evidence and a resolver bound to another session", () => {
+ const stale = fixture();
+ expect(() =>
+ persistQualifiedContainedRootlessReceipt(
+ {
+ baseReceiptFileSha256: stale.baseReceiptFileSha256,
+ baseReceiptPath: stale.baseReceiptPath,
+ sessionRoot: stale.sessionRoot,
+ aggregateLimitEvidence: aggregateEvidence(stale.observerBindings),
+ finalContainerId,
+ },
+ {
+ ...dependencies(stale),
+ now: () => Date.parse("2026-07-20T12:06:00.001Z"),
+ },
+ ),
+ ).toThrow(/stale/u);
+
+ const mismatched = fixture();
+ expect(() =>
+ persistQualifiedContainedRootlessReceipt(
+ {
+ baseReceiptFileSha256: mismatched.baseReceiptFileSha256,
+ baseReceiptPath: mismatched.baseReceiptPath,
+ sessionRoot: mismatched.sessionRoot,
+ aggregateLimitEvidence: aggregateEvidence(
+ mismatched.observerBindings,
+ ),
+ finalContainerId,
+ },
+ {
+ ...dependencies(mismatched),
+ resolveObserverBindings: () => ({
+ ...mismatched.observerBindings,
+ runtimeSessionId: "rt-v61-other1",
+ }),
+ },
+ ),
+ ).toThrow(/path input/u);
+ });
});
From c2c1817326b76a5e3791224c2a2355ccca17190f Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 07:02:17 +0530
Subject: [PATCH 044/143] feat(runtime): define cgroup finalization signal
---
.../contained-cgroup-observer-protocol.d.mts | 32 ++++++
.../contained-cgroup-observer-protocol.mjs | 103 ++++++++++++++++++
...contained-cgroup-observer-protocol.test.ts | 63 +++++++++++
3 files changed, 198 insertions(+)
diff --git a/scripts/contained-cgroup-observer-protocol.d.mts b/scripts/contained-cgroup-observer-protocol.d.mts
index 4c76be22..a41f3f54 100644
--- a/scripts/contained-cgroup-observer-protocol.d.mts
+++ b/scripts/contained-cgroup-observer-protocol.d.mts
@@ -45,6 +45,37 @@ export function createContainedCgroupObserverManifest(input: {
sanitizedSpecSha256: string;
}): ContainedCgroupObserverManifest;
+export interface ContainedCgroupObserverFinalization {
+ schemaVersion: "1";
+ status: "FINALIZE" | "ABORT";
+ reason: "AGGREGATE_TIMEOUT_CONFIRMED" | "QUALIFICATION_ABORTED";
+ manifestPayloadSha256: string;
+ invocationId: string;
+ finalContainerId: string;
+ timeoutObserved: boolean;
+ resultReleased: boolean;
+ cleanupVerified: boolean;
+ decisionAt: string;
+ receiptPayloadSha256: string;
+}
+
+export function validateContainedCgroupObserverFinalization(
+ value: unknown,
+ manifest: ContainedCgroupObserverManifest,
+ options?: { observedAtMs?: number },
+): ContainedCgroupObserverFinalization;
+
+export function createContainedCgroupObserverFinalization(
+ manifest: ContainedCgroupObserverManifest,
+ input: {
+ cleanupVerified: boolean;
+ decisionAt?: Date;
+ resultReleased: boolean;
+ status: "FINALIZE" | "ABORT";
+ timeoutObserved: boolean;
+ },
+): ContainedCgroupObserverFinalization;
+
export function containedCgroupQualificationPaths(input: {
repositoryRoot: string;
runtimeSessionId: string;
@@ -52,6 +83,7 @@ export function containedCgroupQualificationPaths(input: {
}): Readonly<{
evidencePath: string;
failurePath: string;
+ finalizationPath: string;
invocationRoot: string;
manifestPath: string;
observerDraftPath: string;
diff --git a/scripts/contained-cgroup-observer-protocol.mjs b/scripts/contained-cgroup-observer-protocol.mjs
index 0988cbfa..8859a1e7 100644
--- a/scripts/contained-cgroup-observer-protocol.mjs
+++ b/scripts/contained-cgroup-observer-protocol.mjs
@@ -10,6 +10,10 @@ import {
const sha256Pattern = /^[a-f0-9]{64}$/u;
const sessionPattern = /^rt-[a-z0-9][a-z0-9-]{7,13}$/u;
const maximumAgeMs = 5 * 60_000;
+const finalizationReasonByStatus = Object.freeze({
+ ABORT: "QUALIFICATION_ABORTED",
+ FINALIZE: "AGGREGATE_TIMEOUT_CONFIRMED",
+});
const manifestKeys = [
"baseReceiptFileSha256",
@@ -167,6 +171,104 @@ export function createContainedCgroupObserverManifest({
);
}
+export function validateContainedCgroupObserverFinalization(
+ value,
+ manifest,
+ { observedAtMs = Date.now() } = {},
+) {
+ const finalization = object(value, "finalization");
+ exactKeys(
+ finalization,
+ [
+ "cleanupVerified",
+ "decisionAt",
+ "finalContainerId",
+ "invocationId",
+ "manifestPayloadSha256",
+ "reason",
+ "receiptPayloadSha256",
+ "resultReleased",
+ "schemaVersion",
+ "status",
+ "timeoutObserved",
+ ],
+ "finalization",
+ );
+ const { receiptPayloadSha256, ...payload } = finalization;
+ const requestedAtMs = Date.parse(manifest?.requestedAt);
+ const decisionAtMs = Date.parse(finalization.decisionAt);
+ const finalizesQualification =
+ finalization.status === "FINALIZE" &&
+ finalization.timeoutObserved === true &&
+ finalization.resultReleased === false &&
+ finalization.cleanupVerified === true;
+ const abortsQualification =
+ finalization.status === "ABORT" &&
+ !(
+ finalization.timeoutObserved === true &&
+ finalization.resultReleased === false &&
+ finalization.cleanupVerified === true
+ );
+ if (
+ finalization.schemaVersion !== "1" ||
+ (!finalizesQualification && !abortsQualification) ||
+ finalization.reason !== finalizationReasonByStatus[finalization.status] ||
+ finalization.manifestPayloadSha256 !== manifest?.receiptPayloadSha256 ||
+ finalization.invocationId !== manifest?.invocationId ||
+ finalization.finalContainerId !== manifest?.finalContainerId ||
+ typeof finalization.timeoutObserved !== "boolean" ||
+ typeof finalization.resultReleased !== "boolean" ||
+ typeof finalization.cleanupVerified !== "boolean" ||
+ !validTimestamp(finalization.decisionAt, observedAtMs) ||
+ !Number.isFinite(requestedAtMs) ||
+ !Number.isFinite(decisionAtMs) ||
+ decisionAtMs < requestedAtMs ||
+ decisionAtMs - requestedAtMs > maximumAgeMs ||
+ !sha256Pattern.test(receiptPayloadSha256 ?? "") ||
+ sha256CgroupBytes(canonicalCgroupJson(payload)) !== receiptPayloadSha256
+ ) {
+ throw new Error(
+ "contained cgroup observer finalization binding is invalid",
+ );
+ }
+ return finalization;
+}
+
+export function createContainedCgroupObserverFinalization(
+ manifest,
+ {
+ cleanupVerified,
+ decisionAt = new Date(),
+ resultReleased,
+ status,
+ timeoutObserved,
+ },
+) {
+ if (!(decisionAt instanceof Date) || !Number.isFinite(decisionAt.getTime())) {
+ throw new Error("contained cgroup observer decision time is invalid");
+ }
+ const payload = {
+ schemaVersion: "1",
+ status,
+ reason: finalizationReasonByStatus[status],
+ manifestPayloadSha256: manifest?.receiptPayloadSha256,
+ invocationId: manifest?.invocationId,
+ finalContainerId: manifest?.finalContainerId,
+ timeoutObserved,
+ resultReleased,
+ cleanupVerified,
+ decisionAt: decisionAt.toISOString(),
+ };
+ return validateContainedCgroupObserverFinalization(
+ {
+ ...payload,
+ receiptPayloadSha256: sha256CgroupBytes(canonicalCgroupJson(payload)),
+ },
+ manifest,
+ { observedAtMs: decisionAt.getTime() },
+ );
+}
+
export function containedCgroupQualificationPaths({
repositoryRoot,
runtimeSessionId,
@@ -191,6 +293,7 @@ export function containedCgroupQualificationPaths({
return Object.freeze({
evidencePath: resolve(invocationRoot, "aggregate-evidence.json"),
failurePath: resolve(invocationRoot, "failure.json"),
+ finalizationPath: resolve(invocationRoot, "finalization.json"),
invocationRoot,
manifestPath: resolve(invocationRoot, "manifest.json"),
observerDraftPath: resolve(invocationRoot, "observer-draft.json"),
diff --git a/scripts/contained-cgroup-observer-protocol.test.ts b/scripts/contained-cgroup-observer-protocol.test.ts
index 718cc4af..cb7e04a9 100644
--- a/scripts/contained-cgroup-observer-protocol.test.ts
+++ b/scripts/contained-cgroup-observer-protocol.test.ts
@@ -4,7 +4,9 @@ import { describe, expect, it } from "vitest";
import {
containedCgroupQualificationPaths,
+ createContainedCgroupObserverFinalization,
createContainedCgroupObserverManifest,
+ validateContainedCgroupObserverFinalization,
validateContainedCgroupObserverManifest,
} from "./contained-cgroup-observer-protocol.mjs";
@@ -93,6 +95,59 @@ describe("contained cgroup observer protocol", () => {
).toThrow(/binding/u);
});
+ it("accepts only self-bound runtime finalization or an explicit abort", () => {
+ const input = manifest();
+ const finalizedAt = new Date(requestedAt.getTime() + 30_000);
+ const finalized = createContainedCgroupObserverFinalization(input, {
+ cleanupVerified: true,
+ decisionAt: finalizedAt,
+ resultReleased: false,
+ status: "FINALIZE",
+ timeoutObserved: true,
+ });
+ expect(
+ validateContainedCgroupObserverFinalization(finalized, input, {
+ observedAtMs: finalizedAt.getTime(),
+ }),
+ ).toEqual(finalized);
+
+ const aborted = createContainedCgroupObserverFinalization(input, {
+ cleanupVerified: false,
+ decisionAt: finalizedAt,
+ resultReleased: false,
+ status: "ABORT",
+ timeoutObserved: false,
+ });
+ expect(
+ validateContainedCgroupObserverFinalization(aborted, input, {
+ observedAtMs: finalizedAt.getTime(),
+ }),
+ ).toEqual(aborted);
+
+ const mutations = [
+ { ...finalized, resultReleased: true },
+ { ...finalized, cleanupVerified: false },
+ { ...finalized, timeoutObserved: false },
+ { ...finalized, receiptPayloadSha256: "0".repeat(64) },
+ { ...aborted, status: "FINALIZE" },
+ { ...aborted, timeoutObserved: true, cleanupVerified: true },
+ ];
+ for (const changed of mutations) {
+ expect(() =>
+ validateContainedCgroupObserverFinalization(changed, input, {
+ observedAtMs: finalizedAt.getTime(),
+ }),
+ ).toThrow(/finalization binding/u);
+ }
+ expect(() =>
+ validateContainedCgroupObserverFinalization(
+ finalized,
+ { ...input, invocationId: "9".repeat(64) },
+ { observedAtMs: finalizedAt.getTime() },
+ ),
+ ).toThrow(/finalization binding/u);
+ });
+
it("derives only exact repository-contained handshake paths", () => {
expect(
containedCgroupQualificationPaths({
@@ -139,6 +194,14 @@ describe("contained cgroup observer protocol", () => {
invocationId,
"observer-draft.json",
),
+ finalizationPath: resolve(
+ root,
+ ".rt",
+ runtimeSessionId,
+ "run/cgroup-qualification",
+ invocationId,
+ "finalization.json",
+ ),
evidencePath: resolve(
root,
".rt",
From cf906475cb1c13a33971a59ba4678fe2cf27f5f3 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 07:19:57 +0530
Subject: [PATCH 045/143] fix(runtime): fail closed on cgroup qualification
---
scripts/contained-cgroup-control-helper.mjs | 189 ++++++++---
scripts/contained-cgroup-evidence.mjs | 2 +-
scripts/contained-cgroup-evidence.test.ts | 12 +
.../contained-cgroup-observer-protocol.d.mts | 24 ++
.../contained-cgroup-observer-protocol.mjs | 50 ++-
...contained-cgroup-observer-protocol.test.ts | 38 ++-
scripts/contained-cgroup-observer.d.mts | 7 +-
scripts/contained-cgroup-observer.mjs | 305 +++++++++++++++---
scripts/contained-cgroup-observer.test.ts | 119 ++++++-
.../src/counterlab_runner/timeout_proof.py | 3 +-
services/runner/tests/test_timeout_proof.py | 10 +-
11 files changed, 669 insertions(+), 90 deletions(-)
diff --git a/scripts/contained-cgroup-control-helper.mjs b/scripts/contained-cgroup-control-helper.mjs
index e9092499..7a0b0c7f 100644
--- a/scripts/contained-cgroup-control-helper.mjs
+++ b/scripts/contained-cgroup-control-helper.mjs
@@ -96,6 +96,71 @@ function childCompletion(child) {
});
}
+function childRunning(child) {
+ return child.exitCode === null && child.signalCode === null;
+}
+
+async function bounded(promise, timeoutMs, label) {
+ let timer;
+ try {
+ return await Promise.race([
+ promise,
+ new Promise((_, reject) => {
+ timer = setTimeout(
+ () => reject(new Error(`contained cgroup ${label} timed out`)),
+ timeoutMs,
+ );
+ }),
+ ]);
+ } finally {
+ clearTimeout(timer);
+ }
+}
+
+async function reapChildren(children, completions) {
+ for (const child of children) {
+ if (childRunning(child)) child.kill("SIGTERM");
+ }
+ try {
+ await bounded(Promise.all(completions), 2_000, "child reap");
+ } catch {
+ for (const child of children) {
+ if (childRunning(child)) child.kill("SIGKILL");
+ }
+ await bounded(Promise.all(completions), 2_000, "forced child reap");
+ }
+}
+
+function terminationController(activeChildren) {
+ let requestedSignal;
+ const handlers = new Map(
+ ["SIGINT", "SIGTERM"].map((signal) => {
+ const handler = () => {
+ requestedSignal ??= signal;
+ for (const child of activeChildren) {
+ if (childRunning(child)) child.kill("SIGTERM");
+ }
+ };
+ process.on(signal, handler);
+ return [signal, handler];
+ }),
+ );
+ return {
+ assertActive() {
+ if (requestedSignal !== undefined) {
+ throw new Error(
+ `contained cgroup control interrupted by ${requestedSignal}`,
+ );
+ }
+ },
+ dispose() {
+ for (const [signal, handler] of handlers) {
+ process.off(signal, handler);
+ }
+ },
+ };
+}
+
async function memoryControl(requestedBytes) {
writeFileSync("/proc/self/oom_score_adj", "1000\n", "utf8");
const retained = [];
@@ -117,36 +182,48 @@ async function memoryControl(requestedBytes) {
);
}
-async function processControl(attemptedProcesses, scriptPath) {
+async function processControl(
+ attemptedProcesses,
+ scriptPath,
+ activeChildren,
+ controller,
+) {
const children = [];
const completions = [];
let denied = false;
- for (let index = 0; index < attemptedProcesses; index += 1) {
- const child = spawn(
- process.execPath,
- [scriptPath, "--internal-hold", "5000"],
- { stdio: "ignore" },
- );
- children.push(child);
- const completion = childCompletion(child);
- completions.push(completion);
- const state = await Promise.race([
- completion,
- new Promise((accept) =>
- child.once("spawn", () => accept({ started: true })),
- ),
- ]);
- if (!state.started) {
- denied = true;
- break;
+ try {
+ for (let index = 0; index < attemptedProcesses; index += 1) {
+ controller.assertActive();
+ const child = spawn(
+ process.execPath,
+ [scriptPath, "--internal-hold", "5000"],
+ {
+ env: { LANG: "C", LC_ALL: "C", TZ: "UTC" },
+ stdio: "ignore",
+ },
+ );
+ children.push(child);
+ activeChildren.add(child);
+ const completion = childCompletion(child);
+ completions.push(completion);
+ const state = await Promise.race([
+ completion,
+ new Promise((accept) =>
+ child.once("spawn", () => accept({ started: true })),
+ ),
+ ]);
+ if (!state.started) {
+ denied = true;
+ break;
+ }
}
- }
- for (const child of children) {
- if (child.exitCode === null && child.signalCode === null) {
- child.kill("SIGTERM");
+ } finally {
+ await reapChildren(children, completions);
+ for (const child of children) {
+ activeChildren.delete(child);
}
}
- await Promise.all(completions);
+ controller.assertActive();
if (!denied) {
throw new Error("contained cgroup process control escaped its limit");
}
@@ -155,15 +232,37 @@ async function processControl(attemptedProcesses, scriptPath) {
);
}
-async function cpuControl(busyWindowMs, workers, scriptPath) {
+async function cpuControl(
+ busyWindowMs,
+ workers,
+ scriptPath,
+ activeChildren,
+ controller,
+) {
const children = Array.from({ length: workers }, () =>
spawn(
process.execPath,
[scriptPath, "--internal-busy", String(busyWindowMs)],
- { stdio: "ignore" },
+ {
+ env: { LANG: "C", LC_ALL: "C", TZ: "UTC" },
+ stdio: "ignore",
+ },
),
);
- const results = await Promise.all(children.map(childCompletion));
+ children.forEach((child) => activeChildren.add(child));
+ const completions = children.map(childCompletion);
+ let results;
+ try {
+ results = await bounded(
+ Promise.all(completions),
+ busyWindowMs + 2_000,
+ "CPU worker completion",
+ );
+ } finally {
+ await reapChildren(children, completions);
+ children.forEach((child) => activeChildren.delete(child));
+ }
+ controller.assertActive();
if (results.some((result) => !result.started || result.code !== 0)) {
throw new Error("contained cgroup CPU control worker failed");
}
@@ -192,16 +291,34 @@ async function main() {
return;
}
const control = parseContainedCgroupControl(process.argv.slice(2));
- await waitForGo();
- if (control.mode === "memory") {
- await memoryControl(control.requestedBytes);
- return;
- }
- if (control.mode === "processes") {
- await processControl(control.attemptedProcesses, scriptPath);
- return;
+ const activeChildren = new Set();
+ const controller = terminationController(activeChildren);
+ try {
+ await waitForGo();
+ controller.assertActive();
+ if (control.mode === "memory") {
+ await memoryControl(control.requestedBytes);
+ return;
+ }
+ if (control.mode === "processes") {
+ await processControl(
+ control.attemptedProcesses,
+ scriptPath,
+ activeChildren,
+ controller,
+ );
+ return;
+ }
+ await cpuControl(
+ control.busyWindowMs,
+ control.workers,
+ scriptPath,
+ activeChildren,
+ controller,
+ );
+ } finally {
+ controller.dispose();
}
- await cpuControl(control.busyWindowMs, control.workers, scriptPath);
}
if (
diff --git a/scripts/contained-cgroup-evidence.mjs b/scripts/contained-cgroup-evidence.mjs
index 6849e8d3..24a3cdc7 100644
--- a/scripts/contained-cgroup-evidence.mjs
+++ b/scripts/contained-cgroup-evidence.mjs
@@ -240,7 +240,7 @@ export function validateContainedCgroupEvidence(value, expectedValue) {
memoryControl.requestedBytes <= intended.memoryBytes ||
!safeInteger(memoryControl.oomKillBefore) ||
!safeInteger(memoryControl.oomKillAfter, { positive: true }) ||
- memoryControl.oomKillAfter <= memoryControl.oomKillBefore ||
+ memoryControl.oomKillAfter !== memoryControl.oomKillBefore + 1 ||
memoryControl.enforced !== true ||
!safeInteger(processControl.attemptedProcesses, { positive: true }) ||
processControl.attemptedProcesses <= intended.maxProcesses ||
diff --git a/scripts/contained-cgroup-evidence.test.ts b/scripts/contained-cgroup-evidence.test.ts
index cfc12ee4..78cbf3f5 100644
--- a/scripts/contained-cgroup-evidence.test.ts
+++ b/scripts/contained-cgroup-evidence.test.ts
@@ -162,4 +162,16 @@ describe("contained cgroup evidence", () => {
}),
).toThrow(/binding/u);
});
+
+ it("rejects a self-consistent receipt with an ambiguous OOM delta", () => {
+ const changed = evidence();
+ changed.negativeControls.memory.oomKillAfter = 2;
+ const { receiptPayloadSha256: _ignored, ...payload } = changed;
+ changed.receiptPayloadSha256 = sha256CgroupBytes(
+ canonicalCgroupJson(payload),
+ );
+ expect(() => validateContainedCgroupEvidence(changed, expected())).toThrow(
+ /binding/u,
+ );
+ });
});
diff --git a/scripts/contained-cgroup-observer-protocol.d.mts b/scripts/contained-cgroup-observer-protocol.d.mts
index a41f3f54..af516812 100644
--- a/scripts/contained-cgroup-observer-protocol.d.mts
+++ b/scripts/contained-cgroup-observer-protocol.d.mts
@@ -49,7 +49,25 @@ export interface ContainedCgroupObserverFinalization {
schemaVersion: "1";
status: "FINALIZE" | "ABORT";
reason: "AGGREGATE_TIMEOUT_CONFIRMED" | "QUALIFICATION_ABORTED";
+ abortCode:
+ | "CLEANUP_UNVERIFIED"
+ | "OBSERVER_FAILED"
+ | "RESULT_RELEASED"
+ | "RUNTIME_FAILED"
+ | "TIMEOUT_NOT_OBSERVED"
+ | null;
+ cleanup: {
+ taskAbsent: boolean;
+ containerAbsent: boolean;
+ snapshotAbsent: boolean;
+ invocationAliasAbsent: boolean;
+ imageRootfsAbsent: boolean;
+ persistedAuthorityVerified: boolean;
+ readOnlyMountsUnchanged: boolean;
+ imageRootfsUnchanged: boolean;
+ };
manifestPayloadSha256: string;
+ observerDraftPayloadSha256: string;
invocationId: string;
finalContainerId: string;
timeoutObserved: boolean;
@@ -68,8 +86,14 @@ export function validateContainedCgroupObserverFinalization(
export function createContainedCgroupObserverFinalization(
manifest: ContainedCgroupObserverManifest,
input: {
+ abortCode?: Exclude<
+ ContainedCgroupObserverFinalization["abortCode"],
+ null
+ > | null;
+ cleanup: ContainedCgroupObserverFinalization["cleanup"];
cleanupVerified: boolean;
decisionAt?: Date;
+ observerDraftPayloadSha256: string;
resultReleased: boolean;
status: "FINALIZE" | "ABORT";
timeoutObserved: boolean;
diff --git a/scripts/contained-cgroup-observer-protocol.mjs b/scripts/contained-cgroup-observer-protocol.mjs
index 8859a1e7..489ecce0 100644
--- a/scripts/contained-cgroup-observer-protocol.mjs
+++ b/scripts/contained-cgroup-observer-protocol.mjs
@@ -14,6 +14,23 @@ const finalizationReasonByStatus = Object.freeze({
ABORT: "QUALIFICATION_ABORTED",
FINALIZE: "AGGREGATE_TIMEOUT_CONFIRMED",
});
+const allowedAbortCodes = new Set([
+ "CLEANUP_UNVERIFIED",
+ "OBSERVER_FAILED",
+ "RESULT_RELEASED",
+ "RUNTIME_FAILED",
+ "TIMEOUT_NOT_OBSERVED",
+]);
+const cleanupKeys = [
+ "containerAbsent",
+ "imageRootfsAbsent",
+ "imageRootfsUnchanged",
+ "invocationAliasAbsent",
+ "persistedAuthorityVerified",
+ "readOnlyMountsUnchanged",
+ "snapshotAbsent",
+ "taskAbsent",
+];
const manifestKeys = [
"baseReceiptFileSha256",
@@ -180,11 +197,14 @@ export function validateContainedCgroupObserverFinalization(
exactKeys(
finalization,
[
+ "abortCode",
+ "cleanup",
"cleanupVerified",
"decisionAt",
"finalContainerId",
"invocationId",
"manifestPayloadSha256",
+ "observerDraftPayloadSha256",
"reason",
"receiptPayloadSha256",
"resultReleased",
@@ -197,28 +217,36 @@ export function validateContainedCgroupObserverFinalization(
const { receiptPayloadSha256, ...payload } = finalization;
const requestedAtMs = Date.parse(manifest?.requestedAt);
const decisionAtMs = Date.parse(finalization.decisionAt);
+ const cleanup = object(finalization.cleanup, "finalization cleanup");
+ exactKeys(cleanup, cleanupKeys, "finalization cleanup");
+ const cleanupValuesAreBoolean = cleanupKeys.every(
+ (key) => typeof cleanup[key] === "boolean",
+ );
+ const everyCleanupCheckPassed = cleanupKeys.every(
+ (key) => cleanup[key] === true,
+ );
const finalizesQualification =
finalization.status === "FINALIZE" &&
finalization.timeoutObserved === true &&
finalization.resultReleased === false &&
- finalization.cleanupVerified === true;
- const abortsQualification =
- finalization.status === "ABORT" &&
- !(
- finalization.timeoutObserved === true &&
- finalization.resultReleased === false &&
- finalization.cleanupVerified === true
- );
+ finalization.cleanupVerified === true &&
+ everyCleanupCheckPassed;
+ const abortsQualification = finalization.status === "ABORT";
if (
finalization.schemaVersion !== "1" ||
(!finalizesQualification && !abortsQualification) ||
finalization.reason !== finalizationReasonByStatus[finalization.status] ||
+ (finalizesQualification
+ ? finalization.abortCode !== null
+ : !allowedAbortCodes.has(finalization.abortCode)) ||
finalization.manifestPayloadSha256 !== manifest?.receiptPayloadSha256 ||
+ !sha256Pattern.test(finalization.observerDraftPayloadSha256 ?? "") ||
finalization.invocationId !== manifest?.invocationId ||
finalization.finalContainerId !== manifest?.finalContainerId ||
typeof finalization.timeoutObserved !== "boolean" ||
typeof finalization.resultReleased !== "boolean" ||
typeof finalization.cleanupVerified !== "boolean" ||
+ !cleanupValuesAreBoolean ||
!validTimestamp(finalization.decisionAt, observedAtMs) ||
!Number.isFinite(requestedAtMs) ||
!Number.isFinite(decisionAtMs) ||
@@ -237,8 +265,11 @@ export function validateContainedCgroupObserverFinalization(
export function createContainedCgroupObserverFinalization(
manifest,
{
+ abortCode = null,
+ cleanup,
cleanupVerified,
decisionAt = new Date(),
+ observerDraftPayloadSha256,
resultReleased,
status,
timeoutObserved,
@@ -251,7 +282,10 @@ export function createContainedCgroupObserverFinalization(
schemaVersion: "1",
status,
reason: finalizationReasonByStatus[status],
+ abortCode,
+ cleanup,
manifestPayloadSha256: manifest?.receiptPayloadSha256,
+ observerDraftPayloadSha256,
invocationId: manifest?.invocationId,
finalContainerId: manifest?.finalContainerId,
timeoutObserved,
diff --git a/scripts/contained-cgroup-observer-protocol.test.ts b/scripts/contained-cgroup-observer-protocol.test.ts
index cb7e04a9..abd82fdf 100644
--- a/scripts/contained-cgroup-observer-protocol.test.ts
+++ b/scripts/contained-cgroup-observer-protocol.test.ts
@@ -16,6 +16,16 @@ const invocationId = "1".repeat(64);
const finalContainerId = "2".repeat(64);
const sanitizedSpecSha256 = "3".repeat(64);
const requestedAt = new Date("2026-07-20T01:02:03.000Z");
+const verifiedCleanup = {
+ taskAbsent: true,
+ containerAbsent: true,
+ snapshotAbsent: true,
+ invocationAliasAbsent: true,
+ imageRootfsAbsent: true,
+ persistedAuthorityVerified: true,
+ readOnlyMountsUnchanged: true,
+ imageRootfsUnchanged: true,
+};
function manifest() {
return createContainedCgroupObserverManifest({
@@ -99,8 +109,10 @@ describe("contained cgroup observer protocol", () => {
const input = manifest();
const finalizedAt = new Date(requestedAt.getTime() + 30_000);
const finalized = createContainedCgroupObserverFinalization(input, {
+ cleanup: verifiedCleanup,
cleanupVerified: true,
decisionAt: finalizedAt,
+ observerDraftPayloadSha256: "a".repeat(64),
resultReleased: false,
status: "FINALIZE",
timeoutObserved: true,
@@ -112,8 +124,11 @@ describe("contained cgroup observer protocol", () => {
).toEqual(finalized);
const aborted = createContainedCgroupObserverFinalization(input, {
+ abortCode: "TIMEOUT_NOT_OBSERVED",
+ cleanup: { ...verifiedCleanup, taskAbsent: false },
cleanupVerified: false,
decisionAt: finalizedAt,
+ observerDraftPayloadSha256: "a".repeat(64),
resultReleased: false,
status: "ABORT",
timeoutObserved: false,
@@ -124,13 +139,34 @@ describe("contained cgroup observer protocol", () => {
}),
).toEqual(aborted);
+ const failClosedAbort = createContainedCgroupObserverFinalization(input, {
+ abortCode: "RUNTIME_FAILED",
+ cleanup: verifiedCleanup,
+ cleanupVerified: true,
+ decisionAt: finalizedAt,
+ observerDraftPayloadSha256: "a".repeat(64),
+ resultReleased: false,
+ status: "ABORT",
+ timeoutObserved: true,
+ });
+ expect(
+ validateContainedCgroupObserverFinalization(failClosedAbort, input, {
+ observedAtMs: finalizedAt.getTime(),
+ }),
+ ).toEqual(failClosedAbort);
+
const mutations = [
{ ...finalized, resultReleased: true },
{ ...finalized, cleanupVerified: false },
{ ...finalized, timeoutObserved: false },
+ {
+ ...finalized,
+ cleanup: { ...finalized.cleanup, snapshotAbsent: false },
+ },
{ ...finalized, receiptPayloadSha256: "0".repeat(64) },
+ { ...finalized, observerDraftPayloadSha256: "0" },
{ ...aborted, status: "FINALIZE" },
- { ...aborted, timeoutObserved: true, cleanupVerified: true },
+ { ...aborted, abortCode: "UNKNOWN" },
];
for (const changed of mutations) {
expect(() =>
diff --git a/scripts/contained-cgroup-observer.d.mts b/scripts/contained-cgroup-observer.d.mts
index 0577ca25..d561b7c4 100644
--- a/scripts/contained-cgroup-observer.d.mts
+++ b/scripts/contained-cgroup-observer.d.mts
@@ -1,4 +1,5 @@
import type { ContainedCgroupObserverManifest } from "./contained-cgroup-observer-protocol.mjs";
+import type { ContainedCgroupObserverFinalization } from "./contained-cgroup-observer-protocol.mjs";
export function createContainedCgroupObserverReady(
manifest: ContainedCgroupObserverManifest,
@@ -53,8 +54,12 @@ export function observeContainedCgroup(
waitForCgroup(): Promise;
readCgroupFile(name: string): string | Promise;
readProcessStat(pid: number): string | Promise;
- runControl(control: Record): Promise;
+ runControl(
+ control: Record,
+ baselineMemberPids: number[],
+ ): Promise;
publishDraft(draft: Record): Promise;
+ waitForFinalization(): Promise;
waitForCgroupAbsent(): Promise;
},
): Promise>;
diff --git a/scripts/contained-cgroup-observer.mjs b/scripts/contained-cgroup-observer.mjs
index ccbd3ab0..5805b05c 100644
--- a/scripts/contained-cgroup-observer.mjs
+++ b/scripts/contained-cgroup-observer.mjs
@@ -2,7 +2,6 @@
import { spawn } from "node:child_process";
import {
- existsSync,
lstatSync,
readFileSync,
realpathSync,
@@ -20,6 +19,7 @@ import {
import { resolveContainedCgroupObserverBindings } from "./contained-cgroup-observer-bindings.mjs";
import {
containedCgroupQualificationPaths,
+ validateContainedCgroupObserverFinalization,
validateContainedCgroupObserverManifest,
} from "./contained-cgroup-observer-protocol.mjs";
@@ -444,6 +444,20 @@ function cpuLimit(source) {
};
}
+async function observedAggregateLimits(adapter) {
+ return {
+ memoryMaxBytes: scalar(
+ await adapter.readCgroupFile("memory.max"),
+ "memory limit",
+ ),
+ memorySwapMaxBytes: swapScalar(
+ await adapter.readCgroupFile("memory.swap.max"),
+ ),
+ pidsMax: scalar(await adapter.readCgroupFile("pids.max"), "PID limit"),
+ ...cpuLimit(await adapter.readCgroupFile("cpu.max")),
+ };
+}
+
async function stableMembership(adapter) {
const memberPids = parseContainedCgroupMembers(
await adapter.readCgroupFile("cgroup.procs"),
@@ -455,18 +469,28 @@ async function stableMembership(adapter) {
parseContainedProcessStat(await adapter.readProcessStat(pid), pid),
);
}
+ const confirmedMemberPids = parseContainedCgroupMembers(
+ await adapter.readCgroupFile("cgroup.procs"),
+ );
+ if (
+ canonicalCgroupJson(confirmedMemberPids) !== canonicalCgroupJson(memberPids)
+ ) {
+ throw new Error("contained cgroup observer membership did not stabilize");
+ }
const membership = selectContainedCgroupMembership(memberPids, processStats);
return { memberPids, membership, processStats };
}
async function assertCandidateSurvived(adapter, initial) {
- const current = new Set(
- parseContainedCgroupMembers(await adapter.readCgroupFile("cgroup.procs")),
+ const current = parseContainedCgroupMembers(
+ await adapter.readCgroupFile("cgroup.procs"),
);
+ if (
+ canonicalCgroupJson(current) !== canonicalCgroupJson(initial.memberPids)
+ ) {
+ throw new Error("contained cgroup observer candidate membership changed");
+ }
for (const pid of initial.memberPids) {
- if (!current.has(pid)) {
- throw new Error("contained cgroup observer candidate membership changed");
- }
const observed = parseContainedProcessStat(
await adapter.readProcessStat(pid),
pid,
@@ -477,27 +501,28 @@ async function assertCandidateSurvived(adapter, initial) {
throw new Error("contained cgroup observer candidate PID was reused");
}
}
+ const confirmed = parseContainedCgroupMembers(
+ await adapter.readCgroupFile("cgroup.procs"),
+ );
+ if (
+ canonicalCgroupJson(confirmed) !== canonicalCgroupJson(initial.memberPids)
+ ) {
+ throw new Error("contained cgroup observer membership did not stabilize");
+ }
}
export async function observeContainedCgroup(manifest, adapter) {
await adapter.waitForCgroup();
- const observedLimits = {
- memoryMaxBytes: scalar(
- await adapter.readCgroupFile("memory.max"),
- "memory limit",
- ),
- memorySwapMaxBytes: swapScalar(
- await adapter.readCgroupFile("memory.swap.max"),
- ),
- pidsMax: scalar(await adapter.readCgroupFile("pids.max"), "PID limit"),
- ...cpuLimit(await adapter.readCgroupFile("cpu.max")),
- };
+ const observedLimits = await observedAggregateLimits(adapter);
const initial = await stableMembership(adapter);
const cpuBefore = parseContainedCgroupKeyValues(
await adapter.readCgroupFile("cpu.stat"),
);
- await adapter.runControl({ mode: "cpu", busyWindowMs: 500, workers: 4 });
+ await adapter.runControl(
+ { mode: "cpu", busyWindowMs: 500, workers: 4 },
+ initial.memberPids,
+ );
const cpuAfter = parseContainedCgroupKeyValues(
await adapter.readCgroupFile("cpu.stat"),
);
@@ -506,7 +531,10 @@ export async function observeContainedCgroup(manifest, adapter) {
await adapter.readCgroupFile("pids.events"),
);
const attemptedProcesses = observedLimits.pidsMax + 1;
- await adapter.runControl({ mode: "processes", attemptedProcesses });
+ await adapter.runControl(
+ { mode: "processes", attemptedProcesses },
+ initial.memberPids,
+ );
const processesAfter = parseContainedCgroupKeyValues(
await adapter.readCgroupFile("pids.events"),
);
@@ -515,12 +543,21 @@ export async function observeContainedCgroup(manifest, adapter) {
await adapter.readCgroupFile("memory.events"),
);
const requestedBytes = observedLimits.memoryMaxBytes + 64 * 1024 * 1024;
- await adapter.runControl({ mode: "memory", requestedBytes });
+ await adapter.runControl(
+ { mode: "memory", requestedBytes },
+ initial.memberPids,
+ );
const memoryAfter = parseContainedCgroupKeyValues(
await adapter.readCgroupFile("memory.events"),
);
await assertCandidateSurvived(adapter, initial);
+ const confirmedLimits = await observedAggregateLimits(adapter);
+ if (
+ canonicalCgroupJson(confirmedLimits) !== canonicalCgroupJson(observedLimits)
+ ) {
+ throw new Error("contained cgroup observer aggregate limits changed");
+ }
const observedAt = adapter.now().toISOString();
const observation = {
observedLimits,
@@ -530,7 +567,7 @@ export async function observeContainedCgroup(manifest, adapter) {
requestedBytes,
oomKillBefore: memoryBefore.oom_kill,
oomKillAfter: memoryAfter.oom_kill,
- enforced: memoryAfter.oom_kill > memoryBefore.oom_kill,
+ enforced: memoryAfter.oom_kill === memoryBefore.oom_kill + 1,
},
processes: {
attemptedProcesses,
@@ -553,6 +590,17 @@ export async function observeContainedCgroup(manifest, adapter) {
};
const draft = createContainedCgroupObserverDraft(manifest, observation);
await adapter.publishDraft(draft);
+ const finalization = validateContainedCgroupObserverFinalization(
+ await adapter.waitForFinalization(),
+ manifest,
+ { observedAtMs: adapter.now().getTime() },
+ );
+ if (finalization.status !== "FINALIZE") {
+ throw new Error("contained cgroup observer qualification was aborted");
+ }
+ if (finalization.observerDraftPayloadSha256 !== draft.receiptPayloadSha256) {
+ throw new Error("contained cgroup observer finalization draft changed");
+ }
await adapter.waitForCgroupAbsent();
return validateContainedCgroupEvidence(
hashedArtifact(evidencePayload(manifest, observation)),
@@ -581,7 +629,8 @@ function privateFile(path, label) {
realpathSync(path) !== path ||
metadata.uid !== process.getuid() ||
metadata.nlink !== 1 ||
- (metadata.mode & 0o777) !== 0o600 ||
+ (metadata.mode & 0o600) !== 0o600 ||
+ (metadata.mode & 0o077) !== 0 ||
metadata.size < 1 ||
metadata.size > maximumArtifactBytes
) {
@@ -602,6 +651,19 @@ function sleep(milliseconds) {
return new Promise((accept) => setTimeout(accept, milliseconds));
}
+function lstatOrAbsent(path, options) {
+ try {
+ return lstatSync(path, options);
+ } catch (error) {
+ if (error?.code === "ENOENT") return null;
+ throw error;
+ }
+}
+
+function artifactExists(path) {
+ return lstatOrAbsent(path) !== null;
+}
+
async function waitUntil(predicate, label, timeoutMs) {
const deadline = Date.now() + timeoutMs;
while (Date.now() <= deadline) {
@@ -613,6 +675,7 @@ async function waitUntil(predicate, label, timeoutMs) {
function actualCgroupAdapter(manifest, paths) {
const cgroupRoot = resolve(cgroupFilesystemRoot, manifest.cgroupPath);
+ const cgroupParentRoot = resolve(cgroupFilesystemRoot, "counterlab-v6.1");
if (
resolve(
cgroupFilesystemRoot,
@@ -626,6 +689,78 @@ function actualCgroupAdapter(manifest, paths) {
repositoryRoot,
"scripts/contained-cgroup-control-helper.mjs",
);
+ let expectedCgroupIdentity;
+ let expectedParentIdentity;
+ let terminationSignal;
+ const activeHelpers = new Set();
+
+ function handleTermination(signal) {
+ terminationSignal ??= signal;
+ for (const helper of activeHelpers) {
+ if (helper.exitCode === null && helper.signalCode === null) {
+ helper.kill("SIGTERM");
+ }
+ }
+ }
+
+ const signalHandlers = new Map(
+ ["SIGINT", "SIGTERM"].map((signal) => {
+ const handler = () => handleTermination(signal);
+ process.on(signal, handler);
+ return [signal, handler];
+ }),
+ );
+
+ function assertNotTerminated() {
+ if (terminationSignal !== undefined) {
+ throw new Error(
+ `contained cgroup observer interrupted by ${terminationSignal}`,
+ );
+ }
+ }
+
+ function directoryIdentity(path, label) {
+ const metadata = lstatOrAbsent(path, { bigint: true });
+ if (metadata === null) return null;
+ if (
+ metadata.isSymbolicLink() ||
+ !metadata.isDirectory() ||
+ realpathSync(path) !== path ||
+ statfsSync(path).type !== cgroup2Magic
+ ) {
+ throw new Error(`contained cgroup observer ${label} is invalid`);
+ }
+ return `${metadata.dev}:${metadata.ino}`;
+ }
+
+ function assertCgroupParent({ allowAbsent = false } = {}) {
+ const identity = directoryIdentity(cgroupParentRoot, "cgroup parent");
+ if (identity === null) {
+ if (allowAbsent && expectedParentIdentity === undefined) return false;
+ throw new Error("contained cgroup observer cgroup parent is absent");
+ }
+ if (
+ expectedParentIdentity !== undefined &&
+ identity !== expectedParentIdentity
+ ) {
+ throw new Error("contained cgroup observer cgroup parent changed");
+ }
+ expectedParentIdentity ??= identity;
+ return true;
+ }
+
+ function currentCgroupIdentity({ allowParentAbsent = false } = {}) {
+ if (!assertCgroupParent({ allowAbsent: allowParentAbsent })) return null;
+ const identity = directoryIdentity(cgroupRoot, "cgroup path");
+ if (
+ identity !== null &&
+ expectedCgroupIdentity !== undefined &&
+ identity !== expectedCgroupIdentity
+ ) {
+ throw new Error("contained cgroup observer cgroup identity changed");
+ }
+ return identity;
+ }
function cgroupFile(name) {
if (!allowedCgroupFiles.has(name)) {
@@ -635,6 +770,9 @@ function actualCgroupAdapter(manifest, paths) {
if (resolve(cgroupRoot, name) !== path) {
throw new Error("contained cgroup observer file path is invalid");
}
+ if (currentCgroupIdentity() !== expectedCgroupIdentity) {
+ throw new Error("contained cgroup observer cgroup identity changed");
+ }
const metadata = lstatSync(path);
if (
metadata.isSymbolicLink() ||
@@ -646,7 +784,8 @@ function actualCgroupAdapter(manifest, paths) {
return path;
}
- async function runControl(control) {
+ async function runControl(control, baselineMemberPids) {
+ assertNotTerminated();
const args =
control.mode === "memory"
? [
@@ -672,9 +811,10 @@ function actualCgroupAdapter(manifest, paths) {
];
const child = spawn(process.execPath, [helperPath, ...args], {
cwd: repositoryRoot,
- env: process.env,
+ env: { LANG: "C", LC_ALL: "C", TZ: "UTC" },
stdio: ["pipe", "pipe", "pipe"],
});
+ activeHelpers.add(child);
let stdout = "";
let stderr = "";
let outputInvalid = false;
@@ -729,14 +869,32 @@ function actualCgroupAdapter(manifest, paths) {
if (!Number.isSafeInteger(child.pid) || child.pid < 1 || outputInvalid) {
throw new Error("contained cgroup observer helper PID is invalid");
}
+ const helperIdentity = parseContainedProcessStat(
+ readFileSync(`/proc/${child.pid}/stat`, "utf8"),
+ child.pid,
+ );
writeFileSync(cgroupFile("cgroup.procs"), `${child.pid}\n`, "utf8");
+ const membersAfterMove = parseContainedCgroupMembers(
+ readFileSync(cgroupFile("cgroup.procs"), "utf8"),
+ );
+ const movedIdentity = parseContainedProcessStat(
+ readFileSync(`/proc/${child.pid}/stat`, "utf8"),
+ child.pid,
+ );
+ if (
+ !membersAfterMove.includes(child.pid) ||
+ movedIdentity.startTimeTicks !== helperIdentity.startTimeTicks
+ ) {
+ throw new Error("contained cgroup observer helper identity changed");
+ }
child.stdin.end("GO\n");
const result = await bounded(completion, 15_000, "helper completion");
if (
result.error !== undefined ||
outputInvalid ||
(control.mode !== "memory" && result.code !== 0) ||
- (control.mode === "memory" && result.code === 0)
+ (control.mode === "memory" &&
+ (result.code !== null || result.signal !== "SIGKILL"))
) {
throw new Error("contained cgroup observer helper result is invalid");
}
@@ -762,6 +920,23 @@ function actualCgroupAdapter(manifest, paths) {
);
}
}
+ await waitUntil(
+ () =>
+ !parseContainedCgroupMembers(
+ readFileSync(cgroupFile("cgroup.procs"), "utf8"),
+ ).includes(child.pid),
+ "helper membership cleanup",
+ 2_000,
+ );
+ const remainingMembers = parseContainedCgroupMembers(
+ readFileSync(cgroupFile("cgroup.procs"), "utf8"),
+ );
+ if (
+ canonicalCgroupJson(remainingMembers) !==
+ canonicalCgroupJson(baselineMemberPids)
+ ) {
+ throw new Error("contained cgroup observer helper descendants remain");
+ }
} finally {
if (child.exitCode === null && child.signalCode === null) {
child.kill("SIGTERM");
@@ -775,19 +950,30 @@ function actualCgroupAdapter(manifest, paths) {
}
await bounded(completion, 2_000, "forced helper reap");
}
+ activeHelpers.delete(child);
+ assertNotTerminated();
}
}
return {
+ dispose() {
+ for (const [signal, handler] of signalHandlers) {
+ process.off(signal, handler);
+ }
+ },
now: () => new Date(),
async waitForCgroup() {
- await waitUntil(() => existsSync(cgroupRoot), "cgroup creation", 10_000);
- if (
- lstatSync(cgroupRoot).isSymbolicLink() ||
- !lstatSync(cgroupRoot).isDirectory() ||
- realpathSync(cgroupRoot) !== cgroupRoot
- ) {
- throw new Error("contained cgroup observer cgroup path is invalid");
+ await waitUntil(
+ () => {
+ assertNotTerminated();
+ return currentCgroupIdentity({ allowParentAbsent: true }) !== null;
+ },
+ "cgroup creation",
+ 10_000,
+ );
+ expectedCgroupIdentity = currentCgroupIdentity();
+ if (expectedCgroupIdentity === null) {
+ throw new Error("contained cgroup observer cgroup path disappeared");
}
await waitUntil(
() => {
@@ -806,6 +992,7 @@ function actualCgroupAdapter(manifest, paths) {
);
},
readCgroupFile(name) {
+ assertNotTerminated();
const path = cgroupFile(name);
const source = readFileSync(path, "utf8");
if (source.length === 0 || source.length > 8_192) {
@@ -814,14 +1001,42 @@ function actualCgroupAdapter(manifest, paths) {
return source;
},
readProcessStat(pid) {
+ assertNotTerminated();
return readFileSync(`/proc/${pid}/stat`, "utf8");
},
runControl,
publishDraft(draft) {
writeArtifact(paths.observerDraftPath, draft);
},
+ async waitForFinalization() {
+ await waitUntil(
+ () => {
+ assertNotTerminated();
+ return artifactExists(paths.finalizationPath);
+ },
+ "runtime finalization",
+ 30_000,
+ );
+ privateFile(paths.finalizationPath, "finalization");
+ return validateContainedCgroupObserverFinalization(
+ JSON.parse(readFileSync(paths.finalizationPath, "utf8")),
+ manifest,
+ );
+ },
async waitForCgroupAbsent() {
- await waitUntil(() => !existsSync(cgroupRoot), "cgroup cleanup", 30_000);
+ await waitUntil(
+ () => {
+ assertNotTerminated();
+ return currentCgroupIdentity() === null;
+ },
+ "cgroup cleanup",
+ 30_000,
+ );
+ assertCgroupParent();
+ await sleep(20);
+ if (currentCgroupIdentity() !== null) {
+ throw new Error("contained cgroup observer cgroup path reappeared");
+ }
},
};
}
@@ -908,14 +1123,24 @@ async function main() {
throw new Error("contained cgroup observer base receipt changed");
}
const adapter = actualCgroupAdapter(manifest, paths);
- writeArtifact(
- paths.observerReadyPath,
- createContainedCgroupObserverReady(manifest),
- );
- const evidence = await observeContainedCgroup(manifest, adapter);
- writeArtifact(paths.evidencePath, evidence);
+ try {
+ writeArtifact(
+ paths.observerReadyPath,
+ createContainedCgroupObserverReady(manifest),
+ );
+ const evidence = await observeContainedCgroup(manifest, adapter);
+ if (artifactExists(paths.failurePath)) {
+ throw new Error("contained cgroup observer outcome already failed");
+ }
+ writeArtifact(paths.evidencePath, evidence);
+ } finally {
+ adapter.dispose();
+ }
} catch (error) {
- if (!existsSync(paths.failurePath)) {
+ if (
+ !artifactExists(paths.evidencePath) &&
+ !artifactExists(paths.failurePath)
+ ) {
try {
writeArtifact(
paths.failurePath,
diff --git a/scripts/contained-cgroup-observer.test.ts b/scripts/contained-cgroup-observer.test.ts
index 0486970d..4f5a7768 100644
--- a/scripts/contained-cgroup-observer.test.ts
+++ b/scripts/contained-cgroup-observer.test.ts
@@ -18,13 +18,26 @@ import {
validateContainedCgroupObserverFailure,
validateContainedCgroupObserverReady,
} from "./contained-cgroup-observer.mjs";
-import { createContainedCgroupObserverManifest } from "./contained-cgroup-observer-protocol.mjs";
+import {
+ createContainedCgroupObserverFinalization,
+ createContainedCgroupObserverManifest,
+} from "./contained-cgroup-observer-protocol.mjs";
const runtimeSessionId = "rt-v61-test1";
const invocationId = "1".repeat(64);
const finalContainerId = "2".repeat(64);
const sanitizedSpecSha256 = "3".repeat(64);
const now = new Date("2026-07-20T01:02:03.000Z");
+const verifiedCleanup = {
+ taskAbsent: true,
+ containerAbsent: true,
+ snapshotAbsent: true,
+ invocationAliasAbsent: true,
+ imageRootfsAbsent: true,
+ persistedAuthorityVerified: true,
+ readOnlyMountsUnchanged: true,
+ imageRootfsUnchanged: true,
+};
function manifest() {
return createContainedCgroupObserverManifest({
@@ -58,6 +71,14 @@ function processStat(pid: number, parentPid: number, startTime: number) {
return `${pid} (counterlab worker) ${fields.join(" ")}\n`;
}
+function latestDraftHash(drafts: Array>) {
+ const value = drafts.at(-1)?.receiptPayloadSha256;
+ if (typeof value !== "string") {
+ throw new Error("test observer draft hash is unavailable");
+ }
+ return value;
+}
+
function adapter(overrides: Record = {}) {
const counters = {
cpu: { nr_throttled: 0, throttled_usec: 0 },
@@ -106,6 +127,16 @@ function adapter(overrides: Record = {}) {
async publishDraft(draft: Record) {
drafts.push(draft);
},
+ waitForFinalization: async () =>
+ createContainedCgroupObserverFinalization(manifest(), {
+ cleanup: verifiedCleanup,
+ cleanupVerified: true,
+ decisionAt: now,
+ observerDraftPayloadSha256: latestDraftHash(drafts),
+ resultReleased: false,
+ status: "FINALIZE",
+ timeoutObserved: true,
+ }),
waitForCgroupAbsent: async () => undefined,
...overrides,
};
@@ -189,6 +220,92 @@ describe("contained cgroup observer", () => {
);
});
+ it("rejects aborts, unstable membership and limits, and ambiguous OOM kills", async () => {
+ const aborted = adapter({
+ waitForFinalization: async () =>
+ createContainedCgroupObserverFinalization(manifest(), {
+ abortCode: "TIMEOUT_NOT_OBSERVED",
+ cleanupVerified: false,
+ cleanup: { ...verifiedCleanup, taskAbsent: false },
+ decisionAt: now,
+ observerDraftPayloadSha256: latestDraftHash(aborted.drafts),
+ resultReleased: false,
+ status: "ABORT",
+ timeoutObserved: false,
+ }),
+ });
+ await expect(observeContainedCgroup(manifest(), aborted)).rejects.toThrow(
+ /aborted/u,
+ );
+
+ const wrongDraft = adapter({
+ waitForFinalization: async () =>
+ createContainedCgroupObserverFinalization(manifest(), {
+ cleanup: verifiedCleanup,
+ cleanupVerified: true,
+ decisionAt: now,
+ observerDraftPayloadSha256: "f".repeat(64),
+ resultReleased: false,
+ status: "FINALIZE",
+ timeoutObserved: true,
+ }),
+ });
+ await expect(
+ observeContainedCgroup(manifest(), wrongDraft),
+ ).rejects.toThrow(/draft changed/u);
+
+ let membershipReads = 0;
+ const unstableMembership = adapter({
+ readCgroupFile(name: string) {
+ if (name === "cgroup.procs") {
+ membershipReads += 1;
+ return membershipReads === 1 ? "100\n101\n" : "100\n101\n102\n";
+ }
+ return adapter().readCgroupFile(name);
+ },
+ readProcessStat(pid: number) {
+ if (pid === 100) return processStat(100, 1, 12345);
+ if (pid === 101) return processStat(101, 100, 12346);
+ return processStat(102, 100, 12347);
+ },
+ });
+ await expect(
+ observeContainedCgroup(manifest(), unstableMembership),
+ ).rejects.toThrow(/stabilize/u);
+
+ const stable = adapter();
+ let memoryLimitReads = 0;
+ const changedLimits = {
+ ...stable,
+ readCgroupFile(name: string) {
+ if (name === "memory.max") {
+ memoryLimitReads += 1;
+ return String(
+ memoryLimitReads === 1 ? 512 * 1024 * 1024 : 512 * 1024 * 1024 + 1,
+ );
+ }
+ return stable.readCgroupFile(name);
+ },
+ };
+ await expect(
+ observeContainedCgroup(manifest(), changedLimits),
+ ).rejects.toThrow(/limits changed/u);
+
+ const ambiguousOomBase = adapter();
+ const ambiguousOom = {
+ ...ambiguousOomBase,
+ async runControl(control: Record) {
+ await ambiguousOomBase.runControl(control);
+ if (control.mode === "memory") {
+ await ambiguousOomBase.runControl(control);
+ }
+ },
+ };
+ await expect(
+ observeContainedCgroup(manifest(), ambiguousOom),
+ ).rejects.toThrow(/evidence binding/u);
+ });
+
it("binds ready and draft receipts and rejects mutation", () => {
const input = manifest();
const ready = createContainedCgroupObserverReady(input, {
diff --git a/services/runner/src/counterlab_runner/timeout_proof.py b/services/runner/src/counterlab_runner/timeout_proof.py
index e23c099b..8c65a769 100644
--- a/services/runner/src/counterlab_runner/timeout_proof.py
+++ b/services/runner/src/counterlab_runner/timeout_proof.py
@@ -579,7 +579,8 @@ def _validate_aggregate_limit_evidence(
for field in ("requestedBytes", "oomKillBefore", "oomKillAfter")
)
or memory_control.get("requestedBytes") <= intended["memoryBytes"]
- or memory_control.get("oomKillAfter") <= memory_control.get("oomKillBefore")
+ or memory_control.get("oomKillAfter")
+ != memory_control.get("oomKillBefore") + 1
or memory_control.get("enforced") is not True
or not all(
_safe_integer(
diff --git a/services/runner/tests/test_timeout_proof.py b/services/runner/tests/test_timeout_proof.py
index 5d30b655..e3ca1d47 100644
--- a/services/runner/tests/test_timeout_proof.py
+++ b/services/runner/tests/test_timeout_proof.py
@@ -297,7 +297,15 @@ def test_rootless_receipt_binds_control_and_exact_adapter_authority() -> None:
broken_counter = json.loads(json.dumps(rootless))
broken_counter["aggregateLimitEvidence"]["negativeControls"]["memory"][
"oomKillAfter"
- ] = 0
+ ] = 2
+ evidence_payload = {
+ key: value
+ for key, value in broken_counter["aggregateLimitEvidence"].items()
+ if key != "receiptPayloadSha256"
+ }
+ broken_counter["aggregateLimitEvidence"]["receiptPayloadSha256"] = _hash(
+ evidence_payload
+ )
payload = {
key: value
for key, value in broken_counter.items()
From f0c9f7bfcca474ed700b51975f03246df9f5158d Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 07:21:13 +0530
Subject: [PATCH 046/143] test(learner): cover recovery and notebook intake
---
apps/web/e2e/recovery-and-intake.spec.ts | 430 +++++++++++++++++++++++
1 file changed, 430 insertions(+)
create mode 100644 apps/web/e2e/recovery-and-intake.spec.ts
diff --git a/apps/web/e2e/recovery-and-intake.spec.ts b/apps/web/e2e/recovery-and-intake.spec.ts
new file mode 100644
index 00000000..69685e19
--- /dev/null
+++ b/apps/web/e2e/recovery-and-intake.spec.ts
@@ -0,0 +1,430 @@
+import { expect, test, type Page } from "./cloak-test";
+import { SAMPLE_LEAKAGE_QUESTION } from "../shared/sample-authority";
+
+const liveQuestion =
+ "Does this notebook score show that the model generalizes to customers it has never seen?";
+const supportedNotebookPath = new URL(
+ "../../../fixtures/notebooks/customer_churn_leakage.ipynb",
+ import.meta.url,
+).pathname;
+
+const readyHealth = {
+ ok: true,
+ data: {
+ platform: "cloudflare-workers",
+ sample: "available",
+ replay: "available",
+ liveGpt: "configured",
+ liveCodex: "configured",
+ liveKernel: "configured",
+ readiness: "ready",
+ sandbox: "credential-and-privilege-boundary",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ release: {
+ status: "bound",
+ workerVersionId: "11111111-2222-3333-4444-555555555555",
+ workerVersionTag: `git-${"a".repeat(40)}`,
+ workerEvidenceCommit: "a".repeat(40),
+ runnerSourceCommit: "b".repeat(40),
+ runnerImageDigest: `sha256:${"c".repeat(64)}`,
+ timeoutCleanupReceiptSha256: "d".repeat(64),
+ aggregateLimitEvidenceSha256: "9".repeat(64),
+ runtimePolicySha256: "e".repeat(64),
+ proofDependencyManifestSha256: "f".repeat(64),
+ workerArtifactClassification: "PROCESS_BOUND_PARTIAL",
+ workerArtifactManifestSha256: "1".repeat(64),
+ workerBundleSha256: "2".repeat(64),
+ clientAssetsSha256: "3".repeat(64),
+ clientAssetCount: 27,
+ clientPublicAssetsSha256: "4".repeat(64),
+ clientPublicAssetCount: 25,
+ viteVersion: "8.1.4",
+ wranglerVersion: "4.110.0",
+ },
+ requestId: "e2e-ready-intake",
+ },
+} as const;
+
+async function reset(page: Page): Promise {
+ await page.goto("/", { waitUntil: "domcontentloaded" });
+ await page.evaluate(() => {
+ window.localStorage.clear();
+ window.sessionStorage.clear();
+ });
+ await page.reload({ waitUntil: "domcontentloaded" });
+ await expect(
+ page.getByRole("heading", {
+ name: "What result are you trying to understand?",
+ }),
+ ).toBeVisible();
+}
+
+async function storedSessionId(page: Page): Promise {
+ return page.evaluate(() =>
+ window.localStorage.getItem("counterlab.sessionId"),
+ );
+}
+
+async function openSampleModelDuel(page: Page): Promise {
+ await reset(page);
+ await page.getByRole("button", { name: /Try verified sample/i }).click();
+ await expect(
+ page.getByRole("heading", { name: /What do you think the score means/i }),
+ ).toBeVisible();
+ await page.getByRole("button", { name: /Compare two explanations/i }).click();
+ await expect(
+ page.getByRole("heading", {
+ name: /Does your current explanation capture what you mean/i,
+ }),
+ ).toBeVisible();
+}
+
+async function installReadyHealth(page: Page): Promise {
+ await page.route("**/api/health*", async (route) => {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify(readyHealth),
+ });
+ });
+}
+
+async function openReadyLiveIntake(page: Page): Promise {
+ await installReadyHealth(page);
+ await reset(page);
+ await page.getByLabel("Your question or claim").fill(liveQuestion);
+ await page.getByRole("button", { name: /Test this claim/i }).click();
+ await expect(
+ page.getByRole("heading", {
+ name: /Start with evidence that matches your question/i,
+ }),
+ ).toBeVisible();
+ await page
+ .getByRole("button", { name: /Check live notebook tools/i })
+ .click();
+ await expect(
+ page.getByRole("heading", { name: "Test my notebook" }),
+ ).toBeVisible();
+ await expect(
+ page.getByText("Source-bound hosted notebook runner is ready"),
+ ).toBeVisible();
+ await expect(page.getByLabel(/Attach a supported notebook/i)).toBeEnabled();
+}
+
+function isPostTo(requestUrl: string, method: string, suffix: string): boolean {
+ return method === "POST" && new URL(requestUrl).pathname.endsWith(suffix);
+}
+
+for (const response of [
+ {
+ action: "Reject",
+ terminalState: "REJECTED_BY_LEARNER",
+ },
+ {
+ action: "Not enough evidence",
+ terminalState: "INSUFFICIENT_EVIDENCE",
+ },
+] as const) {
+ test(`${response.action} preserves the claim and resubmits only in a fresh session`, async ({
+ page,
+ }) => {
+ const proposalSessionIds: string[] = [];
+ page.on("request", (request) => {
+ if (!isPostTo(request.url(), request.method(), "/belief-test")) return;
+ const segments = new URL(request.url()).pathname.split("/");
+ const encodedSessionId = segments.at(-2);
+ if (encodedSessionId !== undefined) {
+ proposalSessionIds.push(decodeURIComponent(encodedSessionId));
+ }
+ });
+
+ await openSampleModelDuel(page);
+ const closedSessionId = await storedSessionId(page);
+ expect(closedSessionId).toMatch(/^session_/u);
+ await expect(page).toHaveURL(
+ new RegExp(`/session/${encodeURIComponent(closedSessionId!)}$`, "u"),
+ );
+ const preservedClaim = (
+ await page.locator("blockquote").first().innerText()
+ ).trim();
+ expect(preservedClaim).toBe(SAMPLE_LEAKAGE_QUESTION);
+
+ await page.getByText("More ways to respond", { exact: true }).click();
+ await page
+ .getByRole("button", { name: response.action, exact: true })
+ .click();
+ await expect(
+ page.getByText("Your response closed that explanation."),
+ ).toBeVisible();
+ await expect(
+ page.getByRole("button", { name: /Compare two explanations/i }),
+ ).toBeDisabled();
+ await expect(page.locator("body")).not.toContainText(
+ /REJECTED_BY_LEARNER|INSUFFICIENT_EVIDENCE/u,
+ );
+ const terminalResponse = await page.request.get(
+ `/api/sessions/${encodeURIComponent(closedSessionId!)}`,
+ );
+ expect(terminalResponse.ok()).toBe(true);
+ expect((await terminalResponse.json()).data.state).toBe(
+ response.terminalState,
+ );
+
+ await page.reload({ waitUntil: "domcontentloaded" });
+ await expect(
+ page.getByText("Your response closed that explanation."),
+ ).toBeVisible();
+ await expect(
+ page.getByText(SAMPLE_LEAKAGE_QUESTION, { exact: true }),
+ ).toBeVisible();
+ expect(await storedSessionId(page)).toBe(closedSessionId);
+
+ await page
+ .getByRole("button", { name: /Revise in a new investigation/i })
+ .click();
+ await expect(
+ page.getByRole("heading", { name: /What do you think the score means/i }),
+ ).toBeVisible();
+ const freshSessionId = await storedSessionId(page);
+ expect(freshSessionId).toMatch(/^session_/u);
+ expect(freshSessionId).not.toBe(closedSessionId);
+ await expect(page).toHaveURL(
+ new RegExp(`/session/${encodeURIComponent(freshSessionId!)}$`, "u"),
+ );
+ expect(
+ await page.evaluate(() =>
+ window.localStorage.getItem("counterlab.claim"),
+ ),
+ ).toBe(preservedClaim);
+ await expect(
+ page.getByRole("button", { name: /Compare two explanations/i }),
+ ).toBeEnabled();
+
+ await page
+ .getByRole("button", { name: /Compare two explanations/i })
+ .click();
+ await expect(
+ page.getByRole("heading", {
+ name: /Does your current explanation capture what you mean/i,
+ }),
+ ).toBeVisible();
+ expect(proposalSessionIds).toEqual([closedSessionId, freshSessionId]);
+
+ await page.goBack({ waitUntil: "domcontentloaded" });
+ await expect(page).toHaveURL(
+ new RegExp(`/session/${encodeURIComponent(closedSessionId!)}$`, "u"),
+ );
+ await expect(
+ page.getByText("Your response closed that explanation."),
+ ).toBeVisible();
+ await expect(
+ page.getByText(SAMPLE_LEAKAGE_QUESTION, { exact: true }),
+ ).toBeVisible();
+ expect(await storedSessionId(page)).toBe(closedSessionId);
+
+ await page.goForward({ waitUntil: "domcontentloaded" });
+ await expect(page).toHaveURL(
+ new RegExp(`/session/${encodeURIComponent(freshSessionId!)}$`, "u"),
+ );
+ await expect(
+ page.getByRole("heading", {
+ name: /Does your current explanation capture what you mean/i,
+ }),
+ ).toBeVisible();
+ expect(await storedSessionId(page)).toBe(freshSessionId);
+ await page
+ .getByRole("button", { name: /Yes, this captures my view/i })
+ .click();
+ await expect(
+ page.getByRole("heading", {
+ name: /Seal what you expect before the result appears/i,
+ }),
+ ).toBeVisible();
+ expect(proposalSessionIds).toEqual([closedSessionId, freshSessionId]);
+ });
+}
+
+test("live intake cannot continue without an uploaded supported artifact", async ({
+ page,
+}) => {
+ await openReadyLiveIntake(page);
+
+ expect(await storedSessionId(page)).toBeNull();
+ await expect(page.getByText(/Notebook intake passed/i)).toHaveCount(0);
+ await expect(
+ page.getByRole("button", { name: /Compare two explanations/i }),
+ ).toHaveCount(0);
+ await expect(page.locator("body")).not.toContainText(/Preparing artifact/i);
+});
+
+test("malformed live upload remains at intake and creates no session", async ({
+ page,
+}) => {
+ let sessionCreationRequests = 0;
+ page.on("request", (request) => {
+ if (isPostTo(request.url(), request.method(), "/api/live/sessions")) {
+ sessionCreationRequests += 1;
+ }
+ });
+ await openReadyLiveIntake(page);
+
+ await page.getByLabel(/Attach a supported notebook/i).setInputFiles({
+ name: "broken.ipynb",
+ mimeType: "application/x-ipynb+json",
+ buffer: Buffer.from("not-json"),
+ });
+
+ await expect(page.getByRole("alert")).toContainText(/not valid JSON/i);
+ await expect(
+ page.getByRole("heading", { name: "Test my notebook" }),
+ ).toBeVisible();
+ await expect(page.getByLabel(/Attach a supported notebook/i)).toBeEnabled();
+ expect(sessionCreationRequests).toBe(0);
+ expect(await storedSessionId(page)).toBeNull();
+});
+
+test("unsupported live upload is refused without creating a session", async ({
+ page,
+}) => {
+ let sessionCreationRequests = 0;
+ page.on("request", (request) => {
+ if (isPostTo(request.url(), request.method(), "/api/live/sessions")) {
+ sessionCreationRequests += 1;
+ }
+ });
+ await openReadyLiveIntake(page);
+ const unsupportedNotebook = {
+ cells: [
+ {
+ cell_type: "code",
+ execution_count: null,
+ metadata: {},
+ outputs: [],
+ source: ["%load_ext custom_extension\n", "import requests\n"],
+ },
+ ],
+ metadata: {},
+ nbformat: 4,
+ nbformat_minor: 5,
+ };
+
+ await page.getByLabel(/Attach a supported notebook/i).setInputFiles({
+ name: "unsupported.ipynb",
+ mimeType: "application/x-ipynb+json",
+ buffer: Buffer.from(JSON.stringify(unsupportedNotebook)),
+ });
+
+ await expect(page.getByText(/UNSUPPORTED_MAGIC/i)).toBeVisible();
+ await expect(page.getByText(/EXTERNAL_NETWORK_DEPENDENCY/i)).toBeVisible();
+ await expect(
+ page.getByRole("button", { name: /Compare two explanations/i }),
+ ).toBeDisabled();
+ expect(sessionCreationRequests).toBe(0);
+ expect(await storedSessionId(page)).toBeNull();
+});
+
+test("a supported live upload creates one source-bound session", async ({
+ page,
+}) => {
+ let uploadRequests = 0;
+ let sessionCreationRequests = 0;
+ page.on("request", (request) => {
+ if (isPostTo(request.url(), request.method(), "/api/artifacts")) {
+ uploadRequests += 1;
+ }
+ if (isPostTo(request.url(), request.method(), "/api/live/sessions")) {
+ sessionCreationRequests += 1;
+ }
+ });
+ await openReadyLiveIntake(page);
+
+ await page
+ .getByLabel(/Attach a supported notebook/i)
+ .setInputFiles(supportedNotebookPath);
+
+ await expect(
+ page.getByRole("heading", { name: /What do you think the score means/i }),
+ ).toBeVisible();
+ await expect(page.getByText("customer_churn_leakage.ipynb")).toBeVisible();
+ await expect(
+ page.getByRole("button", { name: /Compare two explanations/i }),
+ ).toBeEnabled();
+ expect(await storedSessionId(page)).toMatch(/^session_/u);
+ expect(uploadRequests).toBe(1);
+ expect(sessionCreationRequests).toBe(1);
+});
+
+test("an interrupted upload accepts the same file on retry", async ({
+ page,
+}) => {
+ let uploadAttempts = 0;
+ await page.route("**/api/artifacts", async (route) => {
+ if (route.request().method() !== "POST") {
+ await route.continue();
+ return;
+ }
+ uploadAttempts += 1;
+ if (uploadAttempts === 1) {
+ await route.abort("connectionreset");
+ return;
+ }
+ await route.continue();
+ });
+ await openReadyLiveIntake(page);
+ const upload = page.getByLabel(/Attach a supported notebook/i);
+
+ await upload.setInputFiles(supportedNotebookPath);
+ await expect(page.getByRole("alert")).toContainText(
+ /could not reach the API/i,
+ );
+ await expect(upload).toBeEnabled();
+ await upload.setInputFiles(supportedNotebookPath);
+
+ await expect(
+ page.getByRole("heading", { name: /What do you think the score means/i }),
+ ).toBeVisible();
+ expect(uploadAttempts).toBe(2);
+ expect(await storedSessionId(page)).toMatch(/^session_/u);
+});
+
+test("a lost private-session response retries without re-uploading", async ({
+ page,
+}) => {
+ let uploadRequests = 0;
+ let sessionCreationAttempts = 0;
+ page.on("request", (request) => {
+ if (isPostTo(request.url(), request.method(), "/api/artifacts")) {
+ uploadRequests += 1;
+ }
+ });
+ await page.route("**/api/live/sessions", async (route) => {
+ sessionCreationAttempts += 1;
+ if (sessionCreationAttempts === 1) {
+ await route.abort("connectionreset");
+ return;
+ }
+ await route.continue();
+ });
+ await openReadyLiveIntake(page);
+
+ await page
+ .getByLabel(/Attach a supported notebook/i)
+ .setInputFiles(supportedNotebookPath);
+ await expect(page.getByRole("alert")).toContainText(
+ /could not reach the API/i,
+ );
+ await expect(
+ page.getByRole("button", { name: /Retry private session setup/i }),
+ ).toBeEnabled();
+ await expect(page.getByText("customer_churn_leakage.ipynb")).toBeVisible();
+
+ await page
+ .getByRole("button", { name: /Retry private session setup/i })
+ .click();
+ await expect(
+ page.getByRole("heading", { name: /What do you think the score means/i }),
+ ).toBeVisible();
+ expect(uploadRequests).toBe(1);
+ expect(sessionCreationAttempts).toBe(2);
+ expect(await storedSessionId(page)).toMatch(/^session_/u);
+});
From 88f96c9cb10665740a66ee6a8296fa9a59a51381 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 07:24:11 +0530
Subject: [PATCH 047/143] docs(progress): record observer and recovery gates
---
docs/EVALUATION.md | 13 +++--
docs/PROGRESS.md | 106 +++++++++++++++++++++++++++++++++-----
docs/RELEASE_CHECKLIST.md | 2 +-
3 files changed, 103 insertions(+), 18 deletions(-)
diff --git a/docs/EVALUATION.md b/docs/EVALUATION.md
index 59530b87..9313be45 100644
--- a/docs/EVALUATION.md
+++ b/docs/EVALUATION.md
@@ -103,11 +103,14 @@ They are not browser evidence for branch `feat/learner-ux-v6.1`.
### Current v6.1 local specification
-The Cloak-only Playwright suite collected 22 tests from one spec file covering
-1440 × 900, 1280 × 720, 390 × 844, keyboard/focus, reduced motion, refresh
-phases, sample, replay, mocked live, unsupported intake, rejected verification,
-failed transfer, successful patch, downloads, Boundary Hunt, exact tables, no
-horizontal overflow, and no model call from local controls.
+The current Cloak-only Playwright suite statically collects 40 tests from three
+spec files. It covers the desktop/mobile viewports, keyboard/focus, reduced
+motion, refresh phases, sample, replay, mocked live, unsupported intake,
+rejected verification, failed transfer, successful patch, downloads, Boundary
+Hunt, exact tables, no horizontal overflow, no model call from local controls,
+fresh-session recovery after both learner terminal responses, browser
+back/forward, absent and malformed upload, same-file retry, and lost private
+session response recovery without re-upload.
Zero of those tests ran in this pass because `CLOAK_CDP_ENDPOINT` was absent.
The harness exited with status 1 and refused to launch stock Chromium. No
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 13ce8b1e..c709151c 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -1,6 +1,6 @@
# Progress
-Updated: 2026-07-20
+Updated: 2026-07-21
## Active v6.1 release board
@@ -12,17 +12,17 @@ pass is never presented as a browser or production pass.
### Current checkpoint
- **Branch:** `feat/learner-ux-v6.1`
-- **Latest committed checkpoint:** `9348093a15942e7fb59c2755d3e080a20785b5c0`.
- The fresh exact-image isolation rerun is independently hash-bound through
- release-check receipt v5, deployment receipt v7, Worker release identity,
- exact public response validation, and production-smoke report v6. Historical
- receipt readers remain compatible. The worktree contains only the preserved,
- untracked `scripts/.fuse_hidden0000a9600000801f`, which remains unopened,
- unstaged, and undeleted. The complete web suite passes **616/616**; the
- correctly contained and socket-enabled root suite passes **667/670** with
- **2 skipped**, leaving exactly three stale scientific-evidence failures.
- Qualification, deployment, browser qualification, learner evidence, and
- submission remain unclaimed.
+- **Latest committed checkpoint:** `f0c9f7bfcca474ed700b51975f03246df9f5158d`.
+ The worktree contains only the preserved, untracked
+ `scripts/.fuse_hidden0000a9600000801f`. The source now has a self-hashed
+ cgroup request, observer, FINALIZE/ABORT protocol, fixed negative controls,
+ fail-closed evidence validation, qualified-receipt derivation/store, and 40
+ statically collected Playwright journeys. The production runtime still does
+ not start the observer, write the runtime-owned decision, bind the complete
+ invocation artifact chain, or select the qualified receipt for timeout
+ proof. No exact image, real cgroup sentinel, qualification, deployment,
+ rendered browser journey, learner observation, video, Devpost publication,
+ or submission receipt is claimed.
- **Earlier product checkpoint:**
`5259a495948a68024702ba5239bf8057d1785f08`. The owner partitioned the
preserved integration delta into nine commits and fast-forwarded both
@@ -3381,3 +3381,85 @@ read-only during ownership diagnosis, remains byte-identical to
and align the public repository, video, `/feedback` ID, Devpost fields, and
submission receipt before the authorized feature push and fast-forward
merge to `main`.
+
+## Finish Commander observer and recovery checkpoint — 2026-07-21T01:51:33Z
+
+Deadline: `2026-07-22T00:00:00Z`; **22 hours 08 minutes 27 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD `f0c9f7b`; the only
+worktree entry besides this progress update is the preserved FUSE tombstone
+`scripts/.fuse_hidden0000a9600000801f`. It remains unchanged and unstaged.
+
+### Independently committed source slices
+
+- `0077f1a feat(runtime): add fixed cgroup controls` adds the release-only
+ READY-to-GO memory, process, and CPU control helper.
+- `fbc5ba9 feat(runtime): bind cgroup observer identity` and `37684dd
+ feat(runtime): define cgroup observer protocol` bind the observer to the
+ exact runtime session, attestation, drivers, base receipt, invocation, and
+ repository-contained handshake paths.
+- `771d8bf feat(runtime): observe aggregate cgroup controls` adds the actual
+ source-attested cgroup-v2 observer and its synthetic characterization tests.
+- `317ba44 fix(runtime): bind qualified receipts to live sessions` makes the
+ receipt store resolve live observer bindings itself, require the exact
+ `.rt/` path, reject stale observations, and persist once.
+- `c2c1817 feat(runtime): define cgroup finalization signal` introduces an
+ exact self-hashed runtime FINALIZE/ABORT decision.
+- `cf90647 fix(runtime): fail closed on cgroup qualification` binds FINALIZE to
+ the exact draft and eight cleanup facts, keeps ABORT unconditionally
+ fail-closed, treats only `ENOENT` as absence, pins parent/child cgroup
+ identities with bigint device/inode values, double-checks limits and
+ membership, requires exact candidate restoration after each helper, requires
+ memory `SIGKILL` plus an exact one-event OOM delta, bounds descendant reaping,
+ minimizes helper environments, handles observer termination, and prevents
+ evidence/failure coexistence at the observer output boundary.
+- `f0c9f7b test(learner): cover recovery and notebook intake` adds eight
+ Playwright acceptance journeys for Reject and Not enough evidence recovery,
+ fresh-session resubmission, refresh/back/forward, absent/malformed/
+ unsupported/supported upload, same-file retry, and lost-session-response
+ retry without re-upload.
+
+### Exact verification in these slices
+
+- Complete cgroup observer/protocol/helper/evidence/qualified-receipt Vitest:
+ **7 files, 22/22 passed**.
+- Complete Python timeout-proof file: **7/7 passed**.
+- Repository, web client, generated Worker, and Worker TypeScript: **passed**.
+- Recovery/intake E2E TypeScript: **passed**.
+- Playwright static collection with an inert Cloak endpoint: **40 tests in 3
+ files**. The endpoint was not contacted, no browser launched, and this is not
+ rendered qualification evidence.
+- Scoped Prettier, Python compilation, Node syntax, Git whitespace, and secret
+ scan across the 11 runtime files plus the isolated E2E file: **passed**.
+
+### Live issue matrix
+
+| Gate | Current source status | Required production evidence |
+| --- | --- | --- |
+| Observer semantics | **source implemented and locally tested** | Real delegated cgroup-v2 memory OOM, PID denial, CPU throttling, exact membership, identity, limit, FINALIZE, and cleanup observations |
+| Runtime integration | **open** | Runtime starts observer before the candidate, waits ARMED/draft, writes FINALIZE only after timeout/no-result/complete cleanup and ABORT otherwise, then waits for the exclusive terminal artifact |
+| Qualified receipt chain | **partial** | Store reads and validates exact manifest/draft/finalization/evidence files, requires `failure.json` to be absent, binds finalization into evidence, and supplies the qualified receipt hashes to TypeScript and Python timeout proof |
+| Exact release | **not run** | Clean source `S`, one exact image, real sentinel, refreshed SBOM/VEX/scientific evidence `E`, qualification and release-check receipts |
+| Cloudflare | **not run** | Contained Wrangler account verification, qualified Worker/Container deployment, exact public identity, D1 migrations, production smoke |
+| Browser and accessibility | **40 tests statically collected; 0 rendered** | All tests plus the manual responsive, keyboard, screen-reader, reduced-motion, console/network, download, reconnect, and Web Vitals matrix through CloakBrowser on the exact public tuple |
+| Learner impact and submission | **`NO_DATA`; externally unverified** | Real consented aggregates or unchanged `NO_DATA`, public repo/video/feedback ID, aligned Devpost entry, logged-out link audit, and submission receipt |
+
+### Ordered remaining work
+
+1. Bind finalization into aggregate evidence and make the qualified store verify
+ the complete exclusive invocation artifact chain.
+2. Wire qualification mode through `executeContainedRun`, observer startup,
+ ARMED/draft waits, runtime FINALIZE/ABORT creation, qualified receipt
+ persistence, control-receipt hashes, and Python consumers. Ordinary learner
+ execution must remain unchanged.
+3. Execute the real sentinel only if the repository constitution permits the
+ required delegated `/sys/fs/cgroup` and owned `/proc//stat` reads;
+ otherwise preserve an explicit no-go instead of synthesizing evidence.
+4. Freeze clean source `S`, build once, regenerate exact evidence, qualify, run
+ every deterministic gate, verify contained Wrangler identity, and deploy
+ only the qualified tuple.
+5. Execute all 40 current journeys and the manual accessibility/performance
+ matrix through an injected CloakBrowser endpoint against the exact public
+ release.
+6. Keep learner evidence honest, finish and verify the public submission
+ package, then push the feature branch, verify the remote tip, fast-forward
+ `main`, and push/verify `main` only after every ship gate is factual.
diff --git a/docs/RELEASE_CHECKLIST.md b/docs/RELEASE_CHECKLIST.md
index 9e8c32a9..f4d138a8 100644
--- a/docs/RELEASE_CHECKLIST.md
+++ b/docs/RELEASE_CHECKLIST.md
@@ -348,7 +348,7 @@ after the final source freeze.
no normal-home credential read is permitted.
- [ ] Deploy the exact qualified Container/Worker tuple and bind the active
version to the release source, image, Worker, client, and receipt domains.
-- [ ] Execute production smoke plus all 31 CloakBrowser journeys, including
+- [ ] Execute production smoke plus all 40 CloakBrowser journeys, including
desktop/mobile sample, replay, supported live, malformed/unsupported,
refusal, transfer fail/pass, patch, Capsule, refresh/reconnect,
back/forward, accessibility, console/network, and Web Vitals checks.
From bb1cc0b3e5da8215577dc9f12cf93305cccad08e Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 07:29:08 +0530
Subject: [PATCH 048/143] feat(runtime): bind cgroup finalization evidence
---
scripts/contained-cgroup-evidence.mjs | 4 ++-
scripts/contained-cgroup-evidence.test.ts | 3 +-
scripts/contained-cgroup-observer.mjs | 14 ++++++--
scripts/contained-cgroup-observer.test.ts | 34 +++++++++++++------
.../contained-qualified-receipt-store.test.ts | 3 +-
...ntained-qualified-rootless-receipt.test.ts | 3 +-
scripts/timeout-cleanup-receipt.test.ts | 16 ++++++++-
scripts/timeout-cleanup-receipt.ts | 9 ++---
.../src/counterlab_runner/timeout_proof.py | 4 ++-
services/runner/tests/test_timeout_proof.py | 3 +-
10 files changed, 69 insertions(+), 24 deletions(-)
diff --git a/scripts/contained-cgroup-evidence.mjs b/scripts/contained-cgroup-evidence.mjs
index 24a3cdc7..f877d77b 100644
--- a/scripts/contained-cgroup-evidence.mjs
+++ b/scripts/contained-cgroup-evidence.mjs
@@ -17,6 +17,7 @@ const topLevelKeys = [
"cgroupVersion",
"cleanup",
"finalContainerId",
+ "finalizationPayloadSha256",
"invocationId",
"membership",
"negativeControls",
@@ -203,7 +204,7 @@ export function validateContainedCgroupEvidence(value, expectedValue) {
: new Set();
const { receiptPayloadSha256, ...payload } = evidence;
if (
- evidence.schemaVersion !== "1" ||
+ evidence.schemaVersion !== "2" ||
evidence.status !== "OBSERVED" ||
evidence.authority !== "linux-cgroup-v2" ||
evidence.cgroupVersion !== 2 ||
@@ -211,6 +212,7 @@ export function validateContainedCgroupEvidence(value, expectedValue) {
evidence.finalContainerId !== expected.finalContainerId ||
evidence.sanitizedSpecSha256 !== expected.sanitizedSpecSha256 ||
evidence.runtimeAttestationSha256 !== expected.runtimeAttestationSha256 ||
+ !sha256(evidence.finalizationPayloadSha256) ||
evidence.cgroupId !== `counterlab-v6.1-${expected.invocationId}` ||
evidence.cgroupPath !== `counterlab-v6.1/${expected.invocationId}` ||
evidence.cgroupIdentity !== createContainedCgroupIdentity(expected) ||
diff --git a/scripts/contained-cgroup-evidence.test.ts b/scripts/contained-cgroup-evidence.test.ts
index 78cbf3f5..11bd3371 100644
--- a/scripts/contained-cgroup-evidence.test.ts
+++ b/scripts/contained-cgroup-evidence.test.ts
@@ -20,7 +20,7 @@ const intendedAggregateLimits = {
function evidence() {
const memberPids = [100, 101];
const payload = {
- schemaVersion: "1",
+ schemaVersion: "2",
status: "OBSERVED",
authority: "linux-cgroup-v2",
cgroupVersion: 2,
@@ -33,6 +33,7 @@ function evidence() {
}),
invocationId,
finalContainerId,
+ finalizationPayloadSha256: "f".repeat(64),
sanitizedSpecSha256,
runtimeAttestationSha256: "4".repeat(64),
observedLimits: {
diff --git a/scripts/contained-cgroup-observer.mjs b/scripts/contained-cgroup-observer.mjs
index 5805b05c..a5c28f69 100644
--- a/scripts/contained-cgroup-observer.mjs
+++ b/scripts/contained-cgroup-observer.mjs
@@ -198,9 +198,9 @@ function expectedEvidence(manifest) {
};
}
-function evidencePayload(manifest, observation) {
+function evidencePayload(manifest, observation, finalization) {
return {
- schemaVersion: "1",
+ schemaVersion: "2",
status: "OBSERVED",
authority: "linux-cgroup-v2",
cgroupVersion: 2,
@@ -209,6 +209,8 @@ function evidencePayload(manifest, observation) {
cgroupIdentity: manifest.cgroupIdentity,
invocationId: manifest.invocationId,
finalContainerId: manifest.finalContainerId,
+ finalizationPayloadSha256:
+ finalization?.receiptPayloadSha256 ?? "0".repeat(64),
sanitizedSpecSha256: manifest.sanitizedSpecSha256,
runtimeAttestationSha256: manifest.runtimeAttestationSha256,
observedLimits: observation.observedLimits,
@@ -602,8 +604,14 @@ export async function observeContainedCgroup(manifest, adapter) {
throw new Error("contained cgroup observer finalization draft changed");
}
await adapter.waitForCgroupAbsent();
+ const completedObservation = {
+ ...observation,
+ observedAt: adapter.now().toISOString(),
+ };
return validateContainedCgroupEvidence(
- hashedArtifact(evidencePayload(manifest, observation)),
+ hashedArtifact(
+ evidencePayload(manifest, completedObservation, finalization),
+ ),
expectedEvidence(manifest),
);
}
diff --git a/scripts/contained-cgroup-observer.test.ts b/scripts/contained-cgroup-observer.test.ts
index 4f5a7768..f2a14d15 100644
--- a/scripts/contained-cgroup-observer.test.ts
+++ b/scripts/contained-cgroup-observer.test.ts
@@ -87,9 +87,13 @@ function adapter(overrides: Record = {}) {
};
const controls: Array> = [];
const drafts: Array> = [];
+ const finalizations: Array<
+ ReturnType
+ > = [];
return {
controls,
drafts,
+ finalizations,
now: () => now,
waitForCgroup: async () => undefined,
readCgroupFile(name: string) {
@@ -127,16 +131,22 @@ function adapter(overrides: Record = {}) {
async publishDraft(draft: Record) {
drafts.push(draft);
},
- waitForFinalization: async () =>
- createContainedCgroupObserverFinalization(manifest(), {
- cleanup: verifiedCleanup,
- cleanupVerified: true,
- decisionAt: now,
- observerDraftPayloadSha256: latestDraftHash(drafts),
- resultReleased: false,
- status: "FINALIZE",
- timeoutObserved: true,
- }),
+ waitForFinalization: async () => {
+ const finalization = createContainedCgroupObserverFinalization(
+ manifest(),
+ {
+ cleanup: verifiedCleanup,
+ cleanupVerified: true,
+ decisionAt: now,
+ observerDraftPayloadSha256: latestDraftHash(drafts),
+ resultReleased: false,
+ status: "FINALIZE",
+ timeoutObserved: true,
+ },
+ );
+ finalizations.push(finalization);
+ return finalization;
+ },
waitForCgroupAbsent: async () => undefined,
...overrides,
};
@@ -174,6 +184,10 @@ describe("contained cgroup observer", () => {
{ mode: "memory", requestedBytes: 576 * 1024 * 1024 },
]);
expect(fake.drafts).toHaveLength(1);
+ expect(fake.finalizations).toHaveLength(1);
+ expect(evidence.finalizationPayloadSha256).toBe(
+ fake.finalizations.at(0)?.receiptPayloadSha256,
+ );
expect(() =>
validateContainedCgroupObserverDraft(fake.drafts[0], input),
).not.toThrow();
diff --git a/scripts/contained-qualified-receipt-store.test.ts b/scripts/contained-qualified-receipt-store.test.ts
index 5f7bbbf5..a19f625c 100644
--- a/scripts/contained-qualified-receipt-store.test.ts
+++ b/scripts/contained-qualified-receipt-store.test.ts
@@ -71,7 +71,7 @@ function baseReceipt() {
function aggregateEvidence(bindings = observerBindings) {
const memberPids = [100, 101];
return hashed({
- schemaVersion: "1",
+ schemaVersion: "2",
status: "OBSERVED",
authority: "linux-cgroup-v2",
cgroupVersion: 2,
@@ -84,6 +84,7 @@ function aggregateEvidence(bindings = observerBindings) {
}),
invocationId,
finalContainerId,
+ finalizationPayloadSha256: "f".repeat(64),
sanitizedSpecSha256,
runtimeAttestationSha256: bindings.runtimeAttestationSha256,
observedLimits: {
diff --git a/scripts/contained-qualified-rootless-receipt.test.ts b/scripts/contained-qualified-rootless-receipt.test.ts
index e4526b19..29ae5b10 100644
--- a/scripts/contained-qualified-rootless-receipt.test.ts
+++ b/scripts/contained-qualified-rootless-receipt.test.ts
@@ -66,7 +66,7 @@ function baseReceipt() {
function aggregateEvidence() {
const memberPids = [100, 101];
return hashed({
- schemaVersion: "1",
+ schemaVersion: "2",
status: "OBSERVED",
authority: "linux-cgroup-v2",
cgroupVersion: 2,
@@ -79,6 +79,7 @@ function aggregateEvidence() {
}),
invocationId,
finalContainerId,
+ finalizationPayloadSha256: "f".repeat(64),
sanitizedSpecSha256,
runtimeAttestationSha256: observerBindings.runtimeAttestationSha256,
observedLimits: {
diff --git a/scripts/timeout-cleanup-receipt.test.ts b/scripts/timeout-cleanup-receipt.test.ts
index e4b2e375..958329d8 100644
--- a/scripts/timeout-cleanup-receipt.test.ts
+++ b/scripts/timeout-cleanup-receipt.test.ts
@@ -26,7 +26,7 @@ function aggregateLimitEvidence() {
const sanitizedSpecSha256 = "5".repeat(64);
const memberPids = [100, 101];
const payload = {
- schemaVersion: "1" as const,
+ schemaVersion: "2" as const,
status: "OBSERVED" as const,
authority: "linux-cgroup-v2" as const,
cgroupVersion: 2 as const,
@@ -37,6 +37,7 @@ function aggregateLimitEvidence() {
),
invocationId,
finalContainerId,
+ finalizationPayloadSha256: "f".repeat(64),
sanitizedSpecSha256,
runtimeAttestationSha256: "6".repeat(64),
observedLimits: {
@@ -159,5 +160,18 @@ describe("timeout cleanup aggregate resource authority", () => {
limitMode: QUALIFIED_AGGREGATE_LIMIT_MODE,
}),
).toThrow();
+
+ const ambiguousOom = aggregateLimitEvidence();
+ ambiguousOom.negativeControls.memory.oomKillAfter = 2;
+ const { receiptPayloadSha256: _ignored, ...payload } = ambiguousOom;
+ ambiguousOom.receiptPayloadSha256 = sha256(canonicalJson(payload));
+ expect(() =>
+ assertQualifiedAggregateRuntimeLimits({
+ ...aggregateInput(),
+ aggregateLimitEvidence: ambiguousOom,
+ aggregateLimitIntentEnforced: true,
+ limitMode: QUALIFIED_AGGREGATE_LIMIT_MODE,
+ }),
+ ).toThrow(/negative-control/u);
});
});
diff --git a/scripts/timeout-cleanup-receipt.ts b/scripts/timeout-cleanup-receipt.ts
index 1fa18dfa..8a4e7a6c 100644
--- a/scripts/timeout-cleanup-receipt.ts
+++ b/scripts/timeout-cleanup-receipt.ts
@@ -37,7 +37,7 @@ const AggregateLimitIntentSchema = z.strictObject({
const AggregateLimitEvidenceSchema = z
.strictObject({
- schemaVersion: z.literal("1"),
+ schemaVersion: z.literal("2"),
status: z.literal("OBSERVED"),
authority: z.literal("linux-cgroup-v2"),
cgroupVersion: z.literal(2),
@@ -46,6 +46,7 @@ const AggregateLimitEvidenceSchema = z
cgroupIdentity: Sha256Schema,
invocationId: Sha256Schema,
finalContainerId: Sha256Schema,
+ finalizationPayloadSha256: Sha256Schema,
sanitizedSpecSha256: Sha256Schema,
runtimeAttestationSha256: Sha256Schema,
observedLimits: z.strictObject({
@@ -139,8 +140,8 @@ const AggregateLimitEvidenceSchema = z
});
}
if (
- evidence.negativeControls.memory.oomKillAfter <=
- evidence.negativeControls.memory.oomKillBefore ||
+ evidence.negativeControls.memory.oomKillAfter !==
+ evidence.negativeControls.memory.oomKillBefore + 1 ||
evidence.negativeControls.processes.maxEventsAfter <=
evidence.negativeControls.processes.maxEventsBefore ||
evidence.negativeControls.cpu.nrThrottledAfter <=
@@ -151,7 +152,7 @@ const AggregateLimitEvidenceSchema = z
context.addIssue({
code: "custom",
path: ["negativeControls"],
- message: "aggregate negative-control counters must increase",
+ message: "aggregate negative-control counters are invalid",
});
}
const { receiptPayloadSha256, ...payload } = evidence;
diff --git a/services/runner/src/counterlab_runner/timeout_proof.py b/services/runner/src/counterlab_runner/timeout_proof.py
index 8c65a769..014c47a1 100644
--- a/services/runner/src/counterlab_runner/timeout_proof.py
+++ b/services/runner/src/counterlab_runner/timeout_proof.py
@@ -457,6 +457,7 @@ def _validate_aggregate_limit_evidence(
"cgroupIdentity",
"invocationId",
"finalContainerId",
+ "finalizationPayloadSha256",
"sanitizedSpecSha256",
"runtimeAttestationSha256",
"observedLimits",
@@ -509,12 +510,13 @@ def _validate_aggregate_limit_evidence(
cpu_control = controls.get("cpu")
member_pids = membership.get("memberPids")
if (
- value.get("schemaVersion") != "1"
+ value.get("schemaVersion") != "2"
or value.get("status") != "OBSERVED"
or value.get("authority") != "linux-cgroup-v2"
or value.get("cgroupVersion") != 2
or value.get("invocationId") != invocation_id
or value.get("finalContainerId") != final_container_id
+ or not _SHA256.fullmatch(str(value.get("finalizationPayloadSha256", "")))
or value.get("cgroupId") != f"counterlab-v6.1-{invocation_id}"
or value.get("cgroupPath") != f"counterlab-v6.1/{invocation_id}"
or value.get("sanitizedSpecSha256") != sanitized_spec_sha256
diff --git a/services/runner/tests/test_timeout_proof.py b/services/runner/tests/test_timeout_proof.py
index e3ca1d47..8e4f7c6f 100644
--- a/services/runner/tests/test_timeout_proof.py
+++ b/services/runner/tests/test_timeout_proof.py
@@ -69,7 +69,7 @@ def _rootless(control: dict[str, object], build: dict[str, object]) -> dict[str,
sanitized_spec_sha256 = "a" * 64
member_pids = [100, 101]
aggregate_payload: dict[str, object] = {
- "schemaVersion": "1",
+ "schemaVersion": "2",
"status": "OBSERVED",
"authority": "linux-cgroup-v2",
"cgroupVersion": 2,
@@ -83,6 +83,7 @@ def _rootless(control: dict[str, object], build: dict[str, object]) -> dict[str,
).hexdigest(),
"invocationId": invocation_id,
"finalContainerId": final_container_id,
+ "finalizationPayloadSha256": "7" * 64,
"sanitizedSpecSha256": sanitized_spec_sha256,
"runtimeAttestationSha256": "6" * 64,
"observedLimits": {
From 3cdf38b2626e7341fd7bcb8af33fcda23a6ea131 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 07:39:45 +0530
Subject: [PATCH 049/143] fix(runtime): verify qualification artifact chain
---
.../contained-qualified-receipt-store.d.mts | 20 +-
scripts/contained-qualified-receipt-store.mjs | 262 ++++++++++++++++--
.../contained-qualified-receipt-store.test.ts | 229 +++++++++++++--
3 files changed, 462 insertions(+), 49 deletions(-)
diff --git a/scripts/contained-qualified-receipt-store.d.mts b/scripts/contained-qualified-receipt-store.d.mts
index 591a2738..1f915503 100644
--- a/scripts/contained-qualified-receipt-store.d.mts
+++ b/scripts/contained-qualified-receipt-store.d.mts
@@ -15,16 +15,30 @@ interface QualifiedReceiptStoreDependencies {
};
}
+interface QualifiedReceiptObservationArtifacts {
+ evidence: Record;
+ evidenceFileSha256: string;
+ evidencePath: string;
+ finalizationFileSha256: string;
+ finalizationPath: string;
+ finalizationPayloadSha256: string;
+ manifestFileSha256: string;
+ manifestPath: string;
+ observerDraftFileSha256: string;
+ observerDraftPath: string;
+ observerReadyFileSha256: string;
+ observerReadyPath: string;
+}
+
export function persistQualifiedContainedRootlessReceipt(
- input: {
- aggregateLimitEvidence: unknown;
- } & QualifiedRootlessReceiptStoreBinding,
+ input: QualifiedRootlessReceiptStoreBinding,
dependencies?: QualifiedReceiptStoreDependencies,
): {
qualifiedReceipt: Record;
qualifiedReceiptFileSha256: string;
qualifiedReceiptPath: string;
qualifiedReceiptPayloadSha256: string;
+ qualificationArtifacts: QualifiedReceiptObservationArtifacts;
};
export function verifyQualifiedContainedRootlessReceipt(
diff --git a/scripts/contained-qualified-receipt-store.mjs b/scripts/contained-qualified-receipt-store.mjs
index 9b911998..0a480271 100644
--- a/scripts/contained-qualified-receipt-store.mjs
+++ b/scripts/contained-qualified-receipt-store.mjs
@@ -2,8 +2,21 @@ import { lstatSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
import { isAbsolute, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
-import { sha256CgroupBytes } from "./contained-cgroup-evidence.mjs";
+import {
+ canonicalCgroupJson,
+ sha256CgroupBytes,
+ validateContainedCgroupEvidence,
+} from "./contained-cgroup-evidence.mjs";
import { resolveContainedCgroupObserverBindings } from "./contained-cgroup-observer-bindings.mjs";
+import {
+ validateContainedCgroupObserverDraft,
+ validateContainedCgroupObserverReady,
+} from "./contained-cgroup-observer.mjs";
+import {
+ containedCgroupQualificationPaths,
+ validateContainedCgroupObserverFinalization,
+ validateContainedCgroupObserverManifest,
+} from "./contained-cgroup-observer-protocol.mjs";
import { createQualifiedContainedRootlessReceipt } from "./contained-qualified-rootless-receipt.mjs";
const repositoryRoot = realpathSync(
@@ -51,6 +64,29 @@ function privateFile(path, label) {
}
}
+function lstatOrAbsent(path) {
+ try {
+ return lstatSync(path);
+ } catch (error) {
+ if (error?.code === "ENOENT") return null;
+ throw error;
+ }
+}
+
+function readArtifact(path, label) {
+ privateFile(path, label);
+ const source = readFileSync(path);
+ let value;
+ try {
+ value = JSON.parse(source.toString("utf8"));
+ } catch (error) {
+ throw new Error(`qualified rootless receipt ${label} JSON is invalid`, {
+ cause: error,
+ });
+ }
+ return { source, value };
+}
+
function receiptPaths({ sessionRoot, finalContainerId, runtimeSessionId }) {
if (
!isAbsolute(sessionRoot) ||
@@ -117,7 +153,170 @@ function readBaseReceipt({
if (receipt?.finalContainerId !== finalContainerId) {
throw new Error("qualified rootless base receipt identity changed");
}
- return { expected, receipt };
+ return { expected, receipt, source };
+}
+
+function exactTimestamp(value, label) {
+ const parsed = Date.parse(value);
+ if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) {
+ throw new Error(`qualified rootless receipt ${label} is invalid`);
+ }
+ return parsed;
+}
+
+function readObservationChain({
+ baseReceipt,
+ baseReceiptSource,
+ currentTime,
+ enforceFreshness,
+ observerBindings,
+ sessionRoot,
+}) {
+ const paths = containedCgroupQualificationPaths({
+ repositoryRoot,
+ runtimeSessionId: observerBindings.runtimeSessionId,
+ invocationId: baseReceipt.invocationId,
+ });
+ privateDirectory(paths.qualificationRoot, "qualification root");
+ privateDirectory(paths.invocationRoot, "qualification invocation root");
+ if (lstatOrAbsent(paths.failurePath) !== null) {
+ throw new Error("qualified rootless receipt observation failed");
+ }
+
+ const manifestArtifact = readArtifact(paths.manifestPath, "manifest");
+ const manifestRequestedAt = exactTimestamp(
+ manifestArtifact.value?.requestedAt,
+ "manifest timestamp",
+ );
+ const manifest = validateContainedCgroupObserverManifest(
+ manifestArtifact.value,
+ { observedAtMs: manifestRequestedAt },
+ );
+ const expectedBaseReceiptPath = `.rt/${observerBindings.runtimeSessionId}/run/rootless-specs/${baseReceipt.finalContainerId}.receipt.json`;
+ if (
+ manifest.baseReceiptPath !== expectedBaseReceiptPath ||
+ manifest.baseReceiptFileSha256 !== sha256CgroupBytes(baseReceiptSource) ||
+ manifest.baseReceiptPayloadSha256 !== baseReceipt.receiptPayloadSha256 ||
+ manifest.finalContainerId !== baseReceipt.finalContainerId ||
+ manifest.invocationId !== baseReceipt.invocationId ||
+ manifest.sanitizedSpecSha256 !== baseReceipt.sanitizedSpecSha256 ||
+ canonicalCgroupJson(manifest.intendedAggregateLimits) !==
+ canonicalCgroupJson(baseReceipt.intendedAggregateLimits) ||
+ Object.entries(observerBindings).some(
+ ([name, value]) => manifest[name] !== value,
+ ) ||
+ paths.sessionRoot !== sessionRoot
+ ) {
+ throw new Error("qualified rootless receipt manifest binding changed");
+ }
+
+ const readyArtifact = readArtifact(paths.observerReadyPath, "observer ready");
+ const ready = validateContainedCgroupObserverReady(
+ readyArtifact.value,
+ manifest,
+ );
+ const draftArtifact = readArtifact(paths.observerDraftPath, "observer draft");
+ const draft = validateContainedCgroupObserverDraft(
+ draftArtifact.value,
+ manifest,
+ );
+ const finalizationArtifact = readArtifact(
+ paths.finalizationPath,
+ "finalization",
+ );
+ const finalizationDecisionAt = exactTimestamp(
+ finalizationArtifact.value?.decisionAt,
+ "finalization timestamp",
+ );
+ const finalization = validateContainedCgroupObserverFinalization(
+ finalizationArtifact.value,
+ manifest,
+ { observedAtMs: finalizationDecisionAt },
+ );
+ if (
+ finalization.status !== "FINALIZE" ||
+ finalization.observerDraftPayloadSha256 !== draft.receiptPayloadSha256
+ ) {
+ throw new Error("qualified rootless receipt finalization did not qualify");
+ }
+
+ const evidenceArtifact = readArtifact(
+ paths.evidencePath,
+ "aggregate evidence",
+ );
+ const evidence = validateContainedCgroupEvidence(evidenceArtifact.value, {
+ invocationId: baseReceipt.invocationId,
+ finalContainerId: baseReceipt.finalContainerId,
+ sanitizedSpecSha256: baseReceipt.sanitizedSpecSha256,
+ intendedAggregateLimits: baseReceipt.intendedAggregateLimits,
+ ...observerBindings,
+ });
+ const armedAt = exactTimestamp(ready.armedAt, "observer ready timestamp");
+ const draftObservedAt = exactTimestamp(
+ draft.observedAt,
+ "observer draft timestamp",
+ );
+ const evidenceObservedAt = exactTimestamp(
+ evidence.observedAt,
+ "aggregate evidence timestamp",
+ );
+ if (
+ evidence.finalizationPayloadSha256 !== finalization.receiptPayloadSha256 ||
+ canonicalCgroupJson(evidence.observedLimits) !==
+ canonicalCgroupJson(draft.observedLimits) ||
+ canonicalCgroupJson(evidence.membership) !==
+ canonicalCgroupJson(draft.membership) ||
+ canonicalCgroupJson(evidence.negativeControls) !==
+ canonicalCgroupJson(draft.negativeControls) ||
+ manifestRequestedAt > armedAt ||
+ armedAt > draftObservedAt ||
+ draftObservedAt > finalizationDecisionAt ||
+ finalizationDecisionAt > evidenceObservedAt ||
+ evidenceObservedAt - manifestRequestedAt > maximumObservationAgeMs
+ ) {
+ throw new Error("qualified rootless receipt observation chain changed");
+ }
+ if (
+ enforceFreshness &&
+ (!Number.isFinite(currentTime) ||
+ evidenceObservedAt > currentTime ||
+ currentTime - evidenceObservedAt > maximumObservationAgeMs)
+ ) {
+ throw new Error("qualified rootless receipt observation is stale");
+ }
+ if (lstatOrAbsent(paths.failurePath) !== null) {
+ throw new Error("qualified rootless receipt terminal artifacts conflict");
+ }
+ return {
+ evidence,
+ evidenceFileSha256: sha256CgroupBytes(evidenceArtifact.source),
+ evidencePath: paths.evidencePath,
+ finalizationFileSha256: sha256CgroupBytes(finalizationArtifact.source),
+ finalizationPath: paths.finalizationPath,
+ finalizationPayloadSha256: finalization.receiptPayloadSha256,
+ manifestFileSha256: sha256CgroupBytes(manifestArtifact.source),
+ manifestPath: paths.manifestPath,
+ observerDraftFileSha256: sha256CgroupBytes(draftArtifact.source),
+ observerDraftPath: paths.observerDraftPath,
+ observerReadyFileSha256: sha256CgroupBytes(readyArtifact.source),
+ observerReadyPath: paths.observerReadyPath,
+ };
+}
+
+function observationArtifactIdentity(observation) {
+ return canonicalCgroupJson({
+ evidenceFileSha256: observation.evidenceFileSha256,
+ evidencePath: observation.evidencePath,
+ finalizationFileSha256: observation.finalizationFileSha256,
+ finalizationPath: observation.finalizationPath,
+ finalizationPayloadSha256: observation.finalizationPayloadSha256,
+ manifestFileSha256: observation.manifestFileSha256,
+ manifestPath: observation.manifestPath,
+ observerDraftFileSha256: observation.observerDraftFileSha256,
+ observerDraftPath: observation.observerDraftPath,
+ observerReadyFileSha256: observation.observerReadyFileSha256,
+ observerReadyPath: observation.observerReadyPath,
+ });
}
export function persistQualifiedContainedRootlessReceipt(
@@ -127,26 +326,45 @@ export function persistQualifiedContainedRootlessReceipt(
resolveObserverBindings = resolveContainedCgroupObserverBindings,
} = {},
) {
+ if (Object.hasOwn(input, "aggregateLimitEvidence")) {
+ throw new Error("qualified rootless receipt caller evidence is forbidden");
+ }
const observerBindings = resolveObserverBindings({
sessionRoot: input.sessionRoot,
});
- const observedAt = Date.parse(input.aggregateLimitEvidence?.observedAt);
const currentTime = now();
- if (
- !Number.isFinite(currentTime) ||
- !Number.isFinite(observedAt) ||
- new Date(observedAt).toISOString() !==
- input.aggregateLimitEvidence?.observedAt ||
- Math.abs(currentTime - observedAt) > maximumObservationAgeMs
- ) {
- throw new Error("qualified rootless receipt observation is stale");
- }
- const { expected, receipt: baseReceipt } = readBaseReceipt({
+ const {
+ expected,
+ receipt: baseReceipt,
+ source: baseReceiptSource,
+ } = readBaseReceipt({
...input,
observerBindings,
});
+ const initialObservation = readObservationChain({
+ baseReceipt,
+ baseReceiptSource,
+ currentTime,
+ enforceFreshness: true,
+ observerBindings,
+ sessionRoot: input.sessionRoot,
+ });
+ const observation = readObservationChain({
+ baseReceipt,
+ baseReceiptSource,
+ currentTime: now(),
+ enforceFreshness: true,
+ observerBindings,
+ sessionRoot: input.sessionRoot,
+ });
+ if (
+ observationArtifactIdentity(initialObservation) !==
+ observationArtifactIdentity(observation)
+ ) {
+ throw new Error("qualified rootless receipt terminal artifacts changed");
+ }
const qualifiedReceipt = createQualifiedContainedRootlessReceipt({
- aggregateLimitEvidence: input.aggregateLimitEvidence,
+ aggregateLimitEvidence: observation.evidence,
baseReceipt,
observerBindings,
});
@@ -171,6 +389,7 @@ export function persistQualifiedContainedRootlessReceipt(
qualifiedReceiptFileSha256: sha256CgroupBytes(source),
qualifiedReceiptPath: expected.qualifiedReceiptPath,
qualifiedReceiptPayloadSha256: qualifiedReceipt.receiptPayloadSha256,
+ qualificationArtifacts: observation,
};
}
@@ -181,7 +400,11 @@ export function verifyQualifiedContainedRootlessReceipt(
const observerBindings = resolveObserverBindings({
sessionRoot: input.sessionRoot,
});
- const { expected, receipt: baseReceipt } = readBaseReceipt({
+ const {
+ expected,
+ receipt: baseReceipt,
+ source: baseReceiptSource,
+ } = readBaseReceipt({
...input,
observerBindings,
});
@@ -204,8 +427,15 @@ export function verifyQualifiedContainedRootlessReceipt(
cause: error,
});
}
+ const observation = readObservationChain({
+ baseReceipt,
+ baseReceiptSource,
+ enforceFreshness: false,
+ observerBindings,
+ sessionRoot: input.sessionRoot,
+ });
const expectedReceipt = createQualifiedContainedRootlessReceipt({
- aggregateLimitEvidence: qualifiedReceipt.aggregateLimitEvidence,
+ aggregateLimitEvidence: observation.evidence,
baseReceipt,
observerBindings,
});
diff --git a/scripts/contained-qualified-receipt-store.test.ts b/scripts/contained-qualified-receipt-store.test.ts
index a19f625c..40b58e31 100644
--- a/scripts/contained-qualified-receipt-store.test.ts
+++ b/scripts/contained-qualified-receipt-store.test.ts
@@ -9,6 +9,16 @@ import {
createContainedCgroupIdentity,
sha256CgroupBytes,
} from "./contained-cgroup-evidence.mjs";
+import {
+ createContainedCgroupObserverDraft,
+ createContainedCgroupObserverFailure,
+ createContainedCgroupObserverReady,
+} from "./contained-cgroup-observer.mjs";
+import {
+ containedCgroupQualificationPaths,
+ createContainedCgroupObserverFinalization,
+ createContainedCgroupObserverManifest,
+} from "./contained-cgroup-observer-protocol.mjs";
import {
persistQualifiedContainedRootlessReceipt,
verifyQualifiedContainedRootlessReceipt,
@@ -30,6 +40,16 @@ const observerBindings = {
driverCliSha256: "5".repeat(64),
driverModuleSha256: "6".repeat(64),
};
+const verifiedCleanup = {
+ taskAbsent: true,
+ containerAbsent: true,
+ snapshotAbsent: true,
+ invocationAliasAbsent: true,
+ imageRootfsAbsent: true,
+ persistedAuthorityVerified: true,
+ readOnlyMountsUnchanged: true,
+ imageRootfsUnchanged: true,
+};
function hashed>(payload: T) {
return {
@@ -68,25 +88,9 @@ function baseReceipt() {
});
}
-function aggregateEvidence(bindings = observerBindings) {
+function aggregateObservation(observedAt = "2026-07-20T12:00:00.000Z") {
const memberPids = [100, 101];
- return hashed({
- schemaVersion: "2",
- status: "OBSERVED",
- authority: "linux-cgroup-v2",
- cgroupVersion: 2,
- cgroupId: `counterlab-v6.1-${invocationId}`,
- cgroupPath: `counterlab-v6.1/${invocationId}`,
- cgroupIdentity: createContainedCgroupIdentity({
- invocationId,
- finalContainerId,
- sanitizedSpecSha256,
- }),
- invocationId,
- finalContainerId,
- finalizationPayloadSha256: "f".repeat(64),
- sanitizedSpecSha256,
- runtimeAttestationSha256: bindings.runtimeAttestationSha256,
+ return {
observedLimits: {
memoryMaxBytes: intendedAggregateLimits.memoryBytes,
memorySwapMaxBytes: intendedAggregateLimits.memoryBytes,
@@ -123,17 +127,50 @@ function aggregateEvidence(bindings = observerBindings) {
enforced: true,
},
},
+ observedAt,
+ };
+}
+
+function aggregateEvidence(
+ bindings: typeof observerBindings,
+ finalizationPayloadSha256: string,
+ observedAt = "2026-07-20T12:00:20.000Z",
+) {
+ return hashed({
+ schemaVersion: "2",
+ status: "OBSERVED",
+ authority: "linux-cgroup-v2",
+ cgroupVersion: 2,
+ cgroupId: `counterlab-v6.1-${invocationId}`,
+ cgroupPath: `counterlab-v6.1/${invocationId}`,
+ cgroupIdentity: createContainedCgroupIdentity({
+ invocationId,
+ finalContainerId,
+ sanitizedSpecSha256,
+ }),
+ invocationId,
+ finalContainerId,
+ finalizationPayloadSha256,
+ sanitizedSpecSha256,
+ runtimeAttestationSha256: bindings.runtimeAttestationSha256,
+ ...aggregateObservation(observedAt),
cleanup: { cgroupAbsentAfterTimeout: true },
observer: {
runtimeSessionId: bindings.runtimeSessionId,
driverCliSha256: bindings.driverCliSha256,
driverModuleSha256: bindings.driverModuleSha256,
},
- observedAt: "2026-07-20T12:00:00.000Z",
});
}
-function fixture() {
+function writeArtifact(path: string, value: unknown) {
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, {
+ mode: 0o600,
+ flag: "wx",
+ });
+}
+
+function fixture({ writeEvidence = true, writeFailure = false } = {}) {
const runtimeSessionId = `rt-store-${randomBytes(4).toString("hex")}`;
const parent = resolve(root, ".rt");
mkdirSync(parent, { recursive: true, mode: 0o700 });
@@ -143,12 +180,73 @@ function fixture() {
const specRoot = resolve(runRoot, "rootless-specs");
mkdirSync(specRoot, { recursive: true, mode: 0o700 });
const baseReceiptPath = resolve(specRoot, `${finalContainerId}.receipt.json`);
- const source = `${JSON.stringify(baseReceipt(), null, 2)}\n`;
+ const base = baseReceipt();
+ const source = `${JSON.stringify(base, null, 2)}\n`;
writeFileSync(baseReceiptPath, source, { mode: 0o600, flag: "wx" });
+ const liveObserverBindings = { ...observerBindings, runtimeSessionId };
+ const paths = containedCgroupQualificationPaths({
+ repositoryRoot: root,
+ runtimeSessionId,
+ invocationId,
+ });
+ mkdirSync(paths.invocationRoot, { recursive: true, mode: 0o700 });
+ const manifest = createContainedCgroupObserverManifest({
+ baseReceiptFileSha256: sha256CgroupBytes(source),
+ baseReceiptPayloadSha256: base.receiptPayloadSha256,
+ finalContainerId,
+ intendedAggregateLimits,
+ invocationId,
+ observerBindings: liveObserverBindings,
+ requestedAt: new Date("2026-07-20T11:59:50.000Z"),
+ sanitizedSpecSha256,
+ });
+ const ready = createContainedCgroupObserverReady(manifest, {
+ armedAt: new Date("2026-07-20T11:59:55.000Z"),
+ observerPid: 123,
+ });
+ const draft = createContainedCgroupObserverDraft(
+ manifest,
+ aggregateObservation(),
+ );
+ if (typeof draft.receiptPayloadSha256 !== "string") {
+ throw new Error("observer draft fixture hash is invalid");
+ }
+ const finalization = createContainedCgroupObserverFinalization(manifest, {
+ cleanup: verifiedCleanup,
+ cleanupVerified: true,
+ decisionAt: new Date("2026-07-20T12:00:10.000Z"),
+ observerDraftPayloadSha256: draft.receiptPayloadSha256,
+ resultReleased: false,
+ status: "FINALIZE",
+ timeoutObserved: true,
+ });
+ if (typeof finalization.receiptPayloadSha256 !== "string") {
+ throw new Error("observer finalization fixture hash is invalid");
+ }
+ const evidence = aggregateEvidence(
+ liveObserverBindings,
+ finalization.receiptPayloadSha256,
+ );
+ writeArtifact(paths.manifestPath, manifest);
+ writeArtifact(paths.observerReadyPath, ready);
+ writeArtifact(paths.observerDraftPath, draft);
+ writeArtifact(paths.finalizationPath, finalization);
+ if (writeEvidence) writeArtifact(paths.evidencePath, evidence);
+ if (writeFailure) {
+ writeArtifact(
+ paths.failurePath,
+ createContainedCgroupObserverFailure(manifest, {
+ failedAt: new Date("2026-07-20T12:00:21.000Z"),
+ }),
+ );
+ }
return {
baseReceiptFileSha256: sha256CgroupBytes(source),
baseReceiptPath,
- observerBindings: { ...observerBindings, runtimeSessionId },
+ evidence,
+ finalization,
+ observerBindings: liveObserverBindings,
+ paths,
sessionRoot,
};
}
@@ -168,7 +266,6 @@ describe("qualified rootless receipt store", () => {
baseReceiptFileSha256: input.baseReceiptFileSha256,
baseReceiptPath: input.baseReceiptPath,
sessionRoot: input.sessionRoot,
- aggregateLimitEvidence: aggregateEvidence(input.observerBindings),
finalContainerId,
},
dependencies(input),
@@ -180,6 +277,12 @@ describe("qualified rootless receipt store", () => {
`run/rootless-specs/${finalContainerId}.qualified-receipt.json`,
),
);
+ expect(persisted.qualificationArtifacts.evidencePath).toBe(
+ input.paths.evidencePath,
+ );
+ expect(persisted.qualificationArtifacts.finalizationPayloadSha256).toBe(
+ input.finalization.receiptPayloadSha256,
+ );
expect(readFileSync(input.baseReceiptPath, "utf8")).toBe(
`${JSON.stringify(baseReceipt(), null, 2)}\n`,
);
@@ -202,7 +305,6 @@ describe("qualified rootless receipt store", () => {
baseReceiptFileSha256: input.baseReceiptFileSha256,
baseReceiptPath: input.baseReceiptPath,
sessionRoot: input.sessionRoot,
- aggregateLimitEvidence: aggregateEvidence(input.observerBindings),
finalContainerId,
},
dependencies(input),
@@ -217,7 +319,6 @@ describe("qualified rootless receipt store", () => {
{
baseReceiptPath: wrongHash.baseReceiptPath,
sessionRoot: wrongHash.sessionRoot,
- aggregateLimitEvidence: aggregateEvidence(wrongHash.observerBindings),
baseReceiptFileSha256: "0".repeat(64),
finalContainerId,
},
@@ -236,7 +337,6 @@ describe("qualified rootless receipt store", () => {
{
baseReceiptFileSha256: linked.baseReceiptFileSha256,
sessionRoot: linked.sessionRoot,
- aggregateLimitEvidence: aggregateEvidence(linked.observerBindings),
baseReceiptPath: linkedPath,
finalContainerId,
},
@@ -253,7 +353,6 @@ describe("qualified rootless receipt store", () => {
baseReceiptFileSha256: stale.baseReceiptFileSha256,
baseReceiptPath: stale.baseReceiptPath,
sessionRoot: stale.sessionRoot,
- aggregateLimitEvidence: aggregateEvidence(stale.observerBindings),
finalContainerId,
},
{
@@ -270,9 +369,6 @@ describe("qualified rootless receipt store", () => {
baseReceiptFileSha256: mismatched.baseReceiptFileSha256,
baseReceiptPath: mismatched.baseReceiptPath,
sessionRoot: mismatched.sessionRoot,
- aggregateLimitEvidence: aggregateEvidence(
- mismatched.observerBindings,
- ),
finalContainerId,
},
{
@@ -285,4 +381,77 @@ describe("qualified rootless receipt store", () => {
),
).toThrow(/path input/u);
});
+
+ it("rejects caller-supplied evidence and conflicting terminal artifacts", () => {
+ const callerEvidence = fixture();
+ expect(() =>
+ persistQualifiedContainedRootlessReceipt(
+ {
+ baseReceiptFileSha256: callerEvidence.baseReceiptFileSha256,
+ baseReceiptPath: callerEvidence.baseReceiptPath,
+ sessionRoot: callerEvidence.sessionRoot,
+ finalContainerId,
+ aggregateLimitEvidence: callerEvidence.evidence,
+ } as unknown as Parameters<
+ typeof persistQualifiedContainedRootlessReceipt
+ >[0],
+ dependencies(callerEvidence),
+ ),
+ ).toThrow(/caller evidence is forbidden/u);
+
+ const conflict = fixture({ writeFailure: true });
+ expect(() =>
+ persistQualifiedContainedRootlessReceipt(
+ {
+ baseReceiptFileSha256: conflict.baseReceiptFileSha256,
+ baseReceiptPath: conflict.baseReceiptPath,
+ sessionRoot: conflict.sessionRoot,
+ finalContainerId,
+ },
+ dependencies(conflict),
+ ),
+ ).toThrow(/observation failed/u);
+ });
+
+ it("rejects missing evidence and evidence that diverges from the draft", () => {
+ const missing = fixture({ writeEvidence: false });
+ expect(() =>
+ persistQualifiedContainedRootlessReceipt(
+ {
+ baseReceiptFileSha256: missing.baseReceiptFileSha256,
+ baseReceiptPath: missing.baseReceiptPath,
+ sessionRoot: missing.sessionRoot,
+ finalContainerId,
+ },
+ dependencies(missing),
+ ),
+ ).toThrow();
+
+ const changed = fixture();
+ const changedEvidence = aggregateEvidence(
+ changed.observerBindings,
+ changed.finalization.receiptPayloadSha256,
+ );
+ changedEvidence.membership.memberPids = [100, 102];
+ changedEvidence.membership.memberSetSha256 = sha256CgroupBytes(
+ canonicalCgroupJson(changedEvidence.membership.memberPids),
+ );
+ const { receiptPayloadSha256: ignored, ...payload } = changedEvidence;
+ writeFileSync(
+ changed.paths.evidencePath,
+ `${JSON.stringify(hashed(payload), null, 2)}\n`,
+ { mode: 0o600 },
+ );
+ expect(() =>
+ persistQualifiedContainedRootlessReceipt(
+ {
+ baseReceiptFileSha256: changed.baseReceiptFileSha256,
+ baseReceiptPath: changed.baseReceiptPath,
+ sessionRoot: changed.sessionRoot,
+ finalContainerId,
+ },
+ dependencies(changed),
+ ),
+ ).toThrow(/observation chain changed/u);
+ });
});
From 1ea2ed83530bd469e3689226d4dca19b896bedae Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 07:43:15 +0530
Subject: [PATCH 050/143] fix(runtime): permit honest pre-draft aborts
---
.../contained-cgroup-observer-protocol.d.mts | 24 ++++++++++++-------
.../contained-cgroup-observer-protocol.mjs | 6 ++++-
...contained-cgroup-observer-protocol.test.ts | 18 ++++++++++++++
3 files changed, 39 insertions(+), 9 deletions(-)
diff --git a/scripts/contained-cgroup-observer-protocol.d.mts b/scripts/contained-cgroup-observer-protocol.d.mts
index af516812..5e119e37 100644
--- a/scripts/contained-cgroup-observer-protocol.d.mts
+++ b/scripts/contained-cgroup-observer-protocol.d.mts
@@ -67,7 +67,7 @@ export interface ContainedCgroupObserverFinalization {
imageRootfsUnchanged: boolean;
};
manifestPayloadSha256: string;
- observerDraftPayloadSha256: string;
+ observerDraftPayloadSha256: string | null;
invocationId: string;
finalContainerId: string;
timeoutObserved: boolean;
@@ -86,18 +86,26 @@ export function validateContainedCgroupObserverFinalization(
export function createContainedCgroupObserverFinalization(
manifest: ContainedCgroupObserverManifest,
input: {
- abortCode?: Exclude<
- ContainedCgroupObserverFinalization["abortCode"],
- null
- > | null;
cleanup: ContainedCgroupObserverFinalization["cleanup"];
cleanupVerified: boolean;
decisionAt?: Date;
- observerDraftPayloadSha256: string;
resultReleased: boolean;
- status: "FINALIZE" | "ABORT";
timeoutObserved: boolean;
- },
+ } & (
+ | {
+ abortCode?: null;
+ observerDraftPayloadSha256: string;
+ status: "FINALIZE";
+ }
+ | {
+ abortCode: Exclude<
+ ContainedCgroupObserverFinalization["abortCode"],
+ null
+ >;
+ observerDraftPayloadSha256: string | null;
+ status: "ABORT";
+ }
+ ),
): ContainedCgroupObserverFinalization;
export function containedCgroupQualificationPaths(input: {
diff --git a/scripts/contained-cgroup-observer-protocol.mjs b/scripts/contained-cgroup-observer-protocol.mjs
index 489ecce0..345169e1 100644
--- a/scripts/contained-cgroup-observer-protocol.mjs
+++ b/scripts/contained-cgroup-observer-protocol.mjs
@@ -232,6 +232,10 @@ export function validateContainedCgroupObserverFinalization(
finalization.cleanupVerified === true &&
everyCleanupCheckPassed;
const abortsQualification = finalization.status === "ABORT";
+ const draftBindingIsValid = finalizesQualification
+ ? sha256Pattern.test(finalization.observerDraftPayloadSha256 ?? "")
+ : finalization.observerDraftPayloadSha256 === null ||
+ sha256Pattern.test(finalization.observerDraftPayloadSha256 ?? "");
if (
finalization.schemaVersion !== "1" ||
(!finalizesQualification && !abortsQualification) ||
@@ -240,7 +244,7 @@ export function validateContainedCgroupObserverFinalization(
? finalization.abortCode !== null
: !allowedAbortCodes.has(finalization.abortCode)) ||
finalization.manifestPayloadSha256 !== manifest?.receiptPayloadSha256 ||
- !sha256Pattern.test(finalization.observerDraftPayloadSha256 ?? "") ||
+ !draftBindingIsValid ||
finalization.invocationId !== manifest?.invocationId ||
finalization.finalContainerId !== manifest?.finalContainerId ||
typeof finalization.timeoutObserved !== "boolean" ||
diff --git a/scripts/contained-cgroup-observer-protocol.test.ts b/scripts/contained-cgroup-observer-protocol.test.ts
index abd82fdf..a7829202 100644
--- a/scripts/contained-cgroup-observer-protocol.test.ts
+++ b/scripts/contained-cgroup-observer-protocol.test.ts
@@ -155,6 +155,22 @@ describe("contained cgroup observer protocol", () => {
}),
).toEqual(failClosedAbort);
+ const preDraftAbort = createContainedCgroupObserverFinalization(input, {
+ abortCode: "OBSERVER_FAILED",
+ cleanup: verifiedCleanup,
+ cleanupVerified: true,
+ decisionAt: finalizedAt,
+ observerDraftPayloadSha256: null,
+ resultReleased: false,
+ status: "ABORT",
+ timeoutObserved: false,
+ });
+ expect(
+ validateContainedCgroupObserverFinalization(preDraftAbort, input, {
+ observedAtMs: finalizedAt.getTime(),
+ }),
+ ).toEqual(preDraftAbort);
+
const mutations = [
{ ...finalized, resultReleased: true },
{ ...finalized, cleanupVerified: false },
@@ -165,6 +181,8 @@ describe("contained cgroup observer protocol", () => {
},
{ ...finalized, receiptPayloadSha256: "0".repeat(64) },
{ ...finalized, observerDraftPayloadSha256: "0" },
+ { ...finalized, observerDraftPayloadSha256: null },
+ { ...aborted, observerDraftPayloadSha256: "0" },
{ ...aborted, status: "FINALIZE" },
{ ...aborted, abortCode: "UNKNOWN" },
];
From 40db9bc842aa0206241c7ce7168f932bff0c731c Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 08:12:48 +0530
Subject: [PATCH 051/143] feat(runtime): coordinate aggregate qualification
---
...ned-cgroup-qualification-coordinator.d.mts | 118 +++
...ained-cgroup-qualification-coordinator.mjs | 719 ++++++++++++++++++
...d-cgroup-qualification-coordinator.test.ts | 582 ++++++++++++++
3 files changed, 1419 insertions(+)
create mode 100644 scripts/contained-cgroup-qualification-coordinator.d.mts
create mode 100644 scripts/contained-cgroup-qualification-coordinator.mjs
create mode 100644 scripts/contained-cgroup-qualification-coordinator.test.ts
diff --git a/scripts/contained-cgroup-qualification-coordinator.d.mts b/scripts/contained-cgroup-qualification-coordinator.d.mts
new file mode 100644
index 00000000..2ac886aa
--- /dev/null
+++ b/scripts/contained-cgroup-qualification-coordinator.d.mts
@@ -0,0 +1,118 @@
+import type { ContainedCgroupObserverManifest } from "./contained-cgroup-observer-protocol.mjs";
+
+export interface ContainedCgroupQualificationHandle {
+ readonly finalContainerId: string;
+ readonly invocationId: string;
+}
+
+export interface ContainedCgroupQualificationInput {
+ baseReceiptFileSha256: string;
+ baseReceiptPath: string;
+ baseReceiptPayloadSha256: string;
+ finalContainerId: string;
+ intendedAggregateLimits: {
+ cpuCount: number;
+ maxProcesses: number;
+ memoryBytes: number;
+ };
+ invocationId: string;
+ sanitizedSpecSha256: string;
+ sessionRoot: string;
+}
+
+export interface ContainedCgroupQualificationOutcome {
+ cleanup: {
+ containerAbsent: boolean;
+ imageRootfsAbsent: boolean;
+ imageRootfsUnchanged: boolean;
+ invocationAliasAbsent: boolean;
+ persistedAuthorityVerified: boolean;
+ readOnlyMountsUnchanged: boolean;
+ snapshotAbsent: boolean;
+ taskAbsent: boolean;
+ };
+ resultReleased: boolean;
+ runtimeFailed: boolean;
+ timeoutObserved: boolean;
+}
+
+export interface ContainedCgroupQualifiedReceipt {
+ qualifiedReceipt: Record;
+ qualifiedReceiptFileSha256: string;
+ qualifiedReceiptPath: string;
+ qualifiedReceiptPayloadSha256: string;
+ qualificationArtifacts: Record;
+}
+
+export interface ContainedCgroupQualificationCoordinator {
+ begin(
+ input: ContainedCgroupQualificationInput,
+ ): Promise;
+ waitForDraft(
+ handle: ContainedCgroupQualificationHandle,
+ ): Promise>;
+ complete(
+ handle: ContainedCgroupQualificationHandle,
+ outcome: ContainedCgroupQualificationOutcome,
+ ): Promise;
+}
+
+interface CoordinatorDependencies {
+ now?: () => Date;
+ persistQualifiedReceipt?: (
+ input: Pick<
+ ContainedCgroupQualificationInput,
+ | "baseReceiptFileSha256"
+ | "baseReceiptPath"
+ | "finalContainerId"
+ | "sessionRoot"
+ >,
+ ) => ContainedCgroupQualifiedReceipt;
+ resolveObserverBindings?: (input: { sessionRoot: string }) => {
+ driverCliSha256: string;
+ driverModuleSha256: string;
+ runtimeAttestationSha256: string;
+ runtimeSessionId: string;
+ };
+ sleep?: (milliseconds: number) => Promise;
+ startObserver?: (input: {
+ manifest: ContainedCgroupObserverManifest;
+ manifestPath: string;
+ paths: {
+ evidencePath: string;
+ failurePath: string;
+ finalizationPath: string;
+ invocationRoot: string;
+ manifestPath: string;
+ observerDraftPath: string;
+ observerReadyPath: string;
+ qualificationRoot: string;
+ sessionRoot: string;
+ };
+ runtimeSessionId: string;
+ }) => {
+ completion: Promise<{
+ code: number | null;
+ error?: Error;
+ outputExceeded: boolean;
+ signal: NodeJS.Signals | null;
+ }>;
+ isRunning(): boolean;
+ pid: number;
+ terminate(signal: NodeJS.Signals): boolean;
+ };
+ verifyQualifiedReceipt?: (input: {
+ baseReceiptFileSha256: string;
+ baseReceiptPath: string;
+ finalContainerId: string;
+ qualifiedReceiptFileSha256: string;
+ qualifiedReceiptPath: string;
+ sessionRoot: string;
+ }) => Record;
+}
+
+export function createContainedCgroupQualificationCoordinator(
+ dependencies?: CoordinatorDependencies,
+): ContainedCgroupQualificationCoordinator;
+
+export const containedCgroupQualificationCoordinator: ContainedCgroupQualificationCoordinator;
diff --git a/scripts/contained-cgroup-qualification-coordinator.mjs b/scripts/contained-cgroup-qualification-coordinator.mjs
new file mode 100644
index 00000000..d4742381
--- /dev/null
+++ b/scripts/contained-cgroup-qualification-coordinator.mjs
@@ -0,0 +1,719 @@
+import { spawn } from "node:child_process";
+import {
+ lstatSync,
+ mkdirSync,
+ readFileSync,
+ realpathSync,
+ writeFileSync,
+} from "node:fs";
+import { isAbsolute, relative, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+import {
+ canonicalCgroupJson,
+ sha256CgroupBytes,
+} from "./contained-cgroup-evidence.mjs";
+import { resolveContainedCgroupObserverBindings } from "./contained-cgroup-observer-bindings.mjs";
+import {
+ validateContainedCgroupObserverDraft,
+ validateContainedCgroupObserverFailure,
+ validateContainedCgroupObserverReady,
+} from "./contained-cgroup-observer.mjs";
+import {
+ containedCgroupQualificationPaths,
+ createContainedCgroupObserverFinalization,
+ createContainedCgroupObserverManifest,
+} from "./contained-cgroup-observer-protocol.mjs";
+import {
+ persistQualifiedContainedRootlessReceipt,
+ verifyQualifiedContainedRootlessReceipt,
+} from "./contained-qualified-receipt-store.mjs";
+import { UNQUALIFIED_AGGREGATE_LIMIT_MODE } from "./contained-qualified-rootless-receipt.mjs";
+
+const repositoryRoot = realpathSync(
+ resolve(fileURLToPath(import.meta.url), "../.."),
+);
+const maximumArtifactBytes = 1_048_576;
+const maximumObserverOutputBytes = 8_192;
+const observerArtifactTimeoutMs = 30_000;
+const observerExitTimeoutMs = 30_000;
+const observerForcedExitTimeoutMs = 2_000;
+const maximumObservationAgeMs = 5 * 60_000;
+const sha256Pattern = /^[a-f0-9]{64}$/u;
+const cleanupKeys = [
+ "containerAbsent",
+ "imageRootfsAbsent",
+ "imageRootfsUnchanged",
+ "invocationAliasAbsent",
+ "persistedAuthorityVerified",
+ "readOnlyMountsUnchanged",
+ "snapshotAbsent",
+ "taskAbsent",
+];
+
+function contained(parent, candidate) {
+ const fromParent = relative(parent, candidate);
+ return (
+ fromParent === "" ||
+ (!fromParent.startsWith("..") && !isAbsolute(fromParent))
+ );
+}
+
+function privateDirectory(path, label) {
+ const metadata = lstatSync(path);
+ if (
+ metadata.isSymbolicLink() ||
+ !metadata.isDirectory() ||
+ realpathSync(path) !== path ||
+ metadata.uid !== process.getuid() ||
+ (metadata.mode & 0o777) !== 0o700
+ ) {
+ throw new Error(`contained cgroup coordinator ${label} is invalid`);
+ }
+}
+
+function privateFile(path, label) {
+ const metadata = lstatSync(path);
+ if (
+ metadata.isSymbolicLink() ||
+ !metadata.isFile() ||
+ realpathSync(path) !== path ||
+ metadata.uid !== process.getuid() ||
+ metadata.nlink !== 1 ||
+ (metadata.mode & 0o600) !== 0o600 ||
+ (metadata.mode & 0o077) !== 0 ||
+ metadata.size < 1 ||
+ metadata.size > maximumArtifactBytes
+ ) {
+ throw new Error(`contained cgroup coordinator ${label} is invalid`);
+ }
+}
+
+function lstatOrAbsent(path) {
+ try {
+ return lstatSync(path);
+ } catch (error) {
+ if (error?.code === "ENOENT") return null;
+ throw error;
+ }
+}
+
+function writeArtifact(path, value) {
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, {
+ encoding: "utf8",
+ flag: "wx",
+ mode: 0o600,
+ });
+ privateFile(path, "artifact");
+}
+
+function readArtifact(path, label) {
+ privateFile(path, label);
+ try {
+ return JSON.parse(readFileSync(path, "utf8"));
+ } catch (error) {
+ throw new Error(`contained cgroup coordinator ${label} JSON is invalid`, {
+ cause: error,
+ });
+ }
+}
+
+function exactDate(value, label) {
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
+ throw new Error(`contained cgroup coordinator ${label} is invalid`);
+ }
+ return value;
+}
+
+function chronologicalTimestamp(value, label) {
+ const parsed = Date.parse(value);
+ if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) {
+ throw new Error(`contained cgroup coordinator ${label} is invalid`);
+ }
+ return parsed;
+}
+
+function defaultSleep(milliseconds) {
+ return new Promise((accept) => setTimeout(accept, milliseconds));
+}
+
+async function settleCompletionState() {
+ await Promise.resolve();
+ await Promise.resolve();
+}
+
+function defaultStartObserver({ manifestPath, runtimeSessionId }) {
+ const observerPath = resolve(
+ repositoryRoot,
+ "scripts/contained-cgroup-observer.mjs",
+ );
+ const child = spawn(
+ process.execPath,
+ [
+ observerPath,
+ "--session-id",
+ runtimeSessionId,
+ "--manifest",
+ manifestPath,
+ ],
+ {
+ cwd: repositoryRoot,
+ env: { LANG: "C", LC_ALL: "C", TZ: "UTC" },
+ stdio: ["ignore", "ignore", "pipe"],
+ },
+ );
+ let stderrBytes = 0;
+ let outputExceeded = false;
+ child.stderr.on("data", (chunk) => {
+ stderrBytes += Buffer.byteLength(chunk);
+ if (stderrBytes > maximumObserverOutputBytes) {
+ outputExceeded = true;
+ child.kill("SIGTERM");
+ }
+ });
+ let settled = false;
+ const completion = new Promise((accept) => {
+ const finish = (outcome) => {
+ if (settled) return;
+ settled = true;
+ accept({ ...outcome, outputExceeded });
+ };
+ child.once("error", (error) => finish({ code: null, signal: null, error }));
+ child.once("close", (code, signal) =>
+ finish({ code, signal, error: undefined }),
+ );
+ });
+ return {
+ completion,
+ isRunning: () => child.exitCode === null && child.signalCode === null,
+ pid: child.pid,
+ terminate: (signal) => child.kill(signal),
+ };
+}
+
+function validateBaseReceipt(input, observerBindings) {
+ if (
+ !isAbsolute(input.sessionRoot) ||
+ !contained(repositoryRoot, input.sessionRoot) ||
+ !sha256Pattern.test(input.baseReceiptFileSha256 ?? "") ||
+ !sha256Pattern.test(input.baseReceiptPayloadSha256 ?? "") ||
+ !sha256Pattern.test(input.finalContainerId ?? "") ||
+ !sha256Pattern.test(input.invocationId ?? "") ||
+ !sha256Pattern.test(input.sanitizedSpecSha256 ?? "")
+ ) {
+ throw new Error("contained cgroup coordinator base input is invalid");
+ }
+ const expectedPath = resolve(
+ input.sessionRoot,
+ `run/rootless-specs/${input.finalContainerId}.receipt.json`,
+ );
+ if (
+ input.baseReceiptPath !== expectedPath ||
+ input.sessionRoot !==
+ resolve(repositoryRoot, ".rt", observerBindings.runtimeSessionId)
+ ) {
+ throw new Error("contained cgroup coordinator base path is invalid");
+ }
+ privateFile(input.baseReceiptPath, "base receipt");
+ const source = readFileSync(input.baseReceiptPath);
+ let receipt;
+ try {
+ receipt = JSON.parse(source.toString("utf8"));
+ } catch (error) {
+ throw new Error("contained cgroup coordinator base JSON is invalid", {
+ cause: error,
+ });
+ }
+ const { receiptPayloadSha256, ...payload } = receipt;
+ if (
+ sha256CgroupBytes(source) !== input.baseReceiptFileSha256 ||
+ receiptPayloadSha256 !== input.baseReceiptPayloadSha256 ||
+ sha256CgroupBytes(canonicalCgroupJson(payload)) !== receiptPayloadSha256 ||
+ receipt.schemaVersion !== "4" ||
+ receipt.status !== "VALIDATED" ||
+ receipt.limitMode !== UNQUALIFIED_AGGREGATE_LIMIT_MODE ||
+ receipt.aggregateLimitIntentEnforced !== false ||
+ receipt.aggregateLimitEvidence !== null ||
+ receipt.invocationId !== input.invocationId ||
+ receipt.finalContainerId !== input.finalContainerId ||
+ receipt.sanitizedSpecSha256 !== input.sanitizedSpecSha256 ||
+ canonicalCgroupJson(receipt.intendedAggregateLimits) !==
+ canonicalCgroupJson(input.intendedAggregateLimits)
+ ) {
+ throw new Error("contained cgroup coordinator base binding changed");
+ }
+ return receipt;
+}
+
+function prepareQualificationDirectories(paths) {
+ privateDirectory(resolve(repositoryRoot, ".rt"), "runtime root");
+ privateDirectory(paths.sessionRoot, "session root");
+ try {
+ mkdirSync(paths.qualificationRoot, { mode: 0o700 });
+ } catch (error) {
+ if (error?.code !== "EEXIST") throw error;
+ }
+ privateDirectory(paths.qualificationRoot, "qualification root");
+ mkdirSync(paths.invocationRoot, { mode: 0o700 });
+ privateDirectory(paths.invocationRoot, "invocation root");
+}
+
+function processOutcomeSucceeded(outcome) {
+ return (
+ outcome?.code === 0 &&
+ outcome?.signal === null &&
+ outcome?.error === undefined &&
+ outcome?.outputExceeded === false
+ );
+}
+
+function exactOutcome(value) {
+ if (
+ value === null ||
+ typeof value !== "object" ||
+ Array.isArray(value) ||
+ JSON.stringify(Object.keys(value).sort()) !==
+ JSON.stringify(
+ [
+ "cleanup",
+ "resultReleased",
+ "runtimeFailed",
+ "timeoutObserved",
+ ].sort(),
+ )
+ ) {
+ throw new Error("contained cgroup coordinator outcome is invalid");
+ }
+ const cleanup = value.cleanup;
+ if (
+ cleanup === null ||
+ typeof cleanup !== "object" ||
+ Array.isArray(cleanup) ||
+ JSON.stringify(Object.keys(cleanup).sort()) !==
+ JSON.stringify([...cleanupKeys].sort()) ||
+ cleanupKeys.some((key) => typeof cleanup[key] !== "boolean") ||
+ typeof value.resultReleased !== "boolean" ||
+ typeof value.runtimeFailed !== "boolean" ||
+ typeof value.timeoutObserved !== "boolean"
+ ) {
+ throw new Error("contained cgroup coordinator outcome is invalid");
+ }
+ return value;
+}
+
+function finalizationDecision(state, outcome) {
+ if (state.draft === undefined) {
+ return { abortCode: "OBSERVER_FAILED", status: "ABORT" };
+ }
+ if (outcome.runtimeFailed) {
+ return { abortCode: "RUNTIME_FAILED", status: "ABORT" };
+ }
+ if (!outcome.timeoutObserved) {
+ return { abortCode: "TIMEOUT_NOT_OBSERVED", status: "ABORT" };
+ }
+ if (outcome.resultReleased) {
+ return { abortCode: "RESULT_RELEASED", status: "ABORT" };
+ }
+ if (cleanupKeys.some((key) => outcome.cleanup[key] !== true)) {
+ return { abortCode: "CLEANUP_UNVERIFIED", status: "ABORT" };
+ }
+ return { abortCode: null, status: "FINALIZE" };
+}
+
+export function createContainedCgroupQualificationCoordinator({
+ now = () => new Date(),
+ persistQualifiedReceipt = persistQualifiedContainedRootlessReceipt,
+ resolveObserverBindings = resolveContainedCgroupObserverBindings,
+ sleep = defaultSleep,
+ startObserver = defaultStartObserver,
+ verifyQualifiedReceipt = verifyQualifiedContainedRootlessReceipt,
+} = {}) {
+ const states = new WeakMap();
+
+ function normalizeCompletion(completion) {
+ return completion.then(
+ (outcome) => outcome,
+ (error) => ({
+ code: null,
+ error:
+ error instanceof Error
+ ? error
+ : new Error("contained cgroup observer completion failed"),
+ outputExceeded: false,
+ signal: null,
+ }),
+ );
+ }
+
+ async function boundedCompletion(state, timeoutMs = observerExitTimeoutMs) {
+ if (
+ state.processOutcome !== undefined &&
+ state.process.isRunning() === false
+ ) {
+ return state.processOutcome;
+ }
+ let timer;
+ try {
+ const outcome = await Promise.race([
+ state.process.completion,
+ new Promise((_, reject) => {
+ timer = setTimeout(
+ () =>
+ reject(
+ new Error("contained cgroup coordinator observer timed out"),
+ ),
+ timeoutMs,
+ );
+ }),
+ ]);
+ if (state.process.isRunning() === true) {
+ throw new Error(
+ "contained cgroup coordinator completion preceded observer exit",
+ );
+ }
+ return outcome;
+ } finally {
+ clearTimeout(timer);
+ }
+ }
+
+ async function waitForStoppedProcess(state, timeoutMs) {
+ const deadline = Date.now() + timeoutMs;
+ while (state.process.isRunning() === true && Date.now() <= deadline) {
+ await sleep(10);
+ }
+ if (state.process.isRunning() === true) {
+ throw new Error("contained cgroup coordinator observer did not exit");
+ }
+ return boundedCompletion(state, Math.max(1, deadline - Date.now()));
+ }
+
+ async function stopObserver(state) {
+ if (state.process.isRunning() === false) {
+ return boundedCompletion(state, observerForcedExitTimeoutMs);
+ }
+ let terminationError;
+ try {
+ state.process.terminate("SIGTERM");
+ } catch (error) {
+ terminationError = error;
+ }
+ try {
+ return await waitForStoppedProcess(state, observerForcedExitTimeoutMs);
+ } catch (graceError) {
+ try {
+ state.process.terminate("SIGKILL");
+ } catch (error) {
+ throw new Error(
+ "contained cgroup coordinator could not terminate observer",
+ {
+ cause: new AggregateError(
+ [terminationError, graceError, error].filter(Boolean),
+ ),
+ },
+ );
+ }
+ try {
+ return await waitForStoppedProcess(state, observerForcedExitTimeoutMs);
+ } catch (forcedError) {
+ throw new Error(
+ "contained cgroup coordinator could not reap observer",
+ {
+ cause: new AggregateError(
+ [terminationError, graceError, forcedError].filter(Boolean),
+ ),
+ },
+ );
+ }
+ }
+ }
+
+ async function stopAfterFailure(state, originalError, label) {
+ try {
+ await stopObserver(state);
+ } catch (terminationError) {
+ throw new Error(
+ `contained cgroup coordinator ${label} and observer termination failed`,
+ {
+ cause: new AggregateError([originalError, terminationError]),
+ },
+ );
+ }
+ throw originalError;
+ }
+
+ async function waitForArtifact(state, path, label, validator) {
+ const deadline = Date.now() + observerArtifactTimeoutMs;
+ while (Date.now() <= deadline) {
+ if (lstatOrAbsent(state.paths.failurePath) !== null) {
+ validateContainedCgroupObserverFailure(
+ readArtifact(state.paths.failurePath, "observer failure"),
+ state.manifest,
+ );
+ throw new Error("contained cgroup coordinator observer failed");
+ }
+ if (
+ state.processOutcome !== undefined ||
+ state.process.isRunning() !== true
+ ) {
+ throw new Error(
+ `contained cgroup coordinator observer exited before ${label}`,
+ );
+ }
+ if (lstatOrAbsent(path) !== null) {
+ return validator(readArtifact(path, label), state.manifest);
+ }
+ await sleep(10);
+ }
+ throw new Error(`contained cgroup coordinator ${label} timed out`);
+ }
+
+ async function begin(input) {
+ const observerBindings = resolveObserverBindings({
+ sessionRoot: input.sessionRoot,
+ });
+ const baseReceipt = validateBaseReceipt(input, observerBindings);
+ const paths = containedCgroupQualificationPaths({
+ repositoryRoot,
+ runtimeSessionId: observerBindings.runtimeSessionId,
+ invocationId: input.invocationId,
+ });
+ prepareQualificationDirectories(paths);
+ const requestedAt = exactDate(now(), "request time");
+ const manifest = createContainedCgroupObserverManifest({
+ baseReceiptFileSha256: input.baseReceiptFileSha256,
+ baseReceiptPayloadSha256: input.baseReceiptPayloadSha256,
+ finalContainerId: input.finalContainerId,
+ intendedAggregateLimits: input.intendedAggregateLimits,
+ invocationId: input.invocationId,
+ observerBindings,
+ requestedAt,
+ sanitizedSpecSha256: input.sanitizedSpecSha256,
+ });
+ writeArtifact(paths.manifestPath, manifest);
+ let rawProcessHandle;
+ try {
+ rawProcessHandle = startObserver({
+ manifest,
+ manifestPath: paths.manifestPath,
+ paths,
+ runtimeSessionId: observerBindings.runtimeSessionId,
+ });
+ if (
+ !Number.isSafeInteger(rawProcessHandle?.pid) ||
+ rawProcessHandle.pid < 1 ||
+ !(rawProcessHandle.completion instanceof Promise) ||
+ typeof rawProcessHandle.isRunning !== "function" ||
+ typeof rawProcessHandle.terminate !== "function"
+ ) {
+ throw new Error("contained cgroup coordinator process is invalid");
+ }
+ } catch (error) {
+ if (typeof rawProcessHandle?.terminate === "function") {
+ try {
+ if (
+ rawProcessHandle.completion instanceof Promise &&
+ typeof rawProcessHandle.isRunning === "function"
+ ) {
+ const partialState = {
+ process: {
+ ...rawProcessHandle,
+ completion: normalizeCompletion(rawProcessHandle.completion),
+ },
+ processOutcome: undefined,
+ };
+ partialState.process.completion.then((outcome) => {
+ partialState.processOutcome = outcome;
+ });
+ await stopObserver(partialState);
+ } else {
+ rawProcessHandle.terminate("SIGTERM");
+ rawProcessHandle.terminate("SIGKILL");
+ throw new Error("partial observer exit could not be verified");
+ }
+ } catch (terminationError) {
+ throw new Error(
+ "contained cgroup coordinator observer start and termination failed",
+ {
+ cause: new AggregateError([error, terminationError]),
+ },
+ );
+ }
+ }
+ throw new Error("contained cgroup coordinator observer start failed", {
+ cause: error,
+ });
+ }
+ const processHandle = {
+ ...rawProcessHandle,
+ completion: normalizeCompletion(rawProcessHandle.completion),
+ };
+ const handle = Object.freeze({
+ finalContainerId: input.finalContainerId,
+ invocationId: input.invocationId,
+ });
+ const state = {
+ baseReceipt,
+ input,
+ manifest,
+ observerBindings,
+ paths,
+ process: processHandle,
+ processOutcome: undefined,
+ };
+ states.set(handle, state);
+ processHandle.completion.then((outcome) => {
+ state.processOutcome = outcome;
+ });
+ try {
+ const ready = await waitForArtifact(
+ state,
+ paths.observerReadyPath,
+ "observer readiness",
+ validateContainedCgroupObserverReady,
+ );
+ await settleCompletionState();
+ const readinessCheckedAt = exactDate(now(), "readiness check time");
+ const requestedAtMs = chronologicalTimestamp(
+ manifest.requestedAt,
+ "request timestamp",
+ );
+ const armedAtMs = chronologicalTimestamp(
+ ready.armedAt,
+ "readiness timestamp",
+ );
+ if (
+ state.processOutcome !== undefined ||
+ processHandle.isRunning() !== true ||
+ ready.observerPid !== processHandle.pid ||
+ armedAtMs < requestedAtMs ||
+ armedAtMs > readinessCheckedAt.getTime() ||
+ readinessCheckedAt.getTime() - armedAtMs > maximumObservationAgeMs
+ ) {
+ throw new Error(
+ "contained cgroup coordinator observer readiness binding changed",
+ );
+ }
+ state.ready = ready;
+ return handle;
+ } catch (error) {
+ return stopAfterFailure(state, error, "readiness failed");
+ }
+ }
+
+ async function waitForDraft(handle) {
+ const state = states.get(handle);
+ if (state === undefined || state.ready === undefined || state.finalized) {
+ throw new Error("contained cgroup coordinator handle is invalid");
+ }
+ if (state.draft !== undefined) return state.draft;
+ try {
+ const draft = await waitForArtifact(
+ state,
+ state.paths.observerDraftPath,
+ "observer draft",
+ validateContainedCgroupObserverDraft,
+ );
+ await settleCompletionState();
+ const draftCheckedAt = exactDate(now(), "draft check time");
+ const armedAtMs = chronologicalTimestamp(
+ state.ready.armedAt,
+ "readiness timestamp",
+ );
+ const observedAtMs = chronologicalTimestamp(
+ draft.observedAt,
+ "draft timestamp",
+ );
+ if (
+ state.processOutcome !== undefined ||
+ state.process.isRunning() !== true ||
+ observedAtMs < armedAtMs ||
+ observedAtMs > draftCheckedAt.getTime() ||
+ draftCheckedAt.getTime() - observedAtMs > maximumObservationAgeMs
+ ) {
+ throw new Error(
+ "contained cgroup coordinator draft chronology changed",
+ );
+ }
+ state.draft = draft;
+ return draft;
+ } catch (error) {
+ return stopAfterFailure(state, error, "draft validation failed");
+ }
+ }
+
+ async function complete(handle, rawOutcome) {
+ const state = states.get(handle);
+ if (state === undefined || state.ready === undefined || state.finalized) {
+ throw new Error("contained cgroup coordinator handle is invalid");
+ }
+ const outcome = exactOutcome(rawOutcome);
+ const decision = finalizationDecision(state, outcome);
+ const cleanupVerified = cleanupKeys.every(
+ (key) => outcome.cleanup[key] === true,
+ );
+ const finalization = createContainedCgroupObserverFinalization(
+ state.manifest,
+ {
+ abortCode: decision.abortCode,
+ cleanup: outcome.cleanup,
+ cleanupVerified,
+ decisionAt: exactDate(now(), "decision time"),
+ observerDraftPayloadSha256: state.draft?.receiptPayloadSha256 ?? null,
+ resultReleased: outcome.resultReleased,
+ status: decision.status,
+ timeoutObserved: outcome.timeoutObserved,
+ },
+ );
+ writeArtifact(state.paths.finalizationPath, finalization);
+ state.finalized = true;
+ if (decision.status === "ABORT") {
+ const abortError = new Error(
+ `contained cgroup coordinator qualification aborted: ${decision.abortCode}`,
+ );
+ return stopAfterFailure(state, abortError, "qualification aborted");
+ }
+
+ let processOutcome;
+ try {
+ processOutcome = await boundedCompletion(state);
+ } catch (error) {
+ return stopAfterFailure(state, error, "completion failed");
+ }
+ if (!processOutcomeSucceeded(processOutcome)) {
+ return stopAfterFailure(
+ state,
+ new Error("contained cgroup coordinator observer failed"),
+ "observer failed",
+ );
+ }
+ if (
+ lstatOrAbsent(state.paths.failurePath) !== null ||
+ lstatOrAbsent(state.paths.evidencePath) === null
+ ) {
+ throw new Error(
+ "contained cgroup coordinator terminal outcome is invalid",
+ );
+ }
+ const qualified = persistQualifiedReceipt({
+ baseReceiptFileSha256: state.input.baseReceiptFileSha256,
+ baseReceiptPath: state.input.baseReceiptPath,
+ finalContainerId: state.input.finalContainerId,
+ sessionRoot: state.input.sessionRoot,
+ });
+ verifyQualifiedReceipt({
+ baseReceiptFileSha256: state.input.baseReceiptFileSha256,
+ baseReceiptPath: state.input.baseReceiptPath,
+ finalContainerId: state.input.finalContainerId,
+ qualifiedReceiptFileSha256: qualified.qualifiedReceiptFileSha256,
+ qualifiedReceiptPath: qualified.qualifiedReceiptPath,
+ sessionRoot: state.input.sessionRoot,
+ });
+ return qualified;
+ }
+
+ return Object.freeze({ begin, complete, waitForDraft });
+}
+
+export const containedCgroupQualificationCoordinator =
+ createContainedCgroupQualificationCoordinator();
diff --git a/scripts/contained-cgroup-qualification-coordinator.test.ts b/scripts/contained-cgroup-qualification-coordinator.test.ts
new file mode 100644
index 00000000..f79ceaf6
--- /dev/null
+++ b/scripts/contained-cgroup-qualification-coordinator.test.ts
@@ -0,0 +1,582 @@
+import { randomBytes } from "node:crypto";
+import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
+import { resolve } from "node:path";
+
+import { describe, expect, it } from "vitest";
+
+import {
+ canonicalCgroupJson,
+ createContainedCgroupIdentity,
+ sha256CgroupBytes,
+} from "./contained-cgroup-evidence.mjs";
+import {
+ createContainedCgroupObserverDraft,
+ createContainedCgroupObserverFailure,
+ createContainedCgroupObserverReady,
+} from "./contained-cgroup-observer.mjs";
+import { createContainedCgroupQualificationCoordinator } from "./contained-cgroup-qualification-coordinator.mjs";
+import type { ContainedCgroupObserverManifest } from "./contained-cgroup-observer-protocol.mjs";
+import { UNQUALIFIED_AGGREGATE_LIMIT_MODE } from "./contained-qualified-rootless-receipt.mjs";
+
+const root = process.cwd();
+const invocationId = "1".repeat(64);
+const finalContainerId = "2".repeat(64);
+const sanitizedSpecSha256 = "3".repeat(64);
+const intendedAggregateLimits = {
+ cpuCount: 1,
+ maxProcesses: 16,
+ memoryBytes: 512 * 1024 * 1024,
+};
+const clean = {
+ containerAbsent: true,
+ imageRootfsAbsent: true,
+ imageRootfsUnchanged: true,
+ invocationAliasAbsent: true,
+ persistedAuthorityVerified: true,
+ readOnlyMountsUnchanged: true,
+ snapshotAbsent: true,
+ taskAbsent: true,
+};
+type QualificationPaths = {
+ evidencePath: string;
+ failurePath: string;
+ finalizationPath: string;
+ invocationRoot: string;
+ manifestPath: string;
+ observerDraftPath: string;
+ observerReadyPath: string;
+ qualificationRoot: string;
+ sessionRoot: string;
+};
+
+function hashed>(payload: T) {
+ return {
+ ...payload,
+ receiptPayloadSha256: sha256CgroupBytes(canonicalCgroupJson(payload)),
+ };
+}
+
+function baseReceipt() {
+ return hashed({
+ schemaVersion: "4",
+ status: "VALIDATED",
+ limitMode: UNQUALIFIED_AGGREGATE_LIMIT_MODE,
+ aggregateLimitIntentEnforced: false,
+ invocationId,
+ stagingContainerId: "4".repeat(64),
+ finalContainerId,
+ metadataSha256: "5".repeat(64),
+ originalSpecSha256: "6".repeat(64),
+ baseSpecSha256: "7".repeat(64),
+ sanitizedSpecSha256,
+ configFileSha256: "8".repeat(64),
+ internalMountManifest: [],
+ internalMountManifestSha256: "9".repeat(64),
+ readOnlyMountManifest: [],
+ readOnlyMountManifestSha256: "a".repeat(64),
+ commandSha256: "b".repeat(64),
+ imageAuthority: {},
+ imageRootfs: {},
+ removedFields: [],
+ normalizedFields: [],
+ removedMounts: [],
+ intendedAggregateLimits,
+ enforcedRlimits: [],
+ aggregateLimitEvidence: null,
+ });
+}
+
+function observation(observedAt = "2026-07-20T12:00:10.000Z") {
+ const memberPids = [100, 101];
+ return {
+ observedLimits: {
+ cpuPeriodMicros: 100_000,
+ cpuQuotaMicros: 100_000,
+ memoryMaxBytes: intendedAggregateLimits.memoryBytes,
+ memorySwapMaxBytes: intendedAggregateLimits.memoryBytes,
+ pidsMax: intendedAggregateLimits.maxProcesses,
+ },
+ membership: {
+ descendantsObserved: true,
+ leaderPid: 100,
+ leaderStartTimeTicks: "12345",
+ memberPids,
+ memberSetSha256: sha256CgroupBytes(canonicalCgroupJson(memberPids)),
+ },
+ negativeControls: {
+ cpu: {
+ busyWindowMs: 500,
+ enforced: true,
+ nrThrottledAfter: 1,
+ nrThrottledBefore: 0,
+ throttledUsecAfter: 1,
+ throttledUsecBefore: 0,
+ },
+ memory: {
+ enforced: true,
+ oomKillAfter: 1,
+ oomKillBefore: 0,
+ requestedBytes: intendedAggregateLimits.memoryBytes + 1,
+ },
+ processes: {
+ attemptedProcesses: intendedAggregateLimits.maxProcesses + 1,
+ enforced: true,
+ maxEventsAfter: 1,
+ maxEventsBefore: 0,
+ },
+ },
+ observedAt,
+ };
+}
+
+function aggregateEvidence(
+ observerBindings: ReturnType["observerBindings"],
+ finalizationPayloadSha256: string,
+) {
+ return hashed({
+ schemaVersion: "2",
+ status: "OBSERVED",
+ authority: "linux-cgroup-v2",
+ cgroupVersion: 2,
+ cgroupId: `counterlab-v6.1-${invocationId}`,
+ cgroupPath: `counterlab-v6.1/${invocationId}`,
+ cgroupIdentity: createContainedCgroupIdentity({
+ invocationId,
+ finalContainerId,
+ sanitizedSpecSha256,
+ }),
+ invocationId,
+ finalContainerId,
+ finalizationPayloadSha256,
+ sanitizedSpecSha256,
+ runtimeAttestationSha256: observerBindings.runtimeAttestationSha256,
+ ...observation("2026-07-20T12:00:30.000Z"),
+ cleanup: { cgroupAbsentAfterTimeout: true },
+ observer: {
+ driverCliSha256: observerBindings.driverCliSha256,
+ driverModuleSha256: observerBindings.driverModuleSha256,
+ runtimeSessionId: observerBindings.runtimeSessionId,
+ },
+ });
+}
+
+function writeArtifact(path: string, value: unknown) {
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, {
+ flag: "wx",
+ mode: 0o600,
+ });
+}
+
+function deferred() {
+ let resolvePromise!: (value: T) => void;
+ let rejectPromise!: (reason?: unknown) => void;
+ let settled = false;
+ const promise = new Promise((accept, reject) => {
+ resolvePromise = accept;
+ rejectPromise = reject;
+ });
+ return {
+ get settled() {
+ return settled;
+ },
+ promise,
+ reject(reason?: unknown) {
+ settled = true;
+ rejectPromise(reason);
+ },
+ resolve(value: T) {
+ settled = true;
+ resolvePromise(value);
+ },
+ };
+}
+
+function fixture() {
+ const runtimeSessionId = `rt-coord-${randomBytes(4).toString("hex")}`;
+ const sessionRoot = resolve(root, ".rt", runtimeSessionId);
+ const specRoot = resolve(sessionRoot, "run/rootless-specs");
+ mkdirSync(specRoot, { recursive: true, mode: 0o700 });
+ const receipt = baseReceipt();
+ const source = `${JSON.stringify(receipt, null, 2)}\n`;
+ const baseReceiptPath = resolve(specRoot, `${finalContainerId}.receipt.json`);
+ writeFileSync(baseReceiptPath, source, { flag: "wx", mode: 0o600 });
+ return {
+ input: {
+ baseReceiptFileSha256: sha256CgroupBytes(source),
+ baseReceiptPath,
+ baseReceiptPayloadSha256: receipt.receiptPayloadSha256,
+ finalContainerId,
+ intendedAggregateLimits,
+ invocationId,
+ sanitizedSpecSha256,
+ sessionRoot,
+ },
+ observerBindings: {
+ driverCliSha256: "c".repeat(64),
+ driverModuleSha256: "d".repeat(64),
+ runtimeAttestationSha256: "e".repeat(64),
+ runtimeSessionId,
+ },
+ };
+}
+
+function harness({
+ keepRunningAfterCompletion = false,
+ processPid = 321,
+ readyPid = 321,
+} = {}) {
+ const value = fixture();
+ const processCompletion = deferred<{
+ code: number | null;
+ error?: Error;
+ outputExceeded: boolean;
+ signal: NodeJS.Signals | null;
+ }>();
+ let captured:
+ | {
+ manifest: ContainedCgroupObserverManifest;
+ paths: QualificationPaths;
+ }
+ | undefined;
+ const terminations: NodeJS.Signals[] = [];
+ let forcedStopped = false;
+ const persisted: Array> = [];
+ const verified: Array> = [];
+ const times = [
+ new Date("2026-07-20T12:00:00.000Z"),
+ new Date("2026-07-20T12:00:20.000Z"),
+ ];
+ const qualified = {
+ qualificationArtifacts: {},
+ qualifiedReceipt: {},
+ qualifiedReceiptFileSha256: "f".repeat(64),
+ qualifiedReceiptPath: resolve(
+ value.input.sessionRoot,
+ `run/rootless-specs/${finalContainerId}.qualified-receipt.json`,
+ ),
+ qualifiedReceiptPayloadSha256: "0".repeat(64),
+ };
+ const coordinator = createContainedCgroupQualificationCoordinator({
+ now: () => times.shift() ?? new Date("2026-07-20T12:00:20.000Z"),
+ persistQualifiedReceipt: (input) => {
+ persisted.push(input);
+ return qualified;
+ },
+ resolveObserverBindings: () => value.observerBindings,
+ sleep: async () => undefined,
+ startObserver: ({ manifest, paths }) => {
+ captured = { manifest, paths };
+ writeArtifact(
+ paths.observerReadyPath,
+ createContainedCgroupObserverReady(manifest, {
+ armedAt: new Date("2026-07-20T12:00:05.000Z"),
+ observerPid: readyPid,
+ }),
+ );
+ return {
+ completion: processCompletion.promise,
+ isRunning: () =>
+ !forcedStopped &&
+ (!processCompletion.settled || keepRunningAfterCompletion),
+ pid: processPid,
+ terminate: (signal: NodeJS.Signals) => {
+ terminations.push(signal);
+ forcedStopped = true;
+ processCompletion.resolve({
+ code: null,
+ outputExceeded: false,
+ signal,
+ });
+ return true;
+ },
+ };
+ },
+ verifyQualifiedReceipt: (input) => {
+ verified.push(input);
+ return {};
+ },
+ });
+ return {
+ coordinator,
+ get captured() {
+ if (captured === undefined) throw new Error("observer was not started");
+ return captured;
+ },
+ persisted,
+ processCompletion,
+ qualified,
+ terminations,
+ value,
+ verified,
+ };
+}
+
+async function preparedHarness() {
+ const value = harness();
+ const handle = await value.coordinator.begin(value.value.input);
+ const draft = createContainedCgroupObserverDraft(
+ value.captured.manifest,
+ observation(),
+ );
+ writeArtifact(value.captured.paths.observerDraftPath, draft);
+ await value.coordinator.waitForDraft(handle);
+ return { ...value, draft, handle };
+}
+
+describe("contained cgroup qualification coordinator", () => {
+ it("orders readiness, draft, finalization, process exit, and persistence", async () => {
+ const value = await preparedHarness();
+ const completing = value.coordinator.complete(value.handle, {
+ cleanup: clean,
+ resultReleased: false,
+ runtimeFailed: false,
+ timeoutObserved: true,
+ });
+ const finalization = JSON.parse(
+ readFileSync(value.captured.paths.finalizationPath, "utf8"),
+ );
+ expect(finalization.status).toBe("FINALIZE");
+ expect(finalization.observerDraftPayloadSha256).toBe(
+ value.draft.receiptPayloadSha256,
+ );
+ writeArtifact(
+ value.captured.paths.evidencePath,
+ aggregateEvidence(
+ value.value.observerBindings,
+ finalization.receiptPayloadSha256,
+ ),
+ );
+ value.processCompletion.resolve({
+ code: 0,
+ outputExceeded: false,
+ signal: null,
+ });
+
+ await expect(completing).resolves.toEqual(value.qualified);
+ expect(value.persisted).toEqual([
+ {
+ baseReceiptFileSha256: value.value.input.baseReceiptFileSha256,
+ baseReceiptPath: value.value.input.baseReceiptPath,
+ finalContainerId,
+ sessionRoot: value.value.input.sessionRoot,
+ },
+ ]);
+ expect(value.verified).toHaveLength(1);
+ expect(value.terminations).toEqual([]);
+ });
+
+ it("rejects a ready receipt from a different process", async () => {
+ const value = harness({ readyPid: 322 });
+ await expect(value.coordinator.begin(value.value.input)).rejects.toThrow(
+ /readiness binding changed/u,
+ );
+ expect(value.terminations).toEqual(["SIGTERM"]);
+ expect(value.persisted).toEqual([]);
+ });
+
+ it("terminates a partially invalid observer handle", async () => {
+ const value = harness({ processPid: 0 });
+ await expect(value.coordinator.begin(value.value.input)).rejects.toThrow(
+ /observer start failed/u,
+ );
+ expect(value.terminations).toEqual(["SIGTERM"]);
+ expect(value.persisted).toEqual([]);
+ });
+
+ it("rejects an observer that exits or rejects before readiness", async () => {
+ const exited = harness();
+ exited.processCompletion.resolve({
+ code: 0,
+ outputExceeded: false,
+ signal: null,
+ });
+ await expect(exited.coordinator.begin(exited.value.input)).rejects.toThrow(
+ /readiness|exited/u,
+ );
+ expect(exited.persisted).toEqual([]);
+
+ const rejected = harness();
+ rejected.processCompletion.reject(new Error("observer spawn failed"));
+ await expect(
+ rejected.coordinator.begin(rejected.value.input),
+ ).rejects.toThrow(/readiness|exited/u);
+ expect(rejected.persisted).toEqual([]);
+
+ const rejectedButRunning = harness({
+ keepRunningAfterCompletion: true,
+ });
+ rejectedButRunning.processCompletion.reject(
+ new Error("observer completion channel failed"),
+ );
+ await expect(
+ rejectedButRunning.coordinator.begin(rejectedButRunning.value.input),
+ ).rejects.toThrow(/readiness|exited/u);
+ expect(rejectedButRunning.terminations).toEqual(["SIGTERM"]);
+ });
+
+ it("terminates the observer when draft validation fails", async () => {
+ const value = harness();
+ const handle = await value.coordinator.begin(value.value.input);
+ writeArtifact(value.captured.paths.observerDraftPath, {});
+ await expect(value.coordinator.waitForDraft(handle)).rejects.toThrow(
+ /draft/u,
+ );
+ expect(value.terminations).toEqual(["SIGTERM"]);
+ expect(value.persisted).toEqual([]);
+ });
+
+ it("records an honest pre-draft abort after observer failure", async () => {
+ const value = harness();
+ const handle = await value.coordinator.begin(value.value.input);
+ writeArtifact(
+ value.captured.paths.failurePath,
+ createContainedCgroupObserverFailure(value.captured.manifest, {
+ failedAt: new Date("2026-07-20T12:00:10.000Z"),
+ }),
+ );
+ value.processCompletion.resolve({
+ code: 70,
+ outputExceeded: false,
+ signal: null,
+ });
+ await expect(value.coordinator.waitForDraft(handle)).rejects.toThrow(
+ /observer failed/u,
+ );
+ await expect(
+ value.coordinator.complete(handle, {
+ cleanup: clean,
+ resultReleased: false,
+ runtimeFailed: true,
+ timeoutObserved: false,
+ }),
+ ).rejects.toThrow(/OBSERVER_FAILED/u);
+ const finalization = JSON.parse(
+ readFileSync(value.captured.paths.finalizationPath, "utf8"),
+ );
+ expect(finalization).toMatchObject({
+ abortCode: "OBSERVER_FAILED",
+ observerDraftPayloadSha256: null,
+ status: "ABORT",
+ });
+ expect(value.persisted).toEqual([]);
+ });
+
+ it.each([
+ {
+ expected: "TIMEOUT_NOT_OBSERVED",
+ outcome: {
+ cleanup: clean,
+ resultReleased: false,
+ runtimeFailed: false,
+ timeoutObserved: false,
+ },
+ },
+ {
+ expected: "RESULT_RELEASED",
+ outcome: {
+ cleanup: clean,
+ resultReleased: true,
+ runtimeFailed: false,
+ timeoutObserved: true,
+ },
+ },
+ {
+ expected: "CLEANUP_UNVERIFIED",
+ outcome: {
+ cleanup: { ...clean, snapshotAbsent: false },
+ resultReleased: false,
+ runtimeFailed: false,
+ timeoutObserved: true,
+ },
+ },
+ ])("fails closed with $expected", async ({ expected, outcome }) => {
+ const value = await preparedHarness();
+ await expect(
+ value.coordinator.complete(value.handle, outcome),
+ ).rejects.toThrow(expected);
+ const finalization = JSON.parse(
+ readFileSync(value.captured.paths.finalizationPath, "utf8"),
+ );
+ expect(finalization.abortCode).toBe(expected);
+ expect(finalization.observerDraftPayloadSha256).toBe(
+ value.draft.receiptPayloadSha256,
+ );
+ expect(value.persisted).toEqual([]);
+ });
+
+ it("rejects conflicting evidence and failure after observer exit", async () => {
+ const value = await preparedHarness();
+ const completing = value.coordinator.complete(value.handle, {
+ cleanup: clean,
+ resultReleased: false,
+ runtimeFailed: false,
+ timeoutObserved: true,
+ });
+ const finalization = JSON.parse(
+ readFileSync(value.captured.paths.finalizationPath, "utf8"),
+ );
+ writeArtifact(
+ value.captured.paths.evidencePath,
+ aggregateEvidence(
+ value.value.observerBindings,
+ finalization.receiptPayloadSha256,
+ ),
+ );
+ writeArtifact(
+ value.captured.paths.failurePath,
+ createContainedCgroupObserverFailure(value.captured.manifest, {
+ failedAt: new Date("2026-07-20T12:00:30.000Z"),
+ }),
+ );
+ value.processCompletion.resolve({
+ code: 0,
+ outputExceeded: false,
+ signal: null,
+ });
+
+ await expect(completing).rejects.toThrow(/terminal outcome/u);
+ expect(value.persisted).toEqual([]);
+ });
+
+ it("rejects missing evidence and a nonzero terminal exit", async () => {
+ const missing = await preparedHarness();
+ const missingCompletion = missing.coordinator.complete(missing.handle, {
+ cleanup: clean,
+ resultReleased: false,
+ runtimeFailed: false,
+ timeoutObserved: true,
+ });
+ missing.processCompletion.resolve({
+ code: 0,
+ outputExceeded: false,
+ signal: null,
+ });
+ await expect(missingCompletion).rejects.toThrow(/terminal outcome/u);
+ expect(missing.persisted).toEqual([]);
+
+ const failed = await preparedHarness();
+ const failedCompletion = failed.coordinator.complete(failed.handle, {
+ cleanup: clean,
+ resultReleased: false,
+ runtimeFailed: false,
+ timeoutObserved: true,
+ });
+ const finalization = JSON.parse(
+ readFileSync(failed.captured.paths.finalizationPath, "utf8"),
+ );
+ writeArtifact(
+ failed.captured.paths.evidencePath,
+ aggregateEvidence(
+ failed.value.observerBindings,
+ finalization.receiptPayloadSha256,
+ ),
+ );
+ failed.processCompletion.resolve({
+ code: 70,
+ outputExceeded: false,
+ signal: null,
+ });
+ await expect(failedCompletion).rejects.toThrow(/observer failed/u);
+ expect(failed.persisted).toEqual([]);
+ });
+});
From eaf4a60e0c9654e6673fb15c289729474041815e Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 08:31:52 +0530
Subject: [PATCH 052/143] feat(runtime): wire aggregate qualification
---
scripts/contained-runtime-linearizer.d.mts | 5 +
scripts/contained-runtime-linearizer.mjs | 19 +
scripts/contained-runtime-linearizer.test.ts | 53 +++
scripts/contained-runtime-run.d.mts | 21 +-
scripts/contained-runtime-run.mjs | 123 +++++-
scripts/contained-runtime-server.mjs | 222 +++++-----
scripts/contained-runtime.test.ts | 431 ++++++++++++++++++-
7 files changed, 755 insertions(+), 119 deletions(-)
create mode 100644 scripts/contained-runtime-linearizer.d.mts
create mode 100644 scripts/contained-runtime-linearizer.mjs
create mode 100644 scripts/contained-runtime-linearizer.test.ts
diff --git a/scripts/contained-runtime-linearizer.d.mts b/scripts/contained-runtime-linearizer.d.mts
new file mode 100644
index 00000000..6b4f51d4
--- /dev/null
+++ b/scripts/contained-runtime-linearizer.d.mts
@@ -0,0 +1,5 @@
+export interface ContainedRuntimeLinearizer {
+ run(task: () => T | Promise): Promise;
+}
+
+export function createContainedRuntimeLinearizer(): ContainedRuntimeLinearizer;
diff --git a/scripts/contained-runtime-linearizer.mjs b/scripts/contained-runtime-linearizer.mjs
new file mode 100644
index 00000000..14213402
--- /dev/null
+++ b/scripts/contained-runtime-linearizer.mjs
@@ -0,0 +1,19 @@
+export function createContainedRuntimeLinearizer() {
+ let tail = Promise.resolve();
+
+ return Object.freeze({
+ run(task) {
+ if (typeof task !== "function") {
+ return Promise.reject(
+ new Error("contained runtime linearized task is invalid"),
+ );
+ }
+ const current = tail.then(task, task);
+ tail = current.then(
+ () => undefined,
+ () => undefined,
+ );
+ return current;
+ },
+ });
+}
diff --git a/scripts/contained-runtime-linearizer.test.ts b/scripts/contained-runtime-linearizer.test.ts
new file mode 100644
index 00000000..40e00f50
--- /dev/null
+++ b/scripts/contained-runtime-linearizer.test.ts
@@ -0,0 +1,53 @@
+import { describe, expect, it } from "vitest";
+
+import { createContainedRuntimeLinearizer } from "./contained-runtime-linearizer.mjs";
+
+describe("contained runtime request linearizer", () => {
+ it("does not begin a drain until an accepted run reaches its terminal response", async () => {
+ const linearizer = createContainedRuntimeLinearizer();
+ const events: string[] = [];
+ let releaseRun!: () => void;
+ const runCanFinish = new Promise((resolve) => {
+ releaseRun = resolve;
+ });
+
+ const run = linearizer.run(async () => {
+ events.push("run-started");
+ await runCanFinish;
+ events.push("run-terminal-response");
+ });
+ const drain = linearizer.run(() => {
+ events.push("drain-started");
+ });
+
+ await Promise.resolve();
+ expect(events).toEqual(["run-started"]);
+ releaseRun();
+ await Promise.all([run, drain]);
+ expect(events).toEqual([
+ "run-started",
+ "run-terminal-response",
+ "drain-started",
+ ]);
+ });
+
+ it("continues serializing after a failed request", async () => {
+ const linearizer = createContainedRuntimeLinearizer();
+ const failed = linearizer.run(() => {
+ throw new Error("request failed");
+ });
+ const next = linearizer.run(() => "next response");
+
+ await expect(failed).rejects.toThrow("request failed");
+ await expect(next).resolves.toBe("next response");
+ });
+
+ it("rejects invalid tasks without poisoning the queue", async () => {
+ const linearizer = createContainedRuntimeLinearizer();
+
+ await expect(linearizer.run(null as never)).rejects.toThrow(
+ "linearized task is invalid",
+ );
+ await expect(linearizer.run(() => "ready")).resolves.toBe("ready");
+ });
+});
diff --git a/scripts/contained-runtime-run.d.mts b/scripts/contained-runtime-run.d.mts
index fcdba8a8..90c4b336 100644
--- a/scripts/contained-runtime-run.d.mts
+++ b/scripts/contained-runtime-run.d.mts
@@ -1,3 +1,6 @@
+import type { ContainedCgroupQualificationCoordinator } from "./contained-cgroup-qualification-coordinator.mjs";
+import type { ContainedRuntimeQualificationMode } from "./contained-runtime-request.mjs";
+
export interface ContainedRuntimeCommand {
program: string;
args: string[];
@@ -56,6 +59,7 @@ export type ContainedRuntimeRunContext = Omit<
> & {
cwd: string;
environment: NodeJS.ProcessEnv;
+ qualificationMode: ContainedRuntimeQualificationMode;
stdin: Buffer;
};
@@ -66,8 +70,7 @@ export interface ContainedRuntimeSpawnResult {
error?: Error & { code?: string };
}
-export interface ContainedRunControlReceipt {
- schemaVersion: "2";
+interface ContainedRunControlReceiptBase {
status: "TIMED_OUT_CLEAN" | "TIMED_OUT_UNCLEAN";
timeoutKind: "WALL_CLOCK";
runtimePolicySha256: string;
@@ -92,6 +95,15 @@ export interface ContainedRunControlReceipt {
receiptPayloadSha256: string;
}
+export type ContainedRunControlReceipt =
+ | (ContainedRunControlReceiptBase & {
+ schemaVersion: "2";
+ })
+ | (ContainedRunControlReceiptBase & {
+ schemaVersion: "3";
+ qualificationMode: "aggregate-timeout-proof-v1";
+ });
+
export type ContainedRuntimeSpawn = (
program: string,
args: string[],
@@ -161,9 +173,10 @@ export declare function executeContainedRun(
}) => void,
createInvocationId?: () => string,
now?: () => number,
-): {
+ qualificationCoordinator?: ContainedCgroupQualificationCoordinator,
+): Promise<{
status: number;
stdout: Buffer;
stderr: Buffer;
controlReceipt?: ContainedRunControlReceipt;
-};
+}>;
diff --git a/scripts/contained-runtime-run.mjs b/scripts/contained-runtime-run.mjs
index f414fc01..c22aee13 100644
--- a/scripts/contained-runtime-run.mjs
+++ b/scripts/contained-runtime-run.mjs
@@ -13,6 +13,7 @@ import { isAbsolute, relative, resolve } from "node:path";
import { performance } from "node:perf_hooks";
import { fileURLToPath } from "node:url";
+import { containedCgroupQualificationCoordinator } from "./contained-cgroup-qualification-coordinator.mjs";
import {
persistContainedRootlessSpec,
sanitizeContainedRootlessSpec,
@@ -28,6 +29,7 @@ import {
validateContainedImageAliasTarget,
verifyContainedReadOnlyMounts,
} from "./contained-image-authority.mjs";
+import { AGGREGATE_TIMEOUT_QUALIFICATION_MODE } from "./contained-runtime-request.mjs";
const repositoryRoot = realpathSync(
resolve(fileURLToPath(import.meta.url), "../.."),
@@ -99,7 +101,7 @@ function canonicalJson(value) {
return JSON.stringify(value);
}
-const runControlReceiptKeys = [
+const runControlReceiptV2Keys = [
"candidateWallSeconds",
"cleanupReserveMs",
"commandSha256",
@@ -124,14 +126,24 @@ const runControlReceiptKeys = [
"timeoutKind",
"runtimePolicySha256",
];
+const runControlReceiptV3Keys = [
+ ...runControlReceiptV2Keys,
+ "qualificationMode",
+];
export function validateContainedRunControlReceipt(value) {
if (
value === null ||
typeof value !== "object" ||
Array.isArray(value) ||
- JSON.stringify(Object.keys(value).sort()) !==
- JSON.stringify([...runControlReceiptKeys].sort())
+ !(
+ (value.schemaVersion === "2" &&
+ JSON.stringify(Object.keys(value).sort()) ===
+ JSON.stringify([...runControlReceiptV2Keys].sort())) ||
+ (value.schemaVersion === "3" &&
+ JSON.stringify(Object.keys(value).sort()) ===
+ JSON.stringify([...runControlReceiptV3Keys].sort()))
+ )
) {
throw new Error("contained runtime control receipt shape is invalid");
}
@@ -148,7 +160,9 @@ export function validateContainedRunControlReceipt(value) {
"timeoutObserved",
];
if (
- value.schemaVersion !== "2" ||
+ !["2", "3"].includes(value.schemaVersion) ||
+ (value.schemaVersion === "3" &&
+ value.qualificationMode !== AGGREGATE_TIMEOUT_QUALIFICATION_MODE) ||
!["TIMED_OUT_CLEAN", "TIMED_OUT_UNCLEAN"].includes(value.status) ||
!invocationIdPattern.test(value.invocationId ?? "") ||
!containerIdPattern.test(value.finalContainerId ?? "") ||
@@ -988,14 +1002,23 @@ function resultWasReleased(plan, started) {
);
}
-export function executeContainedRun(
+export async function executeContainedRun(
context,
spawn = spawnSync,
persistSpec = persistContainedRootlessSpec,
verifySpec = verifyPersistedContainedRootlessSpec,
createInvocationId = () => randomBytes(32).toString("hex"),
now = () => performance.now(),
+ qualificationCoordinator = containedCgroupQualificationCoordinator,
) {
+ if (
+ context.qualificationMode !== null &&
+ context.qualificationMode !== AGGREGATE_TIMEOUT_QUALIFICATION_MODE
+ ) {
+ return failedResult("contained runtime qualification mode is invalid");
+ }
+ const qualificationRequested =
+ context.qualificationMode === AGGREGATE_TIMEOUT_QUALIFICATION_MODE;
const startedAtMs = now();
const invocationId = createInvocationId();
if (!invocationIdPattern.test(invocationId)) {
@@ -1408,6 +1431,26 @@ export function executeContainedRun(
);
}
+ let qualificationHandle;
+ if (qualificationRequested) {
+ try {
+ qualificationHandle = await qualificationCoordinator.begin({
+ baseReceiptFileSha256: persisted.receiptFileSha256,
+ baseReceiptPath: persisted.receiptPath,
+ baseReceiptPayloadSha256: prepared.receipt.receiptPayloadSha256,
+ finalContainerId: prepared.finalContainerId,
+ intendedAggregateLimits: prepared.receipt.intendedAggregateLimits,
+ invocationId,
+ sanitizedSpecSha256: prepared.receipt.sanitizedSpecSha256,
+ sessionRoot: context.sessionRoot,
+ });
+ } catch {
+ return finish(
+ failedResult("contained runtime qualification readiness failed"),
+ );
+ }
+ }
+
const candidateStartedAt = now();
const started = executeSpawn(
plan.start.program,
@@ -1424,6 +1467,14 @@ export function executeContainedRun(
{ ...runtimeOptions, input: context.stdin },
);
const timeoutObserved = started.error?.code === "ETIMEDOUT";
+ let qualificationDraftFailed = false;
+ if (qualificationRequested) {
+ try {
+ await qualificationCoordinator.waitForDraft(qualificationHandle);
+ } catch {
+ qualificationDraftFailed = true;
+ }
+ }
let imageRootfsUnchanged = true;
try {
const afterSha256 = snapshotDiffSha256(
@@ -1548,6 +1599,68 @@ export function executeContainedRun(
rootfsCleaned.diagnostics,
),
});
+ if (qualificationRequested) {
+ let resultReleased = true;
+ try {
+ resultReleased = resultWasReleased(plan, started);
+ } catch {
+ resultReleased = true;
+ }
+ const taskAbsent = cleaned.state.valid && !cleaned.state.taskPresent;
+ const containerAbsent =
+ cleaned.state.valid && !cleaned.state.containerPresent;
+ const snapshotAbsent =
+ cleaned.state.valid && !cleaned.state.snapshotPresent;
+ let qualified;
+ try {
+ qualified = await qualificationCoordinator.complete(qualificationHandle, {
+ cleanup: {
+ taskAbsent,
+ containerAbsent,
+ snapshotAbsent,
+ invocationAliasAbsent: lastAliasCleanup.absent === true,
+ imageRootfsAbsent: lastRootfsCleanup.absent === true,
+ persistedAuthorityVerified: persistedAfterRun,
+ readOnlyMountsUnchanged: readOnlyMountsAfterRun,
+ imageRootfsUnchanged,
+ },
+ resultReleased,
+ runtimeFailed: qualificationDraftFailed,
+ timeoutObserved,
+ });
+ } catch {
+ return failedResult("contained runtime aggregate qualification failed");
+ }
+ const controlPayload = {
+ schemaVersion: "3",
+ qualificationMode: AGGREGATE_TIMEOUT_QUALIFICATION_MODE,
+ status: "TIMED_OUT_CLEAN",
+ timeoutKind: "WALL_CLOCK",
+ runtimePolicySha256: CONTAINED_RUNTIME_POLICY_SHA256,
+ invocationId,
+ finalContainerId: prepared.finalContainerId,
+ commandSha256: imageAuthority.commandSha256,
+ rootlessReceiptFileSha256: qualified.qualifiedReceiptFileSha256,
+ rootlessReceiptPayloadSha256: qualified.qualifiedReceiptPayloadSha256,
+ timeoutObserved: true,
+ candidateWallSeconds: cpuLimit,
+ elapsedMs: Math.max(1, Math.floor(now() - candidateStartedAt)),
+ cleanupReserveMs,
+ taskAbsent,
+ containerAbsent,
+ snapshotAbsent,
+ invocationAliasAbsent: lastAliasCleanup.absent === true,
+ imageRootfsAbsent: lastRootfsCleanup.absent === true,
+ persistedAuthorityVerified: persistedAfterRun,
+ readOnlyMountsUnchanged: readOnlyMountsAfterRun,
+ imageRootfsUnchanged,
+ resultReleased,
+ };
+ return {
+ ...finished,
+ controlReceipt: createRunControlReceipt(controlPayload),
+ };
+ }
if (!timeoutObserved || plan.outputDirectory === undefined) return finished;
let resultReleased = true;
diff --git a/scripts/contained-runtime-server.mjs b/scripts/contained-runtime-server.mjs
index 8c50187d..446c3b04 100644
--- a/scripts/contained-runtime-server.mjs
+++ b/scripts/contained-runtime-server.mjs
@@ -24,6 +24,7 @@ import {
probeContainedShimSocketDirectory,
} from "./contained-containerd-config.mjs";
import { createContainedRuntimeEnvironment } from "./contained-runtime-environment.mjs";
+import { createContainedRuntimeLinearizer } from "./contained-runtime-linearizer.mjs";
import { parseContainedRuntimeRequest } from "./contained-runtime-request.mjs";
import { verifyPersistedContainedRootlessSpec } from "./contained-rootless-spec.mjs";
@@ -108,6 +109,7 @@ const environment = createContainedRuntimeEnvironment({
let runtimeState = "ACTIVE";
let drainAttestationSha256;
let drainReceipt;
+const requestLinearizer = createContainedRuntimeLinearizer();
function lines(value) {
return String(value ?? "")
@@ -403,119 +405,129 @@ const server = createServer((socket) => {
socket.on("timeout", () => socket.destroy());
socket.on("error", () => undefined);
socket.on("end", () => {
- let response;
- try {
- const request = parseContainedRuntimeRequest(
- JSON.parse(Buffer.concat(chunks).toString("utf8")),
- );
- const stdin = request.stdin;
- if (request.args[0] === "counterlab-drain") {
- const receipt = createDrainReceipt(request.args, stdin);
- response = {
- schemaVersion: "1",
- exitCode: 0,
- stdoutBase64: Buffer.from(
- `${canonicalJson(receipt)}\n`,
- "utf8",
- ).toString("base64"),
- stderrBase64: "",
- };
- } else {
- if (runtimeState !== "ACTIVE") {
- throw new Error(
- runtimeState === "DRAINED"
- ? "contained runtime is drained"
- : "contained runtime is draining",
- );
- }
- const validation = spawnSync(
- process.execPath,
- [
- resolve(root, "scripts/validate-contained-runtime-command.mjs"),
- ...request.args,
- ],
- {
- cwd: root,
- env: environment,
- encoding: "utf8",
- stdio: ["ignore", "pipe", "pipe"],
- timeout: 10_000,
- },
+ void requestLinearizer.run(async () => {
+ let response;
+ try {
+ const request = parseContainedRuntimeRequest(
+ JSON.parse(Buffer.concat(chunks).toString("utf8")),
);
- if (validation.status !== 0) {
- throw new Error(
- "contained runtime command failed independent validation",
+ const stdin = request.stdin;
+ if (request.args[0] === "counterlab-drain") {
+ let receipt;
+ try {
+ receipt = createDrainReceipt(request.args, stdin);
+ } catch (error) {
+ if (runtimeState === "DRAINING" && drainReceipt === undefined) {
+ runtimeState = "ACTIVE";
+ }
+ throw error;
+ }
+ response = {
+ schemaVersion: "1",
+ exitCode: 0,
+ stdoutBase64: Buffer.from(
+ `${canonicalJson(receipt)}\n`,
+ "utf8",
+ ).toString("base64"),
+ stderrBase64: "",
+ };
+ } else {
+ if (runtimeState !== "ACTIVE") {
+ throw new Error(
+ runtimeState === "DRAINED"
+ ? "contained runtime is drained"
+ : "contained runtime is draining",
+ );
+ }
+ const validation = spawnSync(
+ process.execPath,
+ [
+ resolve(root, "scripts/validate-contained-runtime-command.mjs"),
+ ...request.args,
+ ],
+ {
+ cwd: root,
+ env: environment,
+ encoding: "utf8",
+ stdio: ["ignore", "pipe", "pipe"],
+ timeout: 10_000,
+ },
);
- }
- const result =
- request.args[0] === "run"
- ? executeContainedRun({
- args: request.args,
- binRoot,
- clientFifoRoot,
- containerdSocket,
- cwd: root,
- environment,
- installRoot,
- qualificationMode: request.qualificationMode,
- sessionRoot,
- stdin,
- })
- : spawnSync(
- resolve(binRoot, "nerdctl"),
- [
- "--address",
+ if (validation.status !== 0) {
+ throw new Error(
+ "contained runtime command failed independent validation",
+ );
+ }
+ const result =
+ request.args[0] === "run"
+ ? await executeContainedRun({
+ args: request.args,
+ binRoot,
+ clientFifoRoot,
containerdSocket,
- "--namespace",
- "counterlab-v6.1",
- "--snapshotter",
- "native",
- "--data-root",
- resolve(sessionRoot, "data/nerdctl"),
- "--cgroup-manager",
- "cgroupfs",
- "--cni-path",
- resolve(installRoot, "libexec/cni"),
- "--cni-netconfpath",
- resolve(sessionRoot, "config/cni"),
- "--hosts-dir",
- resolve(sessionRoot, "config/certs.d"),
- "--experimental=false",
- ...request.args,
- ],
- {
cwd: root,
- env: environment,
- input: stdin,
- encoding: null,
- stdio: ["pipe", "pipe", "pipe"],
- timeout: 1_800_000,
- maxBuffer: 32 * 1024 * 1024,
- },
- );
+ environment,
+ installRoot,
+ qualificationMode: request.qualificationMode,
+ sessionRoot,
+ stdin,
+ })
+ : spawnSync(
+ resolve(binRoot, "nerdctl"),
+ [
+ "--address",
+ containerdSocket,
+ "--namespace",
+ "counterlab-v6.1",
+ "--snapshotter",
+ "native",
+ "--data-root",
+ resolve(sessionRoot, "data/nerdctl"),
+ "--cgroup-manager",
+ "cgroupfs",
+ "--cni-path",
+ resolve(installRoot, "libexec/cni"),
+ "--cni-netconfpath",
+ resolve(sessionRoot, "config/cni"),
+ "--hosts-dir",
+ resolve(sessionRoot, "config/certs.d"),
+ "--experimental=false",
+ ...request.args,
+ ],
+ {
+ cwd: root,
+ env: environment,
+ input: stdin,
+ encoding: null,
+ stdio: ["pipe", "pipe", "pipe"],
+ timeout: 1_800_000,
+ maxBuffer: 32 * 1024 * 1024,
+ },
+ );
+ response = {
+ schemaVersion: "1",
+ exitCode: result.status ?? 1,
+ stdoutBase64: Buffer.from(result.stdout ?? "").toString("base64"),
+ stderrBase64: Buffer.from(result.stderr ?? "").toString("base64"),
+ ...(result.controlReceipt === undefined
+ ? {}
+ : { runControlReceipt: result.controlReceipt }),
+ };
+ }
+ } catch (error) {
response = {
schemaVersion: "1",
- exitCode: result.status ?? 1,
- stdoutBase64: Buffer.from(result.stdout ?? "").toString("base64"),
- stderrBase64: Buffer.from(result.stderr ?? "").toString("base64"),
- ...(result.controlReceipt === undefined
- ? {}
- : { runControlReceipt: result.controlReceipt }),
+ exitCode: 1,
+ stdoutBase64: "",
+ stderrBase64: Buffer.from(
+ error instanceof Error
+ ? `${error.message}\n`
+ : "contained runtime failure\n",
+ ).toString("base64"),
};
}
- } catch (error) {
- response = {
- schemaVersion: "1",
- exitCode: 1,
- stdoutBase64: "",
- stderrBase64: Buffer.from(
- error instanceof Error
- ? `${error.message}\n`
- : "contained runtime failure\n",
- ).toString("base64"),
- };
- }
- socket.end(JSON.stringify(response));
+ socket.end(JSON.stringify(response));
+ });
});
});
diff --git a/scripts/contained-runtime.test.ts b/scripts/contained-runtime.test.ts
index e9bb9308..f446ed42 100644
--- a/scripts/contained-runtime.test.ts
+++ b/scripts/contained-runtime.test.ts
@@ -44,6 +44,7 @@ import {
renderContainedContainerdConfig,
} from "./contained-containerd-config.mjs";
import { createContainedRuntimeEnvironment } from "./contained-runtime-environment.mjs";
+import { AGGREGATE_TIMEOUT_QUALIFICATION_MODE } from "./contained-runtime-request.mjs";
const root = process.cwd();
const validator = resolve(
@@ -778,6 +779,207 @@ function absentInspectionResponses() {
return [successful(), missing(), missing()];
}
+function qualifiedRuntimeHarness() {
+ const sessionRoot = resolve(root, ".rt/rt-validator-qualified");
+ const installRoot = resolve(
+ root,
+ "node_modules/.cache/counterlab-v6.1/rootless-tools/install-v2.3.1",
+ );
+ const command = boundedAdapterCommand();
+ const plan = containedRunPlan({
+ args: command,
+ binRoot: resolve(installRoot, "bin"),
+ clientFifoRoot: resolve(sessionRoot, "run/client-fifo"),
+ containerdSocket: resolve(sessionRoot, "run/containerd.sock"),
+ installRoot,
+ invocationId,
+ sessionRoot,
+ });
+ const fixture = imageFixture(command);
+ const stagingContainerId = "d".repeat(64);
+ let finalContainerId = "";
+ const alias = plan.imageAlias;
+ const imageRootfsPath = resolve(
+ sessionRoot,
+ `run/rootless-specs/${invocationId}.image-rootfs`,
+ );
+ const events: string[] = [];
+ let aliasPresent = false;
+ let imageRootfsMounted = false;
+ let stagingPresent = true;
+ let taskPresent = false;
+ let containerPresent = false;
+ let baseSpecSha256 = "";
+
+ writeFileSync(resolve(output, "public-tests.stdout"), "", { mode: 0o600 });
+ writeFileSync(resolve(output, "public-tests.stderr"), "", { mode: 0o600 });
+
+ const fakeSpawn = (
+ _program: string,
+ args: string[],
+ _options: { timeout: number },
+ ) => {
+ const joined = args.join(" ");
+ if (joined.includes("image inspect docker.io/library/counterlab-adapter")) {
+ return successful(fixture.targetSource);
+ }
+ if (joined.includes(`content get ${fixture.manifestDigest}`)) {
+ return successful(fixture.manifestSource);
+ }
+ if (joined.includes(`content get ${fixture.configDigest}`)) {
+ return successful(fixture.configSource);
+ }
+ if (joined.includes(`images inspect ${alias}`)) {
+ return aliasPresent ? successful("{}") : missing();
+ }
+ if (joined.includes(`image inspect ${alias}`)) {
+ return successful(aliasMetadata(alias, fixture));
+ }
+ if (joined.includes("images tag")) {
+ aliasPresent = true;
+ return successful();
+ }
+ if (joined.includes(`images remove ${alias}`)) {
+ events.push("alias-cleaned");
+ aliasPresent = false;
+ return successful();
+ }
+ if (joined.includes("images mount")) {
+ imageRootfsMounted = true;
+ return successful(
+ `${fixture.authority.rootfsChainId}\n${imageRootfsPath}\n`,
+ );
+ }
+ if (joined.includes("images unmount")) {
+ events.push("rootfs-cleaned");
+ imageRootfsMounted = false;
+ return successful(`${imageRootfsPath}\n`);
+ }
+ if (joined.includes(" create ")) {
+ return successful(`${stagingContainerId}\n`);
+ }
+ if (args.includes("--spec")) {
+ return successful(
+ rootlessSpec(stagingContainerId, plan.expected, fixture.authority),
+ );
+ }
+ if (joined.includes("containers info")) {
+ const id = args.at(-1);
+ if (id === stagingContainerId) {
+ return stagingPresent
+ ? successful(containerMetadata(stagingContainerId, alias))
+ : missing();
+ }
+ if (id === finalContainerId) {
+ return containerPresent
+ ? successful(
+ configContainerMetadata(finalContainerId, baseSpecSha256),
+ )
+ : missing();
+ }
+ return missing();
+ }
+ if (joined.includes("tasks list")) {
+ return successful(taskPresent ? `${finalContainerId}\n` : "");
+ }
+ if (joined.includes("snapshots") && joined.includes(" diff ")) {
+ return successful("stable-rootfs-diff");
+ }
+ if (joined.includes("snapshots") && joined.includes(" info ")) {
+ const id = args.at(-1);
+ if (id === imageRootfsPath) {
+ return imageRootfsMounted
+ ? successful(
+ imageRootfsSnapshotMetadata(
+ imageRootfsPath,
+ fixture.authority.rootfsChainId,
+ ),
+ )
+ : missing();
+ }
+ return missing();
+ }
+ if (joined.includes(" rm --force ")) {
+ stagingPresent = false;
+ return successful();
+ }
+ if (joined.includes(" run ")) {
+ events.push("candidate-started");
+ taskPresent = true;
+ containerPresent = true;
+ const label = args.find((entry) =>
+ entry.startsWith("io.counterlab.runtime.base-spec-sha256="),
+ );
+ baseSpecSha256 = label!.split("=", 2)[1]!;
+ const timeoutError = Object.assign(new Error("candidate timed out"), {
+ code: "ETIMEDOUT",
+ });
+ return {
+ status: null,
+ stdout: Buffer.alloc(0),
+ stderr: Buffer.alloc(0),
+ error: timeoutError,
+ };
+ }
+ if (joined.includes("tasks delete")) {
+ events.push("task-cleaned");
+ taskPresent = false;
+ return successful();
+ }
+ if (joined.includes("containers delete")) {
+ events.push("container-cleaned");
+ containerPresent = false;
+ return successful();
+ }
+ throw new Error(`unexpected mock command: ${joined}`);
+ };
+
+ return {
+ context: {
+ args: command,
+ binRoot: resolve(installRoot, "bin"),
+ clientFifoRoot: resolve(sessionRoot, "run/client-fifo"),
+ containerdSocket: resolve(sessionRoot, "run/containerd.sock"),
+ cwd: root,
+ environment: process.env,
+ installRoot,
+ qualificationMode: AGGREGATE_TIMEOUT_QUALIFICATION_MODE,
+ sessionRoot,
+ stdin: Buffer.alloc(0),
+ },
+ events,
+ fakeSpawn,
+ get finalContainerId() {
+ return finalContainerId;
+ },
+ imageRootfsPath,
+ persistSpec: ({
+ finalContainerId: persistedFinalContainerId,
+ receipt,
+ sessionRoot: persistedSessionRoot,
+ }: {
+ finalContainerId: string;
+ receipt: Record;
+ sessionRoot: string;
+ }) => {
+ finalContainerId = persistedFinalContainerId;
+ return {
+ configFileSha256: String(receipt.configFileSha256),
+ configPath: resolve(
+ persistedSessionRoot,
+ `run/rootless-specs/${finalContainerId}.config.json`,
+ ),
+ imageRootfsPath,
+ receiptFileSha256: "4".repeat(64),
+ receiptPath: resolve(
+ persistedSessionRoot,
+ `run/rootless-specs/${finalContainerId}.receipt.json`,
+ ),
+ };
+ },
+ };
+}
+
describe("contained runtime command policy", () => {
it("renders a bounded containerd shim manager configuration", () => {
const config = renderContainedContainerdConfig(root);
@@ -834,7 +1036,7 @@ describe("contained runtime command policy", () => {
symlinkSync(logTarget, runcLogSymlink);
});
- it("accepts only a self-hashed clean timeout control receipt", () => {
+ it("accepts only self-hashed timeout controls with exact v2 or qualified v3 shape", () => {
const payload = {
schemaVersion: "2",
status: "TIMED_OUT_CLEAN",
@@ -867,6 +1069,32 @@ describe("contained runtime command policy", () => {
};
expect(validateContainedRunControlReceipt(receipt)).toEqual(receipt);
+ const qualifiedPayload = {
+ ...payload,
+ schemaVersion: "3",
+ qualificationMode: AGGREGATE_TIMEOUT_QUALIFICATION_MODE,
+ } as const;
+ const qualifiedReceipt = {
+ ...qualifiedPayload,
+ receiptPayloadSha256: createHash("sha256")
+ .update(canonicalJson(qualifiedPayload))
+ .digest("hex"),
+ };
+ expect(validateContainedRunControlReceipt(qualifiedReceipt)).toEqual(
+ qualifiedReceipt,
+ );
+ expect(() =>
+ validateContainedRunControlReceipt({
+ ...qualifiedReceipt,
+ qualificationMode: "unknown",
+ }),
+ ).toThrow(/qualification|binding/u);
+ expect(() =>
+ validateContainedRunControlReceipt({
+ ...receipt,
+ qualificationMode: AGGREGATE_TIMEOUT_QUALIFICATION_MODE,
+ }),
+ ).toThrow(/shape/u);
expect(() =>
validateContainedRunControlReceipt({
...receipt,
@@ -884,6 +1112,184 @@ describe("contained runtime command policy", () => {
).toThrow(/status/u);
});
+ it("rejects an unknown qualification mode before invoking the runtime", async () => {
+ let spawned = false;
+ const result = await executeContainedRun(
+ {
+ args: startupCommand(),
+ binRoot: resolve(root, "node_modules/.cache/unused/bin"),
+ clientFifoRoot: resolve(root, ".rt/unused/client-fifo"),
+ containerdSocket: resolve(root, ".rt/unused/containerd.sock"),
+ cwd: root,
+ environment: process.env,
+ installRoot: resolve(root, "node_modules/.cache/unused"),
+ qualificationMode: "unknown" as never,
+ sessionRoot: resolve(root, ".rt/unused"),
+ stdin: Buffer.alloc(0),
+ },
+ () => {
+ spawned = true;
+ throw new Error("invalid qualification mode must not reach spawn");
+ },
+ );
+
+ expect(result.status).toBe(1);
+ expect(result.stderr.toString("utf8")).toContain(
+ "qualification mode is invalid",
+ );
+ expect(spawned).toBe(false);
+ });
+
+ it("binds a clean timed-out run to the aggregate-qualified receipt", async () => {
+ const harness = qualifiedRuntimeHarness();
+ let completionOutcome: unknown;
+ const qualifiedReceiptFileSha256 = "7".repeat(64);
+ const qualifiedReceiptPayloadSha256 = "8".repeat(64);
+ const coordinator = {
+ async begin(input: { finalContainerId: string; invocationId: string }) {
+ harness.events.push("qualification-ready");
+ expect(input).toMatchObject({
+ finalContainerId: harness.finalContainerId,
+ invocationId,
+ });
+ return {
+ finalContainerId: input.finalContainerId,
+ invocationId: input.invocationId,
+ };
+ },
+ async waitForDraft() {
+ harness.events.push("qualification-draft");
+ return { receiptPayloadSha256: "6".repeat(64) };
+ },
+ async complete(_handle: unknown, outcome: unknown) {
+ harness.events.push("qualification-complete");
+ completionOutcome = outcome;
+ return {
+ qualificationArtifacts: {},
+ qualifiedReceipt: {},
+ qualifiedReceiptFileSha256,
+ qualifiedReceiptPath: resolve(
+ harness.context.sessionRoot,
+ `run/rootless-specs/${harness.finalContainerId}.qualified-receipt.json`,
+ ),
+ qualifiedReceiptPayloadSha256,
+ };
+ },
+ };
+
+ const result = await executeContainedRun(
+ harness.context,
+ harness.fakeSpawn,
+ harness.persistSpec,
+ () => undefined,
+ () => invocationId,
+ undefined,
+ coordinator,
+ );
+
+ expect(result.status).toBe(1);
+ expect(result.stdout).toHaveLength(0);
+ expect(
+ result.controlReceipt,
+ JSON.stringify({
+ events: harness.events,
+ stderr: result.stderr.toString("utf8"),
+ }),
+ ).toMatchObject({
+ schemaVersion: "3",
+ qualificationMode: AGGREGATE_TIMEOUT_QUALIFICATION_MODE,
+ status: "TIMED_OUT_CLEAN",
+ timeoutObserved: true,
+ resultReleased: false,
+ rootlessReceiptFileSha256: qualifiedReceiptFileSha256,
+ rootlessReceiptPayloadSha256: qualifiedReceiptPayloadSha256,
+ });
+ expect(completionOutcome).toEqual({
+ cleanup: {
+ taskAbsent: true,
+ containerAbsent: true,
+ snapshotAbsent: true,
+ invocationAliasAbsent: true,
+ imageRootfsAbsent: true,
+ persistedAuthorityVerified: true,
+ readOnlyMountsUnchanged: true,
+ imageRootfsUnchanged: true,
+ },
+ resultReleased: false,
+ runtimeFailed: false,
+ timeoutObserved: true,
+ });
+ expect(harness.events).toEqual([
+ "qualification-ready",
+ "candidate-started",
+ "qualification-draft",
+ "task-cleaned",
+ "container-cleaned",
+ "rootfs-cleaned",
+ "alias-cleaned",
+ "qualification-complete",
+ ]);
+ expect(() =>
+ validateContainedRunControlReceipt(result.controlReceipt),
+ ).not.toThrow();
+ });
+
+ it.each(["begin", "draft", "complete"] as const)(
+ "fails closed when aggregate qualification %s fails",
+ async (failurePoint) => {
+ const harness = qualifiedRuntimeHarness();
+ const coordinator = {
+ async begin() {
+ harness.events.push("qualification-ready");
+ if (failurePoint === "begin") throw new Error("begin failed");
+ return {
+ finalContainerId: harness.finalContainerId,
+ invocationId,
+ };
+ },
+ async waitForDraft() {
+ harness.events.push("qualification-draft");
+ if (failurePoint === "draft") throw new Error("draft failed");
+ return { receiptPayloadSha256: "6".repeat(64) };
+ },
+ async complete() {
+ harness.events.push("qualification-complete");
+ throw new Error("completion failed");
+ },
+ };
+
+ const result = await executeContainedRun(
+ harness.context,
+ harness.fakeSpawn,
+ harness.persistSpec,
+ () => undefined,
+ () => invocationId,
+ undefined,
+ coordinator,
+ );
+
+ expect(result.status).toBe(1);
+ expect(result.stdout).toHaveLength(0);
+ expect(result.controlReceipt).toBeUndefined();
+ expect(result.stderr.toString("utf8")).toMatch(
+ failurePoint === "begin"
+ ? /qualification readiness failed/u
+ : /aggregate qualification failed/u,
+ );
+ if (failurePoint === "begin") {
+ expect(harness.events).toEqual([
+ "qualification-ready",
+ "rootfs-cleaned",
+ "alias-cleaned",
+ ]);
+ expect(harness.events).not.toContain("candidate-started");
+ } else {
+ expect(harness.events).toContain("candidate-started");
+ expect(harness.events.at(-1)).toBe("qualification-complete");
+ }
+ },
+ );
+
it("maps only the pinned runc state root into the runtime session", () => {
const environment = {
...process.env,
@@ -2113,7 +2519,7 @@ describe("contained runtime command policy", () => {
);
});
- it("fails closed when owned final cleanup fails", () => {
+ it("fails closed when owned final cleanup fails", async () => {
const sessionRoot = resolve(root, ".rt/rt-validator-cleanup");
const installRoot = resolve(
root,
@@ -2280,7 +2686,18 @@ describe("contained runtime command policy", () => {
throw new Error(`unexpected mock command: ${joined}`);
};
- const result = executeContainedRun(
+ const qualificationCoordinator = {
+ begin: () => {
+ throw new Error("normal mode must not start qualification");
+ },
+ waitForDraft: () => {
+ throw new Error("normal mode must not wait for qualification");
+ },
+ complete: () => {
+ throw new Error("normal mode must not complete qualification");
+ },
+ };
+ const result = await executeContainedRun(
{
args: command,
binRoot: resolve(installRoot, "bin"),
@@ -2289,6 +2706,7 @@ describe("contained runtime command policy", () => {
cwd: root,
environment: process.env,
installRoot,
+ qualificationMode: null,
sessionRoot,
stdin: Buffer.alloc(0),
},
@@ -2308,6 +2726,8 @@ describe("contained runtime command policy", () => {
}),
() => undefined,
() => invocationId,
+ undefined,
+ qualificationCoordinator,
);
expect(result.status).toBe(1);
@@ -2347,7 +2767,7 @@ describe("contained runtime command policy", () => {
).toBe(false);
});
- it("rejects an invalid generated container ID without deleting by name", () => {
+ it("rejects an invalid generated container ID without deleting by name", async () => {
const sessionRoot = resolve(root, ".rt/rt-validator-id");
const installRoot = resolve(
root,
@@ -2358,7 +2778,7 @@ describe("contained runtime command policy", () => {
const alias = `docker.io/library/counterlab-runtime-invocation:${invocationId}`;
const calls: string[][] = [];
let aliasPresent = false;
- const result = executeContainedRun(
+ const result = await executeContainedRun(
{
args: command,
binRoot: resolve(installRoot, "bin"),
@@ -2367,6 +2787,7 @@ describe("contained runtime command policy", () => {
cwd: root,
environment: process.env,
installRoot,
+ qualificationMode: null,
sessionRoot,
stdin: Buffer.alloc(0),
},
From 75dfb6543c89b66aa55d013e27872a3c53862f15 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 08:33:02 +0530
Subject: [PATCH 053/143] fix(runner): consume qualified timeout receipts
---
.../runner/src/counterlab_runner/docker.py | 79 +++++++-----
.../src/counterlab_runner/timeout_proof.py | 91 +++++++++-----
services/runner/tests/test_docker_runner.py | 114 ++++++++++++++++++
services/runner/tests/test_timeout_proof.py | 44 ++++++-
4 files changed, 270 insertions(+), 58 deletions(-)
diff --git a/services/runner/src/counterlab_runner/docker.py b/services/runner/src/counterlab_runner/docker.py
index 280e8c7d..cd6cc3b2 100644
--- a/services/runner/src/counterlab_runner/docker.py
+++ b/services/runner/src/counterlab_runner/docker.py
@@ -30,31 +30,35 @@
_SHA256 = re.compile(r"^[a-f0-9]{64}$")
_CONTAINED_RUNTIME_SESSION_ID = re.compile(r"^rt-[a-z0-9][a-z0-9-]{7,13}$")
_CONTAINED_RUNTIME_SUBPROCESS_ENVIRONMENT = {"PATH": "/usr/bin:/bin"}
-_CONTROL_RECEIPT_KEYS = {
- "schemaVersion",
- "status",
- "timeoutKind",
- "runtimePolicySha256",
- "invocationId",
- "finalContainerId",
- "commandSha256",
- "rootlessReceiptFileSha256",
- "rootlessReceiptPayloadSha256",
- "timeoutObserved",
- "candidateWallSeconds",
- "elapsedMs",
- "cleanupReserveMs",
- "taskAbsent",
- "containerAbsent",
- "snapshotAbsent",
- "invocationAliasAbsent",
- "imageRootfsAbsent",
- "persistedAuthorityVerified",
- "readOnlyMountsUnchanged",
- "imageRootfsUnchanged",
- "resultReleased",
- "receiptPayloadSha256",
-}
+_AGGREGATE_TIMEOUT_QUALIFICATION_MODE = "aggregate-timeout-proof-v1"
+_CONTROL_RECEIPT_V2_KEYS = frozenset(
+ {
+ "schemaVersion",
+ "status",
+ "timeoutKind",
+ "runtimePolicySha256",
+ "invocationId",
+ "finalContainerId",
+ "commandSha256",
+ "rootlessReceiptFileSha256",
+ "rootlessReceiptPayloadSha256",
+ "timeoutObserved",
+ "candidateWallSeconds",
+ "elapsedMs",
+ "cleanupReserveMs",
+ "taskAbsent",
+ "containerAbsent",
+ "snapshotAbsent",
+ "invocationAliasAbsent",
+ "imageRootfsAbsent",
+ "persistedAuthorityVerified",
+ "readOnlyMountsUnchanged",
+ "imageRootfsUnchanged",
+ "resultReleased",
+ "receiptPayloadSha256",
+ }
+)
+_CONTROL_RECEIPT_V3_KEYS = _CONTROL_RECEIPT_V2_KEYS | {"qualificationMode"}
_CONTROL_CLEANUP_FIELDS = (
"taskAbsent",
"containerAbsent",
@@ -273,7 +277,16 @@ def _validate_contained_runtime_control_receipt(
value = json.loads(control_bytes)
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise DockerExecutionError("runtime_control_invalid") from exc
- if not isinstance(value, dict) or set(value) != _CONTROL_RECEIPT_KEYS:
+ if not isinstance(value, dict):
+ raise DockerExecutionError("runtime_control_invalid")
+ schema_version = value.get("schemaVersion")
+ if schema_version == "2":
+ expected_keys = _CONTROL_RECEIPT_V2_KEYS
+ elif schema_version == "3":
+ expected_keys = _CONTROL_RECEIPT_V3_KEYS
+ else:
+ raise DockerExecutionError("runtime_control_invalid")
+ if set(value) != expected_keys:
raise DockerExecutionError("runtime_control_invalid")
payload = {
key: entry for key, entry in value.items() if key != "receiptPayloadSha256"
@@ -282,15 +295,21 @@ def _validate_contained_runtime_control_receipt(
value.get("resultReleased") is False
)
if (
- value.get("schemaVersion") != "2"
+ (
+ schema_version == "3"
+ and value.get("qualificationMode")
+ != _AGGREGATE_TIMEOUT_QUALIFICATION_MODE
+ )
or value.get("status") not in {"TIMED_OUT_CLEAN", "TIMED_OUT_UNCLEAN"}
or value.get("timeoutKind") != "WALL_CLOCK"
or value.get("runtimePolicySha256") != CONTAINED_RUNTIME_POLICY_SHA256
or not isinstance(value.get("candidateWallSeconds"), int)
+ or isinstance(value.get("candidateWallSeconds"), bool)
or value["candidateWallSeconds"] != expected_wall_seconds
or value.get("cleanupReserveMs")
!= int(_CONTAINED_RUNTIME_POLICY["cleanupReserveMs"])
or not isinstance(value.get("elapsedMs"), int)
+ or isinstance(value.get("elapsedMs"), bool)
or value["elapsedMs"] < 1
or value["elapsedMs"] > 600_000
or value.get("timeoutObserved") is not True
@@ -314,7 +333,11 @@ def _validate_contained_runtime_control_receipt(
/ ".rt"
/ session_id
/ "run/rootless-specs"
- / f"{value['finalContainerId']}.receipt.json",
+ / (
+ f"{value['finalContainerId']}.qualified-receipt.json"
+ if schema_version == "3"
+ else f"{value['finalContainerId']}.receipt.json"
+ ),
label="rootless_receipt",
must_exist=True,
)
diff --git a/services/runner/src/counterlab_runner/timeout_proof.py b/services/runner/src/counterlab_runner/timeout_proof.py
index 014c47a1..a3d55aa8 100644
--- a/services/runner/src/counterlab_runner/timeout_proof.py
+++ b/services/runner/src/counterlab_runner/timeout_proof.py
@@ -26,6 +26,7 @@
_SOURCE_COMMIT = re.compile(r"^[a-f0-9]{40}$")
_SESSION_ID = re.compile(r"^rt-[a-z0-9][a-z0-9-]{7,13}$")
_QUALIFIED_AGGREGATE_LIMIT_MODE = "container-cgroup-and-process-rlimit"
+_AGGREGATE_TIMEOUT_QUALIFICATION_MODE = "aggregate-timeout-proof-v1"
_BUILD_KEYS = {
"schemaVersion",
"status",
@@ -56,31 +57,34 @@
"buildkitConfigSha256",
"builtAt",
}
-_CONTROL_KEYS = {
- "schemaVersion",
- "status",
- "timeoutKind",
- "runtimePolicySha256",
- "invocationId",
- "finalContainerId",
- "commandSha256",
- "rootlessReceiptFileSha256",
- "rootlessReceiptPayloadSha256",
- "timeoutObserved",
- "candidateWallSeconds",
- "elapsedMs",
- "cleanupReserveMs",
- "taskAbsent",
- "containerAbsent",
- "snapshotAbsent",
- "invocationAliasAbsent",
- "imageRootfsAbsent",
- "persistedAuthorityVerified",
- "readOnlyMountsUnchanged",
- "imageRootfsUnchanged",
- "resultReleased",
- "receiptPayloadSha256",
-}
+_CONTROL_V2_KEYS = frozenset(
+ {
+ "schemaVersion",
+ "status",
+ "timeoutKind",
+ "runtimePolicySha256",
+ "invocationId",
+ "finalContainerId",
+ "commandSha256",
+ "rootlessReceiptFileSha256",
+ "rootlessReceiptPayloadSha256",
+ "timeoutObserved",
+ "candidateWallSeconds",
+ "elapsedMs",
+ "cleanupReserveMs",
+ "taskAbsent",
+ "containerAbsent",
+ "snapshotAbsent",
+ "invocationAliasAbsent",
+ "imageRootfsAbsent",
+ "persistedAuthorityVerified",
+ "readOnlyMountsUnchanged",
+ "imageRootfsUnchanged",
+ "resultReleased",
+ "receiptPayloadSha256",
+ }
+)
+_CONTROL_V3_KEYS = _CONTROL_V2_KEYS | {"qualificationMode"}
_ATTESTATION_KEYS = {
"schemaVersion",
"status",
@@ -302,11 +306,24 @@ def validate_control_receipt(
expected_wall_seconds: int = 1,
expected_runtime_policy_sha256: str = CONTAINED_RUNTIME_POLICY_SHA256,
) -> dict[str, Any]:
- if not isinstance(value, dict) or set(value) != _CONTROL_KEYS:
+ if not isinstance(value, dict):
+ raise RuntimeError("timeout control receipt shape is invalid")
+ schema_version = value.get("schemaVersion")
+ if schema_version == "2":
+ expected_keys = _CONTROL_V2_KEYS
+ elif schema_version == "3":
+ expected_keys = _CONTROL_V3_KEYS
+ else:
+ raise RuntimeError("timeout control receipt shape is invalid")
+ if set(value) != expected_keys:
raise RuntimeError("timeout control receipt shape is invalid")
payload = {key: entry for key, entry in value.items() if key != "receiptPayloadSha256"}
if (
- value.get("schemaVersion") != "2"
+ (
+ schema_version == "3"
+ and value.get("qualificationMode")
+ != _AGGREGATE_TIMEOUT_QUALIFICATION_MODE
+ )
or value.get("status") != "TIMED_OUT_CLEAN"
or value.get("timeoutKind") != "WALL_CLOCK"
or value.get("runtimePolicySha256")
@@ -318,10 +335,14 @@ def validate_control_receipt(
or not _SHA256.fullmatch(str(value.get("rootlessReceiptPayloadSha256", "")))
or value.get("timeoutObserved") is not True
or value.get("candidateWallSeconds") != expected_wall_seconds
+ or not isinstance(value.get("candidateWallSeconds"), int)
+ or isinstance(value.get("candidateWallSeconds"), bool)
or not isinstance(value.get("elapsedMs"), int)
+ or isinstance(value.get("elapsedMs"), bool)
or value["elapsedMs"] < 1
or value["elapsedMs"] > 600_000
or not isinstance(value.get("cleanupReserveMs"), int)
+ or isinstance(value.get("cleanupReserveMs"), bool)
or value["cleanupReserveMs"] < 1
or any(value.get(field) is not True for field in _CLEANUP_FIELDS)
or value.get("resultReleased") is not False
@@ -333,6 +354,20 @@ def validate_control_receipt(
return value
+def _rootless_receipt_name(control: dict[str, Any]) -> str:
+ if control.get("schemaVersion") == "2":
+ suffix = "receipt.json"
+ elif (
+ control.get("schemaVersion") == "3"
+ and control.get("qualificationMode")
+ == _AGGREGATE_TIMEOUT_QUALIFICATION_MODE
+ ):
+ suffix = "qualified-receipt.json"
+ else:
+ raise RuntimeError("timeout control receipt qualification is invalid")
+ return f"{control['finalContainerId']}.{suffix}"
+
+
def validate_rootless_receipt(
value: object,
*,
@@ -886,7 +921,7 @@ def run_timeout_cleanup_proof(
or _sha256_file(rootless_path) != expected_rootless_sha256
):
raise RuntimeError("timeout proof exception receipt hashes changed")
- if rootless_path.name != f"{control['finalContainerId']}.receipt.json":
+ if rootless_path.name != _rootless_receipt_name(control):
raise RuntimeError("timeout control and rootless receipt IDs disagree")
if _sha256_file(rootless_path) != control["rootlessReceiptFileSha256"]:
raise RuntimeError("rootless receipt file hash changed")
diff --git a/services/runner/tests/test_docker_runner.py b/services/runner/tests/test_docker_runner.py
index 786630d4..8bd083c1 100644
--- a/services/runner/tests/test_docker_runner.py
+++ b/services/runner/tests/test_docker_runner.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import hashlib
import json
import os
import stat
@@ -38,6 +39,47 @@ def _artifacts(workspace: Path, limits: RunnerLimits) -> SimpleNamespace:
)
+def _canonical_hash(value: object) -> str:
+ source = json.dumps(
+ value,
+ ensure_ascii=False,
+ allow_nan=False,
+ sort_keys=True,
+ separators=(",", ":"),
+ )
+ return hashlib.sha256(source.encode()).hexdigest()
+
+
+def _runtime_control_receipt(schema_version: str) -> dict[str, object]:
+ payload: dict[str, object] = {
+ "schemaVersion": schema_version,
+ "status": "TIMED_OUT_CLEAN",
+ "timeoutKind": "WALL_CLOCK",
+ "runtimePolicySha256": docker_module.CONTAINED_RUNTIME_POLICY_SHA256,
+ "invocationId": "1" * 64,
+ "finalContainerId": "2" * 64,
+ "commandSha256": "3" * 64,
+ "rootlessReceiptFileSha256": hashlib.sha256(b"{}\n").hexdigest(),
+ "rootlessReceiptPayloadSha256": "4" * 64,
+ "timeoutObserved": True,
+ "candidateWallSeconds": 1,
+ "elapsedMs": 1_001,
+ "cleanupReserveMs": 120_000,
+ "taskAbsent": True,
+ "containerAbsent": True,
+ "snapshotAbsent": True,
+ "invocationAliasAbsent": True,
+ "imageRootfsAbsent": True,
+ "persistedAuthorityVerified": True,
+ "readOnlyMountsUnchanged": True,
+ "imageRootfsUnchanged": True,
+ "resultReleased": False,
+ }
+ if schema_version == "3":
+ payload["qualificationMode"] = "aggregate-timeout-proof-v1"
+ return {**payload, "receiptPayloadSha256": _canonical_hash(payload)}
+
+
def test_docker_command_applies_fixed_isolation_and_only_public_mounts(
tmp_path: Path,
) -> None:
@@ -179,6 +221,78 @@ def test_trusted_repository_root_rejects_relative_and_nested_callers(
require_trusted_repository_root(nested)
+@pytest.mark.parametrize(
+ ("schema_version", "rootless_suffix"),
+ [("2", ".receipt.json"), ("3", ".qualified-receipt.json")],
+)
+def test_runtime_control_receipt_binds_schema_to_exact_rootless_path(
+ tmp_path: Path,
+ monkeypatch: pytest.MonkeyPatch,
+ schema_version: str,
+ rootless_suffix: str,
+) -> None:
+ session_id = "rt-entry123"
+ receipt = _runtime_control_receipt(schema_version)
+ rootless_root = tmp_path / ".rt" / session_id / "run/rootless-specs"
+ rootless_root.mkdir(parents=True)
+ rootless_path = rootless_root / f"{'2' * 64}{rootless_suffix}"
+ rootless_path.write_text("{}\n", encoding="utf-8")
+ control_path = tmp_path / f"control-v{schema_version}.json"
+ control_path.write_text(json.dumps(receipt), encoding="utf-8")
+ monkeypatch.setattr(
+ docker_module,
+ "_trusted_repository_root",
+ lambda: tmp_path.resolve(strict=True),
+ )
+
+ value, _, observed_rootless_path, observed_rootless_sha256 = (
+ docker_module._validate_contained_runtime_control_receipt(
+ control_path,
+ session_id=session_id,
+ expected_wall_seconds=1,
+ )
+ )
+
+ assert value == receipt
+ assert observed_rootless_path == rootless_path
+ assert observed_rootless_sha256 == receipt["rootlessReceiptFileSha256"]
+
+
+@pytest.mark.parametrize(
+ "mutation",
+ [
+ {"qualificationMode": "unknown"},
+ {"candidateWallSeconds": True},
+ ],
+)
+def test_runtime_control_receipt_rejects_invalid_v3_fields(
+ tmp_path: Path,
+ monkeypatch: pytest.MonkeyPatch,
+ mutation: dict[str, object],
+) -> None:
+ receipt = {**_runtime_control_receipt("3"), **mutation}
+ payload = {
+ key: value
+ for key, value in receipt.items()
+ if key != "receiptPayloadSha256"
+ }
+ receipt["receiptPayloadSha256"] = _canonical_hash(payload)
+ control_path = tmp_path / "control-v3.json"
+ control_path.write_text(json.dumps(receipt), encoding="utf-8")
+ monkeypatch.setattr(
+ docker_module,
+ "_trusted_repository_root",
+ lambda: tmp_path.resolve(strict=True),
+ )
+
+ with pytest.raises(DockerExecutionError, match="runtime_control_invalid"):
+ docker_module._validate_contained_runtime_control_receipt(
+ control_path,
+ session_id="rt-entry123",
+ expected_wall_seconds=1,
+ )
+
+
def test_execution_snapshot_is_the_exact_validated_read_only_source(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
diff --git a/services/runner/tests/test_timeout_proof.py b/services/runner/tests/test_timeout_proof.py
index 8e4f7c6f..807b4751 100644
--- a/services/runner/tests/test_timeout_proof.py
+++ b/services/runner/tests/test_timeout_proof.py
@@ -30,9 +30,9 @@ def _hash(value: object) -> str:
return hashlib.sha256(_canonical(value).encode()).hexdigest()
-def _control() -> dict[str, object]:
+def _control(schema_version: str = "2") -> dict[str, object]:
payload: dict[str, object] = {
- "schemaVersion": "2",
+ "schemaVersion": schema_version,
"status": "TIMED_OUT_CLEAN",
"timeoutKind": "WALL_CLOCK",
"runtimePolicySha256": CONTAINED_RUNTIME_POLICY_SHA256,
@@ -55,6 +55,8 @@ def _control() -> dict[str, object]:
"imageRootfsUnchanged": True,
"resultReleased": False,
}
+ if schema_version == "3":
+ payload["qualificationMode"] = "aggregate-timeout-proof-v1"
return {**payload, "receiptPayloadSha256": _hash(payload)}
@@ -222,6 +224,44 @@ def test_control_receipt_rejects_result_release_and_hash_mutation() -> None:
validate_control_receipt({**control, "receiptPayloadSha256": "f" * 64})
+def test_control_receipt_accepts_only_exact_v2_and_qualified_v3_shapes() -> None:
+ assert validate_control_receipt(_control("2"))["schemaVersion"] == "2"
+ assert validate_control_receipt(_control("3"))["schemaVersion"] == "3"
+
+ for mutated in (
+ {**_control("2"), "qualificationMode": "aggregate-timeout-proof-v1"},
+ {
+ key: value
+ for key, value in _control("3").items()
+ if key != "qualificationMode"
+ },
+ {**_control("3"), "qualificationMode": "unknown"},
+ {**_control("2"), "schemaVersion": []},
+ {**_control("2"), "candidateWallSeconds": True},
+ ):
+ payload = {
+ key: value
+ for key, value in mutated.items()
+ if key != "receiptPayloadSha256"
+ }
+ mutated["receiptPayloadSha256"] = _hash(payload)
+ with pytest.raises(RuntimeError, match="shape|not clean"):
+ validate_control_receipt(mutated)
+
+
+def test_control_schema_selects_the_exact_rootless_receipt_name() -> None:
+ assert timeout_proof_module._rootless_receipt_name(_control("2")) == (
+ f"{'2' * 64}.receipt.json"
+ )
+ assert timeout_proof_module._rootless_receipt_name(_control("3")) == (
+ f"{'2' * 64}.qualified-receipt.json"
+ )
+ with pytest.raises(RuntimeError, match="qualification"):
+ timeout_proof_module._rootless_receipt_name(
+ {**_control("3"), "qualificationMode": "unknown"}
+ )
+
+
def test_rootless_rlimit_validator_matches_the_runtime_contract() -> None:
runtime_limits = [
{"type": name, "soft": 1, "hard": 1}
From f19387b1ab214571ea27022efdc0b9432655cc9f Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 08:33:52 +0530
Subject: [PATCH 054/143] test(learner): align live journey selectors
---
apps/web/e2e/judged-flow.spec.ts | 22 +++++++++-------------
1 file changed, 9 insertions(+), 13 deletions(-)
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index 39fdc06b..2517ef29 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -2184,7 +2184,7 @@ test("configured reasoning cannot start without a qualified hosted runner", asyn
page.getByText(/qualified hosted runner is needed/i),
).toBeVisible();
await expect(
- page.getByRole("button", { name: /Continue with my notebook/i }),
+ page.getByLabel(/Attach a supported notebook/i),
).not.toBeVisible();
await expect(page.locator("body")).not.toContainText(
/OPENAI|GPT-|https?:\/\//i,
@@ -2423,10 +2423,8 @@ test("a configured hosted runner completes an untouched leakage notebook", async
page.getByText(/Hosted notebook runner is ready/i),
).toBeVisible();
await page
- .getByRole("button", { name: /Continue with my notebook/i })
- .click();
-
- await page.locator('input[type="file"]').setInputFiles(leakageNotebookPath);
+ .getByLabel(/Attach a supported notebook/i)
+ .setInputFiles(leakageNotebookPath);
await expect(
page.getByText(/leakage-rows-pipeline.ipynb/i).first(),
).toBeVisible();
@@ -2665,10 +2663,8 @@ test("a configured hosted runner completes an untouched class-imbalance notebook
page.getByText(/Hosted notebook runner is ready/i),
).toBeVisible();
await page
- .getByRole("button", { name: /Continue with my notebook/i })
- .click();
-
- await page.locator('input[type="file"]').setInputFiles(imbalanceNotebookPath);
+ .getByLabel(/Attach a supported notebook/i)
+ .setInputFiles(imbalanceNotebookPath);
await expect(
page.getByText(/fraud_class_imbalance.ipynb/i).first(),
).toBeVisible();
@@ -2732,10 +2728,10 @@ test("a configured hosted runner completes an untouched class-imbalance notebook
"When positive cases are rare, compare against the majority baseline and choose class-specific metrics and a threshold that match deployment cost.",
);
await page.getByRole("button", { name: /Try it on defects/i }).click();
- await page.getByLabel(/Lower threshold based on missed-defect cost/i).check();
- await page.getByLabel(/Missing a defect is the costly error/i).check();
- await page.getByLabel(/Confusion matrix shows misses/i).check();
- await page.getByLabel(/Prevalence changes precision/i).check();
+ await page.getByLabel(/Reject the accuracy-only conclusion/i).check();
+ await page.getByLabel(/Defect recall and PR-AUC/i).check();
+ await page.getByLabel(/zero true positives/i).check();
+ await page.getByLabel(/Defects are only 1%/i).check();
await page.getByRole("button", { name: /Check transfer/i }).click();
await expect(page.locator(".imbalance-transfer-pass .eyebrow")).toHaveText(
"Transfer passed",
From e4dc33eb44962b972c0157e5ecb4ed9760c16ee4 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 08:35:15 +0530
Subject: [PATCH 055/143] docs(progress): record aggregate runtime integration
---
docs/PROGRESS.md | 83 +++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 75 insertions(+), 8 deletions(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index c709151c..0904c7ec 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -12,17 +12,16 @@ pass is never presented as a browser or production pass.
### Current checkpoint
- **Branch:** `feat/learner-ux-v6.1`
-- **Latest committed checkpoint:** `f0c9f7bfcca474ed700b51975f03246df9f5158d`.
+- **Latest committed checkpoint:** `f19387b1ab214571ea27022efdc0b9432655cc9f`.
The worktree contains only the preserved, untracked
`scripts/.fuse_hidden0000a9600000801f`. The source now has a self-hashed
cgroup request, observer, FINALIZE/ABORT protocol, fixed negative controls,
- fail-closed evidence validation, qualified-receipt derivation/store, and 40
- statically collected Playwright journeys. The production runtime still does
- not start the observer, write the runtime-owned decision, bind the complete
- invocation artifact chain, or select the qualified receipt for timeout
- proof. No exact image, real cgroup sentinel, qualification, deployment,
- rendered browser journey, learner observation, video, Devpost publication,
- or submission receipt is claimed.
+ fail-closed evidence validation, qualified-receipt derivation/store, runtime
+ observer orchestration, serialized run/drain handling, exact v2/v3 Python
+ consumers, and 40 statically collected Playwright journeys. Source tests do
+ not constitute a real cgroup sentinel: no exact image, aggregate host proof,
+ qualification, deployment, rendered browser journey, learner observation,
+ video, Devpost publication, or submission receipt is claimed.
- **Earlier product checkpoint:**
`5259a495948a68024702ba5239bf8057d1785f08`. The owner partitioned the
preserved integration delta into nine commits and fast-forwarded both
@@ -3463,3 +3462,71 @@ worktree entry besides this progress update is the preserved FUSE tombstone
6. Keep learner evidence honest, finish and verify the public submission
package, then push the feature branch, verify the remote tip, fast-forward
`main`, and push/verify `main` only after every ship gate is factual.
+
+## Finish Commander aggregate integration checkpoint — 2026-07-21T03:04:15Z
+
+Deadline: `2026-07-22T00:00:00Z`; **20 hours 55 minutes 45 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD `f19387b`; the only
+worktree entry besides this progress update is the preserved FUSE tombstone
+`scripts/.fuse_hidden0000a9600000801f`. It remains unchanged and unstaged.
+
+### Independently committed source slices
+
+- `3cdf38b fix(runtime): verify qualification artifact chain` verifies the
+ manifest, readiness, draft, finalization, and exclusive evidence chain before
+ deriving a qualified receipt.
+- `1ea2ed8 fix(runtime): permit honest pre-draft aborts` permits only the
+ explicit null-draft ABORT case while retaining a mandatory draft hash for
+ FINALIZE.
+- `40db9bc feat(runtime): coordinate aggregate qualification` owns observer
+ startup, readiness/draft chronology, FINALIZE/ABORT emission, observer exit,
+ and qualified-receipt persistence.
+- `eaf4a60 feat(runtime): wire aggregate qualification` connects the release-only
+ qualification mode to the runtime, binds control-receipt v3 to the qualified
+ receipt, serializes run and drain requests, and restores `ACTIVE` after a
+ failed drain.
+- `75dfb65 fix(runner): consume qualified timeout receipts` preserves legacy v2
+ replay while requiring the exact qualification mode and
+ `.qualified-receipt.json` path for v3 in both Python consumers.
+- `f19387b test(learner): align live journey selectors` updates the two live
+ upload journeys and imbalance transfer choices to current accessible names.
+
+### Exact verification in these slices
+
+- Runtime, request, observer, coordinator, and qualified-receipt Vitest:
+ **7 files, 68/68 passed**.
+- Complete runner Pytest with kernel and runner source roots: **82/82 passed**.
+ One preceding command omitted the kernel source root and failed collection;
+ it executed no product tests and was corrected before this reported pass.
+- Repository, web client, generated Worker, and Worker TypeScript: **passed**.
+- E2E strict TypeScript: **passed**.
+- Playwright static collection under the explicit non-rendering design-review
+ authority: **40 tests in 3 files**. No browser launched and no rendered
+ qualification is claimed.
+- Scoped Prettier, Python compilation, Node syntax, Git whitespace, and secret
+ scans: **passed**.
+- Independent runtime integration review: **GO** after the initial review found
+ and the implementation repaired the async run/drain race and missing positive
+ qualification-path coverage.
+
+### Current remaining gates
+
+1. The source-level observer/runtime/consumer chain is complete, but no real
+ delegated-cgroup sentinel or exact-image qualification has run. The current
+ repository-only filesystem constitution prohibits the required `/sys` and
+ `/proc` reads, so this gate remains honestly blocked unless the checked-in
+ constitution changes.
+2. The preserved FUSE tombstone prevents a clean physical checkout. It is not
+ deleted, hidden, staged, or modified. A separately contained clean checkout
+ or the natural exit of its proven owner remains required.
+3. Prompt-first Landing and Judge hierarchy, mobile textual controls, and the
+ shared pre-seal forbidden-text regression remain the next source slice.
+4. Final source freeze, one exact image, refreshed source/image-bound evidence,
+ qualification, the complete deterministic release gate, contained Wrangler
+ identity, and Cloudflare deployment remain **not run**.
+5. This session still has no `CLOAK_CDP_ENDPOINT`; rendered desktop/mobile,
+ accessibility, console/network, reconnect, download, and Web Vitals
+ qualification remain **0 current journeys**.
+6. Learner evidence remains **`NO_DATA`**. Devpost, video, public-repository
+ verification, feedback Session ID, logged-out link audit, publication, and
+ submission receipt remain externally unverified.
From bd6e5c32077905894e722b4313c9e09957d16637 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 08:41:46 +0530
Subject: [PATCH 056/143] docs(release): map qualification dependencies
---
.../19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md | 263 ++++++++++++++++++
1 file changed, 263 insertions(+)
create mode 100644 docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
diff --git a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
new file mode 100644
index 00000000..5158aeec
--- /dev/null
+++ b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
@@ -0,0 +1,263 @@
+# Release dependency and review graph
+
+- Checkpoint: `2026-07-21T03:07:00Z`
+- Branch: `feat/learner-ux-v6.1`
+- Committed HEAD: `e4dc33eb44962b972c0157e5ecb4ed9760c16ee4`
+- Deadline: `2026-07-22T00:00:00Z`
+- Time remaining at this checkpoint: **20 hours 53 minutes**
+
+## Purpose and evidence boundary
+
+This document is the operational dependency graph for freezing, qualifying,
+deploying, browser-testing, submitting, and merging one exact CounterLab
+release. A downstream node cannot become green from source code or unit tests
+alone when it requires image, cloud, browser, learner, or submission evidence.
+
+The worktree at this checkpoint has one preserved untracked entry:
+`scripts/.fuse_hidden0000a9600000801f`. It was not read, changed, hidden,
+staged, or deleted for this review. Every current build, qualification,
+release-check, deployment, and production-smoke script that requires a clean
+worktree will reject this state.
+
+No current deployment, rendered CloakBrowser journey, learner outcome, video,
+Devpost submission, branch push, or merge is claimed here.
+
+## Release dependency graph
+
+```mermaid
+flowchart TD
+ A["Source slices atomic commits + focused tests SOURCE COMPLETE for aggregate runtime chain"]
+ B["Review gate independent review + lead diff review PASS for aggregate runtime integration"]
+ C["Clean source freeze S one committed physical tree BLOCKED by preserved FUSE entry"]
+ D["Exact runner and adapter image build receipt v4 bound to S NOT RUN for current source"]
+ E["Real aggregate timeout sentinel observer + controls + FINALIZE + cleanup BLOCKED by /sys and /proc authority"]
+ F["Source/image evidence refresh SBOM + VEX + engine evidence NOT RUN"]
+ G["Evidence commit E reviewed allowlisted evidence only NOT CREATED"]
+ H["Qualification receipt v6 + immutable registry image NOT RUN; Wrangler identity unverified"]
+ I["Release check receipt v5 + deterministic full gate NOT RUN"]
+ J["Cloudflare deployment maintenance + D1 + Container + Worker NOT RUN"]
+ K["Public identity and smoke deployment receipt v7 + /ready + health + live loops NOT RUN"]
+ L["CloakBrowser qualification 40 journeys + manual accessibility/performance matrix 0 rendered journeys"]
+ M["Submission package public app + repo + video + feedback + honest NO_DATA EXTERNALLY UNVERIFIED"]
+ N["Git publication push feature -> verify remote -> fast-forward main -> push main NOT RUN"]
+
+ A --> B --> C --> D --> E --> F --> G --> H --> I --> J --> K --> L --> M --> N
+
+ classDef pass fill:#d9f3ee,stroke:#176b5b,color:#123b34;
+ classDef blocked fill:#fff0d0,stroke:#9a6500,color:#4c3500;
+ classDef pending fill:#eef2f7,stroke:#596579,color:#252b36;
+ classDef external fill:#f7e8ef,stroke:#95506d,color:#482636;
+ class A,B pass;
+ class C,E blocked;
+ class D,F,G,H,I,J,K,L pending;
+ class M,N external;
+```
+
+## Gate ledger
+
+| Gate | Required inputs | Required evidence | Current status | Failure rule |
+| ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| Source slices | Reviewed implementation and tests | Atomic commits, focused tests, type checks, formatting, secret scan, whitespace check | Aggregate observer/runtime/consumer chain is source-complete | Repair in a new independent commit; do not weaken tests |
+| Review | Complete source slice and focused evidence | Independent review plus lead inspection of complete diff | Aggregate runtime review is `GO`; its initial async run/drain race and missing positive path were repaired | Review findings remain open until implementation and regression tests pass |
+| Clean source `S` | Final intended source and no tracked or untracked drift | `git status` empty and exact source commit recorded | **Blocked** by preserved FUSE entry | Do not hide, delete, or sweep the entry; use its proven owner's natural exit or a separately contained clean checkout |
+| Exact image | Clean `S`, source-matching contained runtime, pinned BuildKit | Build receipt v4, source/tree labels, exact runner and adapter digests | **Not run** for current source; cached receipts belong to older commits | Never reuse or retag an earlier image as current |
+| Real sentinel | Exact image, source-matching runtime, release-only qualification mode | Genuine memory OOM, PID denial, CPU throttling, exact membership, FINALIZE, eight cleanup facts, cgroup absence, exclusive evidence artifact | **Blocked** by the checked-in repository-only filesystem rule, which does not permit required `/sys/fs/cgroup` and owned `/proc//stat` reads | Preserve `NO-GO`; never synthesize or inject aggregate evidence |
+| Evidence refresh | Exact image and successful real sentinel while `HEAD == S` | Fresh normalized SBOM, VEX and negative control, scientific-engine bindings, runtime and isolation reports | **Not run** | Any image/source mismatch or stale binding fails closed |
+| Evidence commit `E` | Reviewed allowlisted evidence delta | One clean commit containing only regenerated evidence | **Not created** | Do not include unrelated source or cache artifacts |
+| Qualification | Clean `HEAD == E`, build receipt, timeout receipt, source-matching runtime, authenticated Cloudflare account | Qualified runner receipt v6 and exact registry manifest/config digest | **Not run**; contained Wrangler identity is unverified | Do not promote when account, source, image, timeout, runtime, or evidence differs |
+| Release check | Clean `E`, qualified receipt, exact local images and runtime | Release-check receipt v5 after formatting, tests, mutations, held-out, sandbox, scientific, build, reproduction, patch replay, and secret gates | **Not run** | Any failure blocks deployment |
+| Cloudflare deployment | Qualified receipt, release-check receipt, registry image, required secrets, recovery target, working CloakBrowser endpoint | Exact maintenance/final Worker versions, D1 migration log, exact Container digest, deployment receipt v7 | **Not run** | Stop before mutation when preflight is red; after mutation, automated Worker recovery does not reverse D1 or Container state |
+| Public identity and smoke | Active exact Worker/Container tuple | `/ready`, `/api/health?readiness=probe`, Worker 100% traffic, Container identity, public asset scan, Sample, Replay, leakage and imbalance live evidence, Capsule/replay evidence | **Not run** | Source, image, Worker, client, receipt, or capability mismatch is a release failure |
+| CloakBrowser | Exact public tuple and injected `CLOAK_CDP_ENDPOINT` | All 40 current tests plus responsive, keyboard, screen-reader, 200% zoom, reduced-motion, console/network, downloads, reconnect, and Web Vitals evidence | **40 statically collected; 0 rendered** | Stock Chromium is design-review evidence only and cannot close this gate |
+| Devpost and impact | Qualified public tuple, public repo/video/feedback links, factual copy | Logged-out link audit, public video from exact tuple, Devpost receipt, real consented learner aggregates or explicit `NO_DATA` | **Externally unverified; learner evidence remains `NO_DATA`** | Never infer a submission or learner result |
+| Push and merge | Every ship gate factual and final source reviewed | Verified feature remote tip, fast-forward-only `main`, verified main remote tip | **Not run** | Do not push or merge a `NO-GO` release |
+
+## Code-to-evidence graph
+
+```mermaid
+flowchart LR
+ subgraph Source["Source authority"]
+ R1["scripts/contained-cgroup-* observer, protocol, controls"]
+ R2["scripts/contained-runtime-* request, run, server, supervisor"]
+ R3["services/runner/... docker.py + timeout_proof.py"]
+ U1["apps/web/src + worker learner and authority UI"]
+ end
+
+ subgraph Verification["Pre-release verification"]
+ T1["Vitest + TypeScript"]
+ T2["Pytest + cross-runtime parity"]
+ T3["Playwright source + static collection"]
+ Q1["Independent review"]
+ end
+
+ subgraph Release["Release evidence"]
+ B1["build-source-bound-runner.sh"]
+ S1["real timeout/cgroup sentinel"]
+ E1["refresh-source-bound-scientific-evidence.sh"]
+ Q2["qualify-runner-release.ts"]
+ R4["release-check.sh"]
+ end
+
+ subgraph Production["Production evidence"]
+ C1["deploy-qualified.sh"]
+ C2["deployment receipt + /ready + health"]
+ C3["production-smoke.sh"]
+ P1["CloakBrowser full matrix"]
+ end
+
+ R1 --> T1
+ R2 --> T1
+ R3 --> T2
+ U1 --> T1
+ U1 --> T3
+ T1 --> Q1
+ T2 --> Q1
+ T3 --> Q1
+ Q1 --> B1 --> S1 --> E1 --> Q2 --> R4 --> C1 --> C2 --> C3 --> P1
+```
+
+Source tests demonstrate behavior; they do not manufacture the observation at
+the real-sentinel, Cloudflare, public-identity, or browser nodes.
+
+## Commit and review chain at this checkpoint
+
+The current aggregate release chain is split into independent commits:
+
+- `0077f1a` — fixed cgroup controls;
+- `fbc5ba9` — observer identity bindings;
+- `37684dd` — observer protocol;
+- `771d8bf` — aggregate observer;
+- `317ba44` — live-session qualified-receipt binding;
+- `c2c1817` — finalization signal;
+- `cf90647` — fail-closed cgroup qualification;
+- `bb1cc0b` — finalization-bound aggregate evidence;
+- `3cdf38b` — exclusive qualification artifact-chain verification;
+- `1ea2ed8` — honest pre-draft ABORT handling;
+- `40db9bc` — observer/runtime qualification coordinator;
+- `eaf4a60` — release-only runtime integration and control-receipt v3;
+- `75dfb65` — Python v3 qualified-receipt consumption with v2 replay
+ compatibility;
+- `f19387b` — current live-browser selector alignment;
+- `e4dc33e` — factual progress checkpoint.
+
+Reported verification for the integrated aggregate slice is:
+
+- runtime/request/observer/coordinator/receipt Vitest: **7 files, 68/68
+ passed**;
+- complete runner Pytest with both required source roots: **82/82 passed**;
+- repository, web client, generated Worker, and Worker TypeScript: **passed**;
+- E2E strict TypeScript: **passed**;
+- Playwright static collection: **40 tests in 3 files**, with no browser
+ launched;
+- scoped Prettier, Python compilation, Node syntax, Git whitespace, and secret
+ scans: **passed**;
+- independent runtime integration review: **GO** after the implementation
+ repaired its initial async run/drain race and missing positive
+ qualification-path coverage.
+
+These results are source evidence only. The real sentinel and every downstream
+release node remain open.
+
+## File ownership and reviewer graph
+
+```mermaid
+flowchart TD
+ Lead["Lead integrator sole committer and release owner"]
+ Runtime["Runtime/release implementer contained runtime + timeout consumer"]
+ UI["Learner UI implementer isolated learner components/tests"]
+ Boundary["Boundary implementer bounded Boundary files/tests"]
+ Browser["Browser QA reviewer Playwright evidence only"]
+ Access["Accessibility reviewer semantic/responsive findings"]
+ Docs["Documentation reviewer copy and evidence drafts"]
+ Independent["Independent runtime reviewer race, binding, failure-path audit"]
+
+ Runtime --> Independent --> Lead
+ UI --> Access --> Lead
+ UI --> Browser --> Lead
+ Boundary --> Access
+ Boundary --> Browser
+ Docs --> Lead
+ Lead --> Commit["Selective staged diff atomic local commit"]
+ Commit --> Gate["Relevant tests and delivery gate"]
+```
+
+| Area | Writable owner | Required reviewer/evidence | Integration owner |
+| ----------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------- | ----------------- |
+| `apps/web/src/App.tsx`, global routes/state/CSS | Lead only | React/Vitest, accessibility and browser review | Lead |
+| `apps/web/src/components/learner/`, `features/learner/` | Assigned learner implementer when isolated | Focused component tests, accessibility review, lead diff review | Lead |
+| Boundary Hunt files | Assigned Boundary implementer | Boundary invariants, keyboard/table/non-colour review | Lead |
+| `scripts/contained-*`, release receipts and release scripts | Lead or one explicitly assigned runtime owner at a time | Runtime Vitest, Python parity, independent review, lead diff review | Lead |
+| `services/runner/.../docker.py` and `timeout_proof.py` | One runtime owner at a time | Runner Pytest, cross-language receipt parity, failure-path review | Lead |
+| Worker/API/contracts/session transitions | Lead only | TypeScript, Worker tests, authority-boundary review | Lead |
+| Scientific/SBOM tracked evidence | Lead during exact refresh | Hash/schema verification and evidence-only diff review | Lead |
+| Playwright specifications and evidence | Browser QA owner on assigned files | CloakBrowser execution, console/network and accessibility evidence | Lead |
+| Root release/submission documentation | Lead integrates reviewer drafts | Claim-to-evidence review | Lead |
+| Git commits, push, deployment and merge | Lead only | Clean staged diff and final delivery gate | Lead |
+
+No two writers may own the same shared file or receipt registry concurrently.
+Only the lead stages, commits, deploys, pushes, or fast-forwards `main`.
+
+## Disciplines applied directly
+
+The following disciplines are applied directly from the checked-in repository
+constitution, code, tests, and release scripts. This document does **not** claim
+that home-directory skill files were read.
+
+| Discipline | Direct application in this graph |
+| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Repository safety | All repository work and generated state stay inside the verified root; the FUSE entry is preserved; no destructive cleanup, stash, reset, or outside-root filesystem operation is accepted. |
+| Code review | Each logical slice has focused characterization/regression tests, an isolated diff, independent review where assigned, and lead integration before an atomic commit. |
+| Accessibility | Browser closure requires semantic names, keyboard completion, visible focus, exact-value tables, non-colour meaning, reduced motion, zoom, responsive viewports, touch targets, and screen-reader inspection. |
+| React testing | Component and App behavior is checked with Vitest and strict TypeScript; Playwright covers composed learner journeys, authority labels, refresh, navigation, downloads, and no-result-before-Prediction behavior. |
+| Playwright and CloakBrowser | Static collection is not browser evidence; release qualification requires the injected CloakBrowser CDP authority, accessibility snapshots, screenshots where visual judgment matters, and console/network review. Stock Chromium cannot close the gate. |
+| Cloudflare and Wrangler | The repository-pinned Wrangler, contained account identity, exact registry digest, maintenance freeze, remote D1 migration, Container rollout, final Worker, recovery target, and public identity receipt form one ordered gate. |
+| Delivery gate | Clean `S` -> exact image -> real sentinel -> evidence `E` -> qualification -> release check -> deployment -> public smoke -> full browser matrix -> submission -> push/fast-forward merge. No later node may compensate for an earlier red node. |
+
+## Current blockers and shortest honest path
+
+1. The source-level aggregate runtime chain is implemented and reviewed, but a
+ real delegated-cgroup sentinel has not run.
+2. The current constitution prohibits the sentinel's necessary `/sys/fs/cgroup`
+ and owned `/proc//stat` reads. The release must remain `NO-GO` unless the
+ checked-in authority changes or another constitution-compatible evidence
+ path is established.
+3. The preserved FUSE entry prevents the required clean physical source tree.
+4. No current-source image, scientific-evidence refresh, evidence commit,
+ qualified receipt, or release-check receipt exists.
+5. Contained Wrangler authentication and the intended Cloudflare account have
+ not been verified for this release.
+6. `CLOAK_CDP_ENDPOINT` is absent. `deploy-qualified.sh` performs remote Worker,
+ D1, and Container mutations before invoking the smoke script, while
+ `production-smoke.sh` requires CloakBrowser. CloakBrowser must therefore be
+ preflighted before deployment starts.
+7. Learner evidence remains `NO_DATA`; public repository, video, feedback ID,
+ Devpost publication, and submission receipt remain externally unverified.
+
+The shortest honest path is to resolve the real-sentinel authority and clean
+source blockers, freeze `S`, build once, produce the genuine sentinel, refresh
+and commit evidence as `E`, qualify, release-check, preflight CloakBrowser and
+Wrangler, deploy once, verify the exact public tuple, execute the full browser
+matrix, finalize the factual submission, then push and fast-forward merge.
+
+## Final release review questions
+
+- Does every result-bearing claim name its exact signed or integrity-hashed
+ source?
+- Are Sample, Replay, and Live labels persistent and non-interchangeable?
+- Is `S` the exact runner source and an ancestor of evidence commit `E`?
+- Do the image, aggregate evidence, qualification, release-check, deployment,
+ Worker, client, and public-health hashes agree?
+- Did a real observer produce the aggregate evidence, with no caller-supplied
+ substitute?
+- Did all deterministic gates pass without weakening or skipping a test?
+- Did the exact public tuple complete both untouched supported live flows?
+- Did CloakBrowser execute the full responsive, accessibility, failure,
+ reconnect, download, console/network, and performance matrix?
+- Is learner impact either backed by consented rows or still explicitly
+ `NO_DATA`?
+- Do the app, repository, video, screenshots, feedback ID, Devpost copy, and
+ submission receipt describe the same exact release?
+- Was the feature branch pushed and verified before a fast-forward-only merge
+ to `main`?
+
+Any `no`, unknown, or unavailable answer keeps the corresponding gate open.
From e80b390a7007a6d360afe6c88bae26c2b8fbd4f5 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 08:42:33 +0530
Subject: [PATCH 057/143] fix(judge): tighten first-fold belief break
---
.../features/judge/JudgeModeView.module.css | 52 ++++++++++++-------
.../src/features/judge/JudgeModeView.test.tsx | 50 +++++++++++++++++-
apps/web/src/features/judge/JudgeModeView.tsx | 37 ++++++-------
3 files changed, 100 insertions(+), 39 deletions(-)
diff --git a/apps/web/src/features/judge/JudgeModeView.module.css b/apps/web/src/features/judge/JudgeModeView.module.css
index c9da4676..4e6d24b4 100644
--- a/apps/web/src/features/judge/JudgeModeView.module.css
+++ b/apps/web/src/features/judge/JudgeModeView.module.css
@@ -118,10 +118,10 @@
grid-template-areas:
"copy proof"
"after proof";
- gap: clamp(42px, 7vw, 96px);
+ gap: clamp(32px, 5vw, 68px);
align-items: center;
- min-height: 680px;
- padding-block: 76px;
+ min-height: 620px;
+ padding-block: 52px;
}
.heroCopy {
@@ -157,25 +157,25 @@
max-width: 720px;
margin: 0;
font-family: var(--display);
- font-size: clamp(58px, 7.5vw, 106px);
+ font-size: clamp(52px, 6vw, 82px);
font-weight: 520;
- letter-spacing: -0.055em;
- line-height: 0.88;
+ letter-spacing: -0.05em;
+ line-height: 0.94;
}
.lede {
max-width: 650px;
- margin: 30px 0 0;
+ margin: 22px 0 0;
color: var(--ink);
- font-size: clamp(17px, 1.6vw, 21px);
- line-height: 1.55;
+ font-size: clamp(17px, 1.4vw, 19px);
+ line-height: 1.5;
}
.heroActions {
display: flex;
flex-wrap: wrap;
gap: 12px;
- margin-top: 32px;
+ margin-top: 24px;
}
.heroActions button,
@@ -217,7 +217,7 @@
.scopeLine {
max-width: 600px;
- margin-top: 24px;
+ margin-top: 16px;
color: var(--judge-muted);
font-size: 13px;
line-height: 1.5;
@@ -225,10 +225,10 @@
.twentySecondProof {
position: relative;
- padding: clamp(26px, 4vw, 46px);
+ padding: clamp(22px, 2.6vw, 32px);
color: var(--judge-panel-ink);
background: var(--judge-panel);
- box-shadow: 20px 20px 0 rgba(46, 196, 160, 0.6);
+ box-shadow: 14px 14px 0 rgba(46, 196, 160, 0.6);
}
.twentySecondProof::before {
@@ -248,7 +248,7 @@
display: flex;
justify-content: space-between;
gap: 20px;
- padding-bottom: 18px;
+ padding-bottom: 12px;
border-bottom: 1px solid rgb(255 255 255 / 18%);
font-size: 12px;
letter-spacing: 0.08em;
@@ -256,13 +256,20 @@
}
.twentySecondProof blockquote {
- margin: 28px 0 18px;
+ margin: 18px 0 12px;
color: var(--gold-bright);
font-family: var(--display);
font-size: clamp(21px, 2.4vw, 31px);
line-height: 1.18;
}
+.previewMeta {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 12px;
+ align-items: center;
+}
+
.previewAuthority {
margin: 0 0 14px;
color: var(--judge-panel-muted);
@@ -274,7 +281,7 @@
display: inline-flex;
min-height: 44px;
align-items: center;
- margin-top: 8px;
+ padding-inline: 4px;
color: var(--judge-panel-ink);
font-size: 13px;
font-weight: 720;
@@ -786,7 +793,8 @@
}
.hero h1 {
- font-size: clamp(34px, 10vw, 46px);
+ font-size: clamp(32px, 9.5vw, 40px);
+ line-height: 0.98;
}
.hero .kicker,
@@ -804,12 +812,16 @@
}
.twentySecondProof {
- padding: 18px;
- box-shadow: 10px 10px 0 rgba(46, 196, 160, 0.6);
+ padding: 14px;
+ box-shadow: 8px 8px 0 rgba(46, 196, 160, 0.6);
}
.twentySecondProof blockquote {
- margin-block: 20px 12px;
+ margin-block: 14px 10px;
+ }
+
+ .previewMeta {
+ gap: 8px;
}
.authoritySection,
diff --git a/apps/web/src/features/judge/JudgeModeView.test.tsx b/apps/web/src/features/judge/JudgeModeView.test.tsx
index 6296fb4a..11a8a6f4 100644
--- a/apps/web/src/features/judge/JudgeModeView.test.tsx
+++ b/apps/web/src/features/judge/JudgeModeView.test.tsx
@@ -1,7 +1,7 @@
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
-import { render, screen } from "@testing-library/react";
+import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
@@ -54,6 +54,54 @@ describe("JudgeModeView", () => {
vi.unstubAllGlobals();
});
+ it("keeps the tightened first fold learner-facing with proof one action away", async () => {
+ render(
+ ,
+ );
+
+ expect(
+ screen.getByText(
+ "Seal a Prediction. Change one condition. Fixed evidence—not AI prose—releases one bounded result.",
+ ),
+ ).toBeVisible();
+ expect(
+ screen.queryByText(/Question → Prediction → Test/u),
+ ).not.toBeInTheDocument();
+
+ const proof = screen.getByRole("complementary", {
+ name: /ten second fixed sample preview/i,
+ });
+ expect(proof).toHaveTextContent(
+ /completed fixed sample.*not a live result/i,
+ );
+ expect(within(proof).getByText("Integrity checked")).toBeVisible();
+ expect(proof).toHaveTextContent(
+ /This score proves the model works for customers it has never seen/i,
+ );
+ expect(proof).not.toHaveTextContent(/\b[a-f0-9]{64}\b/iu);
+
+ const proofLink = within(proof).getByRole("link", {
+ name: /inspect verified sample proof/i,
+ });
+ const mechanism = within(proof).getByRole("region", {
+ name: /verified sample belief-break mechanism/i,
+ });
+ expect(
+ proofLink.compareDocumentPosition(mechanism) &
+ Node.DOCUMENT_POSITION_FOLLOWING,
+ ).not.toBe(0);
+ expect(await within(mechanism).findByText("98.5%")).toBeVisible();
+ expect(within(mechanism).getByText("59.4%")).toBeVisible();
+ expect(within(mechanism).getByText("Boundary consequence")).toBeVisible();
+ expect(within(mechanism).getByText("Learner benefit")).toBeVisible();
+ });
+
it("distinguishes sample, live, and legacy replay authority", async () => {
render(
- Seal a Prediction, change one condition, and let fixed evidence—not
- AI prose—release the bounded result. CounterLab turns the
- learner's notebook claim into a checkable Question → Prediction
- → Test → Boundary → Apply → Repair record.
+ Seal a Prediction. Change one condition. Fixed evidence—not AI
+ prose—releases one bounded result.
@@ -94,25 +92,28 @@ export function JudgeModeView({
>
Completed fixed sample · not a live result
- Hashes checked before values
+ Integrity checked
“This score proves the model works for customers it has never seen.”
-
- Approved fixed sample framing. No GPT-5.6, Codex, or runner call
- occurs in this preview.
-
+
-
- setSampleEvidenceInspectionRequest((request) => request + 1)
- }
- >
- Inspect verified sample proof
-
From 830d61b3f6d6ea765a11b8cbce96682b487ab7f0 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 08:47:12 +0530
Subject: [PATCH 058/143] fix(learner): restore prompt-first landing hierarchy
---
apps/web/src/App.test.tsx | 37 +++---
apps/web/src/App.tsx | 30 ++---
.../learner/AccessibilityStyles.test.ts | 18 +--
.../LockedBeliefBreakPreview.module.css | 109 ++++++++++++++++++
.../learner/LockedBeliefBreakPreview.test.tsx | 33 ++++++
.../learner/LockedBeliefBreakPreview.tsx | 39 ++++++-
.../learner/QuestionComposer.module.css | 44 +++----
apps/web/src/styles.css | 99 +++-------------
8 files changed, 254 insertions(+), 155 deletions(-)
create mode 100644 apps/web/src/components/learner/LockedBeliefBreakPreview.module.css
create mode 100644 apps/web/src/components/learner/LockedBeliefBreakPreview.test.tsx
diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
index 467fde5c..d0ca015a 100644
--- a/apps/web/src/App.test.tsx
+++ b/apps/web/src/App.test.tsx
@@ -21,6 +21,9 @@ import { activeRunnerRegistryKey } from "./features/learner/activeRunnerRegistry
import { requireBundledSampleResult, sampleArtifact } from "./sample";
const sampleResult = requireBundledSampleResult();
+const PRE_PREDICTION_RESULT_LANGUAGE =
+ /59\.4%|\bdeceptive\b|\bfairer test\b|\bverified result\b|\bevidence verdict\b|\bsupported hypothesis\b|\bthe fix\b|\bremove customer(?:_| )id\b|\bkeep each customer's rows together\b|\bproves?\b/i;
+const LANDING_FIXED_SAMPLE_VALUES = /98\.5%|59\.4%/i;
const verifiedImbalanceResult = VerifiedResultSetSchema.parse(
JSON.parse(imbalanceResultText),
@@ -1073,12 +1076,7 @@ describe("CounterLab judged flow", () => {
).toBeInTheDocument();
expect(
screen.getByText(
- /for learners testing whether a notebook result means what they think it means/i,
- ),
- ).toBeInTheDocument();
- expect(
- screen.getByText(
- /evidence-first learning.*seal a Prediction.*fixed evidence.*not AI prose/i,
+ /ask a question or attach a supported notebook.*reads the evidence and never runs its cells/i,
),
).toBeInTheDocument();
expect(
@@ -1110,7 +1108,7 @@ describe("CounterLab judged flow", () => {
screen.getByText(/fixed kernels calculate the result/i),
).toBeInTheDocument();
expect(
- screen.getByText(/read the evidence and never run the cells/i),
+ screen.getByText(/reads the evidence and never runs its cells/i),
).toBeInTheDocument();
expect(
screen.getByRole("navigation", {
@@ -1126,21 +1124,16 @@ describe("CounterLab judged flow", () => {
expect(
screen.getByRole("button", { name: /test this claim/i }),
).toBeDisabled();
- expect(
- screen.getByText(/result hidden until your Prediction is sealed/i),
- ).toBeInTheDocument();
- expect(
- screen.getByText(
- /name the claim.*lock your expectation.*change one thing/i,
- ),
- ).toBeInTheDocument();
- expect(
- screen.getByLabelText(/fair-test mechanism with result locked/i),
- ).toHaveTextContent(/familiar-row test.*unseen-entity test/i);
- expect(document.body).not.toHaveTextContent(/98\.5%|59\.4%/i);
- expect(
- screen.getByLabelText(/fair-test mechanism with result locked/i),
- ).toHaveAttribute("data-result-visibility", "locked");
+ const lockedPreview = screen.getByLabelText(
+ /fair-test preview with result locked/i,
+ );
+ expect(lockedPreview).toHaveTextContent(
+ /familiar rows.*change who counts as new.*unseen customers/i,
+ );
+ expect(lockedPreview).toHaveAttribute("data-presentation", "strip");
+ expect(lockedPreview).toHaveAttribute("data-result-visibility", "locked");
+ expect(document.body).not.toHaveTextContent(LANDING_FIXED_SAMPLE_VALUES);
+ expect(document.body).not.toHaveTextContent(PRE_PREDICTION_RESULT_LANGUAGE);
const proofSummary = screen.getByText("Evidence & proof");
await user.click(screen.getByRole("link", { name: /how proof works/i }));
expect(proofSummary.closest("details")).toHaveAttribute("open");
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 30646c11..cc4988ad 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -1098,14 +1098,9 @@ function Landing({
What result are you trying to understand?
-
- For learners testing whether a notebook result means what they
- think it means.
-
-
- Evidence-first learning: seal a Prediction, change one
- condition, and let fixed evidence—not AI prose—release the
- bounded result.
+
+ Ask a question or attach a supported notebook. CounterLab reads
+ the evidence and never runs its cells.
@@ -1130,19 +1125,9 @@ function Landing({
- No account needed · Supported
- Python/scikit-learn notebooks ·
- We read the evidence and never run the cells
+ No account needed · Sample, live,
+ and replay stay clearly labelled
{
expect(theaterStyles).toContain("animation: none");
});
- it("keeps the composer dominant and collapses the landing before fixed tracks overflow", () => {
- expect(globalStyles).toContain(
+ it("keeps the composer dominant in one centered responsive column", () => {
+ expect(globalStyles).not.toContain(
"grid-template-columns: minmax(520px, 1.08fr) minmax(480px, 0.92fr)",
);
expect(globalStyles).toContain("@media (max-width: 1240px)");
expect(globalStyles).toContain(
- ".question-first-layout {\n width: 100%;\n max-width: 1220px;\n min-width: 0;",
+ ".question-first-layout {\n width: 100%;\n max-width: 820px;\n min-width: 0;",
);
expect(globalStyles).toContain(
- ".question-first-layout {\n width: 100%;\n max-width: 780px;\n min-width: 0;",
+ ".question-first-layout {\n width: 100%;\n max-width: 820px;\n min-width: 0;",
);
expect(questionComposerStyles).toContain(
".composer {\n width: 100%;\n max-width: 780px;\n min-width: 0;",
@@ -128,11 +128,15 @@ describe("learner-facing responsive style safeguards", () => {
expect(questionComposerStyles).toContain(
".promptGroup {\n width: 100%;\n max-width: 640px;\n min-width: 0;",
);
- expect(globalStyles).toContain(
- ".landing-belief-break blockquote {\n display: none;",
+ expect(globalStyles).toContain("grid-template-columns: minmax(0, 1fr)");
+ expect(questionComposerStyles).toContain(
+ ".form {\n min-height: 122px;\n padding-bottom: 58px;",
);
expect(questionComposerStyles).toContain(
- "@media (max-width: 560px) {\n .promptGroup {\n display: none;",
+ ".submitText {\n position: static;",
+ );
+ expect(questionComposerStyles).not.toContain(
+ ".promptGroup {\n display: none;",
);
});
});
diff --git a/apps/web/src/components/learner/LockedBeliefBreakPreview.module.css b/apps/web/src/components/learner/LockedBeliefBreakPreview.module.css
new file mode 100644
index 00000000..b8877ea1
--- /dev/null
+++ b/apps/web/src/components/learner/LockedBeliefBreakPreview.module.css
@@ -0,0 +1,109 @@
+.strip {
+ width: 100%;
+ min-width: 0;
+ padding: 14px 16px;
+ display: grid;
+ gap: 11px;
+ overflow: hidden;
+ border: 1px solid var(--line);
+ border-radius: 14px;
+ color: var(--ink);
+ background: color-mix(in srgb, var(--night-raised) 84%, transparent);
+}
+
+.status {
+ margin: 0;
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 12px;
+ color: var(--ink-soft);
+ font-size: 13px;
+ line-height: 1.4;
+}
+
+.status strong {
+ color: var(--aqua-bright);
+ font-size: 13px;
+ font-weight: 720;
+}
+
+.sequence {
+ margin: 0;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
+ align-items: center;
+ gap: 10px;
+ font-size: 15px;
+ line-height: 1.4;
+ text-align: center;
+}
+
+.sequence > span:first-child,
+.sequence > span:last-child {
+ min-width: 0;
+ padding: 9px 10px;
+ border: 1px solid var(--line);
+ border-radius: 10px;
+ background: var(--paper);
+}
+
+.change {
+ display: inline-grid;
+ justify-items: center;
+ gap: 2px;
+ color: var(--ink-soft);
+ font-size: 13px;
+ white-space: nowrap;
+}
+
+.change > span[aria-hidden="true"] {
+ color: var(--aqua-bright);
+ font-size: 18px;
+ line-height: 1;
+}
+
+.heldFixed {
+ margin: 0;
+ color: var(--ink-soft);
+ font-size: 13px;
+ line-height: 1.45;
+ text-align: center;
+}
+
+.heldFixed > span {
+ margin-right: 5px;
+ color: var(--aqua-bright);
+}
+
+@media (max-width: 560px) {
+ .strip {
+ padding: 12px;
+ gap: 9px;
+ }
+
+ .status {
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 2px;
+ }
+
+ .sequence {
+ grid-template-columns: 1fr;
+ gap: 5px;
+ text-align: left;
+ }
+
+ .change {
+ grid-template-columns: auto 1fr;
+ justify-items: start;
+ padding-inline: 9px;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .strip,
+ .strip * {
+ transition: none;
+ }
+}
diff --git a/apps/web/src/components/learner/LockedBeliefBreakPreview.test.tsx b/apps/web/src/components/learner/LockedBeliefBreakPreview.test.tsx
new file mode 100644
index 00000000..c318a4d4
--- /dev/null
+++ b/apps/web/src/components/learner/LockedBeliefBreakPreview.test.tsx
@@ -0,0 +1,33 @@
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import { LockedBeliefBreakPreview } from "./LockedBeliefBreakPreview";
+
+describe("LockedBeliefBreakPreview", () => {
+ it("offers a compact landing strip without revealing the bundled result", () => {
+ render( );
+
+ const preview = screen.getByLabelText(
+ /fair-test preview with result locked/i,
+ );
+ expect(preview).toHaveAttribute("data-presentation", "strip");
+ expect(preview).toHaveAttribute("data-result-visibility", "locked");
+ expect(preview).toHaveTextContent(
+ /familiar rows.*change who counts as new.*unseen customers/i,
+ );
+ expect(preview).toHaveTextContent(
+ /same model, features, preprocessing, sample size, metric, and seed/i,
+ );
+ expect(preview).not.toHaveTextContent(/98\.5%|59\.4%|supports|rejected/i);
+ });
+
+ it("retains the complete locked mechanism for non-landing surfaces", () => {
+ render( );
+
+ const preview = screen.getByLabelText(
+ /belief-break fair-test mechanism with result locked/i,
+ );
+ expect(preview).toHaveAttribute("data-presentation", "compact");
+ expect(preview).toHaveTextContent(/familiar-row test.*unseen-entity test/i);
+ });
+});
diff --git a/apps/web/src/components/learner/LockedBeliefBreakPreview.tsx b/apps/web/src/components/learner/LockedBeliefBreakPreview.tsx
index 67366e69..ee9d1923 100644
--- a/apps/web/src/components/learner/LockedBeliefBreakPreview.tsx
+++ b/apps/web/src/components/learner/LockedBeliefBreakPreview.tsx
@@ -1,4 +1,5 @@
import styles from "./VerifiedBeliefBreakTheater.module.css";
+import previewStyles from "./LockedBeliefBreakPreview.module.css";
export function LockedBeliefBreakEvidence() {
return (
@@ -69,7 +70,43 @@ export function LockedBeliefBreakEvidence() {
);
}
-export function LockedBeliefBreakPreview() {
+export function LockedBeliefBreakPreview({
+ density = "full",
+}: {
+ density?: "full" | "strip";
+}) {
+ if (density === "strip") {
+ return (
+
+
+ Fair-test preview
+ Result locked until Prediction
+
+
+ Familiar rows
+
+ →
+ change who counts as new
+
+ Unseen customers
+
+
+ ✓
+ Same model, features, preprocessing, sample size, metric, and seed.
+
+
+ );
+ }
+
return (
h1:focus-visible {
outline: 3px solid var(--focus-ring);
outline-offset: 6px;
@@ -6678,38 +6670,9 @@ body {
.landing-belief-break {
grid-area: belief-break;
- align-self: center;
+ width: min(780px, 100%);
+ justify-self: center;
min-width: 0;
- padding: clamp(20px, 2.5vw, 30px);
- overflow: hidden;
- border: 1px solid var(--line);
- border-radius: 18px;
- background: var(--night-raised);
- box-shadow: 0 24px 70px rgba(0, 0, 0, 0.34);
-}
-
-.landing-belief-break > header > p {
- margin: 0;
- color: var(--aqua-bright);
- font-size: 12px;
- font-weight: 760;
- line-height: 1.4;
-}
-
-.landing-belief-break > header > h2 {
- margin: 9px 0 0;
- font-family: var(--display);
- font-size: clamp(25px, 2.4vw, 36px);
- font-weight: 520;
- letter-spacing: -0.035em;
- line-height: 1.05;
-}
-
-.landing-belief-break blockquote {
- margin: 13px 0 0;
- color: var(--ink-soft);
- font-size: 15px;
- line-height: 1.5;
}
.landing-comparison-preview {
@@ -6963,15 +6926,8 @@ body {
.question-first-layout {
width: 100%;
- max-width: 780px;
+ max-width: 820px;
min-width: 0;
- grid-template-columns: 1fr;
- grid-template-areas:
- "question"
- "belief-break"
- "support"
- "recent";
- gap: 36px;
}
}
@@ -7119,7 +7075,7 @@ body {
}
.question-first-layout {
- gap: 16px;
+ gap: 14px;
}
.landing-question-column {
@@ -7130,32 +7086,13 @@ body {
font-size: 24px;
}
- .landing-audience {
+ .landing-trust-summary {
margin-top: 8px;
font-size: 15px;
}
- .landing-category {
- margin-top: 6px;
- font-size: 14px;
- }
-
.landing-belief-break {
- padding: 14px;
- border-radius: 17px;
- }
-
- .landing-belief-break > header > p {
- font-size: 12px;
- }
-
- .landing-belief-break > header > h2 {
- margin-top: 6px;
- font-size: 21px;
- }
-
- .landing-belief-break blockquote {
- display: none;
+ width: 100%;
}
.landing-comparison-preview {
From dd7b372fb15048e11224979157d3bf29f9beedc9 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 08:50:02 +0530
Subject: [PATCH 059/143] test(learner): guard prompt-first landing fold
---
apps/web/e2e/judged-flow.spec.ts | 46 ++++++++++++++++++++++++++------
1 file changed, 38 insertions(+), 8 deletions(-)
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index 2517ef29..d19ce8ee 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -30,6 +30,8 @@ const claim =
"The 98 percent random split accuracy proves this model generalizes to customers it has never seen.";
const revision =
"When rows repeat an entity, hold out whole entities and remove identity-derived features before claiming generalization.";
+const prePredictionResultLanguage =
+ /59\.4%|\bdeceptive\b|\bfairer test\b|\bverified result\b|\bevidence verdict\b|\bsupported hypothesis\b|\bthe fix\b|\bremove customer(?:_| )id\b|\bkeep each customer's rows together\b|\bproves?\b/i;
const imbalanceNotebookPath = new URL(
"../../../fixtures/notebooks/fraud_class_imbalance.ipynb",
import.meta.url,
@@ -1332,29 +1334,57 @@ for (const viewport of beliefBreakViewports) {
composer,
"question composer before secondary paths",
);
+ await expectEntirelyInFirstViewport(
+ page,
+ page.getByRole("heading", {
+ name: /What result are you trying to understand/i,
+ }),
+ "Question-first heading",
+ );
await expectEntirelyInFirstViewport(
page,
page.getByText(
- /For learners testing whether a notebook result means what they think it means/i,
+ /Ask a question or attach a supported notebook.*never runs its cells/i,
),
- "learner audience statement",
+ "artifact safety summary",
);
- const preview = page.getByRole("complementary", {
- name: /One changed variable.*Everything else held fixed/i,
- });
+ const submit = page.getByRole("button", { name: /Test this claim/i });
+ await expect(submit).toBeDisabled();
+ await expectEntirelyInFirstViewport(
+ page,
+ submit,
+ "primary Question action",
+ );
+ await expectEntirelyInFirstViewport(
+ page,
+ page.getByRole("group", { name: /Prompt starters/i }),
+ "learner prompt starters",
+ );
+ if (viewport.width <= 620) {
+ const submitBox = await submit.boundingBox();
+ expect(submitBox, "mobile primary Question action bounds").not.toBeNull();
+ expect(submitBox!.width).toBeGreaterThanOrEqual(130);
+ }
+
+ const preview = page.locator(
+ '[data-presentation="strip"][data-result-visibility="locked"]',
+ );
await expectEntirelyInFirstViewport(
page,
preview,
"pre-Prediction fair-test promise",
);
+ await expect(preview).toContainText(/Result locked until Prediction/i);
await expect(preview).toContainText(
- /Result hidden until your Prediction is sealed/i,
+ /Familiar rows.*change who counts as new.*Unseen customers/i,
);
await expect(preview).toContainText(
- /Name the claim.*Lock your expectation.*Change one thing/i,
+ /Same model, features, preprocessing, sample size, metric, and seed/i,
+ );
+ await expect(page.locator("body")).not.toContainText(
+ prePredictionResultLanguage,
);
- await expect(page.locator("body")).not.toContainText("59.4%");
await expect(
page.getByRole("region", {
name: "Verified sample belief-break mechanism",
From dd99800ab7c00ab6951aca618b82b4f6a584b621 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 08:51:37 +0530
Subject: [PATCH 060/143] docs(progress): record prompt-first release
checkpoint
---
docs/PROGRESS.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 71 insertions(+), 3 deletions(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 0904c7ec..56f48b7c 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -12,14 +12,15 @@ pass is never presented as a browser or production pass.
### Current checkpoint
- **Branch:** `feat/learner-ux-v6.1`
-- **Latest committed checkpoint:** `f19387b1ab214571ea27022efdc0b9432655cc9f`.
+- **Latest committed checkpoint:** `dd7b372fb15048e11224979157d3bf29f9beedc9`.
The worktree contains only the preserved, untracked
`scripts/.fuse_hidden0000a9600000801f`. The source now has a self-hashed
cgroup request, observer, FINALIZE/ABORT protocol, fixed negative controls,
fail-closed evidence validation, qualified-receipt derivation/store, runtime
observer orchestration, serialized run/drain handling, exact v2/v3 Python
- consumers, and 40 statically collected Playwright journeys. Source tests do
- not constitute a real cgroup sentinel: no exact image, aggregate host proof,
+ consumers, a tightened Judge first fold, a centered prompt-first Landing,
+ and 40 statically collected Playwright journeys. Source tests do not
+ constitute a real cgroup sentinel: no exact image, aggregate host proof,
qualification, deployment, rendered browser journey, learner observation,
video, Devpost publication, or submission receipt is claimed.
- **Earlier product checkpoint:**
@@ -3530,3 +3531,70 @@ worktree entry besides this progress update is the preserved FUSE tombstone
6. Learner evidence remains **`NO_DATA`**. Devpost, video, public-repository
verification, feedback Session ID, logged-out link audit, publication, and
submission receipt remain externally unverified.
+
+## Prompt-first release-presentation checkpoint — 2026-07-21T03:20:32Z
+
+Deadline: `2026-07-22T00:00:00Z`; **20 hours 39 minutes 28 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD `dd7b372`; the only
+worktree entry besides this progress update is the preserved FUSE tombstone
+`scripts/.fuse_hidden0000a9600000801f`. It remains unchanged and unstaged.
+
+### Independently committed slices
+
+- `bd6e5c3 docs(release): map qualification dependencies` records the source,
+ release, reviewer, browser, Cloudflare, submission, and merge dependency
+ graph without representing local source checks as production closure.
+- `e80b390 fix(judge): tighten first-fold belief break` reduces Judge first-fold
+ copy and visual density, preserves the completed-fixed-sample authority
+ label, and keeps proof one action away before the verified mechanism.
+- `830d61b fix(learner): restore prompt-first landing hierarchy` makes the
+ Question composer the centered dominant action, keeps prompt starters and
+ textual mobile actions visible, and replaces the large pre-result card with
+ a compact locked fair-test strip. The Landing strip contains no fixed sample
+ values or verdict/fix language.
+- `dd7b372 test(learner): guard prompt-first landing fold` updates the
+ desktop/mobile Playwright contract for the new locked strip, visible primary
+ action and prompts, first-viewport containment, mobile action width,
+ horizontal overflow, and the shared pre-Prediction forbidden-text corpus.
+
+### Exact verification in these slices
+
+- Focused Landing component and integration Vitest: **4 files, 85/85 passed**.
+- Complete web Vitest: **79 files, 620/620 passed**.
+- Web client TypeScript and E2E TypeScript: **passed**.
+- Production Vite/Worker build: **passed**. Main client
+ **447.26 kB / 128.68 kB gzip**, client CSS
+ **200.63 kB / 34.69 kB gzip**, Worker
+ **1,863.39 kB / 359.59 kB gzip**.
+- Playwright static collection under the explicit non-rendering design-review
+ authority: **40 tests in 3 files**. No browser launched and no rendered
+ qualification is claimed.
+- Scoped Prettier, Git whitespace, and explicit secret scans: **passed**.
+- One package-manager wrapper retry stopped before test collection with
+ `unable to open database file`; the same checks passed through the
+ repository-local Vitest and TypeScript binaries. One initial Playwright
+ collection command used the wrong repo-local binary path, and one correctly
+ located command failed closed because no browser authority was supplied;
+ explicit non-rendering design-review collection then passed. None of these
+ setup attempts executed product tests or a browser before their corrected
+ passes.
+
+### Current remaining gates
+
+1. The source-level aggregate-cgroup chain remains locally tested but has no
+ real delegated-cgroup sentinel. The checked-in repository-only filesystem
+ constitution prohibits the necessary `/sys` and `/proc` reads, so exact
+ image qualification remains fail-closed.
+2. The preserved FUSE tombstone still prevents a clean physical worktree and
+ is not deleted, hidden, staged, or modified.
+3. Final source freeze, one exact runner image, refreshed source/image-bound
+ evidence, qualification, release checks, contained Wrangler identity, and
+ Cloudflare deployment remain **not run**.
+4. This session still has no `CLOAK_CDP_ENDPOINT`; desktop/mobile rendering,
+ keyboard, screen-reader, reduced-motion, console/network, reconnect,
+ downloads, screenshots, and Web Vitals remain **0 current journeys**.
+5. Learner evidence remains **`NO_DATA`**. Public repository/video/feedback
+ alignment, Devpost completion, logged-out verification, publication, and a
+ submission receipt remain externally unverified.
+6. The feature branch has not been pushed and has not been merged into `main`.
+ Push and fast-forward merge remain downstream of factual release gates.
From 2986046cd24e74a799d4494fc84f716f47e15341 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 09:14:22 +0530
Subject: [PATCH 061/143] test(e2e): authorize private session assertions
---
apps/web/e2e/judged-flow.spec.ts | 26 ++++++++++++++++++------
apps/web/e2e/private-session.ts | 20 ++++++++++++++++++
apps/web/e2e/recovery-and-intake.spec.ts | 4 ++++
3 files changed, 44 insertions(+), 6 deletions(-)
create mode 100644 apps/web/e2e/private-session.ts
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index d19ce8ee..f0d9c7d9 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -25,6 +25,7 @@ import {
snapshotBrowserPerformanceEvidence,
type BrowserPerformanceEvidence,
} from "./performance-evidence";
+import { privateSessionHeaders } from "./private-session";
const claim =
"The 98 percent random split accuracy proves this model generalizes to customers it has never seen.";
@@ -152,9 +153,11 @@ async function writeLiveSmokeEvidence(
if (sessionId === null) {
throw new Error("Live smoke evidence requires a persisted session ID");
}
+ const ownerHeaders = await privateSessionHeaders(page, sessionId);
const completedSessionResponse = await page.request.get(
`/api/sessions/${encodeURIComponent(sessionId)}`,
+ { headers: ownerHeaders },
);
expect(completedSessionResponse.ok()).toBe(true);
const completedSessionPayload = (await completedSessionResponse.json()) as {
@@ -272,7 +275,7 @@ async function writeLiveSmokeEvidence(
const duplicatePublicationResponse = await page.request.post(
`/api/sessions/${encodeURIComponent(sessionId)}/replays`,
- { data: {} },
+ { data: {}, headers: ownerHeaders },
);
expect(duplicatePublicationResponse.status()).toBe(200);
const duplicatePublicationPayload =
@@ -287,6 +290,7 @@ async function writeLiveSmokeEvidence(
const activeStatusResponse = await page.request.get(
`/api/sessions/${encodeURIComponent(sessionId)}/replays/status`,
+ { headers: ownerHeaders },
);
expect(activeStatusResponse.ok()).toBe(true);
const activeStatusPayload = (await activeStatusResponse.json()) as {
@@ -511,6 +515,7 @@ async function writeLiveSmokeEvidence(
const revokedStatusResponse = await page.request.get(
`/api/sessions/${encodeURIComponent(sessionId)}/replays/status`,
+ { headers: ownerHeaders },
);
expect(revokedStatusResponse.ok()).toBe(true);
await expect(revokedStatusResponse.json()).resolves.toMatchObject({
@@ -522,7 +527,7 @@ async function writeLiveSmokeEvidence(
const duplicateRevocationResponse = await page.request.post(
`/api/sessions/${encodeURIComponent(sessionId)}/replays/revoke`,
- { data: {} },
+ { data: {}, headers: ownerHeaders },
);
expect(duplicateRevocationResponse.status()).toBe(200);
await expect(duplicateRevocationResponse.json()).resolves.toMatchObject({
@@ -531,7 +536,7 @@ async function writeLiveSmokeEvidence(
const republishRevokedResponse = await page.request.post(
`/api/sessions/${encodeURIComponent(sessionId)}/replays`,
- { data: {} },
+ { data: {}, headers: ownerHeaders },
);
expect(republishRevokedResponse.status()).toBe(409);
await expect(republishRevokedResponse.json()).resolves.toMatchObject({
@@ -1838,8 +1843,11 @@ test("prediction is immutable and results do not exist before commitment", async
window.localStorage.getItem("counterlab.sessionId"),
);
expect(sessionId).not.toBeNull();
+ const ownerHeaders = await privateSessionHeaders(page, sessionId!);
- const before = await page.request.get(`/api/sessions/${sessionId}`);
+ const before = await page.request.get(`/api/sessions/${sessionId}`, {
+ headers: ownerHeaders,
+ });
expect(before.ok()).toBe(true);
expect((await before.json()).data.verifiedResult).toBeUndefined();
@@ -1854,12 +1862,15 @@ test("prediction is immutable and results do not exist before commitment", async
`/api/sessions/${sessionId}/prediction`,
{
data: { choice: "Accuracy remains near 98%", confidence: 100 },
+ headers: ownerHeaders,
},
);
expect(overwrite.status()).toBe(409);
expect((await overwrite.json()).error.code).toBe("ILLEGAL_TRANSITION");
- const committed = await page.request.get(`/api/sessions/${sessionId}`);
+ const committed = await page.request.get(`/api/sessions/${sessionId}`, {
+ headers: ownerHeaders,
+ });
expect(committed.ok()).toBe(true);
const committedPayload = await committed.json();
expect(committedPayload.data.prediction.confidence).toBe(88);
@@ -2015,7 +2026,10 @@ test("a rejected test releases no result and remains recoverable after refresh",
const sessionId = await page.evaluate(() =>
window.localStorage.getItem("counterlab.sessionId"),
);
- const stored = await page.request.get(`/api/sessions/${sessionId}`);
+ expect(sessionId).not.toBeNull();
+ const stored = await page.request.get(`/api/sessions/${sessionId}`, {
+ headers: await privateSessionHeaders(page, sessionId!),
+ });
expect(stored.ok()).toBe(true);
expect((await stored.json()).data.verifiedResult).toBeUndefined();
diff --git a/apps/web/e2e/private-session.ts b/apps/web/e2e/private-session.ts
new file mode 100644
index 00000000..5c452904
--- /dev/null
+++ b/apps/web/e2e/private-session.ts
@@ -0,0 +1,20 @@
+import type { Page } from "./cloak-test";
+
+const ownerCapabilityPattern = /^cl_owner_[A-Za-z0-9_-]{43}$/u;
+
+export async function privateSessionHeaders(
+ page: Page,
+ sessionId: string,
+): Promise<{ authorization: string }> {
+ const storageKey = `counterlab.ownerCapability.${encodeURIComponent(sessionId)}`;
+ const capability = await page.evaluate(
+ (key) => window.localStorage.getItem(key),
+ storageKey,
+ );
+ if (capability === null || !ownerCapabilityPattern.test(capability)) {
+ throw new Error(
+ `Private session ${sessionId} has no valid browser-held owner capability`,
+ );
+ }
+ return { authorization: `Bearer ${capability}` };
+}
diff --git a/apps/web/e2e/recovery-and-intake.spec.ts b/apps/web/e2e/recovery-and-intake.spec.ts
index 69685e19..fcdd3195 100644
--- a/apps/web/e2e/recovery-and-intake.spec.ts
+++ b/apps/web/e2e/recovery-and-intake.spec.ts
@@ -1,4 +1,5 @@
import { expect, test, type Page } from "./cloak-test";
+import { privateSessionHeaders } from "./private-session";
import { SAMPLE_LEAKAGE_QUESTION } from "../shared/sample-authority";
const liveQuestion =
@@ -164,6 +165,9 @@ for (const response of [
);
const terminalResponse = await page.request.get(
`/api/sessions/${encodeURIComponent(closedSessionId!)}`,
+ {
+ headers: await privateSessionHeaders(page, closedSessionId!),
+ },
);
expect(terminalResponse.ok()).toBe(true);
expect((await terminalResponse.json()).data.state).toBe(
From e9529b0a4da7beaacf33e3a753158647e9b93146 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 09:17:01 +0530
Subject: [PATCH 062/143] test(e2e): await fresh recovery sessions
---
apps/web/e2e/recovery-and-intake.spec.ts | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/apps/web/e2e/recovery-and-intake.spec.ts b/apps/web/e2e/recovery-and-intake.spec.ts
index fcdd3195..0c33c2d6 100644
--- a/apps/web/e2e/recovery-and-intake.spec.ts
+++ b/apps/web/e2e/recovery-and-intake.spec.ts
@@ -186,6 +186,11 @@ for (const response of [
await page
.getByRole("button", { name: /Revise in a new investigation/i })
.click();
+ await expect
+ .poll(() => storedSessionId(page), {
+ message: "the replacement investigation should install a fresh session",
+ })
+ .not.toBe(closedSessionId);
await expect(
page.getByRole("heading", { name: /What do you think the score means/i }),
).toBeVisible();
From 52b40ef59e86a92e1d6e3a5fc2cb3bf385d204d0 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 09:23:49 +0530
Subject: [PATCH 063/143] fix(learner): activate session routes before render
---
apps/web/e2e/judged-flow.spec.ts | 3 +++
apps/web/src/App.test.tsx | 35 ++++++++++++++++++++++++
apps/web/src/App.tsx | 46 +++++++++++++++++++-------------
3 files changed, 66 insertions(+), 18 deletions(-)
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index f0d9c7d9..bfffe219 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -1907,6 +1907,9 @@ test("refresh restores the question and confirmed Prediction phases", async ({
}) => {
await reset(page);
await page.getByRole("button", { name: /Try verified sample/i }).click();
+ await expect(
+ page.getByRole("heading", { name: /What do you think the score means/i }),
+ ).toBeVisible();
await page.reload();
await expect(
page.getByRole("heading", { name: /What do you think the score means/i }),
diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
index d0ca015a..f3cc74ac 100644
--- a/apps/web/src/App.test.tsx
+++ b/apps/web/src/App.test.tsx
@@ -1503,6 +1503,41 @@ describe("CounterLab judged flow", () => {
expect(story.getByText(/sha-256/i)).toBeInTheDocument();
});
+ it("installs the sample session route before exposing the Question stage", async () => {
+ const user = userEvent.setup();
+ const originalPushState = window.history.pushState.bind(window.history);
+ const questionVisibilityAtSessionActivation: boolean[] = [];
+ const pushStateSpy = vi
+ .spyOn(window.history, "pushState")
+ .mockImplementation((data, unused, url) => {
+ if (String(url).startsWith("/session/")) {
+ questionVisibilityAtSessionActivation.push(
+ screen.queryByRole("heading", {
+ name: /what do you think the score means/i,
+ }) !== null,
+ );
+ }
+ originalPushState(data, unused, url);
+ });
+
+ try {
+ render( );
+ await user.click(
+ screen.getByRole("button", { name: /try verified sample/i }),
+ );
+
+ expect(
+ await screen.findByRole("heading", {
+ name: /what do you think the score means/i,
+ }),
+ ).toBeInTheDocument();
+ expect(window.location.pathname).toBe("/session/session_ui");
+ expect(questionVisibilityAtSessionActivation).toEqual([false]);
+ } finally {
+ pushStateSpy.mockRestore();
+ }
+ });
+
it("opens one deterministic stage hint and records only its fixed identity", async () => {
const user = userEvent.setup();
const fetcher = installApi();
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index cc4988ad..c9a5d3da 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -554,6 +554,13 @@ const storageKeys = {
activeRunnerJobKind: "counterlab.activeRunnerJobKind",
} as const;
+function activateSessionRoute(sessionId: string): void {
+ const path = `/session/${encodeURIComponent(sessionId)}`;
+ if (window.location.pathname !== path) {
+ window.history.pushState({}, "", path);
+ }
+}
+
function SkipLink() {
return (
@@ -5434,14 +5441,15 @@ export function App() {
sampleId: "leakage-01",
});
request.assertCurrent();
- setArtifact(sample);
- setSession(created);
window.localStorage.setItem(storageKeys.sessionId, created.sessionId);
window.localStorage.setItem(storageKeys.claim, SAMPLE_LEAKAGE_QUESTION);
window.localStorage.setItem(
storageKeys.claimSessionId,
created.sessionId,
);
+ activateSessionRoute(created.sessionId);
+ setArtifact(sample);
+ setSession(created);
setStage("claim");
});
};
@@ -5539,7 +5547,6 @@ export function App() {
artifactId: uploaded.artifactId,
});
request.assertCurrent();
- setSession(created);
window.localStorage.setItem(storageKeys.sessionId, created.sessionId);
if (claim.trim().length > 0) {
window.localStorage.setItem(storageKeys.claim, claim);
@@ -5548,6 +5555,8 @@ export function App() {
created.sessionId,
);
}
+ activateSessionRoute(created.sessionId);
+ setSession(created);
setStage("claim");
};
@@ -5587,21 +5596,6 @@ export function App() {
request.assertCurrent();
const restartedClaim =
sessionBeliefPresentation(restarted)?.claim ?? sourceClaim;
- setSession(restarted);
- setMode(presentationMode(restarted.mode));
- setClaim(restartedClaim);
- setConfirmed(
- restarted.state !== "INGESTED" &&
- restarted.state !== "BELIEF_TEST_PROPOSED",
- );
- setPrediction(
- restarted.prediction === undefined
- ? null
- : predictionChoiceFromReceipt(restarted.prediction.choice),
- );
- setConfidence(restarted.prediction?.confidence ?? 72);
- setAnalysisPreview(null);
- setSensitiveContentApproved(false);
try {
window.localStorage.setItem(storageKeys.sessionId, restarted.sessionId);
window.localStorage.setItem(
@@ -5622,6 +5616,22 @@ export function App() {
// The reconciled server session remains authoritative when this
// browser blocks optional local recovery storage.
}
+ activateSessionRoute(restarted.sessionId);
+ setSession(restarted);
+ setMode(presentationMode(restarted.mode));
+ setClaim(restartedClaim);
+ setConfirmed(
+ restarted.state !== "INGESTED" &&
+ restarted.state !== "BELIEF_TEST_PROPOSED",
+ );
+ setPrediction(
+ restarted.prediction === undefined
+ ? null
+ : predictionChoiceFromReceipt(restarted.prediction.choice),
+ );
+ setConfidence(restarted.prediction?.confidence ?? 72);
+ setAnalysisPreview(null);
+ setSensitiveContentApproved(false);
setStage(restoredStageForSession(restarted));
});
};
From 6142e5edca0a983e70c3cb4a9df911176ebb3e4b Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 09:29:24 +0530
Subject: [PATCH 064/143] test(e2e): align current learner journeys
---
apps/web/e2e/judged-flow.spec.ts | 71 ++++++++++++++++++++++----------
1 file changed, 50 insertions(+), 21 deletions(-)
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index bfffe219..6a6f49ca 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -1527,7 +1527,7 @@ for (const viewport of beliefBreakViewports) {
}
test("Judge Mode distinguishes every authority path", async ({ page }) => {
- const healthResponse = await page.request.get("/api/health");
+ const healthResponse = await page.request.get("/api/health?readiness=probe");
expect(healthResponse.ok()).toBe(true);
const health = (await healthResponse.json()) as {
data?: {
@@ -1555,7 +1555,8 @@ test("Judge Mode distinguishes every authority path", async ({ page }) => {
name: /see a verified belief break in ten seconds/i,
}),
).toBeVisible();
- await expect(page.getByText("Sample lesson")).toBeVisible();
+ await page.getByRole("button", { name: /Check live readiness/i }).click();
+ await expect(page.getByText("Sample lesson", { exact: true })).toBeVisible();
await expect(page.getByText("Live notebook analysis")).toBeVisible();
await expect(
page.getByText("Verified replay", { exact: true }),
@@ -1567,7 +1568,10 @@ test("Judge Mode distinguishes every authority path", async ({ page }) => {
);
} else {
await expect(
- page.getByText(/live authority is unavailable/i),
+ page.getByRole("status").filter({
+ hasText:
+ /live authority (?:is|remains) unavailable|live authority did not pass the latest readiness check/i,
+ }),
).toBeVisible();
await expect(page.getByRole("link", { name: /run live/i })).toHaveCount(0);
}
@@ -1614,7 +1618,12 @@ test("the first visit explains the lesson before asking for technical knowledge"
}),
).toBeVisible();
await expect(
- page.getByText(/No account needed.*never run the cells/i),
+ page.getByText(/reads the evidence and never runs its cells/i),
+ ).toBeVisible();
+ await expect(
+ page.getByText(
+ /No account needed.*Sample, live, and replay stay clearly labelled/i,
+ ),
).toBeVisible();
await expect(page.getByLabel("Your question or claim")).toBeInViewport();
await expect(page.getByLabel("Attach notebook")).toBeVisible();
@@ -1819,7 +1828,9 @@ test("the lesson keeps one learner decision in focus at a time", async ({
await page.getByRole("button", { name: /Check transfer/i }).click();
await expect(
- page.getByRole("heading", { name: /You applied the rule correctly/i }),
+ page.getByRole("heading", {
+ name: /This fixed forecasting transfer passed/i,
+ }),
).toBeVisible();
expect(await page.evaluate(() => window.scrollY)).toBeLessThan(24);
await expect(
@@ -1831,7 +1842,10 @@ test("the lesson keeps one learner decision in focus at a time", async ({
expect(await page.evaluate(() => window.scrollY)).toBeLessThan(24);
await expect(page.locator("pre.diff")).not.toBeVisible();
await expect(
- page.getByRole("button", { name: "Download proof record", exact: true }),
+ page.getByRole("button", {
+ name: "Download repaired notebook",
+ exact: true,
+ }),
).toBeVisible();
});
@@ -1983,7 +1997,9 @@ test("refresh restores the current lesson and the committed prediction", async (
).toBeVisible();
await page.reload();
await expect(
- page.getByRole("heading", { name: /You applied the rule correctly/i }),
+ page.getByRole("heading", {
+ name: /This fixed forecasting transfer passed/i,
+ }),
).toBeVisible();
await page.getByRole("button", { name: /Verify notebook patch/i }).click();
await waitForSamplePatch(page);
@@ -2073,9 +2089,11 @@ test("local hints and Theater views never request a model or new result", async
"true",
);
await expect(
- page.getByRole("img", {
- name: /Verified accuracy comparison.*familiar rows.*new customers/i,
- }),
+ page
+ .locator('[data-trusted-visual-id="verified_sample_belief_break_v1"]')
+ .getByRole("region", {
+ name: "Verified sample belief-break mechanism",
+ }),
).toBeVisible();
const hint = page.getByLabel("Contextual help");
@@ -2136,10 +2154,21 @@ test("Replay remains visibly labelled and read-only after refresh", async ({
}) => {
await reset(page);
await page.getByRole("button", { name: /Watch verified replay/i }).click();
- const replayBanner = page.getByLabel("Legacy replay status");
+ const replayBanner = page.getByLabel("Replay status");
await expect(replayBanner).toContainText(
"Verified replay · read-only stored evidence",
);
+ await expect(
+ page.getByRole("heading", { name: /Replay verified session/i }),
+ ).toBeVisible();
+ const continueReplay = page.getByRole("button", {
+ name: /Continue replay/i,
+ });
+ await expect(continueReplay).toBeVisible();
+ await continueReplay.click();
+ await expect(page.getByLabel("Legacy replay status")).toContainText(
+ "Verified replay · read-only stored evidence",
+ );
await expect(
page.getByRole("heading", {
name: /Inspect the result without changing its history/i,
@@ -2148,9 +2177,7 @@ test("Replay remains visibly labelled and read-only after refresh", async ({
await expect(
page.getByRole("table", { name: /Stored fixed-kernel comparison/i }),
).toBeVisible();
- await expect(
- page.getByRole("button", { name: /Continue replay/i }),
- ).toHaveCount(0);
+ await expect(continueReplay).toHaveCount(0);
await expect(
page.getByRole("button", { name: /Show me what happened/i }),
).toHaveCount(0);
@@ -2169,7 +2196,7 @@ test("Replay remains visibly labelled and read-only after refresh", async ({
test("missing live capabilities are stated without claiming a model call", async ({
page,
}) => {
- await page.route("**/api/health", async (route) => {
+ await page.route("**/api/health*", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
@@ -2202,7 +2229,7 @@ test("missing live capabilities are stated without claiming a model call", async
test("configured reasoning cannot start without a qualified hosted runner", async ({
page,
}) => {
- await page.route("**/api/health", async (route) => {
+ await page.route("**/api/health*", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
@@ -2241,7 +2268,7 @@ test("configured reasoning cannot start without a qualified hosted runner", asyn
test("partial generation isolation cannot expose live notebook upload", async ({
page,
}) => {
- await page.route("**/api/health", async (route) => {
+ await page.route("**/api/health*", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
@@ -2380,9 +2407,11 @@ test("the judged path is keyboard operable with reduced motion", async ({
page.getByText(/Interpretation recorded locally/i),
).toBeVisible();
- const comparison = page.getByRole("img", {
- name: /Verified accuracy comparison.*familiar rows.*new customers/i,
- });
+ const comparison = page
+ .locator('[data-trusted-visual-id="verified_sample_belief_break_v1"]')
+ .getByRole("region", {
+ name: "Verified sample belief-break mechanism",
+ });
await expect(comparison).toBeVisible();
const observeTab = page.getByRole("tab", { name: /Observe/i });
await observeTab.focus();
@@ -2391,7 +2420,7 @@ test("the judged path is keyboard operable with reduced motion", async ({
await page.keyboard.press("ArrowRight");
await expect(page.getByRole("tab", { name: /Boundary/i })).toBeFocused();
await expect(page.getByRole("tabpanel")).toContainText(
- /Verified sample boundary/i,
+ /Verified sample exploration/i,
);
await page.getByRole("button", { name: /Reveal the map/i }).click();
await expect(
From d184e5a79f0d80b01891c7595a095442925bd58c Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 09:38:56 +0530
Subject: [PATCH 065/143] test(e2e): configure local intake authority
---
apps/web/e2e/recovery-and-intake.spec.ts | 7 ++++---
apps/web/playwright.config.ts | 10 ++++++++++
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/apps/web/e2e/recovery-and-intake.spec.ts b/apps/web/e2e/recovery-and-intake.spec.ts
index 0c33c2d6..43569eac 100644
--- a/apps/web/e2e/recovery-and-intake.spec.ts
+++ b/apps/web/e2e/recovery-and-intake.spec.ts
@@ -5,9 +5,10 @@ import { SAMPLE_LEAKAGE_QUESTION } from "../shared/sample-authority";
const liveQuestion =
"Does this notebook score show that the model generalizes to customers it has never seen?";
const supportedNotebookPath = new URL(
- "../../../fixtures/notebooks/customer_churn_leakage.ipynb",
+ "../../../evals/held-out/notebooks/leakage-rows-pipeline.ipynb",
import.meta.url,
).pathname;
+const supportedNotebookName = "leakage-rows-pipeline.ipynb";
const readyHealth = {
ok: true,
@@ -354,7 +355,7 @@ test("a supported live upload creates one source-bound session", async ({
await expect(
page.getByRole("heading", { name: /What do you think the score means/i }),
).toBeVisible();
- await expect(page.getByText("customer_churn_leakage.ipynb")).toBeVisible();
+ await expect(page.getByText(supportedNotebookName)).toBeVisible();
await expect(
page.getByRole("button", { name: /Compare two explanations/i }),
).toBeEnabled();
@@ -425,7 +426,7 @@ test("a lost private-session response retries without re-uploading", async ({
await expect(
page.getByRole("button", { name: /Retry private session setup/i }),
).toBeEnabled();
- await expect(page.getByText("customer_churn_leakage.ipynb")).toBeVisible();
+ await expect(page.getByText(supportedNotebookName)).toBeVisible();
await page
.getByRole("button", { name: /Retry private session setup/i })
diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts
index 6bf51bb8..610eddd2 100644
--- a/apps/web/playwright.config.ts
+++ b/apps/web/playwright.config.ts
@@ -90,6 +90,9 @@ const baseURL = remoteBaseURL ?? `http://127.0.0.1:${port}`;
const staticDesignReview =
browserAuthority.kind === "stock-chromium-design-review" &&
process.env.COUNTERLAB_E2E_STATIC_CLIENT === "true";
+const localAdmissionKey =
+ process.env.COUNTERLAB_ADMISSION_KEY?.trim() ||
+ ["counterlab", "local", "e2e", "admission", "only", "000000"].join("-");
export default defineConfig({
testDir: "./e2e",
@@ -129,6 +132,13 @@ export default defineConfig({
? "node ../../scripts/serve-built-client.mjs"
: `./node_modules/.bin/vite --host 127.0.0.1 --port ${port}`,
cwd: import.meta.dirname,
+ ...(staticDesignReview
+ ? {}
+ : {
+ env: {
+ COUNTERLAB_ADMISSION_KEY: localAdmissionKey,
+ },
+ }),
url: baseURL,
reuseExistingServer: false,
timeout: 120_000,
From cdbe153f0f3bde97caffcd6535bac6820dfc234c Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 09:42:34 +0530
Subject: [PATCH 066/143] fix(learner): distinguish notebook evidence keys
---
apps/web/e2e/recovery-and-intake.spec.ts | 10 +++++++
apps/web/src/App.test.tsx | 35 +++++++++++++++++++++++-
apps/web/src/App.tsx | 6 ++--
3 files changed, 46 insertions(+), 5 deletions(-)
diff --git a/apps/web/e2e/recovery-and-intake.spec.ts b/apps/web/e2e/recovery-and-intake.spec.ts
index 43569eac..bc72ea35 100644
--- a/apps/web/e2e/recovery-and-intake.spec.ts
+++ b/apps/web/e2e/recovery-and-intake.spec.ts
@@ -338,6 +338,15 @@ test("a supported live upload creates one source-bound session", async ({
}) => {
let uploadRequests = 0;
let sessionCreationRequests = 0;
+ const duplicateKeyWarnings: string[] = [];
+ page.on("console", (message) => {
+ if (
+ message.type() === "error" &&
+ /same key|keys should be unique/i.test(message.text())
+ ) {
+ duplicateKeyWarnings.push(message.text());
+ }
+ });
page.on("request", (request) => {
if (isPostTo(request.url(), request.method(), "/api/artifacts")) {
uploadRequests += 1;
@@ -362,6 +371,7 @@ test("a supported live upload creates one source-bound session", async ({
expect(await storedSessionId(page)).toMatch(/^session_/u);
expect(uploadRequests).toBe(1);
expect(sessionCreationRequests).toBe(1);
+ expect(duplicateKeyWarnings).toEqual([]);
});
test("an interrupted upload accepts the same file on retry", async ({
diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
index f3cc74ac..81090245 100644
--- a/apps/web/src/App.test.tsx
+++ b/apps/web/src/App.test.tsx
@@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
+ ArtifactManifestSchema,
migrateBeliefTestV1ToV2,
VerifiedResultSetSchema,
type EvidenceEvent,
@@ -11,7 +12,7 @@ import {
import imbalanceResultText from "../../../fixtures/held-out/imbalance_epistemic_competing_v2.json?raw";
import { SAMPLE_LEAKAGE_QUESTION } from "../shared/sample-authority";
-import { App } from "./App";
+import { App, notebookEvidenceReferences } from "./App";
import { SessionViewSchema } from "./api";
import {
publicReplayFixture,
@@ -1019,6 +1020,38 @@ async function openSampleModelDuel(user: ReturnType) {
}
describe("CounterLab judged flow", () => {
+ it("assigns distinct evidence identities to metrics from one output", () => {
+ const outputHash = "d".repeat(64);
+ const references = notebookEvidenceReferences(
+ ArtifactManifestSchema.parse({
+ ...uploadedArtifact,
+ cells: [
+ {
+ index: 1,
+ type: "code",
+ sourceSha256: "b".repeat(64),
+ sourceExcerpt: "accuracy and roc_auc",
+ executionCount: 1,
+ outputHashes: [outputHash],
+ symbols: [],
+ metricCandidates: [
+ { name: "accuracy", value: 0.98, outputIndex: 0 },
+ { name: "roc_auc", value: 0.97, outputIndex: 0 },
+ ],
+ },
+ ],
+ }),
+ );
+
+ expect(references.map(({ id }) => id)).toEqual([
+ `${outputHash}-0-accuracy`,
+ `${outputHash}-0-roc_auc`,
+ ]);
+ expect(new Set(references.map(({ id }) => id)).size).toBe(
+ references.length,
+ );
+ });
+
it("keeps Judge Mode on its own refresh-safe route", async () => {
const user = userEvent.setup();
const fetcher = installApi({
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index c9a5d3da..90df385d 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -708,7 +708,7 @@ function notebookScoreDisplay(
};
}
-function notebookEvidenceReferences(
+export function notebookEvidenceReferences(
artifact: ArtifactView | null,
): readonly NotebookEvidenceReference[] {
if (artifact?.fileSha256 === sampleArtifact.fileSha256) {
@@ -733,9 +733,7 @@ function notebookEvidenceReferences(
if (artifact === null) return [];
const metricReferences = artifact.cells.flatMap((cell) =>
cell.metricCandidates.map((metric) => ({
- id:
- cell.outputHashes[metric.outputIndex] ??
- `${cell.sourceSha256}-${metric.outputIndex}-${metric.name}`,
+ id: `${cell.outputHashes[metric.outputIndex] ?? cell.sourceSha256}-${metric.outputIndex}-${metric.name}`,
reference: `Cell ${cell.index} · output ${metric.outputIndex}`,
relevance: `${metric.name} is displayed notebook evidence attached to this question.`,
excerpt: `${metric.name}: ${metric.value.toLocaleString()}`,
From 4f7bbda6e980855b02641f384ca1ae6859c72dc3 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 09:50:37 +0530
Subject: [PATCH 067/143] test(perf): scope first-fold browser budgets
---
apps/web/e2e/judged-flow.spec.ts | 94 +++++++++++++++++++++-----------
1 file changed, 63 insertions(+), 31 deletions(-)
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index 6a6f49ca..d063e16f 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -667,12 +667,13 @@ async function resetWithBrowserFailureObservation(
return failures;
}
-function expectWithinComprehensionBudget(
- startedAt: number,
+async function expectWithinComprehensionBudget(
+ page: Page,
maximumMs: number,
label: string,
) {
- expect(Date.now() - startedAt, label).toBeLessThanOrEqual(maximumMs);
+ const navigationElapsedMs = await page.evaluate(() => performance.now());
+ expect(navigationElapsedMs, label).toBeLessThanOrEqual(maximumMs);
}
function expectNoBrowserFailures(failures: BrowserFailureLog) {
@@ -711,7 +712,6 @@ async function expectBeliefBreakInFirstViewport(
page: Page,
surface: Locator,
modeLabel: RegExp,
- startedAt: number,
) {
const mechanism = surface.getByRole("region", {
name: "Verified sample belief-break mechanism",
@@ -799,6 +799,14 @@ async function expectBeliefBreakInFirstViewport(
await expect(mechanism).toBeVisible();
for (const stage of comprehensionStages) {
+ await Promise.all(
+ stage.required.map(({ locator }) => expect(locator).toBeVisible()),
+ );
+ await expectWithinComprehensionBudget(
+ page,
+ stage.budgetMs,
+ `Judge ${stage.label} must be inspectable within ${stage.budgetMs / 1_000} seconds`,
+ );
for (const required of stage.required) {
await expectEntirelyInFirstViewport(
page,
@@ -806,15 +814,19 @@ async function expectBeliefBreakInFirstViewport(
required.label,
);
}
- expectWithinComprehensionBudget(
- startedAt,
- stage.budgetMs,
- `Judge ${stage.label} must be inspectable within ${stage.budgetMs / 1_000} seconds`,
- );
}
}
-async function captureBeliefBreakScreenshot(page: Page, fileName: string) {
+async function captureBeliefBreakScreenshot(
+ page: Page,
+ fileName: string,
+ options: {
+ performanceBudgetScope?:
+ "navigation-first-fold" | "post-seal-spa-observation";
+ } = {},
+) {
+ const performanceBudgetScope =
+ options.performanceBudgetScope ?? "navigation-first-fold";
const configuredDirectory =
process.env.COUNTERLAB_E2E_BELIEF_BREAK_EVIDENCE_DIR ??
resolve(requiredRuntimeRoot(), "evidence", "belief-break");
@@ -823,7 +835,9 @@ async function captureBeliefBreakScreenshot(page: Page, fileName: string) {
);
await page.screenshot({ path: destination, fullPage: false });
const performance = await snapshotBrowserPerformanceEvidence(page);
- expectFirstFoldPerformanceBudgets(performance);
+ if (performanceBudgetScope === "navigation-first-fold") {
+ expectFirstFoldPerformanceBudgets(performance);
+ }
const evidenceDestination = await ensureRuntimeParent(
resolve(configuredDirectory, fileName.replace(/\.png$/u, ".json")),
);
@@ -834,6 +848,10 @@ async function captureBeliefBreakScreenshot(page: Page, fileName: string) {
browserAuthority: currentBrowserAuthorityLabel(),
capturedAt: new Date().toISOString(),
performance,
+ performanceBudget: {
+ enforced: performanceBudgetScope === "navigation-first-fold",
+ scope: performanceBudgetScope,
+ },
screenshot: fileName,
},
null,
@@ -1322,17 +1340,45 @@ for (const viewport of beliefBreakViewports) {
test(`${viewport.name} Landing keeps the unprimed Question and fair-test promise in the first viewport`, async ({
page,
}) => {
- const navigationStartedAt = Date.now();
await page.setViewportSize(viewport);
const failures = await resetWithBrowserFailureObservation(page);
+ const heading = page.getByRole("heading", {
+ name: /What result are you trying to understand/i,
+ });
+ const artifactSafetySummary = page.getByText(
+ /Ask a question or attach a supported notebook.*never runs its cells/i,
+ );
+ const composer = page.getByLabel("Your question or claim");
+ const submit = page.getByRole("button", { name: /Test this claim/i });
+ const promptStarters = page.getByRole("group", {
+ name: /Prompt starters/i,
+ });
+ const preview = page.locator(
+ '[data-presentation="strip"][data-result-visibility="locked"]',
+ );
+ await Promise.all(
+ [
+ heading,
+ artifactSafetySummary,
+ composer,
+ submit,
+ promptStarters,
+ preview,
+ ].map((locator) => expect(locator).toBeVisible()),
+ );
+ await expectWithinComprehensionBudget(
+ page,
+ 10_000,
+ "Landing Question and fair-test promise must become inspectable within ten seconds",
+ );
+
const skipLink = page.getByRole("link", {
name: /Skip to main content/i,
});
await skipLink.focus();
await expect(skipLink).toBeFocused();
await page.keyboard.press("Tab");
- const composer = page.getByLabel("Your question or claim");
await expect(composer).toBeFocused();
await expectEntirelyInFirstViewport(
page,
@@ -1341,20 +1387,15 @@ for (const viewport of beliefBreakViewports) {
);
await expectEntirelyInFirstViewport(
page,
- page.getByRole("heading", {
- name: /What result are you trying to understand/i,
- }),
+ heading,
"Question-first heading",
);
await expectEntirelyInFirstViewport(
page,
- page.getByText(
- /Ask a question or attach a supported notebook.*never runs its cells/i,
- ),
+ artifactSafetySummary,
"artifact safety summary",
);
- const submit = page.getByRole("button", { name: /Test this claim/i });
await expect(submit).toBeDisabled();
await expectEntirelyInFirstViewport(
page,
@@ -1363,7 +1404,7 @@ for (const viewport of beliefBreakViewports) {
);
await expectEntirelyInFirstViewport(
page,
- page.getByRole("group", { name: /Prompt starters/i }),
+ promptStarters,
"learner prompt starters",
);
if (viewport.width <= 620) {
@@ -1372,9 +1413,6 @@ for (const viewport of beliefBreakViewports) {
expect(submitBox!.width).toBeGreaterThanOrEqual(130);
}
- const preview = page.locator(
- '[data-presentation="strip"][data-result-visibility="locked"]',
- );
await expectEntirelyInFirstViewport(
page,
preview,
@@ -1395,11 +1433,6 @@ for (const viewport of beliefBreakViewports) {
name: "Verified sample belief-break mechanism",
}),
).toHaveCount(0);
- expectWithinComprehensionBudget(
- navigationStartedAt,
- 10_000,
- "Landing Question and fair-test promise must become inspectable within ten seconds",
- );
await expectNoHorizontalOverflow(page);
await captureBeliefBreakScreenshot(
page,
@@ -1412,7 +1445,6 @@ for (const viewport of beliefBreakViewports) {
test(`${viewport.name} Judge Mode shows the honest fixed-sample belief break in the first viewport`, async ({
page,
}) => {
- const navigationStartedAt = Date.now();
const failures = observeBrowserFailures(page);
await installBrowserPerformanceEvidence(page);
await page.setViewportSize(viewport);
@@ -1426,7 +1458,6 @@ for (const viewport of beliefBreakViewports) {
page,
preview,
/Completed fixed sample.*not a live result/i,
- navigationStartedAt,
);
await expect(
preview.getByText(
@@ -1520,6 +1551,7 @@ for (const viewport of beliefBreakViewports) {
await captureBeliefBreakScreenshot(
page,
`belief-break-sample-post-seal-${viewport.name}.png`,
+ { performanceBudgetScope: "post-seal-spa-observation" },
);
await page.waitForLoadState("networkidle");
expectNoBrowserFailures(failures);
From f5155e0cb19fec74330e1d3df9ab0bce0feca52b Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 09:52:29 +0530
Subject: [PATCH 068/143] docs(progress): record browser harness recovery
---
docs/PROGRESS.md | 83 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 56f48b7c..50f0f2eb 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -3598,3 +3598,86 @@ worktree entry besides this progress update is the preserved FUSE tombstone
submission receipt remain externally unverified.
6. The feature branch has not been pushed and has not been merged into `main`.
Push and fast-forward merge remain downstream of factual release gates.
+
+## Browser-harness recovery checkpoint — 2026-07-21T04:20:55Z
+
+Deadline: `2026-07-22T00:00:00Z`; **19 hours 39 minutes 05 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD `4f7bbda`; the only
+worktree entry besides this progress update is the preserved FUSE tombstone
+`scripts/.fuse_hidden0000a9600000801f`. It remains unread, unchanged, and
+unstaged.
+
+### Independently committed slices
+
+- `2986046 test(e2e): authorize private session assertions` sends the
+ browser-held owner capability on direct private-session E2E requests.
+- `e9529b0 test(e2e): await fresh recovery sessions` verifies that Reject and
+ Not enough evidence recovery creates a fresh session before resubmission.
+- `52b40ef fix(learner): activate session routes before render` persists the
+ session and route before exposing learner state, closing refresh and browser
+ history races.
+- `6142e5e test(e2e): align current learner journeys` updates stale accessible
+ names and route expectations without changing production authority.
+- `d184e5a test(e2e): configure local intake authority` supplies a clearly
+ local-only admission key to the local Playwright server and uses a distinct
+ supported live notebook fixture rather than sample evidence.
+- `cdbe153 fix(learner): distinguish notebook evidence keys` gives repeated
+ notebook evidence references stable unique React keys and adds unit/browser
+ console regression coverage.
+- `4f7bbda test(perf): scope first-fold browser budgets` measures current-page
+ visible readiness instead of harness setup time, checks required content in
+ parallel before geometry assertions, and records post-seal SPA metrics as
+ observation-only rather than mislabelling navigation LCP as post-interaction
+ qualification.
+
+### Exact verification in these slices
+
+- Full App integration suite before the evidence-key characterization:
+ **71/71 passed**; the added evidence-key unit test then passed **1/1**.
+- Recovery and route-regression browser runs: **2/2** and **1/1 passed**.
+- Updated learner-selector browser slices: **7/10 passed** on the first run;
+ the corrected three cases then passed **3/3**.
+- Intake browser matrix on fresh local D1 state: malformed/unsupported cases
+ **3/3 passed** and supported/retry cases **3/3 passed**.
+- Duplicate-key supported-upload browser regression: **1/1 passed** with no
+ React duplicate-key console error.
+- Desktop/mobile Landing, Judge first fold, and post-seal sample browser
+ matrix: **6/6 passed in 22.5 seconds**.
+- Current first-fold design-review metrics: Landing LCP **684–740 ms**, Judge
+ LCP **1,216–1,352 ms**, CLS **0–0.02145**, and TTFB **6.2–28.7 ms**.
+- E2E strict TypeScript, scoped Prettier, Git whitespace, and scoped secret
+ scans: **passed**.
+- A prior complete stock-browser run collected **40 tests** and produced
+ **10 passed, 25 failed, 5 skipped**. All 25 failures were classified as
+ harness authorization, stale selectors, route ordering, duplicate evidence
+ identity, or performance-scope problems. Targeted repairs now pass, but the
+ complete 40-test matrix has not yet been rerun, so it is not claimed green.
+
+All rendered checks in this checkpoint used explicitly labelled
+`stock-chromium-design-review` authority. They are useful implementation and
+visual-review evidence, but do not satisfy the required CloakBrowser public
+release qualification.
+
+### Current remaining gates
+
+1. Run the complete 40-test stock-browser matrix against fresh local D1 state,
+ repair any remaining product or harness failures, and record exact results.
+2. Run the final deterministic TypeScript, Vitest, Pytest, build, formatting,
+ secret, whitespace, mutation, and held-out gates at frozen source.
+3. Obtain a clean exact source `S`, execute the real aggregate-cgroup sentinel,
+ build one exact runner image, regenerate source/image-bound SBOM, VEX, and
+ scientific evidence, create evidence commit `E`, and qualify the tuple.
+ The preserved FUSE entry and the constitution's prohibition on required
+ `/sys` and `/proc` reads currently keep this chain fail-closed.
+4. Establish repository-contained Wrangler identity, deploy only the qualified
+ tuple, and verify exact public Worker/Container/source identity and complete
+ production smoke evidence.
+5. Run all public desktop/mobile, keyboard, screen-reader, reduced-motion,
+ reconnect, download, console/network, and Web Vitals journeys through an
+ injected CloakBrowser endpoint. This session still has no
+ `CLOAK_CDP_ENDPOINT`.
+6. Keep learner evidence as `NO_DATA` unless real consented observations occur.
+ Public repository/video/feedback alignment, Devpost publication, logged-out
+ verification, and submission receipt remain externally unverified.
+7. Push the feature branch and fast-forward `main` only after the ship gates
+ above are factual; neither publication action has occurred.
From e0313101952e2e1b66421bfb2c38adae383f7fad Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 09:58:17 +0530
Subject: [PATCH 069/143] docs(release): record local browser matrix
---
docs/PROGRESS.md | 17 +++++++++++++
.../19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md | 24 ++++++++++++-------
2 files changed, 33 insertions(+), 8 deletions(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 50f0f2eb..654960f5 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -3681,3 +3681,20 @@ release qualification.
verification, and submission receipt remain externally unverified.
7. Push the feature branch and fast-forward `main` only after the ship gates
above are factual; neither publication action has occurred.
+
+## Complete local browser-matrix checkpoint — 2026-07-21T04:26:26Z
+
+- The complete Playwright collection executed against fresh repository-local
+ D1 state under explicit `stock-chromium-design-review` authority: **40 tests
+ in 3 files, 35 passed, 5 intentionally skipped, 0 failed in 2.7 minutes**.
+- The five skips are exact and expected at this gate: two public mobile replay
+ journeys require `COUNTERLAB_E2E_BASE_URL`, one public asset scan requires a
+ frozen release manifest and public origin, and two untouched supported live
+ journeys require `COUNTERLAB_E2E_LIVE=1` with a genuinely qualified analyst
+ and hosted runner.
+- All local Sample, Replay, intake, recovery, refresh/history, authority,
+ responsive, keyboard, reduced-motion, transfer, patch-lock, first-fold,
+ console/network, and no-result-before-Prediction journeys passed.
+- This closes the local stock-browser regression gate only. It does not close
+ the exact-public, hosted-live, CloakBrowser, real screen-reader, deployment,
+ or submission gates.
diff --git a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
index 5158aeec..618ecd9c 100644
--- a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
+++ b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
@@ -1,10 +1,10 @@
# Release dependency and review graph
-- Checkpoint: `2026-07-21T03:07:00Z`
+- Checkpoint: `2026-07-21T04:26:26Z`
- Branch: `feat/learner-ux-v6.1`
-- Committed HEAD: `e4dc33eb44962b972c0157e5ecb4ed9760c16ee4`
+- Committed HEAD: `f5155e0cb19fec74330e1d3df9ab0bce0feca52b`
- Deadline: `2026-07-22T00:00:00Z`
-- Time remaining at this checkpoint: **20 hours 53 minutes**
+- Time remaining at this checkpoint: **19 hours 33 minutes 34 seconds**
## Purpose and evidence boundary
@@ -22,6 +22,12 @@ worktree will reject this state.
No current deployment, rendered CloakBrowser journey, learner outcome, video,
Devpost submission, branch push, or merge is claimed here.
+The complete local `stock-chromium-design-review` matrix now passes every
+locally executable journey: **35 passed, 5 intentionally skipped, 0 failed in
+2.7 minutes**. The five skips are the two exact-public mobile replay journeys,
+one exact-public asset scan, and two qualified hosted-runner journeys. This is
+useful source/design evidence; it is not public or CloakBrowser qualification.
+
## Release dependency graph
```mermaid
@@ -37,7 +43,7 @@ flowchart TD
I["Release check receipt v5 + deterministic full gate NOT RUN"]
J["Cloudflare deployment maintenance + D1 + Container + Worker NOT RUN"]
K["Public identity and smoke deployment receipt v7 + /ready + health + live loops NOT RUN"]
- L["CloakBrowser qualification 40 journeys + manual accessibility/performance matrix 0 rendered journeys"]
+ L["Browser qualification local stock 35 pass / 5 public-live skips public CloakBrowser: 0 journeys"]
M["Submission package public app + repo + video + feedback + honest NO_DATA EXTERNALLY UNVERIFIED"]
N["Git publication push feature -> verify remote -> fast-forward main -> push main NOT RUN"]
@@ -68,7 +74,7 @@ flowchart TD
| Release check | Clean `E`, qualified receipt, exact local images and runtime | Release-check receipt v5 after formatting, tests, mutations, held-out, sandbox, scientific, build, reproduction, patch replay, and secret gates | **Not run** | Any failure blocks deployment |
| Cloudflare deployment | Qualified receipt, release-check receipt, registry image, required secrets, recovery target, working CloakBrowser endpoint | Exact maintenance/final Worker versions, D1 migration log, exact Container digest, deployment receipt v7 | **Not run** | Stop before mutation when preflight is red; after mutation, automated Worker recovery does not reverse D1 or Container state |
| Public identity and smoke | Active exact Worker/Container tuple | `/ready`, `/api/health?readiness=probe`, Worker 100% traffic, Container identity, public asset scan, Sample, Replay, leakage and imbalance live evidence, Capsule/replay evidence | **Not run** | Source, image, Worker, client, receipt, or capability mismatch is a release failure |
-| CloakBrowser | Exact public tuple and injected `CLOAK_CDP_ENDPOINT` | All 40 current tests plus responsive, keyboard, screen-reader, 200% zoom, reduced-motion, console/network, downloads, reconnect, and Web Vitals evidence | **40 statically collected; 0 rendered** | Stock Chromium is design-review evidence only and cannot close this gate |
+| CloakBrowser | Exact public tuple and injected `CLOAK_CDP_ENDPOINT` | All 40 current tests plus responsive, keyboard, screen-reader, 200% zoom, reduced-motion, console/network, downloads, reconnect, and Web Vitals evidence | Local stock design review: **35 passed, 5 public/live skips, 0 failed**; public CloakBrowser: **0 journeys** | Stock Chromium is design-review evidence only and cannot close this gate |
| Devpost and impact | Qualified public tuple, public repo/video/feedback links, factual copy | Logged-out link audit, public video from exact tuple, Devpost receipt, real consented learner aggregates or explicit `NO_DATA` | **Externally unverified; learner evidence remains `NO_DATA`** | Never infer a submission or learner result |
| Push and merge | Every ship gate factual and final source reviewed | Verified feature remote tip, fast-forward-only `main`, verified main remote tip | **Not run** | Do not push or merge a `NO-GO` release |
@@ -86,7 +92,7 @@ flowchart LR
subgraph Verification["Pre-release verification"]
T1["Vitest + TypeScript"]
T2["Pytest + cross-runtime parity"]
- T3["Playwright source + static collection"]
+ T3["Playwright source + local stock matrix"]
Q1["Independent review"]
end
@@ -147,8 +153,10 @@ Reported verification for the integrated aggregate slice is:
- complete runner Pytest with both required source roots: **82/82 passed**;
- repository, web client, generated Worker, and Worker TypeScript: **passed**;
- E2E strict TypeScript: **passed**;
-- Playwright static collection: **40 tests in 3 files**, with no browser
- launched;
+- Complete local stock-browser matrix: **40 tests in 3 files**, **35 passed,
+ 5 intentionally skipped, 0 failed in 2.7 minutes**. The public replay,
+ public asset, and real hosted-runner cases remain deferred to the exact
+ release tuple;
- scoped Prettier, Python compilation, Node syntax, Git whitespace, and secret
scans: **passed**;
- independent runtime integration review: **GO** after the implementation
From 9229c8ef4447063f8f6eea717da970e633c36d7e Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 10:08:05 +0530
Subject: [PATCH 070/143] style(tests): normalize scientific UI fixtures
---
.../TrustedLabSceneRenderer.test.tsx | 18 ++++++++----------
.../src/scientific-candidate-v5.test.ts | 3 ++-
2 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/apps/web/src/components/generative-ui/TrustedLabSceneRenderer.test.tsx b/apps/web/src/components/generative-ui/TrustedLabSceneRenderer.test.tsx
index 7aea5009..91e2af99 100644
--- a/apps/web/src/components/generative-ui/TrustedLabSceneRenderer.test.tsx
+++ b/apps/web/src/components/generative-ui/TrustedLabSceneRenderer.test.tsx
@@ -222,16 +222,14 @@ describe("TrustedLabSceneRenderer", () => {
)
- }
+ signedResult={signedResult({
+ integrity: {
+ mode: "hmac-signed",
+ contentHash: resultHash,
+ signature: digest("f"),
+ keyId: "counterlab-result-v1",
+ },
+ } as unknown as Partial)}
/>,
);
expect(screen.getByRole("alert")).toBeVisible();
diff --git a/packages/plan-verifier/src/scientific-candidate-v5.test.ts b/packages/plan-verifier/src/scientific-candidate-v5.test.ts
index 6aa4c189..dbf908b1 100644
--- a/packages/plan-verifier/src/scientific-candidate-v5.test.ts
+++ b/packages/plan-verifier/src/scientific-candidate-v5.test.ts
@@ -561,7 +561,8 @@ describe("scientific v5 candidate verification", () => {
label: "an invented result claim in the scene title",
mutate: (scene: LabSceneV2) => ({
...scene,
- title: "The repeated-identity explanation won after accuracy collapsed.",
+ title:
+ "The repeated-identity explanation won after accuracy collapsed.",
}),
},
{
From 6a92e06b543769164d5ea5a67adfcf5b0e99894b Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 10:13:42 +0530
Subject: [PATCH 071/143] docs(readme): reconcile judge link and evidence
---
README.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 7241ef6f..70f9d9c4 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ misconceptions in supported Jupyter notebooks. Its released Subject Packs cover
entity leakage and class imbalance/metric choice; it is not a generic notebook
copilot or an unrestricted code runner.
-**Live judge surface:**
+**Live judge surface:**
The v6.1 learner UX is implemented locally on
`feat/learner-ux-v6.1`; it has not been pushed, deployed, or browser-qualified.
@@ -274,10 +274,10 @@ The current measured artifact is
- random accuracy 0.984722, group accuracy 0.594444, ablation accuracy 0.673611;
- group entity overlap 0;
- canonical result hash
- `2501654264b9aa85b39fca944e585ff9b04263b83e182bc186d1f16464fee3b0`;
-- 12/12 published critical mutations detected;
+ `a6ae7652e04e4d70196f991c63b8f7bcb3b76f8c4ab833d3ce2b626df0ab6c94`;
+- 14/14 published leakage critical mutations detected;
- class-imbalance majority accuracy 0.989333 with 0 rare-class recall, and
- 12/12 imbalance mutations detected;
+ 15/15 published imbalance mutations detected;
- held-out intake/routing 10/10 and fixed full-loop completion 7/8; the
Random Forest patch is explicitly outside the registered non-sample patch
contract;
From 1aefd208ecbf3a05c1708d1b3242864a2ae837e8 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 10:17:06 +0530
Subject: [PATCH 072/143] docs(submission): align fields demo and screenshot
evidence
---
docs/DEMO_CAPTIONS.vtt | 4 ++--
docs/DEMO_SCRIPT.md | 31 +++++++++++++++-------------
docs/DEVPOST_COPY.md | 31 +++++++++++++++++-----------
docs/SCREENSHOT_PLAN.md | 45 ++++++++++++++++++++++++++++++++++++++---
4 files changed, 80 insertions(+), 31 deletions(-)
diff --git a/docs/DEMO_CAPTIONS.vtt b/docs/DEMO_CAPTIONS.vtt
index 80315b52..24026779 100644
--- a/docs/DEMO_CAPTIONS.vtt
+++ b/docs/DEMO_CAPTIONS.vtt
@@ -7,10 +7,10 @@ CounterLab is for ML learners and instructors. This fixed sample changes one var
CounterLab begins with the Question. It treats notebook evidence as untrusted data, never runs a cell, and sends no raw rows, notebook file, or local path.
00:27.000 --> 00:43.000
-GPT-5.6 proposes these competing explanations from only the approved sanitized packet. It cannot run the notebook or decide the result. The learner seals an immutable Prediction before any experimental value appears.
+GPT-5.6 proposes these competing explanations from only the approved sanitized packet. It cannot run the notebook or decide the result. The learner seals an immutable Prediction before any experimental value appears in this live session.
00:43.000 --> 00:58.000
-Runtime Codex proposes only bounded operation IDs. The fixed scorer selects the test, and the frozen verifier decides whether the fixed kernel may release evidence.
+Runtime Codex composes registered operation IDs into bounded plans. The fixed scorer selects the test, and the frozen verifier decides whether the fixed kernel may release evidence.
00:58.000 --> 01:18.000
The numbers come from the fixed kernel. GPT-5.6 and Codex may explain or propose, but neither computes or verifies them.
diff --git a/docs/DEMO_SCRIPT.md b/docs/DEMO_SCRIPT.md
index 909da168..1952f556 100644
--- a/docs/DEMO_SCRIPT.md
+++ b/docs/DEMO_SCRIPT.md
@@ -1,10 +1,12 @@
# 2:45 demo voiceover and shot list
-Record **Live notebook analysis** only after the exact source has passed the
-Container smoke. A bundled sample is frozen, not a substitute live run. Keep
-**Verified replay** open as a disclosed backup and never present it as a new
-model call. Use [DEMO_CAPTIONS.vtt](DEMO_CAPTIONS.vtt) word for word so the
-spoken roles, on-screen labels, and captions agree.
+Record **Live notebook analysis** only after the exact source commit, runner
+image digest, Worker version, deployment ID, qualification receipt, and
+release-check receipt agree and that deployed tuple passes production smoke. A
+bundled sample is frozen, not a substitute live run. Keep **Verified replay**
+open as a disclosed backup and never present it as a new model call. Use
+[DEMO_CAPTIONS.vtt](DEMO_CAPTIONS.vtt) word for word so the spoken roles,
+on-screen labels, and captions agree.
## 0:00–0:12 — The belief break
@@ -28,7 +30,7 @@ weight. Confirm that the first captures the learner's meaning. Choose “Remain
near 98%,” set confidence, and seal it. Say: “GPT-5.6 proposes these competing
explanations from only the approved sanitized packet. It cannot run the
notebook or decide the result. The learner seals an immutable Prediction before
-any experimental value appears.”
+any experimental value appears in this live session.”
## 0:43–0:58 — Runtime Codex and Fair Test Builder
@@ -36,9 +38,9 @@ Show what changes, what stays fixed, and why whole-customer holdout matches
deployment. If this exact live compile is genuinely rejected and repaired, show
the learner-language explanation and optionally open Evidence & proof for the
sanitized counterexample. If it passes first try, say so. Never stage a failure.
-Say: “Runtime Codex proposes only bounded operation IDs. The fixed scorer
-selects the test, and the frozen verifier decides whether the fixed kernel may
-release evidence.”
+Say: “Runtime Codex composes registered operation IDs into bounded plans. The
+fixed scorer selects the test, and the frozen verifier decides whether the
+fixed kernel may release evidence.”
## 0:58–1:18 — Experiment Theater
@@ -74,11 +76,12 @@ transfer passes, and CounterLab patches only a copy.”
## 2:10–2:30 — Completion and proof
-Show: “You completed one verified entity-leakage loop: this fixed task separated
-performance on familiar rows from performance on new entities. It does not
-establish mastery.” Then open Evidence & proof for the Reasoning Diff and Proof
-Capsule. Say: “The Reasoning Diff is for the learner. The Proof Capsule binds
-the machine-readable evidence, versions, limitations, and event chain.”
+Show: “You completed one verified entity-leakage loop.” Then show “good on
+familiar rows” was compared with “performance on new entities in this fixed
+task,” followed by “Fixed transfer task passed” and the one-task/no-mastery
+limitation. Open Evidence & proof for the Reasoning Diff and Proof Capsule.
+Say: “The Reasoning Diff is for the learner. The Proof Capsule binds the
+machine-readable evidence, versions, limitations, and event chain.”
## 2:30–2:45 — Build truth and close
diff --git a/docs/DEVPOST_COPY.md b/docs/DEVPOST_COPY.md
index 7a23e9bc..00372542 100644
--- a/docs/DEVPOST_COPY.md
+++ b/docs/DEVPOST_COPY.md
@@ -15,13 +15,16 @@ Ask like chat. Prove it like science.
## Submission fields
- **Category:** Education
+- **Submitter type:** **PENDING** owner confirmation in the official form
+- **Country:** **PENDING** owner confirmation in the official form
- **Primary audience:** ML learners and instructors who need to test what a
notebook result actually supports
- **Live judge URL:**
- **License:** MIT
-- **Repository URL:** **PENDING** anonymous-access verification; do not paste a
- private or 404 URL
-- **Public video URL:** **PENDING** capture from the final qualified release
+- **Repository URL:** ;
+ **PENDING** anonymous-access verification
+- **Public YouTube video URL:** **PENDING** capture from the final qualified
+ release
- **Codex `/feedback` Session ID:** **PENDING** owner-supplied primary build
thread ID
- **Exact release receipt:** **PENDING** final source/Container/Worker
@@ -79,8 +82,9 @@ learner-first, source-bound release candidate:
- immutable Prediction, evidence story, Model Duel, Fair Test Builder,
Experiment Theater, Boundary Hunt, visual transfer, Repair preview, and
bounded completion;
-- a ten-second Judge preview bound to checked-in fixed sample evidence and a
- one-click integrity-verified Sample Proof Capsule;
+- an automated ten-second first-viewport inspectability preview bound to
+ checked-in fixed sample evidence and a one-click integrity-verified Sample
+ Proof Capsule; this is not a learner-comprehension measurement;
- recoverable reject/insufficient-evidence paths, supported-artifact gating,
pre-Prediction result-leak regressions, stored proof-event hydration, and
capability-link lifecycle handling;
@@ -189,10 +193,11 @@ claim should be copied into the submission until a measured study exists.
1. Open and confirm the public
build identifier matches the exact production receipt linked by the final
release evidence.
-2. Choose **Start sample** for the credential-free six-stage learner loop. In
- Boundary, make one classification or reveal/skip the map before Apply unlocks.
-3. Choose **Watch verified replay** to inspect the persistent read-only label and
- genuine reject/repair history without a new model call.
+2. Choose **Open disclosed walkthrough** for the credential-free six-stage
+ learner loop. In Boundary, make one classification or reveal/skip the map
+ before Apply unlocks.
+3. Choose **Watch replay** to inspect the persistent read-only label and genuine
+ reject/repair history without a new model call.
4. Choose **Run live** only when Judge Mode reports deployed live authority.
Upload a supported notebook, approve the exact outbound packet, and follow
reconnect, transfer, repaired-copy download, and Proof Capsule export.
@@ -200,9 +205,11 @@ claim should be copied into the submission until a measured study exists.
schema-v7 deployment receipt for the exact commit, Worker version, Container
digest, asset hashes, and smoke outcomes. If those identifiers do not agree,
treat live qualification as not proven.
-6. Run the repository-contained verification commands documented in `README.md`
- for local reproduction. Historical replay evidence remains visibly distinct
- from the exact deployed release.
+6. Use the [MIT-licensed CounterLab repository](https://github.com/ALikesToCode/CounterLab)
+ and run the repository-contained verification commands documented in
+ `README.md` for local reproduction. Historical replay evidence remains
+ visibly distinct from the exact deployed release. If anonymous repository
+ access is not verified, omit these instructions from the submitted entry.
## Submission consistency rule
diff --git a/docs/SCREENSHOT_PLAN.md b/docs/SCREENSHOT_PLAN.md
index 685df6ad..a7fa7569 100644
--- a/docs/SCREENSHOT_PLAN.md
+++ b/docs/SCREENSHOT_PLAN.md
@@ -1,13 +1,52 @@
# Screenshot plan
-Status: planned, not captured for the v6.1 local pass. The required
-`CLOAK_CDP_ENDPOINT` was unavailable, and stock Chromium is forbidden. Do not
-reuse historical screenshots as evidence of this source tree.
+Status: planned, not captured for the final release. The required
+`CLOAK_CDP_ENDPOINT` is unavailable. Stock Chromium may produce explicitly
+labelled design-review artifacts, but it cannot qualify or supply the final
+submission images. Do not reuse historical screenshots as evidence of the
+frozen release.
Capture the current commit through CloakBrowser at 1440 × 900 and 390 × 844.
Add a 1280 × 720 overflow check even if it is not a submission image. Use real
product state and omit browser chrome where the submission permits.
+## Curated Devpost package
+
+Create exactly four primary assets plus one thumbnail from the same qualified
+public tuple:
+
+1. `01-judge-belief-break-1440x900.png` — the primary Judge image: learner
+ claim, persistent Fixed sample label, one changed evaluation unit, held
+ controls, 98.5% familiar rows, 59.4% unseen customers, Boundary consequence,
+ and learner benefit in the first viewport.
+2. `02-live-prediction-and-authority-1440x900.png` — supported live notebook
+ evidence, approved privacy packet, equal-weight models, sealed Prediction,
+ and truthful GPT-5.6/Codex/kernel/verifier provenance. Capture only after a
+ genuine live call and exact release binding.
+3. `03-theater-boundary-1440x900.png` — pinned Prediction, one verified result,
+ controlled variables, Boundary Hunt/full-map handoff, non-colour symbols,
+ and the exact-value table.
+4. `04-transfer-repair-proof-1440x900.png` — deterministic transfer outcome,
+ minimal repair preview, copied-notebook download, Reasoning Diff, and Proof
+ Capsule action.
+5. `thumbnail-judge-belief-break.png` — a legible crop derived from asset 1;
+ do not introduce different values, claims, or release identity.
+
+Store final images under
+`docs/submission-assets//`. Beside every PNG, write a
+same-basename JSON sidecar containing: source commit, runner image digest,
+Worker version, deployment ID, deployment receipt hash, public URL and route,
+viewport, capture timestamp, browser authority, PNG SHA-256, console errors,
+and failed requests. A missing or mismatched field keeps the asset out of the
+submission.
+
+Capture desktop and mobile variants for qualification even when only the five
+curated files are uploaded. The 390 × 844 Judge capture must prove that the
+claim, comparison, Boundary consequence, and learner benefit remain visible
+without horizontal overflow.
+
+## Audit coverage matrix
+
1. **Question-first landing** — “What result are you trying to understand?”,
the claim field, notebook attachment, sample prompt chips, support sentence,
and secondary Judge Mode action. Do not reveal a result above the fold.
From 62cf3c95d1d783327bec548e879ddc11664b3e8f Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 10:18:50 +0530
Subject: [PATCH 073/143] docs(progress): record deterministic and submission
gates
---
docs/PROGRESS.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 654960f5..065b9aa2 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -3698,3 +3698,92 @@ release qualification.
- This closes the local stock-browser regression gate only. It does not close
the exact-public, hosted-live, CloakBrowser, real screen-reader, deployment,
or submission gates.
+
+## Deterministic gate and submission checkpoint — 2026-07-21T04:47:19Z
+
+Deadline: `2026-07-22T00:00:00Z`; **19 hours 12 minutes 41 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD
+`1aefd208ecbf3a05c1708d1b3242864a2ae837e8`. The only worktree entry besides
+this progress update is the preserved, unstaged FUSE tombstone.
+
+### Completed and committed
+
+- `9229c8e style(tests): normalize scientific UI fixtures` repairs the only two
+ repository-wide Prettier drifts without behavior changes; focused tests pass
+ **25/25** for the scientific candidate and **9/9** for the trusted renderer.
+- `6a92e06 docs(readme): reconcile judge link and evidence` points the Judge URL
+ to `/judge` and aligns the published kernel hash and mutation counts with
+ `docs/ACHIEVED_METRICS.json`.
+- `1aefd20 docs(submission): align fields demo and screenshot evidence` adds the
+ missing administrative placeholders, current Judge controls, public YouTube
+ field, repository/license rule, exact-tuple video gate, precise Runtime Codex
+ role, current completion shot, synchronized captions, and a four-image plus
+ thumbnail package with per-asset provenance sidecars.
+
+### Current exact deterministic results
+
+- Complete local Playwright matrix: **35 passed, 5 exact-public/live skips,
+ 0 failed** across **40 tests in 3 files**.
+- Web Vitest: **79 files, 622/622 passed**.
+- Root Vitest: **70 files, 716 passed, 3 failed, 2 skipped**. All three failures
+ are the documented stale scientific-release bindings; no other test failed.
+- Kernel and runner Pytest: **304/304 passed**. Release tooling Pytest:
+ **27/27 passed**.
+- Repository, web client, Worker, and E2E strict TypeScript: **passed**. Worker
+ runtime types regenerated successfully through Wrangler 4.110.0.
+- Production build: **passed**. Main client **447.43 kB / 128.75 kB gzip**,
+ CSS **200.63 kB / 34.69 kB gzip**, Worker
+ **1,863.39 kB / 359.59 kB gzip**.
+- Critical mutations: leakage **14/14**, imbalance **19/19**. Tracked achieved
+ metrics check passed for its published **14/14** and **15/15** benchmark
+ scopes.
+- Held-out intake/routing **10/10**; fixed full-loop completion **7/8**; tracked
+ evidence matched. Patch replay passed with zero group overlap.
+- Sample Boundary and Sample Proof Capsule deterministic checks: **passed**.
+ Contract schemas and release-capability manifest regenerated with no drift.
+- Repository secret scan: **passed across 1,450 files**. Built client/Worker
+ secret scan: **passed across 31 files**. Repository-wide Prettier and Git
+ whitespace: **passed**.
+- Independent P0/P1 source re-audit: **13 focused files, 359/359 tests passed**.
+
+### Explicit red and blocked gates
+
+- Scientific binding check remains red across 10 generated binding files;
+ registry verification reports 14 stale source/vulnerability findings after
+ current source changes. These must be regenerated once from the frozen exact
+ image; no test or hash was weakened.
+- Contained Wrangler `whoami --json` returned `{"loggedIn":false}`. No
+ repository-contained Cloudflare token or account ID is present. Global home
+ login state is outside the permitted filesystem boundary and was not read.
+- The real aggregate-cgroup sentinel remains constitutionally blocked because
+ its required `/sys/fs/cgroup` and owned `/proc//stat` reads are outside
+ the repository-only boundary. The preserved FUSE entry also keeps the main
+ physical worktree unclean.
+- CloakBrowser remains unavailable; qualifying exact-public and real hosted-live
+ journeys remain unexecuted. Learner impact remains `NO_DATA`.
+
+### Ordered work left
+
+1. Finish the two remaining locally actionable source gaps: render named
+ verified Sample proof details from validated Capsule fields (`MB-005`) and
+ add a concise learner-audience/differentiation sentence to Landing
+ (`MB-014`), with focused tests and separate commits.
+2. Add a fail-closed submission-package metadata validator and mark the old
+ submission-readiness audit clearly historical. Verify anonymous repository
+ access without claiming it prematurely.
+3. Resolve repository-contained Wrangler authentication and the clean-source /
+ real-sentinel authority blockers.
+4. Freeze exact source `S`; build one runner image; execute genuine containment
+ and negative controls; regenerate SBOM, VEX, scientific evidence, Sample
+ Capsule, and evidence commit `E`; then rerun the complete release gate.
+5. Deploy only the qualified source/image tuple. Record exact Worker version,
+ deployment ID, Container digest, migrations, public identity, and complete
+ sample/replay/live production smoke.
+6. Run every public desktop/mobile, accessibility, reconnect, download,
+ console/network, and Web Vitals journey through CloakBrowser. Capture the
+ five provenance-bound submission assets from that exact tuple.
+7. Keep learner evidence `NO_DATA` unless consented observations occur; obtain
+ the public video, `/feedback` ID, Devpost slug and `submitted_at`, and verify
+ every link logged out.
+8. Push and verify the feature branch, then fast-forward-only merge and push
+ `main` after every ship gate above is factual. No push or merge has occurred.
From e4f723b78e397e6b5d1012dc9993c15c68b463dc Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 10:23:48 +0530
Subject: [PATCH 074/143] feat(learner): clarify landing audience and method
---
apps/web/e2e/judged-flow.spec.ts | 4 ++++
apps/web/src/App.test.tsx | 5 +++++
apps/web/src/App.tsx | 4 +++-
3 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index d063e16f..ecab0fbf 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -1349,6 +1349,9 @@ for (const viewport of beliefBreakViewports) {
const artifactSafetySummary = page.getByText(
/Ask a question or attach a supported notebook.*never runs its cells/i,
);
+ const audienceDifference = page.getByText(
+ /Built for ML learners: seal a Prediction, then let one controlled test answer—not AI prose/i,
+ );
const composer = page.getByLabel("Your question or claim");
const submit = page.getByRole("button", { name: /Test this claim/i });
const promptStarters = page.getByRole("group", {
@@ -1361,6 +1364,7 @@ for (const viewport of beliefBreakViewports) {
[
heading,
artifactSafetySummary,
+ audienceDifference,
composer,
submit,
promptStarters,
diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
index 81090245..d5ca14e3 100644
--- a/apps/web/src/App.test.tsx
+++ b/apps/web/src/App.test.tsx
@@ -1112,6 +1112,11 @@ describe("CounterLab judged flow", () => {
/ask a question or attach a supported notebook.*reads the evidence and never runs its cells/i,
),
).toBeInTheDocument();
+ expect(
+ screen.getByText(
+ /built for ml learners: seal a prediction, then let one controlled test answer—not ai prose/i,
+ ),
+ ).toBeInTheDocument();
expect(
screen.getByRole("link", { name: /skip to main content/i }),
).toHaveAttribute("href", "#main-content");
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 90df385d..808af02c 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -1105,7 +1105,9 @@ function Landing({
Ask a question or attach a supported notebook. CounterLab reads
- the evidence and never runs its cells.
+ the evidence and never runs its cells. Built for ML learners:
+ seal a Prediction, then let one controlled test answer—not AI
+ prose.
From 9441166c2ddde9e74210c46db4c3415386aa9033 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 10:34:01 +0530
Subject: [PATCH 075/143] feat(proof): expose validated session evidence
---
apps/web/src/app/CounterLabStudio.test.tsx | 78 +++++++-
.../src/components/studio/ProofConsole.tsx | 183 +++++++++++++++++-
apps/web/src/styles/studio.css | 52 +++++
3 files changed, 310 insertions(+), 3 deletions(-)
diff --git a/apps/web/src/app/CounterLabStudio.test.tsx b/apps/web/src/app/CounterLabStudio.test.tsx
index 09df3baa..a6caf122 100644
--- a/apps/web/src/app/CounterLabStudio.test.tsx
+++ b/apps/web/src/app/CounterLabStudio.test.tsx
@@ -1,9 +1,10 @@
import { fireEvent, render, screen, within } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
+import type { StudioContext } from "../components/studio/types";
import { CounterLabStudio } from "./CounterLabStudio";
-const context = {
+const context: StudioContext = {
mode: "live" as const,
stage: "live-compile" as const,
artifact: null,
@@ -11,6 +12,44 @@ const context = {
events: [],
};
+const sampleProofBundle = {
+ schemaVersion: "1",
+ experimentPlan: {
+ concept: "entity_leakage",
+ runs: [
+ { id: "random_row_split" },
+ { id: "customer_group_split" },
+ { id: "identity_ablation" },
+ ],
+ },
+ generatedAdapter: {
+ sha256: "a".repeat(64),
+ commitHash: "b".repeat(40),
+ },
+ publicTests: {
+ passed: 1,
+ failed: 0,
+ command: "python /workspace/public_tests.py",
+ reportHash: "c".repeat(64),
+ },
+ externalVerifier: {
+ status: "VERIFIED",
+ verifiedInvariants: ["baseline_overlap_exists", "canonical_result_hash"],
+ mutations: ["swapped split labels"],
+ reportHash: "d".repeat(64),
+ },
+ patchResult: {
+ status: "VERIFIED",
+ modifiedCells: [3],
+ diff: "--- cell-3-before.py\n+++ cell-3-after.py",
+ verification: {
+ passed: true,
+ invariants: ["EVALUATION_CELL_ONLY", "GROUP_OVERLAP_ZERO"],
+ unchangedCellHashes: ["e".repeat(64)],
+ },
+ },
+} as unknown as NonNullable["proofBundle"];
+
describe("CounterLabStudio", () => {
it("closes proof after compile when it was opened automatically", () => {
const actions = {
@@ -184,6 +223,43 @@ describe("CounterLabStudio", () => {
);
});
+ it("shows validated sample proof details in their evidence tabs", () => {
+ render(
+ ,
+ }}
+ actions={{
+ newAnalysis: vi.fn(),
+ showEvidence: vi.fn(),
+ startOver: vi.fn(),
+ openRecent: vi.fn(),
+ }}
+ >
+ Completed sample
+ ,
+ );
+
+ fireEvent.click(screen.getByRole("tab", { name: "Tests" }));
+ expect(screen.getByText("1 passed · 0 failed")).toBeInTheDocument();
+ expect(screen.getByText("Public test report SHA-256")).toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole("tab", { name: "Verifier" }));
+ expect(screen.getByText("baseline_overlap_exists")).toBeInTheDocument();
+ expect(screen.getByText("canonical_result_hash")).toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole("tab", { name: "Diff" }));
+ expect(screen.getByText("Changed notebook cells: 3")).toBeInTheDocument();
+ expect(screen.getByText(/cell-3-before\.py/iu)).toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole("tab", { name: "Plan" }));
+ expect(screen.getByText("customer_group_split")).toBeInTheDocument();
+ expect(screen.getByText("Generated adapter SHA-256")).toBeInTheDocument();
+ });
+
it("exposes private-session revocation only on demand in provenance", () => {
const revokeSessionAccess = vi.fn();
render(
diff --git a/apps/web/src/components/studio/ProofConsole.tsx b/apps/web/src/components/studio/ProofConsole.tsx
index 05e23f5d..f2c41d83 100644
--- a/apps/web/src/components/studio/ProofConsole.tsx
+++ b/apps/web/src/components/studio/ProofConsole.tsx
@@ -1,6 +1,10 @@
import type { KeyboardEvent as ReactKeyboardEvent } from "react";
-import type { EvidenceEvent, PublicCompilerEvent } from "../../api";
+import type {
+ EvidenceEvent,
+ ProofBundle,
+ PublicCompilerEvent,
+} from "../../api";
import { CapabilityLinkDisclosure } from "../learner/CapabilityLinkDisclosure";
import { GeneratedProofView } from "./GeneratedProofView";
import { ProvenanceLedger } from "./ProvenanceLedger";
@@ -133,6 +137,171 @@ function EvidenceEventList({ events }: { events: readonly EvidenceEvent[] }) {
);
}
+function proofBundleHasTab(
+ proofBundle: ProofBundle | undefined,
+ tab: ProofTab,
+): boolean {
+ if (proofBundle === undefined) return false;
+ if (tab === "Plan" || tab === "Diff" || tab === "Verifier") return true;
+ return tab === "Tests" && proofBundle.schemaVersion === "1";
+}
+
+function ProofBundleEvidence({
+ proofBundle,
+ tab,
+}: {
+ proofBundle: ProofBundle;
+ tab: ProofTab;
+}) {
+ if (tab === "Plan") {
+ const operationIds =
+ proofBundle.schemaVersion === "1"
+ ? proofBundle.experimentPlan.runs.map((run) => run.id)
+ : [
+ proofBundle.experimentPlan.baseline.runId,
+ ...proofBundle.experimentPlan.interventions.map((run) => run.runId),
+ ];
+ return (
+
+ Validated plan
+
+ Registered run identifiers from the session's validated Proof
+ Bundle.
+
+
+ {operationIds.map((operationId) => (
+
+ {operationId}
+
+ ))}
+
+ {proofBundle.schemaVersion === "1" ? (
+
+
+
Generated adapter SHA-256
+
+ {proofBundle.generatedAdapter.sha256}
+
+
+
+
Recorded source commit
+
+ {proofBundle.generatedAdapter.commitHash}
+
+
+
+ ) : (
+
+ Plan verifier: {proofBundle.planVerification.status} ·{" "}
+ {proofBundle.planVerification.invariantCount} invariants
+
+ )}
+
+ );
+ }
+
+ if (tab === "Diff") {
+ return (
+
+ Verified patch
+
+ Changed notebook cells:{" "}
+ {proofBundle.patchResult.modifiedCells.join(", ")}
+
+
+ Patch verification: {proofBundle.patchResult.status} ·{" "}
+ {proofBundle.patchResult.verification.invariants.length} named
+ invariants
+
+
+ {proofBundle.patchResult.diff}
+
+
+ );
+ }
+
+ if (tab === "Tests" && proofBundle.schemaVersion === "1") {
+ return (
+
+ Public tests
+
+ {proofBundle.publicTests.passed} passed ·{" "}
+ {proofBundle.publicTests.failed} failed
+
+
+
+
Public test report SHA-256
+
+ {proofBundle.publicTests.reportHash}
+
+
+
+
+ );
+ }
+
+ if (tab === "Verifier") {
+ if (proofBundle.schemaVersion === "1") {
+ return (
+
+ External verifier · {proofBundle.externalVerifier.status}
+ Accepted named invariants
+
+ {proofBundle.externalVerifier.verifiedInvariants.map(
+ (invariant) => (
+
+ {invariant}
+
+ ),
+ )}
+
+
+ Mutation probes recorded:{" "}
+ {proofBundle.externalVerifier.mutations.length}
+
+
+
+
External verifier report SHA-256
+
+ {proofBundle.externalVerifier.reportHash}
+
+
+
+
+ );
+ }
+ const reports = [
+ ["Experiment plan", proofBundle.planVerification],
+ ["Patch plan", proofBundle.patchPlanVerification],
+ ] as const;
+ return (
+
+ Verifier reports
+ {reports.map(([label, report]) => (
+
+
+ {label} · {report.status}
+
+
+ {report.invariants.map((invariant) => (
+
+ {invariant.name} ·{" "}
+ {invariant.passed ? "passed" : "rejected"}
+
+ ))}
+
+
+ ))}
+
+ );
+ }
+
+ return null;
+}
+
function CompilerEventList({
events,
tab,
@@ -184,6 +353,7 @@ export function ProofConsole({
const evidenceEvents = context.evidenceEvents ?? [];
const proofEventIssues = context.proofEventIssues ?? [];
const proofEventStatus = context.proofEventStatus ?? "idle";
+ const proofBundle = context.session?.proofBundle;
const filteredCompilerEvents = compilerEventsForTab(
context.events,
activeTab,
@@ -193,7 +363,9 @@ export function ProofConsole({
activeTab,
);
const hasTabEvents =
- filteredEvidenceEvents.length > 0 || filteredCompilerEvents.length > 0;
+ filteredEvidenceEvents.length > 0 ||
+ filteredCompilerEvents.length > 0 ||
+ proofBundleHasTab(proofBundle, activeTab);
const moveTab = (
event: ReactKeyboardEvent,
index: number,
@@ -323,6 +495,13 @@ export function ProofConsole({
) : (
<>
+ {proofBundle !== undefined &&
+ proofBundleHasTab(proofBundle, activeTab) ? (
+
+ ) : null}
{filteredEvidenceEvents.length > 0 ? (
) : null}
diff --git a/apps/web/src/styles/studio.css b/apps/web/src/styles/studio.css
index 62a5842f..960df7fe 100644
--- a/apps/web/src/styles/studio.css
+++ b/apps/web/src/styles/studio.css
@@ -506,6 +506,53 @@ kbd {
padding: 12px 16px;
}
+.proof-bundle-evidence {
+ display: grid;
+ gap: 0.65rem;
+ margin-bottom: 1rem;
+ padding: 0.85rem;
+ border: 1px solid var(--line);
+ border-radius: 0.8rem;
+ background: var(--paper-raised);
+}
+
+.proof-bundle-evidence h3,
+.proof-bundle-evidence h4,
+.proof-bundle-evidence p,
+.proof-bundle-evidence ul,
+.proof-bundle-evidence dl {
+ margin: 0;
+}
+
+.proof-bundle-evidence ul {
+ padding-inline-start: 1.25rem;
+}
+
+.proof-bundle-evidence dl {
+ display: grid;
+ gap: 0.45rem;
+}
+
+.proof-bundle-evidence dl > div {
+ display: grid;
+ grid-template-columns: minmax(150px, 0.35fr) minmax(0, 1fr);
+ gap: 0.75rem;
+}
+
+.proof-bundle-evidence code {
+ overflow-wrap: anywhere;
+}
+
+.proof-bundle-evidence pre {
+ max-height: 14rem;
+ margin: 0;
+ overflow: auto;
+ padding: 0.75rem;
+ border-radius: 0.55rem;
+ background: var(--paper);
+ white-space: pre-wrap;
+}
+
.generated-proof-sequence {
display: grid;
gap: 0.75rem;
@@ -859,6 +906,11 @@ kbd {
height: 236px;
}
+ .proof-bundle-evidence dl > div {
+ grid-template-columns: 1fr;
+ gap: 0.2rem;
+ }
+
.command-palette footer span {
display: none;
}
From 783581f01b7f7c030320bed09653be778d81a2e8 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 10:37:22 +0530
Subject: [PATCH 076/143] fix(judge): gate inaccessible source instructions
---
apps/web/src/features/judge/JudgeModeView.module.css | 9 ++++-----
apps/web/src/features/judge/JudgeModeView.test.tsx | 9 +++++++++
apps/web/src/features/judge/JudgeModeView.tsx | 12 +++++++-----
3 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/apps/web/src/features/judge/JudgeModeView.module.css b/apps/web/src/features/judge/JudgeModeView.module.css
index 4e6d24b4..204c80bb 100644
--- a/apps/web/src/features/judge/JudgeModeView.module.css
+++ b/apps/web/src/features/judge/JudgeModeView.module.css
@@ -676,16 +676,15 @@
align-items: end;
}
-.reproduceSection pre {
+.reproductionGate {
max-width: 100%;
- overflow-x: auto;
margin: 0;
padding: 28px;
- color: var(--aqua-bright);
+ color: var(--judge-panel-ink);
background: var(--judge-panel);
border-radius: 2px;
- font-size: 13px;
- line-height: 1.9;
+ font-size: 15px;
+ line-height: 1.65;
}
.footer {
diff --git a/apps/web/src/features/judge/JudgeModeView.test.tsx b/apps/web/src/features/judge/JudgeModeView.test.tsx
index 11a8a6f4..ea34c280 100644
--- a/apps/web/src/features/judge/JudgeModeView.test.tsx
+++ b/apps/web/src/features/judge/JudgeModeView.test.tsx
@@ -219,6 +219,15 @@ describe("JudgeModeView", () => {
expect(document.body).toHaveTextContent(
"11111111-2222-3333-4444-555555555555",
);
+ expect(
+ screen.getByRole("heading", { name: /source reproduction is gated/i }),
+ ).toBeInTheDocument();
+ expect(document.body).toHaveTextContent(
+ /commands are withheld until the repository and MIT license are anonymously accessible/i,
+ );
+ expect(
+ screen.queryByLabelText("CounterLab reproduction commands"),
+ ).not.toBeInTheDocument();
});
it("does not offer live authority for partial generation read isolation", () => {
diff --git a/apps/web/src/features/judge/JudgeModeView.tsx b/apps/web/src/features/judge/JudgeModeView.tsx
index ad16cf62..57079e3e 100644
--- a/apps/web/src/features/judge/JudgeModeView.tsx
+++ b/apps/web/src/features/judge/JudgeModeView.tsx
@@ -529,12 +529,14 @@ export function JudgeModeView({
aria-labelledby="reproduce-title"
>
-
Reproduce locally
-
The demo has commands, not hand-waving.
+
Source & license
+
Source reproduction is gated.
-
- {`./scripts/test-all.sh\n./scripts/run-mutations.sh leakage\n./scripts/reproduce-session.sh leakage-01\n./scripts/replay-patch.sh leakage-01`}
-
+
+ Local reproduction commands are withheld until the repository and MIT
+ License are anonymously accessible. CounterLab will not present
+ unactionable source instructions as public evidence.
+
From d942c082b54f8d344f5605c5f83365442fcc2ad0 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 10:46:43 +0530
Subject: [PATCH 077/143] feat(submission): add fail-closed package preflight
---
docs/SUBMISSION_PACKAGE.json | 106 ++++
package.json | 4 +-
scripts/validate-submission-package.test.ts | 275 ++++++++++
scripts/validate-submission-package.ts | 559 ++++++++++++++++++++
4 files changed, 943 insertions(+), 1 deletion(-)
create mode 100644 docs/SUBMISSION_PACKAGE.json
create mode 100644 scripts/validate-submission-package.test.ts
create mode 100644 scripts/validate-submission-package.ts
diff --git a/docs/SUBMISSION_PACKAGE.json b/docs/SUBMISSION_PACKAGE.json
new file mode 100644
index 00000000..6759784f
--- /dev/null
+++ b/docs/SUBMISSION_PACKAGE.json
@@ -0,0 +1,106 @@
+{
+ "schemaVersion": "1",
+ "submissionState": "DRAFT",
+ "competition": {
+ "hackathonId": "30223",
+ "projectId": "1330312",
+ "category": "Education",
+ "deadline": "2026-07-22T00:00:00Z",
+ "submitterType": null,
+ "country": null
+ },
+ "copy": {
+ "title": "CounterLab — A scientific debugger for beliefs",
+ "tagline": "Ask like chat. Prove it like science.",
+ "audience": "ML learners and instructors who need to test what a notebook result actually supports",
+ "devpostCopy": {
+ "path": "docs/DEVPOST_COPY.md",
+ "sha256": "c1d3822f0d35b163255e14b0052e9c6430d798c7614119ae21e0a0a727ff0357"
+ },
+ "readme": {
+ "path": "README.md",
+ "sha256": "bea86d78f23bc7b6263363542d28440471dbb52cd3dbaa7fda80179c41d4852c"
+ },
+ "license": {
+ "identifier": "MIT",
+ "evidence": {
+ "path": "LICENSE",
+ "sha256": "4280a28f28fccb8e7653bbd5ecf70470d4a269849b13e60a6d490204cd476cd1"
+ }
+ }
+ },
+ "publicProduct": {
+ "judgeUrl": "https://counterlab.cserules.workers.dev/judge",
+ "freeTestPath": "/judge",
+ "capabilityManifest": {
+ "path": "docs/RELEASE_CAPABILITY_MANIFEST.json",
+ "sha256": "42c81b7df4b449cce0848c62949cd680031d1fbdd7d83c3ed6151e689db0211c"
+ }
+ },
+ "release": {
+ "workerEvidenceCommit": null,
+ "runnerSourceCommit": null,
+ "containerImageDigest": null,
+ "workerVersionId": null,
+ "deploymentReceipt": null,
+ "productionSmoke": null
+ },
+ "repository": {
+ "url": "https://github.com/ALikesToCode/CounterLab",
+ "accessMode": "UNVERIFIED",
+ "accessEvidence": null,
+ "privateJudgeAddresses": []
+ },
+ "video": {
+ "publicUrl": null,
+ "durationSeconds": null,
+ "audioPresent": false,
+ "codexRoleCovered": false,
+ "gpt56RoleCovered": false,
+ "script": {
+ "path": "docs/DEMO_SCRIPT.md",
+ "sha256": "ba17a7cedbf386a4e6eb1481e623a656e9b39ea00be4cc9ef586aad8af35f17b"
+ },
+ "captions": {
+ "path": "docs/DEMO_CAPTIONS.vtt",
+ "sha256": "01af76f23a4c30bf0fe3f65108d19aff11aa2d84e6af35001338dc0c5776a9fd"
+ },
+ "verificationEvidence": null
+ },
+ "feedback": {
+ "sessionId": null,
+ "evidence": null
+ },
+ "browserQualification": {
+ "authority": "STOCK_DESIGN_REVIEW",
+ "exactReleaseBound": false,
+ "desktopComplete": false,
+ "mobileComplete": false,
+ "requiredSkips": 5,
+ "failures": 0,
+ "consoleErrors": 0,
+ "failedRequests": 0,
+ "evidence": null
+ },
+ "assets": [],
+ "impact": {
+ "status": "NO_DATA",
+ "aggregate": {
+ "path": "docs/LEARNER_PILOT_RESULTS.json",
+ "sha256": "6f1e4b63a7a0214d5f10d9052faa623ed9cbf64ae8e01f430844c01c092cd035"
+ },
+ "qualifiedReleaseReceiptSha256": null
+ },
+ "publicLinkAudit": {
+ "loggedOut": false,
+ "checkedAt": null,
+ "evidence": null
+ },
+ "officialSubmission": {
+ "state": "NOT_SUBMITTED",
+ "publicSlug": null,
+ "submittedAt": null,
+ "receipt": null,
+ "postSubmitLoggedOutEvidence": null
+ }
+}
diff --git a/package.json b/package.json
index 8976dc25..06174f5f 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,9 @@
"codex:live": "node --import tsx scripts/run-live-codex.ts",
"belief:live:verify": "node --import tsx scripts/verify-live-belief.ts",
"held-out:run": "node --import tsx scripts/run-held-out.ts",
- "held-out:check": "node --import tsx scripts/run-held-out.ts --check"
+ "held-out:check": "node --import tsx scripts/run-held-out.ts --check",
+ "submission:preflight": "node --import tsx scripts/validate-submission-package.ts --draft",
+ "submission:verify": "node --import tsx scripts/validate-submission-package.ts --ready"
},
"dependencies": {
"openai": "6.46.0",
diff --git a/scripts/validate-submission-package.test.ts b/scripts/validate-submission-package.test.ts
new file mode 100644
index 00000000..c924a6da
--- /dev/null
+++ b/scripts/validate-submission-package.test.ts
@@ -0,0 +1,275 @@
+import { createHash } from "node:crypto";
+import { readFile } from "node:fs/promises";
+import { resolve } from "node:path";
+
+import { describe, expect, it } from "vitest";
+
+import {
+ SubmissionPackageSchema,
+ type EvidenceReader,
+ validateSubmissionPackage,
+} from "./validate-submission-package.js";
+
+const root = resolve(import.meta.dirname, "..");
+
+function hash(bytes: Uint8Array): string {
+ return createHash("sha256").update(bytes).digest("hex");
+}
+
+function bytes(value: unknown): Uint8Array {
+ return new TextEncoder().encode(
+ typeof value === "string" ? value : JSON.stringify(value),
+ );
+}
+
+function reference(path: string, content: Uint8Array) {
+ return { path, sha256: hash(content) };
+}
+
+async function draftInput(): Promise {
+ return JSON.parse(
+ await readFile(resolve(root, "docs/SUBMISSION_PACKAGE.json"), "utf8"),
+ );
+}
+
+const repositoryReader: EvidenceReader = async (entry) =>
+ new Uint8Array(await readFile(resolve(root, entry.path)));
+
+function deploymentReceipt() {
+ const worker = "a".repeat(40);
+ const runner = "b".repeat(40);
+ const digest = `sha256:${"c".repeat(64)}`;
+ return {
+ schemaVersion: "7",
+ status: "DEPLOYED",
+ workerName: "counterlab",
+ productionOrigin: "https://counterlab.cserules.workers.dev",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationEvidenceSha256: "0".repeat(64),
+ generationIsolationProbeSha256: "1".repeat(64),
+ generationIsolationVerifiedAt: "2026-07-19T00:01:00.000Z",
+ releaseCheckGenerationIsolationEvidenceSha256: "2".repeat(64),
+ releaseCheckGenerationIsolationProbeSha256: "1".repeat(64),
+ releaseCheckGenerationIsolationVerifiedAt: "2026-07-19T00:01:30.000Z",
+ workerEvidenceCommit: worker,
+ runnerSourceCommit: runner,
+ qualifiedRunnerReceiptSha256: "1".repeat(64),
+ releaseCheckReceiptSha256: "2".repeat(64),
+ releaseCheckCheckedAt: "2026-07-19T00:02:00.000Z",
+ timeoutCleanupReceiptSha256: "d".repeat(64),
+ aggregateLimitEvidenceSha256: "6".repeat(64),
+ runtimeToolchainSha256: "3".repeat(64),
+ runtimePolicySha256: "d".repeat(64),
+ proofDependencyManifestSha256: "e".repeat(64),
+ runtimeAdapterSha256: "4".repeat(64),
+ adapterImageDigest: `sha256:${"5".repeat(64)}`,
+ workerVersionId: "11111111-2222-3333-4444-555555555555",
+ workerTag: `git-${worker}`,
+ workerMessage: `CounterLab Worker ${worker}; runner ${runner}`,
+ containerApplicationId: "container-app-1",
+ containerApplicationVersion: "3",
+ containerImage: `registry.cloudflare.com/account/counterlab-runner@${digest}`,
+ containerState: "active",
+ containerImageDigest: digest,
+ workerArtifactClassification: "PROCESS_BOUND_PARTIAL",
+ workerArtifactManifestSha256: "f".repeat(64),
+ deployConfigSha256: "6".repeat(64),
+ workerBundleSha256: "7".repeat(64),
+ clientAssetsSha256: "8".repeat(64),
+ clientAssetCount: 9,
+ clientPublicAssetsSha256: "9".repeat(64),
+ clientPublicAssetCount: 7,
+ viteVersion: "8.1.4",
+ wranglerVersion: "4.110.0",
+ dryRunSha256: "7".repeat(64),
+ dryRunFileCount: 1,
+ deploymentStatusSha256: "a".repeat(64),
+ workerVersionSha256: "b".repeat(64),
+ containerStatusSha256: "c".repeat(64),
+ deployedAt: "2026-07-19T00:03:00.000Z",
+ verifierVersion: "counterlab-deployment-v7",
+ } as const;
+}
+
+async function readyPackage() {
+ const input = SubmissionPackageSchema.parse(await draftInput());
+ const evidence = new Map();
+ for (const entry of [
+ input.copy.devpostCopy,
+ input.copy.readme,
+ input.copy.license.evidence,
+ input.publicProduct.capabilityManifest,
+ input.video.script,
+ input.video.captions,
+ input.impact.aggregate,
+ ]) {
+ evidence.set(entry.path, await repositoryReader(entry));
+ }
+ const addEvidence = (path: string, content: Uint8Array) => {
+ evidence.set(path, content);
+ return reference(path, content);
+ };
+ const receipt = deploymentReceipt();
+ const receiptBytes = bytes(receipt);
+ const receiptRef = addEvidence(
+ "evidence/deployment-receipt.json",
+ receiptBytes,
+ );
+ const smokeRef = addEvidence(
+ "evidence/production-smoke.json",
+ bytes({
+ schemaVersion: "6",
+ status: "PASSED",
+ baseUrl: receipt.productionOrigin,
+ deployment: {
+ workerVersion: receipt.workerVersionId,
+ workerEvidenceCommit: receipt.workerEvidenceCommit,
+ runnerSourceCommit: receipt.runnerSourceCommit,
+ containerImageDigest: receipt.containerImageDigest,
+ deploymentReceiptSha256: receiptRef.sha256,
+ },
+ }),
+ );
+ const genericEvidence = (name: string) =>
+ addEvidence(`evidence/${name}.json`, bytes({ status: "VERIFIED", name }));
+ const assetNames = [
+ "01-judge-belief-break-1440x900.png",
+ "02-live-prediction-and-authority-1440x900.png",
+ "03-theater-boundary-1440x900.png",
+ "04-transfer-repair-proof-1440x900.png",
+ "thumbnail-judge-belief-break.png",
+ ];
+ const assets = assetNames.map((fileName) => ({
+ fileName,
+ image: addEvidence(
+ `docs/submission-assets/${fileName}`,
+ bytes(`png:${fileName}`),
+ ),
+ provenance: addEvidence(
+ `docs/submission-assets/${fileName}.json`,
+ bytes({ fileName, release: receipt.workerVersionId }),
+ ),
+ }));
+ const ready = {
+ ...input,
+ submissionState: "READY_TO_SUBMIT" as const,
+ competition: {
+ ...input.competition,
+ submitterType: "Individual",
+ country: "India",
+ },
+ release: {
+ workerEvidenceCommit: receipt.workerEvidenceCommit,
+ runnerSourceCommit: receipt.runnerSourceCommit,
+ containerImageDigest: receipt.containerImageDigest,
+ workerVersionId: receipt.workerVersionId,
+ deploymentReceipt: receiptRef,
+ productionSmoke: smokeRef,
+ },
+ repository: {
+ ...input.repository,
+ accessMode: "PUBLIC" as const,
+ accessEvidence: genericEvidence("repository-access"),
+ },
+ video: {
+ ...input.video,
+ publicUrl: "https://youtu.be/counterlab-demo",
+ durationSeconds: 165,
+ audioPresent: true,
+ codexRoleCovered: true,
+ gpt56RoleCovered: true,
+ verificationEvidence: genericEvidence("video"),
+ },
+ feedback: {
+ sessionId: "019f6069-2af2-7132-8181-d313081884c8",
+ evidence: genericEvidence("feedback"),
+ },
+ browserQualification: {
+ authority: "CLOAKBROWSER" as const,
+ exactReleaseBound: true,
+ desktopComplete: true,
+ mobileComplete: true,
+ requiredSkips: 0,
+ failures: 0,
+ consoleErrors: 0,
+ failedRequests: 0,
+ evidence: genericEvidence("browser"),
+ },
+ assets,
+ publicLinkAudit: {
+ loggedOut: true,
+ checkedAt: "2026-07-21T10:00:00.000Z",
+ evidence: genericEvidence("links"),
+ },
+ };
+ return {
+ ready,
+ reader: async (entry: { path: string }) => {
+ const content = evidence.get(entry.path);
+ if (content === undefined) throw new Error("missing test evidence");
+ return content;
+ },
+ };
+}
+
+describe("submission package validator", () => {
+ it("validates the honest current draft and all checked-in hashes", async () => {
+ const result = await validateSubmissionPackage(
+ await draftInput(),
+ "draft",
+ repositoryReader,
+ );
+
+ expect(result.submissionState).toBe("DRAFT");
+ expect(result.impact.status).toBe("NO_DATA");
+ expect(result.officialSubmission.state).toBe("NOT_SUBMITTED");
+ });
+
+ it("fails readiness for the current external and release blockers", async () => {
+ await expect(
+ validateSubmissionPackage(await draftInput(), "ready", repositoryReader),
+ ).rejects.toThrow(/failed \d+ gate/iu);
+ });
+
+ it("accepts a complete, tuple-consistent ready package", async () => {
+ const { ready, reader } = await readyPackage();
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader),
+ ).resolves.toMatchObject({ submissionState: "READY_TO_SUBMIT" });
+ });
+
+ it("rejects evidence hash drift", async () => {
+ const input = SubmissionPackageSchema.parse(await draftInput());
+ input.copy.readme.sha256 = "0".repeat(64);
+
+ await expect(
+ validateSubmissionPackage(input, "draft", repositoryReader),
+ ).rejects.toThrow(/evidence hash mismatch/iu);
+ });
+
+ it("rejects unknown fields, placeholders, and escaping evidence paths", async () => {
+ const input = (await draftInput()) as Record;
+ await expect(
+ validateSubmissionPackage(
+ { ...input, invented: true },
+ "draft",
+ repositoryReader,
+ ),
+ ).rejects.toThrow();
+ const placeholder = structuredClone(input) as {
+ competition: { submitterType: string | null };
+ };
+ placeholder.competition.submitterType = "PENDING";
+ await expect(
+ validateSubmissionPackage(placeholder, "draft", repositoryReader),
+ ).rejects.toThrow(/placeholder/iu);
+ const escaped = structuredClone(input) as {
+ copy: { readme: { path: string } };
+ };
+ escaped.copy.readme.path = "../README.md";
+ await expect(
+ validateSubmissionPackage(escaped, "draft", repositoryReader),
+ ).rejects.toThrow(/inside the repository/iu);
+ });
+});
diff --git a/scripts/validate-submission-package.ts b/scripts/validate-submission-package.ts
new file mode 100644
index 00000000..cdad4488
--- /dev/null
+++ b/scripts/validate-submission-package.ts
@@ -0,0 +1,559 @@
+import { createHash } from "node:crypto";
+import { readFile } from "node:fs/promises";
+import { resolve } from "node:path";
+
+import { z } from "zod";
+
+import { DeploymentReceiptV7Schema } from "../packages/scientific-engine-registry/src/schema.js";
+import { containedInputFile } from "./repository-cli-paths.js";
+
+const Sha256Schema = z.string().regex(/^[a-f0-9]{64}$/u);
+const GitCommitSchema = z.string().regex(/^[a-f0-9]{40}$/u);
+const ImageDigestSchema = z.string().regex(/^sha256:[a-f0-9]{64}$/u);
+const WorkerVersionSchema = z
+ .string()
+ .regex(/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/u);
+const RepositoryPathSchema = z
+ .string()
+ .min(1)
+ .max(512)
+ .refine(
+ (value) =>
+ !value.startsWith("/") &&
+ !value.includes("\\") &&
+ !value.split("/").includes(".."),
+ "evidence path must stay inside the repository",
+ );
+
+const EvidenceReferenceSchema = z
+ .object({
+ path: RepositoryPathSchema,
+ sha256: Sha256Schema,
+ })
+ .strict();
+
+const NullableString = z.string().trim().min(1).nullable();
+const NullableEvidence = EvidenceReferenceSchema.nullable();
+
+const ReleaseSchema = z
+ .object({
+ workerEvidenceCommit: GitCommitSchema.nullable(),
+ runnerSourceCommit: GitCommitSchema.nullable(),
+ containerImageDigest: ImageDigestSchema.nullable(),
+ workerVersionId: WorkerVersionSchema.nullable(),
+ deploymentReceipt: NullableEvidence,
+ productionSmoke: NullableEvidence,
+ })
+ .strict();
+
+const AssetSchema = z
+ .object({
+ fileName: z.string().trim().min(1),
+ image: EvidenceReferenceSchema,
+ provenance: EvidenceReferenceSchema,
+ })
+ .strict();
+
+export const SubmissionPackageSchema = z
+ .object({
+ schemaVersion: z.literal("1"),
+ submissionState: z.enum(["DRAFT", "READY_TO_SUBMIT", "SUBMITTED"]),
+ competition: z
+ .object({
+ hackathonId: z.literal("30223"),
+ projectId: z.literal("1330312"),
+ category: z.literal("Education"),
+ deadline: z.literal("2026-07-22T00:00:00Z"),
+ submitterType: NullableString,
+ country: NullableString,
+ })
+ .strict(),
+ copy: z
+ .object({
+ title: z.literal("CounterLab — A scientific debugger for beliefs"),
+ tagline: z.literal("Ask like chat. Prove it like science."),
+ audience: z.literal(
+ "ML learners and instructors who need to test what a notebook result actually supports",
+ ),
+ devpostCopy: EvidenceReferenceSchema,
+ readme: EvidenceReferenceSchema,
+ license: z
+ .object({
+ identifier: z.literal("MIT"),
+ evidence: EvidenceReferenceSchema,
+ })
+ .strict(),
+ })
+ .strict(),
+ publicProduct: z
+ .object({
+ judgeUrl: z.literal("https://counterlab.cserules.workers.dev/judge"),
+ freeTestPath: z.literal("/judge"),
+ capabilityManifest: EvidenceReferenceSchema,
+ })
+ .strict(),
+ release: ReleaseSchema,
+ repository: z
+ .object({
+ url: z.literal("https://github.com/ALikesToCode/CounterLab"),
+ accessMode: z.enum(["UNVERIFIED", "PUBLIC", "PRIVATE_SHARED"]),
+ accessEvidence: NullableEvidence,
+ privateJudgeAddresses: z.array(z.string().email()).max(2),
+ })
+ .strict(),
+ video: z
+ .object({
+ publicUrl: NullableString,
+ durationSeconds: z.number().positive().max(180).nullable(),
+ audioPresent: z.boolean(),
+ codexRoleCovered: z.boolean(),
+ gpt56RoleCovered: z.boolean(),
+ script: EvidenceReferenceSchema,
+ captions: EvidenceReferenceSchema,
+ verificationEvidence: NullableEvidence,
+ })
+ .strict(),
+ feedback: z
+ .object({
+ sessionId: NullableString,
+ evidence: NullableEvidence,
+ })
+ .strict(),
+ browserQualification: z
+ .object({
+ authority: z.enum(["NONE", "STOCK_DESIGN_REVIEW", "CLOAKBROWSER"]),
+ exactReleaseBound: z.boolean(),
+ desktopComplete: z.boolean(),
+ mobileComplete: z.boolean(),
+ requiredSkips: z.number().int().nonnegative(),
+ failures: z.number().int().nonnegative(),
+ consoleErrors: z.number().int().nonnegative(),
+ failedRequests: z.number().int().nonnegative(),
+ evidence: NullableEvidence,
+ })
+ .strict(),
+ assets: z.array(AssetSchema).max(5),
+ impact: z
+ .object({
+ status: z.enum(["NO_DATA", "DESCRIPTIVE_ONLY"]),
+ aggregate: EvidenceReferenceSchema,
+ qualifiedReleaseReceiptSha256: Sha256Schema.nullable(),
+ })
+ .strict(),
+ publicLinkAudit: z
+ .object({
+ loggedOut: z.boolean(),
+ checkedAt: z.iso.datetime({ offset: true }).nullable(),
+ evidence: NullableEvidence,
+ })
+ .strict(),
+ officialSubmission: z
+ .object({
+ state: z.enum(["NOT_SUBMITTED", "SUBMITTED"]),
+ publicSlug: NullableString,
+ submittedAt: z.iso.datetime({ offset: true }).nullable(),
+ receipt: NullableEvidence,
+ postSubmitLoggedOutEvidence: NullableEvidence,
+ })
+ .strict(),
+ })
+ .strict();
+
+export type SubmissionPackage = z.infer;
+export type SubmissionValidationMode = "draft" | "ready" | "submitted";
+export type EvidenceReader = (
+ reference: z.infer,
+) => Promise;
+
+const EXPECTED_ASSETS = [
+ "01-judge-belief-break-1440x900.png",
+ "02-live-prediction-and-authority-1440x900.png",
+ "03-theater-boundary-1440x900.png",
+ "04-transfer-repair-proof-1440x900.png",
+ "thumbnail-judge-belief-break.png",
+] as const;
+
+const ProductionSmokeV6Schema = z
+ .object({
+ schemaVersion: z.literal("6"),
+ status: z.literal("PASSED"),
+ baseUrl: z.literal("https://counterlab.cserules.workers.dev"),
+ deployment: z
+ .object({
+ workerVersion: WorkerVersionSchema,
+ workerEvidenceCommit: GitCommitSchema,
+ runnerSourceCommit: GitCommitSchema,
+ containerImageDigest: ImageDigestSchema,
+ deploymentReceiptSha256: Sha256Schema,
+ })
+ .passthrough(),
+ })
+ .passthrough();
+
+class SubmissionValidationError extends Error {
+ readonly issues: readonly string[];
+
+ constructor(issues: readonly string[]) {
+ super(`Submission package failed ${issues.length} gate(s)`);
+ this.name = "SubmissionValidationError";
+ this.issues = issues;
+ }
+}
+
+function sha256(bytes: Uint8Array): string {
+ return createHash("sha256").update(bytes).digest("hex");
+}
+
+function parseJson(bytes: Uint8Array, label: string): unknown {
+ try {
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
+ } catch {
+ throw new Error(`${label} is not valid UTF-8 JSON`);
+ }
+}
+
+function collectEvidenceReferences(
+ submission: SubmissionPackage,
+): readonly z.infer[] {
+ const optional = [
+ submission.release.deploymentReceipt,
+ submission.release.productionSmoke,
+ submission.repository.accessEvidence,
+ submission.video.verificationEvidence,
+ submission.feedback.evidence,
+ submission.browserQualification.evidence,
+ submission.publicLinkAudit.evidence,
+ submission.officialSubmission.receipt,
+ submission.officialSubmission.postSubmitLoggedOutEvidence,
+ ].filter(
+ (reference): reference is z.infer =>
+ reference !== null,
+ );
+ return [
+ submission.copy.devpostCopy,
+ submission.copy.readme,
+ submission.copy.license.evidence,
+ submission.publicProduct.capabilityManifest,
+ submission.video.script,
+ submission.video.captions,
+ submission.impact.aggregate,
+ ...submission.assets.flatMap((asset) => [asset.image, asset.provenance]),
+ ...optional,
+ ];
+}
+
+function assertNoPlaceholderStrings(value: unknown, path = "package"): void {
+ if (typeof value === "string" && /^(?:PENDING|TBD|TODO)$/iu.test(value)) {
+ throw new Error(`${path} contains a placeholder value`);
+ }
+ if (Array.isArray(value)) {
+ value.forEach((child, index) =>
+ assertNoPlaceholderStrings(child, `${path}[${index}]`),
+ );
+ } else if (value !== null && typeof value === "object") {
+ Object.entries(value).forEach(([key, child]) =>
+ assertNoPlaceholderStrings(child, `${path}.${key}`),
+ );
+ }
+}
+
+function requireReadyFields(
+ submission: SubmissionPackage,
+ issues: string[],
+): void {
+ const required: Array<[string, unknown]> = [
+ ["competition.submitterType", submission.competition.submitterType],
+ ["competition.country", submission.competition.country],
+ ["release.workerEvidenceCommit", submission.release.workerEvidenceCommit],
+ ["release.runnerSourceCommit", submission.release.runnerSourceCommit],
+ ["release.containerImageDigest", submission.release.containerImageDigest],
+ ["release.workerVersionId", submission.release.workerVersionId],
+ ["release.deploymentReceipt", submission.release.deploymentReceipt],
+ ["release.productionSmoke", submission.release.productionSmoke],
+ ["repository.accessEvidence", submission.repository.accessEvidence],
+ ["video.publicUrl", submission.video.publicUrl],
+ ["video.durationSeconds", submission.video.durationSeconds],
+ ["video.verificationEvidence", submission.video.verificationEvidence],
+ ["feedback.sessionId", submission.feedback.sessionId],
+ ["feedback.evidence", submission.feedback.evidence],
+ ["browserQualification.evidence", submission.browserQualification.evidence],
+ ["publicLinkAudit.checkedAt", submission.publicLinkAudit.checkedAt],
+ ["publicLinkAudit.evidence", submission.publicLinkAudit.evidence],
+ ];
+ for (const [field, value] of required) {
+ if (value === null) issues.push(`${field} is required`);
+ }
+ if (submission.submissionState === "DRAFT") {
+ issues.push("submissionState must be READY_TO_SUBMIT or SUBMITTED");
+ }
+ if (
+ submission.repository.accessMode === "PUBLIC" &&
+ submission.repository.privateJudgeAddresses.length !== 0
+ ) {
+ issues.push("public repository must not claim private judge shares");
+ }
+ if (submission.repository.accessMode === "PRIVATE_SHARED") {
+ const requiredJudges = new Set([
+ "testing@devpost.com",
+ "build-week-event@openai.com",
+ ]);
+ if (
+ submission.repository.privateJudgeAddresses.length !== 2 ||
+ submission.repository.privateJudgeAddresses.some(
+ (address) => !requiredJudges.has(address),
+ )
+ ) {
+ issues.push(
+ "private repository must be shared with both judge addresses",
+ );
+ }
+ } else if (submission.repository.accessMode !== "PUBLIC") {
+ issues.push("repository access must be PUBLIC or PRIVATE_SHARED");
+ }
+ if (
+ submission.video.publicUrl === null ||
+ !/^https:\/\/(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)/u.test(
+ submission.video.publicUrl,
+ )
+ ) {
+ issues.push("video.publicUrl must be a public YouTube URL");
+ }
+ if (
+ !submission.video.audioPresent ||
+ !submission.video.codexRoleCovered ||
+ !submission.video.gpt56RoleCovered
+ ) {
+ issues.push("video must include audio and both Codex and GPT-5.6 roles");
+ }
+ const browser = submission.browserQualification;
+ if (
+ browser.authority !== "CLOAKBROWSER" ||
+ !browser.exactReleaseBound ||
+ !browser.desktopComplete ||
+ !browser.mobileComplete ||
+ browser.requiredSkips !== 0 ||
+ browser.failures !== 0 ||
+ browser.consoleErrors !== 0 ||
+ browser.failedRequests !== 0
+ ) {
+ issues.push(
+ "browser qualification must be exact, complete CloakBrowser evidence",
+ );
+ }
+ const assetNames = submission.assets.map((asset) => asset.fileName).sort();
+ if (
+ JSON.stringify(assetNames) !== JSON.stringify([...EXPECTED_ASSETS].sort())
+ ) {
+ issues.push("assets must contain the exact four screenshots and thumbnail");
+ }
+ if (!submission.publicLinkAudit.loggedOut) {
+ issues.push("public links must be checked logged out");
+ }
+}
+
+async function assertReleaseEvidence(
+ submission: SubmissionPackage,
+ readEvidence: EvidenceReader,
+ issues: string[],
+): Promise {
+ const receiptRef = submission.release.deploymentReceipt;
+ const smokeRef = submission.release.productionSmoke;
+ if (receiptRef === null || smokeRef === null) return;
+ try {
+ const receipt = DeploymentReceiptV7Schema.parse(
+ parseJson(await readEvidence(receiptRef), "deployment receipt"),
+ );
+ const smoke = ProductionSmokeV6Schema.parse(
+ parseJson(await readEvidence(smokeRef), "production smoke"),
+ );
+ const expectedOrigin = new URL(submission.publicProduct.judgeUrl).origin;
+ if (
+ receipt.productionOrigin !== expectedOrigin ||
+ receipt.workerEvidenceCommit !==
+ submission.release.workerEvidenceCommit ||
+ receipt.runnerSourceCommit !== submission.release.runnerSourceCommit ||
+ receipt.containerImageDigest !==
+ submission.release.containerImageDigest ||
+ receipt.workerVersionId !== submission.release.workerVersionId
+ ) {
+ issues.push(
+ "deployment receipt does not match the submission release tuple",
+ );
+ }
+ if (
+ smoke.baseUrl !== expectedOrigin ||
+ smoke.deployment.workerEvidenceCommit !==
+ submission.release.workerEvidenceCommit ||
+ smoke.deployment.runnerSourceCommit !==
+ submission.release.runnerSourceCommit ||
+ smoke.deployment.containerImageDigest !==
+ submission.release.containerImageDigest ||
+ smoke.deployment.workerVersion !== submission.release.workerVersionId ||
+ smoke.deployment.deploymentReceiptSha256 !== receiptRef.sha256
+ ) {
+ issues.push(
+ "production smoke does not match the submission release tuple",
+ );
+ }
+ } catch {
+ issues.push(
+ "release evidence is not a valid schema-v7 receipt and schema-v6 smoke",
+ );
+ }
+}
+
+async function assertImpactEvidence(
+ submission: SubmissionPackage,
+ readEvidence: EvidenceReader,
+ issues: string[],
+): Promise {
+ try {
+ const impact = z
+ .object({
+ schemaVersion: z.literal("2"),
+ status: z.enum(["NO_DATA", "DESCRIPTIVE_ONLY"]),
+ qualifiedReleaseReceiptSha256: Sha256Schema.nullable(),
+ participantCount: z.number().int().nonnegative(),
+ completedSessionCount: z.number().int().nonnegative(),
+ metrics: z.unknown().nullable(),
+ limitations: z.array(z.string().trim().min(1)).min(1),
+ })
+ .passthrough()
+ .parse(
+ parseJson(
+ await readEvidence(submission.impact.aggregate),
+ "impact aggregate",
+ ),
+ );
+ if (impact.status !== submission.impact.status) {
+ issues.push("impact status does not match its aggregate");
+ }
+ if (
+ impact.status === "NO_DATA" &&
+ (impact.participantCount !== 0 ||
+ impact.completedSessionCount !== 0 ||
+ impact.metrics !== null ||
+ impact.qualifiedReleaseReceiptSha256 !== null ||
+ submission.impact.qualifiedReleaseReceiptSha256 !== null)
+ ) {
+ issues.push(
+ "NO_DATA impact must contain zero participants and no metrics",
+ );
+ }
+ } catch {
+ issues.push("impact aggregate is invalid");
+ }
+}
+
+export async function validateSubmissionPackage(
+ input: unknown,
+ mode: SubmissionValidationMode,
+ readEvidence: EvidenceReader,
+): Promise {
+ assertNoPlaceholderStrings(input);
+ const submission = SubmissionPackageSchema.parse(input);
+ const references = collectEvidenceReferences(submission);
+ const seen = new Map();
+ for (const reference of references) {
+ const previous = seen.get(reference.path);
+ if (previous !== undefined && previous !== reference.sha256) {
+ throw new Error(
+ `evidence path has conflicting hashes: ${reference.path}`,
+ );
+ }
+ seen.set(reference.path, reference.sha256);
+ const bytes = await readEvidence(reference);
+ if (sha256(bytes) !== reference.sha256) {
+ throw new Error(`evidence hash mismatch: ${reference.path}`);
+ }
+ }
+
+ const issues: string[] = [];
+ await assertImpactEvidence(submission, readEvidence, issues);
+ if (mode !== "draft") {
+ requireReadyFields(submission, issues);
+ await assertReleaseEvidence(submission, readEvidence, issues);
+ }
+ if (mode === "submitted") {
+ const official = submission.officialSubmission;
+ if (
+ submission.submissionState !== "SUBMITTED" ||
+ official.state !== "SUBMITTED" ||
+ official.publicSlug === null ||
+ official.submittedAt === null ||
+ official.receipt === null ||
+ official.postSubmitLoggedOutEvidence === null
+ ) {
+ issues.push(
+ "submitted package requires slug, timestamp, receipt, and logged-out proof",
+ );
+ } else if (
+ Date.parse(official.submittedAt) >
+ Date.parse(submission.competition.deadline)
+ ) {
+ issues.push("official submission timestamp is after the deadline");
+ }
+ }
+ if (issues.length > 0) throw new SubmissionValidationError(issues);
+ return submission;
+}
+
+async function repositoryEvidenceReader(
+ root: string,
+ reference: z.infer,
+): Promise {
+ const path = await containedInputFile(
+ root,
+ reference.path,
+ "submission evidence",
+ );
+ return new Uint8Array(await readFile(path));
+}
+
+async function main(): Promise {
+ const modeValue = process.argv[2] ?? "--draft";
+ const mode =
+ modeValue === "--draft"
+ ? "draft"
+ : modeValue === "--ready"
+ ? "ready"
+ : modeValue === "--submitted"
+ ? "submitted"
+ : null;
+ if (mode === null || process.argv.length > 3) {
+ throw new Error(
+ "Usage: validate-submission-package.ts --draft|--ready|--submitted",
+ );
+ }
+ const root = resolve(import.meta.dirname, "..");
+ const manifestPath = await containedInputFile(
+ root,
+ "docs/SUBMISSION_PACKAGE.json",
+ "submission package",
+ );
+ const input = parseJson(
+ new Uint8Array(await readFile(manifestPath)),
+ "submission package",
+ );
+ await validateSubmissionPackage(input, mode, (reference) =>
+ repositoryEvidenceReader(root, reference),
+ );
+ process.stdout.write(`SUBMISSION_PACKAGE_${mode.toUpperCase()}_VALID\n`);
+}
+
+const invokedPath = process.argv[1];
+if (
+ invokedPath !== undefined &&
+ resolve(invokedPath) === resolve(import.meta.filename)
+) {
+ main().catch((error: unknown) => {
+ if (error instanceof SubmissionValidationError) {
+ for (const issue of error.issues) process.stderr.write(`- ${issue}\n`);
+ } else {
+ process.stderr.write(
+ `${error instanceof Error ? error.message : "Submission validation failed"}\n`,
+ );
+ }
+ process.exitCode = 1;
+ });
+}
From 74ff037283eef095dafcbe5ea0c3863eb9a03d25 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 10:47:40 +0530
Subject: [PATCH 078/143] docs(audit): mark submission snapshot historical
---
.../audits/counterlab-first-prize/12_SUBMISSION_READINESS.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/docs/audits/counterlab-first-prize/12_SUBMISSION_READINESS.md b/docs/audits/counterlab-first-prize/12_SUBMISSION_READINESS.md
index bdee70be..10557250 100644
--- a/docs/audits/counterlab-first-prize/12_SUBMISSION_READINESS.md
+++ b/docs/audits/counterlab-first-prize/12_SUBMISSION_READINESS.md
@@ -1,5 +1,10 @@
# Submission readiness
+> **Historical audit snapshot:** This file records the submission state
+> observed on 2026-07-18. It is not current readiness evidence. Use
+> `docs/PROGRESS.md`, `docs/DEVPOST_COPY.md`, `docs/SUBMISSION_PACKAGE.json`,
+> and `docs/RELEASE_CHECKLIST.md` for current truth.
+
## Stage One verdict
**FAIL at audit time.** Devpost project `1330312` remained `Untitled` in `submission_pre_draft`, with empty description and no tagline, video URL, public slug, website URL, or submitted timestamp. The official submission deadline is `2026-07-22T00:00:00Z` (Tuesday July 21, 5:00 PM PT).
From 3174a1f997a40f471ed9b8a20e147a0ce279ee15 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 11:29:49 +0530
Subject: [PATCH 079/143] fix(proof): bind stored bundle authority
---
apps/web/src/api.test.ts | 68 +++++
apps/web/src/api.ts | 110 +++++++-
apps/web/src/app/CounterLabStudio.test.tsx | 150 +++++++---
.../src/components/studio/ProofConsole.tsx | 37 ++-
apps/web/src/test-fixtures/proofBundle.ts | 256 ++++++++++++++++++
apps/web/worker/api.test.ts | 123 ++++++++-
apps/web/worker/api.ts | 150 +++++++---
7 files changed, 804 insertions(+), 90 deletions(-)
create mode 100644 apps/web/src/test-fixtures/proofBundle.ts
diff --git a/apps/web/src/api.test.ts b/apps/web/src/api.test.ts
index bbb2b29d..fb3bda77 100644
--- a/apps/web/src/api.test.ts
+++ b/apps/web/src/api.test.ts
@@ -10,6 +10,7 @@ import {
import { ApiClientError, CounterLabApiClient, SessionViewSchema } from "./api";
import { publicReplayFixture } from "./components/replay/ProofCapsuleReplayView.fixture";
+import { createDefaultProofBoundSessionFixture } from "./test-fixtures/proofBundle";
import rawSampleResult from "../../../fixtures/public/leakage_verified_result.json";
const digest = (character: string) => character.repeat(64);
@@ -260,7 +261,74 @@ function jsonResponse(payload: unknown, status = 200): Response {
});
}
+function proofBoundSession() {
+ return createDefaultProofBoundSessionFixture();
+}
+
describe("CounterLabApiClient", () => {
+ it("binds a Proof Bundle to its outer session authority", async () => {
+ const valid = proofBoundSession();
+ expect(SessionViewSchema.parse(valid)).toMatchObject({
+ sessionId: session.sessionId,
+ proofBundle: { bundleId: "bundle_1" },
+ });
+
+ expect(() =>
+ SessionViewSchema.parse({
+ ...valid,
+ proofBundle: { ...valid.proofBundle, sessionId: "session_other" },
+ }),
+ ).toThrow(/different session/i);
+ expect(() =>
+ SessionViewSchema.parse({
+ ...valid,
+ proofBundle: {
+ ...valid.proofBundle,
+ artifactManifest: {
+ ...valid.proofBundle.artifactManifest,
+ artifactId: "artifact_other",
+ },
+ },
+ }),
+ ).toThrow(/different artifact/i);
+ expect(() =>
+ SessionViewSchema.parse({
+ ...valid,
+ mode: { kind: "sample_lesson", sampleId: "sample_other" },
+ }),
+ ).toThrow(/selected lesson/i);
+ expect(() =>
+ SessionViewSchema.parse({ ...valid, state: "PATCH_VERIFIED" }),
+ ).toThrow(/before Reasoning Diff/i);
+ expect(() =>
+ SessionViewSchema.parse({
+ ...valid,
+ prediction: { ...valid.prediction, immutableHash: digest("0") },
+ }),
+ ).toThrow(/prediction authority/i);
+ expect(() =>
+ SessionViewSchema.parse({
+ ...valid,
+ verifiedResult: {
+ ...valid.verifiedResult,
+ resultHash: digest("0"),
+ },
+ }),
+ ).toThrow(/verifiedResult authority/i);
+
+ const fetcher = vi.fn(async () =>
+ jsonResponse({
+ ok: true,
+ data: { ...valid.proofBundle, sessionId: "session_other" },
+ }),
+ );
+ await expect(
+ new CounterLabApiClient({ fetch: fetcher }).getProofBundle(
+ session.sessionId,
+ ),
+ ).rejects.toMatchObject({ code: "PROOF_BUNDLE_LINEAGE_INVALID" });
+ });
+
it("rejects a session result that has no immutable Prediction", async () => {
const verifiedResult = VerifiedResultSetSchema.parse(rawSampleResult);
const malformedSession = {
diff --git a/apps/web/src/api.ts b/apps/web/src/api.ts
index 738fa3d3..4f3f255e 100644
--- a/apps/web/src/api.ts
+++ b/apps/web/src/api.ts
@@ -190,12 +190,20 @@ const sessionViewShape = {
function requireExclusiveBeliefAuthority(
value: {
+ sessionId: string;
+ artifactId: string;
+ mode: z.infer;
+ state: SessionState;
beliefTest?: unknown;
beliefSpec?: unknown;
- prediction?: unknown;
- verifiedResult?: unknown;
+ prediction?: PredictionContract | undefined;
+ verifiedResult?: VerifiedResultSet | undefined;
evidenceVerdict?: unknown;
epistemicReportHash?: unknown;
+ transferResult?: TransferResult | undefined;
+ patchResult?: PatchResult | undefined;
+ reasoningDiff?: ReasoningDiff | undefined;
+ proofBundle?: ProofBundle | undefined;
},
context: z.RefinementCtx,
): void {
@@ -230,6 +238,93 @@ function requireExclusiveBeliefAuthority(
path: ["prediction"],
});
}
+ if (value.proofBundle !== undefined) {
+ const proofBundle = value.proofBundle;
+ if (value.proofBundle.sessionId !== value.sessionId) {
+ context.addIssue({
+ code: "custom",
+ message: "the Proof Bundle belongs to a different session",
+ path: ["proofBundle", "sessionId"],
+ });
+ }
+ if (proofBundle.artifactManifest.artifactId !== value.artifactId) {
+ context.addIssue({
+ code: "custom",
+ message: "the Proof Bundle belongs to a different artifact",
+ path: ["proofBundle", "artifactManifest", "artifactId"],
+ });
+ }
+ if (
+ value.proofBundle.schemaVersion === "2" &&
+ value.mode.kind !== "live_notebook"
+ ) {
+ context.addIssue({
+ code: "custom",
+ message: "a live Proof Bundle requires live notebook mode",
+ path: ["proofBundle", "sessionMode"],
+ });
+ }
+ if (
+ value.proofBundle.schemaVersion === "1" &&
+ value.mode.kind === "sample_lesson" &&
+ value.proofBundle.replayId !== value.mode.sampleId
+ ) {
+ context.addIssue({
+ code: "custom",
+ message: "the sample Proof Bundle does not match the selected lesson",
+ path: ["proofBundle", "replayId"],
+ });
+ }
+ if (
+ value.state !== "REASONING_DIFF_ISSUED" &&
+ value.state !== "PROOF_CAPSULE_ISSUED"
+ ) {
+ context.addIssue({
+ code: "custom",
+ message: "a Proof Bundle cannot be exposed before Reasoning Diff",
+ path: ["proofBundle"],
+ });
+ }
+ const authorityBindings = [
+ [
+ "prediction",
+ value.prediction?.immutableHash,
+ proofBundle.predictionContract.immutableHash,
+ ],
+ [
+ "verifiedResult",
+ value.verifiedResult?.resultHash,
+ proofBundle.verifiedResultSet.resultHash,
+ ],
+ [
+ "transferResult",
+ value.transferResult?.resultHash,
+ proofBundle.transferResult.resultHash,
+ ],
+ [
+ "patchResult",
+ value.patchResult?.resultHash,
+ proofBundle.patchResult.resultHash,
+ ],
+ ["reasoningDiff", value.reasoningDiff?.id, proofBundle.reasoningDiff.id],
+ ] as const;
+ for (const [
+ field,
+ sessionAuthority,
+ bundleAuthority,
+ ] of authorityBindings) {
+ if (
+ sessionAuthority === undefined ||
+ sessionAuthority !== bundleAuthority
+ ) {
+ context.addIssue({
+ code: "custom",
+ message: `the Proof Bundle ${field} authority does not match the session`,
+ path: ["proofBundle", field],
+ });
+ }
+ }
+ }
}
export const SessionViewSchema = z
@@ -1360,7 +1455,16 @@ export class CounterLabApiClient {
return this.request(
`/api/sessions/${encodedId(sessionId)}/proof-bundle`,
ProofBundleSchema,
- );
+ ).then((proofBundle) => {
+ if (proofBundle.sessionId !== sessionId) {
+ throw new ApiClientError({
+ code: "PROOF_BUNDLE_LINEAGE_INVALID",
+ message: "The Proof Bundle belongs to a different session",
+ status: 0,
+ });
+ }
+ return proofBundle;
+ });
}
async getReplay(replayId: string): Promise {
diff --git a/apps/web/src/app/CounterLabStudio.test.tsx b/apps/web/src/app/CounterLabStudio.test.tsx
index a6caf122..76f8deab 100644
--- a/apps/web/src/app/CounterLabStudio.test.tsx
+++ b/apps/web/src/app/CounterLabStudio.test.tsx
@@ -2,6 +2,7 @@ import { fireEvent, render, screen, within } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import type { StudioContext } from "../components/studio/types";
+import { createDefaultProofBoundSessionFixture } from "../test-fixtures/proofBundle";
import { CounterLabStudio } from "./CounterLabStudio";
const context: StudioContext = {
@@ -12,43 +13,8 @@ const context: StudioContext = {
events: [],
};
-const sampleProofBundle = {
- schemaVersion: "1",
- experimentPlan: {
- concept: "entity_leakage",
- runs: [
- { id: "random_row_split" },
- { id: "customer_group_split" },
- { id: "identity_ablation" },
- ],
- },
- generatedAdapter: {
- sha256: "a".repeat(64),
- commitHash: "b".repeat(40),
- },
- publicTests: {
- passed: 1,
- failed: 0,
- command: "python /workspace/public_tests.py",
- reportHash: "c".repeat(64),
- },
- externalVerifier: {
- status: "VERIFIED",
- verifiedInvariants: ["baseline_overlap_exists", "canonical_result_hash"],
- mutations: ["swapped split labels"],
- reportHash: "d".repeat(64),
- },
- patchResult: {
- status: "VERIFIED",
- modifiedCells: [3],
- diff: "--- cell-3-before.py\n+++ cell-3-after.py",
- verification: {
- passed: true,
- invariants: ["EVALUATION_CELL_ONLY", "GROUP_OVERLAP_ZERO"],
- unchangedCellHashes: ["e".repeat(64)],
- },
- },
-} as unknown as NonNullable["proofBundle"];
+const legacyProofSession = createDefaultProofBoundSessionFixture();
+const legacyProofBundle = legacyProofSession.proofBundle;
describe("CounterLabStudio", () => {
it("closes proof after compile when it was opened automatically", () => {
@@ -223,14 +189,13 @@ describe("CounterLabStudio", () => {
);
});
- it("shows validated sample proof details in their evidence tabs", () => {
+ it("shows recorded legacy Proof Bundle details in their evidence tabs", () => {
render(
,
+ mode: "instant",
+ session: legacyProofSession,
}}
actions={{
newAnalysis: vi.fn(),
@@ -244,22 +209,119 @@ describe("CounterLabStudio", () => {
);
fireEvent.click(screen.getByRole("tab", { name: "Tests" }));
- expect(screen.getByText("1 passed · 0 failed")).toBeInTheDocument();
+ expect(screen.getByText("3 passed · 0 failed")).toBeInTheDocument();
expect(screen.getByText("Public test report SHA-256")).toBeInTheDocument();
fireEvent.click(screen.getByRole("tab", { name: "Verifier" }));
- expect(screen.getByText("baseline_overlap_exists")).toBeInTheDocument();
- expect(screen.getByText("canonical_result_hash")).toBeInTheDocument();
+ expect(screen.getByText("zero_group_overlap")).toBeInTheDocument();
fireEvent.click(screen.getByRole("tab", { name: "Diff" }));
expect(screen.getByText("Changed notebook cells: 3")).toBeInTheDocument();
expect(screen.getByText(/cell-3-before\.py/iu)).toBeInTheDocument();
fireEvent.click(screen.getByRole("tab", { name: "Plan" }));
+ expect(screen.getByText("Recorded experiment plan")).toBeInTheDocument();
expect(screen.getByText("customer_group_split")).toBeInTheDocument();
expect(screen.getByText("Generated adapter SHA-256")).toBeInTheDocument();
});
+ it("never labels rejected legacy proof records as verified", () => {
+ if (legacyProofBundle.schemaVersion !== "1") {
+ throw new Error("legacy proof test fixture changed schema");
+ }
+ const rejectedProofBundle = {
+ ...legacyProofBundle,
+ externalVerifier: {
+ ...legacyProofBundle.externalVerifier,
+ status: "REJECTED" as const,
+ },
+ patchResult: {
+ ...legacyProofBundle.patchResult,
+ status: "REJECTED" as const,
+ verification: {
+ ...legacyProofBundle.patchResult.verification,
+ passed: false,
+ },
+ },
+ };
+ const rejectedProofSession = {
+ ...legacyProofSession,
+ patchResult: rejectedProofBundle.patchResult,
+ proofBundle: rejectedProofBundle,
+ };
+ render(
+
+ Rejected proof record
+ ,
+ );
+
+ fireEvent.click(screen.getByRole("tab", { name: "Diff" }));
+ expect(screen.getByText("Rejected patch record")).toBeInTheDocument();
+ expect(screen.getByLabelText("Recorded patch diff")).toHaveAttribute(
+ "tabindex",
+ "0",
+ );
+ expect(screen.queryByText("Verified patch")).not.toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole("tab", { name: "Verifier" }));
+ expect(
+ screen.getByText("Named invariants recorded before rejection"),
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByText("Accepted named invariants"),
+ ).not.toBeInTheDocument();
+ });
+
+ it("requires both verified status and a passing patch report", () => {
+ const inconsistentProofBundle = {
+ ...legacyProofBundle,
+ patchResult: {
+ ...legacyProofBundle.patchResult,
+ verification: {
+ ...legacyProofBundle.patchResult.verification,
+ passed: false,
+ },
+ },
+ };
+ render(
+
+ Inconsistent proof record
+ ,
+ );
+
+ fireEvent.click(screen.getByRole("tab", { name: "Diff" }));
+ expect(screen.getByText("Unverified patch record")).toBeInTheDocument();
+ expect(screen.queryByText("Verified patch")).not.toBeInTheDocument();
+ });
+
it("exposes private-session revocation only on demand in provenance", () => {
const revokeSessionAccess = vi.fn();
render(
diff --git a/apps/web/src/components/studio/ProofConsole.tsx b/apps/web/src/components/studio/ProofConsole.tsx
index f2c41d83..2b484745 100644
--- a/apps/web/src/components/studio/ProofConsole.tsx
+++ b/apps/web/src/components/studio/ProofConsole.tsx
@@ -162,12 +162,12 @@ function ProofBundleEvidence({
...proofBundle.experimentPlan.interventions.map((run) => run.runId),
];
return (
-
- Validated plan
-
- Registered run identifiers from the session's validated Proof
- Bundle.
-
+
+ Recorded experiment plan
+ Registered run identifiers recorded in the session Proof Bundle.
{operationIds.map((operationId) => (
@@ -201,9 +201,17 @@ function ProofBundleEvidence({
}
if (tab === "Diff") {
+ const patchStatus = proofBundle.patchResult.status;
+ const patchVerified =
+ patchStatus === "VERIFIED" && proofBundle.patchResult.verification.passed;
+ const patchHeading = patchVerified
+ ? "Verified patch"
+ : patchStatus === "REJECTED"
+ ? "Rejected patch record"
+ : "Unverified patch record";
return (
-
- Verified patch
+
+ {patchHeading}
Changed notebook cells:{" "}
{proofBundle.patchResult.modifiedCells.join(", ")}
@@ -213,7 +221,7 @@ function ProofBundleEvidence({
{proofBundle.patchResult.verification.invariants.length} named
invariants
-
+
{proofBundle.patchResult.diff}
@@ -242,13 +250,22 @@ function ProofBundleEvidence({
if (tab === "Verifier") {
if (proofBundle.schemaVersion === "1") {
+ const verifierStatus = proofBundle.externalVerifier.status;
+ const invariantLabel =
+ verifierStatus === "VERIFIED"
+ ? "Accepted named invariants"
+ : verifierStatus === "PARTIAL"
+ ? "Named invariants recorded before partial verification"
+ : verifierStatus === "REJECTED"
+ ? "Named invariants recorded before rejection"
+ : "Named invariants recorded without verification";
return (
External verifier · {proofBundle.externalVerifier.status}
- Accepted named invariants
+ {invariantLabel}
{proofBundle.externalVerifier.verifiedInvariants.map(
(invariant) => (
diff --git a/apps/web/src/test-fixtures/proofBundle.ts b/apps/web/src/test-fixtures/proofBundle.ts
new file mode 100644
index 00000000..0bfb9505
--- /dev/null
+++ b/apps/web/src/test-fixtures/proofBundle.ts
@@ -0,0 +1,256 @@
+import {
+ VerifiedResultSetSchema,
+ type ArtifactManifest,
+ type BeliefTest,
+ type SessionMode,
+} from "@counterlab/contracts";
+import {
+ createEvidenceEvent,
+ createProofBundle,
+} from "@counterlab/proof-bundle";
+
+import rawSampleResult from "../../../../fixtures/public/leakage_verified_result.json";
+
+const digest = (character: string) => character.repeat(64);
+
+export function createProofBoundSessionFixture(input: {
+ session: {
+ sessionId: string;
+ artifactId: string;
+ mode: SessionMode;
+ createdAt: string;
+ updatedAt: string;
+ };
+ artifact: ArtifactManifest;
+ beliefTest: BeliefTest;
+}) {
+ const { session, artifact, beliefTest } = input;
+ const verifiedResult = VerifiedResultSetSchema.parse(rawSampleResult);
+ const prediction = {
+ schemaVersion: "1" as const,
+ id: "prediction_1",
+ sessionId: session.sessionId,
+ beliefTestId: beliefTest.id,
+ choice: "Accuracy remains high",
+ confidence: 80,
+ committedAt: "2026-07-14T10:02:00.000Z",
+ immutableHash: digest("3"),
+ };
+ const transferResult = {
+ schemaVersion: "1" as const,
+ id: "transfer_1",
+ sessionId: session.sessionId,
+ taskId: "forecast-future-leakage-v1",
+ outcome: "PASSED" as const,
+ selectedStrategy: "time_ordered_holdout",
+ identifiedRisks: ["future_feature"],
+ evidenceChoices: ["feature created after prediction time"],
+ checks: [
+ {
+ invariant: "time_ordered_split",
+ passed: true,
+ evidence: "training observations precede test observations",
+ },
+ ],
+ evaluatorVersion: "1.0.0",
+ evaluatedAt: "2026-07-14T10:07:00.000Z",
+ resultHash: digest("b"),
+ };
+ const patchResult = {
+ schemaVersion: "1" as const,
+ id: "patch_1",
+ sessionId: session.sessionId,
+ status: "VERIFIED" as const,
+ sourceArtifactHash: artifact.fileSha256,
+ patchedArtifactHash: digest("c"),
+ patchHash: digest("d"),
+ modifiedCells: [3],
+ diff: "--- cell-3-before.py\n+++ cell-3-after.py",
+ verification: {
+ passed: true,
+ invariants: ["zero_group_overlap"],
+ unchangedCellHashes: [digest("e")],
+ },
+ generatedAt: "2026-07-14T10:08:00.000Z",
+ resultHash: digest("f"),
+ };
+ const event = createEvidenceEvent({
+ eventId: "event_1",
+ sessionId: session.sessionId,
+ sequence: 1,
+ timestamp: "2026-07-14T10:09:00.000Z",
+ actor: "system",
+ kind: "reasoning_diff.issued",
+ inputHashes: [],
+ outputHashes: [
+ prediction.immutableHash,
+ digest("4"),
+ digest("6"),
+ digest("7"),
+ verifiedResult.resultHash,
+ transferResult.resultHash,
+ patchResult.resultHash,
+ ],
+ payload: { state: "REASONING_DIFF_ISSUED" },
+ });
+ const reasoningDiff = {
+ schemaVersion: "1" as const,
+ id: "diff_1",
+ sessionId: session.sessionId,
+ dimensions: {
+ belief: { before: "Rows generalize", after: "Hold out entities" },
+ prediction: { before: "Accuracy stays high", after: "Accuracy fell" },
+ code: { before: "Random split", after: "Group split" },
+ transfer: { before: "Random dates", after: "Time holdout" },
+ },
+ evidenceEventHashes: [event.eventHash],
+ issuedAt: "2026-07-14T10:10:00.000Z",
+ };
+ const proofBundle = createProofBundle({
+ schemaVersion: "1",
+ bundleId: "bundle_1",
+ sessionId: session.sessionId,
+ replayId: "leakage-01",
+ createdAt: "2026-07-14T10:10:00.000Z",
+ events: [event],
+ artifactManifest: artifact,
+ beliefTest,
+ predictionContract: prediction,
+ experimentPlan: {
+ schemaVersion: "1",
+ concept: "entity_leakage",
+ datasetAdapter: "customer_churn_v1",
+ competingHypotheses: ["Rows generalize", "Identity leaks"],
+ expectedDiscrimination: [
+ {
+ runId: "customer_group_split",
+ expectedUnderCurrent: "Accuracy remains high",
+ expectedUnderCompeting: "Accuracy falls",
+ },
+ ],
+ runs: [
+ {
+ id: "customer_group_split",
+ split: "group",
+ groupBy: "customer_id",
+ model: "logistic_regression",
+ seed: 1729,
+ },
+ ],
+ metrics: ["accuracy"],
+ views: ["comparison"],
+ invariants: ["zero_group_overlap"],
+ resourceLimits: {
+ wallSeconds: 30,
+ memoryMb: 512,
+ maxProcesses: 4,
+ maxFiles: 16,
+ maxOutputBytes: 1_048_576,
+ },
+ },
+ generatedAdapter: { sha256: digest("4"), commitHash: digest("5") },
+ publicTests: {
+ passed: 3,
+ failed: 0,
+ command: "pytest public_tests.py",
+ reportHash: digest("6"),
+ },
+ externalVerifier: {
+ status: "VERIFIED",
+ verifiedInvariants: ["zero_group_overlap"],
+ mutations: ["group-overlap-leak"],
+ reportHash: digest("7"),
+ },
+ verifiedResultSet: verifiedResult,
+ learnerRevision: {
+ text: "Evaluation must hold out the deployment entity.",
+ recordedAt: "2026-07-14T10:09:00.000Z",
+ eventHash: event.eventHash,
+ },
+ transferResult,
+ patchResult,
+ reasoningDiff,
+ versions: {
+ environment: "test",
+ dependencies: "locked",
+ fixture: "leakage-v1",
+ kernel: "0.1.0",
+ verifier: "0.1.0",
+ prompt: "belief-v1",
+ model: "stored-replay",
+ template: "leakage-template-v1",
+ },
+ limitations: ["Synthetic fixture; no global mastery claim."],
+ reproductionCommands: ["./scripts/reproduce-session.sh leakage-01"],
+ });
+ return {
+ ...session,
+ state: "REASONING_DIFF_ISSUED" as const,
+ version: 12,
+ beliefTest,
+ prediction,
+ verifiedResult,
+ transferResult,
+ patchResult,
+ reasoningDiff,
+ proofBundle,
+ };
+}
+
+export function createDefaultProofBoundSessionFixture() {
+ const artifact: ArtifactManifest = {
+ artifactId: "artifact_1",
+ fileName: "customer_churn_leakage.ipynb",
+ fileSha256: digest("a"),
+ nbformat: 4,
+ support: { status: "SUPPORTED", reasons: [] },
+ cells: [],
+ schemaSummary: {
+ fields: [],
+ entityCandidates: ["customer_id"],
+ targetCandidates: ["churned"],
+ },
+ packageHints: ["sklearn"],
+ createdAt: "2026-07-14T10:00:00.000Z",
+ };
+ const beliefTest: BeliefTest = {
+ schemaVersion: "1",
+ id: "belief_1",
+ concept: "entity_leakage",
+ learnerClaim: "The score proves generalization.",
+ currentHypothesis: {
+ statement: "The model generalizes to unseen customers.",
+ predictedOutcome: "Group-holdout accuracy remains high.",
+ },
+ competingHypothesis: {
+ statement: "Repeated identities inflate the random split.",
+ predictedOutcome: "Group-holdout accuracy falls.",
+ },
+ evidenceRefs: [],
+ alternatives: [],
+ decisiveIntervention: {
+ id: "group-holdout",
+ description: "Hold out complete customers.",
+ controlledVariables: ["model", "seed"],
+ changedVariables: ["split boundary"],
+ discriminatesBecause: "The hypotheses predict different outcomes.",
+ },
+ uncertainty: {
+ confidence: 0.25,
+ limitations: ["The group result is not known yet."],
+ insufficientEvidence: true,
+ },
+ requiresLearnerConfirmation: true,
+ };
+ return createProofBoundSessionFixture({
+ session: {
+ sessionId: "session_1",
+ artifactId: artifact.artifactId,
+ mode: { kind: "sample_lesson", sampleId: "leakage-01" },
+ createdAt: "2026-07-14T10:01:00.000Z",
+ updatedAt: "2026-07-14T10:10:00.000Z",
+ },
+ artifact,
+ beliefTest,
+ });
+}
diff --git a/apps/web/worker/api.test.ts b/apps/web/worker/api.test.ts
index d45c96f8..aa0a1c04 100644
--- a/apps/web/worker/api.test.ts
+++ b/apps/web/worker/api.test.ts
@@ -35,7 +35,10 @@ import {
createEvidenceEvent,
hashCanonical,
} from "@counterlab/session-core";
-import { validateProofBundle } from "@counterlab/proof-bundle";
+import {
+ createProofBundle,
+ validateProofBundle,
+} from "@counterlab/proof-bundle";
import {
validatePublicReplayProjectionV1,
validateProofCapsulePayloadAuthorityV2,
@@ -168,6 +171,10 @@ class MemorySessionRepository implements SessionRepository {
this.eventLog.set(sessionId, structuredClone([...events]));
}
+ replaceSessionForIntegrityTest(session: CounterLabSession): void {
+ this.sessions.set(session.id, structuredClone(session));
+ }
+
close(): void {}
}
@@ -6376,6 +6383,120 @@ describe("Cloudflare Worker API", () => {
expect(download.headers.get("x-content-type-options")).toBe("nosniff");
expect(download.headers.get("cache-control")).toBe("private, no-store");
await expect(download.text()).resolves.toBe(patchedNotebookText);
+
+ const restored = await app.request(`/api/sessions/${sessionId}`);
+ expect(restored.status).toBe(200);
+ await expect(restored.json()).resolves.toMatchObject({
+ data: {
+ sessionId,
+ state: "REASONING_DIFF_ISSUED",
+ proofBundle: { sessionId, schemaVersion: "2" },
+ },
+ });
+ const proofDownload = await app.request(
+ `/api/sessions/${sessionId}/proof-bundle`,
+ );
+ expect(proofDownload.status).toBe(200);
+
+ const storedProofSession = await harness.sessionRepository.find(sessionId);
+ if (storedProofSession?.proofBundle === undefined) {
+ throw new Error("hosted proof bundle was not persisted");
+ }
+ const { integrity: _integrity, ...proofDraft } =
+ storedProofSession.proofBundle;
+ const proofSigningKey =
+ "counterlab-test-proof-signing-key-with-sufficient-entropy";
+ storedProofSession.proofBundle = createProofBundle(proofDraft, {
+ signingKey: proofSigningKey,
+ scientificEngineSnapshotHash: scientificEngineSnapshotValue.authorityHash,
+ });
+ const signedProofBundle = structuredClone(storedProofSession.proofBundle);
+ harness.sessionRepository.replaceSessionForIntegrityTest(
+ storedProofSession,
+ );
+ const proofSigningEnv = {
+ COUNTERLAB_SIGNING_KEY: proofSigningKey,
+ } as unknown as Env & Record;
+ for (const path of [
+ `/api/sessions/${sessionId}`,
+ `/api/sessions/${sessionId}/proof-bundle`,
+ ]) {
+ const missingKey = await app.request(path);
+ expect(missingKey.status).toBe(503);
+ await expect(missingKey.json()).resolves.toMatchObject({
+ error: { code: "PROOF_SIGNING_KEY_REQUIRED" },
+ });
+ const wrongKey = await app.request(path, undefined, {
+ ...proofSigningEnv,
+ COUNTERLAB_SIGNING_KEY: "wrong-proof-signing-key",
+ } as unknown as Env & Record);
+ expect(wrongKey.status).toBe(409);
+ await expect(wrongKey.json()).resolves.toMatchObject({
+ error: { code: "PROOF_BUNDLE_INVALID" },
+ });
+ expect((await app.request(path, undefined, proofSigningEnv)).status).toBe(
+ 200,
+ );
+ }
+
+ if (signedProofBundle.integrity.mode !== "hmac-signed") {
+ throw new Error("hosted proof test did not create a signed bundle");
+ }
+ storedProofSession.proofBundle = {
+ ...signedProofBundle,
+ integrity: {
+ mode: "integrity-hashed",
+ algorithm: "sha256",
+ contentHash: signedProofBundle.integrity.contentHash,
+ eventChainHead: signedProofBundle.integrity.eventChainHead,
+ },
+ };
+ harness.sessionRepository.replaceSessionForIntegrityTest(
+ storedProofSession,
+ );
+ for (const path of [
+ `/api/sessions/${sessionId}`,
+ `/api/sessions/${sessionId}/proof-bundle`,
+ ]) {
+ const downgraded = await app.request(path, undefined, proofSigningEnv);
+ expect(downgraded.status).toBe(409);
+ await expect(downgraded.json()).resolves.toMatchObject({
+ error: { code: "PROOF_BUNDLE_INVALID" },
+ });
+ }
+
+ storedProofSession.proofBundle = structuredClone(signedProofBundle);
+ storedProofSession.artifactId = "artifact_substituted_after_persistence";
+ harness.sessionRepository.replaceSessionForIntegrityTest(
+ storedProofSession,
+ );
+ for (const path of [
+ `/api/sessions/${sessionId}`,
+ `/api/sessions/${sessionId}/proof-bundle`,
+ ]) {
+ const substituted = await app.request(path, undefined, proofSigningEnv);
+ expect(substituted.status).toBe(409);
+ await expect(substituted.json()).resolves.toMatchObject({
+ error: { code: "PROOF_BUNDLE_INVALID" },
+ });
+ }
+
+ storedProofSession.artifactId = uploaded.artifactId;
+ storedProofSession.proofBundle.versions.model =
+ "tampered-after-persistence";
+ harness.sessionRepository.replaceSessionForIntegrityTest(
+ storedProofSession,
+ );
+ for (const path of [
+ `/api/sessions/${sessionId}`,
+ `/api/sessions/${sessionId}/proof-bundle`,
+ ]) {
+ const tampered = await app.request(path, undefined, proofSigningEnv);
+ expect(tampered.status).toBe(409);
+ await expect(tampered.json()).resolves.toMatchObject({
+ error: { code: "PROOF_BUNDLE_INVALID" },
+ });
+ }
});
it("budgets a v5 compiler job through two bounded repairs and scopes its token past the deadline", async () => {
diff --git a/apps/web/worker/api.ts b/apps/web/worker/api.ts
index 98715402..3a3e5379 100644
--- a/apps/web/worker/api.ts
+++ b/apps/web/worker/api.ts
@@ -127,13 +127,17 @@ import {
validateProofCapsulePayloadAuthorityV2,
validateProofCapsuleV2,
} from "@counterlab/proof-capsule";
-import { verifyEvidenceChain } from "@counterlab/proof-bundle";
+import {
+ validateProofBundle,
+ verifyEvidenceChain,
+} from "@counterlab/proof-bundle";
import { Hono } from "hono";
import type { Context } from "hono";
import { z, ZodError } from "zod";
import samplePatchedNotebookText from "../../../fixtures/public/leakage_sample_patch_v1/customer_churn_leakage.patched.ipynb?raw";
import samplePatchKernelResult from "../../../fixtures/public/leakage_sample_patch_v1/patch-kernel-result.json";
+import scientificEngineSnapshotValue from "../../../scientific-engines/snapshot-hash.json";
import replayPatchKernelResult from "../../../replays/leakage-01/patch-kernel-result.json";
import compilerReplaySummary from "../../../replays/leakage-01/compiler/replay-summary.json";
import replayVerifiedResult from "../../../replays/leakage-01/compiler/verified-live-run/verified-result.json";
@@ -4140,13 +4144,80 @@ function requireStoredSampleClaimScope(
}
}
+function requireStoredProofBundleIntegrity(
+ session: Awaited>,
+ signingKey: string | undefined,
+): void {
+ if (session.proofBundle === undefined) return;
+ if (
+ session.proofBundle.integrity.mode === "hmac-signed" &&
+ signingKey === undefined
+ ) {
+ throw new ApiInputError(
+ "PROOF_SIGNING_KEY_REQUIRED",
+ "The signing key for the stored Proof Bundle is unavailable",
+ 503,
+ );
+ }
+ try {
+ const requiresConfiguredSigning =
+ session.proofBundle.schemaVersion === "2" && signingKey !== undefined;
+ const proofBundle = validateProofBundle(session.proofBundle, {
+ ...(session.proofBundle.schemaVersion === "2"
+ ? {
+ scientificEngineSnapshotHash:
+ scientificEngineSnapshotValue.authorityHash,
+ }
+ : {}),
+ ...((session.proofBundle.integrity.mode === "hmac-signed" ||
+ requiresConfiguredSigning) &&
+ signingKey !== undefined
+ ? { signingKey }
+ : {}),
+ });
+ const modeMatches =
+ proofBundle.schemaVersion === "2"
+ ? session.mode.kind === "live_notebook"
+ : session.mode.kind !== "sample_lesson" ||
+ proofBundle.replayId === session.mode.sampleId;
+ if (
+ proofBundle.sessionId !== session.id ||
+ proofBundle.artifactManifest.artifactId !== session.artifactId ||
+ !modeMatches ||
+ (session.state !== "REASONING_DIFF_ISSUED" &&
+ session.state !== "PROOF_CAPSULE_ISSUED") ||
+ session.prediction?.immutableHash !==
+ proofBundle.predictionContract.immutableHash ||
+ session.verifiedResult?.resultHash !==
+ proofBundle.verifiedResultSet.resultHash ||
+ session.transferResult?.resultHash !==
+ proofBundle.transferResult.resultHash ||
+ session.patchResult?.resultHash !== proofBundle.patchResult.resultHash ||
+ session.reasoningDiff?.id !== proofBundle.reasoningDiff.id
+ ) {
+ throw new Error("stored Proof Bundle does not match its outer session");
+ }
+ } catch {
+ throw new ApiInputError(
+ "PROOF_BUNDLE_INVALID",
+ "The stored Proof Bundle failed integrity or lineage validation",
+ 409,
+ );
+ }
+}
+
function isFile(value: string | File | null): value is File {
return value !== null && typeof value !== "string";
}
function statePayload(
+ context: Context,
session: Awaited>,
) {
+ requireStoredProofBundleIntegrity(
+ session,
+ context.env?.COUNTERLAB_SIGNING_KEY,
+ );
const publicProofCapsule =
session.proofCapsule === undefined
? undefined
@@ -4655,7 +4726,7 @@ export function createApi(options: ApiOptions = {}) {
}
return context.json(
jsonSuccess({
- ...statePayload(session),
+ ...statePayload(context, session),
...(ownerCapability === undefined ? {} : { ownerCapability }),
}),
201,
@@ -4707,7 +4778,7 @@ export function createApi(options: ApiOptions = {}) {
}
return context.json(
jsonSuccess({
- ...statePayload(session),
+ ...statePayload(context, session),
...(ownerCapability === undefined ? {} : { ownerCapability }),
}),
201,
@@ -4728,7 +4799,7 @@ export function createApi(options: ApiOptions = {}) {
context.req.param("sessionId"),
);
requireStoredSampleClaimScope(session);
- return context.json(jsonSuccess(statePayload(session)));
+ return context.json(jsonSuccess(statePayload(context, session)));
});
app.get("/api/sessions/:sessionId/artifact", async (context) => {
@@ -4886,7 +4957,7 @@ export function createApi(options: ApiOptions = {}) {
}
return context.json(
jsonSuccess({
- ...statePayload(restarted),
+ ...statePayload(context, restarted),
...(ownerCapability === undefined ? {} : { ownerCapability }),
}),
created ? 201 : 200,
@@ -5093,7 +5164,7 @@ export function createApi(options: ApiOptions = {}) {
insufficient,
{ actor: "system", modelId: "concept-router-v1" },
);
- return context.json(jsonSuccess(statePayload(proposed)));
+ return context.json(jsonSuccess(statePayload(context, proposed)));
}
if (session.mode.kind === "live_notebook") {
@@ -5172,7 +5243,7 @@ export function createApi(options: ApiOptions = {}) {
promptHash: result.provenance.promptHash,
},
);
- return context.json(jsonSuccess(statePayload(proposed)));
+ return context.json(jsonSuccess(statePayload(context, proposed)));
} finally {
await releaseAdmissionBestEffort(
context,
@@ -5201,7 +5272,7 @@ export function createApi(options: ApiOptions = {}) {
actor: "system",
modelId: result.provenance.approvalId,
});
- return context.json(jsonSuccess(statePayload(proposed)));
+ return context.json(jsonSuccess(statePayload(context, proposed)));
});
app.post("/api/sessions/:sessionId/belief-test/confirm", async (context) => {
@@ -5212,7 +5283,9 @@ export function createApi(options: ApiOptions = {}) {
requireMutableSession(current);
if (input.action === "confirm") {
return context.json(
- jsonSuccess(statePayload(await service.confirmBeliefTest(sessionId))),
+ jsonSuccess(
+ statePayload(context, await service.confirmBeliefTest(sessionId)),
+ ),
);
}
if (input.action === "edit") {
@@ -5225,6 +5298,7 @@ export function createApi(options: ApiOptions = {}) {
return context.json(
jsonSuccess(
statePayload(
+ context,
await service.editBeliefSpecV2(sessionId, input.beliefSpec),
),
),
@@ -5238,6 +5312,7 @@ export function createApi(options: ApiOptions = {}) {
return context.json(
jsonSuccess(
statePayload(
+ context,
await service.editBeliefTest(sessionId, input.beliefTest),
),
),
@@ -5246,13 +5321,17 @@ export function createApi(options: ApiOptions = {}) {
if (input.action === "reject") {
return context.json(
jsonSuccess(
- statePayload(await service.rejectBeliefTest(sessionId, input.reason)),
+ statePayload(
+ context,
+ await service.rejectBeliefTest(sessionId, input.reason),
+ ),
),
);
}
return context.json(
jsonSuccess(
statePayload(
+ context,
await service.markInsufficientEvidence(sessionId, input.reason),
),
),
@@ -5290,7 +5369,10 @@ export function createApi(options: ApiOptions = {}) {
};
return context.json(
jsonSuccess(
- statePayload(await service.commitPrediction(sessionId, prediction)),
+ statePayload(
+ context,
+ await service.commitPrediction(sessionId, prediction),
+ ),
),
201,
);
@@ -5458,7 +5540,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(current),
+ ...statePayload(context, current),
runnerJob,
reused: true as const,
}),
@@ -5593,7 +5675,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(started),
+ ...statePayload(context, started),
runnerJob: starting,
...(claimed.reused ? { reused: true as const } : {}),
}),
@@ -5617,7 +5699,7 @@ export function createApi(options: ApiOptions = {}) {
[...sampleAuthority.evidenceHashes],
sampleAuthority.operationSummary,
);
- return context.json(jsonSuccess(statePayload(verified)));
+ return context.json(jsonSuccess(statePayload(context, verified)));
});
app.get("/api/runner/jobs/:jobId/input", async (context) => {
@@ -6647,7 +6729,7 @@ export function createApi(options: ApiOptions = {}) {
const projectedSession = await projectCancellation();
return context.json(
jsonSuccess({
- ...statePayload(projectedSession),
+ ...statePayload(context, projectedSession),
runnerJob: job,
reused: true as const,
runnerAcknowledged: true,
@@ -6716,7 +6798,7 @@ export function createApi(options: ApiOptions = {}) {
}
return context.json(
jsonSuccess({
- ...statePayload(updatedSession),
+ ...statePayload(context, updatedSession),
runnerJob: cancelled,
reused: false as const,
runnerAcknowledged,
@@ -7988,7 +8070,7 @@ export function createApi(options: ApiOptions = {}) {
jsonSuccess({
duplicate: completed.duplicate,
runnerJob: completed.job,
- session: statePayload(updatedSession),
+ session: statePayload(context, updatedSession),
verification,
}),
);
@@ -8254,7 +8336,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(current),
+ ...statePayload(context, current),
runnerJob,
reused: true as const,
}),
@@ -8331,7 +8413,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(current),
+ ...statePayload(context, current),
runnerJob: starting,
...(claimed.reused ? { reused: true as const } : {}),
}),
@@ -8473,7 +8555,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(current),
+ ...statePayload(context, current),
runnerJob: starting,
...(claimed.reused ? { reused: true as const } : {}),
}),
@@ -8492,7 +8574,7 @@ export function createApi(options: ApiOptions = {}) {
sessionId,
sampleResult,
);
- return context.json(jsonSuccess(statePayload(completed)));
+ return context.json(jsonSuccess(statePayload(context, completed)));
});
app.post("/api/sessions/:sessionId/boundary/run", async (context) => {
@@ -8652,7 +8734,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(current),
+ ...statePayload(context, current),
runnerJob,
reused: true as const,
}),
@@ -8705,7 +8787,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(current),
+ ...statePayload(context, current),
runnerJob: starting,
...(claimed.reused ? { reused: true as const } : {}),
}),
@@ -8937,7 +9019,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(current),
+ ...statePayload(context, current),
runnerJob: starting,
selectedRunId: derived.selectedRunId,
configurationHash,
@@ -9210,7 +9292,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(current),
+ ...statePayload(context, current),
runnerJob: starting,
selectedRunId,
configurationHash,
@@ -9498,7 +9580,7 @@ export function createApi(options: ApiOptions = {}) {
const sessionId = context.req.param("sessionId");
requireMutableSession(await service.getSession(sessionId));
const updated = await service.recordRevision(sessionId, revision);
- return context.json(jsonSuccess(statePayload(updated)));
+ return context.json(jsonSuccess(statePayload(context, updated)));
});
app.post("/api/sessions/:sessionId/transfer", async (context) => {
@@ -9571,7 +9653,7 @@ export function createApi(options: ApiOptions = {}) {
const evaluatedAt = (options.now?.() ?? new Date()).toISOString();
const result = await evaluateSubmission(evaluatedAt);
const updated = await service.recordTransferResult(sessionId, result);
- return context.json(jsonSuccess(statePayload(updated)));
+ return context.json(jsonSuccess(statePayload(context, updated)));
});
app.post("/api/sessions/:sessionId/patch/compile", async (context) => {
@@ -9768,7 +9850,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(current),
+ ...statePayload(context, current),
runnerJob,
reused: true as const,
}),
@@ -9853,7 +9935,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(started),
+ ...statePayload(context, started),
runnerJob: starting,
...(claimed.reused ? { reused: true as const } : {}),
}),
@@ -9951,7 +10033,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(current),
+ ...statePayload(context, current),
runnerJob,
reused: true as const,
}),
@@ -10031,7 +10113,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(started),
+ ...statePayload(context, started),
runnerJob: starting,
...(claimed.reused ? { reused: true as const } : {}),
}),
@@ -10100,7 +10182,7 @@ export function createApi(options: ApiOptions = {}) {
const updated = await service.verifyPatch(sessionId, patchResult);
return context.json(
jsonSuccess({
- ...statePayload(updated),
+ ...statePayload(context, updated),
patch: patchResult,
kernelVerification: samplePatchKernelResult,
}),
@@ -10188,6 +10270,10 @@ export function createApi(options: ApiOptions = {}) {
409,
);
}
+ requireStoredProofBundleIntegrity(
+ session,
+ context.env?.COUNTERLAB_SIGNING_KEY,
+ );
context.header(
"content-disposition",
`attachment; filename="counterlab-${session.id}-proof-bundle.json"`,
From 14dfea7281f97c7a05313657860008afdc689ce3 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 11:37:03 +0530
Subject: [PATCH 080/143] docs(progress): reconcile current release gates
---
docs/PROGRESS.md | 72 +++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 62 insertions(+), 10 deletions(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 065b9aa2..9c44a79d 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -12,17 +12,19 @@ pass is never presented as a browser or production pass.
### Current checkpoint
- **Branch:** `feat/learner-ux-v6.1`
-- **Latest committed checkpoint:** `dd7b372fb15048e11224979157d3bf29f9beedc9`.
+- **Latest committed checkpoint:** `3174a1f997a40f471ed9b8a20e147a0ce279ee15`.
The worktree contains only the preserved, untracked
- `scripts/.fuse_hidden0000a9600000801f`. The source now has a self-hashed
- cgroup request, observer, FINALIZE/ABORT protocol, fixed negative controls,
- fail-closed evidence validation, qualified-receipt derivation/store, runtime
- observer orchestration, serialized run/drain handling, exact v2/v3 Python
- consumers, a tightened Judge first fold, a centered prompt-first Landing,
- and 40 statically collected Playwright journeys. Source tests do not
- constitute a real cgroup sentinel: no exact image, aggregate host proof,
- qualification, deployment, rendered browser journey, learner observation,
- video, Devpost publication, or submission receipt is claimed.
+ `scripts/.fuse_hidden0000a9600000801f`. The branch now includes the
+ prompt-first Landing and Judge hierarchy, source-covered P0/P1 learner
+ mechanisms, exact runtime qualification contracts, a fail-closed submission
+ preflight foundation, and stored Proof Bundle validation bound to the
+ session, artifact, mode, state, HMAC policy, and v2 scientific snapshot.
+ The latest complete web run passes **79 files and 626/626 tests**; focused
+ proof tests, web and Worker TypeScript, the production build, scoped
+ formatting, and whitespace checks also pass at this checkpoint. These local
+ results do not constitute a real cgroup sentinel, a refreshed scientific
+ release, CloakBrowser qualification, deployment, learner observation,
+ Devpost publication, or a submission receipt.
- **Earlier product checkpoint:**
`5259a495948a68024702ba5239bf8057d1785f08`. The owner partitioned the
preserved integration delta into nine commits and fast-forwarded both
@@ -3787,3 +3789,53 @@ this progress update is the preserved, unstaged FUSE tombstone.
every link logged out.
8. Push and verify the feature branch, then fast-forward-only merge and push
`main` after every ship gate above is factual. No push or merge has occurred.
+
+## Current done / left reconciliation — 2026-07-21T06:04:25Z
+
+Deadline: `2026-07-22T00:00:00Z`; **17 hours 55 minutes 35 seconds remained at
+this checkpoint**. The feature branch is at committed HEAD
+`3174a1f997a40f471ed9b8a20e147a0ce279ee15`, one commit ahead of its remote
+tracking ref. The only worktree entry besides this progress update is the
+preserved, unstaged FUSE tombstone
+`scripts/.fuse_hidden0000a9600000801f`; it remains untouched.
+
+### Done and committed
+
+| Area | Current factual status | Evidence |
+| --- | --- | --- |
+| Learner journey and P0/P1 product repairs | **SOURCE PASS** | MB-001, MB-002, MB-003, MB-004, MB-005, MB-006, MB-007, MB-009, MB-010, MB-012, MB-013, MB-015, MB-016, MB-017, and MB-048 have source mechanisms and focused regressions. Their public acceptance journeys remain open. |
+| First-fold experience | **SOURCE PASS** | Prompt-first Landing, tightened Judge path, fixed-sample belief break, persistent Sample/Replay/Live authority labels, compact learner progress, post-seal trusted rendering, and the shared pre-Prediction forbidden-text regression are committed. |
+| Intake, recovery, and authority | **SOURCE PASS** | Upload admission is bounded and retry-safe; malformed, unsupported, interrupted, and ambiguous intake are explicit; terminal Reject and insufficient-evidence actions create or recover a source-bound child instead of resubmitting a terminal session; failed transfer keeps Repair locked. |
+| Evidence and Proof Bundle | **SOURCE PASS** | `3174a1f` validates stored Proof Bundles at Worker and client boundaries, binds session/artifact/mode/state, enforces v2 scientific snapshot and HMAC policy, prevents cross-artifact and signed-to-unsigned substitution, and renders only validated stored evidence. |
+| Local browser regression | **PASS, NON-QUALIFYING** | Stock-Chromium design review completed **35 passed, 5 exact-public/live skips, 0 failed** across 40 tests. This is local regression evidence only, not CloakBrowser or public-release qualification. |
+| Current web gate | **PASS** | Web Vitest **79 files, 626/626 tests passed**. Focused proof tests, web TypeScript, Worker TypeScript, production build, scoped Prettier, and Git whitespace checks passed after the latest proof slice. |
+| Broader deterministic gate | **PARTIAL** | The latest broader run passed **721** root Vitest tests with **3 failed and 2 skipped**; the failures are stale generated scientific-release bindings. Python passed **331/331** and critical mutations passed leakage **14/14** and imbalance **19/19**. |
+| Submission groundwork | **SOURCE PARTIAL** | `d942c08` added a fail-closed manifest/preflight and `74ff037` marked the old readiness snapshot historical. The package remains `DRAFT`; no publication or submission is claimed. |
+
+### What is left
+
+| Priority | Required work | Current gate |
+| --- | --- | --- |
+| 1 | Harden the submission validator so production smoke, repository/video/feedback/browser/link/screenshot evidence, learner-impact analysis, deadline state, and the official receipt are strict, tuple-bound, and read from verified bytes exactly once. Add a submitted-state command and adversarial tests. | **IN PROGRESS / NOT YET COMMITTED** |
+| 2 | Resolve the preserved FUSE tombstone without deleting it, obtain a genuinely clean frozen source, run the real aggregate-cgroup and negative-control sentinel, and build one exact runner image. | **BLOCKED on clean physical tree and real host capability** |
+| 3 | Regenerate the source/image-bound SBOM, VEX, scientific-engine, license, health, held-out, Sample Capsule, and qualification evidence from that one frozen tuple; repair the three stale root-test failures without weakening their checks. | **NOT RUN** |
+| 4 | Establish repository-contained Wrangler authentication, verify the intended account and release inputs, run migrations/release checks, and deploy only the qualified source/image tuple. Record exact source commit, image digest, Worker version, deployment ID, and public identity. | **BLOCKED / NOT RUN** |
+| 5 | Execute the exact public sample, replay, two supported live journeys, malformed/unsupported/refusal cases, reconnect/history, transfer fail/pass, patch, Capsule/download, responsive, keyboard, screen-reader, reduced-motion, console/network, and Web Vitals matrix through CloakBrowser. | **BLOCKED: no `CLOAK_CDP_ENDPOINT`; 0 qualifying journeys** |
+| 6 | Capture exact-tuple screenshots and the public <=3:00 video, verify the public repository and every link logged out, populate Devpost, record `/feedback` Session ID, submit, and retain the public slug and `submitted_at` receipt. Keep learner impact `NO_DATA` unless real consented observations occur. | **EXTERNAL EVIDENCE OPEN** |
+| 7 | Push the reviewed feature tip, then fast-forward-only merge and push `main` after the qualified deployment, public browser matrix, and submission facts are recorded. | **NOT RUN; merge remains gated** |
+
+### Current release verdict
+
+```text
+learner product source + focused regressions PASS
+complete deterministic release evidence FAIL (3 stale bindings)
+clean source + exact image + real containment sentinel BLOCKED
+qualified Cloudflare deployment NOT RUN
+CloakBrowser public qualification NOT RUN
+Devpost/video/learner evidence/submission receipt NOT READY
+feature push and verified fast-forward merge to main NOT RUN
+```
+
+The release remains **NO-GO**. The next locally actionable slice is the strict
+submission-evidence validator; it can be completed without pretending that any
+external deployment or submission evidence exists.
From 9ea848486210f2447fc2814a4fc20ee2a69cfa8a Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 11:57:54 +0530
Subject: [PATCH 081/143] fix(submission): bind verified evidence bytes
---
package.json | 3 +-
scripts/validate-submission-package.test.ts | 335 ++++++++++++-
scripts/validate-submission-package.ts | 524 ++++++++++++++++++--
3 files changed, 803 insertions(+), 59 deletions(-)
diff --git a/package.json b/package.json
index 06174f5f..90b7ad42 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,8 @@
"held-out:run": "node --import tsx scripts/run-held-out.ts",
"held-out:check": "node --import tsx scripts/run-held-out.ts --check",
"submission:preflight": "node --import tsx scripts/validate-submission-package.ts --draft",
- "submission:verify": "node --import tsx scripts/validate-submission-package.ts --ready"
+ "submission:verify": "node --import tsx scripts/validate-submission-package.ts --ready",
+ "submission:verify:submitted": "node --import tsx scripts/validate-submission-package.ts --submitted"
},
"dependencies": {
"openai": "6.46.0",
diff --git a/scripts/validate-submission-package.test.ts b/scripts/validate-submission-package.test.ts
index c924a6da..79cf4a58 100644
--- a/scripts/validate-submission-package.test.ts
+++ b/scripts/validate-submission-package.test.ts
@@ -91,6 +91,74 @@ function deploymentReceipt() {
} as const;
}
+function productionSmoke(
+ receipt: ReturnType,
+ deploymentReceiptSha256: string,
+) {
+ const stages = [
+ ["public-readiness", "control_plane", undefined],
+ ["capability-health", "control_plane", undefined],
+ ["public-secret-scan", "control_plane", undefined],
+ ["judge-mode", "control_plane", undefined],
+ ["sample-lesson", "sample", undefined],
+ ["verified-replay", "replay", undefined],
+ ["hosted-capsule-replay", "replay", "entity_leakage"],
+ ["live-leakage", "live_notebook", "entity_leakage"],
+ ["live-imbalance", "live_notebook", "class_imbalance"],
+ ] as const;
+ return {
+ schemaVersion: "6",
+ status: "PASSED",
+ baseUrl: receipt.productionOrigin,
+ startedAt: "2026-07-19T00:04:00.000Z",
+ completedAt: "2026-07-19T00:14:00.000Z",
+ deployment: {
+ workerVersion: receipt.workerVersionId,
+ workerEvidenceCommit: receipt.workerEvidenceCommit,
+ runnerSourceCommit: receipt.runnerSourceCommit,
+ containerImageDigest: receipt.containerImageDigest,
+ deploymentReceiptSha256,
+ timeoutCleanupReceiptSha256: receipt.timeoutCleanupReceiptSha256,
+ runtimePolicySha256: receipt.runtimePolicySha256,
+ proofDependencyManifestSha256: receipt.proofDependencyManifestSha256,
+ aggregateLimitEvidenceSha256: receipt.aggregateLimitEvidenceSha256,
+ workerArtifactClassification: receipt.workerArtifactClassification,
+ workerArtifactManifestSha256: receipt.workerArtifactManifestSha256,
+ workerBundleSha256: receipt.workerBundleSha256,
+ clientAssetsSha256: receipt.clientAssetsSha256,
+ clientAssetCount: receipt.clientAssetCount,
+ clientPublicAssetsSha256: receipt.clientPublicAssetsSha256,
+ clientPublicAssetCount: receipt.clientPublicAssetCount,
+ viteVersion: receipt.viteVersion,
+ wranglerVersion: receipt.wranglerVersion,
+ generationIsolationEvidenceSha256:
+ receipt.generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: receipt.generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: receipt.generationIsolationVerifiedAt,
+ releaseCheckGenerationIsolationEvidenceSha256:
+ receipt.releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256:
+ receipt.releaseCheckGenerationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ receipt.releaseCheckGenerationIsolationVerifiedAt,
+ },
+ stages: stages.map(([id, mode, concept], index) => ({
+ id,
+ mode,
+ ...(concept === undefined ? {} : { concept }),
+ status: "PASSED",
+ startedAt: `2026-07-19T00:${String(index + 4).padStart(2, "0")}:00.000Z`,
+ completedAt: `2026-07-19T00:${String(index + 5).padStart(2, "0")}:00.000Z`,
+ evidence: { verified: true },
+ })),
+ privacy: {
+ containsSecrets: false,
+ containsRawNotebookBytes: false,
+ containsPrivateReasoning: false,
+ },
+ } as const;
+}
+
async function readyPackage() {
const input = SubmissionPackageSchema.parse(await draftInput());
const evidence = new Map();
@@ -117,18 +185,7 @@ async function readyPackage() {
);
const smokeRef = addEvidence(
"evidence/production-smoke.json",
- bytes({
- schemaVersion: "6",
- status: "PASSED",
- baseUrl: receipt.productionOrigin,
- deployment: {
- workerVersion: receipt.workerVersionId,
- workerEvidenceCommit: receipt.workerEvidenceCommit,
- runnerSourceCommit: receipt.runnerSourceCommit,
- containerImageDigest: receipt.containerImageDigest,
- deploymentReceiptSha256: receiptRef.sha256,
- },
- }),
+ bytes(productionSmoke(receipt, receiptRef.sha256)),
);
const genericEvidence = (name: string) =>
addEvidence(`evidence/${name}.json`, bytes({ status: "VERIFIED", name }));
@@ -204,6 +261,7 @@ async function readyPackage() {
};
return {
ready,
+ evidence,
reader: async (entry: { path: string }) => {
const content = evidence.get(entry.path);
if (content === undefined) throw new Error("missing test evidence");
@@ -235,10 +293,252 @@ describe("submission package validator", () => {
const { ready, reader } = await readyPackage();
await expect(
- validateSubmissionPackage(ready, "ready", reader),
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
).resolves.toMatchObject({ submissionState: "READY_TO_SUBMIT" });
});
+ it("rejects the former minimal synthetic production smoke", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const minimal = bytes({
+ schemaVersion: "6",
+ status: "PASSED",
+ baseUrl: "https://counterlab.cserules.workers.dev",
+ deployment: {
+ workerVersion: ready.release.workerVersionId,
+ workerEvidenceCommit: ready.release.workerEvidenceCommit,
+ runnerSourceCommit: ready.release.runnerSourceCommit,
+ containerImageDigest: ready.release.containerImageDigest,
+ deploymentReceiptSha256: ready.release.deploymentReceipt!.sha256,
+ },
+ });
+ const path = "evidence/minimal-production-smoke.json";
+ evidence.set(path, minimal);
+ ready.release.productionSmoke = reference(path, minimal);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ expect.stringMatching(/schema-v6 smoke/iu),
+ ]),
+ });
+ });
+
+ it("rejects private fields inside production-smoke evidence", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const current = evidence.get(ready.release.productionSmoke!.path);
+ if (current === undefined) throw new Error("missing strict smoke fixture");
+ const smoke = JSON.parse(new TextDecoder().decode(current)) as {
+ stages: Array<{ evidence: Record }>;
+ };
+ smoke.stages[0]!.evidence = { token: "redacted" };
+ const privateSmoke = bytes(smoke);
+ const path = "evidence/private-production-smoke.json";
+ evidence.set(path, privateSmoke);
+ ready.release.productionSmoke = reference(path, privateSmoke);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ expect.stringMatching(/schema-v6 smoke/iu),
+ ]),
+ });
+ });
+
+ it("binds every validation mode to one exact package state", async () => {
+ const draft = await draftInput();
+ await expect(
+ validateSubmissionPackage(draft, "ready", repositoryReader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "ready validation requires submissionState READY_TO_SUBMIT",
+ ]),
+ });
+
+ const { ready, reader } = await readyPackage();
+ await expect(
+ validateSubmissionPackage(ready, "draft", reader),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "draft validation requires submissionState DRAFT",
+ ]),
+ });
+ });
+
+ it("rejects readiness at or after the competition deadline", async () => {
+ const { ready, reader } = await readyPackage();
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-22T00:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "ready package cannot be verified at or after the deadline",
+ ]),
+ });
+ });
+
+ it("rejects release evidence dated after the validation instant", async () => {
+ const { ready, reader } = await readyPackage();
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-19T00:10:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "release evidence is dated in the future",
+ ]),
+ });
+ });
+
+ it("hashes and parses each unique evidence file from one immutable read", async () => {
+ const calls = new Map();
+ const reader: EvidenceReader = async (entry) => {
+ const count = (calls.get(entry.path) ?? 0) + 1;
+ calls.set(entry.path, count);
+ if (count > 1) return bytes({ replacedAfterHash: true });
+ return repositoryReader(entry);
+ };
+
+ await expect(
+ validateSubmissionPackage(await draftInput(), "draft", reader),
+ ).resolves.toMatchObject({ submissionState: "DRAFT" });
+ expect(Math.max(...calls.values())).toBe(1);
+ });
+
+ it("uses the canonical learner-pilot schema", async () => {
+ const input = SubmissionPackageSchema.parse(await draftInput());
+ const qualifiedReleaseReceiptSha256 = "a".repeat(64);
+ const invalidImpact = bytes({
+ schemaVersion: "2",
+ status: "DESCRIPTIVE_ONLY",
+ analyzedAt: "2026-07-21T10:00:00.000Z",
+ qualifiedReleaseReceiptSha256,
+ participantCount: 1,
+ completedSessionCount: 1,
+ metrics: {},
+ limitations: [
+ "Results are descriptive pilot observations, not causal estimates or proof of mastery.",
+ ],
+ });
+ input.impact = {
+ status: "DESCRIPTIVE_ONLY",
+ aggregate: reference("evidence/invalid-impact.json", invalidImpact),
+ qualifiedReleaseReceiptSha256,
+ };
+ const reader: EvidenceReader = async (entry) =>
+ entry.path === input.impact.aggregate.path
+ ? invalidImpact
+ : repositoryReader(entry);
+
+ await expect(
+ validateSubmissionPackage(input, "draft", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining(["impact aggregate is invalid"]),
+ });
+ });
+
+ it("binds descriptive learner evidence to the deployed qualified receipt", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const wrongQualifiedReceiptSha256 = "e".repeat(64);
+ const aggregate = bytes({
+ schemaVersion: "2",
+ status: "DESCRIPTIVE_ONLY",
+ analyzedAt: "2026-07-21T10:00:00.000Z",
+ qualifiedReleaseReceiptSha256: wrongQualifiedReceiptSha256,
+ participantCount: 1,
+ completedSessionCount: 1,
+ metrics: {
+ completionRate: 1,
+ taskCount: 1,
+ firstUnassistedTransferPassRate: 1,
+ predictionDifferenceRate: 0,
+ confusionObservedRate: 0,
+ confusionCategoryCounts: {
+ navigation: 0,
+ prediction_meaning: 0,
+ test_rationale: 0,
+ boundary_interpretation: 0,
+ transfer_choice: 0,
+ repair_scope: 0,
+ proof_interpretation: 0,
+ other_no_detail: 0,
+ },
+ abandonmentRate: 0,
+ medianTaskDurationSeconds: 120,
+ reactions: {
+ clarity: {
+ clearer: 1,
+ unchanged: 0,
+ less_clear: 0,
+ declined: 0,
+ },
+ usefulness: {
+ useful: 1,
+ neutral: 0,
+ not_useful: 0,
+ declined: 0,
+ },
+ },
+ abandonmentReasons: {
+ participant_withdrew: 0,
+ accessibility_barrier: 0,
+ technical_failure: 0,
+ time_limit: 0,
+ facilitator_stopped: 0,
+ other_no_detail: 0,
+ },
+ byConcept: {
+ entity_leakage: {
+ tasks: 1,
+ firstUnassistedTransferPassed: 1,
+ predictionDifferedFromResult: 0,
+ confusionObserved: 0,
+ },
+ class_imbalance: {
+ tasks: 0,
+ firstUnassistedTransferPassed: 0,
+ predictionDifferedFromResult: 0,
+ confusionObserved: 0,
+ },
+ },
+ },
+ limitations: [
+ "Results are descriptive pilot observations, not causal estimates or proof of mastery.",
+ ],
+ });
+ const path = "evidence/mismatched-impact.json";
+ evidence.set(path, aggregate);
+ ready.impact = {
+ status: "DESCRIPTIVE_ONLY",
+ aggregate: reference(path, aggregate),
+ qualifiedReleaseReceiptSha256: wrongQualifiedReceiptSha256,
+ };
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "impact aggregate does not match its qualified release",
+ ]),
+ });
+ });
+
it("rejects evidence hash drift", async () => {
const input = SubmissionPackageSchema.parse(await draftInput());
input.copy.readme.sha256 = "0".repeat(64);
@@ -260,7 +560,7 @@ describe("submission package validator", () => {
const placeholder = structuredClone(input) as {
competition: { submitterType: string | null };
};
- placeholder.competition.submitterType = "PENDING";
+ placeholder.competition.submitterType = " PENDING ";
await expect(
validateSubmissionPackage(placeholder, "draft", repositoryReader),
).rejects.toThrow(/placeholder/iu);
@@ -271,5 +571,12 @@ describe("submission package validator", () => {
await expect(
validateSubmissionPackage(escaped, "draft", repositoryReader),
).rejects.toThrow(/inside the repository/iu);
+ const aliased = structuredClone(input) as {
+ copy: { readme: { path: string } };
+ };
+ aliased.copy.readme.path = "./README.md";
+ await expect(
+ validateSubmissionPackage(aliased, "draft", repositoryReader),
+ ).rejects.toThrow(/inside the repository/iu);
});
});
diff --git a/scripts/validate-submission-package.ts b/scripts/validate-submission-package.ts
index cdad4488..5096e2cd 100644
--- a/scripts/validate-submission-package.ts
+++ b/scripts/validate-submission-package.ts
@@ -4,6 +4,7 @@ import { resolve } from "node:path";
import { z } from "zod";
+import { PilotAnalysisSchema } from "../evals/learner-pilot/src/pilot.js";
import { DeploymentReceiptV7Schema } from "../packages/scientific-engine-registry/src/schema.js";
import { containedInputFile } from "./repository-cli-paths.js";
@@ -21,7 +22,12 @@ const RepositoryPathSchema = z
(value) =>
!value.startsWith("/") &&
!value.includes("\\") &&
- !value.split("/").includes(".."),
+ value
+ .split("/")
+ .every(
+ (component) =>
+ component.length > 0 && component !== "." && component !== "..",
+ ),
"evidence path must stay inside the repository",
);
@@ -161,6 +167,9 @@ export const SubmissionPackageSchema = z
export type SubmissionPackage = z.infer;
export type SubmissionValidationMode = "draft" | "ready" | "submitted";
+export type SubmissionValidationOptions = Readonly<{
+ now?: Date;
+}>;
export type EvidenceReader = (
reference: z.infer,
) => Promise;
@@ -173,22 +182,181 @@ const EXPECTED_ASSETS = [
"thumbnail-judge-belief-break.png",
] as const;
+const SmokeDeploymentSchema = z
+ .object({
+ workerVersion: WorkerVersionSchema,
+ workerEvidenceCommit: GitCommitSchema,
+ runnerSourceCommit: GitCommitSchema,
+ containerImageDigest: ImageDigestSchema,
+ deploymentReceiptSha256: Sha256Schema,
+ timeoutCleanupReceiptSha256: Sha256Schema,
+ runtimePolicySha256: Sha256Schema,
+ proofDependencyManifestSha256: Sha256Schema,
+ aggregateLimitEvidenceSha256: Sha256Schema,
+ workerArtifactClassification: z.literal("PROCESS_BOUND_PARTIAL"),
+ workerArtifactManifestSha256: Sha256Schema,
+ workerBundleSha256: Sha256Schema,
+ clientAssetsSha256: Sha256Schema,
+ clientAssetCount: z.number().int().positive(),
+ clientPublicAssetsSha256: Sha256Schema,
+ clientPublicAssetCount: z.number().int().positive(),
+ viteVersion: z.literal("8.1.4"),
+ wranglerVersion: z.literal("4.110.0"),
+ generationIsolationEvidenceSha256: Sha256Schema,
+ generationIsolationProbeSha256: Sha256Schema,
+ generationIsolationVerifiedAt: z.iso.datetime({ offset: true }),
+ releaseCheckGenerationIsolationEvidenceSha256: Sha256Schema,
+ releaseCheckGenerationIsolationProbeSha256: Sha256Schema,
+ releaseCheckGenerationIsolationVerifiedAt: z.iso.datetime({ offset: true }),
+ })
+ .strict()
+ .superRefine((deployment, context) => {
+ if (deployment.clientPublicAssetCount > deployment.clientAssetCount) {
+ context.addIssue({
+ code: "custom",
+ path: ["clientPublicAssetCount"],
+ message: "public client asset count exceeds the full deploy tree",
+ });
+ }
+ if (
+ deployment.releaseCheckGenerationIsolationProbeSha256 !==
+ deployment.generationIsolationProbeSha256
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["releaseCheckGenerationIsolationProbeSha256"],
+ message: "release-check isolation probe must match qualification",
+ });
+ }
+ });
+
+const SmokeStageSchema = z
+ .object({
+ id: z.string().regex(/^[A-Za-z0-9_.:-]{1,256}$/u),
+ mode: z.enum(["control_plane", "sample", "replay", "live_notebook"]),
+ concept: z.enum(["entity_leakage", "class_imbalance"]).optional(),
+ status: z.literal("PASSED"),
+ startedAt: z.iso.datetime({ offset: true }),
+ completedAt: z.iso.datetime({ offset: true }),
+ evidence: z.record(z.string(), z.unknown()),
+ })
+ .strict();
+
+const REQUIRED_SMOKE_STAGES = new Map<
+ string,
+ readonly [
+ "control_plane" | "sample" | "replay" | "live_notebook",
+ "entity_leakage" | "class_imbalance" | undefined,
+ ]
+>([
+ ["public-readiness", ["control_plane", undefined]],
+ ["capability-health", ["control_plane", undefined]],
+ ["public-secret-scan", ["control_plane", undefined]],
+ ["judge-mode", ["control_plane", undefined]],
+ ["sample-lesson", ["sample", undefined]],
+ ["verified-replay", ["replay", undefined]],
+ ["hosted-capsule-replay", ["replay", "entity_leakage"]],
+ ["live-leakage", ["live_notebook", "entity_leakage"]],
+ ["live-imbalance", ["live_notebook", "class_imbalance"]],
+]);
+
const ProductionSmokeV6Schema = z
.object({
schemaVersion: z.literal("6"),
status: z.literal("PASSED"),
baseUrl: z.literal("https://counterlab.cserules.workers.dev"),
- deployment: z
+ startedAt: z.iso.datetime({ offset: true }),
+ completedAt: z.iso.datetime({ offset: true }),
+ deployment: SmokeDeploymentSchema,
+ stages: z.array(SmokeStageSchema).length(REQUIRED_SMOKE_STAGES.size),
+ privacy: z
.object({
- workerVersion: WorkerVersionSchema,
- workerEvidenceCommit: GitCommitSchema,
- runnerSourceCommit: GitCommitSchema,
- containerImageDigest: ImageDigestSchema,
- deploymentReceiptSha256: Sha256Schema,
+ containsSecrets: z.literal(false),
+ containsRawNotebookBytes: z.literal(false),
+ containsPrivateReasoning: z.literal(false),
})
- .passthrough(),
+ .strict(),
})
- .passthrough();
+ .strict()
+ .superRefine((report, context) => {
+ const startedAt = Date.parse(report.startedAt);
+ const completedAt = Date.parse(report.completedAt);
+ if (completedAt < startedAt) {
+ context.addIssue({
+ code: "custom",
+ path: ["completedAt"],
+ message: "smoke report completes before it starts",
+ });
+ }
+ const qualificationAt = Date.parse(
+ report.deployment.generationIsolationVerifiedAt,
+ );
+ const releaseCheckAt = Date.parse(
+ report.deployment.releaseCheckGenerationIsolationVerifiedAt,
+ );
+ if (qualificationAt > startedAt || releaseCheckAt > startedAt) {
+ context.addIssue({
+ code: "custom",
+ path: ["deployment"],
+ message: "isolation verification must precede production smoke",
+ });
+ }
+ if (
+ releaseCheckAt < qualificationAt ||
+ startedAt - releaseCheckAt > 86_400_000
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["deployment", "releaseCheckGenerationIsolationVerifiedAt"],
+ message: "release-check isolation evidence is stale or out of order",
+ });
+ }
+
+ const observed = new Set();
+ let previousStartedAt = Number.NEGATIVE_INFINITY;
+ let previousCompletedAt = Number.NEGATIVE_INFINITY;
+ for (const [index, stage] of report.stages.entries()) {
+ const expected = REQUIRED_SMOKE_STAGES.get(stage.id);
+ if (
+ expected === undefined ||
+ expected[0] !== stage.mode ||
+ expected[1] !== stage.concept ||
+ observed.has(stage.id)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["stages", index],
+ message:
+ "smoke report does not contain the exact required stage matrix",
+ });
+ }
+ observed.add(stage.id);
+ const stageStartedAt = Date.parse(stage.startedAt);
+ const stageCompletedAt = Date.parse(stage.completedAt);
+ if (
+ stageStartedAt < startedAt ||
+ stageCompletedAt < stageStartedAt ||
+ stageStartedAt < previousStartedAt ||
+ stageCompletedAt < previousCompletedAt ||
+ stageCompletedAt > completedAt
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["stages", index],
+ message: "smoke stages must be complete and chronological",
+ });
+ }
+ previousStartedAt = stageStartedAt;
+ previousCompletedAt = stageCompletedAt;
+ }
+ if (observed.size !== REQUIRED_SMOKE_STAGES.size) {
+ context.addIssue({
+ code: "custom",
+ path: ["stages"],
+ message: "smoke report is missing a required stage",
+ });
+ }
+ });
class SubmissionValidationError extends Error {
readonly issues: readonly string[];
@@ -212,6 +380,78 @@ function parseJson(bytes: Uint8Array, label: string): unknown {
}
}
+const FORBIDDEN_EVIDENCE_KEYS = new Set([
+ "authorization",
+ "apikey",
+ "credential",
+ "credentials",
+ "notebookbytes",
+ "privatereasoning",
+ "rawnotebook",
+ "secret",
+ "token",
+ "password",
+ "cookie",
+ "setcookie",
+ "codexauthjson",
+ "openaiapikey",
+]);
+
+function assertPrivacySafeEvidence(value: unknown, label: string): void {
+ const inspect = (current: unknown): void => {
+ if (Array.isArray(current)) {
+ current.forEach(inspect);
+ return;
+ }
+ if (current !== null && typeof current === "object") {
+ const record = current as Record;
+ if (record.nbformat === 4 && Array.isArray(record.cells)) {
+ throw new Error(`${label} contains raw notebook content`);
+ }
+ for (const [key, child] of Object.entries(record)) {
+ const normalized = key.toLowerCase().replaceAll(/[^a-z0-9]/gu, "");
+ if (FORBIDDEN_EVIDENCE_KEYS.has(normalized)) {
+ throw new Error(`${label} contains forbidden private field ${key}`);
+ }
+ inspect(child);
+ }
+ return;
+ }
+ if (typeof current === "string" && /^[\s]*[\[{]/u.test(current)) {
+ try {
+ inspect(JSON.parse(current));
+ } catch (error) {
+ if (error instanceof SyntaxError) return;
+ throw error;
+ }
+ }
+ };
+ inspect(value);
+ const serialized = JSON.stringify(value);
+ if (
+ /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/u.test(serialized) ||
+ /\bsk-[A-Za-z0-9_-]{24,}\b/u.test(serialized) ||
+ /CODEX_AUTH_JSON\s*[:=]/u.test(serialized) ||
+ /COUNTERLAB_RUNNER_SIGNING_(?:PRIVATE_)?KEY\s*[:=]/u.test(serialized)
+ ) {
+ throw new Error(`${label} contains secret or private content`);
+ }
+}
+
+type EvidenceCache = ReadonlyMap;
+
+function cachedEvidence(
+ evidence: EvidenceCache,
+ reference: z.infer,
+ label: string,
+): Uint8Array {
+ const value = evidence.get(reference.path);
+ if (value === undefined) {
+ throw new Error(`${label} was not loaded from verified evidence`);
+ }
+ return value;
+}
+
function collectEvidenceReferences(
submission: SubmissionPackage,
): readonly z.infer[] {
@@ -243,7 +483,10 @@ function collectEvidenceReferences(
}
function assertNoPlaceholderStrings(value: unknown, path = "package"): void {
- if (typeof value === "string" && /^(?:PENDING|TBD|TODO)$/iu.test(value)) {
+ if (
+ typeof value === "string" &&
+ /^(?:PENDING|TBD|TODO)$/iu.test(value.trim())
+ ) {
throw new Error(`${path} contains a placeholder value`);
}
if (Array.isArray(value)) {
@@ -353,19 +596,26 @@ function requireReadyFields(
async function assertReleaseEvidence(
submission: SubmissionPackage,
- readEvidence: EvidenceReader,
+ evidence: EvidenceCache,
issues: string[],
-): Promise {
+ now: Date,
+): Promise {
const receiptRef = submission.release.deploymentReceipt;
const smokeRef = submission.release.productionSmoke;
- if (receiptRef === null || smokeRef === null) return;
+ if (receiptRef === null || smokeRef === null) return null;
try {
- const receipt = DeploymentReceiptV7Schema.parse(
- parseJson(await readEvidence(receiptRef), "deployment receipt"),
+ const receiptValue = parseJson(
+ cachedEvidence(evidence, receiptRef, "deployment receipt"),
+ "deployment receipt",
);
- const smoke = ProductionSmokeV6Schema.parse(
- parseJson(await readEvidence(smokeRef), "production smoke"),
+ const smokeValue = parseJson(
+ cachedEvidence(evidence, smokeRef, "production smoke"),
+ "production smoke",
);
+ assertPrivacySafeEvidence(receiptValue, "deployment receipt");
+ assertPrivacySafeEvidence(smokeValue, "production smoke");
+ const receipt = DeploymentReceiptV7Schema.parse(receiptValue);
+ const smoke = ProductionSmokeV6Schema.parse(smokeValue);
const expectedOrigin = new URL(submission.publicProduct.judgeUrl).origin;
if (
receipt.productionOrigin !== expectedOrigin ||
@@ -395,49 +645,184 @@ async function assertReleaseEvidence(
"production smoke does not match the submission release tuple",
);
}
+ const smokeBindings: ReadonlyArray = [
+ [
+ "timeout cleanup receipt",
+ smoke.deployment.timeoutCleanupReceiptSha256,
+ receipt.timeoutCleanupReceiptSha256,
+ ],
+ [
+ "aggregate-limit evidence",
+ smoke.deployment.aggregateLimitEvidenceSha256,
+ receipt.aggregateLimitEvidenceSha256,
+ ],
+ [
+ "runtime policy",
+ smoke.deployment.runtimePolicySha256,
+ receipt.runtimePolicySha256,
+ ],
+ [
+ "proof dependency manifest",
+ smoke.deployment.proofDependencyManifestSha256,
+ receipt.proofDependencyManifestSha256,
+ ],
+ [
+ "Worker artifact classification",
+ smoke.deployment.workerArtifactClassification,
+ receipt.workerArtifactClassification,
+ ],
+ [
+ "Worker artifact manifest",
+ smoke.deployment.workerArtifactManifestSha256,
+ receipt.workerArtifactManifestSha256,
+ ],
+ [
+ "Worker bundle",
+ smoke.deployment.workerBundleSha256,
+ receipt.workerBundleSha256,
+ ],
+ [
+ "client assets",
+ smoke.deployment.clientAssetsSha256,
+ receipt.clientAssetsSha256,
+ ],
+ [
+ "client asset count",
+ smoke.deployment.clientAssetCount,
+ receipt.clientAssetCount,
+ ],
+ [
+ "public client assets",
+ smoke.deployment.clientPublicAssetsSha256,
+ receipt.clientPublicAssetsSha256,
+ ],
+ [
+ "public client asset count",
+ smoke.deployment.clientPublicAssetCount,
+ receipt.clientPublicAssetCount,
+ ],
+ ["Vite version", smoke.deployment.viteVersion, receipt.viteVersion],
+ [
+ "Wrangler version",
+ smoke.deployment.wranglerVersion,
+ receipt.wranglerVersion,
+ ],
+ [
+ "qualified isolation evidence",
+ smoke.deployment.generationIsolationEvidenceSha256,
+ receipt.generationIsolationEvidenceSha256,
+ ],
+ [
+ "qualified isolation probe",
+ smoke.deployment.generationIsolationProbeSha256,
+ receipt.generationIsolationProbeSha256,
+ ],
+ [
+ "qualified isolation time",
+ smoke.deployment.generationIsolationVerifiedAt,
+ receipt.generationIsolationVerifiedAt,
+ ],
+ [
+ "release-check isolation evidence",
+ smoke.deployment.releaseCheckGenerationIsolationEvidenceSha256,
+ receipt.releaseCheckGenerationIsolationEvidenceSha256,
+ ],
+ [
+ "release-check isolation probe",
+ smoke.deployment.releaseCheckGenerationIsolationProbeSha256,
+ receipt.releaseCheckGenerationIsolationProbeSha256,
+ ],
+ [
+ "release-check isolation time",
+ smoke.deployment.releaseCheckGenerationIsolationVerifiedAt,
+ receipt.releaseCheckGenerationIsolationVerifiedAt,
+ ],
+ ];
+ for (const [label, observed, expected] of smokeBindings) {
+ if (observed !== expected) {
+ issues.push(`production smoke ${label} does not match deployment`);
+ }
+ }
+ if (Date.parse(receipt.deployedAt) > Date.parse(smoke.startedAt)) {
+ issues.push("production smoke started before the recorded deployment");
+ }
+ const smokeCompletedAt = Date.parse(smoke.completedAt);
+ if (
+ Date.parse(receipt.deployedAt) > now.getTime() ||
+ smokeCompletedAt > now.getTime()
+ ) {
+ issues.push("release evidence is dated in the future");
+ }
+ if (smokeCompletedAt > Date.parse(submission.competition.deadline)) {
+ issues.push("production smoke completed after the competition deadline");
+ }
+ if (
+ submission.officialSubmission.submittedAt !== null &&
+ smokeCompletedAt > Date.parse(submission.officialSubmission.submittedAt)
+ ) {
+ issues.push("production smoke completed after the official submission");
+ }
+ return receipt.qualifiedRunnerReceiptSha256;
} catch {
issues.push(
"release evidence is not a valid schema-v7 receipt and schema-v6 smoke",
);
+ return null;
}
}
async function assertImpactEvidence(
submission: SubmissionPackage,
- readEvidence: EvidenceReader,
+ evidence: EvidenceCache,
issues: string[],
+ now: Date,
+ qualifiedReleaseReceiptSha256: string | null,
): Promise {
try {
- const impact = z
- .object({
- schemaVersion: z.literal("2"),
- status: z.enum(["NO_DATA", "DESCRIPTIVE_ONLY"]),
- qualifiedReleaseReceiptSha256: Sha256Schema.nullable(),
- participantCount: z.number().int().nonnegative(),
- completedSessionCount: z.number().int().nonnegative(),
- metrics: z.unknown().nullable(),
- limitations: z.array(z.string().trim().min(1)).min(1),
- })
- .passthrough()
- .parse(
- parseJson(
- await readEvidence(submission.impact.aggregate),
+ const impact = PilotAnalysisSchema.parse(
+ parseJson(
+ cachedEvidence(
+ evidence,
+ submission.impact.aggregate,
"impact aggregate",
),
- );
+ "impact aggregate",
+ ),
+ );
if (impact.status !== submission.impact.status) {
issues.push("impact status does not match its aggregate");
}
+ if (impact.status === "NO_DATA") {
+ if (submission.impact.qualifiedReleaseReceiptSha256 !== null) {
+ issues.push("NO_DATA impact must not bind a qualified release");
+ }
+ if (
+ !impact.limitations.includes(
+ "No consented learner-pilot session records were supplied; no learner outcome is claimed.",
+ )
+ ) {
+ issues.push("NO_DATA impact must retain the canonical limitation");
+ }
+ } else {
+ if (
+ qualifiedReleaseReceiptSha256 === null ||
+ impact.qualifiedReleaseReceiptSha256 !==
+ qualifiedReleaseReceiptSha256 ||
+ submission.impact.qualifiedReleaseReceiptSha256 !==
+ qualifiedReleaseReceiptSha256
+ ) {
+ issues.push("impact aggregate does not match its qualified release");
+ }
+ }
+ const analyzedAt = Date.parse(impact.analyzedAt);
if (
- impact.status === "NO_DATA" &&
- (impact.participantCount !== 0 ||
- impact.completedSessionCount !== 0 ||
- impact.metrics !== null ||
- impact.qualifiedReleaseReceiptSha256 !== null ||
- submission.impact.qualifiedReleaseReceiptSha256 !== null)
+ analyzedAt > now.getTime() ||
+ analyzedAt > Date.parse(submission.competition.deadline) ||
+ (submission.officialSubmission.submittedAt !== null &&
+ analyzedAt > Date.parse(submission.officialSubmission.submittedAt))
) {
issues.push(
- "NO_DATA impact must contain zero participants and no metrics",
+ "impact aggregate was analyzed after its allowed evidence window",
);
}
} catch {
@@ -449,11 +834,13 @@ export async function validateSubmissionPackage(
input: unknown,
mode: SubmissionValidationMode,
readEvidence: EvidenceReader,
+ options: SubmissionValidationOptions = {},
): Promise {
assertNoPlaceholderStrings(input);
const submission = SubmissionPackageSchema.parse(input);
const references = collectEvidenceReferences(submission);
const seen = new Map();
+ const evidence = new Map();
for (const reference of references) {
const previous = seen.get(reference.path);
if (previous !== undefined && previous !== reference.sha256) {
@@ -461,18 +848,65 @@ export async function validateSubmissionPackage(
`evidence path has conflicting hashes: ${reference.path}`,
);
}
+ if (previous !== undefined) continue;
seen.set(reference.path, reference.sha256);
- const bytes = await readEvidence(reference);
- if (sha256(bytes) !== reference.sha256) {
+ const value = new Uint8Array(await readEvidence(reference));
+ if (sha256(value) !== reference.sha256) {
throw new Error(`evidence hash mismatch: ${reference.path}`);
}
+ evidence.set(reference.path, value);
}
+ const now = options.now ?? new Date();
+ if (Number.isNaN(now.getTime()))
+ throw new Error("validation time is invalid");
const issues: string[] = [];
- await assertImpactEvidence(submission, readEvidence, issues);
+ const expectedState = {
+ draft: "DRAFT",
+ ready: "READY_TO_SUBMIT",
+ submitted: "SUBMITTED",
+ } as const;
+ if (submission.submissionState !== expectedState[mode]) {
+ issues.push(
+ `${mode} validation requires submissionState ${expectedState[mode]}`,
+ );
+ }
+ if (mode !== "submitted") {
+ const official = submission.officialSubmission;
+ if (
+ official.state !== "NOT_SUBMITTED" ||
+ official.publicSlug !== null ||
+ official.submittedAt !== null ||
+ official.receipt !== null ||
+ official.postSubmitLoggedOutEvidence !== null
+ ) {
+ issues.push(
+ "draft and ready packages must not claim submission evidence",
+ );
+ }
+ }
+ let qualifiedReleaseReceiptSha256: string | null = null;
if (mode !== "draft") {
requireReadyFields(submission, issues);
- await assertReleaseEvidence(submission, readEvidence, issues);
+ qualifiedReleaseReceiptSha256 = await assertReleaseEvidence(
+ submission,
+ evidence,
+ issues,
+ now,
+ );
+ }
+ await assertImpactEvidence(
+ submission,
+ evidence,
+ issues,
+ now,
+ qualifiedReleaseReceiptSha256,
+ );
+ if (
+ mode === "ready" &&
+ now.getTime() >= Date.parse(submission.competition.deadline)
+ ) {
+ issues.push("ready package cannot be verified at or after the deadline");
}
if (mode === "submitted") {
const official = submission.officialSubmission;
@@ -492,6 +926,8 @@ export async function validateSubmissionPackage(
Date.parse(submission.competition.deadline)
) {
issues.push("official submission timestamp is after the deadline");
+ } else if (Date.parse(official.submittedAt) > now.getTime()) {
+ issues.push("official submission timestamp is in the future");
}
}
if (issues.length > 0) throw new SubmissionValidationError(issues);
From 07c90913b01dbb65281bed3c9f9be585cee421a1 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 12:12:04 +0530
Subject: [PATCH 082/143] fix(submission): bind release evidence chain
---
docs/SUBMISSION_PACKAGE.json | 2 +
scripts/validate-submission-package.test.ts | 473 ++++++++++++++++++--
scripts/validate-submission-package.ts | 256 ++++++++++-
3 files changed, 700 insertions(+), 31 deletions(-)
diff --git a/docs/SUBMISSION_PACKAGE.json b/docs/SUBMISSION_PACKAGE.json
index 6759784f..71f88538 100644
--- a/docs/SUBMISSION_PACKAGE.json
+++ b/docs/SUBMISSION_PACKAGE.json
@@ -42,6 +42,8 @@
"runnerSourceCommit": null,
"containerImageDigest": null,
"workerVersionId": null,
+ "qualifiedRunnerReceipt": null,
+ "releaseCheckReceipt": null,
"deploymentReceipt": null,
"productionSmoke": null
},
diff --git a/scripts/validate-submission-package.test.ts b/scripts/validate-submission-package.test.ts
index 79cf4a58..ce0a79b4 100644
--- a/scripts/validate-submission-package.test.ts
+++ b/scripts/validate-submission-package.test.ts
@@ -4,6 +4,9 @@ import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
+import { RELEASE_CHECK_IDS } from "../packages/scientific-engine-registry/src/schema.js";
+import { canonicalJson } from "../packages/session-core/src/index.js";
+import { createGenerationIsolationEvidence } from "./generation-isolation-evidence.js";
import {
SubmissionPackageSchema,
type EvidenceReader,
@@ -35,42 +38,200 @@ async function draftInput(): Promise {
const repositoryReader: EvidenceReader = async (entry) =>
new Uint8Array(await readFile(resolve(root, entry.path)));
-function deploymentReceipt() {
+function isolationEvidence(input: {
+ sourceCommit: string;
+ sourceTreeSha256: string;
+ localImageTag: string;
+ localImageDigest: string;
+ verifiedAt: string;
+}) {
+ const probePayload = {
+ schemaVersion: "1",
+ probeVersion: "counterlab-generation-isolation-v1",
+ service: "counterlab-hosted-runner",
+ probe: "non-root-startup",
+ checks: [
+ "entrypoint",
+ "non-root-user",
+ "immutable-paths",
+ "codex",
+ "python",
+ "bubblewrap",
+ "bubblewrap-read-isolation",
+ "setpriv",
+ "writable-roots",
+ ],
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ bubblewrapVersion: "0.11.0",
+ bubblewrap: {
+ forbiddenHostPathsHidden: true,
+ parentEnvironmentHidden: true,
+ workspaceVisible: true,
+ workspaceWritable: true,
+ },
+ } as const;
+ return createGenerationIsolationEvidence({
+ ...input,
+ imageUser: "10001:10001",
+ startupProbe: {
+ status: "ready",
+ service: probePayload.service,
+ probe: probePayload.probe,
+ checks: probePayload.checks,
+ generationFilesystemReadIsolation:
+ probePayload.generationFilesystemReadIsolation,
+ generationIsolationProbe: probePayload,
+ generationIsolationProbeSha256: hash(bytes(canonicalJson(probePayload))),
+ },
+ });
+}
+
+function releaseEvidenceChain(
+ options: Readonly<{ qualificationDay?: "2026-07-18" | "2026-07-19" }> = {},
+) {
+ const qualificationDay = options.qualificationDay ?? "2026-07-19";
const worker = "a".repeat(40);
const runner = "b".repeat(40);
- const digest = `sha256:${"c".repeat(64)}`;
- return {
+ const sourceTreeSha256 = "8".repeat(64);
+ const localImageDigest = `sha256:${"d".repeat(64)}`;
+ const registryDigest = `sha256:${"c".repeat(64)}`;
+ const adapterImageDigest = `sha256:${"5".repeat(64)}`;
+ const localImageTag = `counterlab-runner:git-${runner}`;
+ const adapterImageTag = `counterlab-adapter:git-${runner}`;
+ const qualifiedIsolation = isolationEvidence({
+ sourceCommit: runner,
+ sourceTreeSha256,
+ localImageTag,
+ localImageDigest,
+ verifiedAt: `${qualificationDay}T00:00:10.000Z`,
+ });
+ const qualified = {
+ schemaVersion: "6",
+ status: "VERIFIED",
+ sourceCommit: runner,
+ sourceArchiveSha256: "0".repeat(64),
+ sourceTreeSha256,
+ dockerfileSha256: "1".repeat(64),
+ localImageTag,
+ localImageDigest,
+ ociRevision: runner,
+ ociSourceTreeSha256: sourceTreeSha256,
+ engineAuthorityHash: "2".repeat(64),
+ runtimeManifestHash: "3".repeat(64),
+ runtimeToolchainSha256: "4".repeat(64),
+ runtimePolicySha256: "5".repeat(64),
+ proofDependencyManifestSha256: "6".repeat(64),
+ toolchainLockSha256: "7".repeat(64),
+ runtimeAdapterSha256: "8".repeat(64),
+ buildctlSha256: "9".repeat(64),
+ buildkitdSha256: "a".repeat(64),
+ buildkitConfigSha256: "b".repeat(64),
+ adapterDockerfileSha256: "c".repeat(64),
+ adapterImageTag,
+ adapterImageDigest,
+ adapterManifestDigest: `sha256:${"6".repeat(64)}`,
+ adapterOciArchiveSha256: "d".repeat(64),
+ adapterOciRevision: runner,
+ adapterOciSourceTreeSha256: sourceTreeSha256,
+ limitMode: "container-cgroup-and-process-rlimit",
+ aggregateLimitIntentEnforced: true,
+ aggregateLimitEvidenceSha256: "e".repeat(64),
+ timeoutCleanupReceipt: "evidence/timeout-cleanup-receipt.json",
+ timeoutCleanupReceiptSha256: "f".repeat(64),
+ timeoutCleanupPayloadSha256: "0".repeat(64),
+ timeoutRunControlReceiptSha256: "1".repeat(64),
+ timeoutRootlessReceiptSha256: "2".repeat(64),
+ timeoutRuntimeSessionId: "rt-12345678",
+ timeoutVerifiedAt: `${qualificationDay}T00:00:20.000Z`,
+ evidenceCommit: worker,
+ registryImage: `registry.cloudflare.com/account/counterlab-runner:git-${runner}`,
+ registryDigest,
+ registryResolvedAt: `${qualificationDay}T00:00:30.000Z`,
+ qualifiedAt: `${qualificationDay}T00:01:00.000Z`,
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationEvidence: qualifiedIsolation.evidence,
+ generationIsolationEvidenceSha256: qualifiedIsolation.evidenceSha256,
+ generationIsolationProbeSha256: qualifiedIsolation.probeSha256,
+ generationIsolationVerifiedAt: qualifiedIsolation.evidence.verifiedAt,
+ verifierVersion: "counterlab-release-v6",
+ } as const;
+ const qualifiedBytes = bytes(qualified);
+ const freshIsolation = isolationEvidence({
+ sourceCommit: runner,
+ sourceTreeSha256,
+ localImageTag,
+ localImageDigest,
+ verifiedAt: "2026-07-19T00:01:30.000Z",
+ });
+ const releaseCheck = {
+ schemaVersion: "5",
+ status: "PASSED",
+ evidenceCommit: worker,
+ sourceCommit: runner,
+ qualifiedRunnerReceiptSha256: hash(qualifiedBytes),
+ qualifiedAt: qualified.qualifiedAt,
+ runnerImageTag: localImageTag,
+ runnerImageDigest: localImageDigest,
+ adapterImageTag,
+ adapterImageDigest,
+ registryDigest,
+ runtimeToolchainSha256: qualified.runtimeToolchainSha256,
+ runtimePolicySha256: qualified.runtimePolicySha256,
+ proofDependencyManifestSha256: qualified.proofDependencyManifestSha256,
+ aggregateLimitEvidenceSha256: qualified.aggregateLimitEvidenceSha256,
+ runtimeAdapterSha256: qualified.runtimeAdapterSha256,
+ checks: RELEASE_CHECK_IDS.map((id) => ({ id, status: "PASSED" as const })),
+ checkedAt: "2026-07-19T00:02:00.000Z",
+ generationFilesystemReadIsolation: "OS_ENFORCED",
+ generationIsolationEvidenceSha256:
+ qualified.generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: qualified.generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: qualified.generationIsolationVerifiedAt,
+ releaseCheckGenerationIsolationEvidence: freshIsolation.evidence,
+ releaseCheckGenerationIsolationEvidenceSha256:
+ freshIsolation.evidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256: freshIsolation.probeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ freshIsolation.evidence.verifiedAt,
+ verifierVersion: "counterlab-release-check-v5",
+ } as const;
+ const releaseCheckBytes = bytes(releaseCheck);
+ const receipt = {
schemaVersion: "7",
status: "DEPLOYED",
workerName: "counterlab",
productionOrigin: "https://counterlab.cserules.workers.dev",
generationFilesystemReadIsolation: "OS_ENFORCED",
- generationIsolationEvidenceSha256: "0".repeat(64),
- generationIsolationProbeSha256: "1".repeat(64),
- generationIsolationVerifiedAt: "2026-07-19T00:01:00.000Z",
- releaseCheckGenerationIsolationEvidenceSha256: "2".repeat(64),
- releaseCheckGenerationIsolationProbeSha256: "1".repeat(64),
- releaseCheckGenerationIsolationVerifiedAt: "2026-07-19T00:01:30.000Z",
+ generationIsolationEvidenceSha256:
+ qualified.generationIsolationEvidenceSha256,
+ generationIsolationProbeSha256: qualified.generationIsolationProbeSha256,
+ generationIsolationVerifiedAt: qualified.generationIsolationVerifiedAt,
+ releaseCheckGenerationIsolationEvidenceSha256:
+ releaseCheck.releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheckGenerationIsolationProbeSha256:
+ releaseCheck.releaseCheckGenerationIsolationProbeSha256,
+ releaseCheckGenerationIsolationVerifiedAt:
+ releaseCheck.releaseCheckGenerationIsolationVerifiedAt,
workerEvidenceCommit: worker,
runnerSourceCommit: runner,
- qualifiedRunnerReceiptSha256: "1".repeat(64),
- releaseCheckReceiptSha256: "2".repeat(64),
- releaseCheckCheckedAt: "2026-07-19T00:02:00.000Z",
- timeoutCleanupReceiptSha256: "d".repeat(64),
- aggregateLimitEvidenceSha256: "6".repeat(64),
- runtimeToolchainSha256: "3".repeat(64),
- runtimePolicySha256: "d".repeat(64),
- proofDependencyManifestSha256: "e".repeat(64),
- runtimeAdapterSha256: "4".repeat(64),
- adapterImageDigest: `sha256:${"5".repeat(64)}`,
+ qualifiedRunnerReceiptSha256: hash(qualifiedBytes),
+ releaseCheckReceiptSha256: hash(releaseCheckBytes),
+ releaseCheckCheckedAt: releaseCheck.checkedAt,
+ timeoutCleanupReceiptSha256: qualified.timeoutCleanupReceiptSha256,
+ aggregateLimitEvidenceSha256: qualified.aggregateLimitEvidenceSha256,
+ runtimeToolchainSha256: qualified.runtimeToolchainSha256,
+ runtimePolicySha256: qualified.runtimePolicySha256,
+ proofDependencyManifestSha256: qualified.proofDependencyManifestSha256,
+ runtimeAdapterSha256: qualified.runtimeAdapterSha256,
+ adapterImageDigest,
workerVersionId: "11111111-2222-3333-4444-555555555555",
workerTag: `git-${worker}`,
workerMessage: `CounterLab Worker ${worker}; runner ${runner}`,
containerApplicationId: "container-app-1",
containerApplicationVersion: "3",
- containerImage: `registry.cloudflare.com/account/counterlab-runner@${digest}`,
+ containerImage: `registry.cloudflare.com/account/counterlab-runner@${registryDigest}`,
containerState: "active",
- containerImageDigest: digest,
+ containerImageDigest: registryDigest,
workerArtifactClassification: "PROCESS_BOUND_PARTIAL",
workerArtifactManifestSha256: "f".repeat(64),
deployConfigSha256: "6".repeat(64),
@@ -89,10 +250,17 @@ function deploymentReceipt() {
deployedAt: "2026-07-19T00:03:00.000Z",
verifierVersion: "counterlab-deployment-v7",
} as const;
+ return {
+ qualified,
+ qualifiedBytes,
+ releaseCheck,
+ releaseCheckBytes,
+ receipt,
+ };
}
function productionSmoke(
- receipt: ReturnType,
+ receipt: ReturnType["receipt"],
deploymentReceiptSha256: string,
) {
const stages = [
@@ -159,7 +327,9 @@ function productionSmoke(
} as const;
}
-async function readyPackage() {
+async function readyPackage(
+ options: Parameters[0] = {},
+) {
const input = SubmissionPackageSchema.parse(await draftInput());
const evidence = new Map();
for (const entry of [
@@ -177,7 +347,16 @@ async function readyPackage() {
evidence.set(path, content);
return reference(path, content);
};
- const receipt = deploymentReceipt();
+ const chain = releaseEvidenceChain(options);
+ const { receipt } = chain;
+ const qualifiedRef = addEvidence(
+ "evidence/qualified-runner-release.json",
+ chain.qualifiedBytes,
+ );
+ const releaseCheckRef = addEvidence(
+ "evidence/release-check-receipt.json",
+ chain.releaseCheckBytes,
+ );
const receiptBytes = bytes(receipt);
const receiptRef = addEvidence(
"evidence/deployment-receipt.json",
@@ -220,6 +399,8 @@ async function readyPackage() {
runnerSourceCommit: receipt.runnerSourceCommit,
containerImageDigest: receipt.containerImageDigest,
workerVersionId: receipt.workerVersionId,
+ qualifiedRunnerReceipt: qualifiedRef,
+ releaseCheckReceipt: releaseCheckRef,
deploymentReceipt: receiptRef,
productionSmoke: smokeRef,
},
@@ -289,7 +470,7 @@ describe("submission package validator", () => {
).rejects.toThrow(/failed \d+ gate/iu);
});
- it("accepts a complete, tuple-consistent ready package", async () => {
+ it("accepts a tuple-consistent release fixture", async () => {
const { ready, reader } = await readyPackage();
await expect(
@@ -299,6 +480,218 @@ describe("submission package validator", () => {
).resolves.toMatchObject({ submissionState: "READY_TO_SUBMIT" });
});
+ it("rejects a qualification older than 24 hours at deployment", async () => {
+ const { ready, reader } = await readyPackage({
+ qualificationDay: "2026-07-18",
+ });
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "qualified runner evidence is stale at deployment",
+ ]),
+ });
+ });
+
+ it("rejects individually valid receipts with a broken qualified hash edge", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const releaseCheckBytes = evidence.get(
+ ready.release.releaseCheckReceipt!.path,
+ );
+ if (releaseCheckBytes === undefined) {
+ throw new Error("missing release-check fixture");
+ }
+ const releaseCheck = JSON.parse(
+ new TextDecoder().decode(releaseCheckBytes),
+ ) as { qualifiedRunnerReceiptSha256: string };
+ releaseCheck.qualifiedRunnerReceiptSha256 = "f".repeat(64);
+ const changedReleaseCheck = bytes(releaseCheck);
+ const changedReleaseCheckPath = "evidence/drifted-release-check.json";
+ evidence.set(changedReleaseCheckPath, changedReleaseCheck);
+ ready.release.releaseCheckReceipt = reference(
+ changedReleaseCheckPath,
+ changedReleaseCheck,
+ );
+
+ const deploymentBytes = evidence.get(ready.release.deploymentReceipt!.path);
+ if (deploymentBytes === undefined) {
+ throw new Error("missing deployment fixture");
+ }
+ const deployment = JSON.parse(
+ new TextDecoder().decode(deploymentBytes),
+ ) as { releaseCheckReceiptSha256: string };
+ deployment.releaseCheckReceiptSha256 =
+ ready.release.releaseCheckReceipt.sha256;
+ const changedDeployment = bytes(deployment);
+ const changedDeploymentPath = "evidence/drifted-deployment.json";
+ evidence.set(changedDeploymentPath, changedDeployment);
+ ready.release.deploymentReceipt = reference(
+ changedDeploymentPath,
+ changedDeployment,
+ );
+
+ const smokeBytes = evidence.get(ready.release.productionSmoke!.path);
+ if (smokeBytes === undefined) throw new Error("missing smoke fixture");
+ const smoke = JSON.parse(new TextDecoder().decode(smokeBytes)) as {
+ deployment: { deploymentReceiptSha256: string };
+ };
+ smoke.deployment.deploymentReceiptSha256 =
+ ready.release.deploymentReceipt.sha256;
+ const changedSmoke = bytes(smoke);
+ const changedSmokePath = "evidence/drifted-smoke.json";
+ evidence.set(changedSmokePath, changedSmoke);
+ ready.release.productionSmoke = reference(changedSmokePath, changedSmoke);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "release chain qualified receipt hash in release check does not match",
+ ]),
+ });
+ });
+
+ it("rejects deployment timeout evidence that drifts from qualification", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const deploymentBytes = evidence.get(ready.release.deploymentReceipt!.path);
+ if (deploymentBytes === undefined) {
+ throw new Error("missing deployment fixture");
+ }
+ const deployment = JSON.parse(
+ new TextDecoder().decode(deploymentBytes),
+ ) as { timeoutCleanupReceiptSha256: string };
+ deployment.timeoutCleanupReceiptSha256 = "0".repeat(64);
+ const changedDeployment = bytes(deployment);
+ const changedDeploymentPath = "evidence/timeout-drift-deployment.json";
+ evidence.set(changedDeploymentPath, changedDeployment);
+ ready.release.deploymentReceipt = reference(
+ changedDeploymentPath,
+ changedDeployment,
+ );
+
+ const smokeBytes = evidence.get(ready.release.productionSmoke!.path);
+ if (smokeBytes === undefined) throw new Error("missing smoke fixture");
+ const smoke = JSON.parse(new TextDecoder().decode(smokeBytes)) as {
+ deployment: {
+ deploymentReceiptSha256: string;
+ timeoutCleanupReceiptSha256: string;
+ };
+ };
+ smoke.deployment.deploymentReceiptSha256 =
+ ready.release.deploymentReceipt.sha256;
+ smoke.deployment.timeoutCleanupReceiptSha256 =
+ deployment.timeoutCleanupReceiptSha256;
+ const changedSmoke = bytes(smoke);
+ const changedSmokePath = "evidence/timeout-drift-smoke.json";
+ evidence.set(changedSmokePath, changedSmoke);
+ ready.release.productionSmoke = reference(changedSmokePath, changedSmoke);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "release chain deployed timeout-cleanup receipt does not match",
+ ]),
+ });
+ });
+
+ it("rejects fresh isolation evidence for a different source tree", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const releaseCheckBytes = evidence.get(
+ ready.release.releaseCheckReceipt!.path,
+ );
+ if (releaseCheckBytes === undefined) {
+ throw new Error("missing release-check fixture");
+ }
+ const releaseCheck = JSON.parse(
+ new TextDecoder().decode(releaseCheckBytes),
+ ) as {
+ sourceCommit: string;
+ runnerImageTag: string;
+ runnerImageDigest: string;
+ releaseCheckGenerationIsolationEvidence: unknown;
+ releaseCheckGenerationIsolationEvidenceSha256: string;
+ releaseCheckGenerationIsolationProbeSha256: string;
+ releaseCheckGenerationIsolationVerifiedAt: string;
+ };
+ const wrongTreeIsolation = isolationEvidence({
+ sourceCommit: releaseCheck.sourceCommit,
+ sourceTreeSha256: "7".repeat(64),
+ localImageTag: releaseCheck.runnerImageTag,
+ localImageDigest: releaseCheck.runnerImageDigest,
+ verifiedAt: releaseCheck.releaseCheckGenerationIsolationVerifiedAt,
+ });
+ releaseCheck.releaseCheckGenerationIsolationEvidence =
+ wrongTreeIsolation.evidence;
+ releaseCheck.releaseCheckGenerationIsolationEvidenceSha256 =
+ wrongTreeIsolation.evidenceSha256;
+ releaseCheck.releaseCheckGenerationIsolationProbeSha256 =
+ wrongTreeIsolation.probeSha256;
+ const changedReleaseCheck = bytes(releaseCheck);
+ const changedReleaseCheckPath = "evidence/wrong-tree-release-check.json";
+ evidence.set(changedReleaseCheckPath, changedReleaseCheck);
+ ready.release.releaseCheckReceipt = reference(
+ changedReleaseCheckPath,
+ changedReleaseCheck,
+ );
+
+ const deploymentBytes = evidence.get(ready.release.deploymentReceipt!.path);
+ if (deploymentBytes === undefined) {
+ throw new Error("missing deployment fixture");
+ }
+ const deployment = JSON.parse(
+ new TextDecoder().decode(deploymentBytes),
+ ) as {
+ releaseCheckReceiptSha256: string;
+ releaseCheckGenerationIsolationEvidenceSha256: string;
+ };
+ deployment.releaseCheckReceiptSha256 =
+ ready.release.releaseCheckReceipt.sha256;
+ deployment.releaseCheckGenerationIsolationEvidenceSha256 =
+ wrongTreeIsolation.evidenceSha256;
+ const changedDeployment = bytes(deployment);
+ const changedDeploymentPath = "evidence/wrong-tree-deployment.json";
+ evidence.set(changedDeploymentPath, changedDeployment);
+ ready.release.deploymentReceipt = reference(
+ changedDeploymentPath,
+ changedDeployment,
+ );
+
+ const smokeBytes = evidence.get(ready.release.productionSmoke!.path);
+ if (smokeBytes === undefined) throw new Error("missing smoke fixture");
+ const smoke = JSON.parse(new TextDecoder().decode(smokeBytes)) as {
+ deployment: {
+ deploymentReceiptSha256: string;
+ releaseCheckGenerationIsolationEvidenceSha256: string;
+ };
+ };
+ smoke.deployment.deploymentReceiptSha256 =
+ ready.release.deploymentReceipt.sha256;
+ smoke.deployment.releaseCheckGenerationIsolationEvidenceSha256 =
+ wrongTreeIsolation.evidenceSha256;
+ const changedSmoke = bytes(smoke);
+ const changedSmokePath = "evidence/wrong-tree-smoke.json";
+ evidence.set(changedSmokePath, changedSmoke);
+ ready.release.productionSmoke = reference(changedSmokePath, changedSmoke);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "release chain release-check isolation source tree does not match",
+ ]),
+ });
+ });
+
it("rejects the former minimal synthetic production smoke", async () => {
const { ready, reader, evidence } = await readyPackage();
const minimal = bytes({
@@ -323,7 +716,7 @@ describe("submission package validator", () => {
}),
).rejects.toMatchObject({
issues: expect.arrayContaining([
- expect.stringMatching(/schema-v6 smoke/iu),
+ expect.stringMatching(/valid qualified.*smoke chain/iu),
]),
});
});
@@ -347,7 +740,33 @@ describe("submission package validator", () => {
}),
).rejects.toMatchObject({
issues: expect.arrayContaining([
- expect.stringMatching(/schema-v6 smoke/iu),
+ expect.stringMatching(/valid qualified.*smoke chain/iu),
+ ]),
+ });
+ });
+
+ it("rejects prefixed serialized raw-notebook content in smoke evidence", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const current = evidence.get(ready.release.productionSmoke!.path);
+ if (current === undefined) throw new Error("missing strict smoke fixture");
+ const smoke = JSON.parse(new TextDecoder().decode(current)) as {
+ stages: Array<{ evidence: Record }>;
+ };
+ smoke.stages[0]!.evidence = {
+ note: 'captured: {"nbformat":4,"cells":[]}',
+ };
+ const privateSmoke = bytes(smoke);
+ const path = "evidence/raw-notebook-production-smoke.json";
+ evidence.set(path, privateSmoke);
+ ready.release.productionSmoke = reference(path, privateSmoke);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ expect.stringMatching(/valid qualified.*smoke chain/iu),
]),
});
});
diff --git a/scripts/validate-submission-package.ts b/scripts/validate-submission-package.ts
index 5096e2cd..cb67d707 100644
--- a/scripts/validate-submission-package.ts
+++ b/scripts/validate-submission-package.ts
@@ -6,6 +6,8 @@ import { z } from "zod";
import { PilotAnalysisSchema } from "../evals/learner-pilot/src/pilot.js";
import { DeploymentReceiptV7Schema } from "../packages/scientific-engine-registry/src/schema.js";
+import { parseQualifiedRunnerReleaseV6 } from "./generation-isolation-evidence.js";
+import { parseReleaseCheckReceiptV5 } from "./release-check-receipt.js";
import { containedInputFile } from "./repository-cli-paths.js";
const Sha256Schema = z.string().regex(/^[a-f0-9]{64}$/u);
@@ -47,6 +49,8 @@ const ReleaseSchema = z
runnerSourceCommit: GitCommitSchema.nullable(),
containerImageDigest: ImageDigestSchema.nullable(),
workerVersionId: WorkerVersionSchema.nullable(),
+ qualifiedRunnerReceipt: NullableEvidence,
+ releaseCheckReceipt: NullableEvidence,
deploymentReceipt: NullableEvidence,
productionSmoke: NullableEvidence,
})
@@ -417,6 +421,12 @@ function assertPrivacySafeEvidence(value: unknown, label: string): void {
}
return;
}
+ if (
+ typeof current === "string" &&
+ /"nbformat"\s*:\s*4.+"cells"\s*:/su.test(current)
+ ) {
+ throw new Error(`${label} contains raw notebook content`);
+ }
if (typeof current === "string" && /^[\s]*[\[{]/u.test(current)) {
try {
inspect(JSON.parse(current));
@@ -432,7 +442,8 @@ function assertPrivacySafeEvidence(value: unknown, label: string): void {
/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/u.test(serialized) ||
/\bsk-[A-Za-z0-9_-]{24,}\b/u.test(serialized) ||
/CODEX_AUTH_JSON\s*[:=]/u.test(serialized) ||
- /COUNTERLAB_RUNNER_SIGNING_(?:PRIVATE_)?KEY\s*[:=]/u.test(serialized)
+ /COUNTERLAB_RUNNER_SIGNING_(?:PRIVATE_)?KEY\s*[:=]/u.test(serialized) ||
+ /"nbformat"\s*:\s*4.+"cells"\s*:/su.test(serialized)
) {
throw new Error(`${label} contains secret or private content`);
}
@@ -456,6 +467,8 @@ function collectEvidenceReferences(
submission: SubmissionPackage,
): readonly z.infer[] {
const optional = [
+ submission.release.qualifiedRunnerReceipt,
+ submission.release.releaseCheckReceipt,
submission.release.deploymentReceipt,
submission.release.productionSmoke,
submission.repository.accessEvidence,
@@ -511,6 +524,11 @@ function requireReadyFields(
["release.runnerSourceCommit", submission.release.runnerSourceCommit],
["release.containerImageDigest", submission.release.containerImageDigest],
["release.workerVersionId", submission.release.workerVersionId],
+ [
+ "release.qualifiedRunnerReceipt",
+ submission.release.qualifiedRunnerReceipt,
+ ],
+ ["release.releaseCheckReceipt", submission.release.releaseCheckReceipt],
["release.deploymentReceipt", submission.release.deploymentReceipt],
["release.productionSmoke", submission.release.productionSmoke],
["repository.accessEvidence", submission.repository.accessEvidence],
@@ -600,10 +618,27 @@ async function assertReleaseEvidence(
issues: string[],
now: Date,
): Promise {
+ const qualifiedRef = submission.release.qualifiedRunnerReceipt;
+ const releaseCheckRef = submission.release.releaseCheckReceipt;
const receiptRef = submission.release.deploymentReceipt;
const smokeRef = submission.release.productionSmoke;
- if (receiptRef === null || smokeRef === null) return null;
+ if (
+ qualifiedRef === null ||
+ releaseCheckRef === null ||
+ receiptRef === null ||
+ smokeRef === null
+ ) {
+ return null;
+ }
try {
+ const qualifiedValue = parseJson(
+ cachedEvidence(evidence, qualifiedRef, "qualified runner receipt"),
+ "qualified runner receipt",
+ );
+ const releaseCheckValue = parseJson(
+ cachedEvidence(evidence, releaseCheckRef, "release-check receipt"),
+ "release-check receipt",
+ );
const receiptValue = parseJson(
cachedEvidence(evidence, receiptRef, "deployment receipt"),
"deployment receipt",
@@ -612,11 +647,218 @@ async function assertReleaseEvidence(
cachedEvidence(evidence, smokeRef, "production smoke"),
"production smoke",
);
+ assertPrivacySafeEvidence(qualifiedValue, "qualified runner receipt");
+ assertPrivacySafeEvidence(releaseCheckValue, "release-check receipt");
assertPrivacySafeEvidence(receiptValue, "deployment receipt");
assertPrivacySafeEvidence(smokeValue, "production smoke");
+ const qualified = parseQualifiedRunnerReleaseV6(qualifiedValue);
+ const releaseCheck = parseReleaseCheckReceiptV5(releaseCheckValue);
const receipt = DeploymentReceiptV7Schema.parse(receiptValue);
const smoke = ProductionSmokeV6Schema.parse(smokeValue);
const expectedOrigin = new URL(submission.publicProduct.judgeUrl).origin;
+ const expectedContainerImage = `${qualified.registryImage.replace(
+ /:git-[a-f0-9]{40}$/u,
+ "",
+ )}@${qualified.registryDigest}`;
+ const releaseBindings: ReadonlyArray =
+ [
+ [
+ "qualified evidence commit",
+ qualified.evidenceCommit,
+ submission.release.workerEvidenceCommit,
+ ],
+ [
+ "qualified source commit",
+ qualified.sourceCommit,
+ submission.release.runnerSourceCommit,
+ ],
+ [
+ "qualified registry digest",
+ qualified.registryDigest,
+ submission.release.containerImageDigest,
+ ],
+ [
+ "release-check evidence commit",
+ releaseCheck.evidenceCommit,
+ qualified.evidenceCommit,
+ ],
+ [
+ "release-check source commit",
+ releaseCheck.sourceCommit,
+ qualified.sourceCommit,
+ ],
+ [
+ "qualified receipt hash in release check",
+ releaseCheck.qualifiedRunnerReceiptSha256,
+ qualifiedRef.sha256,
+ ],
+ [
+ "qualified receipt hash in deployment",
+ receipt.qualifiedRunnerReceiptSha256,
+ qualifiedRef.sha256,
+ ],
+ [
+ "release-check receipt hash in deployment",
+ receipt.releaseCheckReceiptSha256,
+ releaseCheckRef.sha256,
+ ],
+ ["qualification time", releaseCheck.qualifiedAt, qualified.qualifiedAt],
+ [
+ "release-check time",
+ receipt.releaseCheckCheckedAt,
+ releaseCheck.checkedAt,
+ ],
+ [
+ "qualified local runner tag",
+ releaseCheck.runnerImageTag,
+ qualified.localImageTag,
+ ],
+ [
+ "qualified local runner digest",
+ releaseCheck.runnerImageDigest,
+ qualified.localImageDigest,
+ ],
+ [
+ "qualified adapter tag",
+ releaseCheck.adapterImageTag,
+ qualified.adapterImageTag,
+ ],
+ [
+ "qualified adapter digest",
+ releaseCheck.adapterImageDigest,
+ qualified.adapterImageDigest,
+ ],
+ [
+ "release-check registry digest",
+ releaseCheck.registryDigest,
+ qualified.registryDigest,
+ ],
+ [
+ "deployed registry digest",
+ receipt.containerImageDigest,
+ qualified.registryDigest,
+ ],
+ [
+ "deployed image reference",
+ receipt.containerImage,
+ expectedContainerImage,
+ ],
+ [
+ "runtime toolchain",
+ releaseCheck.runtimeToolchainSha256,
+ qualified.runtimeToolchainSha256,
+ ],
+ [
+ "deployed runtime toolchain",
+ receipt.runtimeToolchainSha256,
+ qualified.runtimeToolchainSha256,
+ ],
+ [
+ "runtime policy",
+ releaseCheck.runtimePolicySha256,
+ qualified.runtimePolicySha256,
+ ],
+ [
+ "deployed runtime policy",
+ receipt.runtimePolicySha256,
+ qualified.runtimePolicySha256,
+ ],
+ [
+ "proof dependency manifest",
+ releaseCheck.proofDependencyManifestSha256,
+ qualified.proofDependencyManifestSha256,
+ ],
+ [
+ "deployed proof dependency manifest",
+ receipt.proofDependencyManifestSha256,
+ qualified.proofDependencyManifestSha256,
+ ],
+ [
+ "aggregate-limit evidence",
+ releaseCheck.aggregateLimitEvidenceSha256,
+ qualified.aggregateLimitEvidenceSha256,
+ ],
+ [
+ "deployed aggregate-limit evidence",
+ receipt.aggregateLimitEvidenceSha256,
+ qualified.aggregateLimitEvidenceSha256,
+ ],
+ [
+ "deployed timeout-cleanup receipt",
+ receipt.timeoutCleanupReceiptSha256,
+ qualified.timeoutCleanupReceiptSha256,
+ ],
+ [
+ "runtime adapter",
+ releaseCheck.runtimeAdapterSha256,
+ qualified.runtimeAdapterSha256,
+ ],
+ [
+ "deployed runtime adapter",
+ receipt.runtimeAdapterSha256,
+ qualified.runtimeAdapterSha256,
+ ],
+ [
+ "deployed adapter digest",
+ receipt.adapterImageDigest,
+ qualified.adapterImageDigest,
+ ],
+ [
+ "qualified isolation evidence",
+ releaseCheck.generationIsolationEvidenceSha256,
+ qualified.generationIsolationEvidenceSha256,
+ ],
+ [
+ "qualified isolation probe",
+ releaseCheck.generationIsolationProbeSha256,
+ qualified.generationIsolationProbeSha256,
+ ],
+ [
+ "qualified isolation time",
+ releaseCheck.generationIsolationVerifiedAt,
+ qualified.generationIsolationVerifiedAt,
+ ],
+ [
+ "deployed qualified isolation evidence",
+ receipt.generationIsolationEvidenceSha256,
+ qualified.generationIsolationEvidenceSha256,
+ ],
+ [
+ "deployed qualified isolation probe",
+ receipt.generationIsolationProbeSha256,
+ qualified.generationIsolationProbeSha256,
+ ],
+ [
+ "deployed qualified isolation time",
+ receipt.generationIsolationVerifiedAt,
+ qualified.generationIsolationVerifiedAt,
+ ],
+ [
+ "deployed release-check isolation evidence",
+ receipt.releaseCheckGenerationIsolationEvidenceSha256,
+ releaseCheck.releaseCheckGenerationIsolationEvidenceSha256,
+ ],
+ [
+ "release-check isolation source tree",
+ releaseCheck.releaseCheckGenerationIsolationEvidence.sourceTreeSha256,
+ qualified.sourceTreeSha256,
+ ],
+ [
+ "deployed release-check isolation probe",
+ receipt.releaseCheckGenerationIsolationProbeSha256,
+ releaseCheck.releaseCheckGenerationIsolationProbeSha256,
+ ],
+ [
+ "deployed release-check isolation time",
+ receipt.releaseCheckGenerationIsolationVerifiedAt,
+ releaseCheck.releaseCheckGenerationIsolationVerifiedAt,
+ ],
+ ];
+ for (const [label, observed, expected] of releaseBindings) {
+ if (observed !== expected) {
+ issues.push(`release chain ${label} does not match`);
+ }
+ }
if (
receipt.productionOrigin !== expectedOrigin ||
receipt.workerEvidenceCommit !==
@@ -746,6 +988,12 @@ async function assertReleaseEvidence(
if (Date.parse(receipt.deployedAt) > Date.parse(smoke.startedAt)) {
issues.push("production smoke started before the recorded deployment");
}
+ if (
+ Date.parse(receipt.deployedAt) - Date.parse(qualified.qualifiedAt) >
+ 86_400_000
+ ) {
+ issues.push("qualified runner evidence is stale at deployment");
+ }
const smokeCompletedAt = Date.parse(smoke.completedAt);
if (
Date.parse(receipt.deployedAt) > now.getTime() ||
@@ -762,10 +1010,10 @@ async function assertReleaseEvidence(
) {
issues.push("production smoke completed after the official submission");
}
- return receipt.qualifiedRunnerReceiptSha256;
+ return qualifiedRef.sha256;
} catch {
issues.push(
- "release evidence is not a valid schema-v7 receipt and schema-v6 smoke",
+ "release evidence is not a valid qualified, checked, deployed, and smoke chain",
);
return null;
}
From 8e33154b2b130fbdb635cf7a91ed75dc9357741f Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 12:53:47 +0530
Subject: [PATCH 083/143] fix(submission): validate publication evidence
---
docs/SUBMISSION_PACKAGE.json | 15 +
scripts/submission-publication-evidence.ts | 618 ++++++++++++
scripts/validate-submission-package.test.ts | 963 ++++++++++++++++---
scripts/validate-submission-package.ts | 991 +++++++++++++++++++-
4 files changed, 2448 insertions(+), 139 deletions(-)
create mode 100644 scripts/submission-publication-evidence.ts
diff --git a/docs/SUBMISSION_PACKAGE.json b/docs/SUBMISSION_PACKAGE.json
index 71f88538..380e2cc2 100644
--- a/docs/SUBMISSION_PACKAGE.json
+++ b/docs/SUBMISSION_PACKAGE.json
@@ -82,6 +82,21 @@
"failures": 0,
"consoleErrors": 0,
"failedRequests": 0,
+ "playwrightReport": null,
+ "evidenceIndex": null,
+ "journeyEvidence": [],
+ "manualEvidence": {
+ "keyboard": null,
+ "screenReaderNames": null,
+ "reducedMotion": null,
+ "noHorizontalOverflow": null,
+ "zoom200": null,
+ "longContent": null,
+ "narrowVisualizations": null,
+ "touchTargets": null,
+ "consoleAndNetwork": null,
+ "webVitals": null
+ },
"evidence": null
},
"assets": [],
diff --git a/scripts/submission-publication-evidence.ts b/scripts/submission-publication-evidence.ts
new file mode 100644
index 00000000..46ab0bd4
--- /dev/null
+++ b/scripts/submission-publication-evidence.ts
@@ -0,0 +1,618 @@
+import { inflateSync } from "node:zlib";
+
+import { z } from "zod";
+
+const Sha256Schema = z.string().regex(/^[a-f0-9]{64}$/u);
+const GitCommitSchema = z.string().regex(/^[a-f0-9]{40}$/u);
+const ImageDigestSchema = z.string().regex(/^sha256:[a-f0-9]{64}$/u);
+const WorkerVersionSchema = z
+ .string()
+ .regex(/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/u);
+
+export const PublicationReleaseBindingSchema = z
+ .object({
+ deploymentReceiptSha256: Sha256Schema,
+ productionOrigin: z.literal("https://counterlab.cserules.workers.dev"),
+ workerEvidenceCommit: GitCommitSchema,
+ runnerSourceCommit: GitCommitSchema,
+ containerImageDigest: ImageDigestSchema,
+ workerVersionId: WorkerVersionSchema,
+ })
+ .strict();
+
+const PublicationPrivacySchema = z
+ .object({
+ containsSecrets: z.literal(false),
+ containsRawNotebook: z.literal(false),
+ containsPersonalData: z.literal(false),
+ })
+ .strict();
+
+const baseReceiptShape = {
+ schemaVersion: z.literal("1"),
+ status: z.literal("VERIFIED"),
+ checkedAt: z.iso.datetime({ offset: true }),
+ release: PublicationReleaseBindingSchema,
+ privacy: PublicationPrivacySchema,
+} as const;
+
+export const RepositoryAccessReceiptSchema = z
+ .object({
+ ...baseReceiptShape,
+ kind: z.literal("repository-access"),
+ repositoryUrl: z.url({ protocol: /^https$/u }),
+ accessMode: z.enum(["PUBLIC", "PRIVATE_SHARED"]),
+ loggedOutChecked: z.literal(true),
+ loggedOutStatusCode: z.union([
+ z.literal(200),
+ z.literal(401),
+ z.literal(403),
+ z.literal(404),
+ ]),
+ authenticatedJudgeStatusCode: z.literal(200),
+ finalUrl: z.url({ protocol: /^https$/u }),
+ anonymousAccessible: z.boolean(),
+ judgeAccessVerified: z.boolean(),
+ sharedJudgeAddresses: z.array(z.email()).max(2),
+ })
+ .strict();
+
+export const VideoVerificationReceiptSchema = z
+ .object({
+ ...baseReceiptShape,
+ kind: z.literal("video-verification"),
+ publicUrl: z.url({ protocol: /^https$/u }),
+ durationSeconds: z.number().positive().max(180),
+ audioPresent: z.literal(true),
+ codexRoleCovered: z.literal(true),
+ gpt56RoleCovered: z.literal(true),
+ loggedOutPlayable: z.literal(true),
+ statusCode: z.literal(200),
+ scriptSha256: Sha256Schema,
+ captionsSha256: Sha256Schema,
+ })
+ .strict();
+
+export const FeedbackSessionReceiptSchema = z
+ .object({
+ ...baseReceiptShape,
+ kind: z.literal("feedback-session"),
+ sessionId: z.uuid(),
+ source: z.literal("codex-feedback"),
+ verifiedByOwner: z.literal(true),
+ contentCaptured: z.literal(false),
+ })
+ .strict();
+
+export const CloakJourneySchema = z
+ .object({
+ id: z.string().trim().min(1).max(512),
+ status: z.literal("PASSED"),
+ attempt: z.literal(0),
+ durationMs: z.number().int().positive(),
+ viewport: z.enum([
+ "375x812",
+ "390x844",
+ "768x1024",
+ "1280x720",
+ "1366x768",
+ "1440x900",
+ "1920x1080",
+ ]),
+ evidenceSha256: Sha256Schema,
+ })
+ .strict();
+
+export const CloakJourneyEvidenceReceiptSchema = z
+ .object({
+ ...baseReceiptShape,
+ kind: z.literal("cloakbrowser-journey-evidence"),
+ authority: z.literal("CLOAKBROWSER"),
+ id: z.string().trim().min(1).max(512),
+ viewport: z.enum([
+ "375x812",
+ "390x844",
+ "768x1024",
+ "1280x720",
+ "1366x768",
+ "1440x900",
+ "1920x1080",
+ ]),
+ journeyStatus: z.literal("PASSED"),
+ attempt: z.literal(0),
+ durationMs: z.number().int().positive(),
+ assertionCount: z.number().int().positive(),
+ consoleErrors: z.literal(0),
+ failedRequests: z.literal(0),
+ })
+ .strict();
+
+export const REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS = [
+ "keyboard",
+ "screenReaderNames",
+ "reducedMotion",
+ "noHorizontalOverflow",
+ "zoom200",
+ "longContent",
+ "narrowVisualizations",
+ "touchTargets",
+ "consoleAndNetwork",
+ "webVitals",
+] as const;
+
+export const CloakManualEvidenceReceiptSchema = z
+ .object({
+ ...baseReceiptShape,
+ kind: z.literal("cloakbrowser-manual-evidence"),
+ authority: z.literal("CLOAKBROWSER"),
+ check: z.enum(REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS),
+ observationCount: z.number().int().positive(),
+ artifactSha256: Sha256Schema,
+ })
+ .strict();
+
+const REQUIRED_CLOAK_VIEWPORTS = [
+ "375x812",
+ "390x844",
+ "768x1024",
+ "1280x720",
+ "1366x768",
+ "1440x900",
+ "1920x1080",
+] as const;
+
+export const REQUIRED_CLOAK_JOURNEY_IDS = [
+ "judged-flow::production release transport::Loaded public release routes and assets retain security headers and contain no secrets",
+ "judged-flow::desktop Landing keeps the unprimed Question and fair-test promise in the first viewport",
+ "judged-flow::desktop Judge Mode shows the honest fixed-sample belief break in the first viewport",
+ "judged-flow::desktop sample remains unprimed before Prediction and mounts the trusted mechanism only after sealing",
+ "judged-flow::mobile Landing keeps the unprimed Question and fair-test promise in the first viewport",
+ "judged-flow::mobile Judge Mode shows the honest fixed-sample belief break in the first viewport",
+ "judged-flow::mobile sample remains unprimed before Prediction and mounts the trusted mechanism only after sealing",
+ "judged-flow::Judge Mode distinguishes every authority path",
+ "judged-flow::the first visit explains the lesson before asking for technical knowledge",
+ "judged-flow::wide desktop keeps the question and canonical progress accessible",
+ "judged-flow::compact desktop keeps the question and canonical progress accessible",
+ "judged-flow::mobile keeps the question and canonical progress accessible",
+ "judged-flow::Try Instantly persists the verified learning loop and exports a valid proof",
+ "judged-flow::the lesson keeps one learner decision in focus at a time",
+ "judged-flow::prediction is immutable and results do not exist before commitment",
+ "judged-flow::the fixed sample keeps approved framing and can return home",
+ "judged-flow::refresh restores the question and confirmed Prediction phases",
+ "judged-flow::refresh restores the current lesson and the committed prediction",
+ "judged-flow::a rejected test releases no result and remains recoverable after refresh",
+ "judged-flow::local hints and Theater views never request a model or new result",
+ "judged-flow::completed lesson steps open as read-only pages",
+ "judged-flow::failed transfer keeps the patch locked and a corrected answer unlocks it",
+ "judged-flow::Replay remains visibly labelled and read-only after refresh",
+ "judged-flow::missing live capabilities are stated without claiming a model call",
+ "judged-flow::configured reasoning cannot start without a qualified hosted runner",
+ "judged-flow::partial generation isolation cannot expose live notebook upload",
+ "judged-flow::unsupported notebooks are parsed without execution and cannot advance",
+ "judged-flow::the judged path is keyboard operable with reduced motion",
+ "judged-flow::a configured hosted runner completes an untouched leakage notebook",
+ "judged-flow::a configured hosted runner completes an untouched class-imbalance notebook",
+ "mobile-public-replay::public mobile replay routing::390x844 opens the replay deep link read-only without root overflow",
+ "mobile-public-replay::public mobile replay routing::375x812 preserves replay authority through browser back and forward",
+ "recovery-and-intake::Reject preserves the claim and resubmits only in a fresh session",
+ "recovery-and-intake::Not enough evidence preserves the claim and resubmits only in a fresh session",
+ "recovery-and-intake::live intake cannot continue without an uploaded supported artifact",
+ "recovery-and-intake::malformed live upload remains at intake and creates no session",
+ "recovery-and-intake::unsupported live upload is refused without creating a session",
+ "recovery-and-intake::a supported live upload creates one source-bound session",
+ "recovery-and-intake::an interrupted upload accepts the same file on retry",
+ "recovery-and-intake::a lost private-session response retries without re-uploading",
+] as const;
+
+function addCloakJourneyIssues(
+ journeys: readonly z.infer[],
+ context: z.RefinementCtx,
+): void {
+ const identifiers = journeys.map((journey) => journey.id);
+ if (new Set(identifiers).size !== identifiers.length) {
+ context.addIssue({
+ code: "custom",
+ path: ["journeys"],
+ message: "journey identifiers must be unique",
+ });
+ }
+ if (
+ JSON.stringify([...identifiers].sort()) !==
+ JSON.stringify([...REQUIRED_CLOAK_JOURNEY_IDS].sort())
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["journeys"],
+ message: "journey identifiers do not match the required registry",
+ });
+ }
+ const observedViewports = new Set(
+ journeys.map((journey) => journey.viewport),
+ );
+ if (
+ REQUIRED_CLOAK_VIEWPORTS.some(
+ (viewport) => !observedViewports.has(viewport),
+ )
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["journeys"],
+ message: "journey evidence does not cover every required viewport",
+ });
+ }
+ const evidenceHashes = journeys.map((journey) => journey.evidenceSha256);
+ if (new Set(evidenceHashes).size !== evidenceHashes.length) {
+ context.addIssue({
+ code: "custom",
+ path: ["journeys"],
+ message: "journey evidence hashes must be unique",
+ });
+ }
+}
+
+export const CloakBrowserExecutionReportSchema = z
+ .object({
+ schemaVersion: z.literal("1"),
+ kind: z.literal("cloakbrowser-execution-report"),
+ status: z.literal("PASSED"),
+ checkedAt: z.iso.datetime({ offset: true }),
+ authority: z.literal("CLOAKBROWSER"),
+ baseUrl: z.literal("https://counterlab.cserules.workers.dev"),
+ release: PublicationReleaseBindingSchema,
+ privacy: PublicationPrivacySchema,
+ browserVersion: z.string().trim().min(1).max(128),
+ playwrightVersion: z.literal("1.61.1"),
+ journeys: z.array(CloakJourneySchema).length(40),
+ failures: z.literal(0),
+ skips: z.literal(0),
+ retries: z.literal(0),
+ consoleErrors: z.literal(0),
+ failedRequests: z.literal(0),
+ })
+ .strict()
+ .superRefine((report, context) => {
+ addCloakJourneyIssues(report.journeys, context);
+ });
+
+const ManualEvidenceSchema = z
+ .object({
+ status: z.literal("PASSED"),
+ evidenceSha256: Sha256Schema,
+ })
+ .strict();
+
+export const CloakBrowserEvidenceIndexSchema = z
+ .object({
+ schemaVersion: z.literal("1"),
+ kind: z.literal("cloakbrowser-evidence-index"),
+ status: z.literal("VERIFIED"),
+ checkedAt: z.iso.datetime({ offset: true }),
+ release: PublicationReleaseBindingSchema,
+ privacy: PublicationPrivacySchema,
+ executionReportSha256: Sha256Schema,
+ manual: z
+ .object({
+ keyboard: ManualEvidenceSchema,
+ screenReaderNames: ManualEvidenceSchema,
+ reducedMotion: ManualEvidenceSchema,
+ noHorizontalOverflow: ManualEvidenceSchema,
+ zoom200: ManualEvidenceSchema,
+ longContent: ManualEvidenceSchema,
+ narrowVisualizations: ManualEvidenceSchema,
+ touchTargets: ManualEvidenceSchema,
+ consoleAndNetwork: ManualEvidenceSchema,
+ webVitals: ManualEvidenceSchema,
+ })
+ .strict(),
+ })
+ .strict();
+
+export const CloakBrowserQualificationReceiptSchema = z
+ .object({
+ ...baseReceiptShape,
+ kind: z.literal("cloakbrowser-qualification"),
+ authority: z.literal("CLOAKBROWSER"),
+ baseUrl: z.literal("https://counterlab.cserules.workers.dev"),
+ exactReleaseBound: z.literal(true),
+ journeyCount: z.number().int().min(40),
+ viewports: z.tuple([
+ z.literal("375x812"),
+ z.literal("390x844"),
+ z.literal("768x1024"),
+ z.literal("1280x720"),
+ z.literal("1366x768"),
+ z.literal("1440x900"),
+ z.literal("1920x1080"),
+ ]),
+ browserVersion: z.string().trim().min(1).max(128),
+ playwrightVersion: z.literal("1.61.1"),
+ playwrightReportSha256: Sha256Schema,
+ browserEvidenceIndexSha256: Sha256Schema,
+ journeys: z.array(CloakJourneySchema).length(40),
+ desktopComplete: z.literal(true),
+ mobileComplete: z.literal(true),
+ keyboardComplete: z.literal(true),
+ screenReaderNamesComplete: z.literal(true),
+ reducedMotionComplete: z.literal(true),
+ noHorizontalOverflow: z.literal(true),
+ zoom200Complete: z.literal(true),
+ longContentComplete: z.literal(true),
+ narrowVisualizationsComplete: z.literal(true),
+ touchTargetsComplete: z.literal(true),
+ requiredSkips: z.literal(0),
+ failures: z.literal(0),
+ consoleErrors: z.literal(0),
+ failedRequests: z.literal(0),
+ webVitals: z
+ .object({
+ lcpMs: z.number().nonnegative().max(2_500),
+ cls: z.number().nonnegative().max(0.1),
+ inpMs: z.number().nonnegative().max(200),
+ })
+ .strict(),
+ })
+ .strict()
+ .superRefine((receipt, context) => {
+ if (receipt.journeyCount !== receipt.journeys.length) {
+ context.addIssue({
+ code: "custom",
+ path: ["journeyCount"],
+ message: "journey count does not match journey evidence",
+ });
+ }
+ addCloakJourneyIssues(receipt.journeys, context);
+ });
+
+const PublicLinkResultSchema = z
+ .object({
+ role: z.enum(["judge", "repository", "video", "devpost"]),
+ url: z.url({ protocol: /^https$/u }),
+ statusCode: z.literal(200),
+ finalUrl: z.url({ protocol: /^https$/u }),
+ })
+ .strict();
+
+export const PublicLinkAuditReceiptSchema = z
+ .object({
+ ...baseReceiptShape,
+ kind: z.literal("public-link-audit"),
+ loggedOut: z.literal(true),
+ links: z.array(PublicLinkResultSchema).length(3),
+ })
+ .strict();
+
+export const PostSubmitLinkAuditReceiptSchema = z
+ .object({
+ ...baseReceiptShape,
+ kind: z.literal("post-submit-link-audit"),
+ loggedOut: z.literal(true),
+ links: z.array(PublicLinkResultSchema).length(4),
+ })
+ .strict();
+
+export const ScreenshotProvenanceReceiptSchema = z
+ .object({
+ ...baseReceiptShape,
+ kind: z.literal("screenshot-provenance"),
+ fileName: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]*\.png$/u),
+ imageSha256: Sha256Schema,
+ publicUrl: z.url({ protocol: /^https$/u }),
+ route: z.string().regex(/^\/[A-Za-z0-9_./?=&%-]*$/u),
+ viewport: z
+ .object({
+ width: z.number().int().positive().max(8_192),
+ height: z.number().int().positive().max(8_192),
+ })
+ .strict(),
+ capturedAt: z.iso.datetime({ offset: true }),
+ authority: z.literal("CLOAKBROWSER"),
+ consoleErrors: z.literal(0),
+ failedRequests: z.literal(0),
+ derivedFromSha256: Sha256Schema.nullable(),
+ })
+ .strict();
+
+export const DevpostSubmissionReceiptSchema = z
+ .object({
+ schemaVersion: z.literal("1"),
+ kind: z.literal("devpost-submission"),
+ status: z.literal("SUBMITTED"),
+ capturedAt: z.iso.datetime({ offset: true }),
+ hackathonId: z.literal("30223"),
+ projectId: z.literal("1330312"),
+ category: z.literal("Education"),
+ publicSlug: z.string().regex(/^[a-z0-9][a-z0-9-]{2,127}$/u),
+ publicUrl: z.url({ protocol: /^https$/u }),
+ submittedAt: z.iso.datetime({ offset: true }),
+ release: PublicationReleaseBindingSchema,
+ copy: z
+ .object({
+ devpostCopySha256: Sha256Schema,
+ readmeSha256: Sha256Schema,
+ videoScriptSha256: Sha256Schema,
+ captionsSha256: Sha256Schema,
+ })
+ .strict(),
+ privacy: PublicationPrivacySchema,
+ })
+ .strict();
+
+const PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10] as const;
+
+function pngCrc32(bytes: Uint8Array): number {
+ let crc = 0xffffffff;
+ for (const byte of bytes) {
+ crc ^= byte;
+ for (let bit = 0; bit < 8; bit += 1) {
+ crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
+ }
+ }
+ return (crc ^ 0xffffffff) >>> 0;
+}
+
+function validPngBitDepth(bitDepth: number, colorType: number): boolean {
+ const allowed = new Map([
+ [0, [1, 2, 4, 8, 16]],
+ [2, [8, 16]],
+ [3, [1, 2, 4, 8]],
+ [4, [8, 16]],
+ [6, [8, 16]],
+ ]);
+ return allowed.get(colorType)?.includes(bitDepth) ?? false;
+}
+
+function joinPngBytes(parts: readonly Uint8Array[]): Uint8Array {
+ const joined = new Uint8Array(
+ parts.reduce((total, part) => total + part.byteLength, 0),
+ );
+ let offset = 0;
+ for (const part of parts) {
+ joined.set(part, offset);
+ offset += part.byteLength;
+ }
+ return joined;
+}
+
+export function readPngDimensions(bytes: Uint8Array): {
+ width: number;
+ height: number;
+} {
+ if (
+ bytes.byteLength < 33 ||
+ PNG_SIGNATURE.some((value, index) => bytes[index] !== value)
+ ) {
+ throw new Error("image is not a PNG");
+ }
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
+ const decoder = new TextDecoder("ascii", { fatal: true });
+ let offset: number = PNG_SIGNATURE.length;
+ let width = 0;
+ let height = 0;
+ let bitDepth = -1;
+ let colorType = -1;
+ let sawHeader = false;
+ let sawPalette = false;
+ let sawImageData = false;
+ let leftImageData = false;
+ let sawEnd = false;
+ const imageData: Uint8Array[] = [];
+ while (offset < bytes.byteLength) {
+ if (bytes.byteLength - offset < 12) {
+ throw new Error("PNG contains a truncated chunk");
+ }
+ const chunkLength = view.getUint32(offset, false);
+ const dataStart = offset + 8;
+ const dataEnd = dataStart + chunkLength;
+ const chunkEnd = dataEnd + 4;
+ if (dataEnd < dataStart || chunkEnd > bytes.byteLength) {
+ throw new Error("PNG chunk length is invalid");
+ }
+ const chunkTypeBytes = bytes.subarray(offset + 4, offset + 8);
+ const chunkType = decoder.decode(chunkTypeBytes);
+ const expectedCrc = view.getUint32(dataEnd, false);
+ const crcInput = bytes.subarray(offset + 4, dataEnd);
+ if (pngCrc32(crcInput) !== expectedCrc) {
+ throw new Error(`PNG ${chunkType} checksum is invalid`);
+ }
+
+ if (!sawHeader) {
+ if (chunkType !== "IHDR" || chunkLength !== 13) {
+ throw new Error("PNG does not begin with a valid IHDR");
+ }
+ width = view.getUint32(dataStart, false);
+ height = view.getUint32(dataStart + 4, false);
+ bitDepth = bytes[dataStart + 8]!;
+ colorType = bytes[dataStart + 9]!;
+ const compression = bytes[dataStart + 10]!;
+ const filtering = bytes[dataStart + 11]!;
+ const interlace = bytes[dataStart + 12]!;
+ if (
+ width === 0 ||
+ height === 0 ||
+ !validPngBitDepth(bitDepth, colorType) ||
+ compression !== 0 ||
+ filtering !== 0 ||
+ interlace !== 0
+ ) {
+ throw new Error("PNG IHDR fields are invalid");
+ }
+ sawHeader = true;
+ } else if (chunkType === "IHDR") {
+ throw new Error("PNG contains more than one IHDR");
+ } else if (chunkType === "PLTE") {
+ if (
+ sawPalette ||
+ sawImageData ||
+ chunkLength === 0 ||
+ chunkLength > 768 ||
+ chunkLength % 3 !== 0 ||
+ colorType === 0 ||
+ colorType === 4
+ ) {
+ throw new Error("PNG palette placement is invalid");
+ }
+ sawPalette = true;
+ } else if (chunkType === "IDAT") {
+ if (leftImageData || chunkLength === 0) {
+ throw new Error("PNG image data is invalid");
+ }
+ sawImageData = true;
+ imageData.push(bytes.subarray(dataStart, dataEnd));
+ } else if (chunkType === "IEND") {
+ if (
+ chunkLength !== 0 ||
+ !sawImageData ||
+ (colorType === 3 && !sawPalette) ||
+ chunkEnd !== bytes.byteLength
+ ) {
+ throw new Error("PNG end chunk is invalid");
+ }
+ sawEnd = true;
+ } else {
+ if (sawImageData) leftImageData = true;
+ const critical = chunkTypeBytes[0]! >= 65 && chunkTypeBytes[0]! <= 90;
+ if (critical)
+ throw new Error(`PNG contains unknown critical chunk ${chunkType}`);
+ }
+ offset = chunkEnd;
+ if (sawEnd) break;
+ }
+ if (!sawHeader || !sawImageData || !sawEnd) {
+ throw new Error("PNG is missing required chunks");
+ }
+ const channels = new Map([
+ [0, 1],
+ [2, 3],
+ [3, 1],
+ [4, 2],
+ [6, 4],
+ ]).get(colorType);
+ if (channels === undefined) throw new Error("PNG color type is invalid");
+ const rowBytes = Math.ceil((width * channels * bitDepth) / 8);
+ const decodedBytes = (rowBytes + 1) * height;
+ if (
+ !Number.isSafeInteger(decodedBytes) ||
+ decodedBytes > 128 * 1_024 * 1_024
+ ) {
+ throw new Error("PNG decoded image exceeds its size limit");
+ }
+ let decoded: Uint8Array;
+ try {
+ decoded = new Uint8Array(
+ inflateSync(joinPngBytes(imageData), {
+ maxOutputLength: decodedBytes + 1,
+ }),
+ );
+ } catch {
+ throw new Error("PNG image data cannot be decoded");
+ }
+ if (decoded.byteLength !== decodedBytes) {
+ throw new Error("PNG decoded image length is invalid");
+ }
+ for (let row = 0; row < height; row += 1) {
+ if (decoded[row * (rowBytes + 1)]! > 4) {
+ throw new Error("PNG contains an invalid row filter");
+ }
+ }
+ return { width, height };
+}
diff --git a/scripts/validate-submission-package.test.ts b/scripts/validate-submission-package.test.ts
index ce0a79b4..8c2cdda0 100644
--- a/scripts/validate-submission-package.test.ts
+++ b/scripts/validate-submission-package.test.ts
@@ -1,12 +1,17 @@
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
+import { deflateSync } from "node:zlib";
import { describe, expect, it } from "vitest";
import { RELEASE_CHECK_IDS } from "../packages/scientific-engine-registry/src/schema.js";
import { canonicalJson } from "../packages/session-core/src/index.js";
import { createGenerationIsolationEvidence } from "./generation-isolation-evidence.js";
+import {
+ REQUIRED_CLOAK_JOURNEY_IDS,
+ REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS,
+} from "./submission-publication-evidence.js";
import {
SubmissionPackageSchema,
type EvidenceReader,
@@ -29,6 +34,90 @@ function reference(path: string, content: Uint8Array) {
return { path, sha256: hash(content) };
}
+function pngCrc32(bytes: Uint8Array): number {
+ let crc = 0xffffffff;
+ for (const byte of bytes) {
+ crc ^= byte;
+ for (let bit = 0; bit < 8; bit += 1) {
+ crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
+ }
+ }
+ return (crc ^ 0xffffffff) >>> 0;
+}
+
+function joinBytes(parts: readonly Uint8Array[]): Uint8Array {
+ const output = new Uint8Array(
+ parts.reduce((total, part) => total + part.byteLength, 0),
+ );
+ let offset = 0;
+ for (const part of parts) {
+ output.set(part, offset);
+ offset += part.byteLength;
+ }
+ return output;
+}
+
+function pngChunk(type: string, data: Uint8Array): Uint8Array {
+ const typeBytes = new TextEncoder().encode(type);
+ const chunk = new Uint8Array(data.byteLength + 12);
+ const view = new DataView(chunk.buffer);
+ view.setUint32(0, data.byteLength, false);
+ chunk.set(typeBytes, 4);
+ chunk.set(data, 8);
+ view.setUint32(
+ 8 + data.byteLength,
+ pngCrc32(joinBytes([typeBytes, data])),
+ false,
+ );
+ return chunk;
+}
+
+function pngFixture(
+ width: number,
+ height: number,
+ discriminator: number,
+): Uint8Array {
+ const header = new Uint8Array(13);
+ const headerView = new DataView(header.buffer);
+ headerView.setUint32(0, width, false);
+ headerView.setUint32(4, height, false);
+ header.set([8, 0, 0, 0, 0], 8);
+ const scanlines = new Uint8Array(height * (width + 1));
+ scanlines[1] = discriminator;
+ return joinBytes([
+ new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]),
+ pngChunk("IHDR", header),
+ pngChunk("IDAT", new Uint8Array(deflateSync(scanlines))),
+ pngChunk("IEND", new Uint8Array()),
+ ]);
+}
+
+function pngHeaderOnlyFixture(width: number, height: number): Uint8Array {
+ const header = new Uint8Array(13);
+ const view = new DataView(header.buffer);
+ view.setUint32(0, width, false);
+ view.setUint32(4, height, false);
+ header.set([8, 0, 0, 0, 0], 8);
+ return joinBytes([
+ new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]),
+ pngChunk("IHDR", header),
+ ]);
+}
+
+function undecodablePngFixture(width: number, height: number): Uint8Array {
+ const header = new Uint8Array(13);
+ const view = new DataView(header.buffer);
+ view.setUint32(0, width, false);
+ view.setUint32(4, height, false);
+ header.set([8, 0, 0, 0, 0], 8);
+ return joinBytes([
+ new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]),
+ pngChunk("IHDR", header),
+ pngChunk("IDAT", new Uint8Array([1, 2, 3, 4])),
+ pngChunk("IEND", new Uint8Array()),
+ ]);
+}
+
async function draftInput(): Promise {
return JSON.parse(
await readFile(resolve(root, "docs/SUBMISSION_PACKAGE.json"), "utf8"),
@@ -349,41 +438,293 @@ async function readyPackage(
};
const chain = releaseEvidenceChain(options);
const { receipt } = chain;
+ const receiptBytes = bytes(receipt);
+ const deploymentReceiptSha256 = hash(receiptBytes);
+ const evidenceRoot = `docs/submission-evidence/${deploymentReceiptSha256}`;
+ const assetRoot = `docs/submission-assets/${deploymentReceiptSha256}`;
const qualifiedRef = addEvidence(
- "evidence/qualified-runner-release.json",
+ `${evidenceRoot}/qualified-runner-release.json`,
chain.qualifiedBytes,
);
const releaseCheckRef = addEvidence(
- "evidence/release-check-receipt.json",
+ `${evidenceRoot}/release-check-receipt.json`,
chain.releaseCheckBytes,
);
- const receiptBytes = bytes(receipt);
const receiptRef = addEvidence(
- "evidence/deployment-receipt.json",
+ `${evidenceRoot}/deployment-receipt.json`,
receiptBytes,
);
const smokeRef = addEvidence(
- "evidence/production-smoke.json",
+ `${evidenceRoot}/production-smoke.json`,
bytes(productionSmoke(receipt, receiptRef.sha256)),
);
- const genericEvidence = (name: string) =>
- addEvidence(`evidence/${name}.json`, bytes({ status: "VERIFIED", name }));
+ const checkedAt = "2026-07-21T10:00:00.000Z";
+ const publicationRelease = {
+ deploymentReceiptSha256,
+ productionOrigin: receipt.productionOrigin,
+ workerEvidenceCommit: receipt.workerEvidenceCommit,
+ runnerSourceCommit: receipt.runnerSourceCommit,
+ containerImageDigest: receipt.containerImageDigest,
+ workerVersionId: receipt.workerVersionId,
+ } as const;
+ const publicationPrivacy = {
+ containsSecrets: false,
+ containsRawNotebook: false,
+ containsPersonalData: false,
+ } as const;
+ const receiptBase = {
+ schemaVersion: "1",
+ status: "VERIFIED",
+ checkedAt,
+ release: publicationRelease,
+ privacy: publicationPrivacy,
+ } as const;
+ const repositoryEvidence = addEvidence(
+ `${evidenceRoot}/repository-access.json`,
+ bytes({
+ ...receiptBase,
+ kind: "repository-access",
+ repositoryUrl: input.repository.url,
+ accessMode: "PUBLIC",
+ loggedOutChecked: true,
+ loggedOutStatusCode: 200,
+ authenticatedJudgeStatusCode: 200,
+ finalUrl: input.repository.url,
+ anonymousAccessible: true,
+ judgeAccessVerified: true,
+ sharedJudgeAddresses: [],
+ }),
+ );
+ const videoUrl = "https://youtu.be/CLabDemo123";
+ const videoEvidence = addEvidence(
+ `${evidenceRoot}/video-verification.json`,
+ bytes({
+ ...receiptBase,
+ kind: "video-verification",
+ publicUrl: videoUrl,
+ durationSeconds: 165,
+ audioPresent: true,
+ codexRoleCovered: true,
+ gpt56RoleCovered: true,
+ loggedOutPlayable: true,
+ statusCode: 200,
+ scriptSha256: input.video.script.sha256,
+ captionsSha256: input.video.captions.sha256,
+ }),
+ );
+ const feedbackSessionId = "019f6069-2af2-7132-8181-d313081884c8";
+ const feedbackEvidence = addEvidence(
+ `${evidenceRoot}/feedback-session.json`,
+ bytes({
+ ...receiptBase,
+ kind: "feedback-session",
+ sessionId: feedbackSessionId,
+ source: "codex-feedback",
+ verifiedByOwner: true,
+ contentCaptured: false,
+ }),
+ );
+ const browserViewports = [
+ "375x812",
+ "390x844",
+ "768x1024",
+ "1280x720",
+ "1366x768",
+ "1440x900",
+ "1920x1080",
+ ] as const;
+ const browserVersion = "CloakBrowser Chromium 140.0.0.0";
+ const journeyEvidence = REQUIRED_CLOAK_JOURNEY_IDS.map((id, index) => {
+ const viewport = browserViewports[index % browserViewports.length]!;
+ const durationMs = 1_000 + index;
+ const sequence = String(index + 1).padStart(2, "0");
+ const evidenceReference = addEvidence(
+ `${evidenceRoot}/cloakbrowser-journeys/${sequence}.json`,
+ bytes({
+ ...receiptBase,
+ kind: "cloakbrowser-journey-evidence",
+ authority: "CLOAKBROWSER",
+ id,
+ viewport,
+ journeyStatus: "PASSED",
+ attempt: 0,
+ durationMs,
+ assertionCount: 3,
+ consoleErrors: 0,
+ failedRequests: 0,
+ }),
+ );
+ return {
+ id,
+ evidence: evidenceReference,
+ journey: {
+ id,
+ status: "PASSED",
+ attempt: 0,
+ durationMs,
+ viewport,
+ evidenceSha256: evidenceReference.sha256,
+ },
+ };
+ });
+ const journeys = journeyEvidence.map((entry) => entry.journey);
+ const browserReport = addEvidence(
+ `${evidenceRoot}/cloakbrowser-playwright-report.json`,
+ bytes({
+ schemaVersion: "1",
+ kind: "cloakbrowser-execution-report",
+ status: "PASSED",
+ checkedAt,
+ authority: "CLOAKBROWSER",
+ baseUrl: receipt.productionOrigin,
+ release: publicationRelease,
+ privacy: publicationPrivacy,
+ browserVersion,
+ playwrightVersion: "1.61.1",
+ journeys,
+ failures: 0,
+ skips: 0,
+ retries: 0,
+ consoleErrors: 0,
+ failedRequests: 0,
+ }),
+ );
+ const manualEvidenceEntries = REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS.map(
+ (check) => {
+ const evidenceReference = addEvidence(
+ `${evidenceRoot}/cloakbrowser-manual/${check}.json`,
+ bytes({
+ ...receiptBase,
+ kind: "cloakbrowser-manual-evidence",
+ authority: "CLOAKBROWSER",
+ check,
+ observationCount: 3,
+ artifactSha256: hash(bytes(`manual-artifact-${check}`)),
+ }),
+ );
+ return [check, evidenceReference] as const;
+ },
+ );
+ const manualEvidenceReferences = Object.fromEntries(manualEvidenceEntries);
+ const manualEvidence = Object.fromEntries(
+ manualEvidenceEntries.map(([check, evidenceReference]) => [
+ check,
+ { status: "PASSED", evidenceSha256: evidenceReference.sha256 },
+ ]),
+ );
+ const browserEvidenceIndex = addEvidence(
+ `${evidenceRoot}/cloakbrowser-evidence-index.json`,
+ bytes({
+ ...receiptBase,
+ kind: "cloakbrowser-evidence-index",
+ executionReportSha256: browserReport.sha256,
+ manual: manualEvidence,
+ }),
+ );
+ const browserEvidence = addEvidence(
+ `${evidenceRoot}/cloakbrowser-qualification.json`,
+ bytes({
+ ...receiptBase,
+ kind: "cloakbrowser-qualification",
+ authority: "CLOAKBROWSER",
+ baseUrl: receipt.productionOrigin,
+ exactReleaseBound: true,
+ journeyCount: 40,
+ viewports: browserViewports,
+ browserVersion,
+ playwrightVersion: "1.61.1",
+ playwrightReportSha256: browserReport.sha256,
+ browserEvidenceIndexSha256: browserEvidenceIndex.sha256,
+ journeys,
+ desktopComplete: true,
+ mobileComplete: true,
+ keyboardComplete: true,
+ screenReaderNamesComplete: true,
+ reducedMotionComplete: true,
+ noHorizontalOverflow: true,
+ zoom200Complete: true,
+ longContentComplete: true,
+ narrowVisualizationsComplete: true,
+ touchTargetsComplete: true,
+ requiredSkips: 0,
+ failures: 0,
+ consoleErrors: 0,
+ failedRequests: 0,
+ webVitals: { lcpMs: 1_200, cls: 0.02, inpMs: 90 },
+ }),
+ );
+ const publicLinkEvidence = addEvidence(
+ `${evidenceRoot}/public-link-audit.json`,
+ bytes({
+ ...receiptBase,
+ kind: "public-link-audit",
+ loggedOut: true,
+ links: [
+ {
+ role: "judge",
+ url: input.publicProduct.judgeUrl,
+ statusCode: 200,
+ finalUrl: input.publicProduct.judgeUrl,
+ },
+ {
+ role: "repository",
+ url: input.repository.url,
+ statusCode: 200,
+ finalUrl: input.repository.url,
+ },
+ {
+ role: "video",
+ url: videoUrl,
+ statusCode: 200,
+ finalUrl: videoUrl,
+ },
+ ],
+ }),
+ );
const assetNames = [
"01-judge-belief-break-1440x900.png",
"02-live-prediction-and-authority-1440x900.png",
"03-theater-boundary-1440x900.png",
"04-transfer-repair-proof-1440x900.png",
"thumbnail-judge-belief-break.png",
- ];
- const assets = assetNames.map((fileName) => ({
+ ] as const;
+ const imageAssets = assetNames.map((fileName, index) => {
+ const viewport =
+ fileName === "thumbnail-judge-belief-break.png"
+ ? { width: 1_200, height: 675 }
+ : { width: 1_440, height: 900 };
+ return {
+ fileName,
+ viewport,
+ image: addEvidence(
+ `${assetRoot}/${fileName}`,
+ pngFixture(viewport.width, viewport.height, index + 1),
+ ),
+ };
+ });
+ const firstImageSha256 = imageAssets[0]!.image.sha256;
+ const assets = imageAssets.map(({ fileName, image, viewport }) => ({
fileName,
- image: addEvidence(
- `docs/submission-assets/${fileName}`,
- bytes(`png:${fileName}`),
- ),
+ image,
provenance: addEvidence(
- `docs/submission-assets/${fileName}.json`,
- bytes({ fileName, release: receipt.workerVersionId }),
+ `${assetRoot}/${fileName}.json`,
+ bytes({
+ ...receiptBase,
+ kind: "screenshot-provenance",
+ fileName,
+ imageSha256: image.sha256,
+ publicUrl: input.publicProduct.judgeUrl,
+ route: input.publicProduct.freeTestPath,
+ viewport,
+ capturedAt: "2026-07-21T09:55:00.000Z",
+ authority: "CLOAKBROWSER",
+ consoleErrors: 0,
+ failedRequests: 0,
+ derivedFromSha256:
+ fileName === "thumbnail-judge-belief-break.png"
+ ? firstImageSha256
+ : null,
+ }),
),
}));
const ready = {
@@ -407,20 +748,20 @@ async function readyPackage(
repository: {
...input.repository,
accessMode: "PUBLIC" as const,
- accessEvidence: genericEvidence("repository-access"),
+ accessEvidence: repositoryEvidence,
},
video: {
...input.video,
- publicUrl: "https://youtu.be/counterlab-demo",
+ publicUrl: videoUrl,
durationSeconds: 165,
audioPresent: true,
codexRoleCovered: true,
gpt56RoleCovered: true,
- verificationEvidence: genericEvidence("video"),
+ verificationEvidence: videoEvidence,
},
feedback: {
- sessionId: "019f6069-2af2-7132-8181-d313081884c8",
- evidence: genericEvidence("feedback"),
+ sessionId: feedbackSessionId,
+ evidence: feedbackEvidence,
},
browserQualification: {
authority: "CLOAKBROWSER" as const,
@@ -431,13 +772,20 @@ async function readyPackage(
failures: 0,
consoleErrors: 0,
failedRequests: 0,
- evidence: genericEvidence("browser"),
+ playwrightReport: browserReport,
+ evidenceIndex: browserEvidenceIndex,
+ journeyEvidence: journeyEvidence.map(({ id, evidence }) => ({
+ id,
+ evidence,
+ })),
+ manualEvidence: manualEvidenceReferences,
+ evidence: browserEvidence,
},
assets,
publicLinkAudit: {
loggedOut: true,
- checkedAt: "2026-07-21T10:00:00.000Z",
- evidence: genericEvidence("links"),
+ checkedAt,
+ evidence: publicLinkEvidence,
},
};
return {
@@ -451,6 +799,114 @@ async function readyPackage(
};
}
+async function submittedPackage() {
+ const { ready, evidence } = await readyPackage();
+ const deploymentReceipt = ready.release.deploymentReceipt;
+ const deploymentReceiptSha256 = deploymentReceipt.sha256;
+ const evidenceRoot = `docs/submission-evidence/${deploymentReceiptSha256}`;
+ const release = {
+ deploymentReceiptSha256,
+ productionOrigin: "https://counterlab.cserules.workers.dev",
+ workerEvidenceCommit: ready.release.workerEvidenceCommit,
+ runnerSourceCommit: ready.release.runnerSourceCommit,
+ containerImageDigest: ready.release.containerImageDigest,
+ workerVersionId: ready.release.workerVersionId,
+ } as const;
+ const privacy = {
+ containsSecrets: false,
+ containsRawNotebook: false,
+ containsPersonalData: false,
+ } as const;
+ const publicSlug = "counterlab-scientific-debugger";
+ const publicUrl = `https://devpost.com/software/${publicSlug}`;
+ const submittedAt = "2026-07-21T23:30:00.000Z";
+ const addEvidence = (path: string, content: Uint8Array) => {
+ evidence.set(path, content);
+ return reference(path, content);
+ };
+ const receipt = addEvidence(
+ `${evidenceRoot}/devpost-submission.json`,
+ bytes({
+ schemaVersion: "1",
+ kind: "devpost-submission",
+ status: "SUBMITTED",
+ capturedAt: "2026-07-21T23:31:00.000Z",
+ hackathonId: ready.competition.hackathonId,
+ projectId: ready.competition.projectId,
+ category: ready.competition.category,
+ publicSlug,
+ publicUrl,
+ submittedAt,
+ release,
+ copy: {
+ devpostCopySha256: ready.copy.devpostCopy.sha256,
+ readmeSha256: ready.copy.readme.sha256,
+ videoScriptSha256: ready.video.script.sha256,
+ captionsSha256: ready.video.captions.sha256,
+ },
+ privacy,
+ }),
+ );
+ const postSubmitLoggedOutEvidence = addEvidence(
+ `${evidenceRoot}/post-submit-link-audit.json`,
+ bytes({
+ schemaVersion: "1",
+ kind: "post-submit-link-audit",
+ status: "VERIFIED",
+ checkedAt: "2026-07-21T23:32:00.000Z",
+ release,
+ privacy,
+ loggedOut: true,
+ links: [
+ {
+ role: "judge",
+ url: ready.publicProduct.judgeUrl,
+ statusCode: 200,
+ finalUrl: ready.publicProduct.judgeUrl,
+ },
+ {
+ role: "repository",
+ url: ready.repository.url,
+ statusCode: 200,
+ finalUrl: ready.repository.url,
+ },
+ {
+ role: "video",
+ url: ready.video.publicUrl,
+ statusCode: 200,
+ finalUrl: ready.video.publicUrl,
+ },
+ {
+ role: "devpost",
+ url: publicUrl,
+ statusCode: 200,
+ finalUrl: publicUrl,
+ },
+ ],
+ }),
+ );
+ const submitted = {
+ ...ready,
+ submissionState: "SUBMITTED" as const,
+ officialSubmission: {
+ state: "SUBMITTED" as const,
+ publicSlug,
+ submittedAt,
+ receipt,
+ postSubmitLoggedOutEvidence,
+ },
+ };
+ return {
+ submitted,
+ evidence,
+ reader: async (entry: { path: string }) => {
+ const content = evidence.get(entry.path);
+ if (content === undefined) throw new Error("missing test evidence");
+ return content;
+ },
+ };
+}
+
describe("submission package validator", () => {
it("validates the honest current draft and all checked-in hashes", async () => {
const result = await validateSubmissionPackage(
@@ -470,8 +926,216 @@ describe("submission package validator", () => {
).rejects.toThrow(/failed \d+ gate/iu);
});
- it("accepts a tuple-consistent release fixture", async () => {
+ it("accepts a fully typed publication-ready fixture", async () => {
+ const { ready, reader } = await readyPackage();
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).resolves.toMatchObject({ submissionState: "READY_TO_SUBMIT" });
+ });
+
+ it("accepts a fully typed submitted fixture captured before the deadline", async () => {
+ const { submitted, reader } = await submittedPackage();
+
+ await expect(
+ validateSubmissionPackage(submitted, "submitted", reader, {
+ now: new Date("2026-07-22T01:00:00.000Z"),
+ }),
+ ).resolves.toMatchObject({
+ submissionState: "SUBMITTED",
+ officialSubmission: { state: "SUBMITTED" },
+ });
+ });
+
+ it("rejects generic JSON in place of a typed publication receipt", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const generic = bytes({ status: "VERIFIED" });
+ const path = ready.repository.accessEvidence!.path;
+ evidence.set(path, generic);
+ ready.repository.accessEvidence = reference(path, generic);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining(["repository access evidence is invalid"]),
+ });
+ });
+
+ it("rejects text bytes presented as a PNG screenshot", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const asset = ready.assets[0]!;
+ const fakePng = bytes("not a screenshot");
+ evidence.set(asset.image.path, fakePng);
+ asset.image = reference(asset.image.path, fakePng);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ `screenshot ${asset.fileName} is not a valid PNG`,
+ ]),
+ });
+ });
+
+ it("rejects a checksum-valid PNG header without image data or an end chunk", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const asset = ready.assets[0]!;
+ const headerOnly = pngHeaderOnlyFixture(1_440, 900);
+ evidence.set(asset.image.path, headerOnly);
+ asset.image = reference(asset.image.path, headerOnly);
+ const provenanceBytes = evidence.get(asset.provenance.path);
+ if (provenanceBytes === undefined)
+ throw new Error("missing screenshot provenance");
+ const provenance = JSON.parse(
+ new TextDecoder().decode(provenanceBytes),
+ ) as { imageSha256: string };
+ provenance.imageSha256 = asset.image.sha256;
+ const changedProvenance = bytes(provenance);
+ evidence.set(asset.provenance.path, changedProvenance);
+ asset.provenance = reference(asset.provenance.path, changedProvenance);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ `screenshot ${asset.fileName} is not a valid PNG`,
+ ]),
+ });
+ });
+
+ it("rejects checksum-valid PNG chunks with undecodable image data", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const asset = ready.assets[0]!;
+ const undecodable = undecodablePngFixture(1_440, 900);
+ evidence.set(asset.image.path, undecodable);
+ asset.image = reference(asset.image.path, undecodable);
+ const provenanceBytes = evidence.get(asset.provenance.path);
+ if (provenanceBytes === undefined)
+ throw new Error("missing screenshot provenance");
+ const provenance = JSON.parse(
+ new TextDecoder().decode(provenanceBytes),
+ ) as { imageSha256: string };
+ provenance.imageSha256 = asset.image.sha256;
+ const changedProvenance = bytes(provenance);
+ evidence.set(asset.provenance.path, changedProvenance);
+ asset.provenance = reference(asset.provenance.path, changedProvenance);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ `screenshot ${asset.fileName} is not a valid PNG`,
+ ]),
+ });
+ });
+
+ it("rejects a public link that redirects to an unrelated destination", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const auditRef = ready.publicLinkAudit.evidence!;
+ const current = evidence.get(auditRef.path);
+ if (current === undefined) throw new Error("missing public-link audit");
+ const audit = JSON.parse(new TextDecoder().decode(current)) as {
+ links: Array<{ role: string; finalUrl: string }>;
+ };
+ audit.links[0]!.finalUrl = "https://example.com/unrelated";
+ const changed = bytes(audit);
+ evidence.set(auditRef.path, changed);
+ ready.publicLinkAudit.evidence = reference(auditRef.path, changed);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "public-link audit judge final destination does not match",
+ ]),
+ });
+ });
+
+ it("rejects publication evidence dated before production smoke completed", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const accessRef = ready.repository.accessEvidence!;
+ const current = evidence.get(accessRef.path);
+ if (current === undefined) throw new Error("missing repository receipt");
+ const access = JSON.parse(new TextDecoder().decode(current)) as {
+ checkedAt: string;
+ };
+ access.checkedAt = "2026-07-18T10:00:00.000Z";
+ const changed = bytes(access);
+ evidence.set(accessRef.path, changed);
+ ready.repository.accessEvidence = reference(accessRef.path, changed);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "repository access predates production release completion",
+ ]),
+ });
+ });
+
+ it("rejects a non-success repository access receipt", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const accessRef = ready.repository.accessEvidence!;
+ const current = evidence.get(accessRef.path);
+ if (current === undefined) throw new Error("missing repository receipt");
+ const access = JSON.parse(new TextDecoder().decode(current)) as {
+ authenticatedJudgeStatusCode: number;
+ };
+ access.authenticatedJudgeStatusCode = 599;
+ const changed = bytes(access);
+ evidence.set(accessRef.path, changed);
+ ready.repository.accessEvidence = reference(accessRef.path, changed);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining(["repository access evidence is invalid"]),
+ });
+ });
+
+ it("rejects CloakBrowser evidence with duplicate journey identities", async () => {
+ const { ready, reader, evidence } = await readyPackage();
+ const browserRef = ready.browserQualification.evidence!;
+ const current = evidence.get(browserRef.path);
+ if (current === undefined) throw new Error("missing browser receipt");
+ const browser = JSON.parse(new TextDecoder().decode(current)) as {
+ journeys: Array<{ id: string }>;
+ };
+ browser.journeys[1]!.id = browser.journeys[0]!.id;
+ const changed = bytes(browser);
+ evidence.set(browserRef.path, changed);
+ ready.browserQualification.evidence = reference(browserRef.path, changed);
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "CloakBrowser qualification evidence is invalid",
+ ]),
+ });
+ });
+
+ it("binds screenshot roles by file name instead of manifest order", async () => {
const { ready, reader } = await readyPackage();
+ ready.assets = [ready.assets[4]!, ...ready.assets.slice(0, 4)];
await expect(
validateSubmissionPackage(ready, "ready", reader, {
@@ -480,6 +1144,72 @@ describe("submission package validator", () => {
).resolves.toMatchObject({ submissionState: "READY_TO_SUBMIT" });
});
+ it("rejects a Devpost receipt for a different public slug", async () => {
+ const { submitted, reader, evidence } = await submittedPackage();
+ const receiptRef = submitted.officialSubmission.receipt;
+ const current = evidence.get(receiptRef.path);
+ if (current === undefined) throw new Error("missing Devpost receipt");
+ const receipt = JSON.parse(new TextDecoder().decode(current)) as {
+ publicSlug: string;
+ publicUrl: string;
+ };
+ receipt.publicSlug = "different-counterlab-project";
+ receipt.publicUrl = `https://devpost.com/software/${receipt.publicSlug}`;
+ const changed = bytes(receipt);
+ evidence.set(receiptRef.path, changed);
+ submitted.officialSubmission.receipt = reference(receiptRef.path, changed);
+
+ await expect(
+ validateSubmissionPackage(submitted, "submitted", reader, {
+ now: new Date("2026-07-22T01:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "Devpost submission receipt does not match the package",
+ ]),
+ });
+ });
+
+ it("rejects a noncanonical Devpost project URL", async () => {
+ const { submitted, reader, evidence } = await submittedPackage();
+ const receiptRef = submitted.officialSubmission.receipt;
+ const current = evidence.get(receiptRef.path);
+ if (current === undefined) throw new Error("missing Devpost receipt");
+ const receipt = JSON.parse(new TextDecoder().decode(current)) as {
+ publicSlug: string;
+ publicUrl: string;
+ };
+ receipt.publicUrl = `https://devpost.com/showcase/${receipt.publicSlug}`;
+ const changed = bytes(receipt);
+ evidence.set(receiptRef.path, changed);
+ submitted.officialSubmission.receipt = reference(receiptRef.path, changed);
+
+ await expect(
+ validateSubmissionPackage(submitted, "submitted", reader, {
+ now: new Date("2026-07-22T01:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "Devpost submission receipt does not match the package",
+ ]),
+ });
+ });
+
+ it("rejects a submission timestamp equal to the deadline", async () => {
+ const { submitted, reader } = await submittedPackage();
+ submitted.officialSubmission.submittedAt = submitted.competition.deadline;
+
+ await expect(
+ validateSubmissionPackage(submitted, "submitted", reader, {
+ now: new Date("2026-07-22T01:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "official submission timestamp is at or after the deadline",
+ ]),
+ });
+ });
+
it("rejects a qualification older than 24 hours at deployment", async () => {
const { ready, reader } = await readyPackage({
qualificationDay: "2026-07-18",
@@ -509,42 +1239,13 @@ describe("submission package validator", () => {
) as { qualifiedRunnerReceiptSha256: string };
releaseCheck.qualifiedRunnerReceiptSha256 = "f".repeat(64);
const changedReleaseCheck = bytes(releaseCheck);
- const changedReleaseCheckPath = "evidence/drifted-release-check.json";
+ const changedReleaseCheckPath = ready.release.releaseCheckReceipt!.path;
evidence.set(changedReleaseCheckPath, changedReleaseCheck);
ready.release.releaseCheckReceipt = reference(
changedReleaseCheckPath,
changedReleaseCheck,
);
- const deploymentBytes = evidence.get(ready.release.deploymentReceipt!.path);
- if (deploymentBytes === undefined) {
- throw new Error("missing deployment fixture");
- }
- const deployment = JSON.parse(
- new TextDecoder().decode(deploymentBytes),
- ) as { releaseCheckReceiptSha256: string };
- deployment.releaseCheckReceiptSha256 =
- ready.release.releaseCheckReceipt.sha256;
- const changedDeployment = bytes(deployment);
- const changedDeploymentPath = "evidence/drifted-deployment.json";
- evidence.set(changedDeploymentPath, changedDeployment);
- ready.release.deploymentReceipt = reference(
- changedDeploymentPath,
- changedDeployment,
- );
-
- const smokeBytes = evidence.get(ready.release.productionSmoke!.path);
- if (smokeBytes === undefined) throw new Error("missing smoke fixture");
- const smoke = JSON.parse(new TextDecoder().decode(smokeBytes)) as {
- deployment: { deploymentReceiptSha256: string };
- };
- smoke.deployment.deploymentReceiptSha256 =
- ready.release.deploymentReceipt.sha256;
- const changedSmoke = bytes(smoke);
- const changedSmokePath = "evidence/drifted-smoke.json";
- evidence.set(changedSmokePath, changedSmoke);
- ready.release.productionSmoke = reference(changedSmokePath, changedSmoke);
-
await expect(
validateSubmissionPackage(ready, "ready", reader, {
now: new Date("2026-07-21T12:00:00.000Z"),
@@ -558,38 +1259,23 @@ describe("submission package validator", () => {
it("rejects deployment timeout evidence that drifts from qualification", async () => {
const { ready, reader, evidence } = await readyPackage();
- const deploymentBytes = evidence.get(ready.release.deploymentReceipt!.path);
- if (deploymentBytes === undefined) {
- throw new Error("missing deployment fixture");
- }
- const deployment = JSON.parse(
- new TextDecoder().decode(deploymentBytes),
- ) as { timeoutCleanupReceiptSha256: string };
- deployment.timeoutCleanupReceiptSha256 = "0".repeat(64);
- const changedDeployment = bytes(deployment);
- const changedDeploymentPath = "evidence/timeout-drift-deployment.json";
- evidence.set(changedDeploymentPath, changedDeployment);
- ready.release.deploymentReceipt = reference(
- changedDeploymentPath,
- changedDeployment,
+ const qualifiedBytes = evidence.get(
+ ready.release.qualifiedRunnerReceipt!.path,
);
-
- const smokeBytes = evidence.get(ready.release.productionSmoke!.path);
- if (smokeBytes === undefined) throw new Error("missing smoke fixture");
- const smoke = JSON.parse(new TextDecoder().decode(smokeBytes)) as {
- deployment: {
- deploymentReceiptSha256: string;
- timeoutCleanupReceiptSha256: string;
- };
+ if (qualifiedBytes === undefined) {
+ throw new Error("missing qualified runner fixture");
+ }
+ const qualified = JSON.parse(new TextDecoder().decode(qualifiedBytes)) as {
+ timeoutCleanupReceiptSha256: string;
};
- smoke.deployment.deploymentReceiptSha256 =
- ready.release.deploymentReceipt.sha256;
- smoke.deployment.timeoutCleanupReceiptSha256 =
- deployment.timeoutCleanupReceiptSha256;
- const changedSmoke = bytes(smoke);
- const changedSmokePath = "evidence/timeout-drift-smoke.json";
- evidence.set(changedSmokePath, changedSmoke);
- ready.release.productionSmoke = reference(changedSmokePath, changedSmoke);
+ qualified.timeoutCleanupReceiptSha256 = "0".repeat(64);
+ const changedQualified = bytes(qualified);
+ const changedQualifiedPath = ready.release.qualifiedRunnerReceipt!.path;
+ evidence.set(changedQualifiedPath, changedQualified);
+ ready.release.qualifiedRunnerReceipt = reference(
+ changedQualifiedPath,
+ changedQualified,
+ );
await expect(
validateSubmissionPackage(ready, "ready", reader, {
@@ -635,52 +1321,13 @@ describe("submission package validator", () => {
releaseCheck.releaseCheckGenerationIsolationProbeSha256 =
wrongTreeIsolation.probeSha256;
const changedReleaseCheck = bytes(releaseCheck);
- const changedReleaseCheckPath = "evidence/wrong-tree-release-check.json";
+ const changedReleaseCheckPath = ready.release.releaseCheckReceipt!.path;
evidence.set(changedReleaseCheckPath, changedReleaseCheck);
ready.release.releaseCheckReceipt = reference(
changedReleaseCheckPath,
changedReleaseCheck,
);
- const deploymentBytes = evidence.get(ready.release.deploymentReceipt!.path);
- if (deploymentBytes === undefined) {
- throw new Error("missing deployment fixture");
- }
- const deployment = JSON.parse(
- new TextDecoder().decode(deploymentBytes),
- ) as {
- releaseCheckReceiptSha256: string;
- releaseCheckGenerationIsolationEvidenceSha256: string;
- };
- deployment.releaseCheckReceiptSha256 =
- ready.release.releaseCheckReceipt.sha256;
- deployment.releaseCheckGenerationIsolationEvidenceSha256 =
- wrongTreeIsolation.evidenceSha256;
- const changedDeployment = bytes(deployment);
- const changedDeploymentPath = "evidence/wrong-tree-deployment.json";
- evidence.set(changedDeploymentPath, changedDeployment);
- ready.release.deploymentReceipt = reference(
- changedDeploymentPath,
- changedDeployment,
- );
-
- const smokeBytes = evidence.get(ready.release.productionSmoke!.path);
- if (smokeBytes === undefined) throw new Error("missing smoke fixture");
- const smoke = JSON.parse(new TextDecoder().decode(smokeBytes)) as {
- deployment: {
- deploymentReceiptSha256: string;
- releaseCheckGenerationIsolationEvidenceSha256: string;
- };
- };
- smoke.deployment.deploymentReceiptSha256 =
- ready.release.deploymentReceipt.sha256;
- smoke.deployment.releaseCheckGenerationIsolationEvidenceSha256 =
- wrongTreeIsolation.evidenceSha256;
- const changedSmoke = bytes(smoke);
- const changedSmokePath = "evidence/wrong-tree-smoke.json";
- evidence.set(changedSmokePath, changedSmoke);
- ready.release.productionSmoke = reference(changedSmokePath, changedSmoke);
-
await expect(
validateSubmissionPackage(ready, "ready", reader, {
now: new Date("2026-07-21T12:00:00.000Z"),
@@ -706,7 +1353,7 @@ describe("submission package validator", () => {
deploymentReceiptSha256: ready.release.deploymentReceipt!.sha256,
},
});
- const path = "evidence/minimal-production-smoke.json";
+ const path = ready.release.productionSmoke!.path;
evidence.set(path, minimal);
ready.release.productionSmoke = reference(path, minimal);
@@ -730,7 +1377,7 @@ describe("submission package validator", () => {
};
smoke.stages[0]!.evidence = { token: "redacted" };
const privateSmoke = bytes(smoke);
- const path = "evidence/private-production-smoke.json";
+ const path = ready.release.productionSmoke!.path;
evidence.set(path, privateSmoke);
ready.release.productionSmoke = reference(path, privateSmoke);
@@ -756,7 +1403,7 @@ describe("submission package validator", () => {
note: 'captured: {"nbformat":4,"cells":[]}',
};
const privateSmoke = bytes(smoke);
- const path = "evidence/raw-notebook-production-smoke.json";
+ const path = ready.release.productionSmoke!.path;
evidence.set(path, privateSmoke);
ready.release.productionSmoke = reference(path, privateSmoke);
@@ -836,6 +1483,25 @@ describe("submission package validator", () => {
expect(Math.max(...calls.values())).toBe(1);
});
+ it("loads every release and browser evidence role exactly once", async () => {
+ const { ready, reader } = await readyPackage();
+ const calls = new Map();
+ const immutableReader: EvidenceReader = async (entry) => {
+ const count = (calls.get(entry.path) ?? 0) + 1;
+ calls.set(entry.path, count);
+ if (count > 1) return bytes({ replacedAfterHash: true });
+ return reader(entry);
+ };
+
+ await expect(
+ validateSubmissionPackage(ready, "ready", immutableReader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).resolves.toMatchObject({ submissionState: "READY_TO_SUBMIT" });
+ expect(calls.size).toBeGreaterThan(60);
+ expect(Math.max(...calls.values())).toBe(1);
+ });
+
it("uses the canonical learner-pilot schema", async () => {
const input = SubmissionPackageSchema.parse(await draftInput());
const qualifiedReleaseReceiptSha256 = "a".repeat(64);
@@ -853,7 +1519,7 @@ describe("submission package validator", () => {
});
input.impact = {
status: "DESCRIPTIVE_ONLY",
- aggregate: reference("evidence/invalid-impact.json", invalidImpact),
+ aggregate: reference("docs/LEARNER_PILOT_RESULTS.json", invalidImpact),
qualifiedReleaseReceiptSha256,
};
const reader: EvidenceReader = async (entry) =>
@@ -939,7 +1605,7 @@ describe("submission package validator", () => {
"Results are descriptive pilot observations, not causal estimates or proof of mastery.",
],
});
- const path = "evidence/mismatched-impact.json";
+ const path = "docs/LEARNER_PILOT_RESULTS.json";
evidence.set(path, aggregate);
ready.impact = {
status: "DESCRIPTIVE_ONLY",
@@ -998,4 +1664,43 @@ describe("submission package validator", () => {
validateSubmissionPackage(aliased, "draft", repositoryReader),
).rejects.toThrow(/inside the repository/iu);
});
+
+ it("rejects a wrong evidence role path before invoking the reader", async () => {
+ const input = SubmissionPackageSchema.parse(await draftInput());
+ input.copy.readme.path = "docs/not-the-readme.md";
+ let reads = 0;
+ const reader: EvidenceReader = async () => {
+ reads += 1;
+ return bytes("unexpected read");
+ };
+
+ await expect(
+ validateSubmissionPackage(input, "draft", reader),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining(["evidence path must be README.md"]),
+ });
+ expect(reads).toBe(0);
+ });
+
+ it("rejects release-bound evidence without a deployment before invoking the reader", async () => {
+ const input = SubmissionPackageSchema.parse(await draftInput());
+ input.release.qualifiedRunnerReceipt = reference(
+ "docs/submission-evidence/unbound/qualified-runner-release.json",
+ bytes({ status: "VERIFIED" }),
+ );
+ let reads = 0;
+ const reader: EvidenceReader = async () => {
+ reads += 1;
+ return bytes("unexpected read");
+ };
+
+ await expect(
+ validateSubmissionPackage(input, "draft", reader),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "release-bound evidence requires a deployment receipt before any read",
+ ]),
+ });
+ expect(reads).toBe(0);
+ });
});
diff --git a/scripts/validate-submission-package.ts b/scripts/validate-submission-package.ts
index cb67d707..8e139c02 100644
--- a/scripts/validate-submission-package.ts
+++ b/scripts/validate-submission-package.ts
@@ -1,5 +1,5 @@
import { createHash } from "node:crypto";
-import { readFile } from "node:fs/promises";
+import { readFile, stat } from "node:fs/promises";
import { resolve } from "node:path";
import { z } from "zod";
@@ -9,6 +9,23 @@ import { DeploymentReceiptV7Schema } from "../packages/scientific-engine-registr
import { parseQualifiedRunnerReleaseV6 } from "./generation-isolation-evidence.js";
import { parseReleaseCheckReceiptV5 } from "./release-check-receipt.js";
import { containedInputFile } from "./repository-cli-paths.js";
+import {
+ CloakBrowserEvidenceIndexSchema,
+ CloakBrowserExecutionReportSchema,
+ CloakBrowserQualificationReceiptSchema,
+ CloakJourneyEvidenceReceiptSchema,
+ CloakManualEvidenceReceiptSchema,
+ DevpostSubmissionReceiptSchema,
+ FeedbackSessionReceiptSchema,
+ PostSubmitLinkAuditReceiptSchema,
+ PublicationReleaseBindingSchema,
+ PublicLinkAuditReceiptSchema,
+ REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS,
+ RepositoryAccessReceiptSchema,
+ ScreenshotProvenanceReceiptSchema,
+ VideoVerificationReceiptSchema,
+ readPngDimensions,
+} from "./submission-publication-evidence.js";
const Sha256Schema = z.string().regex(/^[a-f0-9]{64}$/u);
const GitCommitSchema = z.string().regex(/^[a-f0-9]{40}$/u);
@@ -64,6 +81,28 @@ const AssetSchema = z
})
.strict();
+const BrowserJourneyEvidenceReferenceSchema = z
+ .object({
+ id: z.string().trim().min(1).max(512),
+ evidence: EvidenceReferenceSchema,
+ })
+ .strict();
+
+const BrowserManualEvidenceSchema = z
+ .object({
+ keyboard: NullableEvidence,
+ screenReaderNames: NullableEvidence,
+ reducedMotion: NullableEvidence,
+ noHorizontalOverflow: NullableEvidence,
+ zoom200: NullableEvidence,
+ longContent: NullableEvidence,
+ narrowVisualizations: NullableEvidence,
+ touchTargets: NullableEvidence,
+ consoleAndNetwork: NullableEvidence,
+ webVitals: NullableEvidence,
+ })
+ .strict();
+
export const SubmissionPackageSchema = z
.object({
schemaVersion: z.literal("1"),
@@ -139,6 +178,10 @@ export const SubmissionPackageSchema = z
failures: z.number().int().nonnegative(),
consoleErrors: z.number().int().nonnegative(),
failedRequests: z.number().int().nonnegative(),
+ playwrightReport: NullableEvidence,
+ evidenceIndex: NullableEvidence,
+ journeyEvidence: z.array(BrowserJourneyEvidenceReferenceSchema).max(40),
+ manualEvidence: BrowserManualEvidenceSchema,
evidence: NullableEvidence,
})
.strict(),
@@ -185,6 +228,8 @@ const EXPECTED_ASSETS = [
"04-transfer-repair-proof-1440x900.png",
"thumbnail-judge-belief-break.png",
] as const;
+const MAX_TEXT_EVIDENCE_BYTES = 4 * 1_024 * 1_024;
+const MAX_SCREENSHOT_BYTES = 25 * 1_024 * 1_024;
const SmokeDeploymentSchema = z
.object({
@@ -475,6 +520,8 @@ function collectEvidenceReferences(
submission.video.verificationEvidence,
submission.feedback.evidence,
submission.browserQualification.evidence,
+ submission.browserQualification.playwrightReport,
+ submission.browserQualification.evidenceIndex,
submission.publicLinkAudit.evidence,
submission.officialSubmission.receipt,
submission.officialSubmission.postSubmitLoggedOutEvidence,
@@ -482,6 +529,12 @@ function collectEvidenceReferences(
(reference): reference is z.infer =>
reference !== null,
);
+ const manualBrowserEvidence = REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS.map(
+ (key) => submission.browserQualification.manualEvidence[key],
+ ).filter(
+ (reference): reference is z.infer =>
+ reference !== null,
+ );
return [
submission.copy.devpostCopy,
submission.copy.readme,
@@ -491,6 +544,10 @@ function collectEvidenceReferences(
submission.video.captions,
submission.impact.aggregate,
...submission.assets.flatMap((asset) => [asset.image, asset.provenance]),
+ ...submission.browserQualification.journeyEvidence.map(
+ (journey) => journey.evidence,
+ ),
+ ...manualBrowserEvidence,
...optional,
];
}
@@ -513,6 +570,56 @@ function assertNoPlaceholderStrings(value: unknown, path = "package"): void {
}
}
+function youtubeVideoId(value: string): string | null {
+ try {
+ const url = new URL(value);
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "")
+ return null;
+ const hostname = url.hostname.toLowerCase();
+ const candidate =
+ hostname === "youtu.be"
+ ? url.pathname.slice(1)
+ : hostname === "youtube.com" || hostname === "www.youtube.com"
+ ? url.pathname === "/watch"
+ ? url.searchParams.get("v")
+ : null
+ : null;
+ return candidate !== null && /^[A-Za-z0-9_-]{11}$/u.test(candidate)
+ ? candidate
+ : null;
+ } catch {
+ return null;
+ }
+}
+
+function normalizeDirectPublicUrl(value: string): string | null {
+ try {
+ const url = new URL(value);
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "")
+ return null;
+ url.hash = "";
+ url.hostname = url.hostname.toLowerCase().replace(/^www\./u, "");
+ if (url.pathname.length > 1)
+ url.pathname = url.pathname.replace(/\/$/u, "");
+ return url.toString();
+ } catch {
+ return null;
+ }
+}
+
+function finalDestinationMatches(
+ role: "judge" | "repository" | "video" | "devpost",
+ requestedUrl: string,
+ finalUrl: string,
+): boolean {
+ if (role === "video") {
+ const requestedId = youtubeVideoId(requestedUrl);
+ return requestedId !== null && youtubeVideoId(finalUrl) === requestedId;
+ }
+ const requested = normalizeDirectPublicUrl(requestedUrl);
+ return requested !== null && normalizeDirectPublicUrl(finalUrl) === requested;
+}
+
function requireReadyFields(
submission: SubmissionPackage,
issues: string[],
@@ -538,14 +645,27 @@ function requireReadyFields(
["feedback.sessionId", submission.feedback.sessionId],
["feedback.evidence", submission.feedback.evidence],
["browserQualification.evidence", submission.browserQualification.evidence],
+ [
+ "browserQualification.playwrightReport",
+ submission.browserQualification.playwrightReport,
+ ],
+ [
+ "browserQualification.evidenceIndex",
+ submission.browserQualification.evidenceIndex,
+ ],
["publicLinkAudit.checkedAt", submission.publicLinkAudit.checkedAt],
["publicLinkAudit.evidence", submission.publicLinkAudit.evidence],
];
for (const [field, value] of required) {
if (value === null) issues.push(`${field} is required`);
}
- if (submission.submissionState === "DRAFT") {
- issues.push("submissionState must be READY_TO_SUBMIT or SUBMITTED");
+ if (submission.browserQualification.journeyEvidence.length !== 40) {
+ issues.push("browserQualification.journeyEvidence requires 40 entries");
+ }
+ for (const key of REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS) {
+ if (submission.browserQualification.manualEvidence[key] === null) {
+ issues.push(`browserQualification.manualEvidence.${key} is required`);
+ }
}
if (
submission.repository.accessMode === "PUBLIC" &&
@@ -573,11 +693,9 @@ function requireReadyFields(
}
if (
submission.video.publicUrl === null ||
- !/^https:\/\/(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)/u.test(
- submission.video.publicUrl,
- )
+ youtubeVideoId(submission.video.publicUrl) === null
) {
- issues.push("video.publicUrl must be a public YouTube URL");
+ issues.push("video.publicUrl must identify one public YouTube video");
}
if (
!submission.video.audioPresent ||
@@ -1078,6 +1196,839 @@ async function assertImpactEvidence(
}
}
+type EvidenceReference = z.infer;
+type PublicationReleaseBinding = z.infer<
+ typeof PublicationReleaseBindingSchema
+>;
+
+function expectedPublicationRelease(
+ submission: SubmissionPackage,
+): PublicationReleaseBinding | null {
+ const release = submission.release;
+ if (
+ release.deploymentReceipt === null ||
+ release.workerEvidenceCommit === null ||
+ release.runnerSourceCommit === null ||
+ release.containerImageDigest === null ||
+ release.workerVersionId === null
+ ) {
+ return null;
+ }
+ return PublicationReleaseBindingSchema.parse({
+ deploymentReceiptSha256: release.deploymentReceipt.sha256,
+ productionOrigin: new URL(submission.publicProduct.judgeUrl).origin,
+ workerEvidenceCommit: release.workerEvidenceCommit,
+ runnerSourceCommit: release.runnerSourceCommit,
+ containerImageDigest: release.containerImageDigest,
+ workerVersionId: release.workerVersionId,
+ });
+}
+
+function parseEvidenceReceipt(
+ schema: z.ZodType,
+ reference: EvidenceReference | null,
+ label: string,
+ evidence: EvidenceCache,
+ issues: string[],
+): T | null {
+ if (reference === null) return null;
+ try {
+ const value = parseJson(cachedEvidence(evidence, reference, label), label);
+ assertPrivacySafeEvidence(value, label);
+ return schema.parse(value);
+ } catch {
+ issues.push(`${label} evidence is invalid`);
+ return null;
+ }
+}
+
+function assertPublicationRelease(
+ observed: PublicationReleaseBinding,
+ expected: PublicationReleaseBinding | null,
+ label: string,
+ issues: string[],
+): void {
+ if (expected === null) {
+ issues.push(`${label} cannot bind an incomplete release`);
+ return;
+ }
+ for (const field of Object.keys(expected) as Array<
+ keyof PublicationReleaseBinding
+ >) {
+ if (observed[field] !== expected[field]) {
+ issues.push(`${label} release binding ${field} does not match`);
+ }
+ }
+}
+
+function publicationAvailableAt(
+ submission: SubmissionPackage,
+ evidence: EvidenceCache,
+): number | null {
+ const smokeReference = submission.release.productionSmoke;
+ if (smokeReference === null) return null;
+ try {
+ const smoke = ProductionSmokeV6Schema.parse(
+ parseJson(
+ cachedEvidence(evidence, smokeReference, "production smoke"),
+ "production smoke",
+ ),
+ );
+ return Date.parse(smoke.completedAt);
+ } catch {
+ return null;
+ }
+}
+
+function assertPreSubmissionTimestamp(
+ checkedAt: string,
+ submission: SubmissionPackage,
+ now: Date,
+ notBefore: number | null,
+ label: string,
+ issues: string[],
+): void {
+ const checked = Date.parse(checkedAt);
+ if (notBefore !== null && checked < notBefore) {
+ issues.push(`${label} predates production release completion`);
+ }
+ if (
+ checked > now.getTime() ||
+ checked > Date.parse(submission.competition.deadline) ||
+ (submission.officialSubmission.submittedAt !== null &&
+ checked > Date.parse(submission.officialSubmission.submittedAt))
+ ) {
+ issues.push(`${label} was checked after its allowed evidence window`);
+ }
+}
+
+function assertEvidencePathPolicy(
+ submission: SubmissionPackage,
+ references: readonly EvidenceReference[],
+ issues: string[],
+): void {
+ if (
+ new Set(references.map((reference) => reference.path)).size !==
+ references.length
+ ) {
+ issues.push("each evidence role must use a distinct repository path");
+ }
+ const fixedPaths: ReadonlyArray = [
+ [submission.copy.devpostCopy, "docs/DEVPOST_COPY.md"],
+ [submission.copy.readme, "README.md"],
+ [submission.copy.license.evidence, "LICENSE"],
+ [
+ submission.publicProduct.capabilityManifest,
+ "docs/RELEASE_CAPABILITY_MANIFEST.json",
+ ],
+ [submission.video.script, "docs/DEMO_SCRIPT.md"],
+ [submission.video.captions, "docs/DEMO_CAPTIONS.vtt"],
+ [submission.impact.aggregate, "docs/LEARNER_PILOT_RESULTS.json"],
+ ];
+ for (const [reference, expected] of fixedPaths) {
+ if (reference.path !== expected) {
+ issues.push(`evidence path must be ${expected}`);
+ }
+ }
+
+ const deployment = submission.release.deploymentReceipt;
+ if (deployment === null) {
+ const unexpectedReleaseEvidence = [
+ submission.release.qualifiedRunnerReceipt,
+ submission.release.releaseCheckReceipt,
+ submission.release.productionSmoke,
+ submission.repository.accessEvidence,
+ submission.video.verificationEvidence,
+ submission.feedback.evidence,
+ submission.browserQualification.evidence,
+ submission.browserQualification.playwrightReport,
+ submission.browserQualification.evidenceIndex,
+ submission.publicLinkAudit.evidence,
+ submission.officialSubmission.receipt,
+ submission.officialSubmission.postSubmitLoggedOutEvidence,
+ ...REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS.map(
+ (key) => submission.browserQualification.manualEvidence[key],
+ ),
+ ].some((reference) => reference !== null);
+ if (
+ unexpectedReleaseEvidence ||
+ submission.assets.length > 0 ||
+ submission.browserQualification.journeyEvidence.length > 0
+ ) {
+ issues.push(
+ "release-bound evidence requires a deployment receipt before any read",
+ );
+ }
+ return;
+ }
+ const evidenceRoot = `docs/submission-evidence/${deployment.sha256}`;
+ const expectedEvidencePaths: ReadonlyArray<
+ readonly [EvidenceReference | null, string]
+ > = [
+ [
+ submission.release.qualifiedRunnerReceipt,
+ `${evidenceRoot}/qualified-runner-release.json`,
+ ],
+ [
+ submission.release.releaseCheckReceipt,
+ `${evidenceRoot}/release-check-receipt.json`,
+ ],
+ [deployment, `${evidenceRoot}/deployment-receipt.json`],
+ [
+ submission.release.productionSmoke,
+ `${evidenceRoot}/production-smoke.json`,
+ ],
+ [
+ submission.repository.accessEvidence,
+ `${evidenceRoot}/repository-access.json`,
+ ],
+ [
+ submission.video.verificationEvidence,
+ `${evidenceRoot}/video-verification.json`,
+ ],
+ [submission.feedback.evidence, `${evidenceRoot}/feedback-session.json`],
+ [
+ submission.browserQualification.evidence,
+ `${evidenceRoot}/cloakbrowser-qualification.json`,
+ ],
+ [
+ submission.browserQualification.playwrightReport,
+ `${evidenceRoot}/cloakbrowser-playwright-report.json`,
+ ],
+ [
+ submission.browserQualification.evidenceIndex,
+ `${evidenceRoot}/cloakbrowser-evidence-index.json`,
+ ],
+ [
+ submission.publicLinkAudit.evidence,
+ `${evidenceRoot}/public-link-audit.json`,
+ ],
+ [
+ submission.officialSubmission.receipt,
+ `${evidenceRoot}/devpost-submission.json`,
+ ],
+ [
+ submission.officialSubmission.postSubmitLoggedOutEvidence,
+ `${evidenceRoot}/post-submit-link-audit.json`,
+ ],
+ ];
+ for (const [reference, expected] of expectedEvidencePaths) {
+ if (reference !== null && reference.path !== expected) {
+ issues.push(`evidence path must be ${expected}`);
+ }
+ }
+ for (const [
+ index,
+ journey,
+ ] of submission.browserQualification.journeyEvidence.entries()) {
+ const sequence = String(index + 1).padStart(2, "0");
+ const expected = `${evidenceRoot}/cloakbrowser-journeys/${sequence}.json`;
+ if (journey.evidence.path !== expected) {
+ issues.push(`evidence path must be ${expected}`);
+ }
+ }
+ for (const key of REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS) {
+ const reference = submission.browserQualification.manualEvidence[key];
+ const expected = `${evidenceRoot}/cloakbrowser-manual/${key}.json`;
+ if (reference !== null && reference.path !== expected) {
+ issues.push(`evidence path must be ${expected}`);
+ }
+ }
+ const assetRoot = `docs/submission-assets/${deployment.sha256}`;
+ const imageHashes = submission.assets.map((asset) => asset.image.sha256);
+ if (new Set(imageHashes).size !== imageHashes.length) {
+ issues.push("each screenshot must have distinct image bytes");
+ }
+ for (const asset of submission.assets) {
+ const imagePath = `${assetRoot}/${asset.fileName}`;
+ if (asset.image.path !== imagePath) {
+ issues.push(`screenshot image path must be ${imagePath}`);
+ }
+ if (asset.provenance.path !== `${imagePath}.json`) {
+ issues.push(`screenshot provenance path must be ${imagePath}.json`);
+ }
+ }
+}
+
+function assertLinkSet(
+ links: ReadonlyArray<{
+ role: "judge" | "repository" | "video" | "devpost";
+ url: string;
+ finalUrl: string;
+ }>,
+ expected: ReadonlyMap,
+ label: string,
+ issues: string[],
+): void {
+ const observed = new Map(
+ links.map((link) => [link.role, link]),
+ );
+ if (observed.size !== expected.size) {
+ issues.push(`${label} does not contain each required link exactly once`);
+ return;
+ }
+ for (const [role, url] of expected) {
+ const link = observed.get(role);
+ if (link?.url !== url) {
+ issues.push(`${label} ${role} URL does not match`);
+ } else if (!finalDestinationMatches(link.role, link.url, link.finalUrl)) {
+ issues.push(`${label} ${role} final destination does not match`);
+ }
+ }
+}
+
+function assertPublicationEvidence(
+ submission: SubmissionPackage,
+ evidence: EvidenceCache,
+ issues: string[],
+ now: Date,
+): void {
+ const expectedRelease = expectedPublicationRelease(submission);
+ const notBefore = publicationAvailableAt(submission, evidence);
+ const repository = parseEvidenceReceipt(
+ RepositoryAccessReceiptSchema,
+ submission.repository.accessEvidence,
+ "repository access",
+ evidence,
+ issues,
+ );
+ if (repository !== null) {
+ assertPublicationRelease(
+ repository.release,
+ expectedRelease,
+ "repository access",
+ issues,
+ );
+ assertPreSubmissionTimestamp(
+ repository.checkedAt,
+ submission,
+ now,
+ notBefore,
+ "repository access",
+ issues,
+ );
+ const addresses = [...repository.sharedJudgeAddresses].sort();
+ const expectedAddresses = [
+ ...submission.repository.privateJudgeAddresses,
+ ].sort();
+ if (
+ repository.repositoryUrl !== submission.repository.url ||
+ repository.finalUrl !== submission.repository.url ||
+ repository.accessMode !== submission.repository.accessMode ||
+ JSON.stringify(addresses) !== JSON.stringify(expectedAddresses) ||
+ !repository.judgeAccessVerified ||
+ (repository.accessMode === "PUBLIC" &&
+ (!repository.anonymousAccessible ||
+ repository.loggedOutStatusCode !== 200)) ||
+ (repository.accessMode === "PRIVATE_SHARED" &&
+ (repository.anonymousAccessible ||
+ repository.loggedOutStatusCode === 200))
+ ) {
+ issues.push("repository access receipt does not match the package");
+ }
+ }
+
+ const video = parseEvidenceReceipt(
+ VideoVerificationReceiptSchema,
+ submission.video.verificationEvidence,
+ "video verification",
+ evidence,
+ issues,
+ );
+ if (video !== null) {
+ assertPublicationRelease(
+ video.release,
+ expectedRelease,
+ "video verification",
+ issues,
+ );
+ assertPreSubmissionTimestamp(
+ video.checkedAt,
+ submission,
+ now,
+ notBefore,
+ "video verification",
+ issues,
+ );
+ if (
+ video.publicUrl !== submission.video.publicUrl ||
+ video.durationSeconds !== submission.video.durationSeconds ||
+ video.scriptSha256 !== submission.video.script.sha256 ||
+ video.captionsSha256 !== submission.video.captions.sha256
+ ) {
+ issues.push("video verification receipt does not match the package");
+ }
+ }
+
+ const feedback = parseEvidenceReceipt(
+ FeedbackSessionReceiptSchema,
+ submission.feedback.evidence,
+ "feedback session",
+ evidence,
+ issues,
+ );
+ if (feedback !== null) {
+ assertPublicationRelease(
+ feedback.release,
+ expectedRelease,
+ "feedback session",
+ issues,
+ );
+ assertPreSubmissionTimestamp(
+ feedback.checkedAt,
+ submission,
+ now,
+ notBefore,
+ "feedback session",
+ issues,
+ );
+ if (feedback.sessionId !== submission.feedback.sessionId) {
+ issues.push("feedback Session ID does not match the package");
+ }
+ }
+
+ const verifiedJourneyEvidence = new Map<
+ string,
+ {
+ reference: EvidenceReference;
+ receipt: z.infer;
+ }
+ >();
+ for (const entry of submission.browserQualification.journeyEvidence) {
+ const receipt = parseEvidenceReceipt(
+ CloakJourneyEvidenceReceiptSchema,
+ entry.evidence,
+ `CloakBrowser journey ${entry.id}`,
+ evidence,
+ issues,
+ );
+ if (receipt === null) continue;
+ assertPublicationRelease(
+ receipt.release,
+ expectedRelease,
+ `CloakBrowser journey ${entry.id}`,
+ issues,
+ );
+ assertPreSubmissionTimestamp(
+ receipt.checkedAt,
+ submission,
+ now,
+ notBefore,
+ `CloakBrowser journey ${entry.id}`,
+ issues,
+ );
+ if (receipt.id !== entry.id || verifiedJourneyEvidence.has(entry.id)) {
+ issues.push("CloakBrowser journey evidence identity does not match");
+ continue;
+ }
+ verifiedJourneyEvidence.set(entry.id, {
+ reference: entry.evidence,
+ receipt,
+ });
+ }
+
+ const verifiedManualEvidence = new Map<
+ (typeof REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS)[number],
+ {
+ reference: EvidenceReference;
+ receipt: z.infer;
+ }
+ >();
+ for (const key of REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS) {
+ const reference = submission.browserQualification.manualEvidence[key];
+ const receipt = parseEvidenceReceipt(
+ CloakManualEvidenceReceiptSchema,
+ reference,
+ `CloakBrowser manual evidence ${key}`,
+ evidence,
+ issues,
+ );
+ if (reference === null || receipt === null) continue;
+ assertPublicationRelease(
+ receipt.release,
+ expectedRelease,
+ `CloakBrowser manual evidence ${key}`,
+ issues,
+ );
+ assertPreSubmissionTimestamp(
+ receipt.checkedAt,
+ submission,
+ now,
+ notBefore,
+ `CloakBrowser manual evidence ${key}`,
+ issues,
+ );
+ if (receipt.check !== key) {
+ issues.push(
+ `CloakBrowser manual evidence ${key} identity does not match`,
+ );
+ continue;
+ }
+ verifiedManualEvidence.set(key, { reference, receipt });
+ }
+
+ const browserReportReference =
+ submission.browserQualification.playwrightReport;
+ const browserIndexReference = submission.browserQualification.evidenceIndex;
+ const browserReport = parseEvidenceReceipt(
+ CloakBrowserExecutionReportSchema,
+ browserReportReference,
+ "CloakBrowser execution report",
+ evidence,
+ issues,
+ );
+ const browserIndex = parseEvidenceReceipt(
+ CloakBrowserEvidenceIndexSchema,
+ browserIndexReference,
+ "CloakBrowser evidence index",
+ evidence,
+ issues,
+ );
+ const browser = parseEvidenceReceipt(
+ CloakBrowserQualificationReceiptSchema,
+ submission.browserQualification.evidence,
+ "CloakBrowser qualification",
+ evidence,
+ issues,
+ );
+ if (browserReport !== null) {
+ assertPublicationRelease(
+ browserReport.release,
+ expectedRelease,
+ "CloakBrowser execution report",
+ issues,
+ );
+ assertPreSubmissionTimestamp(
+ browserReport.checkedAt,
+ submission,
+ now,
+ notBefore,
+ "CloakBrowser execution report",
+ issues,
+ );
+ for (const journey of browserReport.journeys) {
+ const verified = verifiedJourneyEvidence.get(journey.id);
+ if (
+ verified === undefined ||
+ verified.reference.sha256 !== journey.evidenceSha256 ||
+ verified.receipt.viewport !== journey.viewport ||
+ verified.receipt.journeyStatus !== journey.status ||
+ verified.receipt.attempt !== journey.attempt ||
+ verified.receipt.durationMs !== journey.durationMs ||
+ Date.parse(verified.receipt.checkedAt) >
+ Date.parse(browserReport.checkedAt)
+ ) {
+ issues.push(
+ `CloakBrowser journey ${journey.id} does not match its report`,
+ );
+ }
+ }
+ }
+ if (browserIndex !== null) {
+ assertPublicationRelease(
+ browserIndex.release,
+ expectedRelease,
+ "CloakBrowser evidence index",
+ issues,
+ );
+ assertPreSubmissionTimestamp(
+ browserIndex.checkedAt,
+ submission,
+ now,
+ notBefore,
+ "CloakBrowser evidence index",
+ issues,
+ );
+ const manualHashes = Object.values(browserIndex.manual).map(
+ (manual) => manual.evidenceSha256,
+ );
+ if (new Set(manualHashes).size !== manualHashes.length) {
+ issues.push("CloakBrowser manual evidence hashes must be unique");
+ }
+ if (
+ browserReport !== null &&
+ new Set([
+ ...manualHashes,
+ ...browserReport.journeys.map((journey) => journey.evidenceSha256),
+ ]).size !==
+ manualHashes.length + browserReport.journeys.length
+ ) {
+ issues.push("CloakBrowser evidence roles must use distinct bytes");
+ }
+ for (const key of REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS) {
+ const verified = verifiedManualEvidence.get(key);
+ if (
+ verified === undefined ||
+ browserIndex.manual[key].evidenceSha256 !== verified.reference.sha256 ||
+ Date.parse(verified.receipt.checkedAt) >
+ Date.parse(browserIndex.checkedAt)
+ ) {
+ issues.push(
+ `CloakBrowser manual evidence ${key} does not match its index`,
+ );
+ }
+ }
+ }
+ if (browser !== null) {
+ assertPublicationRelease(
+ browser.release,
+ expectedRelease,
+ "CloakBrowser qualification",
+ issues,
+ );
+ assertPreSubmissionTimestamp(
+ browser.checkedAt,
+ submission,
+ now,
+ notBefore,
+ "CloakBrowser qualification",
+ issues,
+ );
+ const manifest = submission.browserQualification;
+ if (
+ browser.authority !== manifest.authority ||
+ browser.exactReleaseBound !== manifest.exactReleaseBound ||
+ browser.desktopComplete !== manifest.desktopComplete ||
+ browser.mobileComplete !== manifest.mobileComplete ||
+ browser.requiredSkips !== manifest.requiredSkips ||
+ browser.failures !== manifest.failures ||
+ browser.consoleErrors !== manifest.consoleErrors ||
+ browser.failedRequests !== manifest.failedRequests ||
+ browserReportReference === null ||
+ browserIndexReference === null ||
+ browser.playwrightReportSha256 !== browserReportReference.sha256 ||
+ browser.browserEvidenceIndexSha256 !== browserIndexReference.sha256 ||
+ verifiedJourneyEvidence.size !== browser.journeyCount ||
+ verifiedManualEvidence.size !== REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS.length
+ ) {
+ issues.push("CloakBrowser receipt does not match the package");
+ }
+ if (
+ browserReport !== null &&
+ (browser.browserVersion !== browserReport.browserVersion ||
+ browser.playwrightVersion !== browserReport.playwrightVersion ||
+ browser.failures !== browserReport.failures ||
+ browser.requiredSkips !== browserReport.skips ||
+ browser.consoleErrors !== browserReport.consoleErrors ||
+ browser.failedRequests !== browserReport.failedRequests ||
+ JSON.stringify(browser.journeys) !==
+ JSON.stringify(browserReport.journeys) ||
+ Date.parse(browserReport.checkedAt) > Date.parse(browser.checkedAt))
+ ) {
+ issues.push("CloakBrowser execution report does not match its receipt");
+ }
+ if (
+ browserIndex !== null &&
+ (browserReportReference === null ||
+ browserIndex.executionReportSha256 !== browserReportReference.sha256 ||
+ (browserReport !== null &&
+ Date.parse(browserIndex.checkedAt) <
+ Date.parse(browserReport.checkedAt)) ||
+ Date.parse(browserIndex.checkedAt) > Date.parse(browser.checkedAt))
+ ) {
+ issues.push("CloakBrowser evidence index does not match its receipt");
+ }
+ }
+
+ const linkAudit = parseEvidenceReceipt(
+ PublicLinkAuditReceiptSchema,
+ submission.publicLinkAudit.evidence,
+ "public-link audit",
+ evidence,
+ issues,
+ );
+ if (linkAudit !== null) {
+ assertPublicationRelease(
+ linkAudit.release,
+ expectedRelease,
+ "public-link audit",
+ issues,
+ );
+ assertPreSubmissionTimestamp(
+ linkAudit.checkedAt,
+ submission,
+ now,
+ notBefore,
+ "public-link audit",
+ issues,
+ );
+ if (
+ linkAudit.checkedAt !== submission.publicLinkAudit.checkedAt ||
+ linkAudit.loggedOut !== submission.publicLinkAudit.loggedOut ||
+ submission.video.publicUrl === null
+ ) {
+ issues.push("public-link audit does not match the package");
+ } else {
+ assertLinkSet(
+ linkAudit.links,
+ new Map([
+ ["judge", submission.publicProduct.judgeUrl],
+ ["repository", submission.repository.url],
+ ["video", submission.video.publicUrl],
+ ]),
+ "public-link audit",
+ issues,
+ );
+ }
+ }
+
+ const firstImageSha256 = submission.assets.find(
+ (asset) => asset.fileName === EXPECTED_ASSETS[0],
+ )?.image.sha256;
+ for (const asset of submission.assets) {
+ const provenance = parseEvidenceReceipt(
+ ScreenshotProvenanceReceiptSchema,
+ asset.provenance,
+ `screenshot provenance ${asset.fileName}`,
+ evidence,
+ issues,
+ );
+ if (provenance === null) continue;
+ assertPublicationRelease(
+ provenance.release,
+ expectedRelease,
+ `screenshot ${asset.fileName}`,
+ issues,
+ );
+ assertPreSubmissionTimestamp(
+ provenance.checkedAt,
+ submission,
+ now,
+ notBefore,
+ `screenshot ${asset.fileName}`,
+ issues,
+ );
+ assertPreSubmissionTimestamp(
+ provenance.capturedAt,
+ submission,
+ now,
+ notBefore,
+ `screenshot ${asset.fileName} capture`,
+ issues,
+ );
+ try {
+ const dimensions = readPngDimensions(
+ cachedEvidence(evidence, asset.image, `screenshot ${asset.fileName}`),
+ );
+ const expectedPrimary = asset.fileName !== EXPECTED_ASSETS[4];
+ if (
+ dimensions.width !== provenance.viewport.width ||
+ dimensions.height !== provenance.viewport.height ||
+ (expectedPrimary &&
+ (dimensions.width !== 1_440 || dimensions.height !== 900)) ||
+ (!expectedPrimary &&
+ (dimensions.width > 1_440 || dimensions.height > 900))
+ ) {
+ issues.push(`screenshot ${asset.fileName} dimensions do not match`);
+ }
+ } catch {
+ issues.push(`screenshot ${asset.fileName} is not a valid PNG`);
+ }
+ const publicUrl = new URL(provenance.publicUrl);
+ if (
+ provenance.fileName !== asset.fileName ||
+ provenance.imageSha256 !== asset.image.sha256 ||
+ publicUrl.origin !== new URL(submission.publicProduct.judgeUrl).origin ||
+ `${publicUrl.pathname}${publicUrl.search}` !== provenance.route ||
+ Date.parse(provenance.capturedAt) > Date.parse(provenance.checkedAt) ||
+ (asset.fileName !== EXPECTED_ASSETS[4] &&
+ provenance.derivedFromSha256 !== null) ||
+ (asset.fileName === EXPECTED_ASSETS[4] &&
+ provenance.derivedFromSha256 !== firstImageSha256)
+ ) {
+ issues.push(`screenshot ${asset.fileName} provenance does not match`);
+ }
+ }
+}
+
+function assertSubmittedEvidence(
+ submission: SubmissionPackage,
+ evidence: EvidenceCache,
+ issues: string[],
+ now: Date,
+): void {
+ const expectedRelease = expectedPublicationRelease(submission);
+ const notBefore = publicationAvailableAt(submission, evidence);
+ const official = parseEvidenceReceipt(
+ DevpostSubmissionReceiptSchema,
+ submission.officialSubmission.receipt,
+ "Devpost submission",
+ evidence,
+ issues,
+ );
+ if (official === null) return;
+ assertPublicationRelease(
+ official.release,
+ expectedRelease,
+ "Devpost submission",
+ issues,
+ );
+ const manifest = submission.officialSubmission;
+ let officialUrl: URL | null = null;
+ try {
+ officialUrl = new URL(official.publicUrl);
+ } catch {
+ issues.push("Devpost submission URL is invalid");
+ }
+ if (
+ official.hackathonId !== submission.competition.hackathonId ||
+ official.projectId !== submission.competition.projectId ||
+ official.category !== submission.competition.category ||
+ official.publicSlug !== manifest.publicSlug ||
+ official.submittedAt !== manifest.submittedAt ||
+ official.copy.devpostCopySha256 !== submission.copy.devpostCopy.sha256 ||
+ official.copy.readmeSha256 !== submission.copy.readme.sha256 ||
+ official.copy.videoScriptSha256 !== submission.video.script.sha256 ||
+ official.copy.captionsSha256 !== submission.video.captions.sha256 ||
+ (notBefore !== null && Date.parse(official.submittedAt) < notBefore) ||
+ Date.parse(official.submittedAt) > Date.parse(official.capturedAt) ||
+ Date.parse(official.capturedAt) > now.getTime() ||
+ officialUrl === null ||
+ officialUrl.username !== "" ||
+ officialUrl.password !== "" ||
+ !/^(?:www\.)?devpost\.com$/u.test(officialUrl.hostname) ||
+ officialUrl.pathname.replace(/\/$/u, "") !==
+ `/software/${official.publicSlug}` ||
+ officialUrl.search !== "" ||
+ officialUrl.hash !== ""
+ ) {
+ issues.push("Devpost submission receipt does not match the package");
+ }
+
+ const postAudit = parseEvidenceReceipt(
+ PostSubmitLinkAuditReceiptSchema,
+ manifest.postSubmitLoggedOutEvidence,
+ "post-submit link audit",
+ evidence,
+ issues,
+ );
+ if (postAudit === null || submission.video.publicUrl === null) return;
+ assertPublicationRelease(
+ postAudit.release,
+ expectedRelease,
+ "post-submit link audit",
+ issues,
+ );
+ if (
+ (notBefore !== null && Date.parse(postAudit.checkedAt) < notBefore) ||
+ Date.parse(postAudit.checkedAt) < Date.parse(official.submittedAt) ||
+ Date.parse(postAudit.checkedAt) > now.getTime()
+ ) {
+ issues.push("post-submit link audit timestamp is invalid");
+ }
+ assertLinkSet(
+ postAudit.links,
+ new Map([
+ ["judge", submission.publicProduct.judgeUrl],
+ ["repository", submission.repository.url],
+ ["video", submission.video.publicUrl],
+ ["devpost", official.publicUrl],
+ ]),
+ "post-submit link audit",
+ issues,
+ );
+}
+
export async function validateSubmissionPackage(
input: unknown,
mode: SubmissionValidationMode,
@@ -1087,8 +2038,14 @@ export async function validateSubmissionPackage(
assertNoPlaceholderStrings(input);
const submission = SubmissionPackageSchema.parse(input);
const references = collectEvidenceReferences(submission);
+ const issues: string[] = [];
+ assertEvidencePathPolicy(submission, references, issues);
+ if (issues.length > 0) throw new SubmissionValidationError(issues);
const seen = new Map();
const evidence = new Map();
+ const screenshotPaths = new Set(
+ submission.assets.map((asset) => asset.image.path),
+ );
for (const reference of references) {
const previous = seen.get(reference.path);
if (previous !== undefined && previous !== reference.sha256) {
@@ -1099,6 +2056,12 @@ export async function validateSubmissionPackage(
if (previous !== undefined) continue;
seen.set(reference.path, reference.sha256);
const value = new Uint8Array(await readEvidence(reference));
+ const maximumBytes = screenshotPaths.has(reference.path)
+ ? MAX_SCREENSHOT_BYTES
+ : MAX_TEXT_EVIDENCE_BYTES;
+ if (value.byteLength > maximumBytes) {
+ throw new Error(`evidence exceeds its size limit: ${reference.path}`);
+ }
if (sha256(value) !== reference.sha256) {
throw new Error(`evidence hash mismatch: ${reference.path}`);
}
@@ -1108,7 +2071,6 @@ export async function validateSubmissionPackage(
const now = options.now ?? new Date();
if (Number.isNaN(now.getTime()))
throw new Error("validation time is invalid");
- const issues: string[] = [];
const expectedState = {
draft: "DRAFT",
ready: "READY_TO_SUBMIT",
@@ -1142,6 +2104,7 @@ export async function validateSubmissionPackage(
issues,
now,
);
+ assertPublicationEvidence(submission, evidence, issues, now);
}
await assertImpactEvidence(
submission,
@@ -1170,13 +2133,14 @@ export async function validateSubmissionPackage(
"submitted package requires slug, timestamp, receipt, and logged-out proof",
);
} else if (
- Date.parse(official.submittedAt) >
+ Date.parse(official.submittedAt) >=
Date.parse(submission.competition.deadline)
) {
- issues.push("official submission timestamp is after the deadline");
+ issues.push("official submission timestamp is at or after the deadline");
} else if (Date.parse(official.submittedAt) > now.getTime()) {
issues.push("official submission timestamp is in the future");
}
+ assertSubmittedEvidence(submission, evidence, issues, now);
}
if (issues.length > 0) throw new SubmissionValidationError(issues);
return submission;
@@ -1191,6 +2155,13 @@ async function repositoryEvidenceReader(
reference.path,
"submission evidence",
);
+ const file = await stat(path);
+ const maximumBytes = reference.path.endsWith(".png")
+ ? MAX_SCREENSHOT_BYTES
+ : MAX_TEXT_EVIDENCE_BYTES;
+ if (!file.isFile() || file.size > maximumBytes) {
+ throw new Error(`submission evidence file is invalid: ${reference.path}`);
+ }
return new Uint8Array(await readFile(path));
}
From eee333a9522a8efad8134fa543137c8f4691860e Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 12:57:11 +0530
Subject: [PATCH 084/143] docs(progress): record publication evidence gate
---
docs/PROGRESS.md | 105 +++++++++++++++++++++++++++++++++++++++++------
1 file changed, 93 insertions(+), 12 deletions(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 9c44a79d..c5548041 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -12,18 +12,22 @@ pass is never presented as a browser or production pass.
### Current checkpoint
- **Branch:** `feat/learner-ux-v6.1`
-- **Latest committed checkpoint:** `3174a1f997a40f471ed9b8a20e147a0ce279ee15`.
- The worktree contains only the preserved, untracked
- `scripts/.fuse_hidden0000a9600000801f`. The branch now includes the
- prompt-first Landing and Judge hierarchy, source-covered P0/P1 learner
- mechanisms, exact runtime qualification contracts, a fail-closed submission
- preflight foundation, and stored Proof Bundle validation bound to the
- session, artifact, mode, state, HMAC policy, and v2 scientific snapshot.
- The latest complete web run passes **79 files and 626/626 tests**; focused
- proof tests, web and Worker TypeScript, the production build, scoped
- formatting, and whitespace checks also pass at this checkpoint. These local
- results do not constitute a real cgroup sentinel, a refreshed scientific
- release, CloakBrowser qualification, deployment, learner observation,
+- **Latest product and release-safety checkpoint:**
+ `8e33154b2b130fbdb635cf7a91ed75dc9357741f`. The worktree contains only the
+ preserved, untracked `scripts/.fuse_hidden0000a9600000801f` before this
+ documentation-only ledger update. The branch is one local commit ahead of
+ `origin/feat/learner-ux-v6.1`. In addition to the prompt-first learner flow,
+ source-covered P0/P1 mechanisms, exact runtime contracts, and stored Proof
+ Bundle validation, the branch now has a strict fail-closed submission chain:
+ immutable evidence bytes, qualified-runner -> release-check -> deployment ->
+ production-smoke bindings, typed publication receipts, exact browser journey
+ and manual evidence references, structural PNG decoding, canonical public
+ link checks, and submitted-state verification. Focused submission validation
+ passes **34/34 tests**; repository TypeScript, scoped Prettier, Git whitespace,
+ the four-file secret scan, and the real checked-in `DRAFT` preflight pass.
+ The latest complete web run remains **79 files and 626/626 tests**. These
+ local results do not constitute a real cgroup sentinel, refreshed scientific
+ evidence, CloakBrowser execution, Cloudflare deployment, learner observation,
Devpost publication, or a submission receipt.
- **Earlier product checkpoint:**
`5259a495948a68024702ba5239bf8057d1785f08`. The owner partitioned the
@@ -3839,3 +3843,80 @@ feature push and verified fast-forward merge to main NOT RUN
The release remains **NO-GO**. The next locally actionable slice is the strict
submission-evidence validator; it can be completed without pretending that any
external deployment or submission evidence exists.
+
+## Publication-evidence validation checkpoint — 2026-07-21T07:25:42Z
+
+Deadline: `2026-07-22T00:00:00Z`; **16 hours 34 minutes 18 seconds remained at
+this checkpoint**. The feature branch is at committed product and release-safety
+checkpoint `8e33154b2b130fbdb635cf7a91ed75dc9357741f`. It was one commit ahead of
+`origin/feat/learner-ux-v6.1` before this documentation-only ledger update. The
+only other worktree entry is the preserved, unstaged FUSE tombstone
+`scripts/.fuse_hidden0000a9600000801f`; it remains untouched.
+
+### Completed since the previous reconciliation
+
+| Slice | Status | Exact evidence |
+| --- | --- | --- |
+| Verified submission bytes | **DONE** | `9ea8484` verifies referenced evidence bytes before accepting their claims. |
+| Exact release-evidence chain | **DONE** | `07c9091` binds qualified runner, release check, deployment, production smoke, and publication evidence to one source/image/Worker tuple. |
+| Stored Proof Bundle authority | **DONE** | `3174a1f` validates stored Proof Bundles at Worker and client boundaries and prevents cross-session, cross-artifact, mode, state, and signing-policy substitution. |
+| Publication-evidence validator | **DONE** | `8e33154` adds strict typed receipts for repository access, video, feedback, browser qualification, all current Playwright journeys, seven required viewports, ten manual checks, public links, screenshots, Devpost submission, and post-submit audit. It validates evidence paths before reads, hashes every referenced byte once, structurally decodes PNGs, and rejects missing, reused, stale, oversized, malformed, or tuple-mismatched evidence. |
+| Focused publication verification | **PASS** | Vitest **1 file, 34/34 tests passed**; repository TypeScript passed; scoped Prettier passed; Git whitespace passed; secret scan passed across the four explicit files; the checked-in real package passed only the `DRAFT` preflight. |
+| Complete web regression | **PASS at latest complete run** | Web Vitest **79 files, 626/626 tests passed**. No newer product code changed after that complete run. |
+| Real publication and submission | **NOT DONE** | `docs/SUBMISSION_PACKAGE.json` remains `DRAFT`; no deployment, browser, video, learner, Devpost, or submission receipt is inferred from validator fixtures. |
+
+### Exact work left, in release order
+
+1. **Clean and freeze source.** Resolve the preserved FUSE tombstone without
+ deleting it, obtain a genuinely clean physical tree, and freeze one exact
+ source commit. The tombstone is not release evidence and must not be staged.
+2. **Qualify one exact runner image.** Execute the real aggregate-cgroup
+ sentinel and all containment/negative controls, build the image once, and
+ record the immutable image digest. The current repository-only filesystem
+ constitution blocks the sentinel's required `/sys/fs/cgroup` and owned
+ `/proc//stat` reads; this is unresolved authority, not a passing test.
+3. **Regenerate deterministic release evidence.** From only that frozen
+ source/image tuple, regenerate SBOM, VEX, scientific-engine, license,
+ health, held-out, Sample Capsule, and qualification evidence. Repair the
+ three stale root Vitest binding failures by updating genuine evidence, never
+ by weakening their assertions.
+4. **Authenticate and deploy through the contained Cloudflare path.** Verify
+ the intended account, run migrations and the full release gate, deploy only
+ the qualified tuple, and record exact source commit, runner image digest,
+ Worker version/deployment ID, public identity, and production-smoke receipt.
+ The last contained Wrangler check was logged out; deployment has not run.
+5. **Qualify the public browser experience.** Run the exact current **40-test**
+ public Playwright registry plus the manual accessibility matrix through
+ CloakBrowser at **375x812, 390x844, 768x1024, 1280x720, 1366x768,
+ 1440x900, and 1920x1080**. Include keyboard, focus restoration, screen-reader
+ names, 200% zoom, reduced motion, long content, narrow visualizations,
+ 44-pixel targets, console/network failures, downloads, reconnect/history,
+ and Web Vitals. Current qualifying execution remains **0 journeys** because
+ no `CLOAK_CDP_ENDPOINT` is available.
+6. **Finish truthful submission evidence.** Capture screenshots and the public
+ video only from the exact qualified tuple; verify repository, Judge,
+ feedback, video, and Devpost links logged out; populate the Devpost draft;
+ retain the public slug and `submitted_at` receipt. Learner evidence remains
+ `NO_DATA` unless real consented observations occur.
+7. **Publish and integrate only after the gates pass.** Push the reviewed
+ feature tip, verify the remote, then fast-forward-only merge and push
+ `main`. No current deployment, submission, feature push, or final merge is
+ claimed.
+
+### Current release verdict
+
+```text
+learner product source + focused regressions PASS
+submission/publication validator source PASS
+complete deterministic release evidence FAIL (3 stale bindings)
+clean source + exact image + real containment sentinel BLOCKED
+qualified Cloudflare deployment NOT RUN
+CloakBrowser public qualification NOT RUN (0 journeys)
+screenshots/video/learner evidence/Devpost receipt NOT READY
+feature push and verified fast-forward merge to main NOT RUN
+```
+
+The release remains **NO-GO**. The validator is ready to reject invented or
+misbound publication evidence; the evidence itself must now be produced by a
+clean exact-image qualification, qualified deployment, public CloakBrowser
+run, and real submission workflow.
From 8e9e336e96680fdf4acca813a2cc4cfe7ca064b6 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 13:29:22 +0530
Subject: [PATCH 085/143] docs(release): refresh verification graph
---
docs/PROGRESS.md | 125 +++++++++++++++---
.../19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md | 68 ++++++----
2 files changed, 147 insertions(+), 46 deletions(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index c5548041..91edffb1 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -12,23 +12,23 @@ pass is never presented as a browser or production pass.
### Current checkpoint
- **Branch:** `feat/learner-ux-v6.1`
-- **Latest product and release-safety checkpoint:**
- `8e33154b2b130fbdb635cf7a91ed75dc9357741f`. The worktree contains only the
- preserved, untracked `scripts/.fuse_hidden0000a9600000801f` before this
- documentation-only ledger update. The branch is one local commit ahead of
- `origin/feat/learner-ux-v6.1`. In addition to the prompt-first learner flow,
- source-covered P0/P1 mechanisms, exact runtime contracts, and stored Proof
- Bundle validation, the branch now has a strict fail-closed submission chain:
- immutable evidence bytes, qualified-runner -> release-check -> deployment ->
- production-smoke bindings, typed publication receipts, exact browser journey
- and manual evidence references, structural PNG decoding, canonical public
- link checks, and submitted-state verification. Focused submission validation
- passes **34/34 tests**; repository TypeScript, scoped Prettier, Git whitespace,
- the four-file secret scan, and the real checked-in `DRAFT` preflight pass.
- The latest complete web run remains **79 files and 626/626 tests**. These
- local results do not constitute a real cgroup sentinel, refreshed scientific
- evidence, CloakBrowser execution, Cloudflare deployment, learner observation,
- Devpost publication, or a submission receipt.
+- **Latest committed checkpoint:**
+ `eee333a9522a8efad8134fa543137c8f4691860e`; latest product and release-safety
+ code checkpoint `8e33154b2b130fbdb635cf7a91ed75dc9357741f`. Before this ledger update, the
+ branch is two commits ahead of `origin/feat/learner-ux-v6.1` and the worktree
+ contains only the preserved, untracked
+ `scripts/.fuse_hidden0000a9600000801f`. Fresh verification on this tip passes
+ web Vitest **79 files and 626/626 tests**, repository/web/Worker/E2E strict
+ TypeScript, the production build, Python behavior **331/331** when its one
+ localhost test receives loopback permission, leakage mutations **14/14**,
+ imbalance mutations **19/19**, held-out and generated Sample fixture checks,
+ repository-wide Prettier, Git whitespace, and a 31-file built-artifact secret
+ scan. Root Vitest is **750 passed, 3 stale exact-image/scientific-evidence
+ failures, 2 skipped**. A fresh explicit stock-Chromium design review is **35
+ passed, 5 exact-public/live skips, 0 failed across 40 tests in 3.9 minutes**.
+ These local results do not constitute a real cgroup sentinel, refreshed
+ scientific evidence, CloakBrowser execution, Cloudflare deployment, learner
+ observation, Devpost publication, or a submission receipt.
- **Earlier product checkpoint:**
`5259a495948a68024702ba5239bf8057d1785f08`. The owner partitioned the
preserved integration delta into nine commits and fast-forwarded both
@@ -3920,3 +3920,94 @@ The release remains **NO-GO**. The validator is ready to reject invented or
misbound publication evidence; the evidence itself must now be produced by a
clean exact-image qualification, qualified deployment, public CloakBrowser
run, and real submission workflow.
+
+## Fresh deterministic and browser checkpoint — 2026-07-21T07:56:31Z
+
+Deadline: `2026-07-22T00:00:00Z`; **16 hours 3 minutes 29 seconds remained at
+this checkpoint**. Committed HEAD is
+`eee333a9522a8efad8134fa543137c8f4691860e`, two commits ahead of the remote
+feature branch before this documentation-only update. The only worktree entry
+is the preserved, unstaged FUSE tombstone; all test/build output is ignored and
+repository-contained.
+
+### Fresh exact results
+
+- Web Vitest: **79 files, 626/626 passed**.
+- Capability-enabled root Vitest: **71 files total; 69 passed, 2 failed; 750
+ passed, 3 failed, 2 skipped**. The three persistent failures are precisely
+ the stale exact-image reachability/VEX and scientific integrity bindings.
+ Twenty-four sandbox-only socket/process/namespace failures from an initial
+ restricted run disappeared under the required host capabilities.
+- Python kernel, runner, and release-tooling matrix: **330/331 passed** in the
+ restricted run; the sole localhost kernel-service test then passed **1/1**
+ with loopback permission. No Python behavior remains failing.
+- Repository, web, Worker, and E2E strict TypeScript: **passed**.
+- Production build: **passed**. Worker **1,867.21 kB / 360.17 kB gzip**; main
+ client **449.21 kB / 129.19 kB gzip**; CSS **201.41 kB / 34.82 kB gzip**.
+ Built artifacts passed a **31-file** secret scan.
+- Critical mutations: leakage **14/14** and imbalance **19/19 detected**.
+- Held-out: intake/routing **10/10**, fixed full-loop completion **7/8**, and
+ tracked evidence matched.
+- Sample Boundary: verified **25 cells**, result hash
+ `c948d25fef6ab5f5f33322dde2025557f0704f01bff2bde48ee93149066ccbd3`.
+- Sample Proof Capsule: verified root
+ `e45cf88f1bbdd470723f83def9550d47ac272f7c5aaf8554bec52c2a34f9856b`,
+ **94,236 bytes**.
+- Repository-wide Prettier and Git whitespace: **passed**.
+- Fresh local stock-browser matrix: **35 passed, 5 exact-public/live skips, 0
+ failed across 40 tests in 3.9 minutes**. The JSON report records expected 35,
+ skipped 5, unexpected 0, flaky 0, errors 0. Evidence is retained under
+ `apps/web/test-results/runtime/full-stock-20260721-2/` and explicitly labels
+ browser authority `stock-chromium-design-review`.
+- First-fold stock measurements: Landing desktop LCP **1,208 ms**, CLS **0**;
+ Landing mobile LCP **1,348 ms**, CLS **0.0027**; Judge desktop LCP **2,428
+ ms**, CLS **0.0008**; Judge mobile LCP **2,284 ms**, CLS **0.0214**. These are
+ warm local measurements, not public Web Vitals.
+
+### Blocker movement
+
+- **Clean source now has a reviewed path, not a completed gate.** A linked
+ worktree at an ignored descendant of `.cache/counterlab-release-worktrees/`
+ can check out exact HEAD without copying or hiding the primary FUSE entry.
+ It must bootstrap its own repository-contained pnpm graph, copied Python
+ interpreter, pinned runtime toolchain, and fresh contained runtime. At least
+ 20 GB should be reserved. The worktree has not been created because the real
+ cgroup authority remains blocked and a premature bootstrap would not advance
+ the release.
+- **Scientific failures are fully mapped.** The canonical repair is one full
+ `refresh-source-bound-scientific-evidence.sh` execution from a new build
+ receipt v4, exact OCI image/manifest digest, active contained runtime, pinned
+ Syft/Grype inputs, and genuine exact-image reachability probe. A partial
+ source-only binding refresh is prohibited.
+- **Contained Cloudflare auth remains red.** `wrangler whoami --json` returned
+ `{"loggedIn":false}`. A fresh repository-contained OAuth flow started and
+ timed out waiting for consent; it stored no usable credential and performed
+ no cloud mutation. Configured account ID
+ `9b0a1524e478000ec9b3ff2da6104d81` remains configuration, not authenticated
+ identity evidence.
+- **Real cgroup sentinel remains blocked by explicit filesystem authority.** It
+ needs read-only `/sys/fs/cgroup` and owned test-process
+ `/proc//stat` observations. No such reads were attempted under the
+ current constitution; no aggregate containment pass is claimed.
+- **CloakBrowser remains unavailable.** Qualifying public execution is still
+ **0 journeys**. The fresh stock run cannot close this gate.
+
+### Next executable sequence
+
+1. Receive explicit read-only sentinel authority and complete a new contained
+ Wrangler OAuth window.
+2. Create the reviewed in-repository linked release worktree at exact HEAD;
+ bootstrap and verify its independent contained dependencies/runtime.
+3. Build one exact image, run the genuine sentinel and negative controls,
+ refresh all release evidence once, review the allowlisted delta, and commit
+ evidence independently.
+4. Rerun the complete deterministic release check; all three stale-binding
+ tests must become green from genuine evidence.
+5. Preflight CloakBrowser, qualify and deploy only the exact tuple, execute
+ production smoke and all public browser/manual accessibility journeys.
+6. Finalize exact-tuple screenshots/video/repository/feedback/Devpost evidence,
+ retain `NO_DATA` unless real consented learner rows exist, then push and
+ fast-forward-only merge `main`.
+
+The release remains **NO-GO** at the real-sentinel, authentication,
+exact-image, CloakBrowser, deployment, and submission gates.
diff --git a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
index 618ecd9c..ea0fa415 100644
--- a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
+++ b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
@@ -1,10 +1,10 @@
# Release dependency and review graph
-- Checkpoint: `2026-07-21T04:26:26Z`
+- Checkpoint: `2026-07-21T07:56:31Z`
- Branch: `feat/learner-ux-v6.1`
-- Committed HEAD: `f5155e0cb19fec74330e1d3df9ab0bce0feca52b`
+- Committed HEAD: `eee333a9522a8efad8134fa543137c8f4691860e`
- Deadline: `2026-07-22T00:00:00Z`
-- Time remaining at this checkpoint: **19 hours 33 minutes 34 seconds**
+- Time remaining at this checkpoint: **16 hours 3 minutes 29 seconds**
## Purpose and evidence boundary
@@ -23,8 +23,8 @@ No current deployment, rendered CloakBrowser journey, learner outcome, video,
Devpost submission, branch push, or merge is claimed here.
The complete local `stock-chromium-design-review` matrix now passes every
-locally executable journey: **35 passed, 5 intentionally skipped, 0 failed in
-2.7 minutes**. The five skips are the two exact-public mobile replay journeys,
+locally executable journey on a fresh repository-contained state: **35 passed,
+5 intentionally skipped, 0 failed in 3.9 minutes**. The five skips are the two exact-public mobile replay journeys,
one exact-public asset scan, and two qualified hosted-runner journeys. This is
useful source/design evidence; it is not public or CloakBrowser qualification.
@@ -34,7 +34,7 @@ useful source/design evidence; it is not public or CloakBrowser qualification.
flowchart TD
A["Source slices atomic commits + focused tests SOURCE COMPLETE for aggregate runtime chain"]
B["Review gate independent review + lead diff review PASS for aggregate runtime integration"]
- C["Clean source freeze S one committed physical tree BLOCKED by preserved FUSE entry"]
+ C["Clean source freeze S reviewed linked-worktree path NOT CREATED"]
D["Exact runner and adapter image build receipt v4 bound to S NOT RUN for current source"]
E["Real aggregate timeout sentinel observer + controls + FINALIZE + cleanup BLOCKED by /sys and /proc authority"]
F["Source/image evidence refresh SBOM + VEX + engine evidence NOT RUN"]
@@ -43,7 +43,7 @@ flowchart TD
I["Release check receipt v5 + deterministic full gate NOT RUN"]
J["Cloudflare deployment maintenance + D1 + Container + Worker NOT RUN"]
K["Public identity and smoke deployment receipt v7 + /ready + health + live loops NOT RUN"]
- L["Browser qualification local stock 35 pass / 5 public-live skips public CloakBrowser: 0 journeys"]
+ L["Browser qualification fresh local stock 35 pass / 5 public-live skips public CloakBrowser: 0 journeys"]
M["Submission package public app + repo + video + feedback + honest NO_DATA EXTERNALLY UNVERIFIED"]
N["Git publication push feature -> verify remote -> fast-forward main -> push main NOT RUN"]
@@ -54,29 +54,29 @@ flowchart TD
classDef pending fill:#eef2f7,stroke:#596579,color:#252b36;
classDef external fill:#f7e8ef,stroke:#95506d,color:#482636;
class A,B pass;
- class C,E blocked;
- class D,F,G,H,I,J,K,L pending;
+ class E blocked;
+ class C,D,F,G,H,I,J,K,L pending;
class M,N external;
```
## Gate ledger
-| Gate | Required inputs | Required evidence | Current status | Failure rule |
-| ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
-| Source slices | Reviewed implementation and tests | Atomic commits, focused tests, type checks, formatting, secret scan, whitespace check | Aggregate observer/runtime/consumer chain is source-complete | Repair in a new independent commit; do not weaken tests |
-| Review | Complete source slice and focused evidence | Independent review plus lead inspection of complete diff | Aggregate runtime review is `GO`; its initial async run/drain race and missing positive path were repaired | Review findings remain open until implementation and regression tests pass |
-| Clean source `S` | Final intended source and no tracked or untracked drift | `git status` empty and exact source commit recorded | **Blocked** by preserved FUSE entry | Do not hide, delete, or sweep the entry; use its proven owner's natural exit or a separately contained clean checkout |
-| Exact image | Clean `S`, source-matching contained runtime, pinned BuildKit | Build receipt v4, source/tree labels, exact runner and adapter digests | **Not run** for current source; cached receipts belong to older commits | Never reuse or retag an earlier image as current |
-| Real sentinel | Exact image, source-matching runtime, release-only qualification mode | Genuine memory OOM, PID denial, CPU throttling, exact membership, FINALIZE, eight cleanup facts, cgroup absence, exclusive evidence artifact | **Blocked** by the checked-in repository-only filesystem rule, which does not permit required `/sys/fs/cgroup` and owned `/proc//stat` reads | Preserve `NO-GO`; never synthesize or inject aggregate evidence |
-| Evidence refresh | Exact image and successful real sentinel while `HEAD == S` | Fresh normalized SBOM, VEX and negative control, scientific-engine bindings, runtime and isolation reports | **Not run** | Any image/source mismatch or stale binding fails closed |
-| Evidence commit `E` | Reviewed allowlisted evidence delta | One clean commit containing only regenerated evidence | **Not created** | Do not include unrelated source or cache artifacts |
-| Qualification | Clean `HEAD == E`, build receipt, timeout receipt, source-matching runtime, authenticated Cloudflare account | Qualified runner receipt v6 and exact registry manifest/config digest | **Not run**; contained Wrangler identity is unverified | Do not promote when account, source, image, timeout, runtime, or evidence differs |
-| Release check | Clean `E`, qualified receipt, exact local images and runtime | Release-check receipt v5 after formatting, tests, mutations, held-out, sandbox, scientific, build, reproduction, patch replay, and secret gates | **Not run** | Any failure blocks deployment |
-| Cloudflare deployment | Qualified receipt, release-check receipt, registry image, required secrets, recovery target, working CloakBrowser endpoint | Exact maintenance/final Worker versions, D1 migration log, exact Container digest, deployment receipt v7 | **Not run** | Stop before mutation when preflight is red; after mutation, automated Worker recovery does not reverse D1 or Container state |
-| Public identity and smoke | Active exact Worker/Container tuple | `/ready`, `/api/health?readiness=probe`, Worker 100% traffic, Container identity, public asset scan, Sample, Replay, leakage and imbalance live evidence, Capsule/replay evidence | **Not run** | Source, image, Worker, client, receipt, or capability mismatch is a release failure |
-| CloakBrowser | Exact public tuple and injected `CLOAK_CDP_ENDPOINT` | All 40 current tests plus responsive, keyboard, screen-reader, 200% zoom, reduced-motion, console/network, downloads, reconnect, and Web Vitals evidence | Local stock design review: **35 passed, 5 public/live skips, 0 failed**; public CloakBrowser: **0 journeys** | Stock Chromium is design-review evidence only and cannot close this gate |
-| Devpost and impact | Qualified public tuple, public repo/video/feedback links, factual copy | Logged-out link audit, public video from exact tuple, Devpost receipt, real consented learner aggregates or explicit `NO_DATA` | **Externally unverified; learner evidence remains `NO_DATA`** | Never infer a submission or learner result |
-| Push and merge | Every ship gate factual and final source reviewed | Verified feature remote tip, fast-forward-only `main`, verified main remote tip | **Not run** | Do not push or merge a `NO-GO` release |
+| Gate | Required inputs | Required evidence | Current status | Failure rule |
+| ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| Source slices | Reviewed implementation and tests | Atomic commits, focused tests, type checks, formatting, secret scan, whitespace check | Aggregate observer/runtime/consumer chain is source-complete | Repair in a new independent commit; do not weaken tests |
+| Review | Complete source slice and focused evidence | Independent review plus lead inspection of complete diff | Aggregate runtime review is `GO`; its initial async run/drain race and missing positive path were repaired | Review findings remain open until implementation and regression tests pass |
+| Clean source `S` | Final intended source and no tracked or untracked drift | `git status` empty and exact source commit recorded | **Path reviewed, not created**: a separate linked worktree under ignored in-repository `.cache/` avoids the primary FUSE entry but needs its own contained dependencies/runtime | Do not hide, delete, or sweep the entry; use the reviewed separately contained clean checkout |
+| Exact image | Clean `S`, source-matching contained runtime, pinned BuildKit | Build receipt v4, source/tree labels, exact runner and adapter digests | **Not run** for current source; cached receipts belong to older commits | Never reuse or retag an earlier image as current |
+| Real sentinel | Exact image, source-matching runtime, release-only qualification mode | Genuine memory OOM, PID denial, CPU throttling, exact membership, FINALIZE, eight cleanup facts, cgroup absence, exclusive evidence artifact | **Blocked** by the checked-in repository-only filesystem rule, which does not permit required `/sys/fs/cgroup` and owned `/proc//stat` reads | Preserve `NO-GO`; never synthesize or inject aggregate evidence |
+| Evidence refresh | Exact image and successful real sentinel while `HEAD == S` | Fresh normalized SBOM, VEX and negative control, scientific-engine bindings, runtime and isolation reports | **Not run** | Any image/source mismatch or stale binding fails closed |
+| Evidence commit `E` | Reviewed allowlisted evidence delta | One clean commit containing only regenerated evidence | **Not created** | Do not include unrelated source or cache artifacts |
+| Qualification | Clean `HEAD == E`, build receipt, timeout receipt, source-matching runtime, authenticated Cloudflare account | Qualified runner receipt v6 and exact registry manifest/config digest | **Not run**; contained Wrangler is logged out and one fresh OAuth window timed out without consent | Do not promote when account, source, image, timeout, runtime, or evidence differs |
+| Release check | Clean `E`, qualified receipt, exact local images and runtime | Release-check receipt v5 after formatting, tests, mutations, held-out, sandbox, scientific, build, reproduction, patch replay, and secret gates | **Not run** | Any failure blocks deployment |
+| Cloudflare deployment | Qualified receipt, release-check receipt, registry image, required secrets, recovery target, working CloakBrowser endpoint | Exact maintenance/final Worker versions, D1 migration log, exact Container digest, deployment receipt v7 | **Not run** | Stop before mutation when preflight is red; after mutation, automated Worker recovery does not reverse D1 or Container state |
+| Public identity and smoke | Active exact Worker/Container tuple | `/ready`, `/api/health?readiness=probe`, Worker 100% traffic, Container identity, public asset scan, Sample, Replay, leakage and imbalance live evidence, Capsule/replay evidence | **Not run** | Source, image, Worker, client, receipt, or capability mismatch is a release failure |
+| CloakBrowser | Exact public tuple and injected `CLOAK_CDP_ENDPOINT` | All 40 current tests plus responsive, keyboard, screen-reader, 200% zoom, reduced-motion, console/network, downloads, reconnect, and Web Vitals evidence | Fresh local stock design review: **35 passed, 5 public/live skips, 0 failed in 3.9 minutes**; public CloakBrowser: **0 journeys** | Stock Chromium is design-review evidence only and cannot close this gate |
+| Devpost and impact | Qualified public tuple, public repo/video/feedback links, factual copy | Logged-out link audit, public video from exact tuple, Devpost receipt, real consented learner aggregates or explicit `NO_DATA` | **Externally unverified; learner evidence remains `NO_DATA`** | Never infer a submission or learner result |
+| Push and merge | Every ship gate factual and final source reviewed | Verified feature remote tip, fast-forward-only `main`, verified main remote tip | **Not run** | Do not push or merge a `NO-GO` release |
## Code-to-evidence graph
@@ -154,9 +154,15 @@ Reported verification for the integrated aggregate slice is:
- repository, web client, generated Worker, and Worker TypeScript: **passed**;
- E2E strict TypeScript: **passed**;
- Complete local stock-browser matrix: **40 tests in 3 files**, **35 passed,
- 5 intentionally skipped, 0 failed in 2.7 minutes**. The public replay,
+ 5 intentionally skipped, 0 failed in 3.9 minutes** on fresh current-source
+ local state. The public replay,
public asset, and real hosted-runner cases remain deferred to the exact
release tuple;
+- current web Vitest: **79 files, 626/626 passed**; current capability-enabled
+ root Vitest: **750 passed, 3 stale-evidence failures, 2 skipped**;
+- current Python behavior: **330/331** in the restricted run plus the sole
+ loopback-only service test **1/1** with socket permission;
+- critical mutations: leakage **14/14** and imbalance **19/19 detected**;
- scoped Prettier, Python compilation, Node syntax, Git whitespace, and secret
scans: **passed**;
- independent runtime integration review: **GO** after the implementation
@@ -229,11 +235,15 @@ that home-directory skill files were read.
and owned `/proc//stat` reads. The release must remain `NO-GO` unless the
checked-in authority changes or another constitution-compatible evidence
path is established.
-3. The preserved FUSE entry prevents the required clean physical source tree.
+3. The preserved FUSE entry blocks the primary tree, but a reviewed linked
+ worktree under ignored in-repository `.cache/` can provide a clean exact
+ source without touching it. That worktree and its independent contained
+ dependencies/runtime have not yet been created.
4. No current-source image, scientific-evidence refresh, evidence commit,
qualified receipt, or release-check receipt exists.
-5. Contained Wrangler authentication and the intended Cloudflare account have
- not been verified for this release.
+5. Contained Wrangler authentication is explicitly red: `whoami` returned
+ `loggedIn:false`, and one fresh OAuth window timed out without consent. The
+ intended Cloudflare account has not been authenticated for this release.
6. `CLOAK_CDP_ENDPOINT` is absent. `deploy-qualified.sh` performs remote Worker,
D1, and Container mutations before invoking the smoke script, while
`production-smoke.sh` requires CloakBrowser. CloakBrowser must therefore be
From b37f7d962e53dbfd7f511746e97ba364fe465d34 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 13:49:53 +0530
Subject: [PATCH 086/143] fix(runtime): preserve command response channel
Keep the server response side open after clients finish sending a request so asynchronous serialized responses are not lost. Covers the half-close protocol with a regression test and restores live runtime version and attestation queries.
---
scripts/contained-runtime-server.mjs | 2 +-
scripts/contained-runtime-server.test.ts | 17 +++++++++++++++++
2 files changed, 18 insertions(+), 1 deletion(-)
create mode 100644 scripts/contained-runtime-server.test.ts
diff --git a/scripts/contained-runtime-server.mjs b/scripts/contained-runtime-server.mjs
index 446c3b04..d9643e2b 100644
--- a/scripts/contained-runtime-server.mjs
+++ b/scripts/contained-runtime-server.mjs
@@ -390,7 +390,7 @@ await new Promise((accept, reject) => {
});
});
-const server = createServer((socket) => {
+const server = createServer({ allowHalfOpen: true }, (socket) => {
const chunks = [];
let total = 0;
socket.setTimeout(1_800_000);
diff --git a/scripts/contained-runtime-server.test.ts b/scripts/contained-runtime-server.test.ts
new file mode 100644
index 00000000..add04e75
--- /dev/null
+++ b/scripts/contained-runtime-server.test.ts
@@ -0,0 +1,17 @@
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+
+import { describe, expect, it } from "vitest";
+
+describe("contained runtime command server", () => {
+ it("keeps the response side open after a client finishes its request", () => {
+ const source = readFileSync(
+ resolve(process.cwd(), "scripts/contained-runtime-server.mjs"),
+ "utf8",
+ );
+
+ expect(source).toContain(
+ "const server = createServer({ allowHalfOpen: true }, (socket) => {",
+ );
+ });
+});
From 8c60f6fb5354bb2101ad458c74f716664a7a2adb Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 13:56:53 +0530
Subject: [PATCH 087/143] docs(progress): record runtime release blockers
---
docs/PROGRESS.md | 116 +++++++++++++++---
.../19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md | 101 +++++++++------
2 files changed, 161 insertions(+), 56 deletions(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 91edffb1..a1fc0703 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -13,22 +13,18 @@ pass is never presented as a browser or production pass.
- **Branch:** `feat/learner-ux-v6.1`
- **Latest committed checkpoint:**
- `eee333a9522a8efad8134fa543137c8f4691860e`; latest product and release-safety
- code checkpoint `8e33154b2b130fbdb635cf7a91ed75dc9357741f`. Before this ledger update, the
- branch is two commits ahead of `origin/feat/learner-ux-v6.1` and the worktree
- contains only the preserved, untracked
- `scripts/.fuse_hidden0000a9600000801f`. Fresh verification on this tip passes
- web Vitest **79 files and 626/626 tests**, repository/web/Worker/E2E strict
- TypeScript, the production build, Python behavior **331/331** when its one
- localhost test receives loopback permission, leakage mutations **14/14**,
- imbalance mutations **19/19**, held-out and generated Sample fixture checks,
- repository-wide Prettier, Git whitespace, and a 31-file built-artifact secret
- scan. Root Vitest is **750 passed, 3 stale exact-image/scientific-evidence
- failures, 2 skipped**. A fresh explicit stock-Chromium design review is **35
- passed, 5 exact-public/live skips, 0 failed across 40 tests in 3.9 minutes**.
- These local results do not constitute a real cgroup sentinel, refreshed
- scientific evidence, CloakBrowser execution, Cloudflare deployment, learner
- observation, Devpost publication, or a submission receipt.
+ `b37f7d962e53dbfd7f511746e97ba364fe465d34`, four commits ahead of
+ `origin/feat/learner-ux-v6.1`. The only worktree entry is the preserved,
+ unstaged `scripts/.fuse_hidden0000a9600000801f`. The broad deterministic and
+ stock-browser matrix was last completed at parent checkpoint `8e9e336`; the
+ current runtime response-channel change passes its focused **7-file, 65-test**
+ suite, Node syntax, scoped Prettier, repository TypeScript, a two-file secret
+ scan, and Git whitespace checks. Fresh runtime `rt-mainrel04` returns a
+ schema-v2 `VERIFIED` response and live pinned version data. These results prove
+ the local command channel only; they do not constitute a clean source, exact
+ image, real cgroup sentinel, refreshed scientific evidence, CloakBrowser
+ execution, Cloudflare deployment, learner observation, Devpost publication,
+ or a submission receipt.
- **Earlier product checkpoint:**
`5259a495948a68024702ba5239bf8057d1785f08`. The owner partitioned the
preserved integration delta into nine commits and fast-forwarded both
@@ -4011,3 +4007,91 @@ repository-contained.
The release remains **NO-GO** at the real-sentinel, authentication,
exact-image, CloakBrowser, deployment, and submission gates.
+
+## Contained runtime command-channel checkpoint — 2026-07-21T08:22:38Z
+
+Deadline: `2026-07-22T00:00:00Z`; **15 hours 37 minutes 22 seconds remained at
+this checkpoint**. Committed HEAD is
+`b37f7d962e53dbfd7f511746e97ba364fe465d34`, four commits ahead of
+`origin/feat/learner-ux-v6.1`. The only visible worktree entry is the preserved,
+unstaged FUSE tombstone `scripts/.fuse_hidden0000a9600000801f`; it remains
+untouched.
+
+### Completed in this slice
+
+- Commit `b37f7d9 fix(runtime): preserve command response channel` repairs a
+ deterministic Unix-socket half-close defect in the contained runtime command
+ server. The client finishes its request with `socket.end(request)`; the server
+ now keeps its response side open while the serialized asynchronous handler
+ produces the reply. A source-level characterization failed **1/1** before the
+ repair and passed **1/1** afterward.
+- The complete contained-runtime protocol/entry/attestation/supervisor group
+ passed **7 files, 65/65 tests**. Node syntax, scoped Prettier, repository
+ TypeScript, a two-file secret scan, and Git whitespace checks passed.
+- A fresh primary-root runtime, `rt-mainrel04`, started successfully and stayed
+ active. The repaired command channel returned a schema-v2 attestation with
+ status `VERIFIED`, then returned live BuildKit client **v0.30.0**, containerd
+ **v2.3.1**, and runc **1.4.2** version data. This closes the local zero-byte
+ response regression; it is not an image qualification or deployment receipt.
+- The code-review graph is current at `b37f7d9`: **531 files, 5,515 nodes, and
+ 93,346 edges** across SQL, TypeScript, TSX, Python, JavaScript, and Bash. Its
+ branch-delta scan covers **140 changed files** and reports heuristic medium
+ structural risk **0.60**; independent standards and specification reviews are
+ in progress.
+
+### Corrected release-path finding
+
+The linked-worktree approach described in the previous checkpoint is not a
+viable contained-runtime release path. Two clean retained attempts were made:
+
+- `.cache/counterlab-release-worktrees/8e9e336` failed because its Unix socket
+ path exceeded the kernel limit;
+- `.cache/r/e` then failed the runtime's reviewed physical-root length bound of
+ **42 characters**, which reserves room for containerd shim sockets.
+
+Every descendant worktree is physically longer than the verified repository
+root. The primary physical root is exactly 42 characters and is therefore the
+only currently proven location that can host this contained runtime. Neither
+retained worktree was deleted. The primary root cannot become a clean frozen
+release source until the pre-existing FUSE tombstone is resolved without
+staging, hiding, overwriting, or deleting it.
+
+### Gates still open
+
+1. **Clean source:** blocked by the preserved FUSE tombstone. No current clean
+ source `S` exists.
+2. **Real aggregate containment:** not run. The genuine sentinel still needs a
+ narrowly authorized read-only observation of `/sys/fs/cgroup` and the owned
+ test processes' `/proc//stat`; no such read or pass is claimed.
+3. **Exact image and evidence:** not run for `b37f7d9`. The three stale
+ source/image scientific bindings remain red until one exact image, genuine
+ sentinel, and complete evidence refresh are produced together.
+4. **Cloudflare:** repository-contained Wrangler remains unauthenticated after
+ the last failed `whoami` and timed-out OAuth window. No qualification,
+ migration, deployment, or cloud mutation occurred.
+5. **Browser and submission:** CloakBrowser remains unavailable, so qualifying
+ execution remains **0 journeys**. Stock Chromium evidence remains explicitly
+ non-qualifying. Learner evidence remains `NO_DATA`; Devpost publication,
+ video, submission receipt, push, and merge remain unverified or not run.
+
+### Next executable sequence
+
+1. Resolve the named FUSE tombstone by closing its pre-existing owner without
+ deleting or altering the entry, then verify an empty primary worktree.
+2. Obtain the narrow read-only cgroup/process observation authority and complete
+ a fresh repository-contained Wrangler OAuth flow.
+3. Freeze the resulting primary-root source commit, start a fresh contained
+ runtime there, build one exact image, and execute the real sentinel and all
+ negative controls.
+4. Regenerate and review the complete SBOM, VEX, scientific-engine, license,
+ health, held-out, and source/image binding set once; commit evidence alone;
+ rerun every deterministic release gate until all three stale bindings pass.
+5. Preflight CloakBrowser before cloud mutation, qualify and deploy only the
+ exact tuple, prove public identity and both supported live flows, then run the
+ complete public browser/accessibility matrix.
+6. Finalize only exact-tuple screenshots, video, repository, feedback, Devpost,
+ and learner-evidence claims; push the feature branch and fast-forward `main`
+ only after every publication gate is factual.
+
+The release remains **NO-GO** at clean-source, real-sentinel, exact-image,
+authentication, CloakBrowser, deployment, and submission gates.
diff --git a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
index ea0fa415..e1b4cbe8 100644
--- a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
+++ b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
@@ -1,10 +1,10 @@
# Release dependency and review graph
-- Checkpoint: `2026-07-21T07:56:31Z`
+- Checkpoint: `2026-07-21T08:22:38Z`
- Branch: `feat/learner-ux-v6.1`
-- Committed HEAD: `eee333a9522a8efad8134fa543137c8f4691860e`
+- Committed HEAD: `b37f7d962e53dbfd7f511746e97ba364fe465d34`
- Deadline: `2026-07-22T00:00:00Z`
-- Time remaining at this checkpoint: **16 hours 3 minutes 29 seconds**
+- Time remaining at this checkpoint: **15 hours 37 minutes 22 seconds**
## Purpose and evidence boundary
@@ -34,7 +34,8 @@ useful source/design evidence; it is not public or CloakBrowser qualification.
flowchart TD
A["Source slices atomic commits + focused tests SOURCE COMPLETE for aggregate runtime chain"]
B["Review gate independent review + lead diff review PASS for aggregate runtime integration"]
- C["Clean source freeze S reviewed linked-worktree path NOT CREATED"]
+ R["Contained runtime command channel rt-mainrel04 READY / VERIFIED PASS"]
+ C["Clean source freeze S primary root blocked by FUSE tombstone; linked roots exceed runtime maximum"]
D["Exact runner and adapter image build receipt v4 bound to S NOT RUN for current source"]
E["Real aggregate timeout sentinel observer + controls + FINALIZE + cleanup BLOCKED by /sys and /proc authority"]
F["Source/image evidence refresh SBOM + VEX + engine evidence NOT RUN"]
@@ -47,36 +48,36 @@ flowchart TD
M["Submission package public app + repo + video + feedback + honest NO_DATA EXTERNALLY UNVERIFIED"]
N["Git publication push feature -> verify remote -> fast-forward main -> push main NOT RUN"]
- A --> B --> C --> D --> E --> F --> G --> H --> I --> J --> K --> L --> M --> N
+ A --> B --> R --> C --> D --> E --> F --> G --> H --> I --> J --> K --> L --> M --> N
classDef pass fill:#d9f3ee,stroke:#176b5b,color:#123b34;
classDef blocked fill:#fff0d0,stroke:#9a6500,color:#4c3500;
classDef pending fill:#eef2f7,stroke:#596579,color:#252b36;
classDef external fill:#f7e8ef,stroke:#95506d,color:#482636;
- class A,B pass;
- class E blocked;
- class C,D,F,G,H,I,J,K,L pending;
+ class A,B,R pass;
+ class C,E blocked;
+ class D,F,G,H,I,J,K,L pending;
class M,N external;
```
## Gate ledger
-| Gate | Required inputs | Required evidence | Current status | Failure rule |
-| ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
-| Source slices | Reviewed implementation and tests | Atomic commits, focused tests, type checks, formatting, secret scan, whitespace check | Aggregate observer/runtime/consumer chain is source-complete | Repair in a new independent commit; do not weaken tests |
-| Review | Complete source slice and focused evidence | Independent review plus lead inspection of complete diff | Aggregate runtime review is `GO`; its initial async run/drain race and missing positive path were repaired | Review findings remain open until implementation and regression tests pass |
-| Clean source `S` | Final intended source and no tracked or untracked drift | `git status` empty and exact source commit recorded | **Path reviewed, not created**: a separate linked worktree under ignored in-repository `.cache/` avoids the primary FUSE entry but needs its own contained dependencies/runtime | Do not hide, delete, or sweep the entry; use the reviewed separately contained clean checkout |
-| Exact image | Clean `S`, source-matching contained runtime, pinned BuildKit | Build receipt v4, source/tree labels, exact runner and adapter digests | **Not run** for current source; cached receipts belong to older commits | Never reuse or retag an earlier image as current |
-| Real sentinel | Exact image, source-matching runtime, release-only qualification mode | Genuine memory OOM, PID denial, CPU throttling, exact membership, FINALIZE, eight cleanup facts, cgroup absence, exclusive evidence artifact | **Blocked** by the checked-in repository-only filesystem rule, which does not permit required `/sys/fs/cgroup` and owned `/proc//stat` reads | Preserve `NO-GO`; never synthesize or inject aggregate evidence |
-| Evidence refresh | Exact image and successful real sentinel while `HEAD == S` | Fresh normalized SBOM, VEX and negative control, scientific-engine bindings, runtime and isolation reports | **Not run** | Any image/source mismatch or stale binding fails closed |
-| Evidence commit `E` | Reviewed allowlisted evidence delta | One clean commit containing only regenerated evidence | **Not created** | Do not include unrelated source or cache artifacts |
-| Qualification | Clean `HEAD == E`, build receipt, timeout receipt, source-matching runtime, authenticated Cloudflare account | Qualified runner receipt v6 and exact registry manifest/config digest | **Not run**; contained Wrangler is logged out and one fresh OAuth window timed out without consent | Do not promote when account, source, image, timeout, runtime, or evidence differs |
-| Release check | Clean `E`, qualified receipt, exact local images and runtime | Release-check receipt v5 after formatting, tests, mutations, held-out, sandbox, scientific, build, reproduction, patch replay, and secret gates | **Not run** | Any failure blocks deployment |
-| Cloudflare deployment | Qualified receipt, release-check receipt, registry image, required secrets, recovery target, working CloakBrowser endpoint | Exact maintenance/final Worker versions, D1 migration log, exact Container digest, deployment receipt v7 | **Not run** | Stop before mutation when preflight is red; after mutation, automated Worker recovery does not reverse D1 or Container state |
-| Public identity and smoke | Active exact Worker/Container tuple | `/ready`, `/api/health?readiness=probe`, Worker 100% traffic, Container identity, public asset scan, Sample, Replay, leakage and imbalance live evidence, Capsule/replay evidence | **Not run** | Source, image, Worker, client, receipt, or capability mismatch is a release failure |
-| CloakBrowser | Exact public tuple and injected `CLOAK_CDP_ENDPOINT` | All 40 current tests plus responsive, keyboard, screen-reader, 200% zoom, reduced-motion, console/network, downloads, reconnect, and Web Vitals evidence | Fresh local stock design review: **35 passed, 5 public/live skips, 0 failed in 3.9 minutes**; public CloakBrowser: **0 journeys** | Stock Chromium is design-review evidence only and cannot close this gate |
-| Devpost and impact | Qualified public tuple, public repo/video/feedback links, factual copy | Logged-out link audit, public video from exact tuple, Devpost receipt, real consented learner aggregates or explicit `NO_DATA` | **Externally unverified; learner evidence remains `NO_DATA`** | Never infer a submission or learner result |
-| Push and merge | Every ship gate factual and final source reviewed | Verified feature remote tip, fast-forward-only `main`, verified main remote tip | **Not run** | Do not push or merge a `NO-GO` release |
+| Gate | Required inputs | Required evidence | Current status | Failure rule |
+| ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| Source slices | Reviewed implementation and tests | Atomic commits, focused tests, type checks, formatting, secret scan, whitespace check | Aggregate observer/runtime/consumer chain is source-complete | Repair in a new independent commit; do not weaken tests |
+| Review | Complete source slice and focused evidence | Independent review plus lead inspection of complete diff | Aggregate runtime review is `GO`; its initial async run/drain race and missing positive path were repaired | Review findings remain open until implementation and regression tests pass |
+| Clean source `S` | Final intended source and no tracked or untracked drift | `git status` empty and exact source commit recorded | **Blocked**: the 42-character primary root is the only runtime-capable location and remains unclean because of the preserved FUSE tombstone. Both retained linked worktrees exceed the enforced containerd shim-root maximum | Do not hide, delete, stage, or sweep the entry; no descendant worktree is a proven substitute |
+| Exact image | Clean `S`, source-matching contained runtime, pinned BuildKit | Build receipt v4, source/tree labels, exact runner and adapter digests | **Not run** for current source; cached receipts belong to older commits | Never reuse or retag an earlier image as current |
+| Real sentinel | Exact image, source-matching runtime, release-only qualification mode | Genuine memory OOM, PID denial, CPU throttling, exact membership, FINALIZE, eight cleanup facts, cgroup absence, exclusive evidence artifact | **Blocked** by the checked-in repository-only filesystem rule, which does not permit required `/sys/fs/cgroup` and owned `/proc//stat` reads | Preserve `NO-GO`; never synthesize or inject aggregate evidence |
+| Evidence refresh | Exact image and successful real sentinel while `HEAD == S` | Fresh normalized SBOM, VEX and negative control, scientific-engine bindings, runtime and isolation reports | **Not run** | Any image/source mismatch or stale binding fails closed |
+| Evidence commit `E` | Reviewed allowlisted evidence delta | One clean commit containing only regenerated evidence | **Not created** | Do not include unrelated source or cache artifacts |
+| Qualification | Clean `HEAD == E`, build receipt, timeout receipt, source-matching runtime, authenticated Cloudflare account | Qualified runner receipt v6 and exact registry manifest/config digest | **Not run**; contained Wrangler is logged out and one fresh OAuth window timed out without consent | Do not promote when account, source, image, timeout, runtime, or evidence differs |
+| Release check | Clean `E`, qualified receipt, exact local images and runtime | Release-check receipt v5 after formatting, tests, mutations, held-out, sandbox, scientific, build, reproduction, patch replay, and secret gates | **Not run** | Any failure blocks deployment |
+| Cloudflare deployment | Qualified receipt, release-check receipt, registry image, required secrets, recovery target, working CloakBrowser endpoint | Exact maintenance/final Worker versions, D1 migration log, exact Container digest, deployment receipt v7 | **Not run** | Stop before mutation when preflight is red; after mutation, automated Worker recovery does not reverse D1 or Container state |
+| Public identity and smoke | Active exact Worker/Container tuple | `/ready`, `/api/health?readiness=probe`, Worker 100% traffic, Container identity, public asset scan, Sample, Replay, leakage and imbalance live evidence, Capsule/replay evidence | **Not run** | Source, image, Worker, client, receipt, or capability mismatch is a release failure |
+| CloakBrowser | Exact public tuple and injected `CLOAK_CDP_ENDPOINT` | All 40 current tests plus responsive, keyboard, screen-reader, 200% zoom, reduced-motion, console/network, downloads, reconnect, and Web Vitals evidence | Fresh local stock design review: **35 passed, 5 public/live skips, 0 failed in 3.9 minutes**; public CloakBrowser: **0 journeys** | Stock Chromium is design-review evidence only and cannot close this gate |
+| Devpost and impact | Qualified public tuple, public repo/video/feedback links, factual copy | Logged-out link audit, public video from exact tuple, Devpost receipt, real consented learner aggregates or explicit `NO_DATA` | **Externally unverified; learner evidence remains `NO_DATA`** | Never infer a submission or learner result |
+| Push and merge | Every ship gate factual and final source reviewed | Verified feature remote tip, fast-forward-only `main`, verified main remote tip | **Not run** | Do not push or merge a `NO-GO` release |
## Code-to-evidence graph
@@ -144,7 +145,9 @@ The current aggregate release chain is split into independent commits:
- `75dfb65` — Python v3 qualified-receipt consumption with v2 replay
compatibility;
- `f19387b` — current live-browser selector alignment;
-- `e4dc33e` — factual progress checkpoint.
+- `e4dc33e` — factual progress checkpoint;
+- `b37f7d9` — preserves the runtime command response after the client
+ half-closes its request and adds the focused regression.
Reported verification for the integrated aggregate slice is:
@@ -165,12 +168,21 @@ Reported verification for the integrated aggregate slice is:
- critical mutations: leakage **14/14** and imbalance **19/19 detected**;
- scoped Prettier, Python compilation, Node syntax, Git whitespace, and secret
scans: **passed**;
+- current contained-runtime command-channel suite: **7 files, 65/65 passed**;
+- current Node syntax, scoped Prettier, repository TypeScript, two-file secret
+ scan, and Git whitespace: **passed**;
+- fresh primary runtime `rt-mainrel04`: schema-v2 response status **VERIFIED**;
+ session attestation **READY**; live BuildKit client **v0.30.0**, containerd
+ **v2.3.1**, and runc **1.4.2** version response confirmed;
- independent runtime integration review: **GO** after the implementation
repaired its initial async run/drain race and missing positive
qualification-path coverage.
-These results are source evidence only. The real sentinel and every downstream
-release node remain open.
+The broad web/root/Python/build/browser matrix predates `b37f7d9` and must be
+rerun on the final frozen source. The current focused results are source and
+runtime command-channel evidence only. They are not exact-image, aggregate
+containment, qualification, or deployment evidence; every downstream release
+node remains open.
## File ownership and reviewer graph
@@ -211,11 +223,11 @@ flowchart TD
No two writers may own the same shared file or receipt registry concurrently.
Only the lead stages, commits, deploys, pushes, or fast-forwards `main`.
-## Disciplines applied directly
+## Disciplines and skills applied
-The following disciplines are applied directly from the checked-in repository
-constitution, code, tests, and release scripts. This document does **not** claim
-that home-directory skill files were read.
+The owner explicitly authorized the installed skill instructions for this pass.
+The lead read and applied the following skills while preserving the checked-in
+repository constitution as the governing product and filesystem authority.
| Discipline | Direct application in this graph |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -226,6 +238,12 @@ that home-directory skill files were read.
| Playwright and CloakBrowser | Static collection is not browser evidence; release qualification requires the injected CloakBrowser CDP authority, accessibility snapshots, screenshots where visual judgment matters, and console/network review. Stock Chromium cannot close the gate. |
| Cloudflare and Wrangler | The repository-pinned Wrangler, contained account identity, exact registry digest, maintenance freeze, remote D1 migration, Container rollout, final Worker, recovery target, and public identity receipt form one ordered gate. |
| Delivery gate | Clean `S` -> exact image -> real sentinel -> evidence `E` -> qualification -> release check -> deployment -> public smoke -> full browser matrix -> submission -> push/fast-forward merge. No later node may compensate for an earlier red node. |
+| Diagnosing bugs | Required the red command-server characterization before the one-line half-close repair, followed by focused and original-path runtime proof. |
+| Git workflow | Required selective staging, non-executable test mode, staged-diff review, configured owner identity, and the independent `b37f7d9` commit. |
+| Build graph | Refreshed the graph at `b37f7d9` to 531 files, 5,515 nodes, and 93,346 edges, then used the 140-file branch delta to prioritize review. |
+| Code review | Runs standards and specification reviews as separate read-only axes against `main`; findings do not become closed until accepted tests and public evidence pass. |
+| Browser QA and Playwright | Keeps the stock matrix explicitly non-qualifying and requires the registered CloakBrowser authority, semantic snapshots, screenshots, console/network inspection, accessibility, and responsive evidence. |
+| Wrangler | Requires current documentation, repository-contained CLI state, authenticated-account proof, dry-run/preflight, exact version/digest capture, and qualified-only deployment. |
## Current blockers and shortest honest path
@@ -235,10 +253,11 @@ that home-directory skill files were read.
and owned `/proc//stat` reads. The release must remain `NO-GO` unless the
checked-in authority changes or another constitution-compatible evidence
path is established.
-3. The preserved FUSE entry blocks the primary tree, but a reviewed linked
- worktree under ignored in-repository `.cache/` can provide a clean exact
- source without touching it. That worktree and its independent contained
- dependencies/runtime have not yet been created.
+3. Both retained linked-worktree candidates were attempted and disproved as
+ runtime release roots: their physical paths exceed the 42-character
+ containerd shim limit. The primary physical root is exactly 42 characters
+ and successfully hosts `rt-mainrel04`, but its preserved FUSE tombstone
+ prevents a clean frozen source.
4. No current-source image, scientific-evidence refresh, evidence commit,
qualified receipt, or release-check receipt exists.
5. Contained Wrangler authentication is explicitly red: `whoami` returned
@@ -251,11 +270,13 @@ that home-directory skill files were read.
7. Learner evidence remains `NO_DATA`; public repository, video, feedback ID,
Devpost publication, and submission receipt remain externally unverified.
-The shortest honest path is to resolve the real-sentinel authority and clean
-source blockers, freeze `S`, build once, produce the genuine sentinel, refresh
-and commit evidence as `E`, qualify, release-check, preflight CloakBrowser and
-Wrangler, deploy once, verify the exact public tuple, execute the full browser
-matrix, finalize the factual submission, then push and fast-forward merge.
+The shortest honest path is to resolve the clean-source condition at the primary
+physical root without hiding or deleting the protected tombstone; obtain narrow
+read-only cgroup-observation authority; freeze `S`; build one exact image; run
+the genuine sentinel; refresh and commit evidence as `E`; authenticate contained
+Wrangler; qualify and release-check; preflight CloakBrowser; deploy once; verify
+the exact public tuple and browser matrix; finalize the factual submission; then
+push and fast-forward merge.
## Final release review questions
From 84ef49de84fab2ad6e6a3b0ac7241f128967fa20 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 14:02:44 +0530
Subject: [PATCH 088/143] fix(web): validate bound release isolation
Accept the exact isolation evidence chain emitted by bound Worker health responses and reject a mismatched release-check probe. Keep App, Judge, and recovery fixtures aligned with the production contract.
---
apps/web/e2e/recovery-and-intake.spec.ts | 6 +++++
apps/web/src/App.test.tsx | 6 +++++
apps/web/src/api.test.ts | 24 +++++++++++++++++++
apps/web/src/api.ts | 18 ++++++++++++++
.../src/features/judge/JudgeModeView.test.tsx | 5 ++++
5 files changed, 59 insertions(+)
diff --git a/apps/web/e2e/recovery-and-intake.spec.ts b/apps/web/e2e/recovery-and-intake.spec.ts
index bc72ea35..8d484c14 100644
--- a/apps/web/e2e/recovery-and-intake.spec.ts
+++ b/apps/web/e2e/recovery-and-intake.spec.ts
@@ -29,6 +29,12 @@ const readyHealth = {
workerEvidenceCommit: "a".repeat(40),
runnerSourceCommit: "b".repeat(40),
runnerImageDigest: `sha256:${"c".repeat(64)}`,
+ generationIsolationEvidenceSha256: "5".repeat(64),
+ generationIsolationProbeSha256: "6".repeat(64),
+ releaseCheckGenerationIsolationEvidenceSha256: "7".repeat(64),
+ releaseCheckGenerationIsolationProbeSha256: "6".repeat(64),
+ releaseCheckGenerationIsolationVerifiedAt:
+ "2026-07-19T05:31:00.000+05:30",
timeoutCleanupReceiptSha256: "d".repeat(64),
aggregateLimitEvidenceSha256: "9".repeat(64),
runtimePolicySha256: "e".repeat(64),
diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
index d5ca14e3..2f5465ed 100644
--- a/apps/web/src/App.test.tsx
+++ b/apps/web/src/App.test.tsx
@@ -508,6 +508,12 @@ function installApi(
workerEvidenceCommit: "a".repeat(40),
runnerSourceCommit: "b".repeat(40),
runnerImageDigest: `sha256:${"c".repeat(64)}`,
+ generationIsolationEvidenceSha256: "5".repeat(64),
+ generationIsolationProbeSha256: "6".repeat(64),
+ releaseCheckGenerationIsolationEvidenceSha256: "7".repeat(64),
+ releaseCheckGenerationIsolationProbeSha256: "6".repeat(64),
+ releaseCheckGenerationIsolationVerifiedAt:
+ "2026-07-19T05:31:00.000+05:30",
timeoutCleanupReceiptSha256: "d".repeat(64),
aggregateLimitEvidenceSha256: "9".repeat(64),
runtimePolicySha256: "e".repeat(64),
diff --git a/apps/web/src/api.test.ts b/apps/web/src/api.test.ts
index fb3bda77..5ad1fb00 100644
--- a/apps/web/src/api.test.ts
+++ b/apps/web/src/api.test.ts
@@ -692,6 +692,12 @@ describe("CounterLabApiClient", () => {
workerEvidenceCommit: "a".repeat(40),
runnerSourceCommit: "b".repeat(40),
runnerImageDigest: `sha256:${"c".repeat(64)}`,
+ generationIsolationEvidenceSha256: "5".repeat(64),
+ generationIsolationProbeSha256: "6".repeat(64),
+ releaseCheckGenerationIsolationEvidenceSha256: "7".repeat(64),
+ releaseCheckGenerationIsolationProbeSha256: "6".repeat(64),
+ releaseCheckGenerationIsolationVerifiedAt:
+ "2026-07-19T05:31:00.000+05:30",
timeoutCleanupReceiptSha256: "d".repeat(64),
aggregateLimitEvidenceSha256: "9".repeat(64),
runtimePolicySha256: "e".repeat(64),
@@ -728,6 +734,24 @@ describe("CounterLabApiClient", () => {
"/api/health?readiness=probe",
expect.objectContaining({ method: "GET" }),
);
+
+ const mismatchedProbeClient = new CounterLabApiClient({
+ fetch: vi.fn(async () =>
+ jsonResponse({
+ ok: true,
+ data: {
+ ...health,
+ release: {
+ ...health.release,
+ releaseCheckGenerationIsolationProbeSha256: "8".repeat(64),
+ },
+ },
+ }),
+ ),
+ });
+ await expect(mismatchedProbeClient.getHealth()).rejects.toMatchObject({
+ code: "INVALID_API_RESPONSE",
+ });
});
it("accepts an explicitly OS-enforced generation isolation claim", async () => {
diff --git a/apps/web/src/api.ts b/apps/web/src/api.ts
index 4f3f255e..1bb7c3a0 100644
--- a/apps/web/src/api.ts
+++ b/apps/web/src/api.ts
@@ -93,6 +93,13 @@ const ReleaseIdentitySchema = z
workerEvidenceCommit: z.string().regex(/^[a-f0-9]{40}$/),
runnerSourceCommit: z.string().regex(/^[a-f0-9]{40}$/),
runnerImageDigest: z.string().regex(/^sha256:[a-f0-9]{64}$/),
+ generationIsolationEvidenceSha256: Sha256Digest,
+ generationIsolationProbeSha256: Sha256Digest,
+ releaseCheckGenerationIsolationEvidenceSha256: Sha256Digest,
+ releaseCheckGenerationIsolationProbeSha256: Sha256Digest,
+ releaseCheckGenerationIsolationVerifiedAt: z.iso.datetime({
+ offset: true,
+ }),
timeoutCleanupReceiptSha256: z.string().regex(/^[a-f0-9]{64}$/),
aggregateLimitEvidenceSha256: z.string().regex(/^[a-f0-9]{64}$/),
runtimePolicySha256: z.string().regex(/^[a-f0-9]{64}$/),
@@ -120,6 +127,17 @@ const ReleaseIdentitySchema = z
message: "public client asset count exceeds the full deploy tree",
});
}
+ if (
+ release.status === "bound" &&
+ release.releaseCheckGenerationIsolationProbeSha256 !==
+ release.generationIsolationProbeSha256
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["releaseCheckGenerationIsolationProbeSha256"],
+ message: "release-check isolation probe does not match the deployment",
+ });
+ }
});
export const CapabilityHealthSchema = z
diff --git a/apps/web/src/features/judge/JudgeModeView.test.tsx b/apps/web/src/features/judge/JudgeModeView.test.tsx
index ea34c280..938ab149 100644
--- a/apps/web/src/features/judge/JudgeModeView.test.tsx
+++ b/apps/web/src/features/judge/JudgeModeView.test.tsx
@@ -27,6 +27,11 @@ const configuredHealth: CapabilityHealth = {
workerEvidenceCommit: "a".repeat(40),
runnerSourceCommit: "b".repeat(40),
runnerImageDigest: `sha256:${"c".repeat(64)}`,
+ generationIsolationEvidenceSha256: "5".repeat(64),
+ generationIsolationProbeSha256: "6".repeat(64),
+ releaseCheckGenerationIsolationEvidenceSha256: "7".repeat(64),
+ releaseCheckGenerationIsolationProbeSha256: "6".repeat(64),
+ releaseCheckGenerationIsolationVerifiedAt: "2026-07-19T05:31:00.000+05:30",
timeoutCleanupReceiptSha256: "d".repeat(64),
aggregateLimitEvidenceSha256: "9".repeat(64),
runtimePolicySha256: "e".repeat(64),
From 547cc0f409d081b98d455ba8754cb29dac84f0ec Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 14:17:20 +0530
Subject: [PATCH 089/143] fix(worker): preserve historical proof validation
---
apps/web/worker/api.test.ts | 148 ++++++++++++++++++++++++++++++-----
apps/web/worker/api.ts | 151 ++++++++++++++++++++++++------------
2 files changed, 232 insertions(+), 67 deletions(-)
diff --git a/apps/web/worker/api.test.ts b/apps/web/worker/api.test.ts
index aa0a1c04..f2fd9e68 100644
--- a/apps/web/worker/api.test.ts
+++ b/apps/web/worker/api.test.ts
@@ -4912,9 +4912,22 @@ describe("Cloudflare Worker API", () => {
const patchCallbackBody = (await callback.json()) as {
data: { verification?: unknown; session: unknown };
};
- expect({ status: callback.status }).toEqual({
- status: 200,
- });
+ const completedSession = await harness.sessionRepository.find(
+ harness.sessionId,
+ );
+ const completedEvents = await harness.sessionRepository.listEvents(
+ harness.sessionId,
+ );
+ const completedBundleHash =
+ completedSession?.proofBundle === undefined
+ ? undefined
+ : await hashCanonical(completedSession.proofBundle);
+ expect(
+ completedEvents
+ .filter((event) => event.kind === "reasoning_diff.issued")
+ .map((event) => event.outputHashes.includes(completedBundleHash ?? "")),
+ ).toEqual([true]);
+ expect(callback.status, JSON.stringify(patchCallbackBody)).toBe(200);
expect(patchCallbackBody.data.verification).toMatchObject({
status: "VERIFIED",
});
@@ -6398,25 +6411,75 @@ describe("Cloudflare Worker API", () => {
);
expect(proofDownload.status).toBe(200);
+ const proofSigningKey =
+ "counterlab-test-proof-signing-key-with-sufficient-entropy";
+ const proofSigningEnv = {
+ COUNTERLAB_SIGNING_KEY: proofSigningKey,
+ } as unknown as Env & Record;
+ for (const path of [
+ `/api/sessions/${sessionId}`,
+ `/api/sessions/${sessionId}/proof-bundle`,
+ ]) {
+ const historicalUnsigned = await app.request(
+ path,
+ undefined,
+ proofSigningEnv,
+ );
+ expect(historicalUnsigned.status).toBe(200);
+ const historicalPayload = (await historicalUnsigned.json()) as {
+ data: {
+ integrity?: { mode: string };
+ proofBundle?: { integrity: { mode: string } };
+ };
+ };
+ expect(
+ historicalPayload.data.proofBundle?.integrity ??
+ historicalPayload.data.integrity,
+ ).toMatchObject({ mode: "integrity-hashed" });
+ }
+
const storedProofSession = await harness.sessionRepository.find(sessionId);
if (storedProofSession?.proofBundle === undefined) {
throw new Error("hosted proof bundle was not persisted");
}
+ const replaceIssuedProofBundle = async (
+ proofBundle: NonNullable,
+ ) => {
+ const events = await harness.sessionRepository.listEvents(sessionId);
+ const issuanceIndex = events.findIndex(
+ (event) => event.kind === "reasoning_diff.issued",
+ );
+ if (issuanceIndex !== events.length - 1) {
+ throw new Error("Proof Bundle issuance must be the final test event");
+ }
+ const issuance = events[issuanceIndex];
+ if (issuance === undefined || issuance.outputHashes.length < 2) {
+ throw new Error("Proof Bundle issuance hash is unavailable");
+ }
+ const outputHashes = [...issuance.outputHashes];
+ outputHashes[outputHashes.length - 1] = await hashCanonical(proofBundle);
+ const { eventHash: _eventHash, ...unsignedIssuance } = issuance;
+ const reboundIssuance = { ...unsignedIssuance, outputHashes };
+ events[issuanceIndex] = {
+ ...reboundIssuance,
+ eventHash: await hashCanonical(reboundIssuance),
+ };
+ storedProofSession.proofBundle = structuredClone(proofBundle);
+ harness.sessionRepository.replaceEventsForIntegrityTest(
+ sessionId,
+ events,
+ );
+ harness.sessionRepository.replaceSessionForIntegrityTest(
+ storedProofSession,
+ );
+ };
const { integrity: _integrity, ...proofDraft } =
storedProofSession.proofBundle;
- const proofSigningKey =
- "counterlab-test-proof-signing-key-with-sufficient-entropy";
- storedProofSession.proofBundle = createProofBundle(proofDraft, {
+ const signedProofBundle = createProofBundle(proofDraft, {
signingKey: proofSigningKey,
scientificEngineSnapshotHash: scientificEngineSnapshotValue.authorityHash,
});
- const signedProofBundle = structuredClone(storedProofSession.proofBundle);
- harness.sessionRepository.replaceSessionForIntegrityTest(
- storedProofSession,
- );
- const proofSigningEnv = {
- COUNTERLAB_SIGNING_KEY: proofSigningKey,
- } as unknown as Env & Record;
+ await replaceIssuedProofBundle(signedProofBundle);
for (const path of [
`/api/sessions/${sessionId}`,
`/api/sessions/${sessionId}/proof-bundle`,
@@ -6437,6 +6500,13 @@ describe("Cloudflare Worker API", () => {
expect((await app.request(path, undefined, proofSigningEnv)).status).toBe(
200,
);
+ const blankKey = await app.request(path, undefined, {
+ COUNTERLAB_SIGNING_KEY: " ",
+ } as unknown as Env & Record);
+ expect(blankKey.status).toBe(503);
+ await expect(blankKey.json()).resolves.toMatchObject({
+ error: { code: "PROOF_SIGNING_KEY_REQUIRED" },
+ });
}
if (signedProofBundle.integrity.mode !== "hmac-signed") {
@@ -6458,11 +6528,13 @@ describe("Cloudflare Worker API", () => {
`/api/sessions/${sessionId}`,
`/api/sessions/${sessionId}/proof-bundle`,
]) {
- const downgraded = await app.request(path, undefined, proofSigningEnv);
- expect(downgraded.status).toBe(409);
- await expect(downgraded.json()).resolves.toMatchObject({
- error: { code: "PROOF_BUNDLE_INVALID" },
- });
+ for (const env of [undefined, proofSigningEnv]) {
+ const downgraded = await app.request(path, undefined, env);
+ expect(downgraded.status).toBe(409);
+ await expect(downgraded.json()).resolves.toMatchObject({
+ error: { code: "PROOF_BUNDLE_INVALID" },
+ });
+ }
}
storedProofSession.proofBundle = structuredClone(signedProofBundle);
@@ -6497,6 +6569,46 @@ describe("Cloudflare Worker API", () => {
error: { code: "PROOF_BUNDLE_INVALID" },
});
}
+
+ storedProofSession.proofBundle = structuredClone(signedProofBundle);
+ harness.sessionRepository.replaceSessionForIntegrityTest(
+ storedProofSession,
+ );
+ const issuanceEvents =
+ await harness.sessionRepository.listEvents(sessionId);
+ const issuanceIndex = issuanceEvents.findIndex(
+ (event) => event.kind === "reasoning_diff.issued",
+ );
+ const issuance = issuanceEvents[issuanceIndex];
+ if (issuance === undefined) {
+ throw new Error("Proof Bundle issuance event is unavailable");
+ }
+ const { eventHash: _issuanceHash, ...unsignedIssuance } = issuance;
+ const mismatchedIssuance = {
+ ...unsignedIssuance,
+ outputHashes: [
+ ...unsignedIssuance.outputHashes.slice(0, -1),
+ "f".repeat(64),
+ ],
+ };
+ issuanceEvents[issuanceIndex] = {
+ ...mismatchedIssuance,
+ eventHash: await hashCanonical(mismatchedIssuance),
+ };
+ harness.sessionRepository.replaceEventsForIntegrityTest(
+ sessionId,
+ issuanceEvents,
+ );
+ for (const path of [
+ `/api/sessions/${sessionId}`,
+ `/api/sessions/${sessionId}/proof-bundle`,
+ ]) {
+ const detached = await app.request(path, undefined, proofSigningEnv);
+ expect(detached.status).toBe(409);
+ await expect(detached.json()).resolves.toMatchObject({
+ error: { code: "PROOF_BUNDLE_INVALID" },
+ });
+ }
});
it("budgets a v5 compiler job through two bounded repairs and scopes its token past the deadline", async () => {
diff --git a/apps/web/worker/api.ts b/apps/web/worker/api.ts
index 3a3e5379..5a021d65 100644
--- a/apps/web/worker/api.ts
+++ b/apps/web/worker/api.ts
@@ -4144,14 +4144,43 @@ function requireStoredSampleClaimScope(
}
}
-function requireStoredProofBundleIntegrity(
+async function requireStoredProofBundleIntegrity(
session: Awaited>,
+ service: SessionService,
signingKey: string | undefined,
-): void {
+): Promise {
if (session.proofBundle === undefined) return;
+
+ try {
+ const chain = verifyEvidenceChain(await service.listEvents(session.id));
+ const proofBundleHash = await hashCanonical(session.proofBundle);
+ const issuanceEvents = chain.events.filter(
+ (event) => event.kind === "reasoning_diff.issued",
+ );
+ if (
+ chain.sessionId !== session.id ||
+ issuanceEvents.length !== 1 ||
+ !issuanceEvents[0]?.outputHashes.includes(proofBundleHash)
+ ) {
+ throw new Error(
+ "stored Proof Bundle is not bound to its issuance evidence",
+ );
+ }
+ } catch {
+ throw new ApiInputError(
+ "PROOF_BUNDLE_INVALID",
+ "The stored Proof Bundle failed integrity or lineage validation",
+ 409,
+ );
+ }
+
+ const usableSigningKey =
+ signingKey === undefined || signingKey.trim().length === 0
+ ? undefined
+ : signingKey;
if (
session.proofBundle.integrity.mode === "hmac-signed" &&
- signingKey === undefined
+ usableSigningKey === undefined
) {
throw new ApiInputError(
"PROOF_SIGNING_KEY_REQUIRED",
@@ -4160,8 +4189,6 @@ function requireStoredProofBundleIntegrity(
);
}
try {
- const requiresConfiguredSigning =
- session.proofBundle.schemaVersion === "2" && signingKey !== undefined;
const proofBundle = validateProofBundle(session.proofBundle, {
...(session.proofBundle.schemaVersion === "2"
? {
@@ -4169,10 +4196,9 @@ function requireStoredProofBundleIntegrity(
scientificEngineSnapshotValue.authorityHash,
}
: {}),
- ...((session.proofBundle.integrity.mode === "hmac-signed" ||
- requiresConfiguredSigning) &&
- signingKey !== undefined
- ? { signingKey }
+ ...(session.proofBundle.integrity.mode === "hmac-signed" &&
+ usableSigningKey !== undefined
+ ? { signingKey: usableSigningKey }
: {}),
});
const modeMatches =
@@ -4210,12 +4236,14 @@ function isFile(value: string | File | null): value is File {
return value !== null && typeof value !== "string";
}
-function statePayload(
+async function statePayload(
context: Context,
session: Awaited>,
+ options: ApiOptions,
) {
- requireStoredProofBundleIntegrity(
+ await requireStoredProofBundleIntegrity(
session,
+ sessionService(context, options),
context.env?.COUNTERLAB_SIGNING_KEY,
);
const publicProofCapsule =
@@ -4726,7 +4754,7 @@ export function createApi(options: ApiOptions = {}) {
}
return context.json(
jsonSuccess({
- ...statePayload(context, session),
+ ...(await statePayload(context, session, options)),
...(ownerCapability === undefined ? {} : { ownerCapability }),
}),
201,
@@ -4778,7 +4806,7 @@ export function createApi(options: ApiOptions = {}) {
}
return context.json(
jsonSuccess({
- ...statePayload(context, session),
+ ...(await statePayload(context, session, options)),
...(ownerCapability === undefined ? {} : { ownerCapability }),
}),
201,
@@ -4799,7 +4827,9 @@ export function createApi(options: ApiOptions = {}) {
context.req.param("sessionId"),
);
requireStoredSampleClaimScope(session);
- return context.json(jsonSuccess(statePayload(context, session)));
+ return context.json(
+ jsonSuccess(await statePayload(context, session, options)),
+ );
});
app.get("/api/sessions/:sessionId/artifact", async (context) => {
@@ -4957,7 +4987,7 @@ export function createApi(options: ApiOptions = {}) {
}
return context.json(
jsonSuccess({
- ...statePayload(context, restarted),
+ ...(await statePayload(context, restarted, options)),
...(ownerCapability === undefined ? {} : { ownerCapability }),
}),
created ? 201 : 200,
@@ -5164,7 +5194,9 @@ export function createApi(options: ApiOptions = {}) {
insufficient,
{ actor: "system", modelId: "concept-router-v1" },
);
- return context.json(jsonSuccess(statePayload(context, proposed)));
+ return context.json(
+ jsonSuccess(await statePayload(context, proposed, options)),
+ );
}
if (session.mode.kind === "live_notebook") {
@@ -5243,7 +5275,9 @@ export function createApi(options: ApiOptions = {}) {
promptHash: result.provenance.promptHash,
},
);
- return context.json(jsonSuccess(statePayload(context, proposed)));
+ return context.json(
+ jsonSuccess(await statePayload(context, proposed, options)),
+ );
} finally {
await releaseAdmissionBestEffort(
context,
@@ -5272,7 +5306,9 @@ export function createApi(options: ApiOptions = {}) {
actor: "system",
modelId: result.provenance.approvalId,
});
- return context.json(jsonSuccess(statePayload(context, proposed)));
+ return context.json(
+ jsonSuccess(await statePayload(context, proposed, options)),
+ );
});
app.post("/api/sessions/:sessionId/belief-test/confirm", async (context) => {
@@ -5284,7 +5320,11 @@ export function createApi(options: ApiOptions = {}) {
if (input.action === "confirm") {
return context.json(
jsonSuccess(
- statePayload(context, await service.confirmBeliefTest(sessionId)),
+ await statePayload(
+ context,
+ await service.confirmBeliefTest(sessionId),
+ options,
+ ),
),
);
}
@@ -5297,9 +5337,10 @@ export function createApi(options: ApiOptions = {}) {
}
return context.json(
jsonSuccess(
- statePayload(
+ await statePayload(
context,
await service.editBeliefSpecV2(sessionId, input.beliefSpec),
+ options,
),
),
);
@@ -5311,9 +5352,10 @@ export function createApi(options: ApiOptions = {}) {
}
return context.json(
jsonSuccess(
- statePayload(
+ await statePayload(
context,
await service.editBeliefTest(sessionId, input.beliefTest),
+ options,
),
),
);
@@ -5321,18 +5363,20 @@ export function createApi(options: ApiOptions = {}) {
if (input.action === "reject") {
return context.json(
jsonSuccess(
- statePayload(
+ await statePayload(
context,
await service.rejectBeliefTest(sessionId, input.reason),
+ options,
),
),
);
}
return context.json(
jsonSuccess(
- statePayload(
+ await statePayload(
context,
await service.markInsufficientEvidence(sessionId, input.reason),
+ options,
),
),
);
@@ -5369,9 +5413,10 @@ export function createApi(options: ApiOptions = {}) {
};
return context.json(
jsonSuccess(
- statePayload(
+ await statePayload(
context,
await service.commitPrediction(sessionId, prediction),
+ options,
),
),
201,
@@ -5540,7 +5585,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, current),
+ ...(await statePayload(context, current, options)),
runnerJob,
reused: true as const,
}),
@@ -5675,7 +5720,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, started),
+ ...(await statePayload(context, started, options)),
runnerJob: starting,
...(claimed.reused ? { reused: true as const } : {}),
}),
@@ -5699,7 +5744,9 @@ export function createApi(options: ApiOptions = {}) {
[...sampleAuthority.evidenceHashes],
sampleAuthority.operationSummary,
);
- return context.json(jsonSuccess(statePayload(context, verified)));
+ return context.json(
+ jsonSuccess(await statePayload(context, verified, options)),
+ );
});
app.get("/api/runner/jobs/:jobId/input", async (context) => {
@@ -6729,7 +6776,7 @@ export function createApi(options: ApiOptions = {}) {
const projectedSession = await projectCancellation();
return context.json(
jsonSuccess({
- ...statePayload(context, projectedSession),
+ ...(await statePayload(context, projectedSession, options)),
runnerJob: job,
reused: true as const,
runnerAcknowledged: true,
@@ -6798,7 +6845,7 @@ export function createApi(options: ApiOptions = {}) {
}
return context.json(
jsonSuccess({
- ...statePayload(context, updatedSession),
+ ...(await statePayload(context, updatedSession, options)),
runnerJob: cancelled,
reused: false as const,
runnerAcknowledged,
@@ -8070,7 +8117,7 @@ export function createApi(options: ApiOptions = {}) {
jsonSuccess({
duplicate: completed.duplicate,
runnerJob: completed.job,
- session: statePayload(context, updatedSession),
+ session: await statePayload(context, updatedSession, options),
verification,
}),
);
@@ -8336,7 +8383,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, current),
+ ...(await statePayload(context, current, options)),
runnerJob,
reused: true as const,
}),
@@ -8413,7 +8460,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, current),
+ ...(await statePayload(context, current, options)),
runnerJob: starting,
...(claimed.reused ? { reused: true as const } : {}),
}),
@@ -8555,7 +8602,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, current),
+ ...(await statePayload(context, current, options)),
runnerJob: starting,
...(claimed.reused ? { reused: true as const } : {}),
}),
@@ -8574,7 +8621,9 @@ export function createApi(options: ApiOptions = {}) {
sessionId,
sampleResult,
);
- return context.json(jsonSuccess(statePayload(context, completed)));
+ return context.json(
+ jsonSuccess(await statePayload(context, completed, options)),
+ );
});
app.post("/api/sessions/:sessionId/boundary/run", async (context) => {
@@ -8734,7 +8783,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, current),
+ ...(await statePayload(context, current, options)),
runnerJob,
reused: true as const,
}),
@@ -8787,7 +8836,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, current),
+ ...(await statePayload(context, current, options)),
runnerJob: starting,
...(claimed.reused ? { reused: true as const } : {}),
}),
@@ -9019,7 +9068,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, current),
+ ...(await statePayload(context, current, options)),
runnerJob: starting,
selectedRunId: derived.selectedRunId,
configurationHash,
@@ -9292,7 +9341,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, current),
+ ...(await statePayload(context, current, options)),
runnerJob: starting,
selectedRunId,
configurationHash,
@@ -9580,7 +9629,9 @@ export function createApi(options: ApiOptions = {}) {
const sessionId = context.req.param("sessionId");
requireMutableSession(await service.getSession(sessionId));
const updated = await service.recordRevision(sessionId, revision);
- return context.json(jsonSuccess(statePayload(context, updated)));
+ return context.json(
+ jsonSuccess(await statePayload(context, updated, options)),
+ );
});
app.post("/api/sessions/:sessionId/transfer", async (context) => {
@@ -9653,7 +9704,9 @@ export function createApi(options: ApiOptions = {}) {
const evaluatedAt = (options.now?.() ?? new Date()).toISOString();
const result = await evaluateSubmission(evaluatedAt);
const updated = await service.recordTransferResult(sessionId, result);
- return context.json(jsonSuccess(statePayload(context, updated)));
+ return context.json(
+ jsonSuccess(await statePayload(context, updated, options)),
+ );
});
app.post("/api/sessions/:sessionId/patch/compile", async (context) => {
@@ -9850,7 +9903,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, current),
+ ...(await statePayload(context, current, options)),
runnerJob,
reused: true as const,
}),
@@ -9935,7 +9988,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, started),
+ ...(await statePayload(context, started, options)),
runnerJob: starting,
...(claimed.reused ? { reused: true as const } : {}),
}),
@@ -10033,7 +10086,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, current),
+ ...(await statePayload(context, current, options)),
runnerJob,
reused: true as const,
}),
@@ -10113,7 +10166,7 @@ export function createApi(options: ApiOptions = {}) {
});
return context.json(
jsonSuccess({
- ...statePayload(context, started),
+ ...(await statePayload(context, started, options)),
runnerJob: starting,
...(claimed.reused ? { reused: true as const } : {}),
}),
@@ -10182,7 +10235,7 @@ export function createApi(options: ApiOptions = {}) {
const updated = await service.verifyPatch(sessionId, patchResult);
return context.json(
jsonSuccess({
- ...statePayload(context, updated),
+ ...(await statePayload(context, updated, options)),
patch: patchResult,
kernelVerification: samplePatchKernelResult,
}),
@@ -10259,9 +10312,8 @@ export function createApi(options: ApiOptions = {}) {
});
app.get("/api/sessions/:sessionId/proof-bundle", async (context) => {
- const session = await sessionService(context, options).getSession(
- context.req.param("sessionId"),
- );
+ const service = sessionService(context, options);
+ const session = await service.getSession(context.req.param("sessionId"));
requireStoredSampleClaimScope(session);
if (session.proofBundle === undefined) {
throw new ApiInputError(
@@ -10270,8 +10322,9 @@ export function createApi(options: ApiOptions = {}) {
409,
);
}
- requireStoredProofBundleIntegrity(
+ await requireStoredProofBundleIntegrity(
session,
+ service,
context.env?.COUNTERLAB_SIGNING_KEY,
);
context.header(
From 7e25a098b74d18b30d84a1bc80938ef1d94e95e9 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 14:34:20 +0530
Subject: [PATCH 090/143] fix(live): require exact release readiness
---
apps/web/src/App.test.tsx | 68 ++++++++++-
apps/web/src/App.tsx | 48 ++++----
apps/web/src/api.ts | 14 +++
apps/web/src/features/judge/JudgeModeView.tsx | 16 +--
apps/web/worker/api.test.ts | 107 +++++++++++++++++-
apps/web/worker/api.ts | 15 +++
6 files changed, 229 insertions(+), 39 deletions(-)
diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
index 2f5465ed..fe14745f 100644
--- a/apps/web/src/App.test.tsx
+++ b/apps/web/src/App.test.tsx
@@ -1272,6 +1272,72 @@ describe("CounterLab judged flow", () => {
expect(document.body).not.toHaveTextContent(question);
});
+ it("checks exact live readiness before the evidence chooser sends a notebook", async () => {
+ const user = userEvent.setup();
+ const fetcher = installApi();
+ render( );
+
+ await user.type(
+ screen.getByRole("textbox", { name: /your question or claim/i }),
+ "Will this score hold for new customers?",
+ );
+ await user.click(screen.getByRole("button", { name: /test this claim/i }));
+ await user.upload(
+ screen.getByLabelText(/attach a supported notebook/i),
+ new File(["{}"], uploadedArtifact.fileName, {
+ type: "application/json",
+ }),
+ );
+
+ expect(
+ await screen.findByRole("heading", { name: /test my notebook/i }),
+ ).toBeInTheDocument();
+ expect(
+ fetcher.mock.calls.filter(
+ ([path]) => String(path) === "/api/health?readiness=probe",
+ ),
+ ).toHaveLength(1);
+ expect(
+ fetcher.mock.calls.some(([path]) => String(path) === "/api/artifacts"),
+ ).toBe(false);
+ expect(
+ fetcher.mock.calls.some(
+ ([path]) => String(path) === "/api/live/sessions",
+ ),
+ ).toBe(false);
+ });
+
+ it("uploads from the evidence chooser only after exact live readiness passes", async () => {
+ const user = userEvent.setup();
+ const fetcher = installApi({ liveGpt: "configured", runner: "configured" });
+ render( );
+
+ await user.type(
+ screen.getByRole("textbox", { name: /your question or claim/i }),
+ "Will this score hold for new customers?",
+ );
+ await user.click(screen.getByRole("button", { name: /test this claim/i }));
+ await user.upload(
+ screen.getByLabelText(/attach a supported notebook/i),
+ new File(["{}"], uploadedArtifact.fileName, {
+ type: "application/json",
+ }),
+ );
+
+ expect(
+ await screen.findByRole("heading", {
+ name: /what do you think the score means/i,
+ }),
+ ).toBeInTheDocument();
+ const requestedPaths = fetcher.mock.calls.map(([path]) => String(path));
+ expect(requestedPaths.indexOf("/api/health?readiness=probe")).toBeLessThan(
+ requestedPaths.indexOf("/api/artifacts"),
+ );
+ expect(requestedPaths.indexOf("/api/artifacts")).toBeLessThan(
+ requestedPaths.indexOf("/api/live/sessions"),
+ );
+ });
+
it("restores a saved question on the refresh-safe new investigation route", async () => {
const question = "Does this result generalize beyond familiar rows?";
window.localStorage.setItem("counterlab.claim", question);
@@ -1498,7 +1564,7 @@ describe("CounterLab judged flow", () => {
it("accepts a notebook from the question-first landing without running it", async () => {
const user = userEvent.setup();
- const fetcher = installApi();
+ const fetcher = installApi({ liveGpt: "configured", runner: "configured" });
render( );
await user.upload(
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 808af02c..4e52da13 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -19,6 +19,7 @@ import {
ApiClientError,
counterLabApi,
getSessionBeliefAuthority,
+ isExactLiveAuthorityReady,
type ArtifactView,
type BeliefAnalysisPreview,
type BeliefTest,
@@ -4090,13 +4091,7 @@ function LiveSetup({
busy: boolean;
}) {
const configured = health?.liveGpt === "configured";
- const runnerConfigured =
- health?.readiness === "ready" &&
- health?.liveCodex === "configured" &&
- health.liveKernel === "configured" &&
- health.sandbox === "credential-and-privilege-boundary" &&
- health.generationFilesystemReadIsolation === "OS_ENFORCED" &&
- health.release?.status === "bound";
+ const runnerConfigured = isExactLiveAuthorityReady(health);
return (
@@ -5366,22 +5361,26 @@ export function App() {
stage,
]);
- const checkLiveCapabilities = async (probeReadiness: boolean) => {
+ const checkLiveCapabilities = async (
+ probeReadiness: boolean,
+ ): Promise
=> {
const generation = userRequestGeneration.current;
setCheckingLiveHealth(true);
setLiveHealthError(null);
setLiveHealth(null);
try {
const health = await counterLabApi.getHealth({ probeReadiness });
- if (userRequestGeneration.current !== generation) return;
+ if (userRequestGeneration.current !== generation) return null;
setLiveHealth(health);
+ return health;
} catch (caught) {
- if (userRequestGeneration.current !== generation) return;
+ if (userRequestGeneration.current !== generation) return null;
setLiveHealthError(
caught instanceof ApiClientError
? "CounterLab could not verify the capability response."
: "CounterLab could not reach the capability service.",
);
+ return null;
} finally {
if (userRequestGeneration.current === generation) {
setCheckingLiveHealth(false);
@@ -5583,6 +5582,19 @@ export function App() {
});
};
+ const uploadNotebookAfterReadiness = (file: File) => {
+ setJudgeMode(false);
+ setMode("live");
+ setStage("live-setup");
+ setError(null);
+ window.localStorage.setItem(storageKeys.mode, "live");
+ void (async () => {
+ const health = await checkLiveCapabilities(true);
+ if (!isExactLiveAuthorityReady(health)) return;
+ uploadNotebook(file);
+ })();
+ };
+
const retryLiveSessionSetup = () => {
if (artifact?.support.status !== "SUPPORTED" || session !== null) return;
void withRequest((request) => createLiveArtifactSession(artifact, request));
@@ -5951,11 +5963,7 @@ export function App() {
{
- setMode("live");
- window.localStorage.setItem(storageKeys.mode, "live");
- uploadNotebook(file);
- }}
+ attachNotebook={uploadNotebookAfterReadiness}
testClaim={() => {
window.localStorage.setItem(storageKeys.claim, claim);
setJudgeMode(false);
@@ -5968,20 +5976,16 @@ export function App() {
setStage("question-path");
}}
chooseMode={chooseMode}
- busy={busy}
+ busy={busy || checkingLiveHealth}
recentSessions={recentWorkSessions}
/>
)}
{stage === "question-path" && mode === "live" && (
chooseMode("instant")}
- onAttachNotebook={(file) => {
- setMode("live");
- window.localStorage.setItem(storageKeys.mode, "live");
- uploadNotebook(file);
- }}
+ onAttachNotebook={uploadNotebookAfterReadiness}
onCheckLiveTools={() => chooseMode("live")}
onBack={() => {
window.history.replaceState({}, "", "/");
diff --git a/apps/web/src/api.ts b/apps/web/src/api.ts
index 1bb7c3a0..f8dd79b6 100644
--- a/apps/web/src/api.ts
+++ b/apps/web/src/api.ts
@@ -162,6 +162,20 @@ export const CapabilityHealthSchema = z
export type CapabilityHealth = z.infer;
+export function isExactLiveAuthorityReady(
+ health: CapabilityHealth | null,
+): boolean {
+ return (
+ health?.readiness === "ready" &&
+ health.liveGpt === "configured" &&
+ health.liveCodex === "configured" &&
+ health.liveKernel === "configured" &&
+ health.sandbox === "credential-and-privilege-boundary" &&
+ health.generationFilesystemReadIsolation === "OS_ENFORCED" &&
+ health.release?.status === "bound"
+ );
+}
+
const ApiErrorEnvelopeSchema = z
.object({
ok: z.literal(false),
diff --git a/apps/web/src/features/judge/JudgeModeView.tsx b/apps/web/src/features/judge/JudgeModeView.tsx
index 57079e3e..31e8892f 100644
--- a/apps/web/src/features/judge/JudgeModeView.tsx
+++ b/apps/web/src/features/judge/JudgeModeView.tsx
@@ -1,6 +1,6 @@
import { useState } from "react";
-import type { CapabilityHealth } from "../../api";
+import { isExactLiveAuthorityReady, type CapabilityHealth } from "../../api";
import { VerifiedBeliefBreakMechanism } from "../../components/learner/VerifiedBeliefBreakTheater";
import { verifiedReplay } from "../../sampleReplayMetadata";
import styles from "./JudgeModeView.module.css";
@@ -15,18 +15,6 @@ const learnerStages = [
["06", "Repair", "Unlock the smallest verified notebook correction."],
] as const;
-function liveAuthorityReady(health: CapabilityHealth | null): boolean {
- return (
- health?.readiness === "ready" &&
- health.liveGpt === "configured" &&
- health.liveCodex === "configured" &&
- health.liveKernel === "configured" &&
- health.sandbox === "credential-and-privilege-boundary" &&
- health.generationFilesystemReadIsolation === "OS_ENFORCED" &&
- health.release?.status === "bound"
- );
-}
-
export function JudgeModeView({
health,
healthPending,
@@ -40,7 +28,7 @@ export function JudgeModeView({
onRetryHealth: () => void;
onStartSample: () => void;
}) {
- const liveReady = liveAuthorityReady(health);
+ const liveReady = isExactLiveAuthorityReady(health);
const release = health?.release?.status === "bound" ? health.release : null;
const generationIsolationBoundary =
release !== null &&
diff --git a/apps/web/worker/api.test.ts b/apps/web/worker/api.test.ts
index f2fd9e68..36258dfe 100644
--- a/apps/web/worker/api.test.ts
+++ b/apps/web/worker/api.test.ts
@@ -175,6 +175,10 @@ class MemorySessionRepository implements SessionRepository {
this.sessions.set(session.id, structuredClone(session));
}
+ sessionCountForTest(): number {
+ return this.sessions.size;
+ }
+
close(): void {}
}
@@ -3026,8 +3030,14 @@ describe("Cloudflare Worker API", () => {
artifactCapability: secondBody.data.ownerCapability,
}),
});
- expect(firstSession.status).toBe(201);
- expect(secondSession.status).toBe(201);
+ expect(firstSession.status).toBe(503);
+ expect(secondSession.status).toBe(503);
+ await expect(firstSession.json()).resolves.toMatchObject({
+ error: { code: "LIVE_AUTHORITY_NOT_READY" },
+ });
+ await expect(secondSession.json()).resolves.toMatchObject({
+ error: { code: "LIVE_AUTHORITY_NOT_READY" },
+ });
});
it("canonicalizes an empty browser MIME type before binding the upload", async () => {
@@ -4228,6 +4238,99 @@ describe("Cloudflare Worker API", () => {
});
});
+ it("refuses a supported live session when hosted release readiness is unqualified", async () => {
+ const sessionRepository = new MemorySessionRepository();
+ const artifactStore = new MemoryArtifactStore();
+ const app = createApi({
+ sessionRepository,
+ artifactStore,
+ runnerJobRepository: new MemoryRunnerJobRepository(),
+ runnerObjectStore: new MemoryRunnerObjectStore(),
+ runnerDispatcher: new CapturingRunnerDispatcher(),
+ runnerSigningPrivateKey: TEST_RUNNER_SIGNING_PRIVATE_KEY,
+ admissionControl: new CapturingAdmissionControl(),
+ admissionHmacKey: "admission-test-key-with-sufficient-entropy",
+ now: () => new Date("2026-07-14T10:00:00.000Z"),
+ id: (prefix) => `${prefix}_unqualified_live`,
+ });
+ const sampleResponse = await app.request("/api/artifacts", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ sample: true }),
+ });
+ const sampleBody = (await sampleResponse.json()) as {
+ data: ArtifactManifest;
+ };
+ const uploaded = await saveUploadedArtifact(
+ artifactStore,
+ sampleBody.data.artifactId,
+ );
+
+ const response = await postJson(app, "/api/live/sessions", {
+ artifactId: uploaded.artifactId,
+ });
+
+ expect(response.status).toBe(503);
+ await expect(response.json()).resolves.toMatchObject({
+ error: {
+ code: "LIVE_AUTHORITY_NOT_READY",
+ retryable: true,
+ },
+ });
+ expect(sessionRepository.sessionCountForTest()).toBe(0);
+
+ const workerEvidenceCommit = "a".repeat(40);
+ const readyResponse = await app.request(
+ "/api/live/sessions",
+ {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ artifactId: uploaded.artifactId }),
+ },
+ {
+ OPENAI_API_KEY: "configured-server-key",
+ COUNTERLAB_WORKER_EVIDENCE_COMMIT: workerEvidenceCommit,
+ COUNTERLAB_RUNNER_SOURCE_COMMIT: "b".repeat(40),
+ COUNTERLAB_RUNNER_IMAGE_DIGEST: `sha256:${"c".repeat(64)}`,
+ COUNTERLAB_GENERATION_ISOLATION_EVIDENCE_SHA256: "5".repeat(64),
+ COUNTERLAB_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(64),
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_EVIDENCE_SHA256:
+ "7".repeat(64),
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_PROBE_SHA256: "6".repeat(
+ 64,
+ ),
+ COUNTERLAB_RELEASE_CHECK_GENERATION_ISOLATION_VERIFIED_AT:
+ "2026-07-19T05:31:00.000+05:30",
+ COUNTERLAB_TIMEOUT_CLEANUP_RECEIPT_SHA256: "d".repeat(64),
+ COUNTERLAB_AGGREGATE_LIMIT_EVIDENCE_SHA256: "9".repeat(64),
+ COUNTERLAB_RUNTIME_POLICY_SHA256: "e".repeat(64),
+ COUNTERLAB_PROOF_DEPENDENCY_MANIFEST_SHA256: "f".repeat(64),
+ COUNTERLAB_WORKER_ARTIFACT_CLASSIFICATION: "PROCESS_BOUND_PARTIAL",
+ COUNTERLAB_WORKER_ARTIFACT_MANIFEST_SHA256: "1".repeat(64),
+ COUNTERLAB_WORKER_BUNDLE_SHA256: "2".repeat(64),
+ COUNTERLAB_CLIENT_ASSETS_SHA256: "3".repeat(64),
+ COUNTERLAB_CLIENT_ASSET_COUNT: "27",
+ COUNTERLAB_CLIENT_PUBLIC_ASSETS_SHA256: "4".repeat(64),
+ COUNTERLAB_CLIENT_PUBLIC_ASSET_COUNT: "25",
+ COUNTERLAB_VITE_VERSION: "8.1.4",
+ COUNTERLAB_WRANGLER_VERSION: "4.110.0",
+ CF_VERSION_METADATA: {
+ id: "11111111-2222-3333-4444-555555555555",
+ tag: `git-${workerEvidenceCommit}`,
+ timestamp: "2026-07-19T00:00:00.000Z",
+ },
+ } as unknown as Env,
+ );
+ expect(readyResponse.status).toBe(201);
+ await expect(readyResponse.json()).resolves.toMatchObject({
+ data: {
+ artifactId: uploaded.artifactId,
+ mode: { kind: "live_notebook" },
+ },
+ });
+ expect(sessionRepository.sessionCountForTest()).toBe(1);
+ });
+
it("stores strict learner interactions outside session and evidence authority", async () => {
const harness = await sessionHarness("sample");
const interactions = new MemoryLearnerInteractionRepository();
diff --git a/apps/web/worker/api.ts b/apps/web/worker/api.ts
index 5a021d65..2c38a841 100644
--- a/apps/web/worker/api.ts
+++ b/apps/web/worker/api.ts
@@ -4314,6 +4314,20 @@ export function createApi(options: ApiOptions = {}) {
activeReadinessProbe = probe;
return probe;
};
+ const requireExactLiveReleaseReadiness = async (
+ context: Context,
+ ) => {
+ if (!admissionEnabled(options)) return;
+ const snapshot = await observeReadiness(context);
+ if (!snapshot.ready || snapshot.release.status !== "bound") {
+ throw new ApiInputError(
+ "LIVE_AUTHORITY_NOT_READY",
+ "The exact released live runtime is not ready. Your supported notebook remains available for retry; no live investigation was created.",
+ 503,
+ true,
+ );
+ }
+ };
app.get("/ready", async (context) => {
const snapshot = await observeReadiness(context);
@@ -4791,6 +4805,7 @@ export function createApi(options: ApiOptions = {}) {
422,
);
}
+ await requireExactLiveReleaseReadiness(context);
const session = await sessionService(context, options).createSession({
artifactId: input.artifactId,
mode: { kind: "live_notebook" },
From d88b1590a7214cf99874d59eaae1b8659edaeea7 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 14:38:42 +0530
Subject: [PATCH 091/143] docs(progress): reconcile release readiness
---
docs/PROGRESS.md | 88 ++++++++++++++
.../19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md | 112 +++++++++++-------
2 files changed, 155 insertions(+), 45 deletions(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index a1fc0703..421978dd 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4095,3 +4095,91 @@ staging, hiding, overwriting, or deleting it.
The release remains **NO-GO** at clean-source, real-sentinel, exact-image,
authentication, CloakBrowser, deployment, and submission gates.
+
+## Proof, live-readiness, and active-browser checkpoint — 2026-07-21T09:06:33Z
+
+Deadline: `2026-07-22T00:00:00Z`; **14 hours 53 minutes 27 seconds remained at
+this checkpoint**. Committed HEAD is
+`7e25a098b74d18b30d84a1bc80938ef1d94e95e9`, eight commits ahead of
+`origin/feat/learner-ux-v6.1`. The only worktree path is the preserved,
+unstaged FUSE tombstone `scripts/.fuse_hidden0000a9600000801f`; it remains
+unread, untouched, and untracked.
+
+### Completed and independently reviewed
+
+| Commit | Result | Exact verification |
+| --- | --- | --- |
+| `84ef49d fix(web): validate bound release isolation` | The public health contract now validates the exact bound source, Worker, client assets, runtime policy, and isolation evidence instead of accepting a partial release claim. | Focused Worker health test **1 passed, 40 skipped**; Worker TypeScript passed. |
+| `547cc0f fix(worker): preserve historical proof validation` | Complete stored Proof Bundles are anchored to exactly one issuance event before their persisted integrity mode is evaluated. Historical unsigned evidence survives later key configuration; signed evidence still fails closed on absent, blank, rotated, corrupted, detached, or downgraded authority. | Worker API **106/106 passed**; Proof Bundle package **12/12 passed**; Worker TypeScript, scoped Prettier, six-file secret scan, and Git whitespace passed. |
+| `7e25a09 fix(live): require exact release readiness` | Both learner notebook entry surfaces probe exact readiness before sending notebook bytes, and the Worker independently rechecks exact readiness immediately before live-session creation. Unqualified releases return retryable `LIVE_AUTHORITY_NOT_READY`; Sample and Replay remain separate. | App **74/74 passed**; Worker API **107/107 passed**; API/Judge **50/50 passed**; post-review focus **3/3 passed**; web and Worker TypeScript, scoped Prettier, six-file secret scan, Git whitespace, and production build passed. |
+
+The correct contained package path is now verified. Bare `pnpm` resolves to an
+external wrapper and fails with `[ERROR] unable to open database file`; it is
+not a product failure. `./scripts/run-contained-pnpm.sh` uses the repository's
+pinned pnpm **11.13.1** and built successfully: Worker **391 modules,
+1,868.90 kB / 360.50 kB gzip**; client **231 modules**, main chunk **449.75 kB
+/ 129.26 kB gzip**.
+
+Independent authority review found no justification for isolating the outer
+Codex App Server from provider egress: generated commands already receive
+`networkAccess:false`, while the outer bounded client requires provider access.
+The final exact image still needs a real negative command-egress probe. A
+behavioral Unix-socket half-close integration test remains a P1 improvement;
+the current regression is source-level, while the live `rt-mainrel04` response
+also proved the repaired path operationally.
+
+The operational code-review dependency graph is current in
+`docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md`.
+The last structural snapshot remains **531 files, 5,515 nodes, and 93,346
+edges**; the current branch delta remains **140 files**. Those structural counts
+are not relabelled as current-source output without rerunning their generator.
+
+### Browser and Cloudflare truth
+
+- `codex mcp get playwright_safe` now succeeds and reports the Cloak launcher
+ registered and enabled. This already-running session has no loaded
+ `playwright_safe` tool namespace or injected `CLOAK_CDP_ENDPOINT`; the
+ mandatory fresh-session qualification therefore remains **0 journeys**.
+ Stock Chromium is not substituted.
+- Browser-evidence review found three source gaps before qualification can emit
+ a schema-valid receipt: explicit coverage for `768x1024`, `1366x768`, and
+ `1920x1080`; INP collection; and a deterministic reporter for the 40 journey,
+ manual accessibility, evidence-index, and final qualification receipts.
+- Fresh contained `wrangler whoami --json` returned
+ `{"loggedIn":false}`. No Cloudflare mutation, migration, qualification, or
+ deployment occurred. Authentication outside the contained environment is
+ not claimed as release identity.
+
+### Exact remaining dependency order
+
+1. Add and test the missing CloakBrowser evidence instrumentation while keeping
+ all browser state repository-contained; restart Codex only when ready to run
+ the qualifying tool namespace.
+2. Resolve the pre-existing FUSE handle by its owner without deleting, hiding,
+ staging, or rewriting the tombstone; verify an empty physical-root tree.
+3. Establish constitution-compatible authority for the read-only real cgroup
+ and owned-process observation, freeze source `S`, build one exact image, and
+ execute the real aggregate sentinel and negative controls.
+4. Refresh SBOM, VEX, scientific-engine, license, health, held-out, Sample
+ Capsule, and all source/image bindings exactly once; commit only reviewed
+ evidence and rerun the full deterministic release gate.
+5. Authenticate the repository-contained Wrangler identity, qualify only that
+ tuple, deploy it once, and capture the exact Worker version, Container
+ digest, public identity, supported Sample/Replay/live journeys, reconnect,
+ patch, Capsule, and no-secret smoke receipts.
+6. In the fresh Cloak-enabled session, run all 40 public journeys, seven
+ viewports, keyboard/screen-reader/zoom/reduced-motion checks, console and
+ network assertions, downloads, history/reconnect, and Web Vitals including
+ INP.
+7. Produce screenshots and the public video only from that qualified tuple;
+ keep learner evidence `NO_DATA` unless real consented observations exist;
+ verify logged-out repository, app, feedback, video, and Devpost links; then
+ capture the submission receipt.
+8. Push the reviewed feature branch and fast-forward-only merge and push
+ `main` only after every upstream node is factual and green.
+
+Release remains **NO-GO** at clean source, real containment, exact image and
+evidence, contained Cloudflare authentication, active CloakBrowser
+qualification, deployment, submission, and final merge. The proof and
+live-session source-authority regressions are closed by reviewed commits, not
+by production claims.
diff --git a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
index e1b4cbe8..b4a60976 100644
--- a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
+++ b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
@@ -1,10 +1,10 @@
# Release dependency and review graph
-- Checkpoint: `2026-07-21T08:22:38Z`
+- Checkpoint: `2026-07-21T09:06:33Z`
- Branch: `feat/learner-ux-v6.1`
-- Committed HEAD: `b37f7d962e53dbfd7f511746e97ba364fe465d34`
+- Committed HEAD: `7e25a098b74d18b30d84a1bc80938ef1d94e95e9`
- Deadline: `2026-07-22T00:00:00Z`
-- Time remaining at this checkpoint: **15 hours 37 minutes 22 seconds**
+- Time remaining at this checkpoint: **14 hours 53 minutes 27 seconds**
## Purpose and evidence boundary
@@ -32,9 +32,11 @@ useful source/design evidence; it is not public or CloakBrowser qualification.
```mermaid
flowchart TD
- A["Source slices atomic commits + focused tests SOURCE COMPLETE for aggregate runtime chain"]
- B["Review gate independent review + lead diff review PASS for aggregate runtime integration"]
+ A["Source slices atomic commits + focused tests SOURCE COMPLETE for reviewed P0 repairs"]
+ B["Review gate independent review + lead diff review PASS for reviewed P0 repairs"]
R["Contained runtime command channel rt-mainrel04 READY / VERIFIED PASS"]
+ P["Historical Proof Bundle validation issuance-event anchored PASS"]
+ V["Exact live-session admission browser advisory + Worker authoritative gate PASS"]
C["Clean source freeze S primary root blocked by FUSE tombstone; linked roots exceed runtime maximum"]
D["Exact runner and adapter image build receipt v4 bound to S NOT RUN for current source"]
E["Real aggregate timeout sentinel observer + controls + FINALIZE + cleanup BLOCKED by /sys and /proc authority"]
@@ -44,17 +46,17 @@ flowchart TD
I["Release check receipt v5 + deterministic full gate NOT RUN"]
J["Cloudflare deployment maintenance + D1 + Container + Worker NOT RUN"]
K["Public identity and smoke deployment receipt v7 + /ready + health + live loops NOT RUN"]
- L["Browser qualification fresh local stock 35 pass / 5 public-live skips public CloakBrowser: 0 journeys"]
+ L["Browser qualification launcher registered; current session lacks tools public CloakBrowser: 0 journeys"]
M["Submission package public app + repo + video + feedback + honest NO_DATA EXTERNALLY UNVERIFIED"]
N["Git publication push feature -> verify remote -> fast-forward main -> push main NOT RUN"]
- A --> B --> R --> C --> D --> E --> F --> G --> H --> I --> J --> K --> L --> M --> N
+ A --> B --> R --> P --> V --> C --> D --> E --> F --> G --> H --> I --> J --> K --> L --> M --> N
classDef pass fill:#d9f3ee,stroke:#176b5b,color:#123b34;
classDef blocked fill:#fff0d0,stroke:#9a6500,color:#4c3500;
classDef pending fill:#eef2f7,stroke:#596579,color:#252b36;
classDef external fill:#f7e8ef,stroke:#95506d,color:#482636;
- class A,B,R pass;
+ class A,B,R,P,V pass;
class C,E blocked;
class D,F,G,H,I,J,K,L pending;
class M,N external;
@@ -62,22 +64,22 @@ flowchart TD
## Gate ledger
-| Gate | Required inputs | Required evidence | Current status | Failure rule |
-| ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
-| Source slices | Reviewed implementation and tests | Atomic commits, focused tests, type checks, formatting, secret scan, whitespace check | Aggregate observer/runtime/consumer chain is source-complete | Repair in a new independent commit; do not weaken tests |
-| Review | Complete source slice and focused evidence | Independent review plus lead inspection of complete diff | Aggregate runtime review is `GO`; its initial async run/drain race and missing positive path were repaired | Review findings remain open until implementation and regression tests pass |
-| Clean source `S` | Final intended source and no tracked or untracked drift | `git status` empty and exact source commit recorded | **Blocked**: the 42-character primary root is the only runtime-capable location and remains unclean because of the preserved FUSE tombstone. Both retained linked worktrees exceed the enforced containerd shim-root maximum | Do not hide, delete, stage, or sweep the entry; no descendant worktree is a proven substitute |
-| Exact image | Clean `S`, source-matching contained runtime, pinned BuildKit | Build receipt v4, source/tree labels, exact runner and adapter digests | **Not run** for current source; cached receipts belong to older commits | Never reuse or retag an earlier image as current |
-| Real sentinel | Exact image, source-matching runtime, release-only qualification mode | Genuine memory OOM, PID denial, CPU throttling, exact membership, FINALIZE, eight cleanup facts, cgroup absence, exclusive evidence artifact | **Blocked** by the checked-in repository-only filesystem rule, which does not permit required `/sys/fs/cgroup` and owned `/proc//stat` reads | Preserve `NO-GO`; never synthesize or inject aggregate evidence |
-| Evidence refresh | Exact image and successful real sentinel while `HEAD == S` | Fresh normalized SBOM, VEX and negative control, scientific-engine bindings, runtime and isolation reports | **Not run** | Any image/source mismatch or stale binding fails closed |
-| Evidence commit `E` | Reviewed allowlisted evidence delta | One clean commit containing only regenerated evidence | **Not created** | Do not include unrelated source or cache artifacts |
-| Qualification | Clean `HEAD == E`, build receipt, timeout receipt, source-matching runtime, authenticated Cloudflare account | Qualified runner receipt v6 and exact registry manifest/config digest | **Not run**; contained Wrangler is logged out and one fresh OAuth window timed out without consent | Do not promote when account, source, image, timeout, runtime, or evidence differs |
-| Release check | Clean `E`, qualified receipt, exact local images and runtime | Release-check receipt v5 after formatting, tests, mutations, held-out, sandbox, scientific, build, reproduction, patch replay, and secret gates | **Not run** | Any failure blocks deployment |
-| Cloudflare deployment | Qualified receipt, release-check receipt, registry image, required secrets, recovery target, working CloakBrowser endpoint | Exact maintenance/final Worker versions, D1 migration log, exact Container digest, deployment receipt v7 | **Not run** | Stop before mutation when preflight is red; after mutation, automated Worker recovery does not reverse D1 or Container state |
-| Public identity and smoke | Active exact Worker/Container tuple | `/ready`, `/api/health?readiness=probe`, Worker 100% traffic, Container identity, public asset scan, Sample, Replay, leakage and imbalance live evidence, Capsule/replay evidence | **Not run** | Source, image, Worker, client, receipt, or capability mismatch is a release failure |
-| CloakBrowser | Exact public tuple and injected `CLOAK_CDP_ENDPOINT` | All 40 current tests plus responsive, keyboard, screen-reader, 200% zoom, reduced-motion, console/network, downloads, reconnect, and Web Vitals evidence | Fresh local stock design review: **35 passed, 5 public/live skips, 0 failed in 3.9 minutes**; public CloakBrowser: **0 journeys** | Stock Chromium is design-review evidence only and cannot close this gate |
-| Devpost and impact | Qualified public tuple, public repo/video/feedback links, factual copy | Logged-out link audit, public video from exact tuple, Devpost receipt, real consented learner aggregates or explicit `NO_DATA` | **Externally unverified; learner evidence remains `NO_DATA`** | Never infer a submission or learner result |
-| Push and merge | Every ship gate factual and final source reviewed | Verified feature remote tip, fast-forward-only `main`, verified main remote tip | **Not run** | Do not push or merge a `NO-GO` release |
+| Gate | Required inputs | Required evidence | Current status | Failure rule |
+| ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| Source slices | Reviewed implementation and tests | Atomic commits, focused tests, type checks, formatting, secret scan, whitespace check | Aggregate observer/runtime/consumer chain, release-health validation, historical Proof Bundle validation, and exact live-session admission are source-complete | Repair in a new independent commit; do not weaken tests |
+| Review | Complete source slice and focused evidence | Independent review plus lead inspection of complete diff | Aggregate runtime review is `GO`; its initial async run/drain race and missing positive path were repaired | Review findings remain open until implementation and regression tests pass |
+| Clean source `S` | Final intended source and no tracked or untracked drift | `git status` empty and exact source commit recorded | **Blocked**: the 42-character primary root is the only runtime-capable location and remains unclean because of the preserved FUSE tombstone. Both retained linked worktrees exceed the enforced containerd shim-root maximum | Do not hide, delete, stage, or sweep the entry; no descendant worktree is a proven substitute |
+| Exact image | Clean `S`, source-matching contained runtime, pinned BuildKit | Build receipt v4, source/tree labels, exact runner and adapter digests | **Not run** for current source; cached receipts belong to older commits | Never reuse or retag an earlier image as current |
+| Real sentinel | Exact image, source-matching runtime, release-only qualification mode | Genuine memory OOM, PID denial, CPU throttling, exact membership, FINALIZE, eight cleanup facts, cgroup absence, exclusive evidence artifact | **Blocked** by the checked-in repository-only filesystem rule, which does not permit required `/sys/fs/cgroup` and owned `/proc//stat` reads | Preserve `NO-GO`; never synthesize or inject aggregate evidence |
+| Evidence refresh | Exact image and successful real sentinel while `HEAD == S` | Fresh normalized SBOM, VEX and negative control, scientific-engine bindings, runtime and isolation reports | **Not run** | Any image/source mismatch or stale binding fails closed |
+| Evidence commit `E` | Reviewed allowlisted evidence delta | One clean commit containing only regenerated evidence | **Not created** | Do not include unrelated source or cache artifacts |
+| Qualification | Clean `HEAD == E`, build receipt, timeout receipt, source-matching runtime, authenticated Cloudflare account | Qualified runner receipt v6 and exact registry manifest/config digest | **Not run**; contained Wrangler is logged out and one fresh OAuth window timed out without consent | Do not promote when account, source, image, timeout, runtime, or evidence differs |
+| Release check | Clean `E`, qualified receipt, exact local images and runtime | Release-check receipt v5 after formatting, tests, mutations, held-out, sandbox, scientific, build, reproduction, patch replay, and secret gates | **Not run** | Any failure blocks deployment |
+| Cloudflare deployment | Qualified receipt, release-check receipt, registry image, required secrets, recovery target, working CloakBrowser endpoint | Exact maintenance/final Worker versions, D1 migration log, exact Container digest, deployment receipt v7 | **Not run** | Stop before mutation when preflight is red; after mutation, automated Worker recovery does not reverse D1 or Container state |
+| Public identity and smoke | Active exact Worker/Container tuple | `/ready`, `/api/health?readiness=probe`, Worker 100% traffic, Container identity, public asset scan, Sample, Replay, leakage and imbalance live evidence, Capsule/replay evidence | **Not run** | Source, image, Worker, client, receipt, or capability mismatch is a release failure |
+| CloakBrowser | Exact public tuple and injected `CLOAK_CDP_ENDPOINT` | All 40 current tests plus responsive, keyboard, screen-reader, 200% zoom, reduced-motion, console/network, downloads, reconnect, and Web Vitals evidence | `playwright_safe` is registered and enabled, but this active session has no loaded tool namespace or endpoint. Public CloakBrowser remains **0 journeys**. Current specs also lack three required viewport labels, INP collection, and the deterministic qualification reporter. | Stock Chromium is design-review evidence only and cannot close this gate |
+| Devpost and impact | Qualified public tuple, public repo/video/feedback links, factual copy | Logged-out link audit, public video from exact tuple, Devpost receipt, real consented learner aggregates or explicit `NO_DATA` | **Externally unverified; learner evidence remains `NO_DATA`** | Never infer a submission or learner result |
+| Push and merge | Every ship gate factual and final source reviewed | Verified feature remote tip, fast-forward-only `main`, verified main remote tip | **Not run** | Do not push or merge a `NO-GO` release |
## Code-to-evidence graph
@@ -148,6 +150,13 @@ The current aggregate release chain is split into independent commits:
- `e4dc33e` — factual progress checkpoint;
- `b37f7d9` — preserves the runtime command response after the client
half-closes its request and adds the focused regression.
+- `84ef49d` — validates that the public health payload is bound to the exact
+ source, Worker, client-asset, runtime, and isolation release identity.
+- `547cc0f` — preserves historical integrity-hashed Proof Bundles while binding
+ the complete stored bundle to its issuance event and failing closed for
+ downgrades, tampering, missing keys, and rotated keys.
+- `7e25a09` — requires exact release readiness before browser upload and again
+ at authoritative Worker live-session admission.
Reported verification for the integrated aggregate slice is:
@@ -169,6 +178,13 @@ Reported verification for the integrated aggregate slice is:
- scoped Prettier, Python compilation, Node syntax, Git whitespace, and secret
scans: **passed**;
- current contained-runtime command-channel suite: **7 files, 65/65 passed**;
+- historical proof validation: Worker API **106/106 passed** and Proof Bundle
+ package **12/12 passed**;
+- exact live admission: App **74/74 passed**, Worker API **107/107 passed**, and
+ API/Judge **50/50 passed**;
+- repository-contained pnpm 11.13.1 production build: **passed** with 391 Worker
+ and 231 client modules; Worker **1,868.90 kB / 360.50 kB gzip** and main
+ client **449.75 kB / 129.26 kB gzip**;
- current Node syntax, scoped Prettier, repository TypeScript, two-file secret
scan, and Git whitespace: **passed**;
- fresh primary runtime `rt-mainrel04`: schema-v2 response status **VERIFIED**;
@@ -178,7 +194,7 @@ Reported verification for the integrated aggregate slice is:
repaired its initial async run/drain race and missing positive
qualification-path coverage.
-The broad web/root/Python/build/browser matrix predates `b37f7d9` and must be
+The broad web/root/Python/browser matrix predates `7e25a09` and must be
rerun on the final frozen source. The current focused results are source and
runtime command-channel evidence only. They are not exact-image, aggregate
containment, qualification, or deployment evidence; every downstream release
@@ -229,21 +245,21 @@ The owner explicitly authorized the installed skill instructions for this pass.
The lead read and applied the following skills while preserving the checked-in
repository constitution as the governing product and filesystem authority.
-| Discipline | Direct application in this graph |
-| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Repository safety | All repository work and generated state stay inside the verified root; the FUSE entry is preserved; no destructive cleanup, stash, reset, or outside-root filesystem operation is accepted. |
-| Code review | Each logical slice has focused characterization/regression tests, an isolated diff, independent review where assigned, and lead integration before an atomic commit. |
-| Accessibility | Browser closure requires semantic names, keyboard completion, visible focus, exact-value tables, non-colour meaning, reduced motion, zoom, responsive viewports, touch targets, and screen-reader inspection. |
-| React testing | Component and App behavior is checked with Vitest and strict TypeScript; Playwright covers composed learner journeys, authority labels, refresh, navigation, downloads, and no-result-before-Prediction behavior. |
-| Playwright and CloakBrowser | Static collection is not browser evidence; release qualification requires the injected CloakBrowser CDP authority, accessibility snapshots, screenshots where visual judgment matters, and console/network review. Stock Chromium cannot close the gate. |
-| Cloudflare and Wrangler | The repository-pinned Wrangler, contained account identity, exact registry digest, maintenance freeze, remote D1 migration, Container rollout, final Worker, recovery target, and public identity receipt form one ordered gate. |
-| Delivery gate | Clean `S` -> exact image -> real sentinel -> evidence `E` -> qualification -> release check -> deployment -> public smoke -> full browser matrix -> submission -> push/fast-forward merge. No later node may compensate for an earlier red node. |
-| Diagnosing bugs | Required the red command-server characterization before the one-line half-close repair, followed by focused and original-path runtime proof. |
-| Git workflow | Required selective staging, non-executable test mode, staged-diff review, configured owner identity, and the independent `b37f7d9` commit. |
-| Build graph | Refreshed the graph at `b37f7d9` to 531 files, 5,515 nodes, and 93,346 edges, then used the 140-file branch delta to prioritize review. |
-| Code review | Runs standards and specification reviews as separate read-only axes against `main`; findings do not become closed until accepted tests and public evidence pass. |
-| Browser QA and Playwright | Keeps the stock matrix explicitly non-qualifying and requires the registered CloakBrowser authority, semantic snapshots, screenshots, console/network inspection, accessibility, and responsive evidence. |
-| Wrangler | Requires current documentation, repository-contained CLI state, authenticated-account proof, dry-run/preflight, exact version/digest capture, and qualified-only deployment. |
+| Discipline | Direct application in this graph |
+| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Repository safety | All repository work and generated state stay inside the verified root; the FUSE entry is preserved; no destructive cleanup, stash, reset, or outside-root filesystem operation is accepted. |
+| Code review | Each logical slice has focused characterization/regression tests, an isolated diff, independent review where assigned, and lead integration before an atomic commit. |
+| Accessibility | Browser closure requires semantic names, keyboard completion, visible focus, exact-value tables, non-colour meaning, reduced motion, zoom, responsive viewports, touch targets, and screen-reader inspection. |
+| React testing | Component and App behavior is checked with Vitest and strict TypeScript; Playwright covers composed learner journeys, authority labels, refresh, navigation, downloads, and no-result-before-Prediction behavior. |
+| Playwright and CloakBrowser | Static collection is not browser evidence; release qualification requires the injected CloakBrowser CDP authority, accessibility snapshots, screenshots where visual judgment matters, and console/network review. Stock Chromium cannot close the gate. |
+| Cloudflare and Wrangler | The repository-pinned Wrangler, contained account identity, exact registry digest, maintenance freeze, remote D1 migration, Container rollout, final Worker, recovery target, and public identity receipt form one ordered gate. |
+| Delivery gate | Clean `S` -> exact image -> real sentinel -> evidence `E` -> qualification -> release check -> deployment -> public smoke -> full browser matrix -> submission -> push/fast-forward merge. No later node may compensate for an earlier red node. |
+| Diagnosing bugs | Required the red command-server characterization before the one-line half-close repair, followed by focused and original-path runtime proof. |
+| Git workflow | Required selective staging, non-executable test mode, staged-diff review, configured owner identity, and the independent `b37f7d9` commit. |
+| Build graph | The last full structural graph remains the `b37f7d9` snapshot at 531 files, 5,515 nodes, and 93,346 edges. The operational release graph and current 140-file branch delta are updated here at `7e25a09`; structural counts are not relabelled without rerunning their generator. |
+| Code review | Runs standards and specification reviews as separate read-only axes against `main`; findings do not become closed until accepted tests and public evidence pass. |
+| Browser QA and Playwright | Keeps the stock matrix explicitly non-qualifying and requires the registered CloakBrowser authority, semantic snapshots, screenshots, console/network inspection, accessibility, and responsive evidence. |
+| Wrangler | Requires current documentation, repository-contained CLI state, authenticated-account proof, dry-run/preflight, exact version/digest capture, and qualified-only deployment. |
## Current blockers and shortest honest path
@@ -260,14 +276,20 @@ repository constitution as the governing product and filesystem authority.
prevents a clean frozen source.
4. No current-source image, scientific-evidence refresh, evidence commit,
qualified receipt, or release-check receipt exists.
-5. Contained Wrangler authentication is explicitly red: `whoami` returned
- `loggedIn:false`, and one fresh OAuth window timed out without consent. The
- intended Cloudflare account has not been authenticated for this release.
-6. `CLOAK_CDP_ENDPOINT` is absent. `deploy-qualified.sh` performs remote Worker,
+5. Contained Wrangler authentication is explicitly red: a fresh
+ `wrangler whoami --json` at `7e25a09` returned `{"loggedIn":false}`. The
+ intended Cloudflare account has not been authenticated inside the
+ repository-contained environment for this release.
+6. The Cloak launcher registration is enabled, but the current Codex session
+ predates it and exposes no `playwright_safe` tools or `CLOAK_CDP_ENDPOINT`.
+ A fresh session is required. `deploy-qualified.sh` performs remote Worker,
D1, and Container mutations before invoking the smoke script, while
`production-smoke.sh` requires CloakBrowser. CloakBrowser must therefore be
preflighted before deployment starts.
-7. Learner evidence remains `NO_DATA`; public repository, video, feedback ID,
+7. The checked-in browser evidence path still needs the remaining three
+ required viewport labels, INP capture, and deterministic 40-journey/manual
+ evidence receipts before it can satisfy the publication schema.
+8. Learner evidence remains `NO_DATA`; public repository, video, feedback ID,
Devpost publication, and submission receipt remain externally unverified.
The shortest honest path is to resolve the clean-source condition at the primary
From 3e7528104c5390fef71a2c5b772303d297afb49c Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 14:43:34 +0530
Subject: [PATCH 092/143] test(browser): cover release viewport matrix
---
apps/web/e2e/judged-flow.spec.ts | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index ecab0fbf..4a74c8ed 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -1647,6 +1647,8 @@ test("Judge Mode distinguishes every authority path", async ({ page }) => {
test("the first visit explains the lesson before asking for technical knowledge", async ({
page,
}) => {
+ await page.setViewportSize({ width: 768, height: 1024 });
+ expect(page.viewportSize()).toEqual({ width: 768, height: 1024 });
await reset(page);
await expect(
page.getByRole("heading", {
@@ -1692,7 +1694,7 @@ test("the first visit explains the lesson before asking for technical knowledge"
});
for (const viewport of [
- { name: "wide desktop", width: 1440, height: 900 },
+ { name: "wide desktop", width: 1920, height: 1080 },
{ name: "compact desktop", width: 1280, height: 720 },
{ name: "mobile", width: 390, height: 844 },
] as const) {
@@ -1700,6 +1702,10 @@ for (const viewport of [
page,
}) => {
await page.setViewportSize(viewport);
+ expect(page.viewportSize()).toEqual({
+ width: viewport.width,
+ height: viewport.height,
+ });
await reset(page);
await revealLandingNavigation(page);
@@ -1833,6 +1839,8 @@ test("Try Instantly persists the verified learning loop and exports a valid proo
test("the lesson keeps one learner decision in focus at a time", async ({
page,
}) => {
+ await page.setViewportSize({ width: 1366, height: 768 });
+ expect(page.viewportSize()).toEqual({ width: 1366, height: 768 });
await startInstant(page);
await commitAndOpenResult(page);
From 3db3e525362f84275b24b15d9a2d872bcdbed47d Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 15:07:24 +0530
Subject: [PATCH 093/143] test(browser): capture privacy-safe INP evidence
---
apps/web/e2e/performance-evidence.ts | 122 +++++++++++-
apps/web/src/test/performanceEvidence.test.ts | 179 +++++++++++++++++-
2 files changed, 287 insertions(+), 14 deletions(-)
diff --git a/apps/web/e2e/performance-evidence.ts b/apps/web/e2e/performance-evidence.ts
index 99202653..7c0bca50 100644
--- a/apps/web/e2e/performance-evidence.ts
+++ b/apps/web/e2e/performance-evidence.ts
@@ -1,7 +1,13 @@
import type { Page } from "@playwright/test";
export const BROWSER_PERFORMANCE_EVIDENCE_SCHEMA =
- "counterlab.browser-performance.v1" as const;
+ "counterlab.browser-performance.v2" as const;
+
+export type InteractionToNextPaintStatus =
+ | "measured"
+ | "awaiting-interaction"
+ | "unsupported"
+ | "observation-limit-exceeded";
export interface BrowserPerformanceEvidence {
schemaVersion: typeof BROWSER_PERFORMANCE_EVIDENCE_SCHEMA;
@@ -13,6 +19,7 @@ export interface BrowserPerformanceEvidence {
longTasks: boolean;
navigationTiming: boolean;
resourceTiming: boolean;
+ interactionToNextPaint: boolean;
};
largestContentfulPaintMs: number | null;
cumulativeLayoutShift: number | null;
@@ -27,6 +34,8 @@ export interface BrowserPerformanceEvidence {
count: number;
transferBytes: number;
} | null;
+ interactionToNextPaintMs: number | null;
+ interactionToNextPaintStatus: InteractionToNextPaintStatus;
}
type ObservedMetric = keyof BrowserPerformanceEvidence["support"];
@@ -46,12 +55,14 @@ interface BrowserPerformanceState {
navigationTtfbMs: number | null;
resourceCount: number;
resourceTransferBytes: number;
+ interactionDurations: Map;
+ interactionObservationLimitExceeded: boolean;
observers: PerformanceObserver[];
drainObservers: Array<() => void>;
}
interface PerformanceEvidenceWindow extends Window {
- __counterlabBrowserPerformanceV1?: BrowserPerformanceState;
+ __counterlabBrowserPerformanceV2?: BrowserPerformanceState;
}
interface LayoutShiftPerformanceEntry extends PerformanceEntry {
@@ -59,6 +70,10 @@ interface LayoutShiftPerformanceEntry extends PerformanceEntry {
value?: number;
}
+interface InteractionPerformanceEntry extends PerformanceEntry {
+ interactionId?: unknown;
+}
+
/**
* Runs inside the browser before application code. Keep this function
* self-contained because Playwright serializes it into the target page.
@@ -66,14 +81,20 @@ interface LayoutShiftPerformanceEntry extends PerformanceEntry {
export function initializeBrowserPerformanceEvidence(): void {
const browserWindow = window as PerformanceEvidenceWindow;
if (
- browserWindow.__counterlabBrowserPerformanceV1?.schemaVersion ===
- "counterlab.browser-performance.v1"
+ browserWindow.__counterlabBrowserPerformanceV2?.schemaVersion ===
+ "counterlab.browser-performance.v2"
) {
return;
}
+ // A release-qualification journey should never approach this ceiling. The
+ // bound prevents an unexpectedly long-lived page from retaining unbounded
+ // interaction identifiers; crossing it invalidates INP instead of returning
+ // a partial metric.
+ const maximumObservedInteractions = 10_000;
+
const state: BrowserPerformanceState = {
- schemaVersion: "counterlab.browser-performance.v1",
+ schemaVersion: "counterlab.browser-performance.v2",
support: {
largestContentfulPaint: false,
cumulativeLayoutShift: false,
@@ -81,6 +102,7 @@ export function initializeBrowserPerformanceEvidence(): void {
longTasks: false,
navigationTiming: false,
resourceTiming: false,
+ interactionToNextPaint: false,
},
largestContentfulPaintMs: null,
cumulativeLayoutShift: 0,
@@ -94,10 +116,12 @@ export function initializeBrowserPerformanceEvidence(): void {
navigationTtfbMs: null,
resourceCount: 0,
resourceTransferBytes: 0,
+ interactionDurations: new Map(),
+ interactionObservationLimitExceeded: false,
observers: [],
drainObservers: [],
};
- browserWindow.__counterlabBrowserPerformanceV1 = state;
+ browserWindow.__counterlabBrowserPerformanceV2 = state;
if (typeof PerformanceObserver === "undefined") return;
@@ -108,13 +132,14 @@ export function initializeBrowserPerformanceEvidence(): void {
entryType: string,
metric: ObservedMetric,
processEntries: (entries: PerformanceEntry[]) => void,
+ options: { durationThreshold?: number } = {},
): void => {
if (!PerformanceObserver.supportedEntryTypes.includes(entryType)) return;
try {
const observer = new PerformanceObserver((list) => {
processEntries(list.getEntries());
});
- observer.observe({ type: entryType, buffered: true });
+ observer.observe({ type: entryType, buffered: true, ...options });
state.support[metric] = true;
state.observers.push(observer);
state.drainObservers.push(() => {
@@ -210,6 +235,57 @@ export function initializeBrowserPerformanceEvidence(): void {
state.resourceTransferBytes += entry.transferSize;
}
});
+
+ const eventTimingConstructor = (
+ globalThis as typeof globalThis & {
+ PerformanceEventTiming?: { prototype?: object };
+ }
+ ).PerformanceEventTiming;
+ if (
+ eventTimingConstructor?.prototype !== undefined &&
+ "interactionId" in eventTimingConstructor.prototype
+ ) {
+ observe(
+ "event",
+ "interactionToNextPaint",
+ (entries) => {
+ for (const entry of entries as InteractionPerformanceEntry[]) {
+ const interactionId = entry.interactionId;
+ if (
+ typeof interactionId !== "number" ||
+ !Number.isSafeInteger(interactionId) ||
+ interactionId <= 0 ||
+ !finiteNonNegative(entry.duration)
+ ) {
+ continue;
+ }
+
+ const normalizedInteractionId = interactionId;
+ const previousDuration = state.interactionDurations.get(
+ normalizedInteractionId,
+ );
+ if (previousDuration !== undefined) {
+ state.interactionDurations.set(
+ normalizedInteractionId,
+ Math.max(previousDuration, entry.duration),
+ );
+ continue;
+ }
+ if (state.interactionDurations.size >= maximumObservedInteractions) {
+ state.interactionObservationLimitExceeded = true;
+ continue;
+ }
+ state.interactionDurations.set(
+ normalizedInteractionId,
+ entry.duration,
+ );
+ }
+ },
+ // Event Timing's lowest useful threshold is one frame. Capturing from
+ // 16 ms gives the percentile estimator the broadest practical sample.
+ { durationThreshold: 16 },
+ );
+ }
}
/** Installs collection for future navigations and the currently loaded page. */
@@ -222,15 +298,39 @@ export async function installBrowserPerformanceEvidence(
function snapshotBrowserPerformanceEvidenceInPage(): BrowserPerformanceEvidence {
const browserWindow = window as PerformanceEvidenceWindow;
- const state = browserWindow.__counterlabBrowserPerformanceV1;
- if (state?.schemaVersion !== "counterlab.browser-performance.v1") {
+ const state = browserWindow.__counterlabBrowserPerformanceV2;
+ if (state?.schemaVersion !== "counterlab.browser-performance.v2") {
throw new Error("Browser performance evidence collection is not installed");
}
for (const drainObserver of state.drainObservers) drainObserver();
+ let interactionToNextPaintMs: number | null = null;
+ let interactionToNextPaintStatus: InteractionToNextPaintStatus;
+ if (!state.support.interactionToNextPaint) {
+ interactionToNextPaintStatus = "unsupported";
+ } else if (state.interactionObservationLimitExceeded) {
+ interactionToNextPaintStatus = "observation-limit-exceeded";
+ } else if (state.interactionDurations.size === 0) {
+ interactionToNextPaintStatus = "awaiting-interaction";
+ } else {
+ const longestFirst = [...state.interactionDurations.values()].sort(
+ (left, right) => right - left,
+ );
+ // INP approximates the 98th percentile by ignoring one high outlier for
+ // every 50 interactions. Entries sharing an interactionId were already
+ // collapsed to that interaction's maximum duration.
+ const candidateIndex = Math.min(
+ longestFirst.length - 1,
+ Math.floor(longestFirst.length / 50),
+ );
+ interactionToNextPaintMs = longestFirst[candidateIndex] ?? null;
+ interactionToNextPaintStatus =
+ interactionToNextPaintMs === null ? "awaiting-interaction" : "measured";
+ }
+
return {
- schemaVersion: "counterlab.browser-performance.v1",
+ schemaVersion: "counterlab.browser-performance.v2",
// Never persist origin, credentials, query parameters, or fragments.
pathname: window.location.pathname || "/",
support: { ...state.support },
@@ -259,6 +359,8 @@ function snapshotBrowserPerformanceEvidenceInPage(): BrowserPerformanceEvidence
transferBytes: state.resourceTransferBytes,
}
: null,
+ interactionToNextPaintMs,
+ interactionToNextPaintStatus,
};
}
diff --git a/apps/web/src/test/performanceEvidence.test.ts b/apps/web/src/test/performanceEvidence.test.ts
index 11be0d67..a704daa4 100644
--- a/apps/web/src/test/performanceEvidence.test.ts
+++ b/apps/web/src/test/performanceEvidence.test.ts
@@ -17,10 +17,13 @@ const supportedEntryTypes = [
"longtask",
"navigation",
"resource",
+ "event",
];
class FakePerformanceObserver implements PerformanceObserver {
static readonly callbacks = new Map();
+ static readonly failedTypes = new Set();
+ static readonly observeOptions = new Map();
static readonly supportedEntryTypes = [...supportedEntryTypes];
readonly callback: ObserverCallback;
@@ -31,11 +34,15 @@ class FakePerformanceObserver implements PerformanceObserver {
}
observe(options?: PerformanceObserverInit): void {
- const entryType = options?.type;
- if (entryType === undefined)
+ if (options?.type === undefined)
throw new Error("A test entry type is required");
+ const entryType = options.type;
+ if (FakePerformanceObserver.failedTypes.has(entryType)) {
+ throw new Error(`Observer registration failed for ${entryType}`);
+ }
this.observedTypes.push(entryType);
FakePerformanceObserver.callbacks.set(entryType, this.callback);
+ FakePerformanceObserver.observeOptions.set(entryType, options);
}
disconnect(): void {}
@@ -83,30 +90,56 @@ const originalObserver = Object.getOwnPropertyDescriptor(
globalThis,
"PerformanceObserver",
);
+const originalEventTiming = Object.getOwnPropertyDescriptor(
+ globalThis,
+ "PerformanceEventTiming",
+);
+
+class FakePerformanceEventTiming {}
+Object.defineProperty(FakePerformanceEventTiming.prototype, "interactionId", {
+ configurable: true,
+ value: 0,
+});
beforeEach(() => {
FakePerformanceObserver.callbacks.clear();
+ FakePerformanceObserver.failedTypes.clear();
+ FakePerformanceObserver.observeOptions.clear();
FakePerformanceObserver.supportedEntryTypes.splice(
0,
FakePerformanceObserver.supportedEntryTypes.length,
...supportedEntryTypes,
);
- Reflect.deleteProperty(window, "__counterlabBrowserPerformanceV1");
+ Reflect.deleteProperty(window, "__counterlabBrowserPerformanceV2");
Object.defineProperty(globalThis, "PerformanceObserver", {
configurable: true,
value: FakePerformanceObserver,
writable: true,
});
+ Object.defineProperty(globalThis, "PerformanceEventTiming", {
+ configurable: true,
+ value: FakePerformanceEventTiming,
+ writable: true,
+ });
window.history.replaceState({}, "", "/");
});
afterEach(() => {
- Reflect.deleteProperty(window, "__counterlabBrowserPerformanceV1");
+ Reflect.deleteProperty(window, "__counterlabBrowserPerformanceV2");
if (originalObserver === undefined) {
Reflect.deleteProperty(globalThis, "PerformanceObserver");
} else {
Object.defineProperty(globalThis, "PerformanceObserver", originalObserver);
}
+ if (originalEventTiming === undefined) {
+ Reflect.deleteProperty(globalThis, "PerformanceEventTiming");
+ } else {
+ Object.defineProperty(
+ globalThis,
+ "PerformanceEventTiming",
+ originalEventTiming,
+ );
+ }
window.history.replaceState({}, "", "/");
});
@@ -149,6 +182,11 @@ describe("browser performance evidence", () => {
transferSize: 2_500,
}),
]);
+ FakePerformanceObserver.emit("event", [
+ performanceEntry({ duration: 144, interactionId: 7 }),
+ performanceEntry({ duration: 176, interactionId: 7 }),
+ performanceEntry({ duration: 96, interactionId: 14 }),
+ ]);
const snapshot = await snapshotBrowserPerformanceEvidence(evaluatingPage());
@@ -162,6 +200,7 @@ describe("browser performance evidence", () => {
longTasks: true,
navigationTiming: true,
resourceTiming: true,
+ interactionToNextPaint: true,
},
largestContentfulPaintMs: 620,
cumulativeLayoutShift: 0.15000000000000002,
@@ -176,6 +215,8 @@ describe("browser performance evidence", () => {
count: 2,
transferBytes: 3_500,
},
+ interactionToNextPaintMs: 176,
+ interactionToNextPaintStatus: "measured",
});
expect(Object.keys(snapshot).join(" ")).not.toMatch(
/budget|good|poor|pass|fail|threshold/i,
@@ -215,6 +256,7 @@ describe("browser performance evidence", () => {
false,
false,
false,
+ false,
]);
expect(snapshot.largestContentfulPaintMs).toBeNull();
expect(snapshot.cumulativeLayoutShift).toBeNull();
@@ -222,6 +264,8 @@ describe("browser performance evidence", () => {
expect(snapshot.longTasks).toBeNull();
expect(snapshot.navigationTtfbMs).toBeNull();
expect(snapshot.resources).toBeNull();
+ expect(snapshot.interactionToNextPaintMs).toBeNull();
+ expect(snapshot.interactionToNextPaintStatus).toBe("unsupported");
});
it("reports individual observer entry types as unsupported", async () => {
@@ -241,9 +285,136 @@ describe("browser performance evidence", () => {
longTasks: false,
navigationTiming: false,
resourceTiming: false,
+ interactionToNextPaint: false,
});
});
+ it("groups Event Timing entries by interaction and applies the INP outlier rule", async () => {
+ initializeBrowserPerformanceEvidence();
+
+ const entries = Array.from({ length: 50 }, (_, index) =>
+ performanceEntry({
+ duration: index === 0 ? 1_000 : index === 1 ? 800 : 40 + index,
+ interactionId: index + 1,
+ }),
+ );
+ entries.push(performanceEntry({ duration: 900, interactionId: 2 }));
+ FakePerformanceObserver.emit("event", entries);
+
+ const snapshot = await snapshotBrowserPerformanceEvidence(evaluatingPage());
+
+ // At 50 interactions, INP discards one high outlier. The duplicate event
+ // for interaction 2 contributes only that interaction's maximum duration.
+ expect(snapshot.interactionToNextPaintMs).toBe(900);
+ expect(snapshot.interactionToNextPaintStatus).toBe("measured");
+ expect(FakePerformanceObserver.observeOptions.get("event")).toMatchObject({
+ buffered: true,
+ durationThreshold: 16,
+ type: "event",
+ });
+ });
+
+ it("retains the worst interaction until the first 50-interaction outlier boundary", async () => {
+ initializeBrowserPerformanceEvidence();
+ FakePerformanceObserver.emit(
+ "event",
+ Array.from({ length: 49 }, (_, index) =>
+ performanceEntry({
+ duration: index === 0 ? 1_000 : 40 + index,
+ interactionId: index + 1,
+ }),
+ ),
+ );
+
+ const snapshot = await snapshotBrowserPerformanceEvidence(evaluatingPage());
+
+ expect(snapshot.interactionToNextPaintMs).toBe(1_000);
+ });
+
+ it("does not retain Event Timing targets, names, URLs, or text", async () => {
+ initializeBrowserPerformanceEvidence();
+ FakePerformanceObserver.emit("event", [
+ performanceEntry({
+ duration: 128,
+ interactionId: 1,
+ name: "https://secret.example/private?token=do-not-record",
+ target: { textContent: "learner private answer" },
+ }),
+ ]);
+
+ const snapshot = await snapshotBrowserPerformanceEvidence(evaluatingPage());
+ const serialized = JSON.stringify(snapshot);
+
+ expect(snapshot.interactionToNextPaintMs).toBe(128);
+ expect(serialized).not.toContain("secret.example");
+ expect(serialized).not.toContain("do-not-record");
+ expect(serialized).not.toContain("learner private answer");
+ });
+
+ it("ignores invalid and non-interaction Event Timing entries", async () => {
+ initializeBrowserPerformanceEvidence();
+ FakePerformanceObserver.emit("event", [
+ performanceEntry({ duration: 500, interactionId: 0 }),
+ performanceEntry({ duration: 500, interactionId: -1 }),
+ performanceEntry({
+ duration: Number.POSITIVE_INFINITY,
+ interactionId: 1,
+ }),
+ performanceEntry({ duration: -1, interactionId: 2 }),
+ performanceEntry({ duration: 120, interactionId: 3.5 }),
+ ]);
+
+ const snapshot = await snapshotBrowserPerformanceEvidence(evaluatingPage());
+
+ expect(snapshot.support.interactionToNextPaint).toBe(true);
+ expect(snapshot.interactionToNextPaintMs).toBeNull();
+ expect(snapshot.interactionToNextPaintStatus).toBe("awaiting-interaction");
+ });
+
+ it("marks Event Timing unsupported when interaction IDs are unavailable", async () => {
+ Object.defineProperty(globalThis, "PerformanceEventTiming", {
+ configurable: true,
+ value: undefined,
+ writable: true,
+ });
+ initializeBrowserPerformanceEvidence();
+
+ const snapshot = await snapshotBrowserPerformanceEvidence(evaluatingPage());
+
+ expect(snapshot.support.interactionToNextPaint).toBe(false);
+ expect(snapshot.interactionToNextPaintMs).toBeNull();
+ expect(snapshot.interactionToNextPaintStatus).toBe("unsupported");
+ });
+
+ it("fails the INP metric closed when Event Timing observation cannot start", async () => {
+ FakePerformanceObserver.failedTypes.add("event");
+ initializeBrowserPerformanceEvidence();
+
+ const snapshot = await snapshotBrowserPerformanceEvidence(evaluatingPage());
+
+ expect(snapshot.support.interactionToNextPaint).toBe(false);
+ expect(snapshot.interactionToNextPaintMs).toBeNull();
+ expect(snapshot.interactionToNextPaintStatus).toBe("unsupported");
+ });
+
+ it("fails closed after the bounded per-page interaction observation limit", async () => {
+ initializeBrowserPerformanceEvidence();
+ FakePerformanceObserver.emit(
+ "event",
+ Array.from({ length: 10_001 }, (_, index) =>
+ performanceEntry({ duration: 80, interactionId: index + 1 }),
+ ),
+ );
+
+ const snapshot = await snapshotBrowserPerformanceEvidence(evaluatingPage());
+
+ expect(snapshot.support.interactionToNextPaint).toBe(true);
+ expect(snapshot.interactionToNextPaintMs).toBeNull();
+ expect(snapshot.interactionToNextPaintStatus).toBe(
+ "observation-limit-exceeded",
+ );
+ });
+
it("installs the init script for both future and current documents", async () => {
const calls: string[] = [];
const page = {
From 8a68bdd066e0d62426aeae8766b1bb2f1b44da11 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 15:07:37 +0530
Subject: [PATCH 094/143] test(browser): open tablet navigation before audit
---
apps/web/e2e/judged-flow.spec.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index 4a74c8ed..ff7fd066 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -1650,6 +1650,7 @@ test("the first visit explains the lesson before asking for technical knowledge"
await page.setViewportSize({ width: 768, height: 1024 });
expect(page.viewportSize()).toEqual({ width: 768, height: 1024 });
await reset(page);
+ await revealLandingNavigation(page);
await expect(
page.getByRole("heading", {
name: "What result are you trying to understand?",
From adea1c943581b0d311c8247bd1661d4b508e0694 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 15:12:25 +0530
Subject: [PATCH 095/143] test(browser): emit fail-closed journey evidence
---
apps/web/e2e/cloak-test.ts | 61 +++-
apps/web/e2e/qualification-reporter.ts | 314 ++++++++++++++++++
apps/web/e2e/recovery-and-intake.spec.ts | 12 +-
apps/web/playwright.config.ts | 43 ++-
.../src/test/qualificationReporter.test.ts | 258 ++++++++++++++
scripts/submission-publication-evidence.ts | 49 ++-
6 files changed, 732 insertions(+), 5 deletions(-)
create mode 100644 apps/web/e2e/qualification-reporter.ts
create mode 100644 apps/web/src/test/qualificationReporter.test.ts
diff --git a/apps/web/e2e/cloak-test.ts b/apps/web/e2e/cloak-test.ts
index 4c8a09e2..e6e8583d 100644
--- a/apps/web/e2e/cloak-test.ts
+++ b/apps/web/e2e/cloak-test.ts
@@ -9,6 +9,7 @@ import {
currentBrowserAuthorityLabel,
resolveBrowserAuthority,
} from "./browser-authority";
+import { JOURNEY_OBSERVATION_ATTACHMENT } from "./qualification-reporter";
export { currentBrowserAuthorityLabel };
@@ -101,7 +102,11 @@ function validatedCloakEndpoint(configured: string): string {
return endpoint.toString();
}
-export const test = base.extend({
+type CounterLabAutomaticFixtures = {
+ counterlabJourneyObservation: void;
+};
+
+export const test = base.extend({
browser: [
async ({ playwright }, use) => {
const authority = resolveBrowserAuthority(process.env);
@@ -168,6 +173,60 @@ export const test = base.extend({
},
{ scope: "worker" },
],
+ counterlabJourneyObservation: [
+ async ({ page }, use, testInfo) => {
+ let consoleErrors = 0;
+ let failedRequests = 0;
+ page.on("console", (message) => {
+ if (message.type() === "error") consoleErrors += 1;
+ });
+ page.on("pageerror", () => {
+ consoleErrors += 1;
+ });
+ page.on("requestfailed", () => {
+ failedRequests += 1;
+ });
+ const browserVersion =
+ page.context().browser()?.version().trim() || "unavailable";
+
+ await use();
+
+ const expectedFailureAnnotations = testInfo.annotations.filter(
+ (annotation) =>
+ annotation.type === "counterlab-expected-request-failures",
+ );
+ const expectedRequestFailures = Number.parseInt(
+ expectedFailureAnnotations[0]?.description ?? "0",
+ 10,
+ );
+ const expectedRequestFailuresValid =
+ expectedFailureAnnotations.length <= 1 &&
+ Number.isSafeInteger(expectedRequestFailures) &&
+ expectedRequestFailures >= 0;
+ const viewport = page.viewportSize();
+ await testInfo.attach(JOURNEY_OBSERVATION_ATTACHMENT, {
+ body: Buffer.from(
+ JSON.stringify({
+ schemaVersion: "1",
+ authority: currentBrowserAuthorityLabel(),
+ viewport: viewport ?? { width: 0, height: 0 },
+ consoleErrors,
+ failedRequests: Math.max(
+ 0,
+ failedRequests -
+ (expectedRequestFailuresValid ? expectedRequestFailures : 0),
+ ),
+ expectedRequestFailuresMatched:
+ expectedRequestFailuresValid &&
+ failedRequests === expectedRequestFailures,
+ browserVersion,
+ }),
+ ),
+ contentType: "application/json",
+ });
+ },
+ { auto: true },
+ ],
});
export { expect } from "@playwright/test";
diff --git a/apps/web/e2e/qualification-reporter.ts b/apps/web/e2e/qualification-reporter.ts
new file mode 100644
index 00000000..161bd126
--- /dev/null
+++ b/apps/web/e2e/qualification-reporter.ts
@@ -0,0 +1,314 @@
+import {
+ basename,
+ dirname,
+ isAbsolute,
+ relative,
+ resolve,
+ sep,
+} from "node:path";
+import { mkdir, writeFile } from "node:fs/promises";
+
+import type {
+ FullResult,
+ Reporter,
+ TestCase,
+ TestResult,
+ TestStep,
+} from "@playwright/test/reporter";
+import { z } from "zod";
+
+import {
+ CloakBrowserRawRunSchema,
+ REQUIRED_CLOAK_JOURNEY_IDS,
+ REQUIRED_CLOAK_VIEWPORTS,
+} from "../../../scripts/submission-publication-evidence";
+
+export const JOURNEY_OBSERVATION_ATTACHMENT =
+ "counterlab-journey-observation" as const;
+
+const PageObservationSchema = z
+ .object({
+ schemaVersion: z.literal("1"),
+ authority: z.enum(["CLOAK_CDP_ENDPOINT", "stock-chromium-design-review"]),
+ viewport: z
+ .object({
+ width: z.number().int().positive().max(8_192),
+ height: z.number().int().positive().max(8_192),
+ })
+ .strict(),
+ consoleErrors: z.number().int().nonnegative(),
+ failedRequests: z.number().int().nonnegative(),
+ expectedRequestFailuresMatched: z.boolean(),
+ browserVersion: z.string().trim().min(1).max(128),
+ })
+ .strict();
+
+export type JourneyPageObservation = z.infer;
+
+export interface CapturedJourney {
+ id: string;
+ status: TestResult["status"];
+ expectedStatus: TestCase["expectedStatus"];
+ attempt: number;
+ durationMs: number;
+ assertionCount: number;
+ viewport: string;
+ consoleErrors: number;
+ failedRequests: number;
+ browserVersion: string;
+ browserAuthority:
+ "CLOAK_CDP_ENDPOINT" | "stock-chromium-design-review" | "unavailable";
+ telemetryValid: boolean;
+}
+
+type RawRunAuthority = "CLOAKBROWSER" | "STOCK_CHROMIUM_DESIGN_REVIEW";
+
+interface BuildRawRunInput {
+ authority: RawRunAuthority;
+ baseUrl: string;
+ completedAt: string;
+ journeys: readonly CapturedJourney[];
+ playwrightStatus: FullResult["status"];
+ qualificationRequested: boolean;
+ rootErrors?: number;
+ startedAt: string;
+}
+
+interface QualificationReporterOptions {
+ authority: RawRunAuthority;
+ baseUrl: string;
+ outputFile: string;
+ qualificationRequested: boolean;
+ runtimeRoot: string;
+}
+
+const expectedProductionOrigin =
+ "https://counterlab.cserules.workers.dev" as const;
+
+export function journeyIdForTest(
+ test: Pick,
+): string {
+ const fileName = basename(test.location.file);
+ const fileStem = fileName.replace(/\.spec\.[cm]?[jt]sx?$/u, "");
+ const titles = test
+ .titlePath()
+ .filter((title) => title !== "" && basename(title) !== fileName);
+ return [fileStem, ...titles].join("::");
+}
+
+export function countPlaywrightAssertions(
+ steps: readonly Pick[],
+): number {
+ return steps.reduce(
+ (count, step) =>
+ count +
+ (step.category === "expect" ? 1 : 0) +
+ countPlaywrightAssertions(step.steps),
+ 0,
+ );
+}
+
+export function readJourneyObservation(
+ result: Pick,
+): JourneyPageObservation {
+ const observations = result.attachments.filter(
+ (attachment) => attachment.name === JOURNEY_OBSERVATION_ATTACHMENT,
+ );
+ if (observations.length !== 1) {
+ throw new Error("Expected exactly one journey observation attachment");
+ }
+ const observation = observations[0]!;
+ if (
+ observation.contentType !== "application/json" ||
+ observation.body === undefined ||
+ observation.path !== undefined
+ ) {
+ throw new Error("Journey observation must be in-memory JSON");
+ }
+ return PageObservationSchema.parse(
+ JSON.parse(observation.body.toString("utf8")) as unknown,
+ );
+}
+
+function sortedJourneys(
+ journeys: readonly CapturedJourney[],
+): CapturedJourney[] {
+ const registryOrder = new Map(
+ REQUIRED_CLOAK_JOURNEY_IDS.map((id, index) => [id, index]),
+ );
+ return [...journeys].sort((left, right) => {
+ const leftIndex = registryOrder.get(left.id) ?? Number.MAX_SAFE_INTEGER;
+ const rightIndex = registryOrder.get(right.id) ?? Number.MAX_SAFE_INTEGER;
+ return leftIndex - rightIndex || left.id.localeCompare(right.id);
+ });
+}
+
+function exactJourneyRegistry(journeys: readonly CapturedJourney[]): boolean {
+ const identifiers = journeys.map((journey) => journey.id);
+ return (
+ identifiers.length === REQUIRED_CLOAK_JOURNEY_IDS.length &&
+ new Set(identifiers).size === identifiers.length &&
+ JSON.stringify([...identifiers].sort()) ===
+ JSON.stringify([...REQUIRED_CLOAK_JOURNEY_IDS].sort())
+ );
+}
+
+function exactViewportCoverage(journeys: readonly CapturedJourney[]): boolean {
+ const observed = new Set(journeys.map((journey) => journey.viewport));
+ return REQUIRED_CLOAK_VIEWPORTS.every((viewport) => observed.has(viewport));
+}
+
+export function buildCloakBrowserRawRun(input: BuildRawRunInput) {
+ const expectedJourneyAuthority =
+ input.authority === "CLOAKBROWSER"
+ ? "CLOAK_CDP_ENDPOINT"
+ : "stock-chromium-design-review";
+ const exactCleanRun =
+ input.qualificationRequested &&
+ input.authority === "CLOAKBROWSER" &&
+ input.baseUrl === expectedProductionOrigin &&
+ input.playwrightStatus === "passed" &&
+ (input.rootErrors ?? 0) === 0 &&
+ exactJourneyRegistry(input.journeys) &&
+ exactViewportCoverage(input.journeys) &&
+ input.journeys.every(
+ (journey) =>
+ journey.status === "passed" &&
+ journey.expectedStatus === "passed" &&
+ journey.attempt === 0 &&
+ journey.durationMs > 0 &&
+ journey.assertionCount > 0 &&
+ journey.consoleErrors === 0 &&
+ journey.failedRequests === 0 &&
+ journey.browserVersion.trim() !== "" &&
+ journey.browserVersion !== "unavailable" &&
+ journey.browserAuthority === expectedJourneyAuthority &&
+ journey.telemetryValid,
+ ) &&
+ new Set(input.journeys.map((journey) => journey.browserVersion)).size === 1;
+
+ return CloakBrowserRawRunSchema.parse({
+ schemaVersion: "1",
+ kind: "cloakbrowser-raw-run",
+ status: input.qualificationRequested
+ ? exactCleanRun
+ ? "PASSED"
+ : "FAILED"
+ : "NON_QUALIFYING",
+ authority: input.authority,
+ qualificationRequested: input.qualificationRequested,
+ baseUrl: input.baseUrl,
+ startedAt: input.startedAt,
+ completedAt: input.completedAt,
+ playwrightVersion: "1.61.1",
+ playwrightStatus: input.playwrightStatus,
+ rootErrors: input.rootErrors ?? 0,
+ journeys: sortedJourneys(input.journeys),
+ privacy: {
+ containsSecrets: false,
+ containsRawNotebook: false,
+ containsPersonalData: false,
+ },
+ });
+}
+
+function assertReporterOutput(outputFile: string, runtimeRoot: string): string {
+ if (
+ runtimeRoot.trim() === "" ||
+ !isAbsolute(runtimeRoot) ||
+ !isAbsolute(outputFile)
+ ) {
+ throw new Error(
+ "Qualification reporter paths require an absolute repository runtime root",
+ );
+ }
+ const resolvedRoot = resolve(runtimeRoot);
+ const resolvedOutput = resolve(outputFile);
+ const pathFromRoot = relative(resolvedRoot, resolvedOutput);
+ if (
+ pathFromRoot === "" ||
+ pathFromRoot === ".." ||
+ pathFromRoot.startsWith(`..${sep}`) ||
+ isAbsolute(pathFromRoot)
+ ) {
+ throw new Error(
+ "Qualification reporter output must stay beneath the E2E runtime root",
+ );
+ }
+ return resolvedOutput;
+}
+
+export default class CounterLabQualificationReporter implements Reporter {
+ private readonly capturedJourneys: CapturedJourney[] = [];
+ private readonly startedAt = new Date().toISOString();
+ private rootErrors = 0;
+
+ constructor(private readonly options: QualificationReporterOptions) {}
+
+ printsToStdio(): boolean {
+ return false;
+ }
+
+ onError(): void {
+ this.rootErrors += 1;
+ }
+
+ onTestEnd(test: TestCase, result: TestResult): void {
+ let observation: JourneyPageObservation | null = null;
+ try {
+ observation = readJourneyObservation(result);
+ } catch {
+ observation = null;
+ }
+ this.capturedJourneys.push({
+ id: journeyIdForTest(test),
+ status: result.status,
+ expectedStatus: test.expectedStatus,
+ attempt: result.retry,
+ durationMs: Math.max(0, Math.round(result.duration)),
+ assertionCount: countPlaywrightAssertions(result.steps),
+ viewport:
+ observation === null
+ ? "0x0"
+ : `${observation.viewport.width}x${observation.viewport.height}`,
+ consoleErrors: observation?.consoleErrors ?? 1,
+ failedRequests: observation?.failedRequests ?? 1,
+ browserVersion: observation?.browserVersion ?? "unavailable",
+ browserAuthority: observation?.authority ?? "unavailable",
+ telemetryValid:
+ observation !== null && observation.expectedRequestFailuresMatched,
+ });
+ }
+
+ async onEnd(
+ result: FullResult,
+ ): Promise<{ status?: FullResult["status"] } | undefined> {
+ try {
+ const report = buildCloakBrowserRawRun({
+ authority: this.options.authority,
+ baseUrl: this.options.baseUrl,
+ completedAt: new Date().toISOString(),
+ journeys: this.capturedJourneys,
+ playwrightStatus: result.status,
+ qualificationRequested: this.options.qualificationRequested,
+ rootErrors: this.rootErrors,
+ startedAt: this.startedAt,
+ });
+ const outputFile = assertReporterOutput(
+ this.options.outputFile,
+ this.options.runtimeRoot,
+ );
+ await mkdir(dirname(outputFile), { recursive: true });
+ await writeFile(outputFile, `${JSON.stringify(report, null, 2)}\n`, {
+ encoding: "utf8",
+ mode: 0o600,
+ });
+ if (this.options.qualificationRequested && report.status !== "PASSED") {
+ return { status: "failed" };
+ }
+ return undefined;
+ } catch {
+ return { status: "failed" };
+ }
+ }
+}
diff --git a/apps/web/e2e/recovery-and-intake.spec.ts b/apps/web/e2e/recovery-and-intake.spec.ts
index 8d484c14..9d93ce27 100644
--- a/apps/web/e2e/recovery-and-intake.spec.ts
+++ b/apps/web/e2e/recovery-and-intake.spec.ts
@@ -382,7 +382,11 @@ test("a supported live upload creates one source-bound session", async ({
test("an interrupted upload accepts the same file on retry", async ({
page,
-}) => {
+}, testInfo) => {
+ testInfo.annotations.push({
+ type: "counterlab-expected-request-failures",
+ description: "1",
+ });
let uploadAttempts = 0;
await page.route("**/api/artifacts", async (route) => {
if (route.request().method() !== "POST") {
@@ -415,7 +419,11 @@ test("an interrupted upload accepts the same file on retry", async ({
test("a lost private-session response retries without re-uploading", async ({
page,
-}) => {
+}, testInfo) => {
+ testInfo.annotations.push({
+ type: "counterlab-expected-request-failures",
+ description: "1",
+ });
let uploadRequests = 0;
let sessionCreationAttempts = 0;
page.on("request", (request) => {
diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts
index 610eddd2..acb2a4a4 100644
--- a/apps/web/playwright.config.ts
+++ b/apps/web/playwright.config.ts
@@ -49,8 +49,13 @@ assertNoSymlinkTraversal(runtimeRoot, "COUNTERLAB_E2E_RUNTIME_ROOT");
const outputDir = join(runtimeRoot, "playwright-output");
const resultsFile = join(runtimeRoot, "evidence/results.json");
+const qualificationRunFile = join(
+ runtimeRoot,
+ "evidence/cloakbrowser-raw-run.json",
+);
assertNoSymlinkTraversal(outputDir, "Playwright output directory");
assertNoSymlinkTraversal(resultsFile, "Playwright results file");
+assertNoSymlinkTraversal(qualificationRunFile, "CloakBrowser raw run evidence");
const browserAuthority = resolveBrowserAuthority(process.env);
@@ -93,6 +98,26 @@ const staticDesignReview =
const localAdmissionKey =
process.env.COUNTERLAB_ADMISSION_KEY?.trim() ||
["counterlab", "local", "e2e", "admission", "only", "000000"].join("-");
+const qualificationSetting = process.env.COUNTERLAB_BROWSER_QUALIFICATION;
+if (
+ qualificationSetting !== undefined &&
+ qualificationSetting !== "true" &&
+ qualificationSetting !== "false"
+) {
+ throw new Error(
+ "COUNTERLAB_BROWSER_QUALIFICATION must be exactly true or false",
+ );
+}
+const qualificationRequested = qualificationSetting === "true";
+if (
+ qualificationRequested &&
+ (browserAuthority.kind !== "cloak" ||
+ remoteBaseURL !== "https://counterlab.cserules.workers.dev")
+) {
+ throw new Error(
+ "CloakBrowser qualification requires the exact public CounterLab origin",
+ );
+}
export default defineConfig({
testDir: "./e2e",
@@ -101,7 +126,23 @@ export default defineConfig({
forbidOnly: true,
retries: process.env.CI ? 1 : 0,
outputDir,
- reporter: [["line"], ["json", { outputFile: resultsFile }]],
+ reporter: [
+ ["line"],
+ ["json", { outputFile: resultsFile }],
+ [
+ "./e2e/qualification-reporter.ts",
+ {
+ authority:
+ browserAuthority.kind === "cloak"
+ ? "CLOAKBROWSER"
+ : "STOCK_CHROMIUM_DESIGN_REVIEW",
+ baseUrl: baseURL,
+ outputFile: qualificationRunFile,
+ qualificationRequested,
+ runtimeRoot,
+ },
+ ],
+ ],
use: {
baseURL,
browserName: "chromium",
diff --git a/apps/web/src/test/qualificationReporter.test.ts b/apps/web/src/test/qualificationReporter.test.ts
new file mode 100644
index 00000000..a36ffa21
--- /dev/null
+++ b/apps/web/src/test/qualificationReporter.test.ts
@@ -0,0 +1,258 @@
+import type { TestCase, TestResult, TestStep } from "@playwright/test/reporter";
+import { describe, expect, it } from "vitest";
+
+import {
+ buildCloakBrowserRawRun,
+ countPlaywrightAssertions,
+ journeyIdForTest,
+ readJourneyObservation,
+ type CapturedJourney,
+} from "../../e2e/qualification-reporter";
+import {
+ REQUIRED_CLOAK_JOURNEY_IDS,
+ REQUIRED_CLOAK_VIEWPORTS,
+} from "../../../../scripts/submission-publication-evidence";
+
+function reporterTestCase(
+ file: string,
+ titlePath: readonly string[],
+): TestCase {
+ return {
+ location: { file, line: 1, column: 1 },
+ titlePath: () => [...titlePath],
+ } as TestCase;
+}
+
+function step(category: string, children: readonly TestStep[] = []): TestStep {
+ return { category, steps: [...children] } as TestStep;
+}
+
+function capturedJourneys(): CapturedJourney[] {
+ return REQUIRED_CLOAK_JOURNEY_IDS.map((id, index) => ({
+ id,
+ status: "passed",
+ expectedStatus: "passed",
+ attempt: 0,
+ durationMs: index + 1,
+ assertionCount: 1,
+ viewport:
+ REQUIRED_CLOAK_VIEWPORTS[index % REQUIRED_CLOAK_VIEWPORTS.length]!,
+ consoleErrors: 0,
+ failedRequests: 0,
+ browserVersion: "Chrome/140.0.0.0",
+ browserAuthority: "CLOAK_CDP_ENDPOINT",
+ telemetryValid: true,
+ }));
+}
+
+describe("CloakBrowser qualification reporter", () => {
+ it("derives stable registry IDs from file and describe titles", () => {
+ const testCase = reporterTestCase(
+ "/repo/apps/web/e2e/mobile-public-replay.spec.ts",
+ [
+ "",
+ "",
+ "mobile-public-replay.spec.ts",
+ "public mobile replay routing",
+ "390x844 opens the replay deep link read-only without root overflow",
+ ],
+ );
+
+ expect(journeyIdForTest(testCase)).toBe(
+ "mobile-public-replay::public mobile replay routing::390x844 opens the replay deep link read-only without root overflow",
+ );
+ });
+
+ it("counts nested Playwright expect steps without counting fixtures or API calls", () => {
+ expect(
+ countPlaywrightAssertions([
+ step("fixture", [step("expect")]),
+ step("expect"),
+ step("test.step", [step("pw:api"), step("expect")]),
+ ]),
+ ).toBe(3);
+ });
+
+ it("accepts exactly one privacy-safe in-memory page observation", () => {
+ const body = Buffer.from(
+ JSON.stringify({
+ schemaVersion: "1",
+ authority: "CLOAK_CDP_ENDPOINT",
+ viewport: { width: 1440, height: 900 },
+ consoleErrors: 0,
+ failedRequests: 0,
+ expectedRequestFailuresMatched: true,
+ browserVersion: "Chrome/140.0.0.0",
+ }),
+ );
+ const result = {
+ attachments: [
+ {
+ name: "counterlab-journey-observation",
+ contentType: "application/json",
+ body,
+ },
+ ],
+ } as TestResult;
+
+ expect(readJourneyObservation(result)).toEqual({
+ schemaVersion: "1",
+ authority: "CLOAK_CDP_ENDPOINT",
+ viewport: { width: 1440, height: 900 },
+ consoleErrors: 0,
+ failedRequests: 0,
+ expectedRequestFailuresMatched: true,
+ browserVersion: "Chrome/140.0.0.0",
+ });
+ });
+
+ it("rejects missing, path-based, duplicate, and over-disclosed observations", () => {
+ const validBody = Buffer.from(
+ JSON.stringify({
+ schemaVersion: "1",
+ authority: "CLOAK_CDP_ENDPOINT",
+ viewport: { width: 1440, height: 900 },
+ consoleErrors: 0,
+ failedRequests: 0,
+ expectedRequestFailuresMatched: true,
+ browserVersion: "Chrome/140.0.0.0",
+ }),
+ );
+ const attachment = {
+ name: "counterlab-journey-observation",
+ contentType: "application/json",
+ body: validBody,
+ };
+
+ expect(() =>
+ readJourneyObservation({ attachments: [] } as unknown as TestResult),
+ ).toThrow(/exactly one journey observation/i);
+ expect(() =>
+ readJourneyObservation({
+ attachments: [{ ...attachment, body: undefined, path: "/private" }],
+ } as unknown as TestResult),
+ ).toThrow(/in-memory JSON/i);
+ expect(() =>
+ readJourneyObservation({
+ attachments: [attachment, attachment],
+ } as unknown as TestResult),
+ ).toThrow(/exactly one journey observation/i);
+ expect(() =>
+ readJourneyObservation({
+ attachments: [
+ {
+ ...attachment,
+ body: Buffer.from(
+ JSON.stringify({
+ ...JSON.parse(validBody.toString("utf8")),
+ pageUrl: "https://private.example/session/token",
+ }),
+ ),
+ },
+ ],
+ } as unknown as TestResult),
+ ).toThrow();
+ });
+
+ it("marks only an exact clean 40-journey Cloak run as passed", () => {
+ const report = buildCloakBrowserRawRun({
+ authority: "CLOAKBROWSER",
+ baseUrl: "https://counterlab.cserules.workers.dev",
+ completedAt: "2026-07-21T10:01:00.000Z",
+ journeys: capturedJourneys(),
+ playwrightStatus: "passed",
+ qualificationRequested: true,
+ startedAt: "2026-07-21T10:00:00.000Z",
+ });
+
+ expect(report.status).toBe("PASSED");
+ expect(report.journeys).toHaveLength(40);
+ expect(new Set(report.journeys.map((journey) => journey.viewport))).toEqual(
+ new Set(REQUIRED_CLOAK_VIEWPORTS),
+ );
+ });
+
+ it.each([
+ {
+ name: "missing journey",
+ mutate: (journeys: CapturedJourney[]) => journeys.slice(1),
+ },
+ {
+ name: "extra duplicate journey",
+ mutate: (journeys: CapturedJourney[]) => [
+ ...journeys,
+ { ...journeys[0]! },
+ ],
+ },
+ {
+ name: "retry",
+ mutate: (journeys: CapturedJourney[]) => [
+ { ...journeys[0]!, attempt: 1 },
+ ...journeys.slice(1),
+ ],
+ },
+ {
+ name: "expected failure",
+ mutate: (journeys: CapturedJourney[]) => [
+ { ...journeys[0]!, expectedStatus: "failed" as const },
+ ...journeys.slice(1),
+ ],
+ },
+ {
+ name: "console error",
+ mutate: (journeys: CapturedJourney[]) => [
+ { ...journeys[0]!, consoleErrors: 1 },
+ ...journeys.slice(1),
+ ],
+ },
+ {
+ name: "missing assertion",
+ mutate: (journeys: CapturedJourney[]) => [
+ { ...journeys[0]!, assertionCount: 0 },
+ ...journeys.slice(1),
+ ],
+ },
+ {
+ name: "invalid telemetry",
+ mutate: (journeys: CapturedJourney[]) => [
+ { ...journeys[0]!, telemetryValid: false },
+ ...journeys.slice(1),
+ ],
+ },
+ {
+ name: "missing required viewport",
+ mutate: (journeys: CapturedJourney[]) =>
+ journeys.map((journey) =>
+ journey.viewport === "768x1024"
+ ? { ...journey, viewport: "1440x900" as const }
+ : journey,
+ ),
+ },
+ ])("fails closed for $name", ({ mutate }) => {
+ const report = buildCloakBrowserRawRun({
+ authority: "CLOAKBROWSER",
+ baseUrl: "https://counterlab.cserules.workers.dev",
+ completedAt: "2026-07-21T10:01:00.000Z",
+ journeys: mutate(capturedJourneys()),
+ playwrightStatus: "passed",
+ qualificationRequested: true,
+ startedAt: "2026-07-21T10:00:00.000Z",
+ });
+
+ expect(report.status).toBe("FAILED");
+ });
+
+ it("never upgrades a design-review run into qualifying evidence", () => {
+ const report = buildCloakBrowserRawRun({
+ authority: "STOCK_CHROMIUM_DESIGN_REVIEW",
+ baseUrl: "http://127.0.0.1:5173",
+ completedAt: "2026-07-21T10:01:00.000Z",
+ journeys: capturedJourneys(),
+ playwrightStatus: "passed",
+ qualificationRequested: false,
+ startedAt: "2026-07-21T10:00:00.000Z",
+ });
+
+ expect(report.status).toBe("NON_QUALIFYING");
+ });
+});
diff --git a/scripts/submission-publication-evidence.ts b/scripts/submission-publication-evidence.ts
index 46ab0bd4..10cfebe5 100644
--- a/scripts/submission-publication-evidence.ts
+++ b/scripts/submission-publication-evidence.ts
@@ -151,7 +151,7 @@ export const CloakManualEvidenceReceiptSchema = z
})
.strict();
-const REQUIRED_CLOAK_VIEWPORTS = [
+export const REQUIRED_CLOAK_VIEWPORTS = [
"375x812",
"390x844",
"768x1024",
@@ -250,6 +250,53 @@ function addCloakJourneyIssues(
}
}
+export const CloakBrowserRawJourneySchema = z
+ .object({
+ id: z.string().trim().min(1).max(512),
+ status: z.enum(["passed", "failed", "timedOut", "skipped", "interrupted"]),
+ expectedStatus: z.enum([
+ "passed",
+ "failed",
+ "timedOut",
+ "skipped",
+ "interrupted",
+ ]),
+ attempt: z.number().int().nonnegative(),
+ durationMs: z.number().int().nonnegative(),
+ assertionCount: z.number().int().nonnegative(),
+ viewport: z.string().regex(/^\d+x\d+$/u),
+ consoleErrors: z.number().int().nonnegative(),
+ failedRequests: z.number().int().nonnegative(),
+ browserVersion: z.string().trim().min(1).max(128),
+ browserAuthority: z.enum([
+ "CLOAK_CDP_ENDPOINT",
+ "stock-chromium-design-review",
+ "unavailable",
+ ]),
+ telemetryValid: z.boolean(),
+ })
+ .strict();
+
+export const CloakBrowserRawRunSchema = z
+ .object({
+ schemaVersion: z.literal("1"),
+ kind: z.literal("cloakbrowser-raw-run"),
+ status: z.enum(["PASSED", "FAILED", "NON_QUALIFYING"]),
+ authority: z.enum(["CLOAKBROWSER", "STOCK_CHROMIUM_DESIGN_REVIEW"]),
+ qualificationRequested: z.boolean(),
+ baseUrl: z.string().trim().min(1).max(2_048),
+ startedAt: z.iso.datetime({ offset: true }),
+ completedAt: z.iso.datetime({ offset: true }),
+ playwrightVersion: z.literal("1.61.1"),
+ playwrightStatus: z.enum(["passed", "failed", "timedout", "interrupted"]),
+ rootErrors: z.number().int().nonnegative(),
+ // Preserve at most the initial run plus one retry per registered journey
+ // so a failed qualification still leaves a bounded diagnostic receipt.
+ journeys: z.array(CloakBrowserRawJourneySchema).max(80),
+ privacy: PublicationPrivacySchema,
+ })
+ .strict();
+
export const CloakBrowserExecutionReportSchema = z
.object({
schemaVersion: z.literal("1"),
From a6f6d8d223a69d330f70966489234ee7a67a6fe2 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 15:24:27 +0530
Subject: [PATCH 096/143] test(browser): bind qualification telemetry
---
apps/web/e2e/browser-authority.ts | 23 ++++-
apps/web/e2e/cloak-test.ts | 73 ++++++++-------
apps/web/e2e/qualification-reporter.ts | 89 ++++++++++++++++--
apps/web/e2e/recovery-and-intake.spec.ts | 4 +-
apps/web/playwright.config.ts | 1 +
apps/web/src/test/browserAuthority.test.ts | 19 ++++
.../src/test/qualificationReporter.test.ts | 91 +++++++++++++++++--
scripts/submission-publication-evidence.ts | 56 +++++++++++-
8 files changed, 302 insertions(+), 54 deletions(-)
diff --git a/apps/web/e2e/browser-authority.ts b/apps/web/e2e/browser-authority.ts
index edb33110..a0148fe6 100644
--- a/apps/web/e2e/browser-authority.ts
+++ b/apps/web/e2e/browser-authority.ts
@@ -17,6 +17,27 @@ export type BrowserAuthority =
type BrowserEnvironment = Readonly>;
+export function validateCloakCdpEndpoint(configured: string): string {
+ const endpoint = new URL(configured);
+ if (!["http:", "https:", "ws:", "wss:"].includes(endpoint.protocol)) {
+ throw new Error(
+ "CLOAK_CDP_ENDPOINT must use an http(s) or ws(s) CDP endpoint",
+ );
+ }
+ if (endpoint.username !== "" || endpoint.password !== "") {
+ throw new Error("CLOAK_CDP_ENDPOINT must not contain URL credentials");
+ }
+ if (
+ !new Set(["127.0.0.1", "localhost", "[::1]"]).has(endpoint.hostname) ||
+ endpoint.port === ""
+ ) {
+ throw new Error(
+ "CLOAK_CDP_ENDPOINT must use an explicit port on a loopback host",
+ );
+ }
+ return configured;
+}
+
export function resolveBrowserAuthority(
environment: BrowserEnvironment,
): BrowserAuthority {
@@ -34,7 +55,7 @@ export function resolveBrowserAuthority(
return {
kind: "cloak",
evidenceLabel: "CLOAK_CDP_ENDPOINT",
- endpoint,
+ endpoint: validateCloakCdpEndpoint(endpoint),
};
}
if (requestedAuthority !== STOCK_CHROMIUM_DESIGN_REVIEW) {
diff --git a/apps/web/e2e/cloak-test.ts b/apps/web/e2e/cloak-test.ts
index e6e8583d..15e0eaac 100644
--- a/apps/web/e2e/cloak-test.ts
+++ b/apps/web/e2e/cloak-test.ts
@@ -8,8 +8,14 @@ import { test as base } from "@playwright/test";
import {
currentBrowserAuthorityLabel,
resolveBrowserAuthority,
+ validateCloakCdpEndpoint,
} from "./browser-authority";
-import { JOURNEY_OBSERVATION_ATTACHMENT } from "./qualification-reporter";
+import {
+ JOURNEY_OBSERVATION_ATTACHMENT,
+ journeyIdForParts,
+ summarizeRequestFailures,
+} from "./qualification-reporter";
+import { REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID } from "../../../scripts/submission-publication-evidence";
export { currentBrowserAuthorityLabel };
@@ -89,19 +95,6 @@ export function runtimeOutputPath(candidate: string, label: string): string {
return resolved;
}
-function validatedCloakEndpoint(configured: string): string {
- const endpoint = new URL(configured);
- if (!["http:", "https:", "ws:", "wss:"].includes(endpoint.protocol)) {
- throw new Error(
- "CLOAK_CDP_ENDPOINT must use an http(s) or ws(s) CDP endpoint",
- );
- }
- if (endpoint.username !== "" || endpoint.password !== "") {
- throw new Error("CLOAK_CDP_ENDPOINT must not contain URL credentials");
- }
- return endpoint.toString();
-}
-
type CounterLabAutomaticFixtures = {
counterlabJourneyObservation: void;
};
@@ -118,7 +111,7 @@ export const test = base.extend({
assertContainedPath(artifactsDir, "Playwright browser artifacts");
if (authority.kind === "cloak") {
const browser = await playwright.chromium.connectOverCDP(
- validatedCloakEndpoint(authority.endpoint),
+ validateCloakCdpEndpoint(authority.endpoint),
{
artifactsDir,
timeout: 30_000,
@@ -175,16 +168,33 @@ export const test = base.extend({
],
counterlabJourneyObservation: [
async ({ page }, use, testInfo) => {
+ const journeyId = journeyIdForParts(testInfo.file, testInfo.titlePath);
+ const expectedViewport =
+ REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID[
+ journeyId as keyof typeof REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID
+ ];
+ if (expectedViewport === undefined) {
+ throw new Error(
+ `Unregistered browser qualification journey: ${journeyId}`,
+ );
+ }
+ const [width, height] = expectedViewport
+ .split("x")
+ .map((part) => Number.parseInt(part, 10));
+ await page.setViewportSize({ width: width!, height: height! });
let consoleErrors = 0;
- let failedRequests = 0;
+ const failedRequests: string[] = [];
page.on("console", (message) => {
if (message.type() === "error") consoleErrors += 1;
});
page.on("pageerror", () => {
consoleErrors += 1;
});
- page.on("requestfailed", () => {
- failedRequests += 1;
+ page.on("requestfailed", (request) => {
+ const url = new URL(request.url());
+ failedRequests.push(
+ `${request.method().toUpperCase()} ${url.pathname}`,
+ );
});
const browserVersion =
page.context().browser()?.version().trim() || "unavailable";
@@ -195,30 +205,25 @@ export const test = base.extend({
(annotation) =>
annotation.type === "counterlab-expected-request-failures",
);
- const expectedRequestFailures = Number.parseInt(
- expectedFailureAnnotations[0]?.description ?? "0",
- 10,
+ const expectedRequestFailures = expectedFailureAnnotations.map(
+ (annotation) => annotation.description ?? "",
+ );
+ const requestFailureSummary = summarizeRequestFailures(
+ failedRequests,
+ expectedRequestFailures,
);
- const expectedRequestFailuresValid =
- expectedFailureAnnotations.length <= 1 &&
- Number.isSafeInteger(expectedRequestFailures) &&
- expectedRequestFailures >= 0;
const viewport = page.viewportSize();
await testInfo.attach(JOURNEY_OBSERVATION_ATTACHMENT, {
body: Buffer.from(
JSON.stringify({
- schemaVersion: "1",
+ schemaVersion: "2",
authority: currentBrowserAuthorityLabel(),
viewport: viewport ?? { width: 0, height: 0 },
consoleErrors,
- failedRequests: Math.max(
- 0,
- failedRequests -
- (expectedRequestFailuresValid ? expectedRequestFailures : 0),
- ),
- expectedRequestFailuresMatched:
- expectedRequestFailuresValid &&
- failedRequests === expectedRequestFailures,
+ expectedFailedRequests: requestFailureSummary.expectedCount,
+ failedRequests: requestFailureSummary.unexpectedCount,
+ observedFailedRequests: requestFailureSummary.observedCount,
+ expectedRequestFailuresMatched: requestFailureSummary.matched,
browserVersion,
}),
),
diff --git a/apps/web/e2e/qualification-reporter.ts b/apps/web/e2e/qualification-reporter.ts
index 161bd126..c30691d2 100644
--- a/apps/web/e2e/qualification-reporter.ts
+++ b/apps/web/e2e/qualification-reporter.ts
@@ -19,16 +19,18 @@ import { z } from "zod";
import {
CloakBrowserRawRunSchema,
+ REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID,
REQUIRED_CLOAK_JOURNEY_IDS,
REQUIRED_CLOAK_VIEWPORTS,
} from "../../../scripts/submission-publication-evidence";
+import { containedNewOutputFile } from "../../../scripts/repository-cli-paths";
export const JOURNEY_OBSERVATION_ATTACHMENT =
"counterlab-journey-observation" as const;
const PageObservationSchema = z
.object({
- schemaVersion: z.literal("1"),
+ schemaVersion: z.literal("2"),
authority: z.enum(["CLOAK_CDP_ENDPOINT", "stock-chromium-design-review"]),
viewport: z
.object({
@@ -37,7 +39,9 @@ const PageObservationSchema = z
})
.strict(),
consoleErrors: z.number().int().nonnegative(),
+ expectedFailedRequests: z.number().int().nonnegative(),
failedRequests: z.number().int().nonnegative(),
+ observedFailedRequests: z.number().int().nonnegative(),
expectedRequestFailuresMatched: z.boolean(),
browserVersion: z.string().trim().min(1).max(128),
})
@@ -54,7 +58,9 @@ export interface CapturedJourney {
assertionCount: number;
viewport: string;
consoleErrors: number;
+ expectedFailedRequests: number;
failedRequests: number;
+ observedFailedRequests: number;
browserVersion: string;
browserAuthority:
"CLOAK_CDP_ENDPOINT" | "stock-chromium-design-review" | "unavailable";
@@ -79,20 +85,60 @@ interface QualificationReporterOptions {
baseUrl: string;
outputFile: string;
qualificationRequested: boolean;
+ repositoryRoot: string;
runtimeRoot: string;
}
const expectedProductionOrigin =
"https://counterlab.cserules.workers.dev" as const;
+const allowedExpectedRequestFailures = new Set([
+ "POST /api/artifacts",
+ "POST /api/live/sessions",
+]);
+
+export function summarizeRequestFailures(
+ observed: readonly string[],
+ expected: readonly string[],
+): {
+ expectedCount: number;
+ matched: boolean;
+ observedCount: number;
+ unexpectedCount: number;
+} {
+ const expectedValid =
+ expected.every((failure) => allowedExpectedRequestFailures.has(failure)) &&
+ new Set(expected).size === expected.length;
+ const remaining = [...observed];
+ for (const expectedFailure of expected) {
+ const index = remaining.indexOf(expectedFailure);
+ if (index >= 0) remaining.splice(index, 1);
+ }
+ return {
+ expectedCount: expected.length,
+ matched:
+ expectedValid &&
+ observed.length === expected.length &&
+ remaining.length === 0,
+ observedCount: observed.length,
+ unexpectedCount: remaining.length,
+ };
+}
export function journeyIdForTest(
test: Pick,
): string {
- const fileName = basename(test.location.file);
+ return journeyIdForParts(test.location.file, test.titlePath());
+}
+
+export function journeyIdForParts(
+ file: string,
+ titlePath: readonly string[],
+): string {
+ const fileName = basename(file);
const fileStem = fileName.replace(/\.spec\.[cm]?[jt]sx?$/u, "");
- const titles = test
- .titlePath()
- .filter((title) => title !== "" && basename(title) !== fileName);
+ const titles = titlePath.filter(
+ (title) => title !== "" && basename(title) !== fileName,
+ );
return [fileStem, ...titles].join("::");
}
@@ -102,8 +148,10 @@ export function countPlaywrightAssertions(
return steps.reduce(
(count, step) =>
count +
- (step.category === "expect" ? 1 : 0) +
- countPlaywrightAssertions(step.steps),
+ (step.category === "fixture" || step.category === "hook"
+ ? 0
+ : (step.category === "expect" ? 1 : 0) +
+ countPlaywrightAssertions(step.steps)),
0,
);
}
@@ -155,7 +203,15 @@ function exactJourneyRegistry(journeys: readonly CapturedJourney[]): boolean {
function exactViewportCoverage(journeys: readonly CapturedJourney[]): boolean {
const observed = new Set(journeys.map((journey) => journey.viewport));
- return REQUIRED_CLOAK_VIEWPORTS.every((viewport) => observed.has(viewport));
+ return (
+ REQUIRED_CLOAK_VIEWPORTS.every((viewport) => observed.has(viewport)) &&
+ journeys.every(
+ (journey) =>
+ REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID[
+ journey.id as keyof typeof REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID
+ ] === journey.viewport,
+ )
+ );
}
export function buildCloakBrowserRawRun(input: BuildRawRunInput) {
@@ -179,6 +235,7 @@ export function buildCloakBrowserRawRun(input: BuildRawRunInput) {
journey.durationMs > 0 &&
journey.assertionCount > 0 &&
journey.consoleErrors === 0 &&
+ journey.observedFailedRequests === journey.expectedFailedRequests &&
journey.failedRequests === 0 &&
journey.browserVersion.trim() !== "" &&
journey.browserVersion !== "unavailable" &&
@@ -188,7 +245,7 @@ export function buildCloakBrowserRawRun(input: BuildRawRunInput) {
new Set(input.journeys.map((journey) => journey.browserVersion)).size === 1;
return CloakBrowserRawRunSchema.parse({
- schemaVersion: "1",
+ schemaVersion: "2",
kind: "cloakbrowser-raw-run",
status: input.qualificationRequested
? exactCleanRun
@@ -272,7 +329,9 @@ export default class CounterLabQualificationReporter implements Reporter {
? "0x0"
: `${observation.viewport.width}x${observation.viewport.height}`,
consoleErrors: observation?.consoleErrors ?? 1,
+ expectedFailedRequests: observation?.expectedFailedRequests ?? 0,
failedRequests: observation?.failedRequests ?? 1,
+ observedFailedRequests: observation?.observedFailedRequests ?? 1,
browserVersion: observation?.browserVersion ?? "unavailable",
browserAuthority: observation?.authority ?? "unavailable",
telemetryValid:
@@ -283,6 +342,12 @@ export default class CounterLabQualificationReporter implements Reporter {
async onEnd(
result: FullResult,
): Promise<{ status?: FullResult["status"] } | undefined> {
+ if (
+ this.capturedJourneys.length === 0 &&
+ !this.options.qualificationRequested
+ ) {
+ return undefined;
+ }
try {
const report = buildCloakBrowserRawRun({
authority: this.options.authority,
@@ -299,8 +364,14 @@ export default class CounterLabQualificationReporter implements Reporter {
this.options.runtimeRoot,
);
await mkdir(dirname(outputFile), { recursive: true });
+ await containedNewOutputFile(
+ this.options.repositoryRoot,
+ outputFile,
+ "CloakBrowser raw run evidence",
+ );
await writeFile(outputFile, `${JSON.stringify(report, null, 2)}\n`, {
encoding: "utf8",
+ flag: "wx",
mode: 0o600,
});
if (this.options.qualificationRequested && report.status !== "PASSED") {
diff --git a/apps/web/e2e/recovery-and-intake.spec.ts b/apps/web/e2e/recovery-and-intake.spec.ts
index 9d93ce27..d5284839 100644
--- a/apps/web/e2e/recovery-and-intake.spec.ts
+++ b/apps/web/e2e/recovery-and-intake.spec.ts
@@ -385,7 +385,7 @@ test("an interrupted upload accepts the same file on retry", async ({
}, testInfo) => {
testInfo.annotations.push({
type: "counterlab-expected-request-failures",
- description: "1",
+ description: "POST /api/artifacts",
});
let uploadAttempts = 0;
await page.route("**/api/artifacts", async (route) => {
@@ -422,7 +422,7 @@ test("a lost private-session response retries without re-uploading", async ({
}, testInfo) => {
testInfo.annotations.push({
type: "counterlab-expected-request-failures",
- description: "1",
+ description: "POST /api/live/sessions",
});
let uploadRequests = 0;
let sessionCreationAttempts = 0;
diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts
index acb2a4a4..6de91e4b 100644
--- a/apps/web/playwright.config.ts
+++ b/apps/web/playwright.config.ts
@@ -139,6 +139,7 @@ export default defineConfig({
baseUrl: baseURL,
outputFile: qualificationRunFile,
qualificationRequested,
+ repositoryRoot,
runtimeRoot,
},
],
diff --git a/apps/web/src/test/browserAuthority.test.ts b/apps/web/src/test/browserAuthority.test.ts
index 4a010f6e..27ee578e 100644
--- a/apps/web/src/test/browserAuthority.test.ts
+++ b/apps/web/src/test/browserAuthority.test.ts
@@ -3,6 +3,7 @@ import { describe, expect, it } from "vitest";
import {
resolveBrowserAuthority,
STOCK_CHROMIUM_DESIGN_REVIEW,
+ validateCloakCdpEndpoint,
} from "../../e2e/browser-authority";
describe("browser evidence authority", () => {
@@ -53,4 +54,22 @@ describe("browser evidence authority", () => {
}),
).toThrow(/permits only \/usr\/bin\/chromium/i);
});
+
+ it("requires a credential-free loopback Cloak endpoint with an explicit port", () => {
+ expect(validateCloakCdpEndpoint("ws://127.0.0.1:9222/devtools")).toBe(
+ "ws://127.0.0.1:9222/devtools",
+ );
+ expect(validateCloakCdpEndpoint("http://localhost:9222")).toBe(
+ "http://localhost:9222",
+ );
+ expect(() =>
+ validateCloakCdpEndpoint("https://browser.example.test:9222"),
+ ).toThrow(/loopback host/i);
+ expect(() => validateCloakCdpEndpoint("http://127.0.0.1")).toThrow(
+ /explicit port/i,
+ );
+ expect(() =>
+ validateCloakCdpEndpoint("http://user:secret@127.0.0.1:9222"),
+ ).toThrow(/credentials/i);
+ });
});
diff --git a/apps/web/src/test/qualificationReporter.test.ts b/apps/web/src/test/qualificationReporter.test.ts
index a36ffa21..06f28119 100644
--- a/apps/web/src/test/qualificationReporter.test.ts
+++ b/apps/web/src/test/qualificationReporter.test.ts
@@ -6,9 +6,11 @@ import {
countPlaywrightAssertions,
journeyIdForTest,
readJourneyObservation,
+ summarizeRequestFailures,
type CapturedJourney,
} from "../../e2e/qualification-reporter";
import {
+ REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID,
REQUIRED_CLOAK_JOURNEY_IDS,
REQUIRED_CLOAK_VIEWPORTS,
} from "../../../../scripts/submission-publication-evidence";
@@ -35,10 +37,11 @@ function capturedJourneys(): CapturedJourney[] {
attempt: 0,
durationMs: index + 1,
assertionCount: 1,
- viewport:
- REQUIRED_CLOAK_VIEWPORTS[index % REQUIRED_CLOAK_VIEWPORTS.length]!,
+ viewport: REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID[id],
consoleErrors: 0,
+ expectedFailedRequests: 0,
failedRequests: 0,
+ observedFailedRequests: 0,
browserVersion: "Chrome/140.0.0.0",
browserAuthority: "CLOAK_CDP_ENDPOINT",
telemetryValid: true,
@@ -70,17 +73,19 @@ describe("CloakBrowser qualification reporter", () => {
step("expect"),
step("test.step", [step("pw:api"), step("expect")]),
]),
- ).toBe(3);
+ ).toBe(2);
});
it("accepts exactly one privacy-safe in-memory page observation", () => {
const body = Buffer.from(
JSON.stringify({
- schemaVersion: "1",
+ schemaVersion: "2",
authority: "CLOAK_CDP_ENDPOINT",
viewport: { width: 1440, height: 900 },
consoleErrors: 0,
+ expectedFailedRequests: 0,
failedRequests: 0,
+ observedFailedRequests: 0,
expectedRequestFailuresMatched: true,
browserVersion: "Chrome/140.0.0.0",
}),
@@ -96,24 +101,62 @@ describe("CloakBrowser qualification reporter", () => {
} as TestResult;
expect(readJourneyObservation(result)).toEqual({
- schemaVersion: "1",
+ schemaVersion: "2",
authority: "CLOAK_CDP_ENDPOINT",
viewport: { width: 1440, height: 900 },
consoleErrors: 0,
+ expectedFailedRequests: 0,
failedRequests: 0,
+ observedFailedRequests: 0,
expectedRequestFailuresMatched: true,
browserVersion: "Chrome/140.0.0.0",
});
});
+ it("matches only the exact allowlisted intentional request failure", () => {
+ expect(
+ summarizeRequestFailures(
+ ["POST /api/artifacts"],
+ ["POST /api/artifacts"],
+ ),
+ ).toEqual({
+ expectedCount: 1,
+ matched: true,
+ observedCount: 1,
+ unexpectedCount: 0,
+ });
+ expect(
+ summarizeRequestFailures(["GET /unrelated.js"], ["POST /api/artifacts"]),
+ ).toEqual({
+ expectedCount: 1,
+ matched: false,
+ observedCount: 1,
+ unexpectedCount: 1,
+ });
+ expect(summarizeRequestFailures([], ["POST /api/live/sessions"])).toEqual({
+ expectedCount: 1,
+ matched: false,
+ observedCount: 0,
+ unexpectedCount: 0,
+ });
+ expect(
+ summarizeRequestFailures(
+ ["POST /api/artifacts"],
+ ["POST /not-allowlisted"],
+ ).matched,
+ ).toBe(false);
+ });
+
it("rejects missing, path-based, duplicate, and over-disclosed observations", () => {
const validBody = Buffer.from(
JSON.stringify({
- schemaVersion: "1",
+ schemaVersion: "2",
authority: "CLOAK_CDP_ENDPOINT",
viewport: { width: 1440, height: 900 },
consoleErrors: 0,
+ expectedFailedRequests: 0,
failedRequests: 0,
+ observedFailedRequests: 0,
expectedRequestFailuresMatched: true,
browserVersion: "Chrome/140.0.0.0",
}),
@@ -219,6 +262,13 @@ describe("CloakBrowser qualification reporter", () => {
...journeys.slice(1),
],
},
+ {
+ name: "inconsistent request failure counts",
+ mutate: (journeys: CapturedJourney[]) => [
+ { ...journeys[0]!, observedFailedRequests: 1 },
+ ...journeys.slice(1),
+ ],
+ },
{
name: "missing required viewport",
mutate: (journeys: CapturedJourney[]) =>
@@ -228,6 +278,21 @@ describe("CloakBrowser qualification reporter", () => {
: journey,
),
},
+ {
+ name: "viewport swapped between registered journeys",
+ mutate: (journeys: CapturedJourney[]) => {
+ const desktop = journeys.findIndex(
+ (journey) => journey.viewport === "1440x900",
+ );
+ const mobile = journeys.findIndex(
+ (journey) => journey.viewport === "390x844",
+ );
+ const mutated = [...journeys];
+ mutated[desktop] = { ...mutated[desktop]!, viewport: "390x844" };
+ mutated[mobile] = { ...mutated[mobile]!, viewport: "1440x900" };
+ return mutated;
+ },
+ },
])("fails closed for $name", ({ mutate }) => {
const report = buildCloakBrowserRawRun({
authority: "CLOAKBROWSER",
@@ -255,4 +320,18 @@ describe("CloakBrowser qualification reporter", () => {
expect(report.status).toBe("NON_QUALIFYING");
});
+
+ it("rejects raw evidence whose completion predates its start", () => {
+ expect(() =>
+ buildCloakBrowserRawRun({
+ authority: "CLOAKBROWSER",
+ baseUrl: "https://counterlab.cserules.workers.dev",
+ completedAt: "2026-07-21T09:59:59.000Z",
+ journeys: capturedJourneys(),
+ playwrightStatus: "passed",
+ qualificationRequested: true,
+ startedAt: "2026-07-21T10:00:00.000Z",
+ }),
+ ).toThrow(/completion must not precede/i);
+ });
});
diff --git a/scripts/submission-publication-evidence.ts b/scripts/submission-publication-evidence.ts
index 10cfebe5..216ccd9c 100644
--- a/scripts/submission-publication-evidence.ts
+++ b/scripts/submission-publication-evidence.ts
@@ -204,6 +204,47 @@ export const REQUIRED_CLOAK_JOURNEY_IDS = [
"recovery-and-intake::a lost private-session response retries without re-uploading",
] as const;
+export type RequiredCloakJourneyId =
+ (typeof REQUIRED_CLOAK_JOURNEY_IDS)[number];
+export type RequiredCloakViewport = (typeof REQUIRED_CLOAK_VIEWPORTS)[number];
+
+const cloakJourneyViewportOverrides: Partial<
+ Record
+> = {
+ "judged-flow::mobile Landing keeps the unprimed Question and fair-test promise in the first viewport":
+ "390x844",
+ "judged-flow::mobile Judge Mode shows the honest fixed-sample belief break in the first viewport":
+ "390x844",
+ "judged-flow::mobile sample remains unprimed before Prediction and mounts the trusted mechanism only after sealing":
+ "390x844",
+ "judged-flow::Judge Mode distinguishes every authority path": "390x844",
+ "judged-flow::the first visit explains the lesson before asking for technical knowledge":
+ "768x1024",
+ "judged-flow::wide desktop keeps the question and canonical progress accessible":
+ "1920x1080",
+ "judged-flow::compact desktop keeps the question and canonical progress accessible":
+ "1280x720",
+ "judged-flow::mobile keeps the question and canonical progress accessible":
+ "390x844",
+ "judged-flow::the lesson keeps one learner decision in focus at a time":
+ "1366x768",
+ "judged-flow::the judged path is keyboard operable with reduced motion":
+ "390x844",
+ "mobile-public-replay::public mobile replay routing::390x844 opens the replay deep link read-only without root overflow":
+ "390x844",
+ "mobile-public-replay::public mobile replay routing::375x812 preserves replay authority through browser back and forward":
+ "375x812",
+};
+
+export const REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID = Object.freeze(
+ Object.fromEntries(
+ REQUIRED_CLOAK_JOURNEY_IDS.map((id) => [
+ id,
+ cloakJourneyViewportOverrides[id] ?? "1440x900",
+ ]),
+ ) as Record,
+);
+
function addCloakJourneyIssues(
journeys: readonly z.infer[],
context: z.RefinementCtx,
@@ -266,7 +307,9 @@ export const CloakBrowserRawJourneySchema = z
assertionCount: z.number().int().nonnegative(),
viewport: z.string().regex(/^\d+x\d+$/u),
consoleErrors: z.number().int().nonnegative(),
+ expectedFailedRequests: z.number().int().nonnegative(),
failedRequests: z.number().int().nonnegative(),
+ observedFailedRequests: z.number().int().nonnegative(),
browserVersion: z.string().trim().min(1).max(128),
browserAuthority: z.enum([
"CLOAK_CDP_ENDPOINT",
@@ -279,7 +322,7 @@ export const CloakBrowserRawJourneySchema = z
export const CloakBrowserRawRunSchema = z
.object({
- schemaVersion: z.literal("1"),
+ schemaVersion: z.literal("2"),
kind: z.literal("cloakbrowser-raw-run"),
status: z.enum(["PASSED", "FAILED", "NON_QUALIFYING"]),
authority: z.enum(["CLOAKBROWSER", "STOCK_CHROMIUM_DESIGN_REVIEW"]),
@@ -295,7 +338,16 @@ export const CloakBrowserRawRunSchema = z
journeys: z.array(CloakBrowserRawJourneySchema).max(80),
privacy: PublicationPrivacySchema,
})
- .strict();
+ .strict()
+ .superRefine((run, context) => {
+ if (Date.parse(run.completedAt) < Date.parse(run.startedAt)) {
+ context.addIssue({
+ code: "custom",
+ path: ["completedAt"],
+ message: "raw run completion must not precede its start",
+ });
+ }
+ });
export const CloakBrowserExecutionReportSchema = z
.object({
From ba853d2da53a7fd769b75529be4ad271666ddf2b Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 15:26:58 +0530
Subject: [PATCH 097/143] docs(progress): record browser evidence gates
---
docs/PROGRESS.md | 54 +++++++++++++++++++
.../19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md | 25 +++++++++
2 files changed, 79 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 421978dd..5f7cf855 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4183,3 +4183,57 @@ evidence, contained Cloudflare authentication, active CloakBrowser
qualification, deployment, submission, and final merge. The proof and
live-session source-authority regressions are closed by reviewed commits, not
by production claims.
+
+## Browser qualification instrumentation checkpoint — 2026-07-21T09:56:03Z
+
+Deadline: `2026-07-22T00:00:00Z`; **14 hours 3 minutes 57 seconds remained at
+this checkpoint**. Committed HEAD is
+`a6f6d8d223a69d330f70966489234ee7a67a6fe2`, fourteen commits ahead of
+`origin/feat/learner-ux-v6.1`. The only worktree entry is the preserved,
+unstaged FUSE tombstone `scripts/.fuse_hidden0000a9600000801f`; it remains
+untouched.
+
+### Completed and committed
+
+| Commit | Result | Exact verification |
+| --- | --- | --- |
+| `3e75281 test(browser): cover release viewport matrix` | The 40-journey registry now exercises all seven required desktop, tablet, and mobile viewports. | E2E TypeScript passed; Playwright static collection reported exactly **40 tests in 3 files**. |
+| `3db3e52 test(browser): capture privacy-safe INP evidence` | Browser performance evidence now groups Event Timing entries by interaction, records no target/text/URL data, and fails closed after 10,000 interactions. | Focused performance tests **13/13 passed**; web and E2E TypeScript passed. |
+| `8a68bdd test(browser): open tablet navigation before audit` | The 768x1024 journey opens the intentionally collapsed Modes navigation before asserting secondary actions. | Stock-Chrome design-review smoke **1/1 passed**. |
+| `adea1c9 test(browser): emit fail-closed journey evidence` | A custom reporter records an exact, privacy-safe raw run and rejects wrong authority/origin, missing or duplicate IDs, skips, retries, zero assertions, telemetry gaps, browser errors, request failures, and incomplete viewport coverage. | Reporter tests **14/14 passed**; submission validator **34/34 passed**; web/E2E TypeScript, formatting, whitespace, and secret scan passed. |
+| `a6f6d8d test(browser): bind qualification telemetry` | Follow-up review binds intentional failures to exact method/path pairs, preserves observed/expected/unexpected counts, binds each journey to its primary viewport, rejects non-loopback CDP authority, excludes fixture assertions, checks chronology, and refuses symlink traversal or evidence overwrite. | Browser tests **24/24 passed**; submission validator **34/34 passed**; containment tests **3/3 passed**; web/E2E TypeScript and 40-test collection passed. |
+
+One real stock-Chrome reporter integration smoke passed **1/1** at 768x1024
+and emitted a schema-v2 `NON_QUALIFYING` receipt with 14 learner assertions,
+zero console errors, zero failed requests, and the explicit stock authority.
+It is design evidence only. Two intentional network-failure journeys were then
+executed against an incompletely initialized local D1 instance: **0/2 passed**
+because the `artifacts` table was absent. The reporter nevertheless recorded
+the first exact `POST /api/artifacts` abort and failed closed when the second
+expected `POST /api/live/sessions` abort never occurred. This is not counted as
+a product or browser-qualification pass.
+
+### Still open, in dependency order
+
+1. Finish and test the release-bound browser finalizer: raw-run hash binding,
+ 40 journey receipts, ten explicit manual evidence records, measured
+ LCP/CLS/INP, evidence index, final qualification receipt, and package
+ references.
+2. Resolve the FUSE handle without deleting or altering the tombstone; freeze
+ a clean source commit and run the real aggregate-containment sentinel.
+3. Build one exact image, regenerate all source/image evidence, and pass the
+ complete deterministic release gate without stale bindings.
+4. Authenticate the repository-contained Wrangler state and preflight a fresh
+ Cloak-enabled Codex session before any cloud mutation.
+5. Deploy only the qualified tuple, prove public identity, run all 40 public
+ Cloak journeys plus the ten manual/accessibility/performance checks, and
+ preserve exact evidence.
+6. Finalize repository, screenshots, video, feedback, public links, and
+ Devpost. Learner impact remains honestly `NO_DATA` until real consented
+ observations exist.
+7. Push the feature branch, capture the submission receipt, then fast-forward
+ and push `main` only after all upstream gates are factual.
+
+Release remains **NO-GO** at clean source, real containment, exact image,
+contained Wrangler authentication, qualifying CloakBrowser execution,
+deployment, publication, submission, and final merge.
diff --git a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
index b4a60976..f643e8ee 100644
--- a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
+++ b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
@@ -322,3 +322,28 @@ push and fast-forward merge.
to `main`?
Any `no`, unknown, or unavailable answer keeps the corresponding gate open.
+
+## Browser evidence graph addendum — `a6f6d8d`
+
+```mermaid
+flowchart LR
+ Registry["Exact 40 journey registry"] --> Fixture["Automatic Playwright fixture"]
+ Viewports["7 ID-bound viewports"] --> Fixture
+ Cloak["Loopback Cloak CDP authority"] --> Fixture
+ Fixture --> Raw["Schema-v2 raw run"]
+ INP["Privacy-safe measured INP"] --> Manual["10 explicit manual evidence inputs"]
+ Raw --> Finalizer["Release-bound finalizer — open"]
+ Manual --> Finalizer
+ Deploy["Exact deployment receipt — open"] --> Finalizer
+ Finalizer --> Receipts["40 journey receipts + report + index + qualification"]
+ Receipts --> PublicRun["Exact public Cloak execution — open"]
+ PublicRun --> Submission["Submission evidence — open"]
+```
+
+Committed source now fails closed on stock authority, non-loopback CDP,
+wrong public origin, ID or viewport drift, retries/skips, fixture-only
+assertions, telemetry mismatch, reversed chronology, unexpected browser
+failures, symlink traversal, and evidence overwrite. This closes the browser
+instrumentation source gaps identified at `7e25a09`; it does **not** close the
+finalizer, live Cloak execution, exact release, deployment, or submission
+nodes. Current qualifying journey count remains **0**.
From 0ee8934bbfbfff85c20a2b72748332354b134dbc Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 16:04:45 +0530
Subject: [PATCH 098/143] feat(release): finalize browser qualification
evidence
---
apps/web/e2e/cloak-test.ts | 5 +-
apps/web/e2e/qualification-reporter.ts | 47 +-
apps/web/playwright.config.ts | 77 ++-
.../src/test/qualificationReporter.test.ts | 50 +-
...inalize-cloakbrowser-qualification.test.ts | 404 ++++++++++++
.../finalize-cloakbrowser-qualification.ts | 590 ++++++++++++++++++
scripts/repository-cli-paths.test.ts | 25 +
scripts/repository-cli-paths.ts | 52 +-
scripts/submission-publication-evidence.ts | 286 ++++++++-
scripts/validate-submission-package.test.ts | 402 ++++++++----
scripts/validate-submission-package.ts | 54 +-
11 files changed, 1851 insertions(+), 141 deletions(-)
create mode 100644 scripts/finalize-cloakbrowser-qualification.test.ts
create mode 100644 scripts/finalize-cloakbrowser-qualification.ts
diff --git a/apps/web/e2e/cloak-test.ts b/apps/web/e2e/cloak-test.ts
index 15e0eaac..a571eb55 100644
--- a/apps/web/e2e/cloak-test.ts
+++ b/apps/web/e2e/cloak-test.ts
@@ -216,12 +216,15 @@ export const test = base.extend({
await testInfo.attach(JOURNEY_OBSERVATION_ATTACHMENT, {
body: Buffer.from(
JSON.stringify({
- schemaVersion: "2",
+ schemaVersion: "3",
authority: currentBrowserAuthorityLabel(),
viewport: viewport ?? { width: 0, height: 0 },
consoleErrors,
+ expectedRequestFailures,
expectedFailedRequests: requestFailureSummary.expectedCount,
failedRequests: requestFailureSummary.unexpectedCount,
+ observedRequestFailures:
+ requestFailureSummary.observedRequestFailures,
observedFailedRequests: requestFailureSummary.observedCount,
expectedRequestFailuresMatched: requestFailureSummary.matched,
browserVersion,
diff --git a/apps/web/e2e/qualification-reporter.ts b/apps/web/e2e/qualification-reporter.ts
index c30691d2..2179fa56 100644
--- a/apps/web/e2e/qualification-reporter.ts
+++ b/apps/web/e2e/qualification-reporter.ts
@@ -18,7 +18,10 @@ import type {
import { z } from "zod";
import {
+ ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES,
CloakBrowserRawRunSchema,
+ PublicationReleaseBindingSchema,
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID,
REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID,
REQUIRED_CLOAK_JOURNEY_IDS,
REQUIRED_CLOAK_VIEWPORTS,
@@ -30,7 +33,7 @@ export const JOURNEY_OBSERVATION_ATTACHMENT =
const PageObservationSchema = z
.object({
- schemaVersion: z.literal("2"),
+ schemaVersion: z.literal("3"),
authority: z.enum(["CLOAK_CDP_ENDPOINT", "stock-chromium-design-review"]),
viewport: z
.object({
@@ -39,8 +42,14 @@ const PageObservationSchema = z
})
.strict(),
consoleErrors: z.number().int().nonnegative(),
+ expectedRequestFailures: z
+ .array(z.enum(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES))
+ .max(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES.length),
expectedFailedRequests: z.number().int().nonnegative(),
failedRequests: z.number().int().nonnegative(),
+ observedRequestFailures: z
+ .array(z.enum(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES))
+ .max(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES.length),
observedFailedRequests: z.number().int().nonnegative(),
expectedRequestFailuresMatched: z.boolean(),
browserVersion: z.string().trim().min(1).max(128),
@@ -58,8 +67,10 @@ export interface CapturedJourney {
assertionCount: number;
viewport: string;
consoleErrors: number;
+ expectedRequestFailures: readonly (typeof ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES)[number][];
expectedFailedRequests: number;
failedRequests: number;
+ observedRequestFailures: readonly (typeof ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES)[number][];
observedFailedRequests: number;
browserVersion: string;
browserAuthority:
@@ -76,6 +87,7 @@ interface BuildRawRunInput {
journeys: readonly CapturedJourney[];
playwrightStatus: FullResult["status"];
qualificationRequested: boolean;
+ release: z.infer | null;
rootErrors?: number;
startedAt: string;
}
@@ -85,16 +97,16 @@ interface QualificationReporterOptions {
baseUrl: string;
outputFile: string;
qualificationRequested: boolean;
+ releaseBinding: z.infer | null;
repositoryRoot: string;
runtimeRoot: string;
}
const expectedProductionOrigin =
"https://counterlab.cserules.workers.dev" as const;
-const allowedExpectedRequestFailures = new Set([
- "POST /api/artifacts",
- "POST /api/live/sessions",
-]);
+const allowedExpectedRequestFailures: ReadonlySet = new Set(
+ ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES,
+);
export function summarizeRequestFailures(
observed: readonly string[],
@@ -103,6 +115,7 @@ export function summarizeRequestFailures(
expectedCount: number;
matched: boolean;
observedCount: number;
+ observedRequestFailures: readonly (typeof ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES)[number][];
unexpectedCount: number;
} {
const expectedValid =
@@ -120,6 +133,12 @@ export function summarizeRequestFailures(
observed.length === expected.length &&
remaining.length === 0,
observedCount: observed.length,
+ observedRequestFailures: observed.filter(
+ (
+ failure,
+ ): failure is (typeof ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES)[number] =>
+ allowedExpectedRequestFailures.has(failure),
+ ),
unexpectedCount: remaining.length,
};
}
@@ -223,6 +242,8 @@ export function buildCloakBrowserRawRun(input: BuildRawRunInput) {
input.qualificationRequested &&
input.authority === "CLOAKBROWSER" &&
input.baseUrl === expectedProductionOrigin &&
+ input.release !== null &&
+ input.release.productionOrigin === input.baseUrl &&
input.playwrightStatus === "passed" &&
(input.rootErrors ?? 0) === 0 &&
exactJourneyRegistry(input.journeys) &&
@@ -235,6 +256,16 @@ export function buildCloakBrowserRawRun(input: BuildRawRunInput) {
journey.durationMs > 0 &&
journey.assertionCount > 0 &&
journey.consoleErrors === 0 &&
+ JSON.stringify(journey.expectedRequestFailures) ===
+ JSON.stringify(
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[
+ journey.id as keyof typeof REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID
+ ],
+ ) &&
+ journey.expectedRequestFailures.length ===
+ journey.expectedFailedRequests &&
+ JSON.stringify(journey.observedRequestFailures) ===
+ JSON.stringify(journey.expectedRequestFailures) &&
journey.observedFailedRequests === journey.expectedFailedRequests &&
journey.failedRequests === 0 &&
journey.browserVersion.trim() !== "" &&
@@ -245,7 +276,7 @@ export function buildCloakBrowserRawRun(input: BuildRawRunInput) {
new Set(input.journeys.map((journey) => journey.browserVersion)).size === 1;
return CloakBrowserRawRunSchema.parse({
- schemaVersion: "2",
+ schemaVersion: "3",
kind: "cloakbrowser-raw-run",
status: input.qualificationRequested
? exactCleanRun
@@ -255,6 +286,7 @@ export function buildCloakBrowserRawRun(input: BuildRawRunInput) {
authority: input.authority,
qualificationRequested: input.qualificationRequested,
baseUrl: input.baseUrl,
+ release: input.release,
startedAt: input.startedAt,
completedAt: input.completedAt,
playwrightVersion: "1.61.1",
@@ -329,8 +361,10 @@ export default class CounterLabQualificationReporter implements Reporter {
? "0x0"
: `${observation.viewport.width}x${observation.viewport.height}`,
consoleErrors: observation?.consoleErrors ?? 1,
+ expectedRequestFailures: observation?.expectedRequestFailures ?? [],
expectedFailedRequests: observation?.expectedFailedRequests ?? 0,
failedRequests: observation?.failedRequests ?? 1,
+ observedRequestFailures: observation?.observedRequestFailures ?? [],
observedFailedRequests: observation?.observedFailedRequests ?? 1,
browserVersion: observation?.browserVersion ?? "unavailable",
browserAuthority: observation?.authority ?? "unavailable",
@@ -356,6 +390,7 @@ export default class CounterLabQualificationReporter implements Reporter {
journeys: this.capturedJourneys,
playwrightStatus: result.status,
qualificationRequested: this.options.qualificationRequested,
+ release: this.options.releaseBinding,
rootErrors: this.rootErrors,
startedAt: this.startedAt,
});
diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts
index 6de91e4b..2b62cfb4 100644
--- a/apps/web/playwright.config.ts
+++ b/apps/web/playwright.config.ts
@@ -1,10 +1,23 @@
import { defineConfig } from "@playwright/test";
-import { existsSync, lstatSync, realpathSync } from "node:fs";
+import { createHash } from "node:crypto";
+import {
+ closeSync,
+ constants,
+ existsSync,
+ fstatSync,
+ lstatSync,
+ openSync,
+ readFileSync,
+ realpathSync,
+} from "node:fs";
import { isAbsolute, join, relative, resolve, sep } from "node:path";
import { resolveBrowserAuthority } from "./e2e/browser-authority";
+import { DeploymentReceiptV7Schema } from "../../packages/scientific-engine-registry/src/index.js";
+import { PublicationReleaseBindingSchema } from "../../scripts/submission-publication-evidence.js";
const repositoryRoot = realpathSync(resolve(import.meta.dirname, "../.."));
+const maximumDeploymentReceiptBytes = 5 * 1_024 * 1_024;
const runtimeParent = resolve(import.meta.dirname, "test-results/runtime");
const configuredRuntimeRoot = process.env.COUNTERLAB_E2E_RUNTIME_ROOT;
if (configuredRuntimeRoot !== undefined && !isAbsolute(configuredRuntimeRoot)) {
@@ -118,6 +131,67 @@ if (
"CloakBrowser qualification requires the exact public CounterLab origin",
);
}
+const deploymentReceiptSetting = process.env.COUNTERLAB_E2E_DEPLOYMENT_RECEIPT;
+let qualificationReleaseBinding: ReturnType<
+ typeof PublicationReleaseBindingSchema.parse
+> | null = null;
+if (qualificationRequested) {
+ if (
+ deploymentReceiptSetting === undefined ||
+ deploymentReceiptSetting.trim() === ""
+ ) {
+ throw new Error(
+ "CloakBrowser qualification requires COUNTERLAB_E2E_DEPLOYMENT_RECEIPT",
+ );
+ }
+ const requestedReceipt = resolve(repositoryRoot, deploymentReceiptSetting);
+ if (!isContained(repositoryRoot, requestedReceipt, false)) {
+ throw new Error("CloakBrowser deployment receipt escaped the repository");
+ }
+ assertNoSymlinkTraversal(requestedReceipt, "CloakBrowser deployment receipt");
+ const physicalReceipt = realpathSync(requestedReceipt);
+ if (
+ !isContained(repositoryRoot, physicalReceipt, false) ||
+ !lstatSync(physicalReceipt).isFile()
+ ) {
+ throw new Error(
+ "CloakBrowser deployment receipt must be a physical repository file",
+ );
+ }
+ const receiptDescriptor = openSync(
+ physicalReceipt,
+ constants.O_RDONLY | constants.O_NOFOLLOW,
+ );
+ let deploymentBytes: Buffer;
+ try {
+ const metadata = fstatSync(receiptDescriptor);
+ if (!metadata.isFile() || metadata.size > maximumDeploymentReceiptBytes) {
+ throw new Error(
+ "CloakBrowser deployment receipt is not a bounded regular file",
+ );
+ }
+ deploymentBytes = readFileSync(receiptDescriptor);
+ } finally {
+ closeSync(receiptDescriptor);
+ }
+ const deployment = DeploymentReceiptV7Schema.parse(
+ JSON.parse(deploymentBytes.toString("utf8")) as unknown,
+ );
+ qualificationReleaseBinding = PublicationReleaseBindingSchema.parse({
+ deploymentReceiptSha256: createHash("sha256")
+ .update(deploymentBytes)
+ .digest("hex"),
+ productionOrigin: deployment.productionOrigin,
+ workerEvidenceCommit: deployment.workerEvidenceCommit,
+ runnerSourceCommit: deployment.runnerSourceCommit,
+ containerImageDigest: deployment.containerImageDigest,
+ workerVersionId: deployment.workerVersionId,
+ });
+} else if (deploymentReceiptSetting !== undefined) {
+ throw new Error(
+ "COUNTERLAB_E2E_DEPLOYMENT_RECEIPT is allowed only for qualification",
+ );
+}
export default defineConfig({
testDir: "./e2e",
@@ -139,6 +213,7 @@ export default defineConfig({
baseUrl: baseURL,
outputFile: qualificationRunFile,
qualificationRequested,
+ releaseBinding: qualificationReleaseBinding,
repositoryRoot,
runtimeRoot,
},
diff --git a/apps/web/src/test/qualificationReporter.test.ts b/apps/web/src/test/qualificationReporter.test.ts
index 06f28119..ffaa4794 100644
--- a/apps/web/src/test/qualificationReporter.test.ts
+++ b/apps/web/src/test/qualificationReporter.test.ts
@@ -10,11 +10,21 @@ import {
type CapturedJourney,
} from "../../e2e/qualification-reporter";
import {
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID,
REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID,
REQUIRED_CLOAK_JOURNEY_IDS,
REQUIRED_CLOAK_VIEWPORTS,
} from "../../../../scripts/submission-publication-evidence";
+const qualificationRelease = {
+ deploymentReceiptSha256: "a".repeat(64),
+ productionOrigin: "https://counterlab.cserules.workers.dev",
+ workerEvidenceCommit: "b".repeat(40),
+ runnerSourceCommit: "c".repeat(40),
+ containerImageDigest: `sha256:${"d".repeat(64)}`,
+ workerVersionId: "12345678-1234-1234-1234-123456789abc",
+} as const;
+
function reporterTestCase(
file: string,
titlePath: readonly string[],
@@ -39,9 +49,13 @@ function capturedJourneys(): CapturedJourney[] {
assertionCount: 1,
viewport: REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID[id],
consoleErrors: 0,
- expectedFailedRequests: 0,
+ expectedRequestFailures: REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
+ expectedFailedRequests:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
failedRequests: 0,
- observedFailedRequests: 0,
+ observedRequestFailures: REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
+ observedFailedRequests:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
browserVersion: "Chrome/140.0.0.0",
browserAuthority: "CLOAK_CDP_ENDPOINT",
telemetryValid: true,
@@ -79,12 +93,14 @@ describe("CloakBrowser qualification reporter", () => {
it("accepts exactly one privacy-safe in-memory page observation", () => {
const body = Buffer.from(
JSON.stringify({
- schemaVersion: "2",
+ schemaVersion: "3",
authority: "CLOAK_CDP_ENDPOINT",
viewport: { width: 1440, height: 900 },
consoleErrors: 0,
+ expectedRequestFailures: [],
expectedFailedRequests: 0,
failedRequests: 0,
+ observedRequestFailures: [],
observedFailedRequests: 0,
expectedRequestFailuresMatched: true,
browserVersion: "Chrome/140.0.0.0",
@@ -101,12 +117,14 @@ describe("CloakBrowser qualification reporter", () => {
} as TestResult;
expect(readJourneyObservation(result)).toEqual({
- schemaVersion: "2",
+ schemaVersion: "3",
authority: "CLOAK_CDP_ENDPOINT",
viewport: { width: 1440, height: 900 },
consoleErrors: 0,
+ expectedRequestFailures: [],
expectedFailedRequests: 0,
failedRequests: 0,
+ observedRequestFailures: [],
observedFailedRequests: 0,
expectedRequestFailuresMatched: true,
browserVersion: "Chrome/140.0.0.0",
@@ -123,6 +141,7 @@ describe("CloakBrowser qualification reporter", () => {
expectedCount: 1,
matched: true,
observedCount: 1,
+ observedRequestFailures: ["POST /api/artifacts"],
unexpectedCount: 0,
});
expect(
@@ -131,12 +150,14 @@ describe("CloakBrowser qualification reporter", () => {
expectedCount: 1,
matched: false,
observedCount: 1,
+ observedRequestFailures: [],
unexpectedCount: 1,
});
expect(summarizeRequestFailures([], ["POST /api/live/sessions"])).toEqual({
expectedCount: 1,
matched: false,
observedCount: 0,
+ observedRequestFailures: [],
unexpectedCount: 0,
});
expect(
@@ -150,12 +171,14 @@ describe("CloakBrowser qualification reporter", () => {
it("rejects missing, path-based, duplicate, and over-disclosed observations", () => {
const validBody = Buffer.from(
JSON.stringify({
- schemaVersion: "2",
+ schemaVersion: "3",
authority: "CLOAK_CDP_ENDPOINT",
viewport: { width: 1440, height: 900 },
consoleErrors: 0,
+ expectedRequestFailures: [],
expectedFailedRequests: 0,
failedRequests: 0,
+ observedRequestFailures: [],
observedFailedRequests: 0,
expectedRequestFailuresMatched: true,
browserVersion: "Chrome/140.0.0.0",
@@ -205,6 +228,7 @@ describe("CloakBrowser qualification reporter", () => {
journeys: capturedJourneys(),
playwrightStatus: "passed",
qualificationRequested: true,
+ release: qualificationRelease,
startedAt: "2026-07-21T10:00:00.000Z",
});
@@ -269,6 +293,19 @@ describe("CloakBrowser qualification reporter", () => {
...journeys.slice(1),
],
},
+ {
+ name: "request failure operation assigned to the wrong journey",
+ mutate: (journeys: CapturedJourney[]) => [
+ {
+ ...journeys[0]!,
+ expectedRequestFailures: ["POST /api/artifacts"] as const,
+ expectedFailedRequests: 1,
+ observedRequestFailures: ["POST /api/artifacts"] as const,
+ observedFailedRequests: 1,
+ },
+ ...journeys.slice(1),
+ ],
+ },
{
name: "missing required viewport",
mutate: (journeys: CapturedJourney[]) =>
@@ -301,6 +338,7 @@ describe("CloakBrowser qualification reporter", () => {
journeys: mutate(capturedJourneys()),
playwrightStatus: "passed",
qualificationRequested: true,
+ release: qualificationRelease,
startedAt: "2026-07-21T10:00:00.000Z",
});
@@ -315,6 +353,7 @@ describe("CloakBrowser qualification reporter", () => {
journeys: capturedJourneys(),
playwrightStatus: "passed",
qualificationRequested: false,
+ release: null,
startedAt: "2026-07-21T10:00:00.000Z",
});
@@ -330,6 +369,7 @@ describe("CloakBrowser qualification reporter", () => {
journeys: capturedJourneys(),
playwrightStatus: "passed",
qualificationRequested: true,
+ release: qualificationRelease,
startedAt: "2026-07-21T10:00:00.000Z",
}),
).toThrow(/completion must not precede/i);
diff --git a/scripts/finalize-cloakbrowser-qualification.test.ts b/scripts/finalize-cloakbrowser-qualification.test.ts
new file mode 100644
index 00000000..1af6a26e
--- /dev/null
+++ b/scripts/finalize-cloakbrowser-qualification.test.ts
@@ -0,0 +1,404 @@
+import { createHash } from "node:crypto";
+import { lstat, mkdir, readFile } from "node:fs/promises";
+import { resolve } from "node:path";
+
+import { describe, expect, it } from "vitest";
+
+import { canonicalJson } from "../packages/session-core/src/index.js";
+
+import {
+ CloakBrowserExecutionReportSchema,
+ CloakBrowserQualificationReceiptSchema,
+ CloakManualEvidenceReceiptSchema,
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID,
+ REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID,
+ REQUIRED_CLOAK_JOURNEY_IDS,
+ REQUIRED_CLOAK_MANUAL_CRITERIA,
+ REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS,
+} from "./submission-publication-evidence.js";
+import {
+ buildCloakBrowserQualification,
+ CloakManualObservationManifestSchema,
+ writeQualificationFiles,
+} from "./finalize-cloakbrowser-qualification.js";
+
+const deployedAt = "2026-07-21T10:00:00.000Z";
+const startedAt = "2026-07-21T10:01:00.000Z";
+const completedAt = "2026-07-21T10:05:00.000Z";
+const checkedAt = "2026-07-21T10:06:00.000Z";
+const release = {
+ deploymentReceiptSha256: "a".repeat(64),
+ productionOrigin: "https://counterlab.cserules.workers.dev",
+ workerEvidenceCommit: "b".repeat(40),
+ runnerSourceCommit: "c".repeat(40),
+ containerImageDigest: `sha256:${"d".repeat(64)}`,
+ workerVersionId: "12345678-1234-1234-1234-123456789abc",
+} as const;
+const browserVersion = "CloakBrowser Chromium 150.0.0.0";
+
+function canonicalSha256(value: unknown): string {
+ return createHash("sha256").update(canonicalJson(value)).digest("hex");
+}
+
+function byteSha256(value: Uint8Array): string {
+ return createHash("sha256").update(value).digest("hex");
+}
+
+async function writerFixture(label: string) {
+ const repositoryRoot = resolve(
+ process.cwd(),
+ "node_modules/.cache/counterlab-v6.1",
+ `qualification-writer-${label}-${Date.now()}-${process.pid}`,
+ );
+ await mkdir(resolve(repositoryRoot, "docs"), {
+ mode: 0o700,
+ recursive: true,
+ });
+ const deploymentReceiptBytes = new TextEncoder().encode(
+ `${JSON.stringify({ fixture: label })}\n`,
+ );
+ const outputRoot = resolve(
+ repositoryRoot,
+ "docs/submission-evidence",
+ byteSha256(deploymentReceiptBytes),
+ );
+ const writerRelease = {
+ ...release,
+ deploymentReceiptSha256: byteSha256(deploymentReceiptBytes),
+ };
+ const rawRun = { ...validRawRun(), release: writerRelease };
+ const rawRunCanonicalSha256 = canonicalSha256(rawRun);
+ const manualManifest = validManualManifest();
+ manualManifest.release = writerRelease;
+ manualManifest.rawRunCanonicalSha256 = rawRunCanonicalSha256;
+ manualManifest.observations = manualManifest.observations.map(
+ (observation) => ({
+ ...observation,
+ release: writerRelease,
+ rawRunCanonicalSha256,
+ }),
+ );
+ const artifacts = buildCloakBrowserQualification({
+ checkedAt,
+ deployedAt,
+ manualManifest,
+ rawRun,
+ release: writerRelease,
+ });
+ return { artifacts, deploymentReceiptBytes, outputRoot, repositoryRoot };
+}
+
+async function pathExists(path: string): Promise {
+ try {
+ await lstat(path);
+ return true;
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return false;
+ throw error;
+ }
+}
+
+function validRawRun() {
+ return {
+ schemaVersion: "3",
+ kind: "cloakbrowser-raw-run",
+ status: "PASSED",
+ authority: "CLOAKBROWSER",
+ qualificationRequested: true,
+ baseUrl: "https://counterlab.cserules.workers.dev",
+ release,
+ startedAt,
+ completedAt,
+ playwrightVersion: "1.61.1",
+ playwrightStatus: "passed",
+ rootErrors: 0,
+ journeys: REQUIRED_CLOAK_JOURNEY_IDS.map((id, index) => ({
+ id,
+ status: "passed",
+ expectedStatus: "passed",
+ attempt: 0,
+ durationMs: 1_000 + index,
+ assertionCount: 2,
+ viewport: REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID[id],
+ consoleErrors: 0,
+ expectedRequestFailures:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
+ expectedFailedRequests:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
+ failedRequests: 0,
+ observedRequestFailures:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
+ observedFailedRequests:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
+ browserVersion,
+ browserAuthority: "CLOAK_CDP_ENDPOINT",
+ telemetryValid: true,
+ })),
+ privacy: {
+ containsSecrets: false,
+ containsRawNotebook: false,
+ containsPersonalData: false,
+ },
+ };
+}
+
+function validManualManifest() {
+ const rawRunCanonicalSha256 = canonicalSha256(validRawRun());
+ return {
+ schemaVersion: "1",
+ kind: "cloakbrowser-manual-observation-manifest",
+ authority: "HUMAN_OBSERVATION",
+ browserAuthority: "CLOAKBROWSER",
+ baseUrl: "https://counterlab.cserules.workers.dev",
+ release,
+ rawRunCanonicalSha256,
+ browserVersion,
+ observations: REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS.map((check) => ({
+ schemaVersion: "1",
+ kind: "cloakbrowser-manual-observation",
+ status: "OBSERVED_PASS",
+ authority: "HUMAN_OBSERVATION",
+ browserAuthority: "CLOAKBROWSER",
+ check,
+ baseUrl: "https://counterlab.cserules.workers.dev",
+ release,
+ rawRunCanonicalSha256,
+ browserVersion,
+ observedAt: "2026-07-21T10:05:30.000Z",
+ observationCount: REQUIRED_CLOAK_MANUAL_CRITERIA[check].length,
+ criteria: REQUIRED_CLOAK_MANUAL_CRITERIA[check].map((id) => ({
+ status: "PASSED",
+ observationCount: 1,
+ id,
+ })),
+ webVitals:
+ check === "webVitals"
+ ? { status: "measured", lcpMs: 1_200, cls: 0.02, inpMs: 90 }
+ : null,
+ privacy: {
+ containsSecrets: false,
+ containsRawNotebook: false,
+ containsPersonalData: false,
+ },
+ })),
+ privacy: {
+ containsSecrets: false,
+ containsRawNotebook: false,
+ containsPersonalData: false,
+ },
+ };
+}
+
+function parseGenerated(
+ built: ReturnType,
+ path: string,
+): unknown {
+ const file = built.files.find((entry) => entry.relativePath === path);
+ expect(file, path).toBeDefined();
+ return JSON.parse(new TextDecoder().decode(file!.bytes));
+}
+
+describe("CloakBrowser qualification finalizer", () => {
+ it("builds the exact deterministic 53-file browser evidence chain", () => {
+ const input = {
+ checkedAt,
+ deployedAt,
+ manualManifest: validManualManifest(),
+ rawRun: validRawRun(),
+ release,
+ };
+ const first = buildCloakBrowserQualification(input);
+ const second = buildCloakBrowserQualification(input);
+
+ expect(first.files).toHaveLength(53);
+ expect(first.files.map((file) => file.relativePath)).toEqual(
+ second.files.map((file) => file.relativePath),
+ );
+ expect(first.files.map((file) => file.sha256)).toEqual(
+ second.files.map((file) => file.sha256),
+ );
+ expect(first.files[0]?.relativePath).toBe("cloakbrowser-journeys/01.json");
+ expect(first.files[39]?.relativePath).toBe("cloakbrowser-journeys/40.json");
+ expect(first.files.at(-1)?.relativePath).toBe(
+ "cloakbrowser-qualification.json",
+ );
+ expect(
+ CloakBrowserExecutionReportSchema.parse(
+ parseGenerated(first, "cloakbrowser-playwright-report.json"),
+ ).rawRun.status,
+ ).toBe("PASSED");
+ expect(
+ CloakBrowserQualificationReceiptSchema.parse(
+ parseGenerated(first, "cloakbrowser-qualification.json"),
+ ).webVitals,
+ ).toEqual({ lcpMs: 1_200, cls: 0.02, inpMs: 90 });
+ expect(
+ CloakManualEvidenceReceiptSchema.parse(
+ parseGenerated(first, "cloakbrowser-manual/webVitals.json"),
+ ).artifact.webVitals?.status,
+ ).toBe("measured");
+ });
+
+ it("rejects a missing or reordered manual observation", () => {
+ const missing = validManualManifest();
+ missing.observations = missing.observations.slice(1);
+ expect(() => CloakManualObservationManifestSchema.parse(missing)).toThrow();
+
+ const reordered = validManualManifest();
+ reordered.observations = [
+ reordered.observations[1]!,
+ reordered.observations[0]!,
+ ...reordered.observations.slice(2),
+ ];
+ expect(() => CloakManualObservationManifestSchema.parse(reordered)).toThrow(
+ /ordered registry/i,
+ );
+ });
+
+ it("rejects unmeasured or over-budget INP", () => {
+ const manifest = validManualManifest();
+ const webVitals = manifest.observations.find(
+ (observation) => observation.check === "webVitals",
+ )!;
+ webVitals.webVitals = {
+ status: "measured",
+ lcpMs: 1_200,
+ cls: 0.02,
+ inpMs: 201,
+ };
+ expect(() =>
+ buildCloakBrowserQualification({
+ checkedAt,
+ deployedAt,
+ manualManifest: manifest,
+ rawRun: validRawRun(),
+ release,
+ }),
+ ).toThrow();
+ });
+
+ it("rejects swapped journey viewports and dishonest PASSED status", () => {
+ const raw = validRawRun();
+ const firstDesktop = raw.journeys.findIndex(
+ (journey) => journey.viewport === "1440x900",
+ );
+ const firstMobile = raw.journeys.findIndex(
+ (journey) => journey.viewport === "390x844",
+ );
+ raw.journeys[firstDesktop]!.viewport = "390x844";
+ raw.journeys[firstMobile]!.viewport = "1440x900";
+ expect(() =>
+ buildCloakBrowserQualification({
+ checkedAt,
+ deployedAt,
+ manualManifest: validManualManifest(),
+ rawRun: raw,
+ release,
+ }),
+ ).toThrow(/status does not match/i);
+ });
+
+ it("rejects observations before deployment or after finalization", () => {
+ expect(() =>
+ buildCloakBrowserQualification({
+ checkedAt: "2026-07-21T10:04:59.000Z",
+ deployedAt,
+ manualManifest: validManualManifest(),
+ rawRun: validRawRun(),
+ release,
+ }),
+ ).toThrow(/follow deployment and precede finalization/i);
+
+ const manual = validManualManifest();
+ manual.observations[0]!.observedAt = "2026-07-21T09:59:59.000Z";
+ expect(() =>
+ buildCloakBrowserQualification({
+ checkedAt,
+ deployedAt,
+ manualManifest: manual,
+ rawRun: validRawRun(),
+ release,
+ }),
+ ).toThrow(/follow deployment and precede finalization/i);
+ });
+
+ it("detects tampering in embedded raw and manual evidence", () => {
+ const built = buildCloakBrowserQualification({
+ checkedAt,
+ deployedAt,
+ manualManifest: validManualManifest(),
+ rawRun: validRawRun(),
+ release,
+ });
+ const report = parseGenerated(
+ built,
+ "cloakbrowser-playwright-report.json",
+ ) as Record;
+ report.rawRunCanonicalSha256 = "e".repeat(64);
+ expect(() => CloakBrowserExecutionReportSchema.parse(report)).toThrow(
+ /hash does not match/i,
+ );
+
+ const manual = parseGenerated(
+ built,
+ "cloakbrowser-manual/keyboard.json",
+ ) as Record;
+ manual.artifactSha256 = "f".repeat(64);
+ expect(() => CloakManualEvidenceReceiptSchema.parse(manual)).toThrow(
+ /hash does not match/i,
+ );
+ });
+
+ it("publishes a complete release once and refuses replacement", async () => {
+ const fixture = await writerFixture("complete");
+ await writeQualificationFiles(fixture);
+
+ await expect(
+ readFile(resolve(fixture.outputRoot, "deployment-receipt.json")),
+ ).resolves.toEqual(Buffer.from(fixture.deploymentReceiptBytes));
+ for (const artifact of fixture.artifacts.files) {
+ await expect(
+ readFile(resolve(fixture.outputRoot, artifact.relativePath)),
+ ).resolves.toEqual(Buffer.from(artifact.bytes));
+ }
+ await expect(
+ readFile(
+ resolve(fixture.outputRoot, ".publication-reservation.json"),
+ "utf8",
+ ),
+ ).resolves.toContain(byteSha256(fixture.deploymentReceiptBytes));
+ await expect(writeQualificationFiles(fixture)).rejects.toThrow(
+ /refusing to replace/i,
+ );
+ });
+
+ it("rejects a wrong output root before publishing", async () => {
+ const fixture = await writerFixture("wrong-root");
+ await expect(
+ writeQualificationFiles({
+ ...fixture,
+ outputRoot: resolve(
+ fixture.repositoryRoot,
+ "docs/submission-evidence/wrong",
+ ),
+ }),
+ ).rejects.toThrow(/deployment-receipt hash directory/i);
+ await expect(pathExists(fixture.outputRoot)).resolves.toBe(false);
+ });
+
+ it("leaves the final root absent when staged bytes fail verification", async () => {
+ const fixture = await writerFixture("bad-hash");
+ const [first, ...rest] = fixture.artifacts.files;
+ if (first === undefined) throw new Error("missing generated fixture");
+ await expect(
+ writeQualificationFiles({
+ ...fixture,
+ artifacts: {
+ ...fixture.artifacts,
+ files: [{ ...first, sha256: "f".repeat(64) }, ...rest],
+ },
+ }),
+ ).rejects.toThrow(/failed its hash check/i);
+ await expect(pathExists(fixture.outputRoot)).resolves.toBe(false);
+ });
+});
diff --git a/scripts/finalize-cloakbrowser-qualification.ts b/scripts/finalize-cloakbrowser-qualification.ts
new file mode 100644
index 00000000..e5ac7355
--- /dev/null
+++ b/scripts/finalize-cloakbrowser-qualification.ts
@@ -0,0 +1,590 @@
+import { createHash, randomUUID } from "node:crypto";
+import { constants } from "node:fs";
+import { lstat, open, readFile, rename, writeFile } from "node:fs/promises";
+import { dirname, relative, resolve, sep } from "node:path";
+import { fileURLToPath } from "node:url";
+
+import { z } from "zod";
+
+import { canonicalJson } from "../packages/session-core/src/index.js";
+import { DeploymentReceiptV7Schema } from "../packages/scientific-engine-registry/src/index.js";
+import {
+ CloakBrowserEvidenceIndexSchema,
+ CloakBrowserExecutionReportSchema,
+ CloakBrowserQualificationReceiptSchema,
+ CloakBrowserRawRunSchema,
+ CloakJourneyEvidenceReceiptSchema,
+ CloakManualEvidenceReceiptSchema,
+ CloakManualObservationArtifactSchema,
+ PublicationReleaseBindingSchema,
+ REQUIRED_CLOAK_JOURNEY_IDS,
+ REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS,
+ REQUIRED_CLOAK_VIEWPORTS,
+} from "./submission-publication-evidence.js";
+import {
+ containedDirectory,
+ containedInputFile,
+ containedNewOutputDirectory,
+ containedNewOutputFile,
+ parseStrictNameValueArgs,
+} from "./repository-cli-paths.js";
+
+const CheckedAtSchema = z.iso.datetime({ offset: true });
+const MAX_INPUT_BYTES = 5 * 1_024 * 1_024;
+
+const privacy = {
+ containsSecrets: false,
+ containsRawNotebook: false,
+ containsPersonalData: false,
+} as const;
+const PrivacySchema = z
+ .object({
+ containsSecrets: z.literal(false),
+ containsRawNotebook: z.literal(false),
+ containsPersonalData: z.literal(false),
+ })
+ .strict();
+
+export const CloakManualObservationManifestSchema = z
+ .object({
+ schemaVersion: z.literal("1"),
+ kind: z.literal("cloakbrowser-manual-observation-manifest"),
+ authority: z.literal("HUMAN_OBSERVATION"),
+ browserAuthority: z.literal("CLOAKBROWSER"),
+ baseUrl: z.literal("https://counterlab.cserules.workers.dev"),
+ release: PublicationReleaseBindingSchema,
+ rawRunCanonicalSha256: z.string().regex(/^[a-f0-9]{64}$/u),
+ browserVersion: z.string().trim().min(1).max(128),
+ observations: z
+ .array(CloakManualObservationArtifactSchema)
+ .length(REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS.length),
+ privacy: PrivacySchema,
+ })
+ .strict()
+ .superRefine((manifest, context) => {
+ if (
+ JSON.stringify(manifest.observations.map((entry) => entry.check)) !==
+ JSON.stringify(REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["observations"],
+ message: "manual observations must match the exact ordered registry",
+ });
+ }
+ for (const [index, observation] of manifest.observations.entries()) {
+ if (
+ observation.baseUrl !== manifest.baseUrl ||
+ canonicalJson(observation.release) !==
+ canonicalJson(manifest.release) ||
+ observation.rawRunCanonicalSha256 !== manifest.rawRunCanonicalSha256 ||
+ observation.browserVersion !== manifest.browserVersion
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["observations", index],
+ message: "manual observation does not match its run binding",
+ });
+ }
+ }
+ });
+
+export interface GeneratedQualificationFile {
+ relativePath: string;
+ bytes: Uint8Array;
+ sha256: string;
+}
+
+export interface BuiltCloakBrowserQualification {
+ files: readonly GeneratedQualificationFile[];
+ qualification: z.infer;
+}
+
+function sha256(value: Uint8Array | string): string {
+ return createHash("sha256").update(value).digest("hex");
+}
+
+function jsonBytes(value: unknown): Uint8Array {
+ return new TextEncoder().encode(`${JSON.stringify(value, null, 2)}\n`);
+}
+
+function generatedFile(
+ relativePath: string,
+ value: unknown,
+): GeneratedQualificationFile {
+ const bytes = jsonBytes(value);
+ return { relativePath, bytes, sha256: sha256(bytes) };
+}
+
+function assertTimestampOrder(input: {
+ checkedAt: string;
+ deployedAt: string;
+ rawStartedAt: string;
+ rawCompletedAt: string;
+ manualObservedAt: readonly string[];
+}): void {
+ const checkedAt = Date.parse(input.checkedAt);
+ const deployedAt = Date.parse(input.deployedAt);
+ const rawStartedAt = Date.parse(input.rawStartedAt);
+ const rawCompletedAt = Date.parse(input.rawCompletedAt);
+ if (
+ rawStartedAt < deployedAt ||
+ rawCompletedAt < rawStartedAt ||
+ rawCompletedAt > checkedAt ||
+ input.manualObservedAt.some((observedAt) => {
+ const observed = Date.parse(observedAt);
+ return observed < deployedAt || observed > checkedAt;
+ })
+ ) {
+ throw new Error(
+ "CloakBrowser observations must follow deployment and precede finalization",
+ );
+ }
+}
+
+export function buildCloakBrowserQualification(input: {
+ checkedAt: string;
+ deployedAt: string;
+ manualManifest: unknown;
+ rawRun: unknown;
+ release: unknown;
+}): BuiltCloakBrowserQualification {
+ const checkedAt = CheckedAtSchema.parse(input.checkedAt);
+ const release = PublicationReleaseBindingSchema.parse(input.release);
+ const rawRun = CloakBrowserRawRunSchema.parse(input.rawRun);
+ const manualManifest = CloakManualObservationManifestSchema.parse(
+ input.manualManifest,
+ );
+ if (rawRun.status !== "PASSED") {
+ throw new Error("Only an independently valid PASSED raw run can qualify");
+ }
+ if (canonicalJson(rawRun.release) !== canonicalJson(release)) {
+ throw new Error(
+ "Raw browser evidence does not match the deployment release",
+ );
+ }
+ const rawRunCanonicalSha256 = sha256(canonicalJson(rawRun));
+ const browserVersion = rawRun.journeys[0]!.browserVersion;
+ if (
+ manualManifest.baseUrl !== rawRun.baseUrl ||
+ canonicalJson(manualManifest.release) !== canonicalJson(release) ||
+ manualManifest.rawRunCanonicalSha256 !== rawRunCanonicalSha256 ||
+ manualManifest.browserVersion !== browserVersion
+ ) {
+ throw new Error(
+ "Manual browser observations do not match the exact raw release run",
+ );
+ }
+ assertTimestampOrder({
+ checkedAt,
+ deployedAt: CheckedAtSchema.parse(input.deployedAt),
+ rawStartedAt: rawRun.startedAt,
+ rawCompletedAt: rawRun.completedAt,
+ manualObservedAt: manualManifest.observations.map(
+ (observation) => observation.observedAt,
+ ),
+ });
+
+ const receiptBase = {
+ schemaVersion: "1",
+ status: "VERIFIED",
+ checkedAt,
+ release,
+ privacy,
+ } as const;
+ const files: GeneratedQualificationFile[] = [];
+ const journeys = rawRun.journeys.map((raw, index) => {
+ const receipt = CloakJourneyEvidenceReceiptSchema.parse({
+ ...receiptBase,
+ kind: "cloakbrowser-journey-evidence",
+ authority: "CLOAKBROWSER",
+ id: raw.id,
+ viewport: raw.viewport,
+ journeyStatus: "PASSED",
+ attempt: 0,
+ durationMs: raw.durationMs,
+ assertionCount: raw.assertionCount,
+ consoleErrors: 0,
+ failedRequests: 0,
+ });
+ const file = generatedFile(
+ `cloakbrowser-journeys/${String(index + 1).padStart(2, "0")}.json`,
+ receipt,
+ );
+ files.push(file);
+ return {
+ id: raw.id,
+ status: "PASSED" as const,
+ attempt: 0 as const,
+ durationMs: raw.durationMs,
+ viewport: raw.viewport,
+ evidenceSha256: file.sha256,
+ };
+ });
+
+ const executionReport = CloakBrowserExecutionReportSchema.parse({
+ schemaVersion: "2",
+ kind: "cloakbrowser-execution-report",
+ status: "PASSED",
+ checkedAt,
+ authority: "CLOAKBROWSER",
+ baseUrl: "https://counterlab.cserules.workers.dev",
+ release,
+ privacy,
+ browserVersion,
+ playwrightVersion: "1.61.1",
+ rawRunCanonicalSha256,
+ rawRun,
+ journeys,
+ failures: 0,
+ skips: 0,
+ retries: 0,
+ consoleErrors: 0,
+ failedRequests: 0,
+ });
+ const executionReportFile = generatedFile(
+ "cloakbrowser-playwright-report.json",
+ executionReport,
+ );
+ files.push(executionReportFile);
+
+ const manualReferences: Record<
+ (typeof REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS)[number],
+ { status: "PASSED"; evidenceSha256: string }
+ > = Object.create(null) as Record<
+ (typeof REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS)[number],
+ { status: "PASSED"; evidenceSha256: string }
+ >;
+ for (const observation of manualManifest.observations) {
+ const receipt = CloakManualEvidenceReceiptSchema.parse({
+ ...receiptBase,
+ schemaVersion: "2",
+ kind: "cloakbrowser-manual-evidence",
+ authority: "HUMAN_OBSERVATION",
+ browserAuthority: "CLOAKBROWSER",
+ check: observation.check,
+ observationCount: observation.observationCount,
+ artifactSha256: sha256(canonicalJson(observation)),
+ artifact: observation,
+ });
+ const file = generatedFile(
+ `cloakbrowser-manual/${observation.check}.json`,
+ receipt,
+ );
+ files.push(file);
+ manualReferences[observation.check] = {
+ status: "PASSED",
+ evidenceSha256: file.sha256,
+ };
+ }
+
+ const evidenceIndex = CloakBrowserEvidenceIndexSchema.parse({
+ ...receiptBase,
+ kind: "cloakbrowser-evidence-index",
+ executionReportSha256: executionReportFile.sha256,
+ manual: manualReferences,
+ });
+ const evidenceIndexFile = generatedFile(
+ "cloakbrowser-evidence-index.json",
+ evidenceIndex,
+ );
+ files.push(evidenceIndexFile);
+
+ const webVitals = manualManifest.observations.find(
+ (observation) => observation.check === "webVitals",
+ )!.webVitals!;
+ const qualification = CloakBrowserQualificationReceiptSchema.parse({
+ ...receiptBase,
+ kind: "cloakbrowser-qualification",
+ authority: "CLOAKBROWSER",
+ baseUrl: "https://counterlab.cserules.workers.dev",
+ exactReleaseBound: true,
+ journeyCount: REQUIRED_CLOAK_JOURNEY_IDS.length,
+ viewports: REQUIRED_CLOAK_VIEWPORTS,
+ browserVersion,
+ playwrightVersion: "1.61.1",
+ playwrightReportSha256: executionReportFile.sha256,
+ browserEvidenceIndexSha256: evidenceIndexFile.sha256,
+ journeys,
+ desktopComplete: true,
+ mobileComplete: true,
+ keyboardComplete: true,
+ screenReaderNamesComplete: true,
+ reducedMotionComplete: true,
+ noHorizontalOverflow: true,
+ zoom200Complete: true,
+ longContentComplete: true,
+ narrowVisualizationsComplete: true,
+ touchTargetsComplete: true,
+ requiredSkips: 0,
+ failures: 0,
+ consoleErrors: 0,
+ failedRequests: 0,
+ webVitals: {
+ lcpMs: webVitals.lcpMs,
+ cls: webVitals.cls,
+ inpMs: webVitals.inpMs,
+ },
+ });
+ files.push(generatedFile("cloakbrowser-qualification.json", qualification));
+ return { files, qualification };
+}
+
+async function boundedJsonFile(
+ repositoryRoot: string,
+ requested: string,
+ label: string,
+): Promise<{ bytes: Uint8Array; value: unknown }> {
+ const path = await containedInputFile(repositoryRoot, requested, label);
+ const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
+ try {
+ const metadata = await handle.stat();
+ if (!metadata.isFile() || metadata.size > MAX_INPUT_BYTES) {
+ throw new Error(
+ `${label} exceeds the bounded input size or is not a file`,
+ );
+ }
+ const bytes = await handle.readFile();
+ try {
+ return {
+ bytes,
+ value: JSON.parse(
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes),
+ ),
+ };
+ } catch {
+ throw new Error(`${label} is not valid UTF-8 JSON`);
+ }
+ } finally {
+ await handle.close();
+ }
+}
+
+async function ensureEvidenceParent(repositoryRoot: string): Promise {
+ const parent = resolve(repositoryRoot, "docs/submission-evidence");
+ try {
+ return await containedDirectory(
+ repositoryRoot,
+ parent,
+ "submission evidence parent",
+ );
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
+ return containedNewOutputDirectory(
+ repositoryRoot,
+ parent,
+ "submission evidence parent",
+ );
+ }
+}
+
+async function ensureContainedDirectory(
+ repositoryRoot: string,
+ directory: string,
+ label: string,
+): Promise {
+ try {
+ return await containedDirectory(repositoryRoot, directory, label);
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
+ return containedNewOutputDirectory(repositoryRoot, directory, label);
+ }
+}
+
+async function assertPathAbsent(path: string, label: string): Promise {
+ try {
+ await lstat(path);
+ throw new Error(`${label} already exists; refusing to replace it`);
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
+ }
+}
+
+export async function writeQualificationFiles(input: {
+ repositoryRoot: string;
+ outputRoot: string;
+ deploymentReceiptBytes: Uint8Array;
+ artifacts: BuiltCloakBrowserQualification;
+}): Promise {
+ const deploymentReceiptSha256 = sha256(input.deploymentReceiptBytes);
+ if (
+ input.artifacts.qualification.release.deploymentReceiptSha256 !==
+ deploymentReceiptSha256
+ ) {
+ throw new Error(
+ "qualification artifacts do not match the deployment receipt bytes",
+ );
+ }
+ const evidenceParent = await ensureEvidenceParent(input.repositoryRoot);
+ const expectedRoot = resolve(evidenceParent, deploymentReceiptSha256);
+ if (resolve(input.outputRoot) !== expectedRoot) {
+ throw new Error(
+ "qualification output must use the deployment-receipt hash directory",
+ );
+ }
+ await assertPathAbsent(expectedRoot, "CloakBrowser qualification output");
+ const counterlabState = await ensureContainedDirectory(
+ input.repositoryRoot,
+ resolve(input.repositoryRoot, ".counterlab"),
+ "CounterLab state directory",
+ );
+ const stagingParent = await ensureContainedDirectory(
+ input.repositoryRoot,
+ resolve(counterlabState, "qualification-staging"),
+ "CloakBrowser qualification staging parent",
+ );
+ const reservationParent = await ensureContainedDirectory(
+ input.repositoryRoot,
+ resolve(counterlabState, "qualification-reservations"),
+ "CloakBrowser qualification reservation parent",
+ );
+ const evidenceDevice = (await lstat(evidenceParent)).dev;
+ if (
+ (await lstat(stagingParent)).dev !== evidenceDevice ||
+ (await lstat(reservationParent)).dev !== evidenceDevice
+ ) {
+ throw new Error(
+ "qualification staging and reservation must share the evidence filesystem",
+ );
+ }
+ const stagingRoot = await containedNewOutputDirectory(
+ input.repositoryRoot,
+ resolve(
+ stagingParent,
+ `.pending-${deploymentReceiptSha256.slice(0, 16)}-${randomUUID()}`,
+ ),
+ "CloakBrowser qualification staging output",
+ );
+ await containedNewOutputDirectory(
+ input.repositoryRoot,
+ resolve(stagingRoot, "cloakbrowser-journeys"),
+ "CloakBrowser journey evidence directory",
+ );
+ await containedNewOutputDirectory(
+ input.repositoryRoot,
+ resolve(stagingRoot, "cloakbrowser-manual"),
+ "CloakBrowser manual evidence directory",
+ );
+ const deploymentOutput = await containedNewOutputFile(
+ input.repositoryRoot,
+ resolve(stagingRoot, "deployment-receipt.json"),
+ "deployment receipt copy",
+ );
+ await writeFile(deploymentOutput, input.deploymentReceiptBytes, {
+ flag: "wx",
+ mode: 0o600,
+ });
+ for (const artifact of input.artifacts.files) {
+ const destination = resolve(stagingRoot, artifact.relativePath);
+ const fromRoot = relative(stagingRoot, destination);
+ if (
+ fromRoot === "" ||
+ fromRoot === ".." ||
+ fromRoot.startsWith(`..${sep}`)
+ ) {
+ throw new Error("generated qualification path escaped its output root");
+ }
+ const output = await containedNewOutputFile(
+ input.repositoryRoot,
+ destination,
+ `generated qualification file ${artifact.relativePath}`,
+ );
+ await writeFile(output, artifact.bytes, { flag: "wx", mode: 0o600 });
+ }
+ if (
+ sha256(await readFile(deploymentOutput)) !==
+ sha256(input.deploymentReceiptBytes)
+ ) {
+ throw new Error("staged deployment receipt failed its hash check");
+ }
+ for (const artifact of input.artifacts.files) {
+ const staged = resolve(stagingRoot, artifact.relativePath);
+ if (sha256(await readFile(staged)) !== artifact.sha256) {
+ throw new Error(
+ `staged qualification file failed its hash check: ${artifact.relativePath}`,
+ );
+ }
+ }
+ const reservation = await containedNewOutputFile(
+ input.repositoryRoot,
+ resolve(reservationParent, `${deploymentReceiptSha256}.json`),
+ "CloakBrowser qualification publication reservation",
+ );
+ await writeFile(
+ reservation,
+ `${JSON.stringify({
+ schemaVersion: "1",
+ kind: "cloakbrowser-qualification-publication-reservation",
+ deploymentReceiptSha256,
+ })}\n`,
+ { encoding: "utf8", flag: "wx", mode: 0o600 },
+ );
+ await assertPathAbsent(expectedRoot, "CloakBrowser qualification output");
+ await rename(stagingRoot, expectedRoot);
+ const publishedReservation = await containedNewOutputFile(
+ input.repositoryRoot,
+ resolve(expectedRoot, ".publication-reservation.json"),
+ "published CloakBrowser qualification reservation",
+ );
+ await rename(reservation, publishedReservation);
+}
+
+async function main(): Promise {
+ const repositoryRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
+ const args = parseStrictNameValueArgs(
+ process.argv.slice(2),
+ new Set([
+ "--raw-run",
+ "--manual-manifest",
+ "--deployment-receipt",
+ "--output-root",
+ ]),
+ );
+ if (args.size !== 4)
+ throw new Error("qualification arguments are incomplete");
+ const raw = await boundedJsonFile(
+ repositoryRoot,
+ args.get("--raw-run")!,
+ "CloakBrowser raw run",
+ );
+ const manual = await boundedJsonFile(
+ repositoryRoot,
+ args.get("--manual-manifest")!,
+ "CloakBrowser manual observation manifest",
+ );
+ const deployment = await boundedJsonFile(
+ repositoryRoot,
+ args.get("--deployment-receipt")!,
+ "deployment receipt",
+ );
+ const parsedDeployment = DeploymentReceiptV7Schema.parse(deployment.value);
+ const deploymentReceiptSha256 = sha256(deployment.bytes);
+ const release = PublicationReleaseBindingSchema.parse({
+ deploymentReceiptSha256,
+ productionOrigin: parsedDeployment.productionOrigin,
+ workerEvidenceCommit: parsedDeployment.workerEvidenceCommit,
+ runnerSourceCommit: parsedDeployment.runnerSourceCommit,
+ containerImageDigest: parsedDeployment.containerImageDigest,
+ workerVersionId: parsedDeployment.workerVersionId,
+ });
+ const artifacts = buildCloakBrowserQualification({
+ checkedAt: new Date().toISOString(),
+ deployedAt: parsedDeployment.deployedAt,
+ manualManifest: manual.value,
+ rawRun: raw.value,
+ release,
+ });
+ await writeQualificationFiles({
+ repositoryRoot,
+ outputRoot: resolve(repositoryRoot, args.get("--output-root")!),
+ deploymentReceiptBytes: deployment.bytes,
+ artifacts,
+ });
+ process.stdout.write(
+ `${release.deploymentReceiptSha256} ${artifacts.files.length}\n`,
+ );
+}
+
+if (resolve(process.argv[1] ?? "") === fileURLToPath(import.meta.url)) {
+ await main();
+}
diff --git a/scripts/repository-cli-paths.test.ts b/scripts/repository-cli-paths.test.ts
index a4b05c45..d62b9ba7 100644
--- a/scripts/repository-cli-paths.test.ts
+++ b/scripts/repository-cli-paths.test.ts
@@ -4,7 +4,9 @@ import { resolve } from "node:path";
import { beforeAll, describe, expect, it } from "vitest";
import {
+ containedDirectory,
containedInputFile,
+ containedNewOutputDirectory,
containedNewOutputFile,
parseStrictNameValueArgs,
} from "./repository-cli-paths.js";
@@ -75,4 +77,27 @@ describe("repository-contained release CLI paths", () => {
),
).rejects.toThrow(/contains a symlink/u);
});
+
+ it("creates only a new physical directory beneath the repository", async () => {
+ const output = resolve(stage, `new-directory-${Date.now()}`);
+ await expect(
+ containedNewOutputDirectory(root, output, "test directory"),
+ ).resolves.toBe(output);
+ await expect(
+ containedNewOutputDirectory(root, output, "test directory"),
+ ).rejects.toThrow(/already exists/u);
+ await expect(
+ containedDirectory(root, output, "test directory"),
+ ).resolves.toBe(output);
+ await expect(
+ containedNewOutputDirectory(root, "../outside", "test directory"),
+ ).rejects.toThrow(/escapes the repository/u);
+ await expect(
+ containedNewOutputDirectory(
+ root,
+ resolve(stage, "symlink-parent", "new-directory"),
+ "test directory",
+ ),
+ ).rejects.toThrow(/contains a symlink/u);
+ });
});
diff --git a/scripts/repository-cli-paths.ts b/scripts/repository-cli-paths.ts
index e3f309c9..22fe90a3 100644
--- a/scripts/repository-cli-paths.ts
+++ b/scripts/repository-cli-paths.ts
@@ -1,4 +1,4 @@
-import { lstat, realpath } from "node:fs/promises";
+import { lstat, mkdir, realpath } from "node:fs/promises";
import { isAbsolute, relative, resolve, sep } from "node:path";
function isContained(root: string, candidate: string): boolean {
@@ -66,6 +66,24 @@ export async function containedInputFile(
return physical;
}
+export async function containedDirectory(
+ root: string,
+ requested: string,
+ label: string,
+): Promise {
+ const candidate = resolve(root, requested);
+ if (!isContained(root, candidate) || candidate === root) {
+ throw new Error(`${label} escapes the repository`);
+ }
+ await assertNoSymlinkComponents(root, candidate, label);
+ const physical = await realpath(candidate);
+ const metadata = await lstat(physical);
+ if (!isContained(root, physical) || !metadata.isDirectory()) {
+ throw new Error(`${label} is not a repository directory`);
+ }
+ return physical;
+}
+
export async function containedNewOutputFile(
root: string,
requested: string,
@@ -90,3 +108,35 @@ export async function containedNewOutputFile(
}
return candidate;
}
+
+export async function containedNewOutputDirectory(
+ root: string,
+ requested: string,
+ label: string,
+): Promise {
+ const candidate = resolve(root, requested);
+ if (!isContained(root, candidate) || candidate === root) {
+ throw new Error(`${label} escapes the repository`);
+ }
+ const parent = resolve(candidate, "..");
+ await assertNoSymlinkComponents(root, parent, `${label} parent`);
+ const physicalParent = await realpath(parent);
+ const parentMetadata = await lstat(physicalParent);
+ if (!isContained(root, physicalParent) || !parentMetadata.isDirectory()) {
+ throw new Error(`${label} parent is not a repository directory`);
+ }
+ try {
+ await lstat(candidate);
+ throw new Error(`${label} already exists; refusing to replace it`);
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
+ }
+ await mkdir(candidate, { mode: 0o700 });
+ await assertNoSymlinkComponents(root, candidate, label);
+ const physical = await realpath(candidate);
+ const metadata = await lstat(physical);
+ if (!isContained(root, physical) || !metadata.isDirectory()) {
+ throw new Error(`${label} is not a repository directory`);
+ }
+ return physical;
+}
diff --git a/scripts/submission-publication-evidence.ts b/scripts/submission-publication-evidence.ts
index 216ccd9c..a3ede958 100644
--- a/scripts/submission-publication-evidence.ts
+++ b/scripts/submission-publication-evidence.ts
@@ -1,7 +1,10 @@
+import { createHash } from "node:crypto";
import { inflateSync } from "node:zlib";
import { z } from "zod";
+import { canonicalJson } from "../packages/session-core/src/index.js";
+
const Sha256Schema = z.string().regex(/^[a-f0-9]{64}$/u);
const GitCommitSchema = z.string().regex(/^[a-f0-9]{40}$/u);
const ImageDigestSchema = z.string().regex(/^sha256:[a-f0-9]{64}$/u);
@@ -28,6 +31,10 @@ const PublicationPrivacySchema = z
})
.strict();
+function canonicalSha256(value: unknown): string {
+ return createHash("sha256").update(canonicalJson(value)).digest("hex");
+}
+
const baseReceiptShape = {
schemaVersion: z.literal("1"),
status: z.literal("VERIFIED"),
@@ -140,16 +147,156 @@ export const REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS = [
"webVitals",
] as const;
+export const REQUIRED_CLOAK_MANUAL_CRITERIA = {
+ keyboard: [
+ "keyboardJourneyCompleted",
+ "visibleFocusObserved",
+ "focusRestored",
+ ],
+ screenReaderNames: ["landmarksNamed", "controlsNamed", "asyncStatesNamed"],
+ reducedMotion: ["reducedMotionApplied", "motionParityObserved"],
+ noHorizontalOverflow: [
+ "allRequiredViewportsChecked",
+ "documentOverflowAbsent",
+ "bodyOverflowAbsent",
+ ],
+ zoom200: ["zoomApplied", "contentOperable", "noClipping"],
+ longContent: ["longContentInjected", "contentReadable", "controlsReachable"],
+ narrowVisualizations: [
+ "visualFitsViewport",
+ "exactTableAvailable",
+ "nonColorMeaning",
+ ],
+ touchTargets: ["interactiveTargetsMeasured", "minimum44Px"],
+ consoleAndNetwork: [
+ "consoleErrorsZero",
+ "pageErrorsZero",
+ "unexpectedRequestsZero",
+ ],
+ webVitals: ["lcpMeasured", "clsMeasured", "inpMeasured"],
+} as const satisfies Record<
+ (typeof REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS)[number],
+ readonly string[]
+>;
+
+const ManualCriterionSchema = z
+ .object({
+ id: z.string().regex(/^[a-z][a-zA-Z0-9.-]{0,63}$/u),
+ status: z.literal("PASSED"),
+ observationCount: z.number().int().positive(),
+ })
+ .strict();
+
+export const CloakManualObservationArtifactSchema = z
+ .object({
+ schemaVersion: z.literal("1"),
+ kind: z.literal("cloakbrowser-manual-observation"),
+ status: z.literal("OBSERVED_PASS"),
+ authority: z.literal("HUMAN_OBSERVATION"),
+ browserAuthority: z.literal("CLOAKBROWSER"),
+ check: z.enum(REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS),
+ baseUrl: z.literal("https://counterlab.cserules.workers.dev"),
+ release: PublicationReleaseBindingSchema,
+ rawRunCanonicalSha256: Sha256Schema,
+ browserVersion: z.string().trim().min(1).max(128),
+ observedAt: z.iso.datetime({ offset: true }),
+ observationCount: z.number().int().positive(),
+ criteria: z.array(ManualCriterionSchema).min(1).max(64),
+ webVitals: z
+ .object({
+ status: z.literal("measured"),
+ lcpMs: z.number().nonnegative().max(2_500),
+ cls: z.number().nonnegative().max(0.1),
+ inpMs: z.number().nonnegative().max(200),
+ })
+ .strict()
+ .nullable(),
+ privacy: PublicationPrivacySchema,
+ })
+ .strict()
+ .superRefine((artifact, context) => {
+ const criterionIds = artifact.criteria.map((criterion) => criterion.id);
+ if (new Set(criterionIds).size !== criterionIds.length) {
+ context.addIssue({
+ code: "custom",
+ path: ["criteria"],
+ message: "manual observation criteria must be unique",
+ });
+ }
+ if (
+ JSON.stringify(criterionIds) !==
+ JSON.stringify(REQUIRED_CLOAK_MANUAL_CRITERIA[artifact.check])
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["criteria"],
+ message: "manual observation criteria do not match the required check",
+ });
+ }
+ if (
+ artifact.observationCount !==
+ artifact.criteria.reduce(
+ (total, criterion) => total + criterion.observationCount,
+ 0,
+ )
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["observationCount"],
+ message: "manual observation count must equal its criteria total",
+ });
+ }
+ if ((artifact.check === "webVitals") !== (artifact.webVitals !== null)) {
+ context.addIssue({
+ code: "custom",
+ path: ["webVitals"],
+ message: "measured Web Vitals belong only to the webVitals check",
+ });
+ }
+ });
+
export const CloakManualEvidenceReceiptSchema = z
.object({
...baseReceiptShape,
+ schemaVersion: z.literal("2"),
kind: z.literal("cloakbrowser-manual-evidence"),
- authority: z.literal("CLOAKBROWSER"),
+ authority: z.literal("HUMAN_OBSERVATION"),
+ browserAuthority: z.literal("CLOAKBROWSER"),
check: z.enum(REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS),
observationCount: z.number().int().positive(),
artifactSha256: Sha256Schema,
+ artifact: CloakManualObservationArtifactSchema,
})
- .strict();
+ .strict()
+ .superRefine((receipt, context) => {
+ if (
+ receipt.artifact.check !== receipt.check ||
+ receipt.artifact.observationCount !== receipt.observationCount ||
+ canonicalJson(receipt.artifact.release) !== canonicalJson(receipt.release)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["artifact"],
+ message: "manual observation identity does not match its receipt",
+ });
+ }
+ if (canonicalSha256(receipt.artifact) !== receipt.artifactSha256) {
+ context.addIssue({
+ code: "custom",
+ path: ["artifactSha256"],
+ message: "manual observation hash does not match its artifact",
+ });
+ }
+ if (
+ Date.parse(receipt.artifact.observedAt) > Date.parse(receipt.checkedAt)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["checkedAt"],
+ message: "manual observation must precede receipt issuance",
+ });
+ }
+ });
export const REQUIRED_CLOAK_VIEWPORTS = [
"375x812",
@@ -207,6 +354,12 @@ export const REQUIRED_CLOAK_JOURNEY_IDS = [
export type RequiredCloakJourneyId =
(typeof REQUIRED_CLOAK_JOURNEY_IDS)[number];
export type RequiredCloakViewport = (typeof REQUIRED_CLOAK_VIEWPORTS)[number];
+export const ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES = [
+ "POST /api/artifacts",
+ "POST /api/live/sessions",
+] as const;
+export type AllowedCloakExpectedRequestFailure =
+ (typeof ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES)[number];
const cloakJourneyViewportOverrides: Partial<
Record
@@ -245,6 +398,28 @@ export const REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID = Object.freeze(
) as Record,
);
+const cloakExpectedRequestFailureOverrides: Partial<
+ Record
+> = {
+ "recovery-and-intake::an interrupted upload accepts the same file on retry": [
+ "POST /api/artifacts",
+ ],
+ "recovery-and-intake::a lost private-session response retries without re-uploading":
+ ["POST /api/live/sessions"],
+};
+
+export const REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID = Object.freeze(
+ Object.fromEntries(
+ REQUIRED_CLOAK_JOURNEY_IDS.map((id) => [
+ id,
+ Object.freeze([...(cloakExpectedRequestFailureOverrides[id] ?? [])]),
+ ]),
+ ) as Record<
+ RequiredCloakJourneyId,
+ readonly AllowedCloakExpectedRequestFailure[]
+ >,
+);
+
function addCloakJourneyIssues(
journeys: readonly z.infer[],
context: z.RefinementCtx,
@@ -307,8 +482,14 @@ export const CloakBrowserRawJourneySchema = z
assertionCount: z.number().int().nonnegative(),
viewport: z.string().regex(/^\d+x\d+$/u),
consoleErrors: z.number().int().nonnegative(),
+ expectedRequestFailures: z
+ .array(z.enum(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES))
+ .max(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES.length),
expectedFailedRequests: z.number().int().nonnegative(),
failedRequests: z.number().int().nonnegative(),
+ observedRequestFailures: z
+ .array(z.enum(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES))
+ .max(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES.length),
observedFailedRequests: z.number().int().nonnegative(),
browserVersion: z.string().trim().min(1).max(128),
browserAuthority: z.enum([
@@ -322,12 +503,13 @@ export const CloakBrowserRawJourneySchema = z
export const CloakBrowserRawRunSchema = z
.object({
- schemaVersion: z.literal("2"),
+ schemaVersion: z.literal("3"),
kind: z.literal("cloakbrowser-raw-run"),
status: z.enum(["PASSED", "FAILED", "NON_QUALIFYING"]),
authority: z.enum(["CLOAKBROWSER", "STOCK_CHROMIUM_DESIGN_REVIEW"]),
qualificationRequested: z.boolean(),
baseUrl: z.string().trim().min(1).max(2_048),
+ release: PublicationReleaseBindingSchema.nullable(),
startedAt: z.iso.datetime({ offset: true }),
completedAt: z.iso.datetime({ offset: true }),
playwrightVersion: z.literal("1.61.1"),
@@ -347,11 +529,65 @@ export const CloakBrowserRawRunSchema = z
message: "raw run completion must not precede its start",
});
}
+ const identifiers = run.journeys.map((journey) => journey.id);
+ const exactRegistry =
+ JSON.stringify(identifiers) ===
+ JSON.stringify(REQUIRED_CLOAK_JOURNEY_IDS);
+ const exactCleanRun =
+ run.authority === "CLOAKBROWSER" &&
+ run.qualificationRequested &&
+ run.baseUrl === "https://counterlab.cserules.workers.dev" &&
+ run.release !== null &&
+ run.release.productionOrigin === run.baseUrl &&
+ run.playwrightStatus === "passed" &&
+ run.rootErrors === 0 &&
+ exactRegistry &&
+ run.journeys.every(
+ (journey) =>
+ journey.status === "passed" &&
+ journey.expectedStatus === "passed" &&
+ journey.attempt === 0 &&
+ journey.durationMs > 0 &&
+ journey.assertionCount > 0 &&
+ journey.consoleErrors === 0 &&
+ journey.expectedFailedRequests ===
+ journey.expectedRequestFailures.length &&
+ JSON.stringify(journey.expectedRequestFailures) ===
+ JSON.stringify(
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[
+ journey.id as RequiredCloakJourneyId
+ ],
+ ) &&
+ journey.failedRequests === 0 &&
+ JSON.stringify(journey.observedRequestFailures) ===
+ JSON.stringify(journey.expectedRequestFailures) &&
+ journey.observedFailedRequests === journey.expectedFailedRequests &&
+ journey.browserAuthority === "CLOAK_CDP_ENDPOINT" &&
+ journey.browserVersion !== "unavailable" &&
+ journey.telemetryValid &&
+ REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID[
+ journey.id as RequiredCloakJourneyId
+ ] === journey.viewport,
+ ) &&
+ new Set(run.journeys.map((journey) => journey.browserVersion)).size === 1;
+ const expectedStatus = run.qualificationRequested
+ ? exactCleanRun
+ ? "PASSED"
+ : "FAILED"
+ : "NON_QUALIFYING";
+ if (run.status !== expectedStatus) {
+ context.addIssue({
+ code: "custom",
+ path: ["status"],
+ message:
+ "raw run status does not match its independently checked evidence",
+ });
+ }
});
export const CloakBrowserExecutionReportSchema = z
.object({
- schemaVersion: z.literal("1"),
+ schemaVersion: z.literal("2"),
kind: z.literal("cloakbrowser-execution-report"),
status: z.literal("PASSED"),
checkedAt: z.iso.datetime({ offset: true }),
@@ -361,6 +597,8 @@ export const CloakBrowserExecutionReportSchema = z
privacy: PublicationPrivacySchema,
browserVersion: z.string().trim().min(1).max(128),
playwrightVersion: z.literal("1.61.1"),
+ rawRunCanonicalSha256: Sha256Schema,
+ rawRun: CloakBrowserRawRunSchema,
journeys: z.array(CloakJourneySchema).length(40),
failures: z.literal(0),
skips: z.literal(0),
@@ -371,6 +609,46 @@ export const CloakBrowserExecutionReportSchema = z
.strict()
.superRefine((report, context) => {
addCloakJourneyIssues(report.journeys, context);
+ if (canonicalSha256(report.rawRun) !== report.rawRunCanonicalSha256) {
+ context.addIssue({
+ code: "custom",
+ path: ["rawRunCanonicalSha256"],
+ message: "raw run hash does not match its embedded evidence",
+ });
+ }
+ if (
+ report.rawRun.status !== "PASSED" ||
+ report.rawRun.authority !== "CLOAKBROWSER" ||
+ !report.rawRun.qualificationRequested ||
+ report.rawRun.baseUrl !== report.baseUrl ||
+ canonicalJson(report.rawRun.release) !== canonicalJson(report.release) ||
+ report.rawRun.playwrightVersion !== report.playwrightVersion ||
+ report.rawRun.journeys[0]?.browserVersion !== report.browserVersion ||
+ Date.parse(report.rawRun.completedAt) > Date.parse(report.checkedAt)
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["rawRun"],
+ message: "raw run does not match its execution report",
+ });
+ }
+ for (const [index, journey] of report.journeys.entries()) {
+ const raw = report.rawRun.journeys[index];
+ if (
+ raw === undefined ||
+ raw.id !== journey.id ||
+ raw.status !== "passed" ||
+ raw.attempt !== journey.attempt ||
+ raw.durationMs !== journey.durationMs ||
+ raw.viewport !== journey.viewport
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["journeys", index],
+ message: "journey does not match its embedded raw run",
+ });
+ }
+ }
});
const ManualEvidenceSchema = z
diff --git a/scripts/validate-submission-package.test.ts b/scripts/validate-submission-package.test.ts
index 8c2cdda0..45a37f96 100644
--- a/scripts/validate-submission-package.test.ts
+++ b/scripts/validate-submission-package.test.ts
@@ -9,9 +9,13 @@ import { RELEASE_CHECK_IDS } from "../packages/scientific-engine-registry/src/sc
import { canonicalJson } from "../packages/session-core/src/index.js";
import { createGenerationIsolationEvidence } from "./generation-isolation-evidence.js";
import {
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID,
+ REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID,
REQUIRED_CLOAK_JOURNEY_IDS,
+ REQUIRED_CLOAK_MANUAL_CRITERIA,
REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS,
} from "./submission-publication-evidence.js";
+import { buildCloakBrowserQualification } from "./finalize-cloakbrowser-qualification.js";
import {
SubmissionPackageSchema,
type EvidenceReader,
@@ -524,135 +528,113 @@ async function readyPackage(
contentCaptured: false,
}),
);
- const browserViewports = [
- "375x812",
- "390x844",
- "768x1024",
- "1280x720",
- "1366x768",
- "1440x900",
- "1920x1080",
- ] as const;
const browserVersion = "CloakBrowser Chromium 140.0.0.0";
- const journeyEvidence = REQUIRED_CLOAK_JOURNEY_IDS.map((id, index) => {
- const viewport = browserViewports[index % browserViewports.length]!;
- const durationMs = 1_000 + index;
- const sequence = String(index + 1).padStart(2, "0");
- const evidenceReference = addEvidence(
- `${evidenceRoot}/cloakbrowser-journeys/${sequence}.json`,
- bytes({
- ...receiptBase,
- kind: "cloakbrowser-journey-evidence",
- authority: "CLOAKBROWSER",
- id,
- viewport,
- journeyStatus: "PASSED",
- attempt: 0,
- durationMs,
- assertionCount: 3,
- consoleErrors: 0,
- failedRequests: 0,
- }),
- );
- return {
+ const rawRun = {
+ schemaVersion: "3",
+ kind: "cloakbrowser-raw-run",
+ status: "PASSED",
+ authority: "CLOAKBROWSER",
+ qualificationRequested: true,
+ baseUrl: receipt.productionOrigin,
+ release: publicationRelease,
+ startedAt: "2026-07-21T09:56:00.000Z",
+ completedAt: "2026-07-21T09:58:00.000Z",
+ playwrightVersion: "1.61.1",
+ playwrightStatus: "passed",
+ rootErrors: 0,
+ journeys: REQUIRED_CLOAK_JOURNEY_IDS.map((id, index) => ({
id,
- evidence: evidenceReference,
- journey: {
- id,
- status: "PASSED",
- attempt: 0,
- durationMs,
- viewport,
- evidenceSha256: evidenceReference.sha256,
- },
- };
- });
- const journeys = journeyEvidence.map((entry) => entry.journey);
- const browserReport = addEvidence(
- `${evidenceRoot}/cloakbrowser-playwright-report.json`,
- bytes({
+ status: "passed",
+ expectedStatus: "passed",
+ attempt: 0,
+ durationMs: 1_000 + index,
+ assertionCount: 3,
+ viewport: REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID[id],
+ consoleErrors: 0,
+ expectedRequestFailures:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
+ expectedFailedRequests:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
+ failedRequests: 0,
+ observedRequestFailures:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
+ observedFailedRequests:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
+ browserVersion,
+ browserAuthority: "CLOAK_CDP_ENDPOINT",
+ telemetryValid: true,
+ })),
+ privacy: publicationPrivacy,
+ } as const;
+ const rawRunCanonicalSha256 = hash(bytes(canonicalJson(rawRun)));
+ const browserBuild = buildCloakBrowserQualification({
+ checkedAt,
+ deployedAt: receipt.deployedAt,
+ release: publicationRelease,
+ rawRun,
+ manualManifest: {
schemaVersion: "1",
- kind: "cloakbrowser-execution-report",
- status: "PASSED",
- checkedAt,
- authority: "CLOAKBROWSER",
+ kind: "cloakbrowser-manual-observation-manifest",
+ authority: "HUMAN_OBSERVATION",
+ browserAuthority: "CLOAKBROWSER",
baseUrl: receipt.productionOrigin,
release: publicationRelease,
- privacy: publicationPrivacy,
+ rawRunCanonicalSha256,
browserVersion,
- playwrightVersion: "1.61.1",
- journeys,
- failures: 0,
- skips: 0,
- retries: 0,
- consoleErrors: 0,
- failedRequests: 0,
- }),
- );
- const manualEvidenceEntries = REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS.map(
- (check) => {
- const evidenceReference = addEvidence(
- `${evidenceRoot}/cloakbrowser-manual/${check}.json`,
- bytes({
- ...receiptBase,
- kind: "cloakbrowser-manual-evidence",
- authority: "CLOAKBROWSER",
- check,
- observationCount: 3,
- artifactSha256: hash(bytes(`manual-artifact-${check}`)),
- }),
- );
- return [check, evidenceReference] as const;
+ observations: REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS.map((check) => ({
+ schemaVersion: "1",
+ kind: "cloakbrowser-manual-observation",
+ status: "OBSERVED_PASS",
+ authority: "HUMAN_OBSERVATION",
+ browserAuthority: "CLOAKBROWSER",
+ check,
+ baseUrl: receipt.productionOrigin,
+ release: publicationRelease,
+ rawRunCanonicalSha256,
+ browserVersion,
+ observedAt: "2026-07-21T09:59:00.000Z",
+ observationCount: REQUIRED_CLOAK_MANUAL_CRITERIA[check].length,
+ criteria: REQUIRED_CLOAK_MANUAL_CRITERIA[check].map((id) => ({
+ id,
+ status: "PASSED",
+ observationCount: 1,
+ })),
+ webVitals:
+ check === "webVitals"
+ ? { status: "measured", lcpMs: 1_200, cls: 0.02, inpMs: 90 }
+ : null,
+ privacy: publicationPrivacy,
+ })),
+ privacy: publicationPrivacy,
},
+ });
+ const browserFileReferences = new Map(
+ browserBuild.files.map((file) => [
+ file.relativePath,
+ addEvidence(`${evidenceRoot}/${file.relativePath}`, file.bytes),
+ ]),
);
- const manualEvidenceReferences = Object.fromEntries(manualEvidenceEntries);
- const manualEvidence = Object.fromEntries(
- manualEvidenceEntries.map(([check, evidenceReference]) => [
+ const journeyEvidence = REQUIRED_CLOAK_JOURNEY_IDS.map((id, index) => ({
+ id,
+ evidence: browserFileReferences.get(
+ `cloakbrowser-journeys/${String(index + 1).padStart(2, "0")}.json`,
+ )!,
+ }));
+ const browserReport = browserFileReferences.get(
+ "cloakbrowser-playwright-report.json",
+ )!;
+ const manualEvidenceReferences = Object.fromEntries(
+ REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS.map((check) => [
check,
- { status: "PASSED", evidenceSha256: evidenceReference.sha256 },
+ browserFileReferences.get(`cloakbrowser-manual/${check}.json`)!,
]),
);
- const browserEvidenceIndex = addEvidence(
- `${evidenceRoot}/cloakbrowser-evidence-index.json`,
- bytes({
- ...receiptBase,
- kind: "cloakbrowser-evidence-index",
- executionReportSha256: browserReport.sha256,
- manual: manualEvidence,
- }),
- );
- const browserEvidence = addEvidence(
- `${evidenceRoot}/cloakbrowser-qualification.json`,
- bytes({
- ...receiptBase,
- kind: "cloakbrowser-qualification",
- authority: "CLOAKBROWSER",
- baseUrl: receipt.productionOrigin,
- exactReleaseBound: true,
- journeyCount: 40,
- viewports: browserViewports,
- browserVersion,
- playwrightVersion: "1.61.1",
- playwrightReportSha256: browserReport.sha256,
- browserEvidenceIndexSha256: browserEvidenceIndex.sha256,
- journeys,
- desktopComplete: true,
- mobileComplete: true,
- keyboardComplete: true,
- screenReaderNamesComplete: true,
- reducedMotionComplete: true,
- noHorizontalOverflow: true,
- zoom200Complete: true,
- longContentComplete: true,
- narrowVisualizationsComplete: true,
- touchTargetsComplete: true,
- requiredSkips: 0,
- failures: 0,
- consoleErrors: 0,
- failedRequests: 0,
- webVitals: { lcpMs: 1_200, cls: 0.02, inpMs: 90 },
- }),
- );
+ const browserEvidenceIndex = browserFileReferences.get(
+ "cloakbrowser-evidence-index.json",
+ )!;
+ const browserEvidence = browserFileReferences.get(
+ "cloakbrowser-qualification.json",
+ )!;
const publicLinkEvidence = addEvidence(
`${evidenceRoot}/public-link-audit.json`,
bytes({
@@ -774,10 +756,7 @@ async function readyPackage(
failedRequests: 0,
playwrightReport: browserReport,
evidenceIndex: browserEvidenceIndex,
- journeyEvidence: journeyEvidence.map(({ id, evidence }) => ({
- id,
- evidence,
- })),
+ journeyEvidence,
manualEvidence: manualEvidenceReferences,
evidence: browserEvidence,
},
@@ -799,6 +778,129 @@ async function readyPackage(
};
}
+type ReadyFixture = Awaited>;
+
+function replaceEvidenceValue(
+ fixture: ReadyFixture,
+ path: string,
+ value: unknown,
+) {
+ const content = bytes(value);
+ fixture.evidence.set(path, content);
+ return reference(path, content);
+}
+
+function replaceManualEvidence(
+ fixture: ReadyFixture,
+ key: (typeof REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS)[number],
+ mutateArtifact: (artifact: Record) => void,
+): void {
+ const manualReference =
+ fixture.ready.browserQualification.manualEvidence[key]!;
+ const manual = JSON.parse(
+ new TextDecoder().decode(fixture.evidence.get(manualReference.path)!),
+ ) as {
+ artifact: Record;
+ artifactSha256: string;
+ };
+ mutateArtifact(manual.artifact);
+ manual.artifactSha256 = hash(bytes(canonicalJson(manual.artifact)));
+ const nextManualReference = replaceEvidenceValue(
+ fixture,
+ manualReference.path,
+ manual,
+ );
+ fixture.ready.browserQualification.manualEvidence[key] = nextManualReference;
+
+ const indexReference = fixture.ready.browserQualification.evidenceIndex!;
+ const index = JSON.parse(
+ new TextDecoder().decode(fixture.evidence.get(indexReference.path)!),
+ ) as { manual: Record };
+ index.manual[key]!.evidenceSha256 = nextManualReference.sha256;
+ const nextIndexReference = replaceEvidenceValue(
+ fixture,
+ indexReference.path,
+ index,
+ );
+ fixture.ready.browserQualification.evidenceIndex = nextIndexReference;
+
+ const qualificationReference = fixture.ready.browserQualification.evidence!;
+ const qualification = JSON.parse(
+ new TextDecoder().decode(
+ fixture.evidence.get(qualificationReference.path)!,
+ ),
+ ) as { browserEvidenceIndexSha256: string };
+ qualification.browserEvidenceIndexSha256 = nextIndexReference.sha256;
+ fixture.ready.browserQualification.evidence = replaceEvidenceValue(
+ fixture,
+ qualificationReference.path,
+ qualification,
+ );
+}
+
+function replaceJourneyAssertionCount(
+ fixture: ReadyFixture,
+ index: number,
+ assertionCount: number,
+): void {
+ const journeyEntry =
+ fixture.ready.browserQualification.journeyEvidence[index]!;
+ const journeyReference = journeyEntry.evidence;
+ const journeyReceipt = JSON.parse(
+ new TextDecoder().decode(fixture.evidence.get(journeyReference.path)!),
+ ) as { assertionCount: number };
+ journeyReceipt.assertionCount = assertionCount;
+ const nextJourneyReference = replaceEvidenceValue(
+ fixture,
+ journeyReference.path,
+ journeyReceipt,
+ );
+ journeyEntry.evidence = nextJourneyReference;
+
+ const reportReference = fixture.ready.browserQualification.playwrightReport!;
+ const report = JSON.parse(
+ new TextDecoder().decode(fixture.evidence.get(reportReference.path)!),
+ ) as { journeys: Array<{ evidenceSha256: string }> };
+ report.journeys[index]!.evidenceSha256 = nextJourneyReference.sha256;
+ const nextReportReference = replaceEvidenceValue(
+ fixture,
+ reportReference.path,
+ report,
+ );
+ fixture.ready.browserQualification.playwrightReport = nextReportReference;
+
+ const indexReference = fixture.ready.browserQualification.evidenceIndex!;
+ const evidenceIndex = JSON.parse(
+ new TextDecoder().decode(fixture.evidence.get(indexReference.path)!),
+ ) as { executionReportSha256: string };
+ evidenceIndex.executionReportSha256 = nextReportReference.sha256;
+ const nextIndexReference = replaceEvidenceValue(
+ fixture,
+ indexReference.path,
+ evidenceIndex,
+ );
+ fixture.ready.browserQualification.evidenceIndex = nextIndexReference;
+
+ const qualificationReference = fixture.ready.browserQualification.evidence!;
+ const qualification = JSON.parse(
+ new TextDecoder().decode(
+ fixture.evidence.get(qualificationReference.path)!,
+ ),
+ ) as {
+ browserEvidenceIndexSha256: string;
+ journeys: Array<{ evidenceSha256: string }>;
+ playwrightReportSha256: string;
+ };
+ qualification.playwrightReportSha256 = nextReportReference.sha256;
+ qualification.browserEvidenceIndexSha256 = nextIndexReference.sha256;
+ qualification.journeys[index]!.evidenceSha256 = nextJourneyReference.sha256;
+ fixture.ready.browserQualification.evidence = replaceEvidenceValue(
+ fixture,
+ qualificationReference.path,
+ qualification,
+ );
+}
+
async function submittedPackage() {
const { ready, evidence } = await readyPackage();
const deploymentReceipt = ready.release.deploymentReceipt;
@@ -1133,6 +1235,62 @@ describe("submission package validator", () => {
});
});
+ it("rejects manual observations bound to a different raw browser run", async () => {
+ const fixture = await readyPackage();
+ replaceManualEvidence(fixture, "keyboard", (artifact) => {
+ artifact.browserVersion = "CloakBrowser Chromium 141.0.0.0";
+ });
+
+ await expect(
+ validateSubmissionPackage(fixture.ready, "ready", fixture.reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "CloakBrowser manual evidence keyboard does not match its raw run",
+ ]),
+ });
+ });
+
+ it("rejects qualification Web Vitals that drift from manual observation", async () => {
+ const fixture = await readyPackage();
+ replaceManualEvidence(fixture, "webVitals", (artifact) => {
+ artifact.webVitals = {
+ status: "measured",
+ lcpMs: 1_300,
+ cls: 0.02,
+ inpMs: 90,
+ };
+ });
+
+ await expect(
+ validateSubmissionPackage(fixture.ready, "ready", fixture.reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "CloakBrowser receipt does not match the package",
+ ]),
+ });
+ });
+
+ it("rejects journey assertion counts that drift from the raw run", async () => {
+ const fixture = await readyPackage();
+ replaceJourneyAssertionCount(fixture, 0, 99);
+
+ await expect(
+ validateSubmissionPackage(fixture.ready, "ready", fixture.reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ expect.stringMatching(
+ /^CloakBrowser journey .* does not match its report$/u,
+ ),
+ ]),
+ });
+ });
+
it("binds screenshot roles by file name instead of manifest order", async () => {
const { ready, reader } = await readyPackage();
ready.assets = [ready.assets[4]!, ...ready.assets.slice(0, 4)];
diff --git a/scripts/validate-submission-package.ts b/scripts/validate-submission-package.ts
index 8e139c02..cbef7135 100644
--- a/scripts/validate-submission-package.ts
+++ b/scripts/validate-submission-package.ts
@@ -1706,15 +1706,36 @@ function assertPublicationEvidence(
"CloakBrowser execution report",
issues,
);
+ assertPreSubmissionTimestamp(
+ browserReport.rawRun.startedAt,
+ submission,
+ now,
+ notBefore,
+ "CloakBrowser raw run start",
+ issues,
+ );
+ assertPreSubmissionTimestamp(
+ browserReport.rawRun.completedAt,
+ submission,
+ now,
+ notBefore,
+ "CloakBrowser raw run completion",
+ issues,
+ );
for (const journey of browserReport.journeys) {
const verified = verifiedJourneyEvidence.get(journey.id);
+ const rawJourney = browserReport.rawRun.journeys.find(
+ (entry) => entry.id === journey.id,
+ );
if (
verified === undefined ||
+ rawJourney === undefined ||
verified.reference.sha256 !== journey.evidenceSha256 ||
verified.receipt.viewport !== journey.viewport ||
verified.receipt.journeyStatus !== journey.status ||
verified.receipt.attempt !== journey.attempt ||
verified.receipt.durationMs !== journey.durationMs ||
+ verified.receipt.assertionCount !== rawJourney.assertionCount ||
Date.parse(verified.receipt.checkedAt) >
Date.parse(browserReport.checkedAt)
) {
@@ -1723,6 +1744,29 @@ function assertPublicationEvidence(
);
}
}
+ for (const key of REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS) {
+ const verified = verifiedManualEvidence.get(key);
+ if (verified === undefined) continue;
+ const artifact = verified.receipt.artifact;
+ assertPreSubmissionTimestamp(
+ artifact.observedAt,
+ submission,
+ now,
+ notBefore,
+ `CloakBrowser manual observation ${key}`,
+ issues,
+ );
+ if (
+ artifact.baseUrl !== browserReport.baseUrl ||
+ artifact.rawRunCanonicalSha256 !==
+ browserReport.rawRunCanonicalSha256 ||
+ artifact.browserVersion !== browserReport.browserVersion
+ ) {
+ issues.push(
+ `CloakBrowser manual evidence ${key} does not match its raw run`,
+ );
+ }
+ }
}
if (browserIndex !== null) {
assertPublicationRelease(
@@ -1785,6 +1829,8 @@ function assertPublicationEvidence(
issues,
);
const manifest = submission.browserQualification;
+ const observedWebVitals =
+ verifiedManualEvidence.get("webVitals")?.receipt.artifact.webVitals;
if (
browser.authority !== manifest.authority ||
browser.exactReleaseBound !== manifest.exactReleaseBound ||
@@ -1799,7 +1845,13 @@ function assertPublicationEvidence(
browser.playwrightReportSha256 !== browserReportReference.sha256 ||
browser.browserEvidenceIndexSha256 !== browserIndexReference.sha256 ||
verifiedJourneyEvidence.size !== browser.journeyCount ||
- verifiedManualEvidence.size !== REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS.length
+ verifiedManualEvidence.size !==
+ REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS.length ||
+ observedWebVitals === undefined ||
+ observedWebVitals === null ||
+ observedWebVitals.lcpMs !== browser.webVitals.lcpMs ||
+ observedWebVitals.cls !== browser.webVitals.cls ||
+ observedWebVitals.inpMs !== browser.webVitals.inpMs
) {
issues.push("CloakBrowser receipt does not match the package");
}
From 19d8967d8db69207de149a690f54433170dd419e Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 16:08:23 +0530
Subject: [PATCH 099/143] docs(progress): record browser finalizer gate
---
docs/PROGRESS.md | 82 +++++++++++++++++++
docs/RELEASE_CHECKLIST.md | 17 +++-
.../19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md | 33 +++++++-
3 files changed, 125 insertions(+), 7 deletions(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 5f7cf855..f44ed63f 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4237,3 +4237,85 @@ a product or browser-qualification pass.
Release remains **NO-GO** at clean source, real containment, exact image,
contained Wrangler authentication, qualifying CloakBrowser execution,
deployment, publication, submission, and final merge.
+
+## Release-bound browser finalizer checkpoint — 2026-07-21T10:35:08Z
+
+Deadline: `2026-07-22T00:00:00Z`; **13 hours 24 minutes 52 seconds remained at
+this checkpoint**. Committed HEAD is
+`0ee8934bbfbfff85c20a2b72748332354b134dbc`, sixteen commits ahead of
+`origin/feat/learner-ux-v6.1` and 98 commits ahead of local `main`, with no
+reverse divergence. The only worktree entry is the preserved, untracked FUSE
+tombstone `scripts/.fuse_hidden0000a9600000801f`; it remains untouched.
+
+### Completed and independently reviewed
+
+- Commit `0ee8934 feat(release): finalize browser qualification evidence`
+ closes the source implementation for release-bound browser evidence. The raw
+ run is schema v3, binds the exact deployment receipt/source/image/Worker
+ tuple, records exact expected and observed allowlisted request failures, and
+ fails closed on registry, viewport, retry, assertion, browser, console,
+ network, chronology, or authority drift.
+- The finalizer deterministically builds **53 browser evidence artifacts**:
+ 40 journey receipts, one execution report, ten manual-observation receipts,
+ one evidence index, and one qualification receipt. The copied deployment
+ receipt and publication reservation are infrastructure files, not additional
+ browser claims.
+- Manual keyboard, screen-reader-name, reduced-motion, overflow, zoom,
+ long-content, narrow-visualization, touch-target, console/network, and Web
+ Vitals checks are explicitly labelled `HUMAN_OBSERVATION` on a
+ `CLOAKBROWSER` session. Their criteria, timestamps, browser version, raw-run
+ hash, exact release, and LCP/CLS/INP values are cross-bound; schema validation
+ does not pretend the observations were automated.
+- Inputs use bounded descriptor reads with `O_NOFOLLOW`; staging and
+ reservations live under ignored repository-local `.counterlab/` state on the
+ same filesystem. Publication uses an exclusive release-hash reservation,
+ verifies every staged byte, refuses replacement, and moves the complete
+ directory into its final receipt-hash path.
+- Independent review returned **safe to commit**. Its concurrent same-release
+ probe observed exactly **one successful publisher and one atomic refusal**,
+ with intact final bytes. No reviewer edited files or touched the FUSE entry.
+
+### Exact verification at this checkpoint
+
+- Finalizer/path/submission focused Vitest: **3 files, 50/50 passed**.
+- Browser reporter/authority focused Vitest: **2 files, 25/25 passed**.
+- Complete web Vitest: **80 files, 656/656 passed**.
+- Complete root Vitest: **67 files passed, 6 failed; 740 passed, 27 failed,
+ 2 skipped across 73 files**. The red files remain the known environment and
+ stale-evidence gates: denied loopback/bubblewrap capabilities, Codex test
+ subprocess exits, and scientific integrity/source bindings that must be
+ regenerated only after source freeze. No affected finalizer test failed.
+- Repository, web, generated Worker, and E2E strict TypeScript: **passed**.
+- Playwright static collection: exactly **40 tests in 3 files**. A qualifying
+ configuration without `COUNTERLAB_E2E_DEPLOYMENT_RECEIPT` exited **1** before
+ execution, as required.
+- Focused Prettier, Git whitespace, and a secret scan across all **11 committed
+ files**: **passed**.
+
+### Still open, in strict dependency order
+
+1. Resolve the existing FUSE handle through its owner without deleting,
+ hiding, staging, or rewriting the tombstone; then freeze clean source `S`.
+2. Establish constitution-compatible read-only cgroup/owned-process
+ observation, build one exact image, run the genuine aggregate sentinel and
+ negative controls, refresh all source/image evidence once, and commit
+ evidence `E`.
+3. Rerun the complete deterministic release gate until the 27 current
+ capability/stale-binding failures are green from real evidence.
+4. Authenticate the repository-contained Wrangler identity and preflight a
+ fresh CloakBrowser-capable session before any cloud mutation.
+5. Qualify and deploy only the exact tuple, prove public release identity, run
+ production smoke, then execute all 40 public CloakBrowser journeys and the
+ ten factual human accessibility/performance observations. Current qualifying
+ count remains **0 journeys**.
+6. Produce exact-tuple screenshots and video; verify repository, feedback,
+ public links, and Devpost; keep learner evidence `NO_DATA` unless real
+ consented observations exist; capture the submission receipt.
+7. Push the feature branch before logged-out repository/submission validation,
+ then commit final evidence and fast-forward-only merge and push `main` after
+ all upstream gates are factual.
+
+No Cloudflare mutation, deployment, qualifying browser run, learner outcome,
+Devpost submission, push, or merge is claimed. Release remains **NO-GO** at
+clean source, real containment, exact image/evidence, contained Wrangler
+authentication, CloakBrowser execution, deployment, and submission.
diff --git a/docs/RELEASE_CHECKLIST.md b/docs/RELEASE_CHECKLIST.md
index f4d138a8..3be9e754 100644
--- a/docs/RELEASE_CHECKLIST.md
+++ b/docs/RELEASE_CHECKLIST.md
@@ -296,6 +296,13 @@ after the final source freeze.
- [x] Playwright collection fails closed without CloakBrowser. Rerun and execute
the complete final statically collected suite after source freeze; a
static count is not rendered-browser evidence.
+- [x] Release-bound browser evidence finalization is source-complete at
+ `0ee8934`: schema-v3 raw evidence binds the exact deployed tuple and
+ journey telemetry; 40 journey receipts, ten `HUMAN_OBSERVATION` receipts,
+ the execution report, index, and qualification receipt are cross-bound;
+ bounded no-follow inputs, ignored staging, exclusive reservation, staged
+ hash verification, and no-replacement behavior are independently tested.
+ Public CloakBrowser execution remains 0 journeys and is not checked off.
- [ ] Regenerate current scientific integrity bindings, source/image-bound
engine evidence, and the normalized pnpm 11.13.1 production Node SBOM
after the final source freeze. Current generated evidence is deliberately
@@ -320,15 +327,17 @@ after the final source freeze.
requires two distinct fixed-result Metric bindings. Source-owned oracle,
renderer, mutation, signed-binding, and Subject Pack scopes are covered;
production and rendered-browser proof remain open.
-- [x] Dirty-tree broad verification: web Vitest 72/72 files and 558/558 tests;
- root Vitest 615/642 across 52/58 files with 27 classified
+- [x] Dirty-tree broad verification: web Vitest 80/80 files and 656/656 tests;
+ root Vitest 740 passed, 27 failed, and 2 skipped across 67/73 files, with
+ the same 27 classified
capability/stale-evidence failures; kernel Pytest 220/221 with one denied
localhost-socket test; runner Pytest 75/75; repository, web, and Worker
TypeScript passed; production build passed. The deterministic Sample
Boundary and fixed Sample Proof Capsule checks pass, focused sample
authority/UI tests pass 18/18, the repository secret scan passes across
- 1,384 files, and whitespace passes. Final scoped changed-file Prettier is
- still pending. These are not frozen-release results.
+ 1,384 files, and whitespace passes. Final scoped browser-finalizer
+ Prettier, 11-file secret scan, and whitespace checks pass. These are not
+ frozen-release results.
- [x] Private patch/Capsule downloads now use authenticated fetch, strict byte
and filename validation, bounded timeouts, browser-save initiation after
validation, failure-without-telemetry semantics, and duplicate-action
diff --git a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
index f643e8ee..7eada8e4 100644
--- a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
+++ b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
@@ -286,9 +286,11 @@ repository constitution as the governing product and filesystem authority.
D1, and Container mutations before invoking the smoke script, while
`production-smoke.sh` requires CloakBrowser. CloakBrowser must therefore be
preflighted before deployment starts.
-7. The checked-in browser evidence path still needs the remaining three
- required viewport labels, INP capture, and deterministic 40-journey/manual
- evidence receipts before it can satisfy the publication schema.
+7. Commit `0ee8934` closes the checked-in browser finalizer source path: seven
+ ID-bound viewports, privacy-safe INP, schema-v3 raw evidence, deterministic
+ 40-journey receipts, ten honestly labelled human-observation receipts, and
+ cross-bound report/index/qualification evidence. Exact public CloakBrowser
+ execution and manual observation remain open at **0 qualifying journeys**.
8. Learner evidence remains `NO_DATA`; public repository, video, feedback ID,
Devpost publication, and submission receipt remain externally unverified.
@@ -347,3 +349,28 @@ failures, symlink traversal, and evidence overwrite. This closes the browser
instrumentation source gaps identified at `7e25a09`; it does **not** close the
finalizer, live Cloak execution, exact release, deployment, or submission
nodes. Current qualifying journey count remains **0**.
+
+## Browser evidence graph addendum — `0ee8934`
+
+```mermaid
+flowchart LR
+ Registry["Exact 40 journey registry"] --> Fixture["Automatic Playwright fixture"]
+ Viewports["7 ID-bound viewports"] --> Fixture
+ Cloak["Loopback Cloak CDP authority"] --> Fixture
+ Deploy["Exact deployment receipt"] --> Raw["Schema-v3 raw run"]
+ Fixture --> Raw
+ Raw --> Finalizer["Release-bound finalizer — source complete"]
+ Human["10 HUMAN_OBSERVATION records on CloakBrowser"] --> Finalizer
+ Finalizer --> Receipts["40 journeys + report + 10 manual + index + qualification"]
+ Receipts --> PublicRun["Exact public Cloak execution — open"]
+ PublicRun --> Submission["Submission evidence — open"]
+```
+
+The finalizer is byte- and release-bound, stages only in ignored
+repository-local state, uses an exclusive receipt-hash reservation, refuses
+replacement, and publishes only after every staged hash passes. Independent
+review passed its concurrent-publication probe and all focused gates. Manual
+checks are labelled human observations; their integrity receipts do not claim
+automated accessibility judgment. Current public qualification remains **0
+journeys**, so no production, accessibility, performance, or comprehension
+result is closed by this source commit alone.
From d561bc4eaaceed2a30d8ed59f4bdd0c261bcf329 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 16:16:41 +0530
Subject: [PATCH 100/143] docs(progress): record release blockers
---
docs/PROGRESS.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index f44ed63f..6a7f95fa 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4319,3 +4319,71 @@ No Cloudflare mutation, deployment, qualifying browser run, learner outcome,
Devpost submission, push, or merge is claimed. Release remains **NO-GO** at
clean source, real containment, exact image/evidence, contained Wrangler
authentication, CloakBrowser execution, deployment, and submission.
+
+## Release-blocker reconciliation checkpoint — 2026-07-21T10:44:08Z
+
+Deadline: `2026-07-22T00:00:00Z`; **13 hours 15 minutes 52 seconds remained at
+this checkpoint**. Committed HEAD is
+`19d8967d8db69207de149a690f54433170dd419e`, seventeen commits ahead of
+`origin/feat/learner-ux-v6.1` and 99 commits ahead of local `main`, with no
+reverse divergence. The only worktree entry remains the preserved, untracked
+FUSE tombstone `scripts/.fuse_hidden0000a9600000801f`; it has not been read,
+changed, staged, hidden, ignored, renamed, or deleted.
+
+### Done since the browser-finalizer checkpoint
+
+- Commit `19d8967 docs(progress): record browser finalizer gate` records the
+ reviewed finalizer implementation and its exact verification.
+- The four capability-sensitive root test files that had failed in the
+ restricted sandbox were rerun with their required loopback, process, and
+ bubblewrap access: **4 files, 51/51 tests passed**. Those failures are no
+ longer product regressions. The only remaining full-root failures are the
+ three stale scientific source/image evidence files, which must not be
+ regenerated before the exact source and image are frozen.
+- A fresh repository-contained Wrangler OAuth attempt was started and reached
+ the local callback listener, but the approval window timed out. Wrangler
+ remains unauthenticated and no Cloudflare mutation occurred.
+- The previously attested `rt-mainrel04` runtime is stale rather than safely
+ controllable: the non-destructive attested stop helper refused to proceed,
+ the required runtime root is absent, and the supervisor socket is absent.
+ No PID was killed and no attestation check was bypassed.
+- Independent architecture review confirmed that the existing exact
+ `git archive HEAD` build context does **not** satisfy the separately declared
+ clean-worktree gate while the tombstone exists. Build, qualification,
+ release-check, deployment, and production-smoke all independently require no
+ tracked or untracked drift. Exempting this path would weaken and split the
+ receipt chain and is rejected.
+
+### What remains, in dependency order
+
+1. The owner must close the process retaining the FUSE handle so the tombstone
+ disappears naturally. Do not delete, hide, ignore, stage, rename, or
+ overwrite it. Confirm an empty `git status`, then freeze source `S`.
+2. Start a fresh contained runtime; build exactly one source-bound runner
+ image; run the genuine aggregate-cgroup sentinel and its negative controls;
+ regenerate the scientific-engine, SBOM, VEX, license, held-out, health, and
+ source/image evidence together; commit reviewed evidence `E`.
+3. Rerun the full deterministic release gate, including TypeScript, Vitest,
+ Pytest, mutation, held-out, formatting, secret, build, replay, reproduction,
+ and exact-image qualification. The three stale scientific-binding files are
+ expected to stay red until step 2 completes.
+4. Complete a fresh repository-contained Wrangler login and verify the intended
+ account, required secrets, D1 target, Container image target, and recovery
+ deployment before the first cloud mutation.
+5. Preflight a live CloakBrowser CDP endpoint, then deploy only the qualified
+ tuple. Prove the public source/image/Worker identity and run production
+ smoke, all 40 public journeys, seven viewports, ten manual accessibility and
+ performance observations, downloads, reconnect/history, and Web Vitals.
+ Stock Chromium may supply clearly labelled design-review evidence only.
+6. Capture screenshots and a <=3:00 video from the exact qualified public
+ tuple; verify the app, public repository, feedback ID, setup path, and all
+ Devpost fields while logged out. Keep learner evidence `NO_DATA` unless real
+ consented observations occur.
+7. Push the reviewed feature branch before final logged-out source validation;
+ capture the submission receipt; commit and push final evidence; then
+ fast-forward-only merge and push `main` after every upstream gate is factual.
+
+No clean source, exact image, aggregate containment pass, Cloudflare identity,
+deployment, CloakBrowser journey, learner outcome, submission, push, or merge
+is claimed at this checkpoint. Release remains **NO-GO** until the ordered
+dependencies above are closed with direct evidence.
From faa2c2a152726804417fb535eb06dd591ca0e02e Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 16:35:02 +0530
Subject: [PATCH 101/143] test(browser): require exact live intake authority
---
apps/web/e2e/recovery-and-intake.spec.ts | 28 +++++++++++++++++++-----
1 file changed, 23 insertions(+), 5 deletions(-)
diff --git a/apps/web/e2e/recovery-and-intake.spec.ts b/apps/web/e2e/recovery-and-intake.spec.ts
index d5284839..0ab641ba 100644
--- a/apps/web/e2e/recovery-and-intake.spec.ts
+++ b/apps/web/e2e/recovery-and-intake.spec.ts
@@ -9,6 +9,7 @@ const supportedNotebookPath = new URL(
import.meta.url,
).pathname;
const supportedNotebookName = "leakage-rows-pipeline.ipynb";
+const exactLiveJourney = process.env.COUNTERLAB_E2E_LIVE === "1";
const readyHealth = {
ok: true,
@@ -97,8 +98,13 @@ async function installReadyHealth(page: Page): Promise {
});
}
-async function openReadyLiveIntake(page: Page): Promise {
- await installReadyHealth(page);
+async function openReadyLiveIntake(
+ page: Page,
+ options: { requireExactLiveRelease?: boolean } = {},
+): Promise {
+ if (options.requireExactLiveRelease !== true) {
+ await installReadyHealth(page);
+ }
await reset(page);
await page.getByLabel("Your question or claim").fill(liveQuestion);
await page.getByRole("button", { name: /Test this claim/i }).click();
@@ -342,6 +348,10 @@ test("unsupported live upload is refused without creating a session", async ({
test("a supported live upload creates one source-bound session", async ({
page,
}) => {
+ test.skip(
+ !exactLiveJourney,
+ "Set COUNTERLAB_E2E_LIVE=1 only against an exact release-ready live API.",
+ );
let uploadRequests = 0;
let sessionCreationRequests = 0;
const duplicateKeyWarnings: string[] = [];
@@ -361,7 +371,7 @@ test("a supported live upload creates one source-bound session", async ({
sessionCreationRequests += 1;
}
});
- await openReadyLiveIntake(page);
+ await openReadyLiveIntake(page, { requireExactLiveRelease: true });
await page
.getByLabel(/Attach a supported notebook/i)
@@ -383,6 +393,10 @@ test("a supported live upload creates one source-bound session", async ({
test("an interrupted upload accepts the same file on retry", async ({
page,
}, testInfo) => {
+ test.skip(
+ !exactLiveJourney,
+ "Set COUNTERLAB_E2E_LIVE=1 only against an exact release-ready live API.",
+ );
testInfo.annotations.push({
type: "counterlab-expected-request-failures",
description: "POST /api/artifacts",
@@ -400,7 +414,7 @@ test("an interrupted upload accepts the same file on retry", async ({
}
await route.continue();
});
- await openReadyLiveIntake(page);
+ await openReadyLiveIntake(page, { requireExactLiveRelease: true });
const upload = page.getByLabel(/Attach a supported notebook/i);
await upload.setInputFiles(supportedNotebookPath);
@@ -420,6 +434,10 @@ test("an interrupted upload accepts the same file on retry", async ({
test("a lost private-session response retries without re-uploading", async ({
page,
}, testInfo) => {
+ test.skip(
+ !exactLiveJourney,
+ "Set COUNTERLAB_E2E_LIVE=1 only against an exact release-ready live API.",
+ );
testInfo.annotations.push({
type: "counterlab-expected-request-failures",
description: "POST /api/live/sessions",
@@ -439,7 +457,7 @@ test("a lost private-session response retries without re-uploading", async ({
}
await route.continue();
});
- await openReadyLiveIntake(page);
+ await openReadyLiveIntake(page, { requireExactLiveRelease: true });
await page
.getByLabel(/Attach a supported notebook/i)
From 9ee49ee2f9d3dd3614377322958da7454451b79b Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 16:45:04 +0530
Subject: [PATCH 102/143] test(browser): await verified action responses
---
apps/web/e2e/judged-flow.spec.ts | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index ff7fd066..68b67a38 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -917,10 +917,24 @@ async function startInstant(page: Page) {
).toBeVisible();
}
+function waitForPostResponse(page: Page, routeSuffix: string) {
+ return page.waitForResponse((response) => {
+ const request = response.request();
+ return (
+ request.method() === "POST" &&
+ new URL(response.url()).pathname.endsWith(routeSuffix)
+ );
+ });
+}
+
async function commitAndOpenResult(page: Page) {
await page.getByLabel(/Remain near 98%/i).check();
+ const compileFinished = waitForPostResponse(page, "/lab/compile");
await page.getByRole("button", { name: /Seal my prediction/i }).click();
+ expect((await compileFinished).ok()).toBe(true);
+ const runFinished = waitForPostResponse(page, "/lab/run");
await page.getByRole("button", { name: /Run the fair test/i }).click();
+ expect((await runFinished).ok()).toBe(true);
await expect(
page.getByRole("heading", { name: /Compare the verified result/i }),
).toBeVisible();
@@ -939,6 +953,7 @@ async function authorResultInterpretation(page: Page) {
).toBeVisible();
await expect(page.getByRole("tab", { name: /Explore/i })).toBeEnabled();
await expect(page.getByRole("tab", { name: /Boundary/i })).toBeEnabled();
+ await page.waitForLoadState("networkidle");
}
async function selectLeakageTransferEvidence(page: Page) {
@@ -2004,12 +2019,17 @@ test("refresh restores the current lesson and the committed prediction", async (
).toBeVisible();
await page.getByLabel(/Fall materially/i).check();
await page.getByLabel(/Confidence/i).fill("88");
+ const compileFinished = waitForPostResponse(page, "/lab/compile");
await page.getByRole("button", { name: /Seal my prediction/i }).click();
+ expect((await compileFinished).ok()).toBe(true);
+ await page.waitForLoadState("networkidle");
await page.reload();
await expect(
page.getByRole("heading", { name: /The fair test is ready/i }),
).toBeVisible();
+ const runFinished = waitForPostResponse(page, "/lab/run");
await page.getByRole("button", { name: /Run the fair test/i }).click();
+ expect((await runFinished).ok()).toBe(true);
await expect(
page.getByRole("heading", { name: /Compare the verified result/i }),
).toBeVisible();
From 99e9741e71e8fd2955543f729a11f02c18fcebf0 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 17:50:43 +0530
Subject: [PATCH 103/143] test(browser): reconcile expected HTTP failures
---
apps/web/e2e/cloak-test.ts | 109 ++++-
apps/web/e2e/judged-flow.spec.ts | 58 ++-
apps/web/e2e/qualification-reporter.ts | 208 ++++++++-
apps/web/e2e/recovery-and-intake.spec.ts | 6 +-
.../src/test/qualificationReporter.test.ts | 394 +++++++++++++++++-
docs/SUBMISSION_PACKAGE.json | 11 +-
...inalize-cloakbrowser-qualification.test.ts | 161 +++++--
.../finalize-cloakbrowser-qualification.ts | 48 ++-
scripts/submission-publication-evidence.ts | 195 ++++++++-
scripts/validate-submission-package.test.ts | 109 +++--
scripts/validate-submission-package.ts | 51 ++-
11 files changed, 1221 insertions(+), 129 deletions(-)
diff --git a/apps/web/e2e/cloak-test.ts b/apps/web/e2e/cloak-test.ts
index a571eb55..1f201f8d 100644
--- a/apps/web/e2e/cloak-test.ts
+++ b/apps/web/e2e/cloak-test.ts
@@ -11,11 +11,20 @@ import {
validateCloakCdpEndpoint,
} from "./browser-authority";
import {
+ classifyExpectedHttpErrorObservation,
+ classifyExpectedHttpResourceConsoleObservation,
JOURNEY_OBSERVATION_ATTACHMENT,
journeyIdForParts,
+ summarizeExpectedHttpErrors,
+ summarizeExpectedHttpResourceConsoleErrors,
summarizeRequestFailures,
+ type ExpectedHttpErrorObservation,
} from "./qualification-reporter";
-import { REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID } from "../../../scripts/submission-publication-evidence";
+import {
+ ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS,
+ REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID,
+ REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID,
+} from "../../../scripts/submission-publication-evidence";
export { currentBrowserAuthorityLabel };
@@ -23,6 +32,19 @@ const repositoryRoot = realpathSync(
fileURLToPath(new URL("../../..", import.meta.url)),
);
const runtimeParent = resolve(repositoryRoot, "apps/web/test-results/runtime");
+type AllowedExpectedHttpError =
+ (typeof ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS)[number];
+const allowedExpectedHttpErrors: ReadonlySet = new Set(
+ ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS,
+);
+
+function expectedBrowserOrigin(): string {
+ const configuredBaseUrl = process.env.COUNTERLAB_E2E_BASE_URL;
+ const baseUrl =
+ configuredBaseUrl ??
+ `http://127.0.0.1:${process.env.COUNTERLAB_E2E_PORT ?? "5173"}`;
+ return new URL(baseUrl).origin;
+}
function assertNoSymlinkEscape(candidate: string, label: string): void {
const relativePath = relative(repositoryRoot, candidate);
@@ -182,25 +204,77 @@ export const test = base.extend({
.split("x")
.map((part) => Number.parseInt(part, 10));
await page.setViewportSize({ width: width!, height: height! });
- let consoleErrors = 0;
+ const expectedOrigin = expectedBrowserOrigin();
+ const observedConsoleErrors: (ExpectedHttpErrorObservation | null)[] = [];
+ const observedHttpErrors: (ExpectedHttpErrorObservation | null)[] = [];
const failedRequests: string[] = [];
page.on("console", (message) => {
- if (message.type() === "error") consoleErrors += 1;
+ if (message.type() !== "error") return;
+ observedConsoleErrors.push(
+ classifyExpectedHttpResourceConsoleObservation({
+ expectedOrigin,
+ locationUrl: message.location().url,
+ text: message.text(),
+ }),
+ );
});
page.on("pageerror", () => {
- consoleErrors += 1;
+ observedConsoleErrors.push(null);
});
- page.on("requestfailed", (request) => {
- const url = new URL(request.url());
- failedRequests.push(
- `${request.method().toUpperCase()} ${url.pathname}`,
+ page.on("response", (response) => {
+ if (response.status() < 400) return;
+ observedHttpErrors.push(
+ classifyExpectedHttpErrorObservation({
+ expectedOrigin,
+ method: response.request().method(),
+ status: response.status(),
+ url: response.url(),
+ }),
);
});
+ page.on("requestfailed", (request) => {
+ try {
+ const url = new URL(request.url());
+ failedRequests.push(
+ `${request.method().toUpperCase()} ${url.pathname}`,
+ );
+ } catch {
+ failedRequests.push("UNPARSEABLE");
+ }
+ });
const browserVersion =
page.context().browser()?.version().trim() || "unavailable";
await use();
+ const expectedHttpAnnotations = testInfo.annotations.filter(
+ (annotation) => annotation.type === "counterlab-expected-http-errors",
+ );
+ const expectedHttpAnnotationValues = expectedHttpAnnotations.map(
+ (annotation) => annotation.description ?? "",
+ );
+ const expectedHttpErrorResponses = expectedHttpAnnotationValues.filter(
+ (value): value is AllowedExpectedHttpError =>
+ allowedExpectedHttpErrors.has(value),
+ );
+ const requiredHttpErrorResponses =
+ REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID[
+ journeyId as keyof typeof REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID
+ ];
+ const httpErrorSummary = summarizeExpectedHttpErrors(
+ observedHttpErrors.map((observation) => observation?.signature ?? null),
+ expectedHttpAnnotationValues,
+ );
+ const httpResourceConsoleSummary =
+ summarizeExpectedHttpResourceConsoleErrors(
+ observedConsoleErrors,
+ httpErrorSummary.matched
+ ? observedHttpErrors.filter(
+ (observation): observation is ExpectedHttpErrorObservation =>
+ observation !== null,
+ )
+ : [],
+ );
const expectedFailureAnnotations = testInfo.annotations.filter(
(annotation) =>
annotation.type === "counterlab-expected-request-failures",
@@ -216,13 +290,26 @@ export const test = base.extend({
await testInfo.attach(JOURNEY_OBSERVATION_ATTACHMENT, {
body: Buffer.from(
JSON.stringify({
- schemaVersion: "3",
+ schemaVersion: "4",
authority: currentBrowserAuthorityLabel(),
viewport: viewport ?? { width: 0, height: 0 },
- consoleErrors,
+ totalConsoleErrors: httpResourceConsoleSummary.totalCount,
+ expectedHttpResourceConsoleErrors:
+ httpResourceConsoleSummary.expectedCount,
+ unexpectedConsoleErrors: httpResourceConsoleSummary.unexpectedCount,
+ expectedHttpErrorResponses,
+ observedHttpErrorResponses:
+ httpErrorSummary.observedHttpErrorResponses,
+ unexpectedHttpErrorResponses: httpErrorSummary.unexpectedCount,
+ expectedHttpResponsesMatched:
+ httpErrorSummary.matched &&
+ expectedHttpAnnotationValues.length ===
+ expectedHttpErrorResponses.length &&
+ JSON.stringify(expectedHttpErrorResponses) ===
+ JSON.stringify(requiredHttpErrorResponses),
expectedRequestFailures,
expectedFailedRequests: requestFailureSummary.expectedCount,
- failedRequests: requestFailureSummary.unexpectedCount,
+ unexpectedFailedRequests: requestFailureSummary.unexpectedCount,
observedRequestFailures:
requestFailureSummary.observedRequestFailures,
observedFailedRequests: requestFailureSummary.observedCount,
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index 68b67a38..67dae32c 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -15,6 +15,7 @@ import {
ReasoningDiffV2Schema,
} from "@counterlab/contracts";
import { createHash } from "node:crypto";
+import type { Request } from "@playwright/test";
import { lstat, readFile, realpath, writeFile } from "node:fs/promises";
import { isAbsolute, relative, resolve } from "node:path";
@@ -927,6 +928,28 @@ function waitForPostResponse(page: Page, routeSuffix: string) {
});
}
+function trackNetworkQuiescence(page: Page): () => Promise {
+ const active = new Set();
+ page.on("request", (request) => active.add(request));
+ page.on("requestfinished", (request) => active.delete(request));
+ page.on("requestfailed", (request) => active.delete(request));
+
+ return async () => {
+ const deadline = Date.now() + 10_000;
+ while (Date.now() < deadline) {
+ if (active.size === 0) {
+ await page.waitForTimeout(500);
+ if (active.size === 0) return;
+ } else {
+ await page.waitForTimeout(50);
+ }
+ }
+ throw new Error(
+ `Browser network did not become quiescent (${active.size} request(s) remain)`,
+ );
+ };
+}
+
async function commitAndOpenResult(page: Page) {
await page.getByLabel(/Remain near 98%/i).check();
const compileFinished = waitForPostResponse(page, "/lab/compile");
@@ -1979,26 +2002,39 @@ test("the fixed sample keeps approved framing and can return home", async ({
test("refresh restores the question and confirmed Prediction phases", async ({
page,
}) => {
+ const waitForNetworkQuiescence = trackNetworkQuiescence(page);
await reset(page);
+ const sampleSessionCreated = waitForPostResponse(
+ page,
+ "/api/sample/sessions",
+ );
await page.getByRole("button", { name: /Try verified sample/i }).click();
+ expect((await sampleSessionCreated).ok()).toBe(true);
await expect(
page.getByRole("heading", { name: /What do you think the score means/i }),
).toBeVisible();
+ await waitForNetworkQuiescence();
await page.reload();
await expect(
page.getByRole("heading", { name: /What do you think the score means/i }),
).toBeVisible();
+ const beliefProposed = waitForPostResponse(page, "/belief-test");
await page.getByRole("button", { name: /Compare two explanations/i }).click();
+ expect((await beliefProposed).ok()).toBe(true);
+ await waitForNetworkQuiescence();
await page.reload();
await expect(
page.getByRole("heading", {
name: /Does your current explanation capture what you mean/i,
}),
).toBeVisible();
+ const beliefConfirmed = waitForPostResponse(page, "/belief-test/confirm");
await page
.getByRole("button", { name: /Yes, this captures my view/i })
.click();
+ expect((await beliefConfirmed).ok()).toBe(true);
+ await waitForNetworkQuiescence();
await page.reload();
await expect(
page.getByRole("heading", {
@@ -2010,7 +2046,11 @@ test("refresh restores the question and confirmed Prediction phases", async ({
test("refresh restores the current lesson and the committed prediction", async ({
page,
}) => {
+ const waitForNetworkQuiescence = trackNetworkQuiescence(page);
+ const beliefConfirmed = waitForPostResponse(page, "/belief-test/confirm");
await startInstant(page);
+ expect((await beliefConfirmed).ok()).toBe(true);
+ await waitForNetworkQuiescence();
await page.reload();
await expect(
page.getByRole("heading", {
@@ -2022,7 +2062,7 @@ test("refresh restores the current lesson and the committed prediction", async (
const compileFinished = waitForPostResponse(page, "/lab/compile");
await page.getByRole("button", { name: /Seal my prediction/i }).click();
expect((await compileFinished).ok()).toBe(true);
- await page.waitForLoadState("networkidle");
+ await waitForNetworkQuiescence();
await page.reload();
await expect(
page.getByRole("heading", { name: /The fair test is ready/i }),
@@ -2035,6 +2075,7 @@ test("refresh restores the current lesson and the committed prediction", async (
).toBeVisible();
await authorResultInterpretation(page);
+ await waitForNetworkQuiescence();
await page.reload();
await expect(
@@ -2048,7 +2089,10 @@ test("refresh restores the current lesson and the committed prediction", async (
).toBeVisible();
await authorResultInterpretation(page);
+ const revisionRecorded = waitForPostResponse(page, "/revision");
await recordRevision(page);
+ expect((await revisionRecorded).ok()).toBe(true);
+ await waitForNetworkQuiescence();
await page.reload();
await expect(
page.getByRole("heading", { name: /Try your rule on forecasting/i }),
@@ -2056,18 +2100,24 @@ test("refresh restores the current lesson and the committed prediction", async (
await page.getByLabel(/Time-ordered holdout/i).check();
await page.getByLabel(/Centered rolling target/i).check();
await selectLeakageTransferEvidence(page);
+ const transferEvaluated = waitForPostResponse(page, "/transfer");
await page.getByRole("button", { name: /Check transfer/i }).click();
+ expect((await transferEvaluated).ok()).toBe(true);
await expect(
page.locator(".eyebrow", { hasText: "Transfer passed" }),
).toBeVisible();
+ await waitForNetworkQuiescence();
await page.reload();
await expect(
page.getByRole("heading", {
name: /This fixed forecasting transfer passed/i,
}),
).toBeVisible();
+ const patchStarted = waitForPostResponse(page, "/patch/compile");
await page.getByRole("button", { name: /Verify notebook patch/i }).click();
+ expect((await patchStarted).ok()).toBe(true);
await waitForSamplePatch(page);
+ await waitForNetworkQuiescence();
await page.reload();
await expect(
page.getByRole("heading", {
@@ -2078,7 +2128,11 @@ test("refresh restores the current lesson and the committed prediction", async (
test("a rejected test releases no result and remains recoverable after refresh", async ({
page,
-}) => {
+}, testInfo) => {
+ testInfo.annotations.push({
+ type: "counterlab-expected-http-errors",
+ description: "409 POST /api/sessions/:sessionId/lab/compile",
+ });
await page.route("**/api/sessions/*/lab/compile", async (route) => {
await route.fulfill({
status: 409,
diff --git a/apps/web/e2e/qualification-reporter.ts b/apps/web/e2e/qualification-reporter.ts
index 2179fa56..aff3303a 100644
--- a/apps/web/e2e/qualification-reporter.ts
+++ b/apps/web/e2e/qualification-reporter.ts
@@ -18,9 +18,11 @@ import type {
import { z } from "zod";
import {
+ ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS,
ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES,
CloakBrowserRawRunSchema,
PublicationReleaseBindingSchema,
+ REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID,
REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID,
REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID,
REQUIRED_CLOAK_JOURNEY_IDS,
@@ -33,7 +35,7 @@ export const JOURNEY_OBSERVATION_ATTACHMENT =
const PageObservationSchema = z
.object({
- schemaVersion: z.literal("3"),
+ schemaVersion: z.literal("4"),
authority: z.enum(["CLOAK_CDP_ENDPOINT", "stock-chromium-design-review"]),
viewport: z
.object({
@@ -41,12 +43,22 @@ const PageObservationSchema = z
height: z.number().int().positive().max(8_192),
})
.strict(),
- consoleErrors: z.number().int().nonnegative(),
+ totalConsoleErrors: z.number().int().nonnegative(),
+ expectedHttpResourceConsoleErrors: z.number().int().nonnegative(),
+ unexpectedConsoleErrors: z.number().int().nonnegative(),
+ expectedHttpErrorResponses: z
+ .array(z.enum(ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS))
+ .max(ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS.length),
+ observedHttpErrorResponses: z
+ .array(z.enum(ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS))
+ .max(ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS.length),
+ unexpectedHttpErrorResponses: z.number().int().nonnegative(),
+ expectedHttpResponsesMatched: z.boolean(),
expectedRequestFailures: z
.array(z.enum(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES))
.max(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES.length),
expectedFailedRequests: z.number().int().nonnegative(),
- failedRequests: z.number().int().nonnegative(),
+ unexpectedFailedRequests: z.number().int().nonnegative(),
observedRequestFailures: z
.array(z.enum(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES))
.max(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES.length),
@@ -66,10 +78,15 @@ export interface CapturedJourney {
durationMs: number;
assertionCount: number;
viewport: string;
- consoleErrors: number;
+ totalConsoleErrors: number;
+ expectedHttpResourceConsoleErrors: number;
+ unexpectedConsoleErrors: number;
+ expectedHttpErrorResponses: readonly (typeof ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS)[number][];
+ observedHttpErrorResponses: readonly (typeof ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS)[number][];
+ unexpectedHttpErrorResponses: number;
expectedRequestFailures: readonly (typeof ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES)[number][];
expectedFailedRequests: number;
- failedRequests: number;
+ unexpectedFailedRequests: number;
observedRequestFailures: readonly (typeof ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES)[number][];
observedFailedRequests: number;
browserVersion: string;
@@ -107,6 +124,9 @@ const expectedProductionOrigin =
const allowedExpectedRequestFailures: ReadonlySet = new Set(
ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES,
);
+const allowedExpectedHttpErrors: ReadonlySet = new Set(
+ ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS,
+);
export function summarizeRequestFailures(
observed: readonly string[],
@@ -143,6 +163,152 @@ export function summarizeRequestFailures(
};
}
+type AllowedExpectedHttpError =
+ (typeof ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS)[number];
+
+export interface ExpectedHttpErrorObservation {
+ signature: AllowedExpectedHttpError;
+ ephemeralCorrelationKey: string;
+}
+
+function normalizedExpectedHttpPath(pathname: string): string {
+ if (pathname === "/api/artifacts") return pathname;
+ if (/^\/api\/sessions\/[^/]+\/lab\/compile$/u.test(pathname)) {
+ return "/api/sessions/:sessionId/lab/compile";
+ }
+ return pathname;
+}
+
+export function classifyExpectedHttpErrorObservation(input: {
+ expectedOrigin: string;
+ method: string;
+ status: number;
+ url: string;
+}): ExpectedHttpErrorObservation | null {
+ let url: URL;
+ try {
+ url = new URL(input.url);
+ } catch {
+ return null;
+ }
+ if (
+ url.origin !== input.expectedOrigin ||
+ url.search !== "" ||
+ url.hash !== ""
+ ) {
+ return null;
+ }
+ const signature = `${input.status} ${input.method.toUpperCase()} ${normalizedExpectedHttpPath(url.pathname)}`;
+ if (!allowedExpectedHttpErrors.has(signature)) return null;
+ return {
+ signature: signature as AllowedExpectedHttpError,
+ ephemeralCorrelationKey: `${input.status} ${input.method.toUpperCase()} ${url.origin}${url.pathname}`,
+ };
+}
+
+export function classifyExpectedHttpError(input: {
+ expectedOrigin: string;
+ method: string;
+ status: number;
+ url: string;
+}): AllowedExpectedHttpError | null {
+ return classifyExpectedHttpErrorObservation(input)?.signature ?? null;
+}
+
+export function classifyExpectedHttpResourceConsoleObservation(input: {
+ expectedOrigin: string;
+ locationUrl: string;
+ text: string;
+}): ExpectedHttpErrorObservation | null {
+ const match =
+ /^Failed to load resource: the server responded with a status of (\d{3})(?:\s|$)/u.exec(
+ input.text,
+ );
+ if (match?.[1] === undefined) return null;
+ const status = Number.parseInt(match[1], 10);
+ for (const method of ["POST"] as const) {
+ const classified = classifyExpectedHttpErrorObservation({
+ expectedOrigin: input.expectedOrigin,
+ method,
+ status,
+ url: input.locationUrl,
+ });
+ if (classified !== null) return classified;
+ }
+ return null;
+}
+
+export function classifyExpectedHttpResourceConsoleError(input: {
+ expectedOrigin: string;
+ locationUrl: string;
+ text: string;
+}): AllowedExpectedHttpError | null {
+ return (
+ classifyExpectedHttpResourceConsoleObservation(input)?.signature ?? null
+ );
+}
+
+export function summarizeExpectedHttpErrors(
+ observed: readonly (AllowedExpectedHttpError | null)[],
+ expected: readonly string[],
+): {
+ expectedCount: number;
+ matched: boolean;
+ observedCount: number;
+ observedHttpErrorResponses: readonly AllowedExpectedHttpError[];
+ unexpectedCount: number;
+} {
+ const expectedValid =
+ expected.every((error) => allowedExpectedHttpErrors.has(error)) &&
+ new Set(expected).size === expected.length;
+ const remaining = [...observed];
+ for (const expectedError of expected) {
+ const index = remaining.indexOf(expectedError as AllowedExpectedHttpError);
+ if (index >= 0) remaining.splice(index, 1);
+ }
+ return {
+ expectedCount: expected.length,
+ matched:
+ expectedValid &&
+ observed.length === expected.length &&
+ remaining.length === 0,
+ observedCount: observed.length,
+ observedHttpErrorResponses: observed.filter(
+ (error): error is AllowedExpectedHttpError => error !== null,
+ ),
+ unexpectedCount: remaining.length,
+ };
+}
+
+export function summarizeExpectedHttpResourceConsoleErrors(
+ observed: readonly (ExpectedHttpErrorObservation | null)[],
+ matchedHttpErrors: readonly ExpectedHttpErrorObservation[],
+): {
+ expectedCount: number;
+ totalCount: number;
+ unexpectedCount: number;
+} {
+ const available = [...matchedHttpErrors];
+ let expectedCount = 0;
+ for (const error of observed) {
+ if (error === null) continue;
+ const index = available.findIndex(
+ (candidate) =>
+ candidate.ephemeralCorrelationKey === error.ephemeralCorrelationKey &&
+ candidate.signature === error.signature,
+ );
+ if (index >= 0) {
+ available.splice(index, 1);
+ expectedCount += 1;
+ }
+ }
+ return {
+ expectedCount,
+ totalCount: observed.length,
+ unexpectedCount: observed.length - expectedCount,
+ };
+}
+
export function journeyIdForTest(
test: Pick,
): string {
@@ -255,7 +421,16 @@ export function buildCloakBrowserRawRun(input: BuildRawRunInput) {
journey.attempt === 0 &&
journey.durationMs > 0 &&
journey.assertionCount > 0 &&
- journey.consoleErrors === 0 &&
+ journey.unexpectedConsoleErrors === 0 &&
+ JSON.stringify(journey.expectedHttpErrorResponses) ===
+ JSON.stringify(
+ REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID[
+ journey.id as keyof typeof REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID
+ ],
+ ) &&
+ JSON.stringify(journey.observedHttpErrorResponses) ===
+ JSON.stringify(journey.expectedHttpErrorResponses) &&
+ journey.unexpectedHttpErrorResponses === 0 &&
JSON.stringify(journey.expectedRequestFailures) ===
JSON.stringify(
REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[
@@ -267,7 +442,7 @@ export function buildCloakBrowserRawRun(input: BuildRawRunInput) {
JSON.stringify(journey.observedRequestFailures) ===
JSON.stringify(journey.expectedRequestFailures) &&
journey.observedFailedRequests === journey.expectedFailedRequests &&
- journey.failedRequests === 0 &&
+ journey.unexpectedFailedRequests === 0 &&
journey.browserVersion.trim() !== "" &&
journey.browserVersion !== "unavailable" &&
journey.browserAuthority === expectedJourneyAuthority &&
@@ -276,7 +451,7 @@ export function buildCloakBrowserRawRun(input: BuildRawRunInput) {
new Set(input.journeys.map((journey) => journey.browserVersion)).size === 1;
return CloakBrowserRawRunSchema.parse({
- schemaVersion: "3",
+ schemaVersion: "4",
kind: "cloakbrowser-raw-run",
status: input.qualificationRequested
? exactCleanRun
@@ -360,16 +535,25 @@ export default class CounterLabQualificationReporter implements Reporter {
observation === null
? "0x0"
: `${observation.viewport.width}x${observation.viewport.height}`,
- consoleErrors: observation?.consoleErrors ?? 1,
+ totalConsoleErrors: observation?.totalConsoleErrors ?? 0,
+ expectedHttpResourceConsoleErrors:
+ observation?.expectedHttpResourceConsoleErrors ?? 0,
+ unexpectedConsoleErrors: observation?.unexpectedConsoleErrors ?? 0,
+ expectedHttpErrorResponses: observation?.expectedHttpErrorResponses ?? [],
+ observedHttpErrorResponses: observation?.observedHttpErrorResponses ?? [],
+ unexpectedHttpErrorResponses:
+ observation?.unexpectedHttpErrorResponses ?? 0,
expectedRequestFailures: observation?.expectedRequestFailures ?? [],
expectedFailedRequests: observation?.expectedFailedRequests ?? 0,
- failedRequests: observation?.failedRequests ?? 1,
+ unexpectedFailedRequests: observation?.unexpectedFailedRequests ?? 0,
observedRequestFailures: observation?.observedRequestFailures ?? [],
- observedFailedRequests: observation?.observedFailedRequests ?? 1,
+ observedFailedRequests: observation?.observedFailedRequests ?? 0,
browserVersion: observation?.browserVersion ?? "unavailable",
browserAuthority: observation?.authority ?? "unavailable",
telemetryValid:
- observation !== null && observation.expectedRequestFailuresMatched,
+ observation !== null &&
+ observation.expectedHttpResponsesMatched &&
+ observation.expectedRequestFailuresMatched,
});
}
diff --git a/apps/web/e2e/recovery-and-intake.spec.ts b/apps/web/e2e/recovery-and-intake.spec.ts
index 0ab641ba..ae6d7a9c 100644
--- a/apps/web/e2e/recovery-and-intake.spec.ts
+++ b/apps/web/e2e/recovery-and-intake.spec.ts
@@ -281,7 +281,11 @@ test("live intake cannot continue without an uploaded supported artifact", async
test("malformed live upload remains at intake and creates no session", async ({
page,
-}) => {
+}, testInfo) => {
+ testInfo.annotations.push({
+ type: "counterlab-expected-http-errors",
+ description: "422 POST /api/artifacts",
+ });
let sessionCreationRequests = 0;
page.on("request", (request) => {
if (isPostTo(request.url(), request.method(), "/api/live/sessions")) {
diff --git a/apps/web/src/test/qualificationReporter.test.ts b/apps/web/src/test/qualificationReporter.test.ts
index ffaa4794..30d5360f 100644
--- a/apps/web/src/test/qualificationReporter.test.ts
+++ b/apps/web/src/test/qualificationReporter.test.ts
@@ -3,13 +3,20 @@ import { describe, expect, it } from "vitest";
import {
buildCloakBrowserRawRun,
+ classifyExpectedHttpError,
+ classifyExpectedHttpErrorObservation,
+ classifyExpectedHttpResourceConsoleError,
+ classifyExpectedHttpResourceConsoleObservation,
countPlaywrightAssertions,
journeyIdForTest,
readJourneyObservation,
+ summarizeExpectedHttpErrors,
+ summarizeExpectedHttpResourceConsoleErrors,
summarizeRequestFailures,
type CapturedJourney,
} from "../../e2e/qualification-reporter";
import {
+ REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID,
REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID,
REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID,
REQUIRED_CLOAK_JOURNEY_IDS,
@@ -24,6 +31,10 @@ const qualificationRelease = {
containerImageDigest: `sha256:${"d".repeat(64)}`,
workerVersionId: "12345678-1234-1234-1234-123456789abc",
} as const;
+const compileHttpError =
+ "409 POST /api/sessions/:sessionId/lab/compile" as const;
+const artifactHttpError = "422 POST /api/artifacts" as const;
+const productionOrigin = "https://counterlab.cserules.workers.dev";
function reporterTestCase(
file: string,
@@ -48,11 +59,16 @@ function capturedJourneys(): CapturedJourney[] {
durationMs: index + 1,
assertionCount: 1,
viewport: REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID[id],
- consoleErrors: 0,
+ totalConsoleErrors: 0,
+ expectedHttpResourceConsoleErrors: 0,
+ unexpectedConsoleErrors: 0,
+ expectedHttpErrorResponses: REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID[id],
+ observedHttpErrorResponses: REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID[id],
+ unexpectedHttpErrorResponses: 0,
expectedRequestFailures: REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
expectedFailedRequests:
REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
- failedRequests: 0,
+ unexpectedFailedRequests: 0,
observedRequestFailures: REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
observedFailedRequests:
REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
@@ -93,13 +109,19 @@ describe("CloakBrowser qualification reporter", () => {
it("accepts exactly one privacy-safe in-memory page observation", () => {
const body = Buffer.from(
JSON.stringify({
- schemaVersion: "3",
+ schemaVersion: "4",
authority: "CLOAK_CDP_ENDPOINT",
viewport: { width: 1440, height: 900 },
- consoleErrors: 0,
+ totalConsoleErrors: 0,
+ expectedHttpResourceConsoleErrors: 0,
+ unexpectedConsoleErrors: 0,
+ expectedHttpErrorResponses: [],
+ observedHttpErrorResponses: [],
+ unexpectedHttpErrorResponses: 0,
+ expectedHttpResponsesMatched: true,
expectedRequestFailures: [],
expectedFailedRequests: 0,
- failedRequests: 0,
+ unexpectedFailedRequests: 0,
observedRequestFailures: [],
observedFailedRequests: 0,
expectedRequestFailuresMatched: true,
@@ -117,13 +139,19 @@ describe("CloakBrowser qualification reporter", () => {
} as TestResult;
expect(readJourneyObservation(result)).toEqual({
- schemaVersion: "3",
+ schemaVersion: "4",
authority: "CLOAK_CDP_ENDPOINT",
viewport: { width: 1440, height: 900 },
- consoleErrors: 0,
+ totalConsoleErrors: 0,
+ expectedHttpResourceConsoleErrors: 0,
+ unexpectedConsoleErrors: 0,
+ expectedHttpErrorResponses: [],
+ observedHttpErrorResponses: [],
+ unexpectedHttpErrorResponses: 0,
+ expectedHttpResponsesMatched: true,
expectedRequestFailures: [],
expectedFailedRequests: 0,
- failedRequests: 0,
+ unexpectedFailedRequests: 0,
observedRequestFailures: [],
observedFailedRequests: 0,
expectedRequestFailuresMatched: true,
@@ -168,16 +196,260 @@ describe("CloakBrowser qualification reporter", () => {
).toBe(false);
});
+ it("classifies only exact allowlisted HTTP error responses", () => {
+ expect(
+ classifyExpectedHttpError({
+ expectedOrigin: productionOrigin,
+ method: "POST",
+ status: 409,
+ url: `${productionOrigin}/api/sessions/session-private-value/lab/compile`,
+ }),
+ ).toBe(compileHttpError);
+ expect(
+ classifyExpectedHttpError({
+ expectedOrigin: productionOrigin,
+ method: "POST",
+ status: 422,
+ url: `${productionOrigin}/api/artifacts`,
+ }),
+ ).toBe(artifactHttpError);
+ });
+
+ it.each([
+ {
+ name: "wrong method",
+ method: "GET",
+ status: 409,
+ url: `${productionOrigin}/api/sessions/session-id/lab/compile`,
+ },
+ {
+ name: "wrong status",
+ method: "POST",
+ status: 500,
+ url: `${productionOrigin}/api/sessions/session-id/lab/compile`,
+ },
+ {
+ name: "wrong path",
+ method: "POST",
+ status: 409,
+ url: `${productionOrigin}/api/sessions/session-id/lab/run`,
+ },
+ {
+ name: "query string",
+ method: "POST",
+ status: 409,
+ url: `${productionOrigin}/api/sessions/session-id/lab/compile?retry=1`,
+ },
+ {
+ name: "fragment",
+ method: "POST",
+ status: 409,
+ url: `${productionOrigin}/api/sessions/session-id/lab/compile#fragment`,
+ },
+ {
+ name: "wrong origin",
+ method: "POST",
+ status: 409,
+ url: "https://example.invalid/api/sessions/session-id/lab/compile",
+ },
+ ])("rejects a $name HTTP error response", ({ method, status, url }) => {
+ expect(
+ classifyExpectedHttpError({
+ expectedOrigin: productionOrigin,
+ method,
+ status,
+ url,
+ }),
+ ).toBeNull();
+ });
+
+ it("fails expected HTTP reconciliation for missing, duplicate, and unrelated responses", () => {
+ expect(
+ summarizeExpectedHttpErrors([compileHttpError], [compileHttpError]),
+ ).toEqual({
+ expectedCount: 1,
+ matched: true,
+ observedCount: 1,
+ observedHttpErrorResponses: [compileHttpError],
+ unexpectedCount: 0,
+ });
+ expect(summarizeExpectedHttpErrors([], [compileHttpError])).toMatchObject({
+ matched: false,
+ observedCount: 0,
+ unexpectedCount: 0,
+ });
+ expect(
+ summarizeExpectedHttpErrors(
+ [compileHttpError, compileHttpError],
+ [compileHttpError],
+ ),
+ ).toMatchObject({
+ matched: false,
+ observedCount: 2,
+ unexpectedCount: 1,
+ });
+ expect(
+ summarizeExpectedHttpErrors([null], [compileHttpError]),
+ ).toMatchObject({
+ matched: false,
+ observedCount: 1,
+ unexpectedCount: 1,
+ });
+ });
+
+ it("attributes zero or one exact Chromium resource error only after its response matches", () => {
+ const text =
+ "Failed to load resource: the server responded with a status of 409 (Conflict)";
+ const responseObservation = classifyExpectedHttpErrorObservation({
+ expectedOrigin: productionOrigin,
+ method: "POST",
+ status: 409,
+ url: `${productionOrigin}/api/sessions/session-id/lab/compile`,
+ });
+ const consoleObservation = classifyExpectedHttpResourceConsoleObservation({
+ expectedOrigin: productionOrigin,
+ locationUrl: `${productionOrigin}/api/sessions/session-id/lab/compile`,
+ text,
+ });
+ expect(responseObservation).not.toBeNull();
+ expect(consoleObservation).not.toBeNull();
+ expect(
+ classifyExpectedHttpResourceConsoleError({
+ expectedOrigin: productionOrigin,
+ locationUrl: `${productionOrigin}/api/sessions/session-id/lab/compile`,
+ text,
+ }),
+ ).toBe(compileHttpError);
+ expect(
+ summarizeExpectedHttpResourceConsoleErrors([], [responseObservation!]),
+ ).toEqual({ expectedCount: 0, totalCount: 0, unexpectedCount: 0 });
+ expect(
+ summarizeExpectedHttpResourceConsoleErrors(
+ [consoleObservation],
+ [responseObservation!],
+ ),
+ ).toEqual({ expectedCount: 1, totalCount: 1, unexpectedCount: 0 });
+ expect(
+ summarizeExpectedHttpResourceConsoleErrors([consoleObservation], []),
+ ).toEqual({ expectedCount: 0, totalCount: 1, unexpectedCount: 1 });
+ });
+
+ it("correlates resource console errors to one exact response without persisting its session ID", () => {
+ const text =
+ "Failed to load resource: the server responded with a status of 409 (Conflict)";
+ const sessionAResponse = classifyExpectedHttpErrorObservation({
+ expectedOrigin: productionOrigin,
+ method: "POST",
+ status: 409,
+ url: `${productionOrigin}/api/sessions/session-a/lab/compile`,
+ });
+ const sessionAConsole = classifyExpectedHttpResourceConsoleObservation({
+ expectedOrigin: productionOrigin,
+ locationUrl: `${productionOrigin}/api/sessions/session-a/lab/compile`,
+ text,
+ });
+ const sessionBConsole = classifyExpectedHttpResourceConsoleObservation({
+ expectedOrigin: productionOrigin,
+ locationUrl: `${productionOrigin}/api/sessions/session-b/lab/compile`,
+ text,
+ });
+ expect(sessionAResponse).not.toBeNull();
+ expect(sessionAConsole).not.toBeNull();
+ expect(sessionBConsole).not.toBeNull();
+ expect(sessionAResponse?.signature).toBe(compileHttpError);
+ expect(sessionAConsole?.signature).toBe(compileHttpError);
+ expect(sessionBConsole?.signature).toBe(compileHttpError);
+ expect(
+ summarizeExpectedHttpResourceConsoleErrors(
+ [sessionAConsole],
+ [sessionAResponse!],
+ ),
+ ).toEqual({ expectedCount: 1, totalCount: 1, unexpectedCount: 0 });
+ expect(
+ summarizeExpectedHttpResourceConsoleErrors(
+ [sessionBConsole],
+ [sessionAResponse!],
+ ),
+ ).toEqual({ expectedCount: 0, totalCount: 1, unexpectedCount: 1 });
+ expect(
+ JSON.stringify({
+ observedHttpErrorResponses: [sessionAResponse!.signature],
+ }),
+ ).not.toContain("session-a");
+ });
+
+ it("keeps mismatched resource messages and unrelated console errors unexpected", () => {
+ expect(
+ classifyExpectedHttpResourceConsoleError({
+ expectedOrigin: productionOrigin,
+ locationUrl: `${productionOrigin}/api/artifacts`,
+ text: "Failed to load resource: the server responded with a status of 409 (Conflict)",
+ }),
+ ).toBeNull();
+ expect(
+ classifyExpectedHttpResourceConsoleError({
+ expectedOrigin: productionOrigin,
+ locationUrl: `${productionOrigin}/api/artifacts`,
+ text: "Uncaught Error: unrelated",
+ }),
+ ).toBeNull();
+ expect(
+ summarizeExpectedHttpResourceConsoleErrors(
+ [
+ null,
+ classifyExpectedHttpResourceConsoleObservation({
+ expectedOrigin: productionOrigin,
+ locationUrl: `${productionOrigin}/api/artifacts`,
+ text: "Failed to load resource: the server responded with a status of 422 (Unprocessable Content)",
+ }),
+ classifyExpectedHttpResourceConsoleObservation({
+ expectedOrigin: productionOrigin,
+ locationUrl: `${productionOrigin}/api/artifacts`,
+ text: "Failed to load resource: the server responded with a status of 422 (Unprocessable Content)",
+ }),
+ ],
+ [
+ classifyExpectedHttpErrorObservation({
+ expectedOrigin: productionOrigin,
+ method: "POST",
+ status: 422,
+ url: `${productionOrigin}/api/artifacts`,
+ })!,
+ ],
+ ),
+ ).toEqual({ expectedCount: 1, totalCount: 3, unexpectedCount: 2 });
+ expect(
+ classifyExpectedHttpResourceConsoleObservation({
+ expectedOrigin: productionOrigin,
+ locationUrl: `${productionOrigin}/api/artifacts?retry=1`,
+ text: "Failed to load resource: the server responded with a status of 422 (Unprocessable Content)",
+ }),
+ ).toBeNull();
+ expect(
+ classifyExpectedHttpResourceConsoleObservation({
+ expectedOrigin: productionOrigin,
+ locationUrl: `${productionOrigin}/api/artifacts#fragment`,
+ text: "Failed to load resource: the server responded with a status of 422 (Unprocessable Content)",
+ }),
+ ).toBeNull();
+ });
+
it("rejects missing, path-based, duplicate, and over-disclosed observations", () => {
const validBody = Buffer.from(
JSON.stringify({
- schemaVersion: "3",
+ schemaVersion: "4",
authority: "CLOAK_CDP_ENDPOINT",
viewport: { width: 1440, height: 900 },
- consoleErrors: 0,
+ totalConsoleErrors: 0,
+ expectedHttpResourceConsoleErrors: 0,
+ unexpectedConsoleErrors: 0,
+ expectedHttpErrorResponses: [],
+ observedHttpErrorResponses: [],
+ unexpectedHttpErrorResponses: 0,
+ expectedHttpResponsesMatched: true,
expectedRequestFailures: [],
expectedFailedRequests: 0,
- failedRequests: 0,
+ unexpectedFailedRequests: 0,
observedRequestFailures: [],
observedFailedRequests: 0,
expectedRequestFailuresMatched: true,
@@ -239,6 +511,42 @@ describe("CloakBrowser qualification reporter", () => {
);
});
+ it("accepts one response-bound Chromium resource error without hiding it", () => {
+ const journeys = capturedJourneys().map((journey) =>
+ journey.id ===
+ "judged-flow::a rejected test releases no result and remains recoverable after refresh"
+ ? {
+ ...journey,
+ totalConsoleErrors: 1,
+ expectedHttpResourceConsoleErrors: 1,
+ }
+ : journey,
+ );
+ const report = buildCloakBrowserRawRun({
+ authority: "CLOAKBROWSER",
+ baseUrl: productionOrigin,
+ completedAt: "2026-07-21T10:01:00.000Z",
+ journeys,
+ playwrightStatus: "passed",
+ qualificationRequested: true,
+ release: qualificationRelease,
+ startedAt: "2026-07-21T10:00:00.000Z",
+ });
+
+ expect(report.status).toBe("PASSED");
+ expect(
+ report.journeys.find(
+ (journey) =>
+ journey.id ===
+ "judged-flow::a rejected test releases no result and remains recoverable after refresh",
+ ),
+ ).toMatchObject({
+ totalConsoleErrors: 1,
+ expectedHttpResourceConsoleErrors: 1,
+ unexpectedConsoleErrors: 0,
+ });
+ });
+
it.each([
{
name: "missing journey",
@@ -268,10 +576,72 @@ describe("CloakBrowser qualification reporter", () => {
{
name: "console error",
mutate: (journeys: CapturedJourney[]) => [
- { ...journeys[0]!, consoleErrors: 1 },
+ {
+ ...journeys[0]!,
+ totalConsoleErrors: 1,
+ unexpectedConsoleErrors: 1,
+ },
+ ...journeys.slice(1),
+ ],
+ },
+ {
+ name: "missing expected HTTP error response",
+ mutate: (journeys: CapturedJourney[]) =>
+ journeys.map((journey) =>
+ journey.id ===
+ "judged-flow::a rejected test releases no result and remains recoverable after refresh"
+ ? { ...journey, observedHttpErrorResponses: [] }
+ : journey,
+ ),
+ },
+ {
+ name: "duplicate expected HTTP error response",
+ mutate: (journeys: CapturedJourney[]) =>
+ journeys.map((journey) =>
+ journey.id ===
+ "judged-flow::a rejected test releases no result and remains recoverable after refresh"
+ ? {
+ ...journey,
+ observedHttpErrorResponses: [
+ compileHttpError,
+ compileHttpError,
+ ],
+ }
+ : journey,
+ ),
+ },
+ {
+ name: "HTTP error response assigned to the wrong journey",
+ mutate: (journeys: CapturedJourney[]) => [
+ {
+ ...journeys[0]!,
+ expectedHttpErrorResponses: [artifactHttpError],
+ observedHttpErrorResponses: [artifactHttpError],
+ },
+ ...journeys.slice(1),
+ ],
+ },
+ {
+ name: "unexpected HTTP error response",
+ mutate: (journeys: CapturedJourney[]) => [
+ { ...journeys[0]!, unexpectedHttpErrorResponses: 1 },
...journeys.slice(1),
],
},
+ {
+ name: "mismatched resource console error",
+ mutate: (journeys: CapturedJourney[]) =>
+ journeys.map((journey) =>
+ journey.id ===
+ "judged-flow::a rejected test releases no result and remains recoverable after refresh"
+ ? {
+ ...journey,
+ totalConsoleErrors: 1,
+ unexpectedConsoleErrors: 1,
+ }
+ : journey,
+ ),
+ },
{
name: "missing assertion",
mutate: (journeys: CapturedJourney[]) => [
diff --git a/docs/SUBMISSION_PACKAGE.json b/docs/SUBMISSION_PACKAGE.json
index 380e2cc2..37905774 100644
--- a/docs/SUBMISSION_PACKAGE.json
+++ b/docs/SUBMISSION_PACKAGE.json
@@ -78,10 +78,15 @@
"exactReleaseBound": false,
"desktopComplete": false,
"mobileComplete": false,
- "requiredSkips": 5,
+ "requiredSkips": 8,
"failures": 0,
- "consoleErrors": 0,
- "failedRequests": 0,
+ "totalConsoleErrors": 2,
+ "expectedHttpResourceConsoleErrors": 2,
+ "unexpectedConsoleErrors": 0,
+ "expectedHttpErrorResponses": 2,
+ "observedHttpErrorResponses": 2,
+ "unexpectedHttpErrorResponses": 0,
+ "unexpectedFailedRequests": 0,
"playwrightReport": null,
"evidenceIndex": null,
"journeyEvidence": [],
diff --git a/scripts/finalize-cloakbrowser-qualification.test.ts b/scripts/finalize-cloakbrowser-qualification.test.ts
index 1af6a26e..ed819f43 100644
--- a/scripts/finalize-cloakbrowser-qualification.test.ts
+++ b/scripts/finalize-cloakbrowser-qualification.test.ts
@@ -10,6 +10,7 @@ import {
CloakBrowserExecutionReportSchema,
CloakBrowserQualificationReceiptSchema,
CloakManualEvidenceReceiptSchema,
+ REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID,
REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID,
REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID,
REQUIRED_CLOAK_JOURNEY_IDS,
@@ -100,7 +101,7 @@ async function pathExists(path: string): Promise {
function validRawRun() {
return {
- schemaVersion: "3",
+ schemaVersion: "4",
kind: "cloakbrowser-raw-run",
status: "PASSED",
authority: "CLOAKBROWSER",
@@ -112,28 +113,36 @@ function validRawRun() {
playwrightVersion: "1.61.1",
playwrightStatus: "passed",
rootErrors: 0,
- journeys: REQUIRED_CLOAK_JOURNEY_IDS.map((id, index) => ({
- id,
- status: "passed",
- expectedStatus: "passed",
- attempt: 0,
- durationMs: 1_000 + index,
- assertionCount: 2,
- viewport: REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID[id],
- consoleErrors: 0,
- expectedRequestFailures:
- REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
- expectedFailedRequests:
- REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
- failedRequests: 0,
- observedRequestFailures:
- REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
- observedFailedRequests:
- REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
- browserVersion,
- browserAuthority: "CLOAK_CDP_ENDPOINT",
- telemetryValid: true,
- })),
+ journeys: REQUIRED_CLOAK_JOURNEY_IDS.map((id, index) => {
+ const expectedHttpErrors = REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID[id];
+ return {
+ id,
+ status: "passed",
+ expectedStatus: "passed",
+ attempt: 0,
+ durationMs: 1_000 + index,
+ assertionCount: 2,
+ viewport: REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID[id],
+ totalConsoleErrors: expectedHttpErrors.length,
+ expectedHttpResourceConsoleErrors: expectedHttpErrors.length,
+ unexpectedConsoleErrors: 0,
+ expectedHttpErrorResponses: expectedHttpErrors,
+ observedHttpErrorResponses: expectedHttpErrors,
+ unexpectedHttpErrorResponses: 0,
+ expectedRequestFailures:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
+ expectedFailedRequests:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
+ unexpectedFailedRequests: 0,
+ observedRequestFailures:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
+ observedFailedRequests:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
+ browserVersion,
+ browserAuthority: "CLOAK_CDP_ENDPOINT",
+ telemetryValid: true,
+ };
+ }),
privacy: {
containsSecrets: false,
containsRawNotebook: false,
@@ -222,16 +231,37 @@ describe("CloakBrowser qualification finalizer", () => {
expect(first.files.at(-1)?.relativePath).toBe(
"cloakbrowser-qualification.json",
);
- expect(
- CloakBrowserExecutionReportSchema.parse(
- parseGenerated(first, "cloakbrowser-playwright-report.json"),
- ).rawRun.status,
- ).toBe("PASSED");
- expect(
- CloakBrowserQualificationReceiptSchema.parse(
- parseGenerated(first, "cloakbrowser-qualification.json"),
- ).webVitals,
- ).toEqual({ lcpMs: 1_200, cls: 0.02, inpMs: 90 });
+ const report = CloakBrowserExecutionReportSchema.parse(
+ parseGenerated(first, "cloakbrowser-playwright-report.json"),
+ );
+ expect(report.rawRun.status).toBe("PASSED");
+ expect(report.schemaVersion).toBe("3");
+ expect(report).toMatchObject({
+ totalConsoleErrors: 2,
+ expectedHttpResourceConsoleErrors: 2,
+ unexpectedConsoleErrors: 0,
+ expectedHttpErrorResponses: 2,
+ observedHttpErrorResponses: 2,
+ unexpectedHttpErrorResponses: 0,
+ unexpectedFailedRequests: 0,
+ });
+ const qualification = CloakBrowserQualificationReceiptSchema.parse(
+ parseGenerated(first, "cloakbrowser-qualification.json"),
+ );
+ expect(qualification.webVitals).toEqual({
+ lcpMs: 1_200,
+ cls: 0.02,
+ inpMs: 90,
+ });
+ expect(qualification).toMatchObject({
+ totalConsoleErrors: 2,
+ expectedHttpResourceConsoleErrors: 2,
+ unexpectedConsoleErrors: 0,
+ expectedHttpErrorResponses: 2,
+ observedHttpErrorResponses: 2,
+ unexpectedHttpErrorResponses: 0,
+ unexpectedFailedRequests: 0,
+ });
expect(
CloakManualEvidenceReceiptSchema.parse(
parseGenerated(first, "cloakbrowser-manual/webVitals.json"),
@@ -298,6 +328,53 @@ describe("CloakBrowser qualification finalizer", () => {
).toThrow(/status does not match/i);
});
+ it("rejects missing, duplicate, and misattributed expected HTTP evidence", () => {
+ const missing = validRawRun();
+ const expectedIndex = missing.journeys.findIndex(
+ (journey) => journey.expectedHttpErrorResponses.length === 1,
+ );
+ missing.journeys[expectedIndex]!.observedHttpErrorResponses = [];
+ expect(() =>
+ buildCloakBrowserQualification({
+ checkedAt,
+ deployedAt,
+ manualManifest: validManualManifest(),
+ rawRun: missing,
+ release,
+ }),
+ ).toThrow();
+
+ const duplicate = validRawRun();
+ const duplicateJourney = duplicate.journeys[expectedIndex]!;
+ duplicateJourney.observedHttpErrorResponses = [
+ ...duplicateJourney.expectedHttpErrorResponses,
+ ...duplicateJourney.expectedHttpErrorResponses,
+ ];
+ expect(() =>
+ buildCloakBrowserQualification({
+ checkedAt,
+ deployedAt,
+ manualManifest: validManualManifest(),
+ rawRun: duplicate,
+ release,
+ }),
+ ).toThrow();
+
+ const misattributed = validRawRun();
+ misattributed.journeys[expectedIndex]!.expectedHttpResourceConsoleErrors =
+ 2;
+ misattributed.journeys[expectedIndex]!.totalConsoleErrors = 2;
+ expect(() =>
+ buildCloakBrowserQualification({
+ checkedAt,
+ deployedAt,
+ manualManifest: validManualManifest(),
+ rawRun: misattributed,
+ release,
+ }),
+ ).toThrow(/console error counts/i);
+ });
+
it("rejects observations before deployment or after finalization", () => {
expect(() =>
buildCloakBrowserQualification({
@@ -339,6 +416,24 @@ describe("CloakBrowser qualification finalizer", () => {
/hash does not match/i,
);
+ const aggregate = parseGenerated(
+ built,
+ "cloakbrowser-playwright-report.json",
+ ) as Record;
+ aggregate.totalConsoleErrors = 1;
+ expect(() => CloakBrowserExecutionReportSchema.parse(aggregate)).toThrow(
+ /error totals/i,
+ );
+
+ const qualification = parseGenerated(
+ built,
+ "cloakbrowser-qualification.json",
+ ) as Record;
+ qualification.totalConsoleErrors = 1;
+ expect(() =>
+ CloakBrowserQualificationReceiptSchema.parse(qualification),
+ ).toThrow(/error totals/i);
+
const manual = parseGenerated(
built,
"cloakbrowser-manual/keyboard.json",
diff --git a/scripts/finalize-cloakbrowser-qualification.ts b/scripts/finalize-cloakbrowser-qualification.ts
index e5ac7355..1d7788a3 100644
--- a/scripts/finalize-cloakbrowser-qualification.ts
+++ b/scripts/finalize-cloakbrowser-qualification.ts
@@ -165,6 +165,37 @@ export function buildCloakBrowserQualification(input: {
}
const rawRunCanonicalSha256 = sha256(canonicalJson(rawRun));
const browserVersion = rawRun.journeys[0]!.browserVersion;
+ const errorTotals = rawRun.journeys.reduce(
+ (aggregate, journey) => ({
+ totalConsoleErrors:
+ aggregate.totalConsoleErrors + journey.totalConsoleErrors,
+ expectedHttpResourceConsoleErrors:
+ aggregate.expectedHttpResourceConsoleErrors +
+ journey.expectedHttpResourceConsoleErrors,
+ unexpectedConsoleErrors:
+ aggregate.unexpectedConsoleErrors + journey.unexpectedConsoleErrors,
+ expectedHttpErrorResponses:
+ aggregate.expectedHttpErrorResponses +
+ journey.expectedHttpErrorResponses.length,
+ observedHttpErrorResponses:
+ aggregate.observedHttpErrorResponses +
+ journey.observedHttpErrorResponses.length,
+ unexpectedHttpErrorResponses:
+ aggregate.unexpectedHttpErrorResponses +
+ journey.unexpectedHttpErrorResponses,
+ unexpectedFailedRequests:
+ aggregate.unexpectedFailedRequests + journey.unexpectedFailedRequests,
+ }),
+ {
+ totalConsoleErrors: 0,
+ expectedHttpResourceConsoleErrors: 0,
+ unexpectedConsoleErrors: 0,
+ expectedHttpErrorResponses: 0,
+ observedHttpErrorResponses: 0,
+ unexpectedHttpErrorResponses: 0,
+ unexpectedFailedRequests: 0,
+ },
+ );
if (
manualManifest.baseUrl !== rawRun.baseUrl ||
canonicalJson(manualManifest.release) !== canonicalJson(release) ||
@@ -204,8 +235,13 @@ export function buildCloakBrowserQualification(input: {
attempt: 0,
durationMs: raw.durationMs,
assertionCount: raw.assertionCount,
- consoleErrors: 0,
- failedRequests: 0,
+ totalConsoleErrors: raw.totalConsoleErrors,
+ expectedHttpResourceConsoleErrors: raw.expectedHttpResourceConsoleErrors,
+ unexpectedConsoleErrors: raw.unexpectedConsoleErrors,
+ expectedHttpErrorResponses: raw.expectedHttpErrorResponses,
+ observedHttpErrorResponses: raw.observedHttpErrorResponses,
+ unexpectedHttpErrorResponses: raw.unexpectedHttpErrorResponses,
+ unexpectedFailedRequests: raw.unexpectedFailedRequests,
});
const file = generatedFile(
`cloakbrowser-journeys/${String(index + 1).padStart(2, "0")}.json`,
@@ -223,7 +259,7 @@ export function buildCloakBrowserQualification(input: {
});
const executionReport = CloakBrowserExecutionReportSchema.parse({
- schemaVersion: "2",
+ schemaVersion: "3",
kind: "cloakbrowser-execution-report",
status: "PASSED",
checkedAt,
@@ -239,8 +275,7 @@ export function buildCloakBrowserQualification(input: {
failures: 0,
skips: 0,
retries: 0,
- consoleErrors: 0,
- failedRequests: 0,
+ ...errorTotals,
});
const executionReportFile = generatedFile(
"cloakbrowser-playwright-report.json",
@@ -318,8 +353,7 @@ export function buildCloakBrowserQualification(input: {
touchTargetsComplete: true,
requiredSkips: 0,
failures: 0,
- consoleErrors: 0,
- failedRequests: 0,
+ ...errorTotals,
webVitals: {
lcpMs: webVitals.lcpMs,
cls: webVitals.cls,
diff --git a/scripts/submission-publication-evidence.ts b/scripts/submission-publication-evidence.ts
index a3ede958..098b54ac 100644
--- a/scripts/submission-publication-evidence.ts
+++ b/scripts/submission-publication-evidence.ts
@@ -31,6 +31,13 @@ const PublicationPrivacySchema = z
})
.strict();
+export const ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS = [
+ "409 POST /api/sessions/:sessionId/lab/compile",
+ "422 POST /api/artifacts",
+] as const;
+export type AllowedCloakExpectedHttpError =
+ (typeof ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS)[number];
+
function canonicalSha256(value: unknown): string {
return createHash("sha256").update(canonicalJson(value)).digest("hex");
}
@@ -129,10 +136,41 @@ export const CloakJourneyEvidenceReceiptSchema = z
attempt: z.literal(0),
durationMs: z.number().int().positive(),
assertionCount: z.number().int().positive(),
- consoleErrors: z.literal(0),
- failedRequests: z.literal(0),
+ totalConsoleErrors: z.number().int().nonnegative(),
+ expectedHttpResourceConsoleErrors: z.number().int().nonnegative(),
+ unexpectedConsoleErrors: z.literal(0),
+ expectedHttpErrorResponses: z
+ .array(z.enum(ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS))
+ .max(ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS.length),
+ observedHttpErrorResponses: z
+ .array(z.enum(ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS))
+ .max(ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS.length),
+ unexpectedHttpErrorResponses: z.literal(0),
+ unexpectedFailedRequests: z.literal(0),
})
- .strict();
+ .strict()
+ .superRefine((journey, context) => {
+ if (
+ journey.totalConsoleErrors !==
+ journey.expectedHttpResourceConsoleErrors ||
+ JSON.stringify(journey.expectedHttpErrorResponses) !==
+ JSON.stringify(journey.observedHttpErrorResponses) ||
+ JSON.stringify(journey.expectedHttpErrorResponses) !==
+ JSON.stringify(
+ REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID[
+ journey.id as RequiredCloakJourneyId
+ ],
+ ) ||
+ journey.expectedHttpResourceConsoleErrors >
+ journey.observedHttpErrorResponses.length
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["totalConsoleErrors"],
+ message: "journey error evidence does not reconcile",
+ });
+ }
+ });
export const REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS = [
"keyboard",
@@ -169,8 +207,9 @@ export const REQUIRED_CLOAK_MANUAL_CRITERIA = {
],
touchTargets: ["interactiveTargetsMeasured", "minimum44Px"],
consoleAndNetwork: [
- "consoleErrorsZero",
+ "unexpectedConsoleErrorsZero",
"pageErrorsZero",
+ "expectedHttpErrorsReconciled",
"unexpectedRequestsZero",
],
webVitals: ["lcpMeasured", "clsMeasured", "inpMeasured"],
@@ -420,6 +459,24 @@ export const REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID = Object.freeze(
>,
);
+const cloakExpectedHttpErrorOverrides: Partial<
+ Record
+> = {
+ "judged-flow::a rejected test releases no result and remains recoverable after refresh":
+ ["409 POST /api/sessions/:sessionId/lab/compile"],
+ "recovery-and-intake::malformed live upload remains at intake and creates no session":
+ ["422 POST /api/artifacts"],
+};
+
+export const REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID = Object.freeze(
+ Object.fromEntries(
+ REQUIRED_CLOAK_JOURNEY_IDS.map((id) => [
+ id,
+ Object.freeze([...(cloakExpectedHttpErrorOverrides[id] ?? [])]),
+ ]),
+ ) as Record,
+);
+
function addCloakJourneyIssues(
journeys: readonly z.infer[],
context: z.RefinementCtx,
@@ -481,12 +538,21 @@ export const CloakBrowserRawJourneySchema = z
durationMs: z.number().int().nonnegative(),
assertionCount: z.number().int().nonnegative(),
viewport: z.string().regex(/^\d+x\d+$/u),
- consoleErrors: z.number().int().nonnegative(),
+ totalConsoleErrors: z.number().int().nonnegative(),
+ expectedHttpResourceConsoleErrors: z.number().int().nonnegative(),
+ unexpectedConsoleErrors: z.number().int().nonnegative(),
+ expectedHttpErrorResponses: z
+ .array(z.enum(ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS))
+ .max(ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS.length),
+ observedHttpErrorResponses: z
+ .array(z.enum(ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS))
+ .max(ALLOWED_CLOAK_EXPECTED_HTTP_ERRORS.length),
+ unexpectedHttpErrorResponses: z.number().int().nonnegative(),
expectedRequestFailures: z
.array(z.enum(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES))
.max(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES.length),
expectedFailedRequests: z.number().int().nonnegative(),
- failedRequests: z.number().int().nonnegative(),
+ unexpectedFailedRequests: z.number().int().nonnegative(),
observedRequestFailures: z
.array(z.enum(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES))
.max(ALLOWED_CLOAK_EXPECTED_REQUEST_FAILURES.length),
@@ -499,11 +565,26 @@ export const CloakBrowserRawJourneySchema = z
]),
telemetryValid: z.boolean(),
})
- .strict();
+ .strict()
+ .superRefine((journey, context) => {
+ if (
+ journey.totalConsoleErrors !==
+ journey.expectedHttpResourceConsoleErrors +
+ journey.unexpectedConsoleErrors ||
+ journey.expectedHttpResourceConsoleErrors >
+ journey.observedHttpErrorResponses.length
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["totalConsoleErrors"],
+ message: "console error counts do not match their attributed evidence",
+ });
+ }
+ });
export const CloakBrowserRawRunSchema = z
.object({
- schemaVersion: z.literal("3"),
+ schemaVersion: z.literal("4"),
kind: z.literal("cloakbrowser-raw-run"),
status: z.enum(["PASSED", "FAILED", "NON_QUALIFYING"]),
authority: z.enum(["CLOAKBROWSER", "STOCK_CHROMIUM_DESIGN_REVIEW"]),
@@ -549,7 +630,16 @@ export const CloakBrowserRawRunSchema = z
journey.attempt === 0 &&
journey.durationMs > 0 &&
journey.assertionCount > 0 &&
- journey.consoleErrors === 0 &&
+ journey.unexpectedConsoleErrors === 0 &&
+ JSON.stringify(journey.expectedHttpErrorResponses) ===
+ JSON.stringify(
+ REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID[
+ journey.id as RequiredCloakJourneyId
+ ],
+ ) &&
+ JSON.stringify(journey.observedHttpErrorResponses) ===
+ JSON.stringify(journey.expectedHttpErrorResponses) &&
+ journey.unexpectedHttpErrorResponses === 0 &&
journey.expectedFailedRequests ===
journey.expectedRequestFailures.length &&
JSON.stringify(journey.expectedRequestFailures) ===
@@ -558,7 +648,7 @@ export const CloakBrowserRawRunSchema = z
journey.id as RequiredCloakJourneyId
],
) &&
- journey.failedRequests === 0 &&
+ journey.unexpectedFailedRequests === 0 &&
JSON.stringify(journey.observedRequestFailures) ===
JSON.stringify(journey.expectedRequestFailures) &&
journey.observedFailedRequests === journey.expectedFailedRequests &&
@@ -587,7 +677,7 @@ export const CloakBrowserRawRunSchema = z
export const CloakBrowserExecutionReportSchema = z
.object({
- schemaVersion: z.literal("2"),
+ schemaVersion: z.literal("3"),
kind: z.literal("cloakbrowser-execution-report"),
status: z.literal("PASSED"),
checkedAt: z.iso.datetime({ offset: true }),
@@ -603,8 +693,13 @@ export const CloakBrowserExecutionReportSchema = z
failures: z.literal(0),
skips: z.literal(0),
retries: z.literal(0),
- consoleErrors: z.literal(0),
- failedRequests: z.literal(0),
+ totalConsoleErrors: z.number().int().nonnegative(),
+ expectedHttpResourceConsoleErrors: z.number().int().nonnegative(),
+ unexpectedConsoleErrors: z.literal(0),
+ expectedHttpErrorResponses: z.number().int().nonnegative(),
+ observedHttpErrorResponses: z.number().int().nonnegative(),
+ unexpectedHttpErrorResponses: z.literal(0),
+ unexpectedFailedRequests: z.literal(0),
})
.strict()
.superRefine((report, context) => {
@@ -632,6 +727,54 @@ export const CloakBrowserExecutionReportSchema = z
message: "raw run does not match its execution report",
});
}
+ const totals = report.rawRun.journeys.reduce(
+ (aggregate, journey) => ({
+ totalConsoleErrors:
+ aggregate.totalConsoleErrors + journey.totalConsoleErrors,
+ expectedHttpResourceConsoleErrors:
+ aggregate.expectedHttpResourceConsoleErrors +
+ journey.expectedHttpResourceConsoleErrors,
+ unexpectedConsoleErrors:
+ aggregate.unexpectedConsoleErrors + journey.unexpectedConsoleErrors,
+ expectedHttpErrorResponses:
+ aggregate.expectedHttpErrorResponses +
+ journey.expectedHttpErrorResponses.length,
+ observedHttpErrorResponses:
+ aggregate.observedHttpErrorResponses +
+ journey.observedHttpErrorResponses.length,
+ unexpectedHttpErrorResponses:
+ aggregate.unexpectedHttpErrorResponses +
+ journey.unexpectedHttpErrorResponses,
+ unexpectedFailedRequests:
+ aggregate.unexpectedFailedRequests + journey.unexpectedFailedRequests,
+ }),
+ {
+ totalConsoleErrors: 0,
+ expectedHttpResourceConsoleErrors: 0,
+ unexpectedConsoleErrors: 0,
+ expectedHttpErrorResponses: 0,
+ observedHttpErrorResponses: 0,
+ unexpectedHttpErrorResponses: 0,
+ unexpectedFailedRequests: 0,
+ },
+ );
+ if (
+ report.totalConsoleErrors !== totals.totalConsoleErrors ||
+ report.expectedHttpResourceConsoleErrors !==
+ totals.expectedHttpResourceConsoleErrors ||
+ report.unexpectedConsoleErrors !== totals.unexpectedConsoleErrors ||
+ report.expectedHttpErrorResponses !== totals.expectedHttpErrorResponses ||
+ report.observedHttpErrorResponses !== totals.observedHttpErrorResponses ||
+ report.unexpectedHttpErrorResponses !==
+ totals.unexpectedHttpErrorResponses ||
+ report.unexpectedFailedRequests !== totals.unexpectedFailedRequests
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["totalConsoleErrors"],
+ message: "execution error totals do not match the raw journeys",
+ });
+ }
for (const [index, journey] of report.journeys.entries()) {
const raw = report.rawRun.journeys[index];
if (
@@ -718,8 +861,13 @@ export const CloakBrowserQualificationReceiptSchema = z
touchTargetsComplete: z.literal(true),
requiredSkips: z.literal(0),
failures: z.literal(0),
- consoleErrors: z.literal(0),
- failedRequests: z.literal(0),
+ totalConsoleErrors: z.number().int().nonnegative(),
+ expectedHttpResourceConsoleErrors: z.number().int().nonnegative(),
+ unexpectedConsoleErrors: z.literal(0),
+ expectedHttpErrorResponses: z.number().int().nonnegative(),
+ observedHttpErrorResponses: z.number().int().nonnegative(),
+ unexpectedHttpErrorResponses: z.literal(0),
+ unexpectedFailedRequests: z.literal(0),
webVitals: z
.object({
lcpMs: z.number().nonnegative().max(2_500),
@@ -738,6 +886,23 @@ export const CloakBrowserQualificationReceiptSchema = z
});
}
addCloakJourneyIssues(receipt.journeys, context);
+ const requiredHttpErrorCount = Object.values(
+ REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID,
+ ).reduce((count, errors) => count + errors.length, 0);
+ if (
+ receipt.totalConsoleErrors !==
+ receipt.expectedHttpResourceConsoleErrors ||
+ receipt.expectedHttpResourceConsoleErrors >
+ receipt.observedHttpErrorResponses ||
+ receipt.expectedHttpErrorResponses !== requiredHttpErrorCount ||
+ receipt.observedHttpErrorResponses !== receipt.expectedHttpErrorResponses
+ ) {
+ context.addIssue({
+ code: "custom",
+ path: ["totalConsoleErrors"],
+ message: "qualification error totals do not reconcile",
+ });
+ }
});
const PublicLinkResultSchema = z
diff --git a/scripts/validate-submission-package.test.ts b/scripts/validate-submission-package.test.ts
index 45a37f96..4756b3cc 100644
--- a/scripts/validate-submission-package.test.ts
+++ b/scripts/validate-submission-package.test.ts
@@ -9,6 +9,7 @@ import { RELEASE_CHECK_IDS } from "../packages/scientific-engine-registry/src/sc
import { canonicalJson } from "../packages/session-core/src/index.js";
import { createGenerationIsolationEvidence } from "./generation-isolation-evidence.js";
import {
+ REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID,
REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID,
REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID,
REQUIRED_CLOAK_JOURNEY_IDS,
@@ -530,7 +531,7 @@ async function readyPackage(
);
const browserVersion = "CloakBrowser Chromium 140.0.0.0";
const rawRun = {
- schemaVersion: "3",
+ schemaVersion: "4",
kind: "cloakbrowser-raw-run",
status: "PASSED",
authority: "CLOAKBROWSER",
@@ -542,28 +543,36 @@ async function readyPackage(
playwrightVersion: "1.61.1",
playwrightStatus: "passed",
rootErrors: 0,
- journeys: REQUIRED_CLOAK_JOURNEY_IDS.map((id, index) => ({
- id,
- status: "passed",
- expectedStatus: "passed",
- attempt: 0,
- durationMs: 1_000 + index,
- assertionCount: 3,
- viewport: REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID[id],
- consoleErrors: 0,
- expectedRequestFailures:
- REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
- expectedFailedRequests:
- REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
- failedRequests: 0,
- observedRequestFailures:
- REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
- observedFailedRequests:
- REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
- browserVersion,
- browserAuthority: "CLOAK_CDP_ENDPOINT",
- telemetryValid: true,
- })),
+ journeys: REQUIRED_CLOAK_JOURNEY_IDS.map((id, index) => {
+ const expectedHttpErrors = REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID[id];
+ return {
+ id,
+ status: "passed",
+ expectedStatus: "passed",
+ attempt: 0,
+ durationMs: 1_000 + index,
+ assertionCount: 3,
+ viewport: REQUIRED_CLOAK_JOURNEY_VIEWPORT_BY_ID[id],
+ totalConsoleErrors: expectedHttpErrors.length,
+ expectedHttpResourceConsoleErrors: expectedHttpErrors.length,
+ unexpectedConsoleErrors: 0,
+ expectedHttpErrorResponses: expectedHttpErrors,
+ observedHttpErrorResponses: expectedHttpErrors,
+ unexpectedHttpErrorResponses: 0,
+ expectedRequestFailures:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
+ expectedFailedRequests:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
+ unexpectedFailedRequests: 0,
+ observedRequestFailures:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id],
+ observedFailedRequests:
+ REQUIRED_CLOAK_EXPECTED_REQUEST_FAILURES_BY_ID[id].length,
+ browserVersion,
+ browserAuthority: "CLOAK_CDP_ENDPOINT",
+ telemetryValid: true,
+ };
+ }),
privacy: publicationPrivacy,
} as const;
const rawRunCanonicalSha256 = hash(bytes(canonicalJson(rawRun)));
@@ -752,8 +761,19 @@ async function readyPackage(
mobileComplete: true,
requiredSkips: 0,
failures: 0,
- consoleErrors: 0,
- failedRequests: 0,
+ totalConsoleErrors: browserBuild.qualification.totalConsoleErrors,
+ expectedHttpResourceConsoleErrors:
+ browserBuild.qualification.expectedHttpResourceConsoleErrors,
+ unexpectedConsoleErrors:
+ browserBuild.qualification.unexpectedConsoleErrors,
+ expectedHttpErrorResponses:
+ browserBuild.qualification.expectedHttpErrorResponses,
+ observedHttpErrorResponses:
+ browserBuild.qualification.observedHttpErrorResponses,
+ unexpectedHttpErrorResponses:
+ browserBuild.qualification.unexpectedHttpErrorResponses,
+ unexpectedFailedRequests:
+ browserBuild.qualification.unexpectedFailedRequests,
playwrightReport: browserReport,
evidenceIndex: browserEvidenceIndex,
journeyEvidence,
@@ -1235,6 +1255,45 @@ describe("submission package validator", () => {
});
});
+ it("rejects hidden or unexpected browser telemetry in the package", async () => {
+ const mutations: Array<(browser: Record) => void> = [
+ (browser) => {
+ browser.totalConsoleErrors = 0;
+ },
+ (browser) => {
+ browser.observedHttpErrorResponses = 1;
+ },
+ (browser) => {
+ browser.unexpectedConsoleErrors = 1;
+ },
+ (browser) => {
+ browser.unexpectedHttpErrorResponses = 1;
+ },
+ (browser) => {
+ browser.unexpectedFailedRequests = 1;
+ },
+ ];
+
+ for (const mutate of mutations) {
+ const fixture = await readyPackage();
+ mutate(
+ fixture.ready.browserQualification as unknown as Record<
+ string,
+ unknown
+ >,
+ );
+ await expect(
+ validateSubmissionPackage(fixture.ready, "ready", fixture.reader, {
+ now: new Date("2026-07-21T12:00:00.000Z"),
+ }),
+ ).rejects.toMatchObject({
+ issues: expect.arrayContaining([
+ "browser qualification must be exact, complete CloakBrowser evidence",
+ ]),
+ });
+ }
+ });
+
it("rejects manual observations bound to a different raw browser run", async () => {
const fixture = await readyPackage();
replaceManualEvidence(fixture, "keyboard", (artifact) => {
diff --git a/scripts/validate-submission-package.ts b/scripts/validate-submission-package.ts
index cbef7135..4a824d72 100644
--- a/scripts/validate-submission-package.ts
+++ b/scripts/validate-submission-package.ts
@@ -20,6 +20,7 @@ import {
PostSubmitLinkAuditReceiptSchema,
PublicationReleaseBindingSchema,
PublicLinkAuditReceiptSchema,
+ REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID,
REQUIRED_CLOAK_MANUAL_EVIDENCE_KEYS,
RepositoryAccessReceiptSchema,
ScreenshotProvenanceReceiptSchema,
@@ -176,8 +177,13 @@ export const SubmissionPackageSchema = z
mobileComplete: z.boolean(),
requiredSkips: z.number().int().nonnegative(),
failures: z.number().int().nonnegative(),
- consoleErrors: z.number().int().nonnegative(),
- failedRequests: z.number().int().nonnegative(),
+ totalConsoleErrors: z.number().int().nonnegative(),
+ expectedHttpResourceConsoleErrors: z.number().int().nonnegative(),
+ unexpectedConsoleErrors: z.number().int().nonnegative(),
+ expectedHttpErrorResponses: z.number().int().nonnegative(),
+ observedHttpErrorResponses: z.number().int().nonnegative(),
+ unexpectedHttpErrorResponses: z.number().int().nonnegative(),
+ unexpectedFailedRequests: z.number().int().nonnegative(),
playwrightReport: NullableEvidence,
evidenceIndex: NullableEvidence,
journeyEvidence: z.array(BrowserJourneyEvidenceReferenceSchema).max(40),
@@ -705,6 +711,9 @@ function requireReadyFields(
issues.push("video must include audio and both Codex and GPT-5.6 roles");
}
const browser = submission.browserQualification;
+ const requiredHttpErrorResponses = Object.values(
+ REQUIRED_CLOAK_EXPECTED_HTTP_ERRORS_BY_ID,
+ ).reduce((count, errors) => count + errors.length, 0);
if (
browser.authority !== "CLOAKBROWSER" ||
!browser.exactReleaseBound ||
@@ -712,8 +721,14 @@ function requireReadyFields(
!browser.mobileComplete ||
browser.requiredSkips !== 0 ||
browser.failures !== 0 ||
- browser.consoleErrors !== 0 ||
- browser.failedRequests !== 0
+ browser.totalConsoleErrors !== browser.expectedHttpResourceConsoleErrors ||
+ browser.expectedHttpResourceConsoleErrors >
+ browser.observedHttpErrorResponses ||
+ browser.unexpectedConsoleErrors !== 0 ||
+ browser.expectedHttpErrorResponses !== requiredHttpErrorResponses ||
+ browser.observedHttpErrorResponses !== requiredHttpErrorResponses ||
+ browser.unexpectedHttpErrorResponses !== 0 ||
+ browser.unexpectedFailedRequests !== 0
) {
issues.push(
"browser qualification must be exact, complete CloakBrowser evidence",
@@ -1838,8 +1853,17 @@ function assertPublicationEvidence(
browser.mobileComplete !== manifest.mobileComplete ||
browser.requiredSkips !== manifest.requiredSkips ||
browser.failures !== manifest.failures ||
- browser.consoleErrors !== manifest.consoleErrors ||
- browser.failedRequests !== manifest.failedRequests ||
+ browser.totalConsoleErrors !== manifest.totalConsoleErrors ||
+ browser.expectedHttpResourceConsoleErrors !==
+ manifest.expectedHttpResourceConsoleErrors ||
+ browser.unexpectedConsoleErrors !== manifest.unexpectedConsoleErrors ||
+ browser.expectedHttpErrorResponses !==
+ manifest.expectedHttpErrorResponses ||
+ browser.observedHttpErrorResponses !==
+ manifest.observedHttpErrorResponses ||
+ browser.unexpectedHttpErrorResponses !==
+ manifest.unexpectedHttpErrorResponses ||
+ browser.unexpectedFailedRequests !== manifest.unexpectedFailedRequests ||
browserReportReference === null ||
browserIndexReference === null ||
browser.playwrightReportSha256 !== browserReportReference.sha256 ||
@@ -1861,8 +1885,19 @@ function assertPublicationEvidence(
browser.playwrightVersion !== browserReport.playwrightVersion ||
browser.failures !== browserReport.failures ||
browser.requiredSkips !== browserReport.skips ||
- browser.consoleErrors !== browserReport.consoleErrors ||
- browser.failedRequests !== browserReport.failedRequests ||
+ browser.totalConsoleErrors !== browserReport.totalConsoleErrors ||
+ browser.expectedHttpResourceConsoleErrors !==
+ browserReport.expectedHttpResourceConsoleErrors ||
+ browser.unexpectedConsoleErrors !==
+ browserReport.unexpectedConsoleErrors ||
+ browser.expectedHttpErrorResponses !==
+ browserReport.expectedHttpErrorResponses ||
+ browser.observedHttpErrorResponses !==
+ browserReport.observedHttpErrorResponses ||
+ browser.unexpectedHttpErrorResponses !==
+ browserReport.unexpectedHttpErrorResponses ||
+ browser.unexpectedFailedRequests !==
+ browserReport.unexpectedFailedRequests ||
JSON.stringify(browser.journeys) !==
JSON.stringify(browserReport.journeys) ||
Date.parse(browserReport.checkedAt) > Date.parse(browser.checkedAt))
From d365118e76fd98ae6c516944753db28b8da52881 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 17:52:45 +0530
Subject: [PATCH 104/143] docs(progress): record CloakBrowser recovery
---
docs/PROGRESS.md | 96 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 96 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 6a7f95fa..412d2e51 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4387,3 +4387,99 @@ No clean source, exact image, aggregate containment pass, Cloudflare identity,
deployment, CloakBrowser journey, learner outcome, submission, push, or merge
is claimed at this checkpoint. Release remains **NO-GO** until the ordered
dependencies above are closed with direct evidence.
+
+## CloakBrowser recovery and HTTP telemetry checkpoint — 2026-07-21T12:21:37Z
+
+Deadline: `2026-07-22T00:00:00Z`; **11 hours 38 minutes 23 seconds remained at
+this checkpoint**. Committed HEAD is
+`99e9741e71e8fd2955543f729a11f02c18fcebf0`, 21 commits ahead of
+`origin/feat/learner-ux-v6.1` and 103 commits ahead of local `main`, with no
+reverse divergence. The worktree has two untracked paths: the untouched FUSE
+tombstone `scripts/.fuse_hidden0000a9600000801f` and the agent-owned active
+CloakBrowser runtime `.runtime/`. Neither is staged. The browser runtime will
+be stopped and moved into ignored repository-local cache state without
+deleting the tombstone or any user file.
+
+### Completed and committed
+
+| Commit | Result | Exact verification |
+| --- | --- | --- |
+| `faa2c2a test(browser): require exact live intake authority` | Three result-bearing live-intake journeys now require explicit live authority instead of treating mocked readiness as a live run. | Focused telemetry diagnosis **3/3 passed**. |
+| `9ee49ee test(browser): await verified action responses` | Refresh-sensitive journeys await the exact successful action response and a fresh network-quiescent interval before reloading. | Focused action-wait checks **2/2 passed**; E2E TypeScript passed. |
+| `99e9741 test(browser): reconcile expected HTTP failures` | Raw schema v4 and report schema v3 retain intentional `409` compile rejection and `422` malformed-upload responses as visible, journey-bound evidence while failing closed on every unexpected console, HTTP, page, or transport failure. Response and Chromium resource errors correlate one-to-one by an exact ephemeral URL before only normalized route tokens are persisted. | Reporter **36/36 passed**; finalizer/submission **48/48 passed**; repository, web, and E2E TypeScript passed; draft submission preflight, scoped Prettier, Git whitespace, and the explicit 11-file secret scan passed. Independent review found no remaining P0/P1 in the slice. |
+
+The final pre-commit stock-Chromium design-review matrix completed **40 tests:
+32 passed, 8 exact live/public skips, 0 failed**. It recorded two intentional
+resource-console errors bound to the exact two intentional HTTP responses and
+zero unexpected console, HTTP, or request failures. It remains explicitly
+non-qualifying.
+
+### CloakBrowser and real local Worker evidence
+
+- The repository-mandated CloakBrowser executable is available and reports
+ Chromium `146.0.7680.177`. It was launched headlessly with profile, cache,
+ temporary files, and FFmpeg wholly inside the repository and exposed only on
+ loopback CDP. Playwright connected through that endpoint and recorded real
+ video artifacts.
+- The first local Vite/Worker attempt stopped before browser execution because
+ this host cannot create Docker bridge interfaces. A second static-client run
+ correctly demonstrated that a static server cannot satisfy Worker API POSTs;
+ it was stopped and is not product evidence.
+- The built Worker then started through Wrangler 4.110.0 with Containers
+ explicitly disabled, all nine local D1 migrations applied, and local D1,
+ R2, and Durable Object bindings active. The full CloakBrowser matrix ran
+ **40 tests: 26 passed, 8 failed, 6 exact deployment/live skips**.
+- Three failures expose a real transient `503 EVIDENCE_SNAPSHOT_BUSY` race on
+ `GET /api/sessions/:id/events` while Sample compile advances the evidence
+ chain twice. The immediate retry returns the complete six-event chain. This
+ response is not allowlisted and must be repaired.
+- Three intake failures are local-launch configuration, not parser/UI drift:
+ the externally started Worker omitted the required 32-byte test-only
+ `COUNTERLAB_ADMISSION_KEY`, so admission failed closed before malformed or
+ unsupported notebook parsing. Existing parser and UI tests still verify the
+ exact reason codes and no-session behavior. The Worker will be restarted
+ with the contained test binding and those journeys rerun.
+- Two mobile replay failures expose a current contract mismatch: the real
+ local legacy replay renders `Replay status`, `Replay verified session`, and
+ a read-only `Continue replay` presentation, while the public-mobile spec
+ accepts only the newer Capsule banner/heading and forbids that control. This
+ remains open pending a source contract decision and exact-public replay
+ validation.
+
+This is the first current-source rendered run through the actual
+CloakBrowser binary, but it targets a local Worker without a qualified
+deployment receipt. The qualifying public count therefore remains **0**.
+
+### Next actions in strict order
+
+1. Repair the bounded evidence-snapshot race without weakening chain or
+ compiler-event validation; add finite-race and perpetual-mutation tests,
+ then rerun the Sample Cloak journeys.
+2. Restart the contained local Worker with its test-only admission binding and
+ rerun malformed, unsupported, and supported intake journeys.
+3. Resolve the public-mobile replay contract against the intended read-only
+ experience and rerun both mobile history/deep-link journeys.
+4. Stop agent-owned browser/Worker services, move `.runtime/` into ignored
+ repository cache state, and confirm that the FUSE tombstone is the only
+ remaining worktree entry.
+5. Have the owning process release the FUSE handle naturally; freeze a truly
+ clean source `S`. Do not delete, hide, stage, ignore, or rewrite it.
+6. Obtain constitution-compatible aggregate-cgroup/owned-process observation,
+ build one exact image, run the real sentinel and negative controls, refresh
+ all scientific/SBOM/VEX/license/health evidence once, and pass the full
+ deterministic release gate.
+7. Authenticate repository-contained Wrangler, qualify and deploy only the
+ exact `S`/image/evidence tuple, and prove public release identity and
+ readiness.
+8. Run all 40 public CloakBrowser journeys and the ten factual manual
+ accessibility/performance observations with no skips, retries, or
+ unexpected failures. Capture exact-release screenshots and video.
+9. Keep learner impact `NO_DATA` unless real consented observations occur;
+ complete logged-out repo/app/video/feedback/Devpost verification and capture
+ the submission receipt.
+10. Push the feature branch and fast-forward-only merge and push `main` only
+ after every upstream release and submission gate is factual.
+
+No exact image, aggregate-containment pass, Cloudflare identity, deployment,
+public qualification, learner outcome, Devpost submission, push, or merge is
+claimed. Release remains **NO-GO** while those dependencies are red.
From d7d23afe9dbf828ca75bf92925a5f9a0a104144e Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 17:58:58 +0530
Subject: [PATCH 105/143] fix(worker): stabilize evidence snapshots
---
apps/web/worker/api.test.ts | 67 ++++++++++++++++++++++++++++++++++++-
apps/web/worker/api.ts | 7 +++-
2 files changed, 72 insertions(+), 2 deletions(-)
diff --git a/apps/web/worker/api.test.ts b/apps/web/worker/api.test.ts
index 36258dfe..b6e2e6b8 100644
--- a/apps/web/worker/api.test.ts
+++ b/apps/web/worker/api.test.ts
@@ -420,6 +420,7 @@ class MemoryRunnerJobRepository implements RunnerJobRepository {
private readonly events = new Map();
private readonly callbacks = new Map();
private mutateHistoryAtSnapshotEnd = false;
+ private historySnapshotMutationsRemaining = 0;
private historyReads = 0;
async create(job: RunnerJob): Promise {
@@ -479,7 +480,11 @@ class MemoryRunnerJobRepository implements RunnerJobRepository {
async findForSession(sessionId: string): Promise {
this.historyReads += 1;
- if (this.mutateHistoryAtSnapshotEnd && this.historyReads % 2 === 0) {
+ if (
+ this.historyReads % 2 === 0 &&
+ (this.mutateHistoryAtSnapshotEnd ||
+ this.historySnapshotMutationsRemaining > 0)
+ ) {
const current = [...this.jobs.values()].find(
(job) => job.sessionId === sessionId,
);
@@ -489,6 +494,9 @@ class MemoryRunnerJobRepository implements RunnerJobRepository {
jobVersion: current.jobVersion + 1,
updatedAt: new Date(Date.parse(current.updatedAt) + 1).toISOString(),
});
+ if (this.historySnapshotMutationsRemaining > 0) {
+ this.historySnapshotMutationsRemaining -= 1;
+ }
}
}
return structuredClone(
@@ -501,6 +509,11 @@ class MemoryRunnerJobRepository implements RunnerJobRepository {
this.historyReads = 0;
}
+ mutateHistorySnapshotEnds(times: number): void {
+ this.historySnapshotMutationsRemaining = times;
+ this.historyReads = 0;
+ }
+
async findForState(input: {
sessionId: string;
kind: RunnerJobKind;
@@ -11761,6 +11774,58 @@ describe("Cloudflare Worker API", () => {
});
});
+ it("returns a complete verified snapshot after two bounded history changes", async () => {
+ const { app, artifactId, sessionId, runnerJobs } =
+ await sessionHarness("sample");
+ const route = `/api/sessions/${sessionId}`;
+ await postJson(app, `${route}/belief-test`, {
+ learnerClaim: SAMPLE_LEAKAGE_QUESTION,
+ });
+ await postJson(app, `${route}/belief-test/confirm`, { action: "confirm" });
+ await postJson(app, `${route}/prediction`, {
+ choice: "Accuracy remains near 98%",
+ confidence: 72,
+ });
+ await postJson(app, `${route}/lab/compile`);
+ await runnerJobs.create(
+ RunnerJobSchema.parse({
+ schemaVersion: "1",
+ jobId: "job_bounded_snapshot_race",
+ kind: "LAB_COMPILE",
+ status: "QUEUED",
+ sessionId,
+ artifactId,
+ artifactManifestHash: "a".repeat(64),
+ conceptPack: { id: "entity_leakage", version: "2.0.0" },
+ inputHashes: ["b".repeat(64)],
+ stateVersion: 6,
+ jobVersion: 1,
+ createdAt: "2026-07-14T10:01:00.000Z",
+ updatedAt: "2026-07-14T10:01:00.000Z",
+ attempt: 0,
+ maxAttempts: 2,
+ runnerIdentity: null,
+ timeoutSeconds: 90,
+ outputHashes: [],
+ eventCursor: 0,
+ }),
+ );
+ runnerJobs.mutateHistorySnapshotEnds(2);
+
+ const response = await app.request(`${route}/events`);
+ expect(response.status).toBe(200);
+ await expect(response.json()).resolves.toMatchObject({
+ data: {
+ events: [{}, {}, {}, {}, {}, { kind: "lab.verified" }],
+ integrity: {
+ status: "VERIFIED",
+ eventCount: 6,
+ eventChainHead: expect.stringMatching(/^[a-f0-9]{64}$/u),
+ },
+ },
+ });
+ });
+
it("returns LIVE_UNAVAILABLE without advancing a live session when the key is missing", async () => {
const { app, sessionId, sessionRepository } = await sessionHarness("live");
const learnerClaim =
diff --git a/apps/web/worker/api.ts b/apps/web/worker/api.ts
index 2c38a841..3f3c9dd1 100644
--- a/apps/web/worker/api.ts
+++ b/apps/web/worker/api.ts
@@ -310,6 +310,7 @@ const RUNNER_JOB_TOKEN_GRACE_SECONDS = 120;
const MAX_RUNNER_JOB_TOKEN_TTL_SECONDS = 900;
const MAX_SESSION_RUNNER_HISTORY_JOBS = 32;
const MAX_SESSION_COMPILER_HISTORY_EVENTS = 512;
+const MAX_EVIDENCE_SNAPSHOT_ATTEMPTS = 4;
const JsonObjectSchema = z.record(z.string(), z.unknown());
const HostedPlanLineageSchema = z
@@ -8142,7 +8143,11 @@ export function createApi(options: ApiOptions = {}) {
const service = sessionService(context, options);
const jobs = runnerJobService(context, options);
const sessionId = context.req.param("sessionId");
- for (let attempt = 0; attempt < 2; attempt += 1) {
+ for (
+ let attempt = 0;
+ attempt < MAX_EVIDENCE_SNAPSHOT_ATTEMPTS;
+ attempt += 1
+ ) {
const sessionBefore = await service.getSession(sessionId);
requireStoredSampleClaimScope(sessionBefore);
const jobsBefore = await jobs.listForSession(sessionId);
From ccb876b9b3cbd3f7c9583efa6286968a0c62ec5f Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 18:23:38 +0530
Subject: [PATCH 106/143] fix(replay): harden mobile read-only journey
---
apps/web/e2e/mobile-public-replay.spec.ts | 24 +++++++++++++++----
.../replay/LegacyReplayResult.module.css | 3 +++
2 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/apps/web/e2e/mobile-public-replay.spec.ts b/apps/web/e2e/mobile-public-replay.spec.ts
index ef7581d8..6ef57ec8 100644
--- a/apps/web/e2e/mobile-public-replay.spec.ts
+++ b/apps/web/e2e/mobile-public-replay.spec.ts
@@ -107,6 +107,22 @@ async function expectReadOnlyReplay(page: Page): Promise {
).toHaveCount(0);
}
+async function enterReadOnlyReplay(page: Page): Promise {
+ const replayStatus = page.getByRole("complementary", {
+ name: "Replay status",
+ });
+ await expect(replayStatus).toBeVisible({ timeout: 30_000 });
+ await expect(replayStatus).toContainText(
+ "Verified replay · read-only stored evidence",
+ );
+ await expect(
+ page.getByRole("heading", { name: /Replay verified session/i }),
+ ).toBeVisible();
+
+ await page.getByRole("button", { name: /Continue replay/i }).click();
+ await expectReadOnlyReplay(page);
+}
+
async function readRootOverflow(page: Page): Promise {
return page.evaluate(() => ({
bodyScrollWidth: document.body.scrollWidth,
@@ -194,7 +210,7 @@ test.describe("public mobile replay routing", () => {
expect(response!.request().redirectedFrom()).toBeNull();
expectConfiguredPublicOrigin(response!.url());
await expect(page).toHaveURL(new RegExp(`${replayPath}$`, "u"));
- await expectReadOnlyReplay(page);
+ await enterReadOnlyReplay(page);
await page.waitForLoadState("networkidle");
await page.reload({ waitUntil: "domcontentloaded" });
@@ -249,10 +265,10 @@ test.describe("public mobile replay routing", () => {
await expect(replayLink).toHaveAttribute("href", replayPath);
await replayLink.click();
await expect(page).toHaveURL(new RegExp(`${replayPath}$`, "u"));
- await expectReadOnlyReplay(page);
+ await enterReadOnlyReplay(page);
await page.waitForLoadState("networkidle");
- await page.goBack({ waitUntil: "domcontentloaded" });
+ await page.goBack({ waitUntil: "commit" });
await expect(page).toHaveURL(/\/judge$/u);
await expect(
page.getByRole("heading", {
@@ -261,7 +277,7 @@ test.describe("public mobile replay routing", () => {
).toBeVisible();
await page.waitForLoadState("networkidle");
- await page.goForward({ waitUntil: "domcontentloaded" });
+ await page.goForward({ waitUntil: "commit" });
await expect(page).toHaveURL(new RegExp(`${replayPath}$`, "u"));
await expectReadOnlyReplay(page);
const overflow = await readRootOverflow(page);
diff --git a/apps/web/src/components/replay/LegacyReplayResult.module.css b/apps/web/src/components/replay/LegacyReplayResult.module.css
index 9c894780..7fed72ab 100644
--- a/apps/web/src/components/replay/LegacyReplayResult.module.css
+++ b/apps/web/src/components/replay/LegacyReplayResult.module.css
@@ -30,6 +30,7 @@
.content {
display: grid;
+ min-width: 0;
width: min(980px, calc(100% - 40px));
gap: 20px;
margin: 0 auto;
@@ -74,6 +75,7 @@
.provenance,
.result,
.limitations {
+ min-width: 0;
padding: clamp(20px, 4vw, 32px);
border: 1px solid var(--line);
border-radius: 18px;
@@ -176,6 +178,7 @@
}
.tableWrap {
+ min-width: 0;
overflow-x: auto;
margin-inline: calc(clamp(20px, 4vw, 32px) * -1);
padding-inline: clamp(20px, 4vw, 32px);
From d03703652e1baa0dd2d0359cfd6f1c0690a688ef Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 18:25:01 +0530
Subject: [PATCH 107/143] docs(progress): record focused Cloak repairs
---
docs/PROGRESS.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 412d2e51..36fd776c 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4483,3 +4483,82 @@ deployment receipt. The qualifying public count therefore remains **0**.
No exact image, aggregate-containment pass, Cloudflare identity, deployment,
public qualification, learner outcome, Devpost submission, push, or merge is
claimed. Release remains **NO-GO** while those dependencies are red.
+
+## Focused CloakBrowser repair checkpoint — 2026-07-21T12:53:45Z
+
+Deadline: `2026-07-22T00:00:00Z`; **11 hours 6 minutes 15 seconds remained at
+this checkpoint**. Committed HEAD is
+`ccb876b9b3cbd3f7c9583efa6286968a0c62ec5f` on
+`feat/learner-ux-v6.1`. The visible worktree contains only the active,
+agent-owned CloakBrowser runtime `.runtime/` and the untouched FUSE tombstone
+`scripts/.fuse_hidden0000a9600000801f`. Neither is staged or tracked.
+
+### Completed and committed
+
+| Commit | Result | Exact verification |
+| --- | --- | --- |
+| `d7d23af fix(worker): stabilize evidence snapshots` | The evidence endpoint now permits four bounded coherent-snapshot attempts, covering the two legitimate Sample compile transitions while preserving every chain, session, compiler-history, cursor, cap, and stable-snapshot check. Perpetual mutation still fails closed with retryable `EVIDENCE_SNAPSHOT_BUSY`. | Worker API **108/108 passed**; focused race cases **2/2 passed**; Worker TypeScript, Prettier, whitespace, and scoped secret scan passed; independent review found no P0/P1. |
+| `ccb876b fix(replay): harden mobile read-only journey` | The mobile public-replay journey now follows the intentional labelled replay introduction before asserting the strict read-only result. Grid children may shrink around the horizontally scrollable exact-value table, eliminating root overflow. Browser history waits at navigation commit, which is correct for the SPA route. | App plus legacy replay components **77/77 passed**; E2E TypeScript, production build, Prettier, whitespace, and scoped secret scan passed. Fresh CloakBrowser replay run: **2/2 passed**, zero console/page/request errors, zero unsafe requests, and no root overflow. |
+
+### Focused rendered evidence
+
+- The rebuilt local Worker ran through Wrangler `4.110.0` with Containers
+ disabled, repository-contained D1/R2/Durable Object state, and a test-only
+ admission key. `/ready` truthfully reported admission and persistence ready
+ while analyst, runner, signing, and release identity remained unavailable.
+- The six original Sample/intake repair targets passed through CloakBrowser:
+ desktop and mobile post-seal Sample, Try Instantly, judged unsupported
+ notebook, malformed upload, and unsupported upload. Sample compile produced
+ no transient `/events` error; the malformed upload retained its intentional,
+ exactly correlated `422` response.
+- The first corrected replay run reached the actual UI and exposed two further
+ defects rather than hiding them: `696px` document overflow at a `390px`
+ viewport and a SPA back-navigation wait that incorrectly required a new
+ `DOMContentLoaded` event. Both were repaired.
+- Fresh immutable evidence at
+ `apps/web/test-results/runtime/cloak-replay-regression-v6/evidence/` records
+ both mobile replay journeys passing on CloakBrowser Chromium
+ `146.0.7680.177`. At `390x844`, document/body widths were `375px`; at
+ `375x812`, they were `360px`. Both journeys recorded zero console errors,
+ page errors, failed requests, unsafe requests, and unexpected HTTP errors.
+- The production build passed. Current emitted sizes are Worker `1,868.97 kB`
+ (`360.53 kB` gzip), main client `449.75 kB` (`129.28 kB` gzip), and client
+ CSS `201.45 kB` (`34.83 kB` gzip).
+
+These are real CloakBrowser local-Worker regressions, not a public
+qualification. The exact public qualifying count remains **0 journeys**.
+
+### Remaining dependency chain
+
+1. Run the complete 40-journey CloakBrowser matrix against this local build to
+ catch any further source-level regressions, then stop the Worker/browser and
+ move the agent-owned `.runtime/` into ignored repository cache state.
+2. The FUSE-owning process must release the tombstone naturally. Do not delete,
+ hide, ignore, stage, rename, overwrite, or inspect it. A clean source `S`
+ cannot be frozen while it remains.
+3. The aggregate containment sentinel requires narrowly scoped read-only
+ observation of `/sys/fs/cgroup` and owned `/proc//stat`. The current
+ repository constitution forbids every external filesystem read, so this
+ gate cannot be run without an explicit constitution amendment. No
+ containment pass is claimed.
+4. After clean source and lawful containment observation exist, build one
+ exact runner image, run the genuine timeout/negative controls, refresh all
+ source/image-bound scientific, SBOM, VEX, license, health, and held-out
+ evidence once, review it, and commit evidence `E`.
+5. Verify repository-contained Wrangler authentication and the intended
+ account/secrets, qualify and deploy only the exact tuple, then prove public
+ release identity and capability readiness.
+6. Run all 40 journeys with qualification enabled against the exact public
+ origin and deployment receipt, followed by manual accessibility,
+ performance, download, reconnect, and Web Vitals evidence.
+7. Keep learner impact `NO_DATA` unless real consented observations occur.
+ Complete the exact-release video, logged-out repository/app/feedback/link
+ review, Devpost publication, and submission receipt.
+8. Push the feature branch, then fast-forward-only merge and push `main` only
+ when the upstream release and submission gates are factual.
+
+No exact image, aggregate-containment pass, Cloudflare identity, cloud
+mutation, deployment, public qualification, learner outcome, Devpost
+submission, push, or merge is claimed. Release remains **NO-GO** at the clean
+source, containment, exact-image/evidence, Cloudflare, public-browser, and
+submission gates.
From 29ea8c3b4527c2c03a1ab5cb8a07fafa1f877779 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 18:35:00 +0530
Subject: [PATCH 108/143] fix(replay): make evidence table keyboard reachable
---
apps/web/e2e/mobile-public-replay.spec.ts | 48 +++++++++++++++----
.../replay/LegacyReplayResult.test.tsx | 5 ++
.../components/replay/LegacyReplayResult.tsx | 14 +++++-
3 files changed, 57 insertions(+), 10 deletions(-)
diff --git a/apps/web/e2e/mobile-public-replay.spec.ts b/apps/web/e2e/mobile-public-replay.spec.ts
index 6ef57ec8..ae0a6939 100644
--- a/apps/web/e2e/mobile-public-replay.spec.ts
+++ b/apps/web/e2e/mobile-public-replay.spec.ts
@@ -105,21 +105,53 @@ async function expectReadOnlyReplay(page: Page): Promise {
name: /Continue replay|Show me what happened|Run fair test|Lock my answer|Check my answer|Verify notebook patch/i,
}),
).toHaveCount(0);
+
+ if (await legacyHeading.isVisible()) {
+ const tableRegion = page.getByRole("region", {
+ name: "Stored fixed-kernel comparison values",
+ });
+ await expect(tableRegion).toBeVisible();
+ const dimensions = await tableRegion.evaluate((element) => ({
+ clientWidth: element.clientWidth,
+ scrollWidth: element.scrollWidth,
+ }));
+ expect(dimensions.scrollWidth).toBeGreaterThan(dimensions.clientWidth);
+ await tableRegion.evaluate((element) => {
+ element.scrollLeft = 0;
+ });
+ await tableRegion.focus();
+ await expect(tableRegion).toBeFocused();
+ await tableRegion.press("ArrowRight");
+ await expect
+ .poll(() => tableRegion.evaluate((element) => element.scrollLeft))
+ .toBeGreaterThan(0);
+ const finalValue = page.getByRole("cell").last();
+ await finalValue.scrollIntoViewIfNeeded();
+ await expect(finalValue).toBeVisible();
+ }
}
async function enterReadOnlyReplay(page: Page): Promise {
const replayStatus = page.getByRole("complementary", {
name: "Replay status",
});
- await expect(replayStatus).toBeVisible({ timeout: 30_000 });
- await expect(replayStatus).toContainText(
- "Verified replay · read-only stored evidence",
- );
- await expect(
- page.getByRole("heading", { name: /Replay verified session/i }),
- ).toBeVisible();
+ const finalReplayStatus = page
+ .getByRole("complementary", { name: "Legacy replay status" })
+ .or(page.getByRole("complementary", { name: "Verified replay mode" }));
+ await expect(replayStatus.or(finalReplayStatus)).toBeVisible({
+ timeout: 30_000,
+ });
+
+ if (await replayStatus.isVisible()) {
+ await expect(replayStatus).toContainText(
+ "Verified replay · read-only stored evidence",
+ );
+ await expect(
+ page.getByRole("heading", { name: /Replay verified session/i }),
+ ).toBeVisible();
- await page.getByRole("button", { name: /Continue replay/i }).click();
+ await page.getByRole("button", { name: /Continue replay/i }).click();
+ }
await expectReadOnlyReplay(page);
}
diff --git a/apps/web/src/components/replay/LegacyReplayResult.test.tsx b/apps/web/src/components/replay/LegacyReplayResult.test.tsx
index f8f6334f..70e83b93 100644
--- a/apps/web/src/components/replay/LegacyReplayResult.test.tsx
+++ b/apps/web/src/components/replay/LegacyReplayResult.test.tsx
@@ -199,6 +199,11 @@ describe("LegacyReplayResult", () => {
const table = screen.getByRole("table", {
name: /stored fixed-kernel comparison/i,
});
+ expect(
+ screen.getByRole("region", {
+ name: "Stored fixed-kernel comparison values",
+ }),
+ ).toHaveAttribute("tabindex", "0");
expect(within(table).getByText("Random Rows")).toBeInTheDocument();
expect(within(table).getByText("Whole Customers")).toBeInTheDocument();
expect(within(table).getByText("96.0%")).toBeInTheDocument();
diff --git a/apps/web/src/components/replay/LegacyReplayResult.tsx b/apps/web/src/components/replay/LegacyReplayResult.tsx
index c50c7129..7c0eca59 100644
--- a/apps/web/src/components/replay/LegacyReplayResult.tsx
+++ b/apps/web/src/components/replay/LegacyReplayResult.tsx
@@ -88,7 +88,12 @@ function StoredResultTable({ replay }: { replay: LegacyVerifiedReplay }) {
if (result.concept === "class_imbalance") {
return (
-
+
Stored fixed-kernel comparison. These values are replayed exactly;
@@ -122,7 +127,12 @@ function StoredResultTable({ replay }: { replay: LegacyVerifiedReplay }) {
}
return (
-
+
Stored fixed-kernel comparison. Accuracy is shown beside entity
From b78632f3ce9c75d696575485a8f9fc87fe05be2c Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 18:42:53 +0530
Subject: [PATCH 109/143] docs(progress): record full local verification
---
docs/PROGRESS.md | 94 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 94 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 36fd776c..3ec5b6f1 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4562,3 +4562,97 @@ mutation, deployment, public qualification, learner outcome, Devpost
submission, push, or merge is claimed. Release remains **NO-GO** at the clean
source, containment, exact-image/evidence, Cloudflare, public-browser, and
submission gates.
+
+## Full local Cloak and deterministic verification checkpoint — 2026-07-21T13:11:46Z
+
+Deadline: `2026-07-22T00:00:00Z`; **10 hours 48 minutes 14 seconds remained at
+this checkpoint**. Committed HEAD is
+`29ea8c3b4527c2c03a1ab5cb8a07fafa1f877779` on
+`feat/learner-ux-v6.1`, 26 commits ahead of
+`origin/feat/learner-ux-v6.1` and 108 commits ahead of local `main`, with no
+reverse divergence. The only worktree entry is the preserved, untracked FUSE
+tombstone `scripts/.fuse_hidden0000a9600000801f`. It has not been read,
+changed, staged, hidden, ignored, renamed, overwritten, or deleted.
+
+### Completed and committed
+
+| Commit | Result | Exact verification |
+| --- | --- | --- |
+| `29ea8c3 fix(replay): make evidence table keyboard reachable` | Direct legacy and Proof Capsule replay variants remain accepted as labelled read-only final states. The exact-value table is a named, focusable region that supports keyboard horizontal scrolling without widening the root document. | Component tests **3/3 passed**; E2E TypeScript and production build passed; fresh CloakBrowser replay accessibility run **2/2 passed** with **45** and **51** assertions and zero unexpected console, page, request, unsafe-request, or HTTP telemetry. Independent re-review found no P0/P1. |
+
+### Full local CloakBrowser result
+
+- Immutable raw evidence at
+ `apps/web/test-results/runtime/cloak-local-full-v7/evidence/cloakbrowser-raw-run.json`
+ records **40 tests: 34 passed, 6 exact public/live skips, 0 failed** in
+ approximately 1.3 minutes. Every executed journey records zero unexpected
+ console, HTTP, request, page, or unsafe-request failures.
+- Fresh post-review accessibility evidence at
+ `apps/web/test-results/runtime/cloak-replay-accessibility-v8/evidence/cloakbrowser-raw-run.json`
+ records **2/2 passed** on CloakBrowser Chromium `146.0.7680.177`.
+- These runs use the current local Worker and are explicitly
+ **NON_QUALIFYING** for release. They prove source-level browser behavior but
+ do not substitute for an exact public deployment receipt. Current qualifying
+ public execution remains **0 journeys**.
+
+### Deterministic verification completed
+
+- Web Vitest: **80 files, 674/674 tests passed**.
+- Python kernel, runner, and release tests: **331/331 passed**.
+- Leakage mutations: **14/14 detected**; imbalance mutations: **19/19
+ detected**.
+- Held-out intake/routing: **10/10 passed** and tracked evidence matched;
+ patch-eligible completion: **7/7 verified**; legacy supported completion:
+ **7/8**, with the eighth case the expected RandomForest contract refusal.
+- Sample Boundary fixture: passed with **25 cells**. Sample Proof Capsule:
+ passed with integrity root
+ `e45cf88f1bbdd470723f83def9550d47ac272f7c5aaf8554bec52c2a34f9856b`
+ and size **94,236 bytes**.
+- Achieved-metrics checks passed for leakage **14/14** and imbalance **15/15**;
+ the scientific import allowlist reported **0 findings**.
+- Non-release scientific TypeScript: **11 files, 146/146 passed**. The wider
+ scientific TypeScript suite remains **18/20 files, 239/242 passed** because
+ three registry tests correctly reject stale source/image evidence.
+- Full root Vitest in the restricted sandbox: **73 files; 67 passed, 6
+ failed; 771 tests; 742 passed, 27 failed, 2 skipped**. The three loopback and
+ child-process files were then rerun with their required capabilities and
+ passed **49/49**. The read-isolation test was not rerun because the current
+ repository constitution forbids the external filesystem observation it
+ requires. The remaining scientific registry failures are the expected stale
+ exact-source/image bindings.
+- Repository, web, Worker, and E2E TypeScript passed. Full Prettier, Git
+ whitespace, the 1,460-file repository secret scan, and the production build
+ passed.
+- Production output: Worker **1,868.97 kB / 360.53 kB gzip**; main client
+ **449.90 kB / 129.30 kB gzip**; CSS **201.45 kB / 34.83 kB gzip**.
+
+### Current release gates and next actions
+
+1. **Cloudflare identity:** a new Wrangler `4.110.0` repository-contained OAuth
+ callback is active and awaiting owner approval. The preceding contained
+ `wrangler whoami --json` returned exit 1 with `{"loggedIn":false}`. No cloud
+ mutation has occurred.
+2. **Clean source:** blocked by the preserved FUSE tombstone. No current clean
+ source `S` exists. The owning process must release it naturally; do not
+ inspect, delete, hide, ignore, stage, rename, or overwrite it.
+3. **Real aggregate containment:** not run. The genuine sentinel requires a
+ narrowly scoped read-only observation of `/sys/fs/cgroup` and the owned test
+ processes' `/proc//stat`; the current constitution forbids those
+ reads. No containment pass is claimed.
+4. **Exact image and evidence:** not run for this HEAD. The stale
+ source/image scientific bindings remain red until one exact image, genuine
+ sentinel, and complete evidence refresh are produced together.
+5. **Deployment and public browser qualification:** not run. After the exact
+ tuple qualifies, deploy only that tuple, prove public source/image/Worker
+ identity, run production smoke, and rerun all 40 journeys without the six
+ local-only skips.
+6. **Submission, impact, and publication:** learner evidence remains
+ `NO_DATA`; exact-release video, Devpost submission receipt, push, and merge
+ remain not run. Push the reviewed feature branch only after the release
+ tuple is frozen; fast-forward-only merge and push `main` only after public
+ qualification and submission evidence are factual.
+
+Chrome is no longer the blocker: genuine CloakBrowser local rendering is green
+for every locally executable journey. Release remains **NO-GO** for the clean
+source, constitution-compatible containment observation, exact image/evidence,
+Cloudflare deployment, public qualification, and submission gates.
From 6b85f2a6b713cbc4172a574a0c3be3fa075e8744 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 18:45:42 +0530
Subject: [PATCH 110/143] docs(submission): clarify browser evidence status
---
docs/SCREENSHOT_PLAN.md | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/docs/SCREENSHOT_PLAN.md b/docs/SCREENSHOT_PLAN.md
index a7fa7569..a284c80f 100644
--- a/docs/SCREENSHOT_PLAN.md
+++ b/docs/SCREENSHOT_PLAN.md
@@ -1,10 +1,11 @@
# Screenshot plan
-Status: planned, not captured for the final release. The required
-`CLOAK_CDP_ENDPOINT` is unavailable. Stock Chromium may produce explicitly
-labelled design-review artifacts, but it cannot qualify or supply the final
-submission images. Do not reuse historical screenshots as evidence of the
-frozen release.
+Status: planned, not captured for the final release. Repository-contained
+CloakBrowser is available and passes the current local journey matrix, but no
+exact public release endpoint and deployment receipt exist yet. Local
+CloakBrowser and stock-Chromium design-review artifacts cannot qualify or
+supply the final submission images. Do not reuse historical or local
+screenshots as evidence of the frozen public release.
Capture the current commit through CloakBrowser at 1440 × 900 and 390 × 844.
Add a 1280 × 720 overflow check even if it is not a submission image. Use real
From d16fc77da0f97d7cb60fafa5691ef0be570643f4 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 18:59:07 +0530
Subject: [PATCH 111/143] docs(release): refresh dependency and review graph
---
docs/PROGRESS.md | 20 +-
.../19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md | 314 ++++++++++--------
2 files changed, 186 insertions(+), 148 deletions(-)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 3ec5b6f1..556f83e7 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4628,17 +4628,19 @@ changed, staged, hidden, ignored, renamed, overwritten, or deleted.
### Current release gates and next actions
-1. **Cloudflare identity:** a new Wrangler `4.110.0` repository-contained OAuth
- callback is active and awaiting owner approval. The preceding contained
- `wrangler whoami --json` returned exit 1 with `{"loggedIn":false}`. No cloud
- mutation has occurred.
+1. **Cloudflare identity:** Wrangler `4.110.0` remains unauthenticated in the
+ repository-contained environment. `wrangler whoami --json` returned exit 1
+ with `{"loggedIn":false}`, and two fresh OAuth callback windows expired
+ without a returned authorization code. No cloud mutation has occurred.
2. **Clean source:** blocked by the preserved FUSE tombstone. No current clean
source `S` exists. The owning process must release it naturally; do not
inspect, delete, hide, ignore, stage, rename, or overwrite it.
-3. **Real aggregate containment:** not run. The genuine sentinel requires a
- narrowly scoped read-only observation of `/sys/fs/cgroup` and the owned test
- processes' `/proc//stat`; the current constitution forbids those
- reads. No containment pass is claimed.
+3. **Real aggregate containment:** not run. The genuine sentinel requires
+ delegated `/sys/fs/cgroup` control reads, owned `/proc//stat` reads, an
+ owned-helper membership write to delegated `cgroup.procs`, and a
+ helper-local `/proc/self/oom_score_adj` write. The current constitution
+ forbids those operations; read-only permission would be insufficient. No
+ containment pass is claimed.
4. **Exact image and evidence:** not run for this HEAD. The stale
source/image scientific bindings remain red until one exact image, genuine
sentinel, and complete evidence refresh are produced together.
@@ -4654,5 +4656,5 @@ changed, staged, hidden, ignored, renamed, overwritten, or deleted.
Chrome is no longer the blocker: genuine CloakBrowser local rendering is green
for every locally executable journey. Release remains **NO-GO** for the clean
-source, constitution-compatible containment observation, exact image/evidence,
+source, constitution-compatible sentinel operations, exact image/evidence,
Cloudflare deployment, public qualification, and submission gates.
diff --git a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
index 7eada8e4..247b712c 100644
--- a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
+++ b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
@@ -1,10 +1,10 @@
# Release dependency and review graph
-- Checkpoint: `2026-07-21T09:06:33Z`
+- Checkpoint: `2026-07-21T13:17:03Z`
- Branch: `feat/learner-ux-v6.1`
-- Committed HEAD: `7e25a098b74d18b30d84a1bc80938ef1d94e95e9`
+- Committed HEAD: `6b85f2a6b713cbc4172a574a0c3be3fa075e8744`
- Deadline: `2026-07-22T00:00:00Z`
-- Time remaining at this checkpoint: **14 hours 53 minutes 27 seconds**
+- Time remaining at this checkpoint: **10 hours 42 minutes 57 seconds**
## Purpose and evidence boundary
@@ -19,67 +19,98 @@ staged, or deleted for this review. Every current build, qualification,
release-check, deployment, and production-smoke script that requires a clean
worktree will reject this state.
-No current deployment, rendered CloakBrowser journey, learner outcome, video,
-Devpost submission, branch push, or merge is claimed here.
+No current deployment, public CloakBrowser qualification, learner outcome,
+video, Devpost submission, branch push, or merge is claimed here.
-The complete local `stock-chromium-design-review` matrix now passes every
-locally executable journey on a fresh repository-contained state: **35 passed,
-5 intentionally skipped, 0 failed in 3.9 minutes**. The five skips are the two exact-public mobile replay journeys,
-one exact-public asset scan, and two qualified hosted-runner journeys. This is
-useful source/design evidence; it is not public or CloakBrowser qualification.
+The latest product-code commit, `29ea8c3`, passes every locally executable
+CloakBrowser journey: **34 passed, 6 exact public/live skips, 0 failed**. Its
+fresh replay accessibility follow-up passes **2/2** with 45 and 51 assertions.
+The intervening committed changes through this checkpoint (`b78632f` and
+`6b85f2a`) change documentation only; this graph refresh is also
+documentation-only. This is genuine CloakBrowser source/browser evidence, but
+it targets a local Worker without an exact public deployment receipt and is
+therefore explicitly non-qualifying. Public qualifying execution remains **0
+journeys**.
## Release dependency graph
```mermaid
flowchart TD
- A["Source slices atomic commits + focused tests SOURCE COMPLETE for reviewed P0 repairs"]
- B["Review gate independent review + lead diff review PASS for reviewed P0 repairs"]
- R["Contained runtime command channel rt-mainrel04 READY / VERIFIED PASS"]
- P["Historical Proof Bundle validation issuance-event anchored PASS"]
- V["Exact live-session admission browser advisory + Worker authoritative gate PASS"]
- C["Clean source freeze S primary root blocked by FUSE tombstone; linked roots exceed runtime maximum"]
- D["Exact runner and adapter image build receipt v4 bound to S NOT RUN for current source"]
- E["Real aggregate timeout sentinel observer + controls + FINALIZE + cleanup BLOCKED by /sys and /proc authority"]
- F["Source/image evidence refresh SBOM + VEX + engine evidence NOT RUN"]
- G["Evidence commit E reviewed allowlisted evidence only NOT CREATED"]
- H["Qualification receipt v6 + immutable registry image NOT RUN; Wrangler identity unverified"]
- I["Release check receipt v5 + deterministic full gate NOT RUN"]
- J["Cloudflare deployment maintenance + D1 + Container + Worker NOT RUN"]
- K["Public identity and smoke deployment receipt v7 + /ready + health + live loops NOT RUN"]
- L["Browser qualification launcher registered; current session lacks tools public CloakBrowser: 0 journeys"]
- M["Submission package public app + repo + video + feedback + honest NO_DATA EXTERNALLY UNVERIFIED"]
- N["Git publication push feature -> verify remote -> fast-forward main -> push main NOT RUN"]
-
- A --> B --> R --> P --> V --> C --> D --> E --> F --> G --> H --> I --> J --> K --> L --> M --> N
-
- classDef pass fill:#d9f3ee,stroke:#176b5b,color:#123b34;
- classDef blocked fill:#fff0d0,stroke:#9a6500,color:#4c3500;
- classDef pending fill:#eef2f7,stroke:#596579,color:#252b36;
- classDef external fill:#f7e8ef,stroke:#95506d,color:#482636;
- class A,B,R,P,V pass;
- class C,E blocked;
- class D,F,G,H,I,J,K,L pending;
- class M,N external;
+ P["Product source at 29ea8c3 17 of 18 black-box P0/P1 covered MB-014 comprehension partial"]
+ T["Non-release deterministic core Web 674/674; Python 331/331 green 3 exact-binding tests await evidence E"]
+ B["Local CloakBrowser at 29ea8c3 34 pass / 6 exact skips / 0 fail NON_QUALIFYING"]
+ S["Freeze clean source S BLOCKED by protected FUSE tombstone"]
+ I["Build exact runner image receipt bound to S NOT RUN"]
+ C["Genuine aggregate-cgroup sentinel and negative controls BLOCKED by sentinel-operations authority"]
+ E["Refresh SBOM, VEX, scientific registry, licenses, health, held-out evidence 3 stale-binding tests currently RED"]
+ EC["Reviewed evidence-only commit E NOT CREATED"]
+ W["Contained Wrangler identity, account, secrets and recovery target RED / not verified"]
+ Q["Qualified receipt and release check NOT RUN"]
+ D["D1, Container and Worker deployment NOT RUN"]
+ H["Public identity and production smoke Sample, Replay, two Live packs, reconnect, patch, Capsule NOT RUN"]
+ PB["Public CloakBrowser qualification 40/40 plus 10 manual observations 0 qualifying journeys"]
+ FP["Feature publication push exact feature tip and verify repository logged out NOT RUN"]
+ M["Submission package video, feedback, Devpost receipt MB-C01 through MB-C11 OPEN learner evidence NO_DATA"]
+ GM["Main publication fast-forward main and push only after submission receipt NOT RUN"]
+
+ P --> T
+ P --> B
+ T --> S
+ B --> S
+ S --> I --> C --> E --> EC --> Q
+ W --> Q
+ Q --> D --> H --> PB --> FP --> M --> GM
+
+ classDef green fill:#d9f3ee,stroke:#176b5b,color:#123b34;
+ classDef partial fill:#fff0d0,stroke:#9a6500,color:#4c3500;
+ classDef red fill:#f7e8ef,stroke:#95506d,color:#482636;
+ class B green;
+ class P,T,S,C,W partial;
+ class I,E,EC,Q,D,H,PB,FP,M,GM red;
```
+## Current code-review slices
+
+| Slice | Commit anchors | Source review status | Required downstream proof |
+| ------------------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------ |
+| Recovery and intake | `0509833`, `51d989f`, `b9dd722`, `d6c4aec` | Green | Exact-public refusal and upload matrix |
+| Prediction, transfer, and claim scope | `f12b600`, `134c22f` plus focused follow-ups | Green | Public transfer fail/pass and no-preseal-leak journeys |
+| Stored proof and Sample Capsule | `7bf3c67`, `773e808`, `9441166`, `3174a1f`, `547cc0f`, `d7d23af` | Green | Exact-deployed event, Capsule, and reconnect proof |
+| Belief-break experience | `7bee809`, `0a39471`, `cc61917`, `e80b390`, `830d61b` | Source green; MB-014 partial | Public first-fold and genuine cold-user comprehension evidence |
+| Runtime and release authority | cgroup/runtime chain through `eaf4a60`, then `75dfb65`, `b37f7d9`, `84ef49d`, `7e25a09` | Source green | Clean source, exact image, and genuine sentinel |
+| Submission evidence validation | `d942c08`, `9ea8484`, `07c9091`, `8e33154` | Green | Real public repository, video, feedback, assets, and receipt bytes |
+| Browser qualification | `3e75281`, `3db3e52`, `adea1c9`, `a6f6d8d`, `0ee8934`, `99e9741` | Harness green | Exact-public 40/40 plus ten manual observations |
+| Local Cloak repairs | `d7d23af`, `ccb876b`, `29ea8c3` | Green locally; independent review passed | Public replay, intake, and live verification |
+
+The black-box register now has **17 of 18** product P0/P1 mechanisms covered
+in source. MB-014 remains partial because a cold-user 10/20/30-second
+comprehension result cannot be inferred from tests. None of the 18 is
+production-closed until the exact deployed tuple passes its public acceptance
+journeys. Internal CL-001 submission, CL-006 learner evidence, and optional
+CL-024 continuing Learning Director remain open. CL-002 containment, CL-008
+read isolation, CL-009 category scope, and CL-023 cold-user visual
+comprehension remain partial or mitigated rather than closed. CL-024 stays
+deferred until release and submission gates are green.
+
## Gate ledger
-| Gate | Required inputs | Required evidence | Current status | Failure rule |
-| ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
-| Source slices | Reviewed implementation and tests | Atomic commits, focused tests, type checks, formatting, secret scan, whitespace check | Aggregate observer/runtime/consumer chain, release-health validation, historical Proof Bundle validation, and exact live-session admission are source-complete | Repair in a new independent commit; do not weaken tests |
-| Review | Complete source slice and focused evidence | Independent review plus lead inspection of complete diff | Aggregate runtime review is `GO`; its initial async run/drain race and missing positive path were repaired | Review findings remain open until implementation and regression tests pass |
-| Clean source `S` | Final intended source and no tracked or untracked drift | `git status` empty and exact source commit recorded | **Blocked**: the 42-character primary root is the only runtime-capable location and remains unclean because of the preserved FUSE tombstone. Both retained linked worktrees exceed the enforced containerd shim-root maximum | Do not hide, delete, stage, or sweep the entry; no descendant worktree is a proven substitute |
-| Exact image | Clean `S`, source-matching contained runtime, pinned BuildKit | Build receipt v4, source/tree labels, exact runner and adapter digests | **Not run** for current source; cached receipts belong to older commits | Never reuse or retag an earlier image as current |
-| Real sentinel | Exact image, source-matching runtime, release-only qualification mode | Genuine memory OOM, PID denial, CPU throttling, exact membership, FINALIZE, eight cleanup facts, cgroup absence, exclusive evidence artifact | **Blocked** by the checked-in repository-only filesystem rule, which does not permit required `/sys/fs/cgroup` and owned `/proc//stat` reads | Preserve `NO-GO`; never synthesize or inject aggregate evidence |
-| Evidence refresh | Exact image and successful real sentinel while `HEAD == S` | Fresh normalized SBOM, VEX and negative control, scientific-engine bindings, runtime and isolation reports | **Not run** | Any image/source mismatch or stale binding fails closed |
-| Evidence commit `E` | Reviewed allowlisted evidence delta | One clean commit containing only regenerated evidence | **Not created** | Do not include unrelated source or cache artifacts |
-| Qualification | Clean `HEAD == E`, build receipt, timeout receipt, source-matching runtime, authenticated Cloudflare account | Qualified runner receipt v6 and exact registry manifest/config digest | **Not run**; contained Wrangler is logged out and one fresh OAuth window timed out without consent | Do not promote when account, source, image, timeout, runtime, or evidence differs |
-| Release check | Clean `E`, qualified receipt, exact local images and runtime | Release-check receipt v5 after formatting, tests, mutations, held-out, sandbox, scientific, build, reproduction, patch replay, and secret gates | **Not run** | Any failure blocks deployment |
-| Cloudflare deployment | Qualified receipt, release-check receipt, registry image, required secrets, recovery target, working CloakBrowser endpoint | Exact maintenance/final Worker versions, D1 migration log, exact Container digest, deployment receipt v7 | **Not run** | Stop before mutation when preflight is red; after mutation, automated Worker recovery does not reverse D1 or Container state |
-| Public identity and smoke | Active exact Worker/Container tuple | `/ready`, `/api/health?readiness=probe`, Worker 100% traffic, Container identity, public asset scan, Sample, Replay, leakage and imbalance live evidence, Capsule/replay evidence | **Not run** | Source, image, Worker, client, receipt, or capability mismatch is a release failure |
-| CloakBrowser | Exact public tuple and injected `CLOAK_CDP_ENDPOINT` | All 40 current tests plus responsive, keyboard, screen-reader, 200% zoom, reduced-motion, console/network, downloads, reconnect, and Web Vitals evidence | `playwright_safe` is registered and enabled, but this active session has no loaded tool namespace or endpoint. Public CloakBrowser remains **0 journeys**. Current specs also lack three required viewport labels, INP collection, and the deterministic qualification reporter. | Stock Chromium is design-review evidence only and cannot close this gate |
-| Devpost and impact | Qualified public tuple, public repo/video/feedback links, factual copy | Logged-out link audit, public video from exact tuple, Devpost receipt, real consented learner aggregates or explicit `NO_DATA` | **Externally unverified; learner evidence remains `NO_DATA`** | Never infer a submission or learner result |
-| Push and merge | Every ship gate factual and final source reviewed | Verified feature remote tip, fast-forward-only `main`, verified main remote tip | **Not run** | Do not push or merge a `NO-GO` release |
+| Gate | Required inputs | Required evidence | Current status | Failure rule |
+| ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| Source slices | Reviewed implementation and tests | Atomic commits, focused tests, type checks, formatting, secret scan, whitespace check | At product commit `29ea8c3`, **17/18 black-box product P0/P1 mechanisms are source-covered**; MB-014 remains partial. Core deterministic and locally executable Cloak regressions are green; three release-binding tests await exact evidence. | Repair in a new independent commit; do not weaken tests |
+| Review | Complete source slice and focused evidence | Independent review plus lead inspection of complete diff | Aggregate runtime review is `GO`; its initial async run/drain race and missing positive path were repaired | Review findings remain open until implementation and regression tests pass |
+| Clean source `S` | Final intended source and no tracked or untracked drift | `git status` empty and exact source commit recorded | **Blocked**: the 42-character primary root is the only runtime-capable location and remains unclean because of the preserved FUSE tombstone. Both retained linked worktrees exceed the enforced containerd shim-root maximum | Do not hide, delete, stage, or sweep the entry; no descendant worktree is a proven substitute |
+| Exact image | Clean `S`, source-matching contained runtime, pinned BuildKit | Build receipt v4, source/tree labels, exact runner and adapter digests | **Not run** for current source; cached receipts belong to older commits | Never reuse or retag an earlier image as current |
+| Real sentinel | Exact image, source-matching runtime, release-only qualification mode | Genuine memory OOM, PID denial, CPU throttling, exact membership, FINALIZE, eight cleanup facts, cgroup absence, exclusive evidence artifact | **Blocked** by the repository-only rule. The observer must read delegated `/sys/fs/cgroup` controls and owned `/proc//stat`, write its owned helper PID to delegated `cgroup.procs`, and adjust only its helper's `/proc/self/oom_score_adj`. | Preserve `NO-GO`; never synthesize or inject aggregate evidence |
+| Evidence refresh | Exact image and successful real sentinel while `HEAD == S` | Fresh normalized SBOM, VEX and negative control, scientific-engine bindings, runtime and isolation reports | **Not run** | Any image/source mismatch or stale binding fails closed |
+| Evidence commit `E` | Reviewed allowlisted evidence delta | One clean commit containing only regenerated evidence | **Not created** | Do not include unrelated source or cache artifacts |
+| Qualification | Clean `HEAD == E`, build receipt, timeout receipt, source-matching runtime, authenticated Cloudflare account | Qualified runner receipt v6 and exact registry manifest/config digest | **Not run**; contained Wrangler remains logged out after two fresh OAuth callback windows expired without a returned authorization code | Do not promote when account, source, image, timeout, runtime, or evidence differs |
+| Release check | Clean `E`, qualified receipt, exact local images and runtime | Release-check receipt v5 after formatting, tests, mutations, held-out, sandbox, scientific, build, reproduction, patch replay, and secret gates | **Not run** | Any failure blocks deployment |
+| Cloudflare deployment | Qualified receipt, release-check receipt, registry image, required secrets, recovery target, working CloakBrowser endpoint | Exact maintenance/final Worker versions, D1 migration log, exact Container digest, deployment receipt v7 | **Not run** | Stop before mutation when preflight is red; after mutation, automated Worker recovery does not reverse D1 or Container state |
+| Public identity and smoke | Active exact Worker/Container tuple | `/ready`, `/api/health?readiness=probe`, Worker 100% traffic, Container identity, public asset scan, Sample, Replay, leakage and imbalance live evidence, Capsule/replay evidence | **Not run** | Source, image, Worker, client, receipt, or capability mismatch is a release failure |
+| CloakBrowser | Exact public tuple and injected `CLOAK_CDP_ENDPOINT` | All 40 current tests plus responsive, keyboard, screen-reader, 200% zoom, reduced-motion, console/network, downloads, reconnect, and Web Vitals evidence | Repository-contained CloakBrowser is operational. Latest product-code evidence at `29ea8c3` is **34 passed, 6 exact public/live skips, 0 failed**; replay accessibility is **2/2 passed**. Public qualification remains **0 journeys** until an exact deployed tuple and receipt exist. | Local or stock browser evidence cannot close the public release gate |
+| Feature publication | Exact deployed feature tip and public repository metadata | Verified remote feature tip plus logged-out repository and setup validation | **Not run** | Do not publish a source tip that differs from the deployed tuple |
+| Devpost and impact | Qualified public tuple, public repo/video/feedback links, factual copy | Logged-out link audit, public video from exact tuple, Devpost receipt, real consented learner aggregates or explicit `NO_DATA` | **Externally unverified; learner evidence remains `NO_DATA`** | Never infer a submission or learner result |
+| Main merge | Submitted package receipt and verified feature tip | Fast-forward-only `main` plus verified remote main tip | **Not run** | Do not merge before submission evidence is factual |
## Code-to-evidence graph
@@ -95,14 +126,18 @@ flowchart LR
subgraph Verification["Pre-release verification"]
T1["Vitest + TypeScript"]
T2["Pytest + cross-runtime parity"]
- T3["Playwright source + local stock matrix"]
+ T3["Playwright source + local Cloak matrix"]
Q1["Independent review"]
end
subgraph Release["Release evidence"]
+ F0["clean source S"]
B1["build-source-bound-runner.sh"]
+ O1["delegated/owned sentinel operations authority"]
S1["real timeout/cgroup sentinel"]
E1["refresh-source-bound-scientific-evidence.sh"]
+ EC1["reviewed evidence-only commit E"]
+ W1["contained Wrangler identity + account"]
Q2["qualify-runner-release.ts"]
R4["release-check.sh"]
end
@@ -122,7 +157,9 @@ flowchart LR
T1 --> Q1
T2 --> Q1
T3 --> Q1
- Q1 --> B1 --> S1 --> E1 --> Q2 --> R4 --> C1 --> C2 --> C3 --> P1
+ Q1 --> F0 --> B1 --> S1 --> E1 --> EC1 --> Q2 --> R4 --> C1 --> C2 --> C3 --> P1
+ O1 --> S1
+ W1 --> Q2
```
Source tests demonstrate behavior; they do not manufacture the observation at
@@ -158,47 +195,38 @@ The current aggregate release chain is split into independent commits:
- `7e25a09` — requires exact release readiness before browser upload and again
at authoritative Worker live-session admission.
-Reported verification for the integrated aggregate slice is:
-
-- runtime/request/observer/coordinator/receipt Vitest: **7 files, 68/68
- passed**;
-- complete runner Pytest with both required source roots: **82/82 passed**;
-- repository, web client, generated Worker, and Worker TypeScript: **passed**;
-- E2E strict TypeScript: **passed**;
-- Complete local stock-browser matrix: **40 tests in 3 files**, **35 passed,
- 5 intentionally skipped, 0 failed in 3.9 minutes** on fresh current-source
- local state. The public replay,
- public asset, and real hosted-runner cases remain deferred to the exact
- release tuple;
-- current web Vitest: **79 files, 626/626 passed**; current capability-enabled
- root Vitest: **750 passed, 3 stale-evidence failures, 2 skipped**;
-- current Python behavior: **330/331** in the restricted run plus the sole
- loopback-only service test **1/1** with socket permission;
-- critical mutations: leakage **14/14** and imbalance **19/19 detected**;
-- scoped Prettier, Python compilation, Node syntax, Git whitespace, and secret
- scans: **passed**;
-- current contained-runtime command-channel suite: **7 files, 65/65 passed**;
-- historical proof validation: Worker API **106/106 passed** and Proof Bundle
- package **12/12 passed**;
-- exact live admission: App **74/74 passed**, Worker API **107/107 passed**, and
- API/Judge **50/50 passed**;
-- repository-contained pnpm 11.13.1 production build: **passed** with 391 Worker
- and 231 client modules; Worker **1,868.90 kB / 360.50 kB gzip** and main
- client **449.75 kB / 129.26 kB gzip**;
-- current Node syntax, scoped Prettier, repository TypeScript, two-file secret
- scan, and Git whitespace: **passed**;
-- fresh primary runtime `rt-mainrel04`: schema-v2 response status **VERIFIED**;
- session attestation **READY**; live BuildKit client **v0.30.0**, containerd
- **v2.3.1**, and runc **1.4.2** version response confirmed;
-- independent runtime integration review: **GO** after the implementation
- repaired its initial async run/drain race and missing positive
- qualification-path coverage.
-
-The broad web/root/Python/browser matrix predates `7e25a09` and must be
-rerun on the final frozen source. The current focused results are source and
-runtime command-channel evidence only. They are not exact-image, aggregate
-containment, qualification, or deployment evidence; every downstream release
-node remains open.
+Latest product-code verification at `29ea8c3` is:
+
+- web Vitest: **80 files, 674/674 passed**;
+- Python kernel, runner, and release tests: **331/331 passed**;
+- leakage mutations: **14/14 detected**; imbalance mutations: **19/19
+ detected**;
+- held-out intake/routing: **10/10 passed**; patch-eligible completion: **7/7
+ verified**; Sample Boundary: **25 cells passed**; Sample Proof Capsule:
+ integrity root
+ `e45cf88f1bbdd470723f83def9550d47ac272f7c5aaf8554bec52c2a34f9856b`;
+- non-release scientific TypeScript: **11 files, 146/146 passed**; wider
+ scientific TypeScript: **18/20 files and 239/242 tests passed**, with all
+ three failures confined to stale exact-source/image registry bindings;
+- local CloakBrowser: **40 tests, 34 passed, 6 exact public/live skips, 0
+ failed**; fresh replay accessibility follow-up: **2/2 passed**;
+- root Vitest in the restricted sandbox: **73 files, 67 passed, 6 failed; 771
+ tests, 742 passed, 27 failed, 2 skipped**. Three loopback/child-process files
+ then passed **49/49** with the required capability. Read-isolation was not
+ rerun because its external observation is prohibited by the current
+ constitution; scientific registry failures remain intentionally red until
+ the exact image and evidence refresh;
+- repository, web, Worker, and E2E TypeScript, full Prettier, Git whitespace,
+ the 1,460-file secret scan, and the production build: **passed**;
+- production output: Worker **1,868.97 kB / 360.53 kB gzip**, main client
+ **449.90 kB / 129.30 kB gzip**, and CSS **201.45 kB / 34.83 kB gzip**.
+
+These results prove the latest product code and local browser behavior. The
+later committed checkpoint changes through `6b85f2a` and this graph refresh
+are documentation-only. The results do not manufacture a clean source, exact
+image, real aggregate containment observation, Cloudflare qualification,
+deployment, or public browser receipt; every downstream release node remains
+open.
## File ownership and reviewer graph
@@ -241,34 +269,39 @@ Only the lead stages, commits, deploys, pushes, or fast-forwards `main`.
## Disciplines and skills applied
-The owner explicitly authorized the installed skill instructions for this pass.
-The lead read and applied the following skills while preserving the checked-in
-repository constitution as the governing product and filesystem authority.
-
-| Discipline | Direct application in this graph |
-| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Repository safety | All repository work and generated state stay inside the verified root; the FUSE entry is preserved; no destructive cleanup, stash, reset, or outside-root filesystem operation is accepted. |
-| Code review | Each logical slice has focused characterization/regression tests, an isolated diff, independent review where assigned, and lead integration before an atomic commit. |
-| Accessibility | Browser closure requires semantic names, keyboard completion, visible focus, exact-value tables, non-colour meaning, reduced motion, zoom, responsive viewports, touch targets, and screen-reader inspection. |
-| React testing | Component and App behavior is checked with Vitest and strict TypeScript; Playwright covers composed learner journeys, authority labels, refresh, navigation, downloads, and no-result-before-Prediction behavior. |
-| Playwright and CloakBrowser | Static collection is not browser evidence; release qualification requires the injected CloakBrowser CDP authority, accessibility snapshots, screenshots where visual judgment matters, and console/network review. Stock Chromium cannot close the gate. |
-| Cloudflare and Wrangler | The repository-pinned Wrangler, contained account identity, exact registry digest, maintenance freeze, remote D1 migration, Container rollout, final Worker, recovery target, and public identity receipt form one ordered gate. |
-| Delivery gate | Clean `S` -> exact image -> real sentinel -> evidence `E` -> qualification -> release check -> deployment -> public smoke -> full browser matrix -> submission -> push/fast-forward merge. No later node may compensate for an earlier red node. |
-| Diagnosing bugs | Required the red command-server characterization before the one-line half-close repair, followed by focused and original-path runtime proof. |
-| Git workflow | Required selective staging, non-executable test mode, staged-diff review, configured owner identity, and the independent `b37f7d9` commit. |
-| Build graph | The last full structural graph remains the `b37f7d9` snapshot at 531 files, 5,515 nodes, and 93,346 edges. The operational release graph and current 140-file branch delta are updated here at `7e25a09`; structural counts are not relabelled without rerunning their generator. |
-| Code review | Runs standards and specification reviews as separate read-only axes against `main`; findings do not become closed until accepted tests and public evidence pass. |
-| Browser QA and Playwright | Keeps the stock matrix explicitly non-qualifying and requires the registered CloakBrowser authority, semantic snapshots, screenshots, console/network inspection, accessibility, and responsive evidence. |
-| Wrangler | Requires current documentation, repository-contained CLI state, authenticated-account proof, dry-run/preflight, exact version/digest capture, and qualified-only deployment. |
+The owner requested the listed engineering disciplines. At this checkpoint the
+repository constitution prohibits reading external skill files, so the lead
+applied the same disciplines directly from checked-in instructions and current
+repository evidence. The constitution remains the governing product and
+filesystem authority.
+
+| Discipline | Direct application in this graph |
+| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Repository safety | All repository work and generated state stay inside the verified root; the FUSE entry is preserved; no destructive cleanup, stash, reset, or outside-root filesystem operation is accepted. |
+| Code review | Each logical slice has focused characterization/regression tests, an isolated diff, independent review where assigned, and lead integration before an atomic commit. |
+| Accessibility | Browser closure requires semantic names, keyboard completion, visible focus, exact-value tables, non-colour meaning, reduced motion, zoom, responsive viewports, touch targets, and screen-reader inspection. |
+| React testing | Component and App behavior is checked with Vitest and strict TypeScript; Playwright covers composed learner journeys, authority labels, refresh, navigation, downloads, and no-result-before-Prediction behavior. |
+| Playwright and CloakBrowser | Static collection is not browser evidence; release qualification requires the injected CloakBrowser CDP authority, accessibility snapshots, screenshots where visual judgment matters, and console/network review. Stock Chromium cannot close the gate. |
+| Cloudflare and Wrangler | The repository-pinned Wrangler, contained account identity, exact registry digest, maintenance freeze, remote D1 migration, Container rollout, final Worker, recovery target, and public identity receipt form one ordered gate. |
+| Delivery gate | Clean `S` -> exact image -> real sentinel -> evidence `E` -> qualification -> release check -> deployment -> public smoke -> full browser matrix -> feature push/repo verification -> submission -> fast-forward main. No later node may compensate for an earlier red node. |
+| Diagnosing bugs | Required the red command-server characterization before the one-line half-close repair, followed by focused and original-path runtime proof. |
+| Git workflow | Requires selective staging, staged-diff review, configured owner identity, independent logical commits, and no merge or publication before their factual gates. |
+| Build graph | The last full structural graph remains the `b37f7d9` snapshot at 531 files, 5,515 nodes, and 93,346 edges. The operational release graph is current at `6b85f2a`; structural counts are not relabelled without rerunning their generator. |
+| Code review | Runs standards and specification reviews as separate read-only axes against `main`; findings do not become closed until accepted tests and public evidence pass. |
+| Browser QA and Playwright | Keeps local CloakBrowser evidence explicitly non-qualifying and requires exact-public CloakBrowser authority, semantic snapshots, screenshots, console/network inspection, accessibility, and responsive evidence. |
+| Wrangler | Requires current documentation, repository-contained CLI state, authenticated-account proof, dry-run/preflight, exact version/digest capture, and qualified-only deployment. |
## Current blockers and shortest honest path
1. The source-level aggregate runtime chain is implemented and reviewed, but a
real delegated-cgroup sentinel has not run.
-2. The current constitution prohibits the sentinel's necessary `/sys/fs/cgroup`
- and owned `/proc//stat` reads. The release must remain `NO-GO` unless the
- checked-in authority changes or another constitution-compatible evidence
- path is established.
+2. The current constitution prohibits the sentinel's necessary delegated
+ `/sys/fs/cgroup` control reads, owned `/proc//stat` reads, owned-helper
+ membership write to delegated `cgroup.procs`, and helper-local
+ `/proc/self/oom_score_adj` write. Read-only permission is insufficient. The
+ release must remain `NO-GO` unless the checked-in authority explicitly
+ permits these narrow agent-owned/delegated operations or another
+ constitution-compatible evidence path is established.
3. Both retained linked-worktree candidates were attempted and disproved as
runtime release roots: their physical paths exceed the 42-character
containerd shim limit. The primary physical root is exactly 42 characters
@@ -276,16 +309,17 @@ repository constitution as the governing product and filesystem authority.
prevents a clean frozen source.
4. No current-source image, scientific-evidence refresh, evidence commit,
qualified receipt, or release-check receipt exists.
-5. Contained Wrangler authentication is explicitly red: a fresh
- `wrangler whoami --json` at `7e25a09` returned `{"loggedIn":false}`. The
- intended Cloudflare account has not been authenticated inside the
- repository-contained environment for this release.
-6. The Cloak launcher registration is enabled, but the current Codex session
- predates it and exposes no `playwright_safe` tools or `CLOAK_CDP_ENDPOINT`.
- A fresh session is required. `deploy-qualified.sh` performs remote Worker,
- D1, and Container mutations before invoking the smoke script, while
- `production-smoke.sh` requires CloakBrowser. CloakBrowser must therefore be
- preflighted before deployment starts.
+5. Contained Wrangler authentication is explicitly red: the current
+ repository-contained `wrangler whoami --json` returned
+ `{"loggedIn":false}`, and two fresh OAuth callback windows expired without
+ a returned authorization code. The intended Cloudflare account has not been
+ authenticated inside the contained environment for this release.
+6. CloakBrowser is operational and the local latest-product-code matrix at
+ `29ea8c3` is green.
+ `deploy-qualified.sh` performs remote Worker, D1, and Container mutations
+ before invoking the smoke script, so the exact public origin and deployment
+ receipt must still be supplied to the qualifying run. Local browser
+ evidence cannot close that public gate.
7. Commit `0ee8934` closes the checked-in browser finalizer source path: seven
ID-bound viewports, privacy-safe INP, schema-v3 raw evidence, deterministic
40-journey receipts, ten honestly labelled human-observation receipts, and
@@ -296,11 +330,12 @@ repository constitution as the governing product and filesystem authority.
The shortest honest path is to resolve the clean-source condition at the primary
physical root without hiding or deleting the protected tombstone; obtain narrow
-read-only cgroup-observation authority; freeze `S`; build one exact image; run
-the genuine sentinel; refresh and commit evidence as `E`; authenticate contained
-Wrangler; qualify and release-check; preflight CloakBrowser; deploy once; verify
-the exact public tuple and browser matrix; finalize the factual submission; then
-push and fast-forward merge.
+authority for the sentinel's delegated/owned cgroup and process operations;
+freeze `S`; build one exact image; run the genuine sentinel; refresh and commit
+evidence as `E`; authenticate contained Wrangler; qualify and release-check;
+preflight CloakBrowser; deploy once; verify the exact public tuple and browser
+matrix; push and verify the exact feature tip; finalize the factual submission;
+then fast-forward and push `main`.
## Final release review questions
@@ -320,12 +355,12 @@ push and fast-forward merge.
`NO_DATA`?
- Do the app, repository, video, screenshots, feedback ID, Devpost copy, and
submission receipt describe the same exact release?
-- Was the feature branch pushed and verified before a fast-forward-only merge
- to `main`?
+- Was the exact feature branch pushed and verified logged out before
+ submission, and was `main` fast-forwarded only after the receipt existed?
Any `no`, unknown, or unavailable answer keeps the corresponding gate open.
-## Browser evidence graph addendum — `a6f6d8d`
+## Historical browser evidence graph — `a6f6d8d` (superseded)
```mermaid
flowchart LR
@@ -342,7 +377,8 @@ flowchart LR
PublicRun --> Submission["Submission evidence — open"]
```
-Committed source now fails closed on stock authority, non-loopback CDP,
+This historical checkpoint is superseded by the source-complete `0ee8934`
+finalizer below. At `a6f6d8d`, committed source failed closed on stock authority, non-loopback CDP,
wrong public origin, ID or viewport drift, retries/skips, fixture-only
assertions, telemetry mismatch, reversed chronology, unexpected browser
failures, symlink traversal, and evidence overwrite. This closes the browser
From 6ee595416792c1630f44037c12a005ac5f33c267 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 19:42:19 +0530
Subject: [PATCH 112/143] fix(runtime): recover preserved contained session
safely
---
scripts/contained-runtime-stop-compat.test.ts | 529 +++++++
.../stop-contained-runtime-v3-compat.d.mts | 100 ++
scripts/stop-contained-runtime-v3-compat.mjs | 1238 +++++++++++++++++
3 files changed, 1867 insertions(+)
create mode 100644 scripts/contained-runtime-stop-compat.test.ts
create mode 100644 scripts/stop-contained-runtime-v3-compat.d.mts
create mode 100644 scripts/stop-contained-runtime-v3-compat.mjs
diff --git a/scripts/contained-runtime-stop-compat.test.ts b/scripts/contained-runtime-stop-compat.test.ts
new file mode 100644
index 00000000..6d1051a5
--- /dev/null
+++ b/scripts/contained-runtime-stop-compat.test.ts
@@ -0,0 +1,529 @@
+import { createHash } from "node:crypto";
+import { readFileSync, readdirSync } from "node:fs";
+import { resolve } from "node:path";
+
+import { describe, expect, it, vi } from "vitest";
+
+import {
+ COMPAT_STOP_TARGET,
+ createHistoricalRecoveryReceipt,
+ createLegacyRuntimeRequest,
+ executeCompatRecoveryProtocol,
+ validateCompatTarget,
+ validateLegacyRuntimeResponse,
+} from "./stop-contained-runtime-v3-compat.mjs";
+import {
+ canonicalSupervisorJson,
+ sha256SupervisorBytes,
+ validateRuntimeDrainReceipt,
+} from "./contained-runtime-supervisor-protocol.mjs";
+
+const root = process.cwd();
+
+function repositoryScriptFiles(directory: string): string[] {
+ return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
+ const path = resolve(directory, entry.name);
+ if (entry.isDirectory()) return repositoryScriptFiles(path);
+ return /\.(?:json|mjs|py|sh|ts)$/u.test(entry.name) ? [path] : [];
+ });
+}
+
+function sha256(value: Buffer | string) {
+ return createHash("sha256").update(value).digest("hex");
+}
+
+function hashed>(payload: T) {
+ return {
+ ...payload,
+ receiptPayloadSha256: sha256SupervisorBytes(
+ canonicalSupervisorJson(payload),
+ ),
+ };
+}
+
+function attestation() {
+ return {
+ sessionId: COMPAT_STOP_TARGET.sessionId,
+ pids: {
+ supervisor: 101,
+ containerdRootlesskit: 102,
+ buildkitRootlesskit: 103,
+ },
+ };
+}
+
+function emptyInventory() {
+ return {
+ count: 0,
+ entriesSha256: sha256SupervisorBytes(""),
+ empty: true,
+ };
+}
+
+function drainReceipt(attestationSha256: string) {
+ const empty = emptyInventory();
+ return hashed({
+ schemaVersion: "2",
+ status: "DRAINED",
+ sessionId: COMPAT_STOP_TARGET.sessionId,
+ namespace: "counterlab-v6.1",
+ attestationSha256,
+ tasks: empty,
+ containers: empty,
+ snapshots: {
+ count: 2,
+ entriesSha256: "b".repeat(64),
+ ownedTransientEmpty: true,
+ },
+ invocationAliases: empty,
+ runcState: empty,
+ clientFifos: empty,
+ persistedSpecs: {
+ count: 2,
+ entriesSha256: "c".repeat(64),
+ verified: true,
+ },
+ drainedAt: "2026-07-21T00:00:00.000Z",
+ });
+}
+
+function supervisorTransition(
+ status: "DRAINING" | "STOPPED",
+ attestationSha256: string,
+ receipt?: ReturnType,
+) {
+ const base = {
+ schemaVersion: "1",
+ status,
+ sessionId: COMPAT_STOP_TARGET.sessionId,
+ namespace: "counterlab-v6.1",
+ state: status,
+ supervisorPid: 101,
+ childPids: {
+ containerdRootlesskit: 102,
+ buildkitRootlesskit: 103,
+ },
+ childHandlesOwned: true,
+ buildkitAdmissionOpen: false,
+ activeBuildkitConnections: 0,
+ attestationSha256,
+ };
+ if (status === "DRAINING") return base;
+ return {
+ ...base,
+ childExits: {
+ containerdRootlesskit: { code: 0, signal: null },
+ buildkitRootlesskit: { code: null, signal: "SIGTERM" },
+ },
+ drainReceiptSha256: sha256SupervisorBytes(canonicalSupervisorJson(receipt)),
+ };
+}
+
+function protocolHarness(options?: {
+ mutateReceipt?: (value: ReturnType) => unknown;
+ postDrainExitCode?: number;
+ postDrainMessage?: string;
+ shutdownAttestationSha256?: string;
+ beginSupervisorPid?: number;
+ closureResult?: boolean;
+ readAttestationBytes?: Buffer;
+ invalidChildExit?: boolean;
+}) {
+ const attestationBytes = Buffer.from("pinned-attestation");
+ const attestationSha256 = sha256(attestationBytes);
+ const receipt = drainReceipt(attestationSha256);
+ const calls: string[] = [];
+ const requestSupervisor = vi.fn(async ({ request }) => {
+ calls.push(`supervisor:${request.action}`);
+ if (request.action === "begin-drain") {
+ return {
+ ...supervisorTransition("DRAINING", attestationSha256),
+ supervisorPid: options?.beginSupervisorPid ?? 101,
+ };
+ }
+ const response = supervisorTransition(
+ "STOPPED",
+ options?.shutdownAttestationSha256 ?? attestationSha256,
+ receipt,
+ );
+ if (options?.invalidChildExit) {
+ return {
+ ...response,
+ childExits: {
+ containerdRootlesskit: { code: null, signal: null },
+ buildkitRootlesskit: { code: 0, signal: null },
+ },
+ };
+ }
+ return response;
+ });
+ const requestRuntime = vi.fn(async ({ args }) => {
+ calls.push(`runtime:${args[0]}`);
+ if (args[0] === "counterlab-drain") {
+ return {
+ exitCode: 0,
+ stdout: Buffer.from(
+ JSON.stringify(options?.mutateReceipt?.(receipt) ?? receipt),
+ ),
+ stderr: Buffer.alloc(0),
+ };
+ }
+ return {
+ exitCode: options?.postDrainExitCode ?? 1,
+ stdout: Buffer.alloc(0),
+ stderr: Buffer.from(
+ options?.postDrainMessage ?? "contained runtime is drained",
+ ),
+ };
+ });
+ const waitForClosure = vi.fn(async () => {
+ calls.push("closure");
+ return options?.closureResult ?? true;
+ });
+ const resolved = {
+ runtimeSupervisorSocket: "supervisor.sock",
+ runtimeCommandSocket: "runtime.sock",
+ buildkitSocket: "buildkit.sock",
+ };
+ return {
+ attestationBytes,
+ attestationSha256,
+ calls,
+ receipt,
+ readAttestationBytes: options?.readAttestationBytes,
+ requestRuntime,
+ requestSupervisor,
+ resolved,
+ waitForClosure,
+ };
+}
+
+describe("target-locked historical runtime recovery", () => {
+ it("pins the exact preserved runtime identity without a CI runtime fixture", () => {
+ expect(COMPAT_STOP_TARGET).toMatchObject({
+ sessionId: "rt-release721",
+ attestationSha256:
+ "2f12fe8f9db649f0564238a4569da9e98f5b8e31f085daec14f93538ba9d4569",
+ historicalBundleCommit: "85c5652afc75230a28209883153814ca6cb64c36",
+ supervisorReadySha256:
+ "7446f013639a75c85d59ecb0a39ffa0983fa3d70b9d0623d570e87284462d2c0",
+ });
+ });
+
+ it("rejects every session or attestation other than the pinned target", () => {
+ expect(() =>
+ validateCompatTarget(
+ COMPAT_STOP_TARGET.sessionId,
+ COMPAT_STOP_TARGET.attestationSha256,
+ ),
+ ).not.toThrow();
+ expect(() =>
+ validateCompatTarget("rt-other123", COMPAT_STOP_TARGET.attestationSha256),
+ ).toThrow(/not allowlisted/u);
+ expect(() =>
+ validateCompatTarget(COMPAT_STOP_TARGET.sessionId, "a".repeat(64)),
+ ).toThrow(/not allowlisted/u);
+ });
+
+ it("sends only the historical v1 envelope", () => {
+ const request = createLegacyRuntimeRequest([
+ "counterlab-drain",
+ COMPAT_STOP_TARGET.attestationSha256,
+ ]);
+ expect(JSON.parse(request)).toEqual({
+ schemaVersion: "1",
+ args: ["counterlab-drain", COMPAT_STOP_TARGET.attestationSha256],
+ stdinBase64: "",
+ });
+ expect(request).not.toContain("qualificationMode");
+ });
+
+ it("rejects malformed and non-canonical legacy responses", () => {
+ expect(() =>
+ validateLegacyRuntimeResponse({
+ schemaVersion: "1",
+ exitCode: 0,
+ stdoutBase64: "not base64",
+ stderrBase64: "",
+ }),
+ ).toThrow(/base64/u);
+ expect(() =>
+ validateLegacyRuntimeResponse({
+ schemaVersion: "1",
+ exitCode: 0,
+ stdoutBase64: "YQ===",
+ stderrBase64: "",
+ }),
+ ).toThrow(/base64/u);
+ });
+
+ it("executes drain, admission rejection, shutdown, and closure in order", async () => {
+ const harness = protocolHarness();
+ const result = await executeCompatRecoveryProtocol({
+ attestation: attestation(),
+ attestationPath: "attestation.json",
+ attestationSha256: harness.attestationSha256,
+ resolved: harness.resolved,
+ requestSupervisor: harness.requestSupervisor,
+ requestRuntime: harness.requestRuntime,
+ waitForClosure: harness.waitForClosure,
+ readAttestation: () => harness.attestationBytes,
+ validateDrainReceipt: validateRuntimeDrainReceipt,
+ });
+ expect(result.drainReceipt).toEqual(harness.receipt);
+ expect(harness.calls).toEqual([
+ "supervisor:begin-drain",
+ "runtime:counterlab-drain",
+ "runtime:version",
+ "supervisor:shutdown",
+ "closure",
+ ]);
+ expect(harness.waitForClosure).toHaveBeenCalledWith([
+ "runtime.sock",
+ "buildkit.sock",
+ "supervisor.sock",
+ ]);
+ });
+
+ it("blocks shutdown when drain evidence is nonempty or tampered", async () => {
+ const harness = protocolHarness({
+ mutateReceipt: (value) => ({
+ ...value,
+ tasks: {
+ count: 1,
+ entriesSha256: "d".repeat(64),
+ empty: false,
+ },
+ }),
+ });
+ await expect(
+ executeCompatRecoveryProtocol({
+ attestation: attestation(),
+ attestationPath: "attestation.json",
+ attestationSha256: harness.attestationSha256,
+ resolved: harness.resolved,
+ requestSupervisor: harness.requestSupervisor,
+ requestRuntime: harness.requestRuntime,
+ waitForClosure: harness.waitForClosure,
+ readAttestation: () => harness.attestationBytes,
+ validateDrainReceipt: validateRuntimeDrainReceipt,
+ }),
+ ).rejects.toThrow(/task inventory|hash/u);
+ expect(harness.calls).not.toContain("supervisor:shutdown");
+ });
+
+ it("blocks shutdown if commands remain admitted after drain", async () => {
+ const harness = protocolHarness({ postDrainExitCode: 0 });
+ await expect(
+ executeCompatRecoveryProtocol({
+ attestation: attestation(),
+ attestationPath: "attestation.json",
+ attestationSha256: harness.attestationSha256,
+ resolved: harness.resolved,
+ requestSupervisor: harness.requestSupervisor,
+ requestRuntime: harness.requestRuntime,
+ waitForClosure: harness.waitForClosure,
+ readAttestation: () => harness.attestationBytes,
+ validateDrainReceipt: validateRuntimeDrainReceipt,
+ }),
+ ).rejects.toThrow(/admitted a command/u);
+ expect(harness.calls).not.toContain("supervisor:shutdown");
+ });
+
+ it("blocks shutdown when the supervisor identity is mismatched", async () => {
+ const harness = protocolHarness({ beginSupervisorPid: 999 });
+ await expect(
+ executeCompatRecoveryProtocol({
+ attestation: attestation(),
+ attestationPath: "attestation.json",
+ attestationSha256: harness.attestationSha256,
+ resolved: harness.resolved,
+ requestSupervisor: harness.requestSupervisor,
+ requestRuntime: harness.requestRuntime,
+ waitForClosure: harness.waitForClosure,
+ readAttestation: () => harness.attestationBytes,
+ validateDrainReceipt: validateRuntimeDrainReceipt,
+ }),
+ ).rejects.toThrow(/transition/u);
+ expect(harness.calls).toEqual(["supervisor:begin-drain"]);
+ });
+
+ it("blocks shutdown when the drain rejection text is not exact", async () => {
+ const harness = protocolHarness({ postDrainMessage: "generic failure" });
+ await expect(
+ executeCompatRecoveryProtocol({
+ attestation: attestation(),
+ attestationPath: "attestation.json",
+ attestationSha256: harness.attestationSha256,
+ resolved: harness.resolved,
+ requestSupervisor: harness.requestSupervisor,
+ requestRuntime: harness.requestRuntime,
+ waitForClosure: harness.waitForClosure,
+ readAttestation: () => harness.attestationBytes,
+ validateDrainReceipt: validateRuntimeDrainReceipt,
+ }),
+ ).rejects.toThrow(/admitted a command/u);
+ expect(harness.calls).not.toContain("supervisor:shutdown");
+ });
+
+ it("blocks shutdown when the attestation changes after drain", async () => {
+ const harness = protocolHarness({
+ readAttestationBytes: Buffer.from("changed-attestation"),
+ });
+ await expect(
+ executeCompatRecoveryProtocol({
+ attestation: attestation(),
+ attestationPath: "attestation.json",
+ attestationSha256: harness.attestationSha256,
+ resolved: harness.resolved,
+ requestSupervisor: harness.requestSupervisor,
+ requestRuntime: harness.requestRuntime,
+ waitForClosure: harness.waitForClosure,
+ readAttestation: () =>
+ harness.readAttestationBytes ?? harness.attestationBytes,
+ validateDrainReceipt: validateRuntimeDrainReceipt,
+ }),
+ ).rejects.toThrow(/attestation changed/u);
+ expect(harness.calls).not.toContain("supervisor:shutdown");
+ });
+
+ it("writes no success path after a mismatched shutdown response", async () => {
+ const harness = protocolHarness({
+ shutdownAttestationSha256: "f".repeat(64),
+ });
+ await expect(
+ executeCompatRecoveryProtocol({
+ attestation: attestation(),
+ attestationPath: "attestation.json",
+ attestationSha256: harness.attestationSha256,
+ resolved: harness.resolved,
+ requestSupervisor: harness.requestSupervisor,
+ requestRuntime: harness.requestRuntime,
+ waitForClosure: harness.waitForClosure,
+ readAttestation: () => harness.attestationBytes,
+ validateDrainReceipt: validateRuntimeDrainReceipt,
+ }),
+ ).rejects.toThrow(/shutdown binding/u);
+ expect(harness.waitForClosure).not.toHaveBeenCalled();
+ });
+
+ it("rejects malformed child exits and endpoints that remain reachable", async () => {
+ const invalidExit = protocolHarness({ invalidChildExit: true });
+ await expect(
+ executeCompatRecoveryProtocol({
+ attestation: attestation(),
+ attestationPath: "attestation.json",
+ attestationSha256: invalidExit.attestationSha256,
+ resolved: invalidExit.resolved,
+ requestSupervisor: invalidExit.requestSupervisor,
+ requestRuntime: invalidExit.requestRuntime,
+ waitForClosure: invalidExit.waitForClosure,
+ readAttestation: () => invalidExit.attestationBytes,
+ validateDrainReceipt: validateRuntimeDrainReceipt,
+ }),
+ ).rejects.toThrow(/child exit/u);
+ expect(invalidExit.waitForClosure).not.toHaveBeenCalled();
+
+ const openEndpoint = protocolHarness({ closureResult: false });
+ await expect(
+ executeCompatRecoveryProtocol({
+ attestation: attestation(),
+ attestationPath: "attestation.json",
+ attestationSha256: openEndpoint.attestationSha256,
+ resolved: openEndpoint.resolved,
+ requestSupervisor: openEndpoint.requestSupervisor,
+ requestRuntime: openEndpoint.requestRuntime,
+ waitForClosure: openEndpoint.waitForClosure,
+ readAttestation: () => openEndpoint.attestationBytes,
+ validateDrainReceipt: validateRuntimeDrainReceipt,
+ }),
+ ).rejects.toThrow(/endpoint remained reachable/u);
+ });
+
+ it("is non-destructive and cannot create release qualification evidence", () => {
+ const source = readFileSync(
+ resolve(root, "scripts/stop-contained-runtime-v3-compat.mjs"),
+ "utf8",
+ );
+ expect(source).not.toMatch(
+ /(?:\/proc(?:\/|\b)|\/sys(?:\/|\b)|process\.kill|SIGKILL|unlink|rmdir|rmSync|shutil\.rmtree)/u,
+ );
+ expect(source).not.toContain("safe-stop-receipt.json");
+ expect(source).toContain('"historical-recovery-receipt.json"');
+ expect(source).toContain("qualificationEligible: false");
+ expect(source).toContain("releaseQualification: false");
+ expect(source).toContain('status: "RECOVERY_STOPPED"');
+ expect(source).toContain('"/usr/bin/flock"');
+ expect(source).toContain("validateCurrentStopPrimitives(attestation)");
+ expect(source).toContain(
+ 'await import("./contained-runtime-supervisor-protocol.mjs")',
+ );
+ expect(
+ source.indexOf("validateCurrentStopPrimitives(attestation)"),
+ ).toBeLessThan(
+ source.indexOf(
+ 'await import("./contained-runtime-supervisor-protocol.mjs")',
+ ),
+ );
+ expect(source).toContain(
+ "await validateInstalledToolchain(resolved.installRoot, historicalLock)",
+ );
+ const processExecutables = [
+ ...source.matchAll(/(?:execFileSync|spawnSync)\(\s*"([^"]+)"/gu),
+ ].map((match) => match[1]);
+ expect(new Set(processExecutables)).toEqual(
+ new Set(["/usr/bin/git", "/usr/bin/flock"]),
+ );
+ expect(source).not.toMatch(/(?:\/bin\/rm|\/usr\/bin\/find|find -delete)/u);
+ expect(
+ source.lastIndexOf("validatePreLockIdentity(process.argv.slice(2))"),
+ ).toBeLessThan(source.lastIndexOf("runWithRepositoryLock();"));
+ expect(source).toContain("assertRepositoryLockHeld()");
+
+ const recoveryConsumers = repositoryScriptFiles(
+ resolve(root, "scripts"),
+ ).filter(
+ (path) =>
+ !path.includes("stop-contained-runtime-v3-compat") &&
+ !path.endsWith("contained-runtime-stop-compat.test.ts") &&
+ readFileSync(path, "utf8").includes("historical-recovery-receipt.json"),
+ );
+ expect(recoveryConsumers).toEqual([]);
+ });
+
+ it("constructs a recovery receipt that is semantically ineligible", () => {
+ const harness = protocolHarness();
+ const value = attestation();
+ const shutdown = supervisorTransition(
+ "STOPPED",
+ harness.attestationSha256,
+ harness.receipt,
+ );
+ const receipt = createHistoricalRecoveryReceipt({
+ attestation: {
+ ...value,
+ runtimeToolchainSha256: "e".repeat(64),
+ },
+ attestationSha256: harness.attestationSha256,
+ compatStopHelperSha256: "f".repeat(64),
+ drainReceipt: harness.receipt,
+ shutdown,
+ sessionDirectoryPreserved: true,
+ stoppedAt: "2026-07-21T00:00:00.000Z",
+ });
+ expect(receipt).toMatchObject({
+ status: "RECOVERY_STOPPED",
+ purpose: "historical-runtime-recovery",
+ qualificationEligible: false,
+ releaseQualification: false,
+ });
+ const qualificationView: {
+ status: string;
+ releaseQualification: boolean;
+ } = receipt;
+ expect(
+ qualificationView.status === "STOPPED" &&
+ qualificationView.releaseQualification,
+ ).toBe(false);
+ });
+});
diff --git a/scripts/stop-contained-runtime-v3-compat.d.mts b/scripts/stop-contained-runtime-v3-compat.d.mts
new file mode 100644
index 00000000..32ed8036
--- /dev/null
+++ b/scripts/stop-contained-runtime-v3-compat.d.mts
@@ -0,0 +1,100 @@
+export interface CompatStopTarget {
+ readonly sessionId: "rt-release721";
+ readonly attestationSha256: string;
+ readonly historicalBundleCommit: string;
+ readonly helperManifestSha256: string;
+ readonly runtimeToolchainSha256: string;
+ readonly toolchainLockSha256: string;
+ readonly adapterSha256: string;
+ readonly runtimePolicySha256: string;
+ readonly proofDependencyManifestSha256: string;
+ readonly supervisorReadySha256: string;
+ readonly containerdConfigSha256: string;
+ readonly buildkitConfigSha256: string;
+}
+
+export interface CompatAttestation {
+ sessionId: string;
+ pids: {
+ supervisor: number;
+ containerdRootlesskit: number;
+ buildkitRootlesskit: number;
+ };
+ runtimeToolchainSha256?: string;
+ [key: string]: unknown;
+}
+
+export interface LegacyRuntimeResponse {
+ exitCode: number;
+ stdout: Buffer;
+ stderr: Buffer;
+}
+
+export declare const COMPAT_STOP_TARGET: CompatStopTarget;
+
+export declare function validateCompatTarget(
+ sessionId: string,
+ attestationSha256: string,
+): void;
+export declare function createLegacyRuntimeRequest(args: string[]): string;
+export declare function validateLegacyRuntimeResponse(
+ value: unknown,
+): LegacyRuntimeResponse;
+export declare function validateTargetAttestation(
+ attestation: unknown,
+ sessionId: string,
+): Record;
+export declare function validateHistoricalBundle(
+ attestation: unknown,
+): Record;
+export declare function validateCurrentStopPrimitives(
+ attestation: unknown,
+): void;
+
+export declare function executeCompatRecoveryProtocol(input: {
+ attestation: CompatAttestation;
+ attestationPath: string;
+ attestationSha256: string;
+ resolved: {
+ runtimeSupervisorSocket: string;
+ runtimeCommandSocket: string;
+ buildkitSocket: string;
+ };
+ requestSupervisor: (input: {
+ socketPath: string;
+ timeoutMs: number;
+ request: Record;
+ }) => Promise>;
+ requestRuntime: (input: {
+ socketPath: string;
+ timeoutMs: number;
+ args: string[];
+ }) => Promise;
+ waitForClosure: (paths: string[]) => Promise;
+ readAttestation: (path: string) => Buffer;
+ validateDrainReceipt: (
+ value: unknown,
+ expected: { sessionId: string; attestationSha256: string },
+ ) => unknown;
+}): Promise<{
+ drainReceipt: unknown;
+ shutdown: Record;
+}>;
+
+export declare function createHistoricalRecoveryReceipt(input: {
+ attestation: CompatAttestation;
+ attestationSha256: string;
+ compatStopHelperSha256: string;
+ drainReceipt: unknown;
+ shutdown: Record;
+ sessionDirectoryPreserved: true;
+ stoppedAt: string;
+}): {
+ schemaVersion: "1";
+ status: "RECOVERY_STOPPED";
+ purpose: "historical-runtime-recovery";
+ qualificationEligible: false;
+ releaseQualification: false;
+ receiptPayloadSha256: string;
+ [key: string]: unknown;
+};
diff --git a/scripts/stop-contained-runtime-v3-compat.mjs b/scripts/stop-contained-runtime-v3-compat.mjs
new file mode 100644
index 00000000..fa3eff67
--- /dev/null
+++ b/scripts/stop-contained-runtime-v3-compat.mjs
@@ -0,0 +1,1238 @@
+#!/usr/bin/env node
+
+import { execFileSync, spawnSync } from "node:child_process";
+import { createHash } from "node:crypto";
+import {
+ createReadStream,
+ existsSync,
+ lstatSync,
+ readFileSync,
+ realpathSync,
+ statSync,
+ writeFileSync,
+} from "node:fs";
+import { connect } from "node:net";
+import { isAbsolute, relative, resolve, sep } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const root = realpathSync(resolve(fileURLToPath(import.meta.url), "../.."));
+const self = realpathSync(fileURLToPath(import.meta.url));
+const sha256Pattern = /^[a-f0-9]{64}$/u;
+
+const HISTORICAL_COMPONENT_VERSIONS = Object.freeze({
+ buildctl: "0.30.0",
+ buildkitd: "0.30.0",
+ containerd: "2.3.1",
+ "containerd-shim-runc-v2": "2.3.1",
+ ctr: "2.3.1",
+ nerdctl: "2.3.1",
+ rootlesskit: "3.0.0",
+ runc: "1.4.2",
+});
+
+export const COMPAT_STOP_TARGET = Object.freeze({
+ sessionId: "rt-release721",
+ attestationSha256:
+ "2f12fe8f9db649f0564238a4569da9e98f5b8e31f085daec14f93538ba9d4569",
+ historicalBundleCommit: "85c5652afc75230a28209883153814ca6cb64c36",
+ helperManifestSha256:
+ "31db93f95a991fc128d6b1ee30891f107eeac5462faf4813e9ecaa771fdbc0c9",
+ runtimeToolchainSha256:
+ "bbf0324d9e228f869f2a7fb75758c53ff4779417447bef8c266c5afa119c66b0",
+ toolchainLockSha256:
+ "847199eae28241f7a37ee9a85087d1d2a137b741be106ab3907f3e3bdfe9aa32",
+ adapterSha256:
+ "f368efecf9ad10bd47a0d9a5366454f24a1f30efdff8eafe96fa41d5943cc202",
+ runtimePolicySha256:
+ "f330a93a90d9b58e1fc7338f363cf4c9a7fdab36d88b42208bded5987f675aac",
+ proofDependencyManifestSha256:
+ "7525d8598e90cf8f93edfadf695fd723d1b75041fe7aceba0e9ab701b8f6bf86",
+ supervisorReadySha256:
+ "7446f013639a75c85d59ecb0a39ffa0983fa3d70b9d0623d570e87284462d2c0",
+ containerdConfigSha256:
+ "bdbaae4d53c246e3f3a4e1851d29b8c24d9020748b96d8906e0a76b287d6baba",
+ buildkitConfigSha256:
+ "05860153585b0b3623bbc8b68c00ded38ce83930748fd67992bc9e8056cb21d5",
+});
+
+const HISTORICAL_HELPER_PATHS = Object.freeze({
+ runtimeClient: "scripts/contained-runtime-client.mjs",
+ runtimeRun: "scripts/contained-runtime-run.mjs",
+ rootlessSpec: "scripts/contained-rootless-spec.mjs",
+ imageAuthority: "scripts/contained-image-authority.mjs",
+ runtimeEnvironment: "scripts/contained-runtime-environment.mjs",
+ runtimeAttestation: "scripts/contained-runtime-attestation.mjs",
+ attestationWriter: "scripts/write-contained-runtime-attestation.mjs",
+ runtimeSupervisor: "scripts/contained-runtime-supervisor.mjs",
+ supervisorProtocol: "scripts/contained-runtime-supervisor-protocol.mjs",
+ runcWrapper: "scripts/runtime-bin/runc",
+ containerdConfigWriter: "scripts/contained-containerd-config.mjs",
+ runtimeServer: "scripts/contained-runtime-server.mjs",
+ commandValidator: "scripts/validate-contained-runtime-command.mjs",
+ attestationVerifier: "scripts/verify-contained-runtime.mjs",
+ runtimeLauncher: "scripts/start-contained-runtime.sh",
+ runtimeStop: "scripts/stop-contained-runtime.mjs",
+ timeoutProofDriver: "scripts/verify-contained-runtime-timeout.py",
+ timeoutProofModule: "services/runner/src/counterlab_runner/timeout_proof.py",
+});
+
+const HISTORICAL_HELPER_SHA256 = Object.freeze({
+ runtimeClient:
+ "2de5ed10ca5012342550f80dd522b1c6dc59e16c898963808667c4e5429e4cc8",
+ runtimeRun:
+ "7cc096aa5c933a8e3a8eca5d0584096d20a6c37e80a76bcf1715ecbb53686eb0",
+ rootlessSpec:
+ "430205629ac01cc2353a9f40c159c6f8b2f616a3f58af3d9a4ec896176f6c445",
+ imageAuthority:
+ "f4cd4e4aceeddf490cb241b0c650d3621917b46ee91b4e69a941947ab6fb6861",
+ runtimeEnvironment:
+ "7e1d1921f254e7296b5fa41e1c44fb4520accfb3547778c7997c54af32352b66",
+ runtimeAttestation:
+ "ecc07ce71852aa5868f9124123dd75397c47291c969ed3766baeab043762f0dc",
+ attestationWriter:
+ "8ea2a533657c58719f9ecc387e94f2ec1dd8d69cb51c5db01aa09cfe1edc8246",
+ runtimeSupervisor:
+ "d757ab43fff5f712ac540e1df4073c8ddfe4c4553f1dffd21757f65e075ceab3",
+ supervisorProtocol:
+ "0e180a70b5d09ca695c20fe9a9f82afad64da3fd0a69e535bf28c492c3965bb9",
+ runcWrapper:
+ "610c3d99a15a9f805f459295a8283bf9ab9808f4c6a41a422bb6fe40d621d813",
+ containerdConfigWriter:
+ "30529840626300dbde4e8f1a22a526d9aacacdd973b402cc06c93b08a7385f92",
+ runtimeServer:
+ "1aa52787d2f24cbbd3d61a693e44a5a26a6dcfbac9589888fd36bf390743c062",
+ commandValidator:
+ "4f087cc1cceef985e3bdf1afb0abe6c661fb984c5ac8883aee9067fce355c60f",
+ attestationVerifier:
+ "3cbd97332a47b36b8c7eabb0281e92d6b5128091f735f967c3d5fc3fd07d91b4",
+ runtimeLauncher:
+ "47243e86e7abf182d18e55b67e5c7baa44cc3369b4c91c926d35eb27142e7d52",
+ runtimeStop:
+ "d39a76fda5c10399743d036f106ce94103c9ae02edc13311b84a656ce233abef",
+ timeoutProofDriver:
+ "64a5cf09e935898c38c5d7acb68cbe924e81465915dc7e6848e33240361c4935",
+ timeoutProofModule:
+ "68948a3d8fde48c38ead993d32afc37d40082e0b16bb006c831200a07f7d0e4c",
+});
+
+function canonicalJson(value) {
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
+ if (value !== null && typeof value === "object") {
+ return `{${Object.keys(value)
+ .sort()
+ .map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
+ .join(",")}}`;
+ }
+ return JSON.stringify(value);
+}
+
+function sha256(value) {
+ return createHash("sha256").update(value).digest("hex");
+}
+
+function sha256File(path) {
+ return new Promise((accept, reject) => {
+ const hash = createHash("sha256");
+ const source = createReadStream(path);
+ source.once("error", reject);
+ source.on("data", (chunk) => hash.update(chunk));
+ source.once("end", () => accept(hash.digest("hex")));
+ });
+}
+
+function exactObject(value, keys, label) {
+ if (
+ value === null ||
+ typeof value !== "object" ||
+ Array.isArray(value) ||
+ JSON.stringify(Object.keys(value).sort()) !==
+ JSON.stringify([...keys].sort())
+ ) {
+ throw new Error(`${label} contains missing or unknown fields`);
+ }
+ return value;
+}
+
+function validPid(value) {
+ return Number.isSafeInteger(value) && value > 1;
+}
+
+function assertSha256(value, label) {
+ if (!sha256Pattern.test(value ?? "")) throw new Error(`${label} is invalid`);
+}
+
+function contained(candidate) {
+ const fromRoot = relative(root, candidate);
+ return (
+ fromRoot === "" || (!fromRoot.startsWith("..") && !isAbsolute(fromRoot))
+ );
+}
+
+function repositoryEntry(relativePath, kind, label) {
+ if (
+ typeof relativePath !== "string" ||
+ relativePath.length === 0 ||
+ isAbsolute(relativePath) ||
+ relativePath.split(/[\\/]/u).includes("..")
+ ) {
+ throw new Error(`${label} is not repository-relative`);
+ }
+ const candidate = resolve(root, relativePath);
+ if (!contained(candidate)) throw new Error(`${label} escaped the repository`);
+ let current = root;
+ for (const component of relative(root, candidate)
+ .split(sep)
+ .filter(Boolean)) {
+ current = resolve(current, component);
+ if (lstatSync(current).isSymbolicLink()) {
+ throw new Error(`${label} contains a symbolic link`);
+ }
+ }
+ const metadata = statSync(candidate);
+ if (
+ metadata.uid !== process.getuid() ||
+ (metadata.mode & 0o022) !== 0 ||
+ realpathSync(candidate) !== candidate
+ ) {
+ throw new Error(`${label} ownership or mode is unsafe`);
+ }
+ if (kind === "file" && !metadata.isFile()) {
+ throw new Error(`${label} is not a file`);
+ }
+ if (kind === "directory" && !metadata.isDirectory()) {
+ throw new Error(`${label} is not a directory`);
+ }
+ if (kind === "socket" && !metadata.isSocket()) {
+ throw new Error(`${label} is not a socket`);
+ }
+ return candidate;
+}
+
+function readBounded(path, label, maximumBytes = 1024 * 1024) {
+ const metadata = statSync(path);
+ if (metadata.size > maximumBytes) throw new Error(`${label} is too large`);
+ return readFileSync(path);
+}
+
+export function validateCompatTarget(sessionId, attestationSha256) {
+ if (
+ sessionId !== COMPAT_STOP_TARGET.sessionId ||
+ attestationSha256 !== COMPAT_STOP_TARGET.attestationSha256
+ ) {
+ throw new Error("compatibility stop target is not allowlisted");
+ }
+}
+
+function expectedPaths(sessionId) {
+ const prefix = `.rt/${sessionId}`;
+ return {
+ installRoot:
+ "node_modules/.cache/counterlab-v6.1/rootless-tools/install-v2.3.1",
+ sessionRoot: prefix,
+ containerdRootlesskitApiSocket: `${prefix}/run/containerd-rootless/api.sock`,
+ containerdSocket: `${prefix}/run/containerd.sock`,
+ runtimeCommandSocket: `${prefix}/run/runtime-command.sock`,
+ clientFifoRoot: `${prefix}/run/client-fifo`,
+ runcStateRoot: `${prefix}/run/runc`,
+ buildkitSocket: `${prefix}/run/buildkitd.sock`,
+ buildkitInnerSocket: `${prefix}/run/inner/buildkitd.sock`,
+ buildkitOtelSocket: `${prefix}/run/inner/buildkit-otel.sock`,
+ runtimeSupervisorSocket: `${prefix}/run/runtime-supervisor.sock`,
+ runtimeSupervisorReady: `${prefix}/run/runtime-supervisor-ready.json`,
+ containerdRoot: `${prefix}/data/containerd`,
+ containerdState: `${prefix}/state/containerd`,
+ buildkitRoot: `${prefix}/data/buildkit`,
+ nerdctlData: `${prefix}/data/nerdctl`,
+ home: `${prefix}/home`,
+ tmp: `${prefix}/tmp`,
+ xdgCache: `${prefix}/xdg-cache`,
+ xdgConfig: `${prefix}/xdg-config`,
+ xdgData: `${prefix}/xdg-data`,
+ xdgRuntime: `${prefix}/run/inner`,
+ auth: `${prefix}/auth`,
+ containerdConfig: `${prefix}/config/containerd.toml`,
+ buildkitConfig: `${prefix}/config/buildkitd.toml`,
+ containerdPidFile: `${prefix}/run/containerd-rootlesskit.pid`,
+ buildkitPidFile: `${prefix}/run/buildkit-rootlesskit.pid`,
+ containerdRootlesskitState: `${prefix}/run/containerd-rootless`,
+ buildkitRootlesskitState: `${prefix}/run/buildkit-rootless`,
+ };
+}
+
+export function validateTargetAttestation(attestation, sessionId) {
+ exactObject(
+ attestation,
+ [
+ "schemaVersion",
+ "status",
+ "sessionId",
+ "createdAt",
+ "namespace",
+ "toolchainLockSha256",
+ "adapterSha256",
+ "helperSha256",
+ "runtimePolicySha256",
+ "proofDependencyManifest",
+ "proofDependencyManifestSha256",
+ "runtimeToolchainSha256",
+ "paths",
+ "pids",
+ "fileSha256",
+ ],
+ "runtime attestation",
+ );
+ const paths = expectedPaths(sessionId);
+ exactObject(attestation.paths, Object.keys(paths), "runtime paths");
+ exactObject(
+ attestation.pids,
+ ["supervisor", "containerdRootlesskit", "buildkitRootlesskit"],
+ "runtime PIDs",
+ );
+ exactObject(
+ attestation.fileSha256,
+ ["containerdConfig", "buildkitConfig", "supervisorReady"],
+ "runtime file hashes",
+ );
+ exactObject(
+ attestation.helperSha256,
+ Object.keys(HISTORICAL_HELPER_PATHS),
+ "historical runtime helper hashes",
+ );
+ if (
+ attestation.schemaVersion !== "3" ||
+ attestation.status !== "READY" ||
+ attestation.sessionId !== sessionId ||
+ attestation.namespace !== "counterlab-v6.1" ||
+ !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(
+ attestation.createdAt ?? "",
+ ) ||
+ !Object.values(attestation.pids).every(validPid) ||
+ canonicalJson(attestation.helperSha256) !==
+ canonicalJson(HISTORICAL_HELPER_SHA256) ||
+ sha256(canonicalJson(attestation.helperSha256)) !==
+ COMPAT_STOP_TARGET.helperManifestSha256 ||
+ attestation.runtimeToolchainSha256 !==
+ COMPAT_STOP_TARGET.runtimeToolchainSha256 ||
+ attestation.toolchainLockSha256 !==
+ COMPAT_STOP_TARGET.toolchainLockSha256 ||
+ attestation.adapterSha256 !== COMPAT_STOP_TARGET.adapterSha256 ||
+ attestation.runtimePolicySha256 !==
+ COMPAT_STOP_TARGET.runtimePolicySha256 ||
+ attestation.proofDependencyManifestSha256 !==
+ COMPAT_STOP_TARGET.proofDependencyManifestSha256 ||
+ attestation.fileSha256.supervisorReady !==
+ COMPAT_STOP_TARGET.supervisorReadySha256 ||
+ attestation.fileSha256.containerdConfig !==
+ COMPAT_STOP_TARGET.containerdConfigSha256 ||
+ attestation.fileSha256.buildkitConfig !==
+ COMPAT_STOP_TARGET.buildkitConfigSha256
+ ) {
+ throw new Error("historical runtime attestation identity is invalid");
+ }
+ for (const [key, expected] of Object.entries(paths)) {
+ if (attestation.paths[key] !== expected) {
+ throw new Error(`historical runtime path mismatch: ${key}`);
+ }
+ }
+ exactObject(
+ attestation.proofDependencyManifest,
+ ["schemaVersion", "files"],
+ "runtime proof dependency manifest",
+ );
+ if (
+ attestation.proofDependencyManifest.schemaVersion !== "1" ||
+ !Array.isArray(attestation.proofDependencyManifest.files) ||
+ attestation.proofDependencyManifest.files.length === 0 ||
+ sha256(canonicalJson(attestation.proofDependencyManifest)) !==
+ attestation.proofDependencyManifestSha256
+ ) {
+ throw new Error("runtime proof dependency manifest is invalid");
+ }
+ let previous = "";
+ let policyFound = false;
+ for (const entry of attestation.proofDependencyManifest.files) {
+ exactObject(entry, ["path", "sha256"], "runtime proof dependency");
+ if (
+ typeof entry.path !== "string" ||
+ !(
+ entry.path === "scripts/verify-contained-runtime-timeout.py" ||
+ /^services\/runner\/src\/counterlab_runner\/[A-Za-z0-9_.-]+\.(?:json|py)$/u.test(
+ entry.path,
+ )
+ ) ||
+ entry.path <= previous
+ ) {
+ throw new Error("runtime proof dependency path is invalid");
+ }
+ assertSha256(entry.sha256, "runtime proof dependency hash");
+ if (
+ entry.path ===
+ "services/runner/src/counterlab_runner/contained-runtime-policy.json"
+ ) {
+ policyFound = entry.sha256 === attestation.runtimePolicySha256;
+ }
+ previous = entry.path;
+ }
+ if (!policyFound) throw new Error("runtime policy binding is invalid");
+ return paths;
+}
+
+function gitFile(path) {
+ const fromGit = execFileSync(
+ "/usr/bin/git",
+ ["show", `${COMPAT_STOP_TARGET.historicalBundleCommit}:${path}`],
+ {
+ cwd: root,
+ env: { PATH: "/usr/bin:/bin" },
+ encoding: null,
+ maxBuffer: 8 * 1024 * 1024,
+ stdio: ["ignore", "pipe", "pipe"],
+ timeout: 10_000,
+ },
+ );
+ return Buffer.from(fromGit);
+}
+
+export function validateHistoricalBundle(attestation) {
+ const ancestor = spawnSync(
+ "/usr/bin/git",
+ [
+ "merge-base",
+ "--is-ancestor",
+ COMPAT_STOP_TARGET.historicalBundleCommit,
+ "HEAD",
+ ],
+ {
+ cwd: root,
+ env: { PATH: "/usr/bin:/bin" },
+ stdio: "ignore",
+ timeout: 10_000,
+ },
+ );
+ if (ancestor.status !== 0) {
+ throw new Error("historical runtime bundle is not an ancestor");
+ }
+ if (
+ sha256(gitFile("scripts/contained-runtime-adapter.sh")) !==
+ attestation.adapterSha256 ||
+ sha256(gitFile("scripts/runtime-toolchain-lock.json")) !==
+ attestation.toolchainLockSha256
+ ) {
+ throw new Error("historical runtime root files do not match");
+ }
+ for (const [name, path] of Object.entries(HISTORICAL_HELPER_PATHS)) {
+ if (sha256(gitFile(path)) !== attestation.helperSha256[name]) {
+ throw new Error(`historical runtime helper does not match: ${name}`);
+ }
+ }
+ for (const entry of attestation.proofDependencyManifest.files) {
+ if (sha256(gitFile(entry.path)) !== entry.sha256) {
+ throw new Error(
+ `historical runtime proof file does not match: ${entry.path}`,
+ );
+ }
+ }
+ const lock = JSON.parse(
+ gitFile("scripts/runtime-toolchain-lock.json").toString("utf8"),
+ );
+ exactObject(
+ lock,
+ [
+ "schemaVersion",
+ "distribution",
+ "version",
+ "platform",
+ "archiveUrl",
+ "checksumUrl",
+ "archiveSha256",
+ "licenses",
+ "components",
+ ],
+ "historical runtime lock",
+ );
+ if (
+ lock.schemaVersion !== "1" ||
+ lock.distribution !== "nerdctl-full" ||
+ lock.version !== "2.3.1" ||
+ lock.platform !== "linux-amd64" ||
+ lock.archiveSha256 !==
+ "7a0d8efcf55b10b57d831541266adb9c6ec3d55b44ec041c95f6eb994d1faab9" ||
+ lock.archiveUrl !==
+ "https://github.com/containerd/nerdctl/releases/download/v2.3.1/nerdctl-full-2.3.1-linux-amd64.tar.gz" ||
+ lock.checksumUrl !==
+ "https://github.com/containerd/nerdctl/releases/download/v2.3.1/SHA256SUMS" ||
+ canonicalJson(lock.licenses) !== canonicalJson(["Apache-2.0"])
+ ) {
+ throw new Error("historical runtime lock identity is invalid");
+ }
+ exactObject(
+ lock.components,
+ Object.keys(HISTORICAL_COMPONENT_VERSIONS),
+ "historical runtime components",
+ );
+ const components = Object.fromEntries(
+ Object.entries(lock.components ?? {}).map(([name, component]) => {
+ exactObject(
+ component,
+ ["version", "sha256"],
+ `historical component ${name}`,
+ );
+ assertSha256(component?.sha256, `historical component hash ${name}`);
+ if (component.version !== HISTORICAL_COMPONENT_VERSIONS[name]) {
+ throw new Error(`historical component version mismatch: ${name}`);
+ }
+ return [name, component.sha256];
+ }),
+ );
+ const fingerprint = {
+ schemaVersion: "2",
+ namespace: "counterlab-v6.1",
+ toolchainLockSha256: attestation.toolchainLockSha256,
+ adapterSha256: attestation.adapterSha256,
+ helperSha256: attestation.helperSha256,
+ components,
+ fileSha256: {
+ containerdConfig: attestation.fileSha256.containerdConfig,
+ buildkitConfig: attestation.fileSha256.buildkitConfig,
+ },
+ runtimePolicySha256: attestation.runtimePolicySha256,
+ proofDependencyManifestSha256: attestation.proofDependencyManifestSha256,
+ };
+ if (
+ sha256(canonicalJson(fingerprint)) !== attestation.runtimeToolchainSha256
+ ) {
+ throw new Error("historical runtime fingerprint is invalid");
+ }
+ return lock;
+}
+
+async function validateInstalledToolchain(installRoot, lock) {
+ for (const [name, component] of Object.entries(lock.components)) {
+ const binary = repositoryEntry(
+ `${relative(root, installRoot)}/bin/${name}`,
+ "file",
+ `installed runtime component ${name}`,
+ );
+ const metadata = statSync(binary);
+ if (metadata.size <= 0 || metadata.size > 512 * 1024 * 1024) {
+ throw new Error(`installed runtime component size is invalid: ${name}`);
+ }
+ if ((await sha256File(binary)) !== component.sha256) {
+ throw new Error(`installed runtime component hash mismatch: ${name}`);
+ }
+ }
+}
+
+export function validateCurrentStopPrimitives(attestation) {
+ const requiredCurrentFiles = {
+ adapter: [
+ "scripts/contained-runtime-adapter.sh",
+ attestation.adapterSha256,
+ ],
+ canonicalStop: [
+ "scripts/stop-contained-runtime.mjs",
+ attestation.helperSha256.runtimeStop,
+ ],
+ verifier: [
+ "scripts/verify-contained-runtime.mjs",
+ attestation.helperSha256.attestationVerifier,
+ ],
+ supervisorProtocol: [
+ "scripts/contained-runtime-supervisor-protocol.mjs",
+ attestation.helperSha256.supervisorProtocol,
+ ],
+ };
+ for (const [label, [path, expected]] of Object.entries(
+ requiredCurrentFiles,
+ )) {
+ const file = repositoryEntry(path, "file", `current ${label}`);
+ if (
+ sha256(readBounded(file, `current ${label}`, 2 * 1024 * 1024)) !==
+ expected
+ ) {
+ throw new Error(
+ `current ${label} no longer matches the attested primitive`,
+ );
+ }
+ }
+}
+
+export function createLegacyRuntimeRequest(args) {
+ if (!Array.isArray(args) || args.length === 0) {
+ throw new Error("legacy runtime command is empty");
+ }
+ return `${JSON.stringify({
+ schemaVersion: "1",
+ args,
+ stdinBase64: "",
+ })}\n`;
+}
+
+function decodeCanonicalBase64(value, label) {
+ if (
+ typeof value !== "string" ||
+ value.length > 48 * 1024 * 1024 ||
+ !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(
+ value,
+ )
+ ) {
+ throw new Error(`${label} is invalid base64`);
+ }
+ const decoded = Buffer.from(value, "base64");
+ if (decoded.toString("base64") !== value) {
+ throw new Error(`${label} is not canonical base64`);
+ }
+ return decoded;
+}
+
+export function validateLegacyRuntimeResponse(value) {
+ exactObject(
+ value,
+ ["schemaVersion", "exitCode", "stdoutBase64", "stderrBase64"],
+ "legacy runtime response",
+ );
+ if (value.schemaVersion !== "1" || !Number.isInteger(value.exitCode)) {
+ throw new Error("legacy runtime response identity is invalid");
+ }
+ return {
+ exitCode: value.exitCode,
+ stdout: decodeCanonicalBase64(value.stdoutBase64, "legacy runtime stdout"),
+ stderr: decodeCanonicalBase64(value.stderrBase64, "legacy runtime stderr"),
+ };
+}
+
+function sendLegacyRuntimeCommand({ socketPath, args, timeoutMs }) {
+ return new Promise((accept, reject) => {
+ const request = createLegacyRuntimeRequest(args);
+ const chunks = [];
+ let total = 0;
+ let settled = false;
+ const socket = connect(socketPath);
+ const fail = (error) => {
+ if (settled) return;
+ settled = true;
+ socket.destroy();
+ reject(error);
+ };
+ socket.setTimeout(timeoutMs, () =>
+ fail(new Error("legacy runtime command timed out")),
+ );
+ socket.once("connect", () => socket.end(request));
+ socket.on("data", (chunk) => {
+ total += chunk.byteLength;
+ if (total > 48 * 1024 * 1024) {
+ fail(new Error("legacy runtime response exceeded its bound"));
+ return;
+ }
+ chunks.push(chunk);
+ });
+ socket.once("error", fail);
+ socket.once("end", () => {
+ if (settled) return;
+ settled = true;
+ try {
+ accept(
+ validateLegacyRuntimeResponse(
+ JSON.parse(Buffer.concat(chunks).toString("utf8")),
+ ),
+ );
+ } catch (error) {
+ reject(
+ new Error("legacy runtime response is invalid", { cause: error }),
+ );
+ }
+ });
+ });
+}
+
+function validateSupervisorTransition(
+ response,
+ { attestation, attestationSha256, expectedStatus },
+) {
+ exactObject(
+ response,
+ [
+ "schemaVersion",
+ "status",
+ "sessionId",
+ "namespace",
+ "state",
+ "supervisorPid",
+ "childPids",
+ "childHandlesOwned",
+ "buildkitAdmissionOpen",
+ "activeBuildkitConnections",
+ "attestationSha256",
+ ],
+ "runtime supervisor transition",
+ );
+ exactObject(
+ response.childPids,
+ ["containerdRootlesskit", "buildkitRootlesskit"],
+ "runtime supervisor child PIDs",
+ );
+ if (
+ response.schemaVersion !== "1" ||
+ response.status !== expectedStatus ||
+ response.sessionId !== attestation.sessionId ||
+ response.namespace !== "counterlab-v6.1" ||
+ response.state !== expectedStatus ||
+ response.supervisorPid !== attestation.pids.supervisor ||
+ canonicalJson(response.childPids) !==
+ canonicalJson({
+ containerdRootlesskit: attestation.pids.containerdRootlesskit,
+ buildkitRootlesskit: attestation.pids.buildkitRootlesskit,
+ }) ||
+ response.childHandlesOwned !== true ||
+ response.buildkitAdmissionOpen !== false ||
+ response.activeBuildkitConnections !== 0 ||
+ response.attestationSha256 !== attestationSha256
+ ) {
+ throw new Error("runtime supervisor transition is invalid");
+ }
+ return response;
+}
+
+function validateShutdown(
+ response,
+ { attestation, attestationSha256, drainReceipt },
+) {
+ exactObject(
+ response,
+ [
+ "schemaVersion",
+ "status",
+ "sessionId",
+ "namespace",
+ "state",
+ "supervisorPid",
+ "childPids",
+ "childHandlesOwned",
+ "buildkitAdmissionOpen",
+ "activeBuildkitConnections",
+ "attestationSha256",
+ "childExits",
+ "drainReceiptSha256",
+ ],
+ "runtime supervisor shutdown",
+ );
+ exactObject(
+ response.childPids,
+ ["containerdRootlesskit", "buildkitRootlesskit"],
+ "runtime shutdown child PIDs",
+ );
+ exactObject(
+ response.childExits,
+ ["containerdRootlesskit", "buildkitRootlesskit"],
+ "runtime child exits",
+ );
+ for (const [name, exit] of Object.entries(response.childExits)) {
+ exactObject(exit, ["code", "signal"], `runtime child exit ${name}`);
+ if (!(
+ (exit.code === null || Number.isInteger(exit.code)) &&
+ (exit.signal === null || typeof exit.signal === "string") &&
+ (exit.code !== null || exit.signal !== null)
+ )) {
+ throw new Error(`runtime child exit is invalid: ${name}`);
+ }
+ }
+ if (
+ response.schemaVersion !== "1" ||
+ response.status !== "STOPPED" ||
+ response.state !== "STOPPED" ||
+ response.sessionId !== attestation.sessionId ||
+ response.namespace !== "counterlab-v6.1" ||
+ response.supervisorPid !== attestation.pids.supervisor ||
+ canonicalJson(response.childPids) !==
+ canonicalJson({
+ containerdRootlesskit: attestation.pids.containerdRootlesskit,
+ buildkitRootlesskit: attestation.pids.buildkitRootlesskit,
+ }) ||
+ response.childHandlesOwned !== true ||
+ response.buildkitAdmissionOpen !== false ||
+ response.activeBuildkitConnections !== 0 ||
+ response.attestationSha256 !== attestationSha256 ||
+ response.drainReceiptSha256 !== sha256(canonicalJson(drainReceipt))
+ ) {
+ throw new Error("runtime supervisor shutdown binding is invalid");
+ }
+ return response;
+}
+
+function socketRefusesConnection(path) {
+ return new Promise((accept, reject) => {
+ const socket = connect(path);
+ let settled = false;
+ const finish = (value) => {
+ if (settled) return;
+ settled = true;
+ socket.destroy();
+ accept(value);
+ };
+ socket.setTimeout(2_000, () => finish(false));
+ socket.once("connect", () => finish(false));
+ socket.once("error", (error) => {
+ if (["ECONNREFUSED", "ENOENT"].includes(error?.code)) {
+ finish(true);
+ return;
+ }
+ if (settled) return;
+ settled = true;
+ socket.destroy();
+ reject(error);
+ });
+ });
+}
+
+async function waitForSocketClosure(paths) {
+ for (let attempt = 0; attempt < 100; attempt += 1) {
+ const closed = await Promise.all(paths.map(socketRefusesConnection));
+ if (closed.every(Boolean)) return true;
+ await new Promise((accept) => setTimeout(accept, 100));
+ }
+ return false;
+}
+
+export async function executeCompatRecoveryProtocol({
+ attestation,
+ attestationPath,
+ attestationSha256,
+ resolved,
+ requestSupervisor,
+ requestRuntime = sendLegacyRuntimeCommand,
+ waitForClosure = waitForSocketClosure,
+ readAttestation = (path) => readFileSync(path),
+ validateDrainReceipt,
+}) {
+ if (
+ typeof requestSupervisor !== "function" ||
+ typeof validateDrainReceipt !== "function"
+ ) {
+ throw new Error("historical recovery protocol dependencies are invalid");
+ }
+ const beginDrain = await requestSupervisor({
+ socketPath: resolved.runtimeSupervisorSocket,
+ timeoutMs: 35_000,
+ request: {
+ schemaVersion: "1",
+ action: "begin-drain",
+ sessionId: attestation.sessionId,
+ attestationSha256,
+ },
+ });
+ validateSupervisorTransition(beginDrain, {
+ attestation,
+ attestationSha256,
+ expectedStatus: "DRAINING",
+ });
+
+ const drainResponse = await requestRuntime({
+ socketPath: resolved.runtimeCommandSocket,
+ args: ["counterlab-drain", attestationSha256],
+ timeoutMs: 35_000,
+ });
+ if (drainResponse.exitCode !== 0 || drainResponse.stderr.byteLength !== 0) {
+ throw new Error("historical runtime drain command failed");
+ }
+ const drainReceipt = validateDrainReceipt(
+ JSON.parse(drainResponse.stdout.toString("utf8")),
+ { sessionId: attestation.sessionId, attestationSha256 },
+ );
+
+ const rejectedAfterDrain = await requestRuntime({
+ socketPath: resolved.runtimeCommandSocket,
+ args: ["version", "--format", "json"],
+ timeoutMs: 10_000,
+ });
+ if (
+ rejectedAfterDrain.exitCode === 0 ||
+ !rejectedAfterDrain.stderr
+ .toString("utf8")
+ .includes("contained runtime is drained")
+ ) {
+ throw new Error("historical runtime admitted a command after drain");
+ }
+ if (sha256(readAttestation(attestationPath)) !== attestationSha256) {
+ throw new Error("runtime attestation changed after drain");
+ }
+
+ const shutdown = validateShutdown(
+ await requestSupervisor({
+ socketPath: resolved.runtimeSupervisorSocket,
+ timeoutMs: 35_000,
+ request: {
+ schemaVersion: "1",
+ action: "shutdown",
+ sessionId: attestation.sessionId,
+ attestationSha256,
+ drainReceipt,
+ },
+ }),
+ { attestation, attestationSha256, drainReceipt },
+ );
+
+ const endpoints = [
+ resolved.runtimeCommandSocket,
+ resolved.buildkitSocket,
+ resolved.runtimeSupervisorSocket,
+ ];
+ if (!(await waitForClosure(endpoints))) {
+ throw new Error(
+ "historical runtime endpoint remained reachable after stop",
+ );
+ }
+ return { drainReceipt, shutdown };
+}
+
+export function createHistoricalRecoveryReceipt({
+ attestation,
+ attestationSha256,
+ compatStopHelperSha256,
+ drainReceipt,
+ shutdown,
+ sessionDirectoryPreserved,
+ stoppedAt,
+}) {
+ assertSha256(compatStopHelperSha256, "compatibility stop helper hash");
+ if (
+ sessionDirectoryPreserved !== true ||
+ !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(stoppedAt ?? "")
+ ) {
+ throw new Error("historical recovery completion evidence is invalid");
+ }
+ const payload = {
+ schemaVersion: "1",
+ status: "RECOVERY_STOPPED",
+ purpose: "historical-runtime-recovery",
+ qualificationEligible: false,
+ releaseQualification: false,
+ sessionId: attestation.sessionId,
+ namespace: "counterlab-v6.1",
+ attestationSha256,
+ historicalBundleCommit: COMPAT_STOP_TARGET.historicalBundleCommit,
+ historicalHelperManifestSha256: COMPAT_STOP_TARGET.helperManifestSha256,
+ runtimeToolchainSha256: attestation.runtimeToolchainSha256,
+ compatStopHelperSha256,
+ drainReceiptSha256: sha256(canonicalJson(drainReceipt)),
+ pids: attestation.pids,
+ childExits: shutdown.childExits,
+ shutdownAuthority: "supervisor-owned-child-handles",
+ commandAdmissionClosed: true,
+ runtimeCommandSocketClosed: true,
+ buildkitSocketClosed: true,
+ supervisorSocketClosed: true,
+ sessionDirectoryPreserved,
+ stoppedAt,
+ };
+ return {
+ ...payload,
+ receiptPayloadSha256: sha256(canonicalJson(payload)),
+ };
+}
+
+function recoveryLockPath() {
+ const sessionRoot = repositoryEntry(
+ `.rt/${COMPAT_STOP_TARGET.sessionId}`,
+ "directory",
+ "historical runtime session",
+ );
+ const lockPath = resolve(sessionRoot, "historical-recovery.lock");
+ if (!contained(lockPath)) {
+ throw new Error("historical recovery lock escaped the repository");
+ }
+ return lockPath;
+}
+
+function assertSafeExistingLock(lockPath) {
+ const existing = lstatSync(lockPath);
+ if (
+ existing.isSymbolicLink() ||
+ !existing.isFile() ||
+ existing.uid !== process.getuid() ||
+ (existing.mode & 0o077) !== 0
+ ) {
+ throw new Error("historical recovery lock is unsafe");
+ }
+}
+
+function runWithRepositoryLock() {
+ const lockPath = recoveryLockPath();
+ if (existsSync(lockPath)) {
+ assertSafeExistingLock(lockPath);
+ } else {
+ writeFileSync(lockPath, "", {
+ encoding: "utf8",
+ flag: "wx",
+ mode: 0o600,
+ });
+ }
+ if (
+ lstatSync(lockPath).isSymbolicLink() ||
+ !statSync(lockPath).isFile() ||
+ statSync(lockPath).uid !== process.getuid() ||
+ (statSync(lockPath).mode & 0o077) !== 0
+ ) {
+ throw new Error("historical recovery lock is unsafe");
+ }
+ const locked = spawnSync(
+ "/usr/bin/flock",
+ [
+ "--exclusive",
+ "--nonblock",
+ "--conflict-exit-code",
+ "75",
+ lockPath,
+ process.execPath,
+ self,
+ ...process.argv.slice(2),
+ ],
+ {
+ cwd: root,
+ env: {
+ COUNTERLAB_RT_RELEASE721_RECOVERY_LOCKED: "1",
+ PATH: "/usr/bin:/bin",
+ },
+ stdio: "inherit",
+ timeout: 300_000,
+ },
+ );
+ if (locked.error) {
+ throw new Error("historical recovery lock command failed", {
+ cause: locked.error,
+ });
+ }
+ if (locked.status === 75) {
+ throw new Error("another historical recovery attempt owns the lock");
+ }
+ if (locked.status !== 0) {
+ throw new Error(
+ `historical recovery child failed with status ${String(locked.status)}`,
+ );
+ }
+}
+
+function assertRepositoryLockHeld() {
+ const lockPath = recoveryLockPath();
+ if (!existsSync(lockPath)) {
+ throw new Error("historical recovery lock is missing");
+ }
+ assertSafeExistingLock(lockPath);
+ const probe = spawnSync(
+ "/usr/bin/flock",
+ [
+ "--exclusive",
+ "--nonblock",
+ "--conflict-exit-code",
+ "75",
+ lockPath,
+ "/usr/bin/true",
+ ],
+ {
+ cwd: root,
+ env: { PATH: "/usr/bin:/bin" },
+ stdio: "ignore",
+ timeout: 10_000,
+ },
+ );
+ if (probe.error || probe.status !== 75) {
+ throw new Error("historical recovery process does not own the lock");
+ }
+}
+
+function parseCliArguments(args) {
+ if (
+ args.length !== 4 ||
+ args[0] !== "--session-id" ||
+ args[2] !== "--attestation-sha256"
+ ) {
+ throw new Error(
+ "Usage: stop-contained-runtime-v3-compat --session-id rt-release721 --attestation-sha256 SHA256",
+ );
+ }
+ const sessionId = args[1];
+ const attestationSha256 = args[3];
+ validateCompatTarget(sessionId, attestationSha256);
+ return { sessionId, attestationSha256 };
+}
+
+function validatePreLockIdentity(args) {
+ const { sessionId, attestationSha256 } = parseCliArguments(args);
+ const paths = expectedPaths(sessionId);
+ const attestationPath = repositoryEntry(
+ `${paths.sessionRoot}/attestation.json`,
+ "file",
+ "runtime attestation",
+ );
+ const source = readBounded(
+ attestationPath,
+ "runtime attestation",
+ 256 * 1024,
+ );
+ if (sha256(source) !== attestationSha256) {
+ throw new Error("runtime attestation hash does not match the target");
+ }
+ const attestation = JSON.parse(source.toString("utf8"));
+ validateTargetAttestation(attestation, sessionId);
+ validateHistoricalBundle(attestation);
+ validateCurrentStopPrimitives(attestation);
+}
+
+async function main() {
+ const { sessionId, attestationSha256: requestedAttestationSha256 } =
+ parseCliArguments(process.argv.slice(2));
+
+ const marker = repositoryEntry(
+ "COUNTERLAB_REPO_ROOT",
+ "file",
+ "repository marker",
+ );
+ const selfMetadata = statSync(self);
+ if (
+ statSync(marker).size === 0 ||
+ selfMetadata.uid !== process.getuid() ||
+ (selfMetadata.mode & 0o022) !== 0
+ ) {
+ throw new Error("compatibility stop helper identity is invalid");
+ }
+
+ const paths = expectedPaths(sessionId);
+ const attestationPath = repositoryEntry(
+ `${paths.sessionRoot}/attestation.json`,
+ "file",
+ "runtime attestation",
+ );
+ const outputPath = resolve(
+ root,
+ paths.sessionRoot,
+ "historical-recovery-receipt.json",
+ );
+ if (existsSync(outputPath)) {
+ throw new Error("historical recovery receipt already exists");
+ }
+ const attestationSource = readBounded(
+ attestationPath,
+ "runtime attestation",
+ 256 * 1024,
+ );
+ if (sha256(attestationSource) !== requestedAttestationSha256) {
+ throw new Error("runtime attestation hash does not match the target");
+ }
+ const attestation = JSON.parse(attestationSource.toString("utf8"));
+ validateTargetAttestation(attestation, sessionId);
+ const historicalLock = validateHistoricalBundle(attestation);
+ validateCurrentStopPrimitives(attestation);
+ const supervisorProtocol =
+ await import("./contained-runtime-supervisor-protocol.mjs");
+
+ const pathKinds = {
+ installRoot: "directory",
+ sessionRoot: "directory",
+ containerdRootlesskitApiSocket: "socket",
+ containerdSocket: "socket",
+ runtimeCommandSocket: "socket",
+ clientFifoRoot: "directory",
+ runcStateRoot: "directory",
+ buildkitSocket: "socket",
+ buildkitInnerSocket: "socket",
+ buildkitOtelSocket: "socket",
+ runtimeSupervisorSocket: "socket",
+ runtimeSupervisorReady: "file",
+ containerdRoot: "directory",
+ containerdState: "directory",
+ buildkitRoot: "directory",
+ nerdctlData: "directory",
+ home: "directory",
+ tmp: "directory",
+ xdgCache: "directory",
+ xdgConfig: "directory",
+ xdgData: "directory",
+ xdgRuntime: "directory",
+ auth: "directory",
+ containerdConfig: "file",
+ buildkitConfig: "file",
+ containerdPidFile: "file",
+ buildkitPidFile: "file",
+ containerdRootlesskitState: "directory",
+ buildkitRootlesskitState: "directory",
+ };
+ const resolved = Object.fromEntries(
+ Object.entries(pathKinds).map(([name, kind]) => [
+ name,
+ repositoryEntry(attestation.paths[name], kind, `runtime path ${name}`),
+ ]),
+ );
+ if ((statSync(resolved.runcStateRoot).mode & 0o777) !== 0o700) {
+ throw new Error("contained runc state root is not private");
+ }
+ await validateInstalledToolchain(resolved.installRoot, historicalLock);
+ if (
+ sha256(readBounded(resolved.containerdConfig, "containerd config")) !==
+ attestation.fileSha256.containerdConfig ||
+ sha256(readBounded(resolved.buildkitConfig, "buildkit config")) !==
+ attestation.fileSha256.buildkitConfig ||
+ sha256(readBounded(resolved.runtimeSupervisorReady, "supervisor ready")) !==
+ attestation.fileSha256.supervisorReady
+ ) {
+ throw new Error("runtime session files changed after launch");
+ }
+ const ready = supervisorProtocol.validateSupervisorReadyReceipt(
+ JSON.parse(readFileSync(resolved.runtimeSupervisorReady, "utf8")),
+ {
+ sessionId,
+ supervisorSocket: attestation.paths.runtimeSupervisorSocket,
+ buildkitProxySocket: attestation.paths.buildkitSocket,
+ buildkitInnerSocket: attestation.paths.buildkitInnerSocket,
+ },
+ );
+ if (
+ ready.supervisorPid !== attestation.pids.supervisor ||
+ ready.childPids.containerdRootlesskit !==
+ attestation.pids.containerdRootlesskit ||
+ ready.childPids.buildkitRootlesskit !==
+ attestation.pids.buildkitRootlesskit ||
+ Number(readFileSync(resolved.containerdPidFile, "utf8").trim()) !==
+ ready.childPids.containerdRootlesskit ||
+ Number(readFileSync(resolved.buildkitPidFile, "utf8").trim()) !==
+ ready.childPids.buildkitRootlesskit
+ ) {
+ throw new Error("runtime PID files disagree with the attestation");
+ }
+ if (sha256(readFileSync(attestationPath)) !== requestedAttestationSha256) {
+ throw new Error("runtime attestation changed before drain");
+ }
+
+ const { drainReceipt, shutdown } = await executeCompatRecoveryProtocol({
+ attestation,
+ attestationPath,
+ attestationSha256: requestedAttestationSha256,
+ resolved,
+ requestSupervisor: supervisorProtocol.sendSupervisorRequest,
+ validateDrainReceipt: supervisorProtocol.validateRuntimeDrainReceipt,
+ });
+ const receipt = createHistoricalRecoveryReceipt({
+ attestation,
+ attestationSha256: requestedAttestationSha256,
+ compatStopHelperSha256: sha256(readFileSync(self)),
+ drainReceipt,
+ shutdown,
+ sessionDirectoryPreserved: statSync(resolved.sessionRoot).isDirectory(),
+ stoppedAt: new Date().toISOString(),
+ });
+ writeFileSync(outputPath, `${JSON.stringify(receipt, null, 2)}\n`, {
+ encoding: "utf8",
+ flag: "wx",
+ mode: 0o600,
+ });
+ process.stdout.write(`${canonicalJson(receipt)}\n`);
+}
+
+const invokedPath = process.argv[1] ? realpathSync(process.argv[1]) : null;
+if (invokedPath === self) {
+ if (process.env.COUNTERLAB_RT_RELEASE721_RECOVERY_LOCKED === "1") {
+ assertRepositoryLockHeld();
+ await main();
+ } else {
+ validatePreLockIdentity(process.argv.slice(2));
+ runWithRepositoryLock();
+ }
+}
From 17c1a5e00819c0d3b5cb0ab305fb9c16292cf7bc Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 19:44:25 +0530
Subject: [PATCH 113/143] docs(progress): record historical runtime recovery
---
docs/PROGRESS.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 62 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 556f83e7..639b0d41 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4658,3 +4658,65 @@ Chrome is no longer the blocker: genuine CloakBrowser local rendering is green
for every locally executable journey. Release remains **NO-GO** for the clean
source, constitution-compatible sentinel operations, exact image/evidence,
Cloudflare deployment, public qualification, and submission gates.
+
+## Historical runtime recovery checkpoint — 2026-07-21T14:13:42Z
+
+Deadline: `2026-07-22T00:00:00Z`; **9 hours 46 minutes 18 seconds remained at
+this checkpoint**. Committed HEAD is
+`6ee595416792c1630f44037c12a005ac5f33c267` on
+`feat/learner-ux-v6.1`. The only visible worktree entry is still the preserved,
+untracked FUSE tombstone `scripts/.fuse_hidden0000a9600000801f`; it was not
+opened, changed, staged, hidden, ignored, renamed, overwritten, or deleted.
+
+### Completed and committed
+
+| Commit | Result | Exact verification |
+| --- | --- | --- |
+| `6ee5954 fix(runtime): recover preserved contained session safely` | Added a one-target maintenance helper for the historical `rt-release721` runtime. It validates the exact attestation, historical Git bundle, current unchanged stop primitives, eight installed toolchain hashes, supervisor identity, empty drain receipt, post-drain admission closure, child exits, and endpoint closure. It emits only `historical-recovery-receipt.json` with `RECOVERY_STOPPED`, `qualificationEligible: false`, and `releaseQualification: false`. | Focused recovery and supervisor tests **20/20 passed**; root TypeScript, Node syntax, Prettier, whitespace, and a three-file secret scan passed. Independent review confirmed **8/8** installed binaries matched and no release consumer reads the recovery receipt. |
+
+### Actual recovery result
+
+- Exact target preflight passed for attestation
+ `2f12fe8f9db649f0564238a4569da9e98f5b8e31f085daec14f93538ba9d4569`.
+ The canonical current verifier separately rejected the historical
+ 18-helper manifest, as required; the compatibility path did not relax it.
+- The committed helper acquired its repository-contained lock and failed
+ closed on its first supervisor connection with `ECONNREFUSED`. It did not
+ issue a drain command, request child shutdown, send a signal, or write a
+ recovery receipt.
+- A subsequent read-only probe of the exact runtime-command, BuildKit proxy,
+ and supervisor sockets returned `ECONNREFUSED` for all three. This supports
+ only the bounded statement that those endpoints are closed. It does not
+ provide a supervisor-issued safe-stop receipt or prove process identity via
+ `/proc`.
+- The helper left the repository-contained maintenance lock at
+ `.rt/rt-release721/historical-recovery.lock`. That ignored file is not
+ release evidence. The historical session remains preserved.
+- Accepted maintenance limitation: lock contention is not cryptographic
+ same-UID caller authentication. This is not carried into release authority.
+
+### Remaining gates in dependency order
+
+1. The FUSE-owning process must release the tombstone naturally. A clean
+ source `S` still cannot be claimed; do not delete, hide, ignore, stage,
+ rename, overwrite, or inspect the tombstone.
+2. Obtain an explicit constitution-compatible authorization for the genuine
+ aggregate-cgroup sentinel operations, or keep the sentinel gate red. No
+ `/proc`, `/sys`, cgroup membership, or OOM-score operation was performed.
+3. Reverify repository-contained Wrangler authentication, then—only after a
+ clean source and lawful sentinel exist—build and qualify one exact image and
+ refresh the source/image-bound evidence once.
+4. Deploy only the qualified tuple, prove public identity/readiness, and run
+ all **40** public CloakBrowser journeys plus the ten manual checks. Local
+ source-level Cloak evidence remains **34 passed, 6 exact skips, 0 failed**;
+ public qualifying execution remains **0 journeys**.
+5. Keep learner evidence `NO_DATA` unless real consented observations occur;
+ complete exact-release screenshots/video, logged-out links, repository and
+ `/feedback` evidence, Devpost publication, and a submission receipt.
+6. Push the feature branch and fast-forward-only merge and push `main` only
+ after the release and submission gates are factual.
+
+No safe-stop receipt, clean source, containment pass, exact image, refreshed
+release evidence, Cloudflare mutation, deployment, public qualification,
+learner outcome, Devpost submission, push, or merge is claimed. Release remains
+**NO-GO**.
From 3b1ae9dc2a7161b76baa395b648d667576df231c Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 19:53:37 +0530
Subject: [PATCH 114/143] docs(release): refresh dependency graph
---
.../19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md | 62 ++++++++++++-------
1 file changed, 39 insertions(+), 23 deletions(-)
diff --git a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
index 247b712c..05e74a96 100644
--- a/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
+++ b/docs/audits/counterlab-first-prize/19_RELEASE_DEPENDENCY_AND_REVIEW_GRAPH.md
@@ -1,10 +1,10 @@
# Release dependency and review graph
-- Checkpoint: `2026-07-21T13:17:03Z`
+- Checkpoint: `2026-07-21T14:20:35Z`
- Branch: `feat/learner-ux-v6.1`
-- Committed HEAD: `6b85f2a6b713cbc4172a574a0c3be3fa075e8744`
+- Committed HEAD: `17c1a5e00819c0d3b5cb0ab305fb9c16292cf7bc`
- Deadline: `2026-07-22T00:00:00Z`
-- Time remaining at this checkpoint: **10 hours 42 minutes 57 seconds**
+- Time remaining at this checkpoint: **9 hours 39 minutes 25 seconds**
## Purpose and evidence boundary
@@ -25,12 +25,14 @@ video, Devpost submission, branch push, or merge is claimed here.
The latest product-code commit, `29ea8c3`, passes every locally executable
CloakBrowser journey: **34 passed, 6 exact public/live skips, 0 failed**. Its
fresh replay accessibility follow-up passes **2/2** with 45 and 51 assertions.
-The intervening committed changes through this checkpoint (`b78632f` and
-`6b85f2a`) change documentation only; this graph refresh is also
-documentation-only. This is genuine CloakBrowser source/browser evidence, but
-it targets a local Worker without an exact public deployment receipt and is
-therefore explicitly non-qualifying. Public qualifying execution remains **0
-journeys**.
+Later UI-independent changes include documentation checkpoints and the
+target-locked historical runtime recovery helper at `6ee5954`. Its actual
+attempt failed closed because the preserved supervisor endpoint already
+refused connections. All three exact historical runtime endpoints now refuse
+connections, but no supervisor safe-stop receipt exists and the maintenance
+receipt is explicitly ineligible for qualification. The CloakBrowser evidence
+targets a local Worker without an exact public deployment receipt and remains
+explicitly non-qualifying. Public qualifying execution remains **0 journeys**.
## Release dependency graph
@@ -39,6 +41,7 @@ flowchart TD
P["Product source at 29ea8c3 17 of 18 black-box P0/P1 covered MB-014 comprehension partial"]
T["Non-release deterministic core Web 674/674; Python 331/331 green 3 exact-binding tests await evidence E"]
B["Local CloakBrowser at 29ea8c3 34 pass / 6 exact skips / 0 fail NON_QUALIFYING"]
+ R["Historical recovery at 6ee5954 3 endpoints CLOSED; no stop receipt maintenance-only / NON_QUALIFYING"]
S["Freeze clean source S BLOCKED by protected FUSE tombstone"]
I["Build exact runner image receipt bound to S NOT RUN"]
C["Genuine aggregate-cgroup sentinel and negative controls BLOCKED by sentinel-operations authority"]
@@ -55,6 +58,7 @@ flowchart TD
P --> T
P --> B
+ P --> R
T --> S
B --> S
S --> I --> C --> E --> EC --> Q
@@ -65,7 +69,7 @@ flowchart TD
classDef partial fill:#fff0d0,stroke:#9a6500,color:#4c3500;
classDef red fill:#f7e8ef,stroke:#95506d,color:#482636;
class B green;
- class P,T,S,C,W partial;
+ class P,T,R,S,C,W partial;
class I,E,EC,Q,D,H,PB,FP,M,GM red;
```
@@ -78,6 +82,7 @@ flowchart TD
| Stored proof and Sample Capsule | `7bf3c67`, `773e808`, `9441166`, `3174a1f`, `547cc0f`, `d7d23af` | Green | Exact-deployed event, Capsule, and reconnect proof |
| Belief-break experience | `7bee809`, `0a39471`, `cc61917`, `e80b390`, `830d61b` | Source green; MB-014 partial | Public first-fold and genuine cold-user comprehension evidence |
| Runtime and release authority | cgroup/runtime chain through `eaf4a60`, then `75dfb65`, `b37f7d9`, `84ef49d`, `7e25a09` | Source green | Clean source, exact image, and genuine sentinel |
+| Historical runtime maintenance | `6ee5954`, `17c1a5e` | Helper green; actual attempt fail-closed | Natural tombstone release; maintenance evidence cannot qualify |
| Submission evidence validation | `d942c08`, `9ea8484`, `07c9091`, `8e33154` | Green | Real public repository, video, feedback, assets, and receipt bytes |
| Browser qualification | `3e75281`, `3db3e52`, `adea1c9`, `a6f6d8d`, `0ee8934`, `99e9741` | Harness green | Exact-public 40/40 plus ten manual observations |
| Local Cloak repairs | `d7d23af`, `ccb876b`, `29ea8c3` | Green locally; independent review passed | Public replay, intake, and live verification |
@@ -103,7 +108,7 @@ deferred until release and submission gates are green.
| Real sentinel | Exact image, source-matching runtime, release-only qualification mode | Genuine memory OOM, PID denial, CPU throttling, exact membership, FINALIZE, eight cleanup facts, cgroup absence, exclusive evidence artifact | **Blocked** by the repository-only rule. The observer must read delegated `/sys/fs/cgroup` controls and owned `/proc//stat`, write its owned helper PID to delegated `cgroup.procs`, and adjust only its helper's `/proc/self/oom_score_adj`. | Preserve `NO-GO`; never synthesize or inject aggregate evidence |
| Evidence refresh | Exact image and successful real sentinel while `HEAD == S` | Fresh normalized SBOM, VEX and negative control, scientific-engine bindings, runtime and isolation reports | **Not run** | Any image/source mismatch or stale binding fails closed |
| Evidence commit `E` | Reviewed allowlisted evidence delta | One clean commit containing only regenerated evidence | **Not created** | Do not include unrelated source or cache artifacts |
-| Qualification | Clean `HEAD == E`, build receipt, timeout receipt, source-matching runtime, authenticated Cloudflare account | Qualified runner receipt v6 and exact registry manifest/config digest | **Not run**; contained Wrangler remains logged out after two fresh OAuth callback windows expired without a returned authorization code | Do not promote when account, source, image, timeout, runtime, or evidence differs |
+| Qualification | Clean `HEAD == E`, build receipt, timeout receipt, source-matching runtime, authenticated Cloudflare account | Qualified runner receipt v6 and exact registry manifest/config digest | **Not run**; contained `wrangler whoami --json` remains `{"loggedIn":false}` and the latest fresh OAuth callback expired without a returned authorization code | Do not promote when account, source, image, timeout, runtime, or evidence differs |
| Release check | Clean `E`, qualified receipt, exact local images and runtime | Release-check receipt v5 after formatting, tests, mutations, held-out, sandbox, scientific, build, reproduction, patch replay, and secret gates | **Not run** | Any failure blocks deployment |
| Cloudflare deployment | Qualified receipt, release-check receipt, registry image, required secrets, recovery target, working CloakBrowser endpoint | Exact maintenance/final Worker versions, D1 migration log, exact Container digest, deployment receipt v7 | **Not run** | Stop before mutation when preflight is red; after mutation, automated Worker recovery does not reverse D1 or Container state |
| Public identity and smoke | Active exact Worker/Container tuple | `/ready`, `/api/health?readiness=probe`, Worker 100% traffic, Container identity, public asset scan, Sample, Replay, leakage and imbalance live evidence, Capsule/replay evidence | **Not run** | Source, image, Worker, client, receipt, or capability mismatch is a release failure |
@@ -194,6 +199,12 @@ The current aggregate release chain is split into independent commits:
downgrades, tampering, missing keys, and rotated keys.
- `7e25a09` — requires exact release readiness before browser upload and again
at authoritative Worker live-session admission.
+- `6ee5954` — adds a one-target historical runtime recovery path with exact
+ bundle/toolchain checks, legacy-v1 drain protocol, supervisor-owned shutdown,
+ executable failure tests, and an explicitly non-qualifying receipt.
+- `17c1a5e` — records that the committed recovery attempt issued no drain or
+ shutdown because the supervisor already refused connections; all three exact
+ endpoints subsequently refused read-only probes.
Latest product-code verification at `29ea8c3` is:
@@ -222,11 +233,11 @@ Latest product-code verification at `29ea8c3` is:
**449.90 kB / 129.30 kB gzip**, and CSS **201.45 kB / 34.83 kB gzip**.
These results prove the latest product code and local browser behavior. The
-later committed checkpoint changes through `6b85f2a` and this graph refresh
-are documentation-only. The results do not manufacture a clean source, exact
-image, real aggregate containment observation, Cloudflare qualification,
-deployment, or public browser receipt; every downstream release node remains
-open.
+later committed changes through `17c1a5e` do not alter learner product
+behavior; `6ee5954` is maintenance-only and `17c1a5e` is documentation. These
+results do not manufacture a clean source, exact image, real aggregate
+containment observation, Cloudflare qualification, deployment, or public
+browser receipt; every downstream release node remains open.
## File ownership and reviewer graph
@@ -286,7 +297,7 @@ filesystem authority.
| Delivery gate | Clean `S` -> exact image -> real sentinel -> evidence `E` -> qualification -> release check -> deployment -> public smoke -> full browser matrix -> feature push/repo verification -> submission -> fast-forward main. No later node may compensate for an earlier red node. |
| Diagnosing bugs | Required the red command-server characterization before the one-line half-close repair, followed by focused and original-path runtime proof. |
| Git workflow | Requires selective staging, staged-diff review, configured owner identity, independent logical commits, and no merge or publication before their factual gates. |
-| Build graph | The last full structural graph remains the `b37f7d9` snapshot at 531 files, 5,515 nodes, and 93,346 edges. The operational release graph is current at `6b85f2a`; structural counts are not relabelled without rerunning their generator. |
+| Build graph | The last full structural graph remains the `b37f7d9` snapshot at 531 files, 5,515 nodes, and 93,346 edges. The operational release graph is current at `17c1a5e`; structural counts are not relabelled without rerunning their generator. |
| Code review | Runs standards and specification reviews as separate read-only axes against `main`; findings do not become closed until accepted tests and public evidence pass. |
| Browser QA and Playwright | Keeps local CloakBrowser evidence explicitly non-qualifying and requires exact-public CloakBrowser authority, semantic snapshots, screenshots, console/network inspection, accessibility, and responsive evidence. |
| Wrangler | Requires current documentation, repository-contained CLI state, authenticated-account proof, dry-run/preflight, exact version/digest capture, and qualified-only deployment. |
@@ -311,21 +322,26 @@ filesystem authority.
qualified receipt, or release-check receipt exists.
5. Contained Wrangler authentication is explicitly red: the current
repository-contained `wrangler whoami --json` returned
- `{"loggedIn":false}`, and two fresh OAuth callback windows expired without
- a returned authorization code. The intended Cloudflare account has not been
- authenticated inside the contained environment for this release.
-6. CloakBrowser is operational and the local latest-product-code matrix at
+ `{"loggedIn":false}`, and the latest fresh OAuth callback window expired
+ without a returned authorization code. The intended Cloudflare account has
+ not been authenticated inside the contained environment for this release.
+6. The committed one-target historical recovery path failed closed at the
+ already-unreachable supervisor. The runtime-command, BuildKit proxy, and
+ supervisor endpoints each returned `ECONNREFUSED`; no drain, signal,
+ shutdown, safe-stop receipt, or qualifying receipt occurred. This does not
+ release the FUSE handle or close the clean-source gate.
+7. CloakBrowser is operational and the local latest-product-code matrix at
`29ea8c3` is green.
`deploy-qualified.sh` performs remote Worker, D1, and Container mutations
before invoking the smoke script, so the exact public origin and deployment
receipt must still be supplied to the qualifying run. Local browser
evidence cannot close that public gate.
-7. Commit `0ee8934` closes the checked-in browser finalizer source path: seven
+8. Commit `0ee8934` closes the checked-in browser finalizer source path: seven
ID-bound viewports, privacy-safe INP, schema-v3 raw evidence, deterministic
40-journey receipts, ten honestly labelled human-observation receipts, and
cross-bound report/index/qualification evidence. Exact public CloakBrowser
execution and manual observation remain open at **0 qualifying journeys**.
-8. Learner evidence remains `NO_DATA`; public repository, video, feedback ID,
+9. Learner evidence remains `NO_DATA`; public repository, video, feedback ID,
Devpost publication, and submission receipt remain externally unverified.
The shortest honest path is to resolve the clean-source condition at the primary
From 8e1d80b87f3f192a9fa06585e00220d4956b30ed Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 20:03:35 +0530
Subject: [PATCH 115/143] docs(progress): record browser authority handoff
---
docs/PROGRESS.md | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 639b0d41..635c1e32 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4720,3 +4720,37 @@ No safe-stop receipt, clean source, containment pass, exact image, refreshed
release evidence, Cloudflare mutation, deployment, public qualification,
learner outcome, Devpost submission, push, or merge is claimed. Release remains
**NO-GO**.
+
+## Browser authority handoff — 2026-07-21T14:29:51Z
+
+Deadline: `2026-07-22T00:00:00Z`; **9 hours 30 minutes 9 seconds remained at
+this checkpoint**. Committed HEAD was
+`3b1ae9dc2a7161b76baa395b648d667576df231c` on
+`feat/learner-ux-v6.1`. The only visible worktree entry remained the preserved,
+untracked FUSE tombstone `scripts/.fuse_hidden0000a9600000801f`; it was not
+opened, changed, staged, hidden, ignored, renamed, overwritten, or deleted.
+
+### CloakBrowser check
+
+- The current process environment contains no `CLOAK_CDP_ENDPOINT`.
+- `codex mcp get playwright_safe` returned exit 0 and reported the
+ `playwright_safe` server **enabled**, using the registered
+ `/home/mysterious/.local/bin/playwright-mcp-cloak` launcher with a 45-second
+ startup timeout and 120-second tool timeout.
+- This already-running Codex session does not expose the registered
+ `playwright_safe` tool namespace. Per the repository browser protocol and the
+ installed Playwright skill, a fresh Codex/codex-safe session is required for
+ the MCP tools to be injected.
+- Regular Chrome, stock Chromium, direct Playwright Chromium, and the `@Chrome`
+ surface were not substituted and are not claimed as CloakBrowser evidence.
+- No page was opened, no journey was executed, and no screenshot, console,
+ network, accessibility, or performance result was produced in this check.
+
+### Resume point
+
+Start the next session from this repository through codex-safe, confirm that
+the `playwright_safe` browser tools are present, and then execute the local or
+exact-public matrix that the available release tuple permits. The separate
+clean-source, genuine-containment, exact-image, Cloudflare-authentication,
+deployment, submission, push, and merge gates remain unchanged and red. No
+deployment or publication claim is made.
From 90af3b2b5692a21f0237b7f266841a3330028f63 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 20:09:22 +0530
Subject: [PATCH 116/143] test(runtime): characterize shim root binding
---
scripts/contained-runtime.test.ts | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/scripts/contained-runtime.test.ts b/scripts/contained-runtime.test.ts
index f446ed42..ca5c8efb 100644
--- a/scripts/contained-runtime.test.ts
+++ b/scripts/contained-runtime.test.ts
@@ -1017,6 +1017,24 @@ describe("contained runtime command policy", () => {
).toContain(`socket_dir = '${binding.shimSocketDirectory}'`);
});
+ it("characterizes a distinct shim host root as rejected", () => {
+ const fixtureParent = resolve(
+ root,
+ "node_modules/.cache/counterlab-v6.1/tmp/containerd-config-tests",
+ );
+ mkdirSync(fixtureParent, { recursive: true, mode: 0o700 });
+ const sourceRoot = mkdtempSync(resolve(fixtureParent, "source-"));
+ const shimHostRoot = mkdtempSync(resolve(fixtureParent, "host-"));
+
+ expect(() =>
+ createContainedContainerdConfig({
+ configPath: resolve(sourceRoot, "containerd.toml"),
+ repositoryRoot: sourceRoot,
+ shimSocketRoot: shimHostRoot,
+ }),
+ ).toThrow(/configuration escaped the repository/u);
+ });
+
beforeAll(() => {
mkdirSync(workspace, { recursive: true, mode: 0o700 });
mkdirSync(output, { recursive: true, mode: 0o700 });
From ce772eb57a4dbe1cb3ef50f9a3c17cb37577a50b Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 20:54:04 +0530
Subject: [PATCH 117/143] fix(learner): clarify first-fold hierarchy
---
apps/web/src/App.test.tsx | 4 ++--
apps/web/src/App.tsx | 8 ++++----
.../learner/AccessibilityStyles.test.ts | 14 ++++++++++++++
.../learner/QuestionComposer.module.css | 16 +++++++---------
.../VerifiedBeliefBreakTheater.module.css | 2 +-
.../src/features/judge/JudgeModeView.test.tsx | 3 +++
apps/web/src/features/judge/JudgeModeView.tsx | 4 ++--
apps/web/src/styles.css | 8 ++++++--
8 files changed, 39 insertions(+), 20 deletions(-)
diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
index fe14745f..3a6ae9e8 100644
--- a/apps/web/src/App.test.tsx
+++ b/apps/web/src/App.test.tsx
@@ -1115,12 +1115,12 @@ describe("CounterLab judged flow", () => {
).toBeInTheDocument();
expect(
screen.getByText(
- /ask a question or attach a supported notebook.*reads the evidence and never runs its cells/i,
+ /counterlab is a belief debugger—not a tutor or notebook linter/i,
),
).toBeInTheDocument();
expect(
screen.getByText(
- /built for ml learners: seal a prediction, then let one controlled test answer—not ai prose/i,
+ /attach a supported notebook.*reads the evidence and never runs its cells.*seal a prediction.*one controlled test answer—not ai prose/i,
),
).toBeInTheDocument();
expect(
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 4e52da13..73fb1fb2 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -1105,10 +1105,10 @@ function Landing({
What result are you trying to understand?
- Ask a question or attach a supported notebook. CounterLab reads
- the evidence and never runs its cells. Built for ML learners:
- seal a Prediction, then let one controlled test answer—not AI
- prose.
+ CounterLab is a belief debugger—not a tutor or notebook linter.
+ Ask a question or attach a supported notebook; it reads the
+ evidence and never runs its cells. Seal a Prediction, then let
+ one controlled test answer—not AI prose.
diff --git a/apps/web/src/components/learner/AccessibilityStyles.test.ts b/apps/web/src/components/learner/AccessibilityStyles.test.ts
index cf63d74a..0b231d00 100644
--- a/apps/web/src/components/learner/AccessibilityStyles.test.ts
+++ b/apps/web/src/components/learner/AccessibilityStyles.test.ts
@@ -105,12 +105,26 @@ describe("learner-facing responsive style safeguards", () => {
it("contains horizontal overflow and removes hover motion when requested", () => {
expect(judgeStyles).toContain("overflow-x: clip");
+ expect(globalStyles).toContain(
+ ".landing-question-first {\n color-scheme: dark;\n min-height: 100svh;\n overflow-x: clip;",
+ );
+ expect(questionComposerStyles).not.toContain("overflow-x: auto");
+ expect(questionComposerStyles).toContain(
+ ".promptGroup > div {\n width: 100%;\n max-width: 100%;\n display: grid;",
+ );
expect(judgeStyles).toContain("transition: none !important");
expect(judgeStyles).toContain("transform: none");
expect(theaterStyles).toContain("@media (prefers-reduced-motion: reduce)");
expect(theaterStyles).toContain("animation: none");
});
+ it("keeps programmatic landing-heading focus visible without boxing the headline", () => {
+ expect(globalStyles).toContain(
+ ".landing-intro > h1:focus-visible {\n outline: 0;\n text-decoration: underline;",
+ );
+ expect(globalStyles).toContain("text-decoration-color: var(--focus-ring)");
+ });
+
it("keeps the composer dominant in one centered responsive column", () => {
expect(globalStyles).not.toContain(
"grid-template-columns: minmax(520px, 1.08fr) minmax(480px, 0.92fr)",
diff --git a/apps/web/src/components/learner/QuestionComposer.module.css b/apps/web/src/components/learner/QuestionComposer.module.css
index 6f3081fc..91dd7ed3 100644
--- a/apps/web/src/components/learner/QuestionComposer.module.css
+++ b/apps/web/src/components/learner/QuestionComposer.module.css
@@ -234,9 +234,7 @@
.promptGroup {
margin-top: 8px;
- overflow-x: auto;
- overscroll-behavior-inline: contain;
- scrollbar-width: thin;
+ overflow: visible;
}
.promptGroup > span {
@@ -252,16 +250,16 @@
}
.promptGroup > div {
- width: max-content;
- max-width: none;
- display: flex;
+ width: 100%;
+ max-width: 100%;
+ display: grid;
gap: 6px;
}
.promptGroup button {
- width: auto;
- min-width: max-content;
- max-width: 310px;
+ width: 100%;
+ min-width: 0;
+ max-width: none;
padding-inline: 10px;
border: 1px solid var(--line);
border-radius: 10px;
diff --git a/apps/web/src/components/learner/VerifiedBeliefBreakTheater.module.css b/apps/web/src/components/learner/VerifiedBeliefBreakTheater.module.css
index ab5d7c07..e7d1fc5c 100644
--- a/apps/web/src/components/learner/VerifiedBeliefBreakTheater.module.css
+++ b/apps/web/src/components/learner/VerifiedBeliefBreakTheater.module.css
@@ -877,7 +877,7 @@
.foldTakeaway dd {
margin: 2px 0 0;
- color: var(--ink-soft);
+ color: var(--ink);
font-size: 13px;
line-height: 1.3;
}
diff --git a/apps/web/src/features/judge/JudgeModeView.test.tsx b/apps/web/src/features/judge/JudgeModeView.test.tsx
index 938ab149..14255de6 100644
--- a/apps/web/src/features/judge/JudgeModeView.test.tsx
+++ b/apps/web/src/features/judge/JudgeModeView.test.tsx
@@ -126,6 +126,9 @@ describe("JudgeModeView", () => {
const proof = screen.getByRole("complementary", {
name: /ten second fixed sample preview/i,
});
+ expect(proof).toHaveTextContent(
+ /a belief debugger—not a tutor or notebook linter/i,
+ );
expect(proof).toHaveTextContent(
/completed fixed sample.*not a live result/i,
);
diff --git a/apps/web/src/features/judge/JudgeModeView.tsx b/apps/web/src/features/judge/JudgeModeView.tsx
index 31e8892f..9e8f3d69 100644
--- a/apps/web/src/features/judge/JudgeModeView.tsx
+++ b/apps/web/src/features/judge/JudgeModeView.tsx
@@ -87,8 +87,8 @@ export function JudgeModeView({
- Approved fixed sample framing. No GPT-5.6, Codex, or runner call
- occurs here.
+ A belief debugger—not a tutor or notebook linter. Fixed sample; no
+ GPT-5.6, Codex, or runner call occurs here.
h1:focus-visible {
- outline: 3px solid var(--focus-ring);
- outline-offset: 6px;
+ outline: 0;
+ text-decoration: underline;
+ text-decoration-color: var(--focus-ring);
+ text-decoration-thickness: 2px;
+ text-underline-offset: 7px;
}
.landing-belief-break {
From ca40ad39c6369126b10eb3ee5daf34387a885b93 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 21:01:37 +0530
Subject: [PATCH 118/143] feat(learner): expose first-fold method and authority
---
apps/web/src/App.test.tsx | 30 +++-
apps/web/src/App.tsx | 24 ++-
.../learner/LearnerThesisStrip.module.css | 148 ++++++++++++++++++
.../learner/LearnerThesisStrip.test.tsx | 34 ++++
.../components/learner/LearnerThesisStrip.tsx | 48 ++++++
.../learner/LiveAuthorityStrip.module.css | 133 ++++++++++++++++
.../learner/LiveAuthorityStrip.test.tsx | 37 +++++
.../components/learner/LiveAuthorityStrip.tsx | 47 ++++++
apps/web/src/styles.css | 47 ++++--
9 files changed, 523 insertions(+), 25 deletions(-)
create mode 100644 apps/web/src/components/learner/LearnerThesisStrip.module.css
create mode 100644 apps/web/src/components/learner/LearnerThesisStrip.test.tsx
create mode 100644 apps/web/src/components/learner/LearnerThesisStrip.tsx
create mode 100644 apps/web/src/components/learner/LiveAuthorityStrip.module.css
create mode 100644 apps/web/src/components/learner/LiveAuthorityStrip.test.tsx
create mode 100644 apps/web/src/components/learner/LiveAuthorityStrip.tsx
diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
index 3a6ae9e8..7e5acb49 100644
--- a/apps/web/src/App.test.tsx
+++ b/apps/web/src/App.test.tsx
@@ -1126,6 +1126,19 @@ describe("CounterLab judged flow", () => {
expect(
screen.getByRole("link", { name: /skip to main content/i }),
).toHaveAttribute("href", "#main-content");
+ const fastPaths = screen.getByLabelText(
+ /fastest ways to see counterlab work/i,
+ );
+ await user.tab();
+ expect(
+ within(fastPaths).getByRole("button", {
+ name: /^start verified sample/i,
+ }),
+ ).toHaveFocus();
+ await user.tab();
+ expect(
+ within(fastPaths).getByRole("link", { name: /judge mode/i }),
+ ).toHaveFocus();
await user.tab();
expect(
screen.getByRole("textbox", { name: /your question or claim/i }),
@@ -1135,14 +1148,23 @@ describe("CounterLab judged flow", () => {
screen.getByRole("button", { name: /try verified sample/i }),
).toBeEnabled();
expect(
- screen.getByRole("button", { name: /start verified sample lesson/i }),
+ screen.getByRole("button", { name: /^start verified sample/i }),
).toBeEnabled();
expect(
screen.getByRole("button", { name: /watch verified replay/i }),
).toBeEnabled();
- expect(screen.getByRole("link", { name: /judge mode/i })).toHaveAttribute(
- "href",
- "/judge",
+ expect(
+ within(fastPaths).getByRole("link", { name: /judge mode/i }),
+ ).toHaveAttribute("href", "/judge");
+ expect(
+ screen.getByLabelText(/what happens in 20 seconds/i),
+ ).toHaveTextContent(
+ /state claim.*lock prediction.*controlled test runs.*result passes verification/i,
+ );
+ expect(
+ screen.getByLabelText(/authority roles for a live notebook run/i),
+ ).toHaveTextContent(
+ /gpt-5\.6.*runtime codex.*fixed kernel.*frozen verifier/i,
);
expect(screen.getByText(/no account needed/i)).toBeInTheDocument();
expect(
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 73fb1fb2..38565ec2 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -84,6 +84,8 @@ import {
import { PrivacyPacketSummary } from "./components/learner/PrivacyPacketSummary";
import { QuestionComposer } from "./components/learner/QuestionComposer";
import { LockedBeliefBreakPreview } from "./components/learner/LockedBeliefBreakPreview";
+import { LearnerThesisStrip } from "./components/learner/LearnerThesisStrip";
+import { LiveAuthorityStrip } from "./components/learner/LiveAuthorityStrip";
import {
currentLearnerStage,
learnerSessionStatusLabel,
@@ -1112,6 +1114,18 @@ function Landing({
+
+
-
- Start verified sample lesson →
-
+
+
span {
+ color: var(--ink-soft);
+ font-size: 13px;
+ line-height: 1.4;
+}
+
+@media (max-width: 760px) {
+ .strip {
+ grid-template-columns: 1fr;
+ gap: 12px;
+ }
+}
+
+@media (max-width: 520px) {
+ .strip {
+ padding: 12px;
+ }
+
+ .stages {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 6px;
+ }
+
+ .stages li:not(:last-child)::after {
+ top: 50%;
+ right: -7px;
+ bottom: auto;
+ left: auto;
+ width: 7px;
+ height: 0;
+ border-top: 1px solid var(--line-strong);
+ border-left: 0;
+ }
+
+ .stages li {
+ padding: 9px 7px;
+ }
+
+ .index {
+ position: static;
+ margin-bottom: 6px;
+ }
+
+ .stageCopy strong,
+ .stageCopy > span {
+ font-size: 12px;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .strip,
+ .strip * {
+ transition: none;
+ }
+}
diff --git a/apps/web/src/components/learner/LearnerThesisStrip.test.tsx b/apps/web/src/components/learner/LearnerThesisStrip.test.tsx
new file mode 100644
index 00000000..156333eb
--- /dev/null
+++ b/apps/web/src/components/learner/LearnerThesisStrip.test.tsx
@@ -0,0 +1,34 @@
+import { render, screen, within } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import { LearnerThesisStrip } from "./LearnerThesisStrip";
+
+describe("LearnerThesisStrip", () => {
+ it("explains the opening sequence as three fixed learner stages", () => {
+ render( );
+
+ const strip = screen.getByLabelText("What happens in 20 seconds");
+ const sequence = within(strip).getByRole("list", {
+ name: "CounterLab opening sequence",
+ });
+ const stages = within(sequence).getAllByRole("listitem");
+
+ expect(stages).toHaveLength(3);
+ expect(stages[0]).toHaveTextContent(
+ "State claimName what you want to test",
+ );
+ expect(stages[1]).toHaveTextContent("Lock PredictionCommit before results");
+ expect(stages[2]).toHaveTextContent(
+ "Controlled testRun; result passes verification before release",
+ );
+ });
+
+ it("does not expose a result value or verdict", () => {
+ const { container } = render( );
+
+ expect(container).not.toHaveTextContent(/\b\d{2,3}(?:\.\d+)?%/u);
+ expect(container).not.toHaveTextContent(
+ /\b(?:supports|rejected|inconclusive)\b/iu,
+ );
+ });
+});
diff --git a/apps/web/src/components/learner/LearnerThesisStrip.tsx b/apps/web/src/components/learner/LearnerThesisStrip.tsx
new file mode 100644
index 00000000..6d6eae76
--- /dev/null
+++ b/apps/web/src/components/learner/LearnerThesisStrip.tsx
@@ -0,0 +1,48 @@
+import styles from "./LearnerThesisStrip.module.css";
+
+const stages = [
+ {
+ label: "State claim",
+ action: "Name what you want to test",
+ },
+ {
+ label: "Lock Prediction",
+ action: "Commit before results",
+ },
+ {
+ label: "Controlled test",
+ action: "Run; result passes verification before release",
+ },
+] as const;
+
+export function LearnerThesisStrip() {
+ return (
+
+
+
What happens in 20 seconds
+
+ State claim → lock Prediction → controlled test runs → result passes
+ verification.
+
+
+
+
+ {stages.map((stage, index) => (
+
+
+ {index + 1}
+
+
+ {stage.label}
+ {stage.action}
+
+
+ ))}
+
+
+ );
+}
diff --git a/apps/web/src/components/learner/LiveAuthorityStrip.module.css b/apps/web/src/components/learner/LiveAuthorityStrip.module.css
new file mode 100644
index 00000000..0794dbc6
--- /dev/null
+++ b/apps/web/src/components/learner/LiveAuthorityStrip.module.css
@@ -0,0 +1,133 @@
+.strip {
+ width: 100%;
+ min-width: 0;
+ padding: 12px 14px;
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr);
+ align-items: center;
+ gap: 14px;
+ border: 1px solid color-mix(in srgb, var(--aqua) 28%, var(--line));
+ border-radius: 14px;
+ color: var(--ink);
+ background: color-mix(in srgb, var(--aqua) 5%, var(--paper));
+ font-family: var(--sans);
+}
+
+.heading {
+ display: flex;
+ min-width: 152px;
+ align-items: center;
+ gap: 9px;
+}
+
+.badge {
+ display: inline-flex;
+ min-height: 28px;
+ align-items: center;
+ padding: 4px 8px;
+ border: 1px solid color-mix(in srgb, var(--aqua) 42%, var(--line));
+ border-radius: 999px;
+ color: var(--aqua-bright);
+ background: var(--aqua-soft);
+ font-size: 12px;
+ font-weight: 760;
+ line-height: 1.2;
+ white-space: nowrap;
+}
+
+.heading p {
+ margin: 0;
+ display: grid;
+ gap: 1px;
+}
+
+.heading strong {
+ font-size: 14px;
+ line-height: 1.35;
+}
+
+.heading p > span {
+ color: var(--ink-soft);
+ font-size: 12px;
+ line-height: 1.35;
+}
+
+.authorities {
+ min-width: 0;
+ padding: 0;
+ margin: 0;
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 0;
+}
+
+.authorities > div {
+ min-width: 0;
+ padding: 2px 12px;
+ border-left: 1px solid var(--line);
+}
+
+.authorities dt,
+.authorities dd {
+ margin: 0;
+}
+
+.authorities dt {
+ font-size: 13px;
+ font-weight: 760;
+ line-height: 1.35;
+}
+
+.authorities dd {
+ margin-top: 2px;
+ color: var(--ink-soft);
+ font-size: 13px;
+ line-height: 1.4;
+}
+
+@media (max-width: 880px) {
+ .strip {
+ grid-template-columns: 1fr;
+ gap: 10px;
+ }
+
+ .heading {
+ min-width: 0;
+ }
+
+ .authorities > div:first-child {
+ padding-left: 0;
+ border-left: 0;
+ }
+}
+
+@media (max-width: 560px) {
+ .strip {
+ padding: 12px;
+ }
+
+ .heading {
+ align-items: flex-start;
+ }
+
+ .authorities {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px 0;
+ }
+
+ .authorities > div {
+ padding: 0 9px;
+ }
+
+ .authorities > div:nth-child(odd) {
+ padding-left: 0;
+ border-left: 0;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .strip,
+ .strip * {
+ transition: none;
+ }
+}
diff --git a/apps/web/src/components/learner/LiveAuthorityStrip.test.tsx b/apps/web/src/components/learner/LiveAuthorityStrip.test.tsx
new file mode 100644
index 00000000..fa417105
--- /dev/null
+++ b/apps/web/src/components/learner/LiveAuthorityStrip.test.tsx
@@ -0,0 +1,37 @@
+import { render, screen, within } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import { LiveAuthorityStrip } from "./LiveAuthorityStrip";
+
+describe("LiveAuthorityStrip", () => {
+ it("separates proposal, compilation, computation, and verification roles", () => {
+ render( );
+
+ const strip = screen.getByLabelText(
+ "Authority roles for a live notebook run",
+ );
+ expect(strip).toHaveAttribute("data-authority-context", "live-capability");
+ expect(within(strip).getByText("Live authority")).toBeVisible();
+
+ const responsibilities = within(strip).getAllByRole("definition");
+ expect(responsibilities).toHaveLength(4);
+ expect(strip).toHaveTextContent("GPT-5.6Proposes a bounded belief frame");
+ expect(strip).toHaveTextContent(
+ "Runtime CodexCompiles allowlisted experiment plans",
+ );
+ expect(strip).toHaveTextContent(
+ "Fixed kernelComputes the numerical result",
+ );
+ expect(strip).toHaveTextContent(
+ "Frozen verifierDecides validity and releases evidence",
+ );
+ });
+
+ it("states that the landing strip describes capability rather than an active call", () => {
+ render( );
+
+ expect(
+ screen.getByText("No live call starts on this screen."),
+ ).toBeVisible();
+ });
+});
diff --git a/apps/web/src/components/learner/LiveAuthorityStrip.tsx b/apps/web/src/components/learner/LiveAuthorityStrip.tsx
new file mode 100644
index 00000000..8fc22ce6
--- /dev/null
+++ b/apps/web/src/components/learner/LiveAuthorityStrip.tsx
@@ -0,0 +1,47 @@
+import styles from "./LiveAuthorityStrip.module.css";
+
+const authorities = [
+ {
+ name: "GPT-5.6",
+ responsibility: "Proposes a bounded belief frame",
+ },
+ {
+ name: "Runtime Codex",
+ responsibility: "Compiles allowlisted experiment plans",
+ },
+ {
+ name: "Fixed kernel",
+ responsibility: "Computes the numerical result",
+ },
+ {
+ name: "Frozen verifier",
+ responsibility: "Decides validity and releases evidence",
+ },
+] as const;
+
+export function LiveAuthorityStrip() {
+ return (
+
+
+
Live path
+
+ Live authority
+ No live call starts on this screen.
+
+
+
+
+ {authorities.map((authority) => (
+
+
{authority.name}
+ {authority.responsibility}
+
+ ))}
+
+
+ );
+}
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css
index bab083db..40e9d0f6 100644
--- a/apps/web/src/styles.css
+++ b/apps/web/src/styles.css
@@ -6732,29 +6732,44 @@ body {
text-align: center;
}
-.landing-mobile-sample {
+.landing-entry-actions {
+ width: min(780px, 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+}
+
+.landing-entry-actions :is(button, a) {
min-height: 44px;
padding: 10px 16px;
- display: none;
+ display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
- border: 1px solid var(--line-strong);
- border-radius: 999px;
- background: var(--paper);
- color: var(--ink);
+ border: 1px solid var(--accent);
+ border-radius: 12px;
+ color: var(--accent-ink);
+ background: var(--accent);
cursor: pointer;
font: inherit;
font-size: 14px;
- font-weight: 650;
+ font-weight: 760;
+ text-decoration: none;
}
-.landing-mobile-sample:focus-visible {
+.landing-entry-actions a {
+ border-color: var(--line-strong);
+ color: var(--ink);
+ background: transparent;
+}
+
+.landing-entry-actions :is(button, a):focus-visible {
outline: 3px solid var(--focus-ring);
outline-offset: 3px;
}
-.landing-mobile-sample:disabled {
+.landing-entry-actions button:disabled {
cursor: not-allowed;
opacity: 0.5;
}
@@ -7066,10 +7081,6 @@ body {
min-height: calc(100svh - 86px);
padding: clamp(40px, 7vh, 68px) 20px 54px;
}
-
- .landing-mobile-sample {
- display: inline-flex;
- }
}
@media (max-width: 560px) {
@@ -7086,6 +7097,16 @@ body {
gap: 10px;
}
+ .landing-entry-actions {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .landing-entry-actions :is(button, a) {
+ width: 100%;
+ padding-inline: 10px;
+ }
+
.landing-intro > h1 {
font-size: 24px;
}
From 12f3cf6dbdc4e3ca31c99f6ff1ad12baad43f8d1 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 21:10:51 +0530
Subject: [PATCH 119/143] feat(learner): label explanation authority
---
.../components/learner/ModelDuel.module.css | 8 +++++
.../src/components/learner/ModelDuel.test.tsx | 22 +++++++++++++
apps/web/src/components/learner/ModelDuel.tsx | 32 +++++++++++++++++--
3 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/apps/web/src/components/learner/ModelDuel.module.css b/apps/web/src/components/learner/ModelDuel.module.css
index e49b8986..5a234966 100644
--- a/apps/web/src/components/learner/ModelDuel.module.css
+++ b/apps/web/src/components/learner/ModelDuel.module.css
@@ -61,6 +61,14 @@
margin: 0;
}
+.sourceLabel {
+ margin: 8px 0 0;
+ color: var(--ink-soft);
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 1.4;
+}
+
.prediction {
display: grid;
gap: 5px;
diff --git a/apps/web/src/components/learner/ModelDuel.test.tsx b/apps/web/src/components/learner/ModelDuel.test.tsx
index 17cd5a88..4b8620a7 100644
--- a/apps/web/src/components/learner/ModelDuel.test.tsx
+++ b/apps/web/src/components/learner/ModelDuel.test.tsx
@@ -39,6 +39,9 @@ describe("ModelDuel", () => {
expect(currentCard).toHaveAttribute("data-model-weight", "equal");
expect(alternativeCard).toHaveAttribute("data-model-weight", "equal");
expect(currentCard.className).toBe(alternativeCard.className);
+ expect(currentCard).toHaveTextContent("Your input");
+ expect(alternativeCard).toHaveTextContent("Reviewed Subject Pack draft");
+ expect(alternativeCard).not.toHaveTextContent("AI-suggested draft");
expect(screen.getByText(current.prediction)).toBeInTheDocument();
expect(screen.getByText(alternative.prediction)).toBeInTheDocument();
expect(document.body).not.toHaveTextContent(/correct|incorrect/i);
@@ -48,6 +51,25 @@ describe("ModelDuel", () => {
expect(screen.getByText(current.nonClaims[0]!)).toBeInTheDocument();
});
+ it("labels genuine model-authored framing only when the caller declares it", () => {
+ render(
+ ,
+ );
+
+ expect(
+ screen.getByLabelText("Alternative CounterLab will test"),
+ ).toHaveTextContent("AI-suggested draft");
+ expect(screen.queryByText("Reviewed Subject Pack draft")).toBeNull();
+ });
+
it("routes confirmation, editing, and tertiary decisions through callbacks", async () => {
const user = userEvent.setup();
const confirm = vi.fn();
diff --git a/apps/web/src/components/learner/ModelDuel.tsx b/apps/web/src/components/learner/ModelDuel.tsx
index 89951c74..ac688369 100644
--- a/apps/web/src/components/learner/ModelDuel.tsx
+++ b/apps/web/src/components/learner/ModelDuel.tsx
@@ -9,7 +9,22 @@ export type DuelModel = Readonly<{
nonClaims: readonly string[];
}>;
-function ModelCard({ label, model }: { label: string; model: DuelModel }) {
+export type ModelDuelDraftSource = "subject_pack" | "ai_suggested";
+
+const ALTERNATIVE_SOURCE_LABELS: Record = {
+ subject_pack: "Reviewed Subject Pack draft",
+ ai_suggested: "AI-suggested draft",
+};
+
+function ModelCard({
+ label,
+ model,
+ sourceLabel,
+}: {
+ label: string;
+ model: DuelModel;
+ sourceLabel: string;
+}) {
return (
{label}
+ {sourceLabel}
{model.statement}
Predicts
@@ -55,6 +71,7 @@ export function ModelDuel({
onInsufficientEvidence,
onReject,
confirmed = false,
+ alternativeSource = "subject_pack",
}: {
current: DuelModel;
alternative: DuelModel;
@@ -63,15 +80,24 @@ export function ModelDuel({
onInsufficientEvidence: () => void;
onReject: () => void;
confirmed?: boolean;
+ alternativeSource?: ModelDuelDraftSource;
}) {
const titleId = useId();
const comparison = (
-
+
versus
-
+
);
From 9e07849ed934e78941dda74c23315c37a33042ff Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 21:11:38 +0530
Subject: [PATCH 120/143] feat(learner): clarify verified result direction
---
.../learner/ExperimentTheater.module.css | 35 ++++--
.../learner/ExperimentTheater.test.tsx | 86 ++++++++++++++
.../components/learner/ExperimentTheater.tsx | 106 ++++++++++++++++--
.../VerifiedBeliefBreakTheater.module.css | 26 ++++-
.../VerifiedBeliefBreakTheater.test.tsx | 12 ++
.../learner/VerifiedBeliefBreakTheater.tsx | 75 +++++++------
6 files changed, 290 insertions(+), 50 deletions(-)
diff --git a/apps/web/src/components/learner/ExperimentTheater.module.css b/apps/web/src/components/learner/ExperimentTheater.module.css
index e68d0d2f..c7c87d0a 100644
--- a/apps/web/src/components/learner/ExperimentTheater.module.css
+++ b/apps/web/src/components/learner/ExperimentTheater.module.css
@@ -96,6 +96,10 @@
margin-top: 18px;
}
+.verifiedContentReveal {
+ animation: verified-result-reveal 200ms ease-out both;
+}
+
.dominantVisual {
min-width: 0;
}
@@ -168,9 +172,26 @@
line-height: 1.45;
}
-.comparison > div > span {
+.direction {
+ display: grid;
+ max-width: 150px;
+ justify-items: center;
+ gap: 5px;
color: var(--aqua-bright);
- font-size: 26px;
+ text-align: center;
+}
+
+.directionSymbol {
+ font-size: 30px;
+ font-weight: 800;
+ line-height: 1;
+}
+
+.directionLabel {
+ color: var(--theater-copy);
+ font-size: 12px;
+ font-weight: 750;
+ line-height: 1.3;
}
.finding {
@@ -248,7 +269,6 @@
border: 1px solid var(--theater-line);
border-radius: 12px;
background: var(--theater-raised);
- animation: reveal 180ms ease both;
}
.viewPanel > h3 {
@@ -266,10 +286,10 @@
line-height: 1.6;
}
-@keyframes reveal {
+@keyframes verified-result-reveal {
from {
opacity: 0;
- transform: translateY(4px);
+ transform: translateY(6px);
}
to {
@@ -296,9 +316,8 @@
grid-template-columns: 1fr;
}
- .comparison > div > span {
+ .direction {
justify-self: center;
- transform: rotate(90deg);
}
.viewTabs {
@@ -307,7 +326,7 @@
}
@media (prefers-reduced-motion: reduce) {
- .viewPanel {
+ .verifiedContentReveal {
animation: none;
}
}
diff --git a/apps/web/src/components/learner/ExperimentTheater.test.tsx b/apps/web/src/components/learner/ExperimentTheater.test.tsx
index 8dcf7f23..25875e06 100644
--- a/apps/web/src/components/learner/ExperimentTheater.test.tsx
+++ b/apps/web/src/components/learner/ExperimentTheater.test.tsx
@@ -95,6 +95,92 @@ describe("ExperimentTheater", () => {
expect(screen.getByRole("tablist")).toBeInTheDocument();
});
+ it("shows a non-color downward cue for the verified unseen-customer result", () => {
+ const { rerender } = render(
+ ,
+ );
+
+ expect(screen.queryByRole("note", { name: /downward change/i })).toBeNull();
+ expect(
+ document.querySelector('[data-result-reveal="verified-payload"]'),
+ ).not.toBeInTheDocument();
+
+ rerender(
+ ,
+ );
+
+ const direction = screen.getByRole("note", {
+ name: /downward change.*lower on unseen customers/i,
+ });
+ expect(direction).toHaveAttribute("data-direction", "decrease");
+ expect(direction).toHaveTextContent("↓");
+ expect(direction).toHaveTextContent("Lower on unseen customers");
+ expect(
+ document.querySelector('[data-result-reveal="verified-payload"]'),
+ ).toBeInTheDocument();
+ });
+
+ it("keeps incomparable imbalance measures neutral unless the payload supplies direction", () => {
+ const imbalancePayload: ExperimentTheaterVerifiedPayload = {
+ ...payload,
+ comparison: {
+ title: "Headline accuracy versus rare-class recall",
+ accessibleSummary:
+ "Verified comparison: majority baseline accuracy 98 percent; model rare-class recall 61 percent.",
+ first: {
+ label: "Majority baseline accuracy",
+ value: "98%",
+ },
+ second: {
+ label: "Model rare-class recall",
+ value: "61%",
+ },
+ },
+ };
+ const { rerender } = render(
+ ,
+ );
+
+ const neutralDirection = screen.getByRole("note", {
+ name: /neutral comparison.*no increase or decrease is implied/i,
+ });
+ expect(neutralDirection).toHaveAttribute("data-direction", "neutral");
+ expect(neutralDirection).toHaveTextContent("↔");
+ expect(neutralDirection).toHaveTextContent("Compare");
+ expect(screen.queryByText(/lower on unseen customers/i)).toBeNull();
+
+ rerender(
+ ,
+ );
+
+ const suppliedDirection = screen.getByRole("note", {
+ name: /upward change.*rare-class recall is higher/i,
+ });
+ expect(suppliedDirection).toHaveAttribute("data-direction", "increase");
+ expect(suppliedDirection).toHaveTextContent("↑");
+ expect(suppliedDirection).toHaveTextContent("Higher rare-class recall");
+ });
+
it("mounts only the registered visual after a verified payload", async () => {
const visualPayload: ExperimentTheaterVerifiedPayload = {
...payload,
diff --git a/apps/web/src/components/learner/ExperimentTheater.tsx b/apps/web/src/components/learner/ExperimentTheater.tsx
index 568ac41b..57b4d216 100644
--- a/apps/web/src/components/learner/ExperimentTheater.tsx
+++ b/apps/web/src/components/learner/ExperimentTheater.tsx
@@ -31,19 +31,102 @@ export type ExperimentTheaterTrustedVisual = Readonly<{
revealFinding: boolean;
}>;
+export type ExperimentTheaterComparisonDirection = Readonly<{
+ kind: "decrease" | "increase" | "neutral";
+ label: string;
+ accessibleLabel: string;
+}>;
+
+type ExperimentTheaterComparison = Readonly<{
+ title: string;
+ accessibleSummary: string;
+ first: Readonly<{ label: string; value: string; detail?: string }>;
+ second: Readonly<{ label: string; value: string; detail?: string }>;
+ direction?: ExperimentTheaterComparisonDirection;
+}>;
+
export type ExperimentTheaterVerifiedPayload = Readonly<{
trustedVisual?: ExperimentTheaterTrustedVisual;
- comparison: Readonly<{
- title: string;
- accessibleSummary: string;
- first: Readonly<{ label: string; value: string; detail?: string }>;
- second: Readonly<{ label: string; value: string; detail?: string }>;
- }>;
+ comparison: ExperimentTheaterComparison;
finding: string;
controlledVariables: string;
views: Readonly>;
}>;
+type ResolvedComparisonDirection = ExperimentTheaterComparisonDirection &
+ Readonly<{ symbol: "↓" | "↑" | "↔" }>;
+
+const directionSymbols = {
+ decrease: "↓",
+ increase: "↑",
+ neutral: "↔",
+} as const;
+
+function resolveComparisonDirection(
+ comparison: ExperimentTheaterComparison,
+): ResolvedComparisonDirection {
+ if (comparison.direction !== undefined) {
+ return {
+ ...comparison.direction,
+ symbol: directionSymbols[comparison.direction.kind],
+ };
+ }
+
+ const subjectCues = [
+ comparison.title,
+ comparison.first.label,
+ comparison.second.label,
+ ]
+ .join(" ")
+ .toLocaleLowerCase();
+ const comparesFamiliarRowsWithUnseenCustomers =
+ subjectCues.includes("familiar row") &&
+ (subjectCues.includes("unseen customer") ||
+ subjectCues.includes("new customer"));
+
+ if (comparesFamiliarRowsWithUnseenCustomers) {
+ return {
+ kind: "decrease",
+ symbol: directionSymbols.decrease,
+ label: "Lower on unseen customers",
+ accessibleLabel:
+ "Downward change: the verified result is lower on unseen customers.",
+ };
+ }
+
+ return {
+ kind: "neutral",
+ symbol: directionSymbols.neutral,
+ label: "Compare",
+ accessibleLabel:
+ "Neutral comparison: compare the two verified measures; no increase or decrease is implied.",
+ };
+}
+
+function ComparisonDirection({
+ comparison,
+}: {
+ comparison: ExperimentTheaterComparison;
+}) {
+ const direction = resolveComparisonDirection(comparison);
+
+ return (
+
+
+ {direction.symbol}
+
+
+ {direction.label}
+
+
+ );
+}
+
function TrustedVisual({ visual }: { visual: ExperimentTheaterTrustedVisual }) {
if (visual.id !== "verified_sample_belief_break_v1") {
return (
@@ -170,7 +253,14 @@ export function ExperimentTheater({
) : (
-
+
{verifiedPayload.trustedVisual === undefined ? (
{verifiedPayload.comparison.first.detail}
)}
- →
+
{verifiedPayload.comparison.second.label}
{verifiedPayload.comparison.second.value}
diff --git a/apps/web/src/components/learner/VerifiedBeliefBreakTheater.module.css b/apps/web/src/components/learner/VerifiedBeliefBreakTheater.module.css
index e7d1fc5c..dab038d1 100644
--- a/apps/web/src/components/learner/VerifiedBeliefBreakTheater.module.css
+++ b/apps/web/src/components/learner/VerifiedBeliefBreakTheater.module.css
@@ -252,6 +252,28 @@
line-height: 1.4;
}
+.resultDirection {
+ display: flex;
+ width: fit-content;
+ align-items: center;
+ gap: 8px;
+ margin: 10px auto 0;
+ color: var(--aqua-bright);
+}
+
+.resultDirection > span {
+ font-size: 24px;
+ font-weight: 800;
+ line-height: 1;
+}
+
+.resultDirection > small {
+ color: var(--ink);
+ font-size: 13px;
+ font-weight: 720;
+ line-height: 1.35;
+}
+
.finding {
max-width: 850px;
margin: 16px 0 0;
@@ -959,8 +981,8 @@
}
.previewScoreShift {
- grid-template-columns: minmax(0, 1fr) 46px minmax(0, 1fr);
- gap: 8px;
+ grid-template-columns: minmax(0, 1fr) 74px minmax(0, 1fr);
+ gap: 6px;
}
.embeddedMechanism.preview {
diff --git a/apps/web/src/components/learner/VerifiedBeliefBreakTheater.test.tsx b/apps/web/src/components/learner/VerifiedBeliefBreakTheater.test.tsx
index 72bc538f..1316a4c8 100644
--- a/apps/web/src/components/learner/VerifiedBeliefBreakTheater.test.tsx
+++ b/apps/web/src/components/learner/VerifiedBeliefBreakTheater.test.tsx
@@ -54,6 +54,9 @@ describe("VerifiedBeliefBreakTheater", () => {
name: /changed variable: evaluation unit/i,
}),
).not.toBeInTheDocument();
+ expect(
+ screen.queryByRole("note", { name: /downward change/i }),
+ ).not.toBeInTheDocument();
expect(screen.queryByText("Boundary consequence")).not.toBeInTheDocument();
expect(screen.queryByText("Learner benefit")).not.toBeInTheDocument();
});
@@ -88,6 +91,15 @@ describe("VerifiedBeliefBreakTheater", () => {
expect(theater.querySelectorAll("figure")).toHaveLength(1);
expect(comparison).toHaveAccessibleName(/98.5% to 59.4%/i);
+ const direction = within(theater).getByRole("note", {
+ name: /downward change.*lower on unseen customers/i,
+ });
+ expect(direction).toHaveAttribute("data-direction", "decrease");
+ expect(direction).toHaveTextContent("↓");
+ expect(direction).toHaveTextContent("Lower on unseen customers");
+ expect(
+ theater.querySelector('[data-motion="verified-only"]'),
+ ).toBeInTheDocument();
expect(within(theater).getByText("Fixed-kernel evidence")).toBeVisible();
expect(
within(theater).getByText("Only the evaluation unit changed"),
diff --git a/apps/web/src/components/learner/VerifiedBeliefBreakTheater.tsx b/apps/web/src/components/learner/VerifiedBeliefBreakTheater.tsx
index dfc15789..07e35c7b 100644
--- a/apps/web/src/components/learner/VerifiedBeliefBreakTheater.tsx
+++ b/apps/web/src/components/learner/VerifiedBeliefBreakTheater.tsx
@@ -192,6 +192,20 @@ function MechanismDiagram({
);
}
+function LeakageDirectionCue({ inline = false }: { inline?: boolean }) {
+ return (
+
+ ↓
+ Lower on unseen customers
+
+ );
+}
+
function PreviewComparison({
randomRows,
wholeCustomers,
@@ -310,10 +324,7 @@ function PreviewComparison({
-
- →
- same model
-
+
New-customer test
@@ -366,10 +377,7 @@ function FoldEvidence({ evidence }: { evidence: VerifiedBeliefBreakEvidence }) {
{asPercent(randomRows.metrics.accuracy)}
{randomRows.entityOverlap.count} customers overlap
-
- →
- same model
-
+
New-customer test
{asPercent(wholeCustomers.metrics.accuracy)}
@@ -442,30 +450,33 @@ function VerifiedEvidence({
)}
{isPreview ? null : (
-
-
- Overlapping customers
-
- {randomRows.entityOverlap.count}
- →
- {wholeCustomers.entityOverlap.count}
-
- familiar identities → unseen identities
-
-
- Accuracy
-
- {asPercent(randomRows.metrics.accuracy)}
- →
- {asPercent(wholeCustomers.metrics.accuracy)}
-
- random rows → whole-customer holdout
-
-
+ <>
+
+
+ Overlapping customers
+
+ {randomRows.entityOverlap.count}
+ →
+ {wholeCustomers.entityOverlap.count}
+
+ familiar identities → unseen identities
+
+
+ Accuracy
+
+ {asPercent(randomRows.metrics.accuracy)}
+ ↓
+ {asPercent(wholeCustomers.metrics.accuracy)}
+
+ random rows → whole-customer holdout
+
+
+
+ >
)}
From dabc0586426b5b49fc4000a1b50ccc29821163a3 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 21:14:24 +0530
Subject: [PATCH 121/143] fix(learner): align artifact and proof state labels
---
apps/web/e2e/judged-flow.spec.ts | 24 +-
apps/web/src/App.test.tsx | 4 +-
apps/web/src/App.tsx | 42 ++-
.../components/studio/ProjectSidebar.test.tsx | 55 ++++
.../src/components/studio/ProjectSidebar.tsx | 46 ++-
.../components/studio/ProofConsole.test.tsx | 64 ++++
.../src/components/studio/ProofConsole.tsx | 288 +++++++++++-------
apps/web/src/styles/studio.css | 37 +++
8 files changed, 431 insertions(+), 129 deletions(-)
diff --git a/apps/web/e2e/judged-flow.spec.ts b/apps/web/e2e/judged-flow.spec.ts
index 67dae32c..496c6927 100644
--- a/apps/web/e2e/judged-flow.spec.ts
+++ b/apps/web/e2e/judged-flow.spec.ts
@@ -956,7 +956,11 @@ async function commitAndOpenResult(page: Page) {
await page.getByRole("button", { name: /Seal my prediction/i }).click();
expect((await compileFinished).ok()).toBe(true);
const runFinished = waitForPostResponse(page, "/lab/run");
- await page.getByRole("button", { name: /Run the fair test/i }).click();
+ await page
+ .getByRole("button", {
+ name: /Run the fair test|Reveal verified sample result/i,
+ })
+ .click();
expect((await runFinished).ok()).toBe(true);
await expect(
page.getByRole("heading", { name: /Compare the verified result/i }),
@@ -1567,7 +1571,11 @@ for (const viewport of beliefBreakViewports) {
await expect(
page.getByRole("heading", { name: /The fair test is ready/i }),
).toBeVisible();
- await page.getByRole("button", { name: /Run the fair test/i }).click();
+ await page
+ .getByRole("button", {
+ name: /Run the fair test|Reveal verified sample result/i,
+ })
+ .click();
await expect(
page.getByRole("heading", { name: /Compare the verified result/i }),
).toBeVisible();
@@ -2068,7 +2076,11 @@ test("refresh restores the current lesson and the committed prediction", async (
page.getByRole("heading", { name: /The fair test is ready/i }),
).toBeVisible();
const runFinished = waitForPostResponse(page, "/lab/run");
- await page.getByRole("button", { name: /Run the fair test/i }).click();
+ await page
+ .getByRole("button", {
+ name: /Run the fair test|Reveal verified sample result/i,
+ })
+ .click();
expect((await runFinished).ok()).toBe(true);
await expect(
page.getByRole("heading", { name: /Compare the verified result/i }),
@@ -2510,7 +2522,11 @@ test("the judged path is keyboard operable with reduced motion", async ({
await expect(
page.getByRole("heading", { name: /The fair test is ready/i }),
).toBeVisible();
- await page.getByRole("button", { name: /Run the fair test/i }).focus();
+ await page
+ .getByRole("button", {
+ name: /Run the fair test|Reveal verified sample result/i,
+ })
+ .focus();
await page.keyboard.press("Enter");
await expect(
page.getByRole("heading", { name: /Compare the verified result/i }),
diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
index 7e5acb49..084d3fb8 100644
--- a/apps/web/src/App.test.tsx
+++ b/apps/web/src/App.test.tsx
@@ -3397,7 +3397,7 @@ describe("CounterLab judged flow", () => {
screen.getByRole("heading", { name: /the fair test is ready/i }),
).toBeInTheDocument();
expect(
- screen.getByRole("button", { name: /run the fair test/i }),
+ screen.getByRole("button", { name: /reveal verified sample result/i }),
).toBeEnabled();
expect(screen.queryByText(/new customers 59\.4%/i)).not.toBeInTheDocument();
});
@@ -3433,7 +3433,7 @@ describe("CounterLab judged flow", () => {
expect(document.body).not.toHaveTextContent(/new customers 59\.4%/i);
await user.click(
- screen.getByRole("button", { name: /run the fair test/i }),
+ screen.getByRole("button", { name: /reveal verified sample result/i }),
);
expect(
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 38565ec2..692a819f 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -542,6 +542,35 @@ function presentationMode(mode: SessionView["mode"]): Mode {
return "live";
}
+function patchSourceCopy(mode: SessionView["mode"] | undefined) {
+ if (mode?.kind === "live_notebook") {
+ return {
+ activity: "The uploaded notebook remains read-only.",
+ source: "Original upload is never overwritten",
+ unlock: "The original upload will not be overwritten.",
+ } as const;
+ }
+ if (mode?.kind === "sample_lesson") {
+ return {
+ activity: "The bundled sample artifact remains read-only.",
+ source: "Bundled sample source is never overwritten",
+ unlock: "The bundled sample source will not be overwritten.",
+ } as const;
+ }
+ if (mode?.kind === "verified_replay") {
+ return {
+ activity: "The stored replay artifact remains read-only.",
+ source: "Stored replay source is never overwritten",
+ unlock: "The stored replay source will not be overwritten.",
+ } as const;
+ }
+ return {
+ activity: "The source artifact remains read-only.",
+ source: "Source artifact is never overwritten",
+ unlock: "The source artifact will not be overwritten.",
+ } as const;
+}
+
const storageKeys = {
sessionId: "counterlab.sessionId",
mode: "counterlab.mode",
@@ -1916,7 +1945,11 @@ function BuildScreen({
type="button"
onClick={openResult}
>
- {resultReady ? "Show me what happened" : "Run the fair test"}{" "}
+ {mode === "instant"
+ ? "Reveal verified sample result"
+ : resultReady
+ ? "Show me what happened"
+ : "Run the fair test"}{" "}
@@ -2806,6 +2839,7 @@ function LeakageRealityScreen({
);
const [patchJob, setPatchJob] = useState
(null);
const patchRunner = useRunnerEvents();
+ const sourceCopy = patchSourceCopy(session?.mode);
const accuracyGapPoints =
(random.metrics.accuracy - group.metrics.accuracy) * 100;
@@ -3086,7 +3120,7 @@ function LeakageRealityScreen({
Preparing separate patch job
-
The uploaded notebook remains read-only.
+
{sourceCopy.activity}
)}
@@ -3108,7 +3142,7 @@ function LeakageRealityScreen({
Patch safety
Source
- Original upload is never overwritten
+ {sourceCopy.source}
Scope
@@ -3415,7 +3449,7 @@ function LeakageRealityScreen({
What the patch will change
Random rows → whole-customer holdout
- The original upload will not be overwritten.
+ {sourceCopy.unlock}
{
+ it.each([
+ ["instant", "Bundled sample artifact", "No fixed sample loaded"],
+ ["live", "Uploaded notebook", "No notebook uploaded"],
+ ["replay", "Replay artifact", "Replay artifact unavailable"],
+ ] as const)(
+ "labels the %s artifact origin without implying an upload",
+ (mode, heading, empty) => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText(heading)).toBeInTheDocument();
+ expect(screen.getByText(empty)).toBeInTheDocument();
+ },
+ );
+
+ it("fails closed when the route mode and stored session mode disagree", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Mode mismatch")).toBeInTheDocument();
+ expect(
+ screen.getByRole("button", { name: /mode mismatch/i }),
+ ).toBeDisabled();
+ });
+
it("keeps the keyboard shortcut visible without adding it to the command name", async () => {
const user = userEvent.setup();
const onOpenCommands = vi.fn();
diff --git a/apps/web/src/components/studio/ProjectSidebar.tsx b/apps/web/src/components/studio/ProjectSidebar.tsx
index 753ad5c7..c4c8bfc2 100644
--- a/apps/web/src/components/studio/ProjectSidebar.tsx
+++ b/apps/web/src/components/studio/ProjectSidebar.tsx
@@ -17,6 +17,38 @@ export function ProjectSidebar({
onOpenCommands: () => void;
onClose: () => void;
}) {
+ const expectedSessionMode =
+ context.mode === "instant"
+ ? "sample_lesson"
+ : context.mode === "replay"
+ ? "verified_replay"
+ : "live_notebook";
+ const modeMatchesSession =
+ context.session === null ||
+ context.session.mode.kind === expectedSessionMode;
+ const artifactCopy = !modeMatchesSession
+ ? {
+ heading: "Evidence context",
+ fallbackName: "Mode mismatch",
+ empty: "Artifact details withheld",
+ }
+ : context.mode === "instant"
+ ? {
+ heading: "Bundled sample artifact",
+ fallbackName: "Sample artifact unavailable",
+ empty: "No fixed sample loaded",
+ }
+ : context.mode === "replay"
+ ? {
+ heading: "Replay artifact",
+ fallbackName: "Stored replay evidence",
+ empty: "Replay artifact unavailable",
+ }
+ : {
+ heading: "Uploaded notebook",
+ fallbackName: "Attach a notebook",
+ empty: "No notebook uploaded",
+ };
const evidenceCells =
context.artifact?.cells
.filter(
@@ -59,16 +91,22 @@ export function ProjectSidebar({
- Current notebook
-
+ {artifactCopy.heading}
+
NB
- {context.artifact?.fileName ?? "Choose a notebook"}
+
+ {context.artifact?.fileName ?? artifactCopy.fallbackName}
+
{context.artifact === null
- ? "No artifact selected"
+ ? artifactCopy.empty
: `${context.artifact.cells.length} cells · ${context.artifact.support.status.toLowerCase()}`}
diff --git a/apps/web/src/components/studio/ProofConsole.test.tsx b/apps/web/src/components/studio/ProofConsole.test.tsx
index 62f1040e..39096c1c 100644
--- a/apps/web/src/components/studio/ProofConsole.test.tsx
+++ b/apps/web/src/components/studio/ProofConsole.test.tsx
@@ -52,6 +52,70 @@ function context(overrides: Partial = {}): StudioContext {
}
describe("ProofConsole", () => {
+ it("derives mode, session, and proof chips from authoritative state", () => {
+ render(
+ ,
+ );
+
+ const state = screen.getByLabelText("Proof state");
+ expect(state).toHaveTextContent("Verified sample");
+ expect(state).toHaveTextContent("Prediction sealed");
+ expect(state).toHaveTextContent("Stored chain loaded");
+ });
+
+ it("withholds proof surfaces when route and stored modes disagree", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByLabelText("Proof state")).toHaveTextContent(
+ "Mode mismatch",
+ );
+ expect(screen.getByText("Evidence withheld")).toBeInTheDocument();
+ expect(document.body).not.toHaveTextContent("1 chain event");
+ expect(screen.getByRole("alert")).toHaveTextContent(
+ /proof context does not match this learner mode/i,
+ );
+ expect(document.body).not.toHaveTextContent("session.created");
+ });
+
it("characterizes the existing browser-safe compiler activity view", () => {
render(
["mode"]["kind"],
+): StudioContext["mode"] {
+ if (kind === "sample_lesson") return "instant";
+ if (kind === "verified_replay") return "replay";
+ return "live";
+}
+
function eventSummary(event: PublicCompilerEvent): string {
switch (event.kind) {
case "job.started":
@@ -370,6 +392,17 @@ export function ProofConsole({
const evidenceEvents = context.evidenceEvents ?? [];
const proofEventIssues = context.proofEventIssues ?? [];
const proofEventStatus = context.proofEventStatus ?? "idle";
+ const proofContextMismatch =
+ context.session !== null &&
+ sessionStudioMode(context.session.mode.kind) !== context.mode;
+ const modeLabel =
+ context.session === null
+ ? studioModeLabels[context.mode]
+ : studioModeLabels[sessionStudioMode(context.session.mode.kind)];
+ const sessionLabel =
+ context.session === null
+ ? "No session bound"
+ : learnerSessionStatusLabel(context.session.state);
const proofBundle = context.session?.proofBundle;
const filteredCompilerEvents = compilerEventsForTab(
context.events,
@@ -403,7 +436,10 @@ export function ProofConsole({
document.getElementById(`proof-tab-${nextTab.toLowerCase()}`)?.focus();
};
return (
-
+
Evidence & proof
+
+ {proofContextMismatch ? "Mode mismatch" : modeLabel}
+ {sessionLabel}
+ {proofStatusLabels[proofEventStatus]}
+
- {evidenceEvents.length} chain event
- {evidenceEvents.length === 1 ? "" : "s"} · {context.events.length}{" "}
- runner event{context.events.length === 1 ? "" : "s"}
+ {proofContextMismatch ? (
+ "Evidence withheld"
+ ) : (
+ <>
+ {evidenceEvents.length} chain event
+ {evidenceEvents.length === 1 ? "" : "s"} · {context.events.length}{" "}
+ runner event{context.events.length === 1 ? "" : "s"}
+ >
+ )}
{open ? "Close" : "Open"}
- {open && (
-
-
- {tabs.map((tab, index) => (
-
onTab(tab)}
- onKeyDown={(event) => moveTab(event, index)}
- >
- {tab}
-
- ))}
+ {open &&
+ (proofContextMismatch ? (
+
+
+ Proof context does not match this learner mode.
+
+ Evidence stays withheld until the route and stored session mode
+ agree.
+
+
-
- {proofEventStatus === "loading" ? (
-
- Loading the stored session event chain…
- Runner activity remains a separate event domain.
-
- ) : null}
- {proofEventStatus === "failed" ? (
-
- Stored session event chain is unavailable.
-
- No missing lifecycle evidence was inferred from runner
- activity.
-
-
- ) : null}
- {proofEventStatus === "idle" && evidenceEvents.length === 0 ? (
-
- Stored session event chain not loaded.
-
- Any runner activity shown below remains a separate
- browser-safe stream.
-
-
- ) : null}
- {proofEventIssues.length > 0 ? (
-
-
Conflicting proof activity was excluded.
-
- Validated records remain visible; the conflicting records do
- not contribute to the counts above.
-
-
- {proofEventIssues.map((issue) => (
-
- {issue.code} · {issue.message}
-
- ))}
-
-
- ) : null}
- {activeTab === "Provenance" ? (
- <>
-
- {onRevokeSessionAccess === undefined ? null : (
-
- )}
- >
- ) : !hasTabEvents &&
- proofEventStatus !== "loading" &&
- proofEventStatus !== "failed" ? (
-
-
- No recorded {activeTab.toLowerCase()} evidence is available.
-
-
- Nothing was inferred or reconstructed for this surface.
-
-
- ) : (
- <>
- {proofBundle !== undefined &&
- proofBundleHasTab(proofBundle, activeTab) ? (
-
- ) : null}
- {filteredEvidenceEvents.length > 0 ? (
-
- ) : null}
- {filteredCompilerEvents.length > 0 ? (
-
- ) : null}
- >
- )}
+ ) : (
+
+
+ {tabs.map((tab, index) => (
+ onTab(tab)}
+ onKeyDown={(event) => moveTab(event, index)}
+ >
+ {tab}
+
+ ))}
+
+
+ {proofEventStatus === "loading" ? (
+
+ Loading the stored session event chain…
+ Runner activity remains a separate event domain.
+
+ ) : null}
+ {proofEventStatus === "failed" ? (
+
+ Stored session event chain is unavailable.
+
+ No missing lifecycle evidence was inferred from runner
+ activity.
+
+
+ ) : null}
+ {proofEventStatus === "idle" && evidenceEvents.length === 0 ? (
+
+ Stored session event chain not loaded.
+
+ Any runner activity shown below remains a separate
+ browser-safe stream.
+
+
+ ) : null}
+ {proofEventIssues.length > 0 ? (
+
+
Conflicting proof activity was excluded.
+
+ Validated records remain visible; the conflicting records do
+ not contribute to the counts above.
+
+
+ {proofEventIssues.map((issue) => (
+
+ {issue.code} · {issue.message}
+
+ ))}
+
+
+ ) : null}
+ {activeTab === "Provenance" ? (
+ <>
+
+ {onRevokeSessionAccess === undefined ? null : (
+
+ )}
+ >
+ ) : !hasTabEvents &&
+ proofEventStatus !== "loading" &&
+ proofEventStatus !== "failed" ? (
+
+
+ No recorded {activeTab.toLowerCase()} evidence is available.
+
+
+ Nothing was inferred or reconstructed for this surface.
+
+
+ ) : (
+ <>
+ {proofBundle !== undefined &&
+ proofBundleHasTab(proofBundle, activeTab) ? (
+
+ ) : null}
+ {filteredEvidenceEvents.length > 0 ? (
+
+ ) : null}
+ {filteredCompilerEvents.length > 0 ? (
+
+ ) : null}
+ >
+ )}
+
-
- )}
+ ))}
);
}
diff --git a/apps/web/src/styles/studio.css b/apps/web/src/styles/studio.css
index 960df7fe..4634dff1 100644
--- a/apps/web/src/styles/studio.css
+++ b/apps/web/src/styles/studio.css
@@ -464,6 +464,37 @@ kbd {
font-weight: 600;
}
+.proof-console-context {
+ display: flex;
+ min-width: 0;
+ align-items: center;
+ gap: 6px;
+}
+
+.proof-console-context em {
+ min-height: 24px;
+ padding: 4px 7px;
+ display: inline-flex;
+ align-items: center;
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ color: var(--ink-soft);
+ background: var(--paper);
+ font-family: var(--sans);
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 1.2;
+ white-space: nowrap;
+}
+
+.proof-console[data-proof-context="mismatch"]
+ .proof-console-context
+ em:first-child {
+ color: var(--red);
+ border-color: color-mix(in srgb, var(--red) 55%, var(--line));
+}
+
.proof-console-handle kbd {
margin-left: auto;
}
@@ -474,6 +505,12 @@ kbd {
background: var(--paper);
}
+.proof-context-blocked {
+ height: auto;
+ min-height: 120px;
+ padding: 16px;
+}
+
.proof-tabs {
display: flex;
gap: 4px;
From 71f9ef26f5ff1162513050f257c7270eec0ce135 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 21:19:13 +0530
Subject: [PATCH 122/143] feat(learner): show live test authority phases
---
apps/web/src/App.test.tsx | 9 ++
apps/web/src/App.tsx | 7 ++
.../learner/CompileAuthorityPhase.module.css | 91 +++++++++++++++
.../learner/CompileAuthorityPhase.test.tsx | 73 ++++++++++++
.../learner/CompileAuthorityPhase.tsx | 107 ++++++++++++++++++
5 files changed, 287 insertions(+)
create mode 100644 apps/web/src/components/learner/CompileAuthorityPhase.module.css
create mode 100644 apps/web/src/components/learner/CompileAuthorityPhase.test.tsx
create mode 100644 apps/web/src/components/learner/CompileAuthorityPhase.tsx
diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
index 084d3fb8..43b5223f 100644
--- a/apps/web/src/App.test.tsx
+++ b/apps/web/src/App.test.tsx
@@ -2508,6 +2508,12 @@ describe("CounterLab judged flow", () => {
);
render(
);
+ const planningPhase = (
+ await screen.findByText(/Generated planning · In progress/i)
+ ).closest("li");
+ expect(planningPhase).toHaveAttribute("data-state", "active");
+ expect(screen.getByText(/Fixed testing · Waiting/i)).toBeInTheDocument();
+ expect(screen.getByText(/Verified result · Waiting/i)).toBeInTheDocument();
await user.click(
await screen.findByRole("button", { name: /cancel this test/i }),
);
@@ -2518,6 +2524,9 @@ describe("CounterLab judged flow", () => {
expect(
screen.getByText(/cancelled this test before it could release a result/i),
).toBeInTheDocument();
+ expect(
+ screen.getByText(/Generated planning · Stopped safely/i),
+ ).toBeInTheDocument();
expect(
fetcher.mock.calls.some(
([path, init]) =>
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 692a819f..2f786a2b 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -71,6 +71,7 @@ import {
type FairTestRepairStory,
type PublicTechnicalDetail,
} from "./components/learner/FairTestBuilder";
+import { CompileAuthorityPhase } from "./components/learner/CompileAuthorityPhase";
import { ModelDuel, type DuelModel } from "./components/learner/ModelDuel";
import {
NotebookEvidenceStory,
@@ -4371,6 +4372,7 @@ function LiveCompileScreen({
onRetry: () => void;
onCancel: () => void;
}) {
+ const resultReady = events.some((event) => event.kind === "result.ready");
const verified = events.some(
(event) =>
event.kind === "verifier.verified" || event.kind === "result.ready",
@@ -4438,6 +4440,11 @@ function LiveCompileScreen({
)}
+
strong {
+ color: var(--ink);
+ font-size: 15px;
+ line-height: 1.35;
+}
+
+.intro > span {
+ color: var(--ink-soft);
+ font-size: 13px;
+ line-height: 1.5;
+}
+
+.phases {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 8px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.phase {
+ display: grid;
+ min-width: 0;
+ gap: 5px;
+ padding: 12px;
+ border: 1px solid var(--line);
+ border-radius: 12px;
+ background: var(--paper-deep);
+}
+
+.phase[data-state="active"] {
+ border-color: var(--blue);
+ box-shadow: inset 3px 0 0 var(--blue);
+}
+
+.phase[data-state="complete"] {
+ border-color: var(--aqua);
+ background: var(--aqua-soft);
+}
+
+.phase[data-state="stopped"] {
+ border-color: var(--red);
+ background: var(--red-soft);
+}
+
+.phase > span {
+ color: var(--ink-soft);
+ font-size: 12px;
+ font-weight: 750;
+ line-height: 1.3;
+}
+
+.phase > strong {
+ color: var(--ink);
+ font-size: 14px;
+ line-height: 1.35;
+}
+
+.phase > small {
+ color: var(--ink-soft);
+ font-size: 12px;
+ line-height: 1.4;
+}
+
+@media (max-width: 700px) {
+ .phases {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .root,
+ .phase {
+ scroll-behavior: auto;
+ }
+}
diff --git a/apps/web/src/components/learner/CompileAuthorityPhase.test.tsx b/apps/web/src/components/learner/CompileAuthorityPhase.test.tsx
new file mode 100644
index 00000000..545da914
--- /dev/null
+++ b/apps/web/src/components/learner/CompileAuthorityPhase.test.tsx
@@ -0,0 +1,73 @@
+import { render, screen, within } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+import { CompileAuthorityPhase } from "./CompileAuthorityPhase";
+
+function phase(name: RegExp) {
+ return screen.getByText(name).closest("li");
+}
+
+describe("CompileAuthorityPhase", () => {
+ it("shows generated planning without implying a result", () => {
+ render(
+ ,
+ );
+
+ expect(phase(/Generated planning/)).toHaveAttribute("data-state", "active");
+ expect(phase(/Fixed testing/)).toHaveAttribute("data-state", "pending");
+ expect(phase(/Verified result/)).toHaveAttribute("data-state", "pending");
+ expect(
+ screen.getByText(/Runtime Codex proposes the experiment only/i),
+ ).toBeInTheDocument();
+ expect(document.body).not.toHaveTextContent(/98\.5|59\.4/);
+ });
+
+ it("uses the real run job kind for the fixed-kernel phase", () => {
+ render(
+ ,
+ );
+
+ expect(phase(/Generated planning/)).toHaveAttribute(
+ "data-state",
+ "complete",
+ );
+ expect(phase(/Fixed testing/)).toHaveAttribute("data-state", "active");
+ expect(phase(/Verified result/)).toHaveAttribute("data-state", "pending");
+ });
+
+ it("marks every authority phase complete only when a result-ready event exists", () => {
+ render(
+ ,
+ );
+
+ for (const item of screen.getAllByRole("listitem")) {
+ expect(item).toHaveAttribute("data-state", "complete");
+ }
+ expect(
+ within(phase(/Verified result/)! as HTMLElement).getByText(
+ /Frozen verifier releases/i,
+ ),
+ ).toBeInTheDocument();
+ });
+
+ it("reports a safe stop at the phase owned by the current job", () => {
+ render(
+ ,
+ );
+
+ expect(phase(/Generated planning/)).toHaveAttribute(
+ "data-state",
+ "complete",
+ );
+ expect(phase(/Fixed testing/)).toHaveAttribute("data-state", "stopped");
+ expect(phase(/Verified result/)).toHaveAttribute("data-state", "pending");
+ expect(screen.getByText(/Stopped safely/)).toBeInTheDocument();
+ });
+});
diff --git a/apps/web/src/components/learner/CompileAuthorityPhase.tsx b/apps/web/src/components/learner/CompileAuthorityPhase.tsx
new file mode 100644
index 00000000..2fc0c8b6
--- /dev/null
+++ b/apps/web/src/components/learner/CompileAuthorityPhase.tsx
@@ -0,0 +1,107 @@
+import type { RunnerJob } from "../../api";
+import styles from "./CompileAuthorityPhase.module.css";
+
+type AuthorityPhaseId = "planning" | "testing" | "result";
+type AuthorityPhaseState = "pending" | "active" | "complete" | "stopped";
+
+const phases: readonly {
+ id: AuthorityPhaseId;
+ label: string;
+ title: string;
+ detail: string;
+}[] = [
+ {
+ id: "planning",
+ label: "Generated planning",
+ title: "Runtime Codex proposes",
+ detail: "A bounded plan uses registered operations only.",
+ },
+ {
+ id: "testing",
+ label: "Fixed testing",
+ title: "Fixed kernel runs",
+ detail: "The kernel owns every numerical value.",
+ },
+ {
+ id: "result",
+ label: "Verified result",
+ title: "Frozen verifier releases",
+ detail: "Only verified bindings can reach the result view.",
+ },
+] as const;
+
+function activePhase(jobKind: RunnerJob["kind"] | undefined): AuthorityPhaseId {
+ return jobKind === "LAB_RUN" ? "testing" : "planning";
+}
+
+function phaseState({
+ id,
+ jobKind,
+ resultReady,
+ failed,
+}: {
+ id: AuthorityPhaseId;
+ jobKind: RunnerJob["kind"] | undefined;
+ resultReady: boolean;
+ failed: boolean;
+}): AuthorityPhaseState {
+ if (resultReady) return "complete";
+
+ const current = activePhase(jobKind);
+ if (id === current) return failed ? "stopped" : "active";
+ if (current === "testing" && id === "planning") return "complete";
+ return "pending";
+}
+
+const stateLabels: Record = {
+ pending: "Waiting",
+ active: "In progress",
+ complete: "Complete",
+ stopped: "Stopped safely",
+};
+
+export function CompileAuthorityPhase({
+ jobKind,
+ resultReady,
+ failed,
+}: {
+ jobKind: RunnerJob["kind"] | undefined;
+ resultReady: boolean;
+ failed: boolean;
+}) {
+ return (
+
+
+ Who is allowed to do what
+
+ Runtime Codex proposes the experiment only. The fixed kernel owns the
+ numbers, and the frozen verifier decides whether a result may appear.
+
+
+
+ {phases.map((phase) => {
+ const state = phaseState({
+ id: phase.id,
+ jobKind,
+ resultReady,
+ failed,
+ });
+ return (
+
+
+ {phase.label} · {stateLabels[state]}
+
+ {phase.title}
+ {phase.detail}
+
+ );
+ })}
+
+
+ );
+}
From cb25d908a99dfbd4550337b2f823cea5784df393 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 21:27:27 +0530
Subject: [PATCH 123/143] fix(learner): prevent mobile proof bar overlap
---
.../learner/AccessibilityStyles.test.ts | 13 +++++++
apps/web/src/styles/studio.css | 37 ++++++++++++++++++-
2 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/apps/web/src/components/learner/AccessibilityStyles.test.ts b/apps/web/src/components/learner/AccessibilityStyles.test.ts
index 0b231d00..7e63ab00 100644
--- a/apps/web/src/components/learner/AccessibilityStyles.test.ts
+++ b/apps/web/src/components/learner/AccessibilityStyles.test.ts
@@ -14,6 +14,7 @@ const theaterStyles = stylesheet("./VerifiedBeliefBreakTheater.module.css");
const deferredStyles = stylesheet("./DeferredVerifiedBeliefBreak.module.css");
const judgeStyles = stylesheet("../../features/judge/JudgeModeView.module.css");
const globalStyles = stylesheet("../../styles.css");
+const studioStyles = stylesheet("../../styles/studio.css");
const questionComposerStyles = stylesheet("./QuestionComposer.module.css");
function colorToken(name: string): string {
@@ -125,6 +126,18 @@ describe("learner-facing responsive style safeguards", () => {
expect(globalStyles).toContain("text-decoration-color: var(--focus-ring)");
});
+ it("stacks proof authority state below its handle label on narrow screens", () => {
+ expect(studioStyles).toContain(
+ ".proof-console-handle {\n display: grid;\n min-height: 76px;\n grid-template-columns: minmax(0, 1fr) auto;",
+ );
+ expect(studioStyles).toContain(
+ ".proof-console-context {\n grid-column: 1 / -1;\n flex-wrap: wrap;",
+ );
+ expect(studioStyles).toContain(
+ ".proof-console-handle > strong {\n display: none;",
+ );
+ });
+
it("keeps the composer dominant in one centered responsive column", () => {
expect(globalStyles).not.toContain(
"grid-template-columns: minmax(520px, 1.08fr) minmax(480px, 0.92fr)",
diff --git a/apps/web/src/styles/studio.css b/apps/web/src/styles/studio.css
index 4634dff1..23b80167 100644
--- a/apps/web/src/styles/studio.css
+++ b/apps/web/src/styles/studio.css
@@ -924,7 +924,7 @@ kbd {
}
.studio-frame.proof-open .studio-canvas {
- padding-bottom: calc(342px + env(safe-area-inset-bottom));
+ padding-bottom: calc(382px + env(safe-area-inset-bottom));
}
.console-events li {
@@ -936,7 +936,42 @@ kbd {
}
.proof-console-handle {
+ display: grid;
+ min-height: 76px;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 6px 10px;
padding-inline: 11px;
+ text-align: left;
+ }
+
+ .proof-console-handle > span:first-child {
+ min-width: 0;
+ }
+
+ .proof-console-context {
+ grid-column: 1 / -1;
+ flex-wrap: wrap;
+ gap: 4px;
+ }
+
+ .proof-console-context em {
+ min-height: 22px;
+ padding: 3px 6px;
+ font-size: 11px;
+ }
+
+ .proof-console-handle > strong {
+ display: none;
+ }
+
+ .proof-console-handle kbd {
+ grid-column: 2;
+ grid-row: 1;
+ margin-left: 0;
+ }
+
+ .studio-mobile-command {
+ bottom: calc(86px + env(safe-area-inset-bottom));
}
.proof-console-content {
From 25e42b198423802fa1ecf7fbd3a5447021bd3c69 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 21:29:55 +0530
Subject: [PATCH 124/143] docs(progress): record first-fold qualification work
---
docs/PROGRESS.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 77 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 635c1e32..4ed43d2d 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4754,3 +4754,80 @@ exact-public matrix that the available release tuple permits. The separate
clean-source, genuine-containment, exact-image, Cloudflare-authentication,
deployment, submission, push, and merge gates remain unchanged and red. No
deployment or publication claim is made.
+
+## First-fold authority and proof-state checkpoint — 2026-07-21T15:51:01Z
+
+Deadline: `2026-07-22T00:00:00Z`; **8 hours 8 minutes 59 seconds remained at
+this checkpoint**. Committed HEAD is
+`71f9ef26f5ff1162513050f257c7270eec0ce135` on
+`feat/learner-ux-v6.1`. The earlier FUSE tombstone is no longer present in
+`git status`. The only worktree entry is the preserved untracked
+`counterlab-exact-source-landing-1440x900.png`; it has not been changed,
+staged, hidden, renamed, overwritten, or deleted. The tree is therefore not
+yet a clean source freeze.
+
+### Completed and committed
+
+| Commit | Result | Exact verification |
+| --- | --- | --- |
+| `ce772eb fix(learner): clarify first-fold hierarchy` | Removed mobile prompt overflow, replaced the oversized heading focus box with a visible underline, added first-screen belief-debugger positioning, and raised Boundary payoff contrast. | Focused web tests **109/109 passed**; web TypeScript, Prettier, whitespace, and local CloakBrowser desktop/mobile checks passed. |
+| `ca40ad3 feat(learner): expose first-fold method and authority` | Added the three-stage 20-second method strip, immediate Sample/Judge CTA hierarchy, and truthful GPT-5.6, Runtime Codex, fixed-kernel, and frozen-verifier role strip. The normal learner landing remains value-free before Prediction. | Focused web tests **92/92 passed**; web TypeScript and formatting passed; local CloakBrowser checks at `390x844` and `1440x900` reported zero console warnings. |
+| `12f3cf6 feat(learner): label explanation authority` | Model Duel now distinguishes `Your input`, reviewed fixed Subject Pack framing, and genuine opt-in `AI-suggested draft` output. Sample framing is not mislabelled as a model call. | Component tests **4/4 passed**; web TypeScript, Prettier, and whitespace passed. |
+| `9e07849 feat(learner): clarify verified result direction` | Added non-colour increase/decrease/neutral meaning and a 200 ms verified-only result reveal. Reduced motion removes the reveal, and no timer or pre-verification result path was added. | Component tests **19/19 passed**; selected App integration and accessibility tests passed; web TypeScript, Prettier, and whitespace passed. |
+| `dabc058 fix(learner): align artifact and proof state labels` | Sample, live, and replay artifact copy now matches actual provenance; Sample result disclosure has a distinct CTA; proof chips derive from session/mode/load state; mismatched modes fail closed and withhold evidence and counts. | App, Project Sidebar, and Proof Console tests **87/87 passed**; complete web/Worker TypeScript and formatting passed. |
+| `71f9ef2 feat(learner): show live test authority phases` | Live compile now presents `Generated planning`, `Fixed testing`, and `Verified result` from the real `LAB_COMPILE`/`LAB_RUN` job kind and `result.ready` event. Runtime Codex proposes only; fixed kernels own values; the frozen verifier controls release. | Component plus selected App integration tests **5/5 passed** with **73 skipped** outside the selection; complete web/Worker TypeScript and formatting passed. |
+| `cb25d90 fix(learner): prevent mobile proof bar overlap` | At narrow widths, proof authority chips occupy a dedicated row, the redundant event count is hidden, and the mobile command control clears the taller bar. | Accessibility and Proof Console tests **15/15 passed**. Fresh CloakBrowser at `390x844` measured a `76px` bar, all three chips inside its `351px` context row, document `scrollWidth === clientWidth`, and zero current-page console warnings or errors. |
+
+### Architecture and review evidence
+
+- The Lab Scene audit found that the current verified renderer must not be
+ reused on landing with placeholder provenance. The safe release path keeps
+ the value-free `LockedBeliefBreakPreview`, uses real job state for compile
+ phases, and leaves `VerifiedLabScenePanel` behind the fixed-result gate.
+- The repository-local code-review graph is current at `71f9ef2`: **543
+ files, 5,725 nodes, 97,052 edges, and six languages**. The incremental
+ review covered 31 changed files and 64 changed functions/classes with a
+ **0.60** overall risk score.
+- Read-only local CloakBrowser QA passed landing and Judge Mode at `390x844`
+ and `1440x900`, How proof works navigation/focus, keyboard focus, and the
+ fixed Sample path through sealed Prediction and verified reveal. No landing
+ `98.5%` or `59.4%` value leak appeared; observed non-static requests returned
+ `200` or `201`; current-page console warnings and errors were zero. The
+ initial mobile proof-bar collision was repaired and reverified in `cb25d90`.
+ These checks remain local and non-qualifying.
+- `gpt-realtime-2.1` and `gpt-realtime-whisper` remain deferred. Adding
+ browser audio would require a new ephemeral-secret endpoint, consent,
+ privacy, WebRTC, accessibility, and failure-mode work; it is outside the
+ release-critical voice cut and cannot replace GPT-5.6 Responses authority.
+
+### Active dependency chain
+
+1. Complete the current local CloakBrowser regression on this exact HEAD and
+ repair any rendered failure. Local evidence is not public qualification.
+2. Resolve the preserved untracked screenshot without deleting or
+ overwriting it implicitly, then freeze one genuinely clean source commit
+ `S`.
+3. Run the genuine aggregate-containment sentinel only if its required cgroup
+ and owned-process observations are lawful under the repository
+ constitution. No pass is currently claimed.
+4. Build one exact runner image for `S`; regenerate source/image-bound SBOM,
+ VEX, scientific-engine, license, health, held-out, mutation, and
+ qualification evidence; review and commit the evidence as `E`.
+5. Reverify Wrangler identity, release only the qualified tuple, expose exact
+ source/image/Worker identity publicly, and run public health and production
+ smoke.
+6. Execute the full public CloakBrowser sample, replay, supported live,
+ unsupported, malformed, refusal, transfer fail/pass, patch, Capsule,
+ refresh/reconnect, history, deep-link, accessibility, console/network, and
+ Web Vitals matrix.
+7. Keep learner evidence `NO_DATA` unless real consented participants are
+ observed. Complete the exact-release video, public repository and link
+ checks, `/feedback` Session ID, Devpost fields, submission receipt, and
+ public slug without fabricating any missing item.
+8. Push the reviewed feature branch, then merge and push `main` only after the
+ exact public release and submission evidence are factual.
+
+No new live model call, runner image, aggregate-containment pass, refreshed
+release evidence, Cloudflare deployment, public journey, learner outcome,
+Devpost submission, push, or merge is claimed at this checkpoint. Release
+remains **NO-GO** until those gates close.
From 0d2fa32f045cd47fb8ba17ad6f6717a8c65102d2 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 21:30:25 +0530
Subject: [PATCH 125/143] docs(readme): explain learner authority and mode
claims
---
README.md | 63 ++++++++++++++++++++++++++++++------
docs/SUBMISSION_PACKAGE.json | 2 +-
2 files changed, 54 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index 70f9d9c4..5c65e455 100644
--- a/README.md
+++ b/README.md
@@ -14,9 +14,18 @@ copilot or an unrestricted code runner.
**Live judge surface:**
The v6.1 learner UX is implemented locally on
-`feat/learner-ux-v6.1`; it has not been pushed, deployed, or browser-qualified.
-The public URL represents its separately recorded deployment and must not be
-treated as running this branch.
+`feat/learner-ux-v6.1` and has current local CloakBrowser evidence. It has not
+yet been pushed, deployed, or qualified as one exact public source/image/Worker
+tuple. The public URL represents its separately recorded historical deployment
+and must not be treated as running this branch.
+
+The first screen states the operating contract directly: CounterLab is a
+**belief debugger, not a tutor or notebook linter**. Its visible sequence is
+`State claim → lock Prediction → controlled test runs → result passes
+verification`. GPT-5.6 proposes a bounded belief frame, Runtime Codex compiles
+an allowlisted experiment plan, fixed kernels compute values, and the frozen
+verifier decides whether evidence may be released. No live call begins on the
+landing screen.
## Learner journey
@@ -24,7 +33,8 @@ CounterLab presents one six-stage path while preserving the existing scientific
state machine and authority gates:
1. **Question** — state a claim or attach a supported notebook. Sample prompts
- are optional, and Judge Mode remains a secondary route.
+ are optional. **Start verified sample** and **Judge Mode** are the first
+ visible cold-user paths; claim testing remains available immediately below.
2. **Prediction** — review exact notebook evidence and the sanitized packet,
confirm that the two-model comparison captures the learner's view, then seal
an immutable categorical prediction and confidence.
@@ -48,11 +58,11 @@ local verification record is in
## Judge Mode
-| Path | Mode | Meaning | Secrets required |
-| --------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
-| Verified sample | `sample` | Bundled approved leakage lesson evidence; clearly labelled, credential-free, and separate from live authority | No |
-| Live notebook | `live_notebook` | Artifact-specific GPT analyst plus the authenticated Container runner, typed bounded artifacts, fixed scorer/kernel, frozen verifier, and copied-notebook patch | Server-side model and runner configuration |
-| Verified replay | `replay` | Read-only reconstruction of stored events and payloads; persistently labelled and unable to make a new model call | No |
+| Path | Session mode | GPT-5.6 / Runtime Codex | Numerical source | Mutability | May claim | Must not claim |
+| --------------- | ----------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
+| Verified sample | `sample_lesson` | No live call; reviewed Subject Pack framing | Integrity-checked bundled fixed-kernel fixture | Learner inputs may advance a new Sample session; the verified fixture is immutable | The scoped fixed Sample demonstrates the recorded mechanism | A live run, artifact-specific analysis, or newly calculated browser result |
+| Live notebook | `live_notebook` | Server-side calls may occur only after supported upload, packet approval, and capability admission | Fixed scorer, kernel, and frozen verifier bound to that artifact | Explicit persisted state transitions; original upload remains read-only and patching targets a copy | The bounded artifact-specific result and provenance that actually occurred | Authority when capability is absent, an unsupported notebook result, or a Sample/Replay fallback |
+| Verified replay | `verified_replay` | No new model or compiler call | Stored validated events and signed or integrity-bound payloads | Read-only | A reconstruction of the specific recorded verified session | A current rerun, a new configuration, or live capability |
Sample is not live and cannot enter a live session. Replay does not make a new
model call or borrow sample authority. The rejected compiler run authorized no
@@ -62,6 +72,14 @@ backed by a versioned, precomputed fixed-kernel fixture. It does not add an
arbitrary sample configuration matrix, and the browser never computes a
substitute authoritative result.
+Visible source labels preserve that separation: learner-authored framing is
+`Your input`; reviewed fixed framing is `Reviewed Subject Pack draft`;
+`AI-suggested draft` appears only with genuine model provenance. During a live
+test, `Generated planning`, `Fixed testing`, and `Verified result` are derived
+from the real runner job kind and result-ready evidence. Artifact surfaces say
+`Bundled sample artifact`, `Uploaded notebook`, or `Replay artifact`; a
+route/session mode mismatch withholds proof instead of guessing.
+
## Supported notebook contract
CounterLab accepts `nbformat` 4 `.ipynb` files up to 10 MiB containing the
@@ -152,6 +170,28 @@ The complete authority matrix is in
## Architecture
+Evidence release follows one bounded authority chain:
+
+```text
+Question / supported artifact
+ │
+ ▼
+sanitized packet ──► GPT-5.6 proposal
+ │
+ ▼
+Runtime Codex typed plan ──► fixed candidate scorer
+ │
+ ▼
+fixed kernel result ──► frozen verifier
+ │
+ ▼
+verified UI + Boundary Map + Proof Capsule
+```
+
+The proposal and presentation layers never supply numerical truth. A result
+cannot cross the final boundary until the learner has sealed a Prediction and
+the fixed evidence has passed verification.
+
```text
Vite React Studio
│ typed requests + reconnectable public events
@@ -296,7 +336,10 @@ Map, fixed transfer, patch lock, copied-notebook repair, Reasoning Diff v2,
Proof Capsule v2, resumable six-stage learner navigation, and privacy-safe
interaction evidence. Historical production evidence separately covers the
legacy sample/replay and hosted leakage/imbalance paths identified above. The
-v6.1 CloakBrowser journey is specified but has not been executed.
+v6.1 landing, Judge, proof-navigation, keyboard, and fixed-Sample journeys have
+been executed locally with CloakBrowser at mobile and desktop sizes. Those
+mutable-development checks are non-qualifying; the full matrix against one
+exact public release is still pending.
Failed honestly: the first live Codex run used an unsupported SDK argument; two
repairs then left an unexpected `__pycache__`, so that run remained rejected and
diff --git a/docs/SUBMISSION_PACKAGE.json b/docs/SUBMISSION_PACKAGE.json
index 37905774..f72b6482 100644
--- a/docs/SUBMISSION_PACKAGE.json
+++ b/docs/SUBMISSION_PACKAGE.json
@@ -19,7 +19,7 @@
},
"readme": {
"path": "README.md",
- "sha256": "bea86d78f23bc7b6263363542d28440471dbb52cd3dbaa7fda80179c41d4852c"
+ "sha256": "b39cd4573e3f1413fcd59fca9e466f43f77488faba582d2f308c22c8ffaba46a"
},
"license": {
"identifier": "MIT",
From f7a1b55fd82189ad9d108f16d9dcb3a627c9e975 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 22:30:09 +0530
Subject: [PATCH 126/143] fix(runtime): preserve address-space limit through
nerdctl
---
scripts/contained-rootless-spec.mjs | 95 ++++++-
scripts/contained-runtime-run.mjs | 35 ++-
scripts/contained-runtime.test.ts | 232 +++++++++++++++++-
scripts/timeout-cleanup-receipt.test.ts | 49 ++++
scripts/timeout-cleanup-receipt.ts | 38 +++
.../src/counterlab_runner/timeout_proof.py | 53 +++-
services/runner/tests/test_timeout_proof.py | 95 +++++--
7 files changed, 567 insertions(+), 30 deletions(-)
diff --git a/scripts/contained-rootless-spec.mjs b/scripts/contained-rootless-spec.mjs
index e6887295..b788184f 100644
--- a/scripts/contained-rootless-spec.mjs
+++ b/scripts/contained-rootless-spec.mjs
@@ -27,6 +27,12 @@ const supportedRlimits = new Set([
"RLIMIT_NOFILE",
"RLIMIT_NPROC",
]);
+const nerdctlCreateRlimits = new Set([
+ "RLIMIT_CPU",
+ "RLIMIT_FSIZE",
+ "RLIMIT_NOFILE",
+ "RLIMIT_NPROC",
+]);
const invocationIdPattern = /^[a-f0-9]{64}$/;
const maskedPaths = new Set([
"/proc/acpi",
@@ -387,6 +393,52 @@ function assertExpectedRlimits(expectedRlimits) {
}
}
+function validateReceiptResourceBindings(receipt) {
+ const aggregate = object(
+ receipt?.intendedAggregateLimits,
+ "receipt aggregate resource intent",
+ );
+ assertKnownKeys(
+ aggregate,
+ new Set(["cpuCount", "maxProcesses", "memoryBytes"]),
+ "receipt aggregate resource fields",
+ );
+ if (
+ Object.keys(aggregate).length !== 3 ||
+ !Number.isFinite(aggregate.cpuCount) ||
+ aggregate.cpuCount < 0.25 ||
+ aggregate.cpuCount > 2 ||
+ !Number.isSafeInteger(aggregate.maxProcesses) ||
+ aggregate.maxProcesses < 1 ||
+ aggregate.maxProcesses > 32 ||
+ !Number.isSafeInteger(aggregate.memoryBytes) ||
+ aggregate.memoryBytes < 64 * 1024 * 1024 ||
+ aggregate.memoryBytes > 1024 * 1024 * 1024
+ ) {
+ throw new Error(
+ "contained rootless OCI receipt resource intent is invalid",
+ );
+ }
+ assertExpectedRlimits(receipt?.enforcedRlimits);
+ const byType = new Map(
+ receipt.enforcedRlimits.map((entry) => [entry.type, entry.soft]),
+ );
+ if (
+ byType.get("RLIMIT_AS") !== aggregate.memoryBytes ||
+ byType.get("RLIMIT_NPROC") !== aggregate.maxProcesses ||
+ byType.get("RLIMIT_NOFILE") !== 64 ||
+ (byType.get("RLIMIT_CPU") ?? 0) < 1 ||
+ (byType.get("RLIMIT_CPU") ?? 0) > 300 ||
+ (byType.get("RLIMIT_FSIZE") ?? 0) < 1 ||
+ (byType.get("RLIMIT_FSIZE") ?? 0) > 1_048_576 ||
+ !Array.isArray(receipt?.normalizedFields) ||
+ !receipt.normalizedFields.includes("process.rlimits")
+ ) {
+ throw new Error("contained rootless OCI receipt rlimit binding changed");
+ }
+ return receipt.enforcedRlimits;
+}
+
function assertResourceIntent(linux, expected) {
const resourceIntent = object(expected, "expected resource intent");
if (
@@ -1045,7 +1097,7 @@ function assertNerdctlAnnotations(parsed, expected, containerId) {
assertNerdctlMountAnnotations(annotations, expected);
}
-function assertProcess(processSpec, expected, hostname) {
+function assertProcess(processSpec, expected, hostname, expectedRlimits) {
assertKnownKeys(
processSpec,
new Set([
@@ -1114,7 +1166,7 @@ function assertProcess(processSpec, expected, hostname) {
);
}
assertEmptyCapabilities(processSpec);
- assertRlimits(processSpec, expected.rlimits);
+ assertRlimits(processSpec, expectedRlimits);
}
function assertPathPolicy(linux) {
@@ -1409,8 +1461,16 @@ export function sanitizeContainedRootlessSpec({
const linux = object(parsed.linux, "Linux section");
const hasNerdctlHooks = assertNerdctlHooks(parsed, expected);
assertFilesystemShape(parsed, expected);
- assertProcess(processSpec, expected, parsed.hostname);
assertResourceIntent(linux, expected);
+ const sourceRlimits = expected.rlimits.filter((entry) =>
+ nerdctlCreateRlimits.has(entry.type),
+ );
+ if (sourceRlimits.length !== nerdctlCreateRlimits.size) {
+ throw new Error(
+ "contained rootless OCI staging rlimit intent is incomplete",
+ );
+ }
+ assertProcess(processSpec, expected, parsed.hostname, sourceRlimits);
assertNamespaces(linux);
const linuxNormalization = assertLinuxSecurity(linux);
@@ -1428,6 +1488,19 @@ export function sanitizeContainedRootlessSpec({
processSpec.terminal = false;
processSpec.user.additionalGids = [];
processSpec.env = [...expected.imageAuthority.process.env];
+ const addressSpaceLimit = expected.rlimits.find(
+ (entry) => entry.type === "RLIMIT_AS",
+ );
+ if (addressSpaceLimit === undefined) {
+ throw new Error("contained rootless OCI address-space limit is missing");
+ }
+ // Preserve nerdctl's four validated limits and add only RLIMIT_AS, which
+ // its Docker-compatible ulimit parser cannot express.
+ processSpec.rlimits = [
+ ...processSpec.rlimits.map((entry) => ({ ...entry })),
+ { ...addressSpaceLimit },
+ ].sort((left, right) => left.type.localeCompare(right.type));
+ assertRlimits(processSpec, expected.rlimits);
processSpec.capabilities = {
ambient: [],
bounding: [],
@@ -1535,6 +1608,7 @@ export function sanitizeContainedRootlessSpec({
"process.terminal",
"process.user.additionalGids",
"process.env.HOSTNAME",
+ "process.rlimits",
"process.capabilities",
"root.path",
],
@@ -1895,6 +1969,19 @@ function assertConfigInternalMountsRemoved(config) {
}
}
+function assertConfigRlimits(config, receipt) {
+ let parsed;
+ try {
+ parsed = object(JSON.parse(config), "persisted config");
+ } catch (error) {
+ throw new Error("contained rootless OCI persisted config is invalid", {
+ cause: error,
+ });
+ }
+ const processSpec = object(parsed.process, "persisted process");
+ assertRlimits(processSpec, validateReceiptResourceBindings(receipt));
+}
+
function assertConfigImageRootfs(config, specRoot, receipt) {
const binding = object(receipt?.imageRootfs, "image rootfs binding");
const imageAuthority = object(receipt?.imageAuthority, "image authority");
@@ -2009,6 +2096,7 @@ export function persistContainedRootlessSpec({
const assetRoot = internalMountAssetRoot(specRoot, receipt.invocationId);
const imageRootfsPath = assertConfigImageRootfs(config, specRoot, receipt);
assertConfigRuntimeAnnotations(config, receipt);
+ assertConfigRlimits(config, receipt);
assertConfigInternalMountsRemoved(config);
mkdirSync(imageRootfsPath, { mode: 0o700 });
assertPrivateDirectory(imageRootfsPath, "image rootfs mount point", 0o700);
@@ -2161,6 +2249,7 @@ export function verifyPersistedContainedRootlessSpec({
}
assertConfigImageRootfs(config.toString("utf8"), specRoot, receipt);
assertConfigRuntimeAnnotations(config.toString("utf8"), receipt);
+ assertConfigRlimits(config.toString("utf8"), receipt);
assertConfigInternalMountsRemoved(config.toString("utf8"));
validateReadOnlyMountManifest(receipt);
assertReceiptFinalIdentity(receipt);
diff --git a/scripts/contained-runtime-run.mjs b/scripts/contained-runtime-run.mjs
index c22aee13..9d55feb0 100644
--- a/scripts/contained-runtime-run.mjs
+++ b/scripts/contained-runtime-run.mjs
@@ -292,6 +292,38 @@ function resourceIntent(args) {
return { cpuCount, maxProcesses, memoryBytes, rlimits };
}
+function nerdctlCreateOptions(args, imageArgumentIndex) {
+ // nerdctl 2.3.1 rejects Docker's `as` spelling. The sanitized OCI spec
+ // injects the already-validated RLIMIT_AS before direct ctr execution.
+ const options = [];
+ let omittedAddressSpaceLimit = 0;
+ for (let index = 1; index < imageArgumentIndex; index += 1) {
+ const argument = args[index];
+ if (argument === "--ulimit") {
+ const value = args[index + 1];
+ if (typeof value !== "string" || index + 1 >= imageArgumentIndex) {
+ throw new Error("contained runtime ulimit option is invalid");
+ }
+ if (value.startsWith("as=")) {
+ omittedAddressSpaceLimit += 1;
+ } else {
+ options.push(argument, value);
+ }
+ index += 1;
+ continue;
+ }
+ if (argument.startsWith("--ulimit=as=")) {
+ omittedAddressSpaceLimit += 1;
+ continue;
+ }
+ options.push(argument);
+ }
+ if (omittedAddressSpaceLimit !== 1) {
+ throw new Error("contained runtime address-space limit is invalid");
+ }
+ return options;
+}
+
function contained(parent, candidate) {
const fromParent = relative(parent, candidate);
return (
@@ -380,6 +412,7 @@ export function containedRunPlan({
containerName,
sessionRoot,
};
+ const createOptions = nerdctlCreateOptions(args, commandImageIndex);
const ctrGlobalArgs = [
"--address",
containerdSocket,
@@ -465,7 +498,7 @@ export function containedRunPlan({
args: [
...nerdctlGlobalArgs,
"create",
- ...args.slice(1, commandImageIndex),
+ ...createOptions,
"--label",
invocationLabel,
imageAlias,
diff --git a/scripts/contained-runtime.test.ts b/scripts/contained-runtime.test.ts
index ca5c8efb..9db360b3 100644
--- a/scripts/contained-runtime.test.ts
+++ b/scripts/contained-runtime.test.ts
@@ -588,7 +588,9 @@ function rootlessSpec(
env: [...authority.process.env, `HOSTNAME=${containerId.slice(0, 12)}`],
cwd: authority.process.cwd,
capabilities: {},
- rlimits: expected.rlimits,
+ // Model the exact raw nerdctl spec; RLIMIT_AS is injected only after
+ // this staging document passes the contained-runtime policy.
+ rlimits: expected.rlimits.filter((entry) => entry.type !== "RLIMIT_AS"),
noNewPrivileges: true,
},
root: { path: "rootfs", readonly: true },
@@ -1527,6 +1529,19 @@ describe("contained runtime command policy", () => {
expect(plan.create.args).toContain("create");
expect(plan.create.args).not.toContain("run");
+ expect(plan.create.args).not.toContain("--ulimit=as=1073741824:1073741824");
+ expect(
+ plan.create.args.filter((argument) => argument.startsWith("--ulimit=")),
+ ).toHaveLength(4);
+ const createIndex = plan.create.args.indexOf("create");
+ const invocationLabelIndex = plan.create.args.indexOf("--label");
+ expect(
+ plan.create.args.slice(createIndex + 1, invocationLabelIndex),
+ ).toEqual(
+ startupCommand()
+ .slice(1, -1)
+ .filter((argument) => argument !== "--ulimit=as=1073741824:1073741824"),
+ );
expect(plan.start.program).toBe(resolve(installRoot, "bin/ctr"));
expect(plan.start.args).toEqual(
expect.arrayContaining([
@@ -1571,6 +1586,76 @@ describe("contained runtime command policy", () => {
expect(plan.cleanupStaging.args.slice(-2)).toEqual(["rm", "--force"]);
expect(plan.cleanupImageAlias.args.slice(-2)).toEqual(["images", "remove"]);
expect(plan.expected.rlimits).toHaveLength(5);
+ const fullCommandAuthority = imageFixture(startupCommand()).authority;
+ const fullCommandSha256 = createHash("sha256")
+ .update(canonicalJson(startupCommand()))
+ .digest("hex");
+ const stagingCommandSha256 = createHash("sha256")
+ .update(
+ canonicalJson(
+ startupCommand().filter(
+ (argument) => argument !== "--ulimit=as=1073741824:1073741824",
+ ),
+ ),
+ )
+ .digest("hex");
+ expect(fullCommandAuthority.commandSha256).toBe(fullCommandSha256);
+ expect(fullCommandAuthority.commandSha256).not.toBe(stagingCommandSha256);
+ const splitUlimitCommand = startupCommand();
+ const addressSpaceIndex = splitUlimitCommand.indexOf(
+ "--ulimit=as=1073741824:1073741824",
+ );
+ splitUlimitCommand.splice(
+ addressSpaceIndex,
+ 1,
+ "--ulimit",
+ "as=1073741824:1073741824",
+ );
+ const splitUlimitPlan = containedRunPlan({
+ args: splitUlimitCommand,
+ binRoot: resolve(installRoot, "bin"),
+ clientFifoRoot,
+ containerdSocket: resolve(sessionRoot, "run/containerd.sock"),
+ installRoot,
+ invocationId,
+ sessionRoot,
+ });
+ expect(splitUlimitPlan.create.args).not.toContain(
+ "as=1073741824:1073741824",
+ );
+ expect(splitUlimitPlan.expected.rlimits).toHaveLength(5);
+ const invalidAddressSpaceCommands = [
+ startupCommand().filter(
+ (argument) => argument !== "--ulimit=as=1073741824:1073741824",
+ ),
+ startupCommand().map((argument) =>
+ argument === "--ulimit=as=1073741824:1073741824"
+ ? "--ulimit=as=536870912:536870912"
+ : argument,
+ ),
+ (() => {
+ const command = startupCommand();
+ command.splice(
+ command.length - 1,
+ 0,
+ "--ulimit=as=1073741824:1073741824",
+ );
+ return command;
+ })(),
+ ];
+ for (const args of invalidAddressSpaceCommands) {
+ expect(() =>
+ containedRunPlan({
+ args,
+ binRoot: resolve(installRoot, "bin"),
+ clientFifoRoot,
+ containerdSocket: resolve(sessionRoot, "run/containerd.sock"),
+ installRoot,
+ invocationId,
+ sessionRoot,
+ }),
+ ).toThrow(/resource intent|rlimit intent/u);
+ }
expect(CONTAINED_RUNTIME_CONTROL_BUDGET_SECONDS).toBe(420);
expect(CONTAINED_RUNTIME_CALLER_GRACE_SECONDS).toBe(5);
expect(JSON.stringify(plan)).not.toContain("/run/containerd/fifo");
@@ -1656,7 +1741,11 @@ describe("contained runtime command policy", () => {
expect(prepared.receipt.configFileSha256).toBe(
createHash("sha256").update(prepared.config).digest("hex"),
);
- expect(sanitized.process.rlimits).toEqual(plan.expected.rlimits);
+ expect(sanitized.process.rlimits).toEqual(
+ [...plan.expected.rlimits].sort((left, right) =>
+ left.type.localeCompare(right.type),
+ ),
+ );
expect(sanitized.process.noNewPrivileges).toBe(true);
expect(sanitized.process.terminal).toBe(false);
expect(sanitized.process.user.additionalGids).toEqual([]);
@@ -1734,6 +1823,7 @@ describe("contained runtime command policy", () => {
removedMounts: ["/etc/hostname", "/etc/hosts", "/etc/resolv.conf"],
});
expect(prepared.receipt.normalizedFields).toContain("linux.cgroupsPath");
+ expect(prepared.receipt.normalizedFields).toContain("process.rlimits");
const resourceMutations: Array<(spec: typeof original) => void> = [
(spec) => {
@@ -1771,6 +1861,40 @@ describe("contained runtime command policy", () => {
).toThrow(/cgroup path|resource/u);
}
+ const rlimitMutations: Array<(spec: typeof original) => void> = [
+ (spec) => {
+ spec.process.rlimits.push({
+ type: "RLIMIT_AS",
+ soft: plan.expected.memoryBytes,
+ hard: plan.expected.memoryBytes,
+ });
+ },
+ (spec) => {
+ spec.process.rlimits[0].soft -= 1;
+ },
+ (spec) => {
+ spec.process.rlimits.pop();
+ },
+ (spec) => {
+ spec.process.rlimits.push(structuredClone(spec.process.rlimits[0]));
+ },
+ (spec) => {
+ spec.process.rlimits.push({ type: "RLIMIT_RSS", soft: 1, hard: 1 });
+ },
+ ];
+ for (const mutate of rlimitMutations) {
+ const changed = structuredClone(original);
+ mutate(changed);
+ expect(() =>
+ sanitizeContainedRootlessSpec({
+ containerId,
+ expected,
+ metadataSha256: "3".repeat(64),
+ source: JSON.stringify(changed),
+ }),
+ ).toThrow(/rlimit/u);
+ }
+
const missingHookSource = JSON.parse(source);
missingHookSource.hooks = null;
expect(() =>
@@ -2499,6 +2623,110 @@ describe("contained runtime command policy", () => {
metadataSha256: "9".repeat(64),
source: rootlessSpec("8".repeat(64), plan.expected, fixture.authority),
});
+ type MutableResourceReceipt = typeof prepared.receipt & {
+ baseSpecSha256: string;
+ commandSha256: string;
+ configFileSha256: string;
+ enforcedRlimits: Array<{ type: string; soft: number; hard: number }>;
+ finalContainerId: string;
+ internalMountManifestSha256: string;
+ intendedAggregateLimits: {
+ cpuCount: number;
+ maxProcesses: number;
+ memoryBytes: number;
+ };
+ invocationId: string;
+ normalizedFields: string[];
+ readOnlyMountManifestSha256: string;
+ receiptPayloadSha256: string;
+ sanitizedSpecSha256: string;
+ stagingContainerId: string;
+ };
+ const receiptMutations: Array<(receipt: MutableResourceReceipt) => void> = [
+ (receipt) => {
+ const addressSpace = receipt.enforcedRlimits.find(
+ (entry) => entry.type === "RLIMIT_AS",
+ );
+ if (addressSpace === undefined) throw new Error("missing test rlimit");
+ addressSpace.soft -= 1;
+ addressSpace.hard -= 1;
+ },
+ (receipt) => {
+ receipt.intendedAggregateLimits.memoryBytes -= 1;
+ },
+ (receipt) => {
+ receipt.normalizedFields = receipt.normalizedFields.filter(
+ (field) => field !== "process.rlimits",
+ );
+ },
+ ];
+ for (const mutate of receiptMutations) {
+ const invalid = structuredClone(
+ prepared.receipt,
+ ) as MutableResourceReceipt;
+ mutate(invalid);
+ expect(() =>
+ persistContainedRootlessSpec({
+ config: prepared.config,
+ finalContainerId: prepared.finalContainerId,
+ internalMounts: prepared.internalMounts,
+ receipt: invalid,
+ sessionRoot,
+ }),
+ ).toThrow(/resource|rlimit/u);
+ }
+ const semanticallyChangedConfig = JSON.parse(prepared.config) as {
+ annotations: Record;
+ process: {
+ rlimits: Array<{ type: string; soft: number; hard: number }>;
+ };
+ } & Record;
+ const changedAddressSpace = semanticallyChangedConfig.process.rlimits.find(
+ (entry) => entry.type === "RLIMIT_AS",
+ );
+ if (changedAddressSpace === undefined)
+ throw new Error("missing test rlimit");
+ changedAddressSpace.soft -= 1;
+ changedAddressSpace.hard -= 1;
+ const { annotations: _annotations, ...changedBaseConfig } =
+ semanticallyChangedConfig;
+ const changedBaseSpecSha256 = createHash("sha256")
+ .update(canonicalJson(changedBaseConfig))
+ .digest("hex");
+ semanticallyChangedConfig.annotations[
+ "io.counterlab.runtime.base-spec-sha256"
+ ] = changedBaseSpecSha256;
+ const changedSanitizedSpecSha256 = createHash("sha256")
+ .update(canonicalJson(semanticallyChangedConfig))
+ .digest("hex");
+ const changedConfigSource = `${JSON.stringify(semanticallyChangedConfig, null, 2)}\n`;
+ const changedReceipt = structuredClone(
+ prepared.receipt,
+ ) as MutableResourceReceipt;
+ changedReceipt.baseSpecSha256 = changedBaseSpecSha256;
+ changedReceipt.sanitizedSpecSha256 = changedSanitizedSpecSha256;
+ changedReceipt.configFileSha256 = createHash("sha256")
+ .update(changedConfigSource)
+ .digest("hex");
+ changedReceipt.finalContainerId = createHash("sha256")
+ .update(
+ `counterlab-rootless-v4\0${changedReceipt.invocationId}\0${changedReceipt.stagingContainerId}\0${changedReceipt.sanitizedSpecSha256}\0${changedReceipt.commandSha256}\0${changedReceipt.internalMountManifestSha256}\0${changedReceipt.readOnlyMountManifestSha256}`,
+ )
+ .digest("hex");
+ const { receiptPayloadSha256: _receiptHash, ...changedReceiptPayload } =
+ changedReceipt;
+ changedReceipt.receiptPayloadSha256 = createHash("sha256")
+ .update(canonicalJson(changedReceiptPayload))
+ .digest("hex");
+ expect(() =>
+ persistContainedRootlessSpec({
+ config: changedConfigSource,
+ finalContainerId: changedReceipt.finalContainerId,
+ internalMounts: prepared.internalMounts,
+ receipt: changedReceipt,
+ sessionRoot,
+ }),
+ ).toThrow(/rlimit/u);
const persisted = persistContainedRootlessSpec({
config: prepared.config,
finalContainerId: prepared.finalContainerId,
diff --git a/scripts/timeout-cleanup-receipt.test.ts b/scripts/timeout-cleanup-receipt.test.ts
index 958329d8..d614f05f 100644
--- a/scripts/timeout-cleanup-receipt.test.ts
+++ b/scripts/timeout-cleanup-receipt.test.ts
@@ -8,6 +8,7 @@ import {
QUALIFIED_AGGREGATE_LIMIT_MODE,
TIMEOUT_ROOTLESS_RLIMIT_TYPES,
assertQualifiedAggregateRuntimeLimits,
+ assertRootlessRlimitBindings,
} from "./timeout-cleanup-receipt.js";
const invocationId = "1".repeat(64);
@@ -111,6 +112,54 @@ describe("timeout cleanup aggregate resource authority", () => {
expect(TIMEOUT_ROOTLESS_RLIMIT_TYPES).not.toContain("RLIMIT_CORE");
});
+ it("cross-binds process limits to aggregate memory and process intent", () => {
+ const enforcedRlimits = [
+ {
+ type: "RLIMIT_AS" as const,
+ soft: intendedAggregateLimits.memoryBytes,
+ hard: intendedAggregateLimits.memoryBytes,
+ },
+ { type: "RLIMIT_CPU" as const, soft: 20, hard: 20 },
+ { type: "RLIMIT_FSIZE" as const, soft: 262_144, hard: 262_144 },
+ { type: "RLIMIT_NOFILE" as const, soft: 64, hard: 64 },
+ {
+ type: "RLIMIT_NPROC" as const,
+ soft: intendedAggregateLimits.maxProcesses,
+ hard: intendedAggregateLimits.maxProcesses,
+ },
+ ];
+ expect(() =>
+ assertRootlessRlimitBindings({
+ intendedAggregateLimits,
+ enforcedRlimits,
+ }),
+ ).not.toThrow();
+
+ for (const mutate of [
+ (limits: typeof enforcedRlimits) => {
+ const limit = limits.find((entry) => entry.type === "RLIMIT_AS");
+ if (limit === undefined) throw new Error("missing test rlimit");
+ limit.soft -= 1;
+ limit.hard -= 1;
+ },
+ (limits: typeof enforcedRlimits) => {
+ const limit = limits.find((entry) => entry.type === "RLIMIT_NPROC");
+ if (limit === undefined) throw new Error("missing test rlimit");
+ limit.soft -= 1;
+ limit.hard -= 1;
+ },
+ ]) {
+ const changed = structuredClone(enforcedRlimits);
+ mutate(changed);
+ expect(() =>
+ assertRootlessRlimitBindings({
+ intendedAggregateLimits,
+ enforcedRlimits: changed,
+ }),
+ ).toThrow(/bind aggregate intent/u);
+ }
+ });
+
it("rejects process-only or merely declared aggregate limits", () => {
expect(() =>
assertQualifiedAggregateRuntimeLimits({
diff --git a/scripts/timeout-cleanup-receipt.ts b/scripts/timeout-cleanup-receipt.ts
index 8a4e7a6c..65e1a0f3 100644
--- a/scripts/timeout-cleanup-receipt.ts
+++ b/scripts/timeout-cleanup-receipt.ts
@@ -183,6 +183,35 @@ const RlimitSchema = z
message: "process rlimit soft and hard values must match",
});
+export function assertRootlessRlimitBindings(input: {
+ intendedAggregateLimits: unknown;
+ enforcedRlimits: unknown;
+}): void {
+ const intent = AggregateLimitIntentSchema.parse(
+ input.intendedAggregateLimits,
+ );
+ const limits = z
+ .array(RlimitSchema)
+ .length(5)
+ .refine(
+ (entries) => new Set(entries.map((entry) => entry.type)).size === 5,
+ { message: "all qualified process rlimits must be present once" },
+ )
+ .parse(input.enforcedRlimits);
+ const byType = new Map(limits.map((limit) => [limit.type, limit.soft]));
+ if (
+ byType.get("RLIMIT_AS") !== intent.memoryBytes ||
+ byType.get("RLIMIT_NPROC") !== intent.maxProcesses ||
+ byType.get("RLIMIT_NOFILE") !== 64 ||
+ (byType.get("RLIMIT_CPU") ?? 0) < 1 ||
+ (byType.get("RLIMIT_CPU") ?? 0) > 300 ||
+ (byType.get("RLIMIT_FSIZE") ?? 0) < 1 ||
+ (byType.get("RLIMIT_FSIZE") ?? 0) > 1_048_576
+ ) {
+ throw new Error("qualified process rlimits do not bind aggregate intent");
+ }
+}
+
const TimeoutRootlessReceiptSchema = z
.strictObject({
schemaVersion: z.literal("4"),
@@ -241,6 +270,15 @@ const TimeoutRootlessReceiptSchema = z
"aggregate enforcement claims require observed aggregate evidence",
});
}
+ try {
+ assertRootlessRlimitBindings(receipt);
+ } catch {
+ context.addIssue({
+ code: "custom",
+ path: ["enforcedRlimits"],
+ message: "qualified process rlimits do not bind aggregate intent",
+ });
+ }
});
export type TimeoutProofExpected = {
diff --git a/services/runner/src/counterlab_runner/timeout_proof.py b/services/runner/src/counterlab_runner/timeout_proof.py
index a3d55aa8..c50f4768 100644
--- a/services/runner/src/counterlab_runner/timeout_proof.py
+++ b/services/runner/src/counterlab_runner/timeout_proof.py
@@ -2,6 +2,7 @@
import hashlib
import json
+import math
import os
import re
import subprocess
@@ -150,8 +151,16 @@
"readOnlyMountsUnchanged",
"imageRootfsUnchanged",
)
-_TIMEOUT_PUBLIC_TEST = """import time
+def _timeout_public_test(address_space_bytes: int) -> str:
+ if not _safe_integer(address_space_bytes, positive=True):
+ raise RuntimeError("timeout address-space expectation is invalid")
+ return f"""import resource
+import time
+expected = ({address_space_bytes}, {address_space_bytes})
+observed = resource.getrlimit(resource.RLIMIT_AS)
+if observed != expected:
+ raise AssertionError(f"address-space limit mismatch: {{observed!r}}")
time.sleep(5)
raise AssertionError("timeout cleanup sentinel unexpectedly survived")
"""
@@ -434,13 +443,15 @@ def validate_rootless_receipt(
or authority.get("sourceTreeSha256") != build["sourceTreeSha256"]
or authority.get("configDigest") != build["adapterImageDigest"]
or authority.get("manifestDigest") != build["adapterManifestDigest"]
- or not _validate_enforced_rlimits(value.get("enforcedRlimits"))
+ or not _validate_enforced_rlimits(
+ value.get("enforcedRlimits"), value.get("intendedAggregateLimits")
+ )
):
raise RuntimeError("rootless receipt authority is invalid")
return value
-def _validate_enforced_rlimits(value: object) -> bool:
+def _validate_enforced_rlimits(value: object, intended: object) -> bool:
required = {
"RLIMIT_AS",
"RLIMIT_CPU",
@@ -448,9 +459,25 @@ def _validate_enforced_rlimits(value: object) -> bool:
"RLIMIT_NOFILE",
"RLIMIT_NPROC",
}
- if not isinstance(value, list) or len(value) != len(required):
+ if (
+ not isinstance(intended, dict)
+ or set(intended) != {"cpuCount", "maxProcesses", "memoryBytes"}
+ or not isinstance(intended.get("cpuCount"), (int, float))
+ or isinstance(intended.get("cpuCount"), bool)
+ or not math.isfinite(intended["cpuCount"])
+ or not 0.25 <= intended["cpuCount"] <= 2
+ or not _safe_integer(intended.get("maxProcesses"), positive=True)
+ or not 1 <= intended["maxProcesses"] <= 32
+ or not _safe_integer(intended.get("memoryBytes"), positive=True)
+ or not 64 * 1024 * 1024
+ <= intended["memoryBytes"]
+ <= 1024 * 1024 * 1024
+ or not isinstance(value, list)
+ or len(value) != len(required)
+ ):
return False
observed: set[str] = set()
+ by_type: dict[str, int] = {}
for entry in value:
if (
not isinstance(entry, dict)
@@ -462,7 +489,15 @@ def _validate_enforced_rlimits(value: object) -> bool:
):
return False
observed.add(entry["type"])
- return observed == required
+ by_type[entry["type"]] = entry["soft"]
+ return (
+ observed == required
+ and by_type["RLIMIT_AS"] == intended["memoryBytes"]
+ and by_type["RLIMIT_NPROC"] == intended["maxProcesses"]
+ and by_type["RLIMIT_NOFILE"] == 64
+ and 1 <= by_type["RLIMIT_CPU"] <= 300
+ and 1 <= by_type["RLIMIT_FSIZE"] <= 1_048_576
+ )
def _safe_integer(value: object, *, positive: bool = False) -> bool:
@@ -847,6 +882,9 @@ def run_timeout_cleanup_proof(
if not isinstance(limits, dict):
raise RuntimeError("fixed timeout plan has no resource limits")
limits["wallSeconds"] = 1
+ memory_mb = limits.get("memoryMb")
+ if not _safe_integer(memory_mb, positive=True):
+ raise RuntimeError("fixed timeout plan has no memory limit")
_write_new(
workspace / "experiment-plan.json",
f"{json.dumps(plan, indent=2, sort_keys=True)}\n",
@@ -855,7 +893,10 @@ def run_timeout_cleanup_proof(
encoding="utf-8"
)
_write_new(workspace / "artifact-adapter.py", adapter_source)
- _write_new(workspace / "public_tests.py", _TIMEOUT_PUBLIC_TEST)
+ _write_new(
+ workspace / "public_tests.py",
+ _timeout_public_test(memory_mb * 1024 * 1024),
+ )
artifacts = validate_generated_workspace(workspace, generated_root)
run_root = work / "runs"
diff --git a/services/runner/tests/test_timeout_proof.py b/services/runner/tests/test_timeout_proof.py
index 807b4751..954c2b31 100644
--- a/services/runner/tests/test_timeout_proof.py
+++ b/services/runner/tests/test_timeout_proof.py
@@ -171,14 +171,19 @@ def _rootless(control: dict[str, object], build: dict[str, object]) -> dict[str,
"removedMounts": [],
"intendedAggregateLimits": intended_limits,
"enforcedRlimits": [
- {"type": name, "soft": 1, "hard": 1}
- for name in (
- "RLIMIT_AS",
- "RLIMIT_CPU",
- "RLIMIT_FSIZE",
- "RLIMIT_NOFILE",
- "RLIMIT_NPROC",
- )
+ {
+ "type": "RLIMIT_AS",
+ "soft": intended_limits["memoryBytes"],
+ "hard": intended_limits["memoryBytes"],
+ },
+ {"type": "RLIMIT_CPU", "soft": 20, "hard": 20},
+ {"type": "RLIMIT_FSIZE", "soft": 262_144, "hard": 262_144},
+ {"type": "RLIMIT_NOFILE", "soft": 64, "hard": 64},
+ {
+ "type": "RLIMIT_NPROC",
+ "soft": intended_limits["maxProcesses"],
+ "hard": intended_limits["maxProcesses"],
+ },
],
"aggregateLimitEvidence": aggregate_evidence,
}
@@ -263,26 +268,51 @@ def test_control_schema_selects_the_exact_rootless_receipt_name() -> None:
def test_rootless_rlimit_validator_matches_the_runtime_contract() -> None:
+ intended = {
+ "cpuCount": 1,
+ "maxProcesses": 16,
+ "memoryBytes": 512 * 1024 * 1024,
+ }
runtime_limits = [
- {"type": name, "soft": 1, "hard": 1}
- for name in (
- "RLIMIT_AS",
- "RLIMIT_CPU",
- "RLIMIT_FSIZE",
- "RLIMIT_NOFILE",
- "RLIMIT_NPROC",
- )
+ {
+ "type": "RLIMIT_AS",
+ "soft": intended["memoryBytes"],
+ "hard": intended["memoryBytes"],
+ },
+ {"type": "RLIMIT_CPU", "soft": 20, "hard": 20},
+ {"type": "RLIMIT_FSIZE", "soft": 262_144, "hard": 262_144},
+ {"type": "RLIMIT_NOFILE", "soft": 64, "hard": 64},
+ {
+ "type": "RLIMIT_NPROC",
+ "soft": intended["maxProcesses"],
+ "hard": intended["maxProcesses"],
+ },
]
- assert timeout_proof_module._validate_enforced_rlimits(runtime_limits)
+ assert timeout_proof_module._validate_enforced_rlimits(runtime_limits, intended)
assert not timeout_proof_module._validate_enforced_rlimits(
[
{**entry, "type": "RLIMIT_CORE"}
if entry["type"] == "RLIMIT_NOFILE"
else entry
for entry in runtime_limits
- ]
+ ],
+ intended,
)
+ for limit_type in ("RLIMIT_AS", "RLIMIT_NPROC"):
+ changed = json.loads(json.dumps(runtime_limits))
+ entry = next(item for item in changed if item["type"] == limit_type)
+ entry["soft"] -= 1
+ entry["hard"] -= 1
+ assert not timeout_proof_module._validate_enforced_rlimits(changed, intended)
+
+
+def test_timeout_candidate_observes_the_exact_address_space_limit() -> None:
+ source = timeout_proof_module._timeout_public_test(512 * 1024 * 1024)
+ assert "resource.getrlimit(resource.RLIMIT_AS)" in source
+ assert "expected = (536870912, 536870912)" in source
+ with pytest.raises(RuntimeError, match="address-space"):
+ timeout_proof_module._timeout_public_test(0)
def test_rootless_receipt_binds_control_and_exact_adapter_authority() -> None:
@@ -296,6 +326,35 @@ def test_rootless_receipt_binds_control_and_exact_adapter_authority() -> None:
rootless = _rootless(control, build)
assert validate_rootless_receipt(rootless, control=control, build=build) == rootless
+ for limit_type in ("RLIMIT_AS", "RLIMIT_NPROC"):
+ changed_control = _control()
+ changed = _rootless(changed_control, build)
+ limit = next(
+ entry
+ for entry in changed["enforcedRlimits"]
+ if entry["type"] == limit_type
+ )
+ limit["soft"] -= 1
+ limit["hard"] -= 1
+ changed_payload = {
+ key: value
+ for key, value in changed.items()
+ if key != "receiptPayloadSha256"
+ }
+ changed["receiptPayloadSha256"] = _hash(changed_payload)
+ changed_control["rootlessReceiptPayloadSha256"] = changed[
+ "receiptPayloadSha256"
+ ]
+ changed_control_payload = {
+ key: value
+ for key, value in changed_control.items()
+ if key != "receiptPayloadSha256"
+ }
+ changed_control["receiptPayloadSha256"] = _hash(changed_control_payload)
+ with pytest.raises(RuntimeError, match="authority"):
+ validate_rootless_receipt(
+ changed, control=changed_control, build=build
+ )
with pytest.raises(RuntimeError, match="authority"):
validate_rootless_receipt(
rootless,
From 3c50ebf1afc293fcac8a053f838e71c564943511 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 22:31:46 +0530
Subject: [PATCH 127/143] docs(progress): record containment repair checkpoint
---
docs/PROGRESS.md | 109 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 109 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 4ed43d2d..ead51f85 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4831,3 +4831,112 @@ No new live model call, runner image, aggregate-containment pass, refreshed
release evidence, Cloudflare deployment, public journey, learner outcome,
Devpost submission, push, or merge is claimed at this checkpoint. Release
remains **NO-GO** until those gates close.
+
+## Exact-image containment repair checkpoint — 2026-07-21T17:00:20Z
+
+Deadline: `2026-07-22T00:00:00Z`; **6 hours 59 minutes 40 seconds remained at
+this checkpoint**. The owner explicitly approved removal of only
+`counterlab-exact-source-landing-1440x900.png`; that file was removed without
+touching any other path. Source commit
+`0d2fa32f045cd47fb8ba17ad6f6717a8c65102d2` was then genuinely clean.
+
+### Historical exact build and failed sentinel
+
+The repository-contained builder produced exact OCI artifacts from clean
+source `0d2fa32…`:
+
+- runner config digest:
+ `sha256:d5de7d3ec73ce73db46ae1bfb1714a8dd532e831011ae27a3232236d03acc37c`;
+- runner manifest digest:
+ `sha256:11b8f06b70dadf33f704057886393969e5f145ca71f14614927d75fe846dd060`;
+- adapter config digest:
+ `sha256:b4f31e258b5841b45c72e600f025c276fa684beb668adee02395ad69ba8c39ec`;
+- adapter manifest digest:
+ `sha256:0c4aacca6b3acc92e2a45e2bde0b313a1019161bbe7cef5b80cf8d7a6a15203d`;
+- build receipt:
+ `node_modules/.cache/counterlab-v6.1/releases/runner-build-0d2fa32f045cd47fb8ba17ad6f6717a8c65102d2.json`.
+
+The owner-authorized genuine timeout/aggregate-containment command was invoked
+once. Pinned nerdctl `2.3.1` rejected Docker ulimit type `as` during staging
+container creation. The attempt failed before OCI inspection and before the
+aggregate qualification coordinator began. Therefore the cgroup observer did
+not start, no `/sys/fs/cgroup` control or CounterLab-owned `/proc//stat`
+was read, no helper was moved into a cgroup, no negative control ran, no result
+was released, and no timeout, control, aggregate-authority, or qualified-runner
+receipt was issued. This is a clean **FAIL**, not a partial containment pass.
+
+### Fail-closed repair committed
+
+Commit `f7a1b55 fix(runtime): preserve address-space limit through nerdctl`
+repairs only the incompatible staging boundary:
+
+- the authoritative request and command hash still contain all five exact
+ process limits;
+- only the unsupported address-space option is omitted from temporary nerdctl
+ staging;
+- the raw staging OCI spec must contain exactly the other four validated
+ limits;
+- CounterLab injects only the prevalidated `RLIMIT_AS` value into the
+ sanitized OCI spec before canonical hashing, persistence, and direct `ctr`
+ execution;
+- persisted config and receipt validation independently cross-bind
+ `RLIMIT_AS` to memory and `RLIMIT_NPROC` to the process limit in JavaScript,
+ TypeScript, and Python;
+- the fixed timeout candidate now reads
+ `resource.getrlimit(resource.RLIMIT_AS)` and fails before sleeping unless the
+ real process limit equals the requested value;
+- malformed, missing, duplicate, changed, and extra source limits fail closed.
+
+Focused verification after the final diff:
+
+- runtime/release Vitest: **7 files, 76 tests passed**;
+- runner Pytest: **44 tests passed** across timeout proof, Docker runner, and
+ host pipeline;
+- root TypeScript: passed;
+- Prettier on all changed JS/TS files: passed;
+- Python compilation: passed;
+- Git whitespace check: passed;
+- secret scan: passed across the seven changed files.
+
+The code-review graph was refreshed at the pre-repair source with **543 files,
+5,726 nodes, and 97,066 edges**. Its change analysis rated the seven-file
+runtime delta at `0.55` risk. Independent standards and specification reviews
+found no blocking violation or scope creep. They required two additional
+regressions—proving the command hash covers the original five-limit request and
+proving a self-consistently rehashed config with a changed address-space limit
+fails semantically—and both now pass.
+
+### Remaining gates in dependency order
+
+1. Commit this factual progress checkpoint, freeze the resulting clean source
+ `S2`, start and attest a fresh repository-contained runtime, and build the
+ exact runner and adapter once from `S2`. The historical `0d2fa32…` image is
+ not eligible for the repaired release.
+2. Obtain renewed owner authorization for one genuine aggregate-containment
+ invocation, then require the candidate-side `RLIMIT_AS` observation plus
+ the memory-OOM, PID-denial, CPU-throttling, membership, freshness, cleanup,
+ and exclusive-authority checks to pass together.
+3. Refresh and review the source/image-bound SBOM, VEX, scientific-engine,
+ license, health, held-out, mutation, and negative-control evidence; commit
+ only the allowlisted generated evidence as `E`.
+4. Reverify the Cloudflare identity and required secret names, including
+ `COUNTERLAB_ADMISSION_KEY`; qualify and registry-promote only the `S2`/`E`
+ tuple; run the complete release check.
+5. Deploy only that exact Container and Worker tuple, apply the bounded D1
+ migrations, capture deployment receipt v7, and pass production smoke with
+ public release identity matching all receipts.
+6. Execute and finalize all **40** public CloakBrowser journeys plus the ten
+ manual accessibility/performance checks and exact-release screenshots.
+7. Keep learner evidence `NO_DATA`. The owner will create the video. Complete
+ the remaining non-video submission evidence; owner inputs still required
+ are submitter type, country, the primary Codex `/feedback` UUID, public
+ video metadata, and private-repository sharing if anonymous access remains
+ unavailable.
+8. Push the reviewed feature branch, fast-forward merge it to `main`, verify,
+ and push `main` only after the exact public tuple and submission evidence
+ are factual.
+
+No repaired image, containment pass, refreshed release evidence, registry
+promotion, Cloudflare deployment, public browser qualification, learner
+outcome, Devpost submission, push, or merge is claimed. Release remains
+**NO-GO**.
From 552a1eb693cad364f28c6b004e4318d0d55f52e0 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 22:54:39 +0530
Subject: [PATCH 128/143] fix(runtime): separate setup and candidate deadlines
---
scripts/contained-runtime-run.mjs | 2 +-
scripts/contained-runtime.test.ts | 41 +++++++++++++++++++++++++++++++
2 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/scripts/contained-runtime-run.mjs b/scripts/contained-runtime-run.mjs
index 9d55feb0..b840d9c9 100644
--- a/scripts/contained-runtime-run.mjs
+++ b/scripts/contained-runtime-run.mjs
@@ -1229,7 +1229,7 @@ export async function executeContainedRun(
}
const created = executeSpawn(plan.create.program, plan.create.args, {
- ...runtimeOptions,
+ ...controlOptions,
input: Buffer.alloc(0),
});
if (created.status !== 0) {
diff --git a/scripts/contained-runtime.test.ts b/scripts/contained-runtime.test.ts
index 9db360b3..2ee40055 100644
--- a/scripts/contained-runtime.test.ts
+++ b/scripts/contained-runtime.test.ts
@@ -806,6 +806,7 @@ function qualifiedRuntimeHarness() {
`run/rootless-specs/${invocationId}.image-rootfs`,
);
const events: string[] = [];
+ const commandTimeouts: { create?: number; run?: number } = {};
let aliasPresent = false;
let imageRootfsMounted = false;
let stagingPresent = true;
@@ -858,6 +859,7 @@ function qualifiedRuntimeHarness() {
return successful(`${imageRootfsPath}\n`);
}
if (joined.includes(" create ")) {
+ commandTimeouts.create = _options.timeout;
return successful(`${stagingContainerId}\n`);
}
if (args.includes("--spec")) {
@@ -906,6 +908,7 @@ function qualifiedRuntimeHarness() {
return successful();
}
if (joined.includes(" run ")) {
+ commandTimeouts.run = _options.timeout;
events.push("candidate-started");
taskPresent = true;
containerPresent = true;
@@ -949,6 +952,7 @@ function qualifiedRuntimeHarness() {
sessionRoot,
stdin: Buffer.alloc(0),
},
+ commandTimeouts,
events,
fakeSpawn,
get finalContainerId() {
@@ -1160,6 +1164,43 @@ describe("contained runtime command policy", () => {
expect(spawned).toBe(false);
});
+ it("reserves the candidate wall-clock limit for the candidate process", async () => {
+ const harness = qualifiedRuntimeHarness();
+ const coordinator = {
+ async begin(input: { finalContainerId: string; invocationId: string }) {
+ return input;
+ },
+ async waitForDraft() {
+ return { receiptPayloadSha256: "6".repeat(64) };
+ },
+ async complete() {
+ return {
+ qualificationArtifacts: {},
+ qualifiedReceipt: {},
+ qualifiedReceiptFileSha256: "7".repeat(64),
+ qualifiedReceiptPath: resolve(
+ harness.context.sessionRoot,
+ `run/rootless-specs/${harness.finalContainerId}.qualified-receipt.json`,
+ ),
+ qualifiedReceiptPayloadSha256: "8".repeat(64),
+ };
+ },
+ };
+
+ await executeContainedRun(
+ harness.context,
+ harness.fakeSpawn,
+ harness.persistSpec,
+ () => undefined,
+ () => invocationId,
+ undefined,
+ coordinator,
+ );
+
+ expect(harness.commandTimeouts.create).toBeGreaterThan(20_000);
+ expect(harness.commandTimeouts.run).toBe(20_000);
+ });
+
it("binds a clean timed-out run to the aggregate-qualified receipt", async () => {
const harness = qualifiedRuntimeHarness();
let completionOutcome: unknown;
From 2cc2b2db3c7eba3b2d64458b8bb1de0fce7d5b28 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 22:57:00 +0530
Subject: [PATCH 129/143] docs(progress): record candidate deadline repair
---
docs/PROGRESS.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index ead51f85..5e52e577 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -4940,3 +4940,82 @@ No repaired image, containment pass, refreshed release evidence, registry
promotion, Cloudflare deployment, public browser qualification, learner
outcome, Devpost submission, push, or merge is claimed. Release remains
**NO-GO**.
+
+## Candidate-deadline repair checkpoint — 2026-07-21T17:25:32Z
+
+Deadline: `2026-07-22T00:00:00Z`; **6 hours 34 minutes 28 seconds remained at
+this checkpoint**. Source HEAD is
+`552a1eb693cad364f28c6b004e4318d0d55f52e0` on
+`feat/learner-ux-v6.1`. The tracked worktree was clean immediately after the
+runtime repair commit.
+
+### Exact build and genuine sentinel result
+
+Fresh runtime session `rt-prize721c` attested `VERIFIED` with runtime-policy
+hash `f330a93a90d9b58e1fc7338f363cf4c9a7fdab36d88b42208bded5987f675aac`,
+toolchain hash
+`89e10c42043f4921739d2cc8a70cf2b94fdbc1afb89bb5116a4f01c89318553a`,
+and adapter hash
+`f368efecf9ad10bd47a0d9a5366454f24a1f30efdff8eafe96fa41d5943cc202`.
+It built the runner and adapter from clean source
+`3c50ebf1afc293fcac8a053f838e71c564943511`:
+
+- runner config digest:
+ `sha256:125582f4487c3d0c569d2f1de11f876093c37a0674a7a90abde4930dd3fb9e8e`;
+- runner manifest digest:
+ `sha256:bffdc8a35a3f61c8c266f8f466094b618299ca3d8e10c9801ec546c9c06bcccf`;
+- adapter config digest:
+ `sha256:a6f00ca6de20354f93dda068af3b5162ef6ed2bc02d6a260f64bbaa9e882fce7`;
+- adapter manifest digest:
+ `sha256:1dd385902cb30353adb3a3c54232fe2818dfec9dddf481d2c9bb0b1d304bccae`.
+
+The single authorized genuine sentinel invocation failed closed before the
+candidate and aggregate observer started. A non-qualifying diagnostic, with
+aggregate observation disabled, recovered the swallowed error:
+`nerdctl create` exited with `ETIMEDOUT`. The sentinel's one-second candidate
+wall-clock budget had been applied to container staging. No rootless-spec,
+cgroup-observation, timeout-control, timeout-cleanup, or qualification receipt
+was created; the output directory remained empty and no result was released.
+The failed `3c50ebf…` build is therefore not eligible for release.
+
+### Minimal repair committed
+
+Commit `552a1eb fix(runtime): separate setup and candidate deadlines` reserves
+the candidate wall-clock budget for `ctr run` and gives `nerdctl create` the
+existing bounded control-plane budget. The absolute execution deadline still
+bounds setup, cleanup retains its separate reserve, and resource limits,
+canonical command hashing, OCI authority, and result-release rules are
+unchanged.
+
+Verification:
+
+- the new characterization failed before the repair with create timeout
+ `20,000ms`, then passed with create greater than the candidate budget and
+ candidate run exactly `20,000ms`;
+- runtime Vitest: **9 files, 86 tests passed**;
+- root TypeScript: passed;
+- Prettier on the two changed files: passed;
+- Git whitespace check: passed;
+- secret scan: passed across both changed files;
+- independent read-only review found no authority or deadline regression.
+
+### Immediate release chain
+
+1. Commit this factual checkpoint and freeze the resulting clean source `S3`.
+2. Build one fresh exact runner/adapter tuple from `S3`; no earlier image may be
+ promoted.
+3. Re-attest the runtime and run one genuine aggregate-containment sentinel
+ against that exact tuple. All process limits, cgroup membership, memory,
+ PID, CPU, freshness, negative controls, cleanup, and result withholding must
+ pass together.
+4. Refresh, review, test, and commit the exact source/image-bound evidence.
+5. Qualify and registry-promote only that tuple, run the complete release gate,
+ deploy with Wrangler, and verify public source/image/Worker identity.
+6. Execute the public CloakBrowser release matrix, fix only release-blocking
+ defects, complete non-video submission evidence, then push the feature
+ branch and fast-forward `main`.
+
+No containment pass, new exact image, refreshed evidence, registry promotion,
+Cloudflare deployment, public browser qualification, learner outcome, Devpost
+submission, push, or merge is claimed at this checkpoint. Release remains
+**NO-GO**.
From b804f48b0c84e955af0632dffa3b096ba1eada2c Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 23:28:33 +0530
Subject: [PATCH 130/143] fix(runtime): use delegated cgroup leaf
---
scripts/contained-cgroup-evidence.mjs | 2 +-
scripts/contained-cgroup-evidence.test.ts | 7 +++-
.../contained-cgroup-observer-protocol.mjs | 4 +-
...contained-cgroup-observer-protocol.test.ts | 5 ++-
scripts/contained-cgroup-observer.mjs | 2 +-
...d-cgroup-qualification-coordinator.test.ts | 2 +-
.../contained-qualified-receipt-store.test.ts | 2 +-
...ntained-qualified-rootless-receipt.test.ts | 2 +-
scripts/contained-rootless-spec.mjs | 7 +++-
scripts/contained-runtime-run.mjs | 2 +-
scripts/contained-runtime.test.ts | 12 ++++--
scripts/timeout-cleanup-receipt.test.ts | 15 +++++++-
scripts/timeout-cleanup-receipt.ts | 4 +-
.../src/counterlab_runner/timeout_proof.py | 2 +-
services/runner/tests/test_timeout_proof.py | 37 ++++++++++++++++++-
15 files changed, 84 insertions(+), 21 deletions(-)
diff --git a/scripts/contained-cgroup-evidence.mjs b/scripts/contained-cgroup-evidence.mjs
index f877d77b..f5d80f8e 100644
--- a/scripts/contained-cgroup-evidence.mjs
+++ b/scripts/contained-cgroup-evidence.mjs
@@ -214,7 +214,7 @@ export function validateContainedCgroupEvidence(value, expectedValue) {
evidence.runtimeAttestationSha256 !== expected.runtimeAttestationSha256 ||
!sha256(evidence.finalizationPayloadSha256) ||
evidence.cgroupId !== `counterlab-v6.1-${expected.invocationId}` ||
- evidence.cgroupPath !== `counterlab-v6.1/${expected.invocationId}` ||
+ evidence.cgroupPath !== `counterlab-v6.1-${expected.invocationId}` ||
evidence.cgroupIdentity !== createContainedCgroupIdentity(expected) ||
observed.memoryMaxBytes !== intended.memoryBytes ||
![0, intended.memoryBytes].includes(observed.memorySwapMaxBytes) ||
diff --git a/scripts/contained-cgroup-evidence.test.ts b/scripts/contained-cgroup-evidence.test.ts
index 11bd3371..d903b21a 100644
--- a/scripts/contained-cgroup-evidence.test.ts
+++ b/scripts/contained-cgroup-evidence.test.ts
@@ -25,7 +25,7 @@ function evidence() {
authority: "linux-cgroup-v2",
cgroupVersion: 2,
cgroupId: `counterlab-v6.1-${invocationId}`,
- cgroupPath: `counterlab-v6.1/${invocationId}`,
+ cgroupPath: `counterlab-v6.1-${invocationId}`,
cgroupIdentity: createContainedCgroupIdentity({
invocationId,
finalContainerId,
@@ -112,7 +112,10 @@ describe("contained cgroup evidence", () => {
it("rejects identity, limit, membership, counter, cleanup, and hash drift", () => {
const mutations: Array<(value: ReturnType) => void> = [
(value) => {
- value.cgroupPath = "counterlab-v6.1/escaped";
+ value.cgroupPath = "counterlab-v6.1-escaped";
+ },
+ (value) => {
+ value.cgroupPath = `counterlab-v6.1/${invocationId}`;
},
(value) => {
value.observedLimits.memoryMaxBytes += 1;
diff --git a/scripts/contained-cgroup-observer-protocol.mjs b/scripts/contained-cgroup-observer-protocol.mjs
index 345169e1..5e4a2d8a 100644
--- a/scripts/contained-cgroup-observer-protocol.mjs
+++ b/scripts/contained-cgroup-observer-protocol.mjs
@@ -128,7 +128,7 @@ export function validateContainedCgroupObserverManifest(
!sha256Pattern.test(manifest.driverModuleSha256 ?? "") ||
manifest.baseReceiptPath !== expectedReceiptPath ||
manifest.cgroupId !== `counterlab-v6.1-${manifest.invocationId}` ||
- manifest.cgroupPath !== `counterlab-v6.1/${manifest.invocationId}` ||
+ manifest.cgroupPath !== `counterlab-v6.1-${manifest.invocationId}` ||
manifest.cgroupIdentity !== createContainedCgroupIdentity(manifest) ||
!validTimestamp(manifest.requestedAt, observedAtMs) ||
!sha256Pattern.test(receiptPayloadSha256 ?? "") ||
@@ -163,7 +163,7 @@ export function createContainedCgroupObserverManifest({
invocationId,
finalContainerId,
cgroupId: `counterlab-v6.1-${invocationId}`,
- cgroupPath: `counterlab-v6.1/${invocationId}`,
+ cgroupPath: `counterlab-v6.1-${invocationId}`,
cgroupIdentity: createContainedCgroupIdentity({
invocationId,
finalContainerId,
diff --git a/scripts/contained-cgroup-observer-protocol.test.ts b/scripts/contained-cgroup-observer-protocol.test.ts
index a7829202..29be35f9 100644
--- a/scripts/contained-cgroup-observer-protocol.test.ts
+++ b/scripts/contained-cgroup-observer-protocol.test.ts
@@ -61,7 +61,7 @@ describe("contained cgroup observer protocol", () => {
expect(value.baseReceiptPath).toBe(
`.rt/${runtimeSessionId}/run/rootless-specs/${finalContainerId}.receipt.json`,
);
- expect(value.cgroupPath).toBe(`counterlab-v6.1/${invocationId}`);
+ expect(value.cgroupPath).toBe(`counterlab-v6.1-${invocationId}`);
expect(value.receiptPayloadSha256).toMatch(/^[a-f0-9]{64}$/u);
});
@@ -73,6 +73,9 @@ describe("contained cgroup observer protocol", () => {
(value) => {
value.baseReceiptPath = ".rt/escaped/receipt.json";
},
+ (value) => {
+ value.cgroupPath = `counterlab-v6.1/${invocationId}`;
+ },
(value) => {
value.intendedAggregateLimits.memoryBytes += 1;
},
diff --git a/scripts/contained-cgroup-observer.mjs b/scripts/contained-cgroup-observer.mjs
index a5c28f69..f2217731 100644
--- a/scripts/contained-cgroup-observer.mjs
+++ b/scripts/contained-cgroup-observer.mjs
@@ -683,7 +683,7 @@ async function waitUntil(predicate, label, timeoutMs) {
function actualCgroupAdapter(manifest, paths) {
const cgroupRoot = resolve(cgroupFilesystemRoot, manifest.cgroupPath);
- const cgroupParentRoot = resolve(cgroupFilesystemRoot, "counterlab-v6.1");
+ const cgroupParentRoot = cgroupFilesystemRoot;
if (
resolve(
cgroupFilesystemRoot,
diff --git a/scripts/contained-cgroup-qualification-coordinator.test.ts b/scripts/contained-cgroup-qualification-coordinator.test.ts
index f79ceaf6..28ff2fe3 100644
--- a/scripts/contained-cgroup-qualification-coordinator.test.ts
+++ b/scripts/contained-cgroup-qualification-coordinator.test.ts
@@ -139,7 +139,7 @@ function aggregateEvidence(
authority: "linux-cgroup-v2",
cgroupVersion: 2,
cgroupId: `counterlab-v6.1-${invocationId}`,
- cgroupPath: `counterlab-v6.1/${invocationId}`,
+ cgroupPath: `counterlab-v6.1-${invocationId}`,
cgroupIdentity: createContainedCgroupIdentity({
invocationId,
finalContainerId,
diff --git a/scripts/contained-qualified-receipt-store.test.ts b/scripts/contained-qualified-receipt-store.test.ts
index 40b58e31..2cb669ad 100644
--- a/scripts/contained-qualified-receipt-store.test.ts
+++ b/scripts/contained-qualified-receipt-store.test.ts
@@ -142,7 +142,7 @@ function aggregateEvidence(
authority: "linux-cgroup-v2",
cgroupVersion: 2,
cgroupId: `counterlab-v6.1-${invocationId}`,
- cgroupPath: `counterlab-v6.1/${invocationId}`,
+ cgroupPath: `counterlab-v6.1-${invocationId}`,
cgroupIdentity: createContainedCgroupIdentity({
invocationId,
finalContainerId,
diff --git a/scripts/contained-qualified-rootless-receipt.test.ts b/scripts/contained-qualified-rootless-receipt.test.ts
index 29ae5b10..f86ca8bd 100644
--- a/scripts/contained-qualified-rootless-receipt.test.ts
+++ b/scripts/contained-qualified-rootless-receipt.test.ts
@@ -71,7 +71,7 @@ function aggregateEvidence() {
authority: "linux-cgroup-v2",
cgroupVersion: 2,
cgroupId: `counterlab-v6.1-${invocationId}`,
- cgroupPath: `counterlab-v6.1/${invocationId}`,
+ cgroupPath: `counterlab-v6.1-${invocationId}`,
cgroupIdentity: createContainedCgroupIdentity({
invocationId,
finalContainerId,
diff --git a/scripts/contained-rootless-spec.mjs b/scripts/contained-rootless-spec.mjs
index b788184f..dac263ca 100644
--- a/scripts/contained-rootless-spec.mjs
+++ b/scripts/contained-rootless-spec.mjs
@@ -1520,10 +1520,13 @@ export function sanitizeContainedRootlessSpec({
);
if (hasNerdctlHooks) delete parsed.hooks;
delete parsed.annotations;
- linux.cgroupsPath = `${namespace}/${expected.invocationId}`;
+ // RootlessKit delegates the cgroup namespace root. Keep the authoritative
+ // invocation cgroup as one leaf so every controller is inherited directly
+ // from that delegated root rather than through an unmanaged intermediate.
+ linux.cgroupsPath = `${namespace}-${expected.invocationId}`;
delete linux.sysctl;
if (
- linux.cgroupsPath !== `${namespace}/${expected.invocationId}` ||
+ linux.cgroupsPath !== `${namespace}-${expected.invocationId}` ||
!Object.hasOwn(linux, "resources")
) {
throw new Error("contained rootless OCI spec cgroup normalization failed");
diff --git a/scripts/contained-runtime-run.mjs b/scripts/contained-runtime-run.mjs
index b840d9c9..92f3f010 100644
--- a/scripts/contained-runtime-run.mjs
+++ b/scripts/contained-runtime-run.mjs
@@ -568,7 +568,7 @@ export function containedRunPlan({
"--runc-root",
resolve(sessionRoot, "run/runc"),
"--cgroup",
- `${namespace}/${invocationId}`,
+ `${namespace}-${invocationId}`,
"--platform",
"linux/amd64",
],
diff --git a/scripts/contained-runtime.test.ts b/scripts/contained-runtime.test.ts
index 2ee40055..c62f3c5e 100644
--- a/scripts/contained-runtime.test.ts
+++ b/scripts/contained-runtime.test.ts
@@ -1584,6 +1584,11 @@ describe("contained runtime command policy", () => {
.filter((argument) => argument !== "--ulimit=as=1073741824:1073741824"),
);
expect(plan.start.program).toBe(resolve(installRoot, "bin/ctr"));
+ const cgroupIndex = plan.start.args.indexOf("--cgroup");
+ expect(plan.start.args[cgroupIndex + 1]).toBe(
+ `counterlab-v6.1-${invocationId}`,
+ );
+ expect(plan.start.args[cgroupIndex + 1]).not.toContain("/");
expect(plan.start.args).toEqual(
expect.arrayContaining([
"run",
@@ -1591,7 +1596,7 @@ describe("contained runtime command policy", () => {
"--fifo-dir",
clientFifoRoot,
"--cgroup",
- `counterlab-v6.1/${invocationId}`,
+ `counterlab-v6.1-${invocationId}`,
"--platform",
"linux/amd64",
]),
@@ -1769,7 +1774,8 @@ describe("contained runtime command policy", () => {
const sanitized = JSON.parse(prepared.config);
const original = JSON.parse(source);
- expect(sanitized.linux.cgroupsPath).toBe(`counterlab-v6.1/${invocationId}`);
+ expect(sanitized.linux.cgroupsPath).toBe(`counterlab-v6.1-${invocationId}`);
+ expect(sanitized.linux.cgroupsPath).not.toContain("/");
expect(sanitized.linux.resources).toEqual(original.linux.resources);
const canonicalBase = structuredClone(sanitized);
delete canonicalBase.annotations;
@@ -3037,7 +3043,7 @@ describe("contained runtime command policy", () => {
expect(startCall).toEqual(
expect.arrayContaining([
"--cgroup",
- `counterlab-v6.1/${invocationId}`,
+ `counterlab-v6.1-${invocationId}`,
"--label",
`io.counterlab.runtime.invocation=${invocationId}`,
]),
diff --git a/scripts/timeout-cleanup-receipt.test.ts b/scripts/timeout-cleanup-receipt.test.ts
index d614f05f..c42023c5 100644
--- a/scripts/timeout-cleanup-receipt.test.ts
+++ b/scripts/timeout-cleanup-receipt.test.ts
@@ -32,7 +32,7 @@ function aggregateLimitEvidence() {
authority: "linux-cgroup-v2" as const,
cgroupVersion: 2 as const,
cgroupId: `counterlab-v6.1-${invocationId}`,
- cgroupPath: `counterlab-v6.1/${invocationId}`,
+ cgroupPath: `counterlab-v6.1-${invocationId}`,
cgroupIdentity: sha256(
`counterlab-cgroup-v2\0${invocationId}\0${finalContainerId}\0${sanitizedSpecSha256}`,
),
@@ -210,6 +210,19 @@ describe("timeout cleanup aggregate resource authority", () => {
}),
).toThrow();
+ const nestedPath = aggregateLimitEvidence();
+ nestedPath.cgroupPath = `counterlab-v6.1/${invocationId}`;
+ const { receiptPayloadSha256: _nestedHash, ...nestedPayload } = nestedPath;
+ nestedPath.receiptPayloadSha256 = sha256(canonicalJson(nestedPayload));
+ expect(() =>
+ assertQualifiedAggregateRuntimeLimits({
+ ...aggregateInput(),
+ aggregateLimitEvidence: nestedPath,
+ aggregateLimitIntentEnforced: true,
+ limitMode: QUALIFIED_AGGREGATE_LIMIT_MODE,
+ }),
+ ).toThrow();
+
const ambiguousOom = aggregateLimitEvidence();
ambiguousOom.negativeControls.memory.oomKillAfter = 2;
const { receiptPayloadSha256: _ignored, ...payload } = ambiguousOom;
diff --git a/scripts/timeout-cleanup-receipt.ts b/scripts/timeout-cleanup-receipt.ts
index 65e1a0f3..5571378f 100644
--- a/scripts/timeout-cleanup-receipt.ts
+++ b/scripts/timeout-cleanup-receipt.ts
@@ -42,7 +42,7 @@ const AggregateLimitEvidenceSchema = z
authority: z.literal("linux-cgroup-v2"),
cgroupVersion: z.literal(2),
cgroupId: z.string().regex(/^counterlab-v6\.1-[a-f0-9]{64}$/),
- cgroupPath: z.string().regex(/^counterlab-v6\.1\/[a-f0-9]{64}$/),
+ cgroupPath: z.string().regex(/^counterlab-v6\.1-[a-f0-9]{64}$/),
cgroupIdentity: Sha256Schema,
invocationId: Sha256Schema,
finalContainerId: Sha256Schema,
@@ -99,7 +99,7 @@ const AggregateLimitEvidenceSchema = z
.superRefine((evidence, context) => {
if (
evidence.cgroupId !== `counterlab-v6.1-${evidence.invocationId}` ||
- evidence.cgroupPath !== `counterlab-v6.1/${evidence.invocationId}` ||
+ evidence.cgroupPath !== `counterlab-v6.1-${evidence.invocationId}` ||
evidence.cgroupIdentity !==
sha256(
`counterlab-cgroup-v2\0${evidence.invocationId}\0${evidence.finalContainerId}\0${evidence.sanitizedSpecSha256}`,
diff --git a/services/runner/src/counterlab_runner/timeout_proof.py b/services/runner/src/counterlab_runner/timeout_proof.py
index c50f4768..7278c1a2 100644
--- a/services/runner/src/counterlab_runner/timeout_proof.py
+++ b/services/runner/src/counterlab_runner/timeout_proof.py
@@ -588,7 +588,7 @@ def _validate_aggregate_limit_evidence(
or value.get("finalContainerId") != final_container_id
or not _SHA256.fullmatch(str(value.get("finalizationPayloadSha256", "")))
or value.get("cgroupId") != f"counterlab-v6.1-{invocation_id}"
- or value.get("cgroupPath") != f"counterlab-v6.1/{invocation_id}"
+ or value.get("cgroupPath") != f"counterlab-v6.1-{invocation_id}"
or value.get("sanitizedSpecSha256") != sanitized_spec_sha256
or not _SHA256.fullmatch(str(value.get("sanitizedSpecSha256", "")))
or not _SHA256.fullmatch(str(value.get("runtimeAttestationSha256", "")))
diff --git a/services/runner/tests/test_timeout_proof.py b/services/runner/tests/test_timeout_proof.py
index 954c2b31..2bb845bd 100644
--- a/services/runner/tests/test_timeout_proof.py
+++ b/services/runner/tests/test_timeout_proof.py
@@ -76,7 +76,7 @@ def _rootless(control: dict[str, object], build: dict[str, object]) -> dict[str,
"authority": "linux-cgroup-v2",
"cgroupVersion": 2,
"cgroupId": f"counterlab-v6.1-{invocation_id}",
- "cgroupPath": f"counterlab-v6.1/{invocation_id}",
+ "cgroupPath": f"counterlab-v6.1-{invocation_id}",
"cgroupIdentity": hashlib.sha256(
(
"counterlab-cgroup-v2\0"
@@ -418,6 +418,41 @@ def test_rootless_receipt_binds_control_and_exact_adapter_authority() -> None:
with pytest.raises(RuntimeError, match="aggregate limit evidence"):
validate_rootless_receipt(broken_counter, control=control, build=build)
+ nested_control = _control()
+ nested_path = _rootless(nested_control, build)
+ nested_path["aggregateLimitEvidence"]["cgroupPath"] = (
+ f"counterlab-v6.1/{nested_control['invocationId']}"
+ )
+ nested_evidence_payload = {
+ key: value
+ for key, value in nested_path["aggregateLimitEvidence"].items()
+ if key != "receiptPayloadSha256"
+ }
+ nested_path["aggregateLimitEvidence"]["receiptPayloadSha256"] = _hash(
+ nested_evidence_payload
+ )
+ nested_payload = {
+ key: value
+ for key, value in nested_path.items()
+ if key != "receiptPayloadSha256"
+ }
+ nested_path["receiptPayloadSha256"] = _hash(nested_payload)
+ nested_control["rootlessReceiptPayloadSha256"] = nested_path[
+ "receiptPayloadSha256"
+ ]
+ nested_control_payload = {
+ key: value
+ for key, value in nested_control.items()
+ if key != "receiptPayloadSha256"
+ }
+ nested_control["receiptPayloadSha256"] = _hash(nested_control_payload)
+ with pytest.raises(RuntimeError, match="aggregate limit evidence"):
+ validate_rootless_receipt(
+ nested_path,
+ control=nested_control,
+ build=build,
+ )
+
def test_exactly_one_new_rootless_receipt_is_required(tmp_path: Path) -> None:
first = tmp_path / "first.receipt.json"
From 5209d06e2abd37d0265b67b058bece96d964c013 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Tue, 21 Jul 2026 23:30:23 +0530
Subject: [PATCH 131/143] docs(progress): record delegated cgroup repair
---
docs/PROGRESS.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 5e52e577..79486cc9 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -5019,3 +5019,84 @@ No containment pass, new exact image, refreshed evidence, registry promotion,
Cloudflare deployment, public browser qualification, learner outcome, Devpost
submission, push, or merge is claimed at this checkpoint. Release remains
**NO-GO**.
+
+## Delegated-cgroup leaf repair checkpoint — 2026-07-21T17:58:52Z
+
+Deadline: `2026-07-22T00:00:00Z`; **6 hours 1 minute 8 seconds remained at
+this checkpoint**. Source HEAD is
+`b804f48b0c84e955af0632dffa3b096ba1eada2c` on
+`feat/learner-ux-v6.1`, three commits ahead of its remote tracking ref. The
+worktree was clean immediately after the independent runtime repair commit.
+
+### Second genuine sentinel result
+
+A fresh attested contained runtime built the exact runner and adapter from
+source `2cc2b2db3c7eba3b2d64458b8bb1de0fce7d5b28`. The runner config digest was
+`sha256:b1c98ff610ac580450d81e59a5d6bae8a344bcc8442268c192c5a6728df5fc64`
+and the normalized manifest digest was
+`sha256:fd24191626b4195a323743bba7abeaae44fdfa8335109ad32788e0605b91dea0`.
+The single genuine aggregate-containment invocation armed its independent
+observer but failed closed before the candidate started. A non-qualifying
+observer-disabled diagnostic recovered the exact runtime error: runc could
+not create `counterlab-v6.1/` because the unmanaged intermediate
+cgroup did not expose the delegated controllers. No aggregate evidence or
+qualified receipt was issued, and the `2cc2b2d` image is permanently
+ineligible for release.
+
+### Minimal repair committed
+
+Commit `b804f48 fix(runtime): use delegated cgroup leaf` places every
+invocation directly below RootlessKit's delegated cgroup-v2 namespace root as
+`counterlab-v6.1-`. The OCI spec, direct `ctr` command, observer
+manifest, observer filesystem root, aggregate evidence, TypeScript timeout
+receipt, and Python timeout validator now agree on that one source-bound leaf.
+The former nested path and foreign paths fail closed. No resource limit,
+negative control, result-release rule, or verifier authority was weakened.
+
+Verification after the final diff:
+
+- contained-runtime Vitest: **17 files, 121 tests passed**;
+- focused runner Pytest with a repository-contained temporary root:
+ **38 tests passed**;
+- root TypeScript: passed;
+- scoped Prettier and Python compilation: passed;
+- Git whitespace: passed;
+- secret scan: passed across the **15 changed files**;
+- independent read-only review: no blocking defect or authority regression.
+
+The first Python invocation used pytest's default external temporary root and
+correctly produced ten `run_root_outside_repository` failures. The same suite
+then passed with `TMPDIR` contained under CounterLab. Those initial failures
+were a harness-safety signal, not suppressed product failures.
+
+### Remaining exact release chain
+
+1. Commit this factual checkpoint and use the resulting clean commit as the
+ new exact source `S4`.
+2. Retire the stale held runtime, start and attest a fresh contained runtime,
+ and build one runner/adapter tuple from `S4`.
+3. Run one genuine aggregate sentinel and require the direct leaf, all five
+ process limits, memory/PID/CPU negative controls, membership, freshness,
+ cleanup, and result withholding to pass together.
+4. Refresh and commit only the exact source/image-bound SBOM, VEX,
+ scientific-engine, license, health, held-out, mutation, Sample Capsule, and
+ qualification evidence.
+5. Verify the repository-contained Wrangler identity, five required secret
+ names, zero legacy replay rows, and the active recovery targets. OAuth state
+ is present and currently valid, but live account identity is not yet
+ claimed.
+6. Qualify and registry-promote only the exact tuple, run the complete release
+ check, deploy the bounded D1/Container/Worker sequence, and require the
+ automatic production smoke to pass.
+7. Execute the complete public CloakBrowser matrix and re-audit every public
+ P0/P1 against the exact deployed tuple.
+8. Learner-study evidence is no longer a release blocker and remains honestly
+ `NO_DATA`. Submission metadata is `student`, country `India`; the owner will
+ provide the public video after deployment.
+9. Complete the remaining submission receipts, push the reviewed feature
+ branch, fast-forward it into `main`, verify, and push `main`.
+
+No containment pass, new exact build, refreshed release evidence, registry
+promotion, Cloudflare mutation, public qualification, submission, push, or
+merge is claimed at this checkpoint. Release remains **NO-GO** until the
+remaining exact-tuple gates pass.
From 7c0cbe21d53d32adf424cfeb815fa9b58c26fc07 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Wed, 22 Jul 2026 00:21:11 +0530
Subject: [PATCH 132/143] fix(release): scope Worker secret preflight
---
apps/web/worker/deployment-config.test.ts | 7 +++++++
scripts/deploy-qualified.sh | 1 +
2 files changed, 8 insertions(+)
diff --git a/apps/web/worker/deployment-config.test.ts b/apps/web/worker/deployment-config.test.ts
index 7bd632d6..6e9c1cac 100644
--- a/apps/web/worker/deployment-config.test.ts
+++ b/apps/web/worker/deployment-config.test.ts
@@ -489,6 +489,10 @@ describe("Cloudflare static asset routing", () => {
);
const account = script.indexOf('"${WRANGLER}" whoami --json');
const secretList = script.indexOf('"${WRANGLER}" secret list');
+ const secretValidation = script.indexOf(
+ 'node - "${RELEASE_DIR}/secret-names.json"',
+ secretList,
+ );
expect(finalSecretScan).toBeGreaterThan(build);
expect(manifest).toBeGreaterThan(finalSecretScan);
expect(script.slice(manifest, dryRun)).toContain(
@@ -500,6 +504,9 @@ describe("Cloudflare static asset routing", () => {
expect(strictDryRun).toBeGreaterThan(dryRun);
expect(account).toBeGreaterThan(strictDryRun);
expect(secretList).toBeGreaterThan(account);
+ expect(script.slice(secretList, secretValidation)).toContain(
+ "--name counterlab",
+ );
const maintenance = script.indexOf("MAINTENANCE_TAG=");
const migration = script.indexOf('"${WRANGLER}" d1 migrations apply DB');
diff --git a/scripts/deploy-qualified.sh b/scripts/deploy-qualified.sh
index 08c1d6c5..e7314d1a 100755
--- a/scripts/deploy-qualified.sh
+++ b/scripts/deploy-qualified.sh
@@ -702,6 +702,7 @@ assert_release_authority() {
}
"${WRANGLER}" secret list \
+ --name counterlab \
--config "${RELEASE_CONFIG}" \
--format json >"${RELEASE_DIR}/secret-names.json"
node - "${RELEASE_DIR}/secret-names.json" <<'NODE'
From ef9f283df792a86d77ea576cda4f829adc624933 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Wed, 22 Jul 2026 00:22:21 +0530
Subject: [PATCH 133/143] docs(progress): record delegated release preflight
---
docs/PROGRESS.md | 98 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 98 insertions(+)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 79486cc9..42ab7f2a 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -5100,3 +5100,101 @@ No containment pass, new exact build, refreshed release evidence, registry
promotion, Cloudflare mutation, public qualification, submission, push, or
merge is claimed at this checkpoint. Release remains **NO-GO** until the
remaining exact-tuple gates pass.
+
+## Delegated-runtime and Cloudflare preflight checkpoint — 2026-07-21T18:51:30Z
+
+Deadline: `2026-07-22T00:00:00Z`; **5 hours 8 minutes 30 seconds remained at
+this checkpoint**. HEAD is
+`7c0cbe21d53d32adf424cfeb815fa9b58c26fc07` on
+`feat/learner-ux-v6.1`.
+
+### Exact build and failed sentinel
+
+The attested `rt-prize721f` runtime built source
+`5209d06e2abd37d0265b67b058bece96d964c013` into an exact local tuple. The
+runner config digest is
+`sha256:30213170dd5aa69c233abf0d35ebe1febc6fc118f3182a482168039d22111398`
+and normalized manifest digest is
+`sha256:c93db90b55a02f094b1f225ace946377ccb7704fcfe95a88bda7f0eb37ca165c`.
+The adapter config digest is
+`sha256:30c88829c0d9757118ee67fa440cba8d0805c2847e7c9242097b3d343b52f15c`
+and manifest digest is
+`sha256:e2ff419cfe39c4b6cb48fd6d7c2f5dcd6f606950fdf3808280b01419996108c1`.
+
+The genuine sentinel for invocation
+`4a1c92f0421be9e8d99d7bed20bbf4b60f0e3e3a84ca9292e452f7feee26e0e9`
+failed closed before candidate creation. Containerd connected the shim, runc
+created no `init.pid`, and the observer emitted no draft or aggregate evidence.
+The finalization recorded `timeoutObserved: false`, `resultReleased: true`, and
+`cleanupVerified: false`; no timeout-cleanup or qualification receipt exists.
+
+A narrow read-only observation of the CounterLab-owned containerd process
+confirmed the cause: its cgroup namespace root exposed no controllers. A
+transient user scope with `Delegate=yes` exposed the required controller set.
+Fresh runtime `rt-prize721g`, launched inside that auto-collected scope, is
+attested and its owned containerd cgroup exposes exactly `cpu memory pids`.
+The previous build receipt is session-toolchain-bound, so it cannot be reused;
+the final clean source must be rebuilt in `rt-prize721g` before the renewed
+genuine sentinel.
+
+### Cloudflare preflight and authorized D1 cleanup
+
+- Wrangler authentication resolved to intended account
+ `9b0a1524e478000ec9b3ff2da6104d81`.
+- All five required Worker secret names are present. A new
+ `COUNTERLAB_ADMISSION_KEY` was generated and stored without printing or
+ persisting its value.
+- The secret update created Worker deployment
+ `4d768fdb-123c-42b5-b5a2-1517a0a12ccc`, version
+ `f13185ab-156e-40a4-b195-0c70b247ad0d`; this is a secret-triggered interim
+ deployment, not the qualified release.
+- Before the owner-authorized deletion, remote D1 was exported to the ignored,
+ repository-contained backup
+ `node_modules/.cache/counterlab-v6.1/releases/pre-delete-counterlab-d1-20260721.sql`.
+ Its size is `14,986,677` bytes and SHA-256 is
+ `68b933b62f1b10fd7569edf07b1d238bae4e62ac18b696391481664034bc7be8`.
+- Exactly eight legacy replay rows were deleted. The follow-up query returned
+ zero rows and one restored `replays_no_delete` trigger. No R2 object or other
+ D1 data was deleted.
+- The active Container remains version 35 on the old `7ba24f06...` image.
+ Migrations `0007`, `0008`, and `0009` remain pending for the qualified deploy.
+
+### Release-script repair
+
+Live Wrangler behavior showed that `secret list` must include
+`--name counterlab`; the unscoped command returned an empty list. Commit
+`7c0cbe2 fix(release): scope Worker secret preflight` fixes the fail-closed
+preflight and adds a regression. Verification: web Vitest **83 files, 703 tests
+passed**; TypeScript test formatting, shell syntax, Git whitespace, and a
+two-file secret scan passed.
+
+### Current ownership and next gates
+
+Another active worker owns eighteen learner UI/test modifications and two
+untracked Learning Director files. They remain unstaged and were not edited or
+overwritten by this release slice. The final source freeze waits for that work
+to be completed and reviewed.
+
+Remaining order:
+
+1. Integrate and commit the concurrent learner slice, then obtain a clean final
+ source commit `S`.
+2. Build `S` once in `rt-prize721g`, run one genuine sentinel, and require all
+ cgroup limits, negative controls, ancestry, timeout, cleanup, and result
+ withholding to pass together.
+3. Refresh and commit the exact SBOM, VEX, scientific-engine, license, health,
+ held-out, and generation-isolation evidence; qualify and promote only that
+ tuple.
+4. Run the complete release check and deploy the bounded maintenance Worker,
+ D1 migrations, Container rollout, final Worker, and automatic production
+ smoke.
+5. Run the public CloakBrowser desktop/mobile, authority, failure, reconnect,
+ transfer, patch, Capsule, accessibility, console/network, and Web Vitals
+ matrix.
+6. Finish non-video submission evidence, keep learner impact `NO_DATA`, push
+ the feature branch, fast-forward `main`, and push `main`. The owner records
+ the final public video.
+
+No aggregate-containment pass, qualified tuple, evidence refresh, registry
+promotion, final deployment, public browser qualification, submission, push,
+or merge is claimed. Release remains **NO-GO**.
From 533b6d34f1efe5b43c80261e9c790bdeee7b17a4 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Wed, 22 Jul 2026 00:56:07 +0530
Subject: [PATCH 134/143] fix(runtime): evacuate delegated cgroup after workers
start
---
scripts/contained-runtime-supervisor.mjs | 74 ++++++++++++--------
scripts/contained-runtime-supervisor.test.ts | 25 +++++++
2 files changed, 70 insertions(+), 29 deletions(-)
diff --git a/scripts/contained-runtime-supervisor.mjs b/scripts/contained-runtime-supervisor.mjs
index 61c6f096..44916c45 100644
--- a/scripts/contained-runtime-supervisor.mjs
+++ b/scripts/contained-runtime-supervisor.mjs
@@ -85,27 +85,24 @@ function openPrivateLog(name) {
return openSync(resolve(sessionRoot, "logs", name), "a", 0o600);
}
-const containerdLog = openPrivateLog("containerd.log");
-const containerdRootlesskit = spawn(
- resolve(binRoot, "rootlesskit"),
- [
- `--state-dir=${resolve(runRoot, "containerd-rootless")}`,
- "--net=host",
- "--pidns",
- "--cgroupns",
- "--evacuate-cgroup2=containerd",
- process.execPath,
- resolve(root, "scripts/contained-runtime-server.mjs"),
- "--session-id",
- sessionId,
- ],
- {
- cwd: root,
- env: environment,
- stdio: ["ignore", containerdLog, containerdLog],
- },
-);
-closeSync(containerdLog);
+function socketReady(path) {
+ try {
+ return statSync(path).isSocket();
+ } catch {
+ return false;
+ }
+}
+
+async function waitForInitialSocket(path, child, label) {
+ for (let attempt = 0; attempt < 300; attempt += 1) {
+ if (socketReady(path)) return;
+ if (child.exitCode !== null || child.signalCode !== null) {
+ throw new Error(`${label} exited during launch`);
+ }
+ await new Promise((accept) => setTimeout(accept, 100));
+ }
+ throw new Error(`${label} socket did not become ready`);
+}
const buildkitLog = openPrivateLog("buildkitd.log");
const buildkitRootlesskit = spawn(
@@ -138,6 +135,33 @@ const buildkitRootlesskit = spawn(
},
);
closeSync(buildkitLog);
+await waitForInitialSocket(
+ buildkitInnerSocket,
+ buildkitRootlesskit,
+ "contained BuildKit",
+);
+
+const containerdLog = openPrivateLog("containerd.log");
+const containerdRootlesskit = spawn(
+ resolve(binRoot, "rootlesskit"),
+ [
+ `--state-dir=${resolve(runRoot, "containerd-rootless")}`,
+ "--net=host",
+ "--pidns",
+ "--cgroupns",
+ "--evacuate-cgroup2=containerd",
+ process.execPath,
+ resolve(root, "scripts/contained-runtime-server.mjs"),
+ "--session-id",
+ sessionId,
+ ],
+ {
+ cwd: root,
+ env: environment,
+ stdio: ["ignore", containerdLog, containerdLog],
+ },
+);
+closeSync(containerdLog);
const children = { containerdRootlesskit, buildkitRootlesskit };
let state = "STARTING";
@@ -162,14 +186,6 @@ function childPids() {
return values;
}
-function socketReady(path) {
- try {
- return statSync(path).isSocket();
- } catch {
- return false;
- }
-}
-
async function waitForSockets(paths) {
for (let attempt = 0; attempt < 300; attempt += 1) {
if (paths.every(socketReady)) return;
diff --git a/scripts/contained-runtime-supervisor.test.ts b/scripts/contained-runtime-supervisor.test.ts
index abdc0e30..ecd674b7 100644
--- a/scripts/contained-runtime-supervisor.test.ts
+++ b/scripts/contained-runtime-supervisor.test.ts
@@ -108,6 +108,31 @@ describe("contained runtime supervisor protocol", () => {
);
});
+ it("starts BuildKit before the final delegated-cgroup evacuation", () => {
+ const source = readFileSync(
+ resolve(process.cwd(), "scripts/contained-runtime-supervisor.mjs"),
+ "utf8",
+ );
+ const buildkitLaunch = source.indexOf("const buildkitRootlesskit = spawn(");
+ const buildkitReady = source.indexOf(
+ "await waitForInitialSocket(",
+ buildkitLaunch,
+ );
+ const containerdLaunch = source.indexOf(
+ "const containerdRootlesskit = spawn(",
+ );
+
+ expect(buildkitLaunch).toBeGreaterThan(0);
+ expect(buildkitReady).toBeGreaterThan(buildkitLaunch);
+ expect(containerdLaunch).toBeGreaterThan(buildkitReady);
+ expect(
+ source.slice(
+ containerdLaunch,
+ source.indexOf("closeSync(containerdLog)"),
+ ),
+ ).toContain('"--evacuate-cgroup2=containerd"');
+ });
+
it("accepts only exact hash-bound status, drain, and shutdown requests", () => {
expect(
parseSupervisorRequest(
From 9d8e823baac7d5a4dbb8b2d9deb8c7467fb393e3 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Wed, 22 Jul 2026 02:47:19 +0530
Subject: [PATCH 135/143] fix(runtime): preserve non-root runner execution
---
Dockerfile.runner | 31 +++
apps/web/worker/deployment-config.test.ts | 7 +
scripts/contained-containerd-config.d.mts | 4 +
scripts/contained-containerd-config.mjs | 31 ++-
scripts/contained-rootless-spec.mjs | 15 +-
scripts/contained-runtime-attestation.mjs | 16 +-
scripts/contained-runtime-run.d.mts | 13 +
scripts/contained-runtime-run.mjs | 227 +++++++++++++++-
scripts/contained-runtime-server.mjs | 126 ++++++++-
scripts/contained-runtime-supervisor.mjs | 8 +-
scripts/contained-runtime-supervisor.test.ts | 14 +
scripts/contained-runtime.test.ts | 244 +++++++++++++++---
scripts/normalize_runner_oci.py | 66 ++++-
scripts/runtime-bin/mount.fuse3 | 122 +++++++++
scripts/runtime-toolchain-lock.json | 8 +
scripts/setup-contained-runtime.sh | 13 +-
scripts/start-contained-runtime.sh | 10 +-
scripts/stop-contained-runtime.mjs | 1 +
scripts/test_normalize_runner_oci.py | 70 ++++-
scripts/timeout-cleanup-receipt.test.ts | 5 +-
scripts/timeout-cleanup-receipt.ts | 4 +-
.../validate-contained-runtime-command.mjs | 8 +-
scripts/verify-contained-runtime.mjs | 31 ++-
.../write-contained-runtime-attestation.mjs | 2 +
.../runner/src/counterlab_runner/docker.py | 4 +-
.../src/counterlab_runner/timeout_proof.py | 5 +-
services/runner/tests/test_docker_runner.py | 5 +-
services/runner/tests/test_timeout_proof.py | 8 +-
28 files changed, 1006 insertions(+), 92 deletions(-)
create mode 100644 scripts/runtime-bin/mount.fuse3
diff --git a/Dockerfile.runner b/Dockerfile.runner
index d4faff0e..388da99c 100644
--- a/Dockerfile.runner
+++ b/Dockerfile.runner
@@ -43,6 +43,14 @@ RUN pnpm install --frozen-lockfile --filter @counterlab/hosted-runner... \
&& node --check /out/runner.mjs \
&& mkdir -p /out/codex \
&& cp -a /build/node_modules/.pnpm/@openai+codex@0.144.4-linux-x64/node_modules/@openai/codex/vendor/x86_64-unknown-linux-musl/. /out/codex/ \
+ && mkdir -p \
+ /out/runtime-rootfs/dev/pts \
+ /out/runtime-rootfs/dev/shm \
+ /out/runtime-rootfs/dev/mqueue \
+ /out/runtime-rootfs/sys/fs/cgroup \
+ /out/runtime-rootfs/counterlab-runtime \
+ /out/runtime-rootfs/etc \
+ && touch /out/runtime-rootfs/etc/hosts \
&& chmod -R a+rX /out
FROM python:3.13-slim-trixie@sha256:bffeb7bd6a85767587059c6ba23e1e9122078e3aa3fa836099171b9bb5a9bb00
@@ -90,6 +98,7 @@ COPY fixtures/public/fraud_rare_event.csv ./fixtures/public/fraud_rare_event.csv
COPY --from=python-builder /wheelhouse/ /opt/counterlab-wheelhouse/
COPY --from=node-builder /out/runner.mjs /app/runner.mjs
COPY --from=node-builder /out/codex/ /opt/codex/
+COPY --from=node-builder /out/runtime-rootfs/ /
RUN chmod -R a+rX /opt/codex \
&& ln -s /opt/codex/bin/codex /usr/local/bin/codex \
@@ -108,6 +117,28 @@ RUN chmod -R a+rX /opt/codex \
&& chown -R root:root /app \
&& chmod -R a=rX /app \
&& chmod 555 /app/runner.mjs \
+ && chown root:root \
+ /usr/lib \
+ /usr/lib64 \
+ /usr/lib/x86_64-linux-gnu \
+ /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 \
+ /dev/pts \
+ /dev/shm \
+ /dev/mqueue \
+ /sys/fs/cgroup \
+ /counterlab-runtime \
+ /etc/hosts \
+ && chmod 0755 \
+ /usr/lib \
+ /usr/lib64 \
+ /usr/lib/x86_64-linux-gnu \
+ /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 \
+ /dev/pts \
+ /dev/shm \
+ /dev/mqueue \
+ /sys/fs/cgroup \
+ /counterlab-runtime \
+ && chmod 0644 /etc/hosts \
&& chown -R counterlab-codex:counterlab-codex /work/jobs /run/counterlab-codex \
&& chmod 700 /work/jobs /run/counterlab-codex
diff --git a/apps/web/worker/deployment-config.test.ts b/apps/web/worker/deployment-config.test.ts
index 6e9c1cac..7c75b742 100644
--- a/apps/web/worker/deployment-config.test.ts
+++ b/apps/web/worker/deployment-config.test.ts
@@ -676,6 +676,13 @@ describe("Cloudflare static asset routing", () => {
expect(dockerfile).toContain("/repo/requirements.runner.lock.txt");
expect(dockerfile).toContain("chmod -R a=rX /repo");
expect(dockerfile).toContain("chmod 0555 /usr/local/bin/node");
+ expect(dockerfile).toContain(
+ "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
+ );
+ expect(dockerfile).toContain("/out/runtime-rootfs/dev/pts");
+ expect(dockerfile).toContain("/out/runtime-rootfs/sys/fs/cgroup");
+ expect(dockerfile).toContain("/out/runtime-rootfs/counterlab-runtime");
+ expect(dockerfile).toContain("/out/runtime-rootfs/etc/hosts");
expect(dockerfile).toContain("bubblewrap=0.11.0-2+deb13u1");
expect(dockerfile).toContain(
'test "$(/usr/bin/bwrap --version)" = "bubblewrap 0.11.0"',
diff --git a/scripts/contained-containerd-config.d.mts b/scripts/contained-containerd-config.d.mts
index 6079dd4c..02dad777 100644
--- a/scripts/contained-containerd-config.d.mts
+++ b/scripts/contained-containerd-config.d.mts
@@ -1,16 +1,20 @@
export const CONTAINERD_SHIM_SOCKET_DIR_MAX_LENGTH: number;
+export const CONTAINED_RUNTIME_SNAPSHOTTER: "fuse-overlayfs";
export function renderContainedContainerdConfig(
shimSocketDirectory: string,
+ snapshotterSocket: string,
): string;
export function createContainedContainerdConfig(options: {
configPath: string;
repositoryRoot: string;
shimSocketRoot: string;
+ snapshotterSocket: string;
}): {
shimSocketDirectory: string;
shimSocketRoot: string;
+ snapshotterSocket: string;
};
export function probeContainedShimSocketDirectory(binding: {
diff --git a/scripts/contained-containerd-config.mjs b/scripts/contained-containerd-config.mjs
index 79ae341f..2b03d469 100644
--- a/scripts/contained-containerd-config.mjs
+++ b/scripts/contained-containerd-config.mjs
@@ -5,6 +5,7 @@ import { createServer } from "node:net";
import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
export const CONTAINERD_SHIM_SOCKET_DIR_MAX_LENGTH = 42;
+export const CONTAINED_RUNTIME_SNAPSHOTTER = "fuse-overlayfs";
function isContained(root, candidate) {
const fromRoot = relative(root, candidate);
@@ -13,15 +14,22 @@ function isContained(root, candidate) {
);
}
-export function renderContainedContainerdConfig(shimSocketDirectory) {
+export function renderContainedContainerdConfig(
+ shimSocketDirectory,
+ snapshotterSocket,
+) {
if (
typeof shimSocketDirectory !== "string" ||
!isAbsolute(shimSocketDirectory) ||
resolve(shimSocketDirectory) !== shimSocketDirectory ||
shimSocketDirectory.includes("'") ||
- shimSocketDirectory.length > CONTAINERD_SHIM_SOCKET_DIR_MAX_LENGTH
+ shimSocketDirectory.length > CONTAINERD_SHIM_SOCKET_DIR_MAX_LENGTH ||
+ typeof snapshotterSocket !== "string" ||
+ !isAbsolute(snapshotterSocket) ||
+ resolve(snapshotterSocket) !== snapshotterSocket ||
+ snapshotterSocket.includes("'")
) {
- throw new Error("containerd shim socket directory is invalid");
+ throw new Error("containerd socket configuration is invalid");
}
return [
@@ -38,10 +46,14 @@ export function renderContainedContainerdConfig(shimSocketDirectory) {
" env = []",
` socket_dir = '${shimSocketDirectory}'`,
"",
+ `[proxy_plugins.'${CONTAINED_RUNTIME_SNAPSHOTTER}']`,
+ ' type = "snapshot"',
+ ` address = '${snapshotterSocket}'`,
+ "",
"[plugins.'io.containerd.transfer.v1.local']",
" [[plugins.'io.containerd.transfer.v1.local'.unpack_config]]",
' platform = "linux/amd64"',
- ' snapshotter = "native"',
+ ` snapshotter = "${CONTAINED_RUNTIME_SNAPSHOTTER}"`,
"",
].join("\n");
}
@@ -50,6 +62,7 @@ export function createContainedContainerdConfig({
configPath,
repositoryRoot,
shimSocketRoot,
+ snapshotterSocket,
}) {
const physicalRepositoryRoot = realpathSync(repositoryRoot);
const physicalShimSocketRoot = realpathSync(shimSocketRoot);
@@ -58,10 +71,12 @@ export function createContainedContainerdConfig({
physicalConfigParent,
basename(configPath),
);
+ const resolvedSnapshotterSocket = resolve(snapshotterSocket);
if (
physicalShimSocketRoot !== physicalRepositoryRoot ||
- !isContained(physicalRepositoryRoot, resolvedConfigPath)
+ !isContained(physicalRepositoryRoot, resolvedConfigPath) ||
+ !isContained(physicalRepositoryRoot, resolvedSnapshotterSocket)
) {
throw new Error("containerd configuration escaped the repository");
}
@@ -80,7 +95,10 @@ export function createContainedContainerdConfig({
}
writeFileSync(
resolvedConfigPath,
- renderContainedContainerdConfig(physicalShimSocketRoot),
+ renderContainedContainerdConfig(
+ physicalShimSocketRoot,
+ resolvedSnapshotterSocket,
+ ),
{
encoding: "utf8",
flag: "wx",
@@ -91,6 +109,7 @@ export function createContainedContainerdConfig({
return {
shimSocketDirectory: physicalShimSocketRoot,
shimSocketRoot: physicalShimSocketRoot,
+ snapshotterSocket: resolvedSnapshotterSocket,
};
}
diff --git a/scripts/contained-rootless-spec.mjs b/scripts/contained-rootless-spec.mjs
index dac263ca..7a82d62a 100644
--- a/scripts/contained-rootless-spec.mjs
+++ b/scripts/contained-rootless-spec.mjs
@@ -15,10 +15,13 @@ import {
import { isAbsolute, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
+import { CONTAINED_RUNTIME_SNAPSHOTTER } from "./contained-containerd-config.mjs";
+
const repositoryRoot = realpathSync(
resolve(fileURLToPath(import.meta.url), "../.."),
);
const namespace = "counterlab-v6.1";
+const containedProcessAddressSpaceBytes = 2 * 1024 * 1024 * 1024;
const containerIdPattern = /^[a-f0-9]{64}$/;
const supportedRlimits = new Set([
"RLIMIT_AS",
@@ -174,8 +177,8 @@ function assertNerdctlHooks(parsed, expected) {
`--hosts-dir=[${resolve(expected.sessionRoot, "config/certs.d")}]`,
`--n=${namespace}`,
`--namespace=${namespace}`,
- "--snapshotter=native",
- "--storage-driver=native",
+ `--snapshotter=${CONTAINED_RUNTIME_SNAPSHOTTER}`,
+ `--storage-driver=${CONTAINED_RUNTIME_SNAPSHOTTER}`,
];
for (const [name, event] of [
["createRuntime", "createRuntime"],
@@ -424,7 +427,7 @@ function validateReceiptResourceBindings(receipt) {
receipt.enforcedRlimits.map((entry) => [entry.type, entry.soft]),
);
if (
- byType.get("RLIMIT_AS") !== aggregate.memoryBytes ||
+ byType.get("RLIMIT_AS") !== containedProcessAddressSpaceBytes ||
byType.get("RLIMIT_NPROC") !== aggregate.maxProcesses ||
byType.get("RLIMIT_NOFILE") !== 64 ||
(byType.get("RLIMIT_CPU") ?? 0) < 1 ||
@@ -1597,7 +1600,7 @@ export function sanitizeContainedRootlessSpec({
mode: "containerd-ephemeral-writable-snapshot-readonly-runtime",
mountPath: imageRootfsPath,
parentChainId: expected.imageAuthority.rootfsChainId,
- snapshotter: "native",
+ snapshotter: CONTAINED_RUNTIME_SNAPSHOTTER,
},
removedFields: [
...(hasNerdctlHooks ? ["hooks"] : []),
@@ -1691,7 +1694,7 @@ export function validateContainedContainerInfo({
"contained rootless container metadata snapshot key changed",
);
}
- if (metadata.Snapshotter !== "native") {
+ if (metadata.Snapshotter !== CONTAINED_RUNTIME_SNAPSHOTTER) {
throw new Error(
"contained rootless container metadata snapshotter changed",
);
@@ -2025,7 +2028,7 @@ function assertConfigImageRootfs(config, specRoot, receipt) {
binding.mountPath !== expectedPath ||
!/^sha256:[a-f0-9]{64}$/.test(binding.parentChainId ?? "") ||
binding.parentChainId !== imageAuthority.rootfsChainId ||
- binding.snapshotter !== "native" ||
+ binding.snapshotter !== CONTAINED_RUNTIME_SNAPSHOTTER ||
Object.keys(imageAuthority).length !== 9 ||
!Array.isArray(imageAuthority.layerDigests) ||
imageAuthority.layerDigests.length < 1 ||
diff --git a/scripts/contained-runtime-attestation.mjs b/scripts/contained-runtime-attestation.mjs
index a84184a5..06558895 100644
--- a/scripts/contained-runtime-attestation.mjs
+++ b/scripts/contained-runtime-attestation.mjs
@@ -30,6 +30,7 @@ export const RUNTIME_HELPER_PATHS = Object.freeze({
attestationWriter: "scripts/write-contained-runtime-attestation.mjs",
runtimeSupervisor: "scripts/contained-runtime-supervisor.mjs",
supervisorProtocol: "scripts/contained-runtime-supervisor-protocol.mjs",
+ fuseMountWrapper: "scripts/runtime-bin/mount.fuse3",
runcWrapper: "scripts/runtime-bin/runc",
containerdConfigWriter: "scripts/contained-containerd-config.mjs",
runtimeServer: "scripts/contained-runtime-server.mjs",
@@ -82,6 +83,19 @@ export function createPublicContainedRuntimeAttestation({
attestation,
componentSha256,
}) {
+ const publicComponentNames = [
+ "buildctl",
+ "buildkitd",
+ "containerd",
+ "containerd-shim-runc-v2",
+ "ctr",
+ "nerdctl",
+ "rootlesskit",
+ "runc",
+ ];
+ const publicComponentSha256 = Object.fromEntries(
+ publicComponentNames.map((name) => [name, componentSha256[name]]),
+ );
return {
schemaVersion: "2",
status: "VERIFIED",
@@ -92,7 +106,7 @@ export function createPublicContainedRuntimeAttestation({
adapterSha256: attestation.adapterSha256,
runtimePolicySha256: attestation.runtimePolicySha256,
proofDependencyManifestSha256: attestation.proofDependencyManifestSha256,
- componentSha256,
+ componentSha256: publicComponentSha256,
fileSha256: {
containerdConfig: attestation.fileSha256.containerdConfig,
buildkitConfig: attestation.fileSha256.buildkitConfig,
diff --git a/scripts/contained-runtime-run.d.mts b/scripts/contained-runtime-run.d.mts
index 90c4b336..48acf505 100644
--- a/scripts/contained-runtime-run.d.mts
+++ b/scripts/contained-runtime-run.d.mts
@@ -132,6 +132,10 @@ export declare function validateContainedImageRootfsSnapshot(
expectedParentChainId: string,
): Record;
+export declare function validateContainedRunnerRootfsPermissions(
+ entries: unknown,
+): unknown[];
+
export declare function validateContainedRunControlReceipt(
value: unknown,
): ContainedRunControlReceipt;
@@ -174,6 +178,15 @@ export declare function executeContainedRun(
createInvocationId?: () => string,
now?: () => number,
qualificationCoordinator?: ContainedCgroupQualificationCoordinator,
+ verifyRunnerRootfs?: (
+ plan: ContainedRuntimeRunPlan,
+ imageRootfsPath: string,
+ ) => void,
+ setRootfsMountpointMode?: (
+ plan: ContainedRuntimeRunPlan,
+ imageRootfsPath: string,
+ mode: 0o700 | 0o755,
+ ) => void,
): Promise<{
status: number;
stdout: Buffer;
diff --git a/scripts/contained-runtime-run.mjs b/scripts/contained-runtime-run.mjs
index 92f3f010..3b3bb8a5 100644
--- a/scripts/contained-runtime-run.mjs
+++ b/scripts/contained-runtime-run.mjs
@@ -3,7 +3,9 @@
import { spawnSync } from "node:child_process";
import { createHash, randomBytes } from "node:crypto";
import {
+ chmodSync,
existsSync,
+ lstatSync,
readFileSync,
readdirSync,
realpathSync,
@@ -30,6 +32,7 @@ import {
verifyContainedReadOnlyMounts,
} from "./contained-image-authority.mjs";
import { AGGREGATE_TIMEOUT_QUALIFICATION_MODE } from "./contained-runtime-request.mjs";
+import { CONTAINED_RUNTIME_SNAPSHOTTER } from "./contained-containerd-config.mjs";
const repositoryRoot = realpathSync(
resolve(fileURLToPath(import.meta.url), "../.."),
@@ -75,6 +78,11 @@ if (
const shortCommandTimeoutMs = runtimePolicy.shortCommandTimeoutMs;
const executionControlOverheadMs = runtimePolicy.executionControlOverheadMs;
const cleanupReserveMs = runtimePolicy.cleanupReserveMs;
+// runc 1.4.x is a Go program and cannot reliably initialize its seccomp
+// filter under a 1 GiB virtual-address ceiling. Keep resident memory bounded
+// independently by the stricter container cgroup while retaining a finite
+// per-process address-space ceiling.
+export const CONTAINED_PROCESS_ADDRESS_SPACE_BYTES = 2 * 1024 * 1024 * 1024;
export const CONTAINED_RUNTIME_CONTROL_BUDGET_SECONDS =
(executionControlOverheadMs + cleanupReserveMs) / 1_000;
export const CONTAINED_RUNTIME_CALLER_GRACE_SECONDS =
@@ -282,7 +290,7 @@ function resourceIntent(args) {
memoryBytes > 1024 * 1024 * 1024 ||
byType.get("RLIMIT_CPU") < 1 ||
byType.get("RLIMIT_CPU") > 300 ||
- byType.get("RLIMIT_AS") !== memoryBytes ||
+ byType.get("RLIMIT_AS") !== CONTAINED_PROCESS_ADDRESS_SPACE_BYTES ||
byType.get("RLIMIT_FSIZE") > 1_048_576 ||
byType.get("RLIMIT_NOFILE") !== 64 ||
byType.get("RLIMIT_NPROC") !== maxProcesses
@@ -425,7 +433,7 @@ export function containedRunPlan({
"--namespace",
namespace,
"--snapshotter",
- "native",
+ CONTAINED_RUNTIME_SNAPSHOTTER,
"--data-root",
resolve(sessionRoot, "data/nerdctl"),
"--cgroup-manager",
@@ -476,7 +484,7 @@ export function containedRunPlan({
"images",
"mount",
"--snapshotter",
- "native",
+ CONTAINED_RUNTIME_SNAPSHOTTER,
"--platform",
"linux/amd64",
"--rw",
@@ -489,7 +497,7 @@ export function containedRunPlan({
"images",
"unmount",
"--snapshotter",
- "native",
+ CONTAINED_RUNTIME_SNAPSHOTTER,
"--rm",
],
},
@@ -523,11 +531,23 @@ export function containedRunPlan({
},
inspectSnapshot: {
program: ctr,
- args: [...ctrGlobalArgs, "snapshots", "--snapshotter", "native", "info"],
+ args: [
+ ...ctrGlobalArgs,
+ "snapshots",
+ "--snapshotter",
+ CONTAINED_RUNTIME_SNAPSHOTTER,
+ "info",
+ ],
},
inspectSnapshotDiff: {
program: ctr,
- args: [...ctrGlobalArgs, "snapshots", "--snapshotter", "native", "diff"],
+ args: [
+ ...ctrGlobalArgs,
+ "snapshots",
+ "--snapshotter",
+ CONTAINED_RUNTIME_SNAPSHOTTER,
+ "diff",
+ ],
},
cleanupStaging: {
program: resolve(binRoot, "nerdctl"),
@@ -547,7 +567,7 @@ export function containedRunPlan({
...ctrGlobalArgs,
"snapshots",
"--snapshotter",
- "native",
+ CONTAINED_RUNTIME_SNAPSHOTTER,
"delete",
],
},
@@ -560,7 +580,7 @@ export function containedRunPlan({
"--fifo-dir",
clientFifoRoot,
"--snapshotter",
- "native",
+ CONTAINED_RUNTIME_SNAPSHOTTER,
"--runtime",
"io.containerd.runc.v2",
"--runc-binary",
@@ -825,6 +845,109 @@ function validateMountedImageRootfs(
return lines[0];
}
+const runnerRootfsPermissionContract = Object.freeze([
+ // The session parent remains private. The mounted root itself must be
+ // traversable by the image's non-root process, while all image-owned
+ // descendants retain their exact OCI ownership and executable modes.
+ { path: "/", kind: "directory", mode: 0o755 },
+ { path: "/usr", kind: "directory", mode: 0o755 },
+ { path: "/usr/local", kind: "directory", mode: 0o755 },
+ { path: "/usr/local/bin", kind: "directory", mode: 0o755 },
+ { path: "/usr/local/bin/node", kind: "file", mode: 0o555 },
+ { path: "/usr/bin", kind: "directory", mode: 0o755 },
+ { path: "/usr/bin/bwrap", kind: "file", mode: 0o555 },
+ { path: "/usr/bin/setpriv", kind: "file", mode: 0o555 },
+ { path: "/usr/bin/bash", kind: "file", mode: 0o555 },
+ { path: "/usr/lib", kind: "directory", mode: 0o755 },
+ { path: "/usr/lib64", kind: "directory", mode: 0o755 },
+ { path: "/usr/lib/x86_64-linux-gnu", kind: "directory", mode: 0o755 },
+ {
+ path: "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
+ kind: "file",
+ mode: 0o755,
+ },
+ { path: "/app", kind: "directory", mode: 0o555 },
+ { path: "/app/runner.mjs", kind: "file", mode: 0o555 },
+ { path: "/etc", kind: "directory", mode: 0o755 },
+ { path: "/etc/passwd", kind: "file", mode: 0o444 },
+ { path: "/etc/group", kind: "file", mode: 0o444 },
+ { path: "/etc/hosts", kind: "file", mode: 0o644 },
+ { path: "/repo", kind: "directory", mode: 0o555 },
+ { path: "/repo/scripts", kind: "directory", mode: 0o555 },
+ {
+ path: "/repo/scripts/verify_scientific_runtime.py",
+ kind: "file",
+ mode: 0o444,
+ },
+ { path: "/dev/pts", kind: "directory", mode: 0o755 },
+ { path: "/dev/shm", kind: "directory", mode: 0o755 },
+ { path: "/dev/mqueue", kind: "directory", mode: 0o755 },
+ { path: "/sys/fs/cgroup", kind: "directory", mode: 0o755 },
+ { path: "/counterlab-runtime", kind: "directory", mode: 0o755 },
+]);
+
+export function validateContainedRunnerRootfsPermissions(entries) {
+ if (
+ !Array.isArray(entries) ||
+ entries.length !== runnerRootfsPermissionContract.length
+ ) {
+ throw new Error("contained runner rootfs permission evidence is invalid");
+ }
+ for (
+ let index = 0;
+ index < runnerRootfsPermissionContract.length;
+ index += 1
+ ) {
+ const expected = runnerRootfsPermissionContract[index];
+ const observed = entries[index];
+ if (
+ observed === null ||
+ typeof observed !== "object" ||
+ Array.isArray(observed) ||
+ JSON.stringify(Object.keys(observed).sort()) !==
+ JSON.stringify(["gid", "kind", "mode", "path", "uid"].sort())
+ ) {
+ throw new Error("contained runner rootfs permissions are unsafe");
+ }
+ if (
+ observed.path !== expected.path ||
+ observed.kind !== expected.kind ||
+ observed.mode !== expected.mode ||
+ observed.uid !== 0 ||
+ observed.gid !== 0
+ ) {
+ throw new Error(
+ `contained runner rootfs permissions are unsafe at ${expected.path}: expected ${expected.kind} ${expected.mode.toString(8)} 0:0, observed ${observed.kind} ${Number.isSafeInteger(observed.mode) ? observed.mode.toString(8) : "invalid"} ${Number.isSafeInteger(observed.uid) ? observed.uid : "invalid"}:${Number.isSafeInteger(observed.gid) ? observed.gid : "invalid"}`,
+ );
+ }
+ }
+ return entries;
+}
+
+function verifyContainedRunnerRootfsPermissions(plan, imageRootfsPath) {
+ if (!plan.image.startsWith("counterlab-runner:git-")) return;
+ validateContainedRunnerRootfsPermissions(
+ runnerRootfsPermissionContract.map((expected) => {
+ const metadata = statSync(
+ expected.path === "/"
+ ? imageRootfsPath
+ : resolve(imageRootfsPath, expected.path.slice(1)),
+ );
+ return {
+ path: expected.path,
+ kind: metadata.isDirectory()
+ ? "directory"
+ : metadata.isFile()
+ ? "file"
+ : "other",
+ mode: metadata.mode & 0o777,
+ uid: metadata.uid,
+ gid: metadata.gid,
+ };
+ }),
+ );
+}
+
export function validateContainedImageRootfsSnapshot(
source,
imageRootfsPath,
@@ -863,17 +986,57 @@ function snapshotDiffSha256(result) {
result.stdout.byteLength === 0 ||
result.stdout.byteLength > 4 * 1024 * 1024
) {
- throw new Error("contained runtime image rootfs diff failed");
+ const detail = Buffer.from(result.stderr ?? "")
+ .toString("utf8")
+ .trim()
+ .slice(0, 4_096);
+ throw new Error(
+ detail.length === 0
+ ? "contained runtime image rootfs diff failed"
+ : `contained runtime image rootfs diff failed: ${detail}`,
+ );
}
return sha256(result.stdout);
}
+function setImageRootfsMountpointMode(plan, imageRootfsPath, mode) {
+ if (
+ ![0o700, 0o755].includes(mode) ||
+ typeof imageRootfsPath !== "string" ||
+ !isAbsolute(imageRootfsPath) ||
+ !contained(plan.expected.sessionRoot, imageRootfsPath)
+ ) {
+ throw new Error("contained runtime image rootfs mount point is invalid");
+ }
+ const before = lstatSync(imageRootfsPath);
+ if (
+ before.isSymbolicLink() ||
+ !before.isDirectory() ||
+ before.uid !== process.getuid()
+ ) {
+ throw new Error("contained runtime image rootfs mount point is invalid");
+ }
+ chmodSync(imageRootfsPath, mode);
+ const after = lstatSync(imageRootfsPath);
+ if (
+ after.isSymbolicLink() ||
+ !after.isDirectory() ||
+ after.uid !== process.getuid() ||
+ (after.mode & 0o777) !== mode
+ ) {
+ throw new Error(
+ "contained runtime image rootfs mount point mode is invalid",
+ );
+ }
+}
+
function cleanupImageRootfs(
plan,
imageRootfsPath,
expectedParentChainId,
spawn,
options,
+ setMountpointMode,
) {
if (
typeof imageRootfsPath !== "string" ||
@@ -895,6 +1058,17 @@ function cleanupImageRootfs(
);
if (before.status !== 0) {
const absent = containedRuntimeResourceAbsent(before);
+ try {
+ if (absent) setMountpointMode(plan, imageRootfsPath, 0o700);
+ } catch (error) {
+ return {
+ ok: false,
+ absent,
+ diagnostics: Buffer.from(
+ `${error instanceof Error ? error.message : "contained runtime image rootfs mount point restoration failed"}\n`,
+ ),
+ };
+ }
return {
ok: absent,
absent,
@@ -930,14 +1104,27 @@ function cleanupImageRootfs(
);
const absent = after.status !== 0 && containedRuntimeResourceAbsent(after);
const unmountAccepted = unmounted.status === 0;
+ let mountpointRestored = false;
+ let mountpointDiagnostics = Buffer.alloc(0);
+ if (unmountAccepted && absent) {
+ try {
+ setMountpointMode(plan, imageRootfsPath, 0o700);
+ mountpointRestored = true;
+ } catch (error) {
+ mountpointDiagnostics = Buffer.from(
+ `${error instanceof Error ? error.message : "contained runtime image rootfs mount point restoration failed"}\n`,
+ );
+ }
+ }
return {
- ok: unmountAccepted && absent,
+ ok: unmountAccepted && absent && mountpointRestored,
absent,
diagnostics: appendBuffers(
unmountAccepted ? Buffer.alloc(0) : unmounted.stderr,
unmountAccepted ? Buffer.alloc(0) : resultError(unmounted),
absent ? Buffer.alloc(0) : after.stderr,
absent ? Buffer.alloc(0) : resultError(after),
+ mountpointDiagnostics,
),
};
}
@@ -1043,6 +1230,8 @@ export async function executeContainedRun(
createInvocationId = () => randomBytes(32).toString("hex"),
now = () => performance.now(),
qualificationCoordinator = containedCgroupQualificationCoordinator,
+ verifyRunnerRootfs = verifyContainedRunnerRootfsPermissions,
+ setRootfsMountpointMode = setImageRootfsMountpointMode,
) {
if (
context.qualificationMode !== null &&
@@ -1088,6 +1277,7 @@ export async function executeContainedRun(
imageAuthority.rootfsChainId,
cleanupSpawn,
cleanupOptions,
+ setRootfsMountpointMode,
);
lastRootfsCleanup = rootfsCleanup;
if (rootfsCleanup.ok) imageRootfsOwned = false;
@@ -1385,6 +1575,17 @@ export async function executeContainedRun(
);
}
imageRootfsOwned = true;
+ try {
+ setRootfsMountpointMode(plan, imageRootfsPath, 0o755);
+ } catch (error) {
+ return finish(
+ failedResult(
+ error instanceof Error
+ ? error.message
+ : "contained runtime image rootfs mount point preparation failed",
+ ),
+ );
+ }
const mountedImageRootfs = executeSpawn(
plan.mountImageRootfs.program,
[...plan.mountImageRootfs.args, plan.imageAlias, imageRootfsPath],
@@ -1412,6 +1613,11 @@ export async function executeContainedRun(
imageRootfsPath,
imageAuthority.rootfsChainId,
);
+ // fuse-overlayfs derives the merged-root mode from its private active
+ // snapshot directory. Normalize only that disposable active root before
+ // taking the immutable baseline; the session parent remains mode 0700.
+ setRootfsMountpointMode(plan, imageRootfsPath, 0o755);
+ verifyRunnerRootfs(plan, imageRootfsPath);
imageRootfsBaselineSha256 = snapshotDiffSha256(
executeSpawn(
plan.inspectSnapshotDiff.program,
@@ -1572,6 +1778,7 @@ export async function executeContainedRun(
imageAuthority.rootfsChainId,
cleanupSpawn,
cleanupOptions,
+ setRootfsMountpointMode,
);
lastRootfsCleanup = rootfsCleaned;
if (rootfsCleaned.ok) imageRootfsOwned = false;
diff --git a/scripts/contained-runtime-server.mjs b/scripts/contained-runtime-server.mjs
index d9643e2b..63d69ae8 100644
--- a/scripts/contained-runtime-server.mjs
+++ b/scripts/contained-runtime-server.mjs
@@ -9,6 +9,7 @@ import {
readFileSync,
readdirSync,
realpathSync,
+ statfsSync,
statSync,
} from "node:fs";
import { createServer } from "node:net";
@@ -20,6 +21,7 @@ import {
executeContainedRun,
} from "./contained-runtime-run.mjs";
import {
+ CONTAINED_RUNTIME_SNAPSHOTTER,
createContainedContainerdConfig,
probeContainedShimSocketDirectory,
} from "./contained-containerd-config.mjs";
@@ -47,11 +49,20 @@ const installRoot = resolve(
const binRoot = resolve(installRoot, "bin");
const runtimeWrapperRoot = resolve(root, "scripts/runtime-bin");
const runcWrapper = resolve(runtimeWrapperRoot, "runc");
+const fuseMountWrapper = resolve(runtimeWrapperRoot, "mount.fuse3");
const runcBinary = resolve(binRoot, "runc");
const runcStateRoot = resolve(sessionRoot, "run/runc");
const containerdSocket = resolve(sessionRoot, "run/containerd.sock");
const commandSocket = resolve(sessionRoot, "run/runtime-command.sock");
const clientFifoRoot = resolve(sessionRoot, "run/client-fifo");
+const snapshotterSocket = resolve(sessionRoot, "run/inner/fuse-overlayfs.sock");
+const snapshotterRoot = resolve(sessionRoot, "data/fuse-overlayfs");
+const rootlessSpecRoot = resolve(sessionRoot, "run/rootless-specs");
+const tmpfsMagic = 0x01021994;
+
+// RootlessKit's private copy-up mount may normalize the mountpoint mode.
+// Restore the exact private state-root mode before admitting runc.
+chmodSync(runcStateRoot, 0o700);
function canonicalJson(value) {
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
@@ -69,11 +80,13 @@ function sha256(value) {
}
const runcWrapperMetadata = statSync(runcWrapper);
+const fuseMountWrapperMetadata = statSync(fuseMountWrapper);
const runtimeWrapperMetadata = statSync(runtimeWrapperRoot);
const runcStateMetadata = statSync(runcStateRoot);
const runtimeWrapperEntries = readdirSync(runtimeWrapperRoot).sort();
if (
- JSON.stringify(runtimeWrapperEntries) !== JSON.stringify(["runc"]) ||
+ JSON.stringify(runtimeWrapperEntries) !==
+ JSON.stringify(["mount.fuse3", "runc"]) ||
!runtimeWrapperMetadata.isDirectory() ||
runtimeWrapperMetadata.uid !== process.getuid() ||
(runtimeWrapperMetadata.mode & 0o022) !== 0 ||
@@ -83,6 +96,12 @@ if (
runcWrapperMetadata.uid !== process.getuid() ||
(runcWrapperMetadata.mode & 0o100) === 0 ||
(runcWrapperMetadata.mode & 0o022) !== 0 ||
+ lstatSync(fuseMountWrapper).isSymbolicLink() ||
+ realpathSync(fuseMountWrapper) !== fuseMountWrapper ||
+ !fuseMountWrapperMetadata.isFile() ||
+ fuseMountWrapperMetadata.uid !== process.getuid() ||
+ (fuseMountWrapperMetadata.mode & 0o100) === 0 ||
+ (fuseMountWrapperMetadata.mode & 0o022) !== 0 ||
realpathSync(runcStateRoot) !== runcStateRoot ||
!runcStateMetadata.isDirectory() ||
runcStateMetadata.uid !== process.getuid() ||
@@ -216,7 +235,14 @@ function createDrainReceipt(args, stdin) {
);
const snapshots = spawnSync(
ctr,
- [...base, "snapshots", "--snapshotter", "native", "list", "--quiet"],
+ [
+ ...base,
+ "snapshots",
+ "--snapshotter",
+ CONTAINED_RUNTIME_SNAPSHOTTER,
+ "list",
+ "--quiet",
+ ],
options,
);
const images = spawnSync(
@@ -280,7 +306,7 @@ function createDrainReceipt(args, stdin) {
...base,
"snapshots",
"--snapshotter",
- "native",
+ CONTAINED_RUNTIME_SNAPSHOTTER,
"info",
snapshotIdentity,
],
@@ -338,9 +364,91 @@ const shimSocketBinding = createContainedContainerdConfig({
configPath: resolve(sessionRoot, "config/containerd.toml"),
repositoryRoot: root,
shimSocketRoot: root,
+ snapshotterSocket,
});
await probeContainedShimSocketDirectory(shimSocketBinding);
+const snapshotterBinary = resolve(binRoot, "containerd-fuse-overlayfs-grpc");
+const fuseOverlayfsBinary = resolve(binRoot, "fuse-overlayfs");
+const snapshotterRootFilesystem = statfsSync(snapshotterRoot);
+const rootlessSpecFilesystem = statfsSync(rootlessSpecRoot);
+const runcStateFilesystem = statfsSync(runcStateRoot);
+if (
+ snapshotterRootFilesystem.type !== tmpfsMagic ||
+ rootlessSpecFilesystem.type !== tmpfsMagic ||
+ runcStateFilesystem.type !== tmpfsMagic
+) {
+ throw new Error("contained runtime private backing filesystem is invalid");
+}
+chmodSync(snapshotterRoot, 0o700);
+chmodSync(rootlessSpecRoot, 0o700);
+const snapshotterRootMetadata = statSync(snapshotterRoot);
+const rootlessSpecMetadata = statSync(rootlessSpecRoot);
+for (const [path, label] of [
+ [snapshotterBinary, "snapshotter"],
+ [fuseOverlayfsBinary, "fuse-overlayfs"],
+]) {
+ const metadata = lstatSync(path);
+ if (
+ metadata.isSymbolicLink() ||
+ !metadata.isFile() ||
+ metadata.uid !== process.getuid() ||
+ (metadata.mode & 0o100) === 0 ||
+ (metadata.mode & 0o022) !== 0 ||
+ realpathSync(path) !== path
+ ) {
+ throw new Error(`contained ${label} binary is invalid`);
+ }
+}
+if (
+ !snapshotterRootMetadata.isDirectory() ||
+ snapshotterRootMetadata.uid !== process.getuid() ||
+ (snapshotterRootMetadata.mode & 0o777) !== 0o700 ||
+ !rootlessSpecMetadata.isDirectory() ||
+ rootlessSpecMetadata.uid !== process.getuid() ||
+ (rootlessSpecMetadata.mode & 0o777) !== 0o700
+) {
+ throw new Error("contained snapshotter root is invalid");
+}
+
+const snapshotter = spawn(
+ snapshotterBinary,
+ [snapshotterSocket, snapshotterRoot],
+ {
+ cwd: root,
+ env: environment,
+ stdio: ["ignore", "inherit", "inherit"],
+ },
+);
+
+await new Promise((accept, reject) => {
+ let attempts = 0;
+ const timer = setInterval(() => {
+ attempts += 1;
+ try {
+ if (statSync(snapshotterSocket).isSocket()) {
+ clearInterval(timer);
+ accept();
+ return;
+ }
+ } catch {
+ // The snapshotter is still starting.
+ }
+ if (attempts >= 300) {
+ clearInterval(timer);
+ reject(new Error("contained snapshotter socket did not become ready"));
+ }
+ }, 100);
+ snapshotter.once("exit", (code, signal) => {
+ clearInterval(timer);
+ reject(
+ new Error(
+ `contained snapshotter exited during startup (${String(code ?? signal)})`,
+ ),
+ );
+ });
+});
+
const containerd = spawn(
resolve(binRoot, "containerd"),
[
@@ -480,7 +588,7 @@ const server = createServer({ allowHalfOpen: true }, (socket) => {
"--namespace",
"counterlab-v6.1",
"--snapshotter",
- "native",
+ CONTAINED_RUNTIME_SNAPSHOTTER,
"--data-root",
resolve(sessionRoot, "data/nerdctl"),
"--cgroup-manager",
@@ -542,11 +650,21 @@ await new Promise((accept, reject) => {
const shutdown = () => {
server.close();
containerd.kill("SIGTERM");
+ snapshotter.kill("SIGTERM");
};
process.once("SIGINT", shutdown);
process.once("SIGTERM", shutdown);
containerd.once("exit", (code) => {
+ if (snapshotter.exitCode === null && snapshotter.signalCode === null) {
+ snapshotter.kill("SIGTERM");
+ }
server.close(() => {
process.exitCode = code ?? 1;
});
});
+snapshotter.once("exit", (code) => {
+ if (containerd.exitCode === null && containerd.signalCode === null) {
+ containerd.kill("SIGTERM");
+ process.exitCode = code ?? 1;
+ }
+});
diff --git a/scripts/contained-runtime-supervisor.mjs b/scripts/contained-runtime-supervisor.mjs
index 44916c45..01fa4489 100644
--- a/scripts/contained-runtime-supervisor.mjs
+++ b/scripts/contained-runtime-supervisor.mjs
@@ -59,6 +59,7 @@ const buildkitProxySocket = resolve(runRoot, "buildkitd.sock");
const buildkitInnerSocket = resolve(innerRunRoot, "buildkitd.sock");
const runtimeCommandSocket = resolve(runRoot, "runtime-command.sock");
const containerdSocket = resolve(runRoot, "containerd.sock");
+const runcStateRoot = resolve(runRoot, "runc");
const containerdRootlesskitApi = resolve(
runRoot,
"containerd-rootless/api.sock",
@@ -72,7 +73,7 @@ const environment = createContainedRuntimeEnvironment({
buildkitSocket: buildkitProxySocket,
home: resolve(sessionRoot, "home"),
runcBinary: resolve(binRoot, "runc"),
- runcStateRoot: resolve(runRoot, "runc"),
+ runcStateRoot,
runtimeWrapperRoot: resolve(root, "scripts/runtime-bin"),
tmp: resolve(sessionRoot, "tmp"),
xdgCache: resolve(sessionRoot, "xdg-cache"),
@@ -142,11 +143,16 @@ await waitForInitialSocket(
);
const containerdLog = openPrivateLog("containerd.log");
+const snapshotterRoot = resolve(sessionRoot, "data/fuse-overlayfs");
+const rootlessSpecRoot = resolve(runRoot, "rootless-specs");
const containerdRootlesskit = spawn(
resolve(binRoot, "rootlesskit"),
[
`--state-dir=${resolve(runRoot, "containerd-rootless")}`,
"--net=host",
+ `--copy-up=${snapshotterRoot}`,
+ `--copy-up=${rootlessSpecRoot}`,
+ `--copy-up=${runcStateRoot}`,
"--pidns",
"--cgroupns",
"--evacuate-cgroup2=containerd",
diff --git a/scripts/contained-runtime-supervisor.test.ts b/scripts/contained-runtime-supervisor.test.ts
index ecd674b7..50bbb2c5 100644
--- a/scripts/contained-runtime-supervisor.test.ts
+++ b/scripts/contained-runtime-supervisor.test.ts
@@ -97,6 +97,14 @@ describe("contained runtime supervisor protocol", () => {
expect(launch).toContain('"--pidns"');
expect(launch).toContain('"--cgroupns"');
expect(launch).toContain('"--evacuate-cgroup2=containerd"');
+ expect(launch).toContain("`--copy-up=${snapshotterRoot}`");
+ expect(launch).toContain("`--copy-up=${rootlessSpecRoot}`");
+ expect(launch.indexOf("`--copy-up=${snapshotterRoot}`")).toBeLessThan(
+ launch.indexOf("scripts/contained-runtime-server.mjs"),
+ );
+ expect(launch.indexOf("`--copy-up=${rootlessSpecRoot}`")).toBeLessThan(
+ launch.indexOf("scripts/contained-runtime-server.mjs"),
+ );
expect(launch.indexOf('"--pidns"')).toBeLessThan(
launch.indexOf("scripts/contained-runtime-server.mjs"),
);
@@ -131,6 +139,12 @@ describe("contained runtime supervisor protocol", () => {
source.indexOf("closeSync(containerdLog)"),
),
).toContain('"--evacuate-cgroup2=containerd"');
+ expect(
+ source.slice(
+ containerdLaunch,
+ source.indexOf("closeSync(containerdLog)"),
+ ),
+ ).toContain("`--copy-up=${runcStateRoot}`");
});
it("accepts only exact hash-bound status, drain, and shutdown requests", () => {
diff --git a/scripts/contained-runtime.test.ts b/scripts/contained-runtime.test.ts
index c62f3c5e..909ceeab 100644
--- a/scripts/contained-runtime.test.ts
+++ b/scripts/contained-runtime.test.ts
@@ -6,6 +6,7 @@ import {
readFileSync,
readdirSync,
realpathSync,
+ statSync,
symlinkSync,
writeFileSync,
} from "node:fs";
@@ -21,6 +22,7 @@ import {
containedRunPlan,
executeContainedRun,
validateContainedImageRootfsSnapshot,
+ validateContainedRunnerRootfsPermissions,
validateContainedRunControlReceipt,
} from "./contained-runtime-run.mjs";
import {
@@ -40,6 +42,7 @@ import {
} from "./contained-rootless-spec.mjs";
import {
CONTAINERD_SHIM_SOCKET_DIR_MAX_LENGTH,
+ CONTAINED_RUNTIME_SNAPSHOTTER,
createContainedContainerdConfig,
renderContainedContainerdConfig,
} from "./contained-containerd-config.mjs";
@@ -103,7 +106,7 @@ function startupCommand(): string[] {
"--memory-swap=1024m",
"--cpus=2.0",
"--ulimit=cpu=300:300",
- "--ulimit=as=1073741824:1073741824",
+ "--ulimit=as=2147483648:2147483648",
"--ulimit=fsize=1048576:1048576",
"--ulimit=nofile=64:64",
"--ulimit=nproc=32:32",
@@ -141,7 +144,7 @@ function scientificRuntimeCommand(): string[] {
"--memory-swap=1024m",
"--cpus=2.0",
"--ulimit=cpu=300:300",
- "--ulimit=as=1073741824:1073741824",
+ "--ulimit=as=2147483648:2147483648",
"--ulimit=fsize=1048576:1048576",
"--ulimit=nofile=64:64",
"--ulimit=nproc=32:32",
@@ -187,7 +190,7 @@ function reachabilityCommand(): string[] {
"--memory-swap=1024m",
"--cpus=2.0",
"--ulimit=cpu=300:300",
- "--ulimit=as=1073741824:1073741824",
+ "--ulimit=as=2147483648:2147483648",
"--ulimit=fsize=1048576:1048576",
"--ulimit=nofile=64:64",
"--ulimit=nproc=32:32",
@@ -235,7 +238,7 @@ function boundedAdapterCommand(): string[] {
"--memory-swap=512m",
"--cpus=1.0",
"--ulimit=cpu=20:20",
- "--ulimit=as=536870912:536870912",
+ "--ulimit=as=2147483648:2147483648",
"--ulimit=fsize=262144:262144",
"--ulimit=nofile=64:64",
"--ulimit=nproc=16:16",
@@ -553,8 +556,8 @@ function rootlessSpec(
`--hosts-dir=[${resolve(expected.sessionRoot, "config/certs.d")}]`,
"--n=counterlab-v6.1",
"--namespace=counterlab-v6.1",
- "--snapshotter=native",
- "--storage-driver=native",
+ `--snapshotter=${CONTAINED_RUNTIME_SNAPSHOTTER}`,
+ `--storage-driver=${CONTAINED_RUNTIME_SNAPSHOTTER}`,
"internal",
"oci-hook",
];
@@ -745,7 +748,7 @@ function containerMetadata(
Labels: labels,
Runtime: { Name: "io.containerd.runc.v2" },
SnapshotKey: containerId,
- Snapshotter: "native",
+ Snapshotter: CONTAINED_RUNTIME_SNAPSHOTTER,
});
}
@@ -988,7 +991,8 @@ function qualifiedRuntimeHarness() {
describe("contained runtime command policy", () => {
it("renders a bounded containerd shim manager configuration", () => {
- const config = renderContainedContainerdConfig(root);
+ const snapshotterSocket = resolve(root, ".rt/runtime-snapshotter.sock");
+ const config = renderContainedContainerdConfig(root, snapshotterSocket);
expect(root.length).toBeLessThanOrEqual(
CONTAINERD_SHIM_SOCKET_DIR_MAX_LENGTH,
@@ -997,11 +1001,21 @@ describe("contained runtime command policy", () => {
expect(config).toContain("imports = []");
expect(config).toContain("[plugins.'io.containerd.shim.v1.manager']");
expect(config).toContain(`socket_dir = '${root}'`);
+ expect(config).toContain(
+ `[proxy_plugins.'${CONTAINED_RUNTIME_SNAPSHOTTER}']`,
+ );
+ expect(config).toContain(`address = '${snapshotterSocket}'`);
+ expect(config).toContain(
+ `snapshotter = "${CONTAINED_RUNTIME_SNAPSHOTTER}"`,
+ );
expect(config).toContain("'io.containerd.grpc.v1.cri'");
expect(config).toContain("'io.containerd.nri.v1.nri'");
expect(() =>
- renderContainedContainerdConfig(resolve(root, "shim-sockets")),
- ).toThrow(/directory is invalid/u);
+ renderContainedContainerdConfig(
+ resolve(root, "shim-sockets"),
+ snapshotterSocket,
+ ),
+ ).toThrow(/socket configuration is invalid/u);
});
it("configures the full Linux shim socket path in the repository", () => {
@@ -1015,6 +1029,7 @@ describe("contained runtime command policy", () => {
configPath: resolve(fixtureRoot, "containerd.toml"),
repositoryRoot: root,
shimSocketRoot: root,
+ snapshotterSocket: resolve(fixtureRoot, "fuse-overlayfs.sock"),
});
expect(binding.shimSocketDirectory.length).toBeLessThanOrEqual(42);
expect(realpathSync(binding.shimSocketDirectory)).toBe(realpathSync(root));
@@ -1037,6 +1052,7 @@ describe("contained runtime command policy", () => {
configPath: resolve(sourceRoot, "containerd.toml"),
repositoryRoot: sourceRoot,
shimSocketRoot: shimHostRoot,
+ snapshotterSocket: resolve(sourceRoot, "fuse-overlayfs.sock"),
}),
).toThrow(/configuration escaped the repository/u);
});
@@ -1166,6 +1182,7 @@ describe("contained runtime command policy", () => {
it("reserves the candidate wall-clock limit for the candidate process", async () => {
const harness = qualifiedRuntimeHarness();
+ const rootfsModes: number[] = [];
const coordinator = {
async begin(input: { finalContainerId: string; invocationId: string }) {
return input;
@@ -1195,10 +1212,13 @@ describe("contained runtime command policy", () => {
() => invocationId,
undefined,
coordinator,
+ () => undefined,
+ (_plan, _path, mode) => rootfsModes.push(mode),
);
expect(harness.commandTimeouts.create).toBeGreaterThan(20_000);
expect(harness.commandTimeouts.run).toBe(20_000);
+ expect(rootfsModes).toEqual([0o755, 0o755, 0o700]);
});
it("binds a clean timed-out run to the aggregate-qualified receipt", async () => {
@@ -1246,6 +1266,8 @@ describe("contained runtime command policy", () => {
() => invocationId,
undefined,
coordinator,
+ () => undefined,
+ () => undefined,
);
expect(result.status).toBe(1);
@@ -1327,6 +1349,8 @@ describe("contained runtime command policy", () => {
() => invocationId,
undefined,
coordinator,
+ () => undefined,
+ () => undefined,
);
expect(result.status).toBe(1);
@@ -1434,7 +1458,24 @@ describe("contained runtime command policy", () => {
});
it("uses one exact wrapper and scrubs inherited runtime injection", () => {
- expect(readdirSync(runtimeWrapperRoot).sort()).toEqual(["runc"]);
+ expect(readdirSync(runtimeWrapperRoot).sort()).toEqual([
+ "mount.fuse3",
+ "runc",
+ ]);
+ const fuseMountWrapper = readFileSync(
+ resolve(runtimeWrapperRoot, "mount.fuse3"),
+ "utf8",
+ );
+ expect(fuseMountWrapper).toContain('OPTIONS=("allow_other")');
+ expect(fuseMountWrapper).toContain(
+ "rootless-tools/install-v2.3.1/bin/fuse-overlayfs",
+ );
+ expect(fuseMountWrapper).toContain(
+ '"${MOUNT_MODE}" == "containerd" || "${MOUNT_MODE}" == "unpack"',
+ );
+ expect(fuseMountWrapper).toContain(
+ "UPPER_SEEN == 0 && WORK_SEEN == 0 && READ_ONLY_SEEN == 0",
+ );
const environment = createContainedRuntimeEnvironment({
auth: resolve(root, ".rt/rt-runc-test/auth"),
@@ -1570,7 +1611,7 @@ describe("contained runtime command policy", () => {
expect(plan.create.args).toContain("create");
expect(plan.create.args).not.toContain("run");
- expect(plan.create.args).not.toContain("--ulimit=as=1073741824:1073741824");
+ expect(plan.create.args).not.toContain("--ulimit=as=2147483648:2147483648");
expect(
plan.create.args.filter((argument) => argument.startsWith("--ulimit=")),
).toHaveLength(4);
@@ -1581,7 +1622,7 @@ describe("contained runtime command policy", () => {
).toEqual(
startupCommand()
.slice(1, -1)
- .filter((argument) => argument !== "--ulimit=as=1073741824:1073741824"),
+ .filter((argument) => argument !== "--ulimit=as=2147483648:2147483648"),
);
expect(plan.start.program).toBe(resolve(installRoot, "bin/ctr"));
const cgroupIndex = plan.start.args.indexOf("--cgroup");
@@ -1613,7 +1654,7 @@ describe("contained runtime command policy", () => {
"images",
"mount",
"--snapshotter",
- "native",
+ CONTAINED_RUNTIME_SNAPSHOTTER,
"--platform",
"linux/amd64",
"--rw",
@@ -1625,7 +1666,7 @@ describe("contained runtime command policy", () => {
"images",
"unmount",
"--snapshotter",
- "native",
+ CONTAINED_RUNTIME_SNAPSHOTTER,
"--rm",
]),
});
@@ -1640,7 +1681,7 @@ describe("contained runtime command policy", () => {
.update(
canonicalJson(
startupCommand().filter(
- (argument) => argument !== "--ulimit=as=1073741824:1073741824",
+ (argument) => argument !== "--ulimit=as=2147483648:2147483648",
),
),
)
@@ -1649,13 +1690,13 @@ describe("contained runtime command policy", () => {
expect(fullCommandAuthority.commandSha256).not.toBe(stagingCommandSha256);
const splitUlimitCommand = startupCommand();
const addressSpaceIndex = splitUlimitCommand.indexOf(
- "--ulimit=as=1073741824:1073741824",
+ "--ulimit=as=2147483648:2147483648",
);
splitUlimitCommand.splice(
addressSpaceIndex,
1,
"--ulimit",
- "as=1073741824:1073741824",
+ "as=2147483648:2147483648",
);
const splitUlimitPlan = containedRunPlan({
args: splitUlimitCommand,
@@ -1667,15 +1708,15 @@ describe("contained runtime command policy", () => {
sessionRoot,
});
expect(splitUlimitPlan.create.args).not.toContain(
- "as=1073741824:1073741824",
+ "as=2147483648:2147483648",
);
expect(splitUlimitPlan.expected.rlimits).toHaveLength(5);
const invalidAddressSpaceCommands = [
startupCommand().filter(
- (argument) => argument !== "--ulimit=as=1073741824:1073741824",
+ (argument) => argument !== "--ulimit=as=2147483648:2147483648",
),
startupCommand().map((argument) =>
- argument === "--ulimit=as=1073741824:1073741824"
+ argument === "--ulimit=as=2147483648:2147483648"
? "--ulimit=as=536870912:536870912"
: argument,
),
@@ -1684,7 +1725,7 @@ describe("contained runtime command policy", () => {
command.splice(
command.length - 1,
0,
- "--ulimit=as=1073741824:1073741824",
+ "--ulimit=as=2147483648:2147483648",
);
return command;
})(),
@@ -1734,12 +1775,129 @@ describe("contained runtime command policy", () => {
containedRuntimeResourceAbsent({
status: 1,
stdout: Buffer.alloc(0),
- stderr: Buffer.from("snapshotter native not found\n"),
+ stderr: Buffer.from("snapshotter fuse-overlayfs not found\n"),
}),
).toBe(false);
expect(containedRuntimeResourceAbsent(missing())).toBe(true);
});
+ it("fails closed when the imported runner loses non-root executable modes", () => {
+ const safe = [
+ { path: "/", kind: "directory", mode: 0o755, uid: 0, gid: 0 },
+ { path: "/usr", kind: "directory", mode: 0o755, uid: 0, gid: 0 },
+ {
+ path: "/usr/local",
+ kind: "directory",
+ mode: 0o755,
+ uid: 0,
+ gid: 0,
+ },
+ {
+ path: "/usr/local/bin",
+ kind: "directory",
+ mode: 0o755,
+ uid: 0,
+ gid: 0,
+ },
+ {
+ path: "/usr/local/bin/node",
+ kind: "file",
+ mode: 0o555,
+ uid: 0,
+ gid: 0,
+ },
+ { path: "/usr/bin", kind: "directory", mode: 0o755, uid: 0, gid: 0 },
+ { path: "/usr/bin/bwrap", kind: "file", mode: 0o555, uid: 0, gid: 0 },
+ { path: "/usr/bin/setpriv", kind: "file", mode: 0o555, uid: 0, gid: 0 },
+ { path: "/usr/bin/bash", kind: "file", mode: 0o555, uid: 0, gid: 0 },
+ { path: "/usr/lib", kind: "directory", mode: 0o755, uid: 0, gid: 0 },
+ {
+ path: "/usr/lib64",
+ kind: "directory",
+ mode: 0o755,
+ uid: 0,
+ gid: 0,
+ },
+ {
+ path: "/usr/lib/x86_64-linux-gnu",
+ kind: "directory",
+ mode: 0o755,
+ uid: 0,
+ gid: 0,
+ },
+ {
+ path: "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
+ kind: "file",
+ mode: 0o755,
+ uid: 0,
+ gid: 0,
+ },
+ { path: "/app", kind: "directory", mode: 0o555, uid: 0, gid: 0 },
+ {
+ path: "/app/runner.mjs",
+ kind: "file",
+ mode: 0o555,
+ uid: 0,
+ gid: 0,
+ },
+ { path: "/etc", kind: "directory", mode: 0o755, uid: 0, gid: 0 },
+ { path: "/etc/passwd", kind: "file", mode: 0o444, uid: 0, gid: 0 },
+ { path: "/etc/group", kind: "file", mode: 0o444, uid: 0, gid: 0 },
+ { path: "/etc/hosts", kind: "file", mode: 0o644, uid: 0, gid: 0 },
+ { path: "/repo", kind: "directory", mode: 0o555, uid: 0, gid: 0 },
+ {
+ path: "/repo/scripts",
+ kind: "directory",
+ mode: 0o555,
+ uid: 0,
+ gid: 0,
+ },
+ {
+ path: "/repo/scripts/verify_scientific_runtime.py",
+ kind: "file",
+ mode: 0o444,
+ uid: 0,
+ gid: 0,
+ },
+ { path: "/dev/pts", kind: "directory", mode: 0o755, uid: 0, gid: 0 },
+ { path: "/dev/shm", kind: "directory", mode: 0o755, uid: 0, gid: 0 },
+ {
+ path: "/dev/mqueue",
+ kind: "directory",
+ mode: 0o755,
+ uid: 0,
+ gid: 0,
+ },
+ {
+ path: "/sys/fs/cgroup",
+ kind: "directory",
+ mode: 0o755,
+ uid: 0,
+ gid: 0,
+ },
+ {
+ path: "/counterlab-runtime",
+ kind: "directory",
+ mode: 0o755,
+ uid: 0,
+ gid: 0,
+ },
+ ];
+
+ expect(validateContainedRunnerRootfsPermissions(safe)).toEqual(safe);
+ expect(() =>
+ validateContainedRunnerRootfsPermissions(
+ safe.map((entry) =>
+ entry.path === "/usr/local/bin/node"
+ ? { ...entry, mode: 0o700, uid: 1000, gid: 1000 }
+ : entry,
+ ),
+ ),
+ ).toThrow(
+ "contained runner rootfs permissions are unsafe at /usr/local/bin/node: expected file 555 0:0, observed file 700 1000:1000",
+ );
+ });
+
it("retains bounded cgroup intent without claiming observed enforcement", () => {
const sessionRoot = resolve(root, ".rt/rt-validator-spec");
const installRoot = resolve(
@@ -1857,7 +2015,7 @@ describe("contained runtime command policy", () => {
`run/rootless-specs/${invocationId}.image-rootfs`,
),
parentChainId: fixture.authority.rootfsChainId,
- snapshotter: "native",
+ snapshotter: CONTAINED_RUNTIME_SNAPSHOTTER,
},
removedFields: [
"hooks",
@@ -1912,8 +2070,8 @@ describe("contained runtime command policy", () => {
(spec) => {
spec.process.rlimits.push({
type: "RLIMIT_AS",
- soft: plan.expected.memoryBytes,
- hard: plan.expected.memoryBytes,
+ soft: 2 * 1024 * 1024 * 1024,
+ hard: 2 * 1024 * 1024 * 1024,
});
},
(spec) => {
@@ -2699,7 +2857,7 @@ describe("contained runtime command policy", () => {
addressSpace.hard -= 1;
},
(receipt) => {
- receipt.intendedAggregateLimits.memoryBytes -= 1;
+ receipt.intendedAggregateLimits.memoryBytes = 2 * 1024 * 1024 * 1024;
},
(receipt) => {
receipt.normalizedFields = receipt.normalizedFields.filter(
@@ -2774,13 +2932,19 @@ describe("contained runtime command policy", () => {
sessionRoot,
}),
).toThrow(/rlimit/u);
- const persisted = persistContainedRootlessSpec({
- config: prepared.config,
- finalContainerId: prepared.finalContainerId,
- internalMounts: prepared.internalMounts,
- receipt: prepared.receipt,
- sessionRoot,
- });
+ const previousUmask = process.umask(0o077);
+ let persisted: ReturnType;
+ try {
+ persisted = persistContainedRootlessSpec({
+ config: prepared.config,
+ finalContainerId: prepared.finalContainerId,
+ internalMounts: prepared.internalMounts,
+ receipt: prepared.receipt,
+ sessionRoot,
+ });
+ } finally {
+ process.umask(previousUmask);
+ }
const binding = {
...persisted,
finalContainerId: prepared.finalContainerId,
@@ -2799,6 +2963,7 @@ describe("contained runtime command policy", () => {
)
.map((mount) => mount.source);
expect(internalSources).toEqual([]);
+ expect(statSync(persisted.imageRootfsPath).mode & 0o777).toBe(0o700);
expect(() => verifyPersistedContainedRootlessSpec(binding)).not.toThrow();
expect(() =>
verifyPersistedContainedRootlessSpec({
@@ -2901,7 +3066,7 @@ describe("contained runtime command policy", () => {
memoryBytes: 1024 * 1024 * 1024,
rlimits: [
{ type: "RLIMIT_CPU", soft: 300, hard: 300 },
- { type: "RLIMIT_AS", soft: 1073741824, hard: 1073741824 },
+ { type: "RLIMIT_AS", soft: 2147483648, hard: 2147483648 },
{ type: "RLIMIT_FSIZE", soft: 1048576, hard: 1048576 },
{ type: "RLIMIT_NOFILE", soft: 64, hard: 64 },
{ type: "RLIMIT_NPROC", soft: 32, hard: 32 },
@@ -3021,6 +3186,8 @@ describe("contained runtime command policy", () => {
() => invocationId,
undefined,
qualificationCoordinator,
+ () => undefined,
+ () => undefined,
);
expect(result.status).toBe(1);
@@ -3315,7 +3482,12 @@ describe("contained runtime command policy", () => {
"[[plugins.'io.containerd.transfer.v1.local'.unpack_config]]",
);
expect(containerdConfigWriter).toContain('platform = "linux/amd64"');
- expect(containerdConfigWriter).toContain('snapshotter = "native"');
+ expect(containerdConfigWriter).toContain(
+ 'CONTAINED_RUNTIME_SNAPSHOTTER = "fuse-overlayfs"',
+ );
+ expect(containerdConfigWriter).toContain(
+ 'snapshotter = "${CONTAINED_RUNTIME_SNAPSHOTTER}"',
+ );
expect(containerdConfigWriter).toContain(
"[plugins.'io.containerd.shim.v1.manager']",
);
diff --git a/scripts/normalize_runner_oci.py b/scripts/normalize_runner_oci.py
index 9738d6b4..42a4e536 100644
--- a/scripts/normalize_runner_oci.py
+++ b/scripts/normalize_runner_oci.py
@@ -22,7 +22,7 @@
from typing import Any, BinaryIO, Mapping, Sequence
-POLICY_VERSION = "counterlab-runner-nonroot-v2"
+POLICY_VERSION = "counterlab-runner-nonroot-v3"
RUNNER_UID = 10001
RUNNER_GID = 10001
@@ -144,6 +144,70 @@ def _normalized_identity(
if path == "usr/local/bin/codex":
return (0, 0, 0o777, "root", "root")
+ if _under(path, "usr/bin"):
+ return (
+ 0,
+ 0,
+ _mode_for(member, directory=0o755, regular=0o555),
+ "root",
+ "root",
+ )
+
+ if path == "usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2":
+ return (0, 0, 0o755, "root", "root")
+
+ if _under(path, "usr/lib"):
+ return (
+ 0,
+ 0,
+ _mode_for(member, directory=0o755, regular=0o444),
+ "root",
+ "root",
+ )
+
+ if _under(path, "usr/share") or _under(path, "repo") or _under(path, "etc/ssl"):
+ return (
+ 0,
+ 0,
+ _mode_for(member, directory=0o555, regular=0o444),
+ "root",
+ "root",
+ )
+
+ if path in {
+ "usr/lib64",
+ "dev",
+ "dev/pts",
+ "dev/shm",
+ "dev/mqueue",
+ "sys",
+ "sys/fs",
+ "sys/fs/cgroup",
+ "counterlab-runtime",
+ "etc",
+ }:
+ return (
+ 0,
+ 0,
+ _mode_for(member, directory=0o755, regular=0o755),
+ "root",
+ "root",
+ )
+
+ if path == "etc/hosts":
+ return (0, 0, 0o644, "root", "root")
+
+ if path in {
+ "etc/passwd",
+ "etc/group",
+ "etc/nsswitch.conf",
+ "etc/hostname",
+ "etc/resolv.conf",
+ "etc/ld.so.cache",
+ "etc/ca-certificates.conf",
+ }:
+ return (0, 0, 0o444, "root", "root")
+
if path in {
"usr",
"usr/local",
diff --git a/scripts/runtime-bin/mount.fuse3 b/scripts/runtime-bin/mount.fuse3
new file mode 100644
index 00000000..954e0f66
--- /dev/null
+++ b/scripts/runtime-bin/mount.fuse3
@@ -0,0 +1,122 @@
+#!/bin/bash -p
+set -euo pipefail
+umask 077
+
+fail() {
+ echo "contained FUSE mount wrapper rejected the invocation: $1" >&2
+ exit 2
+}
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)"
+[[ -f "${ROOT_DIR}/COUNTERLAB_REPO_ROOT" ]] || fail "repository marker is missing"
+
+FUSE_OVERLAYFS_BINARY="${ROOT_DIR}/node_modules/.cache/counterlab-v6.1/rootless-tools/install-v2.3.1/bin/fuse-overlayfs"
+[[ -f "${FUSE_OVERLAYFS_BINARY}" && -x "${FUSE_OVERLAYFS_BINARY}" && ! -L "${FUSE_OVERLAYFS_BINARY}" ]] || fail "fuse-overlayfs binary is not pinned"
+(( $# >= 6 )) || fail "arguments are incomplete"
+[[ "$1" == "overlay" ]] || fail "source is not the snapshotter overlay"
+TARGET="$2"
+shift 2
+
+SESSION_SEGMENT="${TARGET#"${ROOT_DIR}/.rt/"}"
+SESSION_SEGMENT="${SESSION_SEGMENT%%/*}"
+[[ "${SESSION_SEGMENT}" =~ ^rt-[a-z0-9][a-z0-9-]{7,13}$ ]] || fail "runtime session ID is invalid"
+SNAPSHOT_ROOT="${ROOT_DIR}/.rt/${SESSION_SEGMENT}/data/fuse-overlayfs/snapshots"
+CHECK_ROOT=""
+case "${TARGET}" in
+ "${ROOT_DIR}/.rt/${SESSION_SEGMENT}"/run/rootless-specs/*.image-rootfs)
+ MOUNT_MODE="runtime"
+ ;;
+ "${ROOT_DIR}/.rt/${SESSION_SEGMENT}"/data/containerd/tmpmounts/containerd-mount*)
+ MOUNT_MODE="unpack"
+ [[ "${TARGET}" =~ ^${ROOT_DIR}/\.rt/${SESSION_SEGMENT}/data/containerd/tmpmounts/containerd-mount[0-9]+$ ]] || fail "unpack target is invalid"
+ ;;
+ "${ROOT_DIR}/.rt/${SESSION_SEGMENT}"/tmp/initialC*)
+ MOUNT_MODE="view"
+ [[ "${TARGET}" =~ ^${ROOT_DIR}/\.rt/${SESSION_SEGMENT}/tmp/initialC[0-9]+$ ]] || fail "image view target is invalid"
+ ;;
+ "${ROOT_DIR}/.rt/${SESSION_SEGMENT}"/run/inner/containerd-mount*)
+ MOUNT_MODE="containerd"
+ [[ "${TARGET}" =~ ^${ROOT_DIR}/\.rt/${SESSION_SEGMENT}/run/inner/containerd-mount[0-9]+$ ]] || fail "containerd target is invalid"
+ ;;
+ "${ROOT_DIR}/.rt/${SESSION_SEGMENT}"/state/containerd/io.containerd.mount-manager.v1.bolt/t/*/*)
+ MOUNT_MODE="mount-manager"
+ [[ "${TARGET}" =~ ^${ROOT_DIR}/\.rt/${SESSION_SEGMENT}/state/containerd/io\.containerd\.mount-manager\.v1\.bolt/t/[0-9]+/[0-9]+$ ]] || fail "mount-manager target is invalid"
+ ;;
+ "${ROOT_DIR}/.rt/${SESSION_SEGMENT}"/data/fuse-overlayfs/fuseoverlayfs-check*/merged)
+ MOUNT_MODE="check"
+ CHECK_ROOT="${TARGET%/merged}"
+ [[ "${CHECK_ROOT}" =~ ^${ROOT_DIR}/\.rt/${SESSION_SEGMENT}/data/fuse-overlayfs/fuseoverlayfs-check[0-9]+$ ]] || fail "check target is invalid"
+ ;;
+ *) fail "target escaped the runtime session" ;;
+esac
+
+OPTIONS=("allow_other")
+LOWER_SEEN=0
+UPPER_SEEN=0
+WORK_SEEN=0
+TYPE_SEEN=0
+READ_ONLY_SEEN=0
+while (( $# > 0 )); do
+ case "$1" in
+ -o)
+ (( $# >= 2 )) || fail "mount option is missing"
+ case "$2" in
+ lowerdir=*)
+ (( LOWER_SEEN == 0 )) || fail "lowerdir is duplicated"
+ LOWER_SEEN=1
+ LOWER_PATHS="${2#lowerdir=}"
+ IFS=':' read -r -a LOWER_ENTRIES <<<"${LOWER_PATHS}"
+ (( ${#LOWER_ENTRIES[@]} >= 1 )) || fail "lowerdir is empty"
+ for ENTRY in "${LOWER_ENTRIES[@]}"; do
+ if [[ "${MOUNT_MODE}" != "check" ]]; then
+ [[ "${ENTRY}" =~ ^${SNAPSHOT_ROOT}/[0-9]+/fs$ ]] || fail "lowerdir escaped the snapshotter root"
+ else
+ [[ "${ENTRY}" == "${CHECK_ROOT}/lower1" || "${ENTRY}" == "${CHECK_ROOT}/lower2" ]] || fail "check lowerdir escaped its root"
+ fi
+ done
+ ;;
+ upperdir=*)
+ [[ "${MOUNT_MODE}" == "runtime" || "${MOUNT_MODE}" == "unpack" || "${MOUNT_MODE}" == "containerd" || "${MOUNT_MODE}" == "mount-manager" ]] || fail "read-only mount has an upperdir"
+ (( UPPER_SEEN == 0 )) || fail "upperdir is duplicated"
+ UPPER_SEEN=1
+ [[ "${2#upperdir=}" =~ ^${SNAPSHOT_ROOT}/[0-9]+/fs$ ]] || fail "upperdir escaped the snapshotter root"
+ ;;
+ workdir=*)
+ [[ "${MOUNT_MODE}" == "runtime" || "${MOUNT_MODE}" == "unpack" || "${MOUNT_MODE}" == "containerd" || "${MOUNT_MODE}" == "mount-manager" ]] || fail "read-only mount has a workdir"
+ (( WORK_SEEN == 0 )) || fail "workdir is duplicated"
+ WORK_SEEN=1
+ [[ "${2#workdir=}" =~ ^${SNAPSHOT_ROOT}/[0-9]+/work$ ]] || fail "workdir escaped the snapshotter root"
+ ;;
+ ro)
+ [[ ( "${MOUNT_MODE}" == "containerd" || "${MOUNT_MODE}" == "unpack" ) && "${READ_ONLY_SEEN}" == 0 ]] || fail "read-only option is not approved"
+ READ_ONLY_SEEN=1
+ ;;
+ *) fail "mount option is not approved" ;;
+ esac
+ OPTIONS+=("$2")
+ shift 2
+ ;;
+ -t)
+ (( $# == 2 && TYPE_SEEN == 0 )) || fail "filesystem type position is invalid"
+ [[ "$2" == "fuse-overlayfs" ]] || fail "filesystem type is not approved"
+ TYPE_SEEN=1
+ shift 2
+ ;;
+ *) fail "argument is not approved" ;;
+ esac
+done
+
+if [[ "${MOUNT_MODE}" == "runtime" || "${MOUNT_MODE}" == "mount-manager" ]]; then
+ (( LOWER_SEEN == 1 && UPPER_SEEN == 1 && WORK_SEEN == 1 && TYPE_SEEN == 1 && READ_ONLY_SEEN == 0 )) || fail "snapshot mount contract is incomplete"
+elif [[ "${MOUNT_MODE}" == "unpack" ]]; then
+ (( LOWER_SEEN == 1 && TYPE_SEEN == 1 && (( UPPER_SEEN == 1 && WORK_SEEN == 1 ) || ( UPPER_SEEN == 0 && WORK_SEEN == 0 && READ_ONLY_SEEN == 0 )) )) || fail "temporary snapshot mount contract is incomplete"
+elif [[ "${MOUNT_MODE}" == "containerd" ]]; then
+ (( LOWER_SEEN == 1 && UPPER_SEEN == 1 && WORK_SEEN == 1 && TYPE_SEEN == 1 && READ_ONLY_SEEN == 1 )) || fail "containerd mount contract is incomplete"
+elif [[ "${MOUNT_MODE}" == "view" ]]; then
+ (( LOWER_SEEN == 1 && UPPER_SEEN == 0 && WORK_SEEN == 0 && TYPE_SEEN == 1 )) || fail "image view mount contract is incomplete"
+else
+ (( LOWER_SEEN == 1 && ${#LOWER_ENTRIES[@]} == 2 && UPPER_SEEN == 0 && WORK_SEEN == 0 && TYPE_SEEN == 1 )) || fail "snapshotter check mount contract is incomplete"
+fi
+printf -v OPTION_STRING '%s,' "${OPTIONS[@]}"
+OPTION_STRING="${OPTION_STRING%,}"
+exec -- "${FUSE_OVERLAYFS_BINARY}" -o "${OPTION_STRING}" "${TARGET}"
diff --git a/scripts/runtime-toolchain-lock.json b/scripts/runtime-toolchain-lock.json
index 82463943..7272cdf7 100644
--- a/scripts/runtime-toolchain-lock.json
+++ b/scripts/runtime-toolchain-lock.json
@@ -20,6 +20,10 @@
"version": "2.3.1",
"sha256": "1419e955fe919dcbc9c0bc813852d0e75b61a04d3a60da02c145214fbf96c281"
},
+ "containerd-fuse-overlayfs-grpc": {
+ "version": "2.1.7",
+ "sha256": "e048f9cc616299544fcdcd647cfecba10f60169537721c57e3628ac6c8a0c5c7"
+ },
"containerd-shim-runc-v2": {
"version": "2.3.1",
"sha256": "3d07a9da185c493f87dc72f88f8a90b71a2fdb7707588c060cc10cbe5a3bae89"
@@ -28,6 +32,10 @@
"version": "2.3.1",
"sha256": "ae1b50af36651b995e0affc500fefd90ae9c085d9e69f7f617eb766d9b7d36ed"
},
+ "fuse-overlayfs": {
+ "version": "1.16",
+ "sha256": "30c6b9e192600d6854e13397974c709d7cabd980b7d1a4d67defd8eb69677e91"
+ },
"nerdctl": {
"version": "2.3.1",
"sha256": "d55cafc6b8a682530a02bc882653535f6e119cadc7623ec058a1154d5b1c5980"
diff --git a/scripts/setup-contained-runtime.sh b/scripts/setup-contained-runtime.sh
index c79fe2c3..0296aefa 100644
--- a/scripts/setup-contained-runtime.sh
+++ b/scripts/setup-contained-runtime.sh
@@ -97,8 +97,10 @@ required = {
"bin/buildctl",
"bin/buildkitd",
"bin/containerd",
+ "bin/containerd-fuse-overlayfs-grpc",
"bin/containerd-shim-runc-v2",
"bin/ctr",
+ "bin/fuse-overlayfs",
"bin/nerdctl",
"bin/rootlesskit",
"bin/runc",
@@ -148,8 +150,10 @@ PY
buildctl \
buildkitd \
containerd \
+ containerd-fuse-overlayfs-grpc \
containerd-shim-runc-v2 \
ctr \
+ fuse-overlayfs \
nerdctl \
rootlesskit \
runc; do
@@ -168,8 +172,10 @@ const executableNames = [
"buildctl",
"buildkitd",
"containerd",
+ "containerd-fuse-overlayfs-grpc",
"containerd-shim-runc-v2",
"ctr",
+ "fuse-overlayfs",
"nerdctl",
"rootlesskit",
"runc",
@@ -220,8 +226,10 @@ const componentVersions = {
buildctl: "0.30.0",
buildkitd: "0.30.0",
containerd: "2.3.1",
+ "containerd-fuse-overlayfs-grpc": "2.1.7",
"containerd-shim-runc-v2": "2.3.1",
ctr: "2.3.1",
+ "fuse-overlayfs": "1.16",
nerdctl: "2.3.1",
rootlesskit: "3.0.0",
runc: "1.4.2",
@@ -235,7 +243,10 @@ exactKeys(attestation, [
"executableSha256",
], "runtime installation attestation");
const componentNames = Object.keys(componentVersions);
-const legacyAttestedNames = componentNames.filter((name) => name !== "ctr");
+const legacyAttestedNames = componentNames.filter(
+ (name) =>
+ !["ctr", "containerd-fuse-overlayfs-grpc", "fuse-overlayfs"].includes(name),
+);
const attestedNames = Object.keys(attestation.executableSha256).sort();
if (
JSON.stringify(attestedNames) !== JSON.stringify(componentNames.sort()) &&
diff --git a/scripts/start-contained-runtime.sh b/scripts/start-contained-runtime.sh
index 52d02af2..937e4b7f 100644
--- a/scripts/start-contained-runtime.sh
+++ b/scripts/start-contained-runtime.sh
@@ -40,11 +40,13 @@ SESSION_ROOT="${SESSION_PARENT}/${SESSION_ID}"
BIN_ROOT="${INSTALL_ROOT}/bin"
CONTAINERD_ROOTLESSKIT_API="${SESSION_ROOT}/run/containerd-rootless/api.sock"
CONTAINERD_SOCKET="${SESSION_ROOT}/run/containerd.sock"
+SNAPSHOTTER_SOCKET="${SESSION_ROOT}/run/inner/fuse-overlayfs.sock"
RUNTIME_COMMAND_SOCKET="${SESSION_ROOT}/run/runtime-command.sock"
BUILDKIT_SOCKET="${SESSION_ROOT}/run/buildkitd.sock"
BUILDKIT_INNER_SOCKET="${SESSION_ROOT}/run/inner/buildkitd.sock"
BUILDKIT_OTEL_SOCKET="${SESSION_ROOT}/run/inner/buildkit-otel.sock"
RUNC_STATE_ROOT="${SESSION_ROOT}/run/runc"
+ROOTLESS_SPEC_ROOT="${SESSION_ROOT}/run/rootless-specs"
SUPERVISOR_SOCKET="${SESSION_ROOT}/run/runtime-supervisor.sock"
SUPERVISOR_READY="${SESSION_ROOT}/run/runtime-supervisor-ready.json"
CONTAINERD_CONFIG="${SESSION_ROOT}/config/containerd.toml"
@@ -71,12 +73,14 @@ mkdir -p \
"${SESSION_ROOT}/config/cni" \
"${SESSION_ROOT}/data/buildkit" \
"${SESSION_ROOT}/data/containerd" \
+ "${SESSION_ROOT}/data/fuse-overlayfs" \
"${SESSION_ROOT}/data/nerdctl" \
"${SESSION_ROOT}/home" \
"${SESSION_ROOT}/logs" \
"${SESSION_ROOT}/run" \
"${SESSION_ROOT}/run/client-fifo" \
"${SESSION_ROOT}/run/inner" \
+ "${ROOTLESS_SPEC_ROOT}" \
"${RUNC_STATE_ROOT}" \
"${SESSION_ROOT}/state/containerd" \
"${SESSION_ROOT}/tmp" \
@@ -88,11 +92,13 @@ chmod 700 \
"${SESSION_ROOT}/auth" \
"${SESSION_ROOT}/config" \
"${SESSION_ROOT}/data" \
+ "${SESSION_ROOT}/data/fuse-overlayfs" \
"${SESSION_ROOT}/home" \
"${SESSION_ROOT}/logs" \
"${SESSION_ROOT}/run" \
"${SESSION_ROOT}/run/client-fifo" \
"${SESSION_ROOT}/run/inner" \
+ "${ROOTLESS_SPEC_ROOT}" \
"${RUNC_STATE_ROOT}" \
"${SESSION_ROOT}/state" \
"${SESSION_ROOT}/tmp" \
@@ -141,7 +147,7 @@ nohup node "${ROOT_DIR}/scripts/contained-runtime-supervisor.mjs" \
SUPERVISOR_PID=$!
for _ in {1..300}; do
- if [[ -S "${CONTAINERD_ROOTLESSKIT_API}" && -S "${CONTAINERD_SOCKET}" && -S "${RUNTIME_COMMAND_SOCKET}" && -S "${BUILDKIT_SOCKET}" && -S "${BUILDKIT_INNER_SOCKET}" && -S "${SUPERVISOR_SOCKET}" && -f "${SUPERVISOR_READY}" ]]; then
+ if [[ -S "${CONTAINERD_ROOTLESSKIT_API}" && -S "${CONTAINERD_SOCKET}" && -S "${SNAPSHOTTER_SOCKET}" && -S "${RUNTIME_COMMAND_SOCKET}" && -S "${BUILDKIT_SOCKET}" && -S "${BUILDKIT_INNER_SOCKET}" && -S "${SUPERVISOR_SOCKET}" && -f "${SUPERVISOR_READY}" ]]; then
break
fi
if ! SUPERVISOR_LIVENESS="$(kill -0 "${SUPERVISOR_PID}" 2>&1)"; then
@@ -150,7 +156,7 @@ for _ in {1..300}; do
fi
sleep 0.1
done
-[[ -S "${CONTAINERD_ROOTLESSKIT_API}" && -S "${CONTAINERD_SOCKET}" && -S "${RUNTIME_COMMAND_SOCKET}" && -S "${BUILDKIT_SOCKET}" && -S "${BUILDKIT_INNER_SOCKET}" && -S "${SUPERVISOR_SOCKET}" && -f "${SUPERVISOR_READY}" ]] || {
+[[ -S "${CONTAINERD_ROOTLESSKIT_API}" && -S "${CONTAINERD_SOCKET}" && -S "${SNAPSHOTTER_SOCKET}" && -S "${RUNTIME_COMMAND_SOCKET}" && -S "${BUILDKIT_SOCKET}" && -S "${BUILDKIT_INNER_SOCKET}" && -S "${SUPERVISOR_SOCKET}" && -f "${SUPERVISOR_READY}" ]] || {
echo "Contained runtime sockets did not become ready; retained logs are inside ${SESSION_ROOT}." >&2
exit 1
}
diff --git a/scripts/stop-contained-runtime.mjs b/scripts/stop-contained-runtime.mjs
index 6c3c771e..1c3f4c05 100644
--- a/scripts/stop-contained-runtime.mjs
+++ b/scripts/stop-contained-runtime.mjs
@@ -374,6 +374,7 @@ if (
const endpoints = [
resolve(sessionRoot, "run/runtime-command.sock"),
resolve(sessionRoot, "run/buildkitd.sock"),
+ resolve(sessionRoot, "run/inner/fuse-overlayfs.sock"),
supervisorSocket,
];
if (!(await waitForSocketClosure(endpoints))) {
diff --git a/scripts/test_normalize_runner_oci.py b/scripts/test_normalize_runner_oci.py
index 28e5a97d..9c00cad3 100644
--- a/scripts/test_normalize_runner_oci.py
+++ b/scripts/test_normalize_runner_oci.py
@@ -16,6 +16,42 @@ def _fixture_layer() -> bytes:
("usr/local/", tarfile.DIRTYPE, b""),
("usr/local/bin/", tarfile.DIRTYPE, b""),
("usr/local/bin/node", tarfile.REGTYPE, b"node"),
+ ("usr/bin/", tarfile.DIRTYPE, b""),
+ ("usr/bin/bwrap", tarfile.REGTYPE, b"bwrap"),
+ ("usr/bin/setpriv", tarfile.REGTYPE, b"setpriv"),
+ ("usr/bin/bash", tarfile.REGTYPE, b"bash"),
+ ("usr/lib/", tarfile.DIRTYPE, b""),
+ ("usr/lib64/", tarfile.DIRTYPE, b""),
+ ("usr/lib/x86_64-linux-gnu/", tarfile.DIRTYPE, b""),
+ ("usr/lib/x86_64-linux-gnu/libexample.so", tarfile.REGTYPE, b"lib"),
+ (
+ "usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
+ tarfile.REGTYPE,
+ b"loader",
+ ),
+ ("dev/", tarfile.DIRTYPE, b""),
+ ("dev/pts/", tarfile.DIRTYPE, b""),
+ ("dev/shm/", tarfile.DIRTYPE, b""),
+ ("dev/mqueue/", tarfile.DIRTYPE, b""),
+ ("sys/", tarfile.DIRTYPE, b""),
+ ("sys/fs/", tarfile.DIRTYPE, b""),
+ ("sys/fs/cgroup/", tarfile.DIRTYPE, b""),
+ ("counterlab-runtime/", tarfile.DIRTYPE, b""),
+ ("etc/", tarfile.DIRTYPE, b""),
+ ("etc/hosts", tarfile.REGTYPE, b""),
+ ("etc/passwd", tarfile.REGTYPE, b"user"),
+ ("etc/group", tarfile.REGTYPE, b"group"),
+ ("etc/shadow", tarfile.REGTYPE, b"secret"),
+ ("etc/ssl/", tarfile.DIRTYPE, b""),
+ ("etc/ssl/cert.pem", tarfile.REGTYPE, b"cert"),
+ ("usr/share/", tarfile.DIRTYPE, b""),
+ ("usr/share/data.txt", tarfile.REGTYPE, b"data"),
+ ("repo/", tarfile.DIRTYPE, b""),
+ ("repo/scripts/", tarfile.DIRTYPE, b""),
+ ("repo/scripts/verify.py", tarfile.REGTYPE, b"verify"),
+ ("repo/fixtures/", tarfile.DIRTYPE, b""),
+ ("repo/fixtures/public/", tarfile.DIRTYPE, b""),
+ ("repo/fixtures/public/sample.csv", tarfile.REGTYPE, b"sample"),
("app/", tarfile.DIRTYPE, b""),
("app/runner.mjs", tarfile.REGTYPE, b"runner"),
("app/config.json", tarfile.REGTYPE, b"{}"),
@@ -44,10 +80,42 @@ def test_rewrites_only_reviewed_runtime_metadata() -> None:
normalized, report = rewrite_layer_bytes(_fixture_layer())
members = _members(normalized)
- assert report.changed_entries == 11
+ assert report.changed_entries == 42
assert members["usr"].mode == 0o755
assert members["usr/local/bin/node"].mode == 0o555
assert members["usr/local/bin/node"].uid == 0
+ assert members["usr/lib"].mode == 0o755
+ assert members["usr/lib64"].mode == 0o755
+ assert members["usr/lib/x86_64-linux-gnu"].mode == 0o755
+ assert (
+ members["usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"].mode
+ == 0o755
+ )
+ assert members["usr/bin"].mode == 0o755
+ assert members["usr/bin/bwrap"].mode == 0o555
+ assert members["usr/bin/setpriv"].mode == 0o555
+ assert members["usr/bin/bash"].mode == 0o555
+ assert members["usr/lib/x86_64-linux-gnu/libexample.so"].mode == 0o444
+ assert members["usr/share"].mode == 0o555
+ assert members["usr/share/data.txt"].mode == 0o444
+ assert members["repo"].mode == 0o555
+ assert members["repo/scripts/verify.py"].mode == 0o444
+ for mount_target in (
+ "dev/pts",
+ "dev/shm",
+ "dev/mqueue",
+ "sys/fs/cgroup",
+ "counterlab-runtime",
+ ):
+ assert members[mount_target].mode == 0o755
+ assert members[mount_target].uid == 0
+ assert members[mount_target].gid == 0
+ assert members["etc/hosts"].mode == 0o644
+ assert members["etc/passwd"].mode == 0o444
+ assert members["etc/group"].mode == 0o444
+ assert members["etc/ssl"].mode == 0o555
+ assert members["etc/ssl/cert.pem"].mode == 0o444
+ assert members["etc/shadow"].mode == 0o700
assert members["app"].mode == 0o555
assert members["app"].uid == 0
assert members["app/runner.mjs"].mode == 0o555
diff --git a/scripts/timeout-cleanup-receipt.test.ts b/scripts/timeout-cleanup-receipt.test.ts
index c42023c5..2e0a6766 100644
--- a/scripts/timeout-cleanup-receipt.test.ts
+++ b/scripts/timeout-cleanup-receipt.test.ts
@@ -5,6 +5,7 @@ import { describe, expect, it } from "vitest";
import { canonicalJson } from "../packages/session-core/src/index.js";
import {
+ TIMEOUT_PROCESS_ADDRESS_SPACE_BYTES,
QUALIFIED_AGGREGATE_LIMIT_MODE,
TIMEOUT_ROOTLESS_RLIMIT_TYPES,
assertQualifiedAggregateRuntimeLimits,
@@ -116,8 +117,8 @@ describe("timeout cleanup aggregate resource authority", () => {
const enforcedRlimits = [
{
type: "RLIMIT_AS" as const,
- soft: intendedAggregateLimits.memoryBytes,
- hard: intendedAggregateLimits.memoryBytes,
+ soft: TIMEOUT_PROCESS_ADDRESS_SPACE_BYTES,
+ hard: TIMEOUT_PROCESS_ADDRESS_SPACE_BYTES,
},
{ type: "RLIMIT_CPU" as const, soft: 20, hard: 20 },
{ type: "RLIMIT_FSIZE" as const, soft: 262_144, hard: 262_144 },
diff --git a/scripts/timeout-cleanup-receipt.ts b/scripts/timeout-cleanup-receipt.ts
index 5571378f..7e41504a 100644
--- a/scripts/timeout-cleanup-receipt.ts
+++ b/scripts/timeout-cleanup-receipt.ts
@@ -173,6 +173,8 @@ export const TIMEOUT_ROOTLESS_RLIMIT_TYPES = [
"RLIMIT_NPROC",
] as const;
+export const TIMEOUT_PROCESS_ADDRESS_SPACE_BYTES = 2 * 1024 * 1024 * 1024;
+
const RlimitSchema = z
.strictObject({
type: z.enum(TIMEOUT_ROOTLESS_RLIMIT_TYPES),
@@ -200,7 +202,7 @@ export function assertRootlessRlimitBindings(input: {
.parse(input.enforcedRlimits);
const byType = new Map(limits.map((limit) => [limit.type, limit.soft]));
if (
- byType.get("RLIMIT_AS") !== intent.memoryBytes ||
+ byType.get("RLIMIT_AS") !== TIMEOUT_PROCESS_ADDRESS_SPACE_BYTES ||
byType.get("RLIMIT_NPROC") !== intent.maxProcesses ||
byType.get("RLIMIT_NOFILE") !== 64 ||
(byType.get("RLIMIT_CPU") ?? 0) < 1 ||
diff --git a/scripts/validate-contained-runtime-command.mjs b/scripts/validate-contained-runtime-command.mjs
index b4a50469..95e517d8 100644
--- a/scripts/validate-contained-runtime-command.mjs
+++ b/scripts/validate-contained-runtime-command.mjs
@@ -336,7 +336,7 @@ function validateRun(runArgs) {
options.get("--cpus")?.[0] !== "2.0" ||
!sameValues(options.get("--ulimit") ?? [], [
"cpu=300:300",
- "as=1073741824:1073741824",
+ "as=2147483648:2147483648",
"fsize=1048576:1048576",
"nofile=64:64",
"nproc=32:32",
@@ -395,7 +395,7 @@ function validateRun(runArgs) {
options.get("--cpus")?.[0] !== "2.0" ||
!sameValues(options.get("--ulimit") ?? [], [
"cpu=300:300",
- "as=1073741824:1073741824",
+ "as=2147483648:2147483648",
"fsize=1048576:1048576",
"nofile=64:64",
"nproc=32:32",
@@ -489,7 +489,7 @@ function validateRun(runArgs) {
options.get("--cpus")?.[0] !== "2.0" ||
!sameValues(options.get("--ulimit") ?? [], [
"cpu=300:300",
- "as=1073741824:1073741824",
+ "as=2147483648:2147483648",
"fsize=1048576:1048576",
"nofile=64:64",
"nproc=32:32",
@@ -592,7 +592,7 @@ function validateRun(runArgs) {
memory !== memorySwap ||
(ulimits.get("cpu") ?? 0) < 1 ||
(ulimits.get("cpu") ?? 0) > 60 ||
- ulimits.get("as") !== memoryMegabytes * 1024 * 1024 ||
+ ulimits.get("as") !== 2 * 1024 * 1024 * 1024 ||
(ulimits.get("fsize") ?? 0) < 1 ||
(ulimits.get("fsize") ?? 0) > 1_048_576 ||
ulimits.get("nofile") !== 64 ||
diff --git a/scripts/verify-contained-runtime.mjs b/scripts/verify-contained-runtime.mjs
index 9274b49e..326ccca8 100644
--- a/scripts/verify-contained-runtime.mjs
+++ b/scripts/verify-contained-runtime.mjs
@@ -19,6 +19,7 @@ import {
createRuntimeToolchainFingerprint,
} from "./contained-runtime-attestation.mjs";
import { createContainedRuntimeEnvironment } from "./contained-runtime-environment.mjs";
+import { CONTAINED_RUNTIME_SNAPSHOTTER } from "./contained-containerd-config.mjs";
import {
sendSupervisorRequest,
validateSupervisorReadyReceipt,
@@ -128,6 +129,7 @@ const expectedPaths = {
sessionRoot: sessionPrefix,
containerdRootlesskitApiSocket: `${sessionPrefix}/run/containerd-rootless/api.sock`,
containerdSocket: `${sessionPrefix}/run/containerd.sock`,
+ snapshotterSocket: `${sessionPrefix}/run/inner/fuse-overlayfs.sock`,
runtimeCommandSocket: `${sessionPrefix}/run/runtime-command.sock`,
clientFifoRoot: `${sessionPrefix}/run/client-fifo`,
runcStateRoot: `${sessionPrefix}/run/runc`,
@@ -137,6 +139,7 @@ const expectedPaths = {
runtimeSupervisorSocket: `${sessionPrefix}/run/runtime-supervisor.sock`,
runtimeSupervisorReady: `${sessionPrefix}/run/runtime-supervisor-ready.json`,
containerdRoot: `${sessionPrefix}/data/containerd`,
+ snapshotterRoot: `${sessionPrefix}/data/fuse-overlayfs`,
containerdState: `${sessionPrefix}/state/containerd`,
buildkitRoot: `${sessionPrefix}/data/buildkit`,
nerdctlData: `${sessionPrefix}/data/nerdctl`,
@@ -238,8 +241,11 @@ if (sha256File(adapter) !== attestation.adapterSha256) {
for (const [key, helperPath] of Object.entries(RUNTIME_HELPER_PATHS)) {
const helper = repositoryPath(helperPath, "file", `runtime helper ${key}`);
secureFile(helper, `runtime helper ${key}`);
- if (key === "runcWrapper" && (statSync(helper).mode & 0o111) === 0) {
- throw new Error("contained runc wrapper is not executable");
+ if (
+ ["fuseMountWrapper", "runcWrapper"].includes(key) &&
+ (statSync(helper).mode & 0o111) === 0
+ ) {
+ throw new Error(`contained ${key} is not executable`);
}
if (sha256File(helper) !== attestation.helperSha256[key]) {
throw new Error(`runtime helper changed after session launch: ${key}`);
@@ -254,7 +260,7 @@ const runtimeWrapperRoot = repositoryPath(
secureFile(runtimeWrapperRoot, "runtime wrapper directory");
if (
JSON.stringify(readdirSync(runtimeWrapperRoot).sort()) !==
- JSON.stringify(["runc"])
+ JSON.stringify(["mount.fuse3", "runc"])
) {
throw new Error("runtime wrapper directory contains an unknown entry");
}
@@ -304,8 +310,10 @@ const expectedComponentVersions = {
buildctl: "0.30.0",
buildkitd: "0.30.0",
containerd: "2.3.1",
+ "containerd-fuse-overlayfs-grpc": "2.1.7",
"containerd-shim-runc-v2": "2.3.1",
ctr: "2.3.1",
+ "fuse-overlayfs": "1.16",
nerdctl: "2.3.1",
rootlesskit: "3.0.0",
runc: "1.4.2",
@@ -339,6 +347,7 @@ const pathKinds = {
sessionRoot: "directory",
containerdRootlesskitApiSocket: "socket",
containerdSocket: "socket",
+ snapshotterSocket: "socket",
runtimeCommandSocket: "socket",
clientFifoRoot: "directory",
runcStateRoot: "directory",
@@ -348,6 +357,7 @@ const pathKinds = {
runtimeSupervisorSocket: "socket",
runtimeSupervisorReady: "file",
containerdRoot: "directory",
+ snapshotterRoot: "directory",
containerdState: "directory",
buildkitRoot: "directory",
nerdctlData: "directory",
@@ -472,13 +482,24 @@ const containerdConfigText = readFileSync(
const shimSocketMatches = [
...containerdConfigText.matchAll(/^\s*socket_dir = '([^']+)'\s*$/gmu),
];
+const snapshotterSocketMatches = [
+ ...containerdConfigText.matchAll(/^\s*address = '([^']+)'\s*$/gmu),
+];
if (
shimSocketMatches.length !== 1 ||
shimSocketMatches[0][1].length > 42 ||
shimSocketMatches[0][1] !== root ||
- realpathSync(shimSocketMatches[0][1]) !== root
+ realpathSync(shimSocketMatches[0][1]) !== root ||
+ snapshotterSocketMatches.length !== 1 ||
+ snapshotterSocketMatches[0][1] !== resolvedPaths.snapshotterSocket ||
+ !containerdConfigText.includes(
+ `[proxy_plugins.'${CONTAINED_RUNTIME_SNAPSHOTTER}']`,
+ ) ||
+ !containerdConfigText.includes(
+ `snapshotter = "${CONTAINED_RUNTIME_SNAPSHOTTER}"`,
+ )
) {
- throw new Error("runtime shim socket directory is not repository-contained");
+ throw new Error("runtime containerd binding is not repository-contained");
}
const material = createRuntimeToolchainFingerprint({
diff --git a/scripts/write-contained-runtime-attestation.mjs b/scripts/write-contained-runtime-attestation.mjs
index 5177de14..29b8cda8 100644
--- a/scripts/write-contained-runtime-attestation.mjs
+++ b/scripts/write-contained-runtime-attestation.mjs
@@ -73,6 +73,7 @@ const paths = {
sessionRoot: sessionPrefix,
containerdRootlesskitApiSocket: `${sessionPrefix}/run/containerd-rootless/api.sock`,
containerdSocket: `${sessionPrefix}/run/containerd.sock`,
+ snapshotterSocket: `${sessionPrefix}/run/inner/fuse-overlayfs.sock`,
runtimeCommandSocket: `${sessionPrefix}/run/runtime-command.sock`,
clientFifoRoot: `${sessionPrefix}/run/client-fifo`,
runcStateRoot: `${sessionPrefix}/run/runc`,
@@ -82,6 +83,7 @@ const paths = {
runtimeSupervisorSocket: `${sessionPrefix}/run/runtime-supervisor.sock`,
runtimeSupervisorReady: `${sessionPrefix}/run/runtime-supervisor-ready.json`,
containerdRoot: `${sessionPrefix}/data/containerd`,
+ snapshotterRoot: `${sessionPrefix}/data/fuse-overlayfs`,
containerdState: `${sessionPrefix}/state/containerd`,
buildkitRoot: `${sessionPrefix}/data/buildkit`,
nerdctlData: `${sessionPrefix}/data/nerdctl`,
diff --git a/services/runner/src/counterlab_runner/docker.py b/services/runner/src/counterlab_runner/docker.py
index cd6cc3b2..944e4fb5 100644
--- a/services/runner/src/counterlab_runner/docker.py
+++ b/services/runner/src/counterlab_runner/docker.py
@@ -31,6 +31,7 @@
_CONTAINED_RUNTIME_SESSION_ID = re.compile(r"^rt-[a-z0-9][a-z0-9-]{7,13}$")
_CONTAINED_RUNTIME_SUBPROCESS_ENVIRONMENT = {"PATH": "/usr/bin:/bin"}
_AGGREGATE_TIMEOUT_QUALIFICATION_MODE = "aggregate-timeout-proof-v1"
+CONTAINED_PROCESS_ADDRESS_SPACE_BYTES = 2 * 1024 * 1024 * 1024
_CONTROL_RECEIPT_V2_KEYS = frozenset(
{
"schemaVersion",
@@ -404,6 +405,7 @@ def build_docker_command(
"""Build a list-form Docker command with no inherited credentials."""
memory_bytes = limits.memory_mb * 1024 * 1024
+ address_space_bytes = max(memory_bytes, CONTAINED_PROCESS_ADDRESS_SPACE_BYTES)
return (
docker_bin,
"run",
@@ -421,7 +423,7 @@ def build_docker_command(
f"--memory-swap={limits.memory_mb}m",
f"--cpus={limits.cpu_count}",
f"--ulimit=cpu={limits.wall_seconds}:{limits.wall_seconds}",
- f"--ulimit=as={memory_bytes}:{memory_bytes}",
+ f"--ulimit=as={address_space_bytes}:{address_space_bytes}",
f"--ulimit=fsize={limits.max_output_bytes}:{limits.max_output_bytes}",
"--ulimit=nofile=64:64",
f"--ulimit=nproc={limits.max_processes}:{limits.max_processes}",
diff --git a/services/runner/src/counterlab_runner/timeout_proof.py b/services/runner/src/counterlab_runner/timeout_proof.py
index 7278c1a2..f23e6a11 100644
--- a/services/runner/src/counterlab_runner/timeout_proof.py
+++ b/services/runner/src/counterlab_runner/timeout_proof.py
@@ -12,6 +12,7 @@
from typing import Any
from .docker import (
+ CONTAINED_PROCESS_ADDRESS_SPACE_BYTES,
CONTAINED_RUNTIME_POLICY_SHA256,
DockerAdapterExecutor,
DockerExecutionError,
@@ -492,7 +493,7 @@ def _validate_enforced_rlimits(value: object, intended: object) -> bool:
by_type[entry["type"]] = entry["soft"]
return (
observed == required
- and by_type["RLIMIT_AS"] == intended["memoryBytes"]
+ and by_type["RLIMIT_AS"] == CONTAINED_PROCESS_ADDRESS_SPACE_BYTES
and by_type["RLIMIT_NPROC"] == intended["maxProcesses"]
and by_type["RLIMIT_NOFILE"] == 64
and 1 <= by_type["RLIMIT_CPU"] <= 300
@@ -895,7 +896,7 @@ def run_timeout_cleanup_proof(
_write_new(workspace / "artifact-adapter.py", adapter_source)
_write_new(
workspace / "public_tests.py",
- _timeout_public_test(memory_mb * 1024 * 1024),
+ _timeout_public_test(CONTAINED_PROCESS_ADDRESS_SPACE_BYTES),
)
artifacts = validate_generated_workspace(workspace, generated_root)
diff --git a/services/runner/tests/test_docker_runner.py b/services/runner/tests/test_docker_runner.py
index 8bd083c1..d2ef8787 100644
--- a/services/runner/tests/test_docker_runner.py
+++ b/services/runner/tests/test_docker_runner.py
@@ -114,10 +114,7 @@ def test_docker_command_applies_fixed_isolation_and_only_public_mounts(
assert (
f"--ulimit=cpu={limits.wall_seconds}:{limits.wall_seconds}" in command
)
- assert (
- f"--ulimit=as={limits.memory_mb * 1024 * 1024}:"
- f"{limits.memory_mb * 1024 * 1024}" in command
- )
+ assert "--ulimit=as=2147483648:2147483648" in command
assert (
f"--ulimit=fsize={limits.max_output_bytes}:{limits.max_output_bytes}"
in command
diff --git a/services/runner/tests/test_timeout_proof.py b/services/runner/tests/test_timeout_proof.py
index 2bb845bd..4f1d534c 100644
--- a/services/runner/tests/test_timeout_proof.py
+++ b/services/runner/tests/test_timeout_proof.py
@@ -173,8 +173,8 @@ def _rootless(control: dict[str, object], build: dict[str, object]) -> dict[str,
"enforcedRlimits": [
{
"type": "RLIMIT_AS",
- "soft": intended_limits["memoryBytes"],
- "hard": intended_limits["memoryBytes"],
+ "soft": timeout_proof_module.CONTAINED_PROCESS_ADDRESS_SPACE_BYTES,
+ "hard": timeout_proof_module.CONTAINED_PROCESS_ADDRESS_SPACE_BYTES,
},
{"type": "RLIMIT_CPU", "soft": 20, "hard": 20},
{"type": "RLIMIT_FSIZE", "soft": 262_144, "hard": 262_144},
@@ -276,8 +276,8 @@ def test_rootless_rlimit_validator_matches_the_runtime_contract() -> None:
runtime_limits = [
{
"type": "RLIMIT_AS",
- "soft": intended["memoryBytes"],
- "hard": intended["memoryBytes"],
+ "soft": timeout_proof_module.CONTAINED_PROCESS_ADDRESS_SPACE_BYTES,
+ "hard": timeout_proof_module.CONTAINED_PROCESS_ADDRESS_SPACE_BYTES,
},
{"type": "RLIMIT_CPU", "soft": 20, "hard": 20},
{"type": "RLIMIT_FSIZE", "soft": 262_144, "hard": 262_144},
From f6b26c309705d8f33c46096473763a3b9d5bdcd0 Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Wed, 22 Jul 2026 02:48:03 +0530
Subject: [PATCH 136/143] fix(runtime): mark FUSE mount helper executable
---
scripts/runtime-bin/mount.fuse3 | 0
1 file changed, 0 insertions(+), 0 deletions(-)
mode change 100644 => 100755 scripts/runtime-bin/mount.fuse3
diff --git a/scripts/runtime-bin/mount.fuse3 b/scripts/runtime-bin/mount.fuse3
old mode 100644
new mode 100755
From 6006bcdc8b7126d2caec5011945ee023dea7aa3e Mon Sep 17 00:00:00 2001
From: Abhyudaya Baiju Tharakan <22f3001492@ds.study.iitm.ac.in>
Date: Wed, 22 Jul 2026 03:03:22 +0530
Subject: [PATCH 137/143] feat(authority): bound learning guidance and
callbacks
---
apps/web/worker/api.test.ts | 306 ++++-
apps/web/worker/api.ts | 364 ++++-
.../worker/d1-runner-job-repository.test.ts | 50 +-
apps/web/worker/d1-runner-job-repository.ts | 16 +
packages/belief-analyst/src/index.test.ts | 84 +-
packages/belief-analyst/src/index.ts | 257 +++-
.../src/learning-director.test.ts | 686 ++++++++++
.../belief-analyst/src/learning-director.ts | 1203 +++++++++++++++++
packages/concept-registry/src/index.ts | 15 +
packages/contracts/src/index.ts | 247 ++++
packages/session-core/src/domain.ts | 2 +
packages/session-core/src/index.test.ts | 331 ++++-
packages/session-core/src/runner-jobs.test.ts | 535 ++++++++
packages/session-core/src/runner-jobs.ts | 558 +++++++-
packages/session-core/src/service.ts | 183 ++-
15 files changed, 4730 insertions(+), 107 deletions(-)
create mode 100644 packages/belief-analyst/src/learning-director.test.ts
create mode 100644 packages/belief-analyst/src/learning-director.ts
diff --git a/apps/web/worker/api.test.ts b/apps/web/worker/api.test.ts
index b6e2e6b8..de94a504 100644
--- a/apps/web/worker/api.test.ts
+++ b/apps/web/worker/api.test.ts
@@ -550,6 +550,14 @@ class MemoryRunnerJobRepository implements RunnerJobRepository {
this.events.set(job.jobId, events);
}
+ async appendTerminalEvent(
+ job: RunnerJob,
+ expectedVersion: number,
+ event: PublicCompilerEvent,
+ ): Promise {
+ await this.appendEvent(job, expectedVersion, event);
+ }
+
async listEvents(
jobId: string,
afterCursor: number,
@@ -935,8 +943,19 @@ async function liveBeliefInput(
);
expect(preview.status).toBe(200);
const body = (await preview.json()) as {
- data: { previewHash: string; requiresSensitiveApproval: boolean };
+ data: {
+ previewHash: string;
+ requiresSensitiveApproval: boolean;
+ sanitizedContent: Record;
+ learningDirectorPacket: Record;
+ };
};
+ expect(body.data.previewHash).toBe(
+ await hashCanonical({
+ beliefAnalyst: body.data.sanitizedContent,
+ learningDirector: body.data.learningDirectorPacket,
+ }),
+ );
return {
learnerClaim,
previewHash: body.data.previewHash,
@@ -1085,6 +1104,77 @@ function structuredResponsesResult(output: unknown): Response {
);
}
+function learningDirectorToolResult(
+ concept: "entity_leakage" | "class_imbalance",
+): Response {
+ const functionCall = (name: string, index: number) => ({
+ id: `function_call_${index}`,
+ type: "function_call",
+ status: "completed",
+ call_id: `call_${index}`,
+ name,
+ arguments: JSON.stringify({ concept }),
+ });
+ return new Response(
+ JSON.stringify({
+ id: "resp_learning_director_tools",
+ object: "response",
+ status: "completed",
+ model: "configured-model",
+ output: [
+ functionCall("get_subject_pack_capabilities", 1),
+ functionCall("list_trusted_scene_recipes", 2),
+ functionCall("list_verified_boundary_views", 3),
+ ],
+ }),
+ { status: 200, headers: { "content-type": "application/json" } },
+ );
+}
+
+function learningDirectorReadyResult(
+ concept: "entity_leakage" | "class_imbalance",
+): Response {
+ const leakage = concept === "entity_leakage";
+ return structuredResponsesResult({
+ decision: {
+ status: "READY",
+ plan: {
+ concept,
+ introductionStages: [
+ "Question",
+ "Prediction",
+ "Test",
+ "Boundary",
+ "Apply",
+ ],
+ primaryEmphasis: "Boundary",
+ scaffoldIds: [leakage ? "compare-splits" : "compare-metrics"],
+ candidateExperimentIds: [
+ leakage ? "group-holdout" : "threshold-and-majority-baseline",
+ ],
+ sceneRecipeId: leakage
+ ? "entity-overlap-stage"
+ : "confusion-matrix-stage",
+ boundaryViewId: leakage
+ ? "test-fraction-by-repeat-rate"
+ : "threshold-by-prevalence",
+ evidenceHashes: [],
+ nonClaims: ["bounded-claim-only", "no-mastery-claim"],
+ },
+ },
+ });
+}
+
+function learningDirectorClarificationResult(): Response {
+ return structuredResponsesResult({
+ decision: {
+ status: "CLARIFICATION_REQUIRED",
+ questionId: "learning-emphasis",
+ choices: ["controls-first", "boundary-first"],
+ },
+ });
+}
+
async function preparedScientificHostedRunner(
runnerJobs: MemoryRunnerJobRepository = new MemoryRunnerJobRepository(),
) {
@@ -1112,7 +1202,9 @@ async function preparedScientificHostedRunner(
.spyOn(globalThis, "fetch")
.mockResolvedValueOnce(
structuredResponsesResult(liveBeliefSpecWire(artifact.manifest)),
- );
+ )
+ .mockResolvedValueOnce(learningDirectorToolResult("entity_leakage"))
+ .mockResolvedValueOnce(learningDirectorReadyResult("entity_leakage"));
try {
const proposed = await harness.app.request(
`/api/sessions/${harness.sessionId}/belief-test`,
@@ -1240,7 +1332,9 @@ async function preparedScientificImbalanceHostedRunner(
.spyOn(globalThis, "fetch")
.mockResolvedValueOnce(
structuredResponsesResult(liveImbalanceBeliefSpecWire(artifact)),
- );
+ )
+ .mockResolvedValueOnce(learningDirectorToolResult("class_imbalance"))
+ .mockResolvedValueOnce(learningDirectorReadyResult("class_imbalance"));
try {
const proposed = await intakeApp.request(
`/api/sessions/${sessionId}/belief-test`,
@@ -7443,6 +7537,15 @@ describe("Cloudflare Worker API", () => {
},
);
expect(revised.status).toBe(200);
+ const sessionBeforeDuplicate = await harness.sessionRepository.find(
+ harness.bundle.sessionId,
+ );
+ const objectsBeforeDuplicate = structuredClone([
+ ...harness.runnerObjects.objects.entries(),
+ ]);
+ const eventsBeforeDuplicate = await harness.runnerJobs.listEvents(jobId, 0);
+ const objectGet = vi.spyOn(harness.runnerObjects, "get");
+ const objectPut = vi.spyOn(harness.runnerObjects, "put");
const duplicate = await postJson(
harness.app,
`/api/runner/jobs/${jobId}/callback`,
@@ -7457,7 +7560,17 @@ describe("Cloudflare Worker API", () => {
session: { state: "REVISION_RECORDED" },
},
});
- expect(await harness.runnerJobs.listEvents(jobId, 0)).toHaveLength(4);
+ expect(objectGet).not.toHaveBeenCalled();
+ expect(objectPut).not.toHaveBeenCalled();
+ expect([...harness.runnerObjects.objects.entries()]).toEqual(
+ objectsBeforeDuplicate,
+ );
+ expect(await harness.runnerJobs.listEvents(jobId, 0)).toEqual(
+ eventsBeforeDuplicate,
+ );
+ expect(
+ await harness.sessionRepository.find(harness.bundle.sessionId),
+ ).toEqual(sessionBeforeDuplicate);
const transfer = await postJson(
harness.app,
@@ -11090,6 +11203,8 @@ describe("Cloudflare Worker API", () => {
});
expect(harness.dispatcher.cancelled).toHaveLength(1);
+ const objectGet = vi.spyOn(harness.runnerObjects, "get");
+ const objectPut = vi.spyOn(harness.runnerObjects, "put");
const lateCallback = await postJson(
harness.app,
`/api/runner/jobs/${firstBody.data.runnerJob.jobId}/callback`,
@@ -11099,15 +11214,10 @@ describe("Cloudflare Worker API", () => {
idempotencyKey: "callback-after-start-over",
jobId: firstBody.data.runnerJob.jobId,
stateVersion: harness.dispatch.job.stateVersion,
- status: "FAILED",
- outputHashes: [],
+ status: "VERIFIED",
+ outputHashes: ["a".repeat(64)],
finalEventCursor: 0,
occurredAt: "2026-07-14T10:00:01.000Z",
- error: {
- code: "LATE_RUNNER_CALLBACK",
- message: "Late completion after browser reset",
- retryable: false,
- },
},
{ authorization: `Bearer ${harness.dispatch.token}` },
);
@@ -11120,6 +11230,8 @@ describe("Cloudflare Worker API", () => {
firstBody.data.runnerJob.jobId,
);
expect(afterLateCallback).toMatchObject({ status: "CANCELLED" });
+ expect(objectGet).not.toHaveBeenCalled();
+ expect(objectPut).not.toHaveBeenCalled();
});
it("acquires one opaque runner lease and releases it on cancellation", async () => {
@@ -11926,7 +12038,9 @@ describe("Cloudflare Worker API", () => {
.spyOn(globalThis, "fetch")
.mockResolvedValueOnce(
structuredResponsesResult(liveBeliefSpecWire(artifact.manifest)),
- );
+ )
+ .mockResolvedValueOnce(learningDirectorToolResult("entity_leakage"))
+ .mockResolvedValueOnce(learningDirectorReadyResult("entity_leakage"));
try {
const response = await app.request(
@@ -11952,6 +12066,17 @@ describe("Cloudflare Worker API", () => {
claim: learnerClaim,
learnerDecision: "UNDECIDED",
},
+ learningDirector: {
+ schemaVersion: "1",
+ clarificationUsed: false,
+ decision: {
+ status: "READY",
+ plan: {
+ primaryEmphasis: "Boundary",
+ sceneRecipeId: "entity-overlap-stage",
+ },
+ },
+ },
});
expect(body.data).not.toHaveProperty("beliefTest");
const beliefSpecId = (body.data.beliefSpec as { id: string }).id;
@@ -12099,6 +12224,163 @@ describe("Cloudflare Worker API", () => {
}
});
+ it("keeps learner confirmation available when the optional Director response is invalid", async () => {
+ const { app, sessionId, artifactStore } = await sessionHarness("live");
+ const artifact = await artifactStore.find("artifact_uploaded_not_sample");
+ if (artifact === undefined) throw new Error("live artifact is missing");
+ const learnerClaim =
+ "The notebook accuracy proves generalization to new customers.";
+ const beliefInput = await liveBeliefInput(app, sessionId, learnerClaim);
+ const upstream = vi
+ .spyOn(globalThis, "fetch")
+ .mockResolvedValueOnce(
+ structuredResponsesResult(liveBeliefSpecWire(artifact.manifest)),
+ )
+ .mockResolvedValueOnce(learningDirectorToolResult("entity_leakage"))
+ .mockResolvedValueOnce(
+ structuredResponsesResult({
+ decision: {
+ status: "CLARIFICATION_REQUIRED",
+ question: "The verified result proves leakage; continue?",
+ choices: ["controls-first", "boundary-first"],
+ },
+ }),
+ );
+
+ try {
+ const proposed = await app.request(
+ `/api/sessions/${sessionId}/belief-test`,
+ {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify(beliefInput),
+ },
+ {
+ OPENAI_API_KEY: "server-only-key",
+ OPENAI_MODEL: "configured-model",
+ } as unknown as Env & Record,
+ );
+ expect(proposed.status).toBe(200);
+ const proposedBody = (await proposed.json()) as {
+ data: Record;
+ };
+ expect(proposedBody).toMatchObject({
+ data: {
+ state: "BELIEF_TEST_PROPOSED",
+ beliefSpec: { claim: learnerClaim },
+ },
+ });
+ expect(proposedBody.data).not.toHaveProperty("learningDirector");
+
+ const confirmation = await postJson(
+ app,
+ `/api/sessions/${sessionId}/belief-test/confirm`,
+ { action: "confirm" },
+ );
+ expect(confirmation.status).toBe(200);
+ await expect(confirmation.json()).resolves.toMatchObject({
+ data: { state: "BELIEF_TEST_CONFIRMED" },
+ });
+ } finally {
+ upstream.mockRestore();
+ }
+ });
+
+ it("allows one bounded Learning Director clarification before belief confirmation", async () => {
+ const { app, sessionId, sessionRepository, artifactStore } =
+ await sessionHarness("live");
+ const artifact = await artifactStore.find("artifact_uploaded_not_sample");
+ if (artifact === undefined) throw new Error("live artifact is missing");
+ const learnerClaim =
+ "The notebook accuracy proves generalization to new customers.";
+ const beliefInput = await liveBeliefInput(app, sessionId, learnerClaim);
+ const upstream = vi
+ .spyOn(globalThis, "fetch")
+ .mockResolvedValueOnce(
+ structuredResponsesResult(liveBeliefSpecWire(artifact.manifest)),
+ )
+ .mockResolvedValueOnce(learningDirectorToolResult("entity_leakage"))
+ .mockResolvedValueOnce(learningDirectorClarificationResult())
+ .mockResolvedValueOnce(learningDirectorToolResult("entity_leakage"))
+ .mockResolvedValueOnce(learningDirectorReadyResult("entity_leakage"));
+ const env = {
+ OPENAI_API_KEY: "server-only-key",
+ OPENAI_MODEL: "configured-model",
+ } as unknown as Env & Record;
+
+ try {
+ const proposed = await app.request(
+ `/api/sessions/${sessionId}/belief-test`,
+ {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify(beliefInput),
+ },
+ env,
+ );
+ expect(proposed.status).toBe(200);
+ await expect(proposed.json()).resolves.toMatchObject({
+ data: {
+ learningDirector: {
+ clarificationUsed: true,
+ decision: {
+ status: "CLARIFICATION_REQUIRED",
+ choices: ["controls-first", "boundary-first"],
+ },
+ },
+ },
+ });
+
+ const answered = await app.request(
+ `/api/sessions/${sessionId}/learning-director`,
+ {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ answer: "controls-first" }),
+ },
+ env,
+ );
+ expect(answered.status).toBe(200);
+ await expect(answered.json()).resolves.toMatchObject({
+ data: {
+ learningDirector: {
+ clarificationUsed: true,
+ decision: { status: "READY" },
+ },
+ },
+ });
+
+ const secondAnswer = await app.request(
+ `/api/sessions/${sessionId}/learning-director`,
+ {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ answer: "boundary-first" }),
+ },
+ env,
+ );
+ expect(secondAnswer.status).toBe(409);
+ const confirmation = await postJson(
+ app,
+ `/api/sessions/${sessionId}/belief-test/confirm`,
+ { action: "confirm" },
+ );
+ expect(confirmation.status).toBe(200);
+ expect(upstream).toHaveBeenCalledTimes(5);
+ expect(
+ (await sessionRepository.listEvents(sessionId)).map(({ kind }) => kind),
+ ).toEqual([
+ "session.created",
+ "belief_spec.proposed",
+ "learning_director.clarification_requested",
+ "learning_director.ready",
+ "belief_spec.confirmed",
+ ]);
+ } finally {
+ upstream.mockRestore();
+ }
+ });
+
it("returns an honest insufficient-evidence Belief Spec before any live model call", async () => {
const harness = await sessionHarness("sample");
const sample = await harness.artifactStore.find(harness.artifactId);
diff --git a/apps/web/worker/api.ts b/apps/web/worker/api.ts
index 3f3c9dd1..5598a913 100644
--- a/apps/web/worker/api.ts
+++ b/apps/web/worker/api.ts
@@ -12,6 +12,8 @@ import {
LearnerInteractionInputSchema,
LearnerInteractionReceiptSchema,
LearnerInteractionRecordSchema,
+ LearningDirectorClarificationChoiceIdSchema,
+ LearningDirectorSessionStateSchema,
LeakageTransferSubmissionSchema,
PatchPlanV1Schema,
PatchResultSchema,
@@ -73,8 +75,10 @@ import {
import {
ApprovedSampleBeliefAnalyst,
BeliefAnalystError,
+ LearningDirectorError,
buildSanitizedAnalystContext,
createLiveBeliefAnalystFromEnv,
+ createLiveLearningDirectorFromEnv,
requireApplicableClaim,
} from "@counterlab/belief-analyst";
import {
@@ -116,6 +120,7 @@ import {
getSessionBeliefAuthority,
hashCanonical,
resolveSessionEvidenceAuthority,
+ type RunnerCallbackClaimToken,
type RunnerJobRepository,
type CounterLabSession,
type SessionRepository,
@@ -278,6 +283,8 @@ type AppBindings = {
Bindings: WorkerBindings;
Variables: {
requestId: string;
+ runnerCallbackClaim:
+ { jobId: string; claim: RunnerCallbackClaimToken } | undefined;
};
};
@@ -377,6 +384,9 @@ const ConfirmationSchema = z.union([
})
.strict(),
]);
+const LearningDirectorAnswerSchema = z
+ .object({ answer: LearningDirectorClarificationChoiceIdSchema })
+ .strict();
const PredictionRequestSchema = z
.object({
choice: z.string().trim().min(1).max(300),
@@ -1662,22 +1672,24 @@ async function dispatchRecoverableRunnerJob(input: {
retryable: true,
} as const;
try {
- const failed = await input.jobs.transition(
+ await input.jobs.failJobWithEvent(
dispatchJob.jobId,
dispatchJob.jobVersion,
- "FAILED",
- { runnerIdentity: input.dispatcher.identity, error: failure },
+ {
+ runnerIdentity: input.dispatcher.identity,
+ error: failure,
+ },
+ {
+ schemaVersion: "1",
+ eventId: requestId(input.options, "compiler_event"),
+ jobId: dispatchJob.jobId,
+ cursor: dispatchJob.eventCursor + 1,
+ at: requestNow(input.options).toISOString(),
+ kind: "job.failed",
+ code: failure.code,
+ message: failure.message,
+ },
);
- await input.jobs.appendEvent(failed.jobId, failed.jobVersion, {
- schemaVersion: "1",
- eventId: requestId(input.options, "compiler_event"),
- jobId: failed.jobId,
- cursor: failed.eventCursor + 1,
- at: requestNow(input.options).toISOString(),
- kind: "job.failed",
- code: failure.code,
- message: failure.message,
- });
} catch (stateError) {
if (!(stateError instanceof ConcurrentRunnerJobUpdateError)) {
console.error("CounterLab could not persist runner dispatch failure", {
@@ -3768,6 +3780,7 @@ async function appendScientificAuthorityEvents(input: {
jobs: RunnerJobService;
job: RunnerJob;
callback: RunnerCallback;
+ callbackClaim?: RunnerCallbackClaimToken | undefined;
authorityAt: string;
report: EpistemicVerificationReport;
}): Promise {
@@ -3812,6 +3825,7 @@ async function appendScientificAuthorityEvents(input: {
input.job.jobId,
updated.jobVersion,
event,
+ input.callbackClaim,
);
}
return updated.eventCursor;
@@ -3821,6 +3835,7 @@ async function appendInteractiveAuthorityEvents(input: {
jobs: RunnerJobService;
job: RunnerJob;
callback: RunnerCallback;
+ callbackClaim?: RunnerCallbackClaimToken | undefined;
authorityAt: string;
invariantCount: number;
resultHash: string;
@@ -3879,6 +3894,7 @@ async function appendInteractiveAuthorityEvents(input: {
input.job.jobId,
current.jobVersion,
event,
+ input.callbackClaim,
);
}
return current.eventCursor;
@@ -3888,6 +3904,7 @@ async function appendBoundaryAuthorityEvents(input: {
jobs: RunnerJobService;
job: RunnerJob;
callback: RunnerCallback;
+ callbackClaim?: RunnerCallbackClaimToken | undefined;
authorityAt: string;
report: BoundaryMapVerificationReportV1;
}): Promise {
@@ -3961,6 +3978,7 @@ async function appendBoundaryAuthorityEvents(input: {
input.job.jobId,
current.jobVersion,
event,
+ input.callbackClaim,
);
}
return current.eventCursor;
@@ -4237,6 +4255,45 @@ function isFile(value: string | File | null): value is File {
return value !== null && typeof value !== "string";
}
+function learningDirectorRegistries(concept: BeliefSpecV2["concept"]) {
+ const pack = getConceptPack(concept);
+ return {
+ [concept]: pack.scientificMethod.learningDirectorPresentation,
+ };
+}
+
+function learningDirectorPacket(
+ sanitizedContent: ReturnType,
+ clarificationAlreadyUsed: boolean,
+ answer?: string,
+) {
+ const seenHashes = new Set();
+ const approvedEvidence = sanitizedContent.evidence.flatMap((evidence) => {
+ if (seenHashes.has(evidence.sourceHash)) return [];
+ seenHashes.add(evidence.sourceHash);
+ return [{ hash: evidence.sourceHash, excerpt: evidence.sourceExcerpt }];
+ });
+ return {
+ concept: sanitizedContent.concept,
+ subjectPackVersion: sanitizedContent.conceptPack.version,
+ approvedEvidence,
+ candidateExperimentIds: [
+ ...sanitizedContent.conceptPack.candidateExperimentIds,
+ ],
+ clarificationAlreadyUsed,
+ ...(answer === undefined ? {} : { answer }),
+ };
+}
+
+function approvedLiveReasoningPackets(
+ sanitizedContent: ReturnType,
+) {
+ return {
+ beliefAnalyst: sanitizedContent,
+ learningDirector: learningDirectorPacket(sanitizedContent, false),
+ };
+}
+
async function statePayload(
context: Context,
session: Awaited>,
@@ -4267,6 +4324,9 @@ async function statePayload(
...(session.beliefSpec === undefined
? {}
: { beliefSpec: session.beliefSpec }),
+ ...(session.learningDirector === undefined
+ ? {}
+ : { learningDirector: session.learningDirector }),
...(session.prediction === undefined
? {}
: { prediction: session.prediction }),
@@ -5107,15 +5167,18 @@ export function createApi(options: ApiOptions = {}) {
manifest: artifact.manifest,
concept: routing.concept,
});
+ const outboundPackets = approvedLiveReasoningPackets(sanitizedContent);
return context.json(
jsonSuccess({
schemaVersion: "1" as const,
concept: routing.concept,
conceptTitle: getConceptPack(routing.concept).title,
- previewHash: await hashCanonical(sanitizedContent),
+ previewHash: await hashCanonical(outboundPackets),
requiresSensitiveApproval:
+ sanitizedContent.privacy.suppressedFieldCount > 0 ||
sanitizedContent.privacy.redactions.length > 0,
sanitizedContent,
+ learningDirectorPacket: outboundPackets.learningDirector,
}),
);
});
@@ -5223,7 +5286,8 @@ export function createApi(options: ApiOptions = {}) {
manifest: artifact.manifest,
concept: routing.concept,
});
- const expectedPreviewHash = await hashCanonical(sanitizedContent);
+ const outboundPackets = approvedLiveReasoningPackets(sanitizedContent);
+ const expectedPreviewHash = await hashCanonical(outboundPackets);
if (input.previewHash !== expectedPreviewHash) {
throw new ApiInputError(
"SANITIZED_PREVIEW_REQUIRED",
@@ -5232,6 +5296,7 @@ export function createApi(options: ApiOptions = {}) {
);
}
const containsSensitiveRedaction =
+ sanitizedContent.privacy.suppressedFieldCount > 0 ||
sanitizedContent.privacy.redactions.length > 0;
if (
containsSensitiveRedaction &&
@@ -5282,7 +5347,7 @@ export function createApi(options: ApiOptions = {}) {
manifest: artifact.manifest,
concept: routing.concept,
});
- const proposed = await service.proposeBeliefSpecV2(
+ let proposed = await service.proposeBeliefSpecV2(
session.id,
result.beliefSpec,
{
@@ -5291,6 +5356,41 @@ export function createApi(options: ApiOptions = {}) {
promptHash: result.provenance.promptHash,
},
);
+ if (result.beliefSpec.supportState === "SUPPORTED") {
+ try {
+ const directorResult = await createLiveLearningDirectorFromEnv(
+ {
+ OPENAI_API_KEY: context.env?.OPENAI_API_KEY,
+ OPENAI_BASE_URL: context.env?.OPENAI_BASE_URL,
+ OPENAI_MODEL: context.env?.OPENAI_MODEL,
+ OPENAI_REASONING_EFFORT: context.env?.OPENAI_REASONING_EFFORT,
+ OPENAI_TIMEOUT_MS: context.env?.OPENAI_TIMEOUT_MS,
+ },
+ {
+ registries: learningDirectorRegistries(
+ result.beliefSpec.concept,
+ ),
+ sessionId: session.id,
+ },
+ ).decide(outboundPackets.learningDirector);
+ proposed = await service.recordLearningDirector(
+ session.id,
+ LearningDirectorSessionStateSchema.parse({
+ schemaVersion: "1",
+ beliefSpecHash: await hashCanonical(result.beliefSpec),
+ approvedPacketHash: expectedPreviewHash,
+ subjectPackVersion:
+ outboundPackets.learningDirector.subjectPackVersion,
+ clarificationUsed:
+ directorResult.decision.status === "CLARIFICATION_REQUIRED",
+ decision: directorResult.decision,
+ provenance: directorResult.provenance,
+ }),
+ );
+ } catch (error) {
+ if (!(error instanceof LearningDirectorError)) throw error;
+ }
+ }
return context.json(
jsonSuccess(await statePayload(context, proposed, options)),
);
@@ -5327,6 +5427,114 @@ export function createApi(options: ApiOptions = {}) {
);
});
+ app.post("/api/sessions/:sessionId/learning-director", async (context) => {
+ const input = LearningDirectorAnswerSchema.parse(await readJson(context));
+ const service = sessionService(context, options);
+ const sessionId = context.req.param("sessionId");
+ const current = await service.getSession(sessionId);
+ requireMutableSession(current);
+ const directorState = current.learningDirector;
+ if (
+ current.state !== "BELIEF_TEST_PROPOSED" ||
+ current.mode.kind !== "live_notebook" ||
+ current.beliefSpec === undefined ||
+ directorState?.decision.status !== "CLARIFICATION_REQUIRED"
+ ) {
+ throw new ApiInputError(
+ "LEARNING_DIRECTOR_NOT_AWAITING_ANSWER",
+ "This investigation is not awaiting a Learning Director clarification",
+ 409,
+ );
+ }
+ if (!directorState.decision.choices.includes(input.answer)) {
+ throw new ApiInputError(
+ "LEARNING_DIRECTOR_ANSWER_INVALID",
+ "Choose one of the fixed clarification answers",
+ 400,
+ );
+ }
+ const artifact = await artifacts(context, options).find(current.artifactId);
+ if (artifact === undefined) {
+ throw new ApiInputError(
+ "ARTIFACT_NOT_FOUND",
+ "Session artifact was not found",
+ 404,
+ );
+ }
+ const sanitizedContent = buildSanitizedAnalystContext({
+ sessionId,
+ learnerClaim: current.beliefSpec.claim,
+ manifest: artifact.manifest,
+ concept: current.beliefSpec.concept,
+ });
+ const approvedPackets = approvedLiveReasoningPackets(sanitizedContent);
+ if (
+ (await hashCanonical(approvedPackets)) !==
+ directorState.approvedPacketHash
+ ) {
+ throw new ApiInputError(
+ "LEARNING_DIRECTOR_PACKET_CHANGED",
+ "The approved Learning Director packet no longer matches this investigation",
+ 409,
+ );
+ }
+
+ const answerHash = await hashCanonical(input.answer);
+ const operationKey = `${sessionId}:director:${answerHash}`;
+ const admission = await admitOperation(context, options, {
+ kind: "analyst",
+ sessionId,
+ operationKey,
+ });
+ if (admission.leaseStatus === "already-active") {
+ throw new ApiInputError(
+ "ANALYST_IN_PROGRESS",
+ "This Learning Director answer is already being processed",
+ 409,
+ true,
+ 1,
+ );
+ }
+ try {
+ const result = await createLiveLearningDirectorFromEnv(
+ {
+ OPENAI_API_KEY: context.env?.OPENAI_API_KEY,
+ OPENAI_BASE_URL: context.env?.OPENAI_BASE_URL,
+ OPENAI_MODEL: context.env?.OPENAI_MODEL,
+ OPENAI_REASONING_EFFORT: context.env?.OPENAI_REASONING_EFFORT,
+ OPENAI_TIMEOUT_MS: context.env?.OPENAI_TIMEOUT_MS,
+ },
+ {
+ registries: learningDirectorRegistries(current.beliefSpec.concept),
+ sessionId,
+ },
+ ).decide(learningDirectorPacket(sanitizedContent, true, input.answer));
+ const updated = await service.recordLearningDirector(
+ sessionId,
+ LearningDirectorSessionStateSchema.parse({
+ schemaVersion: "1",
+ beliefSpecHash: directorState.beliefSpecHash,
+ approvedPacketHash: directorState.approvedPacketHash,
+ subjectPackVersion: directorState.subjectPackVersion,
+ clarificationUsed: true,
+ decision: result.decision,
+ provenance: result.provenance,
+ }),
+ { clarificationAnswerHash: answerHash },
+ );
+ return context.json(
+ jsonSuccess(await statePayload(context, updated, options)),
+ );
+ } finally {
+ await releaseAdmissionBestEffort(
+ context,
+ options,
+ "analyst",
+ operationKey,
+ );
+ }
+ });
+
app.post("/api/sessions/:sessionId/belief-test/confirm", async (context) => {
const input = ConfirmationSchema.parse(await readJson(context));
const service = sessionService(context, options);
@@ -5993,6 +6201,7 @@ export function createApi(options: ApiOptions = {}) {
app.put("/api/runner/jobs/:jobId/outputs/:generatedPath", async (context) => {
const jobId = context.req.param("jobId");
const { claims, job } = await authorizeRunner(context, options, jobId);
+ const jobs = runnerJobService(context, options);
if (job.status !== "RUNNING" && job.status !== "REPAIRING") {
throw new ApiInputError(
"RUNNER_JOB_NOT_ACTIVE",
@@ -6071,24 +6280,36 @@ export function createApi(options: ApiOptions = {}) {
403,
);
}
- const body = await readBoundedText(
- context,
- generatedPath.endsWith(".ipynb") ? maxNotebookBytes(context) : 1_048_576,
- );
- const contentType = generatedPath.endsWith(".json")
- ? "application/json"
- : generatedPath.endsWith(".ipynb")
- ? "application/x-ipynb+json; charset=utf-8"
- : "text/markdown; charset=utf-8";
- await runnerObjectStore(context, options).put(
- `${claims.outputPrefix}${generatedPath}`,
- body,
- contentType,
- );
- return context.json(
- jsonSuccess({ path: generatedPath, sha256: await sha256Text(body) }),
- 201,
+ const outputClaim = await jobs.claimOutputWrite(
+ jobId,
+ job.jobVersion,
+ context.get("requestId"),
+ generatedPath,
);
+ try {
+ const body = await readBoundedText(
+ context,
+ generatedPath.endsWith(".ipynb")
+ ? maxNotebookBytes(context)
+ : 1_048_576,
+ );
+ const contentType = generatedPath.endsWith(".json")
+ ? "application/json"
+ : generatedPath.endsWith(".ipynb")
+ ? "application/x-ipynb+json; charset=utf-8"
+ : "text/markdown; charset=utf-8";
+ await runnerObjectStore(context, options).put(
+ `${claims.outputPrefix}${generatedPath}`,
+ body,
+ contentType,
+ );
+ return context.json(
+ jsonSuccess({ path: generatedPath, sha256: await sha256Text(body) }),
+ 201,
+ );
+ } finally {
+ await jobs.releaseOutputWriteClaim(jobId, outputClaim.claim);
+ }
});
app.post("/api/runner/jobs/:jobId/resume", async (context) => {
@@ -6872,7 +7093,7 @@ export function createApi(options: ApiOptions = {}) {
app.post("/api/runner/jobs/:jobId/callback", async (context) => {
const jobId = context.req.param("jobId");
const callbackPath = `/api/runner/jobs/${jobId}/callback`;
- const { claims, job } = await authorizeRunner(
+ const { claims } = await authorizeRunner(
context,
options,
jobId,
@@ -6886,6 +7107,18 @@ export function createApi(options: ApiOptions = {}) {
403,
);
}
+ const jobs = runnerJobService(context, options);
+ const claimedCallback = await jobs.claimCallback(
+ callback,
+ context.get("requestId"),
+ );
+ const job = claimedCallback.job;
+ const callbackClaim = claimedCallback.duplicate
+ ? undefined
+ : claimedCallback.claim;
+ if (callbackClaim !== undefined) {
+ context.set("runnerCallbackClaim", { jobId, claim: callbackClaim });
+ }
const service = sessionService(context, options);
const currentSession = await service.getSession(job.sessionId);
if (
@@ -6898,6 +7131,29 @@ export function createApi(options: ApiOptions = {}) {
409,
);
}
+ const storedLabAuthority = HostedExperimentLineageV5Schema.safeParse(
+ currentSession.labVerification,
+ );
+ const storedPlanAuthority = HostedPlanLineageSchema.safeParse(
+ currentSession.labVerification,
+ );
+ const callbackAlreadyProjected =
+ (storedLabAuthority.success && storedLabAuthority.data.jobId === jobId) ||
+ (storedPlanAuthority.success &&
+ storedPlanAuthority.data.jobId === jobId) ||
+ currentSession.resultAuthority?.jobId === jobId ||
+ currentSession.boundaryMapAuthority?.jobId === jobId ||
+ currentSession.patchAuthority?.jobId === jobId;
+ if (claimedCallback.duplicate && callbackAlreadyProjected) {
+ return context.json(
+ jsonSuccess({
+ duplicate: true as const,
+ runnerJob: job,
+ session: await statePayload(context, currentSession, options),
+ verification: null,
+ }),
+ );
+ }
let verification:
| Awaited>
@@ -7127,6 +7383,7 @@ export function createApi(options: ApiOptions = {}) {
jobs,
job: scientificRunJob,
callback,
+ callbackClaim,
authorityAt: requestNow(options).toISOString(),
report: boundaryMapAuthority.report,
});
@@ -7169,6 +7426,7 @@ export function createApi(options: ApiOptions = {}) {
jobs,
job: scientificRunJob,
callback,
+ callbackClaim,
authorityAt: requestNow(options).toISOString(),
invariantCount:
scientificInteractiveAuthority.report.invariantCount,
@@ -7249,6 +7507,7 @@ export function createApi(options: ApiOptions = {}) {
jobs,
job: scientificRunJob,
callback,
+ callbackClaim,
authorityAt: requestNow(options).toISOString(),
report: epistemicAuthority.report,
});
@@ -7746,9 +8005,11 @@ export function createApi(options: ApiOptions = {}) {
}
}
- const completed = await runnerJobService(context, options).recordCallback(
+ const completed = await jobs.recordCallback(
terminalCallback,
+ callbackClaim,
);
+ context.set("runnerCallbackClaim", undefined);
await releaseAdmissionBestEffort(
context,
options,
@@ -10635,7 +10896,19 @@ export function createApi(options: ApiOptions = {}) {
),
);
- app.onError((error, context) => {
+ app.onError(async (error, context) => {
+ const callbackClaim = context.get("runnerCallbackClaim");
+ if (callbackClaim !== undefined) {
+ context.set("runnerCallbackClaim", undefined);
+ try {
+ await runnerJobService(context, options).releaseCallbackClaim(
+ callbackClaim.jobId,
+ callbackClaim.claim,
+ );
+ } catch {
+ // Preserve the request error; the persisted claim remains observable.
+ }
+ }
if (error instanceof ApiInputError) {
if (error.retryAfterSeconds !== undefined) {
context.header("retry-after", String(error.retryAfterSeconds));
@@ -10743,6 +11016,25 @@ export function createApi(options: ApiOptions = {}) {
status: status as 400,
});
}
+ if (error instanceof LearningDirectorError) {
+ const status =
+ error.code === "INVALID_INPUT" ||
+ error.code === "INVALID_RESPONSE" ||
+ error.code === "UNREGISTERED_REFERENCE" ||
+ error.code === "CLARIFICATION_ALREADY_USED" ||
+ error.code === "TOOLS_REQUIRED" ||
+ error.code === "TOOL_CALL_LIMIT" ||
+ error.code === "TURN_LIMIT" ||
+ error.code === "DUPLICATE_TOOL_CALL_ID" ||
+ error.code === "UNKNOWN_TOOL" ||
+ error.code === "MALFORMED_TOOL_CALL" ||
+ error.code === "TOOL_ARGUMENT_REJECTED"
+ ? 422
+ : 503;
+ return context.json(jsonError(error.code, error.message, status), {
+ status: status as 422,
+ });
+ }
console.error("CounterLab Worker request failed", {
requestId: context.get("requestId"),
name: error.name,
diff --git a/apps/web/worker/d1-runner-job-repository.test.ts b/apps/web/worker/d1-runner-job-repository.test.ts
index 762cff4d..c931f952 100644
--- a/apps/web/worker/d1-runner-job-repository.test.ts
+++ b/apps/web/worker/d1-runner-job-repository.test.ts
@@ -410,7 +410,7 @@ describe("D1RunnerJobRepository", () => {
};
await expect(service.recordCallback(callback)).resolves.toMatchObject({
duplicate: false,
- job: { status: "VERIFIED", jobVersion: 5 },
+ job: { status: "VERIFIED", jobVersion: 6 },
});
await expect(service.recordCallback(callback)).resolves.toMatchObject({
duplicate: true,
@@ -419,6 +419,54 @@ describe("D1RunnerJobRepository", () => {
await expect(
service.transition(streamed.jobId, streamed.jobVersion, "REPAIRING"),
).rejects.toThrow(/changed during update|terminal/i);
+
+ const dispatchQueued = await service.createJob({
+ jobId: "job_dispatch_failure",
+ kind: "LAB_COMPILE",
+ sessionId: "session_live_1",
+ artifactId: "artifact_live_1",
+ artifactManifestHash: "a".repeat(64),
+ conceptPack: { id: "entity_leakage", version: "2.0.0" },
+ inputHashes: ["b".repeat(64)],
+ stateVersion: 4,
+ maxAttempts: 3,
+ timeoutSeconds: 90,
+ });
+ const dispatchStarting = await service.transition(
+ dispatchQueued.jobId,
+ dispatchQueued.jobVersion,
+ "STARTING",
+ { runnerIdentity: "runner-test" },
+ );
+ await expect(
+ service.failJobWithEvent(
+ dispatchStarting.jobId,
+ dispatchStarting.jobVersion,
+ {
+ runnerIdentity: "runner-test",
+ error: {
+ code: "RUNNER_DISPATCH_FAILED",
+ message: "The runner did not acknowledge dispatch.",
+ retryable: true,
+ },
+ },
+ {
+ schemaVersion: "1",
+ eventId: "dispatch_failed_event",
+ jobId: dispatchStarting.jobId,
+ cursor: 1,
+ kind: "job.failed",
+ code: "RUNNER_DISPATCH_FAILED",
+ message: "The runner did not acknowledge dispatch.",
+ at: "2026-07-15T00:00:08.000Z",
+ },
+ ),
+ ).resolves.toMatchObject({ status: "FAILED", eventCursor: 1 });
+ await expect(
+ service.listEvents(dispatchStarting.jobId, 0),
+ ).resolves.toEqual([
+ expect.objectContaining({ kind: "job.failed", cursor: 1 }),
+ ]);
database.sqlite.close();
});
});
diff --git a/apps/web/worker/d1-runner-job-repository.ts b/apps/web/worker/d1-runner-job-repository.ts
index 21c70eaf..9cf97eb8 100644
--- a/apps/web/worker/d1-runner-job-repository.ts
+++ b/apps/web/worker/d1-runner-job-repository.ts
@@ -196,6 +196,22 @@ export class D1RunnerJobRepository implements RunnerJobRepository {
job: RunnerJob,
expectedVersion: number,
event: PublicCompilerEvent,
+ ): Promise {
+ await this.persistEventAndJob(job, expectedVersion, event);
+ }
+
+ async appendTerminalEvent(
+ job: RunnerJob,
+ expectedVersion: number,
+ event: PublicCompilerEvent,
+ ): Promise {
+ await this.persistEventAndJob(job, expectedVersion, event);
+ }
+
+ private async persistEventAndJob(
+ job: RunnerJob,
+ expectedVersion: number,
+ event: PublicCompilerEvent,
): Promise {
const parsedJob = RunnerJobSchema.parse(job);
const parsedEvent = PublicCompilerEventSchema.parse(event);
diff --git a/packages/belief-analyst/src/index.test.ts b/packages/belief-analyst/src/index.test.ts
index 622e5b67..17db2963 100644
--- a/packages/belief-analyst/src/index.test.ts
+++ b/packages/belief-analyst/src/index.test.ts
@@ -550,7 +550,7 @@ describe("privacy-preserving analyst input", () => {
expect(serialized).toContain("[REDACTED_SENSITIVE_FIELD_1]");
expect(serialized).toContain("[REDACTED_SENSITIVE_FIELD_2]");
expect(context.privacy).toMatchObject({
- policyVersion: "outbound-privacy-v2",
+ policyVersion: "outbound-privacy-v3",
suppressedFieldCount: 2,
});
expect(context.privacy.redactions).toContainEqual(
@@ -590,6 +590,88 @@ describe("privacy-preserving analyst input", () => {
expect.objectContaining({ category: "identifier" }),
);
});
+
+ it("discovers sensitive fields beyond the 64-field projection cap", () => {
+ const artifact = manifest();
+ artifact.schemaSummary.fields = [
+ ...Array.from({ length: 64 }, (_, index) => ({
+ name: `public_feature_${index}`,
+ inferredType: "number",
+ privacyClass: "feature",
+ })),
+ {
+ name: "SchoolRecordKey",
+ inferredType: "string",
+ privacyClass: "identifier",
+ },
+ ];
+ artifact.schemaSummary.entityCandidates = ["schoolrecordkey"];
+ artifact.schemaSummary.targetCandidates = [];
+ artifact.cells[0]!.sourceExcerpt = "schoolrecordkey = row.SCHOOLRECORDKEY";
+ artifact.cells[0]!.symbols = ["SchoolRecordKey"];
+
+ const context = buildSanitizedAnalystContext({
+ sessionId: "session_1",
+ learnerClaim: "Does SCHOOLRECORDKEY affect generalization?",
+ manifest: artifact,
+ concept: "entity_leakage",
+ });
+ const serialized = JSON.stringify(context);
+
+ expect(context.schemaSummary.fields).toHaveLength(64);
+ expect(context.privacy.suppressedFieldCount).toBe(1);
+ expect(serialized.toLocaleLowerCase()).not.toContain("schoolrecordkey");
+ expect(serialized).toContain("[REDACTED_SENSITIVE_FIELD_1]");
+ });
+
+ it("normalizes Unicode field aliases and redacts them before truncation", () => {
+ const artifact = manifest();
+ const fullWidthField = "SchoolRecordKey";
+ artifact.schemaSummary.fields.push({
+ name: fullWidthField,
+ inferredType: "lookup for schoolrecordkey",
+ privacyClass: "identifier",
+ });
+ artifact.cells[0]!.sourceExcerpt = `${"x".repeat(690)} SCHOOLRECORDKEY`;
+ artifact.cells[0]!.symbols.push("schoolrecordkey");
+ artifact.cells[0]!.metricCandidates[0]!.name = "schoolrecordkey_accuracy";
+
+ const context = buildSanitizedAnalystContext({
+ sessionId: "session_1",
+ learnerClaim: "Does schoolrecordkey change generalization?",
+ manifest: artifact,
+ concept: "entity_leakage",
+ });
+ const serialized = JSON.stringify(context);
+
+ expect(serialized).not.toContain(fullWidthField);
+ expect(serialized.toLocaleLowerCase()).not.toContain("schoolrecordkey");
+ expect(context.evidence[0]!.sourceExcerpt.length).toBeLessThanOrEqual(700);
+ expect(context.privacy.suppressedFieldCount).toBe(2);
+ });
+
+ it("redacts multi-codepoint case variants of sensitive field names", () => {
+ const artifact = manifest();
+ artifact.schemaSummary.fields.push({
+ name: "İD",
+ inferredType: "string",
+ privacyClass: "identifier",
+ });
+ artifact.cells[0]!.sourceExcerpt = "i\u0307d = row.i\u0307d";
+ artifact.cells[0]!.symbols.push("i\u0307d");
+
+ const context = buildSanitizedAnalystContext({
+ sessionId: "session_1",
+ learnerClaim: "Does i\u0307d affect generalization?",
+ manifest: artifact,
+ concept: "entity_leakage",
+ });
+ const serialized = JSON.stringify(context);
+
+ expect(serialized).not.toContain("i\u0307d");
+ expect(serialized).not.toContain("İD");
+ expect(serialized).toContain("[REDACTED_SENSITIVE_FIELD_");
+ });
});
describe("evidence resolution", () => {
diff --git a/packages/belief-analyst/src/index.ts b/packages/belief-analyst/src/index.ts
index 5eeb11f2..899bad8d 100644
--- a/packages/belief-analyst/src/index.ts
+++ b/packages/belief-analyst/src/index.ts
@@ -19,6 +19,15 @@ import OpenAI from "openai";
import { zodTextFormat } from "openai/helpers/zod";
import { z } from "zod";
+import {
+ LearningDirectorController,
+ OpenAILearningDirectorTransport,
+ type LearningDirectorRegistries,
+ type LearningDirectorTransport,
+} from "./learning-director.js";
+
+export * from "./learning-director.js";
+
export const APPROVED_LEAKAGE_SAMPLE_SHA256 =
"d0e9f3238753f1ca55534446d83e36041590f31c607a011def3f1d0db3a5bbc9";
@@ -406,7 +415,7 @@ type SanitizedAnalystContext = {
metricCandidates: ArtifactManifest["cells"][number]["metricCandidates"];
}>;
privacy: {
- policyVersion: "outbound-privacy-v2";
+ policyVersion: "outbound-privacy-v3";
suppressedFieldCount: number;
redactions: Array<{
category: "secret" | "path" | "identifier" | "sensitive_field";
@@ -452,8 +461,8 @@ export function deriveSafetyIdentifier(sessionId: string): string {
.digest("hex");
}
-function sanitizeText(value: string, maximum = MAX_EXCERPT_CHARACTERS): string {
- const redacted = value
+function redactCommonSensitiveText(value: string): string {
+ return value
.normalize("NFKC")
.replace(/\bsk-[A-Za-z0-9_-]{10,}\b/g, "[REDACTED_SECRET]")
.replace(
@@ -488,31 +497,149 @@ function sanitizeText(value: string, maximum = MAX_EXCERPT_CHARACTERS): string {
/\b((?:account|customer|employee|patient|student|user)[_-]?id\s*[:=]\s*)(["'])[^"'\n]+\2/giu,
"$1[REDACTED_IDENTIFIER]",
);
- return redacted.slice(0, maximum);
+}
+
+function sanitizeText(value: string, maximum = MAX_EXCERPT_CHARACTERS): string {
+ return redactCommonSensitiveText(value).slice(0, maximum);
}
const OUTBOUND_PUBLIC_PRIVACY_CLASSES = new Set(["feature", "target"]);
+const MAX_PRIVACY_FIELD_GROUPS = 512;
+const MAX_PRIVACY_FIELD_NAME_CHARACTERS = 256;
function canExposeSchemaField(privacyClass: string): boolean {
return OUTBOUND_PUBLIC_PRIVACY_CLASSES.has(privacyClass.trim().toLowerCase());
}
+type FieldPrivacyInventory = {
+ aliasesByCanonicalName: ReadonlyMap;
+ replacementEntries: readonly {
+ normalizedName: string;
+ alias: string;
+ }[];
+ suppressedFieldCount: number;
+};
+
+function canonicalFieldName(value: string): string {
+ return value.normalize("NFKC").toLocaleLowerCase("en-US");
+}
+
+function escapedRegex(value: string): string {
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
+}
+
+function fieldNameVariants(value: string): string[] {
+ const normalized = value.normalize("NFKC");
+ return [
+ ...new Set(
+ [
+ normalized,
+ normalized.toLocaleLowerCase("en-US"),
+ normalized.toLocaleUpperCase("en-US"),
+ ].map((variant) => variant.normalize("NFKC")),
+ ),
+ ];
+}
+
+function fieldPrivacyInventory(
+ manifest: ArtifactManifest,
+): FieldPrivacyInventory {
+ const groups = new Map<
+ string,
+ {
+ normalizedName: string;
+ fields: ArtifactManifest["schemaSummary"]["fields"];
+ sensitive: boolean;
+ }
+ >();
+ for (const field of manifest.schemaSummary.fields) {
+ const normalizedName = field.name.normalize("NFKC");
+ if (normalizedName.length > MAX_PRIVACY_FIELD_NAME_CHARACTERS) {
+ throw new BeliefAnalystError(
+ "INVALID_INPUT",
+ "schema field names exceed the bounded outbound privacy policy",
+ );
+ }
+ const key = canonicalFieldName(normalizedName);
+ const existing = groups.get(key);
+ if (existing === undefined) {
+ groups.set(key, {
+ normalizedName,
+ fields: [field],
+ sensitive: !canExposeSchemaField(field.privacyClass),
+ });
+ continue;
+ }
+ existing.fields.push(field);
+ if (!canExposeSchemaField(field.privacyClass)) existing.sensitive = true;
+ }
+ if (groups.size > MAX_PRIVACY_FIELD_GROUPS) {
+ throw new BeliefAnalystError(
+ "INVALID_INPUT",
+ "schema fields exceed the bounded outbound privacy policy",
+ );
+ }
+
+ const aliasesByCanonicalName = new Map();
+ const replacementEntries: Array<{ normalizedName: string; alias: string }> =
+ [];
+ let aliasIndex = 0;
+ let suppressedFieldCount = 0;
+ for (const [key, group] of groups) {
+ if (group.sensitive) {
+ aliasIndex += 1;
+ const alias = `[REDACTED_SENSITIVE_FIELD_${aliasIndex}]`;
+ aliasesByCanonicalName.set(key, alias);
+ for (const variant of fieldNameVariants(group.normalizedName)) {
+ replacementEntries.push({ normalizedName: variant, alias });
+ }
+ suppressedFieldCount += group.fields.length;
+ } else {
+ aliasesByCanonicalName.set(key, sanitizeText(group.normalizedName, 120));
+ }
+ }
+ replacementEntries.sort(
+ (left, right) => right.normalizedName.length - left.normalizedName.length,
+ );
+ return { aliasesByCanonicalName, replacementEntries, suppressedFieldCount };
+}
+
+function sanitizeWithFieldPrivacy(
+ value: string,
+ inventory: FieldPrivacyInventory,
+ maximum = MAX_EXCERPT_CHARACTERS,
+): string {
+ let safe = redactCommonSensitiveText(value);
+ if (inventory.replacementEntries.length > 0) {
+ const aliasByVariant = new Map(
+ inventory.replacementEntries.map(({ normalizedName, alias }) => [
+ canonicalFieldName(normalizedName),
+ alias,
+ ]),
+ );
+ const pattern = inventory.replacementEntries
+ .map(({ normalizedName }) => escapedRegex(normalizedName))
+ .join("|");
+ safe = safe.replace(new RegExp(pattern, "giu"), (match) => {
+ return (
+ aliasByVariant.get(canonicalFieldName(match)) ??
+ "[REDACTED_SENSITIVE_FIELD]"
+ );
+ });
+ }
+ return safe.slice(0, maximum);
+}
+
function sanitizeArtifactText(
value: string,
manifest: ArtifactManifest,
maximum = MAX_EXCERPT_CHARACTERS,
): string {
- let safe = sanitizeText(value, maximum);
- let sensitiveFieldIndex = 0;
- for (const field of manifest.schemaSummary.fields) {
- if (!canExposeSchemaField(field.privacyClass)) {
- sensitiveFieldIndex += 1;
- safe = safe
- .split(field.name)
- .join(`[REDACTED_SENSITIVE_FIELD_${sensitiveFieldIndex}]`);
- }
- }
- return safe;
+ return sanitizeWithFieldPrivacy(
+ value,
+ fieldPrivacyInventory(manifest),
+ maximum,
+ );
}
function validateAnalystInput(input: BeliefAnalystInput): ArtifactManifest {
@@ -554,28 +681,32 @@ export function buildSanitizedAnalystContext(
): SanitizedAnalystContext {
const manifest = validateAnalystInput(input);
const conceptPack = getConceptPack(input.concept);
- const schemaFieldAliases = new Map();
- const sensitiveFieldAliases = new Map();
- let sensitiveFieldIndex = 0;
+ const privacyInventory = fieldPrivacyInventory(manifest);
const schemaFields = manifest.schemaSummary.fields
.slice(0, 64)
.map((field) => {
- if (canExposeSchemaField(field.privacyClass)) {
- const safeName = sanitizeText(field.name, 120);
- schemaFieldAliases.set(field.name, safeName);
+ const canonicalName = canonicalFieldName(field.name);
+ const safeName =
+ privacyInventory.aliasesByCanonicalName.get(canonicalName) ??
+ "[REDACTED_SCHEMA_FIELD]";
+ if (!safeName.startsWith("[REDACTED_SENSITIVE_FIELD_")) {
return {
name: safeName,
- inferredType: sanitizeText(field.inferredType, 120),
+ inferredType: sanitizeWithFieldPrivacy(
+ field.inferredType,
+ privacyInventory,
+ 120,
+ ),
privacyClass: field.privacyClass.trim().toLowerCase(),
};
}
- sensitiveFieldIndex += 1;
- const alias = `[REDACTED_SENSITIVE_FIELD_${sensitiveFieldIndex}]`;
- schemaFieldAliases.set(field.name, alias);
- sensitiveFieldAliases.set(field.name, alias);
return {
- name: alias,
- inferredType: sanitizeText(field.inferredType, 120),
+ name: safeName,
+ inferredType: sanitizeWithFieldPrivacy(
+ field.inferredType,
+ privacyInventory,
+ 120,
+ ),
privacyClass: "sensitive_identifier",
};
});
@@ -583,11 +714,7 @@ export function buildSanitizedAnalystContext(
value: string,
maximum = MAX_EXCERPT_CHARACTERS,
): string => {
- let safe = sanitizeText(value, maximum);
- for (const [fieldName, alias] of sensitiveFieldAliases) {
- safe = safe.split(fieldName).join(alias);
- }
- return safe;
+ return sanitizeWithFieldPrivacy(value, privacyInventory, maximum);
};
const evidenceCells = manifest.cells
.filter(
@@ -641,13 +768,15 @@ export function buildSanitizedAnalystContext(
: { rowCount: manifest.schemaSummary.rowCount }),
entityCandidates: manifest.schemaSummary.entityCandidates.map(
(candidate, index) =>
- schemaFieldAliases.get(candidate) ??
- `[REDACTED_ENTITY_FIELD_${index + 1}]`,
+ privacyInventory.aliasesByCanonicalName.get(
+ canonicalFieldName(candidate),
+ ) ?? `[REDACTED_ENTITY_FIELD_${index + 1}]`,
),
targetCandidates: manifest.schemaSummary.targetCandidates.map(
(candidate, index) =>
- schemaFieldAliases.get(candidate) ??
- `[REDACTED_TARGET_FIELD_${index + 1}]`,
+ privacyInventory.aliasesByCanonicalName.get(
+ canonicalFieldName(candidate),
+ ) ?? `[REDACTED_TARGET_FIELD_${index + 1}]`,
),
hash: schemaSummaryHash(manifest.schemaSummary),
},
@@ -664,15 +793,19 @@ export function buildSanitizedAnalystContext(
return {
...projected,
privacy: {
- policyVersion: "outbound-privacy-v2",
- suppressedFieldCount: sensitiveFieldAliases.size,
+ policyVersion: "outbound-privacy-v3",
+ suppressedFieldCount: privacyInventory.suppressedFieldCount,
redactions: [
markerCounts("[REDACTED_SECRET]", "secret"),
markerCounts("[REDACTED_PATH]", "path"),
markerCounts("[REDACTED_IDENTIFIER]", "identifier"),
{
category: "sensitive_field" as const,
- count: Array.from(sensitiveFieldAliases.values()).reduce(
+ count: Array.from(
+ new Set(
+ privacyInventory.replacementEntries.map(({ alias }) => alias),
+ ),
+ ).reduce(
(count, marker) => count + serialized.split(marker).length - 1,
0,
),
@@ -1490,3 +1623,47 @@ export function createLiveBeliefAnalystFromEnv(
: { transport: overrides.transport }),
});
}
+
+export function createLiveLearningDirectorFromEnv(
+ env: Readonly>,
+ input: {
+ registries: LearningDirectorRegistries;
+ sessionId: string;
+ },
+ overrides: { transport?: LearningDirectorTransport } = {},
+): LearningDirectorController {
+ const apiKey = env.OPENAI_API_KEY?.trim();
+ if (
+ (apiKey === undefined || apiKey.length === 0) &&
+ overrides.transport === undefined
+ ) {
+ throw new BeliefAnalystError(
+ "LIVE_UNAVAILABLE",
+ "OPENAI_API_KEY is not configured",
+ { availableAlternatives: ["approved-sample", "verified-replay"] },
+ );
+ }
+ const configuredEffort = env.OPENAI_REASONING_EFFORT?.trim() || "medium";
+ if (!REASONING_EFFORTS.has(configuredEffort as ReasoningEffort)) {
+ throw new BeliefAnalystError(
+ "CONFIGURATION_ERROR",
+ `unsupported OPENAI_REASONING_EFFORT: ${configuredEffort}`,
+ { allowed: [...REASONING_EFFORTS] },
+ );
+ }
+ const baseURL = normalizeResponsesBaseURL(env.OPENAI_BASE_URL);
+ const transport =
+ overrides.transport ??
+ new OpenAILearningDirectorTransport({
+ apiKey: apiKey ?? "",
+ ...(baseURL === undefined ? {} : { baseURL }),
+ timeoutMs: normalizeResponsesTimeout(env.OPENAI_TIMEOUT_MS),
+ });
+ return new LearningDirectorController({
+ transport,
+ registries: input.registries,
+ model: env.OPENAI_MODEL?.trim() || "gpt-5.6",
+ reasoningEffort: configuredEffort as ReasoningEffort,
+ safetyIdentifier: deriveSafetyIdentifier(input.sessionId),
+ });
+}
diff --git a/packages/belief-analyst/src/learning-director.test.ts b/packages/belief-analyst/src/learning-director.test.ts
new file mode 100644
index 00000000..fc21083e
--- /dev/null
+++ b/packages/belief-analyst/src/learning-director.test.ts
@@ -0,0 +1,686 @@
+import { describe, expect, it } from "vitest";
+import { zodTextFormat } from "openai/helpers/zod";
+
+import {
+ DEFAULT_LEARNING_DIRECTOR_MAX_OUTPUT_TOKENS,
+ LEARNING_DIRECTOR_INSTRUCTIONS,
+ LEARNING_DIRECTOR_NON_CLAIM_IDS,
+ LEARNING_DIRECTOR_TOOL_NAMES,
+ LEARNING_DIRECTOR_TOOLS,
+ LearningDirectorController,
+ LearningDirectorError,
+ LearningDirectorStructuredOutputSchema,
+ OpenAILearningDirectorTransport,
+ type LearningDirectorDecision,
+ type LearningDirectorFunctionCall,
+ type LearningDirectorInput,
+ type LearningDirectorRegistries,
+ type LearningDirectorTransport,
+ type LearningDirectorTransportRequest,
+ type LearningDirectorTransportResult,
+} from "./learning-director.js";
+
+const HASH_A = "a".repeat(64);
+const HASH_B = "b".repeat(64);
+const HASH_C = "c".repeat(64);
+
+const registries: LearningDirectorRegistries = {
+ entity_leakage: {
+ scaffoldIds: ["compare-splits", "hold-controls-fixed"],
+ sceneRecipeIds: ["entity-overlap-stage"],
+ boundaryViewIds: ["test-fraction-by-repeat-rate"],
+ },
+ class_imbalance: {
+ scaffoldIds: ["compare-metrics"],
+ sceneRecipeIds: ["confusion-matrix-stage"],
+ boundaryViewIds: ["threshold-by-prevalence"],
+ },
+};
+
+function packet(
+ overrides: Partial = {},
+): LearningDirectorInput {
+ return {
+ concept: "entity_leakage",
+ subjectPackVersion: "2.1.0",
+ approvedEvidence: [
+ { hash: HASH_A, excerpt: "A row-level split is recorded." },
+ { hash: HASH_B, excerpt: "The entity field repeats across rows." },
+ ],
+ candidateExperimentIds: ["group-holdout", "identity-ablation"],
+ clarificationAlreadyUsed: false,
+ ...overrides,
+ };
+}
+
+function readyPlan(
+ overrides: Partial<
+ Extract