Skip to content

feat(stardust): 0.26.0 — published-origin gate hardened with element-level criteria (#125) - #397

Merged
paolomoz merged 5 commits into
mainfrom
stardust/published-origin-gate-hardening
Sep 26, 2026
Merged

paolomoz merged 5 commits into
mainfrom
stardust/published-origin-gate-hardening

Conversation

@paolomoz

@paolomoz paolomoz commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Why

A 96-page rollout gated every deployed page on stitched 1440 captures and pixel-compare. Its coupon-listing page PASSED at 6.7 % / Δh 69 px while all 284 cards were visibly broken: a fixed-height card with overflow: hidden, the body ~30 px too low, the description clipped mid-glyph, the details link pushed under the primary button, the sort control on another default. The links were in the served DOM — pure CSS geometry, which pixelmatch underweights (16.5 % over the text boxes alone). "PASS" meant "right shapes at the right places", not "every element present and legible". Element presence and legibility are checkable, so the gate now checks them — and the same run showed that only 5 of 96 pages had ever been in a pixel table at all.

What

The rule. Every crafted prototype and every deployed page is a row in a pixel table: gate-all.mjs --stage prototype writes stardust/replica/gates/prototypes-<w>/summary.{json,md} at the end of Phase 4 (build = the served prototype, origin = the archetype round's cached live.png), the default stage writes gates/all-<w>/. gate-evidence.mjs reads the tables as the source of record: with a table for a width the row decides pixel-gate-<w>, a page without a row is OPEN: no table row; without any table the gate.sh logs stand in and --check names the gap. The eval requires the prototype table.

Coverage carries the verdict. update-coverage.mjs --gate <summary.json> writes each row into delivery.gate and flips a failing page (no documented override) to failed; verify.mjs never marks a page verified while delivery.gate.pass is false. Completion derives from one place. Rollout Phase H reports both table totals.

Four criteria per row: pixel % ≤ 10 AND |Δh| ≤ 5 % of origin height AND clipped = 0 (+ documented allowance) AND content MISSING + HIDDEN links / headings = 0, plus required repeated units within 4 px. The pixel-only verdict is kept beside the full one as calibration data.

Where the probes run:

stage pixel + height clip-probe content-presence unit-geometry
gate.sh --full, prototype regime ✓ ✓ (build side) – (content-diff already reconciles the prototype) when units.json declares a family
gate.sh --full, published regime (pub<N>) ✓ ✓ ✓ live vs served when declared
gate-all --stage prototype ✓ ✓ – when declared
gate-all (deployed, default) ✓ ✓ ✓ when declared

Element lines fail a --full round and outrank a pixel PASS in gate-evidence the way the overflow assert does. In C-deliver each cluster subagent runs gate-all --only over its own pages inside the fan-out; C-final's roster run is the recorded unit gate-all (--skip-existing over the clusters' captures, resumable at its boundary) followed by --gate (handoff contract § 3, row C).

Browser tier: best available, registered. The probes elect real Chrome (window-free) and fall back to bundled Chromium when it is not installed; every probe records the tier it ran, summary.md has a browser column and the calibration line counts degraded pages. Sites without bot management measure identically on both tiers; on the recorded Akamai site, Chromium got HTTP 400 on every origin page and Chrome 200 on 59 of 96.

New scripts: replica/gate-all.mjs (the tables); diff/clip-probe.mjs (D1), diff/content-presence.mjs (D2), diff/unit-geometry.mjs (D3, --families stardust/replica/units.json --slug <s>), diff/measure-live.mjs (library: the shared settle / session / shadow-DOM measurement). pixel-compare.mjs --text-boxes (D4, auxiliary). Repeated-unit families are declared once per project: { "<family>": { origin, build, n, required, pages[], templates[] } } (recreation-procedure § Repeated-unit families).

Upstreamed field feedback: window-free real Chrome as the escalation tier (STARDUST_HEADED_WINDOW=1 opts into a window); Akamai's HTTP 400 escalation as a challenge marker; setup steps probe the probe deps first and install them as devDependencies only on failure, never --no-save; deploy-batch.mjs surfaces the admin x-error header (the 200-images-per-document cap).

Docs: gate doc § The all-pages published-origin gate + pass-bar item 7, replica Phase 4 / 5 / Setup, recreation-procedure § Repeated-unit families, handoff contract § 3 rows C and E, deploy Step 10 item 7, rollout Phase E / H, diff SKILL § The published-origin probes, eval criteria, scripts index. Tests for the four instruments plus the extended gate.sh (62 checks), gate-evidence (32) and update-coverage contracts.

Calibration (96-page roster, same captures)

table pixel-only PASS full PASS
deployed (all-1440) 66 (+1 override) 28 (+1 override)
prototypes (prototypes-1440, 5 archetypes) 4 4

Failing by criterion on the deployed table: pixel 30, height 6, clip 23, content 31; content n/a on 37 pages whose origin is edge-blocked from the probing network.

Review notes (karlpauls)

1 recorded unit, 2 coverage verdict, 3 per cluster first, 4 import before install, 5 tier in the evidence — done in the last three commits. 6 (version bump via plugin-version.mjs) waits for #396; this branch sets .claude-plugin/plugin.json to 0.26.0 and will be rebased on that field once #396 lands.

Size

Versus main: code +1705 / −36, tests +472 / −4, skill prose +188 / −17, CHANGELOG +71. Removed as compensation along the way: pixel-compare --pad (the rejected union metric), sbs-crop.mjs, the measure-live CLI (library only), the content-diff --published handoff.

Open questions

Default tolerances (--clip-max 0, --unit-tol 4, the 25 % count tolerance); whether buttons and control state should join the blocking set once session-variable labels are modelled; the English word lists behind the count and "read more" heuristics are flags (--count-words, --more-words), not yet per-locale defaults.

Verification

npm run lint:stardust clean; airbnb eslint clean on the new scripts; all affected test suites pass.

🤖 Generated with Claude Code

paolomoz and others added 2 commits September 25, 2026 21:40
…level criteria (#125)

A 96-page rollout passed /offers/offers at 6.7 % pixel while all 284 coupon
cards were clipped and their "View details" links pushed under the Clip
button: pixel PASS meant "right shapes", not "every element present and
legible". New replica/scripts/gate-all.mjs runs every deployed page through
stitched captures + pixel-compare AND two symmetric DOM probes, verdict =
pixel ≤ 10 % AND |Δh| ≤ 5 % AND clipped = 0 AND MISSING+HIDDEN links/headings
= 0 (+ required repeated units within 4 px); the pixel-only verdict is kept
beside it as calibration data. New diff probes: clip-probe (D1),
content-presence (D2, content-diff --published hands over), unit-geometry +
measure-live (D3); pixel-compare --text-boxes / --pad auxiliary (D4);
sbs-crop folds the crop helpers. Upstreamed walgreens feedback A1 (window-free
real Chrome by default), A2 (Akamai 400 as challenge), A3 (devDependencies,
never --no-save), B1, B7, and the DA 200-image cap via the admin x-error
header. Docs: gate doc § all-pages gate, replica Phase 5, deploy Step 10,
rollout Phase E, diff SKILL, scripts index. Tests for the four instruments.
Calibration on the walgreens roster: 66 pixel-only PASS → 28 full PASS.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…alve the prose, configurable word lists

Site name and URL removed everywhere; the recorded coupon-page example is told
once in the gate doc and referenced by finding number elsewhere. SKILL and
reference prose cut from 177 to 125 lines, the CHANGELOG entry from 91 to 51,
script doc headers to usage plus a short why. The two English heuristics are
flags (clip-probe --more-words, content-presence --count-words / --more-words).
Fixes a real bug: the "read more" regex lived inside the injected template
literal, so \b and \s were consumed by the string; the collapsible check also
scanned the whole parent scope and read every box collapsible on a page with
one toggle — now the box, its siblings, or a ≤ 3-child wrapper.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Tessl Plugin Lint

⚠️ stardust — 11 warning(s)
⚠ Skill 'stardust': SKILL.md is approximately 7971 tokens (recommended maximum: 5000). Consider moving detailed content to separate reference files.
⚠ Skill 'extract': SKILL.md is approximately 11295 tokens (recommended maximum: 5000). Consider moving detailed content to separate reference files.
⚠ Skill 'direct': SKILL.md is approximately 12333 tokens (recommended maximum: 5000). Consider moving detailed content to separate reference files.
⚠ Skill 'prototype': SKILL.md is approximately 18059 tokens (recommended maximum: 5000). Consider moving detailed content to separate reference files.
⚠ Skill 'migrate': SKILL.md is approximately 7522 tokens (recommended maximum: 5000). Consider moving detailed content to separate reference files.
⚠ Skill 'uplift': SKILL.md is approximately 6612 tokens (recommended maximum: 5000). Consider moving detailed content to separate reference files.
⚠ Skill 'audit': SKILL.md is approximately 5433 tokens (recommended maximum: 5000). Consider moving detailed content to separate reference files.
⚠ Skill 'replica': SKILL.md is approximately 8536 tokens (recommended maximum: 5000). Consider moving detailed content to separate reference files.
⚠ Skill 'reskin': SKILL.md is approximately 6069 tokens (recommended maximum: 5000). Consider moving detailed content to separate reference files.
⚠ Skill 'deploy': SKILL.md is approximately 48515 tokens (recommended maximum: 5000). Consider moving detailed content to separate reference files.
⚠ Skill 'rollout': SKILL.md is approximately 10404 tokens (recommended maximum: 5000). Consider moving detailed content to separate reference files.

✔ Plugin adobe/stardust@0.21.1 is valid

✅ All 1 plugin(s) lint passed with 11 warning(s) total.

Updated by tessl-lint for commit 512ebd5.

…gate.sh --full round

Rule: every crafted prototype and every deployed page is a row in a pixel
table — gate-all --stage prototype writes gates/prototypes-<w>/summary.*
(build = the served prototype, origin = the archetype round's cached
live.png), the default stage writes gates/all-<w>/. gate-evidence.mjs reads
the tables as the source of record (--tables): with a table for the width
the row decides pixel-gate-<w> and a page without a row is OPEN: no table
row; without any table the log rule stands and --check names the gap. The
eval requires the prototype table; rollout Phase H reports both totals.

gate.sh --full now runs clip-probe on the build side in every regime,
content-presence live vs build in the published regime (GATE_REGIME
overrides the URL heuristic) and unit-geometry when
stardust/replica/units.json declares a repeated-unit family for the slug
({ family: { origin, build, n, required, pages[], templates[] } }, also read
by gate-all); a clipped count, a MISSING/HIDDEN link or heading or a
required unit off fails the round, and outranks a pixel PASS in
gate-evidence the way the overflow assert does.

Compensation: pixel-compare --pad (the rejected union metric) is removed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@karlpauls

Copy link
Copy Markdown
Contributor

Read against the shape of our recorded hands-off runs (36- and 64-page rosters, delivery fanned out to one subagent per template cluster). The four criteria are the right bar — hidden links and clipped chrome are exactly what those runs shipped while the pixel gate said PASS — and window-free real Chrome fixes a real problem: the old headed tier needed a display, which a container never has. Six notes, none blocking; happy to follow up in a PR if you would rather keep this one focused.

  1. Record gate-all as a unit. stardust/rollout/progress.json is where a hands-off harness learns that a long step is running and where it may stop and resume; a 64-page gate-all is 30–45 min at the default concurrency (two stitched captures + probes per page), i.e. one of the longest steps of a run. One recorded unit gate-all with the boundary ask after it, and a handoff-contract.md § 3 row beside E/E2 (the contract is not in the PR yet), would let it run unattended without a session growing through it.
  2. Let coverage carry the verdict. rollout/SKILL.md records delivery.gate from summary.json; nothing that derives completion reads that field, so a page failing gate-all can still read as verified in coverage. If gate-all's per-page result flows into update-coverage.mjs (a failing page is not verified until it passes or is recorded as accepted debt), completion stays derived from one place — the same closure as the default-content fix in 0.25.0. The cap row from 0.25.2 has the same gap.
  3. Per cluster first, roster last. --only <slugs> already exists: each cluster's subagent can run it on its own pages at the end of its chain (in parallel with the other clusters, as their pixel gates already are), and the all-pages pass at C-final becomes --skip-existing over captures that exist. That keeps the added wall time inside the fan-out instead of on the long pole.
  4. Import before install. The setup step could probe import('pixelmatch') (as the old text did) and run npm i -D … only when it fails; where the harness already resolves the probe deps, the delivered code repo then stays untouched. Worth a sentence either way: the devDependencies land in the repo the skills push.
  5. Name the tier and the degradations in the evidence. When channel: 'chrome' is absent the instrument runs the lesser tier with a stderr warning; summary.json could carry per page which tier measured it and whether content was n/a, so a run that quietly degraded to pixel-only on 37 of 96 pages is visible in the artifact, not only in a log line.
  6. Version bump after chore(stardust): sync manifest versions and guard against drift #396. chore(stardust): sync manifest versions and guard against drift #396 adds scripts/plugin-version.mjs to lint:stardust; with it merged first, node scripts/plugin-version.mjs plugins/stardust 0.26.0 writes all three manifests and keeps this PR green.

Not yet exercised in a run on our side — these are reading notes; the first run on 0.26.0 will be calibration for the tolerances you list as open.

🤖 Generated with Claude Code

… evidence

The probes elect real Chrome (window-free) and fall back to bundled Chromium
when it is not installed; every probe records the tier it actually ran
(chrome | chromium-fallback | chromium) in its JSON, gate-all's summary.md
gains a `browser` column and its calibration line counts degraded pages, so
a run that lost its origin measurements on a bot-managed site is visible in
the artifact (review note 5).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… a recorded unit; probe deps before install

Review notes 1–4 on the PR. update-coverage.mjs --gate <summary.json> writes
each pixel-table row into delivery.gate and flips a failing page (no
documented override) to failed; verify.mjs never marks a page verified while
delivery.gate.pass is false, so completion derives from one place. Handoff
contract § 3: each cluster subagent runs gate-all --only over its own pages
inside the fan-out; C-final's roster run is the recorded unit `gate-all`
(--skip-existing over the clusters' captures, resumable at its boundary)
followed by --gate. Replica Setup probes the probe deps before installing
them as devDependencies.

Compensation: sbs-crop.mjs removed (any image tool crops), measure-live is a
library (no CLI; measure.mjs measures selectors), content-diff --published
handoff removed (the docs name content-presence) — net −96 code lines in
this increment.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@paolomoz

Copy link
Copy Markdown
Collaborator Author

Thanks Karl — 1 to 5 are in (4e79190, a87f40e, and the latest commit): gate-all is a recorded unit at C-final with per-cluster --only runs inside the fan-out (handoff contract § 3, row C), update-coverage.mjs --gate carries the verdict into coverage and verify.mjs refuses verified while the gate fails, the setup step probes the deps before installing, and every probe records the browser tier it actually ran (summary.md has a browser column, the calibration line counts degraded pages). On Chrome vs Chromium: it only matters on bot-managed origins — on the recorded Akamai site Chromium got HTTP 400 on every origin page and Chrome 200 on 59 of 96 — so the probes elect Chrome and fall back to Chromium, tagged chromium-fallback in the evidence. 6 waits for #396; I'll rebase the version field once it lands.

@paolomoz
paolomoz merged commit 27b87cc into main Sep 26, 2026
8 of 10 checks passed
@paolomoz
paolomoz deleted the stardust/published-origin-gate-hardening branch September 26, 2026 08:36

This branch is waiting to be deployed

1 waiting deployment
eval — 65ce1b37 Waiting Sep 26, 2026 by paolomoz via tessl-eval #901
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants