@substrat-run/control-plane-api 0.171.0 → 0.172.0

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 (41) hide show
  1. package/dist/aggregate-reads.d.ts +0 -14
  2. package/dist/aggregate-reads.d.ts.map +1 -1
  3. package/dist/aggregate-reads.js +15 -11
  4. package/dist/aggregate-reads.js.map +1 -1
  5. package/dist/aggregate-source.d.ts +23 -0
  6. package/dist/aggregate-source.d.ts.map +1 -1
  7. package/dist/aggregate-source.js +16 -1
  8. package/dist/aggregate-source.js.map +1 -1
  9. package/dist/api.d.ts.map +1 -1
  10. package/dist/api.js +33 -9
  11. package/dist/api.js.map +1 -1
  12. package/dist/cf-observability.d.ts.map +1 -1
  13. package/dist/cf-observability.js +105 -21
  14. package/dist/cf-observability.js.map +1 -1
  15. package/dist/copy-job.d.ts.map +1 -1
  16. package/dist/copy-job.js +5 -3
  17. package/dist/copy-job.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/observability.d.ts +48 -2
  23. package/dist/observability.d.ts.map +1 -1
  24. package/dist/observability.js +38 -1
  25. package/dist/observability.js.map +1 -1
  26. package/dist/platform-drain.d.ts.map +1 -1
  27. package/dist/platform-drain.js +1 -0
  28. package/dist/platform-drain.js.map +1 -1
  29. package/dist/platform-entry.generated.d.ts +1 -1
  30. package/dist/platform-entry.generated.d.ts.map +1 -1
  31. package/dist/platform-entry.generated.js +2 -2
  32. package/dist/platform-entry.generated.js.map +1 -1
  33. package/dist/tenant-request-query.d.ts +12 -3
  34. package/dist/tenant-request-query.d.ts.map +1 -1
  35. package/dist/tenant-request-query.js +29 -6
  36. package/dist/tenant-request-query.js.map +1 -1
  37. package/dist/vertical-client.d.ts +74 -12
  38. package/dist/vertical-client.d.ts.map +1 -1
  39. package/dist/vertical-client.js +233 -33
  40. package/dist/vertical-client.js.map +1 -1
  41. package/package.json +9 -9
@@ -9,8 +9,8 @@
9
9
  export const PLATFORM_ENTRY_MODULE = 'substrat-platform-entry.js';
10
10
  /** The import specifier the uploader replaces with `./<the bundle's entry>`. */
11
11
  export const PLATFORM_ENTRY_PLACEHOLDER = '__SUBSTRAT_VERTICAL_ENTRY__';
12
- /** The entry itself — 50067 bytes of ESM. */
13
- export const PLATFORM_ENTRY_SOURCE = "// substrat-platform-entry.ts\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\n// packages/contracts/dist/auth-diagnostics.js\nvar AUTH_CHECK_STAGES = [\"bearer\", \"session\", \"mcp-audience\", \"mcp-caller\"];\nvar AUTH_CHECK_OUTCOMES = [\"accepted\", \"refused\", \"absent\", \"error\"];\nvar AUTH_CHECK_REASONS = [\n \"verified\",\n \"session-invalid\",\n \"credential-missing\",\n \"discovery-failed\",\n \"key-resolution-failed\",\n \"token-expired\",\n \"token-not-active\",\n \"issuer-invalid\",\n \"audience-invalid\",\n \"claims-invalid\",\n \"signature-invalid\",\n \"token-invalid\",\n \"verification-failed\",\n \"subject-missing\",\n \"assertion-not-access-token\",\n \"delegation-refused\",\n \"token-not-for-app\",\n \"resource-mismatch\",\n \"caller-resolved\",\n \"caller-refused\",\n \"caller-error\"\n];\nvar AUTH_CHECK_CAP = 16;\nvar AUTH_DIAGNOSTICS_KEY = /* @__PURE__ */ Symbol.for(\"substrat.auth-diagnostics\");\nfunction authCheckOf(value) {\n if (!value || typeof value !== \"object\")\n return void 0;\n const v = value;\n if (!AUTH_CHECK_STAGES.includes(v.stage) || !AUTH_CHECK_OUTCOMES.includes(v.outcome) || !AUTH_CHECK_REASONS.includes(v.reason))\n return void 0;\n return { stage: v.stage, outcome: v.outcome, reason: v.reason };\n}\nfunction tallyAuthCheck(record2, value) {\n const check = authCheckOf(value);\n if (!check)\n return;\n const checks = record2.authChecks ??= [];\n if (checks.length < AUTH_CHECK_CAP)\n checks.push(check);\n else\n record2.authChecksDropped = (record2.authChecksDropped ?? 0) + 1;\n}\n\n// packages/contracts/dist/invocation-record.js\nvar INVOCATION_RECORD_HEADER = \"x-substrat-invocation-record\";\nvar REQUEST_ID_HEADER = \"x-substrat-request-id\";\nvar PARENT_REQUEST_ID_HEADER = \"x-substrat-parent-request-id\";\nvar REQUEST_ID_SHAPE = /^[0-9A-HJKMNP-TV-Z]{26}$/;\nfunction parentRequestIdOf(value) {\n return typeof value === \"string\" && REQUEST_ID_SHAPE.test(value) ? value : void 0;\n}\nvar INVOCATION_RECORD_FIELD_MAX = 128;\nvar FIELDS = [\"operation\", \"problemCode\", \"principalKind\"];\nfunction encodeInvocationRecord(record2) {\n const params = new URLSearchParams();\n for (const key of FIELDS) {\n const v = record2[key];\n if (typeof v === \"string\" && v !== \"\")\n params.set(key, v.slice(0, INVOCATION_RECORD_FIELD_MAX));\n }\n const value = params.toString();\n return value === \"\" ? null : value;\n}\nvar FIELD_COVERAGE_HEADER = \"x-substrat-field-coverage\";\nfunction invocationLevelOf(status, threw, problemCode) {\n if (threw || status === null || status >= 500)\n return \"error\";\n if (status >= 400)\n return \"warn\";\n if (problemCode)\n return \"warn\";\n return \"info\";\n}\n\n// packages/contracts/dist/trace-sink.js\nvar TRACE_SINK_KEY = /* @__PURE__ */ Symbol.for(\"substrat.trace-sink\");\nvar STARTS_KEY = /* @__PURE__ */ Symbol.for(\"substrat.trace-starts\");\nfunction starts() {\n const slots = globalThis;\n return slots[STARTS_KEY] ??= /* @__PURE__ */ new Map();\n}\nfunction markTraceStart(invocationId, at = performance.now()) {\n const map = starts();\n if (!map.has(invocationId))\n map.set(invocationId, at);\n}\nfunction forgetTraceStart(invocationId) {\n starts().delete(invocationId);\n}\nfunction installedTraceSink() {\n return globalThis[TRACE_SINK_KEY];\n}\n\n// packages/kernel/dist/invocation-line.js\nfunction invocationLine(f) {\n const emitted = f.emitted;\n return {\n substrat: \"invocation\",\n ...f.kind ? { kind: f.kind } : {},\n tenantId: f.tenantId,\n scopeId: f.scopeId,\n vertical: f.vertical ?? null,\n surface: f.surface ?? null,\n method: f.method ?? null,\n path: f.path ?? null,\n status: f.status ?? null,\n threw: f.threw,\n durationMs: f.durationMs,\n invocationId: f.invocationId,\n level: f.level,\n operation: f.operation ?? null,\n problemCode: f.problemCode ?? null,\n principalKind: f.principalKind ?? null,\n eventCount: emitted ? emitted.total : null,\n eventTypes: emitted ? distinct(emitted.events.map((e) => e.type)) : [],\n entities: emitted && f.withEntities !== false ? distinct(emitted.events.map((e) => e.entity)) : [],\n versionId: f.versionId ?? null,\n ...f.outputFields ? { outputFields: f.outputFields, ...f.fieldCoverageId ? { fieldCoverageId: f.fieldCoverageId } : {} } : {},\n ...f.storeCalls && f.storeCalls.length > 0 ? { storeCalls: f.storeCalls } : {},\n ...f.storeCallsDropped ? { storeCallsDropped: f.storeCallsDropped } : {},\n ...f.outboundCalls && f.outboundCalls.length > 0 ? { outboundCalls: f.outboundCalls } : {},\n ...f.outboundCallsDropped ? { outboundCallsDropped: f.outboundCallsDropped } : {},\n ...f.authChecks?.length ? { authChecks: f.authChecks } : {},\n ...f.authChecksDropped ? { authChecksDropped: f.authChecksDropped } : {},\n ...f.parentInvocationId ? { parentInvocationId: f.parentInvocationId, ...f.parentScopeId ? { parentScopeId: f.parentScopeId } : {} } : {},\n ...f.async ?? {}\n };\n}\nvar STORE_CALL_LOG_CAP = 16;\nfunction tallyStoreCall(tally, call) {\n const calls = tally.storeCalls ?? (tally.storeCalls = []);\n if (calls.length < STORE_CALL_LOG_CAP) {\n calls.push({ binding: call.binding, operation: call.operation, outcome: call.outcome, durationMs: call.durationMs });\n } else {\n tally.storeCallsDropped = (tally.storeCallsDropped ?? 0) + 1;\n }\n}\nvar OUTBOUND_CALL_LOG_CAP = 16;\nfunction tallyOutboundCall(tally, call) {\n const calls = tally.outboundCalls ?? (tally.outboundCalls = []);\n if (calls.length < OUTBOUND_CALL_LOG_CAP) {\n calls.push({\n host: call.host,\n path: call.path,\n method: call.method,\n status: call.status,\n outcome: call.outcome,\n durationMs: call.durationMs,\n atMs: call.atMs,\n ...call.reason !== void 0 ? { reason: call.reason } : {}\n });\n } else {\n tally.outboundCallsDropped = (tally.outboundCallsDropped ?? 0) + 1;\n }\n}\nvar STORE_CALL_SINK_KEY = \"substrat.store-call-sink\";\nfunction distinct(values) {\n return [...new Set(values)];\n}\n\n// packages/kernel/dist/ulid.js\nvar B32 = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\nvar MAX_ULID_TIME = 2 ** 48 - 1;\nvar ULID_SHAPE = /^[0-9A-HJKMNP-TV-Z]{26}$/;\nvar unencodable = (t) => `not an encodable ULID instant: ${t} (want 0..${MAX_ULID_TIME})`;\nfunction encodeTime(time) {\n let ts = \"\";\n let t = time;\n for (let i = 0; i < 10; i++) {\n ts = B32[t % 32] + ts;\n t = Math.floor(t / 32);\n }\n return ts;\n}\nfunction createUlid() {\n let lastTime = -1;\n const lastRand = new Array(16).fill(0);\n function freshRandom() {\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n for (let i = 0; i < 16; i++)\n lastRand[i] = bytes[i] % 32;\n }\n function incrementRandom() {\n for (let i = 15; i >= 0; i--) {\n if (lastRand[i] < 31) {\n lastRand[i]++;\n return true;\n }\n lastRand[i] = 0;\n }\n return false;\n }\n const mint = ((now = Date.now()) => {\n if (!Number.isSafeInteger(now))\n throw new RangeError(unencodable(now));\n let time = now;\n if (time <= lastTime) {\n time = lastTime;\n if (!incrementRandom()) {\n time = lastTime + 1;\n freshRandom();\n }\n } else {\n freshRandom();\n }\n if (time < 0 || time > MAX_ULID_TIME)\n throw new RangeError(unencodable(time));\n lastTime = time;\n let r = \"\";\n for (const d of lastRand)\n r += B32[d];\n return encodeTime(time) + r;\n });\n mint.seedFrom = (id) => {\n const time = ulidTime(id);\n if (time < lastTime)\n return;\n const rand = [];\n for (const ch of id.slice(10))\n rand.push(B32.indexOf(ch));\n if (time === lastTime) {\n let ahead = false;\n for (let i = 0; i < 16; i++) {\n if (rand[i] !== lastRand[i]) {\n ahead = rand[i] > lastRand[i];\n break;\n }\n }\n if (!ahead)\n return;\n }\n lastTime = time;\n for (let i = 0; i < 16; i++)\n lastRand[i] = rand[i];\n };\n return mint;\n}\nvar processUlid = createUlid();\nfunction ulid(now = Date.now()) {\n return processUlid(now);\n}\nfunction ulidTime(id) {\n if (!ULID_SHAPE.test(id))\n throw new Error(`not a ULID: ${id}`);\n let t = 0;\n for (const ch of id.slice(0, 10))\n t = t * 32 + B32.indexOf(ch);\n if (t > MAX_ULID_TIME)\n throw new Error(`not a ULID: ${id}`);\n return t;\n}\n\n// packages/vertical-host/src/outbound-calls.ts\nvar EGRESS_REFUSAL_HEADER = \"x-substrat-egress\";\nvar STORE_CALL_HOST = \"store.substrat.internal\";\nvar REDACTED = \"[redacted]\";\nvar PATH_MAX = 200;\nvar WORDS = /^[a-z]{1,16}(?:[-_][a-z]{1,16}){0,3}$/;\nvar WORDS_WITH_EXTENSION = /^[a-z]{1,16}(?:[-_][a-z]{1,16}){0,3}\\.[a-z][a-z0-9]{0,4}$/;\nvar SEGMENT_MAX = 40;\nvar SMALL_INTEGER = /^[0-9]{1,10}$/;\nvar VERSION = /^v[0-9]{1,3}(?:\\.[0-9]{1,3}){0,2}$/;\nfunction keepsSegment(segment) {\n if (segment.length > SEGMENT_MAX) return false;\n return segment === \"\" || WORDS.test(segment) || WORDS_WITH_EXTENSION.test(segment) || SMALL_INTEGER.test(segment) || VERSION.test(segment);\n}\nfunction redactOutboundPath(pathname) {\n const path = pathname.split(\"/\").map((segment) => keepsSegment(segment) ? segment : REDACTED).join(\"/\");\n return path.length > PATH_MAX ? `${path.slice(0, PATH_MAX)}…` : path;\n}\nvar ERROR_CLASS = /^[A-Z][A-Za-z]{0,30}(?:Error|Exception)$/;\nfunction errorName(e) {\n const name = e?.name;\n return typeof name === \"string\" && ERROR_CLASS.test(name) ? name : \"Error\";\n}\nfunction answeredOutcome(response) {\n if (response.status === 403 && response.headers?.get?.(EGRESS_REFUSAL_HEADER) === \"refused\") return \"refused\";\n return response.status >= 400 ? \"http-error\" : \"ok\";\n}\nfunction rejectedOutcome(e) {\n const name = errorName(e);\n if (name === \"TimeoutError\") return { outcome: \"timeout\" };\n if (name === \"AbortError\") return { outcome: \"aborted\" };\n return { outcome: \"failed\", reason: name };\n}\nvar SINCE = /* @__PURE__ */ Symbol.for(\"substrat.outbound-since\");\nfunction sinceMap() {\n const g = globalThis;\n return g[SINCE] ??= /* @__PURE__ */ new WeakMap();\n}\nfunction markRequestStart(recording, started) {\n try {\n sinceMap().set(recording, started);\n } catch {\n }\n}\nfunction destinationOf(input, init) {\n const fromRequest = typeof input === \"object\" && input !== null && !(input instanceof URL);\n const raw = fromRequest ? input.url : input;\n let url;\n try {\n url = new URL(String(raw));\n } catch {\n return null;\n }\n const initMethod = init?.method;\n const requestMethod = fromRequest ? input.method : void 0;\n const method = typeof initMethod === \"string\" ? initMethod : typeof requestMethod === \"string\" ? requestMethod : \"GET\";\n return { url, method: method.toUpperCase().slice(0, 16) };\n}\nfunction record(recording, destination, startedAt, end) {\n try {\n const call = {\n host: destination.url.host.toLowerCase(),\n path: redactOutboundPath(destination.url.pathname),\n method: destination.method,\n status: end.status,\n outcome: end.outcome,\n durationMs: Math.max(0, Date.now() - startedAt),\n atMs: Math.max(0, startedAt - (sinceMap().get(recording) ?? startedAt)),\n ...end.reason !== void 0 ? { reason: end.reason } : {}\n };\n tallyOutboundCall(recording, call);\n } catch {\n }\n}\nvar WRAPPED = /* @__PURE__ */ Symbol.for(\"substrat.outbound-fetch\");\nvar IN_CALL = /* @__PURE__ */ Symbol.for(\"substrat.outbound-fetch.in-call\");\nfunction recordingFetch(original, current) {\n const wrapped = function fetch(input, init) {\n const g = globalThis;\n if (g[IN_CALL] === true) return original.call(globalThis, input, init);\n let recording;\n let destination = null;\n try {\n recording = current();\n if (recording) destination = destinationOf(input, init);\n } catch {\n recording = void 0;\n }\n if (!recording || !destination || destination.url.hostname.toLowerCase() === STORE_CALL_HOST) {\n return original.call(globalThis, input, init);\n }\n const rec = recording;\n const dest = destination;\n const startedAt = Date.now();\n let pending;\n g[IN_CALL] = true;\n try {\n pending = original.call(globalThis, input, init);\n } finally {\n g[IN_CALL] = false;\n }\n return pending.then(\n (response) => {\n let status = null;\n let outcome = \"failed\";\n try {\n status = response.status;\n outcome = answeredOutcome(response);\n } catch {\n }\n record(rec, dest, startedAt, { status, outcome });\n return response;\n },\n (e) => {\n let end = { outcome: \"failed\", reason: \"Error\" };\n try {\n end = rejectedOutcome(e);\n } catch {\n }\n record(rec, dest, startedAt, { status: null, ...end });\n throw e;\n }\n );\n };\n Object.defineProperty(wrapped, WRAPPED, { value: true });\n return wrapped;\n}\nfunction installOutboundRecorder(current) {\n try {\n const g = globalThis;\n const existing = g.fetch;\n if (typeof existing !== \"function\" || existing[WRAPPED]) return;\n g.fetch = recordingFetch(existing, current);\n } catch {\n }\n}\n\n// packages/contracts/dist/wire-headers.js\nvar PLATFORM_SECRET_HEADER = \"x-substrat-platform\";\nvar CONNECTOR_ATTACHMENT_META_MAX_BYTES = 8 * 1024;\n\n// packages/contracts/dist/wire-auth.js\nvar PlatformCallError = class extends Error {\n};\nfunction secretMatches(presented, expected) {\n const given = presented ?? \"\";\n let diff = given.length ^ expected.length | (given.length === 0 ? 1 : 0);\n for (let i = 0; i < expected.length; i++) {\n diff |= (i < given.length ? given.charCodeAt(i) : 0) ^ expected.charCodeAt(i);\n }\n return diff === 0;\n}\nfunction assertPlatformCall(headers, options = {}) {\n const { expectedSecret } = options;\n if (!expectedSecret) {\n throw new PlatformCallError(\"platform calls are not configured on this deployment\");\n }\n if (!secretMatches(headers.get(PLATFORM_SECRET_HEADER), expectedSecret)) {\n throw new PlatformCallError(\"not a platform call\");\n }\n}\n\n// packages/vertical-host/src/routed-node.ts\nvar ROUTED_ID = /^[0-9A-HJKMNP-TV-Z]{26}$/;\nvar RouterAssertionError = class extends Error {\n};\nfunction readRoutedNode(headers, options = {}) {\n const rawTenant = headers.get(\"x-substrat-tenant\");\n const rawScope = headers.get(\"x-substrat-scope\");\n if (!rawTenant && !rawScope) return null;\n const { expectedSecret, allowUnsigned: allowUnsigned2 = false } = options;\n if (expectedSecret) {\n if (!secretMatches(headers.get(\"x-substrat-router\"), expectedSecret)) {\n throw new RouterAssertionError(\"router assertion is not signed by a known router\");\n }\n } else if (!allowUnsigned2) {\n throw new RouterAssertionError(\n \"router assertion cannot be verified: this worker has no ROUTER_SECRET configured\"\n );\n }\n if (!rawTenant || !rawScope) {\n throw new RouterAssertionError(\"router assertion is incomplete\");\n }\n if (!ROUTED_ID.test(rawTenant) || !ROUTED_ID.test(rawScope)) {\n throw new RouterAssertionError(\"router assertion carries a malformed id\");\n }\n return {\n tenantId: rawTenant,\n scopeId: rawScope,\n surface: headers.get(\"x-substrat-surface\") ?? \"app\",\n verticalSlug: headers.get(\"x-substrat-vertical\")\n };\n}\n\n// packages/vertical-host/src/invocation-log.ts\nfunction builtinAsyncLocalStorage() {\n try {\n const proc = globalThis.process;\n const mod = proc?.getBuiltinModule?.(\"node:async_hooks\");\n return typeof mod?.AsyncLocalStorage === \"function\" ? mod.AsyncLocalStorage : void 0;\n } catch {\n return void 0;\n }\n}\nfunction storeCallSlot(provided) {\n try {\n const g = globalThis;\n const key = Symbol.for(STORE_CALL_SINK_KEY);\n const existing = g[key];\n if (typeof existing?.run === \"function\" && typeof existing.current === \"function\") return existing;\n const Als = provided ?? builtinAsyncLocalStorage();\n if (!Als) return void 0;\n const storage = new Als();\n const slot2 = {\n current() {\n const tally = storage.getStore()?.tally;\n return tally ? (call) => tallyStoreCall(tally, call) : void 0;\n },\n invocationId: () => storage.getStore()?.invocationId,\n run: (tally, fn, invocationId) => storage.run({ tally, invocationId }, fn),\n recording: () => storage.getStore()?.tally\n };\n g[key] = slot2;\n return slot2;\n } catch {\n return void 0;\n }\n}\nfunction currentRecording() {\n const slot2 = globalThis[Symbol.for(STORE_CALL_SINK_KEY)];\n return typeof slot2?.recording === \"function\" ? slot2.recording() : void 0;\n}\nfunction runRecording(stamp, started, provided, fn) {\n const slot2 = storeCallSlot(provided);\n if (!slot2) return fn();\n try {\n const authSink = {\n record(check) {\n const record2 = currentRecording();\n if (record2) tallyAuthCheck(record2, check);\n }\n };\n globalThis[AUTH_DIAGNOSTICS_KEY] = authSink;\n } catch {\n }\n markRequestStart(stamp.record, started);\n installOutboundRecorder(currentRecording);\n return slot2.run(stamp.record, fn, stamp.invocationId);\n}\nfunction versionIdOf(env) {\n const v = env?.SUBSTRAT_VERSION_ID;\n return typeof v === \"string\" && v.length > 0 ? v : null;\n}\nvar UPLOAD_LINK_LOG_SECRET_PREFIX = \"sbup_\";\nvar UPLOAD_LINK_SECRET_SEGMENT = /^(\\/api\\/attachment-uploads\\/[^/]+\\/[^/]+\\/)[^/]+/;\nvar SECRET_SHAPED_SEGMENT = new RegExp(`(^|/)${UPLOAD_LINK_LOG_SECRET_PREFIX}[^/]*`, \"g\");\nvar redactUploadLinkSecret = (pathname) => pathname.replace(UPLOAD_LINK_SECRET_SEGMENT, \"$1[redacted]\").replace(SECRET_SHAPED_SEGMENT, \"$1[redacted]\");\nfunction pathOf(url) {\n try {\n return redactUploadLinkSecret(new URL(url).pathname);\n } catch {\n return \"/\";\n }\n}\nvar STAMPS = /* @__PURE__ */ Symbol.for(\"substrat.invocation-stamp\");\nfunction stamps() {\n const g = globalThis;\n return g[STAMPS] ??= /* @__PURE__ */ new WeakMap();\n}\nfunction invocationStampOf(request) {\n return stamps().get(request);\n}\nfunction withRequestId(response, id) {\n const headers = response?.headers;\n try {\n headers?.set?.(REQUEST_ID_HEADER, id);\n return response;\n } catch {\n }\n try {\n const r = response;\n if (r.status === 101) return response;\n const copy = new Response(r.body, r);\n copy.headers.set(REQUEST_ID_HEADER, id);\n return copy;\n } catch {\n return response;\n }\n}\nfunction beginStamp(request, env, options) {\n const stamp = { invocationId: ulid(), record: {} };\n if (installedTraceSink()) markTraceStart(stamp.invocationId);\n const fieldCoverageId = armedFieldCoverageId(request.headers, env, options);\n if (fieldCoverageId) stamp.fieldCoverageId = fieldCoverageId;\n stamps().set(request, stamp);\n return stamp;\n}\nfunction armedFieldCoverageId(headers, env, options) {\n let id;\n try {\n id = headers.get(FIELD_COVERAGE_HEADER);\n } catch {\n return void 0;\n }\n if (id === null || !ROUTED_ID.test(id)) return void 0;\n return routedNodeOrNull(headers, env, options) !== null ? id : void 0;\n}\nfunction writeInvocationLine(stamp, done, options) {\n traceRequest(stamp, done);\n try {\n writeLineOrThrow(stamp, done, options);\n } catch (e) {\n try {\n console.log(JSON.stringify({ substrat: \"invocation-log-fault\", detail: String(e) }));\n } catch {\n }\n }\n}\nfunction traceRequest(stamp, done) {\n const sink = installedTraceSink();\n if (!sink) return;\n try {\n const { record: record2, invocationId } = stamp;\n const durationMs = Date.now() - done.started;\n const line = invocationLine({\n tenantId: \"\",\n scopeId: null,\n vertical: null,\n surface: null,\n method: done.request.method,\n path: pathOf(done.request.url),\n status: done.status,\n threw: done.threw,\n durationMs,\n invocationId,\n level: invocationLevelOf(done.status, done.threw, record2.problemCode),\n operation: record2.operation,\n problemCode: record2.problemCode,\n principalKind: record2.principalKind,\n ...record2.emitted ? { emitted: record2.emitted } : {},\n versionId: versionIdOf(done.env)\n });\n sink.head(invocationId, {\n startedAt: new Date(done.started).toISOString(),\n method: line.method,\n path: line.path,\n status: line.status,\n threw: line.threw,\n durationMs: line.durationMs,\n level: line.level,\n operation: line.operation,\n problemCode: line.problemCode,\n principalKind: line.principalKind,\n eventCount: line.eventCount,\n eventTypes: line.eventTypes,\n entities: line.entities,\n versionId: line.versionId,\n // Calls the record kept no room for: a truncated list must read as truncated.\n ...record2.outboundCallsDropped ? { stepsDropped: record2.outboundCallsDropped } : {}\n });\n for (const call of record2.outboundCalls ?? []) {\n sink.step(invocationId, {\n type: \"outbound\",\n atMs: call.atMs,\n durationMs: call.durationMs,\n host: call.host,\n path: call.path,\n method: call.method,\n status: call.status,\n outcome: call.outcome,\n ...call.reason ? { reason: call.reason } : {}\n });\n }\n for (const check of record2.authChecks ?? []) {\n sink.step(invocationId, { type: \"log\", atMs: durationMs, level: check.outcome === \"refused\" || check.outcome === \"error\" ? \"warn\" : \"info\", message: `auth ${check.stage}: ${check.outcome} (${check.reason})` });\n }\n if (record2.authChecksDropped) sink.head(invocationId, { stepsDropped: record2.authChecksDropped });\n sink.end(invocationId);\n forgetTraceStart(invocationId);\n } catch {\n }\n}\nfunction writeLineOrThrow(stamp, done, options) {\n const node = routedNodeOrNull(done.request.headers, done.env, options);\n if (!node) return;\n const { record: record2, invocationId } = stamp;\n const line = invocationLine({\n tenantId: node.tenantId,\n scopeId: node.scopeId,\n vertical: node.verticalSlug,\n surface: node.surface,\n method: done.request.method,\n path: pathOf(done.request.url),\n status: done.status,\n threw: done.threw,\n durationMs: Date.now() - done.started,\n invocationId,\n level: invocationLevelOf(done.status, done.threw, record2.problemCode),\n operation: record2.operation,\n problemCode: record2.problemCode,\n principalKind: record2.principalKind,\n ...record2.emitted ? { emitted: record2.emitted } : {},\n versionId: versionIdOf(done.env),\n ...record2.outputFields ? { outputFields: record2.outputFields } : {},\n ...stamp.fieldCoverageId ? { fieldCoverageId: stamp.fieldCoverageId } : {},\n ...record2.storeCalls && record2.storeCalls.length > 0 ? { storeCalls: record2.storeCalls } : {},\n ...record2.storeCallsDropped ? { storeCallsDropped: record2.storeCallsDropped } : {},\n ...record2.outboundCalls && record2.outboundCalls.length > 0 ? { outboundCalls: record2.outboundCalls } : {},\n ...record2.outboundCallsDropped ? { outboundCallsDropped: record2.outboundCallsDropped } : {},\n ...record2.authChecks?.length ? { authChecks: record2.authChecks } : {},\n ...record2.authChecksDropped ? { authChecksDropped: record2.authChecksDropped } : {}\n });\n console.log(JSON.stringify(line));\n}\nvar OTHER_HANDLERS = [\"scheduled\", \"queue\", \"email\", \"tail\", \"trace\", \"alarm\", \"test\"];\nfunction withInvocationLog(worker, options = {}) {\n const inner = worker?.fetch;\n if (typeof inner !== \"function\") return worker;\n const wrapped = {\n async fetch(request, env, ctx) {\n if (invocationStampOf(request)) return inner.call(worker, request, env, ctx);\n const started = Date.now();\n const stamp = beginStamp(request, env, options);\n let status = null;\n let threw = false;\n try {\n const response = withRequestId(\n await runRecording(stamp, started, options.asyncLocalStorage, () => inner.call(worker, request, env, ctx)),\n stamp.invocationId\n );\n status = response.status;\n handRecordToRouter(response, stamp.record, request, env, options);\n return response;\n } catch (e) {\n threw = true;\n throw e;\n } finally {\n writeInvocationLine(stamp, { request, env, status, threw, started }, options);\n }\n }\n };\n for (const name of OTHER_HANDLERS) {\n const h = worker[name];\n if (typeof h === \"function\") wrapped[name] = h.bind(worker);\n }\n return wrapped;\n}\nfunction handRecordToRouter(response, record2, request, env, options) {\n try {\n const value = encodeInvocationRecord(record2);\n if (value === null || !routedNodeOrNull(request.headers, env, options)) return;\n response.headers?.set?.(\n INVOCATION_RECORD_HEADER,\n value\n );\n } catch {\n }\n}\nfunction routedNodeOrNull(headers, env, options) {\n try {\n return readRoutedNode(headers, {\n expectedSecret: options.routerSecret?.(env),\n allowUnsigned: options.allowUnsigned?.(env) ?? false\n });\n } catch (e) {\n if (!(e instanceof RouterAssertionError)) console.log(JSON.stringify({ substrat: \"invocation-log-fault\", detail: String(e) }));\n return null;\n }\n}\n\n// packages/contracts/dist/scope-store-envelope.js\nfunction bytesToBase64Url(bytes) {\n let binary = \"\";\n for (const byte of bytes)\n binary += String.fromCharCode(byte);\n return btoa(binary).replaceAll(\"+\", \"-\").replaceAll(\"/\", \"_\").replaceAll(\"=\", \"\");\n}\nfunction base64UrlToBytes(value) {\n if (!/^[A-Za-z0-9_-]+$/.test(value))\n return void 0;\n const padded = value.replaceAll(\"-\", \"+\").replaceAll(\"_\", \"/\") + \"=\".repeat((4 - value.length % 4) % 4);\n try {\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++)\n bytes[i] = binary.charCodeAt(i);\n return bytes;\n } catch {\n return void 0;\n }\n}\nfunction copyBytes(bytes) {\n const copy = new ArrayBuffer(bytes.byteLength);\n new Uint8Array(copy).set(bytes);\n return copy;\n}\nvar SMALL_ORDER_KEYS = [\n \"0000000000000000000000000000000000000000000000000000000000000000\",\n // y = 0, order 4\n \"0100000000000000000000000000000000000000000000000000000000000000\",\n // the identity, order 1\n \"26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05\",\n // order 8\n \"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a\",\n // order 8\n \"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f\",\n // y = p - 1, order 2\n \"edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f\",\n // y = p, a non-canonical 0\n \"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f\"\n // y = p + 1, a non-canonical identity\n];\nfunction isSmallOrderKey(bytes) {\n const unsigned = Uint8Array.from(bytes);\n unsigned[31] = unsigned[31] & 127;\n const hex = [...unsigned].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n return SMALL_ORDER_KEYS.includes(hex);\n}\nasync function scopeStoreKeyId(publicKey) {\n const bytes = base64UrlToBytes(publicKey);\n if (!bytes || bytes.length !== 32)\n throw new Error(\"a store public key is not 32 bytes of base64url\");\n if (isSmallOrderKey(bytes))\n throw new Error(\"a store public key is a small-order point\");\n const hash = await crypto.subtle.digest(\"SHA-256\", copyBytes(bytes));\n return bytesToBase64Url(new Uint8Array(hash).slice(0, 8));\n}\nfunction scopeStorePublicKeyProblem(key) {\n if (key.length === 0)\n return \"empty\";\n const bytes = base64UrlToBytes(key);\n if (bytes?.length !== 32)\n return \"length\";\n if (isSmallOrderKey(bytes))\n return \"small-order\";\n if (bytesToBase64Url(bytes) !== key)\n return \"non-canonical\";\n return void 0;\n}\nvar PUBLIC_KEY_PROBLEM = {\n empty: \"a store public key list is empty or has an empty entry\",\n length: \"a store public key is not 32 bytes of base64url\",\n \"small-order\": \"a store public key is a small-order point\",\n \"non-canonical\": \"a store public key is not canonical base64url\"\n};\nfunction parseScopeStorePublicKeys(text) {\n const keys = text.split(\",\").map((key) => key.trim());\n if (keys.some((key) => key.length === 0))\n throw new Error(PUBLIC_KEY_PROBLEM.empty);\n const seen = /* @__PURE__ */ new Set();\n for (const key of keys) {\n const problem2 = scopeStorePublicKeyProblem(key);\n if (problem2)\n throw new Error(PUBLIC_KEY_PROBLEM[problem2]);\n if (seen.has(key))\n throw new Error(\"a store public key is listed twice\");\n seen.add(key);\n }\n return keys;\n}\nasync function verifyScopeStoreEnvelope(publicKey, canonical, signature) {\n const keyBytes = base64UrlToBytes(publicKey);\n const sigBytes = base64UrlToBytes(signature);\n if (!keyBytes || !sigBytes || sigBytes.length !== 64)\n return false;\n try {\n const key = await crypto.subtle.importKey(\"raw\", copyBytes(keyBytes), { name: \"Ed25519\" }, false, [\"verify\"]);\n return await crypto.subtle.verify(\"Ed25519\", key, copyBytes(sigBytes), new TextEncoder().encode(canonical));\n } catch {\n return false;\n }\n}\n\n// packages/contracts/dist/wire-signing.js\nvar WIRE_ENVELOPE_VERSION = 1;\nvar WIRE_ENVELOPE_TAG = `substrat-wire/${WIRE_ENVELOPE_VERSION}`;\nvar WIRE_ENVELOPE_HEADER = \"x-substrat-wire-envelope\";\nvar WIRE_SIGNATURE_HEADER = \"x-substrat-wire-signature\";\nvar WIRE_ENVELOPE_MAX_LIFETIME_SECONDS = 120;\nvar WIRE_CLOCK_SKEW_SECONDS = 30;\nvar SHAPES = {\n router: {\n // An end user's request the router resolved: always a tenant, a scope and a surface. The\n // body is a stream the router does not buffer, so a route binds none.\n route: { scope: \"required\", surface: \"required\", vertical: \"optional\", body: \"absent\" },\n // An internal call the router makes itself, such as a peer invocation.\n call: { scope: \"optional\", surface: \"absent\", vertical: \"optional\", body: \"required\" }\n },\n platform: {\n call: { scope: \"optional\", surface: \"absent\", vertical: \"absent\", body: \"required\" }\n }\n};\nfunction wireEnvelopeShape(iss, kind) {\n const byKind = Object.hasOwn(SHAPES, iss) ? SHAPES[iss] : void 0;\n return byKind && Object.hasOwn(byKind, kind) ? byKind[kind] : void 0;\n}\nvar LINES = 15;\nvar ULID = /^[0-9A-HJKMNP-TV-Z]{26}$/;\nvar METHOD = /^[!#$%&'*+.^_`|~0-9A-Z-]{1,32}$/;\nvar DIGEST = /^[0-9a-f]{64}$/;\nvar SECONDS = /^(0|[1-9]\\d{0,14})$/;\nvar CONTROL = /[\\u0000-\\u001f\\u007f]/;\nvar LONE_SURROGATE = /[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/;\nvar MAX_TEXT = 255;\nvar isText = (value) => value.length <= MAX_TEXT && !CONTROL.test(value) && !LONE_SURROGATE.test(value);\nvar isWireAudience = (value) => value !== \"\" && isText(value);\nfunction isCanonicalBase64Url(text, length) {\n const bytes = base64UrlToBytes(text);\n return bytes?.length === length && bytesToBase64Url(bytes) === text;\n}\nfunction admits(presence, value, valid) {\n if (value === \"\")\n return presence !== \"required\";\n return presence !== \"absent\" && valid(value);\n}\nfunction parseWireEnvelope(canonical) {\n const lines = canonical.split(\"\\n\");\n if (lines.length !== LINES)\n return void 0;\n const [tag, iss, kind, kid, aud, tenantId, scopeId, surface, vertical2, method, targetDigest, bodyDigest, iat, exp, nonce] = lines;\n if (tag !== WIRE_ENVELOPE_TAG)\n return void 0;\n const shape = wireEnvelopeShape(iss, kind);\n if (!shape)\n return void 0;\n if (!isCanonicalBase64Url(kid, 8) || !isCanonicalBase64Url(nonce, 16))\n return void 0;\n if (!isWireAudience(aud))\n return void 0;\n if (tenantId === \"\" !== (scopeId === \"\"))\n return void 0;\n if (!admits(shape.scope, tenantId, (id) => ULID.test(id)) || !admits(shape.scope, scopeId, (id) => ULID.test(id)))\n return void 0;\n if (!admits(shape.surface, surface, isText) || !admits(shape.vertical, vertical2, isText))\n return void 0;\n if (!METHOD.test(method) || !DIGEST.test(targetDigest) || !admits(shape.body, bodyDigest, (d) => DIGEST.test(d)))\n return void 0;\n if (!SECONDS.test(iat) || !SECONDS.test(exp))\n return void 0;\n const orNull = (value) => value === \"\" ? null : value;\n return {\n v: 1,\n iss,\n kind,\n kid,\n aud,\n tenantId: orNull(tenantId),\n scopeId: orNull(scopeId),\n surface: orNull(surface),\n vertical: orNull(vertical2),\n method,\n targetDigest,\n bodyDigest: orNull(bodyDigest),\n iat: Number(iat),\n exp: Number(exp),\n nonce\n };\n}\nfunction decodeWireEnvelope(header) {\n const bytes = base64UrlToBytes(header);\n if (!bytes)\n return void 0;\n let canonical;\n try {\n canonical = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true }).decode(bytes);\n } catch {\n return void 0;\n }\n const claims = parseWireEnvelope(canonical);\n return claims ? { canonical, claims } : void 0;\n}\nfunction bytesOf(data) {\n if (typeof data === \"string\")\n return new TextEncoder().encode(data);\n const view = ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);\n return view.buffer instanceof ArrayBuffer ? view : view.slice();\n}\nasync function wireDigest(data) {\n const hash = await crypto.subtle.digest(\"SHA-256\", bytesOf(data));\n return [...new Uint8Array(hash)].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\nfunction wireRequestBinding(request) {\n const url = new URL(request.url);\n return { method: request.method.toUpperCase(), target: `${url.pathname}${url.search}` };\n}\nfunction lifetimeAdmitted(iat, exp) {\n return exp > iat && exp - iat <= WIRE_ENVELOPE_MAX_LIFETIME_SECONDS;\n}\nvar PREPARED = /* @__PURE__ */ new WeakMap();\nasync function wireVerificationKeys(config) {\n const listed = (text) => text?.trim() ? parseScopeStorePublicKeys(text) : [];\n const router = listed(config.router);\n const platform = listed(config.platform);\n if (router.some((key) => platform.includes(key)))\n throw new Error(\"a wire public key is listed for both issuers\");\n const byKid = async (keys2) => new Map(await Promise.all(keys2.map(async (key) => [await scopeStoreKeyId(key), key])));\n const own = Object.freeze({ router: await byKid(router), platform: await byKid(platform) });\n const keys = Object.freeze({ router: new Map(own.router), platform: new Map(own.platform) });\n PREPARED.set(keys, own);\n return keys;\n}\nvar WIRE_REFUSED = Object.freeze({ ok: false });\nasync function verifyWireEnvelope(input) {\n const refuse = (reason) => {\n try {\n void Promise.resolve(input.onRefusal?.(reason)).catch(() => {\n });\n } catch {\n }\n return WIRE_REFUSED;\n };\n const { expected, keys, nowSeconds } = input;\n const own = PREPARED.get(keys);\n if (!own)\n throw new TypeError(\"verifyWireEnvelope takes the key set wireVerificationKeys built\");\n const replay = expected.kind === \"call\" ? expected.replay : \"none\";\n if (replay !== \"none\" && typeof replay?.claim !== \"function\") {\n throw new TypeError(\"a call's expectation states its replay policy: 'none' or a nonce cache\");\n }\n if (replay !== \"none\" && expected.scopeId == null) {\n throw new TypeError(\"a call that keeps nonces pins the scope whose object holds them\");\n }\n if (replay !== \"none\" && typeof input.clockSeconds !== \"function\") {\n throw new TypeError(\"a call that keeps nonces takes a clockSeconds function to time its claim\");\n }\n const header = input.headers.get(WIRE_ENVELOPE_HEADER);\n const signature = input.headers.get(WIRE_SIGNATURE_HEADER);\n if (!header || !signature)\n return refuse(\"missing\");\n const decoded = decodeWireEnvelope(header);\n if (!decoded)\n return refuse(\"malformed\");\n const { canonical, claims } = decoded;\n if (claims.iss !== expected.iss)\n return refuse(\"issuer\");\n if (claims.kind !== expected.kind)\n return refuse(\"kind\");\n const publicKey = own[claims.iss].get(claims.kid);\n if (!publicKey)\n return refuse(\"kid\");\n if (!await verifyScopeStoreEnvelope(publicKey, canonical, signature))\n return refuse(\"signature\");\n if (claims.aud !== expected.aud)\n return refuse(\"audience\");\n if (!lifetimeAdmitted(claims.iat, claims.exp))\n return refuse(\"lifetime\");\n if (!(nowSeconds >= claims.iat - WIRE_CLOCK_SKEW_SECONDS))\n return refuse(\"not-yet-valid\");\n if (!(nowSeconds <= claims.exp + WIRE_CLOCK_SKEW_SECONDS))\n return refuse(\"expired\");\n if (claims.method !== expected.method)\n return refuse(\"method\");\n if (claims.targetDigest !== await wireDigest(expected.target))\n return refuse(\"target\");\n if (expected.kind === \"call\") {\n const body = typeof expected.body === \"function\" ? await expected.body() : expected.body;\n if (body == null || claims.bodyDigest !== await wireDigest(body))\n return refuse(\"body\");\n }\n if (expected.tenantId !== void 0 && claims.tenantId !== expected.tenantId)\n return refuse(\"scope\");\n if (expected.scopeId !== void 0 && claims.scopeId !== expected.scopeId)\n return refuse(\"scope\");\n if (replay !== \"none\") {\n const until = claims.exp + WIRE_CLOCK_SKEW_SECONDS;\n const claimedAt = input.clockSeconds();\n if (!Number.isFinite(claimedAt) || claimedAt < 0) {\n throw new TypeError(\"clockSeconds read something other than a time in seconds\");\n }\n if (!(claimedAt <= until))\n return refuse(\"claim-after-until\");\n if (await replay.claim(claims.nonce, until) !== true)\n return refuse(\"replay\");\n }\n return { ok: true, claims };\n}\nvar WIRE_ROUTER_KEYS_VAR = \"SUBSTRAT_WIRE_ROUTER_KEYS\";\nvar WIRE_PLATFORM_KEYS_VAR = \"SUBSTRAT_WIRE_PLATFORM_KEYS\";\nvar WIRE_AUDIENCE_VAR = \"SUBSTRAT_WIRE_AUDIENCE\";\nvar WIRE_STATE_VAR = \"SUBSTRAT_WIRE_STATE\";\nfunction wireStateOf(config) {\n const listed = (value) => typeof value === \"string\" && value.trim() !== \"\";\n if (!listed(config.routerKeys) && !listed(config.platformKeys))\n return \"legacy-only\";\n return config.state === \"legacy-only\" || config.state === \"signed-only\" ? config.state : \"dual\";\n}\nvar ROUTE_ENDPOINT = Object.freeze({ kind: \"route\", issuers: Object.freeze([\"router\"]) });\nvar CALL_ENDPOINT = Object.freeze({ kind: \"call\", issuers: Object.freeze([\"platform\"]) });\nvar PEER_INVOKE_ENDPOINT = Object.freeze({ kind: \"call\", issuers: Object.freeze([\"platform\", \"router\"]) });\nvar ROUTER_CALLS = Object.freeze([\"/internal/vertical-invoke\", \"/internal/connector-call\", \"/internal/connector-probe\"]);\nfunction tryDecode(path, decoder) {\n try {\n return decoder(path);\n } catch {\n return path.replace(/(?:%[0-9A-Fa-f]{2})+/g, (run) => {\n try {\n return decoder(run);\n } catch {\n return run;\n }\n });\n }\n}\nfunction collapsed(path) {\n const segments = [];\n for (const segment of path.split(\"/\")) {\n if (segment === \"..\")\n segments.pop();\n else if (segment !== \"\" && segment !== \".\")\n segments.push(segment);\n }\n return `/${segments.join(\"/\")}`;\n}\nvar INTERNAL = /^\\/internal(?:\\/|$)/i;\nfunction wireEndpoint(pathname) {\n const routed = tryDecode(pathname.includes(\"%25\") ? pathname.replace(/%25/g, \"%2525\") : pathname, decodeURI);\n const forms = [pathname, routed, tryDecode(pathname, decodeURIComponent)];\n if (!forms.some((form) => INTERNAL.test(form) || INTERNAL.test(collapsed(form))))\n return ROUTE_ENDPOINT;\n return ROUTER_CALLS.includes(pathname) ? PEER_INVOKE_ENDPOINT : CALL_ENDPOINT;\n}\nvar WIRE_REPLAY_FREE_CALLS = Object.freeze([\n \"/internal/query\",\n \"/internal/redrain-count\",\n \"/internal/lifecycle-flow\",\n \"/internal/operation-series\",\n \"/internal/connector-grants\",\n // A connector probe (vertical-connectors.md §6.3) reads no scope and writes nothing in the\n // vertical: any state it changes comes back in the response for the platform to apply. It names\n // no scope either, so there is no store a nonce could be kept in.\n \"/internal/connector-probe\"\n]);\nvar SAFE_METHODS = /* @__PURE__ */ new Set([\"GET\", \"HEAD\", \"OPTIONS\"]);\nfunction wireReplayOf(method, pathname) {\n return SAFE_METHODS.has(method.toUpperCase()) || WIRE_REPLAY_FREE_CALLS.includes(pathname) ? \"none\" : \"nonce\";\n}\nvar UNSIGNED = Object.freeze([\n [FIELD_COVERAGE_HEADER, \"route\", (value) => ULID.test(value)],\n [PARENT_REQUEST_ID_HEADER, \"call\", (value) => parentRequestIdOf(value) !== void 0]\n]);\nvar WIRE_UNSIGNED_HEADERS = Object.freeze(Object.fromEntries(UNSIGNED.map(([name, kind]) => [name, kind])));\nfunction wireUnsignedHeadersOf(kind, headers) {\n const kept = [];\n for (const [name, on, valid] of UNSIGNED) {\n const value = on === kind ? headers.get(name) : null;\n if (value !== null && valid(value))\n kept.push([name, value]);\n }\n return kept;\n}\n\n// packages/control-plane-api/platform-entry/wire-gate.ts\nvar WIRE_CLAIM_TIMEOUT_MS = 1e3;\nvar WIRE_REFUSAL_BODY = '{\"type\":\"about:blank\",\"title\":\"Unauthorized\",\"status\":401,\"detail\":\"unauthorized\",\"error\":\"unauthorized\"}';\nvar WIRE_FAULT_BODY = '{\"type\":\"about:blank\",\"title\":\"Service unavailable\",\"status\":503,\"detail\":\"this request could not be verified\",\"error\":\"this request could not be verified\"}';\nvar problem = (body, status) => new Response(body, { status, headers: { \"content-type\": \"application/problem+json\" } });\nvar ASSERTED_PREFIX = \"x-substrat-\";\nvar TENANT = \"x-substrat-tenant\";\nvar SCOPE = \"x-substrat-scope\";\nvar SURFACE = \"x-substrat-surface\";\nvar VERTICAL = \"x-substrat-vertical\";\nvar ROUTER_CREDENTIAL = \"x-substrat-router\";\nvar GateFault = class extends Error {\n constructor(fault) {\n super(fault);\n this.fault = fault;\n }\n fault;\n};\nfunction claimedThrough(reach, timeoutMs) {\n return {\n async claim(nonce, until) {\n let cache;\n try {\n cache = reach();\n } catch {\n throw new GateFault(\"nonce-store\");\n }\n if (cache == null) throw new GateFault(\"no-nonce-store\");\n let timer;\n try {\n return await Promise.race([\n cache.claim(nonce, until),\n new Promise((_, reject) => {\n timer = setTimeout(() => reject(new Error(\"the nonce store did not answer in time\")), timeoutMs);\n })\n ]);\n } catch {\n throw new GateFault(\"nonce-store\");\n } finally {\n clearTimeout(timer);\n }\n }\n };\n}\nvar builtKeys;\nfunction keysFor(router, platform) {\n const text = `${router}\n${platform}`;\n if (builtKeys?.text !== text) builtKeys = { text, keys: wireVerificationKeys({ router, platform }) };\n return builtKeys.keys;\n}\nvar textOf = (value) => typeof value === \"string\" ? value : \"\";\nfunction stateOf(env) {\n const e = env ?? {};\n return wireStateOf({ routerKeys: e[WIRE_ROUTER_KEYS_VAR], platformKeys: e[WIRE_PLATFORM_KEYS_VAR], state: e[WIRE_STATE_VAR] });\n}\nfunction legacyOf(request, call, env, options) {\n if (call) {\n let credential = request.headers.has(PLATFORM_SECRET_HEADER) ? \"invalid\" : \"absent\";\n try {\n assertPlatformCall(request.headers, { expectedSecret: options.platformSecret(env) });\n credential = \"valid\";\n } catch {\n }\n return { credential, identity: null };\n }\n try {\n const node = options.routedNode(request.headers, env);\n return node ? { credential: \"valid\", identity: node } : { credential: \"absent\", identity: null };\n } catch {\n return { credential: \"invalid\", identity: null };\n }\n}\nvar sameIdentity = (legacy, claims) => legacy.tenantId === claims.tenantId && legacy.scopeId === claims.scopeId && legacy.surface === claims.surface && legacy.verticalSlug === claims.vertical;\nvar servedAs = (who) => ({\n tenantId: who?.tenantId ?? null,\n scopeId: who?.scopeId ?? null\n});\nfunction withWireGate(worker, options) {\n const inner = worker?.fetch;\n if (typeof inner !== \"function\") return worker;\n const clock = options.clock ?? Date.now;\n const seconds = () => Math.floor(clock() / 1e3);\n const timeoutMs = options.claimTimeoutMs ?? WIRE_CLAIM_TIMEOUT_MS;\n const log = options.log ?? ((line) => console.log(line));\n const serve = (request, env, ctx) => inner.call(worker, request, env, ctx);\n async function judged(request, env, ctx, state) {\n const nowSeconds = seconds();\n const e = env;\n const aud = textOf(e[WIRE_AUDIENCE_VAR]);\n const { method, target } = wireRequestBinding(request);\n const pathname = new URL(request.url).pathname;\n const endpoint = wireEndpoint(pathname);\n const call = endpoint.kind === \"call\";\n const claimed = call ? decodeWireEnvelope(request.headers.get(WIRE_ENVELOPE_HEADER) ?? \"\")?.claims : void 0;\n const issuer = claimed && endpoint.issuers.includes(claimed.iss) ? claimed.iss : endpoint.issuers[0];\n let claims;\n const line = (outcome, legacy2, extra = {}) => {\n const v = e[\"SUBSTRAT_VERSION_ID\"];\n const record2 = {\n substrat: \"wire\",\n state,\n issuer: claims?.iss ?? (endpoint.issuers.length === 1 ? endpoint.issuers[0] : null),\n kind: endpoint.kind,\n outcome,\n reason: null,\n fault: null,\n legacy: legacy2,\n tenantId: null,\n scopeId: null,\n versionId: typeof v === \"string\" && v.length > 0 ? v : null,\n ...extra\n };\n try {\n log(JSON.stringify(record2));\n } catch {\n }\n };\n const faulted = (fault, legacy2) => {\n line(\"fault\", legacy2, { fault });\n return problem(WIRE_FAULT_BODY, 503);\n };\n const legacy = legacyOf(request, call, env, options);\n let downstream = request;\n const staticFault = (fault, extra = {}) => {\n if (state !== \"dual\") return faulted(fault, legacy.credential);\n line(\"fault\", legacy.credential, { fault, ...extra });\n return serve(downstream, env, ctx);\n };\n let keys;\n try {\n keys = await keysFor(textOf(e[WIRE_ROUTER_KEYS_VAR]), textOf(e[WIRE_PLATFORM_KEYS_VAR]));\n } catch {\n return staticFault(\"keys\", servedAs(legacy.identity));\n }\n const readBody = async () => {\n if (request.body === null) return \"\";\n let body;\n try {\n body = await request.arrayBuffer();\n } catch {\n return null;\n }\n downstream = new Request(request, { body });\n return body;\n };\n const keepsNonces = call && wireReplayOf(method, pathname) === \"nonce\";\n const pinned = keepsNonces ? claimed?.scopeId ?? void 0 : void 0;\n const reach = options.nonceCache;\n const store = pinned !== void 0 && reach ? claimedThrough(() => reach(env, pinned, claimed?.tenantId ?? null), timeoutMs) : void 0;\n const expected = call ? {\n iss: issuer,\n kind: \"call\",\n aud,\n method,\n target,\n body: readBody,\n replay: store ?? \"none\",\n scopeId: store ? pinned : void 0\n } : { iss: \"router\", kind: \"route\", aud, method, target };\n let reason = null;\n let noStore = !store;\n try {\n const verdict = await verifyWireEnvelope({\n headers: request.headers,\n keys,\n expected,\n nowSeconds,\n clockSeconds: seconds,\n onRefusal: (why) => {\n reason = why;\n }\n });\n if (verdict.ok) claims = verdict.claims;\n } catch (error) {\n if (error instanceof GateFault && error.fault === \"no-nonce-store\") {\n claims = claimed;\n noStore = true;\n } else {\n return faulted(error instanceof GateFault ? error.fault : \"verifier\", legacy.credential);\n }\n }\n if (claims && keepsNonces && noStore) {\n return staticFault(claims.scopeId == null ? \"unscoped\" : \"no-nonce-store\", servedAs(legacy.identity ?? claims));\n }\n const mismatched = !!claims && !!legacy.identity && !sameIdentity(legacy.identity, claims);\n if (state === \"dual\" && legacy.credential === \"valid\") {\n const outcome = !claims ? \"legacy-accepted\" : mismatched ? \"dual-mismatch\" : \"signed-accepted\";\n line(outcome, \"valid\", { reason, ...servedAs(legacy.identity ?? claims ?? null) });\n return serve(downstream, env, ctx);\n }\n if (!claims) {\n line(\"refused\", legacy.credential, { reason });\n if (state === \"dual\") return serve(downstream, env, ctx);\n return problem(WIRE_REFUSAL_BODY, 401);\n }\n let proven;\n try {\n proven = provenAs(downstream, claims, env, options);\n } catch {\n return faulted(\"verifier\", legacy.credential);\n }\n line(mismatched ? \"dual-mismatch\" : \"signed-accepted\", legacy.credential, servedAs(claims));\n return serve(proven, env, ctx);\n }\n return {\n ...worker,\n fetch(request, env, ctx) {\n const state = stateOf(env);\n if (state === \"legacy-only\") return serve(request, env, ctx);\n return judged(request, env, ctx, state);\n }\n };\n}\nfunction provenAs(request, claims, env, options) {\n const headers = new Headers(request.headers);\n for (const name of [...headers.keys()]) if (name.startsWith(ASSERTED_PREFIX)) headers.delete(name);\n for (const [name, value] of wireUnsignedHeadersOf(claims.kind, request.headers)) headers.set(name, value);\n const put = (name, value) => {\n if (value) headers.set(name, value);\n };\n if (claims.kind === \"route\") {\n put(TENANT, claims.tenantId);\n put(SCOPE, claims.scopeId);\n put(SURFACE, claims.surface);\n put(VERTICAL, claims.vertical);\n put(ROUTER_CREDENTIAL, options.routerSecret(env));\n } else {\n put(VERTICAL, claims.vertical);\n put(PLATFORM_SECRET_HEADER, options.platformSecret(env));\n }\n return new Request(request, { headers });\n}\n\n// packages/control-plane-api/platform-entry/nonce-store.ts\nfunction nonceStoreOf(registered) {\n return (env, scopeId, tenantId) => {\n const hostFor = registered();\n if (!hostFor) return void 0;\n const store = hostFor(env);\n if (typeof store?.claimWireNonceLocal !== \"function\" || tenantId === null) return void 0;\n const claim = store.claimWireNonceLocal.bind(store);\n return { claim: (nonce, until) => claim(tenantId, scopeId, nonce, until) };\n };\n}\n\n// packages/vertical-host/src/scope-sweep-host.ts\nvar SCOPE_SWEEP_HOST_KEY = \"substrat.scope-sweep-host\";\nfunction slot() {\n return globalThis;\n}\nfunction registeredScopeSweepHost() {\n return slot()[Symbol.for(SCOPE_SWEEP_HOST_KEY)];\n}\n\n// substrat-platform-entry.ts\nimport * as vertical from \"__SUBSTRAT_VERTICAL_ENTRY__\";\nexport * from \"__SUBSTRAT_VERTICAL_ENTRY__\";\nvar routerSecret = (env) => env.ROUTER_SECRET;\nvar allowUnsigned = (env) => env.ALLOW_DEV_NODE === \"true\";\nvar substrat_platform_entry_default = withWireGate(\n withInvocationLog(vertical.default, { routerSecret, allowUnsigned, asyncLocalStorage: AsyncLocalStorage }),\n {\n routedNode: (headers, env) => readRoutedNode(headers, { expectedSecret: routerSecret(env), allowUnsigned: allowUnsigned(env) }),\n routerSecret,\n platformSecret: (env) => env.PLATFORM_SECRET,\n nonceCache: nonceStoreOf(registeredScopeSweepHost)\n }\n);\nexport {\n substrat_platform_entry_default as default\n};\n";
12
+ /** The entry itself — 50959 bytes of ESM. */
13
+ export const PLATFORM_ENTRY_SOURCE = "// substrat-platform-entry.ts\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\n// packages/contracts/dist/auth-diagnostics.js\nvar AUTH_CHECK_STAGES = [\"bearer\", \"session\", \"mcp-audience\", \"mcp-caller\"];\nvar AUTH_CHECK_OUTCOMES = [\"accepted\", \"refused\", \"absent\", \"error\"];\nvar AUTH_CHECK_REASONS = [\n \"verified\",\n \"session-invalid\",\n \"credential-missing\",\n \"discovery-failed\",\n \"key-resolution-failed\",\n \"token-expired\",\n \"token-not-active\",\n \"issuer-invalid\",\n \"audience-invalid\",\n \"claims-invalid\",\n \"signature-invalid\",\n \"token-invalid\",\n \"verification-failed\",\n \"subject-missing\",\n \"assertion-not-access-token\",\n \"delegation-refused\",\n \"token-not-for-app\",\n \"resource-mismatch\",\n \"caller-resolved\",\n \"caller-refused\",\n \"caller-error\"\n];\nvar AUTH_CHECK_CAP = 16;\nvar AUTH_DIAGNOSTICS_KEY = /* @__PURE__ */ Symbol.for(\"substrat.auth-diagnostics\");\nfunction authCheckOf(value) {\n if (!value || typeof value !== \"object\")\n return void 0;\n const v = value;\n if (!AUTH_CHECK_STAGES.includes(v.stage) || !AUTH_CHECK_OUTCOMES.includes(v.outcome) || !AUTH_CHECK_REASONS.includes(v.reason))\n return void 0;\n return { stage: v.stage, outcome: v.outcome, reason: v.reason };\n}\nfunction tallyAuthCheck(record2, value) {\n const check = authCheckOf(value);\n if (!check)\n return;\n const checks = record2.authChecks ??= [];\n if (checks.length < AUTH_CHECK_CAP)\n checks.push(check);\n else\n record2.authChecksDropped = (record2.authChecksDropped ?? 0) + 1;\n}\n\n// packages/contracts/dist/invocation-record.js\nvar INVOCATION_RECORD_HEADER = \"x-substrat-invocation-record\";\nvar REQUEST_ID_HEADER = \"x-substrat-request-id\";\nvar PARENT_REQUEST_ID_HEADER = \"x-substrat-parent-request-id\";\nvar REQUEST_ID_SHAPE = /^[0-9A-HJKMNP-TV-Z]{26}$/;\nfunction parentRequestIdOf(value) {\n return typeof value === \"string\" && REQUEST_ID_SHAPE.test(value) ? value : void 0;\n}\nvar INVOCATION_RECORD_FIELD_MAX = 128;\nvar FIELDS = [\"operation\", \"problemCode\", \"principalKind\", \"entityTypes\", \"route\"];\nfunction capped(key, v) {\n if (v.length <= INVOCATION_RECORD_FIELD_MAX)\n return v;\n if (key !== \"entityTypes\")\n return v.slice(0, INVOCATION_RECORD_FIELD_MAX);\n const cut = v.lastIndexOf(ENTITY_TYPES_SEPARATOR, INVOCATION_RECORD_FIELD_MAX);\n return cut > 0 ? v.slice(0, cut) : null;\n}\nfunction encodeInvocationRecord(record2) {\n const params = new URLSearchParams();\n for (const key of FIELDS) {\n const v = record2[key];\n const kept = typeof v === \"string\" && v !== \"\" ? capped(key, v) : null;\n if (kept)\n params.set(key, kept);\n }\n const value = params.toString();\n return value === \"\" ? null : value;\n}\nvar ENTITY_TYPES_SEPARATOR = \",\";\nfunction joinEntityTypes(types) {\n const kept = [...new Set(types)].filter((t) => t !== \"\" && !t.includes(ENTITY_TYPES_SEPARATOR)).sort();\n return kept.length > 0 ? kept.join(ENTITY_TYPES_SEPARATOR) : null;\n}\nvar FIELD_COVERAGE_HEADER = \"x-substrat-field-coverage\";\nfunction invocationLevelOf(status, threw, problemCode) {\n if (threw || status === null || status >= 500)\n return \"error\";\n if (status >= 400)\n return \"warn\";\n if (problemCode)\n return \"warn\";\n return \"info\";\n}\n\n// packages/contracts/dist/trace-sink.js\nvar TRACE_SINK_KEY = /* @__PURE__ */ Symbol.for(\"substrat.trace-sink\");\nvar STARTS_KEY = /* @__PURE__ */ Symbol.for(\"substrat.trace-starts\");\nfunction starts() {\n const slots = globalThis;\n return slots[STARTS_KEY] ??= /* @__PURE__ */ new Map();\n}\nfunction markTraceStart(invocationId, at = performance.now()) {\n const map = starts();\n if (!map.has(invocationId))\n map.set(invocationId, at);\n}\nfunction forgetTraceStart(invocationId) {\n starts().delete(invocationId);\n}\nfunction installedTraceSink() {\n return globalThis[TRACE_SINK_KEY];\n}\n\n// packages/kernel/dist/invocation-line.js\nfunction invocationLine(f) {\n const emitted = f.emitted;\n return {\n substrat: \"invocation\",\n ...f.kind ? { kind: f.kind } : {},\n tenantId: f.tenantId,\n scopeId: f.scopeId,\n vertical: f.vertical ?? null,\n surface: f.surface ?? null,\n method: f.method ?? null,\n path: f.path ?? null,\n route: f.route ?? null,\n status: f.status ?? null,\n threw: f.threw,\n durationMs: f.durationMs,\n invocationId: f.invocationId,\n level: f.level,\n operation: f.operation ?? null,\n problemCode: f.problemCode ?? null,\n principalKind: f.principalKind ?? null,\n eventCount: emitted ? emitted.total : null,\n eventTypes: emitted ? distinct(emitted.events.map((e) => e.type)) : [],\n entities: emitted && f.withEntities !== false ? distinct(emitted.events.map((e) => e.entity)) : [],\n entityTypes: entityTypesOf(emitted),\n versionId: f.versionId ?? null,\n ...f.outputFields ? { outputFields: f.outputFields, ...f.fieldCoverageId ? { fieldCoverageId: f.fieldCoverageId } : {} } : {},\n ...f.storeCalls && f.storeCalls.length > 0 ? { storeCalls: f.storeCalls } : {},\n ...f.storeCallsDropped ? { storeCallsDropped: f.storeCallsDropped } : {},\n ...f.outboundCalls && f.outboundCalls.length > 0 ? { outboundCalls: f.outboundCalls } : {},\n ...f.outboundCallsDropped ? { outboundCallsDropped: f.outboundCallsDropped } : {},\n ...f.authChecks?.length ? { authChecks: f.authChecks } : {},\n ...f.authChecksDropped ? { authChecksDropped: f.authChecksDropped } : {},\n ...f.parentInvocationId ? { parentInvocationId: f.parentInvocationId, ...f.parentScopeId ? { parentScopeId: f.parentScopeId } : {} } : {},\n ...f.async ?? {}\n };\n}\nvar STORE_CALL_LOG_CAP = 16;\nfunction tallyStoreCall(tally, call) {\n const calls = tally.storeCalls ?? (tally.storeCalls = []);\n if (calls.length < STORE_CALL_LOG_CAP) {\n calls.push({ binding: call.binding, operation: call.operation, outcome: call.outcome, durationMs: call.durationMs });\n } else {\n tally.storeCallsDropped = (tally.storeCallsDropped ?? 0) + 1;\n }\n}\nvar OUTBOUND_CALL_LOG_CAP = 16;\nfunction tallyOutboundCall(tally, call) {\n const calls = tally.outboundCalls ?? (tally.outboundCalls = []);\n if (calls.length < OUTBOUND_CALL_LOG_CAP) {\n calls.push({\n host: call.host,\n path: call.path,\n method: call.method,\n status: call.status,\n outcome: call.outcome,\n durationMs: call.durationMs,\n atMs: call.atMs,\n ...call.reason !== void 0 ? { reason: call.reason } : {}\n });\n } else {\n tally.outboundCallsDropped = (tally.outboundCallsDropped ?? 0) + 1;\n }\n}\nvar STORE_CALL_SINK_KEY = \"substrat.store-call-sink\";\nfunction entityTypesOf(emitted) {\n return emitted ? joinEntityTypes(emitted.events.map((e) => e.entity.slice(0, Math.max(0, e.entity.indexOf(\":\"))))) : null;\n}\nfunction distinct(values) {\n return [...new Set(values)];\n}\n\n// packages/kernel/dist/ulid.js\nvar B32 = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\nvar MAX_ULID_TIME = 2 ** 48 - 1;\nvar ULID_SHAPE = /^[0-9A-HJKMNP-TV-Z]{26}$/;\nvar unencodable = (t) => `not an encodable ULID instant: ${t} (want 0..${MAX_ULID_TIME})`;\nfunction encodeTime(time) {\n let ts = \"\";\n let t = time;\n for (let i = 0; i < 10; i++) {\n ts = B32[t % 32] + ts;\n t = Math.floor(t / 32);\n }\n return ts;\n}\nfunction createUlid() {\n let lastTime = -1;\n const lastRand = new Array(16).fill(0);\n function freshRandom() {\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n for (let i = 0; i < 16; i++)\n lastRand[i] = bytes[i] % 32;\n }\n function incrementRandom() {\n for (let i = 15; i >= 0; i--) {\n if (lastRand[i] < 31) {\n lastRand[i]++;\n return true;\n }\n lastRand[i] = 0;\n }\n return false;\n }\n const mint = ((now = Date.now()) => {\n if (!Number.isSafeInteger(now))\n throw new RangeError(unencodable(now));\n let time = now;\n if (time <= lastTime) {\n time = lastTime;\n if (!incrementRandom()) {\n time = lastTime + 1;\n freshRandom();\n }\n } else {\n freshRandom();\n }\n if (time < 0 || time > MAX_ULID_TIME)\n throw new RangeError(unencodable(time));\n lastTime = time;\n let r = \"\";\n for (const d of lastRand)\n r += B32[d];\n return encodeTime(time) + r;\n });\n mint.seedFrom = (id) => {\n const time = ulidTime(id);\n if (time < lastTime)\n return;\n const rand = [];\n for (const ch of id.slice(10))\n rand.push(B32.indexOf(ch));\n if (time === lastTime) {\n let ahead = false;\n for (let i = 0; i < 16; i++) {\n if (rand[i] !== lastRand[i]) {\n ahead = rand[i] > lastRand[i];\n break;\n }\n }\n if (!ahead)\n return;\n }\n lastTime = time;\n for (let i = 0; i < 16; i++)\n lastRand[i] = rand[i];\n };\n return mint;\n}\nvar processUlid = createUlid();\nfunction ulid(now = Date.now()) {\n return processUlid(now);\n}\nfunction ulidTime(id) {\n if (!ULID_SHAPE.test(id))\n throw new Error(`not a ULID: ${id}`);\n let t = 0;\n for (const ch of id.slice(0, 10))\n t = t * 32 + B32.indexOf(ch);\n if (t > MAX_ULID_TIME)\n throw new Error(`not a ULID: ${id}`);\n return t;\n}\n\n// packages/vertical-host/src/outbound-calls.ts\nvar EGRESS_REFUSAL_HEADER = \"x-substrat-egress\";\nvar STORE_CALL_HOST = \"store.substrat.internal\";\nvar REDACTED = \"[redacted]\";\nvar PATH_MAX = 200;\nvar WORDS = /^[a-z]{1,16}(?:[-_][a-z]{1,16}){0,3}$/;\nvar WORDS_WITH_EXTENSION = /^[a-z]{1,16}(?:[-_][a-z]{1,16}){0,3}\\.[a-z][a-z0-9]{0,4}$/;\nvar SEGMENT_MAX = 40;\nvar SMALL_INTEGER = /^[0-9]{1,10}$/;\nvar VERSION = /^v[0-9]{1,3}(?:\\.[0-9]{1,3}){0,2}$/;\nfunction keepsSegment(segment) {\n if (segment.length > SEGMENT_MAX) return false;\n return segment === \"\" || WORDS.test(segment) || WORDS_WITH_EXTENSION.test(segment) || SMALL_INTEGER.test(segment) || VERSION.test(segment);\n}\nfunction redactOutboundPath(pathname) {\n const path = pathname.split(\"/\").map((segment) => keepsSegment(segment) ? segment : REDACTED).join(\"/\");\n return path.length > PATH_MAX ? `${path.slice(0, PATH_MAX)}…` : path;\n}\nvar ERROR_CLASS = /^[A-Z][A-Za-z]{0,30}(?:Error|Exception)$/;\nfunction errorName(e) {\n const name = e?.name;\n return typeof name === \"string\" && ERROR_CLASS.test(name) ? name : \"Error\";\n}\nfunction answeredOutcome(response) {\n if (response.status === 403 && response.headers?.get?.(EGRESS_REFUSAL_HEADER) === \"refused\") return \"refused\";\n return response.status >= 400 ? \"http-error\" : \"ok\";\n}\nfunction rejectedOutcome(e) {\n const name = errorName(e);\n if (name === \"TimeoutError\") return { outcome: \"timeout\" };\n if (name === \"AbortError\") return { outcome: \"aborted\" };\n return { outcome: \"failed\", reason: name };\n}\nvar SINCE = /* @__PURE__ */ Symbol.for(\"substrat.outbound-since\");\nfunction sinceMap() {\n const g = globalThis;\n return g[SINCE] ??= /* @__PURE__ */ new WeakMap();\n}\nfunction markRequestStart(recording, started) {\n try {\n sinceMap().set(recording, started);\n } catch {\n }\n}\nfunction destinationOf(input, init) {\n const fromRequest = typeof input === \"object\" && input !== null && !(input instanceof URL);\n const raw = fromRequest ? input.url : input;\n let url;\n try {\n url = new URL(String(raw));\n } catch {\n return null;\n }\n const initMethod = init?.method;\n const requestMethod = fromRequest ? input.method : void 0;\n const method = typeof initMethod === \"string\" ? initMethod : typeof requestMethod === \"string\" ? requestMethod : \"GET\";\n return { url, method: method.toUpperCase().slice(0, 16) };\n}\nfunction record(recording, destination, startedAt, end) {\n try {\n const call = {\n host: destination.url.host.toLowerCase(),\n path: redactOutboundPath(destination.url.pathname),\n method: destination.method,\n status: end.status,\n outcome: end.outcome,\n durationMs: Math.max(0, Date.now() - startedAt),\n atMs: Math.max(0, startedAt - (sinceMap().get(recording) ?? startedAt)),\n ...end.reason !== void 0 ? { reason: end.reason } : {}\n };\n tallyOutboundCall(recording, call);\n } catch {\n }\n}\nvar WRAPPED = /* @__PURE__ */ Symbol.for(\"substrat.outbound-fetch\");\nvar IN_CALL = /* @__PURE__ */ Symbol.for(\"substrat.outbound-fetch.in-call\");\nfunction recordingFetch(original, current) {\n const wrapped = function fetch(input, init) {\n const g = globalThis;\n if (g[IN_CALL] === true) return original.call(globalThis, input, init);\n let recording;\n let destination = null;\n try {\n recording = current();\n if (recording) destination = destinationOf(input, init);\n } catch {\n recording = void 0;\n }\n if (!recording || !destination || destination.url.hostname.toLowerCase() === STORE_CALL_HOST) {\n return original.call(globalThis, input, init);\n }\n const rec = recording;\n const dest = destination;\n const startedAt = Date.now();\n let pending;\n g[IN_CALL] = true;\n try {\n pending = original.call(globalThis, input, init);\n } finally {\n g[IN_CALL] = false;\n }\n return pending.then(\n (response) => {\n let status = null;\n let outcome = \"failed\";\n try {\n status = response.status;\n outcome = answeredOutcome(response);\n } catch {\n }\n record(rec, dest, startedAt, { status, outcome });\n return response;\n },\n (e) => {\n let end = { outcome: \"failed\", reason: \"Error\" };\n try {\n end = rejectedOutcome(e);\n } catch {\n }\n record(rec, dest, startedAt, { status: null, ...end });\n throw e;\n }\n );\n };\n Object.defineProperty(wrapped, WRAPPED, { value: true });\n return wrapped;\n}\nfunction installOutboundRecorder(current) {\n try {\n const g = globalThis;\n const existing = g.fetch;\n if (typeof existing !== \"function\" || existing[WRAPPED]) return;\n g.fetch = recordingFetch(existing, current);\n } catch {\n }\n}\n\n// packages/contracts/dist/wire-headers.js\nvar PLATFORM_SECRET_HEADER = \"x-substrat-platform\";\nvar CONNECTOR_ATTACHMENT_META_MAX_BYTES = 8 * 1024;\n\n// packages/contracts/dist/wire-auth.js\nvar PlatformCallError = class extends Error {\n};\nfunction secretMatches(presented, expected) {\n const given = presented ?? \"\";\n let diff = given.length ^ expected.length | (given.length === 0 ? 1 : 0);\n for (let i = 0; i < expected.length; i++) {\n diff |= (i < given.length ? given.charCodeAt(i) : 0) ^ expected.charCodeAt(i);\n }\n return diff === 0;\n}\nfunction assertPlatformCall(headers, options = {}) {\n const { expectedSecret } = options;\n if (!expectedSecret) {\n throw new PlatformCallError(\"platform calls are not configured on this deployment\");\n }\n if (!secretMatches(headers.get(PLATFORM_SECRET_HEADER), expectedSecret)) {\n throw new PlatformCallError(\"not a platform call\");\n }\n}\n\n// packages/vertical-host/src/routed-node.ts\nvar ROUTED_ID = /^[0-9A-HJKMNP-TV-Z]{26}$/;\nvar RouterAssertionError = class extends Error {\n};\nfunction readRoutedNode(headers, options = {}) {\n const rawTenant = headers.get(\"x-substrat-tenant\");\n const rawScope = headers.get(\"x-substrat-scope\");\n if (!rawTenant && !rawScope) return null;\n const { expectedSecret, allowUnsigned: allowUnsigned2 = false } = options;\n if (expectedSecret) {\n if (!secretMatches(headers.get(\"x-substrat-router\"), expectedSecret)) {\n throw new RouterAssertionError(\"router assertion is not signed by a known router\");\n }\n } else if (!allowUnsigned2) {\n throw new RouterAssertionError(\n \"router assertion cannot be verified: this worker has no ROUTER_SECRET configured\"\n );\n }\n if (!rawTenant || !rawScope) {\n throw new RouterAssertionError(\"router assertion is incomplete\");\n }\n if (!ROUTED_ID.test(rawTenant) || !ROUTED_ID.test(rawScope)) {\n throw new RouterAssertionError(\"router assertion carries a malformed id\");\n }\n return {\n tenantId: rawTenant,\n scopeId: rawScope,\n surface: headers.get(\"x-substrat-surface\") ?? \"app\",\n verticalSlug: headers.get(\"x-substrat-vertical\")\n };\n}\n\n// packages/vertical-host/src/invocation-log.ts\nfunction builtinAsyncLocalStorage() {\n try {\n const proc = globalThis.process;\n const mod = proc?.getBuiltinModule?.(\"node:async_hooks\");\n return typeof mod?.AsyncLocalStorage === \"function\" ? mod.AsyncLocalStorage : void 0;\n } catch {\n return void 0;\n }\n}\nfunction storeCallSlot(provided) {\n try {\n const g = globalThis;\n const key = Symbol.for(STORE_CALL_SINK_KEY);\n const existing = g[key];\n if (typeof existing?.run === \"function\" && typeof existing.current === \"function\") return existing;\n const Als = provided ?? builtinAsyncLocalStorage();\n if (!Als) return void 0;\n const storage = new Als();\n const slot2 = {\n current() {\n const tally = storage.getStore()?.tally;\n return tally ? (call) => tallyStoreCall(tally, call) : void 0;\n },\n invocationId: () => storage.getStore()?.invocationId,\n run: (tally, fn, invocationId) => storage.run({ tally, invocationId }, fn),\n recording: () => storage.getStore()?.tally\n };\n g[key] = slot2;\n return slot2;\n } catch {\n return void 0;\n }\n}\nfunction currentRecording() {\n const slot2 = globalThis[Symbol.for(STORE_CALL_SINK_KEY)];\n return typeof slot2?.recording === \"function\" ? slot2.recording() : void 0;\n}\nfunction runRecording(stamp, started, provided, fn) {\n const slot2 = storeCallSlot(provided);\n if (!slot2) return fn();\n try {\n const authSink = {\n record(check) {\n const record2 = currentRecording();\n if (record2) tallyAuthCheck(record2, check);\n }\n };\n globalThis[AUTH_DIAGNOSTICS_KEY] = authSink;\n } catch {\n }\n markRequestStart(stamp.record, started);\n installOutboundRecorder(currentRecording);\n return slot2.run(stamp.record, fn, stamp.invocationId);\n}\nfunction versionIdOf(env) {\n const v = env?.SUBSTRAT_VERSION_ID;\n return typeof v === \"string\" && v.length > 0 ? v : null;\n}\nvar UPLOAD_LINK_LOG_SECRET_PREFIX = \"sbup_\";\nvar UPLOAD_LINK_SECRET_SEGMENT = /^(\\/api\\/attachment-uploads\\/[^/]+\\/[^/]+\\/)[^/]+/;\nvar SECRET_SHAPED_SEGMENT = new RegExp(`(^|/)${UPLOAD_LINK_LOG_SECRET_PREFIX}[^/]*`, \"g\");\nvar redactUploadLinkSecret = (pathname) => pathname.replace(UPLOAD_LINK_SECRET_SEGMENT, \"$1[redacted]\").replace(SECRET_SHAPED_SEGMENT, \"$1[redacted]\");\nfunction pathOf(url) {\n try {\n return redactUploadLinkSecret(new URL(url).pathname);\n } catch {\n return \"/\";\n }\n}\nvar STAMPS = /* @__PURE__ */ Symbol.for(\"substrat.invocation-stamp\");\nfunction stamps() {\n const g = globalThis;\n return g[STAMPS] ??= /* @__PURE__ */ new WeakMap();\n}\nfunction invocationStampOf(request) {\n return stamps().get(request);\n}\nfunction withRequestId(response, id) {\n const headers = response?.headers;\n try {\n headers?.set?.(REQUEST_ID_HEADER, id);\n return response;\n } catch {\n }\n try {\n const r = response;\n if (r.status === 101) return response;\n const copy = new Response(r.body, r);\n copy.headers.set(REQUEST_ID_HEADER, id);\n return copy;\n } catch {\n return response;\n }\n}\nfunction beginStamp(request, env, options) {\n const stamp = { invocationId: ulid(), record: {} };\n if (installedTraceSink()) markTraceStart(stamp.invocationId);\n const fieldCoverageId = armedFieldCoverageId(request.headers, env, options);\n if (fieldCoverageId) stamp.fieldCoverageId = fieldCoverageId;\n stamps().set(request, stamp);\n return stamp;\n}\nfunction armedFieldCoverageId(headers, env, options) {\n let id;\n try {\n id = headers.get(FIELD_COVERAGE_HEADER);\n } catch {\n return void 0;\n }\n if (id === null || !ROUTED_ID.test(id)) return void 0;\n return routedNodeOrNull(headers, env, options) !== null ? id : void 0;\n}\nfunction writeInvocationLine(stamp, done, options) {\n traceRequest(stamp, done);\n try {\n writeLineOrThrow(stamp, done, options);\n } catch (e) {\n try {\n console.log(JSON.stringify({ substrat: \"invocation-log-fault\", detail: String(e) }));\n } catch {\n }\n }\n}\nfunction traceRequest(stamp, done) {\n const sink = installedTraceSink();\n if (!sink) return;\n try {\n const { record: record2, invocationId } = stamp;\n const durationMs = Date.now() - done.started;\n const line = invocationLine({\n tenantId: \"\",\n scopeId: null,\n vertical: null,\n surface: null,\n method: done.request.method,\n path: pathOf(done.request.url),\n status: done.status,\n threw: done.threw,\n durationMs,\n invocationId,\n level: invocationLevelOf(done.status, done.threw, record2.problemCode),\n operation: record2.operation,\n problemCode: record2.problemCode,\n principalKind: record2.principalKind,\n ...record2.emitted ? { emitted: record2.emitted } : {},\n versionId: versionIdOf(done.env)\n });\n sink.head(invocationId, {\n startedAt: new Date(done.started).toISOString(),\n method: line.method,\n path: line.path,\n status: line.status,\n threw: line.threw,\n durationMs: line.durationMs,\n level: line.level,\n operation: line.operation,\n problemCode: line.problemCode,\n principalKind: line.principalKind,\n eventCount: line.eventCount,\n eventTypes: line.eventTypes,\n entities: line.entities,\n versionId: line.versionId,\n // Calls the record kept no room for: a truncated list must read as truncated.\n ...record2.outboundCallsDropped ? { stepsDropped: record2.outboundCallsDropped } : {}\n });\n for (const call of record2.outboundCalls ?? []) {\n sink.step(invocationId, {\n type: \"outbound\",\n atMs: call.atMs,\n durationMs: call.durationMs,\n host: call.host,\n path: call.path,\n method: call.method,\n status: call.status,\n outcome: call.outcome,\n ...call.reason ? { reason: call.reason } : {}\n });\n }\n for (const check of record2.authChecks ?? []) {\n sink.step(invocationId, { type: \"log\", atMs: durationMs, level: check.outcome === \"refused\" || check.outcome === \"error\" ? \"warn\" : \"info\", message: `auth ${check.stage}: ${check.outcome} (${check.reason})` });\n }\n if (record2.authChecksDropped) sink.head(invocationId, { stepsDropped: record2.authChecksDropped });\n sink.end(invocationId);\n forgetTraceStart(invocationId);\n } catch {\n }\n}\nfunction writeLineOrThrow(stamp, done, options) {\n const node = routedNodeOrNull(done.request.headers, done.env, options);\n if (!node) return;\n const { record: record2, invocationId } = stamp;\n const line = invocationLine({\n tenantId: node.tenantId,\n scopeId: node.scopeId,\n vertical: node.verticalSlug,\n surface: node.surface,\n method: done.request.method,\n path: pathOf(done.request.url),\n route: record2.route ?? null,\n status: done.status,\n threw: done.threw,\n durationMs: Date.now() - done.started,\n invocationId,\n level: invocationLevelOf(done.status, done.threw, record2.problemCode),\n operation: record2.operation,\n problemCode: record2.problemCode,\n principalKind: record2.principalKind,\n ...record2.emitted ? { emitted: record2.emitted } : {},\n versionId: versionIdOf(done.env),\n ...record2.outputFields ? { outputFields: record2.outputFields } : {},\n ...stamp.fieldCoverageId ? { fieldCoverageId: stamp.fieldCoverageId } : {},\n ...record2.storeCalls && record2.storeCalls.length > 0 ? { storeCalls: record2.storeCalls } : {},\n ...record2.storeCallsDropped ? { storeCallsDropped: record2.storeCallsDropped } : {},\n ...record2.outboundCalls && record2.outboundCalls.length > 0 ? { outboundCalls: record2.outboundCalls } : {},\n ...record2.outboundCallsDropped ? { outboundCallsDropped: record2.outboundCallsDropped } : {},\n ...record2.authChecks?.length ? { authChecks: record2.authChecks } : {},\n ...record2.authChecksDropped ? { authChecksDropped: record2.authChecksDropped } : {}\n });\n console.log(JSON.stringify(line));\n}\nvar OTHER_HANDLERS = [\"scheduled\", \"queue\", \"email\", \"tail\", \"trace\", \"alarm\", \"test\"];\nfunction withInvocationLog(worker, options = {}) {\n const inner = worker?.fetch;\n if (typeof inner !== \"function\") return worker;\n const wrapped = {\n async fetch(request, env, ctx) {\n if (invocationStampOf(request)) return inner.call(worker, request, env, ctx);\n const started = Date.now();\n const stamp = beginStamp(request, env, options);\n let status = null;\n let threw = false;\n try {\n const response = withRequestId(\n await runRecording(stamp, started, options.asyncLocalStorage, () => inner.call(worker, request, env, ctx)),\n stamp.invocationId\n );\n status = response.status;\n handRecordToRouter(response, stamp.record, request, env, options);\n return response;\n } catch (e) {\n threw = true;\n throw e;\n } finally {\n writeInvocationLine(stamp, { request, env, status, threw, started }, options);\n }\n }\n };\n for (const name of OTHER_HANDLERS) {\n const h = worker[name];\n if (typeof h === \"function\") wrapped[name] = h.bind(worker);\n }\n return wrapped;\n}\nfunction handRecordToRouter(response, record2, request, env, options) {\n try {\n const value = encodeInvocationRecord({ ...record2, entityTypes: entityTypesOf(record2.emitted) });\n if (value === null || !routedNodeOrNull(request.headers, env, options)) return;\n response.headers?.set?.(\n INVOCATION_RECORD_HEADER,\n value\n );\n } catch {\n }\n}\nfunction routedNodeOrNull(headers, env, options) {\n try {\n return readRoutedNode(headers, {\n expectedSecret: options.routerSecret?.(env),\n allowUnsigned: options.allowUnsigned?.(env) ?? false\n });\n } catch (e) {\n if (!(e instanceof RouterAssertionError)) console.log(JSON.stringify({ substrat: \"invocation-log-fault\", detail: String(e) }));\n return null;\n }\n}\n\n// packages/contracts/dist/scope-store-envelope.js\nfunction bytesToBase64Url(bytes) {\n let binary = \"\";\n for (const byte of bytes)\n binary += String.fromCharCode(byte);\n return btoa(binary).replaceAll(\"+\", \"-\").replaceAll(\"/\", \"_\").replaceAll(\"=\", \"\");\n}\nfunction base64UrlToBytes(value) {\n if (!/^[A-Za-z0-9_-]+$/.test(value))\n return void 0;\n const padded = value.replaceAll(\"-\", \"+\").replaceAll(\"_\", \"/\") + \"=\".repeat((4 - value.length % 4) % 4);\n try {\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++)\n bytes[i] = binary.charCodeAt(i);\n return bytes;\n } catch {\n return void 0;\n }\n}\nfunction copyBytes(bytes) {\n const copy = new ArrayBuffer(bytes.byteLength);\n new Uint8Array(copy).set(bytes);\n return copy;\n}\nvar SMALL_ORDER_KEYS = [\n \"0000000000000000000000000000000000000000000000000000000000000000\",\n // y = 0, order 4\n \"0100000000000000000000000000000000000000000000000000000000000000\",\n // the identity, order 1\n \"26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05\",\n // order 8\n \"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a\",\n // order 8\n \"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f\",\n // y = p - 1, order 2\n \"edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f\",\n // y = p, a non-canonical 0\n \"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f\"\n // y = p + 1, a non-canonical identity\n];\nfunction isSmallOrderKey(bytes) {\n const unsigned = Uint8Array.from(bytes);\n unsigned[31] = unsigned[31] & 127;\n const hex = [...unsigned].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n return SMALL_ORDER_KEYS.includes(hex);\n}\nasync function scopeStoreKeyId(publicKey) {\n const bytes = base64UrlToBytes(publicKey);\n if (!bytes || bytes.length !== 32)\n throw new Error(\"a store public key is not 32 bytes of base64url\");\n if (isSmallOrderKey(bytes))\n throw new Error(\"a store public key is a small-order point\");\n const hash = await crypto.subtle.digest(\"SHA-256\", copyBytes(bytes));\n return bytesToBase64Url(new Uint8Array(hash).slice(0, 8));\n}\nfunction scopeStorePublicKeyProblem(key) {\n if (key.length === 0)\n return \"empty\";\n const bytes = base64UrlToBytes(key);\n if (bytes?.length !== 32)\n return \"length\";\n if (isSmallOrderKey(bytes))\n return \"small-order\";\n if (bytesToBase64Url(bytes) !== key)\n return \"non-canonical\";\n return void 0;\n}\nvar PUBLIC_KEY_PROBLEM = {\n empty: \"a store public key list is empty or has an empty entry\",\n length: \"a store public key is not 32 bytes of base64url\",\n \"small-order\": \"a store public key is a small-order point\",\n \"non-canonical\": \"a store public key is not canonical base64url\"\n};\nfunction parseScopeStorePublicKeys(text) {\n const keys = text.split(\",\").map((key) => key.trim());\n if (keys.some((key) => key.length === 0))\n throw new Error(PUBLIC_KEY_PROBLEM.empty);\n const seen = /* @__PURE__ */ new Set();\n for (const key of keys) {\n const problem2 = scopeStorePublicKeyProblem(key);\n if (problem2)\n throw new Error(PUBLIC_KEY_PROBLEM[problem2]);\n if (seen.has(key))\n throw new Error(\"a store public key is listed twice\");\n seen.add(key);\n }\n return keys;\n}\nasync function verifyScopeStoreEnvelope(publicKey, canonical, signature) {\n const keyBytes = base64UrlToBytes(publicKey);\n const sigBytes = base64UrlToBytes(signature);\n if (!keyBytes || !sigBytes || sigBytes.length !== 64)\n return false;\n try {\n const key = await crypto.subtle.importKey(\"raw\", copyBytes(keyBytes), { name: \"Ed25519\" }, false, [\"verify\"]);\n return await crypto.subtle.verify(\"Ed25519\", key, copyBytes(sigBytes), new TextEncoder().encode(canonical));\n } catch {\n return false;\n }\n}\n\n// packages/contracts/dist/wire-signing.js\nvar WIRE_ENVELOPE_VERSION = 1;\nvar WIRE_ENVELOPE_TAG = `substrat-wire/${WIRE_ENVELOPE_VERSION}`;\nvar WIRE_ENVELOPE_HEADER = \"x-substrat-wire-envelope\";\nvar WIRE_SIGNATURE_HEADER = \"x-substrat-wire-signature\";\nvar WIRE_ENVELOPE_MAX_LIFETIME_SECONDS = 120;\nvar WIRE_CLOCK_SKEW_SECONDS = 30;\nvar SHAPES = {\n router: {\n // An end user's request the router resolved: always a tenant, a scope and a surface. The\n // body is a stream the router does not buffer, so a route binds none.\n route: { scope: \"required\", surface: \"required\", vertical: \"optional\", body: \"absent\" },\n // An internal call the router makes itself, such as a peer invocation.\n call: { scope: \"optional\", surface: \"absent\", vertical: \"optional\", body: \"required\" }\n },\n platform: {\n call: { scope: \"optional\", surface: \"absent\", vertical: \"absent\", body: \"required\" }\n }\n};\nfunction wireEnvelopeShape(iss, kind) {\n const byKind = Object.hasOwn(SHAPES, iss) ? SHAPES[iss] : void 0;\n return byKind && Object.hasOwn(byKind, kind) ? byKind[kind] : void 0;\n}\nvar LINES = 15;\nvar ULID = /^[0-9A-HJKMNP-TV-Z]{26}$/;\nvar METHOD = /^[!#$%&'*+.^_`|~0-9A-Z-]{1,32}$/;\nvar DIGEST = /^[0-9a-f]{64}$/;\nvar SECONDS = /^(0|[1-9]\\d{0,14})$/;\nvar CONTROL = /[\\u0000-\\u001f\\u007f]/;\nvar LONE_SURROGATE = /[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/;\nvar MAX_TEXT = 255;\nvar isText = (value) => value.length <= MAX_TEXT && !CONTROL.test(value) && !LONE_SURROGATE.test(value);\nvar isWireAudience = (value) => value !== \"\" && isText(value);\nfunction isCanonicalBase64Url(text, length) {\n const bytes = base64UrlToBytes(text);\n return bytes?.length === length && bytesToBase64Url(bytes) === text;\n}\nfunction admits(presence, value, valid) {\n if (value === \"\")\n return presence !== \"required\";\n return presence !== \"absent\" && valid(value);\n}\nfunction parseWireEnvelope(canonical) {\n const lines = canonical.split(\"\\n\");\n if (lines.length !== LINES)\n return void 0;\n const [tag, iss, kind, kid, aud, tenantId, scopeId, surface, vertical2, method, targetDigest, bodyDigest, iat, exp, nonce] = lines;\n if (tag !== WIRE_ENVELOPE_TAG)\n return void 0;\n const shape = wireEnvelopeShape(iss, kind);\n if (!shape)\n return void 0;\n if (!isCanonicalBase64Url(kid, 8) || !isCanonicalBase64Url(nonce, 16))\n return void 0;\n if (!isWireAudience(aud))\n return void 0;\n if (tenantId === \"\" !== (scopeId === \"\"))\n return void 0;\n if (!admits(shape.scope, tenantId, (id) => ULID.test(id)) || !admits(shape.scope, scopeId, (id) => ULID.test(id)))\n return void 0;\n if (!admits(shape.surface, surface, isText) || !admits(shape.vertical, vertical2, isText))\n return void 0;\n if (!METHOD.test(method) || !DIGEST.test(targetDigest) || !admits(shape.body, bodyDigest, (d) => DIGEST.test(d)))\n return void 0;\n if (!SECONDS.test(iat) || !SECONDS.test(exp))\n return void 0;\n const orNull = (value) => value === \"\" ? null : value;\n return {\n v: 1,\n iss,\n kind,\n kid,\n aud,\n tenantId: orNull(tenantId),\n scopeId: orNull(scopeId),\n surface: orNull(surface),\n vertical: orNull(vertical2),\n method,\n targetDigest,\n bodyDigest: orNull(bodyDigest),\n iat: Number(iat),\n exp: Number(exp),\n nonce\n };\n}\nfunction decodeWireEnvelope(header) {\n const bytes = base64UrlToBytes(header);\n if (!bytes)\n return void 0;\n let canonical;\n try {\n canonical = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true }).decode(bytes);\n } catch {\n return void 0;\n }\n const claims = parseWireEnvelope(canonical);\n return claims ? { canonical, claims } : void 0;\n}\nfunction bytesOf(data) {\n if (typeof data === \"string\")\n return new TextEncoder().encode(data);\n const view = ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);\n return view.buffer instanceof ArrayBuffer ? view : view.slice();\n}\nasync function wireDigest(data) {\n const hash = await crypto.subtle.digest(\"SHA-256\", bytesOf(data));\n return [...new Uint8Array(hash)].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\nfunction wireRequestBinding(request) {\n const url = new URL(request.url);\n return { method: request.method.toUpperCase(), target: `${url.pathname}${url.search}` };\n}\nfunction lifetimeAdmitted(iat, exp) {\n return exp > iat && exp - iat <= WIRE_ENVELOPE_MAX_LIFETIME_SECONDS;\n}\nvar PREPARED = /* @__PURE__ */ new WeakMap();\nasync function wireVerificationKeys(config) {\n const listed = (text) => text?.trim() ? parseScopeStorePublicKeys(text) : [];\n const router = listed(config.router);\n const platform = listed(config.platform);\n if (router.some((key) => platform.includes(key)))\n throw new Error(\"a wire public key is listed for both issuers\");\n const byKid = async (keys2) => new Map(await Promise.all(keys2.map(async (key) => [await scopeStoreKeyId(key), key])));\n const own = Object.freeze({ router: await byKid(router), platform: await byKid(platform) });\n const keys = Object.freeze({ router: new Map(own.router), platform: new Map(own.platform) });\n PREPARED.set(keys, own);\n return keys;\n}\nvar WIRE_REFUSED = Object.freeze({ ok: false });\nasync function verifyWireEnvelope(input) {\n const refuse = (reason) => {\n try {\n void Promise.resolve(input.onRefusal?.(reason)).catch(() => {\n });\n } catch {\n }\n return WIRE_REFUSED;\n };\n const { expected, keys, nowSeconds } = input;\n const own = PREPARED.get(keys);\n if (!own)\n throw new TypeError(\"verifyWireEnvelope takes the key set wireVerificationKeys built\");\n const replay = expected.kind === \"call\" ? expected.replay : \"none\";\n if (replay !== \"none\" && typeof replay?.claim !== \"function\") {\n throw new TypeError(\"a call's expectation states its replay policy: 'none' or a nonce cache\");\n }\n if (replay !== \"none\" && expected.scopeId == null) {\n throw new TypeError(\"a call that keeps nonces pins the scope whose object holds them\");\n }\n if (replay !== \"none\" && typeof input.clockSeconds !== \"function\") {\n throw new TypeError(\"a call that keeps nonces takes a clockSeconds function to time its claim\");\n }\n const header = input.headers.get(WIRE_ENVELOPE_HEADER);\n const signature = input.headers.get(WIRE_SIGNATURE_HEADER);\n if (!header || !signature)\n return refuse(\"missing\");\n const decoded = decodeWireEnvelope(header);\n if (!decoded)\n return refuse(\"malformed\");\n const { canonical, claims } = decoded;\n if (claims.iss !== expected.iss)\n return refuse(\"issuer\");\n if (claims.kind !== expected.kind)\n return refuse(\"kind\");\n const publicKey = own[claims.iss].get(claims.kid);\n if (!publicKey)\n return refuse(\"kid\");\n if (!await verifyScopeStoreEnvelope(publicKey, canonical, signature))\n return refuse(\"signature\");\n if (claims.aud !== expected.aud)\n return refuse(\"audience\");\n if (!lifetimeAdmitted(claims.iat, claims.exp))\n return refuse(\"lifetime\");\n if (!(nowSeconds >= claims.iat - WIRE_CLOCK_SKEW_SECONDS))\n return refuse(\"not-yet-valid\");\n if (!(nowSeconds <= claims.exp + WIRE_CLOCK_SKEW_SECONDS))\n return refuse(\"expired\");\n if (claims.method !== expected.method)\n return refuse(\"method\");\n if (claims.targetDigest !== await wireDigest(expected.target))\n return refuse(\"target\");\n if (expected.kind === \"call\") {\n const body = typeof expected.body === \"function\" ? await expected.body() : expected.body;\n if (body == null || claims.bodyDigest !== await wireDigest(body))\n return refuse(\"body\");\n }\n if (expected.tenantId !== void 0 && claims.tenantId !== expected.tenantId)\n return refuse(\"scope\");\n if (expected.scopeId !== void 0 && claims.scopeId !== expected.scopeId)\n return refuse(\"scope\");\n if (replay !== \"none\") {\n const until = claims.exp + WIRE_CLOCK_SKEW_SECONDS;\n const claimedAt = input.clockSeconds();\n if (!Number.isFinite(claimedAt) || claimedAt < 0) {\n throw new TypeError(\"clockSeconds read something other than a time in seconds\");\n }\n if (!(claimedAt <= until))\n return refuse(\"claim-after-until\");\n if (await replay.claim(claims.nonce, until) !== true)\n return refuse(\"replay\");\n }\n return { ok: true, claims };\n}\nvar WIRE_ROUTER_KEYS_VAR = \"SUBSTRAT_WIRE_ROUTER_KEYS\";\nvar WIRE_PLATFORM_KEYS_VAR = \"SUBSTRAT_WIRE_PLATFORM_KEYS\";\nvar WIRE_AUDIENCE_VAR = \"SUBSTRAT_WIRE_AUDIENCE\";\nvar WIRE_STATE_VAR = \"SUBSTRAT_WIRE_STATE\";\nfunction wireStateOf(config) {\n const listed = (value) => typeof value === \"string\" && value.trim() !== \"\";\n if (!listed(config.routerKeys) && !listed(config.platformKeys))\n return \"legacy-only\";\n return config.state === \"legacy-only\" || config.state === \"signed-only\" ? config.state : \"dual\";\n}\nvar ROUTE_ENDPOINT = Object.freeze({ kind: \"route\", issuers: Object.freeze([\"router\"]) });\nvar CALL_ENDPOINT = Object.freeze({ kind: \"call\", issuers: Object.freeze([\"platform\"]) });\nvar PEER_INVOKE_ENDPOINT = Object.freeze({ kind: \"call\", issuers: Object.freeze([\"platform\", \"router\"]) });\nvar ROUTER_CALLS = Object.freeze([\"/internal/vertical-invoke\", \"/internal/connector-call\", \"/internal/connector-probe\"]);\nfunction tryDecode(path, decoder) {\n try {\n return decoder(path);\n } catch {\n return path.replace(/(?:%[0-9A-Fa-f]{2})+/g, (run) => {\n try {\n return decoder(run);\n } catch {\n return run;\n }\n });\n }\n}\nfunction collapsed(path) {\n const segments = [];\n for (const segment of path.split(\"/\")) {\n if (segment === \"..\")\n segments.pop();\n else if (segment !== \"\" && segment !== \".\")\n segments.push(segment);\n }\n return `/${segments.join(\"/\")}`;\n}\nvar INTERNAL = /^\\/internal(?:\\/|$)/i;\nfunction wireEndpoint(pathname) {\n const routed = tryDecode(pathname.includes(\"%25\") ? pathname.replace(/%25/g, \"%2525\") : pathname, decodeURI);\n const forms = [pathname, routed, tryDecode(pathname, decodeURIComponent)];\n if (!forms.some((form) => INTERNAL.test(form) || INTERNAL.test(collapsed(form))))\n return ROUTE_ENDPOINT;\n return ROUTER_CALLS.includes(pathname) ? PEER_INVOKE_ENDPOINT : CALL_ENDPOINT;\n}\nvar WIRE_REPLAY_FREE_CALLS = Object.freeze([\n \"/internal/query\",\n \"/internal/redrain-count\",\n \"/internal/lifecycle-flow\",\n \"/internal/operation-series\",\n \"/internal/connector-grants\",\n // A connector probe (vertical-connectors.md §6.3) reads no scope and writes nothing in the\n // vertical: any state it changes comes back in the response for the platform to apply. It names\n // no scope either, so there is no store a nonce could be kept in.\n \"/internal/connector-probe\"\n]);\nvar SAFE_METHODS = /* @__PURE__ */ new Set([\"GET\", \"HEAD\", \"OPTIONS\"]);\nfunction wireReplayOf(method, pathname) {\n return SAFE_METHODS.has(method.toUpperCase()) || WIRE_REPLAY_FREE_CALLS.includes(pathname) ? \"none\" : \"nonce\";\n}\nvar UNSIGNED = Object.freeze([\n [FIELD_COVERAGE_HEADER, \"route\", (value) => ULID.test(value)],\n [PARENT_REQUEST_ID_HEADER, \"call\", (value) => parentRequestIdOf(value) !== void 0]\n]);\nvar WIRE_UNSIGNED_HEADERS = Object.freeze(Object.fromEntries(UNSIGNED.map(([name, kind]) => [name, kind])));\nfunction wireUnsignedHeadersOf(kind, headers) {\n const kept = [];\n for (const [name, on, valid] of UNSIGNED) {\n const value = on === kind ? headers.get(name) : null;\n if (value !== null && valid(value))\n kept.push([name, value]);\n }\n return kept;\n}\n\n// packages/control-plane-api/platform-entry/wire-gate.ts\nvar WIRE_CLAIM_TIMEOUT_MS = 1e3;\nvar WIRE_REFUSAL_BODY = '{\"type\":\"about:blank\",\"title\":\"Unauthorized\",\"status\":401,\"detail\":\"unauthorized\",\"error\":\"unauthorized\"}';\nvar WIRE_FAULT_BODY = '{\"type\":\"about:blank\",\"title\":\"Service unavailable\",\"status\":503,\"detail\":\"this request could not be verified\",\"error\":\"this request could not be verified\"}';\nvar problem = (body, status) => new Response(body, { status, headers: { \"content-type\": \"application/problem+json\" } });\nvar ASSERTED_PREFIX = \"x-substrat-\";\nvar TENANT = \"x-substrat-tenant\";\nvar SCOPE = \"x-substrat-scope\";\nvar SURFACE = \"x-substrat-surface\";\nvar VERTICAL = \"x-substrat-vertical\";\nvar ROUTER_CREDENTIAL = \"x-substrat-router\";\nvar GateFault = class extends Error {\n constructor(fault) {\n super(fault);\n this.fault = fault;\n }\n fault;\n};\nfunction claimedThrough(reach, timeoutMs) {\n return {\n async claim(nonce, until) {\n let cache;\n try {\n cache = reach();\n } catch {\n throw new GateFault(\"nonce-store\");\n }\n if (cache == null) throw new GateFault(\"no-nonce-store\");\n let timer;\n try {\n return await Promise.race([\n cache.claim(nonce, until),\n new Promise((_, reject) => {\n timer = setTimeout(() => reject(new Error(\"the nonce store did not answer in time\")), timeoutMs);\n })\n ]);\n } catch {\n throw new GateFault(\"nonce-store\");\n } finally {\n clearTimeout(timer);\n }\n }\n };\n}\nvar builtKeys;\nfunction keysFor(router, platform) {\n const text = `${router}\n${platform}`;\n if (builtKeys?.text !== text) builtKeys = { text, keys: wireVerificationKeys({ router, platform }) };\n return builtKeys.keys;\n}\nvar textOf = (value) => typeof value === \"string\" ? value : \"\";\nfunction stateOf(env) {\n const e = env ?? {};\n return wireStateOf({ routerKeys: e[WIRE_ROUTER_KEYS_VAR], platformKeys: e[WIRE_PLATFORM_KEYS_VAR], state: e[WIRE_STATE_VAR] });\n}\nfunction legacyOf(request, call, env, options) {\n if (call) {\n let credential = request.headers.has(PLATFORM_SECRET_HEADER) ? \"invalid\" : \"absent\";\n try {\n assertPlatformCall(request.headers, { expectedSecret: options.platformSecret(env) });\n credential = \"valid\";\n } catch {\n }\n return { credential, identity: null };\n }\n try {\n const node = options.routedNode(request.headers, env);\n return node ? { credential: \"valid\", identity: node } : { credential: \"absent\", identity: null };\n } catch {\n return { credential: \"invalid\", identity: null };\n }\n}\nvar sameIdentity = (legacy, claims) => legacy.tenantId === claims.tenantId && legacy.scopeId === claims.scopeId && legacy.surface === claims.surface && legacy.verticalSlug === claims.vertical;\nvar servedAs = (who) => ({\n tenantId: who?.tenantId ?? null,\n scopeId: who?.scopeId ?? null\n});\nfunction withWireGate(worker, options) {\n const inner = worker?.fetch;\n if (typeof inner !== \"function\") return worker;\n const clock = options.clock ?? Date.now;\n const seconds = () => Math.floor(clock() / 1e3);\n const timeoutMs = options.claimTimeoutMs ?? WIRE_CLAIM_TIMEOUT_MS;\n const log = options.log ?? ((line) => console.log(line));\n const serve = (request, env, ctx) => inner.call(worker, request, env, ctx);\n async function judged(request, env, ctx, state) {\n const nowSeconds = seconds();\n const e = env;\n const aud = textOf(e[WIRE_AUDIENCE_VAR]);\n const { method, target } = wireRequestBinding(request);\n const pathname = new URL(request.url).pathname;\n const endpoint = wireEndpoint(pathname);\n const call = endpoint.kind === \"call\";\n const claimed = call ? decodeWireEnvelope(request.headers.get(WIRE_ENVELOPE_HEADER) ?? \"\")?.claims : void 0;\n const issuer = claimed && endpoint.issuers.includes(claimed.iss) ? claimed.iss : endpoint.issuers[0];\n let claims;\n const line = (outcome, legacy2, extra = {}) => {\n const v = e[\"SUBSTRAT_VERSION_ID\"];\n const record2 = {\n substrat: \"wire\",\n state,\n issuer: claims?.iss ?? (endpoint.issuers.length === 1 ? endpoint.issuers[0] : null),\n kind: endpoint.kind,\n outcome,\n reason: null,\n fault: null,\n legacy: legacy2,\n tenantId: null,\n scopeId: null,\n versionId: typeof v === \"string\" && v.length > 0 ? v : null,\n ...extra\n };\n try {\n log(JSON.stringify(record2));\n } catch {\n }\n };\n const faulted = (fault, legacy2) => {\n line(\"fault\", legacy2, { fault });\n return problem(WIRE_FAULT_BODY, 503);\n };\n const legacy = legacyOf(request, call, env, options);\n let downstream = request;\n const staticFault = (fault, extra = {}) => {\n if (state !== \"dual\") return faulted(fault, legacy.credential);\n line(\"fault\", legacy.credential, { fault, ...extra });\n return serve(downstream, env, ctx);\n };\n let keys;\n try {\n keys = await keysFor(textOf(e[WIRE_ROUTER_KEYS_VAR]), textOf(e[WIRE_PLATFORM_KEYS_VAR]));\n } catch {\n return staticFault(\"keys\", servedAs(legacy.identity));\n }\n const readBody = async () => {\n if (request.body === null) return \"\";\n let body;\n try {\n body = await request.arrayBuffer();\n } catch {\n return null;\n }\n downstream = new Request(request, { body });\n return body;\n };\n const keepsNonces = call && wireReplayOf(method, pathname) === \"nonce\";\n const pinned = keepsNonces ? claimed?.scopeId ?? void 0 : void 0;\n const reach = options.nonceCache;\n const store = pinned !== void 0 && reach ? claimedThrough(() => reach(env, pinned, claimed?.tenantId ?? null), timeoutMs) : void 0;\n const expected = call ? {\n iss: issuer,\n kind: \"call\",\n aud,\n method,\n target,\n body: readBody,\n replay: store ?? \"none\",\n scopeId: store ? pinned : void 0\n } : { iss: \"router\", kind: \"route\", aud, method, target };\n let reason = null;\n let noStore = !store;\n try {\n const verdict = await verifyWireEnvelope({\n headers: request.headers,\n keys,\n expected,\n nowSeconds,\n clockSeconds: seconds,\n onRefusal: (why) => {\n reason = why;\n }\n });\n if (verdict.ok) claims = verdict.claims;\n } catch (error) {\n if (error instanceof GateFault && error.fault === \"no-nonce-store\") {\n claims = claimed;\n noStore = true;\n } else {\n return faulted(error instanceof GateFault ? error.fault : \"verifier\", legacy.credential);\n }\n }\n if (claims && keepsNonces && noStore) {\n return staticFault(claims.scopeId == null ? \"unscoped\" : \"no-nonce-store\", servedAs(legacy.identity ?? claims));\n }\n const mismatched = !!claims && !!legacy.identity && !sameIdentity(legacy.identity, claims);\n if (state === \"dual\" && legacy.credential === \"valid\") {\n const outcome = !claims ? \"legacy-accepted\" : mismatched ? \"dual-mismatch\" : \"signed-accepted\";\n line(outcome, \"valid\", { reason, ...servedAs(legacy.identity ?? claims ?? null) });\n return serve(downstream, env, ctx);\n }\n if (!claims) {\n line(\"refused\", legacy.credential, { reason });\n if (state === \"dual\") return serve(downstream, env, ctx);\n return problem(WIRE_REFUSAL_BODY, 401);\n }\n let proven;\n try {\n proven = provenAs(downstream, claims, env, options);\n } catch {\n return faulted(\"verifier\", legacy.credential);\n }\n line(mismatched ? \"dual-mismatch\" : \"signed-accepted\", legacy.credential, servedAs(claims));\n return serve(proven, env, ctx);\n }\n return {\n ...worker,\n fetch(request, env, ctx) {\n const state = stateOf(env);\n if (state === \"legacy-only\") return serve(request, env, ctx);\n return judged(request, env, ctx, state);\n }\n };\n}\nfunction provenAs(request, claims, env, options) {\n const headers = new Headers(request.headers);\n for (const name of [...headers.keys()]) if (name.startsWith(ASSERTED_PREFIX)) headers.delete(name);\n for (const [name, value] of wireUnsignedHeadersOf(claims.kind, request.headers)) headers.set(name, value);\n const put = (name, value) => {\n if (value) headers.set(name, value);\n };\n if (claims.kind === \"route\") {\n put(TENANT, claims.tenantId);\n put(SCOPE, claims.scopeId);\n put(SURFACE, claims.surface);\n put(VERTICAL, claims.vertical);\n put(ROUTER_CREDENTIAL, options.routerSecret(env));\n } else {\n put(VERTICAL, claims.vertical);\n put(PLATFORM_SECRET_HEADER, options.platformSecret(env));\n }\n return new Request(request, { headers });\n}\n\n// packages/control-plane-api/platform-entry/nonce-store.ts\nfunction nonceStoreOf(registered) {\n return (env, scopeId, tenantId) => {\n const hostFor = registered();\n if (!hostFor) return void 0;\n const store = hostFor(env);\n if (typeof store?.claimWireNonceLocal !== \"function\" || tenantId === null) return void 0;\n const claim = store.claimWireNonceLocal.bind(store);\n return { claim: (nonce, until) => claim(tenantId, scopeId, nonce, until) };\n };\n}\n\n// packages/vertical-host/src/scope-sweep-host.ts\nvar SCOPE_SWEEP_HOST_KEY = \"substrat.scope-sweep-host\";\nfunction slot() {\n return globalThis;\n}\nfunction registeredScopeSweepHost() {\n return slot()[Symbol.for(SCOPE_SWEEP_HOST_KEY)];\n}\n\n// substrat-platform-entry.ts\nimport * as vertical from \"__SUBSTRAT_VERTICAL_ENTRY__\";\nexport * from \"__SUBSTRAT_VERTICAL_ENTRY__\";\nvar routerSecret = (env) => env.ROUTER_SECRET;\nvar allowUnsigned = (env) => env.ALLOW_DEV_NODE === \"true\";\nvar substrat_platform_entry_default = withWireGate(\n withInvocationLog(vertical.default, { routerSecret, allowUnsigned, asyncLocalStorage: AsyncLocalStorage }),\n {\n routedNode: (headers, env) => readRoutedNode(headers, { expectedSecret: routerSecret(env), allowUnsigned: allowUnsigned(env) }),\n routerSecret,\n platformSecret: (env) => env.PLATFORM_SECRET,\n nonceCache: nonceStoreOf(registeredScopeSweepHost)\n }\n);\nexport {\n substrat_platform_entry_default as default\n};\n";
14
14
  /**
15
15
  * #2245: the entry's `node:async_hooks` import, as it appears in `PLATFORM_ENTRY_SOURCE`, and
16
16
  * the line the uploader puts in its place for a script that can have no `AsyncLocalStorage`
@@ -1 +1 @@
1
- {"version":3,"file":"platform-entry.generated.js","sourceRoot":"","sources":["../src/platform-entry.generated.ts"],"names":[],"mappings":"AAAA,sOAAsO;AACtO,sEAAsE;AAEtE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,4BAA4B,CAAC;AAElE,gFAAgF;AAChF,MAAM,CAAC,MAAM,0BAA0B,GAAG,6BAA6B,CAAC;AAExE,6CAA6C;AAC7C,MAAM,CAAC,MAAM,qBAAqB,GAAW,67lDAA67lD,CAAC;AAE3+lD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,yDAAyD,CAAC;AACnG,MAAM,CAAC,MAAM,2BAA2B,GAAG,sCAAsC,CAAC;AAElF;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,8BAA8B,CAAC;AAEtE,+CAA+C;AAC/C,MAAM,CAAC,MAAM,uBAAuB,GAAW,8pWAA8pW,CAAC"}
1
+ {"version":3,"file":"platform-entry.generated.js","sourceRoot":"","sources":["../src/platform-entry.generated.ts"],"names":[],"mappings":"AAAA,sOAAsO;AACtO,sEAAsE;AAEtE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,4BAA4B,CAAC;AAElE,gFAAgF;AAChF,MAAM,CAAC,MAAM,0BAA0B,GAAG,6BAA6B,CAAC;AAExE,6CAA6C;AAC7C,MAAM,CAAC,MAAM,qBAAqB,GAAW,y1nDAAy1nD,CAAC;AAEv4nD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,yDAAyD,CAAC;AACnG,MAAM,CAAC,MAAM,2BAA2B,GAAG,sCAAsC,CAAC;AAElF;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,8BAA8B,CAAC;AAEtE,+CAA+C;AAC/C,MAAM,CAAC,MAAM,uBAAuB,GAAW,8pWAA8pW,CAAC"}
@@ -1,7 +1,8 @@
1
1
  /**
2
- * The query a tenant-grain request read takes (#1746): the tenant, the window, and the
3
- * facet filters. Shared by the three request routes so they refuse and accept the same
4
- * things, and so the count, the histogram and the list beside it always describe one set.
2
+ * The query a tenant-grain request read takes (#1746): the tenant, the window, the facet
3
+ * filters and the free-text search (#2762). Shared by the three request routes so they refuse
4
+ * and accept the same things, and so the count, the histogram and the list beside it always
5
+ * describe one set — which is why the two counting routes refuse `search` outright.
5
6
  */
6
7
  import { z } from '@substrat-run/contracts';
7
8
  import { type TenantRequestScope } from './observability.js';
@@ -44,6 +45,14 @@ export interface QueryReader {
44
45
  * session's own, or staff's explicit one) and is never read from the query here.
45
46
  */
46
47
  export declare function parseTenantRequestQuery(tenantId: string, q: QueryReader, now?: number): TenantRequestScope;
48
+ /**
49
+ * #2762: refuse `search` on a read that counts. The histogram and the facets are computed from
50
+ * cubes (#1877) holding neither a request id nor a path, so no count could honour the text the
51
+ * list is filtered by. A refusal, never an ignored filter: counts of every request would read
52
+ * as counts of the matching ones. A semantic refusal, so no `errors` list: one would make it
53
+ * read as a parse failure, which answers neither the `reason` nor this sentence.
54
+ */
55
+ export declare function refuseSearchOnCounts(scope: TenantRequestScope): void;
47
56
  /**
48
57
  * The tenant, the narrowing within it and the resolved window — the half every tenant-grain
49
58
  * aggregate read shares (#1746 requests, #1747 log patterns).
@@ -1 +1 @@
1
- {"version":3,"file":"tenant-request-query.d.ts","sourceRoot":"","sources":["../src/tenant-request-query.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,CAAC,EAA8B,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAiF,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAG5I,6FAA6F;AAC7F,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C,gGAAgG;AAChG,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EAC5E,GAAG,EAAE,CAAC,CAAC,aAAa,EACpB,GAAG,GAAE,MAAmB,GACvB,IAAI,CAWN;AAED,8FAA8F;AAC9F,wBAAgB,mBAAmB,CACjC,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EAC5E,GAAG,GAAE,MAAmB,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAI9B;AAkBD,0FAA0F;AAC1F,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,GAAE,MAAmB,GAAG,kBAAkB,CAatH;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,CAAC,EAAE,WAAW,EACd,GAAG,GAAE,MAAmB,GACvB,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAC,CA6BnC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,aAAa,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,EAC9D,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACtE,MAAM,EAAE,CAQV"}
1
+ {"version":3,"file":"tenant-request-query.d.ts","sourceRoot":"","sources":["../src/tenant-request-query.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,CAAC,EAAkG,MAAM,yBAAyB,CAAC;AAC5I,OAAO,EAOL,KAAK,kBAAkB,EACxB,MAAM,oBAAoB,CAAC;AAG5B,6FAA6F;AAC7F,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C,gGAAgG;AAChG,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EAC5E,GAAG,EAAE,CAAC,CAAC,aAAa,EACpB,GAAG,GAAE,MAAmB,GACvB,IAAI,CAWN;AAED,8FAA8F;AAC9F,wBAAgB,mBAAmB,CACjC,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EAC5E,GAAG,GAAE,MAAmB,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAI9B;AA0BD,0FAA0F;AAC1F,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,GAAE,MAAmB,GAAG,kBAAkB,CAetH;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAOpE;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,CAAC,EAAE,WAAW,EACd,GAAG,GAAE,MAAmB,GACvB,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAC,CA6BnC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,aAAa,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,EAC9D,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACtE,MAAM,EAAE,CAQV"}
@@ -1,10 +1,11 @@
1
1
  /**
2
- * The query a tenant-grain request read takes (#1746): the tenant, the window, and the
3
- * facet filters. Shared by the three request routes so they refuse and accept the same
4
- * things, and so the count, the histogram and the list beside it always describe one set.
2
+ * The query a tenant-grain request read takes (#1746): the tenant, the window, the facet
3
+ * filters and the free-text search (#2762). Shared by the three request routes so they refuse
4
+ * and accept the same things, and so the count, the histogram and the list beside it always
5
+ * describe one set — which is why the two counting routes refuse `search` outright.
5
6
  */
6
- import { z, tenantId as tenantIdSchema } from '@substrat-run/contracts';
7
- import { INVOCATION_KINDS, REQUEST_FACET_KEYS } from './observability.js';
7
+ import { z, substratError, tenantId as tenantIdSchema, ENTITY_TYPES_SEPARATOR, INVOCATION_RECORD_FIELD_MAX } from '@substrat-run/contracts';
8
+ import { INVOCATION_KINDS, REQUEST_FACET_KEYS, REQUEST_SEARCH_MAX, REQUEST_SEARCH_NOT_COUNTED, } from './observability.js';
8
9
  import { stableDeploymentRefFor } from './deploy.js';
9
10
  /** The widest window a tenant-grain read may cover — the same three days `hours` caps at. */
10
11
  export const TENANT_WINDOW_MAX_HOURS = 72;
@@ -53,6 +54,14 @@ const facetValue = {
53
54
  surface: z.string().min(1).max(80),
54
55
  // #1901: what kind of work a line is about; `request` matches the lines that carry none.
55
56
  kind: z.enum(INVOCATION_KINDS),
57
+ // #2762: ONE entity type — a value holding the separator is a set, never a type.
58
+ entity: z
59
+ .string()
60
+ .min(1)
61
+ .max(INVOCATION_RECORD_FIELD_MAX)
62
+ .refine((v) => !v.includes(ENTITY_TYPES_SEPARATOR), 'entity is one entity type'),
63
+ // #2762: a route as the line writes it — `<METHOD> <pattern>`, never longer than the record keeps.
64
+ path: z.string().min(1).max(INVOCATION_RECORD_FIELD_MAX),
56
65
  };
57
66
  /**
58
67
  * Parse the shared part of a request read. `tenantId` is the caller's resolved tenant (the
@@ -71,7 +80,21 @@ export function parseTenantRequestQuery(tenantId, q, now = Date.now()) {
71
80
  .max(REQUEST_WHERE_VALUES_MAX)
72
81
  .parse([...new Set(values)]);
73
82
  }
74
- return { ...base, ...(Object.keys(where).length > 0 ? { where } : {}) };
83
+ // #2762: surrounding space is not part of what a reader looks for; an empty one is no filter.
84
+ const { search } = z.object({ search: z.string().max(REQUEST_SEARCH_MAX) }).parse({ search: q.one('search')?.trim() ?? '' });
85
+ return { ...base, ...(Object.keys(where).length > 0 ? { where } : {}), ...(search ? { search } : {}) };
86
+ }
87
+ /**
88
+ * #2762: refuse `search` on a read that counts. The histogram and the facets are computed from
89
+ * cubes (#1877) holding neither a request id nor a path, so no count could honour the text the
90
+ * list is filtered by. A refusal, never an ignored filter: counts of every request would read
91
+ * as counts of the matching ones. A semantic refusal, so no `errors` list: one would make it
92
+ * read as a parse failure, which answers neither the `reason` nor this sentence.
93
+ */
94
+ export function refuseSearchOnCounts(scope) {
95
+ if (scope.search === undefined)
96
+ return;
97
+ throw substratError('validation_failed', 'request counts cannot be narrowed by search; search applies to the request list', { reason: REQUEST_SEARCH_NOT_COUNTED });
75
98
  }
76
99
  /**
77
100
  * The tenant, the narrowing within it and the resolved window — the half every tenant-grain
@@ -1 +1 @@
1
- {"version":3,"file":"tenant-request-query.js","sourceRoot":"","sources":["../src/tenant-request-query.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tenant-request-query.js","sourceRoot":"","sources":["../src/tenant-request-query.ts"],"names":[],"mappings":"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"}
@@ -99,6 +99,13 @@ export interface ProvisionInstanceInput {
99
99
  scopeId: ScopeId;
100
100
  /** The first admin — whoever asked for the instance. */
101
101
  owner: PrincipalId;
102
+ /**
103
+ * The running scope a sibling was asked for from (#2773). `owner` is that scope's principal, so
104
+ * the vertical binds the seat to the login the parent already binds to it, rather than opening
105
+ * a first-sign-in window any login could take. Absent on a first install. A vertical that
106
+ * predates the field ignores it (its body parse strips unknown keys).
107
+ */
108
+ parentScopeId?: ScopeId;
102
109
  slug: string;
103
110
  name: string;
104
111
  /**
@@ -1198,13 +1205,25 @@ export interface TransferredScope {
1198
1205
  }
1199
1206
  /** How many times a copy is restarted because the source took a write while it was read (#2450). */
1200
1207
  export declare const TRANSFER_ATTEMPTS = 3;
1208
+ /**
1209
+ * How many change batches one attempt's catch-up reads before it gives up (#2450): a source that
1210
+ * writes faster than the copy drains its log never reaches a final batch, and the copy then starts
1211
+ * again like one torn at a quiet moment, and is refused once the attempts are spent.
1212
+ */
1213
+ export declare const CATCH_UP_BATCHES = 50;
1201
1214
  /**
1202
1215
  * A paged copy in flight (#2450): everything needed to resume it from another request or from a
1203
1216
  * background job, and nothing that is the scope's data. JSON, so a job can store it.
1204
1217
  *
1205
- * `cursor` is the source's next page, `null` once every page is staged. `seq` is the last page the
1206
- * destination staged under `stageId`. `revision` is the source's write revision as the first page
1207
- * read it: the copy is one copy only while every later page reads the same.
1218
+ * `cursor` is the source's next page, `null` once every page is staged. `seq` is the last page (or
1219
+ * change batch) the destination staged under `stageId`. `revision` is the source's write revision
1220
+ * as the first page read it.
1221
+ *
1222
+ * Without `capture` the copy is one copy only while every later page reads that same revision (a
1223
+ * quiet moment). With it, the first page opened a capture at the source under the stage's id, every
1224
+ * write since is logged there, and once the pages are staged the copy catches up on them in change
1225
+ * batches (`batch`, the next to ask for) until a final one: the copy is then true at the moment
1226
+ * that batch names, whatever the pages read.
1208
1227
  */
1209
1228
  export interface TransferState {
1210
1229
  from: ScopeId;
@@ -1219,7 +1238,10 @@ export interface TransferState {
1219
1238
  pages: number;
1220
1239
  rows: number;
1221
1240
  /** The `_substrat_meta` rows read so far, for `dumpMetaValue` once the copy is done. */
1222
- meta: ScopeDumpTable[];
1241
+ meta: ScopeDumpPageTable[];
1242
+ /** Absent from a copy read at a quiet moment, and from a record stored before captures. */
1243
+ capture?: true;
1244
+ batch?: number;
1223
1245
  }
1224
1246
  /** A copy finished (`done`), or one still to move (`state`). */
1225
1247
  export type TransferProgress = {
@@ -1229,12 +1251,15 @@ export type TransferProgress = {
1229
1251
  };
1230
1252
  /** The two ends a copy reads and writes, as `transferScope` and the job use them. */
1231
1253
  export type TransferEnds = {
1232
- source: Pick<VerticalClient, 'exportScope' | 'exportScopeStamped' | 'exportScopePage'>;
1233
- dest: Pick<VerticalClient, 'stageScopePage' | 'discardStagedScope'>;
1254
+ /** Without the capture verbs (a client assembled from part of one), a copy reads at a quiet moment. */
1255
+ source: Pick<VerticalClient, 'exportScope' | 'exportScopeStamped' | 'exportScopePage'> & Partial<Pick<VerticalClient, 'exportScopeChanges' | 'endScopeCapture'>>;
1256
+ dest: Pick<VerticalClient, 'stageScopePage' | 'discardStagedScope'> & Partial<Pick<VerticalClient, 'stageScopeChanges'>>;
1234
1257
  };
1235
1258
  export interface TransferOptions {
1236
1259
  retry?: <T>(run: () => Promise<T>) => Promise<T>;
1237
1260
  attempts?: number;
1261
+ /** Default `CATCH_UP_BATCHES`. */
1262
+ catchUpBatches?: number;
1238
1263
  /** A carry's export: the source mints its load stamp (`exportScopeStamped`). A fork's is not. */
1239
1264
  stamp?: boolean;
1240
1265
  /**
@@ -1253,10 +1278,18 @@ export interface TransferOptions {
1253
1278
  * before. A destination that predates them gets the pages merged into one dump, which is the old
1254
1279
  * ceiling, and only there.
1255
1280
  *
1256
- * Each page is read in one turn of the source, so it is consistent on its own; the pages are one
1257
- * consistent read only while the source's write revision stays where the first page read it. When
1258
- * it moves the copy starts again, up to `TRANSFER_ATTEMPTS` times, and is then refused (409) with
1259
- * nothing moved: a carry's later fences assume a copy that was true at one moment.
1281
+ * Each page is read in one turn of the source, so it is consistent on its own. A carry's later
1282
+ * fences assume a copy that was true at one moment, and there are two ways to get one. A source
1283
+ * that can capture (#2450 step 3) logs every write from its first page on, and once the pages are
1284
+ * staged the copy catches up on them in change batches until a final batch, read in the same turn
1285
+ * as the log ran empty, names that moment. A source that cannot (an older deployment, or a table
1286
+ * with a unique expression index, which no row trigger can key) is copied at a quiet moment: the
1287
+ * pages are one read only while its write revision stays where the first page read it.
1288
+ *
1289
+ * Either way a copy that does not come true starts again, up to `TRANSFER_ATTEMPTS` times, and is
1290
+ * then refused (409) with nothing moved: a quiet-moment copy the source wrote under, or a catch-up
1291
+ * whose capture was lost (it expired, a migration moved the schema, a load replaced the store) or
1292
+ * that did not reach a final batch in `CATCH_UP_BATCHES`.
1260
1293
  *
1261
1294
  * `beginTransfer` and `transferStep` are the same copy in resumable steps, for one that outlasts a
1262
1295
  * request; this runs them to the end.
@@ -1271,12 +1304,19 @@ scope: {
1271
1304
  * Start a copy (#2450): read the source's first page and stage it. Finished at once when the scope
1272
1305
  * fits that page (unless `alwaysStage`), when the source predates the paged verbs, or when the
1273
1306
  * destination does (its pages are then merged here, as the whole dump it takes).
1307
+ *
1308
+ * The first page asks the source to capture under the stage's id. A source that refuses (412: a
1309
+ * table it cannot capture) is read again without one, and one that answers a plain page (it does
1310
+ * not echo `captured`) has its capture ended: either way the copy is read at a quiet moment. A copy
1311
+ * that catches up and fits its first page catches up here, so it is still finished at once.
1274
1312
  */
1275
- export declare function beginTransfer({ source, dest }: TransferEnds, scope: {
1313
+ export declare function beginTransfer(ends: TransferEnds, scope: {
1276
1314
  from: ScopeId;
1277
1315
  to?: ScopeId;
1278
1316
  }, opts?: TransferOptions & {
1279
1317
  attempt?: number;
1318
+ /** False: read at a quiet moment, asking for no capture (a copy that met an end unable to catch up). */
1319
+ capture?: boolean;
1280
1320
  }): Promise<TransferProgress>;
1281
1321
  /**
1282
1322
  * Move the next pages of a copy (#2450), until it is staged whole or `until` says to stop (a job's
@@ -1285,9 +1325,31 @@ export declare function beginTransfer({ source, dest }: TransferEnds, scope: {
1285
1325
  *
1286
1326
  * `stopped` (a job being cancelled or forgotten): the copy starts nothing new. A torn copy then
1287
1327
  * discards its stage and answers where it was, opening no other: whoever stopped it settles it.
1328
+ *
1329
+ * Once a captured copy's pages are staged, the step catches up: each change batch the source
1330
+ * serves is staged after the last page, until a final one. A batch is staged at the first number
1331
+ * the stage has not taken (`staged: false` means it has), so a step resumed from a record older
1332
+ * than the stage (a step that faulted after staging) applies the batch served now after the one
1333
+ * staged then, and the newer rows win.
1288
1334
  */
1289
- export declare function transferStep({ source, dest }: TransferEnds, state: TransferState, opts?: TransferOptions & {
1335
+ export declare function transferStep(ends: TransferEnds, state: TransferState, opts?: TransferOptions & {
1290
1336
  until?: () => boolean;
1291
1337
  stopped?: () => boolean;
1292
1338
  }): Promise<TransferProgress>;
1339
+ /**
1340
+ * End the capture `copyId` at the source (#2450), best effort: one left open expires on its own once
1341
+ * nobody reads it. Every copy that stops (failed, cancelled, superseded or restarted) calls it.
1342
+ */
1343
+ export declare function endCapture(ends: {
1344
+ source: Partial<Pick<VerticalClient, 'endScopeCapture'>>;
1345
+ }, from: ScopeId, copyId: string): Promise<void>;
1346
+ /**
1347
+ * Give a copy up (#2450), best effort: discard its stage at the destination and end its capture at
1348
+ * the source, the two halves every copy that stops leaves behind. An end left out is skipped (one
1349
+ * that could not be resolved); its half then waits for a wipe, or expires.
1350
+ */
1351
+ export declare function abandonCopy(ends: {
1352
+ source?: Partial<Pick<VerticalClient, 'endScopeCapture'>>;
1353
+ dest?: Pick<VerticalClient, 'discardStagedScope'>;
1354
+ }, state: Pick<TransferState, 'from' | 'into' | 'stageId' | 'capture'>): Promise<void>;
1293
1355
  //# sourceMappingURL=vertical-client.d.ts.map