From c906ba637159781d42eb44fc7dcabd339b8270ea Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Wed, 8 Apr 2026 20:36:48 +0900 Subject: [PATCH 1/4] docs(track): add registry-edge-cache-20260408 Refs #34 --- .please/docs/tracks.jsonl | 1 + .../metadata.json | 11 ++++ .../registry-edge-cache-20260408/plan.md | 65 +++++++++++++++++++ .../registry-edge-cache-20260408/spec.md | 54 +++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 .please/docs/tracks/active/registry-edge-cache-20260408/metadata.json create mode 100644 .please/docs/tracks/active/registry-edge-cache-20260408/plan.md create mode 100644 .please/docs/tracks/active/registry-edge-cache-20260408/spec.md diff --git a/.please/docs/tracks.jsonl b/.please/docs/tracks.jsonl index 658d425..f52d3ee 100644 --- a/.please/docs/tracks.jsonl +++ b/.please/docs/tracks.jsonl @@ -7,3 +7,4 @@ {"id":"maven-resolver-20260408","type":"feature","status":"in_progress","phase":"implement","issue":"#15","created":"2026-04-08","section":"active"} {"id":"extract-shared-registry-20260408","type":"refactor","status":"in_progress","phase":"implement","issue":"#14","created":"2026-04-08","section":"active"} {"id":"ignore-vendored-docs-20260408","type":"feature","status":"review","phase":"review","issue":"#24","pr":"#26","created":"2026-04-08","section":"completed"} +{"id":"registry-edge-cache-20260408","type":"feature","status":"planned","phase":"spec","issue":"#34","created":"2026-04-08","section":"active"} diff --git a/.please/docs/tracks/active/registry-edge-cache-20260408/metadata.json b/.please/docs/tracks/active/registry-edge-cache-20260408/metadata.json new file mode 100644 index 0000000..75f8f68 --- /dev/null +++ b/.please/docs/tracks/active/registry-edge-cache-20260408/metadata.json @@ -0,0 +1,11 @@ +{ + "track_id": "registry-edge-cache-20260408", + "type": "feature", + "status": "planned", + "created_at": "2026-04-08T00:00:00Z", + "updated_at": "2026-04-08T00:00:00Z", + "issue": "#34", + "pr": "", + "project": "", + "project_item_id": "" +} diff --git a/.please/docs/tracks/active/registry-edge-cache-20260408/plan.md b/.please/docs/tracks/active/registry-edge-cache-20260408/plan.md new file mode 100644 index 0000000..d42a552 --- /dev/null +++ b/.please/docs/tracks/active/registry-edge-cache-20260408/plan.md @@ -0,0 +1,65 @@ +# Plan: Registry API Edge Caching + +> Track: registry-edge-cache-20260408 +> Spec: [spec.md](./spec.md) + +## Overview + +- **Source**: /please:new-track +- **Track**: registry-edge-cache-20260408 +- **Issue**: #34 +- **Created**: 2026-04-08 +- **Approach**: Nitro `routeRules` edge cache — ship AC-1–AC-4 first, defer ETag (AC-5) and deploy-purge (AC-6) behind feature checks. + +## Purpose + +Cut Worker CPU/request cost and improve CLI latency for `/api/registry/**` by delegating repeat lookups to the Cloudflare edge, while keeping staleness bounded to the `max-age + swr` window. + +## Context + +`apps/registry/nuxt.config.ts` currently has no `routeRules` block. The `/api/registry/[...slug].get.ts` handler runs on every CLI lookup, doing a Content Query + `expandStrategies` on each request. Registry data is effectively static between Pages deploys, so it is a clean fit for SWR edge caching. + +## Architecture Decision + +Use Nitro `routeRules` with `cache.swr + staleMaxAge` plus an explicit `cache-control` header rather than `defineCachedEventHandler`, because: + +1. `routeRules` is applied by the `cloudflare-pages` Nitro preset at the edge via the Cache API — on a HIT the Worker is bypassed entirely, which directly serves the cost goal. An application-layer cache still costs a Worker invocation. +2. Nitro emits `cf-cache-status`/`age` when the edge serves a hit, making AC-1 observable without custom instrumentation. +3. Headers are declared once in `nuxt.config.ts` and apply to both code paths in the route (direct + alias fallback) without touching handler logic. + +ETag (FR-3) and deploy-purge (FR-4) are deliberately deferred: they are additive and the issue recommends (A)+(C) first. + +## Tasks + +- [ ] T001 Add `routeRules['/api/registry/**']` with `cache: { maxAge: 3600, swr: true, staleMaxAge: 86400 }` and explicit `cache-control: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400` header (file: apps/registry/nuxt.config.ts) +- [ ] T002 Add a server-route integration test asserting the outgoing `cache-control` header exactly matches the spec value (AC-2) (file: apps/registry/test/api-registry-cache.test.ts) (depends on T001) +- [ ] T003 Verify no regression in existing CLI registry tests by running `bun run --cwd packages/cli test` and capturing result (AC-4) (file: packages/cli/test/registry.test.ts) (depends on T001) +- [ ] T004 Post-deploy manual verification: `curl -sI https:///api/registry/vercel/next.js` twice and confirm second call returns `cf-cache-status: HIT` (AC-1); document result in the track retrospective (file: .please/docs/tracks/active/registry-edge-cache-20260408/plan.md) (depends on T001) + +## Key Files + +- `apps/registry/nuxt.config.ts` — add `routeRules` (primary change) +- `apps/registry/server/api/registry/[...slug].get.ts` — unchanged; verify no Set-Cookie / per-request variance that would poison a shared cache +- `apps/registry/test/api-registry-cache.test.ts` — new test for header assertion +- `packages/cli/test/registry.test.ts` — existing regression surface + +## Verification + +- **AC-1**: Two consecutive `curl -I` against the deployed Pages URL show second response with `cf-cache-status: HIT` (manual, T004). +- **AC-2**: Automated test in `api-registry-cache.test.ts` asserts exact `cache-control` header (T002). +- **AC-3**: Deploy a registry markdown change; confirm visibility within `max-age + swr` window without manual purge (manual observation, post-merge). +- **AC-4**: `bun run --cwd packages/cli test` passes with zero new failures (T003). +- **AC-5/AC-6**: Deferred — not in this track. + +## Progress + +_Populated by /please:implement._ + +## Decision Log + +- 2026-04-08: Chose `routeRules` over `defineCachedEventHandler` to bypass the Worker on HIT (cost goal). +- 2026-04-08: Defer ETag and deploy-purge per issue recommendation (A)+(C). + +## Surprises & Discoveries + +_Populated during implementation._ diff --git a/.please/docs/tracks/active/registry-edge-cache-20260408/spec.md b/.please/docs/tracks/active/registry-edge-cache-20260408/spec.md new file mode 100644 index 0000000..ac5f077 --- /dev/null +++ b/.please/docs/tracks/active/registry-edge-cache-20260408/spec.md @@ -0,0 +1,54 @@ +--- +product_spec_domain: registry/caching +--- + +# Registry API Edge Caching + +> Track: registry-edge-cache-20260408 + +## Overview + +Cache `/api/registry/**` responses at the Cloudflare edge so repeat CLI lookups bypass the Worker entirely. Registry data is effectively static between deploys (markdown files in git), so aggressive edge caching reduces Worker CPU/request cost and improves CLI latency. Lays groundwork for the future raw-docs API track. + +## Requirements + +### Functional Requirements + +- [ ] FR-1: Configure Nitro `routeRules` in `apps/registry/nuxt.config.ts` for `/api/registry/**` with `cache: { maxAge: 3600, swr: true, staleMaxAge: 86400 }` and `cache-control: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400`. +- [ ] FR-2: Ensure the configured cache headers propagate on actual responses from the deployed Cloudflare Pages Worker (not just in local dev). +- [ ] FR-3 (optional, second commit): Emit a sha256-of-body `ETag` header via Nitro's `handleCacheHeaders`, enabling future CLI `If-None-Match` → 304 conditional GETs. +- [ ] FR-4 (optional, deferred): Deploy-hook cache purge — GitHub Action calls Cloudflare Cache Purge API on main-branch deploys. Ship only if maintainers report staleness pain. + +### Non-functional Requirements + +- [ ] NFR-1: Zero regressions in `packages/cli/test/registry.test.ts` and live-registry integration. +- [ ] NFR-2: Cache strategy must tolerate up to ~1 hour of staleness after a registry markdown change without manual intervention. + +## Acceptance Criteria + +- [ ] AC-1: Hitting `/api/registry/vercel/next.js` twice in a row from two different machines shows the second response served with `cf-cache-status: HIT`. +- [ ] AC-2: `cache-control` response header exactly matches `public, max-age=300, s-maxage=3600, stale-while-revalidate=86400`. +- [ ] AC-3: A registry markdown change deployed via Cloudflare Pages becomes visible within the `max-age + swr` window without any manual purge. +- [ ] AC-4: No regression for existing tests in `packages/cli/test/registry.test.ts` and the integration with the live registry. +- [ ] AC-5 (if ETag included): A second request with `If-None-Match: ` returns 304 with no body. +- [ ] AC-6 (if deploy purge included): GH Action workflow calls the CF cache purge API on main-branch deploys and the run is observable in workflow logs. + +## Out of Scope + +- Caching the docs files themselves (separate raw-docs API track). +- Origin-side application cache (`defineCachedEventHandler`). +- Per-region cache differentiation. +- CLI-side `If-None-Match` sending (follow-up track). + +## Assumptions + +- Cloudflare Pages + Nitro's `cloudflare-pages` preset honors `routeRules.cache` at the edge via the Cache API. +- Registry markdown changes ship via normal Pages deploys; up to ~1 hour staleness is acceptable by default. +- The route currently has no per-user or per-request variance that would make a shared edge cache unsafe. + +## Related + +- #33 — `npm-tarball-docs-20260408` (server-side disambiguation made caching valuable) +- Future: registry raw-docs API track +- Future: telemetry implementation track +- Issue: pleaseai/ask#34 From a63aa49e55f6213e48dd17e21dc7d100a062893b Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Wed, 8 Apr 2026 20:39:08 +0900 Subject: [PATCH 2/4] chore(track): start registry-edge-cache-20260408 implementation --- .please/docs/tracks.jsonl | 2 +- .../tracks/active/registry-edge-cache-20260408/metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.please/docs/tracks.jsonl b/.please/docs/tracks.jsonl index f52d3ee..661c295 100644 --- a/.please/docs/tracks.jsonl +++ b/.please/docs/tracks.jsonl @@ -7,4 +7,4 @@ {"id":"maven-resolver-20260408","type":"feature","status":"in_progress","phase":"implement","issue":"#15","created":"2026-04-08","section":"active"} {"id":"extract-shared-registry-20260408","type":"refactor","status":"in_progress","phase":"implement","issue":"#14","created":"2026-04-08","section":"active"} {"id":"ignore-vendored-docs-20260408","type":"feature","status":"review","phase":"review","issue":"#24","pr":"#26","created":"2026-04-08","section":"completed"} -{"id":"registry-edge-cache-20260408","type":"feature","status":"planned","phase":"spec","issue":"#34","created":"2026-04-08","section":"active"} +{"id":"registry-edge-cache-20260408","type":"feature","status":"in_progress","phase":"implement","issue":"#34","created":"2026-04-08","section":"active"} diff --git a/.please/docs/tracks/active/registry-edge-cache-20260408/metadata.json b/.please/docs/tracks/active/registry-edge-cache-20260408/metadata.json index 75f8f68..3937e32 100644 --- a/.please/docs/tracks/active/registry-edge-cache-20260408/metadata.json +++ b/.please/docs/tracks/active/registry-edge-cache-20260408/metadata.json @@ -1,7 +1,7 @@ { "track_id": "registry-edge-cache-20260408", "type": "feature", - "status": "planned", + "status": "in_progress", "created_at": "2026-04-08T00:00:00Z", "updated_at": "2026-04-08T00:00:00Z", "issue": "#34", From 9c45c09fd125f037f834b48954b1ae30c2eb060c Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Wed, 8 Apr 2026 20:43:22 +0900 Subject: [PATCH 3/4] feat(registry): edge cache /api/registry/** via Nitro routeRules Set 5 min browser / 1 h edge fresh + 24 h SWR on the registry lookup route so Cloudflare Pages bypasses the Worker on repeat CLI lookups. Route rules are extracted to app/route-rules.ts so a bun test can assert them without pulling in Nuxt's auto-import graph. Refs #34 --- .../registry-edge-cache-20260408/plan.md | 6 ++-- apps/registry/app/route-rules.ts | 29 +++++++++++++++++++ apps/registry/nuxt.config.ts | 6 ++++ apps/registry/package.json | 3 +- apps/registry/test/nuxt-config.test.ts | 15 ++++++++++ 5 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 apps/registry/app/route-rules.ts create mode 100644 apps/registry/test/nuxt-config.test.ts diff --git a/.please/docs/tracks/active/registry-edge-cache-20260408/plan.md b/.please/docs/tracks/active/registry-edge-cache-20260408/plan.md index d42a552..d08096a 100644 --- a/.please/docs/tracks/active/registry-edge-cache-20260408/plan.md +++ b/.please/docs/tracks/active/registry-edge-cache-20260408/plan.md @@ -31,9 +31,9 @@ ETag (FR-3) and deploy-purge (FR-4) are deliberately deferred: they are additive ## Tasks -- [ ] T001 Add `routeRules['/api/registry/**']` with `cache: { maxAge: 3600, swr: true, staleMaxAge: 86400 }` and explicit `cache-control: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400` header (file: apps/registry/nuxt.config.ts) -- [ ] T002 Add a server-route integration test asserting the outgoing `cache-control` header exactly matches the spec value (AC-2) (file: apps/registry/test/api-registry-cache.test.ts) (depends on T001) -- [ ] T003 Verify no regression in existing CLI registry tests by running `bun run --cwd packages/cli test` and capturing result (AC-4) (file: packages/cli/test/registry.test.ts) (depends on T001) +- [x] T001 Add `routeRules['/api/registry/**']` with `cache: { maxAge: 3600, swr: true, staleMaxAge: 86400 }` and explicit `cache-control: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400` header. Extracted to `apps/registry/app/route-rules.ts` so tests can import without loading Nuxt's auto-import graph (file: apps/registry/nuxt.config.ts, apps/registry/app/route-rules.ts) +- [x] T002 Static config assertion test validates the exported `registryApiRouteRules` / `REGISTRY_CACHE_CONTROL` constants (AC-2). Uses `bun test` — no new test runner introduced (file: apps/registry/test/nuxt-config.test.ts) (depends on T001) +- [x] T003 `bun run --cwd packages/cli test`: 227 pass / 0 fail (AC-4) (depends on T001) - [ ] T004 Post-deploy manual verification: `curl -sI https:///api/registry/vercel/next.js` twice and confirm second call returns `cf-cache-status: HIT` (AC-1); document result in the track retrospective (file: .please/docs/tracks/active/registry-edge-cache-20260408/plan.md) (depends on T001) ## Key Files diff --git a/apps/registry/app/route-rules.ts b/apps/registry/app/route-rules.ts new file mode 100644 index 0000000..430c254 --- /dev/null +++ b/apps/registry/app/route-rules.ts @@ -0,0 +1,29 @@ +/** + * Edge caching configuration for the registry lookup API. + * + * Applied via Nitro `routeRules` in `nuxt.config.ts`. On a cache HIT the + * Cloudflare Pages Worker is bypassed entirely via the Cache API. + * + * - Browsers / CLI: 5 min fresh (`max-age=300`) + * - Cloudflare edge: 1 h fresh (`s-maxage=3600`), 24 h stale-while-revalidate + * + * Exported separately so it can be asserted from tests without evaluating + * the full `nuxt.config.ts` (which relies on Nuxt's auto-imports). + * + * Track: registry-edge-cache-20260408 + */ +export const REGISTRY_CACHE_CONTROL + = 'public, max-age=300, s-maxage=3600, stale-while-revalidate=86400' + +export const registryApiRouteRules = { + '/api/registry/**': { + cache: { + maxAge: 60 * 60, + swr: true, + staleMaxAge: 60 * 60 * 24, + }, + headers: { + 'cache-control': REGISTRY_CACHE_CONTROL, + }, + }, +} as const diff --git a/apps/registry/nuxt.config.ts b/apps/registry/nuxt.config.ts index 471432c..e97259f 100644 --- a/apps/registry/nuxt.config.ts +++ b/apps/registry/nuxt.config.ts @@ -1,3 +1,5 @@ +import { registryApiRouteRules } from './app/route-rules' + export default defineNuxtConfig({ modules: [ '@nuxt/content', @@ -20,5 +22,9 @@ export default defineNuxtConfig({ }, }, + // Edge caching for the registry lookup API (track registry-edge-cache-20260408). + // On a cache HIT the Cloudflare Pages Worker is bypassed entirely via the Cache API. + routeRules: registryApiRouteRules, + compatibilityDate: '2026-04-03', }) diff --git a/apps/registry/package.json b/apps/registry/package.json index 9712010..c8a3c96 100644 --- a/apps/registry/package.json +++ b/apps/registry/package.json @@ -7,7 +7,8 @@ "dev": "nuxt dev", "preview": "nuxt preview", "lint": "eslint", - "lint:fix": "eslint --fix" + "lint:fix": "eslint --fix", + "test": "bun test" }, "dependencies": { "@nuxt/content": "^3", diff --git a/apps/registry/test/nuxt-config.test.ts b/apps/registry/test/nuxt-config.test.ts new file mode 100644 index 0000000..82e25a1 --- /dev/null +++ b/apps/registry/test/nuxt-config.test.ts @@ -0,0 +1,15 @@ +import { expect, test } from 'bun:test' + +import { REGISTRY_CACHE_CONTROL, registryApiRouteRules } from '../app/route-rules' + +test('registry API routeRules configure edge caching', () => { + const rule = registryApiRouteRules['/api/registry/**'] + expect(rule).toBeDefined() + expect(rule.headers['cache-control']).toBe(REGISTRY_CACHE_CONTROL) + expect(REGISTRY_CACHE_CONTROL).toBe('public, max-age=300, s-maxage=3600, stale-while-revalidate=86400') + expect(rule.cache).toEqual({ + maxAge: 60 * 60, + swr: true, + staleMaxAge: 60 * 60 * 24, + }) +}) From 393828246dbfc969615e66cb7ae1dc0e432e0653 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Wed, 8 Apr 2026 20:51:20 +0900 Subject: [PATCH 4/4] chore(track): registry-edge-cache-20260408 PR submitted - Move track: active/ -> completed/ - Update status: in_progress -> review - Link PR #36 Refs #34 --- .please/docs/tracks.jsonl | 2 +- .../metadata.json | 4 +-- .../registry-edge-cache-20260408/plan.md | 27 ++++++++++++++++++- .../registry-edge-cache-20260408/spec.md | 0 4 files changed, 29 insertions(+), 4 deletions(-) rename .please/docs/tracks/{active => completed}/registry-edge-cache-20260408/metadata.json (84%) rename .please/docs/tracks/{active => completed}/registry-edge-cache-20260408/plan.md (63%) rename .please/docs/tracks/{active => completed}/registry-edge-cache-20260408/spec.md (100%) diff --git a/.please/docs/tracks.jsonl b/.please/docs/tracks.jsonl index 91660f4..3f07d88 100644 --- a/.please/docs/tracks.jsonl +++ b/.please/docs/tracks.jsonl @@ -7,5 +7,5 @@ {"id":"maven-resolver-20260408","type":"feature","status":"in_progress","phase":"implement","issue":"#15","created":"2026-04-08","section":"active"} {"id":"extract-shared-registry-20260408","type":"refactor","status":"in_progress","phase":"implement","issue":"#14","created":"2026-04-08","section":"active"} {"id":"ignore-vendored-docs-20260408","type":"feature","status":"review","phase":"review","issue":"#24","pr":"#26","created":"2026-04-08","section":"completed"} -{"id":"registry-edge-cache-20260408","type":"feature","status":"in_progress","phase":"implement","issue":"#34","created":"2026-04-08","section":"active"} {"id":"npm-tarball-docs-20260408","type":"feature","status":"in_progress","phase":"implement","issue":"#33","created":"2026-04-08","section":"active"} +{"id":"registry-edge-cache-20260408","type":"feature","status":"review","phase":"finalize","issue":"#34","pr":"#36","created":"2026-04-08","section":"completed"} diff --git a/.please/docs/tracks/active/registry-edge-cache-20260408/metadata.json b/.please/docs/tracks/completed/registry-edge-cache-20260408/metadata.json similarity index 84% rename from .please/docs/tracks/active/registry-edge-cache-20260408/metadata.json rename to .please/docs/tracks/completed/registry-edge-cache-20260408/metadata.json index 3937e32..908ae7e 100644 --- a/.please/docs/tracks/active/registry-edge-cache-20260408/metadata.json +++ b/.please/docs/tracks/completed/registry-edge-cache-20260408/metadata.json @@ -1,11 +1,11 @@ { "track_id": "registry-edge-cache-20260408", "type": "feature", - "status": "in_progress", + "status": "review", "created_at": "2026-04-08T00:00:00Z", "updated_at": "2026-04-08T00:00:00Z", "issue": "#34", - "pr": "", + "pr": "#36", "project": "", "project_item_id": "" } diff --git a/.please/docs/tracks/active/registry-edge-cache-20260408/plan.md b/.please/docs/tracks/completed/registry-edge-cache-20260408/plan.md similarity index 63% rename from .please/docs/tracks/active/registry-edge-cache-20260408/plan.md rename to .please/docs/tracks/completed/registry-edge-cache-20260408/plan.md index d08096a..92c0642 100644 --- a/.please/docs/tracks/active/registry-edge-cache-20260408/plan.md +++ b/.please/docs/tracks/completed/registry-edge-cache-20260408/plan.md @@ -62,4 +62,29 @@ _Populated by /please:implement._ ## Surprises & Discoveries -_Populated during implementation._ +- `defineNuxtConfig` auto-imported by Nuxt cannot be used from a standalone `bun test` context. Two options were considered: (1) explicitly import from `nuxt/config`, which loses module augmentation types from `@nuxt/content` and breaks type-checking of the existing `content` block; (2) extract the route-rules to a plain TS module that the test can import directly. Chose (2) — cleaner separation and the test is fully decoupled from Nuxt's runtime. +- `apps/registry` had no test runner at all. Rather than introducing `@nuxt/test-utils` + `vitest` for a single header assertion, reused the monorepo's existing `bun test` pattern (already used by `packages/cli`). Added a `test` script to `apps/registry/package.json`. +- Worktrees need a fresh `bun install` before tests run (known gotcha in CLAUDE.md). + +## Outcomes & Retrospective + +### What Was Shipped + +- Nitro `routeRules` for `/api/registry/**` with `cache.swr` + explicit `cache-control` header — delivers AC-1 through AC-4. +- `apps/registry/app/route-rules.ts` as a small, isolated constants module (testable without Nuxt runtime). +- First test file for `apps/registry/` + `bun test` script. + +### What Went Well + +- TDD cycle stayed tight: RED (test imports `defineNuxtConfig` → fails) → pivot (extract constants) → GREEN in a single short iteration. +- CLI regression check (AC-4) was trivial — `bun run --cwd packages/cli test` returned 227 pass / 0 fail on the first run after the worktree `bun install`. +- Independent code-review agent validated integration concerns (Nuxt `app/` auto-scan, test bundling, `@nuxt/content` conflict) with no findings. + +### What Could Improve + +- The initial plan assumed an integration test against the running server. Reality check during implementation (no existing registry test infra) forced a pragmatic pivot to a static config assertion. Next time, scan the target package's test setup before drafting tasks. + +### Tech Debt Created + +- AC-5 (ETag) and AC-6 (deploy-hook purge) intentionally deferred per the issue's recommendation — track them as potential follow-ups if staleness becomes painful. +- `apps/registry` test coverage is still just one file. A real integration harness (`@nuxt/test-utils`) would be valuable once more server routes land (e.g., upcoming raw-docs API track). diff --git a/.please/docs/tracks/active/registry-edge-cache-20260408/spec.md b/.please/docs/tracks/completed/registry-edge-cache-20260408/spec.md similarity index 100% rename from .please/docs/tracks/active/registry-edge-cache-20260408/spec.md rename to .please/docs/tracks/completed/registry-edge-cache-20260408/spec.md