@jimhoyd/urlcode 0.5.5 → 0.5.8

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 (136) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +16 -1
  2. package/.claude/skills/urlcode-operations/SKILL.md +5 -1
  3. package/README.md +24 -14
  4. package/dist/BUILD-MANIFEST.json +55 -54
  5. package/dist/agents-guide.js +5 -2
  6. package/dist/assets.js +2 -2
  7. package/dist/authoring.js +3 -58
  8. package/dist/aws.js +21 -4
  9. package/dist/body-schema.js +2 -3
  10. package/dist/capabilities.js +1 -1
  11. package/dist/capability-query.js +3 -3
  12. package/dist/catalog.js +1 -1
  13. package/dist/cli.js +42 -15
  14. package/dist/cloudflare.js +26 -1
  15. package/dist/config.js +1 -1
  16. package/dist/context.js +6 -6
  17. package/dist/egress.js +1 -1
  18. package/dist/examples.js +2 -2
  19. package/dist/explain.js +4 -6
  20. package/dist/extension-artifacts.js +8 -8
  21. package/dist/extension-transport.js +6 -4
  22. package/dist/function-worker.js +5 -2
  23. package/dist/functions.js +1 -1
  24. package/dist/guest-api.js +22 -15
  25. package/dist/http-policy.js +1 -3
  26. package/dist/http-response.js +61 -10
  27. package/dist/init-with.js +8 -8
  28. package/dist/interchange.js +3 -2
  29. package/dist/logging.js +4 -4
  30. package/dist/match.js +1 -1
  31. package/dist/mcp-authoring.js +1 -2
  32. package/dist/mcp.js +2 -2
  33. package/dist/object-guards.js +17 -0
  34. package/dist/pattern-guard.js +33 -3
  35. package/dist/policies/agents.js +1 -1
  36. package/dist/policies/cache.js +16 -4
  37. package/dist/policies/compression.js +1 -1
  38. package/dist/policies/security.js +1 -1
  39. package/dist/policies/throttle.js +1 -1
  40. package/dist/policies.js +1 -1
  41. package/dist/policy.js +3 -3
  42. package/dist/prerender.js +1 -2
  43. package/dist/project-dependencies.js +8 -8
  44. package/dist/proxy.js +1 -1
  45. package/dist/readiness.js +16 -17
  46. package/dist/review.js +2 -2
  47. package/dist/route-diff.js +4 -4
  48. package/dist/router.js +1 -1
  49. package/dist/runtime.js +4 -1
  50. package/dist/scaffold.js +1 -1
  51. package/dist/schema-query.js +1 -1
  52. package/dist/server.js +49 -7
  53. package/dist/signals.js +2 -2
  54. package/dist/site.js +2 -2
  55. package/dist/trusted-functions.js +1 -1
  56. package/dist/types/assets.d.ts +3 -2
  57. package/dist/types/authoring.d.ts +3 -6
  58. package/dist/types/body-schema.d.ts +2 -1
  59. package/dist/types/catalog.d.ts +2 -1
  60. package/dist/types/config.d.ts +0 -8
  61. package/dist/types/context.d.ts +3 -1
  62. package/dist/types/egress.d.ts +1 -1
  63. package/dist/types/explain.d.ts +4 -4
  64. package/dist/types/extension-artifacts.d.ts +4 -13
  65. package/dist/types/extension-transport.d.ts +6 -4
  66. package/dist/types/functions.d.ts +1 -1
  67. package/dist/types/http-response.d.ts +7 -3
  68. package/dist/types/init-with.d.ts +3 -9
  69. package/dist/types/logging.d.ts +4 -3
  70. package/dist/types/match.d.ts +2 -1
  71. package/dist/types/object-guards.d.ts +7 -0
  72. package/dist/types/pattern-guard.d.ts +0 -1
  73. package/dist/types/policies/cache.d.ts +4 -3
  74. package/dist/types/policies/compression.d.ts +1 -1
  75. package/dist/types/policies/throttle.d.ts +1 -1
  76. package/dist/types/policy.d.ts +4 -3
  77. package/dist/types/project-dependencies.d.ts +1 -40
  78. package/dist/types/proxy.d.ts +2 -1
  79. package/dist/types/readiness.d.ts +12 -16
  80. package/dist/types/review.d.ts +0 -2
  81. package/dist/types/route-diff.d.ts +4 -3
  82. package/dist/types/router.d.ts +1 -1
  83. package/dist/types/server.d.ts +22 -0
  84. package/dist/types/signals.d.ts +3 -2
  85. package/dist/types/site.d.ts +1 -2
  86. package/dist/types/trusted-functions.d.ts +1 -1
  87. package/dist/types/types.d.ts +17 -11
  88. package/dist/types/vercel.d.ts +1 -0
  89. package/dist/types/verify-deployment.d.ts +1 -1
  90. package/dist/types.js +19 -12
  91. package/dist/vercel.js +12 -3
  92. package/dist/verify-deployment.js +29 -11
  93. package/docs/AI-AUTHORING.md +10 -1
  94. package/docs/FUNCTION-SECURITY.md +4 -0
  95. package/docs/README.md +2 -2
  96. package/docs/TOOLING.md +27 -1
  97. package/examples/assets/Makefile +1 -1
  98. package/examples/assets/example.yaml +3 -3
  99. package/examples/aws/example.yaml +3 -3
  100. package/examples/cloudflare/example.yaml +3 -3
  101. package/examples/compliance/README.md +1 -1
  102. package/examples/compliance/example.yaml +1 -1
  103. package/examples/compliance/rules.mjs +2 -2
  104. package/examples/conditions/example.yaml +3 -3
  105. package/examples/cookbook/README.md +4 -4
  106. package/examples/cookbook/example.yaml +3 -3
  107. package/examples/coverage-waiver/example.yaml +3 -3
  108. package/examples/egress/example.yaml +2 -2
  109. package/examples/extensions/example.yaml +1 -1
  110. package/examples/lifecycle/example.yaml +2 -2
  111. package/examples/not-found/README.md +2 -2
  112. package/examples/not-found/example.yaml +3 -3
  113. package/examples/prerender/README.md +4 -4
  114. package/examples/prerender/example.yaml +2 -2
  115. package/examples/provider-conformance/example.yaml +2 -2
  116. package/examples/shared-blocks/example.yaml +3 -3
  117. package/examples/vercel/example.yaml +3 -3
  118. package/llms-full.txt +49 -18
  119. package/llms.txt +9 -2
  120. package/package.json +14 -13
  121. package/skills/urlcode/SKILL.md +7 -7
  122. package/starters/default/.github/workflows/urlcode.yml +2 -1
  123. package/starters/default/AGENTS.md +3 -3
  124. package/starters/default/Makefile +1 -1
  125. package/starters/default/README.md +15 -13
  126. package/starters/default/tests/requests.json +0 -50
  127. package/starters/default/urlcode.yaml +1 -4
  128. package/dist/scripts/operational-drills.js +0 -33
  129. package/starters/default/functions/hello.mjs +0 -3
  130. package/starters/default/middleware/headers.mjs +0 -6
  131. package/starters/default/routes/functions.yaml +0 -20
  132. package/starters/default/routes/marketing/links.yaml +0 -7
  133. package/starters/page/README.md +0 -14
  134. package/starters/page/public/index.html +0 -12
  135. package/starters/page/tests/requests.json +0 -17
  136. package/starters/page/urlcode.yaml +0 -6
@@ -11,6 +11,15 @@ validates. Features outside that contract do not silently degrade — they fail
11
11
  validation. So the cost of guessing is a broken project, and the whole job here
12
12
  is to author only what the pinned revision implements and then prove it.
13
13
 
14
+ ## No project yet? Install the scoped package
15
+
16
+ The npm package is `@jimhoyd/urlcode` — always scoped. There is no unscoped
17
+ `urlcode` package on the registry; `npm view urlcode` 404s. Install with
18
+ `npm install @jimhoyd/urlcode`, then scaffold with
19
+ `npx urlcode init .` (works in a directory holding only
20
+ `package.json`, `package-lock.json`, `node_modules` or `.git`). Once installed,
21
+ the rest of this skill and `urlcode context --project DIR` take over.
22
+
14
23
  ## Declarative-first default
15
24
 
16
25
  > Use URLCode's highest-level declarative features whenever possible. Generate custom code only when the framework cannot express the requirement.
@@ -38,6 +47,12 @@ supplies a host file, `get_extensions`. Bare `urlcode capabilities`, `recipes
38
47
  list`, the compact `llms.txt` index and `llms-full.txt` remain deliberate
39
48
  fallback/reference: in a source checkout read the matching task guide from
40
49
  `docs/`; in an npm installation search the heading in `llms-full.txt`.
50
+ [URLCode AI](https://urlcode.ai/) is an optional, separate hosted service for
51
+ shared skills and LLM tooling. Its remote MCP supplements the local
52
+ project-aware `urlcode` server; never replace `.mcp.json` or put its bearer
53
+ token in project files. Configure it only through the MCP client's secret
54
+ facility; see the URLCode tooling guide for connection details. Its
55
+ machine-readable entry point is `https://urlcode.ai/llms.txt`.
41
56
  When the project has an operator host file, inspect `urlcode extensions
42
57
  --project <dir> --host-file <absolute-file> --json` (MCP: `get_extensions`)
43
58
  before writing extension configuration or project hooks. The report is the
@@ -145,7 +160,7 @@ urlcode audit --project ./my-links --expect-routes <actual intended count>
145
160
  ```
146
161
 
147
162
  Use the real intended route count, including any `site`-generated routes. In a
148
- runtime checkout, substitute `node src/cli.ts` for `urlcode`; in a project made
163
+ runtime checkout, substitute `node packages/core/src/cli.ts` for `urlcode`; in a project made
149
164
  from `urlcode-template`, the equivalent npm scripts work. External bindings
150
165
  require an already reviewed policy — add `--policy` where needed.
151
166
 
@@ -40,6 +40,10 @@ does not duplicate the repository's authored documentation tree.
40
40
 
41
41
  `llms.txt` at the repository root indexes all of the above alongside the
42
42
  authoring docs.
43
+ [URLCode AI](https://urlcode.ai/) is the optional hosted companion for shared
44
+ skills and LLM tooling. It does not replace local deployment checks or the
45
+ project-aware `urlcode mcp` server. Its machine-readable entry point is
46
+ `https://urlcode.ai/llms.txt`.
43
47
 
44
48
  ## Workflow
45
49
 
@@ -72,7 +76,7 @@ urlcode verify-deployment --project ./my-links --target https://links.example \
72
76
 
73
77
  Run the actual commands and report actual results, never "should work" or
74
78
  "should be reachable". `verify-deployment` needs a real target; do not
75
- simulate its output. In a runtime checkout, substitute `node src/cli.ts` for
79
+ simulate its output. In a runtime checkout, substitute `node packages/core/src/cli.ts` for
76
80
  `urlcode`. Pass `--policy` where a snapshot needs bindings already reviewed
77
81
  by the operator.
78
82
 
package/README.md CHANGED
@@ -10,7 +10,7 @@ console as operator-installed extensions instead of building them again.
10
10
 
11
11
  [![Verify](https://github.com/jimhoyd-com/urlcode/actions/workflows/ci.yml/badge.svg)](https://github.com/jimhoyd-com/urlcode/actions/workflows/ci.yml)
12
12
 
13
- [Documentation](docs/README.md) · [The framework](docs/FRAMEWORK.md) · [For AI agents](llms.txt) · [Starter](https://github.com/jimhoyd-com/urlcode-template) · [Contributing](CONTRIBUTING.md) · [Security](SECURITY.md)
13
+ [Documentation](docs/README.md) · [The framework](docs/FRAMEWORK.md) · [For AI agents](llms.txt) · [URLCode AI](https://urlcode.ai/) · [Starter](https://github.com/jimhoyd-com/urlcode-template) · [Contributing](CONTRIBUTING.md) · [Security](SECURITY.md)
14
14
 
15
15
  **Your AI should build your application, not your framework.** Coding agents
16
16
  rebuild the same routing, validation, middleware, policies and authentication
@@ -37,8 +37,8 @@ come only from operator grants pinned to the project revision; grants govern
37
37
  that injected context, not the ambient Node environment a trusted, in-process
38
38
  module can reach on its own like any other code in the host.
39
39
 
40
- URLCode is not a URL shortener: stored short links are an operator-installed
41
- extension, not core's job. It is not a
40
+ URLCode is not a URL shortener: stored short links are declared through the
41
+ operator-installed `store` extension's collections, not core's job. It is not a
42
42
  general Node web framework: routing, validation, middleware wiring and
43
43
  policies are declared in YAML, not hand-wired; isolating a specific route's
44
44
  code from the host is an explicit `sandbox: true` opt-in, not something every
@@ -48,7 +48,7 @@ See [project direction](docs/PROJECT-DIRECTION.md).
48
48
 
49
49
  ## The framework
50
50
 
51
- Four packages, one project shape. A project climbs from redirects to a full
51
+ Six packages, one project shape. A project climbs from redirects to a full
52
52
  application by adding YAML; the operator wires trusted packages in one host
53
53
  file outside the project. The full map, the composition contract and the rules
54
54
  an AI agent must follow are in [the framework](docs/FRAMEWORK.md).
@@ -59,6 +59,8 @@ an AI agent must follow are in [the framework](docs/FRAMEWORK.md).
59
59
  | [urlcode-ui](packages/ui) (workspace source) | Shared presentation: escaped templates, shadcn/ui partials, themes, translations | signed `extension-bundles@v…` GitHub Release |
60
60
  | [urlcode-auth](packages/auth) (workspace source) | Accounts: password, passkeys, OIDC, email codes, TOTP, sessions, roles, account page | signed `extension-bundles@v…` GitHub Release |
61
61
  | [urlcode-admin](packages/admin) (workspace source) | Administration: users, sessions, roles, audit, approvals, cases, impersonation | signed `extension-bundles@v…` GitHub Release |
62
+ | [urlcode-store](packages/store) (workspace source) | Durable bounded JSON collections exposed as a typed CRUD API | signed `extension-bundles@v…` GitHub Release |
63
+ | [urlcode-forms](packages/forms) (workspace source) | Bounded server-rendered form flows: escaped controls, admission, CSRF, validation | unreleased bundle source |
62
64
 
63
65
  Core stays available from npm, GitHub Releases and Homebrew. New sites install
64
66
  extensions from an immutable, attested `extension-bundles@v…` GitHub Release;
@@ -69,9 +71,10 @@ and channel alignment](docs/VERSION-ALIGNMENT.md).
69
71
 
70
72
  `urlcode-dynamic-link` and `urlcode-short` were published once as
71
73
  `0.1.0-alpha.1` and have since been retired: both were unpublished from npm and
72
- their repositories deleted, and neither has a successor. Nothing supported
73
- provides stored short links today a project that needs them owns that storage
74
- itself. Anything still pinned to `@jimhoyd/urlcode-dynamic-link@0.1.0-alpha.1`
74
+ their repositories deleted, and neither has a direct successor. A project that
75
+ wants stored short links declares a collection through the `store` extension
76
+ (see [docs/STORE.md](docs/STORE.md)) instead. Anything still pinned to
77
+ `@jimhoyd/urlcode-dynamic-link@0.1.0-alpha.1`
75
78
  also has to deal with its exact declared peer `@jimhoyd/urlcode: 0.4.0-alpha.1`,
76
79
  which cannot be installed beside core `0.4.0-alpha.2` and never will be.
77
80
 
@@ -107,7 +110,7 @@ it. Cross-repository acceptance is tracked in
107
110
  ## Status
108
111
 
109
112
  <!-- urlcode-current-version:start -->
110
- This checkout prepares the coordinated `0.5.5` core, UI, auth, admin and store
113
+ This checkout prepares the coordinated `0.5.8` core, UI, auth, admin and store
111
114
  release. Package availability remains a live registry fact: use `npm run
112
115
  release:status` or the release's signed `train.json` before selecting an
113
116
  install combination. Independent package versioning remains enabled, and a
@@ -130,7 +133,8 @@ remain open.
130
133
 
131
134
  Core has no native `link` handler. Stored short links moved out to a
132
135
  mount-based `urlcode-dynamic-link` extension, which has since been retired and
133
- unpublished; no supported package provides them.
136
+ unpublished; a project that needs them declares a collection through the
137
+ `store` extension instead (see [docs/STORE.md](docs/STORE.md)).
134
138
 
135
139
  URLCode is free and open-source software licensed under the
136
140
  [Apache License 2.0](LICENSE). Commercial use, modification, redistribution and
@@ -144,6 +148,8 @@ Start with the [YAML guide and recipe book](docs/YAML-GUIDE.md),
144
148
  authoring, use [the AI guide](docs/AI-AUTHORING.md), the bundled agent skills
145
149
  ([authoring](.claude/skills/urlcode-authoring/SKILL.md),
146
150
  [operations](.claude/skills/urlcode-operations/SKILL.md)) and [llms.txt](llms.txt).
151
+ For optional shared skills and hosted LLM tooling, use [URLCode AI](https://urlcode.ai/);
152
+ its remote MCP augments, rather than replaces, the local project server.
147
153
  Agents can also read project-pinned, signed declarative extension schemas through
148
154
  the read-only MCP tools described in [extensions](docs/EXTENSIONS.md#signed-declarative-artifacts);
149
155
  those artifacts are inert data, not an alternate executable package channel.
@@ -211,11 +217,12 @@ urlcode init my-urls && cd my-urls
211
217
  urlcode dev
212
218
  ```
213
219
 
214
- Open `http://127.0.0.1:3000/hello/Ada` for the function route and `/go` for
215
- the redirect. Edit the YAML; valid changes reload. `urlcode test` runs the
216
- project's HTTP fixtures. The [install guide](docs/INSTALL.md) covers the
217
- checksum-verified script, project-local installs, the container image and
218
- signed provenance. To work from a clone: `git clone … && make dev`.
220
+ The starter deliberately has no routes. Ask the local MCP `get_context` tool
221
+ (or run `urlcode context --project .`), then add the smallest declarative route
222
+ or custom code the application needs. `urlcode test` runs the project's HTTP
223
+ fixtures. The [install guide](docs/INSTALL.md) covers the checksum-verified
224
+ script, project-local installs, the container image and signed provenance. To
225
+ work from a clone: `git clone … && make dev`.
219
226
 
220
227
  ## A URL that runs your function
221
228
 
@@ -283,6 +290,9 @@ capability matrix and a copyable task prompt. `urlcode mcp` exposes read-only
283
290
  inspection, validation and conversion previews over stdio, and
284
291
  `--allow-authoring` adds project-confined authoring tools
285
292
  ([tooling](docs/TOOLING.md)).
293
+ [URLCode AI](https://urlcode.ai/) is the optional hosted companion for shared
294
+ skills and LLM tooling; its authenticated remote MCP setup is documented in
295
+ [tooling](docs/TOOLING.md#optional-hosted-ai-mcp).
286
296
 
287
297
  ## Built with URLCode
288
298
 
@@ -4,21 +4,21 @@
4
4
  "dist/adapters.js": "a7153ec52f2ed7e0cdd8ed7d433504815f402ec53b2b1c3d38d0cf0e2fcf6509",
5
5
  "dist/agent-context.js": "1e4e9c7b85d155c06d99d8b70f625bd7ab31330a0207aef6ed1803243b759611",
6
6
  "dist/agent-lists.js": "35ab484198897501d011cb6cc00b5a6787192e19df29c97646e8c118fcc19eef",
7
- "dist/agents-guide.js": "421df50bcf258f23aba742148e53074512584a10ba35a6264e70d3eacc016e3e",
8
- "dist/assets.js": "0833b093d2fe457ce68281110dc07d47914992753607741c13b339d92c76cf5c",
7
+ "dist/agents-guide.js": "307d92879034ef019213ab5c15ff5806de3333da4bb263d46fa87049d1a85d5c",
8
+ "dist/assets.js": "85d8e379dab466bc406b2ea1a9385f1fcecb35d05d825eb83089c55a62f6d1a4",
9
9
  "dist/authoring-files.js": "ad32814f9ae9c549c6247396f982aa700d13a293cc0a589609eccc68f8965550",
10
- "dist/authoring.js": "a52d289c67043cad170babb6adab7ca23be1d7c7e5a67e43eaadfa73d2fe7665",
11
- "dist/aws.js": "7d8e3a97b4f6dbefbd68da058f05192469d1efcaed5458b284863ebe462e060e",
12
- "dist/body-schema.js": "0227ef8cf2b380e3ef65a358533aa0e0f4a119c94e63747b8eb03fe65a6b9193",
10
+ "dist/authoring.js": "a5620752a79d70525d36718411470fbc7ac9c7b4c8abb09a53355f92f9805393",
11
+ "dist/aws.js": "34bdbb85657426dc54a8301b5bcfa38c7559371215c430939438cc6b71dfa29e",
12
+ "dist/body-schema.js": "1c42bcd24187dfe0a7c5326f110081b714595fdfe577c2faea35cab3ca31d45a",
13
13
  "dist/build-cloudflare.js": "ff92e72d0a63d70dd8c28b52978edd9fb23cb16601e4dfebd07c2cef7bd73661",
14
14
  "dist/build-static.js": "3920b54bad9a44c75de79f0bef2b2a919bfb3e20a8619eaf8d48cad2856978ac",
15
15
  "dist/bulk.js": "aac88d422bd9a7a341a421fb4e29ff5580b28d39250b7f1f5315ba20ae92da8a",
16
- "dist/capabilities.js": "bb98f5bbf54cc4e82fa197f999a621ad815273176f02692c35566886e2bb8777",
17
- "dist/capability-query.js": "9f68bc94901451d7b9ecdd3aee8c3376dca7f0b890e31d8205306cf8120dea86",
18
- "dist/catalog.js": "c378ef9bfd63b940790f6975f7db54557c2459722b65a7c170b97df24184bc52",
19
- "dist/cli.js": "b8ff5c19dd2e1619ad4ba7de6eb811770f38e0b394f2bcded156d388d39c8f0b",
16
+ "dist/capabilities.js": "f14c17de63764f6f4686fc1404bf21aa5cf56125ad81e6d6157a429438fc0cbf",
17
+ "dist/capability-query.js": "00fbd8b592dea05de7487f3de21e6aa8808a6d2a90e8808811abeb843375dab3",
18
+ "dist/catalog.js": "629ed1d3580d5c5e238a4293e5b8491ba99d6948accda316bbd2ee3d7902cc0f",
19
+ "dist/cli.js": "5a99ca3f3d7c374f809a214e453b9b634c4057702b5ac92090b45e12c126fa34",
20
20
  "dist/client-address.js": "9d0d01466aab23124330605c5a0e0f981f87c897858acfad909f546a63df165b",
21
- "dist/cloudflare.js": "31da478a6b851b5923c14dbd11885684cecec51cc7e0a5d6031d8512f3ecc040",
21
+ "dist/cloudflare.js": "b236918e499215ee42c2865181b3ed92decba0bf9d604f17f8b08736c86751d9",
22
22
  "dist/compliance-rules/baseline.js": "6296dea6bcb9f9bb80ed6ed5ab2f456e7f74e6de2be5d2d98ca8c983fb4df3ab",
23
23
  "dist/compliance-rules/privacy.js": "4cfb1c07d7df698c362dfc8761cd3bba893769e92d7fb0a3901e1c2d25e801f1",
24
24
  "dist/compliance-rules/shared.js": "86fd6db6a93375689acc64975cbc394ce9452dea21ae5941e743f99822515995",
@@ -26,69 +26,70 @@
26
26
  "dist/compliance.js": "29a18da3cefea94802ac7d817d1ab4e3674abd5c3ac4ef893cb8b01c6a23fb61",
27
27
  "dist/conditions.js": "ff25e97da550a3bdd80af55522045669438aa31a77d2bc83ad602bcefdee0475",
28
28
  "dist/config-worker.js": "bc2170d60c35f8d5de227cbfc4c5067dc61d7cbc5b98aed88e68a11797072da5",
29
- "dist/config.js": "6fecd80a269c5c8097449205a09a7e3522336e1000dcb3a3c88adb1b349093db",
30
- "dist/context.js": "225a25ad8dce00f52de2bb52a17aa85128cf34c4b82aa0cae846c8fc89b1cc52",
29
+ "dist/config.js": "b5e6d30dd5679da001203772a15e3ed5da735d9115082344e2cc397c020d2aa4",
30
+ "dist/context.js": "4061e18ecbcf1db69c6c76070349d0621774c419f2f775f06952470191e64e44",
31
31
  "dist/ecosystem-cli.js": "553911bf7e51c579ecd9d806f2ccfcba03ac601b87aa677c35147af36840e24d",
32
- "dist/egress.js": "2ae29fe2cb4590fd2f715abe6817db47fed2f50dd31f37946ecf42eafbb0dd9e",
32
+ "dist/egress.js": "d7e3a2a92c9fcb5a1a1a038ff39df0b3a1d72978d84c36c7de295fa1e5af861b",
33
33
  "dist/errors.js": "fef26eb834dd61b8b5f587eb7ab265d216122d182545f0a1c5b19c20840dffb0",
34
- "dist/examples.js": "8523799797d2530fe48ec5e96c34970bdba03c49afb31fdaab6a9c162a17dfa9",
34
+ "dist/examples.js": "22f36ceaa0ce4455b31d05d453d26a6c865009cfc3ad66c5519e82353b78e24a",
35
35
  "dist/explain-cli.js": "59050ad1139d419a23af4a844f2dc6b927c2bfd6599af7a2130b304733485f2d",
36
- "dist/explain.js": "480f9590df4171ba0dc7187c878fc95623eefbc2fc47d38b974f36bc27ce6d00",
37
- "dist/extension-artifacts.js": "01e1618799a0e920ef63b5cddf3680f245204895ad47bf956a4e4d8cbf706508",
36
+ "dist/explain.js": "29d0416dcbbc3ea349c9f927ea7f8d0f8f1cc0b6d25346a4deabc140aa517711",
37
+ "dist/extension-artifacts.js": "8c41bd33eea467869bf73b32dcce5ea7b533042058afd333102b53db6326b477",
38
38
  "dist/extension-bundles.js": "48d4cb64d2bbee8010447845eef491ee0be994e4867755f69ad8ee9105afbb29",
39
- "dist/extension-transport.js": "5b312e754cac0c7623e35379d91478c2787134e8564caae2f87bb91c8ecc4fd4",
39
+ "dist/extension-transport.js": "8e56d20eb68dadf05026436af2eef19fed5296884c73834b1623333275c8900e",
40
40
  "dist/extensions.js": "ec4b1784fe278a5293ec12651765184bf57dac7146c0627dc9cb31deeb01a928",
41
41
  "dist/feature-plan.js": "214af2d3aac042386df2ca7ce06ee505a78d2e80a38bb4ef0e5b96a4a9866a97",
42
42
  "dist/function-sources.js": "16fad4abc81c7ee07b6cbcef2d23a9fc50e97a17cb4dde1db53469bbb555c96f",
43
- "dist/function-worker.js": "35771790ada4e1b36d447d4967a5e6cf0b543f32944e4c904f5fbf8998e824a5",
44
- "dist/functions.js": "d9f5a493ba389191707107972562edc5b5f435579622d5a0caa4d942d7b1326a",
45
- "dist/guest-api.js": "49aec9ed72733ad119e63bcfcee18158dcc8ceb951e1823b8947f6407ff24afc",
43
+ "dist/function-worker.js": "17f8c81d3e1ad7e509266ba30b8975adc3e845e4d873f805e59369b8cf09f3ec",
44
+ "dist/functions.js": "9a12458423bbaad1b5073447e4305002d63348949c8bc07878dc9671a2c51a9f",
45
+ "dist/guest-api.js": "7668bb0e8bbbfc05c5e34bace436727735f5aa52f776fbc3e9edca3e7ff91fcb",
46
46
  "dist/header-validation.js": "465181dbb08ff05f52defdd29fda025c0c64589bf319fa87fa6d3ab4b68216d5",
47
- "dist/http-policy.js": "393a6383bdf9e05597c98721b3a4c4207a9bbe359883babcbfa06b696c70f56c",
48
- "dist/http-response.js": "565826d1ece4bd30dfc1150c75800acec1fef584f732e964409aa92958fc8550",
47
+ "dist/http-policy.js": "4fc00bc235ec70b0ed0a71e4af7501cc255a7e02268fd59387d7498075cfc284",
48
+ "dist/http-response.js": "c34d3e1436284f1339de411c6839419d1118f993ad67a8caa17da0268a779f66",
49
49
  "dist/index.js": "5e535104c672e70fa42f847ce6722c3b0bb2e3a9eac37f6a3c45d1a771f65f50",
50
- "dist/init-with.js": "4b4bfca3081f57c525ec2b3617c4735d773c5317f285337948227ede4734f214",
50
+ "dist/init-with.js": "3e642ee85b0b51887b6fc877d6c8099500446376641d9f60bf31f64f407bbf8b",
51
51
  "dist/interchange-cli.js": "35bd70ba8077af5c3e39404ff0e5d28707632090a59a90645141252449fd3f04",
52
- "dist/interchange.js": "3352228bc4ca652dd285fdaeddc8586f731fea5bd49521f408cce9a727f50ea8",
53
- "dist/logging.js": "8ec57fea26983aa071b05fe15d680d6550b03488101163ed9666da674118cf31",
52
+ "dist/interchange.js": "3d9c79ca946b6cc3d14895c5bb3fdb3064ed0ca750cd326270fec2ba8c287403",
53
+ "dist/logging.js": "92f7a2cafc75be0cce560e0797bcaf8c98ab4d67338eda1dfc8c3a914f1b03a3",
54
54
  "dist/manifest.js": "83eed0d59621039ef364cf6e7b54c5bd47e0d8cf0a5abe7b07344bfc1db56ed8",
55
- "dist/match.js": "2299245c0ba85481afe470c95f0f79502b6310b6870f776d16d88525a5e5da63",
56
- "dist/mcp-authoring.js": "1183a56c5decb99ecb67a5da7bd91d1b494bc17a2965051aa63c4a7e291462b5",
57
- "dist/mcp.js": "630936eaabd15cca18d0bf8249333f4f1b6eded29a1369512355e9200a705612",
55
+ "dist/match.js": "9069dfc59d485c7ed3b618d4ab4cfabcd67299ea4c45c646565e44a57b4bd51d",
56
+ "dist/mcp-authoring.js": "0753fb4f330ab710c7ee115315d7beb33b11faa80ca1b4fc114f5561c8abf7a7",
57
+ "dist/mcp.js": "f5cc0992b4e595fa36e33f84cb83e5917409ae7875c2f7a4686208cecaf5e671",
58
+ "dist/object-guards.js": "5af6012c803e9d587cb4270f52011bd2ba3e4655f73198dddc4ca327cdf89675",
58
59
  "dist/observability.js": "f4b1ab496f051fe3ef2ed36b2b2e469c63a8940700e2df852e6ff2d9cccdd5ae",
59
60
  "dist/operator-host.js": "e3dac9d43a83beb775202b4ab9eeeba7ac63cebfc5dcc669be38d825be587bd0",
60
- "dist/pattern-guard.js": "6f157ced99666a89ca5af1418f5a14568d850985042dea309bf9ec0c19dbaca5",
61
+ "dist/pattern-guard.js": "cebd2af729d7999fffea489bb9bf9d5af945bd08b32b77f0463fb89af86fb167",
61
62
  "dist/plugins.js": "a61aa933cd9e1b41020b86fae78aa3a7f45f5313124aa11b50cb57898722385e",
62
- "dist/policies/agents.js": "c1dbc1e90339f472eeeb2372f7f4637f59a5d210797ea680ac8120a02aa20c10",
63
- "dist/policies/cache.js": "d08184e50b0ecb7331b29e4145aaefacb23b64ddaf3555d579743762f90c1629",
64
- "dist/policies/compression.js": "800f3ae3b1a4c1fe58c3d9908364ea9d78f655ff50c7f7589f584378e7f194b0",
65
- "dist/policies/security.js": "c68d39f5fa2d7d16f5bfb3dcff2ac71428cc41814970eecf8b931debd40c0f3e",
66
- "dist/policies/throttle.js": "bbb0cb1c24cf70d0a1ac32fc0618efb6c44e5b5d42564842f9c585ff7e501603",
67
- "dist/policies.js": "3750a7032ec7326aa544f4808683ad9eb91d4f2c5afe79904420cd8a355710a9",
68
- "dist/policy.js": "2da95b3f1f77249b1e9f399a06b88909b68594fdbc958176075e91ff23104a7c",
69
- "dist/prerender.js": "6210999e848ab86c87f265943960ef63716896da7a40e248221a6a30425c56b8",
70
- "dist/project-dependencies.js": "b43a8f3125f62d38f0e71237b7813a785ef0613a83fcdec3fa8314f33758a534",
63
+ "dist/policies/agents.js": "b07f162fa8bfadf8866c09f9499a84c01740834703085e9ccf5a8cd1e02f6113",
64
+ "dist/policies/cache.js": "a52da3590b60048e4f5888591d26b16155c5250cacc212a9a2329d0f2a2149b2",
65
+ "dist/policies/compression.js": "afd6ee2b9c8122878b9f0e56b3d09613b44297b2297f6b76225979cbd4148ca5",
66
+ "dist/policies/security.js": "56dae854eb612cb83f3f81b144dd54034d45fefe7801afbfa4129b1e1609f1c0",
67
+ "dist/policies/throttle.js": "c3406b979d1144cfa96058b7874055de926bcea4fae240585b918092c987418a",
68
+ "dist/policies.js": "790694d06edb0a986a8a95e4eb6dcad0dc006cc53d84d4511b9e125a9b09a563",
69
+ "dist/policy.js": "2712abe24088ded09594dc8f048b47b94bd3dd85cc986c3a7a5b0c3ce7178796",
70
+ "dist/prerender.js": "363a369409323866d22aa3997ba57a27bae5b617d775fc3c74ba8944f02d4aa5",
71
+ "dist/project-dependencies.js": "d773224590221e9088444254d8e4e2aad63d968f39facac248f5084f29f92a03",
71
72
  "dist/project-tests.js": "63562978411ee94a69dcf45d4d47e84fba009d8654ac421c2a02a418cd80b4db",
72
73
  "dist/provider-verification.js": "ffcf9f05e8cb5f71ea46a97e279e8a613fdfdf9435ab02bd57af11e2680a9107",
73
- "dist/proxy.js": "34107e59ac0dbbea090efa3cf5badf3226c5302ba25e8f8ff9b2b5596ed80e50",
74
- "dist/readiness.js": "04143b22dbb8deec4b21b0f86c0c45c67f62a511efe1f99764b660791ef83343",
74
+ "dist/proxy.js": "b48f24fcee5fd425ae96b6617882be3861cb310988ac456df0f0fa7e53134ac9",
75
+ "dist/readiness.js": "862c6d5caf6a1e00217425348801616c8355ada7d3fa0e5f0caab8899346e672",
75
76
  "dist/recipes.js": "7af62ea9f612c82ea21e97e9d14451f05e0b509d6e420fb99fec96b22e3533f1",
76
- "dist/review.js": "65c078fb027a4600dbbaa610d2f4cd639badffe7e508fe63e5ae90b17e86f17b",
77
- "dist/route-diff.js": "8d71d50d332ffaaed588656bd32aa36763260d1ad87bb62d7ea1c8fb23383969",
78
- "dist/router.js": "e5318f3a3b7aa9e0edfe2552f67555f1d7cc820ceec5c77d452c439b834f7434",
79
- "dist/runtime.js": "6731200cdc1e8ac66b5d0ba7d0f42686ec31321b24e7d51130fe130e69478f37",
77
+ "dist/review.js": "102da9cea5a0edbd2ff2c8c26f5c25a9252832197c9fe19c54b754011a87d537",
78
+ "dist/route-diff.js": "f6e5b8965ededdcc76df431648751787af0b108141590744f248a69886c5f040",
79
+ "dist/router.js": "e8ffb65f6dc594439f032186b096788f5450ed5a2653f0a74eafbfc2505141e8",
80
+ "dist/runtime.js": "aa05e127b28e4f94b6802b01a2f74362d5a97b98cb9ed8b49dcfc0040159b812",
80
81
  "dist/sandbox.js": "c88c5aa5dd95b4f55f1ba62da7969ad66dbd97c30f72e5b5f2817a2098ac1c65",
81
- "dist/scaffold.js": "0f936cabd20b609c7ebfebd7895710ce6c30882c3088a2356ac75bb4e0d10c55",
82
- "dist/schema-query.js": "bfd1844acd8d54ac361115191fcfbaf828a67e223b9ec6e9c22f158761af149b",
83
- "dist/server.js": "165b66fc4ed3b8e7c46ae4402ef66a5bc988c0910fb3e11556b040781e09d03c",
84
- "dist/signals.js": "b55e54efc8fb6703e08f2a1e808ec8e011bda67738db8ceadf1b248f695795be",
85
- "dist/site.js": "9f8154d0a62ed1b650c064a24e609d50dd370f0ff2cee2567628029e30855616",
82
+ "dist/scaffold.js": "378f0d21351c0e4c69c6bb31bd8b55735fa01d056f4a9b150462fea74f5e8867",
83
+ "dist/schema-query.js": "becded534b3a85a60f5b2ee819252a973bc8ff3890bdfece0428877453fbcb12",
84
+ "dist/server.js": "b3b66948fc8cd6be791529994fc765d77a20990f181bf19517d51e03726cab59",
85
+ "dist/signals.js": "81476f857b053696cddc5ed0d3f219ae48ba9523681bf13bc93830d03896d6eb",
86
+ "dist/site.js": "0b8d5e25bb0e2357e4f8b6995f45f7898087c7f37cdc5302e4c83ec5a4caa43d",
86
87
  "dist/tooling.js": "9fa72ac5fdcdb56c9e17ee5d8efae83e100d3ad862f90e855dd5766424830b57",
87
- "dist/trusted-functions.js": "f3800c75f45ce90faf85ba6664398ad9031c5a9ac51d5ce42bdcff08a497d48e",
88
- "dist/types.js": "483bf520fb8088d9793c40b6a79cce068506a6137f8128fd3923e179ee3377b3",
88
+ "dist/trusted-functions.js": "0f5f6038c8ae96ccca3a4ec6da17910c8872dc6e9a2da8753e1b6e2a1fcef0a9",
89
+ "dist/types.js": "a378e6bb6ffbcbe6d703a36949f7bf6c00973ade4808575828be4f90841e1120",
89
90
  "dist/typescript-authoring.js": "df3d9c82e6b545cae5caae04c443ed4d2feae96def5e5cde250c0a90d83ae98c",
90
- "dist/vercel.js": "a0da20c095d0c7fa8f579ed4dd5951e7a34191f8a72a57c5e782c9eab39f6f08",
91
- "dist/verify-deployment.js": "2d3b6301b70684abfccea669f7901afda7fae9d9d66902809ecdef65f22aea16",
92
- "dist/scripts/operational-drills.js": "c26c406d28013cc554b25a5b6bf8baf4e71d5e67e50d866fae546f538a192317"
91
+ "dist/vercel.js": "fa4034aa1582f41c8b9d380ad4e068dded6adb002155b5079e2c858b0e34f90f",
92
+ "dist/verify-deployment.js": "85b4318efaf65608d193010c8904bc337f472bd5eac97ff3a7d52bbf017f81cd",
93
+ "dist/scripts/operational-drills.js": "26f487454f7602b26d17c28eccd78aa59954cd7975791ac140092c614b36bc19"
93
94
  }
94
95
  }
@@ -34,6 +34,9 @@ export function renderAgentsGuide({ routes } ) {
34
34
  const handlers = handlerNames.filter(name => native.has(name));
35
35
  const policies = Object.keys(registry).filter(name => native.has(`policies.${name}` ));
36
36
  const site = Object.entries(generatedPaths).map(([key, path]) => `\`${key}\` (${path})`);
37
+ const auditGuidance = routes === 0
38
+ ? 'With no active routes, this initial audit intentionally exits nonzero with `no-active-routes`. Add the first route and its fixture, then make the audit pass; remove `allow-empty-project: true` from the generated GitHub workflow at that point.'
39
+ : 'Run all three after every change.';
37
40
  return `# Working on this project
38
41
 
39
42
  This project uses URLCode: URL behavior is declared in \`urlcode.yaml\`, and the
@@ -58,7 +61,7 @@ static serving and authentication. Read this file before changing anything.
58
61
 
59
62
  When present, \`${mcpConfigFile}\` registers the read-only \`urlcode mcp\` server; prefer its
60
63
  tools (also \`get_manifest\`) to reading documents. Inspect \`get_extensions\` before
61
- replacing extension behavior. \`--allow-authoring\` is an operator opt-in; never add it. For a committed artifact lock, use \`get_extension_artifacts\`/\`get_extension_artifact\`; they expose verified inert data and never activate an extension.
64
+ replacing extension behavior. \`--allow-authoring\` is an operator opt-in; never add it. For a committed artifact lock, use \`get_extension_artifacts\`/\`get_extension_artifact\`; they expose verified inert data and never activate an extension. [URLCode AI](https://urlcode.ai/) is a separate optional hosted service for shared skills and LLM tooling; its remote MCP never replaces this local project server, and its credential belongs only in a client secret facility, never project files. Its machine-readable entry point is \`https://urlcode.ai/llms.txt\`.
62
65
 
63
66
  ## What the runtime provides (this version)
64
67
 
@@ -95,7 +98,7 @@ urlcode test
95
98
  urlcode audit --expect-routes ${routes}
96
99
  \`\`\`
97
100
 
98
- Run all three after every change, updating the route count deliberately and adding \`tests/requests.json\` fixtures for every new route (positive/negative, every active method, HEAD). No global install: use \`node /path/to/urlcode/src/cli.ts\`.
101
+ ${auditGuidance} \`N\` counts declared routes plus one route for each active \`site.*\` convention; an audit mismatch reports the declared/generated split. Update it deliberately and add \`tests/requests.json\` fixtures for every new route (positive/negative, every active method, HEAD). No global install: use \`node /path/to/urlcode/packages/core/src/cli.ts\`.
99
102
 
100
103
  ## Feedback
101
104
 
package/dist/assets.js CHANGED
@@ -9,8 +9,8 @@ import { assert, HttpError } from './errors.js';
9
9
 
10
10
 
11
11
  /** The header surface assetResponse reads for conditional and range requests. */
12
-
13
-
12
+
13
+
14
14
 
15
15
  // Asset bytes are immutable between successful reloads. No request opens a file.
16
16
  const denied = /^(?:node_modules|urlcode\.ya?ml|package(?:-lock)?\.json|.*\.(?:pem|key|p12|pfx|env))$/i;
package/dist/authoring.js CHANGED
@@ -10,17 +10,14 @@ import { prepareFunctionSnapshot, requestedPermissions } from './policy.js';
10
10
  import { assert } from './errors.js';
11
11
  import { renderPackageManifest } from './project-dependencies.js';
12
12
 
13
- import { redirectStarter } from './context.js';
14
13
 
15
14
 
16
-
15
+
17
16
 
18
17
 
19
18
 
20
19
 
21
20
 
22
-
23
-
24
21
 
25
22
  // Agents install the runtime before they can read its docs, so `init .` has to work after `npm init` and `npm install`.
26
23
  // A directory is accepted in place only when it holds nothing but what npm and git create; anything else is user work
@@ -41,18 +38,7 @@ async function inspectExisting(target )
41
38
  }
42
39
  return { entries: new Set(names), packageJson, pinned };
43
40
  }
44
- /** Adds what the starter needs to an existing package.json and changes nothing else; a conflicting script is refused, never overwritten. */
45
- export function mergePackageJson(text , scripts , dependency , version ) {
46
- const manifest = JSON.parse(text) ;
47
- for (const [name, command] of Object.entries(scripts)) {
48
- const current = manifest.scripts?.[name];
49
- assert(current === undefined || current === command, `package.json already defines scripts.${name}; remove it or set it to: ${command}`);
50
- manifest.scripts = { ...manifest.scripts, [name]: command };
51
- }
52
- if (manifest.dependencies?.[dependency] === undefined && manifest.devDependencies?.[dependency] === undefined) manifest.dependencies = { ...manifest.dependencies, [dependency]: version };
53
- return JSON.stringify(manifest, null, 2) + '\n';
54
- }
55
- export async function initProject(destination , { manifest, template = 'default' } = {}) {
41
+ export async function initProject(destination , { manifest } = {}) {
56
42
  const target = resolve(destination);
57
43
  await mkdir(dirname(target), { recursive: true });
58
44
  const existing = await inspectExisting(target);
@@ -60,23 +46,11 @@ export async function initProject(destination , { manifest, template = 'd
60
46
  // A new directory is reserved exclusively before copying; an existing one is only ever added to.
61
47
  if (!existing) await mkdir(target);
62
48
  try {
63
- if (template === 'redirects') { await writeRedirectsStarter(target, existing); return target; }
64
- const source = fileURLToPath(new URL(`../starters/${template === 'page' ? 'page' : 'default'}/`, import.meta.url));
49
+ const source = fileURLToPath(new URL('../starters/default/', import.meta.url));
65
50
  for (const file of await readdir(source)) {
66
51
  if (file === '.gitignore' || file === 'AGENTS.md' || file === mcpConfigFile) continue;
67
52
  await cp(join(source,file), join(target,file === 'gitignore.template' ? '.gitignore' : file), { recursive: true, force: false, errorOnExist: true });
68
53
  }
69
- if (template === 'page') {
70
- const routes = Object.keys((await loadDocument(target)).routes).length;
71
- // The page starter is the smallest project, but an agent opened in it still needs the same first-step guidance and MCP registration.
72
- await writeExclusive(join(target, 'AGENTS.md'), renderAgentsGuide({ routes }));
73
- await writeExclusive(join(target, mcpConfigFile), renderMcpConfig('.', { local: manifest !== undefined || existing?.pinned === true }));
74
- if (manifest) {
75
- const pkg = await open(join(target,'package.json'), 'wx', 0o644);
76
- try { await pkg.writeFile(renderPackageManifest(target, manifest)); } finally { await pkg.close(); }
77
- }
78
- return target;
79
- }
80
54
  // AGENTS.md is generated from the installed runtime's capability catalog so
81
55
  // it names only what this version implements; the starter carries a
82
56
  // committed copy for clones, kept identical by test.
@@ -102,35 +76,6 @@ export async function initProject(destination , { manifest, template = 'd
102
76
  }
103
77
  return target;
104
78
  }
105
- const redirectFixtures = [
106
- { path: '/old', status: 301, expectHeaders: { location: 'https://example.com/new' } },
107
- { path: '/users/42', status: 308, expectHeaders: { location: 'https://example.com/profiles/42' } },
108
- { path: '/people/7', status: 302, expectHeaders: { location: '/profiles/7' } },
109
- { path: '/legacy/a/b/c/d/e/f/g/h/i', status: 302, expectHeaders: { location: 'https://example.com/modern/a/b/c/d/e/f/g/h/i' } },
110
- { path: '/search?q=tea', status: 302, expectHeaders: { location: 'https://example.com/find?q=tea' } },
111
- { path: '/missing', status: 404 },
112
- ];
113
- /** The redirect starter is the `--task redirects` starter, so init and `urlcode context` cannot disagree. */
114
- async function writeRedirectsStarter(target , existing ) {
115
- const starter = redirectStarter();
116
- const version = (JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')) ).version;
117
- const files = {
118
- [starter.file]: starter.yaml,
119
- ...starter.companions,
120
- 'tests/requests.json': JSON.stringify(redirectFixtures, null, 2) + '\n',
121
- };
122
- await mkdir(join(target, 'tests'));
123
- for (const [name, body] of Object.entries(files)) await writeExclusive(join(target, name), body);
124
- if (existing?.packageJson !== undefined) await writeFile(join(target, 'package.json'), mergePackageJson(existing.packageJson, starter.packageScripts, '@jimhoyd/urlcode', version));
125
- else await writeExclusive(join(target, 'package.json'), JSON.stringify({ name: 'redirects', version: '1.0.0', private: true, scripts: starter.packageScripts, dependencies: { '@jimhoyd/urlcode': version } }, null, 2) + '\n');
126
- const routes = Object.keys((await loadDocument(target)).routes).length;
127
- await writeExclusive(join(target, 'AGENTS.md'), renderAgentsGuide({ routes }));
128
- await writeExclusive(join(target, mcpConfigFile), renderMcpConfig('.', { local: true }));
129
- }
130
- async function writeExclusive(file , body ) {
131
- const handle = await open(file, 'wx', 0o644);
132
- try { await handle.writeFile(body); } finally { await handle.close(); }
133
- }
134
79
  export async function addRedirect(project , destination , alias ) {
135
80
  const loaded = await loadDocument(project);
136
81
  const slug = alias || randomBytes(6).toString('base64url');
package/dist/aws.js CHANGED
@@ -6,6 +6,7 @@ import { activateNativeOnly, lazyRuntime, resolveOrigin } from './adapters.js';
6
6
  import { prepareResponse, errorResponse } from './http-response.js';
7
7
 
8
8
  import { assert, ConfigError, HttpError } from './errors.js';
9
+ import { isRecord } from './object-guards.js';
9
10
 
10
11
 
11
12
  /** A Lambda payload format 2.0 event, as far as this adapter reads it. */
@@ -18,7 +19,20 @@ import { assert, ConfigError, HttpError } from './errors.js';
18
19
 
19
20
 
20
21
  const platformOrigins = ['URLCODE_PUBLIC_HOST'];
21
- const isRecord = (value ) => value !== null && typeof value === 'object';
22
+ // Payload format 2.0 joins every repeated header with a comma before this
23
+ // adapter ever sees it (there is no `multiValueHeaders` on this format), so a
24
+ // client that sent one occurrence with a comma in its value is
25
+ // indistinguishable, at this layer, from two occurrences API Gateway joined.
26
+ // Splitting unconditionally corrupts the former and inflates its count. Only
27
+ // the headers RFC 7230/9110 define as a comma-separated list are safe to
28
+ // split; every other header (including any project-declared one) is kept as
29
+ // the single value the platform handed over, whatever characters it holds.
30
+ const listValuedHeaders = new Set([
31
+ 'accept','accept-charset','accept-encoding','accept-language','access-control-allow-headers','access-control-allow-methods',
32
+ 'access-control-expose-headers','access-control-request-headers','allow','cache-control','connection','content-encoding',
33
+ 'content-language','expect','forwarded','if-match','if-none-match','pragma','te','trailer','transfer-encoding','upgrade',
34
+ 'vary','via','warning','www-authenticate','x-forwarded-for',
35
+ ]);
22
36
 
23
37
  // Payload format 2.0 only, as used by Lambda Function URLs and API Gateway
24
38
  // HTTP APIs. Format 1.0 supplies the path and query already decoded, so the
@@ -44,9 +58,12 @@ function requestHeaders(event )
44
58
  for (const [name,value] of Object.entries(event.headers || {})) {
45
59
  if (value === undefined) continue;
46
60
  const key = name.toLowerCase();
47
- // API Gateway joins repeats with a comma; the count reflects that a client
48
- // sent more than one, which route policy uses to reject ambiguous inputs.
49
- const parts = key === 'cookie' ? [value] : String(value).split(',');
61
+ // API Gateway joins repeats of a *list-valued* header with a comma; the
62
+ // count then reflects that a client sent more than one, which route
63
+ // policy uses to reject ambiguous inputs. Any other header is scalar as
64
+ // far as this adapter is concerned: splitting it on comma would mangle a
65
+ // legitimate value and miscount a single occurrence as several.
66
+ const parts = listValuedHeaders.has(key) ? String(value).split(',') : [String(value)];
50
67
  for (const part of parts) headers.append(key, part.trim());
51
68
  counts[key] = parts.length;
52
69
  }
@@ -1,5 +1,6 @@
1
1
  import { assert } from './errors.js';
2
2
  import { assertSafePattern, maxPatternInputLength } from './pattern-guard.js';
3
+ import { isRecord, own } from './object-guards.js';
3
4
 
4
5
  /**
5
6
  * The JSON Schema subset a route may declare for `request.body.schema`.
@@ -18,8 +19,6 @@ export const uuidFormat = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a
18
19
  const types = ['object', 'array', 'string', 'integer', 'number', 'boolean', 'null'];
19
20
  const keywords = new Set(['type','properties','required','additionalProperties','items','enum','minLength','maxLength','pattern','format','minimum','maximum','minItems','maxItems']);
20
21
  const limits = { depth: 6, nodes: 128, properties: 64, enums: 64, length: 8192, items: 10000 };
21
- const isRecord = (value ) => typeof value === 'object' && value !== null && !Array.isArray(value);
22
- const own = (object , key ) => Object.hasOwn(object, key);
23
22
  const patterns = new WeakMap ();
24
23
 
25
24
  /** Rejects, at load time, any schema outside the supported subset or its limits. */
@@ -75,7 +74,7 @@ const describe = (value ) => value === null ? 'null' : Array.isA
75
74
  * is the schema keyword that failed, and `expected` is the schema's own
76
75
  * constraint. Nothing the client sent is ever placed in an issue.
77
76
  */
78
-
77
+
79
78
  const escapePointer = (name ) => name.replace(/~/g, '~0').replace(/\//g, '~1');
80
79
  /** Structured failures for `value`; `checkBodySchema` renders the same list as text. */
81
80
  export function bodySchemaIssues(schema , value , path = '', issues = [], max = 8) {
@@ -6,7 +6,7 @@ import { effectivePolicies, registry } from './policies.js';
6
6
 
7
7
  export const capabilityTargets = ['self-hosted', 'cloudflare', 'aws', 'vercel', 'static'] ;
8
8
 
9
- /** `static` never reaches the policy-module handshake (src/types.ts `TargetName`): it refuses every
9
+ /** `static` never reaches the policy-module handshake (packages/core/src/types.ts `TargetName`): it refuses every
10
10
  * runtime policy outright, so it needs no entry in that exhaustive per-target record. */
11
11
 
12
12
 
@@ -1,11 +1,12 @@
1
1
  import {readdirSync,readFileSync,statSync} from 'node:fs';
2
2
  import {fileURLToPath} from 'node:url';
3
- import {parse} from 'yaml';
4
3
  import {capabilityDetails,capabilityNames,capabilityTargets,formatCapabilities,getCapabilities,routeCapabilities} from './capabilities.js';
5
4
 
6
5
  import {ConfigError} from './errors.js';
6
+ import {parseYaml} from './config.js';
7
7
  import {getSchemaFragment} from './schema-query.js';
8
8
 
9
+ import {isRecord as object} from './object-guards.js';
9
10
 
10
11
 
11
12
 
@@ -18,7 +19,6 @@ import {getSchemaFragment} from './schema-query.js';
18
19
 
19
20
 
20
21
  const root=(...parts )=>fileURLToPath(new URL('../'+parts.join('/'),import.meta.url));
21
- const object=(value ) =>value!==null&&typeof value==='object'&&!Array.isArray(value);
22
22
  function yamlFiles(directory ) {
23
23
  const out =[];
24
24
  for(const entry of readdirSync(directory,{withFileTypes:true}).sort((a,b)=>a.name.localeCompare(b.name))) {
@@ -32,7 +32,7 @@ function yamlFiles(directory ) {
32
32
  function usage(base ,files ,name ) {
33
33
  const result =[];
34
34
  for(const file of files) {
35
- let document ;try{document=parse(readFileSync(file,'utf8'));}catch{continue;}
35
+ let document ;try{document=parseYaml(readFileSync(file,'utf8'));}catch{continue;}
36
36
  if(!object(document))continue;
37
37
  const routes =[];
38
38
  if(name==='extension'&&object(document.extensions)&&Object.keys(document.extensions).length)routes.push('(project)');
package/dist/catalog.js CHANGED
@@ -23,7 +23,7 @@ import {assert} from './errors.js';
23
23
 
24
24
 
25
25
  /** The fields the capability preflight determines; `npm run check` refuses hand-written values that differ. */
26
-
26
+
27
27
 
28
28
 
29
29
  const schemaFile=fileURLToPath(new URL('../schemas/recipe.schema.json',import.meta.url));