@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.
- package/.claude/skills/urlcode-authoring/SKILL.md +36 -22
- package/.claude/skills/urlcode-operations/SKILL.md +16 -22
- package/.claude-plugin/marketplace.json +1 -1
- package/CONTRIBUTING.md +20 -16
- package/README.md +83 -80
- package/ROADMAP.md +61 -331
- package/SECURITY.md +23 -8
- package/dist/BUILD-MANIFEST.json +42 -47
- package/dist/agents-guide.js +18 -18
- package/dist/build-static.js +136 -0
- package/dist/capabilities.js +85 -31
- package/dist/capability-query.js +0 -1
- package/dist/cli.js +24 -32
- package/dist/compliance-rules/baseline.js +2 -10
- package/dist/compliance-rules/privacy.js +5 -16
- package/dist/compliance-rules/shared.js +0 -2
- package/dist/compliance.js +6 -8
- package/dist/config.js +15 -8
- package/dist/context.js +9 -10
- package/dist/examples.js +2 -2
- package/dist/explain-cli.js +7 -5
- package/dist/explain.js +10 -5
- package/dist/extensions.js +61 -2
- package/dist/function-sources.js +34 -2
- package/dist/function-worker.js +3 -1
- package/dist/functions.js +84 -13
- package/dist/guest-api.js +29 -3
- package/dist/index.js +3 -6
- package/dist/manifest.js +11 -7
- package/dist/mcp-authoring.js +2 -2
- package/dist/mcp.js +1 -1
- package/dist/observability.js +1 -21
- package/dist/policies/cache.js +8 -3
- package/dist/policies.js +3 -1
- package/dist/policy.js +28 -9
- package/dist/prerender.js +4 -0
- package/dist/project-tests.js +3 -3
- package/dist/readiness.js +35 -9
- package/dist/route-diff.js +12 -5
- package/dist/router.js +5 -7
- package/dist/runtime.js +77 -58
- package/dist/sandbox.js +48 -0
- package/dist/scaffold.js +0 -0
- package/dist/scripts/operational-drills.js +12 -54
- package/dist/server.js +3 -29
- package/dist/tooling.js +1 -1
- package/dist/trusted-functions.js +210 -0
- package/dist/types/build-static.d.ts +43 -0
- package/dist/types/capabilities.d.ts +14 -5
- package/dist/types/compliance-rules/shared.d.ts +0 -2
- package/dist/types/compliance.d.ts +0 -3
- package/dist/types/config.d.ts +2 -1
- package/dist/types/context.d.ts +2 -1
- package/dist/types/examples.d.ts +1 -1
- package/dist/types/explain.d.ts +6 -0
- package/dist/types/extensions.d.ts +55 -0
- package/dist/types/function-sources.d.ts +4 -0
- package/dist/types/functions.d.ts +48 -5
- package/dist/types/guest-api.d.ts +1 -0
- package/dist/types/index.d.ts +3 -6
- package/dist/types/manifest.d.ts +5 -3
- package/dist/types/observability.d.ts +1 -14
- package/dist/types/project-tests.d.ts +1 -2
- package/dist/types/readiness.d.ts +12 -3
- package/dist/types/router.d.ts +2 -1
- package/dist/types/runtime.d.ts +0 -27
- package/dist/types/sandbox.d.ts +12 -0
- package/dist/types/scaffold.d.ts +0 -2
- package/dist/types/server.d.ts +1 -4
- package/dist/types/tooling.d.ts +3 -3
- package/dist/types/trusted-functions.d.ts +29 -0
- package/dist/types/types.d.ts +22 -8
- package/dist/types/verify-deployment.d.ts +2 -2
- package/dist/types.js +25 -5
- package/dist/typescript-authoring.js +67 -18
- package/dist/verify-deployment.js +3 -3
- package/docs/AI-AUTHORING.md +109 -10
- package/docs/ASSETS.md +2 -1
- package/docs/AUTH-BACKUP.md +32 -0
- package/docs/AWS.md +4 -4
- package/docs/BEST-PRACTICES.md +17 -8
- package/docs/CAPABILITIES.md +30 -17
- package/docs/CAPACITY.md +128 -31
- package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
- package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
- package/docs/CI.md +8 -3
- package/docs/CLOUDFLARE.md +1 -2
- package/docs/CODEBASE-AUDIT-2026-09-20.md +278 -0
- package/docs/COMPLIANCE.md +6 -9
- package/docs/DEPLOYMENT-CHECKS.md +1 -1
- package/docs/DEVELOPMENT-PIPELINE.md +181 -0
- package/docs/EXTENSIONS.md +193 -11
- package/docs/FRAMEWORK.md +53 -33
- package/docs/FUNCTION-SECURITY.md +173 -32
- package/docs/INSTALL.md +0 -5
- package/docs/LOAD-TESTING.md +4 -4
- package/docs/LOCAL-DEVELOPMENT.md +3 -0
- package/docs/MIDDLEWARE.md +27 -16
- package/docs/MONITORING.md +2 -19
- package/docs/OBSERVABILITY.md +7 -16
- package/docs/OPEN-DECISIONS.md +184 -0
- package/docs/OPERATIONAL-PROOF.md +26 -30
- package/docs/OPERATIONS.md +23 -32
- package/docs/POLICIES.md +27 -8
- package/docs/PRERENDER.md +25 -13
- package/docs/PROJECT-DIRECTION.md +35 -10
- package/docs/READINESS.md +6 -2
- package/docs/README.md +20 -41
- package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
- package/docs/RELEASE-READINESS.md +50 -30
- package/docs/RELEASE-SECURITY.md +72 -86
- package/docs/RESILIENCE.md +16 -15
- package/docs/ROUTING.md +7 -10
- package/docs/SANDBOX-REVIEW.md +19 -6
- package/docs/SCAFFOLDING.md +0 -2
- package/docs/SECURITY-AUDIT.md +42 -2
- package/docs/SPECIFICATION.md +63 -27
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +288 -0
- package/docs/SPIKE-BUSINESS-SUITE.md +16 -0
- package/docs/SPIKE-CORE-LAYERING.md +368 -0
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +211 -0
- package/docs/SPIKE-LAMBDA-COMPILE.md +12 -2
- package/docs/STARTERS.md +1 -1
- package/docs/STATIC.md +105 -0
- package/docs/TOOLING.md +17 -12
- package/docs/TUNNELS.md +0 -3
- package/docs/TYPESCRIPT-AUTHORING.md +32 -12
- package/docs/TYPESCRIPT.md +25 -4
- package/docs/VERCEL.md +4 -5
- package/docs/VERSION-ALIGNMENT.md +46 -0
- package/docs/YAML-GUIDE.md +0 -3
- package/docs/YAML-REFERENCE.md +16 -6
- package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
- package/docs/{MANAGEMENT-SECURITY.md → archive/2026-09-19/MANAGEMENT-SECURITY.md} +20 -0
- package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +26 -8
- package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +274 -43
- package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
- package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
- package/docs/archive/2026-09-19/ROADMAP.md +386 -0
- package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +12 -2
- package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +28 -12
- package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +776 -0
- package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +18 -2
- package/docs/archive/README.md +27 -0
- package/docs/policies/agents.md +1 -1
- package/docs/policies/cache.md +13 -0
- package/docs/policies/compression.md +3 -2
- package/docs/policies/security.md +3 -2
- package/examples/assets/example.yaml +2 -2
- package/examples/aws/example.yaml +1 -1
- package/examples/cloudflare/example.yaml +1 -1
- package/examples/conditions/example.yaml +1 -1
- package/examples/cookbook/example.yaml +1 -1
- package/examples/cookbook/middleware/bucket.mjs +12 -2
- package/examples/cookbook/middleware/locale.mjs +7 -3
- package/examples/cookbook/route-index.json +1 -1
- package/examples/cookbook/routes/middleware.yaml +1 -1
- package/examples/egress/example.yaml +1 -1
- package/examples/extensions/example.yaml +1 -1
- package/examples/prerender/README.md +14 -6
- package/examples/prerender/example.yaml +1 -1
- package/examples/prerender/functions/page.mjs +4 -2
- package/examples/prerender/middleware/template.mjs +1 -1
- package/examples/prerender/prerender.mjs +1 -1
- package/examples/prerender/urlcode.yaml +8 -4
- package/examples/provider-conformance/example.yaml +1 -1
- package/examples/vercel/example.yaml +1 -1
- package/llms-full.txt +686 -239
- package/llms.txt +27 -15
- package/package.json +32 -5
- package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +36 -22
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +16 -22
- package/recipes/authenticated-json-api/README.md +4 -3
- package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
- package/recipes/authenticated-json-api/recipe.yaml +2 -2
- package/recipes/contact-form/functions/contact.mjs +2 -1
- package/recipes/contact-form/recipe.yaml +3 -3
- package/recipes/cors-api/README.md +2 -2
- package/recipes/cors-api/recipe.yaml +2 -2
- package/recipes/health-page/README.md +1 -1
- package/recipes/health-page/recipe.yaml +1 -1
- package/recipes/json-api/README.md +1 -1
- package/recipes/json-api/recipe.yaml +4 -4
- package/recipes/middleware/README.md +8 -4
- package/recipes/middleware/middleware/bucket.mjs +12 -2
- package/recipes/middleware/middleware/locale.mjs +7 -3
- package/recipes/middleware/recipe.yaml +1 -1
- package/recipes/protected-download/README.md +1 -1
- package/recipes/protected-download/recipe.yaml +2 -2
- package/recipes/redirect/recipe.yaml +1 -1
- package/recipes/static-plus-api/README.md +2 -2
- package/recipes/static-plus-api/public/index.html +1 -1
- package/recipes/static-plus-api/recipe.yaml +2 -2
- package/recipes/static-plus-api/urlcode.yaml +1 -1
- package/recipes/typescript/README.md +2 -1
- package/recipes/typescript/recipe.yaml +5 -5
- package/recipes/webhook-receiver/README.md +5 -1
- package/recipes/webhook-receiver/recipe.yaml +2 -1
- package/recipes/webhook-receiver/urlcode.yaml +7 -0
- package/schemas/recipe.schema.json +4 -3
- package/schemas/urlcode.schema.json +14 -41
- package/skills/urlcode/SKILL.md +32 -11
- package/starters/default/AGENTS.md +18 -18
- package/starters/default/urlcode.yaml +0 -1
- package/dist/link-api.js +0 -136
- package/dist/link-cli.js +0 -141
- package/dist/link-events.js +0 -76
- package/dist/link-records.js +0 -31
- package/dist/link-store-worker.js +0 -150
- package/dist/link-store.js +0 -250
- package/dist/management-policy.js +0 -40
- package/dist/sqlite-version.js +0 -6
- package/dist/types/link-api.d.ts +0 -30
- package/dist/types/link-cli.d.ts +0 -37
- package/dist/types/link-events.d.ts +0 -27
- package/dist/types/link-records.d.ts +0 -11
- package/dist/types/link-store-worker.d.ts +0 -1
- package/dist/types/link-store.d.ts +0 -130
- package/dist/types/management-policy.d.ts +0 -8
- package/dist/types/sqlite-version.d.ts +0 -1
- package/docs/DYNAMIC-LINKS.md +0 -61
- package/docs/links/cli.md +0 -110
- package/docs/links/limits.md +0 -175
- package/docs/links/management-api.md +0 -80
- package/docs/links/pools.md +0 -75
- package/docs/links/setup.md +0 -135
- package/docs/yaml/links.md +0 -30
- package/examples/live-links/README.md +0 -11
- package/examples/live-links/example.yaml +0 -21
- package/examples/live-links/tests/requests.json +0 -6
- package/examples/live-links/urlcode.yaml +0 -16
|
@@ -1,11 +1,75 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
# Function execution: trusted by default, sandboxed opt-in
|
|
2
|
+
|
|
3
|
+
`function` and `middleware` routes run **trusted and unsandboxed by default**:
|
|
4
|
+
in the host process, with full Node, filesystem and network access, exactly
|
|
5
|
+
like any other project code (docs/SPIKE-DEFAULT-TRUST-MODEL.md). This is a
|
|
6
|
+
deliberate, maintainer-decided reversal of alpha.2's blanket sandbox — see
|
|
7
|
+
that spike document for the full rationale. It is a call the project makes,
|
|
8
|
+
not a property the runtime can verify: URLCode cannot know whether your code
|
|
9
|
+
is safe to trust, only whether you asked for isolation.
|
|
10
|
+
|
|
11
|
+
Declare `sandbox: true` on a route when its code specifically warrants
|
|
12
|
+
isolation: it processes input from a source the project doesn't fully trust
|
|
13
|
+
(a third-party webhook payload, for example), it is a contribution nobody on
|
|
14
|
+
the team has reviewed, or it handles a secret sensitive enough that a bug in
|
|
15
|
+
that one route should not be able to reach the rest of the process or the
|
|
16
|
+
filesystem. A sandboxed route runs in QuickJS inside WebAssembly, in a
|
|
17
|
+
separate worker thread, with none of the host access described below — its
|
|
18
|
+
guarantees are unchanged from every earlier release and are described in
|
|
19
|
+
full in the rest of this document. Absence of `sandbox` (or `sandbox: false`)
|
|
20
|
+
means trusted; there is no separate `unsafe`/`trusted` field to opt back into
|
|
21
|
+
the old sandboxed-by-default behavior — set `sandbox: true` per route instead.
|
|
22
|
+
|
|
23
|
+
**Either way, binding grants are unaffected.** Trusting a route's code by
|
|
24
|
+
default does not grant it any `env`/`secrets` it was not explicitly declared
|
|
25
|
+
in YAML and approved by an operator policy pinned to the project revision
|
|
26
|
+
(see "Granting selected bindings" below). A trusted function only *can* do
|
|
27
|
+
more with Node once it runs — it does not receive anything more than a
|
|
28
|
+
sandboxed one would.
|
|
29
|
+
|
|
30
|
+
This is a claim about `context`/`context.secrets` injection, not an
|
|
31
|
+
access-control guarantee on trusted code. The binding grant governs only what
|
|
32
|
+
URLCode hands a route through `context`; it does not restrict what trusted
|
|
33
|
+
(non-`sandbox`) code can independently do, because that code has full Node
|
|
34
|
+
access by design. A trusted function can read `process.env`, open files or
|
|
35
|
+
make network calls on its own regardless of what its route was or was not
|
|
36
|
+
granted — withholding a binding grant limits what URLCode gives the code
|
|
37
|
+
through `context`, not what the code itself, running with full Node access,
|
|
38
|
+
can go and get. A sandboxed route has no such independent access: the guest
|
|
39
|
+
API is all it has, so its binding grant *is* effectively its whole reach into
|
|
40
|
+
the environment. Trusted code's reach is not bounded that way; treat the
|
|
41
|
+
grant as scoping `context`, not as scoping the process.
|
|
42
|
+
|
|
43
|
+
## Migrating to the trusted default
|
|
44
|
+
|
|
45
|
+
If you are upgrading a project from a release before this change shipped:
|
|
46
|
+
**every existing `function` and `middleware` route silently changes execution
|
|
47
|
+
mode**, from sandboxed to trusted, unless it already has (or you add)
|
|
48
|
+
`sandbox: true`. This is a real behavior change on upgrade, not a
|
|
49
|
+
documentation update — a route that used to run with no filesystem or network
|
|
50
|
+
access will, after the upgrade, run with full Node access unless you opt it
|
|
51
|
+
back into the sandbox.
|
|
52
|
+
|
|
53
|
+
Before upgrading:
|
|
54
|
+
|
|
55
|
+
- List every `function` and `middleware` route in the project.
|
|
56
|
+
- For each one, decide whether you fully trust that code to run in-process
|
|
57
|
+
with full Node/filesystem/network access — the same trust you would extend
|
|
58
|
+
to any other code you deploy to that server.
|
|
59
|
+
- Add `sandbox: true` explicitly to any route whose code you do not fully
|
|
60
|
+
trust, that processes input from a source you don't control, or that handles
|
|
61
|
+
a secret binding you want isolated — before you upgrade, not after.
|
|
62
|
+
- Routes you do want running trusted need no change; that is now the default.
|
|
63
|
+
|
|
64
|
+
The change moves the sandbox from an unconditional guarantee to an explicit,
|
|
65
|
+
per-route choice, mainly for performance: the previous blanket sandbox capped
|
|
66
|
+
concurrency at two workers with no queue shared across every function route on
|
|
67
|
+
the server, which does not scale to real concurrent traffic. It also brings
|
|
68
|
+
first-party code in line with how the rest of the Node ecosystem treats
|
|
69
|
+
deployed application code. The sandbox itself is unchanged for routes that opt
|
|
70
|
+
into it; only the default for routes that declare neither option has changed.
|
|
71
|
+
|
|
72
|
+
## What "sandboxed" (`sandbox: true`) still guarantees
|
|
9
73
|
|
|
10
74
|
- Function sources are parsed/snapshotted without importing them into Node.
|
|
11
75
|
- Code runs in QuickJS inside WebAssembly, with no host JS functions/objects
|
|
@@ -22,9 +86,79 @@ is no `unsafe`, `trusted` or automatic host-execution fallback.
|
|
|
22
86
|
Operator grants are exact-name, route-scoped and pinned to configuration/source.
|
|
23
87
|
|
|
24
88
|
The guest API is intentionally narrower than Node or full Fetch; see the
|
|
25
|
-
[implemented contract](SPECIFICATION.md).
|
|
26
|
-
or binary/stream APIs must be rewritten
|
|
27
|
-
|
|
89
|
+
[implemented contract](SPECIFICATION.md). A function moving from trusted to
|
|
90
|
+
`sandbox: true` that uses Node/network or binary/stream APIs must be rewritten
|
|
91
|
+
for the supported guest profile, or stay trusted. Redirects need none of this
|
|
92
|
+
machinery either way.
|
|
93
|
+
|
|
94
|
+
This engine — worker spawning, the module-allowlist walk, the two-layer
|
|
95
|
+
deadline, `maxBytes` and response-shape validation — is one implementation
|
|
96
|
+
shared by route dispatch and by `@jimhoyd/urlcode/sandbox`'s `SandboxPool`,
|
|
97
|
+
the public primitive an extension package uses to run a project-supplied hook
|
|
98
|
+
through this exact isolation when the project's own config declares
|
|
99
|
+
`sandbox: true` on it (see [EXTENSIONS.md](EXTENSIONS.md#project-level-lifecycle-hooks)
|
|
100
|
+
and [TYPESCRIPT.md](TYPESCRIPT.md)). Every guarantee above applies identically
|
|
101
|
+
through that entry point; there is no separate, weaker sandbox for extensions
|
|
102
|
+
to reach for, and no "trusted" mode exported there — trusted execution needs
|
|
103
|
+
no primitive at all (docs/SPIKE-DEFAULT-TRUST-MODEL.md).
|
|
104
|
+
|
|
105
|
+
## What the trusted default can and can't do
|
|
106
|
+
|
|
107
|
+
A trusted route (no `sandbox`, or `sandbox: false`) has none of the guest
|
|
108
|
+
restrictions above:
|
|
109
|
+
|
|
110
|
+
- Full Node built-ins, `process`, the filesystem, `fetch`, sockets, workers
|
|
111
|
+
and npm packages are available, exactly as in any other Node module.
|
|
112
|
+
- Module resolution is ordinary Node ESM resolution: bare specifiers, dynamic
|
|
113
|
+
`import()` and node_modules all work. There is no dependency-graph allowlist
|
|
114
|
+
and no per-module/total source-size budget (function-sources.ts's
|
|
115
|
+
`MODULE_LIMIT`/`MODULE_BYTE_LIMIT`/`TOTAL_BYTE_LIMIT` apply only to what a
|
|
116
|
+
sandboxed snapshot bundles).
|
|
117
|
+
- Node's own module cache is shared across invocations and across the whole
|
|
118
|
+
process; there is no fresh heap per call. Module-level state persists
|
|
119
|
+
between requests exactly like an ordinary long-running Node server, so a
|
|
120
|
+
trusted function that mutates shared/global state affects later requests
|
|
121
|
+
the way hand-written server code would.
|
|
122
|
+
- There is no worker-thread deadline that force-terminates a stuck call. A
|
|
123
|
+
trusted invocation races a configurable timeout, but that race can only
|
|
124
|
+
reject the *call*; it cannot preempt code that blocks the event loop
|
|
125
|
+
synchronously. See [capacity](CAPACITY.md) for what this means for one slow
|
|
126
|
+
or hung trusted route's effect on the rest of the process.
|
|
127
|
+
- A snapshot reload re-imports a trusted route's own entry file fresh (each
|
|
128
|
+
reload gets its own cache-busted module registration), so editing the
|
|
129
|
+
`source` file a route declares and reloading picks up the change, the same
|
|
130
|
+
as the sandboxed pool rebuilding from scratch. A file that entry file
|
|
131
|
+
merely *imports* is not similarly busted: Node's own module cache is
|
|
132
|
+
keyed by the resolved URL of that import statement, which this runtime
|
|
133
|
+
does not rewrite, so an edited dependency two files deep from the route
|
|
134
|
+
keeps serving its old content until the process restarts. Restructure a
|
|
135
|
+
route so the code you expect to hot-reload is the declared entry file
|
|
136
|
+
itself, or restart rather than reload after editing a trusted route's
|
|
137
|
+
dependencies. A `sandbox: true` route has no such gap: reload always
|
|
138
|
+
rebuilds its whole snapshot, dependencies included.
|
|
139
|
+
|
|
140
|
+
What does **not** change with trust: `args` are still exactly the validated
|
|
141
|
+
values the route declares (never raw request input), and `env`/`secrets` are
|
|
142
|
+
still exactly what the route's YAML requests and an operator policy grants,
|
|
143
|
+
pinned to the project revision — trust changes where code runs, not what
|
|
144
|
+
it is handed *through `context`*. It does not change what the code can go get
|
|
145
|
+
on its own once it is running; see "binding grants are unaffected" above for
|
|
146
|
+
that distinction.
|
|
147
|
+
|
|
148
|
+
## Trusted code, not trusted requests
|
|
149
|
+
|
|
150
|
+
"Trusted" describes the code's authorship — first-party project code you
|
|
151
|
+
reviewed and deployed — not the requests it handles. Every request, in either
|
|
152
|
+
mode, still carries client-controlled path, query, header and body data that is
|
|
153
|
+
exactly as adversarial as it always was. Running trusted means that code
|
|
154
|
+
executes with full Node access if it mishandles that input; it does not mean
|
|
155
|
+
the input itself became safe to trust. Declare `parameters` and `request.body`
|
|
156
|
+
validation in YAML, check `args` and any other request data again inside
|
|
157
|
+
function/middleware code, and implement your own authentication and
|
|
158
|
+
authorization — no route, sandboxed or trusted, adds automatic auth.
|
|
159
|
+
`sandbox: true` narrows what a bug or an unreviewed dependency in the *code*
|
|
160
|
+
can do with that same request data; it is not a substitute for validating or
|
|
161
|
+
authenticating the request itself.
|
|
28
162
|
|
|
29
163
|
## Granting selected bindings
|
|
30
164
|
|
|
@@ -63,26 +197,28 @@ urlcode serve --project /srv/my-links --policy /etc/urlcode/my-links-policy.json
|
|
|
63
197
|
|
|
64
198
|
`dev`, `test` and `validate --local` use the same policy rules even for `.env.local`.
|
|
65
199
|
The JavaScript API accepts an equivalent operator-supplied `permissions` object.
|
|
66
|
-
Every config
|
|
67
|
-
|
|
68
|
-
|
|
200
|
+
Every config change invalidates the grant, and so does a module change within
|
|
201
|
+
what the approval digest actually hashes: for a sandboxed route, its
|
|
202
|
+
middleware/function sources and their full dependency graph; for a trusted
|
|
203
|
+
route, only its own entry-file source (see the next paragraph — a trusted
|
|
204
|
+
route's transitive dependencies are explicitly **not** part of that digest).
|
|
205
|
+
Inspect/review the new revision before updating the operator file. Policies
|
|
206
|
+
are read at startup, not hot-reloaded. A failed development candidate leaves
|
|
207
|
+
the previous approved snapshot running.
|
|
69
208
|
|
|
70
209
|
Granting a secret deliberately makes it available to every middleware and function
|
|
71
|
-
in that route
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
filesystem handle, database path or management token. Native link data changes do
|
|
84
|
-
not authorize new code or bindings. Management requires a separate operator CLI
|
|
85
|
-
or token-protected listener. See [dynamic links](DYNAMIC-LINKS.md).
|
|
210
|
+
in that route, trusted or sandboxed alike. A sandboxed route's middleware
|
|
211
|
+
sources and their full dependency graph are included in the approval digest,
|
|
212
|
+
as before; a trusted route's own entry-file source is included too, so
|
|
213
|
+
changing that file's content invalidates the grant, but a change to a helper
|
|
214
|
+
module it merely imports does not by itself (see function-sources.ts's
|
|
215
|
+
`collectTrustedSources`) — a known, documented gap versus the sandboxed path's
|
|
216
|
+
full dependency-graph hashing: a trusted route's grant scope is entry-file-only,
|
|
217
|
+
not transitive. Either way, code can include any granted data
|
|
218
|
+
in its HTTP response: neither the sandbox nor the trusted default promises
|
|
219
|
+
secrecy from code that was explicitly authorized to read a value. Minimize
|
|
220
|
+
grants, use scoped/short-lived credentials and revoke/restart when needed.
|
|
221
|
+
Other routes get none of that context.
|
|
86
222
|
|
|
87
223
|
## Next capability work
|
|
88
224
|
|
|
@@ -90,9 +226,14 @@ Outbound requests need a host-owned broker with explicit destination/method
|
|
|
90
226
|
allowlists, private/metadata/loopback-address restrictions, DNS/rebinding defenses,
|
|
91
227
|
redirect revalidation, deadlines and byte/concurrency limits. Application YAML
|
|
92
228
|
must not grant those permissions. Persistent state needs similarly scoped access.
|
|
93
|
-
Until such brokers are implemented and tested, these capabilities are unavailable
|
|
94
|
-
Provider adapters must preserve
|
|
95
|
-
|
|
229
|
+
Until such brokers are implemented and tested, these capabilities are unavailable
|
|
230
|
+
to a *sandboxed* route. Provider adapters must preserve a `sandbox: true`
|
|
231
|
+
route's isolation or reject deployment; they cannot silently downgrade a
|
|
232
|
+
route that explicitly asked for the sandbox into unrestricted Node execution.
|
|
233
|
+
(A trusted route, by contrast, already has unrestricted Node execution by
|
|
234
|
+
design on the self-hosted target — see "What the trusted default can and
|
|
235
|
+
can't do" above; non-Node targets refuse `function`/`middleware` entirely,
|
|
236
|
+
trusted or sandboxed, since neither execution mode exists there.)
|
|
96
237
|
|
|
97
238
|
## Verification and remaining risk
|
|
98
239
|
|
package/docs/INSTALL.md
CHANGED
|
@@ -6,11 +6,6 @@ pick whichever fits how you already manage tools. All of them require
|
|
|
6
6
|
TypeScript source (`dist/`, with declarations; see [TypeScript](TYPESCRIPT.md)),
|
|
7
7
|
so the installed `urlcode` command runs `dist/cli.js` and needs no build tool.
|
|
8
8
|
|
|
9
|
-
Live short-link storage additionally needs a Node build carrying the patched
|
|
10
|
-
SQLite WAL fix. Run `urlcode doctor` after installing and check `liveLinks`;
|
|
11
|
-
everything except live links works on any supported build. See
|
|
12
|
-
[dynamic links](DYNAMIC-LINKS.md#node-build-requirement).
|
|
13
|
-
|
|
14
9
|
## npm
|
|
15
10
|
|
|
16
11
|
```sh
|
package/docs/LOAD-TESTING.md
CHANGED
|
@@ -75,8 +75,8 @@ largest one that makes a number go green.
|
|
|
75
75
|
|
|
76
76
|
## What a passing run does not prove
|
|
77
77
|
|
|
78
|
-
- **GET and HEAD only.** Function routes with request bodies
|
|
79
|
-
|
|
78
|
+
- **GET and HEAD only.** Function routes with request bodies are not exercised.
|
|
79
|
+
`workload` states this in every report.
|
|
80
80
|
- **Redirects are not followed**, so a redirect's destination is never loaded.
|
|
81
81
|
- **One client, one host, no slow peers.** Tail latency under adversarial
|
|
82
82
|
clients, connection churn or packet loss is not measured.
|
|
@@ -87,5 +87,5 @@ largest one that makes a number go green.
|
|
|
87
87
|
runtime and application revisions, host, and command with any figure you keep.
|
|
88
88
|
|
|
89
89
|
`scripts/operational-drills.ts` covers the adjacent ground — mixed
|
|
90
|
-
native/function
|
|
91
|
-
|
|
90
|
+
native/function load, an invalid reload and rollback — as a local proof,
|
|
91
|
+
never a statement about production.
|
|
@@ -10,6 +10,9 @@ ship, together with the declarations; `dist` is never committed. Make is an opti
|
|
|
10
10
|
shortcut layer; npm and the CLI work on Windows, macOS and Linux. No global
|
|
11
11
|
package install, hosting account, database or Docker is needed for the local loop.
|
|
12
12
|
|
|
13
|
+
For the repository's prose-only checks, CI selection and release helper commands,
|
|
14
|
+
see [the development pipeline](DEVELOPMENT-PIPELINE.md).
|
|
15
|
+
|
|
13
16
|
## Try the runtime
|
|
14
17
|
|
|
15
18
|
From the runtime checkout, `make dev` installs locked dependencies if needed and
|
package/docs/MIDDLEWARE.md
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Middleware is reusable JavaScript around any route handler. It is optional and
|
|
4
4
|
route-local; plain redirects and assets retain their native fast path when no
|
|
5
|
-
middleware is attached.
|
|
5
|
+
middleware is attached. Middleware runs trusted and unsandboxed by default,
|
|
6
|
+
in-process with full Node access, the same as a `function` route; add
|
|
7
|
+
`sandbox: true` on the route to run the whole chain isolated instead (see
|
|
8
|
+
[trust model and sandbox opt-in](FUNCTION-SECURITY.md)).
|
|
6
9
|
|
|
7
10
|
```yaml
|
|
8
11
|
version: "1"
|
|
@@ -49,37 +52,45 @@ reading one consumes it for downstream code. There is no `clone()` or streaming
|
|
|
49
52
|
API; pass parsed data through `context.state` when needed.
|
|
50
53
|
|
|
51
54
|
Function responses support the existing text/JSON guest API. To transform their
|
|
52
|
-
body, read it and return a new `Response`. Native redirect/respond/
|
|
55
|
+
body, read it and return a new `Response`. Native redirect/respond/page/static/
|
|
53
56
|
download bodies are opaque and cannot be read through `text()` or `json()`.
|
|
54
57
|
Returning the same native response preserves original bytes, including binary
|
|
55
58
|
files, ranges and HEAD lengths. You may add headers, but cannot change its
|
|
56
59
|
original status or existing native headers while preserving that body. To replace
|
|
57
60
|
status, destination or content, return a new `Response` instead. Replacement
|
|
58
|
-
responses follow the normal
|
|
61
|
+
responses follow the normal response size limits, and (on a `sandbox: true`
|
|
62
|
+
route) the guest's text/JSON constraints. To wrap a shared
|
|
59
63
|
template around file content, render it through a function at build time and
|
|
60
64
|
publish the result: see [prerendering](PRERENDER.md).
|
|
61
65
|
|
|
62
66
|
Route selection, enabled/expiry checks, methods and input/body validation run
|
|
63
67
|
before middleware. Their errors do not pass through the chain. A missing file
|
|
64
|
-
inside a selected static mount is a downstream 404 response.
|
|
65
|
-
errors (missing, disabled, expired or unavailable store) precede middleware. YAML
|
|
68
|
+
inside a selected static mount is a downstream 404 response. YAML
|
|
66
69
|
`response.headers` apply last and override matching middleware headers. Runtime
|
|
67
70
|
framing and asset metadata protections still apply.
|
|
68
71
|
|
|
69
|
-
##
|
|
72
|
+
## Trust, isolation and testing
|
|
70
73
|
|
|
71
|
-
The whole chain and handler run
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
The whole chain and handler run as one unit, in one execution mode, chosen by
|
|
75
|
+
the route's `sandbox` field — not a per-middleware-entry choice. By default
|
|
76
|
+
(`sandbox` false/absent) that means trusted, in-process execution with full
|
|
77
|
+
Node access and no fixed worker-pool ceiling. With `sandbox: true` it means
|
|
78
|
+
one fresh QuickJS/WASM guest with one memory budget and one deadline: no
|
|
79
|
+
Node, filesystem, shell, fetch or ambient environment, and modules limited to
|
|
80
|
+
this route's declared dependency graph. Either way, all middleware on a route
|
|
81
|
+
receive that route's approved bindings, so review the whole chain; source
|
|
82
|
+
changes invalidate grants. See [trust model and sandbox opt-in](FUNCTION-SECURITY.md).
|
|
76
83
|
|
|
77
|
-
Invalid responses and repeated `next()` calls fail with 502,
|
|
78
|
-
returns
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
Invalid responses and repeated `next()` calls fail with 502, and the deadline
|
|
85
|
+
returns 504 either way. A `sandbox: true` chain also sheds load with 503 when
|
|
86
|
+
the shared worker pool is exhausted, and cannot extend the deadline or catch
|
|
87
|
+
the outer worker termination; forgotten downstream work is still drained
|
|
88
|
+
within it. A trusted chain has no worker pool to exhaust (see
|
|
89
|
+
[capacity](CAPACITY.md)), but its deadline is a race against the call's own
|
|
90
|
+
promise rather than a forced kill — it cannot preempt code that blocks the
|
|
91
|
+
event loop synchronously.
|
|
81
92
|
|
|
82
93
|
Include explicit request fixtures for middleware-wrapped routes: test success,
|
|
83
94
|
early responses, validation failures and every configured method. Audit will
|
|
84
95
|
report missing coverage instead of assuming native handler behavior. Benchmark
|
|
85
|
-
with middleware enabled to measure its actual
|
|
96
|
+
with middleware enabled to measure its actual overhead, sandboxed or trusted.
|
package/docs/MONITORING.md
CHANGED
|
@@ -19,7 +19,7 @@ analytics from these logs, and adding it would mean logging user data. See
|
|
|
19
19
|
| Endpoint | Meaning | Alert when |
|
|
20
20
|
|---|---|---|
|
|
21
21
|
| `GET /_urlcode/health` | The process is alive and not shutting down. | It fails at all (`UrlcodeDown`). |
|
|
22
|
-
| `GET /_urlcode/ready` | The active snapshot
|
|
22
|
+
| `GET /_urlcode/ready` | The active snapshot and every function worker are available. | It fails for longer than replacement takes (`UrlcodeNotReady`). |
|
|
23
23
|
|
|
24
24
|
Both return `{status, version, routes}`. A third endpoint,
|
|
25
25
|
`GET /_urlcode/metrics`, exists only with `startServer({ metrics: true })` and
|
|
@@ -38,27 +38,10 @@ configuration digest and route count, so keep them internal.
|
|
|
38
38
|
| `reload` | `status` (`ok`/`rejected`); `version` and `routes` on `ok` | A `rejected` reload means the last-good snapshot is still serving and a deploy did not take effect. |
|
|
39
39
|
| `watch` | `status` | Development watcher failure; not used by `serve`. |
|
|
40
40
|
| `function_worker` | `status` (`started`/`restarting`), `slot`; `attempt` and `delayMs` on `restarting` | Sustained `restarting` means a function is failing on real traffic. |
|
|
41
|
-
| `link_store_worker` | `status`, `readOnly`, `attempt`, `delayMs` | The same signal for link-store connections. `status: "restarting"` reports an automatic replacement with its backoff; sustained restarts mean the underlying fault is not recoverable. |
|
|
42
|
-
| `link_observer` | `status` (`failed`/`dropped`/`closed`); `reason` on `failed`; `dropped` on `dropped`; the delivery totals on `closed` | Only when an operator enables `linkEvents`. The link event channel below could not keep up or its collector failed. `dropped` means click records were discarded; like `logs_dropped`, anything built on that channel is incomplete while it fires. |
|
|
43
41
|
| `logs_dropped` | `count` | The logger shed records because the collector fell behind. Every other signal is unreliable while this fires. |
|
|
44
42
|
| `observer` | `status` (`failed`), `name` | An in-process observer threw; the request was unaffected. Written to the log only, never to observers. Sustained failures mean the observer's own sink is broken. |
|
|
45
43
|
| `throttle`, `agents`, `cache` | `route`, `outcome`; `remaining` or `list` | Policy decisions; see [policies](POLICIES.md). `throttle` logs `allowed` only in report mode. |
|
|
46
44
|
| `site` | `key`, `path`, `status` (`generated`/`shadowed`); or `severity` (`info`/`warning`) and `message` | Activation records for [site conventions](SITE.md). `shadowed` means a declared route took the path; an `info`/`warning` line reports an omitted `Sitemap:` line (no `--origin`), skipped list names or a far-future `security.txt` expiry. |
|
|
47
|
-
| `management_request` | `timestamp`, `requestId`, `collection`, `action`, `authenticated`, `principal`, `status`, `outcome`, `durationMs` | Operator activity on the link-management API. `status` 0 means no response headers were sent before the peer disconnected; such a request may still have committed a mutation. |
|
|
48
|
-
|
|
49
|
-
### The link event channel
|
|
50
|
-
|
|
51
|
-
`link_request` is **not** a stdout record. It is delivered to an `observe()`
|
|
52
|
-
function the embedding operator process supplies, after the response is over, so
|
|
53
|
-
it can never change, delay or fail a redirect. It carries `requestId`,
|
|
54
|
-
`collection`, `route`, `method`, `status`, `outcome`
|
|
55
|
-
(`completed`/`aborted`/`missing`/`disabled`/`expired`/`invalid_code`/`invalid_record`/`unavailable`)
|
|
56
|
-
and `durationMs`. The short code is redacted unless `includeCode` is set, because
|
|
57
|
-
a code identifies the link somebody followed.
|
|
58
|
-
|
|
59
|
-
The queue is bounded: under overload it drops events and reports the count
|
|
60
|
-
through `link_observer` rather than growing memory. Alert on those drops if you
|
|
61
|
-
count clicks — a quiet channel and a dropping channel look identical downstream.
|
|
62
45
|
|
|
63
46
|
Startup prints `listening` with the effective `origin`, which is what functions
|
|
64
47
|
and absolute URLs see. Behind a proxy or tunnel this must be your public origin;
|
|
@@ -69,7 +52,7 @@ forwarded headers are deliberately not trusted. See [tunnels](TUNNELS.md).
|
|
|
69
52
|
`startServer({ metrics: true })` serves `GET /_urlcode/metrics` in Prometheus
|
|
70
53
|
text format: requests by status class and by configured route, in-flight
|
|
71
54
|
gauges, shed 503s, reloads, worker restarts and healthy slots, policy
|
|
72
|
-
outcomes,
|
|
55
|
+
outcomes, dropped logs and observer errors, all prefixed
|
|
73
56
|
`urlcode_`. The same numbers are available in process as `app.metrics()`. The
|
|
74
57
|
endpoint shares the probes' admission budget and bind host and is off by
|
|
75
58
|
default; it discloses route patterns and traffic shape, so keep it internal
|
package/docs/OBSERVABILITY.md
CHANGED
|
@@ -33,16 +33,12 @@ named.
|
|
|
33
33
|
| `reload` | `status` `ok`/`rejected`; `version` string and `routes` integer on `ok` | `app.reload()` or the development watcher swapped, or refused to swap, the snapshot. |
|
|
34
34
|
| `watch` | `status` `failed` | The development watcher could not fingerprint the project. |
|
|
35
35
|
| `function_worker` | `status` `started`/`restarting`, `slot` integer; `attempt` and `delayMs` integers on `restarting` | A function worker became ready or is scheduled for replacement. |
|
|
36
|
-
| `link_store_worker` | `status` `started`/`restarting`, `readOnly` boolean; `attempt` and `delayMs` on `restarting` | The same for a link-store connection. |
|
|
37
36
|
| `signal` | `outcome` (`accepted`, `delivered`, `failed`, `dropped`), positive `count` | Best-effort webhook totals; no destination, request data or secrets. |
|
|
38
|
-
| `link_observer` | `status` `failed`/`dropped`/`closed`; `reason` `timeout`/`error` on `failed`; `dropped` integer on `dropped`; `queued`, `delivered`, `dropped`, `failed`, `timedOut`, `closed` on `closed` | Only with `linkEvents`: the link collector failed, the queue shed events, or the channel drained at shutdown. |
|
|
39
|
-
| `link_request` | `requestId`, `collection`, `route`, `code` (only with `includeCode`), `method`, `status`, `outcome`, `durationMs` | Delivered to the `linkEvents.observe` function only, after the response is over. It never reaches the log or observers; its `outcome` is counted in the metrics. |
|
|
40
37
|
| `logs_dropped` | `count` integer | The JSON logger shed records because stdout was not writable. Written by the logger itself, so observers do not see it. |
|
|
41
38
|
| `observer` | `status` `failed`, `name` string | An observer hook threw or rejected. Written to the default log only, never to observers. |
|
|
42
39
|
| `throttle` | `route`, `outcome` `allowed`/`exceeded`, `remaining` integer | A throttle decision. `allowed` is logged only in `mode: report`; enforce mode logs refusals. |
|
|
43
40
|
| `agents` | `route`, `list` string, `outcome` `denied`/`reported` | A User-Agent matched a list. The list name is logged, never the header. |
|
|
44
41
|
| `cache` | `route`, `outcome` `hit`/`stale`/`miss`/`store` | A cache lookup or store. |
|
|
45
|
-
| `management_request` | `timestamp`, `requestId`, `collection`, `action`, `authenticated`, `principal`, `status`, `outcome`, `durationMs` | The link-management API (`startLinkApi`), which has its own logger; `principal` is the operator-configured principal id. |
|
|
46
42
|
| `listening` | `address`, `port`, `mode`, `origin` | Printed once by the CLI at startup, not emitted by the server. |
|
|
47
43
|
|
|
48
44
|
Every event carries `event` (its name). Numbers are JSON numbers, never
|
|
@@ -51,11 +47,10 @@ strings.
|
|
|
51
47
|
### Privacy guarantees
|
|
52
48
|
|
|
53
49
|
No event, snapshot or exposition carries a request URL, path, query string,
|
|
54
|
-
header, body, client address, User-Agent string, binding, secret
|
|
55
|
-
|
|
50
|
+
header, body, client address, User-Agent string, binding, secret or user
|
|
51
|
+
exception text. `route` is always a configured pattern
|
|
56
52
|
from reviewed YAML. `requestId` is server-generated unless
|
|
57
|
-
`--trust-request-id` accepts one from a trusted proxy.
|
|
58
|
-
`link_request` is redacted unless the operator sets `includeCode`. An observer
|
|
53
|
+
`--trust-request-id` accepts one from a trusted proxy. An observer
|
|
59
54
|
that logs should keep the same rule; nothing in an event lets it break it.
|
|
60
55
|
|
|
61
56
|
## Observers
|
|
@@ -98,12 +93,12 @@ call. A hook that throws or returns a rejecting promise is isolated: the
|
|
|
98
93
|
request is unaffected, the next observer still runs, `observers.errors` in
|
|
99
94
|
the snapshot increments and one `observer` record goes to the default log.
|
|
100
95
|
Nothing is retried; an observer that needs delivery guarantees owns its own
|
|
101
|
-
queue
|
|
96
|
+
queue.
|
|
102
97
|
|
|
103
98
|
`onMetrics` receives a fresh snapshot every `metricsIntervalMs`
|
|
104
99
|
(`startServer` option, `0` off by default, 1 s to 1 h) and once at `close()`.
|
|
105
|
-
`onClose` runs in reverse order after the runtime
|
|
106
|
-
|
|
100
|
+
`onClose` runs in reverse order after the runtime has closed.
|
|
101
|
+
`app.observers` lists the `{ name, version }` pairs.
|
|
107
102
|
|
|
108
103
|
`createRuntime(project, { observers })` takes the same array for embedding
|
|
109
104
|
without the server: the runtime's own sink and counters are then yours, and
|
|
@@ -124,7 +119,7 @@ and capped at 10 000 keys.
|
|
|
124
119
|
|
|
125
120
|
| Field | Type | Meaning |
|
|
126
121
|
|---|---|---|
|
|
127
|
-
| `version` | gauge | Snapshot shape version, currently `
|
|
122
|
+
| `version` | gauge | Snapshot shape version, currently `2`. |
|
|
128
123
|
| `uptimeSeconds`, `rssBytes` | gauge | Process facts. |
|
|
129
124
|
| `requests.total`, `requests.byStatusClass.{2xx,3xx,4xx,5xx}` | counter | Application responses, including shed 503s. |
|
|
130
125
|
| `requests.inFlight` | gauge | Requests holding application admission now. |
|
|
@@ -135,13 +130,10 @@ and capped at 10 000 keys.
|
|
|
135
130
|
| `watch.failed` | counter | Development watcher failures. |
|
|
136
131
|
| `functionWorkers.started`, `functionWorkers.restarts` | counter | Worker starts and scheduled replacements. |
|
|
137
132
|
| `functionWorkers.healthySlots`, `functionWorkers.slots` | gauge | Ready slots and configured slots of the serving runtime. |
|
|
138
|
-
| `linkStoreWorkers.started`, `linkStoreWorkers.restarts` | counter | The same for link-store connections. |
|
|
139
133
|
| `policies.throttle.{allowed,exceeded}` | counter | Throttle decisions (see the catalogue for what enforce mode logs). |
|
|
140
134
|
| `policies.agents.{denied,reported}` | counter | Agents decisions. |
|
|
141
135
|
| `policies.cache.{hit,stale,miss,store}` | counter | Cache outcomes. |
|
|
142
|
-
| `linkRequests.{completed,aborted,missing,disabled,expired,invalid_code,invalid_record,unavailable}` | counter | Dynamic link outcomes, counted whether or not `linkEvents` is set. |
|
|
143
136
|
| `signals.{accepted,delivered,failed,dropped}` | counter | Best-effort webhook outcomes; exposed as `signals_total` with outcome labels. |
|
|
144
|
-
| `linkObserver.failed`, `linkObserver.dropped` | counter | Link collector failures and dropped events. |
|
|
145
137
|
| `logsDropped` | counter | Records the JSON logger shed. |
|
|
146
138
|
| `observers.errors` | counter | Observer hooks that threw or rejected. |
|
|
147
139
|
|
|
@@ -171,7 +163,6 @@ urlcode_function_worker_restarts_total 0
|
|
|
171
163
|
urlcode_function_worker_healthy_slots 2
|
|
172
164
|
urlcode_throttle_total{outcome="exceeded"} 14
|
|
173
165
|
urlcode_cache_total{outcome="hit"} 511
|
|
174
|
-
urlcode_link_requests_total{outcome="completed"} 0
|
|
175
166
|
urlcode_logs_dropped_total 0
|
|
176
167
|
urlcode_observer_errors_total 0
|
|
177
168
|
urlcode_uptime_seconds 86400
|