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
9 changes: 5 additions & 4 deletions contracts/operator-surface.v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ Every element carrying a state carries it in text as well as hue — a status ch

### Core 4: One source of visual truth

Literal colour, font, or size in an inline `style=` attribute is a violation; zero are tolerated. Computed geometry in an inline `style=` — a bar width, a chart offset — is permitted only for sites enumerated on the exemption register. The register lives in `ledger/`, not in this contract, so that shrinking it is a convergent change requiring no amendment.
Literal colour, font, or size in an inline `style=` attribute, or in a `<style>` block outside the token module (`webtheme.py`'s token block), is a violation; zero are tolerated. Computed geometry in an inline `style=` — a bar width, a chart offset — is permitted only for sites enumerated on the exemption register. The register lives in `ledger/`, not in this contract, so that shrinking it is a convergent change requiring no amendment.

**Machine check:** `visual.single_source` — zero inline `style=` attributes carrying a literal colour, font, or size anywhere in `src/`; every inline `style=` site carrying computed geometry appears on the ledger's exemption register.
**Machine check:** `visual.single_source` — zero inline `style=` attributes carrying a literal colour, font, or size anywhere in `src/`; every inline `style=` site carrying computed geometry appears on the ledger's exemption register; and zero literal colour/font/size declarations in any `<style>` block outside the token module.

**Tier:** A

Expand Down Expand Up @@ -126,7 +126,7 @@ The surface is server-rendered HTML composed in Python, with small inline script

No kanban drag-board — its central gesture fights machine-owned custody. No client-side state that dies on refresh. No new chart library or JS dependency. No unbounded query behind a view.

**Machine check:** `antigoals.enforced` — the dependency manifest declares no charting or drag-and-drop library; every adapter call reached from a view passes an explicit limit; no view stores state only in the browser.
**Machine check:** `antigoals.enforced` — the dependency manifest declares no charting or drag-and-drop library; every adapter call reached from a view passes an explicit limit; no view holds state that does not survive a refresh (state persisted in `localStorage` or on the server survives; state held only in page memory does not).

**Tier:** A

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

## Changelog

- **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.
- **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.
141 changes: 140 additions & 1 deletion ledger/checks/_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,9 +433,148 @@ def style_sites_in(bucket: str) -> list[str]:
return sorted(f"{f}:{n}" for f, n, b, _d in inline_style_sites() if b == bucket)


# ------------------------------------------------------ token-pair luminance
# ------------------------------------------------- `<style>`-block census
#
# Added by the owner-ratified DRAFT true-up #1 (2026-09-04). Core 4's frozen
# text used to be scoped to inline `style=` attributes ONLY, so the retired
# palette webtrust.py hardcodes inside a whole `<style>` BLOCK was recorded by
# the SEED reconcile and deliberately NOT scored -- the same defect wearing a
# different tag, unreachable by the clause. The amendment widened the clause to
# reach "a `<style>` block outside the token module (`webtheme.py`'s token
# block)", and this is the engine that measures it.
#
# The token module is the ONE exemption, and it is the clause's own point: the
# token block IS the single source of visual truth, so literal values there are
# not a violation but the definition.

#: The module whose `<style>` block Core 4 exempts by name.
TOKEN_MODULE = WEBTHEME

_STYLE_TAG = re.compile(r"<style[^>]*>(.*?)</style>", re.DOTALL)
#: An f-string placeholder naming a bare module-level constant -- the shape
#: `webtrust.py:374` (`<style>{_CSS}</style>`) uses to embed its stylesheet.
_NAME_PLACEHOLDER = re.compile(r"\{([A-Za-z_][A-Za-z_0-9]*)\}")


def _module_str_constants(path: Path) -> dict[str, tuple[str, int]]:
"""Module-level `NAME = "..."` string constants -> (value, line).

Read by PARSING, never importing -- the same rule the rest of this kit
follows, so the census stays in-process and side-effect free.
"""
out: dict[str, tuple[str, int]] = {}
for node in ast.parse(read(path)).body:
if not isinstance(node, ast.Assign) or not isinstance(node.value, ast.Constant):
continue
if not isinstance(node.value.value, str):
continue
for target in node.targets:
if isinstance(target, ast.Name):
out[target.id] = (node.value.value, node.value.lineno)
return out


def style_blocks_outside_token_module() -> list[tuple[str, int, str]]:
"""Every `<style>` block embedded by a module OTHER than the token module.

Returns (file, line-of-the-CSS-text, css). A block's CSS may be written
straight into the tag, or interpolated from a module-level constant; both
are resolved, and the constant's OWN line is reported so a reader lands on
the stylesheet rather than on the tag that emits it.

Raises if a block interpolates something this census cannot resolve -- a
block it cannot read would UNDERSTATE the violation, which is the one
direction a census must never fail in (same rule as `inline_style_sites`).
"""
out: list[tuple[str, int, str]] = []
for path in src_modules():
if path == TOKEN_MODULE:
continue # the exemption Core 4 names by module
text = read(path)
if "<style" not in text:
continue
consts = _module_str_constants(path)
tags = list(_STYLE_TAG.finditer(text))
opened = text.count("<style")
if len(tags) != opened:
raise AssertionError(
f"`<style>`-block census cannot parse {path.name}: {opened} `<style` "
f"opening(s) but {len(tags)} closed block(s). A block whose `</style>` "
f"lives in a different string literal is INVISIBLE to this census, which "
f"would understate the violation -- fix the matcher, never the count."
)
for tag in tags:
body = tag.group(1)
tag_line = text.count("\n", 0, tag.start()) + 1
literal = _NAME_PLACEHOLDER.sub("", body)
if literal.strip():
out.append((path.name, tag_line, literal))
for name in _NAME_PLACEHOLDER.findall(body):
resolved = consts.get(name)
if resolved is None:
raise AssertionError(
f"`<style>`-block census cannot resolve {{{name}}} embedded at "
f"{path.name}:{tag_line} -- it is not a module-level string "
f"constant. The census is only honest while every block it finds "
f"can be READ; extend the resolver, never drop the block."
)
out.append((path.name, resolved[1], resolved[0]))
return out


#: Shared with the token-pair luminance engine below -- both read real CSS, and
#: both must strip comments FIRST or a commented-out declaration is scored.
_CSS_COMMENT = re.compile(r"/\*.*?\*/", re.DOTALL)
_CSS_DECL_SPLIT = re.compile(r"[;\n]")


def _block_declarations(css: str) -> list[tuple[int, str]]:
"""(offset-within-css, declaration) for every declaration inside a brace.

Selectors are skipped by a brace-depth walk rather than a regex, so a
pseudo-selector (`p.subtle`, `details summary`) is never mistaken for a
`property:value` pair.
"""
stripped = _CSS_COMMENT.sub(lambda m: " " * len(m.group(0)), css)
out: list[tuple[int, str]] = []
depth, start = 0, 0
for i, ch in enumerate(stripped):
if ch == "{":
if depth == 0:
start = i + 1
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
chunk = stripped[start:i]
pos = start
for piece in _CSS_DECL_SPLIT.split(chunk):
if ":" in piece.strip():
out.append((pos, piece.strip()))
pos += len(piece) + 1
return out


def style_block_literal_sites() -> list[tuple[str, int, str, list[str]]]:
"""(file, line, declaration, reasons) for every LITERAL colour/font/size
declaration inside a `<style>` block outside the token module.

Classified by `classify_style` -- the SAME rule the inline census uses, so
the two halves of Core 4 are measured identically and one cannot quietly
become stricter than the other.
"""
out: list[tuple[str, int, str, list[str]]] = []
for name, css_line, css in style_blocks_outside_token_module():
for offset, decl in _block_declarations(css):
bucket, reasons = classify_style(decl)
if bucket != LITERAL:
continue
out.append((name, css_line + css.count("\n", 0, offset), decl, reasons))
return sorted(out, key=lambda e: (e[0], e[1], e[2]))


# ------------------------------------------------------ token-pair luminance

_CSS_DECL = re.compile(r"(--[a-z0-9-]+)\s*:\s*([^;]+);")
_HEX_EXACT = re.compile(r"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
_VAR_ONLY = re.compile(r"var\((--[a-z0-9-]+)\)")
Expand Down
50 changes: 44 additions & 6 deletions ledger/checks/mutation_harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
WEBBROWSE,
WEBPWA,
WEBTHEME,
WEBTRUST,
WIDGETS,
collapse,
expected_flip_direction,
Expand Down Expand Up @@ -430,6 +431,24 @@ def _mo005_the_worst_literal_site_is_migrated(w: World) -> None:
w.replace(WEBPWA, "font:16px -apple-system,sans-serif;padding:32px;", "")


def _mo005b_the_page_local_palette_goes(w: World) -> None:
"""FIXED (the OTHER half): webtrust.py's page-local `<style>` block stops
declaring its own retired palette -- the eight literal colour declarations
at webtrust.py:258-259 go, as they would if the module imported the token
CSS instead of copying it.

This is the half Core 4's frozen text could not reach until the 2026-09-04
DRAFT true-up widened it, so it gets its OWN mutation: `_mo005_...` proves
the INLINE bucket discriminates and says nothing about a `<style>` block.
"""
w.replace(
WEBTRUST,
" --ground:#0D0D0C; --raise:#151513; --ink:#F2EEE6; --mid:#A6A199;\n"
" --quiet:#9C978F; --amber:#D9A253; --rule:#1F1F1D; --rule-hi:#333330;\n",
"",
)


_UNREGISTERED_SITE = "\n_LEDGER_MUTATION = f'<div style=\"height:{0}px\"></div>'\n"


Expand Down Expand Up @@ -582,9 +601,20 @@ def _mo032_the_register_grows(w: World) -> None:
w.append(WIDGETS, _UNREGISTERED_SITE)


def _mo033_the_contract_quote_is_corrected(w: World) -> None:
"""FIXED: the amendment strikes the markdown emphasis that broke Freeze 7."""
w.replace(OPERATOR_CONTRACT_PATH, "**never a count**", "never a count")
def _mo033_the_emphasis_comes_back(w: World) -> None:
"""REGRESSION (2026-09-04 true-up #1 retargeted this row's probe from a pin
to a genuine conformance check -- the seed pin `_mo033_the_contract_quote_
is_corrected` it replaced asserted the OPPOSITE direction, git-blame).

The corrected Changelog quotation re-acquires the markdown emphasis that
broke Freeze 7 at seed: markup is part of the exact match, so an emphasised
quote stops being a substring of the plain source sentence it cites.
"""
w.replace(
OPERATOR_CONTRACT_PATH,
"unclaimed item, never a count",
"unclaimed item, **never a count**",
)


def _mo034_the_changelog_records_a_look(w: World) -> None:
Expand Down Expand Up @@ -697,9 +727,16 @@ def _mo034_the_changelog_records_a_look(w: World) -> None:
),
Mutation(
"OSV1-005",
"the worst literal site is migrated (census 66 -> 65)",
"the worst literal INLINE site is migrated (census 66 -> 65)",
_mo005_the_worst_literal_site_is_migrated,
),
Mutation(
"OSV1-005",
"webtrust.py's page-local `<style>` block stops declaring its own retired "
"palette (block census 40 -> 32) -- the half Core 4 could not reach before "
"the 2026-09-04 true-up",
_mo005b_the_page_local_palette_goes,
),
Mutation(
"OSV1-006",
"an inline computed-geometry site appears that nobody registered",
Expand Down Expand Up @@ -796,8 +833,9 @@ def _mo034_the_changelog_records_a_look(w: World) -> None:
),
Mutation(
"OSV1-033",
"an amendment strikes the markdown emphasis that broke the quote",
_mo033_the_contract_quote_is_corrected,
"the corrected Changelog quotation re-acquires the markdown emphasis that "
"broke Freeze 7 at seed",
_mo033_the_emphasis_comes_back,
),
Mutation(
"OSV1-034",
Expand Down
Loading
Loading