@jimhoyd/urlcode 0.4.0-alpha.1 → 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 (232) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +36 -22
  2. package/.claude/skills/urlcode-operations/SKILL.md +16 -22
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CONTRIBUTING.md +20 -16
  5. package/README.md +83 -80
  6. package/ROADMAP.md +61 -331
  7. package/SECURITY.md +23 -8
  8. package/dist/BUILD-MANIFEST.json +42 -47
  9. package/dist/agents-guide.js +18 -18
  10. package/dist/build-static.js +136 -0
  11. package/dist/capabilities.js +85 -31
  12. package/dist/capability-query.js +0 -1
  13. package/dist/cli.js +24 -32
  14. package/dist/compliance-rules/baseline.js +2 -10
  15. package/dist/compliance-rules/privacy.js +5 -16
  16. package/dist/compliance-rules/shared.js +0 -2
  17. package/dist/compliance.js +6 -8
  18. package/dist/config.js +15 -8
  19. package/dist/context.js +9 -10
  20. package/dist/examples.js +2 -2
  21. package/dist/explain-cli.js +7 -5
  22. package/dist/explain.js +10 -5
  23. package/dist/extensions.js +61 -2
  24. package/dist/function-sources.js +34 -2
  25. package/dist/function-worker.js +3 -1
  26. package/dist/functions.js +84 -13
  27. package/dist/guest-api.js +29 -3
  28. package/dist/index.js +3 -6
  29. package/dist/manifest.js +11 -7
  30. package/dist/mcp-authoring.js +2 -2
  31. package/dist/mcp.js +1 -1
  32. package/dist/observability.js +1 -21
  33. package/dist/policies/cache.js +8 -3
  34. package/dist/policies.js +3 -1
  35. package/dist/policy.js +28 -9
  36. package/dist/prerender.js +4 -0
  37. package/dist/project-tests.js +3 -3
  38. package/dist/readiness.js +35 -9
  39. package/dist/route-diff.js +12 -5
  40. package/dist/router.js +5 -7
  41. package/dist/runtime.js +77 -58
  42. package/dist/sandbox.js +48 -0
  43. package/dist/scaffold.js +0 -0
  44. package/dist/scripts/operational-drills.js +12 -54
  45. package/dist/server.js +3 -29
  46. package/dist/tooling.js +1 -1
  47. package/dist/trusted-functions.js +210 -0
  48. package/dist/types/build-static.d.ts +43 -0
  49. package/dist/types/capabilities.d.ts +14 -5
  50. package/dist/types/compliance-rules/shared.d.ts +0 -2
  51. package/dist/types/compliance.d.ts +0 -3
  52. package/dist/types/config.d.ts +2 -1
  53. package/dist/types/context.d.ts +2 -1
  54. package/dist/types/examples.d.ts +1 -1
  55. package/dist/types/explain.d.ts +6 -0
  56. package/dist/types/extensions.d.ts +55 -0
  57. package/dist/types/function-sources.d.ts +4 -0
  58. package/dist/types/functions.d.ts +48 -5
  59. package/dist/types/guest-api.d.ts +1 -0
  60. package/dist/types/index.d.ts +3 -6
  61. package/dist/types/manifest.d.ts +5 -3
  62. package/dist/types/observability.d.ts +1 -14
  63. package/dist/types/project-tests.d.ts +1 -2
  64. package/dist/types/readiness.d.ts +12 -3
  65. package/dist/types/router.d.ts +2 -1
  66. package/dist/types/runtime.d.ts +0 -27
  67. package/dist/types/sandbox.d.ts +12 -0
  68. package/dist/types/scaffold.d.ts +0 -2
  69. package/dist/types/server.d.ts +1 -4
  70. package/dist/types/tooling.d.ts +3 -3
  71. package/dist/types/trusted-functions.d.ts +29 -0
  72. package/dist/types/types.d.ts +22 -8
  73. package/dist/types/verify-deployment.d.ts +2 -2
  74. package/dist/types.js +25 -5
  75. package/dist/typescript-authoring.js +67 -18
  76. package/dist/verify-deployment.js +3 -3
  77. package/docs/AI-AUTHORING.md +109 -10
  78. package/docs/ASSETS.md +2 -1
  79. package/docs/AUTH-BACKUP.md +32 -0
  80. package/docs/AWS.md +4 -4
  81. package/docs/BEST-PRACTICES.md +17 -8
  82. package/docs/CAPABILITIES.md +30 -17
  83. package/docs/CAPACITY.md +128 -31
  84. package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
  85. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
  86. package/docs/CI.md +8 -3
  87. package/docs/CLOUDFLARE.md +1 -2
  88. package/docs/CODEBASE-AUDIT-2026-09-20.md +278 -0
  89. package/docs/COMPLIANCE.md +6 -9
  90. package/docs/DEPLOYMENT-CHECKS.md +1 -1
  91. package/docs/DEVELOPMENT-PIPELINE.md +181 -0
  92. package/docs/EXTENSIONS.md +193 -11
  93. package/docs/FRAMEWORK.md +53 -33
  94. package/docs/FUNCTION-SECURITY.md +173 -32
  95. package/docs/INSTALL.md +0 -5
  96. package/docs/LOAD-TESTING.md +4 -4
  97. package/docs/LOCAL-DEVELOPMENT.md +3 -0
  98. package/docs/MIDDLEWARE.md +27 -16
  99. package/docs/MONITORING.md +2 -19
  100. package/docs/OBSERVABILITY.md +7 -16
  101. package/docs/OPEN-DECISIONS.md +184 -0
  102. package/docs/OPERATIONAL-PROOF.md +26 -30
  103. package/docs/OPERATIONS.md +23 -32
  104. package/docs/POLICIES.md +27 -8
  105. package/docs/PRERENDER.md +25 -13
  106. package/docs/PROJECT-DIRECTION.md +35 -10
  107. package/docs/READINESS.md +6 -2
  108. package/docs/README.md +20 -41
  109. package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
  110. package/docs/RELEASE-READINESS.md +50 -30
  111. package/docs/RELEASE-SECURITY.md +72 -86
  112. package/docs/RESILIENCE.md +16 -15
  113. package/docs/ROUTING.md +7 -10
  114. package/docs/SANDBOX-REVIEW.md +19 -6
  115. package/docs/SCAFFOLDING.md +0 -2
  116. package/docs/SECURITY-AUDIT.md +42 -2
  117. package/docs/SPECIFICATION.md +63 -27
  118. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +288 -0
  119. package/docs/SPIKE-BUSINESS-SUITE.md +16 -0
  120. package/docs/SPIKE-CORE-LAYERING.md +368 -0
  121. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +211 -0
  122. package/docs/SPIKE-LAMBDA-COMPILE.md +12 -2
  123. package/docs/STARTERS.md +1 -1
  124. package/docs/STATIC.md +105 -0
  125. package/docs/TOOLING.md +17 -12
  126. package/docs/TUNNELS.md +0 -3
  127. package/docs/TYPESCRIPT-AUTHORING.md +32 -12
  128. package/docs/TYPESCRIPT.md +25 -4
  129. package/docs/VERCEL.md +4 -5
  130. package/docs/VERSION-ALIGNMENT.md +46 -0
  131. package/docs/YAML-GUIDE.md +0 -3
  132. package/docs/YAML-REFERENCE.md +16 -6
  133. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
  134. package/docs/{MANAGEMENT-SECURITY.md → archive/2026-09-19/MANAGEMENT-SECURITY.md} +20 -0
  135. package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +26 -8
  136. package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +274 -43
  137. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
  138. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
  139. package/docs/archive/2026-09-19/ROADMAP.md +386 -0
  140. package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +12 -2
  141. package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +28 -12
  142. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +776 -0
  143. package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +18 -2
  144. package/docs/archive/README.md +27 -0
  145. package/docs/policies/agents.md +1 -1
  146. package/docs/policies/cache.md +13 -0
  147. package/docs/policies/compression.md +3 -2
  148. package/docs/policies/security.md +3 -2
  149. package/examples/assets/example.yaml +2 -2
  150. package/examples/aws/example.yaml +1 -1
  151. package/examples/cloudflare/example.yaml +1 -1
  152. package/examples/conditions/example.yaml +1 -1
  153. package/examples/cookbook/example.yaml +1 -1
  154. package/examples/cookbook/middleware/bucket.mjs +12 -2
  155. package/examples/cookbook/middleware/locale.mjs +7 -3
  156. package/examples/cookbook/route-index.json +1 -1
  157. package/examples/cookbook/routes/middleware.yaml +1 -1
  158. package/examples/egress/example.yaml +1 -1
  159. package/examples/extensions/example.yaml +1 -1
  160. package/examples/prerender/README.md +14 -6
  161. package/examples/prerender/example.yaml +1 -1
  162. package/examples/prerender/functions/page.mjs +4 -2
  163. package/examples/prerender/middleware/template.mjs +1 -1
  164. package/examples/prerender/prerender.mjs +1 -1
  165. package/examples/prerender/urlcode.yaml +8 -4
  166. package/examples/provider-conformance/example.yaml +1 -1
  167. package/examples/vercel/example.yaml +1 -1
  168. package/llms-full.txt +686 -239
  169. package/llms.txt +27 -15
  170. package/package.json +32 -5
  171. package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
  172. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +36 -22
  173. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +16 -22
  174. package/recipes/authenticated-json-api/README.md +4 -3
  175. package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
  176. package/recipes/authenticated-json-api/recipe.yaml +2 -2
  177. package/recipes/contact-form/functions/contact.mjs +2 -1
  178. package/recipes/contact-form/recipe.yaml +3 -3
  179. package/recipes/cors-api/README.md +2 -2
  180. package/recipes/cors-api/recipe.yaml +2 -2
  181. package/recipes/health-page/README.md +1 -1
  182. package/recipes/health-page/recipe.yaml +1 -1
  183. package/recipes/json-api/README.md +1 -1
  184. package/recipes/json-api/recipe.yaml +4 -4
  185. package/recipes/middleware/README.md +8 -4
  186. package/recipes/middleware/middleware/bucket.mjs +12 -2
  187. package/recipes/middleware/middleware/locale.mjs +7 -3
  188. package/recipes/middleware/recipe.yaml +1 -1
  189. package/recipes/protected-download/README.md +1 -1
  190. package/recipes/protected-download/recipe.yaml +2 -2
  191. package/recipes/redirect/recipe.yaml +1 -1
  192. package/recipes/static-plus-api/README.md +2 -2
  193. package/recipes/static-plus-api/public/index.html +1 -1
  194. package/recipes/static-plus-api/recipe.yaml +2 -2
  195. package/recipes/static-plus-api/urlcode.yaml +1 -1
  196. package/recipes/typescript/README.md +2 -1
  197. package/recipes/typescript/recipe.yaml +5 -5
  198. package/recipes/webhook-receiver/README.md +5 -1
  199. package/recipes/webhook-receiver/recipe.yaml +2 -1
  200. package/recipes/webhook-receiver/urlcode.yaml +7 -0
  201. package/schemas/recipe.schema.json +4 -3
  202. package/schemas/urlcode.schema.json +14 -41
  203. package/skills/urlcode/SKILL.md +32 -11
  204. package/starters/default/AGENTS.md +18 -18
  205. package/starters/default/urlcode.yaml +0 -1
  206. package/dist/link-api.js +0 -136
  207. package/dist/link-cli.js +0 -141
  208. package/dist/link-events.js +0 -76
  209. package/dist/link-records.js +0 -31
  210. package/dist/link-store-worker.js +0 -150
  211. package/dist/link-store.js +0 -250
  212. package/dist/management-policy.js +0 -40
  213. package/dist/sqlite-version.js +0 -6
  214. package/dist/types/link-api.d.ts +0 -30
  215. package/dist/types/link-cli.d.ts +0 -37
  216. package/dist/types/link-events.d.ts +0 -27
  217. package/dist/types/link-records.d.ts +0 -11
  218. package/dist/types/link-store-worker.d.ts +0 -1
  219. package/dist/types/link-store.d.ts +0 -130
  220. package/dist/types/management-policy.d.ts +0 -8
  221. package/dist/types/sqlite-version.d.ts +0 -1
  222. package/docs/DYNAMIC-LINKS.md +0 -61
  223. package/docs/links/cli.md +0 -110
  224. package/docs/links/limits.md +0 -175
  225. package/docs/links/management-api.md +0 -80
  226. package/docs/links/pools.md +0 -75
  227. package/docs/links/setup.md +0 -135
  228. package/docs/yaml/links.md +0 -30
  229. package/examples/live-links/README.md +0 -11
  230. package/examples/live-links/example.yaml +0 -21
  231. package/examples/live-links/tests/requests.json +0 -6
  232. package/examples/live-links/urlcode.yaml +0 -16
@@ -0,0 +1,184 @@
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.
@@ -1,22 +1,21 @@
1
1
  # Operational evidence and deployment acceptance
2
2
 
3
- `node scripts/operational-drills.ts` creates and deletes its own temporary project
4
- and store. It runs mixed real HTTP requests against native redirects, isolated
5
- functions and live links while updating records; asserts responses; rejects a bad
6
- reload; activates and rolls back a configuration; then closes all users of the
7
- store, explicitly verifies a successful WAL checkpoint, and restores a copied database. It checks integrity, record/version and
8
- latest durable audit revision. `URLCODE_SOAK_SECONDS=60` selects a longer run
9
- (1–3600 seconds, default 5). Output is JSON with request count, batch p99 duration,
10
- RSS and restore time. Batch latency is not per-request p99 or a capacity promise.
11
- CI runs the short drill on all six supported Node/OS combinations.
3
+ `node scripts/operational-drills.ts` creates and deletes its own temporary
4
+ project. It runs mixed real HTTP requests against native redirects and
5
+ `function` routes; asserts responses; rejects a bad reload; and activates and
6
+ rolls back a configuration. `URLCODE_SOAK_SECONDS=60` selects a longer run
7
+ (1–3600 seconds, default 5). Output is JSON with request count, batch p99
8
+ duration and RSS. Batch latency is not per-request p99 or a capacity promise.
9
+ CI's `verify` job runs the short drill across a 3-Node (22/24/26) × 3-OS
10
+ (Linux/macOS/Windows) matrix, nine combinations, but only on a push to `main`.
11
+ A pull request runs the same drill across all three Node versions on Linux
12
+ only (3 of the 9 combinations); the macOS/Windows legs only run once a PR
13
+ merges, per `.github/workflows/ci.yml`'s matrix.
12
14
 
13
- The container job additionally uses `--disk-full-dir /state` on a disposable
14
- 16 MiB tmpfs. It reserves recovery space, writes until a real volume exhaustion
15
- failure, checks available disk space, releases the reserve, proves a subsequent
16
- mutation succeeds, and checks integrity and equal committed link/audit counts.
17
- Never point this option at production storage: it intentionally consumes up to
18
- 84 MiB of writes in a newly created temporary child directory. The CI mount is
19
- nonpersistent. An I/O device failure or power cut is a different failure mode.
15
+ Core has no durable store of its own, so this drill has no backup/restore or
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.
20
19
 
21
20
  ## Required proof on the intended deployment
22
21
 
@@ -27,19 +26,16 @@ substitute for it. The deployment owner must record:
27
26
 
28
27
  | Exercise | Acceptance evidence |
29
28
  |---|---|
30
- | Soak | At least 24 hours at expected peak and burst load through actual TLS/ingress; native/function/live-link mix, slow clients, response correctness, p50/p95/p99, throughput, error/rejection rate, RSS plateau, CPU, FD and disk/WAL growth. Define numerical SLOs before starting |
31
- | Restore | Restore a consistent backup onto a separate host/volume. Reconcile collection counts, latest committed revisions and audit journal; measure RPO/RTO against agreed targets. Test encrypted backup access and credential recovery |
32
- | Logical export/restore | Run `links export` against a store under write load, restore it with `links import` onto a separate store, and reconcile record counts and every field. Prove the export's digest verifies, that a truncated stream is rejected, and that stale management ETags are discarded because the restore reassigns versions. An export carries no audit journal, so pair it with a file backup |
33
- | Kill/restart | Kill server and writer with outstanding reads/writes. Reconcile uncertain mutations by version/request ID, run integrity checks, restore readiness, and prove no duplicate successful conditional writes |
34
- | Disk/log exhaustion | Fill the actual disposable staging storage type and log destination. Verify bounded 503s, audit/mutation atomicity, alert delivery through an independent sink, reserve-space recovery and readiness after recovery |
35
- | Rollback | Deploy candidate by exact digest beside last-good, run route assertions, switch ingress, drain, then switch back. Verify configuration/policy compatibility and audit continuity; never downgrade to an unaudited writer |
36
- | Monitoring | Deliver test alerts for missing logs, sustained errors, pool rejection/failure, low disk, restart storms, backup age and failed readiness to a named on-call owner |
29
+ | Soak | At least 24 hours at expected peak and burst load through actual TLS/ingress; native/function mix, slow clients, response correctness, p50/p95/p99, throughput, error/rejection rate, RSS plateau, CPU and FD growth. Define numerical SLOs before starting |
30
+ | Kill/restart | Kill the server with outstanding requests. Reconcile uncertain mutations by request ID, restore readiness, and prove no duplicate side effects from application code |
31
+ | Rollback | Deploy candidate by exact digest beside last-good, run route assertions, switch ingress, drain, then switch back. Verify configuration/policy compatibility |
32
+ | Monitoring | Deliver test alerts for missing logs, sustained errors, pool rejection/failure, low disk, restart storms and failed readiness to a named on-call owner |
33
+
34
+ If your deployment adds a durable-state extension, add that extension's own
35
+ backup/restore, logical export/import and disk/log exhaustion exercises to
36
+ this table; core's proof above does not cover them.
37
37
 
38
38
  Record date, operator, source/app/policy/image digests, topology, hardware/limits,
39
- commands, duration, synthetic dataset size, raw metrics/log locations, result,
40
- RPO/RTO and unresolved findings. A reviewer signs the acceptance record; a blank
41
- record is not a pass. Do not copy only the main file of a live WAL database.
42
- The executable local drill uses a fully quiesced, explicitly checkpointed database intentionally. A last
43
- read-only connection can leave WAL frames even after every connection closes;
44
- closing alone is insufficient proof that the main database file is a full backup. For a
45
- live-backup system, prove its SQLite-consistent snapshot method separately.
39
+ commands, duration, synthetic dataset size, raw metrics/log locations, result
40
+ and unresolved findings. A reviewer signs the acceptance record; a blank
41
+ record is not a pass.
@@ -45,8 +45,10 @@ docker run --rm --name my-links \
45
45
  ```
46
46
 
47
47
  Replace the example mount with your app. The image uses the unprivileged `node`
48
- user; ensure mounted config/functions are readable by it. Only operator-owned components such as the optional link store can use writable
49
- mounts. Sandboxed application functions cannot access mounted files or installed
48
+ user; ensure mounted config/functions are readable by it. Core has no writable
49
+ mount of its own; a future mount-based extension (like `auth`/`admin`, see
50
+ [extensions](EXTENSIONS.md)) is the place for operator-owned writable state.
51
+ Sandboxed application functions cannot access mounted files or installed
50
52
  Node packages. The resource values above illustrate
51
53
  container limits, not a sizing recommendation; large configuration compilation
52
54
  can need more memory. Measure your workload. Tag/redeploy immutable image digests
@@ -63,7 +65,10 @@ at the proxy; they are unauthenticated and reveal route count/config digest.
63
65
 
64
66
  If functions perform sensitive actions, implement authentication and authorization
65
67
  in the application. A short URL is not automatically an access-control mechanism.
66
- Functions are untrusted and isolated in WASM by default. Keep separate deployment
68
+ Functions and middleware run trusted and unsandboxed by default, in the host
69
+ process with full Node, filesystem and network access; a route that declares
70
+ `sandbox: true` runs isolated in QuickJS/WebAssembly instead (see
71
+ [function security](FUNCTION-SECURITY.md)). Keep separate deployment
67
72
  processes/containers and narrowly scoped credentials as additional boundaries.
68
73
  Do not expose a public code-upload/multi-tenant service on the basis of the self-hosted release alone
69
74
  without separate security review and stronger service-level containment.
@@ -89,14 +94,14 @@ production does not watch or refresh secret values automatically.
89
94
 
90
95
  - `GET /_urlcode/health`: process liveness.
91
96
  - `GET /_urlcode/ready`: 200 when the active snapshot and all function workers
92
- are available and configured link-store readers are healthy; 503 while a worker/store
97
+ are available; 503 while a worker
93
98
  is unavailable. Busy workers alone do not
94
- mark readiness down. A failed worker or store connection is replaced with
99
+ mark readiness down. A failed worker is replaced with
95
100
  exponential backoff (250 ms doubling to a 30-second ceiling) and readiness
96
101
  reports 503 until every slot is serving again. Replacement does not stop, so a
97
102
  request-triggered deadline cannot disable functions until an operator restarts;
98
103
  a cause that keeps recurring keeps the instance shedding load and needs an
99
- operator. Alert on sustained `function_worker`/`link_store_worker` restart events.
104
+ operator. Alert on sustained `function_worker` restart events.
100
105
  - Probes are answered from their own admission budget (16 by default,
101
106
  `--max-in-flight-health`), so they stay available while the application is
102
107
  saturated without being an unmetered endpoint. They are unauthenticated and
@@ -125,11 +130,15 @@ production does not watch or refresh secret values automatically.
125
130
  admitted through response completion; excess requests receive 503. Health probes
126
131
  remain available under admission saturation. A 15-second socket inactivity
127
132
  timeout closes stalled readers/writers. Proxy timeouts/rate limits still matter.
128
- - Functions: 2 concurrent workers (`--workers`), no queue, 5-second deadline
129
- (`--function-timeout-ms`), 1 MiB buffered response (`--max-response-bytes`) and
130
- 16 KiB response headers. Saturation 503; timeout 504; error 502.
133
+ - Functions: a `sandbox: true` route gets 2 concurrent workers (`--workers`),
134
+ no queue and a 5-second deadline (`--function-timeout-ms`); a trusted route
135
+ (`sandbox` false or absent, the default) shares the in-flight admission cap
136
+ instead of a worker pool and races the same deadline. Either mode buffers
137
+ 1 MiB of response (`--max-response-bytes`) and 16 KiB response headers.
138
+ Saturation 503; timeout 504; error 502.
131
139
  QuickJS guests have a 32 MiB heap and 512 KiB stack budget and no network or
132
- host capabilities. Outer workers have additional V8 limits. Total process/WASM
140
+ host capabilities; a trusted route has neither budget and full Node access.
141
+ Outer workers have additional V8 limits. Total process/WASM
133
142
  memory still needs deployment-level limits; do not equate guest budget with RSS.
134
143
 
135
144
  `urlcode serve`/`dev` and the JavaScript server API both configure workers,
@@ -140,24 +149,14 @@ deployment controls, not portable route behavior. Horizontal replicas
140
149
  must use identical application/config versions and secret bindings. In-memory
141
150
  function state is reset after every invocation, not durable/shared application state.
142
151
  General application storage needs a future explicit capability broker; no
143
- storage/network access is exposed to the guest. The optional native
144
- [link store](DYNAMIC-LINKS.md) supports live short-link records on one host.
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.
145
155
 
146
156
  The health version combines route-definition and asset-representation digests;
147
157
  it does not identify the complete function/runtime release. Record runtime commit,
148
158
  application commit, dependency locks and image digest in your deployment system.
149
159
 
150
- ## Optional dynamic-link deployment
151
-
152
- Keep SQLite and management tokens outside the application, in a private durable
153
- local directory. Initialize through `links init/create`, bind public serving with
154
- `--link-store`, and expose management on a separate private listener. Restrict
155
- its token to your trusted backend; apply ingress limits and backups. Public
156
- serving opens read-only pools; management has a separate writer and read pool.
157
- Budget connections across processes and monitor writer health separately. See
158
- [dynamic-link operations](DYNAMIC-LINKS.md). Multiple host replicas must not share
159
- this file over a network filesystem; no distributed adapter is included yet.
160
-
161
160
  ## Deployment and rollback procedure
162
161
 
163
162
  1. Build a candidate from pinned runtime/application revisions and lockfiles.
@@ -174,7 +173,7 @@ this file over a network filesystem; no distributed adapter is included yet.
174
173
  This is an operator procedure, not an implemented deployment control plane.
175
174
  Rollback cannot undo a function's external side effects or migrate an app's
176
175
  state automatically. Plan those independently. Keep Git definitions backed up;
177
- back up any app-owned persistent state separately. YAML routes require no database; dynamic link records require separate backups.
176
+ back up any app-owned persistent state separately. YAML routes require no database.
178
177
 
179
178
  ## Capacity and incident planning
180
179
 
@@ -200,11 +199,3 @@ adapter is made by the current release.
200
199
  The [2026-09-16 internal audit](SECURITY-AUDIT.md) records fixes, regression evidence
201
200
  and remaining security/operational gates. This is not an independent assessment.
202
201
 
203
- ## Management hardening baseline
204
-
205
- Management is now restricted to literal loopback addresses. Prefer `--auth-file`
206
- for individual expiring, revocable credentials with collection/action scopes.
207
- Every successful built-in store mutation has an atomic, durable SQLite audit row;
208
- HTTP request logs remain best effort. See [management security](MANAGEMENT-SECURITY.md)
209
- for policy examples, compatibility, archival and rollback requirements, and
210
- [operational proof](OPERATIONAL-PROOF.md) for executable recovery drills.
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
 
@@ -40,11 +41,29 @@ routes:
40
41
  ```
41
42
 
42
43
  `policies` at the top level sets project defaults; `routes.<path>.policies`
43
- adjusts them for one route. One route-level short form exists: `auth`, which
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). The pattern is reserved for `cache`: a route-level
46
- `cache: {strategy, maxAge}` may later expand to `policies.cache` the same way,
47
- but it is not implemented, and `policies.cache` is the only cache form today. Both accept the same keys: `profile` plus one entry
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
54
+ `policies.cache` the same way — the compiler merges it into that route's
55
+ `policies` before anything else reads the project, so `routes`, `audit` and
56
+ `explain` see only the expanded long form, and it is refused alongside a
57
+ direct `policies.cache` on the same route (use one form):
58
+
59
+ ```yaml
60
+ routes:
61
+ /feed:
62
+ function: { source: functions/feed.mjs }
63
+ cache: { strategy: swr, maxAge: 30 } # expands to policies: { cache: {...} }
64
+ ```
65
+
66
+ Both accept the same keys: `profile` plus one entry
48
67
  per policy, each either an object or `false`. Unknown keys fail validation, as
49
68
  everywhere in the project format. The
50
69
  [field reference](YAML-REFERENCE.md) lists every accepted field with its
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
@@ -14,7 +16,7 @@ and no capability.
14
16
  ## Why render ahead of time
15
17
 
16
18
  A function response body is readable by middleware through `text()`. A native
17
- `page`, `static`, `download`, `redirect`, `respond` or `link` body is not — that
19
+ `page`, `static`, `download`, `redirect` or `respond` body is not — that
18
20
  opacity is a deliberate contract, not a gap, and it is what keeps native file
19
21
  serving free of guest code. See [middleware](MIDDLEWARE.md).
20
22
 
@@ -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
 
@@ -165,7 +169,7 @@ service and redirect; provider asset adapters are not implemented.
165
169
 
166
170
  The first two rows above are the sandbox's snapshot budgets: at most 127 guest
167
171
  modules and 4 MiB of module source in one snapshot. They are deliberate — part
168
- of what [function security](FUNCTION-SECURITY.md) promises about untrusted guest
172
+ of what [function security](FUNCTION-SECURITY.md) promises about sandboxed guest
169
173
  code — and the render step does not relax them for trusted generated content.
170
174
  Serving a project that crosses either still fails at startup, naming the module
171
175
  that crossed it:
@@ -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.
@@ -6,12 +6,31 @@ under. It describes intent and
6
6
  boundaries, not a delivery schedule; the [roadmap](../ROADMAP.md) owns sequence
7
7
  and the [readiness register](RELEASE-READINESS.md) owns what is proven.
8
8
 
9
+ ## Design principle: declarative-first
10
+
11
+ > Use URLCode's highest-level declarative features whenever possible. Generate custom code only when the framework cannot express the requirement.
12
+
13
+ This is the default for human authors, coding agents, examples and framework
14
+ changes. Before writing application code, check the installed version's declarative
15
+ primitives, YAML configuration, policies, supported extensions and reusable recipes
16
+ or templates. Prefer the highest-level supported abstraction that satisfies the
17
+ requirement; do not recreate framework behavior in a lower-level handler.
18
+
19
+ Custom functions or middleware remain valid for application-specific behavior the
20
+ framework cannot express. Keep that code focused, explain the missing capability,
21
+ and report reusable gaps to the owning repository. Never invent YAML fields or
22
+ bypass target limits, sandbox isolation or operator grants to avoid custom code.
23
+
9
24
  ## What URLCode is
10
25
 
11
26
  A portable runtime for programmable URL behavior. A project declares its public
12
- URL surface in YAML, adds isolated JavaScript only where declarative handlers are
27
+ URL surface in YAML, adds JavaScript only where declarative handlers are
13
28
  not enough, and runs the same definition locally, in a container, or on operator
14
- infrastructure. The project format is deliberately bounded so a runtime can
29
+ infrastructure. That JavaScript runs trusted, in the host process, like any
30
+ other project code, unless the project isolates a specific route with
31
+ `sandbox: true` (docs/SPIKE-DEFAULT-TRUST-MODEL.md) — a judgment call the
32
+ project makes per route, not a default the runtime imposes on all guest code.
33
+ The project format is deliberately bounded so a runtime can
15
34
  validate it, inspect it, test it and eventually carry it across hosting providers.
16
35
 
17
36
  Git owns route definitions and code. Operators own credentials, storage and
@@ -19,12 +38,18 @@ capability grants. Application data stays in the operator's systems.
19
38
 
20
39
  ## What URLCode is not
21
40
 
22
- - **Not a URL shortener.** Short links are one handler beside redirects,
23
- validated responses, request functions, middleware, pages, static assets and
24
- downloads. The [live-link store](DYNAMIC-LINKS.md) is optional and single-host.
25
- - **Not a general Node web framework.** Guest code runs inside WASM isolation
26
- with no ambient filesystem, network or Node APIs. Behavior that cannot be
27
- expressed in the bounded contract is rejected rather than emulated.
41
+ - **Not a URL shortener.** Core is redirects, validated responses, request
42
+ functions, middleware, pages, static assets and downloads. Stored short
43
+ links are not a core handler; the `urlcode-dynamic-link` extension package
44
+ that owned them has been retired and unpublished.
45
+ - **Not a general Node web framework.** There is no framework code to write
46
+ for routing, validation, middleware wiring or policies those are declared
47
+ in YAML and enforced by the runtime. Function/middleware code that needs
48
+ isolation from the host (untrusted input, an unreviewed contribution, a
49
+ particularly sensitive secret) opts into `sandbox: true`, which runs it
50
+ inside WASM isolation with no ambient filesystem, network or Node APIs.
51
+ Behavior that cannot be expressed in the bounded contract of a sandboxed
52
+ route is rejected rather than emulated.
28
53
  - **Not a hosting account system.** There is no end-user identity, billing or
29
54
  public account surface. Management is a private operator API.
30
55
  - **Not a provider configuration format.** Provider infrastructure settings do
@@ -54,7 +79,7 @@ and the database owns the machinery. One level up, a route should read
54
79
 
55
80
  ```yaml
56
81
  /admin:
57
- auth: { required: true, roles: [admin] }
82
+ auth: { role: admin }
58
83
  function: { source: functions/admin.mjs }
59
84
  ```
60
85
 
@@ -79,7 +104,7 @@ lines an agent generated, how many are the idea and how many are plumbing. A
79
104
  traditional build might be 2,900 lines of business logic inside 18,400; the
80
105
  same application on URLCode should be the same 2,900 inside a few thousand.
81
106
  Until a reproducible benchmark shows that ratio, the thesis is a hypothesis,
82
- and [next steps](NEXT-STEPS.md) puts the benchmark before the features.
107
+ and [the roadmap](../ROADMAP.md) puts the benchmark before the features.
83
108
 
84
109
  ## License
85
110
 
package/docs/READINESS.md CHANGED
@@ -17,14 +17,18 @@ printed, and no remote load-test target is accepted.
17
17
 
18
18
  ## Inventory and count reconciliation
19
19
 
20
- `routes` reports each configured route's pattern, handler, exact allowed methods
20
+ `routes` reports each configured route's pattern, handler, exact allowed methods,
21
+ execution mode (`sandbox`, with `sandboxReason` when the route declares one)
21
22
  and active/disabled/expired state. It includes routes from YAML includes. A
22
23
  parameter pattern is one route; its possible URLs are not a finite route count.
23
24
  A static mount is one route, even when it contains many files.
24
25
 
25
26
  `routes --compare previous.json` diffs the current inventory against a saved
26
27
  `routes` report: added, removed and changed routes (handler, methods, state,
27
- middleware count, policies, generated marker and the policy description). It
28
+ execution mode and its reason, middleware count, policies, generated marker and
29
+ the policy description). A route that flips between trusted and sandboxed
30
+ execution is a change, including when its handler is native and only its
31
+ middleware runs project code. It
28
32
  prints JSON, or Markdown tables with `--format markdown`, and always exits 0;
29
33
  it reports, it does not judge. The [GitHub action](CI.md) posts this diff on
30
34
  pull requests.