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
4 changes: 2 additions & 2 deletions media/hac-335/evidence/capture-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"manifestId": "HAC-335-capture-manifest",
"issue": "HAC-335",
"generator": "media/hac-335/bin/capture-cockpit.mjs",
"capturedFromSha": "7d62f6fe169cf354a548447dae0ce1ea7e210a08",
"capturedFromSha": "07e1a93f8d575b7bb03db1195b4a26594a8aad32",
"capturedSurface": "media/hac-341/cockpit.html (merged executable surface)",
"servedFrom": "repository root — the cockpit resolves shared identity from /assets",
"viewport": "1440x900",
"captureSourceDigest": "24924971fd814b5f1baa460e420ebe3f9c9c9d09ec125ee616354f01d557819a",
"captureSourceDigest": "718d9c9e3f80a1633093df74131787509910fae14fac2ed6126cd9008c9d7879",
"captureSourceFiles": [
"assets/fonts/geist-mono-variable.woff2",
"assets/fonts/geist-variable.woff2",
Expand Down
5 changes: 5 additions & 0 deletions media/hac-341/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,11 @@ The issue's acceptance requires real human falsification. **No human cold-read
has been performed and none is claimed.** The protocol below is prepared; the
results are not.

The runnable kit is [`cold-read/`](./cold-read/) — a moderator script to read
verbatim and a per-reader response sheet. Use the **public custom domain**
`https://interlock.marcellelabs.io`; every `*.vercel.app` URL is behind Vercel
SSO and will send a cold reader to a login page.

Deterministic URLs for testers:

```
Expand Down
21 changes: 20 additions & 1 deletion media/hac-341/bin/verify-cockpit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,9 @@ if (defaultArm && perturbedArm) {
}

// The cockpit must consume that derivation rather than re-deriving it inline.
if (!/from '\.\/lib\/arm-view\.mjs'/.test(cockpit)) {
// Matched on the module, not the specifier prefix, so the rewrite fix below
// does not read as the cockpit having dropped the import.
if (!/from '\/media\/hac-341\/lib\/arm-view\.mjs'/.test(cockpit)) {
fail('cockpit does not consume the shared arm-view derivation');
}
if (/environmentEvidence\[0\][^\n]*basisRevision|basisRevision[^\n]*environmentEvidence\[0\]/.test(cockpit)) {
Expand Down Expand Up @@ -1067,6 +1069,23 @@ if (!/aria-pressed/.test(cockpit)) fail('toggle state is not exposed to assistiv
if (!/aria-live/.test(cockpit)) fail('state changes are not announced');
if (!/data-glyph/.test(cockpit)) fail('state is encoded by colour alone; no glyph channel');

/* Every module import and evidence fetch resolves from the site root.
*
* `vercel.json` rewrites `/` and `/cockpit` onto this file. A rewrite serves
* these bytes without changing the request URL, and a document-relative
* specifier resolves against the *request* URL — so `./lib/guide.mjs` became
* `/lib/guide.mjs` and aborted, and the two URLs a judge actually visits
* rendered a blank page while still returning HTTP 200.
*
* The visual gate could not see it: it loads `/media/hac-341/cockpit.html`
* directly, which is the one path where the relative form happens to work.
* This is a static check for that reason — it needs no server and no
* deployment, and it fails on the specifier rather than on the symptom.
*/
for (const m of cockpit.matchAll(/(?:^|\s)(?:import\b[^;]*?from|await\s+fetch\(|import\()\s*['"](\.[^'"]*)['"]/g)) {
fail(`document-relative reference "${m[1]}" breaks under the vercel.json rewrite; anchor it at /media/hac-341/`);
}

const checksLabel = local.checks.label;
if (errors.length) {
process.stderr.write(`HAC-341 cockpit contract violated:\n${errors.map((e) => ` - ${e}`).join('\n')}\n`);
Expand Down
6 changes: 3 additions & 3 deletions media/hac-341/cockpit.html
Original file line number Diff line number Diff line change
Expand Up @@ -704,14 +704,14 @@
from frozen evidence. A field that is absent there is absent here.
What the *selected arm* shows is derived in lib/arm-view.mjs, so the binding
between an arm and its evidence can be asserted without a browser. */
import { armView } from './lib/arm-view.mjs';
import { armView } from '/media/hac-341/lib/arm-view.mjs';

Check failure on line 707 in media/hac-341/cockpit.html

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not import modules using an absolute path

See more on https://sonarcloud.io/project/issues?id=Marcelle-Labs_interlock&issues=AaAvPpRKedvjRoV8FOBO&open=AaAvPpRKedvjRoV8FOBO&pullRequest=33
/* The guided layer's derivation. It lives beside arm-view.mjs for the same
reason: step 5 claims that four things were held constant and four changed,
and a claim like that has to be checked against the frozen arms rather than
typed into markup. `ablationDelta` reads both arms and reports what actually
moved, so a marker that has stopped being true fails the gate. */
import { guideView, guideRoute, stateOfStep, stepOfState, GUIDE_STEPS,
GUIDE_FREE_STATE } from './lib/guide.mjs';
GUIDE_FREE_STATE } from '/media/hac-341/lib/guide.mjs';

Check failure on line 714 in media/hac-341/cockpit.html

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not import modules using an absolute path

See more on https://sonarcloud.io/project/issues?id=Marcelle-Labs_interlock&issues=AaAvPpRKedvjRoV8FOBP&open=AaAvPpRKedvjRoV8FOBP&pullRequest=33

const app = document.getElementById('app');
const drawer = document.getElementById('drawer');
Expand Down Expand Up @@ -1717,7 +1717,7 @@
addEventListener('popstate', () => { closeDrawer(); resolve(); });

try {
const res = await fetch('./evidence/view-model.json', { cache: 'no-store' });
const res = await fetch('/media/hac-341/evidence/view-model.json', { cache: 'no-store' });
if (!res.ok) throw new Error(String(res.status));
MODEL = await res.json();
resolve();
Expand Down
100 changes: 100 additions & 0 deletions media/hac-341/cold-read/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# HAC-341 cold read — moderator kit

**Status: READY_FOR_HUMAN_TEST. Not run. No result is claimed.**

Three unfamiliar readers, tested one at a time, no narration. This kit is the
whole protocol: the URLs, what you say, and where the answers go. Budget 15
minutes per reader.

A reader is *unfamiliar* if they have not seen the cockpit, the README, the
Devpost copy or the video, and have not had Interlock explained to them. A
colleague who has heard the pitch is not a cold reader.

## Before you start

Open [`response-sheet.md`](./response-sheet.md), one copy per reader.

Check the surface is live and public — in a **logged-out / private window**:

```
https://interlock.marcellelabs.io/cockpit?run=hac330-local&proof=local&state=run.local.treatment
```

If that redirects to a login page, stop: you are on a protected URL. The
`*.vercel.app` deployment URLs are SSO-protected and **must not be used for the
cold read**. Only the custom domain is public.

## The URLs

| Purpose | URL |
| --- | --- |
| L1 default (start here) | `https://interlock.marcellelabs.io/cockpit?run=hac330-local&proof=local&state=run.local.treatment` |
| Baseline arm | `…/cockpit?run=hac330-local&proof=local&state=run.local.baseline` |
| Perturbed arm | `…/cockpit?run=hac330-local&proof=local&state=run.local.perturbed` |
| Cloud overview | `…/cockpit?run=hac340-cloud&proof=cloud&state=run.cloud.overview` |
| Reduced motion / static | append `&static=1` to any of the above |
| Missing run (must show unavailable, never substitute) | `…/cockpit?run=nope&proof=local&state=run.local.treatment` |

## What you say

Read these aloud verbatim. **Do not explain anything, do not answer questions,
do not point at the screen.** If a reader asks what something means, say: *"Tell
me what you think it means."*

> I'm going to show you a web page. I'm not going to tell you what it is. I'll
> ask you a few questions as you look at it. There are no wrong answers — I'm
> testing the page, not you. Please think out loud.

Open the L1 default URL. Start the timer.

1. **At 5 seconds** — cover the screen. *"What problem is this page about?"*
2. **At 30 seconds** — *"What changed, and why did it change?"*
3. **At 60 seconds** — *"If you didn't believe this, how would you check it
yourself?"*
4. **Switch to the cloud overview URL. At 120 seconds** — *"Is what you're
looking at now the same experiment as before, or a different one?"*
5. **Open-ended** — *"Is there anything here you think the page is claiming that
it hasn't actually shown you?"*

Then repeat steps 1–4 with `&static=1` and **keyboard only** (no mouse). The
semantic answers must match; presentation may differ.

## Targets

| # | Question | Target |
| --- | --- | --- |
| 1 | Identifies the core problem at 5 s | **3/3** |
| 2 | Explains the causal delta at 30 s | **3/3** |
| 3 | Finds verifiable evidence by 60 s | **≥2/3** |
| 4 | Distinguishes local proof from Google Cloud participation by 120 s | **≥2/3** |

## Automatic failures

These override every score above. If any occurs, the cold read **fails**
regardless of questions 1–4:

- a reader infers that **Agent Runtime** or **Agent Gateway** participated;
- a reader believes the local experiment and the cloud run are **one combined
experiment**, or that the cloud run reproduced the 140/120 counterfactual;
- a reader reads `ALLOW` as *verified*, *authorized* or *safe*, or reads
`EXECUTED` as `OBSERVED`;
- a reader concludes Interlock is **universally** safe, production-ready, or
that it prevents collisions in general;
- the keyboard/reduced-motion pass yields a **different semantic answer**.

## Recording the result

Write **verbatim answers** on the response sheet. Do not summarise into a score
without them — the wording is the finding. A reader who says "it stops two
robots fighting over the same file" has understood something different from one
who says "it decides whether two jobs can run at once", and only the verbatim
text preserves that.

Only comprehension failures demonstrated by this protocol justify further UX
change. A reader being slow is data; a moderator's hunch is not.

## After the run

Record the outcome on HAC-341 with the verbatim sheets attached. Until three
real readers have completed this, HAC-341 stays open and every surface that
mentions the cold read must continue to say it has not been run.
125 changes: 125 additions & 0 deletions media/hac-341/cold-read/response-sheet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# HAC-341 cold read — response sheet

One copy per reader. Write what they said, not what they meant.

```
Reader #: ______ Date: ____________ Moderator: ____________
Surface: https://interlock.marcellelabs.io/cockpit commit: ____________
Browser / OS: ____________________ Logged out / private window? Y / N
Unfamiliar with Interlock (has not seen cockpit, README, Devpost or video)? Y / N
```

---

## Pass 1 — default, mouse allowed

**Q1 · 5 s — "What problem is this page about?"**

```
verbatim:



```
Identified the core problem? **Y / N**

---

**Q2 · 30 s — "What changed, and why did it change?"**

```
verbatim:



```
Explained the causal delta? **Y / N**

---

**Q3 · 60 s — "How would you check it yourself?"**

```
verbatim:



```
Located verifiable evidence? **Y / N**

---

**Q4 · 120 s, after switching to the cloud URL — "Same experiment, or different?"**

```
verbatim:



```
Distinguished the two proof classes? **Y / N**

---

**Q5 · open — "Anything the page claims but hasn't shown you?"**

```
verbatim:



```

---

## Pass 2 — `&static=1`, keyboard only

```
Q1 verbatim:

Q2 verbatim:

Q3 verbatim:

Q4 verbatim:
```

Semantic answers match pass 1? **Y / N**
If N, which diverged and how:

```

```

---

## Automatic failures — tick any that occurred

```
[ ] inferred Agent Runtime participated
[ ] inferred Agent Gateway participated
[ ] believed local + cloud are one combined experiment
[ ] believed the cloud run reproduced the 140/120 counterfactual
[ ] read ALLOW as verified / authorized / safe
[ ] read EXECUTED as OBSERVED
[ ] concluded Interlock is universally safe / production-ready
[ ] concluded Interlock prevents collisions in general
[ ] keyboard or reduced-motion pass gave a different semantic answer
```

Any tick = this reader is a **FAIL**, whatever the Y/N answers above.

---

## Moderator notes

Where did they look first? Where did they get stuck? What did they scroll past?

```



```

Did the moderator explain, prompt, or point at any stage? **Y / N**
(If Y, this reader's result is void — the read was not cold.)
16 changes: 15 additions & 1 deletion test/hac-341-identity-gates.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -329,12 +329,26 @@ describe('the selected arm drives its own evidence', () => {

it('fails when the cockpit stops consuming the shared derivation', () => {
const r = perturbed((a) => {
a.edit('media/hac-341/cockpit.html', "import { armView } from './lib/arm-view.mjs';", '');
a.edit('media/hac-341/cockpit.html', "import { armView } from '/media/hac-341/lib/arm-view.mjs';", '');
}, gate);
expect(r.code).not.toBe(0);
expect(r.out).toMatch(/does not consume the shared arm-view derivation/);
});

it('fails when a reference goes document-relative and breaks the rewrite', () => {
// vercel.json rewrites `/` and `/cockpit` onto cockpit.html without
// changing the request URL, so a `./` specifier resolves against the
// request and 404s. The page still returns 200 and renders blank, and the
// visual gate cannot see it because it loads the file path directly.
const r = perturbed((a) => {
a.edit('media/hac-341/cockpit.html',
"await fetch('/media/hac-341/evidence/view-model.json'",
"await fetch('./evidence/view-model.json'");
}, gate);
expect(r.code).not.toBe(0);
expect(r.out).toMatch(/document-relative reference .* breaks under the vercel\.json rewrite/);
});

it('fails when the cockpit reads a basis off the environment again', () => {
const r = perturbed((a) => {
a.edit('media/hac-341/cockpit.html', 'const short =',
Expand Down
Loading