release: anc.dev routing-drift fix + post-#73 promotion#85
Merged
Conversation
## Summary Adds `send_metrics: false` to `wrangler.jsonc`. Project-scoped opt-out of Cloudflare Wrangler's CLI telemetry. Belt-and-suspenders with two existing layers: - **User shell env var** `WRANGLER_SEND_METRICS=false` (set in dotfiles at `config/shell/telemetry.sh:40`). - **Per-machine config** via `wrangler telemetry disable` (writes to `~/.config/.wrangler` or equivalent). The project-scoped layer travels with the repo, so CI runs and any contributor's local wrangler invocations stay opted out regardless of their shell environment or per-machine config. ## Changelog ### Changed - `wrangler.jsonc`: add `send_metrics: false` to opt the project out of Wrangler CLI telemetry. ## Type of Change - [x] `chore`: Maintenance tasks (dependencies, config, etc.) ## Related Issues/Stories - Story: Telemetry opt-out hardening (project-scoped layer to back up the env-var and per-machine settings). - Issue: n/a - Architecture: n/a - Related PRs: n/a ## Testing - [x] Manual testing completed - [x] All tests passing **Test Summary:** - Pre-push hook: 200/200 unit + regression tests pass - `bun x wrangler deploy --dry-run --env staging` succeeds with the new field present - Wrangler reads `send_metrics` from the project config (Cloudflare docs: same field name in both `.toml` and `.jsonc` formats) ## Files Modified **Modified:** - `wrangler.jsonc`: add `send_metrics: false` with a comment explaining the three-layer opt-out (env var, machine config, project config). **Created:** None. **Renamed:** None. **Deleted:** None. ## Key Features n/a (config field addition). ## Benefits - CI runners (GitHub Actions) inherit the opt-out without needing a workflow-level `WRANGLER_SEND_METRICS` env addition. - New contributors' local wrangler runs stay opted out without touching their shell config. - Three independent layers means any single mechanism breaking (env not exported, machine config wiped, etc.) still leaves the project opted out. ## Breaking Changes - [x] No breaking changes. ## Deployment Notes - [x] No special deployment steps required. The field affects CLI telemetry only, not Worker runtime behavior. Production behavior on anc.dev is unchanged. ## Checklist - [x] Code follows project conventions and style guidelines - [x] Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) - [x] Self-review of code completed - [x] Tests added/updated and passing - [x] No new warnings or errors introduced - [x] Changes are backward compatible ## Additional Context User-level dotfile already has `WRANGLER_SEND_METRICS=false` at `~/dotfiles/config/shell/telemetry.sh:40`. `wrangler telemetry status` confirmed "Status: Disabled (set by environment variable)". This PR adds the project-scoped third layer.
#75) ## Summary Silences the lone biome warning surfaced by `bun run lint`. The test name in `tests/build.test.ts:312` used `${SITE_SPEC_VERSION}` inside a single-quoted string, which biome's `noTemplateCurlyInString` rule flags as a likely typo (developer probably meant a template literal). The actual assertion on line 320 already uses real template-string interpolation; only the human-readable test name needed the fix. Switched the placeholder notation to angle brackets (`v<SITE_SPEC_VERSION>`) so the description still reads as "renders the version with a v prefix" without tripping the lint. ## Type of Change - [x] `chore`: Maintenance tasks (dependencies, config, etc.) ## Related Issues/Stories - Story: n/a - Issue: n/a - Architecture: n/a - Related PRs: n/a ## Testing - [x] Manual testing completed - [x] All tests passing **Test Summary:** - `bun run lint`: 0 errors, 0 warnings (was: 0 errors, 1 warning) - `bun test tests/build.test.ts`: 128 of 128 pass ## Files Modified **Modified:** - `tests/build.test.ts` (one-line change: rename test description placeholder from `${SITE_SPEC_VERSION}` to `<SITE_SPEC_VERSION>`) **Created:** None. **Renamed:** None. **Deleted:** None. ## Breaking Changes - [x] No breaking changes ## Deployment Notes - [x] No special deployment steps required ## Checklist - [x] Code follows project conventions and style guidelines - [x] Commit messages follow Conventional Commits - [x] Self-review of code completed - [x] Tests added/updated and passing - [x] No new warnings or errors introduced - [x] Changes are backward compatible
## Summary Closes the parity gap between `scripts/hooks/pre-push` and `.github/workflows/ci.yml`. CI's gate is install, lint, build, test, `wrangler deploy --dry-run`. The local hook only ran the first three. The header comment justified the omission with "requires worker secrets and is already enforced in CI", but `--dry-run` needs no secrets (CI runs it without any wrangler credentials), so the claim was stale. Net effect: pushes that would fail wrangler config or bundle validation now fail locally instead of round-tripping through CI. ## Type of Change - [x] `feat`: New feature (non-breaking change which adds functionality) ## Related Issues/Stories - Story: n/a - Issue: n/a - Architecture: n/a - Related PRs: n/a ## Testing - [x] Manual testing completed - [x] All tests passing **Test Summary:** Verified end-to-end on the current dev tip by piping a synthetic non-delete ref to the hook. All four steps pass: - `bun run lint`: 0 errors - `bun run build`: 111 pages, 97 scorecards, 96 badges, no orphan warnings - `bun test`: 200 of 200 pass - `bun x wrangler deploy --dry-run`: 237 KiB upload (gzip 28 KiB), `env.ASSETS` binding resolved Wall time roughly 10 seconds. ## Files Modified **Modified:** - `scripts/hooks/pre-push` (add wrangler dry-run as step 4; update header comment to reflect new step list and remove the stale "requires worker secrets" justification) - `README.md` (describe the new step in the contributor onboarding block) **Created:** None. **Renamed:** None. **Deleted:** None. ## Key Features - Local pre-push gate now mirrors the CI workflow exactly (lint, build, test, wrangler dry-run). ## Benefits - Catches wrangler config or bundle errors locally before push, saving the round-trip of pushing, watching CI fail, and force-pushing a fix. - Aligns with the project's local-CI-parity rule: green local push almost always means a green CI run. ## Breaking Changes - [x] No breaking changes ## Deployment Notes - [x] No special deployment steps required. Contributors already on the hook (those who ran the one-time `git config core.hooksPath scripts/hooks` per the README) pick up the new step automatically on next push. New clones still need the same one-time setup. ## Checklist - [x] Code follows project conventions and style guidelines - [x] Commit messages follow Conventional Commits - [x] Self-review of code completed - [x] Tests added/updated and passing - [x] No new warnings or errors introduced - [x] Changes are backward compatible
…77) ## Summary Pre-Implementation Validation gate for the live-scoring v3 plan ([docs/plans/2026-04-28-002](docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md), lines 795-832). Measures whether the install-binary-only constraint (R8) bounces out the bulk of HN-typical traffic before U2 starts on the sandbox image. Result: 76.0% tight hit rate against 50 trending CLI repos. Gate decision: `pass-ship-as-written`. U2 unblocked. The companion plan-amend commit on `dev` (08a9a24) folds this gate's two findings (F1, F4) back into the U1, U4, U6, U8 specs so reviewers can see the plan reflecting reality. ## Changelog ### Added - ### Changed - ### Fixed - ### Documentation - ## Type of Change - [x] `feat`: New feature (non-breaking change which adds functionality) - [ ] `fix` - [ ] `refactor` - [ ] `perf` - [ ] `docs` - [ ] `test` - [ ] `chore` - [ ] `ci` - [ ] `style` - [ ] `build` - [ ] `BREAKING CHANGE` ## Related Issues/Stories - Story: n/a - Issue: n/a - Architecture: docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md (Pre-Implementation Validation gate, lines 795-832) - Related PRs: dev commit 08a9a24 (plan amend with F1+F4+musl satisfied+gate passed) ## Testing - [x] Unit tests added/updated - [ ] Integration tests added/updated - [x] Manual testing completed - [x] All tests passing **Test Summary:** - Unit tests: 200 passing (no test surface for the new files; script is local-only, report is data) - Integration tests: n/a for this PR - Coverage: gate report itself IS the integration evidence, against 50 real-world repos The script ran end-to-end against live GitHub + brew + crates.io + npm + pypi + go.proxy endpoints. Aggregate stats in the report match the per-repo classification table. ## Files Modified **Modified:** - None **Created:** - `scripts/measure-discovery-hit-rate.mjs` (local-only, not deployed; biome-ignored under `scripts/`) - `docs/research/2026-05-04-discovery-chain-hit-rate.md` (gate write-up; markdownlint-excluded under `docs/research/`) **Renamed:** - None **Deleted:** - None ## Key Features - **Paper version of U4's 4-step discovery chain.** Steps 2-4 only (step 1 needs a binary-asset URL paste, not relevant here). Plus registry-fast-path (step 0) and per-language quotas (Rust/Python/Go/JS, ~12-13 each). - **Dual classification per repo: tight + loose.** - **Tight (production-realistic, gate decision):** step-3 also requires repository-field match against the input GitHub repo, plus where available a binary-target check (crates.io `bin_names`, npm `bin`, pypi `bdist_wheel`, brew `homepage`). Result: 76.0% hit rate. - **Loose (U4-spec-as-written):** original predicates (just 200 + has-bin/wheel). Result: 92.0% hit rate, but inflated by cross-registry name collisions (`cobra` on crates.io is an unrelated Python-Haskell joke crate). - **Five findings, two of which land back in the plan amend (F1 = U1+U4 spec change; F4 = U6+U8 spec change).** F1 is the load-bearing one: without per-registry repo-match, U4 produces wrong-answer failures, not just missed-opportunity bounces. ## Benefits - Gate evidence committed before U2 starts, per the plan's verification requirement. - Shipped as a reproducer (`bun scripts/measure-discovery-hit-rate.mjs`), not a one-off measurement, so the gate can be re-run on a different sample window or different language mix. - Surfaces a real wrong-answer risk in U4 as written; absorbing F1 prevents a class of correctness bugs at U4 implementation time. ## Breaking Changes - [x] No breaking changes - [ ] Breaking changes described below ## Deployment Notes - [x] No special deployment steps required - [ ] Deployment steps documented below The script writes `.context/discovery-hit-rate-results.json` (gitignored) on each run. No site or worker changes. ## Screenshots/Recordings n/a (no UI surface). ## Checklist - [x] Code follows project conventions and style guidelines - [x] Commit messages follow Conventional Commits - [x] Self-review of code completed - [x] Tests added/updated and passing (full suite green: 200/200) - [x] No new warnings or errors introduced - [x] Changes are backward compatible (additive only) ## Additional Context The 16 percentage-point gap between loose (92%) and tight (76%) is the size of the U4 spec gap that needs closing at U4 implementation time. The plan-amend commit on `dev` (08a9a24) makes the F1 tightening a HARD requirement on U4 and introduces a new step 0.5 (`discovery-hints.yaml` lookup) to absorb known false-negatives where ecosystem metadata is incomplete (Aider, OpenHands, Sherlock).
…ild (U1) (#78) ## Summary Plan U1 ([docs/plans/2026-04-28-002, lines 856-907](docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md)). Two precomputed indexes emit at build time so the Worker (U4 + U5 + U6) can do O(1) lookups without parsing `registry.yaml` at request time. Pure build-step change, no Cloudflare bindings touched. The discovery-hints surface absorbs known false-negatives surfaced by the Pre-Implementation Validation gate (finding F1 in [docs/research/2026-05-04-discovery-chain-hit-rate.md](docs/research/2026-05-04-discovery-chain-hit-rate.md)) — three trending CLIs (Aider, OpenHands, Sherlock) that real users will paste but whose ecosystem registry metadata is incomplete enough to bounce U4's tightened step 3. ## Changelog ### Added - Build emits `dist/registry-index.json` (dual-keyed slug + owner/repo lookup) and `dist/discovery-hints-index.json` (owner/repo install-spec hints) so the live-scoring path can resolve inputs without re-parsing `registry.yaml`. ### Changed - ### Fixed - ### Documentation - ## Type of Change - [x] `feat` - [ ] `fix` - [ ] `refactor` - [ ] `perf` - [ ] `docs` - [ ] `test` - [ ] `chore` - [ ] `ci` - [ ] `style` - [ ] `build` - [ ] `BREAKING CHANGE` ## Related Issues/Stories - Story: n/a - Issue: n/a - Architecture: docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md (U1, lines 856-907) - Related PRs: #77 (Pre-Implementation Validation gate; surfaced F1 which seeded the hints file) ## Testing - [x] Unit tests added/updated - [x] Integration tests added/updated - [x] Manual testing completed - [x] All tests passing **Test Summary:** - Unit tests: 224 passing (up from 200; new tests/registry-index.test.ts + regression #7) - Integration tests: regression #7 asserts both dist/*.json files emit with the documented shapes from the actual `bun run build` artifact - Coverage: every documented behavior in the plan U1 spec has a test (happy path, url-only fallback, missing-identifier warning, owner/repo collision warning, hint shape validation, hint+registry collision drop, KNOWN_PM contract) ## Files Modified **Modified:** - `src/build/build.mjs` (13 lines: import, path constant, orchestrator call after `loadRegistry`) - `tests/regression.test.ts` (35 lines: regression #7 block with 3 dist-artifact assertions) **Created:** - `src/build/registry-index.mjs` (121 lines: pure-data emit module) - `discovery-hints.yaml` (45 lines: 3 seed hints with rationale notes) - `tests/registry-index.test.ts` (162 lines: 22 unit tests) **Renamed:** - None **Deleted:** - None ## Key Features - **Two indexes from one build pass.** `emitBuildIndexes` is the single side-effecting orchestrator; pure functions (`buildRegistryIndex`, `buildDiscoveryHintsIndex`, `deriveOwnerRepo`) are individually testable. - **Drift guard between data sources.** Hints colliding with `registry.yaml` entries are dropped at build time (registry wins, committed scorecards beat opinionated hints). Regression #7 enforces zero overlap as an invariant on every PR. - **Typo guard on hint pm field.** `KNOWN_PM` mirrors U4's parse-install table; an unknown value (e.g. `yum`) fails the build immediately rather than landing as a runtime bounce. - **Hint format encodes intent for future maintainers.** Each hint carries a `note:` explaining why ecosystem metadata fell short, so the hint can be dropped when upstream improves. ## Benefits - Unblocks U4 (input parser + discovery chain), U5 (Worker route), and U6 (sandbox install + score) — they can all import and consume the indexes without touching `registry.yaml` or `discovery-hints.yaml` directly. - Lifts the discovery chain's expected production hit rate from the gate's tight 76.0% closer to 82-85% by absorbing the 3 known false-negatives (no need to wait for upstream pypi/brew metadata fixes from Aider/OpenHands/Sherlock). - Surfaces registry collisions explicitly (the `cf`/`wrangler` alias warning is now visible in build output). ## Breaking Changes - [x] No breaking changes - [ ] Breaking changes described below ## Deployment Notes - [x] No special deployment steps required - [ ] Deployment steps documented below The new dist artifacts are static assets; they will ship via the existing `env.ASSETS` binding once U3 + U5 land. No Worker, DO, R2, or container changes here. ## Screenshots/Recordings n/a (no UI surface). ## Checklist - [x] Code follows project conventions and style guidelines (biome formatted, markdownlint clean) - [x] Commit messages follow Conventional Commits - [x] Self-review of code completed - [x] Tests added/updated and passing (224/224) - [x] No new warnings or errors introduced - [x] Changes are backward compatible (additive only — no existing artifact shape changed) ## Additional Context The cf/wrangler alias case is documented as expected behavior — both `name: wrangler` and `name: cf` in `registry.yaml` deliberately point at `cloudflare/workers-sdk`. The collision warning is real but harmless: `by_slug` keeps both entries; `by_owner_repo` resolves to whichever loaded last. For live-scoring's purpose (resolve a pasted GitHub URL to SOME committed scorecard) either choice is correct.
#79) ## Summary Plan U2 ([docs/plans/2026-04-28-002, lines 945-988](docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md)). Live-scoring sandbox image: Alpine 3.21 + Cloudflare Sandbox SDK 0.9.2-musl + cargo-binstall + pip + npm + go runtime, with the agentnative-cli v0.3.1 musl bottle baked in at `/usr/local/bin/anc`. NO COMPILERS, NO TOOLCHAINS — install paths are precompiled-only per Premise #2 of the 2026-04-17 CEO design. This unblocks U3 (`wrangler.jsonc` Containers binding by digest) and U6 (sandbox install + score). It also produces the artifact the Pre-Implementation Validation gate (#77) was empirically validating against. ## Changelog ### Added - New `docker/sandbox/` image for the live-scoring path (Alpine + musl, no toolchains, anc v0.3.1 musl baked in). ### Changed - ### Fixed - ### Documentation - `docker/sandbox/README.md` documents build, smoke-test, and SHA-pin bump procedures for the four pinned external assets. ## Type of Change - [x] `feat` - [ ] `fix` - [ ] `refactor` - [ ] `perf` - [ ] `docs` - [ ] `test` - [ ] `chore` - [ ] `ci` - [ ] `style` - [ ] `build` - [ ] `BREAKING CHANGE` ## Related Issues/Stories - Story: n/a - Issue: n/a - Architecture: docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md (U2, lines 945-988) - Related PRs: #77 (gate measurement), #78 (U1 build indexes) ## Testing - [x] Unit tests added/updated - [x] Integration tests added/updated - [x] Manual testing completed - [x] All tests passing **Test Summary:** - Unit tests: 236 passing (up from 224; new tests/dockerfile-sandbox.test.ts adds 12) - Integration tests: not in CI (no docker daemon); manual smoke commands documented in `docker/sandbox/README.md` - Coverage: SHA-pin discipline, no-toolchains invariant, pm coverage, brew-absence rationale, sandbox runtime wiring (COPY, ENTRYPOINT, PATH) The image-size budget check (≤350 MB compressed) lives in the README as a manual step rather than a test, since CI doesn't have docker. Static text assertions guard the parts that can be verified without a daemon. ## Files Modified **Modified:** - None **Created:** - `docker/sandbox/Dockerfile` (multi-stage Alpine + musl image; 4 SHA-pinned external assets) - `docker/sandbox/README.md` (build, smoke-test, push-by-digest, SHA-pin bump, omissions) - `tests/dockerfile-sandbox.test.ts` (12 static-shape tests) **Renamed:** - None **Deleted:** - None ## Key Features - **Multi-stage Alpine 3.21 + musl.** Stage 1 pulls cloudflare/sandbox:0.9.2-musl@sha256... and stage 2 copies just `/container-server/sandbox` + `libstdc++.so.6` into a fresh alpine:3.21@sha256... base, keeping the final image lean. - **Four SHA-pinned external assets.** cloudflare/sandbox digest, alpine digest, cargo-binstall v1.19.0 musl tarball sha256, and the agentnative v0.3.1 musl tarball sha256. Each verifies via `sha256sum -c` after download. - **No-toolchains invariant.** apk add includes runtimes only (bash, python3, py3-pip, nodejs, npm, go, ca-certificates, curl, git). Static test rejects rust, rustup, cargo, build-base, gcc, g++, clang, make. - **brew intentionally absent.** Linuxbrew on Alpine + musl isn't supported (linuxbrew assumes glibc symbols). Inputs that resolve to `pm: brew` via U4's chain hit U6's `chain_resolved_install_failed` bounce class. Trade-off documented in-Dockerfile so a future maintainer doesn't silently re-add brew without revisiting U8's CTA copy. ## Benefits - Validates end-to-end that the upstream musl release (v0.3.1) drops cleanly into Alpine: the Dockerfile's `anc --version` step would fail the build if linkage were broken. - Lifts the gate's tight 76% paper hit rate to a real install ceiling once U6 lands. - Sets the SHA-pin discipline pattern U3 will inherit (pin Containers `image:` to digest, never tag). ## Breaking Changes - [x] No breaking changes - [ ] Breaking changes described below ## Deployment Notes - [x] No special deployment steps required - [ ] Deployment steps documented below The image is not yet referenced by any deployed surface — U3 wires the wrangler.jsonc Containers binding by digest in a follow-up PR. Until U3 + U6 land, this image is a build artifact only. ## Screenshots/Recordings n/a (no UI surface). ## Checklist - [x] Code follows project conventions and style guidelines (biome, markdownlint clean) - [x] Commit messages follow Conventional Commits - [x] Self-review of code completed - [x] Tests added/updated and passing (236/236) - [x] No new warnings or errors introduced - [x] Changes are backward compatible (additive only) ## Additional Context The plan's verification gate for this unit was "Image builds, fits `basic` instance type, runs `anc --version` via the Sandbox SDK." All three are documented as manual steps in `docker/sandbox/README.md` rather than CI tests, because CI runs `bun test` (no docker). The static shape assertions are the part that survives without a daemon. Once a docker runner exists in CI (or via a separate workflow), the smoke-test commands in the README port directly. The four pinned digests / sha256s should be reviewed for staleness before each deploy of U6 (the consumer). The README's "SHA pinning" section names the bump procedure for each asset.
## Summary Plan U4 ([docs/plans/2026-04-28-002, lines 1061-1191](docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md)). Pure logic + injectable HTTP boundary; no Cloudflare runtime bindings touched. Test-first per the plan's Execution note for the parser tables. Consumes U1's two indexes (registry-index + discovery-hints-index) and emits the install spec shape U6 will execute. This closes Phase 1 of the plan (foundation: data + image + parser). Phase 2 (Worker DO + sandbox install + R2 cache) starts at U5 in a fresh session. ## Changelog ### Added - Input parser + 4-step GitHub URL discovery chain (registry-fast-path -> hint short-circuit -> GitHub Releases asset -> brew/crates/npm/pypi/go distribution lookup -> README install-block parse) under `src/worker/score/`. ### Changed - ### Fixed - ### Documentation - ## Type of Change - [x] `feat` - [ ] `fix` - [ ] `refactor` - [ ] `perf` - [ ] `docs` - [ ] `test` - [ ] `chore` - [ ] `ci` - [ ] `style` - [ ] `build` - [ ] `BREAKING CHANGE` ## Related Issues/Stories - Story: n/a - Issue: n/a - Architecture: docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md (U4, lines 1061-1191) - Related PRs: #77 (gate), #78 (U1 indexes), #79 (U2 image), 08a9a24 (plan amend with F1+F4) ## Testing - [x] Unit tests added/updated - [x] Integration tests added/updated - [x] Manual testing completed - [x] All tests passing **Test Summary:** - Unit tests: 315 passing (up from 236; 91 new across four test files) - Integration tests: discover-binary tests use a mocked fetcher exercising every step and every F1 tightening case (cobra-class collision, library-only crate, npm repo mismatch, pypi project_urls match, go proxy match, brew priority over crates) - Coverage: every plan-listed test scenario for U4 has a test, plus the F1-finding-specific scenarios (hint short-circuiting step 3 collision; step 3 repo-mismatch and bin-target rejects) ## Files Modified **Modified:** - None **Created:** - `src/worker/score/parse-install.ts` (122 lines: pure-function install-command parser table) - `src/worker/score/validate.ts` (75 lines: input classifier with HTTPS+github.com-only URL validation, Punycode-aware homoglyph guard) - `src/worker/score/registry-lookup.ts` (76 lines: registry+hints lookup with case-insensitive owner/repo match) - `src/worker/score/discover-binary.ts` (315 lines: 4-step live discovery chain with F1-tightened step 3 + per-registry loose/tight breadcrumbs) - `tests/score-parse-install.test.ts` (33 tests: every plan-listed install-command shape + flag handling + shell-prompt + error paths) - `tests/score-validate.test.ts` (21 tests: slug/command/url classification + URL error matrix + homoglyph) - `tests/score-registry-lookup.test.ts` (10 tests: registry-beats-hint ordering, case-insensitive lookup, miss propagation) - `tests/score-discover-binary.test.ts` (18 tests: hint short-circuit, releases asset, F1 tightening cases per registry, README parse) **Renamed:** - None **Deleted:** - None ## Key Features - **Test-first parser tables.** parse-install.ts is a pure function with a switch over package-manager prefix; the 33 tests in score-parse-install.test.ts ARE the spec for the supported shapes. - **F1 tightening lands as a HARD requirement.** Every step-3 predicate now requires a repository-field match against the input GitHub repo (case-insensitive substring); crates.io additionally requires non-empty `bin_names` on the latest version. Without this, U4 would produce wrong-answer failures (R9 violation) — the 16-percentage-point delta between loose 92% and tight 76% measured in the gate. - **Step 0.5 hints in two places (defense in depth).** registry-lookup.ts checks hints right after the registry; discover-binary.ts also checks hints at step 0.5 before any HTTP call. The orchestrator path goes through registry-lookup first, so discover-binary's hint check never fires in normal flow — but if a future caller invokes discover-binary directly without registry-lookup, the short-circuit still fires. - **Injectable fetcher.** discover-binary takes an optional `fetcher: typeof fetch` argument so tests can mock the entire HTTP surface deterministically. Default is `globalThis.fetch.bind(globalThis)`. - **Per-registry loose/tight breadcrumbs in the chain_no_resolve envelope.** When the chain exhausts, the response carries each registry's loose/tight booleans plus a tight-rejection reason (e.g. `crate_is_library_only`). Sets U6/U8 up to surface diagnostic detail in the bounce CTA. ## Benefits - Closes Phase 1 of the plan; U5/U6/U7 can build directly on this contract. - Eliminates the wrong-answer failure class surfaced by the gate (cobra/Python-Haskell crate, etc.); the chain now bounces correctly when registries disagree about which project a name refers to. - Establishes the testing pattern (mocked fetcher + canned per-URL responses) that U6's sandbox-side tests will inherit. ## Breaking Changes - [x] No breaking changes - [ ] Breaking changes described below ## Deployment Notes - [x] No special deployment steps required - [ ] Deployment steps documented below These modules aren't yet imported by `src/worker/index.ts` — that wiring lands in U5 (`/api/score` route). This PR ships the contracts that U5/U6 will consume; nothing user-visible changes in production until U5 + U6 + U7 + U8 land. ## Screenshots/Recordings n/a (no UI surface). ## Checklist - [x] Code follows project conventions and style guidelines (biome, markdownlint clean; 3 prior optional-chain warnings auto-fixed) - [x] Commit messages follow Conventional Commits - [x] Self-review of code completed - [x] Tests added/updated and passing (315/315) - [x] No new warnings or errors introduced - [x] Changes are backward compatible (additive only) ## Additional Context **Known degradation, deferred to U6:** parse-install.ts sets `binary = package` for npm and bun, where the real binary name often differs (`npm i -g typescript` -> `tsc`). The U6 `which <binary>` check will catch the mismatch and bounce as `chain_resolved_no_binary_produced` — that's the correct UX; the diagnostic copy in U8's CTA explicitly handles this case. Full resolution would require fetching `package.bin` from npm metadata at parse time, which would push parse-install.ts from pure-sync to async. Out of scope for U4's parser. **The `direct` install spec shape** (returned by step 2 — releases-asset hits) carries `{pm: 'direct', url, binary}` rather than the `{pm, package, binary}` shape U6 will see for ecosystem hits. U6's install-spec executor needs to discriminate on `pm === 'direct'` and call `curl -fsSL <url> | tar xz -C /usr/local/bin/` (per plan U6 line 1311) for that branch.
## Summary Plan U3 ([docs/plans/2026-04-28-002, lines 1026-1080](docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md)). First-ever DO + Containers + R2 + ratelimits bindings on this Worker. One-way migration (`new_sqlite_classes`) gated by the standard pre-push `wrangler dry-run` check. The U2 sandbox image was pushed to docker.io ahead of this PR so `containers[].image` pins to a real digest, not a placeholder. This unblocks U5 (Worker `/api/score` route) and U6 (sandbox install + score). Phase 1 + this binding step is the last work before the Worker code that actually consumes any of it. ## Changelog ### Added - New Cloudflare bindings on the live-scoring path: `SCORE` Durable Object, `SCORE_CACHE` R2 bucket, `SCORE_LIMITER` rate-limit (10 req/60s), and a digest-pinned `Sandbox` Containers image. Mirrored under `env.staging` with staging-distinct bucket name and rate-limit namespace. ### Changed - ### Fixed - `cargo-binstall -V` (not `--version`) in the U2 Dockerfile — cargo-binstall reserves `--version` for the package version to install, not the binary version. ### Documentation - ## Type of Change - [x] `feat` - [ ] `fix` - [ ] `refactor` - [ ] `perf` - [ ] `docs` - [ ] `test` - [ ] `chore` - [ ] `ci` - [ ] `style` - [ ] `build` - [ ] `BREAKING CHANGE` ## Related Issues/Stories - Story: n/a - Issue: n/a - Architecture: docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md (U3, lines 1026-1080) - Related PRs: #77 (gate), #78 (U1 indexes), #79 (U2 image), #80 (U4 parser) ## Testing - [x] Unit tests added/updated - [x] Integration tests added/updated - [x] Manual testing completed - [x] All tests passing **Test Summary:** - Unit tests: 315 passing (cargo-binstall regex fix preserved test count) - Integration tests: `bun x wrangler deploy --dry-run --env=""` AND `--env staging` both pass; bindings listed correctly with the digest-pinned container - Coverage: pre-push hook validates the dry-run on every push to this branch ## Files Modified **Modified:** - `docker/sandbox/Dockerfile` (1 line: `cargo-binstall --version` -> `cargo-binstall -V`) - `src/worker-configuration.d.ts` (regenerated via `bun run types`; adds `SCORE`, `SCORE_CACHE`, `SCORE_LIMITER` to `Env`) - `src/worker/index.ts` (re-exports `Sandbox` so wrangler's binding resolver finds it) - `tests/dockerfile-sandbox.test.ts` (regex updated for `-V`) - `wrangler.jsonc` (containers + durable_objects + migrations + r2_buckets + ratelimits at top-level + env.staging mirror) **Created:** - `src/worker/score/do.ts` (stub `Sandbox` DO; full impl in U6) **Renamed:** - None **Deleted:** - None ## Key Features - **Image pinned by digest, not tag.** `docker.io/brettdavies/anc-sandbox@sha256:f4cc85fa6e39ab5a7901bbf7f291228e04e36fe126f0ca994c793800b8288ad4` (v3-rc1 pushed 2026-05-04). Tags can be force-moved upstream; digests can't. - **Migration uses `new_sqlite_classes`, not legacy `new_classes`.** Per the plan's one-way-gate framing — the DO is created with SQLite-backed storage. Reverting needs a follow-up migration with `deleted_classes` (documented in RELEASES.md when U9 lands). - **env.staging mirrors all five binding sets explicitly.** Wrangler does not inherit `durable_objects` / `containers` / `migrations` / `ratelimits` / `r2_buckets` from top-level — it emits a warning when they're missing under an env. Staging distinctions: distinct R2 bucket name (`anc-score-cache-staging`), distinct rate-limit namespace (1002), explicit Worker name (`agentnative-site-staging`) needed for the containers app-name derivation. - **Sandbox DO uses legacy class-form pattern.** `extends DurableObject` from `cloudflare:workers` would break Bun's test runtime (the virtual module isn't bun-resolvable). U6 switches to `extends Sandbox` from `@cloudflare/sandbox`, which IS a real npm package and bun-resolvable. ## Benefits - Closes the binding-declaration phase of the plan; U5/U6/U7 can now reference SCORE / SCORE_CACHE / SCORE_LIMITER directly without further wrangler.jsonc changes. - Validates end-to-end that the U2 image artifact actually pulls correctly via Cloudflare's Containers binding (dry-run resolves the digest). - Surfaces the env.staging inheritance gotcha (DO + containers + migrations don't inherit) explicitly in code, with a comment, so future binding additions don't repeat the trap. ## Breaking Changes - [x] No breaking changes - [ ] Breaking changes described below The migration is additive — no existing binding semantics changed. The Worker's asset-first invariant is preserved (no new request routing wired up; that's U5). ## Deployment Notes - [ ] No special deployment steps required - [x] Deployment steps documented below: **Soft prerequisites for first deploy** (per plan U3 line 776): - R2 buckets must exist: `anc-score-cache` (prod) and `anc-score-cache-staging` (staging). Created via wrangler R2 CLI or dashboard. - CF API token scopes must include: `Workers Scripts: Write` + `Containers: Write` + `Durable Objects: Write` + `R2 Storage: Write`. - Docker Hub repo `docker.io/brettdavies/anc-sandbox` must be pullable (already is — `v3-rc1` tag + digest both reachable). These are environment-side preconditions, not blockers for the `dev` merge. The first deploy of these bindings to staging is its own milestone PR per the plan's "First v3 deploy is its own milestone PR; no other changes bundled" framing — separate from this binding-declaration PR. ## Screenshots/Recordings n/a (no UI surface). ## Checklist - [x] Code follows project conventions and style guidelines (biome + markdownlint clean) - [x] Commit messages follow Conventional Commits - [x] Self-review of code completed - [x] Tests added/updated and passing (315/315) - [x] No new warnings or errors introduced - [x] Changes are backward compatible (additive only) ## Additional Context The Bun-can't-resolve-`cloudflare:workers` finding is a real gotcha the plan didn't anticipate. The legacy class-form DO pattern is fine as a stub but unsightly long-term. U6 will replace it entirely with the Sandbox-SDK-extending form, which is bun-resolvable as a real npm dep. If you'd rather force the modern form now (with a vitest/bun mock for `cloudflare:workers`), happy to switch — but the legacy form here costs zero (it's stub code that gets replaced) and keeps the test setup simple. The `DOCKERHUB_PAT_ANC` token used for the push is in 1Password (`secrets-dev` vault). To push a future image: ```sh /home/brett/.claude/skills/1password/scripts/read_field.sh "DOCKERHUB_PAT_ANC" credential | \ /home/brett/.claude/skills/docker-engine/scripts/sg-docker.sh login -u brettdavies --password-stdin /home/brett/.claude/skills/docker-engine/scripts/sg-docker.sh build -f docker/sandbox/Dockerfile \ -t docker.io/brettdavies/anc-sandbox:v3-rcN --platform linux/amd64 . /home/brett/.claude/skills/docker-engine/scripts/sg-docker.sh push docker.io/brettdavies/anc-sandbox:v3-rcN ``` Capture the `digest:` line from the push output and update `wrangler.jsonc` `containers[].image` to the new digest.
Stand up site-side prose-check enforcement parallel to the stack that
shipped on `agentnative-spec` v0.3.1. Vale rule packs (universal `brand`
vendored from spec at v0.4.0; fresh site-channel `site` pack), the
`write-good` and `proselint` baselines, a CSS-grep deployment-layer scan
that catches font-family swaps Vale cannot see, and the orchestrator
(Vale plus LanguageTool over Tailscale) all wire into the existing
pre-push hook. Pre-push runs seven stages end-to-end with 0 prose-check
blocking on the cleaned corpus.
- New `scripts/sync-prose-tooling.sh` (parallel to `sync-spec.sh`) that
vendors the prose-check tooling from the latest `agentnative-spec` v*
tag: `BRAND.md`, `styles/brand/*.yml`, `styles/brand/README.md`,
`styles/config/vocabularies/brand/{accept,reject}.txt`,
`scripts/prose-check.sh`, `scripts/generate-pack-readme.mjs`. Separate
sync clock from `sync-spec.sh` because prose tooling and the principles
contract release on different cadences.
- New `.vale.ini` composing Vale, brand, site, write-good, proselint,
with brand-tier and site-tier rules at error severity. Two new
site-channel rules: `site.BannedFonts` (nine reflex font names) and
`site.BannedAesthetics` (seven banned visual patterns). Site vocabulary
at `styles/config/vocabularies/site/accept.txt` (213 entries).
- New `scripts/check-banned-fonts.sh` (yq-driven CSS/TS/build-script
scan that reads tokens from `styles/site/BannedFonts.yml`, single SoT
downstream of the Vale rule pack).
- New `scripts/test-prose-check.mjs` and five fixture cases under
`scripts/__fixtures__/prose-check/` (3 brand cases, 2 site cases).
Manual invocation via `bun scripts/test-prose-check.mjs`.
- Three new pre-push stages after the existing lint, build, test,
wrangler pipeline: pack-README drift check, banned-font deployment scan,
prose-check (Vale and LanguageTool).
- `RELEASES.md` prose scrubbing section now uses the local `.vale.ini`
directly rather than pointing at the spec checkout, and gains a "Scrub
before submit" framing paragraph and a new top-level "PR body" section
copied from `agentnative-cli/RELEASES.md`.
- `.impeccable.md` trimmed from 165 to 109 lines: inherits universal
voice and audience framing from the vendored `BRAND.md`; keeps only
site-channel-specific content (visual system, palette, typography stack,
code-block treatment, OG image, tech stack, JS budget).
- `scripts/SYNCS.md` documents the new sync vehicle, the prose-tooling
row in the upstream table, the new mermaid edge, the new orchestration
trigger, and the SITE-LOCAL DIVERGENCE note on `prose-check.sh`.
- `docs/plans/2026-05-07-001-feat-prose-check-site-plan.md` rescoped:
introduces U0 (sync-prose-tooling.sh authoring and initial vendor)
before the existing U1 to U4. Drops the deferred follow-up framing of
"consumer sync extension" since the new sync vehicle ships in this PR.
- [x] `feat`: New feature (non-breaking change which adds functionality)
- [ ] `fix`: Bug fix (non-breaking change which fixes an issue)
- [ ] `refactor`: Code refactoring (no functional changes)
- [ ] `perf`: Performance improvement
- [ ] `docs`: Documentation update
- [ ] `test`: Adding or updating tests
- [ ] `chore`: Maintenance tasks (dependencies, config, etc.)
- [ ] `ci`: CI/CD configuration changes
- [ ] `style`: Code style/formatting changes
- [ ] `build`: Build system changes
- [ ] `BREAKING CHANGE`: Breaking API change (requires major version
bump)
- Story: n/a
- Issue: n/a
- Architecture:
`docs/plans/2026-05-07-001-feat-prose-check-site-plan.md` (this repo).
Spec-side companion:
`agentnative-spec/docs/plans/2026-05-06-001-feat-prose-check-stack-plan.md`.
Voice-enforcement reference:
`agentnative-spec/docs/architecture/voice-enforcement.md`.
- Related PRs: agentnative-spec#22 (v0.3.1 prose refresh that originally
shipped the stack), agentnative-spec#25 (v0.4.0 contract additions, the
tag this PR vendors against).
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [x] Manual testing completed
- [x] All tests passing
**Test Summary:**
End-to-end pre-push verification on synthetic push protocol input:
```text
==> bun run lint pass
==> bun run build 7 principles, 111 pages, 97 scorecards
==> bun test 315/315 pass
==> bun x wrangler deploy --dry-run succeeded
==> Pack-README drift check clean
==> Banned-font deployment scan clean
==> prose-check (Vale + LanguageTool) 0 blocking, 1083 warning
==> pre-push checks passed
```
Fixture runner: `bun scripts/test-prose-check.mjs` reports 5/5 OK on the
brand and site cases.
`scripts/sync-prose-tooling.sh` runs cleanly against `agentnative-spec`
v0.4.0 and is idempotent (re-running produces zero `git diff` against a
fixed spec tag).
`scripts/check-banned-fonts.sh` exits 0 on the current corpus; planted
`font-family: 'Plus Jakarta Sans'` in `src/_test.css` reliably fires the
deployment-layer check (verified before commit).
**Modified:**
- `.gitignore` (vale-sync baseline pack ignore lines)
- `.impeccable.md` (trimmed, 165 to 109 lines, inherits from BRAND.md)
- `RELEASES.md` (scrub procedure update, PR body section copied from
CLI)
- `content/badge.md`, `content/methodology.md`,
`content/scorecard-schema.md` (prose drift fixes)
- `docker/sandbox/README.md` (one prose drift fix)
- `docs/DESIGN.md` (one prose drift fix; per-file Vale disable for
`site.BannedFonts` rationale-doc)
- `docs/plans/2026-05-07-001-feat-prose-check-site-plan.md` (rescoped to
introduce U0)
- `package.json` (markdownlint ignore patterns extended for vale-sync
baseline pack dirs)
- `scripts/SYNCS.md` (new sync vehicle entry; SITE-LOCAL DIVERGENCE
note)
- `scripts/hooks/pre-push` (three new stages: pack-README drift,
banned-font scan, prose-check)
- `.vale.ini` (NEW)
- `scripts/prose-check.sh`, `scripts/generate-pack-readme.mjs` (vendored
from spec)
**Created:**
- `BRAND.md` (vendored from spec at v0.4.0)
- `scripts/sync-prose-tooling.sh`
- `scripts/check-banned-fonts.sh`
- `scripts/test-prose-check.mjs`
-
`scripts/__fixtures__/prose-check/{marketing-register,hedge-words,filler-adjectives,banned-fonts,banned-aesthetics}/case.md`
- `styles/brand/{MarketingRegister,HedgeWords,FillerAdjectives}.yml`
- `styles/brand/README.md`
- `styles/site/{BannedFonts,BannedAesthetics}.yml`
- `styles/site/README.md`
- `styles/config/vocabularies/{brand,site}/{accept,reject}.txt`
**Renamed:** None.
**Deleted:** None.
- Two-tier deterministic prose check (Vale plus LanguageTool) wired into
pre-push without any new CI workflow.
- Single source of truth on banned font tokens: the YAML rule pack. Both
the prose-layer Vale rule and the deployment-layer CSS-grep read the
same file.
- Sync vehicle (`sync-prose-tooling.sh`) shares the `SPEC_REMOTE_URL`
and `SPEC_ROOT` env vars with `sync-spec.sh` so the existing operator
workflow stays familiar.
- Catches narrative drift on banned fonts (current example:
`docs/DESIGN.md` historically enumerated rejected fonts in narrative;
the rule fired and the cleanup pivoted the narrative to reference
`styles/site/README.md`).
- Catches deployment-layer drift on banned fonts that Vale
(markdown-only) cannot see.
- Brand pack vendored verbatim, including its released README. No
downstream regeneration; sync-script atomicity is the integrity
guarantee.
- [x] No breaking changes
- [ ] Breaking changes described below:
- [x] No special deployment steps required
- [ ] Deployment steps documented below:
Pre-push activation requires no contributor action beyond the existing
`git config core.hooksPath scripts/hooks` (one-time, already done by
site contributors). New tools needed locally: `vale` (brew install
vale), `jaq` (already in the dev tool list). `vale sync` runs once to
materialize the gitignored baseline packs.
n/a (no UI changes).
- [x] Code follows project conventions and style guidelines
- [x] Commit messages follow [Conventional
Commits](https://www.conventionalcommits.org/)
- [x] Self-review of code completed
- [x] Tests added/updated and passing
- [x] No new warnings or errors introduced
- [x] Changes are backward compatible (or breaking changes documented)
The vendored `scripts/prose-check.sh` carries a SITE-LOCAL DIVERGENCE
marker for two consumer-side carveouts the upstream orchestrator does
not yet support natively (nested `*/node_modules/*` matching;
`src/data/spec/` and `content/principles/` exclusions for vendored
content; `dist/` and `.claude/` build/tooling directories; three
additional LT denylist rules for site domain jargon: `IN_PRINCIPAL`,
`CONTRACT_CONTACT`, `TO_DO_HYPHEN`). Tracked upstream at
`agentnative-spec/.context/compound-engineering/todos/010-pending-p0-prose-check-consumer-exclusion-config.md`
with a Phase 1 (`--exclude PATTERN` CLI flag) and Phase 2
(`.proseignore` unified SoT consumed by the orchestrator, Vale, and
`markdownlint-cli2`).
Each `bash scripts/sync-prose-tooling.sh` run overwrites the patch; the
operator re-applies and `git diff scripts/prose-check.sh` post-sync
surfaces any regression.
Reconciles the site to spec v0.4.0 (eight principles; the new P8 covers Discoverable Through Agent Skill Bundles), migrates the channel-context filename to the canonical `PRODUCT.md` expected by the `/impeccable` skill loader, hoists `DESIGN.md` from `docs/` to the repo root for the same reason, and refreshes the vendored `BRAND.md` plus Vale stack from spec `main` HEAD. Decouples `sync-prose-tooling.sh` from spec release tags (now tracks `main` so prose-tooling iterates on its own cadence), un-vendors `scripts/prose-check.sh` (consumer-owned now; ends the regression where every sync clobbered the SITE-LOCAL DIVERGENCE block), and decouples the OG image's version footer from anc's binary so social shares show the spec version the site currently presents. - New principle **P8: Discoverable Through Agent Skill Bundles** at `/p8` and `/p8.md`, plus card on the homepage index. Cites `p8-must-bundle-install`, `p8-should-bundle-exists`, `p8-may-install-all`, `p8-may-bundle-update` from spec v0.4.0. - `## Inheritance` section in `PRODUCT.md` self-documenting the three-tier waterfall (BRAND.md universal, PRODUCT.md channel delta, DESIGN.md visual system). - `## Channel artifacts` table and "Source of truth" header arrive in `BRAND.md` from spec main. - Voice docs (`docs/research/VOICE.md`) codify Register 1a (homepage lede) vs 1b (principle page) split, with surface-specific notes for both. - `Definition-as-card coupling` documented in `content/principles/README.md`: the Definition first paragraph is also the homepage card and budgets to 2-3 sentences. - "Seven principles" sweeps to "Eight principles" across `_intro.md`, `about.md`, `check.md`, `install.md`, `methodology.md`, `scorecard-schema.md`, build comments, leaderboard hero, AI-summary prompt. - OG image decoupled from per-feature couplings on two axes: card text no longer cites the principle count (now reads "a standard for CLIs that agents can operate"), and the version footer now reads from `content/principles/VERSION` (SITE_SPEC_VERSION, what the site presents) instead of the highest `scorecards/anc-v*.json` (what anc was last compiled against). The OG and in-page footer now always agree on the version. Per-tool badge SVGs continue to use each scorecard's own `spec_version` since they describe the binary that scored each tool. - Rename `.impeccable.md` to `PRODUCT.md` (canonical filename for the `/impeccable` skill loader's `load-context.mjs`). - Hoist `docs/DESIGN.md` to `DESIGN.md` (root) so the loader finds it without `IMPECCABLE_CONTEXT_DIR=docs/`. - `scripts/sync-prose-tooling.sh` tracks spec `main` HEAD instead of latest `v*` tag. The principle contract still pins to tags via `sync-spec.sh`; tooling iterates faster than the contract and does not need release ceremony. - Re-sync `BRAND.md` and Vale stack (`scripts/generate-pack-readme.mjs`, `styles/brand/README.md`, `styles/site/README.md`, `styles/config/vocabularies/brand/accept.txt`) from spec main HEAD `1625416`. - `content/principles/p8-discoverable-skill-bundle.md` Definition compressed from 4 sentences to 2 so the homepage card matches P1-P7 weight. - `scripts/prose-check.sh` un-vendored (consumer-owned). The SITE-LOCAL DIVERGENCE block (consumer-specific path exclusions and LT denylist additions for `IN_PRINCIPAL`, `CONTRACT_CONTACT`, `TO_DO_HYPHEN`) was being clobbered on every prose-tooling sync. Long-term sidecar-config fix tracked at `agentnative-spec/.context/compound-engineering/todos/012-pending-p3-prose-check-sidecar-config-and-revendor.md`. - Vale file-pattern exemptions in `.vale.ini` updated for the renamed paths (`PRODUCT.md`, `DESIGN.md`) so the policy docs that legitimately enumerate banned fonts and aesthetics stop firing the rules they define. - Stale `.impeccable.md` references swept across `styles/site/BannedFonts.yml`, `styles/site/BannedAesthetics.yml`, `styles/site/README.md`, `scripts/check-banned-fonts.sh`. - Pre-existing biome and markdownlint blockers cleared (do.ts suppression named the wrong rule; vendored `coverage-matrix.json` flagged for format; one over-long bullet in `RELEASES.md`). - Broaden `RELEASES.md` "no explainer prose" rule from `## Summary` only to the entire PR body. Same do-not-paste guidance now covers every section. - [x] `feat`: New feature (P8 + Inheritance section + Channel artifacts table) - [x] `refactor`: Code refactoring (PRODUCT.md / DESIGN.md renames, sync-prose-tooling.sh main-tracking, prose-check.sh un-vendor, OG version-source decoupling) - [x] `docs`: Documentation update (Voice register codification, Definition-as-card coupling note, RELEASES.md prose rule) - [ ] `BREAKING CHANGE`: not breaking (anchor slugs preserved, all `/p<n>` URLs stable, internal renames only) - Story: None. - Issue: None. - Architecture: spec PR [#29](brettdavies/agentnative#29) (`release: PRODUCT.md migration + channel artifact stack docs`) merged to spec `main` 2026-05-13. - Related PRs: cli `docs/migrate-impeccable-to-product` and skill `docs/migrate-impeccable-to-product` (parallel migrations, not yet PRed). - [x] Unit tests added/updated - [ ] Integration tests added/updated - [x] Manual testing completed - [x] All tests passing **Test Summary:** - Unit tests: 315 passing (regression #1 LOCKED_SLUGS extended to include `p8-discoverable-skill-bundle`; regression #2 llms.txt bullet count assertion updated to 8 in p1..p8 order; build.test.ts OG alt-text snapshot updated to the count-decoupled string) - Pre-push hook: passes (md-wrap, markdownlint 35 files / 0 errors, check-links 48 files, validate-principles 8 files / 57 IDs, pack-README drift, prose-check 0 blocking / 1100 warning) - Coverage: not measured for site (no coverage tool wired) **Modified:** - Content: `content/_intro.md`, `content/about.md`, `content/check.md`, `content/install.md`, `content/methodology.md`, `content/scorecard-schema.md`, `content/changelog.md`, `content/principles/README.md`, `content/principles/VERSION` (and all `content/principles/p1-*.md` through `p7-*.md` reconciled to spec v0.4.0) - Build: `src/build/build.mjs`, `src/build/coverage.mjs`, `src/build/llms.mjs`, `src/build/scorecards-render.mjs`, `src/build/shell.mjs` - Voice docs: `docs/research/VOICE.md` - Scripts: `scripts/sync-prose-tooling.sh`, `scripts/SYNCS.md`, `scripts/og/og.html`, `scripts/og/generate.ts`, `scripts/check-banned-fonts.sh`, `scripts/design/generate-palette.mjs`, `scripts/prose-check.sh` - Styles: `styles/site/README.md`, `styles/site/BannedFonts.yml`, `styles/site/BannedAesthetics.yml`, `styles/brand/README.md`, `styles/config/vocabularies/brand/accept.txt`, `styles/config/vocabularies/site/accept.txt` - Vendored: `BRAND.md`, `scripts/generate-pack-readme.mjs`, `src/data/spec/` (vendored to v0.4.0), `src/data/coverage-matrix.json` - Top-level: `AGENTS.md`, `RELEASES.md`, `.vale.ini`, `biome.json` - Tests: `tests/build.test.ts`, `tests/regression.test.ts` - Worker: `src/worker/score/do.ts` - Public: `public/og-image.png` **Created:** - `content/principles/p8-discoverable-skill-bundle.md` **Renamed:** - `.impeccable.md` to `PRODUCT.md` - `docs/DESIGN.md` to `DESIGN.md` **Deleted:** - None. - [x] No breaking changes. - [ ] Breaking changes described below. Anchor slugs preserved (`#p1-non-interactive-by-default` through `#p8-discoverable-skill-bundle` are permanent). Per-principle URLs unchanged for `/p1` through `/p7`; `/p8` is new. The `.impeccable.md` to `PRODUCT.md` and `docs/DESIGN.md` to `DESIGN.md` renames are source-tree paths, not user-facing URLs. - [x] No special deployment steps required. - [ ] Deployment steps documented below. OG image regenerated as part of this PR (`public/og-image.png`). Cloudflare Worker deployment proceeds normally on merge. Social-share platforms cache OG previews aggressively; expect Twitter, Slack, etc. to keep showing the v0.3.0 card until they re-scrape `https://anc.dev/`. - [x] Code follows project conventions and style guidelines - [x] Commit messages follow Conventional Commits - [x] Self-review of code completed - [x] Tests added/updated and passing - [x] No new warnings or errors introduced - [x] Changes are backward compatible This PR completes the site-side half of the cross-repo migration coordinated with `agentnative-spec`. Parallel work on `agentnative-cli` and `agentnative-skill` is staged on `docs/migrate-impeccable-to-product` branches in those repos pending separate PRs. The un-vendoring of `scripts/prose-check.sh` is a workaround. The long-term sidecar-config fix (Option C in the cross-repo migration analysis) is tracked upstream at `agentnative-spec/.context/compound-engineering/todos/012-pending-p3-prose-check-sidecar-config-and-revendor.md` (P3). When that lands, this site can re-vendor the script and drop the CONSUMER-OWNED header. The OG version-source decoupling is symmetric in spirit to the prose-tooling decoupling: both sever a coupling that made the site lag behind a downstream binary release. Per-tool badge SVGs intentionally remain coupled to each scorecard's `spec_version` since they describe the binary that scored that specific tool.
…#84) ## Summary Migrate the live-scoring sandbox container image off the deprecated Docker Hub registry to the Cloudflare managed registry. Build is now a separate local operation (`wrangler containers build -p`) and deploy never rebuilds. The PR also revises the two-env-block pin model: instead of forcing both pins to match, the default workflow is staging-leads-prod (new images land on `env.staging` only, soak on the staging Worker, then a release PR to main promotes the top-level pin). A new CI guard enforces this discipline: every PR verifies its pinned tags exist in the CF managed registry, and main-targeting PRs additionally enforce pin equality (released state). Two prose-check fixes ride along. Before this PR: the staging container app was degraded with repeating `ImagePullError "the image registry credentials are invalid"` against the docker.io digest. After this PR's staging deploy (already executed locally before opening the PR), the container app reports 6/6 healthy on `registry.cloudflare.com/<acct>/anc-sandbox:30f61f1`. The top-level (production) pin is updated to the same tag, but production is intentionally NOT deployed in this PR. A separate release PR will handle the dev to main promotion together with the routing-drift fix (anc.dev is currently bound to the staging Worker rather than the named-production Worker; both will be reconciled in the follow-up release). Plan reference: `docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md` § U3-followup. ## Changelog ### Changed - Adopt staging-leads-prod as the default sandbox image workflow. The two `wrangler.jsonc` pins are now described as independent (staging advances during development; prod advances at release). `RELEASES.md § Sandbox image releases` documents the soak-then-promote flow and the lockstep shortcut for low-risk bumps. ### Fixed - Resolve staging container `ImagePullError` caused by Docker Hub registry deprecation. The sandbox image now lives in the Cloudflare managed registry and is pinned by `<git-sha>` tag in both env blocks. - Add CI guard that verifies every PR's pinned sandbox image tags exist in the CF managed registry. Catches the failure mode where a contributor bumps a pin but forgets `wrangler containers build -p`. The dry-run did not validate tag existence; this surfaces the issue at PR time rather than deploy time. ### Documentation - Document the local-build-once + soak-then-promote image release workflow in `RELEASES.md` and `docker/sandbox/README.md`. Includes image-retention discipline (never delete a tag that backed a shipped Worker version) and the Durable Object migration one-way wall note. ## Type of Change - [x] `fix`: Bug fix (non-breaking change which fixes an issue) ## Related Issues/Stories - Story: live-scoring plan U3-followup - Issue: None. - Architecture: `docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md` - Related PRs: None. ## Testing - [x] Manual testing completed - [x] All tests passing **Test Summary:** Four experimental-verification items from the U3-followup plan, all answered on the staging deploy: 1. `.dockerignore` semantics: the sentinel file is absent from the built image. `docker run --entrypoint sh anc-sandbox:30f61f1 -c "find / -name '.ignored-sentinel.txt'"` returns empty. 2. Multi-env image-share: partial in this PR (only staging deployed). The Cloudflare registry shows one `anc-sandbox` tag (`30f61f1`); the prod side of this check completes in the follow-up release PR when the named-prod Worker also pins the same tag. 3. Recorded image reference shape per Worker version: the image reference does NOT live on the Worker version (binding types observed: `assets`, `durable_object_namespace`, `r2_bucket`, `ratelimit`). It lives on the container application's `configuration.image` field in `name:tag` form (`anc-sandbox:30f61f1`). The container application is at version `v2`; `v1` was the docker.io URI. Rollback semantics: Worker code rollback is independent from container image rollback; image rollback is via the container application version, not `wrangler rollback`. 4. No-op deploy fast path: a second consecutive `wrangler deploy --env staging` with no changes reported `no changes agentnative-site-staging-sandbox-staging` and `No changes to be made`. The container application was not re-uploaded. Live state observed after the staging deploy: - Container application `a0309fd2-9622-4dd8-a6a8-faf95292f08e`: state `ready`, 6 healthy instances, 0 failed, configured image `registry.cloudflare.com/<acct>/anc-sandbox:30f61f1`. - `anc.dev` returns 200 (no `x-robots-tag`, served via the custom domain). - `agentnative-site-staging.brettdavies.workers.dev` returns 200 with `x-robots-tag: noindex` (the staging-host guard correctly distinguishes the two hosts on the same Worker). CI guard verified end-to-end on the PR's own commits: - Output: `verified: top-level pin anc-sandbox:30f61f1 is in the CF managed registry` and `verified: env.staging pin equals top-level pin (lockstep)`. Step duration ~4s. - Pin equality is enforced only on main-targeting PRs; this PR (dev-targeting) would accept divergent pins. Pins are equal in this PR because the migration sets both env blocks to the same starting tag. Pre-push `wrangler --dry-run` passes for both env blocks; `prose-check` reports 0 blocking after the `LT.PLURAL_MODIFIER` denylist addition. ## Files Modified **Modified:** - `wrangler.jsonc`: pin both env blocks at `registry.cloudflare.com/<acct>/anc-sandbox:30f61f1`, replacing the Docker Hub digest pin. Comments rewritten to describe each pin's role (top-level advances at release; staging advances during development) and the soak-then-promote workflow. - `docker/sandbox/README.md`: replace the `docker push` recipe with `wrangler containers build -p` instructions. Defer workflow choice to `RELEASES.md`. Add sections for image-retention discipline, build-context exclusions, and the GHA fallback path. - `RELEASES.md`: new subsection "Sandbox image releases (live-scoring)" under `## Deploy`. Documents the default soak-then-promote flow, the lockstep shortcut, the CI invariants enforced per PR target, image-retention discipline, the DO migration one-way wall, and the GHA fallback caveat. - `scripts/prose-check.sh`: add `LT.PLURAL_MODIFIER` to the site-local denylist for Cloudflare CLI subcommand chains (`wrangler containers images list`, `kv namespaces list`, `r2 buckets list`, `hyperdrive configs list`). - `.github/workflows/ci.yml`: add the pinned-image existence guard step to the `gate` job. Independent registry-existence check per env block on every PR; pin-equality check fires only when `base_ref == main`. Gated to same-repo PRs since forks do not have access to the CF API secrets. **Created:** - `docker/sandbox/.dockerignore`: forward-looking regression scaffold. Excludes the sentinel + standard editor/OS junk patterns. - `docker/sandbox/.ignored-sentinel.txt`: regression probe. Must always stay excluded from the build context; if it appears in a deployed image layer, `.dockerignore` is no longer being read by the builder. **Renamed:** None. **Deleted:** None.
docs/research/ should be guarded from main like docs/plans/ et al. The note doesn't affect production runtime (build doesn't ingest docs/), and the release pipeline excluding it matches the spirit of guard-main-docs. Follow-up: add docs/research/ to the brettdavies/.github reusable guard-main-docs workflow.
brettdavies
added a commit
that referenced
this pull request
May 15, 2026
Release PR #85 (merge SHA e79b7ce, 2026-05-15) shipped the routing-drift fix + named-prod promotion + production-side U3-followup deploy. anc.dev now serves from agentnative-site (named-prod); DO migration v1 applied to the named-prod Worker on this deploy. Surprise finding during execution: the staging binding had already cleared between the prior session's audit and the start of the release session, so the planned mid-merge detach was a no-op; CF derives custom-domain record ids deterministically from (account, zone, hostname), so the new prod binding reused the same id as the prior staging binding. Plan updates: - Discovered post-U3 shipment: routing drift OPEN to RESOLVED with PR #85 / merge SHA / verification summary. - U3 entry: Production-Worker side now shipped (was pending). - U3-followup entry: Production-Worker container app shipped (was pending). - Pending list: drop item 1 (routing-drift); U5-U9 renumbered to 1-5. - Branch baseline: post-#85 state; next-session work begins with U5. - U3 unit body and U3-followup spec body: stale 'pending' lines updated to reference PR #85. Follow-ups (out of scope for this plan): - Orphan DO namespace on staging (a4fb92ed020241cb802c1d5176a39608) needs quarterly cleanup. - Add docs/research/ to the brettdavies/.github reusable guard-main-docs.yml so it blocks from main like docs/plans/.
3 tasks
brettdavies
added a commit
that referenced
this pull request
May 15, 2026
## Summary Address the wrangler 4.x "Multiple environments are defined ... but no target environment was specified" warning that appears in production-deploy logs and in every pre-push run. The fix is to pass `--env=""` explicitly on production (which means "use the top-level config") and to mirror the dual-env pattern in the pre-push hook so both production and staging configurations are validated on every push. No behavior change. Wrangler was already using the top-level config on bare `wrangler deploy`; this just makes the intent explicit and removes the cosmetic warning. Surfaced during the post-deploy log review for release PR #85. ## Changelog ## Type of Change - [x] `chore`: Maintenance tasks (dependencies, config, etc.) ## Related Issues/Stories - Story: Cleanup item from the post-deploy log audit of release PR #85. - Issue: None. - Architecture: None. - Related PRs: #85. ## Files Modified **Modified:** - `.github/workflows/deploy.yml`: production-step `command:` now reads `deploy --env=""` with a brief inline comment explaining why. - `scripts/hooks/pre-push`: replaces the single bare `wrangler deploy --dry-run` with two dry-runs, one per environment (`--env=""` and `--env staging`). The hook now catches binding mistakes in either environment before push instead of only validating the top-level config. **Created:** - None. **Renamed:** - None. **Deleted:** - None. ## Testing - [x] Manual testing completed - [x] All tests passing **Test Summary:** - Local `bun x wrangler deploy --dry-run --env=""`: clean, binding list matches the production-side `anc-score-cache` R2 bucket. - Local `bun x wrangler deploy --dry-run --env staging`: clean, binding list matches the staging-side `anc-score-cache-staging` R2 bucket. - Pre-push hook fired both dry-runs in succession when pushing this branch and passed. - The next production deploy (via a release PR to `main` later) will exercise the `deploy.yml` change; the warning should be absent from the run log.
3 tasks
brettdavies
added a commit
that referenced
this pull request
May 15, 2026
…r npm URL (#87) ## Summary Correct the `cf` entry in `registry.yaml`. The previous entry claimed `repo: cloudflare/workers-sdk`, but `cf` is not in that repo. I enumerated all 29 packages in `cloudflare/workers-sdk` and none has `bin: cf`. The npm `cf` package itself (currently v0.0.5) ships pre-bundled and declares no `repository`, `homepage`, `bugs`, or `author` fields. Cloudflare's Technical Preview is published to npm without disclosing the source repository. The wrong `repo:` had two downstream effects: 1. The build's registry-index emitted a deterministic but incorrect mapping from `cloudflare/workers-sdk` to `cf` (overwriting wrangler in YAML order). A user pasting `github.com/cloudflare/workers-sdk` into the future live-scoring form would land on `/score/cf` instead of `/score/wrangler`. The build's "duplicate owner/repo" warning was a symptom of this. 2. The `/score/cf` page linked to the wrong upstream repository. Replace `repo:` with `url: https://www.npmjs.com/package/cf` (the canonical distribution surface for `cf` today) and add an inline comment explaining why there is no GitHub repo. The registry schema treats `url:` as the fallback when `repo:` is absent, so the scorecard page still renders. Surfaced during the post-deploy log review for release PR #85. ## Changelog ### Fixed - `/score/cf` and the registry-index now reflect `cf`'s actual distribution surface: no GitHub source repo is publicly declared, so the project link is the npm package page. The reverse-lookup map for `cloudflare/workers-sdk` now resolves correctly to `wrangler`. ## Type of Change - [x] `fix`: Bug fix (non-breaking change which fixes an issue) ## Related Issues/Stories - Story: Cleanup item from the post-deploy log audit of release PR #85. - Issue: None. - Architecture: None. - Related PRs: #85, #86. ## Files Modified **Modified:** - `registry.yaml`: `cf` entry replaces `repo: cloudflare/workers-sdk` with `url: https://www.npmjs.com/package/cf` and adds an inline comment explaining the rationale. **Created:** - None. **Renamed:** - None. **Deleted:** - None. ## Testing - [x] Manual testing completed - [x] All tests passing **Test Summary:** - `bun run build`: clean output. The previous "duplicate owner/repo cloudflare/workers-sdk" warning is gone. `cf` now joins `make` and `nvidia-smi` in the "no parseable owner/repo, owner/repo entry skipped" bucket (legitimate; these tools have no canonical source repository). - Build stats unchanged: 8 principles, 112 HTML pages, 112 MD pages, 97 scorecard pages, 96 badges. - `dist/score/cf.html` and `dist/score/cf.md` still emit. The fallback from `repo:` to `url:` does not break scorecard rendering. - Pre-push hook passed both wrangler dry-runs (production and staging environments). **Evidence for the upstream claim:** - Enumerated all 29 packages under `cloudflare/workers-sdk/packages/`: `chrome-devtools-patches`, `cli`, `codemod`, `containers-shared`, `create-cloudflare`, `devprod-status-bot`, `edge-preview-authenticated-proxy`, `format-errors`, `kv-asset-handler`, `lint-config-shared`, `local-explorer-ui`, `miniflare`, `mock-npm-registry`, `pages-shared`, `playground-preview-worker`, `quick-edit-extension`, `quick-edit`, `solarflare-theme`, `turbo-r2-archive`, `unenv-preset`, `vite-plugin-cloudflare`, `vitest-pool-workers`, `workers-editor-shared`, `workers-playground`, `workers-shared`, `workers-tsconfig`, `workers-utils`, `workflows-shared`, `wrangler`. None has `bin: cf` in package.json. The package named `cli` is `@cloudflare/cli-shared-helpers` (internal helpers, no executable). - npm registry metadata for `cf`: `repository: null`, `homepage: null`, `bugs: null`, `author: null`. Tarball contents are entirely pre-bundled JavaScript under `cf-dist/` (bundler output, no source files).
3 tasks
brettdavies
added a commit
that referenced
this pull request
May 15, 2026
…prod assets (#88) ## Summary Split the wrangler configuration into two fully independent files (`wrangler.jsonc` for production, `wrangler.staging.jsonc` for staging) and force a single-character byte divergence between the two compiled Worker scripts. Workaround for [cloudflare/workers-sdk#13925](cloudflare/workers-sdk#13925). The bug we hit: in a single-config multi-env shape (one wrangler.jsonc with a top-level block plus `env.staging`), staging deploys silently overwrite what production serves for any URL path whose asset content differs between the two builds. Cloudflare's asset deduplication appears to key on the compiled Worker script's etag; the multi-env shape produces byte-identical scripts (the bindings differ but they're not part of the script bytes), so the two Workers share an asset namespace. This was caught during a post-release log audit on PR #85. anc.dev was observed serving the post-PR-#87 content for `/registry-index.json`, `/score/cf.html`, and `/score/cf.md`, despite production having no new deploy since PR #85's merge. Production-deployed assets had been silently overlaid by the subsequent staging deploy. Full reproduction with version IDs, etags, and content hashes is in the upstream issue. ## Changelog ### Changed - Deploy configuration now uses two independent wrangler config files (`wrangler.jsonc` for production on anc.dev, `wrangler.staging.jsonc` for staging on the workers.dev subdomain). The previous single-file multi-env shape (top-level plus `env.staging`) is gone. Adding new bindings or changing observability now requires editing both files; the trade-off is real asset isolation between environments. - Every response now carries an `X-Build-Env` header set to `production`, `staging`, or `development` (the development value surfaces only when the Worker code is exercised by the bun test runner). The header is diagnostic and also the mechanism that keeps the two compiled Worker scripts at distinct etags: each config's `define` substitutes a different literal string into `src/worker/index.ts`'s `__BUILD_ENV__` reference. ### Fixed - Staging deploys no longer overwrite production-served asset content. The two compiled Worker scripts now have distinct etags by construction, which keeps their asset namespaces isolated even on Cloudflare's current shared-namespace implementation. ### Documentation - `RELEASES.md` deploy section documents the two-config layout and links to [cloudflare/workers-sdk#13925](cloudflare/workers-sdk#13925). - `RELEASES.md` sandbox image release procedure points at `wrangler.jsonc` and `wrangler.staging.jsonc` instead of the old `env.staging` block. - `docker/sandbox/README.md` updated to match. ## Type of Change - [x] `fix`: Bug fix (non-breaking change which fixes an issue) ## Related Issues/Stories - Story: Surfaced during a post-deploy log audit of release PR #85 plus the cf-vs-wrangler registry-data investigation in PR #87. The first staging deploy after PR #87 silently replaced anc.dev's `/registry-index.json` content. - Issue: [cloudflare/workers-sdk#13925](cloudflare/workers-sdk#13925) - Architecture: None (workaround pattern; awaiting upstream resolution). - Related PRs: #85, #86, #87. ## Files Modified **Modified:** - `wrangler.jsonc`: dropped the `env.staging` block, added a `define` for `__BUILD_ENV__: "production"`. - `.github/workflows/deploy.yml`: staging job uses `--config wrangler.staging.jsonc`; production job retains the default `wrangler deploy`. - `.github/workflows/ci.yml`: sandbox image-pin guard reads both configs via `--config`. - `scripts/hooks/pre-push`: wrangler dry-run step now covers both configs. - `src/worker/index.ts`: declared `__BUILD_ENV__` and the `BUILD_ENV` constant with a `development` fallback for tests. - `src/worker/headers.ts`: `applyHeaders` accepts a `buildEnv` option and emits `X-Build-Env` on every response. - `tests/worker.test.ts`: every `applyHeaders` call site updated with `buildEnv: 'development'`; three new tests cover the `X-Build-Env` header for each env value. - `RELEASES.md`: deploy table updated, new subsection on the two-config layout, sandbox image release procedure rewritten. - `docker/sandbox/README.md`: pin location updated. - `styles/config/vocabularies/site/accept.txt`: legitimate technical terms (`configs`, `etag`, `etags`, `deduplication`, `namespaces`, `envs`, `CF's`) added so the new prose passes Vale at the error tier. **Created:** - `wrangler.staging.jsonc`: standalone staging config with all the same fields production needs, plus `define` setting `__BUILD_ENV__: "staging"`. **Renamed:** - None. **Deleted:** - None. ## Testing - [x] Unit tests added/updated - [x] All tests passing **Test Summary:** - 318 unit and regression tests pass (315 prior + 3 new `X-Build-Env` cases). - `bun x wrangler deploy --dry-run --config wrangler.jsonc`: clean, lists the production-side bindings (anc-score-cache R2, rate-limit namespace 1001, prod container app). - `bun x wrangler deploy --dry-run --config wrangler.staging.jsonc`: clean, lists the staging-side bindings (anc-score-cache-staging R2, rate-limit namespace 1002, staging container app). - The two dry-run outputs report different compressed sizes (28.36 vs 28.35 KiB), evidence that the compiled scripts now have distinct content thanks to the `define` substitution. - Pre-push gate (lint, build, 318 tests, both wrangler dry-runs, pack-README, banned-fonts, prose-check) passes end-to-end. - prose-check: 0 blocking, 1108 warning. **Post-merge verification plan:** After the staging deploy on this PR's merge to dev: - `curl -sI https://agentnative-site-staging.brettdavies.workers.dev/` returns 200 with `X-Build-Env: staging` and `X-Robots-Tag: noindex`. - The deployed staging Worker version has a new script etag (different from the previously-staged version). After the next release PR to main: - `curl -sI https://anc.dev/` returns 200 with `X-Build-Env: production` and no `X-Robots-Tag` header. - Compare the new production Worker version's script etag against the staging version's: they should differ. That confirms the fix is in effect.
3 tasks
brettdavies
added a commit
that referenced
this pull request
May 15, 2026
## Summary Reverts PR #88. The split-config + `__BUILD_ENV__` substitution shipped as an asset-sharing fix, but the underlying observation it was meant to fix had a different cause. What actually triggered the revert: PR #88's staging deploy failed with `DURABLE_OBJECT_ALREADY_HAS_APPLICATION` (the new config produced a different container-app name than the existing app's, and a DO can only bind to one app). During that failed deploy, anc.dev's custom-domain binding moved from `agentnative-site` (prod) to `agentnative-site-staging`. Restoring anc.dev to prod via the CF API immediately reverted the served content to PR #85's deployed baseline, which suggests the "asset overlay" symptoms we saw earlier in the day were actually anc.dev routing drift to staging, not cross-environment asset sharing. Concretely, this revert restores: - Single `wrangler.jsonc` with `env.staging` block. The container app naming returns to `agentnative-site-staging-sandbox-staging` (env-suffixed), which matches the existing CF resource so deploys succeed again. - `src/worker/index.ts` and `src/worker/headers.ts` lose the `__BUILD_ENV__` / `X-Build-Env` plumbing. No diagnostic header on responses; tests stop asserting it. - `deploy.yml`, `ci.yml`, `scripts/hooks/pre-push`, `RELEASES.md`, `docker/sandbox/README.md` all go back to the multi-env shape. - `styles/config/vocabularies/site/accept.txt` loses the seven technical terms added for PR #88's prose (`configs`, `etag`, `etags`, `deduplication`, `namespaces`, `envs`, `CF's`). These were only added because PR #88's RELEASES.md prose needed them; without that prose, the additions are noise. The upstream issue at [cloudflare/workers-sdk#13925](cloudflare/workers-sdk#13925) needs a follow-up correction comment noting the routing-drift alternative explanation. That's a follow-up, not part of this revert. ## Changelog ### Changed - Roll back the two-config wrangler split. Production and staging deploys continue to use a single `wrangler.jsonc` with an `env.staging` block; deploys use `wrangler deploy` (prod) and `wrangler deploy --env staging` (staging) respectively. ## Type of Change - [x] `revert`: Reverting a previous change ## Related Issues/Stories - Story: Failed staging deploy for PR #88, plus the recurrence of the anc.dev routing-drift bug during that deploy. The routing-drift explanation is a better fit for the asset-overlay symptoms we saw earlier in the day than the asset-sharing theory PR #88 was built on. - Issue: [cloudflare/workers-sdk#13925](cloudflare/workers-sdk#13925) (will be updated with a follow-up comment). - Architecture: None. - Related PRs: #88 (the PR being reverted). ## Files Modified **Modified:** - `wrangler.jsonc`: restore `env.staging` block, drop the `define` for `__BUILD_ENV__`. - `.github/workflows/deploy.yml`: staging job back to `wrangler deploy --env staging`; production job back to `deploy --env=""`. - `.github/workflows/ci.yml`: image-pin guard reads both pins via the multi-env shape again. - `scripts/hooks/pre-push`: dry-runs go back to `--env=""` and `--env staging`. - `src/worker/index.ts`: `__BUILD_ENV__` declaration and `BUILD_ENV` constant removed; `applyHeaders` call loses the `buildEnv` field. - `src/worker/headers.ts`: `ApplyHeadersOptions` loses `buildEnv`; `X-Build-Env` header no longer emitted. - `tests/worker.test.ts`: all `applyHeaders` call sites have `buildEnv` removed; the three `X-Build-Env` tests are removed. - `RELEASES.md`: deploy table and sandbox-image-release procedure revert to the multi-env wording. - `docker/sandbox/README.md`: pin location reverts to `env.staging.containers[0].image`. - `styles/config/vocabularies/site/accept.txt`: seven added terms removed. **Created:** - None. **Renamed:** - None. **Deleted:** - `wrangler.staging.jsonc` (the standalone staging config introduced by PR #88). ## Testing - [x] Unit tests added/updated - [x] All tests passing **Test Summary:** - 315 unit and regression tests pass (back to the pre-PR-#88 count of 315, after the three `X-Build-Env` tests are removed by the revert). - `bun x wrangler deploy --dry-run --env=""`: clean, prod bindings (anc-score-cache R2, rate-limit namespace 1001). - `bun x wrangler deploy --dry-run --env staging`: clean, staging bindings (anc-score-cache-staging R2, rate-limit namespace 1002). - Pre-push gate (lint, build, tests, both dry-runs, pack-README, banned-fonts, prose-check) passes end-to-end. **Post-merge plan:** Once the staging deploy on this revert lands cleanly (it should: the multi-env shape matches the existing CF resources), curl staging and prod and verify both still respond. Then file a follow-up comment on workers-sdk#13925 explaining the routing-drift alternative explanation and what we learned today.
Merged
3 tasks
brettdavies
added a commit
that referenced
this pull request
May 15, 2026
…eritance from top-level (#90) ## Summary Explicitly override two inheritable keys in `env.staging` so they stop silently inheriting destructive values from the top-level config: `routes` (which has been quietly stealing anc.dev's custom-domain binding away from production on every dev push since the 2026-04-30 v0.1 launch) and `triggers` (prophylactic; no current scheduled triggers, but the same trap shape). The "routing-drift bug" we have been chasing for two weeks turns out to be documented Wrangler behavior. Per the [Inheritable keys list](https://developers.cloudflare.com/workers/wrangler/configuration/), `routes` is an inheritable key. The top-level config declares `routes: [{ pattern: "anc.dev", custom_domain: true }]`. `env.staging` had no `routes` field, so it silently inherited that array. Every `wrangler deploy --env staging` ran with `routes: [{anc.dev}]` in scope and re-attached anc.dev to `agentnative-site-staging`, transferring the custom-domain binding away from `agentnative-site`. The deployment log includes a `Deployed agentnative-site-staging triggers ... anc.dev (custom domain)` line on every staging deploy that nobody had read as "the prod custom domain just moved". Explicit empty arrays break the inheritance without changing any other behavior. Today's filed-then-retracted upstream issue at [cloudflare/workers-sdk#13925](cloudflare/workers-sdk#13925) (rewritten to describe this trap honestly) suggests Wrangler add a deploy-time warning when an env block inherits a `routes` array containing custom domains. ## Changelog ### Fixed - Stop staging deploys from re-attaching `anc.dev` to the staging Worker on every dev push. The "routing-drift bug" tracked since 2026-04-30 was caused by `env.staging` silently inheriting the top-level `routes` array. Explicit `routes: []` override on `env.staging` makes the staging Worker's deployment stop asserting ownership of `anc.dev`. ### Changed - Added explicit `triggers: { crons: [] }` override on `env.staging` as a prophylactic against the same inheritance pattern firing on a future scheduled-trigger addition. ## Type of Change - [x] `fix`: Bug fix (non-breaking change which fixes an issue) ## Related Issues/Stories - Story: Closes the chronic routing-drift bug. anc.dev should now stay on the production Worker across staging deploys. - Issue: [cloudflare/workers-sdk#13925](cloudflare/workers-sdk#13925) (rewritten to document the actual inheritance trap). - Architecture: None. - Related PRs: #85 (manual routing-drift fix that this PR addresses structurally), #88 (asset-sharing fix that was reverted by #89 once the routes-inheritance explanation surfaced), #89 (the revert of #88). ## Files Modified **Modified:** - `wrangler.jsonc`: two explicit overrides added on `env.staging` (`routes: []` and `triggers: { crons: [] }`) with inline comments documenting why. No other config changed. **Created:** - None. **Renamed:** - None. **Deleted:** - None. ## Testing - [x] Unit tests added/updated - [x] All tests passing **Test Summary:** - 315 unit and regression tests pass (unchanged from pre-PR baseline). - `bun x wrangler deploy --dry-run --env=""`: clean. Production-side bindings unchanged. - `bun x wrangler deploy --dry-run --env staging`: clean. Staging-side bindings unchanged. - The deployment log's `Deployed agentnative-site-staging triggers` section is now empty (dry-run output suppresses the section entirely when there are no triggers, which matches the expected post-fix behavior). - Pre-push gate passes end-to-end. **Audit of every inheritable key:** | Key | Top-level | env.staging | Status | |---|---|---|---| | `name` | `agentnative-site` | `agentnative-site-staging` | Explicit override | | `main` | `src/worker/index.ts` | inherited | Safe (same source by design) | | `compatibility_date` | `2026-04-01` | inherited | Safe | | `compatibility_flags` | `["nodejs_compat"]` | inherited | Safe | | `account_id` | via env var | via env var | Same account | | `workers_dev` | `false` | `true` | Explicit override | | `routes` | `[{anc.dev}]` | `[]` | **Explicit override (this PR)** | | `triggers` | not set | `{ crons: [] }` | **Explicit override (this PR, prophylactic)** | | `observability` | `{enabled: true, ...}` | inherited | Safe (same intent) | | `assets` | `{directory: "./dist", ...}` | inherited | Safe (per-Worker asset stores; the "asset overlay" symptoms were routing drift) | | `send_metrics` | `false` | inherited | Safe (both opt out) | | `migrations` | `[{tag: v1, ...}]` | explicitly declared | Already overridden | | `preview_urls`, `route` (singular), `tsconfig`, `rules`, `build`, `no_bundle`, `find_additional_modules`, `base_dir`, `preserve_file_names`, `minify`, `keep_names`, `logpush`, `limits`, `placement` | not set | not set | n/a | If any of the currently-unset inheritable keys gets set at the top level later, re-audit `env.staging` and add an explicit override if the inherited value would be destructive for staging. **Post-merge plan:** After this PR's staging deployment on dev, verify: - Staging deploy log's `Deployed agentnative-site-staging triggers` section is empty (no `anc.dev (custom domain)` line). - CF API: `anc.dev` custom-domain binding stays on `service: agentnative-site` after the deployment completes. - `curl https://anc.dev/` returns 200 from the production Worker (no `X-Robots-Tag` header, content matches main's deployed assets). - `curl https://agentnative-site-staging.brettdavies.workers.dev/` returns 200 with `X-Robots-Tag: noindex`. If anc.dev binding moves to staging after this deployment, the fix is wrong and we need a different mechanism.
3 tasks
brettdavies
added a commit
that referenced
this pull request
May 15, 2026
## Summary Third production release of the day. Bundles all post-PR-#85 dev work that survived today's incident, namely the three PRs that are net additive after the failed split-config experiment (PR #88) and its revert (PR #89) cancel out: PR #86 (explicit Wrangler env target + dual-env pre-push dry-run), PR #87 (corrected `cf` registry entry pointing at its npm distribution surface), and PR #90 (the actual fix for the 2-week routing-drift bug: explicit `routes: []` override on `env.staging` to break Wrangler's inheritable-keys inheritance, plus prophylactic `triggers: { crons: [] }` override for the same trap shape). The headline is PR #90's wrangler.jsonc change. anc.dev is currently bound to the production Worker (after a manual rebind earlier today), and PR #90's staging-deploy verification confirmed the routing fix holds: the `Deployed agentnative-site-staging triggers` block no longer lists `anc.dev (custom domain)`, and the CF API confirms the binding stays on `agentnative-site` through staging deploys. Promoting that wrangler.jsonc state to main means the routing fix is in the committed source-of-truth, not just in dev's history. User-facing effects on anc.dev after this deploy: the corrected `/registry-index.json` and `/score/cf` content from PR #87 reach production (today they live on staging only); the env-target explicitness from PR #86 removes the wrangler ambiguity warning from the next production deploy log. The Worker code itself, bindings, and DO migrations are unchanged. ## Changelog ### Fixed - Stop staging deploys from re-attaching `anc.dev` to the staging Worker on every dev push. The routing-drift bug tracked since 2026-04-30 was caused by `env.staging` silently inheriting the top-level `routes` array. Explicit `routes: []` override on `env.staging` breaks the inheritance. anc.dev now stays on the production Worker across staging deploys. - Correct the `cf` entry in `registry.yaml`. The previous entry claimed `repo: cloudflare/workers-sdk`, but `cf` is not in that repo and the npm package itself declares no repository. Replaced with a `url:` pointing at the npm distribution page. Side effect: the build's reverse-lookup map for `cloudflare/workers-sdk` now correctly resolves to `wrangler` instead of `cf`. ### Changed - Pass `--env=""` explicitly on the production wrangler-action deploy command. Removes the "Multiple environments are defined" ambiguity warning from production deploy logs. - Pre-push hook now runs `wrangler deploy --dry-run` for both the production and staging environments instead of one bare invocation. Catches binding mistakes in either environment before push. - Added explicit `triggers: { crons: [] }` override on `env.staging` as a prophylactic against the same inheritance trap shape on scheduled triggers. Currently no scheduled triggers; the override forces a deliberate decision when adding any. ## Type of Change - [x] `fix`: Bug fix (non-breaking change which fixes an issue) The release is multi-typed (one fix headline plus two ride-along changes) but `fix` headlines because PR #90's routes-inheritance fix is the durable resolution of the 2-week routing-drift incident. ## Related Issues/Stories - Story: Closes the production side of the routing-drift fix arc. PR #85 brought `agentnative-site` (named-prod) current and manually rebound anc.dev to prod, but the fix was not durable because the underlying wrangler.jsonc still inherited the prod route into `env.staging` on every staging deploy. PR #90 fixed the inheritance in source; this release ships that to main. - Issue: [cloudflare/workers-sdk#13925](cloudflare/workers-sdk#13925), rewritten today as a docs/UX bug describing the inheritance trap and recommending a deploy-time warning when env blocks silently inherit a `routes` array containing custom domains. - Architecture: `docs/solutions/integration-issues/wrangler-routes-inheritance-staging-custom-domain-drift-2026-05-15.md` (dev-only) for the full investigation writeup. - Related PRs: #85, #86, #87, #88 (reverted), #89 (revert), #90. ## Files Modified **Modified:** - `wrangler.jsonc`: env.staging block now explicitly overrides `routes: []` and `triggers: { crons: [] }` to break Wrangler's inheritable-keys inheritance. Top-level production config unchanged (`routes: [{ pattern: "anc.dev", custom_domain: true }]`, `workers_dev: false`). - `.github/workflows/deploy.yml`: production job's wrangler-action command is `deploy --env=""` (was bare `deploy`); staging job's command is unchanged (`deploy --env staging`). - `scripts/hooks/pre-push`: replaces the single bare `wrangler deploy --dry-run` step with two dry-runs, one per environment (`--env=""` and `--env staging`). - `registry.yaml`: `cf` entry replaces `repo: cloudflare/workers-sdk` with `url: https://www.npmjs.com/package/cf` and an inline comment explaining why. **Created:** - None. **Renamed:** - None. **Deleted:** - None. ## Testing - [x] Unit tests added/updated - [x] All tests passing **Test Summary:** - 315 unit and regression tests pass. - `bun x wrangler deploy --dry-run --env=""`: clean, lists the production-side bindings (Sandbox DO, R2 bucket `anc-score-cache`, rate-limit namespace 1001, ASSETS). Container image pinned at `:30f61f1`. - `bun x wrangler deploy --dry-run --env staging`: clean, lists the staging-side bindings (R2 `anc-score-cache-staging`, rate-limit namespace 1002). The `Deployed triggers` section in dry-run output is suppressed. - Pre-push hook (lint, build, both wrangler dry-runs, pack-README, banned-fonts, prose-check) passes end-to-end. - prose-check: 0 blocking. **Post-merge verification plan** (after the production deploy on this PR's merge): - Production deploy log's `Deployed agentnative-site triggers` section lists only `anc.dev (custom domain)`. The wrangler ambiguity warning is gone from the log thanks to `--env=""`. - `curl https://anc.dev/registry-index.json` returns the corrected mapping (`cloudflare/workers-sdk` → `wrangler`, md5 should match the dev-side build, which is `f50579f244013d2b76e999a9502f4e46`). - `curl -sI https://anc.dev/` returns 200 with no `X-Robots-Tag` header (production Worker). - CF API confirms `anc.dev` Custom Domain record stays on `service: agentnative-site` after the deployment completes. - A subsequent dev push (next staging deploy) leaves `anc.dev` on production. This is the durability test: pre-PR-#90, every staging deploy would have flipped the binding; post-PR-#90, it should not.
6 tasks
brettdavies
added a commit
that referenced
this pull request
May 19, 2026
Per direction: nothing from this plan ships to production until the entire plan (through U10) is complete. Scrub the two forward-looking promotion mentions from the in-flight state-of-the-world sections. Changes: - "Shipped to dev" U7 entry no longer says "production stays on `:30f61f1` until a release PR promotes the image"; replaced with "Production is not in scope until U10 ships and the whole plan is complete." - "Branch baseline for next session" no longer describes `main`'s current pin or names a future release-PR-to-main batch. Now says "U5/U6/U7 are dev-only; nothing about this plan ships to production until the entire plan (through U10) is complete." Pre-existing historical narration of PR #84 / PR #85 (the established staging-leads-prod workflow and the production-side U3 deploy that already happened) stays intact: the rule is no forward scheduling of a promotion, not the erasure of past prod work.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second production release since the v0.1 launch (#60, 2026-04-30) and the first since #73 (2026-05-03). The headline is the routing-drift fix:
anc.devis currently bound to the staging Worker (agentnative-site-staging), not the named-production Worker (agentnative-site). This release bringsagentnative-sitecurrent with every dev-side change since #73, manually detachesanc.devfrom staging via the CF API immediately before merge, and letsdeploy.ymlreattach the domain to the named-prod Worker per the top-levelroutes:field inwrangler.jsonc.This is also the first deploy that applies the live-scoring Durable Object migration (
v1: new_sqlite_classes: ["Sandbox"]) to the named-prod Worker. DO migrations are one-way walls: once this deployment lands,wrangler rollbackcannot cross the v1 boundary on production. Treated as a milestone.The R2 bucket
anc-score-cache(referenced by the top-level wrangler config) did not exist on the account. It was created out-of-band viawrangler r2 bucket create anc-score-cachebefore opening this PR so binding validation passes at deploy time.This release bundles 11 PRs from
dev(post-#73):#77,#78,#79,#80,#81,#84). The/api/scoreroute is NOT user-facing yet (U5 onward still pending). This release ships the wrangler bindings, the DO stub returning{error: 'sandbox_stub_until_u6'}, the input parser plus 4-step GitHub URL discovery chain, the registry and discovery indexes, and the Alpine plus musl sandbox image pinned atregistry.cloudflare.com/<acct>/anc-sandbox:30f61f1.#83): adds principle P8 (Discoverable Through Agent Skill Bundles), renamesdocs/DESIGN.mdtoDESIGN.md, renames.impeccable.mdtoPRODUCT.md, refreshes prose-tooling channel.#82): vendored Vale rule packs (brand plus site channels) andprose-check.shorchestrator with a blocking-category whitelist (TYPOS|GRAMMAR|CONFUSED_WORDS). Pre-push only; not in CI.#76), biome warning silenced (#75), project-scoped wrangler telemetry opt-out (#74).Changelog
Added
/principles/p8-discoverable-skill-bundleand listed on/. Spec advances from v0.3.0 to v0.4.0 (#83).src/worker/score/do.ts; build-timeregistry-indexanddiscovery-hints-index; Alpine plus musl sandbox image atdocker/sandbox/Dockerfilewithancbaked in via the brew-installable musl binary; input parser and 4-step GitHub URL discovery chain atsrc/worker/score/{parse-install,registry-lookup,discover-binary,validate}.ts. The/api/scoreroute itself ships in a later release (#77,#78,#79,#80,#81).styles/brand/*,styles/site/*,styles/config/vocabularies/{brand,site}/{accept,reject}.txt),scripts/prose-check.shorchestrator,scripts/check-banned-fonts.shdeployment-layer scan, andscripts/sync-prose-tooling.shrule-pack sync. LanguageTool blocking-category whitelist (TYPOS|GRAMMAR|CONFUSED_WORDS) keeps signal high (#82).PRODUCT.mdat the repo root (channel-context file expected by the/impeccableskill). Renamed from.impeccable.md(#83).wrangler deploy --dry-runstep in the pre-push hook, catching deploy-time binding validation failures before push (#76).send_metrics: falseinwrangler.jsonc. Belt-and-suspenders with the per-user shell env var and per-machine wrangler config; travels with the repo (#74).Changed
docs/DESIGN.mdrenamed toDESIGN.mdat the repo root, matching the/impeccablechannel-context layout (#83).#83).wrangler.jsoncpins (containers[0].imagetop-level,env.staging.containers[0].image) are independent: staging advances during development, prod advances at release.RELEASES.md § Sandbox image releasesdocuments the soak-then-promote default and the lockstep shortcut for low-risk bumps (#84).registry.cloudflare.com/<acct>/anc-sandbox:30f61f1) instead of the deprecated Docker Hub URI. Build is decoupled from deploy viawrangler containers build -p; deploy never rebuilds (#84).Fixed
ImagePullErrorcaused by Docker Hub registry deprecation. The sandbox image now lives in the Cloudflare managed registry and is pinned by<git-sha>tag in both env blocks (#84).:30f61f1(lockstep), so the equality check passes by default (#84).noTemplateCurlyInStringwarning on a footer test name. The string was intentional and is now annotated with abiome-ignorecomment (#75).Documentation
RELEASES.md § Sandbox image releases (live-scoring): full spec for image bumps including local-build-once viawrangler containers build -p, soak-then-promote default flow, lockstep shortcut, image-retention discipline (never delete a tag that backed a shipped Worker version), and the DO-migration one-way wall note (#84).docker/sandbox/README.md: developer-facing image build and push reference (#84).Type of Change
feat: New feature (non-breaking change which adds functionality)The release is multi-typed (feat plus fix plus chore plus docs) but
featheadlines because the live-scoring scaffolding (4 units) is the largest user-facing surface introduced, even though the/api/scoreroute is not yet wired.Related Issues/Stories
agentnative-sitedeploys at the new image pin) and resolves the routing drift discovered during the post-U3 audit.anc.devwas bound to the staging Worker since 2026-04-30; this release reattaches it to the named-prod Worker.docs/plans/2026-04-28-002-feat-live-scoring-cf-sandbox-plan.md(live-scoring v3 plan; this release closes Pending feat(M1): scaffold bun + wrangler + biome, apply DESIGN.md §12 edits #1).Files Modified
Modified:
wrangler.jsonc: bindings for Containers, Durable Objects, R2 buckets, and Rate Limits on both env blocks; image pinned atregistry.cloudflare.com/<acct>/anc-sandbox:30f61f1;send_metrics: false; DO migrations v1 (#74,#81,#84).RELEASES.md: sandbox image release workflow, status-check context pitfall, telemetry opt-out documentation (#74,#84).package.json: spec v0.4.0 bump and prose-check scripts (#82,#83).src/worker/index.ts,src/worker/headers.ts: routing prep for/api/score(stub still returns 503 until U5) (#81).src/build/build.mjs: registry-index plus discovery-hints-index emission (#78).tests/regression.test.ts: prose-check plus live-scoring scaffolding coverage (#78,#82).git diff origin/main..HEAD --name-only.Created:
content/principles/p8-discoverable-skill-bundle.md,src/data/spec/principles/p8-discoverable-skill-bundle.md,content/principles/README.md(#83).docker/sandbox/Dockerfile,docker/sandbox/README.md,docker/sandbox/.dockerignore,docker/sandbox/.ignored-sentinel.txt,tests/dockerfile-sandbox.test.ts(#79,#84).discovery-hints.yaml,src/build/registry-index.mjs,tests/registry-index.test.ts(#78).src/worker/score/{parse-install,registry-lookup,discover-binary,validate,do}.tsand 4 paired test files (#80,#81).styles/{brand,site,config/vocabularies/brand,config/vocabularies/site}/*(Vale rule packs),scripts/prose-check.sh,scripts/check-banned-fonts.sh,scripts/sync-prose-tooling.sh,scripts/__fixtures__/prose-check/**,.vale.ini,BRAND.md(#82).scripts/measure-discovery-hit-rate.mjs(#77)..github/workflows/ci.ymladditions: sandbox image registry-existence guard plus pin-equality guard (#84).Renamed:
docs/DESIGN.mdtoDESIGN.mdat repo root (#83)..impeccable.mdtoPRODUCT.md(#83).Deleted:
Testing
Test Summary:
bun run buildclean: 8 principles, 112 HTML pages, 112 MD pages, 97 scorecard pages, 96 badges.bun x wrangler deploy --dry-runvalidates the named-prod environment bindings end-to-end: Sandbox DO, R2 bucketanc-score-cache(created out-of-band before this PR), SCORE_LIMITER (10 requests per 60 s), ASSETS. Container imageanc-sandbox:30f61f1resolves to the CF managed registry.:30f61f1, lockstep), so the new main-targeting equality check is expected to pass on its first exercised PR run.Post-merge verification plan (executed after the deploy.yml run on the merge SHA):
deploy.ymlproduction-deploy log lists ONLYanc.dev (custom domain)under triggers (noworkers.devURL, since named-prod hasworkers_dev: false).workers.devURL, noanc.dev.curl -sI https://anc.dev/returns 200 with NOx-robots-tagheader.curl -sI https://agentnative-site-staging.brettdavies.workers.dev/returns 200 WITHx-robots-tag: noindex./accounts/<acct>/workers/domainsshows one record for hostnameanc.devwith serviceagentnative-site.