@jimhoyd/urlcode 0.4.0-alpha.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +17 -19
  2. package/.claude/skills/urlcode-operations/SKILL.md +9 -9
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CONTRIBUTING.md +20 -16
  5. package/README.md +59 -64
  6. package/ROADMAP.md +66 -376
  7. package/dist/BUILD-MANIFEST.json +26 -25
  8. package/dist/agents-guide.js +6 -6
  9. package/dist/authoring.js +15 -1
  10. package/dist/build-static.js +2 -0
  11. package/dist/capability-query.js +0 -1
  12. package/dist/catalog.js +0 -1
  13. package/dist/cli.js +25 -9
  14. package/dist/config.js +1 -1
  15. package/dist/explain-cli.js +4 -2
  16. package/dist/explain.js +8 -2
  17. package/dist/extensions.js +1 -1
  18. package/dist/http-response.js +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/init-with.js +36 -11
  21. package/dist/manifest.js +8 -2
  22. package/dist/mcp-authoring.js +4 -4
  23. package/dist/mcp.js +1 -1
  24. package/dist/policies/cache.js +2 -2
  25. package/dist/policies.js +3 -1
  26. package/dist/prerender.js +4 -0
  27. package/dist/project-dependencies.js +305 -0
  28. package/dist/readiness.js +3 -0
  29. package/dist/route-diff.js +12 -5
  30. package/dist/runtime.js +1 -1
  31. package/dist/trusted-functions.js +4 -5
  32. package/dist/types/authoring.d.ts +9 -1
  33. package/dist/types/capability-query.d.ts +0 -1
  34. package/dist/types/catalog.d.ts +0 -4
  35. package/dist/types/config.d.ts +1 -9
  36. package/dist/types/explain.d.ts +6 -1
  37. package/dist/types/extensions.d.ts +1 -1
  38. package/dist/types/http-response.d.ts +0 -1
  39. package/dist/types/index.d.ts +1 -0
  40. package/dist/types/init-with.d.ts +7 -13
  41. package/dist/types/manifest.d.ts +5 -2
  42. package/dist/types/project-dependencies.d.ts +78 -0
  43. package/dist/types/readiness.d.ts +2 -0
  44. package/dist/types/trusted-functions.d.ts +1 -4
  45. package/dist/types/types.d.ts +8 -1
  46. package/dist/types.js +8 -1
  47. package/dist/typescript-authoring.js +18 -7
  48. package/docs/AI-AUTHORING.md +15 -7
  49. package/docs/ASSETS.md +2 -1
  50. package/docs/AUTH-BACKUP.md +32 -0
  51. package/docs/AWS.md +9 -0
  52. package/docs/BEST-PRACTICES.md +17 -9
  53. package/docs/CAPABILITIES.md +1 -1
  54. package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
  55. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
  56. package/docs/CI.md +8 -3
  57. package/docs/CODEBASE-AUDIT-2026-09-20.md +284 -0
  58. package/docs/COMPOSING-A-SITE.md +278 -0
  59. package/docs/DEVELOPMENT-PIPELINE.md +270 -0
  60. package/docs/EXTENSIONS.md +51 -11
  61. package/docs/FRAMEWORK.md +67 -48
  62. package/docs/FUNCTION-SECURITY.md +44 -0
  63. package/docs/INSTALL.md +13 -8
  64. package/docs/LOCAL-DEVELOPMENT.md +3 -0
  65. package/docs/MIDDLEWARE.md +10 -4
  66. package/docs/OPEN-DECISIONS.md +224 -212
  67. package/docs/OPERATIONAL-PROOF.md +3 -3
  68. package/docs/OPERATIONS.md +3 -3
  69. package/docs/POLICIES.md +13 -5
  70. package/docs/PRERENDER.md +23 -11
  71. package/docs/PROJECT-DIRECTION.md +3 -3
  72. package/docs/READINESS.md +10 -5
  73. package/docs/README.md +20 -44
  74. package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
  75. package/docs/RELEASE-0.4.1.md +73 -0
  76. package/docs/RELEASE-READINESS.md +6 -6
  77. package/docs/RELEASE-SECURITY.md +96 -181
  78. package/docs/RESILIENCE.md +3 -3
  79. package/docs/ROUTING.md +3 -4
  80. package/docs/SECURITY-AUDIT.md +2 -2
  81. package/docs/SPECIFICATION.md +12 -6
  82. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +6 -5
  83. package/docs/SPIKE-BUSINESS-SUITE.md +14 -6
  84. package/docs/SPIKE-CORE-LAYERING.md +49 -18
  85. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +7 -5
  86. package/docs/STARTERS.md +17 -5
  87. package/docs/STATIC.md +14 -3
  88. package/docs/TOOLING.md +10 -7
  89. package/docs/TYPESCRIPT-AUTHORING.md +6 -1
  90. package/docs/VERCEL.md +10 -2
  91. package/docs/VERSION-ALIGNMENT.md +76 -201
  92. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
  93. package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +102 -0
  94. package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +15 -5
  95. package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +15 -3
  96. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
  97. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
  98. package/docs/archive/2026-09-19/ROADMAP.md +387 -0
  99. package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +11 -0
  100. package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +25 -14
  101. package/docs/archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md +365 -0
  102. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +778 -0
  103. package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +12 -2
  104. package/docs/archive/README.md +28 -0
  105. package/docs/policies/agents.md +1 -1
  106. package/docs/policies/compression.md +3 -2
  107. package/docs/policies/security.md +3 -2
  108. package/docs/yaml/functions.md +10 -2
  109. package/docs/yaml/middleware.md +5 -3
  110. package/examples/assets/example.yaml +1 -1
  111. package/examples/cookbook/middleware/envelope.mjs +4 -2
  112. package/examples/cookbook/route-index.json +1 -1
  113. package/examples/cookbook/routes/middleware.yaml +1 -1
  114. package/examples/prerender/README.md +14 -6
  115. package/examples/prerender/functions/page.mjs +4 -2
  116. package/examples/prerender/middleware/template.mjs +1 -1
  117. package/examples/prerender/prerender.mjs +1 -1
  118. package/examples/prerender/urlcode.yaml +8 -4
  119. package/llms-full.txt +503 -88
  120. package/llms.txt +6 -4
  121. package/package.json +27 -4
  122. package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
  123. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +17 -19
  124. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +9 -9
  125. package/recipes/authenticated-json-api/README.md +4 -3
  126. package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
  127. package/recipes/authenticated-json-api/recipe.yaml +1 -1
  128. package/recipes/contact-form/functions/contact.mjs +2 -1
  129. package/recipes/contact-form/recipe.yaml +2 -2
  130. package/recipes/cors-api/README.md +2 -2
  131. package/recipes/cors-api/recipe.yaml +1 -1
  132. package/recipes/health-page/README.md +1 -1
  133. package/recipes/json-api/README.md +1 -1
  134. package/recipes/json-api/recipe.yaml +3 -3
  135. package/recipes/middleware/README.md +8 -4
  136. package/recipes/middleware/middleware/envelope.mjs +4 -2
  137. package/recipes/protected-download/README.md +1 -1
  138. package/recipes/protected-download/recipe.yaml +1 -1
  139. package/recipes/static-plus-api/README.md +2 -2
  140. package/recipes/static-plus-api/public/index.html +1 -1
  141. package/recipes/static-plus-api/recipe.yaml +1 -1
  142. package/recipes/static-plus-api/urlcode.yaml +1 -1
  143. package/recipes/typescript/recipe.yaml +4 -4
  144. package/skills/urlcode/SKILL.md +6 -6
  145. package/starters/default/AGENTS.md +6 -6
  146. package/docs/SPIKE-LAMBDA-COMPILE.md +0 -201
  147. package/docs/SPIKE-MONOREPO.md +0 -322
@@ -1,181 +1,96 @@
1
- # Candidate and release security process
2
-
3
- URLCode 0.3.0 is the Apache-2.0 self-hosted baseline; licensing is defined
4
- in [LICENSE](../LICENSE). Two workflows share one audited build path and differ
5
- only in what they do with its output:
6
-
7
- - `candidate.yml` is dispatched manually on main and retains a **signed build
8
- candidate** for 30 days without publishing it. Use it to review a commit
9
- before tagging it.
10
- - `release.yml` runs on a `v*` tag whose commit is already on main, and publishes
11
- a GitHub release. Publication to npm and GHCR is opt-in per repository variable
12
- (`PUBLISH_NPM`, `PUBLISH_CONTAINER`), so a release can be artifacts-only.
13
-
14
- Neither workflow is a statement that a release is production-ready; see
15
- [release readiness](RELEASE-READINESS.md).
16
-
17
- ## Publishing an alpha
18
-
19
- An alpha such as `0.4.0-alpha.1` follows the same path: tag `v0.4.0-alpha.1`
20
- on a commit that is already on main. The release workflow publishes to npm
21
- only when the repository variable `PUBLISH_NPM` is `true` and the npm trusted
22
- publisher for this repository and `release.yml` exists; otherwise the run is
23
- artifacts-only (a GitHub release with the signed tarball). Publish order is
24
- core ui auth admin, because the extension packages declare
25
- `@jimhoyd/urlcode >=0.4.0-alpha.1 <0.5.0` and must resolve the core alpha.
26
-
27
- 1. Report vulnerabilities privately through [GitHub advisories](https://github.com/jimhoyd-com/urlcode/security/advisories/new).
28
- The maintainer triages impact, confirms affected exact revisions, coordinates a
29
- fix/retest privately, and publishes an advisory with upgrade guidance when safe.
30
- Current reviewed main is the security support baseline; no guaranteed SLA/backports.
31
- 2. Change dependencies through protected PRs. Review upstream provenance/advisories,
32
- lockfile integrity and tests. Direct dependencies are exact, npm installs use
33
- `npm ci`, CI actions use full commit SHAs, and container bases use SHA-256 digests.
34
- Dependabot proposes updates; it does not authorize merging. Never silently
35
- refresh dependencies during a candidate build.
36
- 3. After protected main checks pass, a maintainer manually dispatches
37
- `.github/workflows/candidate.yml` **on main**, or pushes a `v<version>` tag to
38
- run `.github/workflows/release.yml`. The release workflow refuses a tag whose
39
- commit is not an ancestor of main, refuses a tag that disagrees with
40
- `package.json`, and refuses to build a private or unlicensed package. It reads and validates the digest-pinned Node image directly from Dockerfile,
41
- installs without lifecycle scripts, verifies, package-tests and runs local drills.
42
- It packs the runtime and creates a CycloneDX dependency SBOM plus a manifest
43
- recording source commit, lockfile hash, engine versions and artifact hashes.
44
- The package contains `dist/`, produced inside that build from the tagged
45
- TypeScript sources by Node's type stripping (`scripts/build.ts`); `dist` is
46
- never committed. The manifest records the Node version that stripped it, the
47
- locked TypeScript version and a SHA-256 per emitted file
48
- (`dist/BUILD-MANIFEST.json`), so a download can be verified by running
49
- `npm run build` on the tagged commit with that Node version and comparing its
50
- `dist/` file by file. The build strips types and rewrites specifier
51
- extensions; it never bundles, minifies or transforms syntax, so every line of
52
- `dist/x.js` is the corresponding line of `src/x.ts`. CI's `build-fidelity`
53
- job builds twice and diffs the trees, so the transform is known to be
54
- deterministic before a tag is cut.
55
- 4. The pinned official `actions/attest` action signs provenance for the package,
56
- SBOM and manifest using short-lived GitHub OIDC/Sigstore credentials. No long-lived
57
- signing key is stored. Signing permissions exist only in this manual job; build
58
- commands run in a container without passing GitHub tokens. Candidate files are
59
- retained as GitHub Actions artifacts for 30 days. A release additionally attaches
60
- them to the GitHub release, and publishes to npm and to GHCR when those
61
- repository variables are enabled.
62
-
63
- **npm publication holds no credential.** The registry is configured with a
64
- trusted publisher naming this repository and `release.yml`, so the publish
65
- step exchanges the job's OIDC identity for a credential that lives for the
66
- length of one publish. There is no npm token in the repository's secrets to
67
- leak, revoke or rotate, and a fork or another workflow cannot publish under
68
- this package's name. Provenance is generated on that same identity, so
69
- `--provenance` is not passed and its absence is not a downgrade.
70
-
71
- A bearer token would silently take precedence over this exchange, so the
72
- publish step must reference none; `test/release.test.ts` fails if one
73
- reappears in it, and checks the npm and Node floors below which the exchange
74
- is not attempted at all.
75
- 5. Download the candidate for the intended commit and verify **each file**, e.g.
76
- `gh attestation verify jimhoyd-urlcode-0.3.0.tgz --repo jimhoyd-com/urlcode --signer-workflow jimhoyd-com/urlcode/.github/workflows/candidate.yml --source-ref refs/heads/main --source-digest APPROVED_COMMIT_SHA`
77
- (use `release.yml` as the signer workflow for a tagged release).
78
- Check the verified provenance's source commit against the approved commit, and
79
- compare package/SBOM hashes with the signed manifest. A signature establishes
80
- provenance, not safety, reproducible bytes or production approval. See
81
- [GitHub verification](https://cli.github.com/manual/gh_attestation_verify) and
82
- [the official attestation action](https://github.com/actions/attest).
83
- 6. Before registry publication, assign patch/release ownership and retention, and
84
- document version/support policy. Before claiming hostile multi-tenant or
85
- deployment-specific readiness, close independent-review and deployment gates. Keep last-good verified artifacts and compatible policies;
86
- rehearse rollback. Never overwrite a published artifact/version or downgrade a
87
- management writer past its audit/security baseline.
88
-
89
- The SBOM describes npm dependencies; it is not a complete OS/container SBOM. Hosted
90
- runners and action runtimes remain platform-controlled. Digest pins improve supply
91
- chain integrity but do not prove byte-for-byte reproducibility or engine safety.
92
- Signing verification must be demonstrated on a successful main workflow run before
93
- claiming a candidate has been signed. Workflow definition alone is not that evidence.
94
- `release.yml` ran successfully for the first time publishing `v0.4.0-alpha.1`
95
- (2026-09-18), after the fixes below; treat any repository whose workflow has not
96
- had a real successful tagged run the same way this one was treated before that.
97
-
98
- ## What broke on every first release, and why
99
-
100
- Four repositories (core, ui, auth, admin) each cut their first tagged release in
101
- the same session. Every one hit a subset of the same bugs, because each
102
- `release.yml` was written and reviewed but never actually run end-to-end against
103
- a real tag before. None of this is repository-specific; check for all of it
104
- before trusting an unexercised release workflow:
105
-
106
- - **`npm ci` on the bare runner's root-owned npm.** A build that runs entirely
107
- inside a pinned Docker image never calls `actions/setup-node`, so a later
108
- `npm install --global npm@11.5.1` (needed for the trusted-publishing floor)
109
- hits the runner's preinstalled, root-owned npm and fails `EACCES`. Add
110
- `actions/setup-node` before any step that installs global npm packages, even
111
- if the main build never touches the runner's own Node.
112
- - **The floor check must run after the pin, not before.** A guard asserting
113
- "npm ≥ 11.5.1" is useless directly after `setup-node` with `node-version: '22'`,
114
- which bundles npm ~10.9.x — it can never pass. The floor only means something
115
- once the publish step's own `npm install --global npm@11.5.1` has actually run.
116
- - **`npm publish` refuses an unqualified prerelease.** `You must specify a tag
117
- using --tag when publishing a prerelease version.` npm's safety default is
118
- `latest`; a prerelease must derive an explicit dist-tag from its version
119
- (`0.1.0-alpha.1` → `alpha`, anything without a `-` → `latest`) and pass
120
- `--tag`. This path is only exercised by a package's *first* prerelease, so it
121
- silently sat broken in every repository until each hit it for the first time.
122
- - **`npm pack --pack-destination candidate` needs `candidate/` to exist first.**
123
- npm does not create the destination directory; `mkdir -p candidate` first.
124
- - **A private repository's unauthenticated `git fetch origin main` cannot work.**
125
- If checkout uses `persist-credentials: false` (correct, for a step that
126
- should not need write access) and the repo is private, `git fetch` fails
127
- `could not read Username for 'https://github.com'` before ever reaching the
128
- version check. Compare the tag against main through the GitHub API instead
129
- (`gh api repos/OWNER/REPO/compare/main...SHA --jq .status`, expecting
130
- `identical` or `behind`) — it needs no credentials and stays read-only. Public
131
- repositories can keep the plain fetch; it works there.
132
- - **`--conditions=development` in `npm run verify`'s test script resolves peers
133
- to source that a real npm install never ships.** The regular CI job symlinks
134
- sibling checkouts in place of `node_modules`, so `./src/*.ts` exists and the
135
- flag is correct there. A release installs real published tarballs of its
136
- peers, which only ever ship `dist/`, so the same flag makes every import of a
137
- peer fail `ERR_MODULE_NOT_FOUND`. Drop the flag for the release-workflow test
138
- invocation specifically (run `node scripts/check-sqlite.mjs` explicitly first,
139
- since bypassing `npm test` skips that pretest hook), and audit any test file
140
- that separately hardcodes the flag in a spawned child process — it has to be
141
- fixed the same way, independently, wherever it appears.
142
- - **A peer-install command with the wrong flag combination is a silent no-op.**
143
- `npm install --no-save --no-package-lock --ignore-scripts --legacy-peer-deps
144
- <peer>@<version>` installed *nothing*, with no error, when the target package
145
- names already appear in `peerDependencies` — `npm ci` earlier reports "added N
146
- packages" as if it worked. Confirm the install actually happened
147
- (`ls node_modules/@scope/*/package.json` and print each version) rather than
148
- trusting the exit code; `--no-save --ignore-scripts <specs>` (no
149
- `--no-package-lock`, no `--legacy-peer-deps`) is the version that works, paired
150
- with `git diff --exit-code -- package.json package-lock.json` to prove nothing
151
- was recorded as a dependency.
152
- - **A version published from an unbuilt checkout is burned forever.** npm never
153
- allows a version to be replaced. `@jimhoyd/urlcode-auth@0.1.0-alpha.1` reached
154
- the registry from something other than the CI workflow (a manual `npm
155
- publish` run before `npm run build` had produced `dist/`), so the published
156
- tarball contained only metadata files and no code. Every consumer's typecheck
157
- failed with `Cannot find module '@jimhoyd/urlcode-auth'` — a real, correct
158
- failure, not a bug in the consumer. The only fix is bumping to a new version
159
- and publishing that instead; nothing can repair or unpublish the bad one.
160
- **Never run `npm publish` by hand outside the release workflow** — the
161
- workflow is the only place that reliably builds before packing.
162
- - **`ENEEDAUTH` on `npm publish` under trusted publishing usually means the
163
- registry-side configuration doesn't exist or doesn't permit direct publish
164
- yet**, not a workflow bug. Trusted publishing needs an entry under the
165
- package's npm settings ("Trusted Publisher") naming the exact GitHub
166
- org/repo and workflow filename, with no environment set unless the workflow
167
- declares one; recent npm UI changes default new configurations to
168
- "stage publish" only; "allow npm publish" (direct publish, which is what
169
- this project's workflows do) must be explicitly enabled too. A wrong
170
- org/repo/workflow match tends to surface as a 404, not `ENEEDAUTH`;
171
- `ENEEDAUTH` is the signature of no matching configuration existing at all.
172
- - **Publish order matters and is easy to get backwards.** Extension packages
173
- declare `@jimhoyd/urlcode >=X <Y` as a peer range; publish core before ui,
174
- auth or admin, or their own release-workflow peer-install step has nothing
175
- real to resolve against.
176
-
177
- None of the above is exotic; all nine bugs were found by actually running each
178
- workflow against a real tag, one release at a time, and reading the actual
179
- failure rather than guessing from the workflow source. Treat "the workflow file
180
- looks right" and "the workflow has actually published successfully once" as two
181
- different, unrelated claims.
1
+ # Candidate and release security
2
+
3
+ The [development pipeline](DEVELOPMENT-PIPELINE.md) is the operational runbook
4
+ for CI selection, release planning, tags, publication and recovery. The
5
+ [September 19 audit](CI-RELEASE-AUDIT-2026-09-19.md) explains the changes;
6
+ [the prior process and failure history](archive/2026-09-19/RELEASE-SECURITY.md)
7
+ is retained as a historical record, not current instructions.
8
+
9
+ The license remains Apache-2.0. A successful release is not an independent
10
+ security assessment, hostile multi-tenant readiness claim or deployment proof.
11
+ See [release readiness](RELEASE-READINESS.md) and [security](../SECURITY.md).
12
+
13
+ ## Source and validation
14
+
15
+ A release must name the checked-out commit, already on main, with successful
16
+ full verification of that exact SHA and CodeQL analysis. Full verification
17
+ means a successful nightly or manual `ci.yml` run across every supported OS/Node
18
+ combination; the compact push/PR matrix alone does not authorize publication. Package manifests,
19
+ lockfile versions and tag names must agree. Published peer floors must exist and
20
+ extension tests must resolve the published packages, not workspace source.
21
+ The candidate builds all package archives once using the digest-pinned Node
22
+ image from Dockerfile; publishers promote those exact signed bytes. The builder
23
+ installs Git from Debian for repository-fixture tests; Git is a test dependency,
24
+ not an addition to the runtime image or npm package. Locked dependencies,
25
+ verification, runtime audit, package installation tests and local drills precede
26
+ packing. Build commands in that container receive no GitHub token.
27
+
28
+ The candidate workflow stores artifacts without publishing. A tagged release
29
+ publishes npm only when `PUBLISH_NPM=true` and core images only when
30
+ `PUBLISH_CONTAINER=true`. Candidate and release artifacts are retained for 90 days, and each package
31
+ GitHub release stores the complete signed candidate bundle. Retries verify and
32
+ reuse original retained or durable bytes; missing originals fail closed. Keep independent last-good artifacts and
33
+ rehearse deployment rollback; Actions retention is not an archival guarantee.
34
+
35
+ ## Identity and provenance
36
+
37
+ The four per-package workflow filenames are stable npm trusted-publisher
38
+ identities. npm publishing uses OIDC, npm 11.5.1 and a supported Node version,
39
+ with no long-lived npm token. Renaming a workflow or changing the repository
40
+ requires a reviewed registry trust migration. Successful preparation does not
41
+ prove the registry-side identity permits direct publication.
42
+
43
+ `actions/attest` signs the candidate files with GitHub OIDC/Sigstore provenance.
44
+ The candidate bundle includes all four archives, dependency SBOM, build manifest,
45
+ train metadata, checksums and Homebrew formula. Each publisher retains that
46
+ bundle and publishes only its selected npm archive. New annotated version tags
47
+ pin the candidate run ID and signed-manifest SHA256. The run ID is also bound
48
+ into the manifest; the digest prevents another attempt of that run from
49
+ substituting different artifacts. The core manifest records source SHA, lockfile hash, Node and
50
+ TypeScript versions and emitted-file hashes. `dist/` is built, never committed.
51
+
52
+ Verify an artifact with `gh attestation verify <tarball> --repo
53
+ jimhoyd-com/urlcode`, and constrain verification to the expected workflow,
54
+ source ref and source digest for the selected release. Compare the source and
55
+ artifact hashes to the intended release, rather than accepting any signed file.
56
+ A signature establishes provenance, not safety or reproducibility. The npm SBOM
57
+ is not a complete OS/container SBOM; hosted runners remain platform-controlled.
58
+
59
+ ## Immutability and channels
60
+
61
+ Never delete/recreate or move a version tag to repair a workflow. A source fix
62
+ requires a new version. Repeating publication of an existing npm version requires
63
+ identical SHA-512 integrity. GitHub assets are compared and missing assets added;
64
+ existing unequal assets are never clobbered. Transient registry errors fail
65
+ closed rather than count as an unpublished version.
66
+
67
+ Alpha versions use npm/GHCR `alpha` and GitHub prerelease classification.
68
+ Explicit stable versions use npm `latest` and normal GitHub releases. Only the
69
+ stable core release advances GitHub `latest`, so the core installer cannot pick
70
+ an extension release. Stable preparation removes Changesets prerelease mode;
71
+ existing alpha artifacts, tags and channel pointers remain unchanged. Mutable npm
72
+ and container channels cannot regress to an older version. Existing core image
73
+ versions are reused only with matching source labels; unlabeled historical
74
+ images require a reviewed migration rather than an inferred identity.
75
+
76
+ The coordinator verifies candidate availability and provenance before creating
77
+ any version tag, then creates one at a time and waits for successful publication
78
+ and consumer-facing registry installability before releasing dependents. Shared publication concurrency avoids cross-version races.
79
+ The active immutable-tag rule blocks release tag updates/deletions with no
80
+ bypass actors; its configuration is in `.github/rulesets/release-tags.json`. No automation needs permission to bypass main checks or approve
81
+ its own PR. A repository-scoped GitHub App is the preferred eventual automation
82
+ identity; a narrowly scoped fine-grained PAT can support a maintainer script.
83
+
84
+ ## Remaining validation
85
+
86
+ Keep full supported-OS/Node coverage and archive-install tests green. The actual
87
+ publish path, OIDC trust for every package, tag protection, GHCR behavior and
88
+ partial-failure recovery must be verified on an authorized release; local tests
89
+ and workflow inspection cannot prove them. The release helpers do not alter
90
+ historical npm channels, GitHub flags, tags or registry artifacts.
91
+
92
+ The coordinated `.3` release proved the existing OIDC identities, but exposed
93
+ missing-artifact retry behavior tracked in #223. New workflows fail closed or
94
+ recover verified durable bytes; old immutable tags retain their original
95
+ workflow code. The new promotion path still needs a future explicitly authorized
96
+ release rehearsal; implementing it does not publish a new version.
@@ -156,6 +156,6 @@ admission/fairness, production metrics/exporters, dedicated slow-reader protecti
156
156
  provider-level mitigation validation and sustained failure/soak testing. These are
157
157
  free-runtime/operator requirements.
158
158
 
159
- Core has no durable store of its own to recover; a future durable-state
160
- extension (such as the planned `urlcode-dynamic-link`) is responsible for its
161
- own management listener, backup and recovery procedure once it exists.
159
+ Core has no durable store of its own to recover; a durable-state extension is
160
+ responsible for its own management listener, backup and recovery procedure. No
161
+ such extension ships today `urlcode-dynamic-link` has been retired.
package/docs/ROUTING.md CHANGED
@@ -84,10 +84,9 @@ any per-visitor session record. Git owns route behavior and code; user-created r
84
84
  their own persistence, backup and export lifecycle.
85
85
 
86
86
  Core has no native handler for this today: the `link` handler that implemented
87
- it was removed. Stored short links are moving to a future
88
- `urlcode-dynamic-link` extension package (mount-based, like `auth`/`admin`,
89
- not yet published); a project needing them declares an `extension` mount once
90
- that package exists.
87
+ it was removed. The `urlcode-dynamic-link` extension package that replaced it
88
+ has been retired and unpublished, so a project needing stored short links owns
89
+ that storage itself.
91
90
 
92
91
  Functions still cannot access databases, the filesystem or network directly.
93
92
  General application state and realtime sessions remain future work.
@@ -85,7 +85,7 @@ Management event status 0 means no response headers were sent before disconnect.
85
85
  An aborted request may have committed a mutation: reconcile record/version before
86
86
  retrying. These logs are best effort, may be dropped under pressure, identify a configured credential ID (or a legacy shared token), and are not a
87
87
  tamper-evident journal. Successful store mutations now have separate transactional
88
- audit records; see [management security](MANAGEMENT-SECURITY.md). A failed sink needs collector/operator recovery;
88
+ audit records; see [management security](archive/2026-09-19/MANAGEMENT-SECURITY.md). A failed sink needs collector/operator recovery;
89
89
  URLCode cannot report failures reliably through the same broken output stream.
90
90
 
91
91
  ## Boundaries checked
@@ -122,7 +122,7 @@ limits. No anonymous code/config upload service is approved by these changes.
122
122
  collection/action scopes, expiry and hot revocation, plus atomic SQLite mutation
123
123
  audits are implemented. Legacy shared tokens remain for compatibility. There is
124
124
  no public user-account system, MFA/SSO, built-in rate limiter, credential issuance
125
- service or external tamper-evident archive. See [management security](MANAGEMENT-SECURITY.md).
125
+ service or external tamper-evident archive. See [management security](archive/2026-09-19/MANAGEMENT-SECURITY.md).
126
126
 
127
127
  **Before claiming operational readiness:** execute sustained mixed-workload soak,
128
128
  backup restoration, disk-full, process-kill, proxy timeout and rollback drills on
@@ -158,10 +158,10 @@ keys preserve repeated values. Headers are forwarded only through explicit maps.
158
158
  ## Stored links
159
159
 
160
160
  Core has no native `link` handler or `dynamicLinks` project flag; both were
161
- removed. Stored short links are moving to a future `urlcode-dynamic-link`
162
- extension package (mount-based, like `auth`/`admin`), not yet published. A
163
- project needing them declares an `extension` mount once that package exists;
164
- there is no in-core replacement or deprecation shim for `link`/`dynamicLinks`.
161
+ removed. The `urlcode-dynamic-link` extension package that replaced them has
162
+ been retired and unpublished, so a project needing stored short links owns that
163
+ storage itself; there is no in-core replacement or deprecation shim for
164
+ `link`/`dynamicLinks`.
165
165
 
166
166
  ## Functions
167
167
 
@@ -202,7 +202,11 @@ entry may likewise be a string, normalized to `{source: <string>}`. Only the
202
202
  long form exists after loading, so `routes`, `audit`, `explain`, revision hashes
203
203
  and the field reference describe the expansion.
204
204
 
205
- ES modules only (`.mjs` or `.js`, independent of Node package settings).
205
+ ES modules only. `.mjs` is loaded as ESM in both modes. A `sandbox: true` route
206
+ also reads `.js` as ESM independently of Node package settings, but a trusted
207
+ route imports `.js` through Node's own loader, so an ESM `.js` module needs the
208
+ nearest `package.json` to declare `"type": "module"`; under `"type": "commonjs"`
209
+ it fails activation. Prefer `.mjs` for modules that must work in either mode.
206
210
  [Build-time TypeScript authoring](TYPESCRIPT-AUTHORING.md) can produce these
207
211
  JavaScript modules in a separate output project; serving does not transpile them.
208
212
  The build never imports application code into Node, uses fixed compiler settings,
@@ -329,7 +333,9 @@ are local Git-owned examples; they grant no capabilities. [Build-time TypeScript
329
333
  is separate from runtime execution.
330
334
 
331
335
  The [tooling SDK and optional local MCP](TOOLING.md) inspect and validate without
332
- executing handlers or reading binding values. [Provider conformance](PROVIDER-VERIFICATION.md)
336
+ executing handlers or reading binding values. MCP defaults to inspection; the
337
+ explicit `--allow-authoring` option enables bounded project writes, separately
338
+ from host-file loading and grants. [Provider conformance](PROVIDER-VERIFICATION.md)
333
339
  distinguishes local adapter replay from actual deployment observations; no real
334
340
  provider deployment is implied by CI. [Proxy and signal egress](EGRESS.md) requires
335
341
  external revision-pinned operator grants and bounded host-owned transport;
@@ -32,7 +32,7 @@ Before building the application or a new harness, investigate:
32
32
  - Agent productivity studies, framework comparison applications and standard
33
33
  CRUD/full-stack benchmark applications.
34
34
  - URLCode's existing [agent harness](../benchmarks/agent/README.md), authoring
35
- evals, [next-steps plan](NEXT-STEPS.md), and runtime benchmarks in
35
+ evals, [next-steps plan](archive/2026-09-19/NEXT-STEPS.md), and runtime benchmarks in
36
36
  `benchmarks/routing.ts`, `benchmarks/bulk.ts` and
37
37
  `benchmarks/sandbox-vs-trusted.ts`.
38
38
 
@@ -198,8 +198,9 @@ For each verified, actionable issue include:
198
198
  - Acceptance criteria and related/duplicate issue links.
199
199
 
200
200
  Use the owning repository's issue template and the ownership map in
201
- [AGENTS.md](../AGENTS.md): core/runtime issues belong here, public documentation
202
- in `urlcode-docs`, and extension defects in the corresponding extension repository.
201
+ [AGENTS.md](../AGENTS.md): core/runtime issues belong here, documentation
202
+ included -- `urlcode-docs` is deleted and cannot accept issues -- and extension
203
+ defects in the corresponding extension repository.
203
204
  Update existing issues with new evidence rather than duplicating them. Track
204
205
  prepared, filed, duplicate and unverified dispositions in the findings report;
205
206
  file verified issues and retain unresolved hypotheses as explicitly unverified
@@ -220,8 +221,8 @@ current contract when documents and historical benchmark assumptions differ.
220
221
 
221
222
  Preserve Agent A's baseline before editing guidance. Then propose the smallest
222
223
  authoritative set of changes that makes the principle clear to people and agents,
223
- including when custom JavaScript is appropriate. Public authoring guidance belongs
224
- in `urlcode-docs`; contributor records and spikes remain here. Do not copy the rule
224
+ including when custom JavaScript is appropriate. Public authoring guidance and
225
+ contributor records both belong here, now that `urlcode-docs` is deleted. Do not copy the rule
225
226
  everywhere or expose the experimental prompt as product guidance by accident.
226
227
 
227
228
  ## Phase 6 — Fastify comparison
@@ -1,17 +1,25 @@
1
1
  # URLCode business suite spike
2
2
 
3
+ > Review update, 2026-09-19: Current status: an unapproved candidate list, gated on observed repetition
4
+ > and benchmark evidence under PROJECT-DIRECTION.md. The short-link products
5
+ > are retired; the seven-product recommendation below is historical, not agreed
6
+ > current scope. No business-suite implementation is implied.
7
+
8
+
3
9
  Date: 2026-09-18. Status: proposal, not an implemented contract or production claim.
4
10
  Core inspected at `50790d3` (0.4.0-alpha.1), plus local auth, admin, UI and
5
11
  shortener source/status files. Competitor research below is a documentation
6
12
  review, not hands-on benchmarking. Features and commercial packaging can change.
7
13
 
8
14
  > **Update:** this proposal was written when `link`/`LinkStore` was still a
9
- > native core feature. Core no longer has that API — it was extracted to the
10
- > separate [`urlcode-dynamic-link`](https://github.com/jimhoyd-com/urlcode-dynamic-link)
11
- > package. References below to core owning link storage (e.g. "Reuse core's
12
- > LinkStore", "Retain core link semantics", the `core LinkStore -> short`
13
- > dependency line) describe the pre-extraction state this spike was proposing
14
- > against, not current core.
15
+ > native core feature. Core no longer has that API — it was extracted to a
16
+ > separate `urlcode-dynamic-link` package, which has since been retired,
17
+ > unpublished and deleted (September 2026), along with the `urlcode-short`
18
+ > shortener this document proposes. Those two products are no longer planned.
19
+ > References below to core owning link storage (e.g. "Reuse core's LinkStore",
20
+ > "Retain core link semantics", the `core LinkStore -> short` dependency line)
21
+ > describe the pre-extraction state this spike was proposing against, and the
22
+ > `urlcode-short` migration sections record an abandoned plan.
15
23
 
16
24
  ## Recommendation
17
25
 
@@ -1,24 +1,41 @@
1
1
  # Spike: layering `link` and `middleware` out of core
2
2
 
3
+ > Review update, 2026-09-19: Current baseline: **both extractions this spike
4
+ > proposes are over, and neither ended in a shipped separate package.** Link
5
+ > extraction completed and the resulting package was then retired. Middleware
6
+ > extraction never completed: core still implements native middleware, and the
7
+ > separate middleware extension — which added a different API with per-entry
8
+ > sandboxing — has itself been unpublished at `0.1.0-alpha.2` and its
9
+ > repository deleted. The extraction direction below is superseded, and it is
10
+ > not an instruction to remove core middleware: core's native `middleware:`
11
+ > array is now the only implementation of per-route middleware there is.
12
+ >
13
+ > An earlier version of this note said "monorepo work is starting now:
14
+ > middleware moves in as a separate package first, and folds into core
15
+ > afterward." Monorepo work is still starting, for core, auth, admin and UI —
16
+ > but the middleware clause is void. There is no package to move in and
17
+ > nothing to fold in afterward.
18
+
19
+
3
20
  Status: proposal, nothing implemented. No code in this repository does any of
4
21
  this, and nothing here is committed scope. This combines two extraction ideas
5
22
  into one spike because they share a mechanism and a sequence, not because
6
23
  either is blocked on the other.
7
24
 
8
- > **Update:** this spike's `link` extraction (Phase 1 below) has since been
9
- > implemented — core no longer has a native `link`/`LinkStore`/`dynamicLinks`
10
- > API; it now lives in the separate
11
- > [`urlcode-dynamic-link`](https://github.com/jimhoyd-com/urlcode-dynamic-link)
12
- > package. Present-tense references to `link` as a core feature elsewhere in
13
- > this document describe the pre-extraction state this spike was analyzing,
14
- > not current core.
25
+ > **Update:** this spike's `link` extraction (Phase 1 below) was implemented
26
+ > core no longer has a native `link`/`LinkStore`/`dynamicLinks` API — and the
27
+ > separate `urlcode-dynamic-link` package that received it has since been
28
+ > retired, unpublished from npm and its repository deleted (September 2026).
29
+ > Nothing owns stored links today. Present-tense references to `link` as a core
30
+ > feature elsewhere in this document describe the pre-extraction state this
31
+ > spike was analyzing, not current core.
15
32
 
16
33
  ## The shape this is aiming at
17
34
 
18
35
  Core's job is to stay the smallest thing that is still a complete product on
19
36
  its own: YAML routing (`redirect`/`respond`/`page`/`static`/`download`) plus
20
37
  `function`, the one primitive with an actual serverless story
21
- ([`SPIKE-LAMBDA-COMPILE.md`](SPIKE-LAMBDA-COMPILE.md) — compile each `function`
38
+ ([`SPIKE-LAMBDA-COMPILE.md`](archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md) — compile each `function`
22
39
  route to its own Lambda, the pattern `src/build-cloudflare.ts` already uses).
23
40
  That base alone serves static and dynamic content and deploys anywhere
24
41
  (`docs/FRAMEWORK.md` layer 1–3). Everything past that — accounts, admin,
@@ -41,7 +58,8 @@ honest instead of inventing two different shapes:
41
58
  - **Mount ownership** (`route.extension`, `RuntimeExtension.activate` →
42
59
  `ExtensionInstance.handle`): an extension owns an exclusive `/prefix/*` and
43
60
  answers every request under it. This is the shape `link` needs — it already
44
- behaves like a self-contained mount (`docs/DYNAMIC-LINKS.md`), and
61
+ behaves like a self-contained mount (documented in the since-retired
62
+ `urlcode-dynamic-link` package), and
45
63
  `auth`/`admin` already use it for `/account/*` and `/admin/*`
46
64
  (`docs/FRAMEWORK.md:10-15`).
47
65
  - **Wrap without owning** (`ExtensionInstance.authorize`, called when a route
@@ -161,7 +179,15 @@ before anything here is treated as settled.
161
179
  need to exist or be attached before their Phase 2 work can be written or
162
180
  verified, matching the constraint already flagged for `link`.
163
181
 
164
- ## Repo governance for the two new repos (decided)
182
+ ## Repo governance for the two new repos (decided — both repos since deleted)
183
+
184
+ > **Historical, 2026-09-19.** Both repositories this section governs were
185
+ > created, released once, and then deleted; both packages are unpublished. The
186
+ > decisions below were applied while they existed and are kept as the recorded
187
+ > template for any future extension repository — not as a description of
188
+ > anything live. Note in particular that the "published public from the start"
189
+ > departure recorded below is the one whose consequences are worth reading
190
+ > back: both repos that took it are gone within days of their first release.
165
191
 
166
192
  Both `urlcode-dynamic-link` and `urlcode-middleware` follow `GOVERNANCE.md`
167
193
  and `AGENTS.md` as written, with one explicit decision recorded here per
@@ -176,7 +202,8 @@ AGENTS.md's "do not publish packages without an explicit decision":
176
202
  admins or automation, CODEOWNERS recording ownership. CI/release workflow
177
203
  shape copied from core's `release.yml` (candidate build → audit → pack →
178
204
  attest → publish via trusted publisher, no long-lived npm token), per the
179
- pattern `docs/NEXT-STEPS.md` §2.1 already used for `auth`/`admin`/`ui`.
205
+ pattern §2.1 of the archived plan (`docs/archive/2026-09-19/NEXT-STEPS.md`)
206
+ already used for `auth`/`admin`/`ui`.
180
207
  CodeQL required on main, secret scanning and push protection on, same as
181
208
  core.
182
209
  - **Published public from the start** — both the GitHub repo and the npm
@@ -225,9 +252,10 @@ system tax." The real costs are narrower and different for each:
225
252
  cost described above still applies, in full, to whichever specific
226
253
  `middleware:` wrap a developer explicitly opts into `sandbox: true` — and
227
254
  the same mitigation still matters there: native declarative primitives
228
- (`docs/NEXT-STEPS.md`'s Phase 4.2 `auth: { required: true, roles: [...] }`
229
- direction) reduce how much logic ever needs to reach for the sandboxed
230
- opt-in at all, for the cases where a developer does judge it warranted.
255
+ (the Phase 4.2 `auth: { required: true, role: ... }` direction in the
256
+ archived plan, `docs/archive/2026-09-19/NEXT-STEPS.md`) reduce how much logic
257
+ ever needs to reach for the sandboxed opt-in at all, for the cases where a
258
+ developer does judge it warranted.
231
259
 
232
260
  ## Other core pieces considered and set aside
233
261
 
@@ -253,14 +281,17 @@ part of the smallest complete product:
253
281
  them would shrink core below the "complete product on its own" bar rather
254
282
  than trim it.
255
283
  - **Management API / operator grants / credential policy** — foundation that
256
- extensions themselves depend on (`docs/MANAGEMENT-SECURITY.md`,
284
+ extensions themselves depend on (`docs/archive/2026-09-19/MANAGEMENT-SECURITY.md`,
257
285
  `docs/FUNCTION-SECURITY.md`); moving it out would mean extracting the thing
258
286
  the extraction pattern relies on.
259
287
 
260
288
  Nothing else in core matches the `link`/`middleware` shape today. If a third
261
- candidate is going to be found, `docs/REPETITION-LOG.md`'s discipline (`docs/NEXT-STEPS.md`
262
- Phase 6 — extract from observed repetition, not speculation) is the more
263
- defensible way to find it than continuing to eyeball the handler list.
289
+ candidate is going to be found, the repetition-log discipline of Phase 6 in the
290
+ archived plan (`docs/archive/2026-09-19/NEXT-STEPS.md`) — extract from observed
291
+ repetition, not speculation — is the more defensible way to find it than
292
+ continuing to eyeball the handler list. That plan records Phase 6 as never
293
+ started, and no `docs/REPETITION-LOG.md` was ever written, so the log itself
294
+ would have to be started before it could inform the decision.
264
295
 
265
296
  Recommendation: before either Phase 2 begins, attach `urlcode-auth`,
266
297
  `urlcode-admin` and `urlcode-ui` to a session and confirm (a) their actual
@@ -3,8 +3,7 @@
3
3
  Status: **decided by the maintainer and implemented in `0.4.0-alpha.2`**
4
4
  (`sandbox`/`sandboxReason` in `schemas/urlcode.schema.json`; see the
5
5
  `0.4.0-alpha.2` entry in [the roadmap](../ROADMAP.md) and the resulting contract
6
- in [function security](FUNCTION-SECURITY.md)). `0.4.0-alpha.2` is not yet
7
- published to npm. This overturns an
6
+ in [function security](FUNCTION-SECURITY.md)). The release is published; this file retains the decision rationale. This overturns an
8
7
  explicit, previously-stated project rule — see "What this reverses" below —
9
8
  so treat it as a deliberate, recorded policy change, not a code cleanup. The
10
9
  body below is preserved as written at decision time; where it calls downstream
@@ -153,11 +152,14 @@ with `add_repo` before treating it as settled.
153
152
  and `peers.json` in `auth`/`admin`/`ui` (`docs/SPIKE-CORE-LAYERING.md`'s
154
153
  §2.2 reference) should pin deliberately to a core version that includes
155
154
  this change, not inherit it silently on a routine bump.
156
- - **`urlcode-dynamic-link` (built and published since; was planned when this was written): unaffected.** It's a
155
+ - **`urlcode-dynamic-link` (built and published after this was written, then retired, unpublished and deleted in September 2026): moot.** It was a
157
156
  mount-based extension like `auth`, not a `function`/`middleware` consumer —
158
157
  nothing here changes its design.
159
- - **`urlcode-middleware` (built and published since; was planned when this
160
- was written): same rule applies — decided.** First-party middleware is trusted by default, exactly like
158
+ - **`urlcode-middleware` (built and published since, then retired, unpublished
159
+ and deleted in September 2026): the rule outlived the package — decided.**
160
+ The decision below was made for that package but is not about it: it governs
161
+ core's own native `middleware:` array, which is where per-route middleware
162
+ lives now that the extension package is gone. First-party middleware is trusted by default, exactly like
161
163
  `function`; `sandbox: true` is the same opt-in a developer reaches for
162
164
  when a specific `middleware:` wrap genuinely warrants it (e.g. it's
163
165
  processing input from a source the developer doesn't fully trust). This