{"_id":"@altimist/did-publisher","_rev":"8-631003f58523fea31f743fbc7d3a7530","name":"@altimist/did-publisher","dist-tags":{"latest":"0.6.0"},"versions":{"0.1.0":{"name":"@altimist/did-publisher","version":"0.1.0","_id":"@altimist/did-publisher@0.1.0","maintainers":[{"name":"altimistdev","email":"developer@altimist.com"}],"dist":{"shasum":"2576279ab41876958b7a829ca395345ed2c05cba","tarball":"https://registry.npmjs.org/@altimist/did-publisher/-/did-publisher-0.1.0.tgz","fileCount":21,"integrity":"sha512-iaQ7QmWZfM/mLOz6CJZTPCbKRvA/byuhTnd17lvN6o93Jr1VQUMAu6kftONUcvpjm2sjl9qO2P9cFSDdoZS+iA==","signatures":[{"sig":"MEUCIHkla6O3NBHkbcszEOifDL3O5TMer0zM+p6jkhISEVPgAiEAgU4tYqoaRf2o/2aPmHAxB8UGbJCyaOyqHmRQQQm3x1o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":22312},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"e0b065dd9e0ab29e4eb6593a51859898219f204c","private":false,"scripts":{"lint":"echo \"(no lint configured)\" && exit 0","test":"vitest run","build":"tsc","test:cov":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"altimistdev","email":"developer@altimist.com"},"_npmVersion":"11.7.0","description":"Mounts altimist-id's Resolver API on altimist-web's resolver-surface paths (DID document, revocations, team issuer keys). Phase 2a of F-010.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^22.10.0","@vitest/coverage-v8":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/did-publisher_0.1.0_1777366019730_0.016383649390675714","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@altimist/did-publisher","version":"0.2.0","_id":"@altimist/did-publisher@0.2.0","maintainers":[{"name":"altimistdev","email":"developer@altimist.com"}],"dist":{"shasum":"d713d5103e478797f771df3f8d54b2d96592c2d4","tarball":"https://registry.npmjs.org/@altimist/did-publisher/-/did-publisher-0.2.0.tgz","fileCount":24,"integrity":"sha512-o1x4r7JvX4ktWGi5v887NYpC53kHfKks6yEvHAJLeIfQx/ucTvonfBvz54aoxaMb8YYpLFPPDosWpj6jTdBpcA==","signatures":[{"sig":"MEUCIBgEb7Wol951+qjoVDlzGCOEuXJCUNL2XxLJWP2kCqk1AiEAxf14rVkUZ02pdIW7ozZ6QFUKc29VE7SPM/SAfKq0WlA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":28231},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"e2f05c576aca772b852de98641ee28442538a2b8","private":false,"scripts":{"lint":"echo \"(no lint configured)\" && exit 0","test":"vitest run","build":"tsc","test:cov":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"altimistdev","email":"developer@altimist.com"},"_npmVersion":"11.7.0","description":"Mounts altimist-id's Resolver API on altimist-web's resolver-surface paths (DID document, revocations, team issuer keys). Phase 2a of F-010.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^22.10.0","@vitest/coverage-v8":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/did-publisher_0.2.0_1777387194724_0.747856382102122","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@altimist/did-publisher","version":"0.3.0","_id":"@altimist/did-publisher@0.3.0","maintainers":[{"name":"altimistdev","email":"developer@altimist.com"}],"dist":{"shasum":"2c2e7365f8f522b596aa6b5eb06454b04fe877d2","tarball":"https://registry.npmjs.org/@altimist/did-publisher/-/did-publisher-0.3.0.tgz","fileCount":24,"integrity":"sha512-uh/YXvYk1XvRX43eLpyNn+T8qKzaqA+dxed9I7fIhvy8Qb+po8aEYLNKJK/fYPEMIgww41QXBAmjDcziaCIdVg==","signatures":[{"sig":"MEYCIQDTi22tWINvK9Fe2TMlzDy26DBVt2hj/9dMDorZ4qGYLQIhALjZjNv4kxHmrj4Cyy/PnR0WyYEw5yG8Lw8RfHNJEMeY","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":29178},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"500f4cdcaf5eab359e840a7038d22b597e566736","private":false,"scripts":{"lint":"echo \"(no lint configured)\" && exit 0","test":"vitest run","build":"tsc","test:cov":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"altimistdev","email":"developer@altimist.com"},"_npmVersion":"11.7.0","description":"Mounts altimist-id's Resolver API on altimist-web's resolver-surface paths (DID document, revocations, team issuer keys). Phase 2a of F-010.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^22.10.0","@vitest/coverage-v8":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/did-publisher_0.3.0_1778582651426_0.695831352608483","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@altimist/did-publisher","version":"0.4.0","_id":"@altimist/did-publisher@0.4.0","maintainers":[{"name":"altimistdev","email":"developer@altimist.com"}],"dist":{"shasum":"2d8ad529f04ae7066174e79b81c97c177e55d7a7","tarball":"https://registry.npmjs.org/@altimist/did-publisher/-/did-publisher-0.4.0.tgz","fileCount":24,"integrity":"sha512-BGi8FTMP6vwvByHB1r1/wefH13a6V8hJwM+/KoBnPtiHDlU4G13sw79Qb4FCcCLiUD+sAtPDtxzdOanAmwGGJA==","signatures":[{"sig":"MEYCIQDebZQleWCcOJNXj4jI3aKRhHL/FKUY1DNByyiTq6o0EAIhAKVKAov5oTKV4cLU7M0cJGVAWygOP17609QYffscrZuV","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":47588},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"f933ee97c103a8aeb013070440f057d7143129d7","private":false,"scripts":{"lint":"echo \"(no lint configured)\" && exit 0","test":"vitest run","build":"tsc","test:cov":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"altimistdev","email":"developer@altimist.com"},"_npmVersion":"11.7.0","description":"Mounts altimist-id's Resolver API on altimist-web's resolver-surface paths (DID document, revocations, team issuer keys). Phase 2a of F-010.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^22.10.0","@vitest/coverage-v8":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/did-publisher_0.4.0_1784725026200_0.30242849188107224","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@altimist/did-publisher","version":"0.5.0","_id":"@altimist/did-publisher@0.5.0","maintainers":[{"name":"altimistdev","email":"developer@altimist.com"}],"dist":{"shasum":"365637bcdc63918cc13298187a27c99c053614c6","tarball":"https://registry.npmjs.org/@altimist/did-publisher/-/did-publisher-0.5.0.tgz","fileCount":24,"integrity":"sha512-4xTeag3u42DF7dqLS7wGrap29gCNHgsnA6GENbtJaUX6tb910LjAm+4djrWuKu3h5nSTseOSRjp+bmQlBbzXbQ==","signatures":[{"sig":"MEQCIBOiVABfmV71xeCrd+LlidV2zdSwn7F/YHKtexhdYhaYAiAYPwrULo5hdBoBYOcofIVHBjQSy615sGxbm2TUmJ5TeQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":57380},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"71c635a150edf4229574117ec9da6bb552d45a87","private":false,"scripts":{"lint":"echo \"(no lint configured)\" && exit 0","test":"vitest run","build":"tsc","test:cov":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"altimistdev","email":"developer@altimist.com"},"deprecated":"Broken in workerd — redirect:\"error\" is refused, every proxied route returns 503. Use 0.5.1.","_npmVersion":"11.6.2","description":"Mounts altimist-id's Resolver API on altimist-web's resolver-surface paths (DID document, revocations, team issuer keys). Phase 2a of F-010.","directories":{},"_nodeVersion":"25.2.1","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^22.10.0","@vitest/coverage-v8":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/did-publisher_0.5.0_1788356968459_0.7998866291296787","host":"s3://npm-registry-packages-npm-production"}},"0.5.1":{"name":"@altimist/did-publisher","version":"0.5.1","_id":"@altimist/did-publisher@0.5.1","maintainers":[{"name":"altimistdev","email":"developer@altimist.com"}],"dist":{"shasum":"1b8bd69014f0fca1d67ce6c8c3474bb0229495a9","tarball":"https://registry.npmjs.org/@altimist/did-publisher/-/did-publisher-0.5.1.tgz","fileCount":24,"integrity":"sha512-a6Al7CYfWFe1TWkONJgy/I3eHtLACK+x1SGM176xikvM+zlWR00DXqfnqlbe50GAG46PWBLLwHXIzk8I20ETVg==","signatures":[{"sig":"MEUCIQDXZsOyBtOPwc6aOOO2DsVxT89HvKVByA5ZGLn5FIcOtQIgfimVSNngR+SwkaHnPv+5pscyFAvm96VpiNOnzHZHFd8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":60645},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"614b2638a96cd5bd7e0b5f016ba4cde5c98d6ff1","private":false,"scripts":{"lint":"echo \"(no lint configured)\" && exit 0","test":"vitest run","build":"tsc","test:cov":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"altimistdev","email":"developer@altimist.com"},"_npmVersion":"11.6.2","description":"Mounts altimist-id's Resolver API on altimist-web's resolver-surface paths (DID document, revocations, team issuer keys). Phase 2a of F-010.","directories":{},"_nodeVersion":"25.2.1","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^22.10.0","@vitest/coverage-v8":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/did-publisher_0.5.1_1788359314304_0.5772252273131009","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@altimist/did-publisher","version":"0.6.0","description":"Mounts altimist-id's Resolver API on altimist-web's resolver-surface paths (DID document, revocations, team issuer keys). Phase 2a of F-010.","private":false,"type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"engines":{"node":">=20.0.0"},"scripts":{"build":"tsc","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","test:cov":"vitest run --coverage","lint":"echo \"(no lint configured)\" && exit 0","prepublishOnly":"npm test && npm run build"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"devDependencies":{"@types/node":"^22.10.0","@vitest/coverage-v8":"^2.1.0","typescript":"^5.7.0","vitest":"^2.1.0"},"gitHead":"61ed4c2f22550e17dd11a9744e65afb44c7dc25b","_id":"@altimist/did-publisher@0.6.0","_nodeVersion":"25.2.1","_npmVersion":"11.6.2","dist":{"integrity":"sha512-ftCQ/uquGA0ldhrnAl1jczVN7m5PNI+C0DnUlto0yCjxWz47Xju1ukcMNmbMYadhZnuYXP01YwP1OetJ668HvA==","shasum":"3309bccb9bb9a8b63913b177ec69545f4cd4f035","tarball":"https://registry.npmjs.org/@altimist/did-publisher/-/did-publisher-0.6.0.tgz","fileCount":24,"unpackedSize":70976,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBSpXZz3GKOJe9YTfzLE9xItvlhLOcy/QsrIhxclFYDYAiAuqnh6hsFjM9az7usydoSlfbA3nimG8uuZtIznJ0D6RA=="}]},"_npmUser":{"name":"altimistdev","email":"developer@altimist.com"},"directories":{},"maintainers":[{"name":"altimistdev","email":"developer@altimist.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/did-publisher_0.6.0_1788788851791_0.26450180497320663"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-28T08:46:59.566Z","modified":"2026-09-07T13:47:32.125Z","0.1.0":"2026-04-28T08:46:59.858Z","0.2.0":"2026-04-28T14:39:54.868Z","0.3.0":"2026-05-12T10:44:11.578Z","0.4.0":"2026-07-22T12:57:06.366Z","0.5.0":"2026-09-02T13:49:28.594Z","0.5.1":"2026-09-02T14:28:34.486Z","0.6.0":"2026-09-07T13:47:31.930Z"},"description":"Mounts altimist-id's Resolver API on altimist-web's resolver-surface paths (DID document, revocations, team issuer keys). Phase 2a of F-010.","maintainers":[{"name":"altimistdev","email":"developer@altimist.com"}],"readme":"# @altimist/did-publisher\n\nProxy [altimist-id](https://github.com/altimist/altimist-id)'s Resolver API to the public did:web URLs (`<handle>.altimist.com/.well-known/did.json`, `altimist.com/users/<handle>/did.json`, `altimist.com/.well-known/revocations.json`, `altimist.com/.well-known/team-issuers/<team>.json`). Two consumption shapes:\n\n- **`routeResolverRequest()`** — single fetch handler dispatcher for Cloudflare Workers / Bun.serve / Deno.serve. **The recommended path** ([ADR-012](https://github.com/altimist/altimist-strategy/blob/main/decisions/ADR-012-adopt-separate-routing-layer-for-resolver-surface.md) Option W).\n- **`didJsonHandler` / `revocationsHandler` / `teamIssuersHandler`** — Next.js App Router route-handler factories. v0.1 API; retained for any consumer that wants to mount per-route handlers under file-based routing.\n\nDecision rationale in [ADR-011](https://github.com/altimist/altimist-strategy/blob/main/decisions/ADR-011-publish-did-via-altimist-web-runtime-proxy.md) (original placement on `altimist-web`, never deployed) and [ADR-012](https://github.com/altimist/altimist-strategy/blob/main/decisions/ADR-012-adopt-separate-routing-layer-for-resolver-surface.md) (Cloudflare Worker placement, current direction).\n\n## Install\n\n```bash\nnpm install @altimist/did-publisher\n```\n\n## Usage — Cloudflare Workers (recommended)\n\n```ts\n// src/index.ts\nimport { routeResolverRequest } from '@altimist/did-publisher';\n\nexport default {\n  async fetch(request: Request, env: Env): Promise<Response> {\n    const response = await routeResolverRequest(request, {\n      origin: env.ALTIMIST_ID_ORIGIN,    // e.g. \"https://altimist.id\"\n      apex: env.ALTIMIST_ID_APEX,        // \"altimist.com\" / \"staging.altimist.com\"\n      payProxySecret: env.FINTERNET_PAY_PROXY_SECRET, // optional (F-024); omit to fall back to platform IP\n      trustCloudflareHeaders: true, // ONLY when this genuinely IS a CF Worker behind Cloudflare (see below)\n    });\n    return response ?? new Response('Not Found', { status: 404 });\n  },\n};\n```\n\n`routeResolverRequest()` returns:\n- A `Response` for the resolver-surface URLs (see the route table below).\n- `null` for everything else — the caller decides what to do (404, fall through to another origin, etc).\n\n## Usage — Next.js App Router (v0.1 API)\n\nIn a Next.js project that owns `*.altimist.com`, mount three route handlers:\n\n```ts\n// app/.well-known/did.json/route.ts\nimport { didJsonHandler } from '@altimist/did-publisher';\n\nexport const GET = didJsonHandler({\n  origin: process.env.ALTIMIST_ID_URL!,        // e.g. \"https://altimist.id\"\n  apex: process.env.RESOLVER_APEX || 'altimist.com',\n});\n```\n\n```ts\n// app/.well-known/revocations.json/route.ts\nimport { revocationsHandler } from '@altimist/did-publisher';\n\nexport const GET = revocationsHandler({\n  origin: process.env.ALTIMIST_ID_URL!,\n});\n```\n\n```ts\n// app/.well-known/team-issuers/[team]/route.ts\nimport { teamIssuersHandler } from '@altimist/did-publisher';\n\nexport const GET = teamIssuersHandler({\n  origin: process.env.ALTIMIST_ID_URL!,\n});\n```\n\n## What each route does\n\n| Path | Behaviour | Cache | CORS |\n|---|---|---|---|\n| `<handle>.<apex>/.well-known/did.json` | Proxies `${origin}/api/resolver/did/<handle>` | altimist-id's, forwarded (measured 2026-09-07: `public, max-age=0, s-maxage=60, must-revalidate`) | altimist-id's, forwarded |\n| `<apex>/users/<handle>/did.json` | Proxies `${origin}/api/resolver/did/<handle>?form=path` (F-011 path-form DID hosting) | altimist-id's, forwarded | altimist-id's, forwarded |\n| `<apex>/.well-known/revocations.json` | Proxies `${origin}/api/resolver/revocations` | altimist-id's, forwarded (measured 2026-09-07: `public, max-age=0, s-maxage=30, must-revalidate`) | altimist-id's, forwarded |\n| `<apex>/.well-known/team-issuers/<team>.json` | Proxies `${origin}/api/resolver/team-issuers/<team>` | altimist-id's, forwarded (measured 2026-09-07: `public, max-age=0, s-maxage=300, must-revalidate`) | altimist-id's, forwarded |\n| `<handle>.<apex>/finternet-pay/v1/descriptors` (POST) | Proxies `${origin}/api/pay/<handle>/descriptors` — the [altimist-id F-024](https://github.com/altimist/altimist-id/blob/main/docs/specs/F-024-finternet-pay-send-only-resolution.md) signed payment-descriptor endpoint. Body forwarded verbatim; `x-finternet-pay-proxy` + `CF-Connecting-IP` forwarded only when **both** `payProxySecret` and `trustCloudflareHeaders` are set (and the request carries `CF-Connecting-IP`). Non-POST → `405` locally (never proxied) | `no-store` (never cached — money path, at any status, including guards/405) | none — see below |\n| `<apex>/users/<handle>/finternet-pay/v1/descriptors` (POST) | Path-form of the above (mirrors F-011 path-form DID hosting) | `no-store` | none |\n\n> **`trustCloudflareHeaders` — read before setting.** `CF-Connecting-IP` is only authentic when Cloudflare's own edge terminates the connection: CF strips or overwrites any client-supplied value before the Worker sees it. Set `trustCloudflareHeaders: true` **only** in a deployment that is provably a genuine Cloudflare Worker sitting directly behind Cloudflare's network (like [`altimist-com-router`](https://github.com/altimist/altimist-com-router)). **Never** set it in a Bun/Deno (or any) self-hosted deployment reachable directly from the public internet — there an attacker can forge `CF-Connecting-IP`, and with a matching `payProxySecret` the proxy would forward that forged IP as genuine, which altimist-id would then trust. Left unset (the default) the header pair is never forwarded and altimist-id safely falls back to its platform-observed IP.\n\n`<apex>` is configured per environment (`altimist.com` for production, `staging.altimist.com` for staging). Handle URLs require a single-label subdomain (`patrick.altimist.com`); apex paths require the host to be exactly the apex (returns 404 otherwise).\n\n## Cache policy is altimist-id's, and is forwarded rather than restated\n\n**This package sets no cache policy of its own** (v0.5.0). Every resolver\nresponse carries `Cache-Control` verbatim from altimist-id, whatever that is at\nthe time; the \"currently\" values in the table above are altimist-id's, quoted\nfor orientation, not a contract this package enforces. The one exception is the\nFinternetPay money path, which is unconditionally `no-store` at any status.\n\nIt was not always so, and the reason is worth keeping. Up to v0.4.0 this\npackage held its own copies of the three cache strings. altimist-id then\nremoved `stale-while-revalidate=86400` from did.json — cache purge is a\nconfirmed no-op in production, so swr let the CDN serve a **revoked device\nkey** to a third-party verifier for up to 24 hours — shipped it, and verified\nit on its own route. The header a verifier actually received never changed,\nbecause `DID_CACHE` here overrode it. A second copy of a policy is drift by\nconstruction; forwarding removes the class rather than the instance.\n\n**This did not by itself close the revocation-staleness hole, and altimist-id\nhas since closed it.** When v0.5.0 shipped, altimist-id still sent\n`stale-while-revalidate=86400` on `revocations.json`, which does not deliver\nthe **60s upper bound on revocation propagation** F-010's non-functional AC\nstates when the origin is degraded — and because the old constant here held\nthe same string, forwarding changed nothing on that route. It became a\none-line change in altimist-id needing no publish here, which was the point,\nand that change has landed: measured 2026-09-07, none of the three read\nroutes sends `stale-while-revalidate` at all.\n\nTwo consequences worth knowing:\n\n- A `Cache-Control` is forwarded at **any** status, not only `200`. altimist-id\n  answers an unknown handle with an explicit `no-store`, and dropping that is\n  worse than passing it on.\n- Responses this package **generates itself** carry no cache header at all: the\n  no-handle and wrong-host guards, and the synthesised `503` below.\n\n## CORS policy is altimist-id's too, and is forwarded the same way\n\n**This package sets no CORS policy of its own** (v0.6.0). Every proxied\nresolver response carries upstream's `Access-Control-*` headers verbatim —\ntoday `access-control-allow-origin: *`, `access-control-allow-methods:\nGET, HEAD, OPTIONS` and `access-control-max-age: 86400`.\n\nThis is the second half of the same row-51 defect, and it was live in\nproduction until v0.6.0. Measured 2026-09-07:\n\n```\nhttps://altimist.id/api/resolver/did/rich       200, access-control-allow-origin: *\nhttps://rich.altimist.com/.well-known/did.json  200, no ACAO, x-alt-cache: miss\n```\n\n`x-alt-cache: miss` proves the response was built fresh, so it was neither a\nstale cache nor deploy lag. The response builder rebuilt the `Response` from\nscratch with a two-header allowlist — `Content-Type` plus `Cache-Control` —\nand dropped every other upstream header silently. altimist-id had been\nsending the CORS headers all along and they never reached a caller, so **no\nbrowser on any other origin could resolve an Altimist DID**: a cross-origin\n`fetch` without an `Access-Control-Allow-Origin` is unreadable, and did:web\nresolution is inherently cross-origin.\n\nThe rules mirror the cache-policy ones exactly:\n\n- Collected by **prefix** (`access-control-`), not by a list of header names.\n  A named list is what caused the bug; a second one would only move it —\n  altimist-id adding `Access-Control-Expose-Headers` would arrive as another\n  silent drop.\n- Forwarded **unvalidated**, including a policy this package would consider\n  wrong. Filtering or correcting upstream's CORS decisions would be a second\n  opinion about them, which is the class row 51 is about.\n- **Nothing is invented.** In particular there is no\n  `Access-Control-Allow-Credentials`: it is illegal per the Fetch standard\n  alongside the `*` altimist-id sends, and a browser rejects such a response\n  rather than relaxing.\n- Forwarded at **any** status, so a browser can read altimist-id's own 404.\n- Responses this package **generates itself** carry no CORS header, for the\n  same reason they carry no cache header — there is no upstream response to\n  forward one from. So a cross-origin caller asking a host that carries no\n  handle at all (the apex, a multi-label host) sees a CORS failure rather\n  than a readable `404`.\n- **Nothing but the `access-control-` family rides along.** altimist-id is a\n  Next.js app and sends a `Vary` naming four RSC-internal headers, plus\n  `X-Powered-By`; neither means anything on a did:web document.\n- The money path gets **no** CORS headers at any status. altimist-id sends it\n  none, it is a `POST` needing a preflight this package does not answer, and\n  granting a cross-origin read of a payment descriptor is altimist-id's\n  decision to make, not a proxy's to infer.\n\n> **Known gap, in altimist-id rather than here.** Measured 2026-09-07,\n> altimist-id sends **no** `Access-Control-*` on its resolver 404s\n> (`/api/resolver/did/<unknown>` answers `404` with `Cache-Control` and\n> nothing else). Forwarding faithfully means a cross-origin browser resolving\n> an unknown handle still sees a CORS failure instead of a readable\n> `{\"error\":\"unknown handle\"}`. Closing that is a change to altimist-id's\n> resolver routes; this package will forward it the day it ships, with no\n> publish needed here.\n\n## Failure modes\n\n- **Upstream 4xx** (404 unknown handle, etc.): pass through with the same status and upstream's `Cache-Control` and `Access-Control-*` if it sent them.\n- **Upstream 3xx**: returns `503`. Redirects are never followed — \"upstream\" means the configured `origin` and nothing else, because since v0.5.0 upstream also chooses the cache policy. (`redirect: \"manual\"` plus a status check, not `redirect: \"error\"`, which workerd refuses.)\n- **Upstream 5xx or timeout**: returns `503` so consumer apps fail-closed (DID resolution unavailable). No cache header and no CORS header — a fail-closed response this package invented carries no policy it invented.\n- **A body that never fully arrives**: returns `503`. A partial read is a transport failure, never a `200 {}`.\n\n## Cloudflare cache purge\n\naltimist-id is responsible for purging the edge cache on every mutation (device enrolment, revocation, etc.). This package is read-only — see [altimist-id's `src/lib/cache-purge.ts`](https://github.com/altimist/altimist-id/blob/main/src/lib/cache-purge.ts) for the purge side.\n\n## License\n\nUNLICENSED — internal Altimist.\n","readmeFilename":"README.md"}