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
21 changes: 12 additions & 9 deletions contracts/operator-surface.v1.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Operator Surface Contract — v1

**Status:** DRAFT
**Status:** FROZEN

**Scope:** This contract governs the operator surface of amplifier-work-tracker: the human web surface one operator watches — L0 Mission Control (`GET /`), L1 Project Observatory (`GET /projects/{name}`), L2 Item Detail (`GET /projects/{name}/items/{id}`), the login, `/setup` and `/trust` onboarding pages, and the PWA shell. Its consumer is a human outside this repo's own commits; a silent change breaks trained perception, and that dependence is what makes it a seam. *(Brief A §1, webapp.py:4551, webbrowse.py:287, webbrowse.py:611; Brief B §8)*

Expand Down Expand Up @@ -64,9 +64,9 @@ Literal colour, font, or size in an inline `style=` attribute, or in a `<style>`

### Core 5: Reads never write

No `GET` route mutates state. The surface may poll itself aggressively; it writes only through explicit operator actions, which are `POST`.
No `GET` handler reaches a mutating adapter call; the surface may poll itself aggressively and writes work-tracker state only through explicit operator actions, which are `POST`. The one named exception: `GET /auth/logout` clears the session cookie.

**Machine check:** `reads.never_write` — a route audit over every registered handler: no `GET` handler reaches a mutating adapter call.
**Machine check:** `reads.never_write` — a route audit over every registered handler, asserting the clause's first sentence against each read-only one.

**Tier:** A

Expand Down Expand Up @@ -154,7 +154,7 @@ A page's leading content is what the operator came to see, not a form to fill in

**Tier:** NOT-ASSERTABLE

**Reviewed at cadence:** owner review of L0/L1/L2 at each ENCODE gate and before any Freeze stamp.
**Reviewed at cadence:** owner review of L0/L1/L2 at each ENCODE gate and before any Freeze stamp, and at each `ledger/reconcile-report.md` re-check.

*(Brief B §5, wt-v4-observatory/BRIEF.md:82-88)*

Expand All @@ -168,7 +168,7 @@ The surface exists so the time between an alarm appearing and the operator ackno

**Tier:** NOT-ASSERTABLE

**Reviewed at cadence:** owner review at each ENCODE gate; promoted by Backlogged 6.
**Reviewed at cadence:** owner review at each ENCODE gate and at each `ledger/reconcile-report.md` re-check; promoted by Backlogged 6.

*(Brief B §6, wt-v2-poa.md:242-244, wt-v4-observatory/BRIEF.md:109-118)*

Expand Down Expand Up @@ -254,7 +254,7 @@ Tier-A-checkable Core clauses: 1, 3, 4, 5, 8, 9, 10, 11, and the token half of 2

**Good:** the sweep reports zero pixels matching `--alarm` or `--blocked`.

**Bad:** the same page with the retired-palette region reinstated — a hardcoded amber outside the token set — is reported as alarm-coloured pixels on a calm page.
**Bad:** the same page with the retired-palette region reinstated — a hardcoded amber outside the token set — is reported as pixels of a hue outside the token set on a calm page: `#D9A253` is far enough from `--alarm` that the sweep counts it in its own bucket rather than as alarm colour.

**Test location:** `tests/conformance/operator_surface/browser/test_tier_b.py` (Core 2).

Expand All @@ -278,7 +278,7 @@ Tier-A-checkable Core clauses: 1, 3, 4, 5, 8, 9, 10, 11, and the token half of 2

**Good:** the post-swap DOM snapshot preserves scroll offset, the open disclosure, and the pause flag, and the live region survives to announce.

**Bad:** a whole-body innerHTML replacement that recreates the region loses all four; the snapshot shows offset zero, the disclosure closed, the pause flag cleared, and a fresh live region with nothing announced.
**Bad:** a whole-body innerHTML replacement that recreates the region — the open disclosure closes and every tagged live region is replaced by a fresh node (chromium preserves scroll and the pause flag by itself; a reflowing replacement loses scroll too).

**Test location:** `tests/conformance/operator_surface/browser/test_tier_b.py` (Core 6).

Expand All @@ -288,9 +288,9 @@ Tier-A-checkable Core clauses: 1, 3, 4, 5, 8, 9, 10, 11, and the token half of 2

**Scenario:** L0, L1, and L2 loaded at each viewport in both themes.

**Good:** `scrollWidth == clientWidth` at every viewport, every interactive target measures at least 44px, and computed text contrast is at least 4.5:1.
**Good:** no element's border box extends past `clientWidth` at any viewport, every interactive target measures at least 44px, and computed text contrast is at least 4.5:1.

**Bad:** a fixture with a fixed-width element wider than 430px emits `scrollWidth > clientWidth`; a fixture using the recorded 4.27:1 ink pair emits a contrast number below the floor.
**Bad:** in a fixture with a fixed-width element wider than 430px, that element extends past `clientWidth`; a fixture using the recorded 4.27:1 ink pair emits a contrast number below the floor.

**Test location:** `tests/conformance/operator_surface/browser/test_tier_b.py` (Core 7).

Expand Down Expand Up @@ -378,6 +378,9 @@ Before this contract moves from DRAFT to FROZEN, all of the following conditions

## Changelog

- **2026-09-05 — FROZEN.** Owner ratification and signature (Freeze 10): owner's literal words "Ok, do the freeze" and "looked, ratify." Status moves DRAFT → FROZEN. From this entry on, this file changes only by a sibling proposal (`operator-surface.v2-candidate.md`) carrying the target line, the exact change, real evidence, and what does not change; `hooks-candidate-guard` refuses in-place edits.
- **2026-09-05 — Freeze 8 record (ratification input, never a machine check):** the owner looked at the rendered L0 (Mission Control), L1 (Project Observatory) and L2 (Item Detail) at 430, 900 and 1280px in both themes — the eighteen captures from the pinned-browser run (chromium 148.0.7778.0 / playwright 1.60.0) on main @ d039b32 — and said "looked".
- **2026-09-05 — DRAFT true-up #2, owner-ratified ("ratify."):** the three pre-lock fixes from the Freeze 9 external review (independent reviewer, not the author; verdict REQUEST CHANGES → approve once landed): RC-1 Core 5 reworded to what `reads.never_write` asserts, naming `GET /auth/logout`'s cookie clear as the one exception; RC-2 Conformance 1/3/4 halves reworded to the measured defects (hue outside the token set; disclosure + live-region node identity; element-level overflow past `clientWidth`); RC-3 Core 12/13 cadences gain a standing trigger at each reconcile re-check. Six lower findings deferred to post-lock proposals.
- **2026-09-04 — DRAFT true-up #1,** owner-ratified ("yep, do it all."): Core 4 widened to reach per-page `<style>` blocks outside the token module (evidence: `webtrust.py`'s hardcoded retired palette); Core 10's machine-check wording aligned to the clause ("does not survive a refresh"); the Changelog's `webapp.py:38-39` quotation made byte-exact (Freeze 7). Status remains DRAFT.
- **2026-09-04 — ENCODE gate:** owner reviewed the DRAFT text and ratified it (literal: "lgtm."). Status remains DRAFT.
- **2026-09-04 — DRAFT.** First draft, authored at the ENCODE gate from Phase-0 evidence (Brief A, shipped surface; Brief B, prior decisions), nine owner-ratified decisions, and four conformance rulings. Owner ratification, literal: *"Let's make hero the velocity, along w/ other numbers that matter, such as the active/in-flight, blocked, need attention, open, etc. Focus is on observability, etc. The rest looks good to me."* That overrode a recorded invariant — *"the dashboard's hero is the AGE of the oldest unclaimed item, never a count"*, rationale *"a giant `0` trains a viewer to stop looking. An age reads as neglect"* (`webapp.py:37-44`). The owner weighed that alternative and chose observability, so Core 1 asserts velocity with the counts that matter; the concern behind the alternative survives in the form the owner accepted, as Core 8. Settled by ruling: a human-perception seam is admissible, so every clause admitting a machine check carries one and the two that cannot are named NOT-ASSERTABLE; no ceiling constant enters Core, the exemption register living in `ledger/` (Core 4, Backlogged 2); the custody boundary is a one-way citation, leaving `contracts/custody-coordination.v1.md` untouched; CLI `--json` is Reserved 1, a different seam being a different contract.
31 changes: 23 additions & 8 deletions ledger/checks/mutation_harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -1083,13 +1083,27 @@ def _mo033_the_emphasis_comes_back(w: World) -> None:
)


def _mo034_the_changelog_records_a_look(w: World) -> None:
"""FIXED: a Changelog entry records the owner's rendered-page look."""
w.append(
OPERATOR_CONTRACT_PATH,
"\n- **2026-09-05 — owner looked at the rendered L0, L1 and L2 at 430, 900 and "
"1280px in both themes.**\n",
def _mo034_the_changelog_record_is_removed(w: World) -> None:
"""REGRESSION (2026-09-05: the owner looked and the FROZEN stamp recorded
it, so this row flipped GAP -> CONFORMS and its probe was retargeted from
the pin; the mutation it replaces, `_mo034_the_changelog_records_a_look`,
pushed the OPPOSITE direction -- git-blame).

The Freeze 8 record is deleted from a locked Changelog. It is the only
trace of the one act that can satisfy Freeze 8, and no agent may write it
back (Phase-1 ruling 6), so a probe that did not notice its removal would
leave a Freeze condition silently un-met.
"""
line = next(
(ln for ln in w.raw(OPERATOR_CONTRACT_PATH).splitlines() if "Freeze 8 record" in ln),
None,
)
if line is None:
raise HarnessOutOfDate(
"no 'Freeze 8 record' entry in contracts/operator-surface.v1.md, so OSV1-034's "
"mutation has nothing to remove -- the row and this mutation are out of step"
)
w.replace(OPERATOR_CONTRACT_PATH, line + "\n", "")


#: One entry per counterfactual. Rows with several separable halves get one
Expand Down Expand Up @@ -1455,8 +1469,9 @@ def _mo034_the_changelog_records_a_look(w: World) -> None:
),
Mutation(
"OSV1-034",
"a Changelog entry records the owner's rendered-page look",
_mo034_the_changelog_records_a_look,
"the Freeze 8 record -- the owner's rendered-page look -- is deleted from the "
"locked Changelog",
_mo034_the_changelog_record_is_removed,
),
)

Expand Down
68 changes: 55 additions & 13 deletions ledger/checks/test_operator_rows.py
Original file line number Diff line number Diff line change
Expand Up @@ -2278,23 +2278,65 @@ def test_row_osv1_033() -> None:
)


def test_row_osv1_034() -> None:
"""Freeze 8 pin: the Changelog records no look at the rendered pages.
#: The Changelog's Freeze 8 entry, located by its own label rather than by
#: position -- a record that moved up or down the list is still the record.
_FREEZE_8_RECORD = "Freeze 8 record"
#: A dated Changelog bullet: `- **YYYY-MM-DD — ...`. Freeze 8 asks for the look
#: to be "recorded in the Changelog", and this file's Changelog is dated
#: entries; an undated one could not be placed against a tree.
_DATED_ENTRY = re.compile(r"^- \*\*\d{4}-\d{2}-\d{2} ")


HONEST LIMIT, and it is irreducible: this can only ever assert that a
RECORD exists, never that the owner looked. Freeze 8 says "never a machine
check" for exactly that reason.
def test_row_osv1_034() -> None:
"""Freeze 8 CONFORMS: the Changelog carries a dated record of the owner's
look at the rendered pages.

RETARGETED 2026-09-05, in the same change that flipped this row. The pin
asserted the ABSENCE of the record -- "the Changelog mentions none of 430,
900, 1280". The owner looked, the owner-ratified FROZEN stamp recorded it,
the pin went red the way a pin is meant to, and this is the real check that
replaced it. Flip direction is now REGRESSION: this going red means the
record was removed from a locked contract.

Each named element is asserted SEPARATELY -- three levels, three viewports,
both themes -- because a record that quietly drops one of them describes a
partial look, and Freeze 8 names all of them.

HONEST LIMIT, and it is irreducible -- UNCHANGED BY THE FLIP: this can only
ever assert that a RECORD exists, never that the owner looked. Freeze 8
says "never a machine check" for exactly that reason, and no retargeting
can turn a byte fact into an attestation.
"""
contract = read(OPERATOR_CONTRACT_PATH)
changelog = contract[contract.index("## Changelog") :]
entry = next(
(line.strip() for line in changelog.splitlines() if _FREEZE_8_RECORD in line), None
)
assert entry, (
f"OSV1-034 (Freeze 8) REGRESSION: the Changelog no longer carries a "
f"{_FREEZE_8_RECORD!r} entry. The owner's look is the only thing that can "
f"satisfy Freeze 8, and that record is its only trace -- deleting it un-meets "
f"a Freeze condition on a FROZEN contract. It is not re-creatable by an agent: "
f"writing it back would be a fabricated attestation (Phase-1 ruling 6)."
)
assert _DATED_ENTRY.match(entry), (
f"OSV1-034 (Freeze 8): the Freeze 8 record lost its date. Freeze 8 asks for the "
f"look to be recorded in the Changelog, and an undated record cannot be placed "
f"against the tree that was looked at:\n {entry[:160]}"
)
for level in ("L0", "L1", "L2"):
assert level in entry, (
f"OSV1-034 (Freeze 8): the Freeze 8 record no longer names {level}. The "
f"clause requires the look to have covered L0, L1 AND L2 -- a record naming "
f"fewer describes a partial look."
)
for viewport in ("430", "900", "1280"):
assert viewport not in changelog, (
f"OSV1-034 (Freeze 8) PIN BROKE THE RIGHT WAY: the Changelog now mentions "
f"{viewport}px. If the OWNER looked and an owner-ratified amendment "
f"recorded it, flip this row. If an agent wrote that entry, revert it -- "
f"it is a fabricated attestation (work_item_pipeline-eah)."
assert viewport in entry, (
f"OSV1-034 (Freeze 8): the Freeze 8 record no longer names {viewport}px. The "
f"clause names all three viewports; dropping one narrows the recorded look "
f"without narrowing the condition."
)
assert "looked at the rendered" not in changelog, (
"OSV1-034 (Freeze 8): the Changelog now records a look at the rendered pages. "
"Confirm it was the OWNER's, then flip the row."
assert "both themes" in entry, (
"OSV1-034 (Freeze 8): the Freeze 8 record no longer says the look covered BOTH "
"themes. A look at one theme is half of what the clause asks for."
)
Loading
Loading