Skip to content

fix(stardust): replica run follow-ups — hard gates, served sitemap and index, bookkeeping, migrate units - #387

Draft
karlpauls wants to merge 5 commits into
mainfrom
stardust/replica-run-followups
Draft

karlpauls wants to merge 5 commits into
mainfrom
stardust/replica-run-followups

Conversation

@karlpauls

Copy link
Copy Markdown
Contributor

Follow-ups from two recorded hands-off replica runs of one 36-page source site — one at 0.22.1, one at 0.25.0 — each item verified against the run artifacts (ledger, journal, coverage, served origin) before it was written. Five commits, one theme each; every script change carries a test, every rule change names the recorded fact behind it in the changelog (## Unreleased).

What changes

Gates that passed a broken site

  • Horizontal overflow is a hard assert: measure.mjs prints the root scrollWidth per width per side, gate.sh runs it on the build side every round and fails the round on overflow whatever the pixel number says, gate-evidence.mjs records it over a PASS pixel line. Two delivered pages had rendered 373 and 400 px wide at a 360 viewport and passed as "logged, not iterated further".
  • content-diff.mjs compares placeholder, aria-label, title and icons (small images by file name, inline svg, icon-font glyphs), 🔴 on interactive elements — a dropped search placeholder, a wrong flag in a locale link and empty social-icon boxes had read as 0 🔴.
  • Image renditions per breakpoint and box-sizing per container are lifted values (measure.mjs reports both); media-reconcile leaves the prototype gate until the delivered content file exists; a capture that exits 1 is retried once and otherwise re-queued, never read as a verdict.

Index, sitemap, search

  • Chrome and fragment documents carry Robots | noindex; assemble.mjs --verify-origin compares the SERVED sitemap with the coverage rows and fails on a mismatch. The served sitemap had listed every page plus 22 chrome documents while the report said "36 urls" from the local file.
  • A 404 on /query-index.json is answered by helix-query.yaml in the code branch first (skeleton in the dynamics reference), the sheet-backed index only when the code branch is not writable.
  • Search parity is count + top titles against the source (dynamics-check.mjs compareSearchResults, fixture-tested); the search pattern ranks title matches first, dedupes and caps at the source's visible count.
  • update-coverage.mjs --new lets a page built in D2 enter coverage; inventory.mjs keeps such rows on a re-run.

Bookkeeping, capture, tooling

  • ledger.mjs refuses an end without an open start under --strict and warns when the journal has no section for the phase; the start line is the first command of a phase.
  • crawl.mjs lists the uncaptured first-level targets under every captured locale root (_crawl-log.json#captureGaps, copied to state.json site.captureGaps, counts in the extract end line) — two runs learned about 15 uncaptured locale pages six hours later from the link audit.
  • thumb.mjs --max-bytes (default 150 KB); optimize.mjs tags findings the source shares as source parity and keeps them out of the score (63 hand-accepted findings on both runs); both deploy drivers take --token-env / --token-file.
  • Migrate render runs as recorded units (stardust/migrate/progress.json, same shape as the C-deliver unit ledger): plan, one render unit per template cluster of ≤ ~8 siblings, assets, report — the one long phase that still had no resume point.

Checks

  • 27 of 28 test files pass locally; the one failure is qa/scripts/test/dropdown-unreachable.test.mjs, which needs Playwright installed in the checkout. Tests that need a browser skip with a line and test their pure functions directly.
  • lint:stardust clean (harness-neutral, script-paths, scripts-index 8799 bytes, script-help 58/0).
  • Contract stays free of runner endpoints; no schema changed (source parity and the origin of a D2-built page ride on existing fields, documented — proper schema fields are a follow-up).

Follow-ups not in this PR

  • The hands-off harness prompt must name the migrate progress file for the per-unit boundary ask to apply there.
  • Recording the source's visible search-result count automatically during dynamics detect (today the detect step records it by hand).
  • Explicit chrome-control assertion (a control present on one side only is structural) and a per-locale footer content-diff in C0 — both observed only on the older run.

🤖 Generated with Claude Code

karlpauls and others added 5 commits September 23, 2026 12:31
…query.yaml first, search parity counts, --new coverage rows, optimize source parity

Five defects observed on two recorded hands-off runs of the same 36-page
source site (one at an older skills commit, one at current main), verified
against their artifacts.

- A5 served sitemap indexes chrome documents (P1). The served /sitemap.xml
  listed every page plus the 22 nav/footer documents (58 urls for 36 pages);
  assemble.mjs wrote a correct local sitemap under stardust/rollout/site/,
  which .hlxignore keeps unserved, and Phase D read that file. Rule: chrome
  and fragment documents (nav, footer, per-locale nav-*/footer-*, locale
  shells that are not pages) carry a `Robots | noindex` metadata row at
  write time (Phase C); `assemble.mjs --verify-origin <live-origin>` compares
  the SERVED sitemap with the assembled set and exits 1 on any extra or
  missing path (network only with the flag; a sitemap index is followed;
  result recorded in manifest.json); the D-site ledger end names the SERVED
  count. helix-sitemap.yaml + helix-query.yaml exclude globs are documented
  as the additive alternative.
- B1 query index: wrong remedy for a 404 (P1). /query-index.json stayed 404;
  the run probed the admin configuration service, read its 403 as "no index
  can be configured" and built a sheet-backed interim index. Rule: the FIRST
  remedy is helix-query.yaml in the code branch (skeleton in
  dynamics/reference/listings.md), push, publish live, poll no more often
  than every 5 s for at most 10 minutes; no configuration write is involved;
  the sheet-backed index is the fallback only when the code branch is not
  writable (rollout Phase D2, dynamics triage rule 8, patterns.md).
- B2 search relevance and duplicates (P1). The typeahead returned 10
  unbounded entries (section pages, two home pages under one title) where
  the source returned 3 title matches. Rule: title matches rank first,
  description and body text only while fewer than N title hits (N = the
  source's visible count, read during detect), dedupe by title +
  description, cap the dropdown at N; dynamics-check.mjs `search-query`
  compares result COUNT and the top-3 titles with the source's recorded
  values (`expectCount`, `expectTitles`) and fails on a count mismatch —
  pure `compareSearchResults()` with a fixture test.
- B3 D2-built pages cannot enter coverage (P2). A search page built in D2
  stayed outside coverage/pages.json, verify --all and the sitemap. Rule:
  `update-coverage.mjs --new <slug> --path --template --origin [--title]
  [--status]` adds a schema-shaped row under the coverage lock (the pages
  schema allows no origin property: the origin is recorded in
  source.migratedHtml as `<origin>:<slug>`), creates or extends the template
  row, is idempotent, refuses captured slugs and taken paths; assemble.mjs
  includes such rows.
- E1 optimize findings that mirror the source (P2). All 63 findings (JSON-LD,
  title length, meta description, duplicate titles) mirrored the source and
  were accepted by hand. Rule: with the extract capture present
  (stardust/current/pages/<slug>.json + rendered sidecar; --current <dir>),
  optimize.mjs tags a finding whose condition the source shares
  `fixability: out-of-scope` + evidence prefix `source parity:` (the findings
  schema has no parity property), autofix unavailable; such findings are
  listed in their own report section and excluded from the health score,
  the open P1/P2/P3 counts and the gate.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…gap listing per locale root, thumb size cap, uploader --token-env/--token-file

Five defects observed on two recorded hands-off replica runs of the same
36-page source site.

- D2 — ledger `start` written at phase end. A recorded run wrote a phase's
  `start` and `end` one second apart after 109 minutes of work, so a
  supervisor tailing the ledger saw an idle run. ledger.mjs: an `end` now
  needs an open `start` for the same skill + phase (a start with no later
  end); missing → warning in plain mode; under --strict exit 2, nothing
  written, one stderr line naming the missing start command. Lines pair in
  their canonical forms (aliases, case). run-status.md § Rules: the `start`
  line is the FIRST command of a phase, before any script runs.
- D3 — journal without sections for early phases. `ledger.mjs … end` warns
  when <dir>/journal.md has no `## ` heading naming the phase (case- and
  separator-insensitive; absent file skipped; never an exit-code change).
  replica/SKILL.md bookkeeping: every phase, extract and preserve-direction
  included, opens with the start line and closes with the end line plus a
  journal section `## <Phase name> — <what happened> (<date>)`.
- C1 — locale root captured, its subtree not, found six hours later in the
  rollout link audit (15 × 404, then repointed). crawl.mjs post-pass: for
  every captured locale root (a one- or two-segment path of locale codes)
  the same-origin links on that page under its own path are listed and the
  ones the crawl never captured are written to _crawl-log.json#captureGaps
  ({roots: [{root, slug, linked, uncaptured[]}], detail}) with one stderr
  line; `detail` is the ready ledger string ("uncaptured first-level
  targets: ca/en 15, fr/fr 0"). crawl.mjs is now importable (playwright
  imported inside main, real-path main guard), so the helpers have a
  fixture-HTML unit test without a browser. extract/SKILL.md Prep mode: copy
  roots[] to state.json.site.captureGaps and carry `detail` in the extract
  `end` ledger line.
- C2 — thumbnails read into context at full size (eight of 268–608 KB,
  3.2 MB in one context). thumb.mjs --max-bytes <n> (default 150000): a
  thumbnail over the cap is re-encoded at a lower --max-height (the same
  bottom crop) until it fits; the line carries the crop, the cap and the
  final size; exit 1 when one row still exceeds it. extract/SKILL.md Phase
  2.5: a thumbnail enters the context only after thumb.mjs (≤ 150 KB), or
  the vision check runs in a subagent returning one line per thumbnail.
- E7 — token source per driver. da-media-upload.mjs --token-env <NAME>
  (default DA_TOKEN) and --token-file <path> (read once at start; never
  both); the value is never printed, never in the re-run command, never in
  the ledger; the expiry, HALT and re-run lines name the variable or file.
  JWT-expiry preflight and the 401 policy unchanged. deploy/SKILL.md token
  paragraph names both flags.

Tests: ledger.test.mjs (+4 checks), thumb.test.mjs (+3, the size on every
line), da-media-upload.test.mjs (+2), new extract/scripts/test/crawl.test.mjs;
state, deploy-batch and file-lock suites unchanged and green; both lints
clean; --help of every changed script prints and writes nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…attributes, rendition and box-sizing as lifted values, media-reconcile off the prototype gate, capture retry

Six items observed on two recorded hands-off replica runs of the same 36-page
source site, each verified against artifacts and independent screenshots.

- A2 horizontal overflow at 360 accepted as a residual (P1): measure.mjs prints
  a root line per width per side (scrollWidth beside scrollHeight and the
  viewport, "◄◄ OVERFLOW +n px"; root / rootDeltas in the JSON); gate.sh runs it
  on the build side every round and fails the round (exit 2) on overflow
  whatever the pixel number says; gate-evidence.mjs records "FAIL: horizontal
  overflow" over a PASS pixel line, withholds pixel-gate-<w>, shows /ovf+<px> on
  the row and overflowX in the ledger; source-fidelity-gate.md § Iteration
  discipline gains the HARD assert no iteration cap waives.
- A3 content-diff compares text nodes only (P1): a second in-page inventory
  (placeholder, aria-label, title; icons — small <img> by file name, inline svg,
  icon-font glyphs by ::before/::after content, css mask/background icons, empty
  icon boxes) and a pure differ: MISSING/EXTRA <ATTR>, MISSING ICON, ICON DIFF,
  ICON KIND, EXTRA ICON — 🔴 on interactive elements, 🟡 otherwise. Output format
  and exit codes unchanged; --json gains findings[]; playwright is imported lazily
  and the module has a main-module guard so the differ is unit-tested without a
  browser; new skills/diff/scripts/test/content-diff.test.mjs (the fixture e2e
  skips with a line where playwright is not importable).
- E3 image rendition per breakpoint (P2): measure.mjs reports img { naturalWidth,
  naturalHeight, currentSrc } per <img>/<picture> match and img.naturalWidth /
  img.file deltas; recreation-procedure.md § Image renditions per breakpoint —
  record the rendition live selects per gate width, author the same
  srcset/sizes, read a sub-3 px root delta with equal boxes as a rendition-ratio
  symptom, not a layout bug.
- E4 media-reconcile demanded on prototype files (P2): gate-evidence.mjs
  --content <dir> (default content); without the page's delivered content file
  the gate reads "n/a: no delivered content file yet" (never OPEN), leaves the
  sibling acceptance set and --check, and is required again once the file
  exists; source-fidelity-gate.md places media-reconcile in the delivery chain
  (row C), not in the prototype gate.
- E5 box model lifted as a first-class value (P2): boxSizing joins measure.mjs's
  default props so --against names a content-box / border-box fork per box;
  recreation-procedure.md § Box model is a lifted value — lifted per container,
  declared per block, the boilerplate reset scoped on a replica, never universal.
- E6 capture exit 1 under parallel Chromium load (P2): gate.sh retries a capture
  that exits 1 (live, build, overflow probe) ONCE; two exit-1 attempts end the
  round with exit 1 "re-queue, not a verdict"; 3 / 4 / 124 stay final; run-bg
  slot defaults untouched; source-fidelity-gate.md names capture exit 1 as
  re-queue.

Tests: measure, gate (50 checks), gate-evidence (28), content-diff, run-bg,
foundation-freeze (19) pass; lint:stardust clean; every changed script answers
--help without writing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… unused import

Three follow-ups the recorded hands-off runs asked for after the bookkeeping,
rollout and gate fixes landed.

- deploy-batch.mjs takes `--token-file <path>` with exactly the uploader's
  semantics: read once at start and trimmed, never together with
  `--token-env`, a missing or empty file is a usage error (exit 2) naming the
  path; the expiry and HALT lines name the file, and the file-mode re-run
  command carries no `<var>=<fresh token>` prefix. Header, --help and the
  deploy card's token paragraph say both drivers take both flags.
  deploy-batch.test.mjs: two checks (file read + HALT/expiry lines + resume;
  the three usage errors).
- inventory.mjs keeps the rows `update-coverage.mjs --new` added (a page
  built outside the migrated tree; `source.migratedHtml` is the origin
  marker `<origin>:<slug>`) on a re-run, status untouched, instead of
  dropping them — the page is registered once. A marker row gives way only
  when the migrated tree now holds a file for its slug or a migrated page
  owns its path. One `Kept` report line. inventory.test.mjs: kept across a
  re-run (template row, counts, report line), replaced once the slug is
  migrated. The "re-run the same --new line after inventory" caveat is
  removed from rollout/SKILL.md, coverage-model.md and the update-coverage
  header.
- da-media-upload.mjs: the unused `renameSync` import is removed.

Tests: deploy-batch (12), inventory (7), update-coverage, da-media-upload,
ledger all pass; lint:stardust clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…low-ups; migrate render as recorded units

The shared documents the three fix commits (rollout + dynamics, bookkeeping +
extract + uploader, replica gates) were not allowed to touch, plus the
recorded-units pattern for migrate.

- replica/reference/handoff-contract.md: § 1 row 4 (content-diff pairs
  placeholder / aria-label / title and icons), row 5 (horizontal-overflow
  assert every round, capture exit 1 = re-queue), row 9 (migrate runs as
  recorded units), the acceptance paragraph (media-reconcile required only
  once the delivered content file exists; collector sentence) and a new
  migrate unit-ledger paragraph; § 3 rows D (served sitemap verified, chrome
  `Robots | noindex`, `D-site end` names the SERVED count), D2
  (helix-query.yaml first, search parity by count + titles, coverage row for
  pages built there), E2 (site.captureGaps decided at Phase 1); § 4 rows for
  the uploader and the batch driver (`--token-env | --token-file`),
  gate-evidence (`--content`, `/ovf`, `n/a:`, pixel shape), new rows for
  measure.mjs, ledger.mjs and thumb.mjs, the three rollout writer rows
  replaced, inventory keeps `--new` rows; § 5 ledger rule (start first,
  journal section per end) and the migrate-units checkpoint block. No
  runner endpoint anywhere in the card.
- migrate/SKILL.md Phase 2: "Recorded units" — plan / render per template
  cluster of at most ~8 siblings / assets / report in
  stardust/migrate/progress.json with the C-deliver ledger's shape; progress
  write, checkpoint commit, next unit; a resumed session runs only the units
  not done (the driver is idempotent). A recorded run rendered 31 siblings
  through five parallel builders as one unit inside one session — its most
  expensive — with no resume point between plan and report.
- stardust/reference/scripts-index.md: usage lines for content-diff,
  dynamics-check, crawl, thumb, gate-evidence, gate.sh, measure, assemble,
  inventory, optimize, update-coverage, ledger, the uploader and the batch
  driver; 76 index lines unchanged, 8799 bytes.
- state-machine.md: `site.captureGaps`. journal-format.md: in a stardust
  phase the one-line summary starts with the phase name; ledger.mjs accepts
  either heading shape.
- replica/SKILL.md: the overflow assert beside the pass bar; Phase 5 names
  media-reconcile as required once the delivered content file exists
  (+381 bytes).
- CHANGELOG.md: `## Unreleased` above 0.25.0 — intro plus three groups
  (gates that passed a broken site; index, sitemap, search; bookkeeping,
  capture, tooling), the migrate-units item and the deploy-batch
  --token-file / inventory keep items included.

lint:stardust clean (harness-neutral 100 files, script-paths 304 references,
scripts-index 76/76, script-help 58 ok).

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

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 11080 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 7909 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 48206 tokens (recommended maximum: 5000). Consider moving detailed content to separate reference files.
⚠ Skill 'rollout': SKILL.md is approximately 10165 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 169206b.

This branch is waiting to be deployed

1 waiting deployment
eval 888fec5a Waiting Sep 23, 2026 by karlpauls via tessl-eval #867
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.

1 participant