# Project export: PromiseProof

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: GPT-5.6 investigates, Codex repairs, a human approves and an unchanged deterministic verifier decides PASS.
- Devpost: https://devpost.com/software/promiseproof
- GitHub: https://github.com/AlexPaiva/PromiseProof
- Demo: https://promiseproof.alex0paiva0.workers.dev/
- Video: https://www.youtube.com/embed/9nOFdWnhnsI?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Alexandre Paiva (87 commits)

## Devpost submission (written by the team)

### Overview

A setting is a promise. When a product tells you that personalization is off, you would expect that nothing identifying you reaches its recommendation/personalization system right? But what if every setting, service and backend says it is off while one identifying event still crosses that boundary? With a coding agent these issues can be detected and repaired but how can you trust it to be fixed? As a developer, I have learned firsthand that it claiming "it's fixed" is indeed a claim, not a fact. This is the catch PromiseProof tackles: the agent that changed the code should not be the one that decides whether its own repair worked. PromiseProof separates those roles. GPT-5.6 investigates, Codex repairs, a human approves the exact patch and an unchanged deterministic verifier decides PASS. Instructions for Judges No account, API key or install is required to see the core idea. The verdict path uses no backend API, evidence upload or model call. Watch the five-stage walkthrough (a recorded run through all 5 steps: Observe, Investigate, Replay, Repair, Prove): https://promiseproof.alex0paiva0.workers.dev/walkthrough/ Challenge the proof yourself: https://promiseproof.alex0paiva0.workers.dev/verify/?judge=1 On load: PASS and BOUND_AND_REPRODUCED. Press Tamper OFF evidence: BROKEN_PROMISE, violation PP_IDENTIFIABLE_EVENT_LEAK, and the report you sealed a moment ago becomes STALE_OR_MISMATCH. Seal the failing result: it binds honestly as BROKEN_PROMISE. A report can bind a failure; it cannot manufacture a PASS. Download report.json and report.md, or load your own OFF and ON bundles under "Bring your own evidence." Reset to return to the original PASS. On load: PASS and BOUND_AND_REPRODUCED. Press Tamper OFF evidence: BROKEN_PROMISE, violation PP_IDENTIFIABLE_EVENT_LEAK, and the report you sealed a moment ago becomes STALE_OR_MISMATCH. Seal the failing result: it binds honestly as BROKEN_PROMISE. A report can bind a failure; it cannot manufacture a PASS. Download report.json and report.md, or load your own OFF and ON bundles under "Bring your own evidence." Reset to return to the original PASS. Inspect the bundled GitHub Action: https://github.com/AlexPaiva/PromiseProof/tree/submission-rc-03/.github/actions/verify What I built during Build Week PromiseProof started as an empty repository during the submission period. In one week I built the Signal Shelf reference application, two distinguishable cross-system defects, the deterministic evidence and evaluation layer, a bounded GPT-5.6 investigation, an authentic Codex source-repair workflow, exact human patch approval, isolated verification, the five-stage walkthrough, an external-evidence CLI, a hosted semantic challenge and a standalone GitHub Action tested on Windows, Ubuntu and macOS. The public commit history, the Build Week log, the pull requests and the immutable submission-final tag make that work inspectable. This is not a pre-existing platform with one new feature. The complete working implementation shown here was built, tested, productized and released during the Build Week window. See BUILD_WEEK.md and the immutable snapshot at tag submission-final.

### Inspiration

A setting is a promise. When a product or service presents a control as off the users expect the system to honor it everywhere, not just on the screen. In the example of a distributed product the interface, browser storage, the network and the backend can each look correct on their own while the combined behavior quietly breaks the promise: one hidden path still behaves as if the setting were on. There is a second problem arriving fast: while coding agents can now investigate and repair failures like this the model that changed the code is not a trustworthy judge of whether the change actually kept the promise. I wanted to let AI do the open-ended work of diagnosis and repair, while making it structurally impossible for a model to declare its own success.

### What it does

PromiseProof takes one concrete user-facing promise, "when personalization is off, nothing that identifies the user should reach recommendations" and then turns it into a deterministic check, and carries a real failure of that promise through a complete repair-acceptance loop: Observe: A Playwright journey and independent network capture catch one identifiable request crossing the service boundary while personalization is off. Broken promise. Investigate: GPT-5.6 proposes ranked competing causes and selects one allowlisted diagnostic replay, within strict limits. Replay: Deterministic code runs the chosen replay and records the facts. Repair & approve: Codex prepares a constrained two-file patch containing the source repair and a focused regression test in a disposable worktree. A human reviews the exact diff and approves it by its digest. Prove: The approved patch is applied only in a fresh worktree and an unchanged Playwright journey and deterministic evaluator decide PASS. And a judge can verify all of it directly: watch the five-stage lifecycle, tamper one meaningful observation (using the live challenge page), see the prior sealed report become stale, see the current evidence become BROKEN_PROMISE, seal an honest failing report, download JSON and Markdown proof and run the same authority through the CLI or a GitHub Action. How I built it GPT-5.6 investigates Receives a sanitized, versioned dossier through the OpenAI Responses API. Proposes two to four ranked competing hypotheses. Selects one of two allowlisted factual replays through a strict function call. Its result schema has no verdict field and no free-form cause field. It never sees the seeded fixture, source paths, logs, or root-cause labels. Codex repairs Produces an authentic constrained two-file patch containing the source repair and a focused regression test. Works in a disposable git worktree with a restricted writable surface through the pinned Codex SDK. Codex also ran repeated red-team reviews that surfaced the original free-text-cause and verdict-language weakness, which led to the ID-only investigation schema and the strict report validation the verifier uses today. A human approves Reviews the exact patch and approves it by its digest in a real terminal. Nothing merges automatically, no model approval. The unchanged verifier decides The same Playwright journey and deterministic evaluator that caught the break. Checks OFF, Reload, ON, Browser and Control. Models are structurally excluded from PASS. Adoption surfaces: a hosted browser verifier, a repository-local CLI and a self-contained GitHub Action tested as a standalone consumer on Windows, Ubuntu and macOS. No model call runs in the verdict path on any surface. Challenges I ran into Two failures with the same visible symptom: An initialization race and a preference-propagation failure both break the same OFF promise, but they produce different evidence, different violation codes and require a different diagnostic replay. One flag cannot explain both and one fix cannot silence the other. Preventing a false green: Disabling all personalization must fail and not pass. OFF must stay protected while ON functionality keeps working, and the contextual recommendation feed must stay useful. The check is designed so "turn everything off" cannot satisfy the promise. Keeping AI useful without granting it authority: GPT-5.6 can reason about uncertainty but cannot declare success. Codex can prepare a patch but cannot merge or approve it. Only unchanged deterministic evidence determines PASS. Making the proof portable: Evidence is canonicalized and bound with SHA-256 to a pinned evaluator fingerprint, and check reproduces the complete report rather than trusting hashes, so a stale or forged report is rejected. Accomplishments I'm proud of Built the complete product within the Build Week window as a solo developer. Two distinguishable seeded failures with different evidence and violation codes. Two allowlisted diagnostic replays. One authentic, human-approved Codex source repair. Five independent verification clauses. Three developer surfaces: browser, CLI and GitHub Action. The GitHub Action tested as a standalone consumer on Windows, Ubuntu and macOS. Byte-identical browser and CLI gate reports for the same evidence. A hosted semantic tamper challenge that runs entirely in the browser. Complete report reproduction rather than hash-only checking. Zero model-owned verdicts and zero model calls in the verifier verdict path. An immutable submission-final snapshot of the complete submission. What I learned The most useful lesson to me was that integrity and correctness are two different things. A report can be perfectly attached to its evidence and still honestly say that the promise is broken. While binding proves that a report is faithful to its evidence it does not (and should not!) manufacture a passing verdict. I also learned that bounded AI is not weaker AI. GPT-5.6 stays valuable precisely because it investigates uncertainty, while its inability to award PASS is what protects the result. Finally, the hard part of AI-assisted repair is not producing a patch. It is building an acceptance process that proves the exact approved patch fixed the restricted state without breaking the permitted one.

### What's next

Build deterministic evidence-producer adapters so more apps can emit compatible evidence. Validate the architecture against a second real application. Add another contract family only after independent validation. Improve developer onboarding around normalized evidence production. Explore reusable typed interfaces once more than one integration proves the abstraction. No promise of universal support, automated evidence collection or arbitrary contracts. Testing and supported platforms Hosted, no rebuild: https://promiseproof.alex0paiva0.workers.dev/verify/?judge=1 Repository-local CLI: npm run promiseproof -- gate|verify|check GitHub Action: uses: AlexPaiva/PromiseProof/.github/actions/verify@submission-rc-03 Install for local runs: npm ci then npx playwright install chromium (Chromium is only needed for the walkthrough tests, not for the verifier!). Supported platforms: the CLI is directly verified on Windows and the bundled Action is exercised on Windows, Ubuntu and macOS. Local no-key checks: npm run demo:rehearse, then npm run test:external and npm run test:action. Current limitations One synthetic reference application (Signal Shelf), synthetic by design so one broken promise is visible and repairable end to end. Not a legal or regulatory compliance statement. Exactly one external contract family is supported: activity-personalization/v1. External evidence is evaluated but not collection-attested. Binding proves a report matches its evidence and the pinned evaluator source but it does not prove the evidence was collected honestly. Integrity is repository-level and content-addressed. It is not a signature, notarization, certification or third-party attestation. The main branch stays intentionally seeded-broken so the red-to-green repair can be demonstrated, production is not automatically repaired.

## README (from the GitHub repository)

<div align="center">

# 🛡️ PromiseProof

### When software breaks a promise, the AI that repairs it doesn't get the final word.

**GPT-5.6 investigates. Codex repairs. Neither decides PASS.**

A human approves the exact patch. An unchanged deterministic verifier decides whether the promise is actually fixed.

[![CI](https://github.com/AlexPaiva/PromiseProof/actions/workflows/submission-hardening.yml/badge.svg)](https://github.com/AlexPaiva/PromiseProof/actions/workflows/submission-hardening.yml)
[![OpenAI Build Week 2026](https://img.shields.io/badge/OpenAI-Build_Week_2026-10a37f)](https://openai.com)
[![License: MIT](https://img.shields.io/badge/License-MIT-3f6bf0.svg)](LICENSE)
[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6)](https://www.typescriptlang.org/)
[![Playwright](https://img.shields.io/badge/Playwright-verified-2fa968)](https://playwright.dev/)
[![Cloudflare Workers](https://img.shields.io/badge/hosted-Cloudflare_Workers-f38020)](https://workers.cloudflare.com/)

**[▶ Live demo](https://promiseproof.alex0paiva0.workers.dev/)** · **[⚡ Challenge the proof](https://promiseproof.alex0paiva0.workers.dev/verify/?judge=1)** · **[Judge Start Here](JUDGE_START_HERE.md)** · **[Walkthrough](https://promiseproof.alex0paiva0.workers.dev/walkthrough/)** · [How it works](#how-it-works) · [Run it in CI](#try-it-yourself)

![PromiseProof: find the boundary that broke the promise](public/og-card.png)

</div>

---

## Contents

1. [The 30-second version](#the-30-second-version)
2. [See the proof](#see-the-proof)
3. [The problem](#the-problem)
4. [Who decides it is fixed](#who-decides-it-is-fixed)
5. [Try it yourself](#try-it-yourself)
6. [Use it on your own project](#use-it-on-your-own-project)
7. [How it works](#how-it-works)
8. [GPT-5.6 and Codex](#gpt-56-and-codex)
9. [Architecture](#architecture)
10. [Limitations](#limitations)

## The 30-second version

1. **A user turned personalization off.** The interface, the browser storage, and the backend all reported the same thing: off.
2. **One identifiable request still crossed into recommendations.** The product had quietly broken the promise it showed the user.
3. **GPT-5.6 investigated within strict limits, Codex proposed a real source repair in isolation, and a human approved the exact change.**
4. **The same unchanged test that caught the break decided whether the repair worked, not a model.** You can challenge that verdict yourself in the browser, and drop the identical check into your CI.

Most tools confirm that your tests pass. PromiseProof answers the one question a model should never answer about its own work: *is the fix actually real?*

## See the proof

Three states, no setup: the verifier passes, breaks under a tamper, and the same authority gates your CI. Same evaluator, no model in the verdict path, nothing uploaded.

<div align="center">
  <img src="docs/screenshots/03-unchanged-verifier.png" alt="PromiseProof verifier returning PASS with five passing clauses" width="840">
  <br>
  <sub>The unchanged evaluator, running in your browser, returns <b>PASS</b> bound to this evidence. Five clauses hold across OFF, reload, and the ON control, and the verdict path makes no model call.</sub>
</div>

<br>

<div align="center">
  <img src="docs/screenshots/04-challenge-and-ci.png" alt="PromiseProof verifier after a tamper, showing BROKEN_PROMISE and a stale sealed report" width="840">
  <br>
  <sub>Change one load-bearing observation and the same evaluator flips to <b>BROKEN_PROMISE</b> with <code>PP_IDENTIFIABLE_EVENT_LEAK</code>, while the report you sealed a moment ago no longer reproduces (<b>STALE_OR_MISMATCH</b>). A PASS cannot be carried onto changed evidence.</sub>
</div>

<br>

<div align="center">
  <img src="docs/screenshots/05-cli-gate.png" alt="Terminal running the PromiseProof gate: a kept promise exits 0, a broken promise exits 2" width="840">
  <br>
  <sub>The same deterministic authority in your terminal and CI: a kept promise exits <code>0</code>, a broken one exits <code>2</code> and fails the build.</sub>
</div>

The full lifecycle, from broken promise to proof, plays in order in the [five-stage walkthrough](https://promiseproof.alex0paiva0.workers.dev/walkthrough/).

## The problem

A user turns personalization **off**. The UI says off. Browser storage says off. The backend says off. And yet one *identifiable* request still crosses the boundary to the recommendation service.

Monitoring can show that a promise broke. PromiseProof carries that evidence through bounded diagnosis, constrained repair, and independent verification, and it never lets the one thing that might be wrong, a model, declare itself correct.

It turns an "off means off" promise into an executable check, helps locate the implementation boundary that broke it, lets AI investigate and propose a repair, then hands the verdict to an unchanged deterministic test.

### Who it is for

PromiseProof is for product, QA, privacy, reliability, and platform engineers responsible for user-facing controls that cross browser, storage, network, and backend boundaries. It turns an ambiguous report like "OFF did not behave like OFF" into reproducible evidence and a bounded diagnostic action, so a team finds the responsible subsystem sooner. As coding agents start changing repositories on their own, it also gives those teams an acceptance gate that is inspectable, human-approved, and reproducible in the CI they already trust.

## Why this is more than a privacy test

The personalization toggle is the proving example, not the limit of the idea. User-facing promises routinely cross the interface, storage, network, and backend at once, and every one of those surfaces can look locally correct while the combined behavior is wrong. A monitor can surface that contradiction. PromiseProof is what happens next: it carries the contradiction through bounded diagnosis, a real source repair, exact human approval, independent acceptance, full report reproduction, and CI enforcement, without ever letting the model that proposed the fix certify it. It supports one contract family today, `activity-personalization/v1`, and the mechanism is built to generalize; the honest scope is stated in [Limitations](#limitations).

## Who decides it is fixed

> **A model may investigate and repair. Deterministic evidence keeps the verdict.**

The final investigation schema has **no verdict field**. GPT-5.6 proposes and ranks candidate causes and requests one *allowlisted* diagnostic replay. Codex proposes a constrained two-file repair in a disposable worktree. A human approves the exact patch by its fingerprint. Then an **unchanged Playwright journey and deterministic evaluator**, the very ones that caught the break, decide PASS or FAIL.

The AI never grades its own work. That is the whole point. The authority table and the tests that enforce it are in [JUDGE_START_HERE.md](JUDGE_START_HERE.md#the-model-cannot-award-itself-pass).

## Why the patch is small

The approved repair is intentionally small, and that is the point. Generating two changed files is not the hard part. The hard part is proving that the correct implementation boundary changed, that the restricted OFF behavior is now protected, that the permitted ON behavior still works, that the applied patch is exactly what the human approved, and that neither model was able to certify its own code. A one-line fix carried through that chain proves more than a large patch a model graded itself.

| Verified fact | Current release |
| --- | --- |
| Distinguishable seeded failures | 2 |
| Allowlisted factual replays | 2 |
| Authentic approved Codex repair | 1 |
| Independent verification clauses | 5 |
| Developer surfaces | Browser, CLI, GitHub Action |
| Action runner operating systems | Windows, Ubuntu, macOS |
| Model-owned verdicts | 0 |
| Model calls in the verifier path | 0 |

## Try it yourself

Three ways in, none of them require an API key.

**1. Watch the complete lifecycle.** T

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 138 recognized source files, 1609 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- OpenAI (technology) — detected in the code
- TypeScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (120 of 173)

```
.env.example
.gitattributes
.github/actions/verify/action.yml
.github/actions/verify/package.json
.github/actions/verify/README.md
.github/consumer-smoke/assert.mjs
.github/consumer-smoke/symlink.mjs
.github/workflows/submission-hardening.yml
.gitignore
ADOPTION.md
AGENTS.md
artifacts/judge/judge-bundle.json
artifacts/milestone-03-live-stability.json
artifacts/verify/activity-personalization.v1.schema.json
artifacts/verify/broken-off.example.json
artifacts/verify/passing-gate.report.json
artifacts/verify/passing-gate.report.md
artifacts/verify/passing-off.example.json
artifacts/verify/passing-on.example.json
artifacts/verify/producer-template.mjs
artifacts/verify/README.md
BUILD_WEEK.md
docs/evidence/milestone-04-authentic-repair/artifact-manifest.json
docs/evidence/milestone-04-authentic-repair/candidate.patch
docs/evidence/milestone-04-authentic-repair/original/human-decision.json
docs/evidence/milestone-04-authentic-repair/original/lifecycle.json
docs/evidence/milestone-04-authentic-repair/original/playwright/propagation-expected-red.last-run.json
docs/evidence/milestone-04-authentic-repair/original/playwright/propagation-green.last-run.json
docs/evidence/milestone-04-authentic-repair/original/playwright/race-off-five.last-run.json
docs/evidence/milestone-04-authentic-repair/original/playwright/race-off-single.last-run.json
docs/evidence/milestone-04-authentic-repair/original/playwright/race-on-five.last-run.json
docs/evidence/milestone-04-authentic-repair/original/playwright/race-on-single.last-run.json
docs/evidence/milestone-04-authentic-repair/original/playwright/startup-regression.last-run.json
docs/evidence/milestone-04-authentic-repair/original/verification-receipt.json
docs/evidence/milestone-04-authentic-repair/README.md
docs/evidence/milestone-04-authentic-repair/sanitized/candidate-state.json
docs/evidence/milestone-04-authentic-repair/summary.json
docs/evidence/milestone-04-authentic-repair/verification.md
index.html
JUDGE_START_HERE.md
judge.html
landing/index.html
LICENSE
package.json
playwright.config.ts
README.md
scripts/build-site.mjs
scripts/check-action-bundle.mjs
scripts/demo-rehearse.ts
scripts/evidence-verify.ts
scripts/repair-race.ts
scripts/serve-site.mjs
scripts/test-expected-red.ts
scripts/test-expected-red.unit.ts
scripts/verify-live-stability.ts
scripts/verify-live-stability.unit.ts
src/action/index.ts
src/client/main.ts
src/client/styles.css
src/investigation/artifact.ts
src/investigation/authority-boundary.ts
src/investigation/canonical-json.ts
src/investigation/contracts.ts
src/investigation/dispatcher.ts
src/investigation/dossier.ts
src/investigation/immutable.ts
src/investigation/openai-provider.ts
src/investigation/prompt.ts
src/investigation/provider.ts
src/investigation/runner.ts
src/investigation/schemas.ts
src/investigation/validation.ts
src/judge/bundle-contract.ts
src/judge/main.ts
src/judge/rehearsal.ts
src/judge/styles.css
src/repair/approval.ts
src/repair/artifact.ts
src/repair/codex-provider.ts
src/repair/command-environment.ts
src/repair/contracts.ts
src/repair/diff-validator.ts
src/repair/eligibility.ts
src/repair/foundation.ts
src/repair/git.ts
src/repair/prompt.ts
src/repair/provider.ts
src/repair/repair-flow.ts
src/repair/retirement.ts
src/repair/runner.ts
src/repair/schemas.ts
src/repair/verification.ts
src/repair/windows-sandbox.ts
src/repair/worktree.ts
src/server/api.ts
src/server/app.ts
src/server/domain.ts
src/server/main.ts
src/server/preference-service.ts
src/server/store.ts
src/server/validation.ts
src/shared/diagnostics.ts
src/shared/evaluator.ts
src/shared/types.ts
src/verify-ui/main.ts
src/verify-ui/styles.css
src/verify/adapter.ts
src/verify/binding.ts
src/verify/check.ts
src/verify/cli.ts
src/verify/examples.ts
src/verify/outcome.ts
src/verify/report.ts
src/verify/schema.ts
src/verify/verify.ts
tests/action/action.unit.ts
tests/action/fixture.unit.ts
tests/contracts/personalization-off.spec.ts
tests/contracts/playwright.config.ts
tests/control/personalization-on.spec.ts
[53 more files omitted for size]
```

### Dependencies

- package.json: @openai/codex-sdk@0.144.4, @playwright/test@^1.53.2, @types/express@^5.0.3, @types/node@^22.15.32, cross-env@^7.0.3, esbuild@^0.25.5, express@^5.1.0, openai@^6.47.0, tsx@^4.20.3, typescript@^5.8.3, vite@^7.0.0, zod@^4.4.3

### Recent commits (newest first)

- Merge pull request #8 from AlexPaiva/feature/readme-proof-refresh
- Merge pull request #7 from AlexPaiva/feature/verify-try-panel
- docs: refresh the proof screenshots and add the CLI gate shot
- feat: make the verifier control panel a clear "Try it here" call to action
- Merge pull request #6 from AlexPaiva/feature/verify-clarity
- style: violet accent, verifier header arrows, numbered index, centered proof
- feat: unify the verifier chrome with the overview and add evidence templates
- feat: add a results glossary and button tooltips to the hosted verifier
- Merge pull request #5 from AlexPaiva/feature/readme-polish
- docs: tighten the adoption intro and the model-authority wording
- docs: add a contents index, a use-it-on-your-project guide, and sell the model roles
- Merge pull request #4 from AlexPaiva/feature/final-visual-story
- docs: surface the committed reproducible receipt and add a CI badge
- docs: lead with a visual judge proof and summarize the finished build week
- docs: tighten the judge guide wording
- style: improve wording in the verifier and walkthrough copy
- style: improve wording on the verifier page hero
- Merge pull request #3 from AlexPaiva/feature/judge-package
- feat: surface the in-browser challenge on the landing
- docs: add judge start guide and pin the action release tag

## Key source files (fetched from GitHub, selected and truncated for size)

### JUDGE_START_HERE.md

```markdown
# PromiseProof: Judge Start Here

## 15-second explanation

When AI says "fixed," that is a claim, not a fact. GPT-5.6 investigates. Codex repairs. Neither decides PASS. An unchanged deterministic verifier does.

## Judge in 60 seconds

**1. Watch the recorded repair lifecycle.**
https://promiseproof.alex0paiva0.workers.dev/walkthrough/

Five stages: Observe, Investigate, Replay, Repair, Prove. It replays one authentic run: a user turned personalization off, one identifiable request still reached the recommendation service, GPT-5.6 ranked competing hypotheses and selected one allowlisted factual replay, Codex proposed a two-file repair, a human approved the exact patch, and an unchanged test decided PASS.

**2. Challenge the proof yourself.**
https://promiseproof.alex0paiva0.workers.dev/verify/?judge=1

After the page loads, verification runs entirely in the browser. The verdict path uses no backend API, evidence upload, or model call. Expected states:

- On load: `PASS` and `BOUND_AND_REPRODUCED`.
- After you tamper one OFF observation: `BROKEN_PROMISE`, violation `PP_IDENTIFIABLE_EVENT_LEAK`, and the report you sealed a moment earlier becomes `STALE_OR_MISMATCH`.
- After you seal the failing evidence: `BROKEN_PROMISE` and `BOUND_AND_REPRODUCED`. A report can honestly bind a failing verdict; it cannot manufacture a PASS.
- After reset: the original `PASS` and original evidence bindings return.

**3. Inspect the GitHub Action.**
https://github.com/AlexPaiva/PromiseProof/tree/submission-rc-03/.github/actions/verify

A bundled Action that runs the same verifier in CI with no `npm install`, no browser, and no API key.

## Who has authority

| Actor | May investigate | May propose source changes | May approve the exact patch | May decide PASS |
| --- | --- | --- | --- | --- |
| GPT-5.6 | Yes | No | No | No |
| Codex | Yes | Yes, in isolation | No | No |
| Human | Yes | No | Yes | No |
| Unchanged verifier | No | No | No | Yes |

## The model cannot award itself PASS

This split is enforced in code and covered by tests, not merely asserted:

- **No verdict field exists.** The final investigation contract in `src/investigation/contracts.ts` has no overall-verdict field; the only verdict-adjacent limitation code is `diagnostic_not_verdict` ("Diagnostic hypotheses do not determine the product promise verdict.").
- **Verdict language from the model is rejected.** `tests/investigation/investigation.unit.ts` asserts `PP_INV_VERDICT_LANGUAGE_REJECTED` when model output uses reserved verdict wording.
- **A claimed model verdict is ignored by the matrix.** `tests/judge/rehearsal.unit.ts`, "verification matrix rejects missing propagation control, browser errors, and model verdict claims," proves a supplied `modelVerificationVerdict` cannot turn a fail into a pass.
- **Approval is human and digest-bound.** `tests/repair/artifact-approval.unit.ts`, "accepts only the exact APPROVE or REJECT phrase for the current digest," proves the patch is approved by a human ag
[truncated — 3119 more characters]
```

### AGENTS.md

```markdown
# PromiseProof — OpenAI Build Week

## Objective

Build a Developer Tools submission that proves whether a product keeps
the personalization choice it presents to users.

## Canonical promise

When activity-based personalization is OFF:

- No identifiable activity reaches the recommendation service.
- The feed remains functional using contextual recommendations.
- The preference survives a reload.

When personalization is ON:

- Expected activity reaches the recommendation service.
- Behavioral recommendations remain functional.

## Seeded root causes

1. Initialization race:
   the activity collector starts before preference hydration completes.

2. Propagation failure:
   the frontend changes to OFF but the backend preference remains ON.

The defects must produce different evidence and different diagnostic
actions.

## Required architecture

- Playwright executes the user journey.
- Deterministic code collects and evaluates evidence.
- GPT-5.6 maintains hypotheses and selects a whitelisted diagnostic replay.
- Codex prepares a minimal source patch and regression test.
- Repairs occur in a disposable Git worktree.
- A human reviews the diff.
- Playwright decides whether the repair passes.

## Forbidden shortcuts

- Never weaken the contract or assertion thresholds.
- Never disable recommendations globally.
- Never hard-code the diagnosis from a selected demo mode.
- Never let a model declare that verification passed.
- Never claim legal or regulatory compliance.
- Do not add another product promise before the canonical loop works.
- Do not create accounts, billing, GitHub OAuth, or production crawling.

## Current milestone — Milestone 04 bounded Codex repair

Milestone 03 is frozen at commit `65a5dd6` and tag
`milestone-03-gpt56-investigation`. Its evaluator, canonical contract, seeded
defects, evidence capture, GPT-5.6 schemas, live receipt, and diagnostic replays
remain outside repair write scope.

Build one complete repair path for the initialization-race evidence signature:

verified deterministic violation
→ validated Milestone 03 live receipt
→ replay-grounded repair eligibility
→ detached disposable Git worktree
→ one bounded Codex SDK turn
→ minimal source patch plus one regression test
→ deterministic diff firewall
→ explicit human approve or reject decision
→ second fresh detached verification worktree
→ exact approved patch reapplied by digest
→ unchanged Playwright contract and controls
→ retained deterministic repair receipt
→ safe worktree cleanup only after evidence is saved

The propagation defect remains intentionally broken. It demonstrates that the
investigation selected a different replay and that the race repair is not a
global disablement or universal hard-coded path.

Milestone 04 invariants:

1. Derive repair eligibility from versioned, runtime-validated evidence. Require
   the exact race violation, the startup-order replay, replay-grounded ordering
   facts, and the completed live GPT-5.6 cohort. Never use `DEMO_MODE`,
[truncated — 5825 more characters]
```

### package.json

```
{
  "name": "promiseproof",
  "version": "0.1.0",
  "private": true,
  "license": "MIT",
  "type": "module",
  "description": "Deterministic product-promise verification for activity-based personalization.",
  "engines": {
    "node": ">=22.12.0",
    "npm": ">=10"
  },
  "scripts": {
    "dev": "npm run dev:race",
    "dev:race": "cross-env NODE_ENV=development DEMO_MODE=initialization-race tsx watch src/server/main.ts",
    "dev:propagation": "cross-env NODE_ENV=development DEMO_MODE=propagation-failure tsx watch src/server/main.ts",
    "dev:test": "npm run dev:test:race",
    "dev:test:race": "cross-env NODE_ENV=test DEMO_MODE=initialization-race tsx src/server/main.ts",
    "dev:test:propagation": "cross-env NODE_ENV=test DEMO_MODE=propagation-failure tsx src/server/main.ts",
    "build": "npm run typecheck && vite build && esbuild src/server/main.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/server.mjs",
    "start": "npm run start:race",
    "start:race": "cross-env NODE_ENV=production DEMO_MODE=initialization-race node dist/server.mjs",
    "start:propagation": "cross-env NODE_ENV=production DEMO_MODE=propagation-failure node dist/server.mjs",
    "typecheck": "tsc --noEmit",
    "promiseproof": "tsx src/verify/cli.ts",
    "test:external": "tsx --test tests/verify/core.unit.ts tests/verify/cli.unit.ts tests/verify/check.unit.ts",
    "test:external:pipeline": "cross-env PROMISEPROOF_EQUIVALENCE_MODE=initialization-race playwright test --config=tests/verify/pipeline.playwright.config.ts && cross-env PROMISEPROOF_EQUIVALENCE_MODE=propagation-failure playwright test --config=tests/verify/pipeline.playwright.config.ts",
    "test:verify:judge": "playwright test --config=tests/verify/judge-mode.playwright.config.ts",
    "evidence:verify": "tsx scripts/evidence-verify.ts",
    "demo:rehearse": "tsx scripts/demo-rehearse.ts",
    "test:judge:unit": "tsx --test tests/judge/rehearsal.unit.ts",
    "test:judge:interface": "playwright test tests/judge/interface.spec.ts",
    "test:judge:screenshots": "playwright test tests/judge/screenshots.spec.ts",
    "test:evidence:unit": "tsx --test tests/evidence/evidence-verify.unit.ts",
    "test": "npm run test:investigation:unit && npm run test:live-stability:unit && npm run test:repair:unit && npm run test:race && npm run test:propagation",
    "test:race": "playwright test tests/health tests/control tests/detector tests/diagnostics tests/investigation/offline.spec.ts",
    "test:propagation": "playwright test --config=tests/propagation/playwright.config.ts",
    "test:health": "playwright test tests/health tests/control",
    "test:detector": "playwright test tests/detector tests/diagnostics",
    "test:determinism": "playwright test --config=tests/determinism/playwright.config.ts && playwright test --config=tests/propagation/determinism.config.ts",
    "test:investigation:unit": "tsx --test tests/investigation/investigation.unit.ts",
    "test:live-stability:unit": "tsx --test scripts/verify-live-stability.unit.ts",
    "test:repair:unit": "tsx --test tests/repair/artifact-approval.unit.ts tests/repair/codex-provider.unit.ts tests/repair/eligibility.unit.ts tests/repair/recovery.unit.ts tests/repair/verification.unit.ts tests/repair/windows-sandbox.unit.ts tests/repair/worktree-plan.unit.ts tests/repair/worktree.unit.ts",
    "test:repair:elevated-sandbox": "tsx --test tests/repair/elevated-sandbox.integration.ts",
    "test:repair:offline": "npm run test:repair:unit && tsx --test tests/repair/offline.integration.ts",
    "test:investigation:offline": "npm run test:investigation:unit && npm run test:live-stability:unit && playwright test tests/investigation/offline.spec.ts && playwright test --config=tests/investigation/propagation.offline.config.ts",
    "investigate:live": "npm run investigate:live:race && npm run investigate:live:propagation",
    "investigate:live:race": "node --env-file-if-exists=.env.local ./node_modules/@playwright/test/cli.js test --config=tests/investigation/race.live.config.ts",
    "investigate:live:propagation": "node --env-file-if-exists=.env.local ./node_modules/@playwright/test/cli.js test --config=tests/investigation/propagation.live.config.ts",
    "investigate:live:stability": "npm run prepare:investigation:live-stability && npm run investigate:live:stability:race && npm run investigate:live:stability:propagation && npm run verify:investigation:live-stability",
    "prepare:investigation:live-stability": "node ./node_modules/tsx/dist/cli.mjs scripts/verify-live-stability.ts --capture-source-snapshot",
    "investigate:live:stability:race": "npm run investigate:live:race -- --repeat-each=3 --output=test-results/investigation-live-stability-race",
    "investigate:live:stability:propagation": "npm run investigate:live:propagation -- --repeat-each=3 --output=test-results/investigation-live-stability-propagation",
    "verify:investigation:live-stability": "node --env-file-if-exists=.env.local ./node_modules/tsx/dist/cli.mjs scripts/verify-live-stability.ts",
    "test:expected-red:unit": "tsx --test scripts/test-expected-red.unit.ts",
    "test:expected-red": "npm run test:expected-red:unit && tsx scripts/test-expected-red.ts",
    "verify:promise": "npm run verify:promise:race",
    "verify:promise:race": "playwright test --config=tests/contracts/playwright.config.ts",
    "verify:promise:propagation": "playwright test --config=tests/propagation/contract.config.ts",
    "repair:race:prepare": "node --env-file-if-exists=.env.local ./node_modules/tsx/dist/cli.mjs scripts/repair-race.ts prepare",
    "repair:race:status": "node ./node_modules/tsx/dist/cli.mjs scripts/repair-race.ts status",
    "repair:race:review": "node ./node_modules/tsx/dist/cli.mjs scripts/repair-race.ts review",
    "repair:race:retire": "node ./node_modules/tsx/dist/cli.mjs scripts/repair-race.ts retire",
    "repair:race:verify": "node ./node_modules/tsx/dist/cli.mjs scripts/repair-race.ts verify",
    "repai
[truncated — 941 more characters]
```

### .github/actions/verify/package.json

```
{
  "name": "promiseproof-verify-action",
  "private": true,
  "type": "commonjs",
  "description": "Bundled PromiseProof Verify GitHub Action. The committed dist/index.js is generated; run `npm run build:action` from the repository root to regenerate it.",
  "main": "dist/index.js"
}

```

### src/server/main.ts

```typescript
import { createApplication } from "./app.js";
import { readDemoMode } from "./domain.js";

function readPort(value = process.env.PORT): number {
  if (value === undefined) {
    return 4173;
  }

  const parsed = Number(value);
  if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65_535) {
    throw new Error(`Invalid PORT "${value}".`);
  }

  return parsed;
}

const demoMode = readDemoMode();

const port = readPort();
const { application, dispose } = await createApplication({ demoMode });
const server = application.listen(port, "127.0.0.1", () => {
  console.log(`PromiseProof listening at http://127.0.0.1:${port}`);
});

let shuttingDown = false;

function shutDown(): void {
  if (shuttingDown) {
    return;
  }
  shuttingDown = true;

  server.close(async (error) => {
    try {
      await dispose();
    } catch (disposeError) {
      console.error(disposeError);
      process.exitCode = 1;
    }
    if (error !== undefined) {
      console.error(error);
      process.exitCode = 1;
    }
  });
}

process.once("SIGINT", shutDown);
process.once("SIGTERM", shutDown);

```

### src/server/app.ts

```typescript
import path from "node:path";

import express, {
  type ErrorRequestHandler,
  type Express,
  type Request,
  type Response,
} from "express";
import { createServer as createViteServer } from "vite";

import { createApiRouter } from "./api.js";
import { readDemoMode } from "./domain.js";
import { PromiseProofStore } from "./store.js";
import { RequestValidationError } from "./validation.js";
import type { DemoMode } from "../shared/types.js";

export interface ApplicationOptions {
  demoMode?: DemoMode;
  production?: boolean;
  store?: PromiseProofStore;
}

export interface ApplicationRuntime {
  application: Express;
  dispose: () => Promise<void>;
}

function apiNotFound(request: Request, response: Response): void {
  response.status(404).json({
    error: {
      code: "PP_API_NOT_FOUND",
      message: `No API route matches ${request.method} ${request.originalUrl}.`,
    },
  });
}

export async function createApplication(
  options: ApplicationOptions = {},
): Promise<ApplicationRuntime> {
  const application = express();
  const store = options.store ?? new PromiseProofStore();
  const demoMode = options.demoMode ?? readDemoMode();
  const production = options.production ?? process.env.NODE_ENV === "production";
  let dispose = async (): Promise<void> => undefined;

  application.disable("x-powered-by");
  application.use((_request, response, next) => {
    // Test URLs carry a synthetic user ID; never forward it in a Referer header
    // when the browser calls the logical recommendation-service boundary.
    response.setHeader("referrer-policy", "no-referrer");
    next();
  });
  application.use(express.json({ limit: "32kb", strict: true }));
  application.use("/api", (_request, response, next) => {
    response.setHeader("cache-control", "no-store");
    next();
  });
  application.use("/api", createApiRouter(store, demoMode));
  application.use("/api", apiNotFound);

  // The judge walkthrough is a separate static entry point. It never touches the
  // Signal Shelf application, its state, or the seeded fixture.
  application.use((request, _response, next) => {
    if (
      request.method === "GET" &&
      (request.path === "/judge" || request.path === "/judge/")
    ) {
      request.url = "/judge.html";
    }
    next();
  });

  if (production) {
    const clientDirectory = path.resolve(process.cwd(), "dist/client");
    application.use(express.static(clientDirectory));
    application.use((request, response, next) => {
      if (request.method !== "GET" || !request.accepts("html")) {
        next();
        return;
      }

      response.sendFile(path.join(clientDirectory, "index.html"));
    });
  } else {
    const vite = await createViteServer({
      appType: "spa",
      server: {
        middlewareMode: true,
        hmr: process.env.NODE_ENV === "test" ? false : undefined,
      },
    });
    dispose = async () => vite.close();
    application.use(vite.middlewares);
  }

  const errorHandler: ErrorRequestHandler = (error, _request, response, _next) => {
    if (error instanceof RequestValidationError) {
      response.status(400).json({
        error: { code: error.code, message: error.message },
      });
      return;
    }

    if (error instanceof SyntaxError && "body" in error) {
      response.status(400).json({
        error: {
          code: "PP_INVALID_JSON",
          message: "Request body contains invalid JSON.",
        },
      });
      return;
    }

    console.error(error);
    response.status(500).json({
      error: {
        code: "PP_INTERNAL_ERROR",
        message: "The server could not complete the request.",
      },
    });
  };

  application.use(errorHandler);
  return { application, dispose };
}

```

### src/verify/cli.ts

```typescript
import {
  lstat,
  mkdir,
  readFile,
  stat,
  writeFile,
} from "node:fs/promises";
import path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";

import {
  CHECK_EXIT,
  checkGate,
  checkSingle,
} from "./check.js";
import {
  brokenOffExample,
  passingOffExample,
  passingOnExample,
  producerTemplate,
  scaffoldReadme,
} from "./examples.js";
import {
  EXIT_CODE,
  type ExternalOutcome,
} from "./outcome.js";
import {
  createGateReport,
  createVerifyReport,
  serializeGateReportMarkdown,
  serializeReportJson,
  serializeVerifyReportMarkdown,
} from "./report.js";
import {
  externalBundleJsonSchema,
  MAX_INPUT_BYTES,
} from "./schema.js";
import {
  gateValidationIssues,
  runGate,
  verifyBundle,
} from "./verify.js";

const HELP = `PromiseProof external evidence verifier

Usage:
  npm run promiseproof -- init --out <directory>
  npm run promiseproof -- verify --evidence <bundle.json> --out <directory>
  npm run promiseproof -- gate --off <off-bundle.json> --on <on-bundle.json> --out <directory>
  npm run promiseproof -- check --report <report.json> --evidence <bundle.json>
  npm run promiseproof -- check --report <report.json> --off <off.json> --on <on.json>
  npm run promiseproof -- --help

Exit codes (verify / gate):
  0  PASS
  1  usage, I/O, or unexpected execution error
  2  BROKEN_PROMISE
  3  INVALID_EVIDENCE

Exit codes (check): the report is reproduced by re-running the unchanged
evaluator on the supplied evidence, not merely hash-compared.
  0  BOUND_AND_REPRODUCED
  1  usage, I/O, or unexpected execution error
  3  INVALID_REPORT_OR_EVIDENCE
  4  STALE_OR_MISMATCH

Supported contract family: activity-personalization/v1
`;

const REPORT_FILENAMES = ["report.json", "report.md"] as const;
const SCAFFOLD_FILES = {
  "activity-personalization.v1.schema.json": () =>
    serializeJson(externalBundleJsonSchema()),
  "broken-off.example.json": () => serializeJson(brokenOffExample),
  "passing-off.example.json": () => serializeJson(passingOffExample),
  "passing-on.example.json": () => serializeJson(passingOnExample),
  "producer-template.mjs": () => producerTemplate,
  "README.md": () => scaffoldReadme,
} as const;

class UsageError extends Error {}
class InvalidEvidenceFileError extends Error {}

interface CliIo {
  readonly stdout: (message: string) => void;
  readonly stderr: (message: string) => void;
}

interface ParsedOptions {
  readonly [name: string]: string;
}

function serializeJson(value: unknown): string {
  return `${JSON.stringify(value, null, 2)}\n`;
}

function parseOptions(
  args: readonly string[],
  allowed: readonly string[],
): ParsedOptions {
  const options: Record<string, string> = {};
  const allowedSet = new Set(allowed);

  for (let index = 0; index < args.length; index += 2) {
    const name = args[index];
    const value = args[index + 1];
    if (
      name === undefined ||
      value === undefined ||
      !name.startsWith("--") ||
      value.startsWith("--")
    ) {
      throw new UsageError("Options must be supplied as --name <value> pairs.");
    }

    const key = name.slice(2);
    if (!allowedSet.has(key)) {
      throw new UsageError(`Unknown option: ${name}`);
    }
    if (options[key] !== undefined) {
      throw new UsageError(`Duplicate option: ${name}`);
    }
    options[key] = value;
  }

  for (const required of allowed) {
    if (options[required] === undefined) {
      throw new UsageError(`Missing required option: --${required}`);
    }
  }

  return options;
}

function safeOutputDirectory(rawPath: string): string {
  if (rawPath.trim().length === 0) {
    throw new UsageError("--out must not be empty.");
  }

  const normalizedSegments = rawPath.replaceAll("\\", "/").split("/");
  if (normalizedSegments.includes("..")) {
    throw new UsageError("--out must not contain path traversal.");
  }

  const resolved = path.resolve(rawPath);
  if (resolved === path.parse(resolved).root) {
    throw new UsageError("--out must not be a filesystem root.");
  }
  return resolved;
}

function safeChildPath(directory: string, filename: string): string {
  if (
    path.basename(filename) !== filename ||
    filename === "." ||
    filename === ".."
  ) {
    throw new Error("Generated filename is unsafe.");
  }

  const child = path.resolve(directory, filename);
  if (path.dirname(child) !== directory) {
    throw new Error("Generated path escaped the output directory.");
  }
  return child;
}

async function ensureOutputDirectory(directory: string): Promise<void> {
  try {
    const existing = await lstat(directory);
    if (existing.isSymbolicLink() || !existing.isDirectory()) {
      throw new Error("Output path must be a real directory.");
    }
  } catch (error) {
    if (
      typeof error === "object" &&
      error !== null &&
      "code" in error &&
      error.code === "ENOENT"
    ) {
      await mkdir(directory, { recursive: true });
      return;
    }
    throw error;
  }
}

async function refuseUnsafeReportTarget(target: string): Promise<void> {
  try {
    const existing = await lstat(target);
    if (existing.isSymbolicLink() || !existing.isFile()) {
      throw new Error(`Refusing unsafe report target: ${path.basename(target)}`);
    }
  } catch (error) {
    if (
      typeof error === "object" &&
      error !== null &&
      "code" in error &&
      error.code === "ENOENT"
    ) {
      return;
    }
    throw error;
  }
}

async function writeReports(
  outputDirectory: string,
  json: string,
  markdown: string,
): Promise<void> {
  await ensureOutputDirectory(outputDirectory);
  const jsonPath = safeChildPath(outputDirectory, REPORT_FILENAMES[0]);
  const markdownPath = safeChildPath(outputDirectory, REPORT_FILENAMES[1]);
  await refuseUnsafeReportTarget(jsonPath);
  await refuseUnsafeReportTarget(markdownPath);
  await writeFile(jsonPath, json, "utf8");
  await writeFile(markdownPath, markdown, "utf8");
}

async function writeScaffold(outputDirectory: str
[truncated — 6890 more characters]
```

### src/client/main.ts

```typescript
import "./styles.css";

import type {
  ActivityPayload,
  ActivityReceipt,
  ClientTimelineEntry,
  DemoMode,
  PersonalizationPreference,
  RecommendationItem,
  RecommendationReceipt,
  RecommendationSource,
  RunEvidenceLedger,
} from "../shared/types";

declare global {
  interface Window {
    __PP_TIMELINE__: ClientTimelineEntry[];
    __PP_APP_READY__: boolean;
  }
}

interface PreferenceResponse {
  userId: string;
  preference: PersonalizationPreference;
  updatedAt?: string;
}

interface ConfigurationResponse {
  demoMode: DemoMode;
}

interface ActivityResponse {
  accepted: true;
  receipt: ActivityReceipt;
}

interface RecommendationResponse {
  source: RecommendationSource;
  items: RecommendationItem[];
  receipt: RecommendationReceipt;
}

const SAFE_IDENTIFIER = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
const DEFAULT_RUN_ID = "demo-run";
const DEFAULT_USER_ID = "synthetic-user-001";
const STARTUP_ITEM_ID = "signal-shelf-home";

const query = new URLSearchParams(window.location.search);
const runId = safeIdentifier(query.get("runId"), DEFAULT_RUN_ID);
const userId = safeIdentifier(query.get("userId"), DEFAULT_USER_ID);
const storageKey = `promiseproof:personalization:${userId}`;

// The race fixture deliberately uses this unsafe default before hydration. The
// propagation fixture hydrates first, isolating its separate write-boundary fault.
let inMemoryPreference: PersonalizationPreference = "on";
let backendPreference: PersonalizationPreference | null = null;
let recommendationSource: RecommendationSource | null = null;

window.__PP_TIMELINE__ = [];
window.__PP_APP_READY__ = false;

const appRoot = required<HTMLElement>("#app-root");
const toggle = required<HTMLInputElement>("#personalization-toggle");
const preferenceState = required<HTMLElement>("#preference-state");
const browserPreference = required<HTMLElement>("#browser-preference");
const storedPreferenceElement = required<HTMLElement>("#stored-preference");
const backendPreferenceElement = required<HTMLElement>("#backend-preference");
const preferenceAgreement = required<HTMLElement>("#preference-agreement");
const sourceBadge = required<HTMLElement>("#recommendation-source");
const evidenceSource = required<HTMLElement>("#evidence-recommendation-source");
const itemsContainer = required<HTMLElement>("#recommendation-items");
const receiptLabel = required<HTMLElement>("#activity-receipt-label");
const receiptCount = required<HTMLElement>("#activity-receipt-count");
const timeline = required<HTMLOListElement>("#startup-timeline");
const syncStatus = required<HTMLElement>("#sync-status");
const runIdElement = required<HTMLElement>("#run-id");

runIdElement.textContent = runId;
renderPreference();

toggle.addEventListener("change", () => {
  void updatePreference(toggle.checked ? "on" : "off");
});

void startApplication();

async function startApplication(): Promise<void> {
  try {
    const { demoMode } = await fetchJson<ConfigurationResponse>(
      "/api/configuration",
      { referrerPolicy: "no-referrer" },
    );

    if (demoMode === "initialization-race") {
      setStatus("Collector starting with the in-memory default…", "working");

      // The await is the seeded race: hydration cannot begin until a real
      // recommendation-service activity receipt has already returned.
      await runStartupCollector();
      await hydratePreference();
    } else {
      setStatus("Restoring the saved preference before collection…", "working");
      await hydratePreference();
      await runStartupCollector();
    }

    await loadRecommendations();
    await refreshEvidence();

    appRoot.dataset.ready = "true";
    window.__PP_APP_READY__ = true;
    toggle.disabled = false;
    renderPreferenceStatus();
  } catch (error) {
    appRoot.dataset.ready = "error";
    window.__PP_APP_READY__ = false;
    setStatus(readableError(error), "error");
    itemsContainer.setAttribute("aria-busy", "false");
  }
}

async function runStartupCollector(): Promise<void> {
  recordTimeline("collector_started", {
    inMemoryPreference,
  });

  if (inMemoryPreference !== "on") {
    recordTimeline("collector_suppressed", { inMemoryPreference });
    return;
  }

  const dispatched = recordTimeline("activity_dispatched", {
    eventType: "page_view",
    userId,
  });
  const payload: ActivityPayload = {
    runId,
    userId,
    eventType: "page_view",
    itemId: STARTUP_ITEM_ID,
    clientSequence: dispatched.sequence,
    occurredAt: dispatched.timestamp,
  };

  const response = await fetchJson<ActivityResponse>(
    "/api/recommendations/activity",
    {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(payload),
      referrerPolicy: "no-referrer",
    },
  );

  recordTimeline("activity_received", {
    receiptId: response.receipt.receiptId,
    service: response.receipt.service,
  });
}

async function hydratePreference(): Promise<void> {
  recordTimeline("preference_hydration_started");

  const response = await fetchJson<PreferenceResponse>(
    `/api/preferences/${encodeURIComponent(userId)}`,
    { referrerPolicy: "no-referrer" },
  );
  let hydratedPreference = response.preference;
  let authoritativePreference = response.preference;
  const storedPreference = readStoredPreference();

  // The browser value represents the user's last explicit choice. If a fresh
  // backend has no matching state, restore that choice before rendering a feed.
  if (storedPreference !== null && storedPreference !== response.preference) {
    recordTimeline("preference_sync_dispatched", {
      preference: storedPreference,
    });
    const restored = await persistBackendPreference(storedPreference);
    hydratedPreference = restored.preference;
    recordTimeline("preference_sync_acknowledged", {
      preference: restored.preference,
    });

    const readback = await fetchBackendPreference();
    authoritativePreference = readback.preference;
    recordTimeline("backend_preference_o
[truncated — 11810 more characters]
```

### src/action/index.ts

```typescript
// PromiseProof Verify GitHub Action entry point.
//
// This is bundled by esbuild into .github/actions/verify/dist/index.js and runs
// as a node20 Action. It reuses the frozen verifier modules and never
// re-implements an evaluator rule. It talks to the Actions runner only through
// the documented environment files (GITHUB_OUTPUT, GITHUB_STEP_SUMMARY) and
// workflow command protocol (::error::, ::warning::). It makes no network
// request, no model call, and reads only the files it is explicitly given.
import { randomUUID } from "node:crypto";
import {
  appendFileSync,
  existsSync,
  lstatSync,
  mkdirSync,
  realpathSync,
  statSync,
} from "node:fs";
import { open, readFile, rename, unlink } from "node:fs/promises";
import path from "node:path";

import { EVALUATOR_SOURCE_SHA256 } from "../verify/binding.js";
import { checkGate, checkSingle } from "../verify/check.js";
import { MAX_INPUT_BYTES } from "../verify/schema.js";
import { SUPPORTED_CONTRACT_FAMILY } from "../verify/outcome.js";
import {
  createGateReport,
  createVerifyReport,
  serializeGateReportMarkdown,
  serializeReportJson,
  serializeVerifyReportMarkdown,
  type GateReport,
  type VerifyReport,
} from "../verify/report.js";
import { runGate, verifyBundle } from "../verify/verify.js";

const CONTRACT = SUPPORTED_CONTRACT_FAMILY;

// A usage or environment problem, distinct from a product verdict. Exit 1.
class ActionUsageError extends Error {}

class ActionInvalidInputError extends Error {
  constructor(
    readonly status: "INVALID_EVIDENCE" | "INVALID_REPORT_OR_EVIDENCE",
    readonly mode: "verify" | "gate" | "check",
  ) {
    super(
      status === "INVALID_EVIDENCE"
        ? "evidence exceeds the input-size limit"
        : "report or evidence exceeds the input-size limit",
    );
  }
}

const OUTPUT_NAMES = [
  "status",
  "mode",
  "report_json",
  "report_markdown",
  "evaluator_sha256",
  "evidence_sha256",
  "off_evidence_sha256",
  "on_evidence_sha256",
] as const;

const UNSAFE_PATH_CHARACTERS =
  /[\u0000-\u001f\u007f\u202a-\u202e\u2066-\u2069]/u;

function workflowCommandValue(message: string): string {
  return message
    .replaceAll("%", "%25")
    .replaceAll("\r", "%0D")
    .replaceAll("\n", "%0A");
}

function emit(kind: "error" | "warning", message: string): void {
  process.stdout.write(`::${kind}::${workflowCommandValue(message)}\n`);
}

function readInput(name: string): string {
  return (process.env[`INPUT_${name.toUpperCase()}`] ?? "").trim();
}

function readPathInput(name: string): string {
  const raw = process.env[`INPUT_${name.toUpperCase()}`] ?? "";
  if (UNSAFE_PATH_CHARACTERS.test(raw)) {
    throw new ActionUsageError(`${name} contains a forbidden control character.`);
  }
  return raw.trim();
}

const collectedOutputs: Record<string, string> = {};
function setOutput(name: string, value: string): void {
  if (!/^[a-z0-9_]+$/u.test(name)) {
    throw new ActionUsageError("an internal output name is invalid.");
  }
  if (/[\r\n\u0000]/u.test(value)) {
    throw new ActionUsageError(`the ${name} output is not single-line safe.`);
  }
  collectedOutputs[name] = value;
  const file = process.env.GITHUB_OUTPUT;
  if (file === undefined || file === "") return;
  appendFileSync(file, `${name}=${value}\n`, "utf8");
}

function initializeOutputs(): void {
  for (const name of OUTPUT_NAMES) {
    setOutput(name, "");
  }
}

function appendSummary(markdown: string): void {
  const file = process.env.GITHUB_STEP_SUMMARY;
  if (file === undefined || file === "") return;
  appendFileSync(file, `${markdown}\n`, "utf8");
}

function actionHeader(mode: string, status: string): string {
  return [
    "## PromiseProof Verify",
    "",
    `- mode: ${mode}`,
    `- status: ${status}`,
    `- contract family: ${CONTRACT}`,
    "- no model call in the verdict path",
    "- external evidence collection is not attested",
    "",
  ].join("\n");
}

function appendEvaluationSummary(
  heading: string,
  evaluation: VerifyReport | GateReport["evaluations"]["off"],
): void {
  appendSummary(`### ${heading}`);
  appendSummary("");
  for (const clause of evaluation.clauses) {
    appendSummary(`- ${clause.id}: ${clause.passed ? "PASS" : "FAIL"}`);
  }
  if (evaluation.violations.length > 0) {
    appendSummary(
      `- violation codes: ${evaluation.violations.map((item) => item.code).join(", ")}`,
    );
  }
}

// ---- path safety: everything must resolve inside GITHUB_WORKSPACE ----
function workspaceRoot(): string {
  const raw = process.env.GITHUB_WORKSPACE;
  if (raw === undefined || raw === "") {
    throw new ActionUsageError("GITHUB_WORKSPACE is not set.");
  }
  if (!existsSync(raw)) {
    throw new ActionUsageError("GITHUB_WORKSPACE does not exist.");
  }
  return realpathSync(raw);
}

// Resolve the nearest existing ancestor through realpath and require that it is
// contained by the workspace. This catches absolute escapes, `..` traversal,
// and symlinks whose target leaves the workspace, for existing and not-yet
// created paths alike.
function assertInsideWorkspace(root: string, target: string): void {
  let probe = target;
  while (!existsSync(probe)) {
    const parent = path.dirname(probe);
    if (parent === probe) break;
    probe = parent;
  }
  const real = existsSync(probe) ? realpathSync(probe) : probe;
  if (real !== root && !real.startsWith(root + path.sep)) {
    throw new ActionUsageError("a path resolves outside the workspace.");
  }
}

function resolveExistingFile(root: string, relInput: string, label: string): string {
  if (relInput === "") {
    throw new ActionUsageError(`${label} is required but was empty.`);
  }
  const target = path.resolve(root, relInput);
  assertInsideWorkspace(root, target);
  if (!existsSync(target)) {
    throw new ActionUsageError(`${label} does not exist.`);
  }
  const canonical = realpathSync(target);
  assertInsideWorkspace(root, canonical);
  if (!statSync(canonical).isFile()) {
    throw new ActionUsageError(`${label} is not a regular f
[truncated — 14222 more characters]
```

### playwright.config.ts

```typescript
import { fileURLToPath } from 'node:url';

import { defineConfig } from '@playwright/test';

export const promiseProofBaseUrl =
  process.env.PROMISEPROOF_BASE_URL ?? 'http://127.0.0.1:4173';

export default defineConfig({
  testDir: './tests',
  testIgnore: ['**/contracts/**', '**/propagation/**'],
  fullyParallel: false,
  workers: 1,
  retries: 0,
  forbidOnly: Boolean(process.env.CI),
  reporter: [['list']],
  outputDir: fileURLToPath(
    new URL('./test-results/race-green', import.meta.url),
  ),
  expect: {
    timeout: 5_000,
  },
  use: {
    baseURL: promiseProofBaseUrl,
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  webServer: {
    command: 'npm run dev:test:race',
    url: `${promiseProofBaseUrl}/api/health`,
    reuseExistingServer: false,
    timeout: 120_000,
    stdout: 'pipe',
    stderr: 'pipe',
  },
});

```

[120 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]