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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The GitHub release body is a summary, not a copy of the changelog section. Write

## Unreleased

- Every `theme-contract.json` requirement now carries a required `scope`, and `scripts/check-theme-contract.py` enforces it. `fleet` binds every Spark-derived store theme and is what a live check (`--store` + `--theme-id`) enforces by default; `spark` binds Spark's own working copy and is what a local check (`--root`, CI, `make contract`) enforces by default, on top of the fleet rules. `--scope fleet|spark` overrides either default, and the gate's output names the scope and how many requirements ran. `pixels` is the only fleet rule; the four runtime hooks #66 added in 1.5.0 without a changelog line (`cart-badge`, `mobile-nav-toggle`, `mobile-nav`, `cart-drawer`; the 1.5.0 entry below still describes the contract as `pixels` only) are scoped to Spark. The first fleet sweep against 1.5.0 reported nine of thirteen live Spark themes failing `mobile-nav` on the file rule alone, while four of those storefronts served `#mobile-nav` from another file: derived themes are forks that may carry a hook elsewhere, and a file-location rule against the fleet reports forks, not faults. Decided 2026-09-21. `docs/theme-contract.md` lists all five rules with their scope, the rule for promoting one to `fleet`, and the invocation for each case: a fork's working copy (`--root ../fork --scope fleet`), a derived live theme (default), and Spark's own copy on a dev store (`--scope spark`, since a live check otherwise assumes a derived theme).
- Every `theme-contract.json` requirement now carries a required `scope`, and `scripts/check-theme-contract.py` enforces it. `fleet` binds every Spark-derived store theme and is what a live check (`--store` + `--theme-id`) enforces by default; `spark` binds Spark's own working copy and is what a local check (`--root`, CI, `make contract`) enforces by default, on top of the fleet rules. `--scope fleet|spark` overrides either default, and the gate's output names the scope and how many requirements ran. `pixels` is the only fleet rule; the four runtime hooks #66 added in 1.5.0 without a changelog line (`cart-badge`, `mobile-nav-toggle`, `mobile-nav`, `cart-drawer`; the 1.5.0 entry below still describes the contract as `pixels` only) are scoped to Spark. Derived themes are forks that may carry a hook in a different file, so a file-location rule applied to them reports forks, not faults. `docs/theme-contract.md` lists all five rules with their scope, the rule for promoting one to `fleet`, and the invocation for each case: a fork's working copy (`--root ../fork --scope fleet`), a derived live theme (default), and Spark's own copy on a dev store (`--scope spark`, since a live check otherwise assumes a derived theme).

## 1.5.0 - 2026-09-21

Expand Down
14 changes: 1 addition & 13 deletions TODOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,7 @@
**Priority:** P1
**Effort:** S
**What:** `scripts/check-theme-contract.py` masks only DTL comments (`{# #}`, `{% comment %}`, `{% verbatim %}`) before searching for a requirement's `must_contain`. A live theme carrying `<!-- {% pixels %} -->` passes the `pixels` rule while the browser discards the rendered tracker iframes, so the storefront emits no events. Wrap the checker's mask with an HTML-comment mask (checker-side only; `check-templates.py`'s masking has other consumers) and add the negative test. `{% if False %}{% pixels %}{% endif %}` also passes and is not text-fixable; document it under "Verifying on a storefront".
**Why:** `pixels` is now the only fleet-scoped rule, so this is the whole fleet sweep's blind spot. Surfaced by the adversarial pass on the contract-scope PR, 2026-09-21.

### Theme contract: harden the live check's transport
**Priority:** P2
**Effort:** S
**What:** `read_remote_sources` uses the default `urllib` opener, so a 30x from the store forwards the `Authorization: Bearer` header to the redirect target, and `--store` accepts any URL scheme. Assert `https://` and install a non-following redirect handler. Also confirm the templates endpoint is unpaginated at the largest theme size (the checker reads `results` and never follows `next`), and quote remote template names in violation lines so a name containing a newline cannot fabricate a second `- [` line.
**Why:** Pre-existing, but the unattended fleet sweep runs this against every store twice a week with a real admin key.

### Fleet sweep: pass `--scope fleet` explicitly and record the applied count (next-mind)
**Priority:** P2
**Effort:** S
**What:** `next-mind/scripts/spark_fleet_check.py` invokes the checker with no `--scope`, discards stdout (where the "N of M requirement(s)" line goes), and turns any non-violation exit-1 (scope refusal, traceback on a malformed API entry) into a `failing_active` row. Pass `--scope fleet`, capture the applied count into the fleet JSON and ledger, and distinguish infrastructure errors from violations (a separate exit code from the checker would help).
**Why:** The sweep now depends on an implicit default that this repo changed under it; the ledger should say which rules ran.
**Why:** `pixels` is now the only fleet-scoped rule, so a commented-out tag is the one way a derived theme can pass the contract while serving no tracker.

### Preview mode placeholder suppression
**Priority:** P2
Expand Down
2 changes: 1 addition & 1 deletion tests/test_theme_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ def test_fleet_scope_on_a_live_theme_matches_the_default(self):
self.assertIn("fleet scope, 1 of 5 requirement(s)", result.stdout)

def test_fleet_sweep_invocation_uses_env_key_and_sparks_own_contract(self):
# The next-mind fleet sweep runs exactly this: --store and --theme-id,
# A fleet sweep runs exactly this: --store and --theme-id,
# the key in $NTK_APIKEY, no --contract and no --scope. It must land on
# Spark's shipped contract at the fleet scope and pass a fork.
with serve_theme(FORK_WITHOUT_SPARK_HOOKS) as base_url:
Expand Down
Loading