@jimhoyd/urlcode 0.4.0-alpha.2 → 0.4.0-alpha.3

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 (115) 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 +44 -54
  6. package/ROADMAP.md +61 -376
  7. package/dist/BUILD-MANIFEST.json +15 -15
  8. package/dist/agents-guide.js +6 -6
  9. package/dist/build-static.js +2 -0
  10. package/dist/cli.js +3 -3
  11. package/dist/explain-cli.js +4 -2
  12. package/dist/explain.js +7 -1
  13. package/dist/extensions.js +1 -1
  14. package/dist/manifest.js +7 -1
  15. package/dist/mcp-authoring.js +2 -2
  16. package/dist/mcp.js +1 -1
  17. package/dist/policies.js +3 -1
  18. package/dist/prerender.js +4 -0
  19. package/dist/readiness.js +3 -0
  20. package/dist/route-diff.js +12 -5
  21. package/dist/types/explain.d.ts +6 -0
  22. package/dist/types/extensions.d.ts +1 -1
  23. package/dist/types/manifest.d.ts +5 -1
  24. package/dist/types/readiness.d.ts +2 -0
  25. package/dist/types/types.d.ts +8 -1
  26. package/dist/types.js +8 -1
  27. package/dist/typescript-authoring.js +18 -7
  28. package/docs/AI-AUTHORING.md +10 -6
  29. package/docs/ASSETS.md +2 -1
  30. package/docs/AUTH-BACKUP.md +32 -0
  31. package/docs/BEST-PRACTICES.md +17 -9
  32. package/docs/CAPABILITIES.md +1 -1
  33. package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
  34. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
  35. package/docs/CI.md +8 -3
  36. package/docs/CODEBASE-AUDIT-2026-09-20.md +278 -0
  37. package/docs/DEVELOPMENT-PIPELINE.md +181 -0
  38. package/docs/EXTENSIONS.md +15 -5
  39. package/docs/FRAMEWORK.md +22 -18
  40. package/docs/FUNCTION-SECURITY.md +44 -0
  41. package/docs/LOCAL-DEVELOPMENT.md +3 -0
  42. package/docs/OPEN-DECISIONS.md +184 -212
  43. package/docs/OPERATIONAL-PROOF.md +3 -3
  44. package/docs/OPERATIONS.md +3 -3
  45. package/docs/POLICIES.md +13 -5
  46. package/docs/PRERENDER.md +23 -11
  47. package/docs/PROJECT-DIRECTION.md +3 -3
  48. package/docs/READINESS.md +6 -2
  49. package/docs/README.md +18 -41
  50. package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
  51. package/docs/RELEASE-READINESS.md +6 -6
  52. package/docs/RELEASE-SECURITY.md +68 -168
  53. package/docs/RESILIENCE.md +3 -3
  54. package/docs/ROUTING.md +3 -4
  55. package/docs/SECURITY-AUDIT.md +2 -2
  56. package/docs/SPECIFICATION.md +7 -5
  57. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +6 -5
  58. package/docs/SPIKE-BUSINESS-SUITE.md +14 -6
  59. package/docs/SPIKE-CORE-LAYERING.md +48 -17
  60. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +7 -5
  61. package/docs/SPIKE-LAMBDA-COMPILE.md +8 -0
  62. package/docs/STATIC.md +14 -3
  63. package/docs/TOOLING.md +4 -3
  64. package/docs/TYPESCRIPT-AUTHORING.md +6 -1
  65. package/docs/VERSION-ALIGNMENT.md +46 -205
  66. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
  67. package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +102 -0
  68. package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +15 -5
  69. package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +15 -3
  70. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
  71. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
  72. package/docs/archive/2026-09-19/ROADMAP.md +386 -0
  73. package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +10 -0
  74. package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +25 -14
  75. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +776 -0
  76. package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +12 -2
  77. package/docs/archive/README.md +27 -0
  78. package/docs/policies/agents.md +1 -1
  79. package/docs/policies/compression.md +3 -2
  80. package/docs/policies/security.md +3 -2
  81. package/examples/assets/example.yaml +1 -1
  82. package/examples/cookbook/route-index.json +1 -1
  83. package/examples/cookbook/routes/middleware.yaml +1 -1
  84. package/examples/prerender/README.md +14 -6
  85. package/examples/prerender/functions/page.mjs +4 -2
  86. package/examples/prerender/middleware/template.mjs +1 -1
  87. package/examples/prerender/prerender.mjs +1 -1
  88. package/examples/prerender/urlcode.yaml +8 -4
  89. package/llms-full.txt +117 -45
  90. package/llms.txt +5 -4
  91. package/package.json +24 -4
  92. package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
  93. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +17 -19
  94. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +9 -9
  95. package/recipes/authenticated-json-api/README.md +4 -3
  96. package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
  97. package/recipes/authenticated-json-api/recipe.yaml +1 -1
  98. package/recipes/contact-form/functions/contact.mjs +2 -1
  99. package/recipes/contact-form/recipe.yaml +2 -2
  100. package/recipes/cors-api/README.md +2 -2
  101. package/recipes/cors-api/recipe.yaml +1 -1
  102. package/recipes/health-page/README.md +1 -1
  103. package/recipes/json-api/README.md +1 -1
  104. package/recipes/json-api/recipe.yaml +3 -3
  105. package/recipes/middleware/README.md +8 -4
  106. package/recipes/protected-download/README.md +1 -1
  107. package/recipes/protected-download/recipe.yaml +1 -1
  108. package/recipes/static-plus-api/README.md +2 -2
  109. package/recipes/static-plus-api/public/index.html +1 -1
  110. package/recipes/static-plus-api/recipe.yaml +1 -1
  111. package/recipes/static-plus-api/urlcode.yaml +1 -1
  112. package/recipes/typescript/recipe.yaml +4 -4
  113. package/skills/urlcode/SKILL.md +6 -6
  114. package/starters/default/AGENTS.md +6 -6
  115. package/docs/SPIKE-MONOREPO.md +0 -322
@@ -1,212 +1,184 @@
1
- # Open decisions
2
-
3
- Status: written 2026-09-19 from a review of the nine repositories. Every item
4
- here is a maintainer decision that documentation cannot make. Each states what
5
- is actually true today, what the options are, what it costs to leave open, and
6
- a recommendation. Nothing here is committed scope; the
7
- [roadmap](../ROADMAP.md) owns sequence and the
8
- [readiness register](RELEASE-READINESS.md) owns what is proven.
9
-
10
- This page exists because several documented positions have drifted apart from
11
- each other and from the source. The drift is not the decisions themselves — it
12
- is that they were never recorded in one place where the next contributor, human
13
- or agent, would look.
14
-
15
- ## 1. Publish `0.4.0-alpha.2` decided
16
-
17
- **Decided 2026-09-19: publish `alpha.2`.** The recommendation below was taken
18
- rather than walking back a correct declaration. The alignment is prepared
19
- across all nine repositories and the order is recorded in
20
- [the version register](VERSIONS.md); what remains is merging and tagging.
21
-
22
- The second hazard was taken with it: `@jimhoyd/urlcode-auth` moves `latest`
23
- up to `0.1.0-alpha.2` so that a plain `npm install` no longer resolves a build
24
- older than the floor `@jimhoyd/urlcode-admin` declares. That is a registry
25
- operation (`npm dist-tag add`), not a source change, and it is listed in the
26
- release steps.
27
-
28
- The original entry, for the record:
29
-
30
- > **Today:** the repository source is `0.4.0-alpha.2`. The npm dist-tags are
31
- > `latest = 0.3.0` and `alpha = 0.4.0-alpha.1`. `alpha.2` carries the
32
- > trusted-by-default execution change ([the decision](SPIKE-DEFAULT-TRUST-MODEL.md)).
33
- >
34
- > **Why it blocks other things:** `@jimhoyd/urlcode-middleware@0.1.0-alpha.1` is
35
- > published with `peerDependencies: { "@jimhoyd/urlcode": ">=0.4.0-alpha.2" }`.
36
- > No published core version satisfies that range, so a registry install of that
37
- > package cannot resolve its peer. The range is correct; the publication order
38
- > was not.
39
- >
40
- > **Recommendation:** publish `alpha.2`. It is the only option that does not
41
- > require walking back a correct declaration.
42
-
43
- ## 2. Consolidate the repositories, or commit to the split
44
-
45
- **Today:** nine repositories. [The monorepo spike](SPIKE-MONOREPO.md) is
46
- complete, its layout is marked decided (option A), its migration mechanics and
47
- sequencing are written, and it has three open questions for the maintainer. It
48
- has been neither accepted nor rejected. It was drafted for four repositories;
49
- `urlcode-dynamic-link`, `urlcode-middleware` and `urlcode-short` have appeared
50
- since.
51
-
52
- **Cost of leaving it open:** the migration gets larger with every repository
53
- added, and the coordination work the spike describes — pinning reviewed peer
54
- revisions by hand, chasing prose across repositories when a contract changes —
55
- is paid again on every contract change in the meantime.
56
-
57
- **Recommendation:** decide before the next structural change, in either
58
- direction. A recorded "no, and here is what we do instead about cross-repo
59
- drift" closes this as well as a yes does.
60
-
61
- ## 3. One way to attach middleware, or two
62
-
63
- **Today:** core has the native `routes.<path>.middleware[]` array.
64
- `urlcode-middleware` provides the same capability through the extension seam
65
- and states that core's native array "keeps working unchanged".
66
- [The layering spike](SPIKE-CORE-LAYERING.md) proposed *extracting* middleware
67
- to narrow core to YAML plus `function`; what exists is an addition alongside it.
68
-
69
- **Why this is a principle question:** the
70
- [extension model review](SPIKE-EXTENSION-MODEL.md) rejected an earlier design
71
- for exactly this reason two ways to protect a route would drift apart — and
72
- resolved it by removing one. The same test applies here.
73
-
74
- **Options:** deprecate the native array on a stated timeline and complete the
75
- extraction; or keep both and document precisely when each is correct, treating
76
- the extension as a bounded variant rather than a successor.
77
-
78
- **Recommendation:** decide explicitly and write the answer into both
79
- repositories. Either is defensible; leaving both undescribed is not.
80
-
81
- ## 4. Where the backlog lives
82
-
83
- **Today:** [next steps](NEXT-STEPS.md) holds roughly thirty tracked items
84
- across nine phases. The repository has one open issue (#58). `AGENTS.md`
85
- instructs contributors and agents to file what they find as issues on the
86
- owning repository.
87
-
88
- **The contradiction:** the instruction points at the issue tracker; the actual
89
- plan is a Markdown file that no tracker reflects. A plan document also has no
90
- state, so items stay written as future work after they ship — which is how
91
- route-level `auth` came to be described as an invented field in merged agent
92
- guidance (see item 7).
93
-
94
- **Options:** move the phase items into issues and keep the document as the
95
- narrative that links them; or keep the document as the system of record and
96
- amend `AGENTS.md` to say so.
97
-
98
- **Recommendation:** issues for items, document for sequence. It is the only
99
- arrangement where "done" is recorded automatically.
100
-
101
- ## 5. Gate the business suite on evidence
102
-
103
- **Today:** [the business suite spike](SPIKE-BUSINESS-SUITE.md) proposes seven
104
- applications. [Project direction](PROJECT-DIRECTION.md) states the evidence
105
- test: the framework grows from measured repetition, not from a list of things
106
- applications might need. Phase 6 of [next steps](NEXT-STEPS.md) says candidate
107
- areas are built only when the repetition log shows them repeating. The
108
- repetition log does not exist yet, and the Phase 0 agent benchmark has not been
109
- run.
110
-
111
- **Recommendation:** record on the spike itself that it is gated behind the
112
- benchmark and the repetition log, or amend the evidence test. Holding both
113
- positions unannotated makes the stated principle decorative.
114
-
115
- ## 6. Finish the documentation move, or describe the split
116
-
117
- **Today:** `AGENTS.md` states that urlcode-docs is the documentation home and
118
- the only place readers are sent, that new reader-facing pages go there, and
119
- that pages still under `docs/` here are being migrated. Roughly forty
120
- reader-facing pages remain in this repository, several duplicated in
121
- urlcode-docs.
122
-
123
- **Observed consequence:** the same fact drifts between copies. The public
124
- documentation site carried the correct trusted-by-default contract while
125
- `docs/OPERATIONS.md` in this repository still described functions as untrusted
126
- and isolated by default. <!-- trust-model-prose: historical -->
127
-
128
- **Options:** finish the migration against a dated list; or state which pages
129
- stay here permanently and why, and add a check that fails when a new
130
- reader-facing page appears beside them.
131
-
132
- ## 7. A review window, and a check that does not need one
133
-
134
- **Today:** [governance](../GOVERNANCE.md) records one maintainer, a required
135
- approval count of zero, and that "an independent human review is not yet
136
- guaranteed". Pull requests and CI are mandatory; a second pair of eyes is not.
137
-
138
- **What that permitted, concretely:** on 2026-09-19, three coordinated pull
139
- requests (urlcode#158, urlcode-template#6, urlcode-docs#17) opened and merged
140
- within nine minutes. They recorded a genuine improvement the declarative-first
141
- principle, propagated to every copy in one change — and alongside it the
142
- statement that agents must "never invent an `auth` field". Route-level `auth` is
143
- implemented: it is defined in `schemas/urlcode.schema.json` as a short form
144
- expanding to `policies.extensions.auth`, expanded in `src/config.ts`, typed in
145
- `src/types.ts`, and asserted in `test/recipes.test.ts`. The guidance now
146
- instructs agents away from a supported declarative short form and toward the
147
- lower-level policy form the opposite of the principle the same change
148
- introduced in the generated project guide, the bundled starter, both
149
- authoring skills, the packaged plugin skill and the two generated `llms` files.
150
-
151
- Nothing failed. Lint, typecheck, generated-resource checks, the package smoke
152
- test and 501 tests all passed, because no check compares what the guidance
153
- claims against what the schema implements.
154
-
155
- **The check, now implemented.** `scripts/check-guidance-claims.ts` runs inside
156
- `npm run check` and exits non-zero on a contradiction between agent-facing
157
- guidance and the schema, so this class of error fails CI instead of depending on
158
- a reviewer's attention:
159
-
160
- - Inputs: the agent-facing surfaces — `src/agents-guide.ts`,
161
- `starters/default/AGENTS.md`, `skills/urlcode/SKILL.md`,
162
- `.claude/skills/*/SKILL.md`, `packaging/claude-plugin/skills/*/SKILL.md`,
163
- `llms.txt`, `llms-full.txt`, `docs/AI-AUTHORING.md`.
164
- - Assertion one: every YAML field named as valid in those files resolves in
165
- `schemas/urlcode.schema.json`. A guidance file may not teach a field the
166
- schema does not accept.
167
- - Assertion two, the one that would have caught this: no field that the schema
168
- *does* define is described as unsupported, invented or nonexistent. Match the
169
- negative phrasings deliberately ("never invent", "does not exist",
170
- "unsupported field") within a short window of a schema-resolvable field name.
171
- - Assertion three: handlers listed as available match the capability catalog,
172
- so a removed handler (`link`, extracted to `urlcode-dynamic-link` in
173
- `f7dbe54`) cannot linger in generated guidance.
174
- - Exits non-zero on violation, with a documented `<!-- guidance-claims: ignore -->`
175
- marker for text that is deliberately about another version. Verified against
176
- both regressions: reintroducing the "never invent an `auth` field" sentence
177
- fails the check, and adding the removed `link` handler to the inventory line
178
- fails it.
179
-
180
- What remains a decision: whether `npm run check` membership is enough, or the
181
- check should also be named in the repository's required status checks so it
182
- cannot be bypassed.
183
-
184
- **Recommendation:** the check is in; keep it required. It is the part that does
185
- not depend on a person being available. Whether to
186
- also raise the required approval count is a separate call, and
187
- [governance](../GOVERNANCE.md) already states the condition — when the trusted
188
- maintainer team grows.
189
-
190
- ## 8. One publishing convention
191
-
192
- **Today:** `urlcode-dynamic-link` and `urlcode-middleware` keep
193
- `"private": true` on their main branch and drop it in the release commit;
194
- `urlcode-auth`, `urlcode-admin` and `urlcode-ui` do not. All are published.
195
- Both practices are defensible; having both means the flag no longer indicates
196
- publication state.
197
-
198
- **Recommendation:** pick one and state it where release process is documented.
199
-
200
- ## 9. Adjudicate the vendored skill drift
201
-
202
- **Today:** `npm run check:downstream-skills` reports that `urlcode-template`'s
203
- vendored authoring and operations skills differ from core's current `main` by
204
- 79 and 91 lines. The report is advisory by design and never fails, because a
205
- downstream repository can correctly pin an older published core version
206
- ([issue 155](https://github.com/jimhoyd-com/urlcode/issues/155)).
207
-
208
- **What is missing:** the judgment the report defers. No one has read the two
209
- diffs and recorded which side is right for the template's `0.4.0-alpha.1` pin.
210
-
211
- **Recommendation:** review both diffs once, record the verdict, and re-run the
212
- report when the template repins.
1
+ # Decisions to align
2
+
3
+ Reviewed 2026-09-19 against core `db375bf` and the current public package sources.
4
+ This is the maintainer's decision list, not a second implementation backlog.
5
+ The [roadmap](../ROADMAP.md) gives sequence and the [archive](archive/README.md)
6
+ keeps earlier discussions. Recommendations below are not accepted decisions.
7
+
8
+ ## Principles already settled, in plain language
9
+
10
+ - **Describe first, code only when needed.** Use a supported YAML feature or
11
+ extension before writing plumbing. Custom application code is still welcome.
12
+ - **Your application code runs like normal Node code.** Functions and middleware
13
+ are trusted by default. `sandbox: true` deliberately restricts a route's whole
14
+ function/middleware chain. Request data still needs validation in either mode.
15
+ - **A grant controls what URLCode supplies, not what trusted code can access.**
16
+ Trusted code can independently read the host environment, files and network.
17
+ Opt-in sandboxing retains its existing isolation and revision-pinned grants.
18
+ - **Portable does not mean every host supports every feature.** Keep infrastructure
19
+ out of route YAML and reject unsupported targets before activation.
20
+ - **Core works alone; optional packages add accounts, admin and presentation.**
21
+ Core never imports those implementations. Shared UI belongs in `urlcode-ui`.
22
+ - **Documentation stays beside the owning code.** Core guides live here;
23
+ extension contracts and implementation status live in their repositories.
24
+ `urlcode-docs` is deleted. `urlcode-short` and `urlcode-dynamic-link` are retired.
25
+ - **Passing tests proves the tested behavior.** It does not prove deployment,
26
+ accessibility, hostile tenant isolation or independent security assessment.
27
+ - **Keep the free runtime useful.** Apache-2.0 remains unchanged; no mandatory
28
+ hosted account, paid capability gate or provider lock-in belongs in core.
29
+
30
+ ## Decisions still needed
31
+
32
+ | Decision | What the code says today | Recommendation and consequence |
33
+ |---|---|---|
34
+ | Where does work status live? | Several old plans repeated issues and continued calling delivered work unfinished. | Issues for actionable status, this short roadmap for sequence, archive for completed proposals. Preserve evidence gaps when archiving. |
35
+ | Expand into business applications now? | No collection handler or proposed business suite is implemented; the model-backed benchmark evidence is missing. | Measure existing tasks and record repeated application plumbing before selecting a collection/CMS/forms project. Retired short-link products stay retired. [Proposal](SPIKE-BUSINESS-SUITE.md). |
36
+ | Which provider execution model next? | AWS/Vercel still reject function/middleware despite the trusted default. | Decide demand first, then compare one Node deployment per project against one Lambda per route. Do not promise either today. [Proposal](SPIKE-LAMBDA-COMPILE.md). |
37
+ | Fold extension schemas into retrieved context? | `urlcode extensions` and the MCP `get_extensions` query return the registered configuration and policy schemas, but `src/context.ts:113` reports `extensions` as names only, so an author writing `extensions.<name>.config` or `policies.extensions.<name>` must run the separate operator-authorized query first. | Decide from retrieval and task evidence, not preference: the existing small-task harness can measure whether folding schemas into bounded context improves authoring. Keep the token budget bounded and never auto-load a project-selected host file. This is a discovery improvement, not a defect in the existing query. |
38
+ | Retire the UI primitive fallback? | Auth and admin use the kit when supplied, and retain tested primitive rendering without it. | Keep both until an explicit compatibility/deprecation decision; adoption is already implemented. |
39
+
40
+ The broader [AI benchmark proposal](SPIKE-AI-FRAMEWORK-BENCHMARK.md) also needs a
41
+ chosen application, model-run budget and execution authorization. The existing
42
+ small-task harness can supply evidence without committing to that larger study.
43
+
44
+ ## Closed questions removed from the active list
45
+
46
+ - Publishing convention is recorded in [version alignment](VERSION-ALIGNMENT.md):
47
+ publishable manifests on main, releases through reviewed tags/workflows.
48
+ - Core `0.4.0-alpha.2` and current extension releases exist; publishing that
49
+ already-shipped version is not a next step.
50
+ - Auth/admin kit adoption and shared form helpers are implemented in their code.
51
+ - The template pins `0.4.0-alpha.2`. Its skill differences were read against that
52
+ pin: omitted handlers and advice about the removed management API are stale,
53
+ not intentional older-version behavior.
54
+ - The guidance checks run through `npm run check` inside `verify`; a regex check
55
+ is not a schema validator for every example. Extending its coverage is tracked
56
+ separately, not a reason to weaken review or bypass required checks.
57
+
58
+ ## Accepted: per-package release tags
59
+
60
+ **Decided 2026-09-19.** Workspace packages under `packages/` release on
61
+ Changesets' own `<package name>@<version>` form for example
62
+ `@jimhoyd/urlcode-ui@0.1.0-alpha.6`. Core keeps bare `v*`.
63
+
64
+ **The problem.** Core and all three extensions arrived here triggering on
65
+ `tags: ['v*']`, and their alpha tags overlap outright: ui shipped
66
+ `v0.1.0-alpha.2` through `-alpha.5`, admin `v0.1.0-alpha.1` and `-alpha.3`,
67
+ auth `v0.1.0-alpha.1` through `-alpha.3`. Across four repositories that was
68
+ fine. In one repository a single bare tag push starts more than one release
69
+ workflow. Each one fails closed on its own tag-matches-manifest check, so
70
+ nothing can mis-publish but "two workflows race and one errors on every
71
+ release" is not a release process, and the failure is confusing rather than
72
+ informative.
73
+
74
+ **Why Changesets' form rather than a prefix like `ui-v0.1.0-alpha.6`.** Both
75
+ work and both are valid ref names. The deciding factor is that Changesets is
76
+ already the chosen release flow, and `changeset tag` emits the
77
+ `<name>@<version>` form natively. Picking anything else means writing and
78
+ maintaining a translation layer between the tool that computes the version and
79
+ the tag that triggers the publish new code whose only job is to disagree
80
+ with a default. The spike chose Changesets partly because it is "cheap and
81
+ low-risk for an agent or a human to generate correctly"; hand-rolling the tag
82
+ shape undercuts exactly that.
83
+
84
+ **Why the two schemes cannot collide.** A scoped package name begins with `@`,
85
+ and GitHub's `v*` filter requires a leading `v`, so no tag can match both.
86
+ Verified rather than assumed, including that `*` does not match `/` in a filter
87
+ pattern, so `@jimhoyd/urlcode-ui@*` matches the version segment only.
88
+
89
+ **Core's asymmetry is forced, not preferred.** Under layout A core is the
90
+ repository root rather than a workspace member, so Changesets does not manage
91
+ it and `changeset version` will not bump it. Core therefore keeps the tag
92
+ scheme and release workflow it already had.
93
+
94
+ [`scripts/check-release-tags.ts`](../scripts/check-release-tags.ts) enforces
95
+ this in `npm run check`: it rejects a workspace package workflow that does not
96
+ trigger on its own `<name>@*`, rejects any workflow other than core's claiming
97
+ `v*`, and independently asserts that no two filters can match the same tag. The
98
+ reasoning above is the kind of prose that rots as soon as `auth` and `admin`
99
+ arrive, which is the whole argument this repository makes for enforcing checks
100
+ over documented intent.
101
+
102
+ ## Done: the monorepo migration is complete
103
+
104
+ **Closed 2026-09-19.** `urlcode-ui`, `urlcode-auth` and `urlcode-admin` are
105
+ workspace packages under `packages/`, and all three have been released from
106
+ this repository `@jimhoyd/urlcode-ui@0.1.0-alpha.6`,
107
+ `@jimhoyd/urlcode-auth@0.1.0-alpha.6`, `@jimhoyd/urlcode-admin@0.1.0-alpha.4`,
108
+ each on `alpha` with `latest` deliberately held behind. Core's dist-tags are
109
+ unchanged. The three source repositories are gone; their history survives only
110
+ as verified `git bundle`s, because the repository allows squash merges only and
111
+ the imported commits did not survive onto `main`.
112
+
113
+ The operational runbook is [DEVELOPMENT-PIPELINE.md](DEVELOPMENT-PIPELINE.md)
114
+ and [RELEASE-SECURITY.md](RELEASE-SECURITY.md). The plan itself is archived at
115
+ [archive/2026-09-19/SPIKE-MONOREPO.md](archive/2026-09-19/SPIKE-MONOREPO.md),
116
+ whose closing note records what the plan got wrong — chiefly that its
117
+ strongest argument, the reach of the enforcing checks, only became true after
118
+ both checkers were changed to discover workspace packages.
119
+
120
+ The section below is kept for the middleware decision it records, which is
121
+ still the reason there is no `packages/middleware`.
122
+
123
+ ## Accepted: monorepo first middleware withdrawn rather than consolidated
124
+
125
+ The maintainer confirmed that monorepo work is starting now. The earlier
126
+ recommendation to keep repositories separate for now is superseded.
127
+
128
+ **Reversed 2026-09-19: the middleware half of this decision no longer applies.**
129
+ This section used to say "move middleware into the monorepo as its own package
130
+ first, preserving its existing API and behavior," and explicitly: "do not
131
+ unpublish or retire the middleware package as part of the initial move."
132
+ That instruction was overtaken. `@jimhoyd/urlcode-middleware` has been
133
+ **unpublished** from npm at `0.1.0-alpha.2` and `jimhoyd-com/urlcode-middleware`
134
+ **deleted** the package was withdrawn outright instead of migrated, so there
135
+ is no `packages/middleware` to create and no subsequent fold-into-core step.
136
+ The reversal is recorded here rather than deleted because the instruction it
137
+ replaces was explicit, and a reader who remembers it should be able to see that
138
+ it was changed deliberately and not simply forgotten.
139
+
140
+ Nothing was lost in capability terms: per-route middleware is **native to
141
+ core** via the `middleware:` array ([MIDDLEWARE.md](MIDDLEWARE.md)), and the
142
+ deleted package only ever offered the same behavior through the extension
143
+ seam. Trust remains the default and explicit sandbox choices keep their
144
+ meaning. The generic extension wrapping hook (`ExtensionInstance.middleware`,
145
+ `RuntimeExtension.cacheSensitive`) stays in core's contract for other
146
+ extensions it is no longer exercised by any shipped package, which is worth
147
+ knowing before it is assumed to be covered. Static targets continue rejecting
148
+ request-time middleware because there is no server to run it.
149
+
150
+ Migration scope is therefore **core, auth, admin and UI**. Template and the
151
+ distribution tap stay outside that package move. The observed stale peer pins
152
+ and checkout-limited guidance checks strengthen the case for shared
153
+ verification: carry those checks across the new package paths, since merely
154
+ moving files does not prove every generated skill is covered. The cleanup PRs
155
+ that superseded the earlier zero-open-PR survey have since merged, and a fresh
156
+ survey again reports zero open pull requests across all four in-scope
157
+ repositories re-run it per repository immediately before that repository
158
+ moves rather than trusting this line.
159
+
160
+ The [archived monorepo plan](archive/2026-09-19/SPIKE-MONOREPO.md) records
161
+ migration context.
162
+ [Issue 172](https://github.com/jimhoyd-com/urlcode/issues/172), which tracked
163
+ "consolidate middleware into core after moving it into the monorepo," is
164
+ **moot and still open** there is nothing left to consolidate. It should be
165
+ closed with a pointer to this section. Migration starting is not a claim that
166
+ it has landed.
167
+
168
+ ## Source review baseline
169
+
170
+ | Repository | Reviewed commit | Code checked |
171
+ |---|---|---|
172
+ | core | `db375bf` | Runtime dispatch, schema normalization, capabilities, static compiler, prerender, MCP and resource generators |
173
+ | auth | `71957dd` | Lifecycle hooks, UI rendering and shared helper imports |
174
+ | admin | `f3b4882` | UI rendering, auth-service integration and shared helper imports |
175
+ | UI | `0e96f7f` | Shared forms, kit/host exports and copied core contract |
176
+ | ~~middleware~~ | `f201f4b` | Extension wrapping, per-entry sandbox dispatch and scaffolding — **repository deleted 2026-09-19; this baseline is unreachable except through the local `urlcode-middleware.bundle`** |
177
+ | template | `4e09e50` | Exact core pin, generated guide and both vendored skills |
178
+
179
+ The Homebrew tap (`73eaaef`) still selects stable core `0.3.0`; its old trust
180
+ behavior belongs to that pin and must not be rewritten as alpha.2 behavior.
181
+ The other organization tap and Scoop bucket contain Gitroll, not URLCode.
182
+ The deleted documentation repository is historical context, not a second source
183
+ of current contracts; its former GitHub links no longer resolve. This review is targeted source inspection, not an audit of
184
+ every execution path or an independent security assessment.
@@ -13,9 +13,9 @@ only (3 of the 9 combinations); the macOS/Windows legs only run once a PR
13
13
  merges, per `.github/workflows/ci.yml`'s matrix.
14
14
 
15
15
  Core has no durable store of its own, so this drill has no backup/restore or
16
- disk-exhaustion exercise: a future extension package owning durable state
17
- (such as the planned `urlcode-dynamic-link`) is responsible for its own
18
- persistence proof once it exists.
16
+ disk-exhaustion exercise: an extension package owning durable state is
17
+ responsible for its own persistence proof. No such package ships today — the
18
+ `urlcode-dynamic-link` package that did has been retired.
19
19
 
20
20
  ## Required proof on the intended deployment
21
21
 
@@ -149,9 +149,9 @@ deployment controls, not portable route behavior. Horizontal replicas
149
149
  must use identical application/config versions and secret bindings. In-memory
150
150
  function state is reset after every invocation, not durable/shared application state.
151
151
  General application storage needs a future explicit capability broker; no
152
- storage/network access is exposed to the guest. Stored short links are moving
153
- to a future `urlcode-dynamic-link` extension package, not yet published; core
154
- no longer has a native link store.
152
+ storage/network access is exposed to the guest. Core no longer has a native
153
+ link store, and the `urlcode-dynamic-link` extension package that replaced it
154
+ has been retired and unpublished.
155
155
 
156
156
  The health version combines route-definition and asset-representation digests;
157
157
  it does not identify the complete function/runtime release. Record runtime commit,
package/docs/POLICIES.md CHANGED
@@ -3,9 +3,10 @@
3
3
  Policies are host-enforced behavior declared in YAML around a route: a
4
4
  per-client request budget, a User-Agent deny list, a security-header profile,
5
5
  content-coding negotiation and an HTTP caching strategy. They run in the host
6
- process, outside the sandbox, so they can see the client address, keep counters
7
- across requests and touch the transport, which route middleware by design
8
- cannot ([middleware](MIDDLEWARE.md), [function security](FUNCTION-SECURITY.md)).
6
+ process, outside function/middleware execution trusted or sandboxed alike
7
+ so they can see the client address, keep counters across requests and touch
8
+ the transport, which route middleware by design cannot
9
+ ([middleware](MIDDLEWARE.md), [function security](FUNCTION-SECURITY.md)).
9
10
 
10
11
  Everything here is optional and off by default. A project with no `policies`
11
12
  key and no `profiles` key behaves exactly as before: no policy module is
@@ -20,7 +21,7 @@ compiled and the request path is unchanged. Each policy has its own page:
20
21
  | `cache` | [cache](policies/cache.md) | Named strategies from RFC 9111/5861/8246/9213 plus an origin memory cache |
21
22
 
22
23
  The design and the reasoning behind each choice are in the
23
- [extensions spike](SPIKE-EXTENSIONS.md). Operators who need behavior the
24
+ [extensions spike](archive/2026-09-19/SPIKE-EXTENSIONS.md). Operators who need behavior the
24
25
  declarative block cannot express pass host [plugins](PLUGINS.md) in code; plugins
25
26
  are never named in YAML.
26
27
 
@@ -42,7 +43,14 @@ routes:
42
43
  `policies` at the top level sets project defaults; `routes.<path>.policies`
43
44
  adjusts them for one route. Two route-level short forms exist. `auth`
44
45
  expands to `policies.extensions.auth` when the project declares an auth
45
- [extension](EXTENSIONS.md). `cache: {strategy, maxAge, ...}` expands to
46
+ [extension](EXTENSIONS.md), carrying the same keys minus `required`;
47
+ `required: false` documents the intent and emits no policy. It accepts
48
+ `required`, `role`, `permission`, `verified`, `freshWithinSeconds` and
49
+ `onDeny` and nothing else — `role` is singular, and there is no `roles`. Like
50
+ `cache` below, it is refused rather than silently ignored in three cases: when
51
+ the project declares no `extensions.auth`, when the route also sets
52
+ `policies.extensions.auth` (use one form), and when the route sets
53
+ `policies.extensions: false`. `cache: {strategy, maxAge, ...}` expands to
46
54
  `policies.cache` the same way — the compiler merges it into that route's
47
55
  `policies` before anything else reads the project, so `routes`, `audit` and
48
56
  `explain` see only the expanded long form, and it is refused alongside a
package/docs/PRERENDER.md CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  Render a project's function and middleware routes once, at build time, into a
4
4
  project whose routes are all native `page` handlers. The published site answers
5
- from prevalidated byte buffers: no guest code runs to serve a request, so the
6
- sandbox, its deadline and its memory budget are not on the request path at all.
5
+ from prevalidated byte buffers: no guest code runs to serve a request at all,
6
+ so whatever execution mode the source routes used trusted by default, or
7
+ isolated QuickJS/WASM where a route declares `sandbox: true` — along with its
8
+ deadline and memory budget where sandboxed, is not on the request path.
7
9
 
8
10
  The shared orchestration ships as a build helper, `@jimhoyd/urlcode/prerender`, and the
9
11
  runnable recipe is [`examples/prerender`](../examples/prerender/README.md), which
@@ -24,7 +26,7 @@ and publish the result:
24
26
 
25
27
  ```
26
28
  function + template middleware ──render once──▶ HTML file ──▶ page route
27
- (sandbox, build time) (no sandbox)
29
+ (trusted or sandboxed, build time) (no guest code)
28
30
  ```
29
31
 
30
32
  The alternative — reading Markdown through `next().text()` on a native route —
@@ -111,9 +113,11 @@ specifically:
111
113
  instead of hanging on its worker threads.
112
114
 
113
115
  The helper is operator build tooling. It runs in Node with normal filesystem
114
- access because it is not guest code; nothing here gives the sandbox a filesystem,
115
- and no host-code fallback is introduced. It is a separate package export from the
116
- runtime for that reason. Review it as you review any deployment tooling.
116
+ access because it is not guest code; it does not itself widen a source route's
117
+ declared execution mode a route with `sandbox: true` still renders isolated,
118
+ with no filesystem, and no host-code fallback is introduced for it. It is a
119
+ separate package export from the runtime for that reason. Review it as you
120
+ review any deployment tooling.
117
121
 
118
122
  ## Assembling a site
119
123
 
@@ -196,8 +200,9 @@ Two consequences worth knowing:
196
200
  and their imports exceed the budgets cannot be split, and fails with the
197
201
  collector's message. That is a route to make smaller, not a pass to add.
198
202
 
199
- The [urlcode-docs showcase](https://github.com/jimhoyd-com/urlcode-docs) renders
200
- 62 documentation pages this way.
203
+ The `urlcode-docs` site rendered 62 documentation pages this way. That
204
+ repository has since been deleted, so no link is given; the runnable
205
+ version of the same pattern is [`examples/prerender`](../examples/prerender/README.md).
201
206
 
202
207
  ## Larger sites: generating the source project
203
208
 
@@ -219,9 +224,8 @@ things to hold onto:
219
224
  interpolates, but content injected as raw HTML is published as written. Whatever
220
225
  produces that HTML owns its safety.
221
226
 
222
- The [urlcode-docs showcase](https://github.com/jimhoyd-com/urlcode-docs) builds
223
- its site this way. That is a working integration, not a deployment or performance
224
- claim.
227
+ The `urlcode-docs` site built itself this way before that repository was
228
+ retired. It was a working integration, not a deployment or performance claim.
225
229
 
226
230
  ## What this is not
227
231
 
@@ -231,3 +235,11 @@ native bodies readable. Not a template engine — the template is ordinary
231
235
  middleware you write. Not a CLI command: prerendering is a step inside a build
232
236
  that already runs JavaScript, so the helper is a library. Content compilation,
233
237
  sanitization, search, asset assembly and deployment stay in the application.
238
+
239
+
240
+ Trusted build-time functions and middleware use ordinary Node imports, including
241
+ npm packages and dynamic imports. The pass planner applies source-graph budgets
242
+ only to `sandbox: true` routes; it does not parse trusted modules as sandbox
243
+ code. Page-count and output-byte budgets still apply to both modes. After
244
+ rendering, emit native page/file routes without middleware, then use the static
245
+ build target. Static hosting cannot execute middleware on incoming requests.
@@ -40,8 +40,8 @@ capability grants. Application data stays in the operator's systems.
40
40
 
41
41
  - **Not a URL shortener.** Core is redirects, validated responses, request
42
42
  functions, middleware, pages, static assets and downloads. Stored short
43
- links are moving to a future `urlcode-dynamic-link` extension package
44
- (mount-based, like `auth`/`admin`, not yet published), not a core handler.
43
+ links are not a core handler; the `urlcode-dynamic-link` extension package
44
+ that owned them has been retired and unpublished.
45
45
  - **Not a general Node web framework.** There is no framework code to write
46
46
  for routing, validation, middleware wiring or policies — those are declared
47
47
  in YAML and enforced by the runtime. Function/middleware code that needs
@@ -104,7 +104,7 @@ lines an agent generated, how many are the idea and how many are plumbing. A
104
104
  traditional build might be 2,900 lines of business logic inside 18,400; the
105
105
  same application on URLCode should be the same 2,900 inside a few thousand.
106
106
  Until a reproducible benchmark shows that ratio, the thesis is a hypothesis,
107
- and [next steps](NEXT-STEPS.md) puts the benchmark before the features.
107
+ and [the roadmap](../ROADMAP.md) puts the benchmark before the features.
108
108
 
109
109
  ## License
110
110