Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ test-results/
.env
.env.*
!.env.example
.wrangler/
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ A local-first scientific instrument for inspecting next-token prediction, changi

## Start here

| I want to… | Read or run |
| ----------------------------------- | ----------------------------------------------------------- |
| try the two-minute teaching loop | [User guide](docs/user-guide.md) |
| understand what the evidence means | [Scientific status](#scientific-status) |
| make a code or documentation change | [Developer guide](docs/developer-guide.md) |
| start a fresh coding-agent session | [Agent operating contract](AGENTS.md) |
| review release readiness | [Generated release evidence](release-evidence/summary.md) |
| deploy or roll back the static app | [Static release runbook](docs/deployment/static-release.md) |
| I want to… | Read or run |
| ----------------------------------- | ------------------------------------------------------------------------------------------ |
| try the two-minute teaching loop | [User guide](docs/user-guide.md) |
| understand what the evidence means | [Scientific status](#scientific-status) |
| make a code or documentation change | [Developer guide](docs/developer-guide.md) |
| start a fresh coding-agent session | [Agent operating contract](AGENTS.md) |
| review release readiness | [Generated release evidence](release-evidence/summary.md) |
| deploy or roll back the static app | [Static release runbook](docs/deployment/static-release.md) (`wrangler.toml` at repo root) |

## Current instrument

Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ instrument, browser or release criterion. Only the named evidence profile can do
- `architecture/` explains package boundaries, runtime flow, replay integrity and static release.
- `implementation/00-five-phase-plan.md` records the delivery sequence.
- `implementation/05-session-05-handover.md` records the completed Phase 5 source and local gates.
- `implementation/06-cloudflare-workspace-deploy.md` records the Wrangler workspace-root deploy
target; it does not rebind the Phase 5 candidate.
- `implementation/06-post-phase-5-review.md` is the post-candidate engineering review: split
evidence, remaining launch blockers and owner decisions.
- `deployment/static-release.md` is the host-neutral build, smoke and rollback runbook.
Expand Down
35 changes: 35 additions & 0 deletions docs/adr/0010-static-host-wrangler-target.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# ADR 0010 — Explicit Wrangler target at the workspace root

- Status: Accepted
- Date: 28 August 2026

## Context

The Observatory is a pnpm workspace. The prepared static host builds from the repository root so
install and `pnpm build` can see every package. Cloudflare Workers Builds then runs
`npx wrangler deploy` from that same root.

Wrangler 4.x application detection refuses to guess a package at a workspace root when no
configuration file is present. A first Pages deploy therefore failed after a successful Vite
build with: the detection logic was run in the root of a workspace instead of targeting a
specific project.

Adding Wrangler as a workspace dependency would make a host tool part of the application lockfile.
Pointing the Cloudflare root directory at `apps/observatory` would break the workspace install.

## Decision

Commit a repository-root `wrangler.toml` that names the built observatory assets and no Worker
script. Keep Wrangler out of `package.json`. Dashboard builds continue to use `pnpm build` and
`npx wrangler deploy` from `/`.

The Cloudflare project name must match `thinking-machine-observatory`. Classic Pages output-directory
uploads remain valid and do not require this file.

## Consequences

- The default host deploy command can target the static bundle without changing the dashboard
working directory.
- `_headers` remains the security-header contract; Vite still copies it into `apps/observatory/dist`.
- A renamed Cloudflare project must be reconciled with `wrangler.toml` before the next deploy.
- This does not introduce Functions, a Worker script or an application backend.
3 changes: 2 additions & 1 deletion docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ repository and revision.
- [Runtime and interaction flows](runtime-flows.md)
- [Trace, replay and persistence](trace-integrity.md)
- [Release, offline and deployment boundary](release-boundary.md)
- [Architecture decisions](../adr)
- [Architecture decisions](../adr), including [ADR 0010](../adr/0010-static-host-wrangler-target.md)
for the workspace-root Wrangler target
- [Implementation handovers](../implementation)
- [Model verification boundary](../../model-tools/README.md)
- [Agent and contributor contract](../../AGENTS.md)
Expand Down
6 changes: 6 additions & 0 deletions docs/architecture/release-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ or claim the service worker provides offline model availability. Static-host pol
source, while the deployed-origin header smoke remains a distinct evidence record that cannot pass
until a real URL is observed.

The Cloudflare host builds from the repository root and deploys with `npx wrangler deploy`.
Repository-root `wrangler.toml` is the explicit static-asset target for that command: it names
`apps/observatory/dist` and does not introduce a Worker script. Wrangler stays a host tool, not a
workspace dependency. See [static release](../deployment/static-release.md) and
[ADR 0010](../adr/0010-static-host-wrangler-target.md).

## Browser and accessibility evidence

The ordinary Playwright configuration runs Chromium, Firefox and WebKit. The release suite covers
Expand Down
38 changes: 34 additions & 4 deletions docs/deployment/static-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

## Decision

The first public candidate remains a static HTTPS application with no Functions, backend, account
system or analytics. Cloudflare Pages is the prepared host because it can build the pnpm workspace,
serve immutable hashed assets and apply the checked `apps/observatory/public/_headers` rules to
static responses. This is a deployment target, not a new application dependency.
The first public candidate remains a static HTTPS application with no Functions, Worker script,
backend, account system or analytics. Cloudflare Pages / Workers Builds is the prepared host
because it can build the pnpm workspace, serve immutable hashed assets and apply the checked
`apps/observatory/public/_headers` rules to static responses. This is a deployment target, not a
new application dependency. Wrangler is invoked only by the host; it is not a workspace package.

The model and tokenizer remain on their pinned upstream Hugging Face source. The release does not
mirror 327.8 MB of weights. The interface discloses the download before the user initiates it, and
Expand All @@ -20,6 +21,7 @@ the exact identities, hashes, licence and verification profile remain in the rep
- build command: `pnpm build`
- output directory: `apps/observatory/dist`
- application base: `/` on a dedicated origin
- deploy target: repository-root `wrangler.toml`, which names `./apps/observatory/dist`

`public/_headers` is copied into the production root. It provides the content security policy,
cross-origin isolation boundary, permissions policy, referrer policy, MIME protection, immutable
Expand All @@ -28,6 +30,32 @@ families required by the optional model path; inline scripts, framing, object em
other origins, camera, microphone, location, payment and USB are denied. Inline styles remain
allowed because probability bars use bounded React style values.

## Cloudflare dashboard

Keep the project root at the repository root so `pnpm install --frozen-lockfile` can see the
workspace. Do not set the root directory to `apps/observatory`.

| Setting | Value |
| ---------------- | ------------------------------ |
| Framework preset | None |
| Build command | `pnpm build` |
| Deploy command | `npx wrangler deploy` |
| Root directory | `/` |
| Project name | `thinking-machine-observatory` |

The default host command `npx wrangler deploy` is load-bearing. Wrangler 4.x application detection
refuses to guess a package when it is started at a pnpm workspace root. The committed
`wrangler.toml` is the explicit project target: it selects the built observatory assets and
declares that there is no Worker script. `-c wrangler.toml` is optional once that file exists at
the root; keep it if a future host image starts Wrangler from another directory.

The `name` field must match the Cloudflare project. If the dashboard project was created under a
different name, change the dashboard name to `thinking-machine-observatory` or change the config
to match — do not leave them split.

Classic Pages Git integration that uploads a build output directory, without a Wrangler deploy
command, still uses `apps/observatory/dist` and `_headers`. That path does not need `wrangler.toml`.

## Release smoke

1. Confirm the deployed commit and lockfile hash match `release-evidence/manifest.json`.
Expand Down Expand Up @@ -56,6 +84,8 @@ browser/runtime cache and are outside the application service worker.

## Host references

- [Cloudflare Workers static assets](https://developers.cloudflare.com/workers/static-assets/)
- [Cloudflare Workers headers](https://developers.cloudflare.com/workers/static-assets/headers/)
- [Cloudflare Pages custom headers](https://developers.cloudflare.com/pages/configuration/headers/)
- [Cloudflare Pages serving and caching behaviour](https://developers.cloudflare.com/pages/configuration/serving-pages/)
- [Cloudflare Pages limits](https://developers.cloudflare.com/pages/platform/limits/)
5 changes: 5 additions & 0 deletions docs/implementation/05-session-05-handover.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ documentation map, fresh-agent start and handover rules, corrected three-engine
and present-tense Phase 5/evidence wording. That change did not alter the built application,
dependency lock, evidence manifest or accepted release claims.

A later Cloudflare Pages / Workers Builds deploy failed after a successful production build:
Wrangler 4.x application detection refuses to guess a package at the pnpm workspace root. That
host-target repair is recorded in [session 06](06-cloudflare-workspace-deploy.md) and does not
rebind this candidate.

The first published quality job exposed a shallow-checkout integration issue: `phase5:verify`
correctly asks Git for the earlier candidate, but the default checkout contained only the PR head.
The quality job now fetches full history before verifying candidate ancestry and lockfile contents.
Expand Down
45 changes: 45 additions & 0 deletions docs/implementation/06-cloudflare-workspace-deploy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Session 06 handover — Cloudflare workspace deploy target

## Outcome

The first Cloudflare Pages / Workers Builds deploy failed after a successful `pnpm build`. Wrangler
4.127.1 ran `npx wrangler deploy` at the pnpm workspace root and refused application detection.

This change adds an explicit static-asset target so the host's default deploy command can run from
`/`. It does not change the built application, sampler, worker protocol, evidence manifest or
accepted release claims. The Phase 5 source candidate remains
`ae9f7605a9cf613695c4363faa3e5250b67673a8`. This note was rebased onto current `main`
(`a63181c`); the broader follow-up review is
[06-post-phase-5-review](06-post-phase-5-review.md).

## Scientific boundary

No model path, header contract or evidence class changed. The host still serves
`apps/observatory/dist` with `public/_headers`. There is still no Worker script, Pages Function or
application backend.

## What landed

- repository-root `wrangler.toml` naming `./apps/observatory/dist` and no `main` script;
- ADR 0010 for the workspace-root Wrangler target;
- runbook dashboard settings for Workers Builds and the classic Pages output-directory path;
- static-release policy checks for the Wrangler target and workspace-root deploy command.

## Observed commands

Recorded on this branch after the change:

- `node --experimental-strip-types scripts/verify-static-release.ts` passed;
- `pnpm build` emitted `apps/observatory/dist` including `_headers` and `service-worker.js`;
- `npx wrangler@4.127.1 deploy` at a workspace root without `wrangler.toml` still failed with the
original application-detection error;
- `npx wrangler@4.127.1 deploy --dry-run` from `/` with the committed config read
`apps/observatory/dist` and exited 0. The same command with `-c wrangler.toml` also exited 0.

A live Cloudflare deploy is not claimed here. That remains a deployed-origin evidence record.

## Next bounded slice

Redeploy the existing Cloudflare project after this lands. Confirm the dashboard project name
matches `thinking-machine-observatory`, then attach the live URL, observed headers and commit to
the release manifest.
20 changes: 20 additions & 0 deletions scripts/verify-static-release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const licence = readFileSync(resolve(root, 'LICENSE'), 'utf8');
const notices = readFileSync(resolve(root, 'THIRD_PARTY_NOTICES.md'), 'utf8');
const privacy = readFileSync(resolve(root, 'PRIVACY.md'), 'utf8');
const deployment = readFileSync(resolve(root, 'docs/deployment/static-release.md'), 'utf8');
const wrangler = readFileSync(resolve(root, 'wrangler.toml'), 'utf8');

const requiredHeaderFragments = [
"default-src 'self'",
Expand Down Expand Up @@ -37,5 +38,24 @@ for (const statement of ['No account', 'No analytics', 'No prompt telemetry']) {
if (!privacy.includes(statement)) throw new Error(`Privacy notice omits ${statement}.`);
}
if (!deployment.includes('Rollback')) throw new Error('Static deployment runbook needs rollback.');
if (!deployment.includes('npx wrangler deploy')) {
throw new Error('Static deployment runbook needs the workspace-root Wrangler deploy command.');
}
if (!deployment.includes('thinking-machine-observatory')) {
throw new Error('Static deployment runbook needs the Cloudflare project name.');
}

if (!wrangler.includes('name = "thinking-machine-observatory"')) {
throw new Error('Wrangler config must name the Cloudflare project.');
}
if (!wrangler.includes('directory = "./apps/observatory/dist"')) {
throw new Error('Wrangler config must target the observatory production bundle.');
}
if (/(^|\\n)\\s*main\\s*=/.test(wrangler)) {
throw new Error('Wrangler config must not introduce a Worker script.');
}
if (wrangler.includes('pages_build_output_dir')) {
throw new Error('Wrangler config must stay on static assets, not a Pages Functions project.');
}

console.log('Static release policy, rights, third-party notices and privacy wording are complete.');
10 changes: 10 additions & 0 deletions wrangler.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Static-host contract for Cloudflare Workers Builds / Pages.
# Wrangler 4.x refuses to auto-detect a project at a pnpm workspace root.
# This file is the explicit target so `npx wrangler deploy` can run from `/`.
# There is no Worker script, Pages Function or application backend.

name = "thinking-machine-observatory"
compatibility_date = "2026-08-28"

[assets]
directory = "./apps/observatory/dist"
Loading