Skip to main content

noxid_codegen_server_js/
lib.rs

1use noxid_execution_ir::{
2    EndpointExecutionBoundary, ExecutionBoundary, ExecutionProgram, QueueExecutionBoundary,
3    TaskExecutionBoundary,
4};
5use noxid_ir::{
6    EndpointInputSection, EndpointKind, EndpointLimitScope, EndpointLimitWindow, SemanticBinaryOp,
7    SemanticExpr, SemanticExprKind, SemanticId, SemanticTemplatePart,
8};
9use noxid_source::js_escape;
10
11mod agents;
12
13pub use agents::AgentRuntimeOptions;
14
15pub const HANDLER_SCHEMA_VERSION: u32 = 8;
16
17// The `embedded/` copy is what a published crate carries: a tarball holds only
18// this directory, so `include_str!` may not reach above it. `tools/server-lifecycle-exports.txt`
19// remains the source of truth --- `tools/sync-embedded.sh` refreshes the copy and
20// `crates/cli/tests/embedded_assets_in_sync.rs` fails on drift.
21const SERVER_LIFECYCLE_EXPORTS: &str =
22    include_str!("../embedded/tools/server-lifecycle-exports.txt");
23
24/// The compiler-owned server surface that must survive composition and
25/// deployment bundling. Keep the names in the shared tools file so the Rust
26/// emitters and the Farm bridge cannot drift independently.
27pub fn server_lifecycle_exports() -> impl Iterator<Item = &'static str> {
28    SERVER_LIFECYCLE_EXPORTS
29        .lines()
30        .filter(|line| !line.is_empty())
31}
32
33pub fn server_lifecycle_export_required(name: &str, tracing_export: ServerTracingExport) -> bool {
34    tracing_export == ServerTracingExport::Otlp
35        || !matches!(
36            name,
37            "flushNoxidTracing" | "abandonNoxidTracing" | "noxidTracingExporterSnapshot"
38        )
39}
40
41fn missing_server_lifecycle_exports(
42    handler: &str,
43    tracing_export: ServerTracingExport,
44) -> Vec<&'static str> {
45    server_lifecycle_exports()
46        .filter(|name| server_lifecycle_export_required(name, tracing_export))
47        .filter(|name| {
48            ![
49                format!("export const {name} "),
50                format!("export function {name}("),
51                format!("export async function {name}("),
52            ]
53            .iter()
54            .any(|declaration| handler.contains(declaration))
55        })
56        .collect()
57}
58
59const LANGUAGE_VALUE_EQUALITY_FUNCTION: &str = r#"function $noxEqual($noxLeft, $noxRight) {
60  if ($noxLeft === $noxRight) return true;
61  if (Array.isArray($noxLeft) || Array.isArray($noxRight)) return Array.isArray($noxLeft) && Array.isArray($noxRight) && $noxLeft.length === $noxRight.length && $noxLeft.every(($noxValue, $noxIndex) => $noxEqual($noxValue, $noxRight[$noxIndex]));
62  if ($noxLeft === null || $noxRight === null || typeof $noxLeft !== "object" || typeof $noxRight !== "object") return false;
63  const $noxLeftKeys = Object.keys($noxLeft).sort();
64  const $noxRightKeys = Object.keys($noxRight).sort();
65  return $noxLeftKeys.length === $noxRightKeys.length && $noxLeftKeys.every(($noxKey, $noxIndex) => $noxKey === $noxRightKeys[$noxIndex] && $noxEqual($noxLeft[$noxKey], $noxRight[$noxKey]));
66}
67"#;
68
69#[derive(Clone, Debug)]
70pub struct ServerJavaScriptOutput {
71    pub handler: String,
72    pub manifest: String,
73    pub server_actions: usize,
74    pub edge_actions: usize,
75}
76
77#[derive(Clone, Copy, Debug, Default)]
78pub struct AgentSurfaceOptions<'a> {
79    pub openapi_json: Option<&'a str>,
80    pub serve_openapi: bool,
81    pub mcp: bool,
82    pub principal_authority_import: Option<&'a str>,
83}
84
85#[derive(Clone, Debug)]
86pub struct ServerRuntimeOptions {
87    pub db_pool: u64,
88    pub pubsub: Option<PubSubRuntimeOptions>,
89    pub development_trace_capture: bool,
90    pub application_namespace: Option<String>,
91    /// WO-30 model declarations. Empty means the emitted graph carries no
92    /// models runtime section at all.
93    pub models: ModelRuntimeOptions,
94    pub tracing_export: ServerTracingExport,
95    pub tracing_service_name: String,
96    pub otlp_endpoint_secret: bool,
97    pub otlp_headers_secret: bool,
98    /// WO-31 agent declarations. An agent without `model:` is not an engine
99    /// agent, so a build with none carries no `agents` runtime section.
100    pub agents: AgentRuntimeOptions,
101}
102
103/// The compile-time half of the model boundary: every declared model, plus the
104/// strict JSON Schema for every declared type a `generateObject` call could
105/// name. Both are frozen into the emitted graph so a request resolves nothing
106/// but its own secret.
107#[derive(Clone, Debug, Default, PartialEq, Eq)]
108pub struct ModelRuntimeOptions {
109    pub models: Vec<noxid_model_ir::LoweredModel>,
110    pub type_schemas: Vec<noxid_model_ir::ModelTypeSchema>,
111}
112
113impl ModelRuntimeOptions {
114    pub fn is_empty(&self) -> bool {
115        self.models.is_empty()
116    }
117}
118
119impl Default for ServerRuntimeOptions {
120    fn default() -> Self {
121        Self {
122            db_pool: 10,
123            pubsub: None,
124            development_trace_capture: false,
125            application_namespace: None,
126            models: ModelRuntimeOptions::default(),
127            tracing_export: ServerTracingExport::Stdout,
128            tracing_service_name: "noxid.application".into(),
129            otlp_endpoint_secret: false,
130            otlp_headers_secret: false,
131            agents: AgentRuntimeOptions::default(),
132        }
133    }
134}
135
136#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
137pub enum ServerTracingExport {
138    #[default]
139    Stdout,
140    Otlp,
141}
142
143impl ServerTracingExport {
144    pub fn as_str(self) -> &'static str {
145        match self {
146            Self::Stdout => "stdout",
147            Self::Otlp => "otlp",
148        }
149    }
150}
151
152/// Emit one lazy getter per declared server secret. Values resolve and cache
153/// only when their own property is read, so a missing telemetry credential
154/// cannot poison an unrelated application-secret read.
155pub fn server_secrets_prelude_javascript(secrets: &[String]) -> String {
156    if secrets.is_empty() {
157        return "const __NOXID_TRACING_SECRET_FAILURE__ = false;\nconst __noxidServerEnvironment = (environment) => environment;\n".into();
158    }
159    let names = secrets
160        .iter()
161        .map(|name| format!("\"{}\"", js_escape(name)))
162        .collect::<Vec<_>>()
163        .join(", ");
164    format!(
165        r#"const __NOXID_DECLARED_SECRETS__ = Object.freeze([{names}]);
166const __NOXID_SECRET_CACHE__ = Object.create(null);
167let __NOXID_TRACING_SECRET_FAILURE__ = false;
168let __NOXID_TRACING_SECRET_ERROR_LOGGED__ = false;
169function __noxidResolveSecret(name) {{
170  if (Object.hasOwn(__NOXID_SECRET_CACHE__, name)) return __NOXID_SECRET_CACHE__[name];
171  const value = globalThis.process?.env?.[name];
172  if (typeof value !== "string" || value.length === 0) {{
173    if (name === "OTEL_EXPORTER_OTLP_ENDPOINT" || name === "OTEL_EXPORTER_OTLP_HEADERS") {{
174      __NOXID_TRACING_SECRET_FAILURE__ = true;
175      if (!__NOXID_TRACING_SECRET_ERROR_LOGGED__) {{
176        __NOXID_TRACING_SECRET_ERROR_LOGGED__ = true;
177        try {{ console.error(JSON.stringify(Object.freeze({{ schema: "noxid.tracing.error.v1", event: "tracing.export.disabled", code: "TRACING_EXPORT_SECRET_MISSING", exporter: "otlp" }}))); }} catch {{}}
178      }}
179    }}
180    throw new Error("error[SERVER_SECRET_MISSING]: declared server secret " + name + " is missing from the environment");
181  }}
182  Object.defineProperty(__NOXID_SECRET_CACHE__, name, {{ value, enumerable: true }});
183  return value;
184}}
185const __NOXID_SECRET_VALUES__ = Object.create(null);
186for (const name of __NOXID_DECLARED_SECRETS__) {{
187  Object.defineProperty(__NOXID_SECRET_VALUES__, name, {{ get: () => __noxidResolveSecret(name), enumerable: true }});
188}}
189Object.freeze(__NOXID_SECRET_VALUES__);
190function __noxidServerEnvironment(environment) {{
191  if (environment !== null && typeof environment === "object" && Object.getOwnPropertyDescriptor(environment, "secrets")) return environment;
192  const enriched = Object.create(environment ?? null);
193  Object.defineProperty(enriched, "secrets", {{ value: __NOXID_SECRET_VALUES__, enumerable: false }});
194  return enriched;
195}}
196"#
197    )
198}
199
200/// The single compiler-owned bridge from the closed `noxid.trace.v1` record
201/// schema to OpenTelemetry semantic-convention attributes. Keeping duplicate
202/// source fields (such as `code`) explicit makes the exported contract golden-
203/// testable without teaching the JavaScript runtime Noxid semantics.
204pub fn otel_span_attribute_mapping() -> &'static [(&'static str, &'static str, Option<&'static str>)]
205{
206    &[
207        ("method", "http.request.method", None),
208        ("route", "http.route", None),
209        ("status", "http.response.status_code", None),
210        ("durationMs", "noxid.duration_ms", None),
211        ("code", "error.type", None),
212        ("semanticId", "noxid.semantic_id", None),
213        ("code", "noxid.diagnostic_code", None),
214        ("capability", "noxid.capability", None),
215        ("state", "noxid.queue.state", Some("queue.state")),
216    ]
217}
218
219#[derive(Clone, Copy, Debug, PartialEq, Eq)]
220pub enum PubSubDriver {
221    Memory,
222    Postgres,
223    Redis,
224}
225
226impl PubSubDriver {
227    fn as_str(self) -> &'static str {
228        match self {
229            Self::Memory => "memory",
230            Self::Postgres => "postgres",
231            Self::Redis => "redis",
232        }
233    }
234}
235
236#[derive(Clone, Copy, Debug, PartialEq, Eq)]
237pub struct PubSubRuntimeOptions {
238    pub driver: PubSubDriver,
239    pub coalescing_ms: u64,
240}
241
242#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
243pub enum ServerTracingMode {
244    Off,
245    #[default]
246    Requests,
247    Full,
248}
249
250impl ServerTracingMode {
251    pub fn as_str(self) -> &'static str {
252        match self {
253            Self::Off => "off",
254            Self::Requests => "requests",
255            Self::Full => "full",
256        }
257    }
258}
259
260pub fn generate(
261    program: &ExecutionProgram,
262    host_import: &str,
263    validator_import: &str,
264    middleware_import: &str,
265    base_path: &str,
266    server_secrets: &[String],
267) -> Result<ServerJavaScriptOutput, String> {
268    generate_with_startup(
269        program,
270        host_import,
271        validator_import,
272        middleware_import,
273        None,
274        base_path,
275        server_secrets,
276    )
277}
278
279pub fn generate_with_startup(
280    program: &ExecutionProgram,
281    host_import: &str,
282    validator_import: &str,
283    middleware_import: &str,
284    startup_import: Option<&str>,
285    base_path: &str,
286    server_secrets: &[String],
287) -> Result<ServerJavaScriptOutput, String> {
288    generate_with_agent_surfaces(
289        program,
290        host_import,
291        validator_import,
292        middleware_import,
293        startup_import,
294        base_path,
295        server_secrets,
296        AgentSurfaceOptions::default(),
297    )
298}
299
300#[allow(clippy::too_many_arguments)]
301pub fn generate_with_agent_surfaces(
302    program: &ExecutionProgram,
303    host_import: &str,
304    validator_import: &str,
305    middleware_import: &str,
306    startup_import: Option<&str>,
307    base_path: &str,
308    server_secrets: &[String],
309    agent_surfaces: AgentSurfaceOptions<'_>,
310) -> Result<ServerJavaScriptOutput, String> {
311    generate_with_tracing(
312        program,
313        host_import,
314        validator_import,
315        middleware_import,
316        startup_import,
317        base_path,
318        server_secrets,
319        agent_surfaces,
320        ServerTracingMode::Requests,
321    )
322}
323
324// Keep the established import/base-path arguments explicit while adding
325// compiler-owned surface and tracing contracts; call sites should not assemble
326// an opaque options bag containing unrelated handler inputs.
327#[allow(clippy::too_many_arguments)]
328pub fn generate_with_tracing(
329    program: &ExecutionProgram,
330    host_import: &str,
331    validator_import: &str,
332    middleware_import: &str,
333    startup_import: Option<&str>,
334    base_path: &str,
335    server_secrets: &[String],
336    agent_surfaces: AgentSurfaceOptions<'_>,
337    tracing_mode: ServerTracingMode,
338) -> Result<ServerJavaScriptOutput, String> {
339    generate_with_runtime_options(
340        program,
341        host_import,
342        validator_import,
343        middleware_import,
344        startup_import,
345        base_path,
346        server_secrets,
347        agent_surfaces,
348        tracing_mode,
349        ServerRuntimeOptions::default(),
350    )
351}
352
353#[allow(clippy::too_many_arguments)]
354pub fn generate_with_runtime_options(
355    program: &ExecutionProgram,
356    host_import: &str,
357    validator_import: &str,
358    middleware_import: &str,
359    startup_import: Option<&str>,
360    base_path: &str,
361    server_secrets: &[String],
362    agent_surfaces: AgentSurfaceOptions<'_>,
363    tracing_mode: ServerTracingMode,
364    runtime_options: ServerRuntimeOptions,
365) -> Result<ServerJavaScriptOutput, String> {
366    let callable = program
367        .boundaries
368        .iter()
369        .filter(|boundary| matches!(boundary.target.as_str(), "server" | "edge"))
370        .collect::<Vec<_>>();
371    let schemas = callable
372        .iter()
373        .map(|boundary| schema_javascript(boundary))
374        .collect::<Vec<_>>()
375        .join(",\n  ");
376    let compiled_actions = callable
377        .iter()
378        .filter_map(|boundary| {
379            boundary.body.as_ref().map(|body| {
380                Ok(format!(
381                    "\"{}\": async (args) => ({})",
382                    js_escape(boundary.action.as_str()),
383                    compiler_body_javascript(body)?,
384                ))
385            })
386        })
387        .collect::<Result<Vec<_>, String>>()?
388        .join(",\n  ");
389    let endpoint_schemas = program
390        .endpoints
391        .iter()
392        .map(endpoint_schema_javascript)
393        .collect::<Vec<_>>()
394        .join(",\n  ");
395    let uses_uploads = program
396        .endpoints
397        .iter()
398        .flat_map(|endpoint| &endpoint.inputs)
399        .any(|input| input.file.is_some());
400    let compiled_endpoints = program
401        .endpoints
402        .iter()
403        .filter_map(|endpoint| {
404            if endpoint.statements.is_empty() {
405                return None;
406            }
407            Some(
408                noxid_ir::computational_statements_javascript(
409                    &endpoint.statements,
410                    2,
411                    &compiler_body_javascript,
412                )
413                .map(|body| {
414                    // ADR 0137 rule 2: the dispatcher already passes the
415                    // frozen data context as the second argument, so binding
416                    // it here is the whole of `context.principal`.
417                    format!(
418                        "\"{}\": async (args, context) => {{\n{}{}}}",
419                        js_escape(endpoint.id.as_str()),
420                        body,
421                        "  ",
422                    )
423                }),
424            )
425        })
426        .collect::<Result<Vec<_>, String>>()?
427        .join(",\n  ");
428    let task_schemas = program
429        .tasks
430        .iter()
431        .map(task_schema_javascript)
432        .collect::<Vec<_>>()
433        .join(",\n  ");
434    let compiled_tasks = program
435        .tasks
436        .iter()
437        .filter_map(|task| {
438            if task.host_key.is_some() {
439                return None;
440            }
441            Some(
442                compiler_statements_javascript(&task.statements, 2).map(|body| {
443                    format!(
444                        "\"{}\": async (_args, context) => {{\n{body}  }}",
445                        js_escape(task.id.as_str()),
446                    )
447                }),
448            )
449        })
450        .collect::<Result<Vec<_>, String>>()?
451        .join(",\n  ");
452    let queue_schemas = program
453        .queues
454        .iter()
455        .map(queue_schema_javascript)
456        .collect::<Vec<_>>()
457        .join(",\n  ");
458    let live_resource_schemas = program
459        .live_resources
460        .iter()
461        .map(|resource| {
462            let capabilities = resource
463                .capabilities
464                .iter()
465                .map(|capability| format!("\"{}\"", js_escape(capability)))
466                .collect::<Vec<_>>()
467                .join(", ");
468            let route_scopes = resource
469                .route_scopes
470                .iter()
471                .map(|scope| {
472                    let parameters = scope
473                        .parameters
474                        .iter()
475                        .map(|parameter| format!(
476                            "Object.freeze({{ name: \"{}\", type: \"{}\", catchAll: {} }})",
477                            js_escape(&parameter.name),
478                            js_escape(&parameter.ty),
479                            parameter.catch_all,
480                        ))
481                        .collect::<Vec<_>>()
482                        .join(", ");
483                    let middleware = scope
484                        .middleware
485                        .iter()
486                        .filter_map(|id| id.as_str().strip_prefix("middleware:"))
487                        .map(|name| format!("\"{}\"", js_escape(name)))
488                        .collect::<Vec<_>>()
489                        .join(", ");
490                    format!(
491                        "Object.freeze({{ id: \"{}\", pattern: \"{}\", parameters: Object.freeze([{}]), middleware: Object.freeze([{}]) }})",
492                        js_escape(scope.route.as_str()),
493                        js_escape(&scope.pattern),
494                        parameters,
495                        middleware,
496                    )
497                })
498                .collect::<Vec<_>>()
499                .join(", ");
500            format!(
501                "Object.freeze({{ id: \"{}\", name: \"{}\", capabilities: Object.freeze([{}]), routeScopes: Object.freeze([{}]) }})",
502                js_escape(resource.id.as_str()),
503                js_escape(&resource.name),
504                capabilities,
505                route_scopes,
506            )
507        })
508        .collect::<Vec<_>>()
509        .join(",\n  ");
510    let presence_schemas = program
511        .presences
512        .iter()
513        .map(|presence| {
514            let capabilities = presence
515                .capabilities
516                .iter()
517                .map(|capability| format!("\"{}\"", js_escape(capability)))
518                .collect::<Vec<_>>()
519                .join(", ");
520            let route_scopes = presence
521                .route_scopes
522                .iter()
523                .map(|scope| {
524                    let parameters = scope
525                        .parameters
526                        .iter()
527                        .map(|parameter| format!(
528                            "Object.freeze({{ name: \"{}\", type: \"{}\", catchAll: {} }})",
529                            js_escape(&parameter.name),
530                            js_escape(&parameter.ty),
531                            parameter.catch_all,
532                        ))
533                        .collect::<Vec<_>>()
534                        .join(", ");
535                    let middleware = scope
536                        .middleware
537                        .iter()
538                        .filter_map(|id| id.as_str().strip_prefix("middleware:"))
539                        .map(|name| format!("\"{}\"", js_escape(name)))
540                        .collect::<Vec<_>>()
541                        .join(", ");
542                    format!(
543                        "Object.freeze({{ id: \"{}\", pattern: \"{}\", parameters: Object.freeze([{}]), middleware: Object.freeze([{}]) }})",
544                        js_escape(scope.route.as_str()),
545                        js_escape(&scope.pattern),
546                        parameters,
547                        middleware,
548                    )
549                })
550                .collect::<Vec<_>>()
551                .join(", ");
552            format!(
553                "Object.freeze({{ id: \"{}\", component: \"{}\", stream: \"{}\", recordType: \"{}\", memberType: \"{}\", snapshotType: \"{}\", capabilities: Object.freeze([{}]), routeScopes: Object.freeze([{}]), ttlMilliseconds: {}, heartbeatMilliseconds: {} }})",
554                js_escape(presence.id.as_str()),
555                js_escape(&presence.component_name),
556                js_escape(presence.stream.as_str()),
557                js_escape(presence.record_type.as_str()),
558                js_escape(presence.member_type.as_str()),
559                js_escape(presence.snapshot_type.as_str()),
560                capabilities,
561                route_scopes,
562                presence.ttl_ms,
563                presence.heartbeat_ms,
564            )
565        })
566        .collect::<Vec<_>>()
567        .join(",\n  ");
568    let live_surface = !program.live_resources.is_empty() || !program.presences.is_empty();
569    let application_namespace = runtime_options
570        .application_namespace
571        .as_deref()
572        .unwrap_or("codegen_test");
573    let pubsub_options = runtime_options.pubsub.or_else(|| {
574        live_surface.then_some(PubSubRuntimeOptions {
575            driver: PubSubDriver::Memory,
576            coalescing_ms: 250,
577        })
578    });
579    let compiled_queues = program
580        .queues
581        .iter()
582        .filter_map(|queue| {
583            if queue.host_key.is_some() {
584                return None;
585            }
586            Some(
587                compiler_statements_javascript(&queue.statements, 2).map(|body| {
588                    format!(
589                        "\"{}\": async (args, context) => {{\n{body}  }}",
590                        js_escape(queue.id.as_str()),
591                    )
592                }),
593            )
594        })
595        .collect::<Result<Vec<_>, String>>()?
596        .join(",\n  ");
597    // Remote bodies may call temporal builtins; the helper module travels
598    // with the handler when any compiled body needs it.
599    let compiled_server_bodies =
600        format!("{compiled_actions}\n{compiled_endpoints}\n{compiled_tasks}\n{compiled_queues}");
601    let date_helpers = if compiled_server_bodies.contains("$noxDate.") {
602        format!("{}\n", noxid_ir::DATE_HELPERS_JS)
603    } else {
604        String::new()
605    };
606    let endpoint = if base_path == "/" {
607        "/_noxid/actions/".to_string()
608    } else {
609        format!("{base_path}/_noxid/actions/")
610    };
611    let invalidation_endpoint = if base_path == "/" {
612        "/_noxid/revalidate".to_string()
613    } else {
614        format!("{base_path}/_noxid/revalidate")
615    };
616    let task_endpoint = if base_path == "/" {
617        "/_noxid/tasks/".to_string()
618    } else {
619        format!("{base_path}/_noxid/tasks/")
620    };
621    let queue_drain_endpoint = if base_path == "/" {
622        "/_noxid/queue/drain".to_string()
623    } else {
624        format!("{base_path}/_noxid/queue/drain")
625    };
626    let live_resource_endpoint = if base_path == "/" {
627        "/_noxid/live".to_string()
628    } else {
629        format!("{base_path}/_noxid/live")
630    };
631    let presence_write_endpoint = if base_path == "/" {
632        "/_noxid/presence".to_string()
633    } else {
634        format!("{base_path}/_noxid/presence")
635    };
636    let equality_function = if compiled_server_bodies.contains("$noxEqual(") {
637        LANGUAGE_VALUE_EQUALITY_FUNCTION
638    } else {
639        ""
640    };
641    let uses_endpoint_storage = program
642        .endpoints
643        .iter()
644        .any(|endpoint| endpoint.limit.is_some() || endpoint.idempotent);
645    let uses_redis_pubsub =
646        pubsub_options.is_some_and(|options| options.driver == PubSubDriver::Redis);
647    let uses_presence_storage = !program.presences.is_empty();
648    // The agent engine persists every run under the `agent_runs` namespace, so
649    // an engine agent brings the storage runtime in on its own.
650    let uses_agent_storage = !runtime_options.agents.is_empty();
651    let storage_import_javascript = if uses_endpoint_storage
652        || uses_presence_storage
653        || uses_redis_pubsub
654        || uses_uploads
655        || uses_agent_storage
656    {
657        format!(
658            "import * as __noxidServerStorageRuntime from \"./noxid-server.js\";\n{}{}{}",
659            if uses_endpoint_storage || uses_presence_storage || uses_agent_storage {
660                "const __noxidStorage = __noxidServerStorageRuntime.storage;\nconst __noxidSharedRateLimit = __noxidServerStorageRuntime.__noxidEndpointRateLimit;\nconst __noxidSharedIdempotencyPrepare = __noxidServerStorageRuntime.__noxidEndpointIdempotencyPrepare;\nconst __noxidSharedIdempotencyComplete = __noxidServerStorageRuntime.__noxidEndpointIdempotencyComplete;\nconst __noxidSharedIdempotencyRelease = __noxidServerStorageRuntime.__noxidEndpointIdempotencyRelease;\n"
661            } else {
662                ""
663            },
664            if uses_redis_pubsub {
665                "const __noxidRedisPubSubPublish = __noxidServerStorageRuntime.__noxidRedisPubSubPublish;\nconst __noxidRedisPubSubSubscribe = __noxidServerStorageRuntime.__noxidRedisPubSubSubscribe;\n"
666            } else {
667                ""
668            },
669            if uses_uploads {
670                "const __noxidCreateUploadSink = __noxidServerStorageRuntime.__noxidCreateUploadSink;\n"
671            } else {
672                ""
673            },
674        )
675    } else {
676        String::new()
677    };
678    let startup_import_javascript = startup_import.map_or_else(String::new, |specifier| {
679        format!(
680            "import {{ startServerPlugins as __noxidStartServerPlugins }} from \"{}\";\n",
681            js_escape(specifier)
682        )
683    });
684    let principal_authority_import_javascript = agent_surfaces
685        .principal_authority_import
686        .map_or_else(|| "const __noxidCloseDatabase = async () => {};\n".into(), |specifier| {
687            format!(
688                "import {{ __installNoxidPrincipalAuthority, closeDatabase as __noxidCloseDatabase }} from \"{}\";\n",
689                js_escape(specifier)
690            )
691        });
692    let otlp_import_javascript = match runtime_options.tracing_export {
693        ServerTracingExport::Stdout => String::new(),
694        ServerTracingExport::Otlp => {
695            "import { createOtlpTraceExporter } from \"../assets/noxid-runtime.js\";\n".into()
696        }
697    };
698    let principal_authority_import_javascript =
699        format!("{principal_authority_import_javascript}{otlp_import_javascript}");
700    let live_resource_runtime = if live_surface {
701        format!(
702            "const liveResourcePath = \"{}\";\nconst presenceWritePath = \"{}\";\nconst liveResourceSchemas = Object.freeze([\n  {}\n]);\nconst presenceSchemas = Object.freeze([\n  {}\n]);\n{}",
703            js_escape(&live_resource_endpoint),
704            js_escape(&presence_write_endpoint),
705            live_resource_schemas,
706            presence_schemas,
707            LIVE_RESOURCE_RUNTIME,
708        )
709    } else {
710        String::new()
711    };
712    let handler_runtime = HANDLER_RUNTIME
713        .replace(
714            "  /* noxid-server:startup */",
715            if startup_import.is_some() {
716                "  await __noxidStartServerPlugins(environment);"
717            } else {
718                ""
719            },
720        )
721        .replace(
722            "/* noxid-server:live-invalidation */",
723            if pubsub_options.is_some() {
724                r#"async function __noxidPublishLiveInvalidations(resources, principal) {
725  for (const semanticId of resources) {
726    await __noxidPubSubPublish(__noxidPubSubEvent("invalidation", semanticId, principal));
727  }
728}"#
729            } else {
730                "async function __noxidPublishLiveInvalidations() {}"
731            },
732        )
733        .replace(
734            "/* noxid-server:live-resource-transport-runtime */",
735            &live_resource_runtime,
736        )
737        .replace(
738            "    /* noxid-server:live-resource-request */",
739            if live_surface {
740                "    const liveResourceResponse = await handleLiveResourceRequest(request, url, environment, executionContext);\n    if (liveResourceResponse !== null) return liveResourceResponse;"
741            } else {
742                ""
743            },
744        );
745    let trace_export_javascript = match runtime_options.tracing_export {
746        ServerTracingExport::Stdout => {
747            "if (loggable) { try { console.log(JSON.stringify(record)); } catch {} }"
748        }
749        ServerTracingExport::Otlp => {
750            "if (loggable && event !== \"request.start\") { try { __noxidOtlpExporter.enqueue(Object.freeze({ ...record, traceFlags: trace.traceFlags })); } catch {} }"
751        }
752    };
753    let trace_runtime = TRACE_RUNTIME
754        .replace(
755            "  /* noxid-server:development-trace-context */",
756            if runtime_options.development_trace_capture {
757                "  developmentCapture = true;"
758            } else {
759                ""
760            },
761        )
762        .replace(
763            "  /* noxid-server:development-trace-capture */",
764            if runtime_options.development_trace_capture {
765                r#"  try {
766    const capture = globalThis.__NOXID_DEV_TRACE_CAPTURE__;
767    if (typeof capture === "function") capture(Object.freeze({ ...record }));
768  } catch {}"#
769            } else {
770                ""
771            },
772        )
773        .replace(
774            "  /* noxid-server:trace-span-id */",
775            if runtime_options.tracing_export == ServerTracingExport::Otlp {
776                "  try { return __noxidOtlpExporter.nextSpanId(); } catch {}"
777            } else {
778                ""
779            },
780        )
781        .replace("  /* noxid-server:trace-export */", trace_export_javascript);
782    let trace_runtime = if runtime_options.tracing_export == ServerTracingExport::Otlp {
783        let attribute_mapping = otel_span_attribute_mapping()
784            .iter()
785            .map(|(source, target, event)| {
786                format!(
787                    "Object.freeze({{ source: \"{}\", target: \"{}\", event: {} }})",
788                    js_escape(source),
789                    js_escape(target),
790                    event
791                        .map(|event| format!("\"{}\"", js_escape(event)))
792                        .unwrap_or_else(|| "null".into()),
793                )
794            })
795            .collect::<Vec<_>>()
796            .join(", ");
797        format!(
798            r#"const __noxidOtlpExporter = createOtlpTraceExporter({{
799  endpoint: __noxidTracingEnvironmentValue(globalThis.process?.env, "OTEL_EXPORTER_OTLP_ENDPOINT", tracingOtlpEndpointSecret),
800  headers: __noxidTracingEnvironmentValue(globalThis.process?.env, "OTEL_EXPORTER_OTLP_HEADERS", tracingOtlpHeadersSecret),
801  serviceName: "{}",
802  disabled: __NOXID_TRACING_SECRET_FAILURE__,
803  attributeMapping: Object.freeze([{attribute_mapping}]),
804}});
805export async function flushNoxidTracing() {{ try {{ await __noxidOtlpExporter.flush(); }} catch {{}} }}
806export function abandonNoxidTracing() {{ try {{ return __noxidOtlpExporter.abandon(); }} catch {{ return 0; }} }}
807export function noxidTracingExporterSnapshot() {{
808  try {{ return __noxidOtlpExporter.snapshot(); }}
809  catch {{ return Object.freeze({{ capacity: 0, batchSize: 0, queued: 0, inFlight: 0, dropped: 0, closed: true }}); }}
810}}
811{trace_runtime}"#,
812            js_escape(&runtime_options.tracing_service_name),
813        )
814    } else {
815        trace_runtime.to_string()
816    };
817    // The models section is emitted only when a model is declared, so a
818    // model-free project's server graph carries none of this code.
819    let model_runtime = if runtime_options.models.is_empty() {
820        String::new()
821    } else {
822        let declarations = runtime_options
823            .models
824            .models
825            .iter()
826            .map(|model| {
827                format!(
828                    "  \"{}\": Object.freeze({{ id: \"{}\", name: \"{}\", provider: \"{}\", modelId: \"{}\", baseUrl: Object.freeze({{ kind: \"{}\", value: \"{}\" }}), temperature: {}, maxTokens: {}, retries: {}, secret: \"{}\" }}),",
829                    js_escape(&model.name),
830                    js_escape(model.id.as_str()),
831                    js_escape(&model.name),
832                    model.provider,
833                    js_escape(&model.model_id),
834                    model.base_url.kind(),
835                    js_escape(model.base_url.value()),
836                    model
837                        .temperature
838                        .clone()
839                        .unwrap_or_else(|| "null".to_string()),
840                    model
841                        .max_tokens
842                        .map(|value| value.to_string())
843                        .unwrap_or_else(|| "null".into()),
844                    model.retries,
845                    js_escape(&model.secret),
846                )
847            })
848            .collect::<Vec<_>>()
849            .join("\n");
850        format!(
851            "\n// noxid-runtime:feature-start:models\nconst modelDeclarations = Object.freeze({{\n{declarations}\n}});\nconst modelTypeSchemas = {};\n{MODEL_SCENARIO_RUNTIME}{MODEL_RUNTIME}// noxid-runtime:feature-end:models\n",
852            noxid_model_ir::type_schema_registry_javascript(&runtime_options.models.type_schemas),
853        )
854    };
855    // WO-31: the agents section, and the two seams that reach it — the request
856    // router and the queue worker's startup reconciliation. Both collapse to
857    // nothing when the build declares no engine agent.
858    let agent_runtime = agents::agents_runtime_javascript(
859        &runtime_options.agents,
860        base_path,
861        noxid_ir::DEFAULT_ENDPOINT_TIMEOUT_MS,
862    );
863    let endpoint_runtime = ENDPOINT_RUNTIME.replace(
864        "  /* noxid-server:agent-run-request */",
865        if agent_runtime.is_empty() {
866            ""
867        } else {
868            "  const agentRunResponse = await handleAgentRunRequest(request, url, environment, executionContext);\n  if (agentRunResponse !== null) return agentRunResponse;"
869        },
870    );
871    let queue_runtime = QUEUE_RUNTIME.replace(
872        "  /* noxid-server:agent-run-reconcile */",
873        if agent_runtime.is_empty() {
874            ""
875        } else {
876            "  void __noxidReconcileAgentRuns().catch((cause) => notify(cause));"
877        },
878    );
879    let (pubsub_configuration, pubsub_runtime) = pubsub_options.map_or_else(
880        || (String::new(), ""),
881        |options| {
882            (
883                format!(
884                    "const applicationNamespace = \"{}\";\nconst pubSubDriver = \"{}\";\nconst pubSubCoalescingMs = {};\n",
885                    js_escape(application_namespace),
886                    options.driver.as_str(),
887                    options.coalescing_ms,
888                ),
889                PUBSUB_RUNTIME,
890            )
891        },
892    );
893    let validator_symbols = if uses_uploads {
894        "typeValidators, __noxidCreateFileValidationState, __noxidValidateFileChunk, __noxidFinalizeFileValidation, __noxidCreateFileRef"
895    } else {
896        "typeValidators"
897    };
898    let handler = format!(
899        "import * as hostModule from \"{}\";\nimport {{ {} }} from \"{}\";\nimport * as middlewareRegistry from \"{}\";\n{}{}{}\n{}{}{}{}const databasePoolSize = {};\n{}const __noxidConfiguredEnvironmentProxies = new WeakSet();\nfunction __noxidConfiguredServerEnvironment(environment) {{\n  environment = __noxidServerEnvironment(environment);\n  if (__noxidConfiguredEnvironmentProxies.has(environment)) return environment;\n  const pool = Object.getOwnPropertyDescriptor(environment, \"dbPool\");\n  if (pool?.value === databasePoolSize && pool.enumerable === false && pool.writable === false && pool.configurable === false) return environment;\n  const processEnvironment = globalThis.process?.env;\n  if (environment !== processEnvironment && Object.isExtensible(environment) && (pool === undefined || pool.configurable)) {{\n    Object.defineProperty(environment, \"dbPool\", {{ value: databasePoolSize, enumerable: false, writable: false, configurable: false }});\n    return environment;\n  }}\n  const enriched = new Proxy(environment, {{\n    get(target, property, receiver) {{ return property === \"dbPool\" ? databasePoolSize : Reflect.get(target, property, receiver); }},\n    set(target, property, value) {{ return property === \"dbPool\" ? false : Reflect.set(target, property, value, target); }},\n    defineProperty(target, property, descriptor) {{ return property === \"dbPool\" ? false : Reflect.defineProperty(target, property, descriptor); }},\n    deleteProperty(target, property) {{ return property === \"dbPool\" ? false : Reflect.deleteProperty(target, property); }},\n  }});\n  __noxidConfiguredEnvironmentProxies.add(enriched);\n  return enriched;\n}}\nconst tracingExport = \"{}\";\nconst tracingOtlpEndpointSecret = {};\nconst tracingOtlpHeadersSecret = {};\nfunction __noxidTracingEnvironmentValue(environment, name, secret) {{\n  try {{\n    const configured = __noxidServerEnvironment(environment);\n    const value = secret ? configured?.secrets?.[name] : globalThis.process?.env?.[name];\n    return typeof value === \"string\" && value.length > 0 ? value : null;\n  }} catch {{ return null; }}\n}}\nconst middlewareHandlers = typeof middlewareRegistry === \"undefined\" ? Object.freeze({{}}) : middlewareRegistry.middleware ?? Object.freeze({{}});\nconst globalMiddlewareHandlers = typeof middlewareRegistry === \"undefined\" ? Object.freeze({{}}) : middlewareRegistry.globalMiddlewareHandlers ?? Object.freeze({{}});\nconst globalMiddleware = typeof middlewareRegistry === \"undefined\" ? Object.freeze([]) : middlewareRegistry.globalMiddleware ?? Object.freeze([]);\nconst hostActions = hostModule.actions ?? hostModule.default ?? Object.create(null);\nconst hostEndpoints = hostModule.endpoints ?? hostActions;\nconst hostTasks = hostModule.tasks ?? Object.create(null);\nconst hostQueues = hostModule.queues ?? Object.create(null);\nconst compiledActions = Object.freeze({{\n  {}\n}});\nconst compiledEndpoints = Object.freeze({{\n  {}\n}});\nconst compiledTasks = Object.freeze({{\n  {}\n}});\nconst compiledQueues = Object.freeze({{\n  {}\n}});\nconst endpointSchemas = Object.freeze([\n  {}\n]);\nconst taskSchemas = Object.freeze([\n  {}\n]);\nconst queueSchemas = Object.freeze([\n  {}\n]);\nexport const taskSchedules = Object.freeze(taskSchemas.map((task) => Object.freeze({{ name: task.name, schedule: task.schedule }})));\nexport const closeDatabase = __noxidCloseDatabase;\nconst authorize = hostModule.authorize;\nconst invalidateCache = hostModule.invalidateCache;\nconst schemas = Object.freeze({{\n  {}\n}});\nconst endpointPrefix = \"{}\";\nconst invalidationEndpoint = \"{}\";\nconst taskPrefix = \"{}\";\nconst queueDrainPath = \"{}\";\nconst applicationBasePath = \"{}\";\nconst openapiDocument = {};\nconst openapiEnabled = {};\nconst mcpEnabled = {};\nconst tracingMode = \"{}\";\n\n{}{}{}{}{}{}{}{}",
900        js_escape(host_import),
901        validator_symbols,
902        js_escape(validator_import),
903        js_escape(middleware_import),
904        storage_import_javascript,
905        startup_import_javascript,
906        principal_authority_import_javascript,
907        server_secrets_prelude_javascript(server_secrets),
908        date_helpers,
909        equality_function,
910        noxid_ir::SERVER_MIDDLEWARE_RESULT_JAVASCRIPT,
911        runtime_options.db_pool,
912        pubsub_configuration,
913        runtime_options.tracing_export.as_str(),
914        runtime_options.otlp_endpoint_secret,
915        runtime_options.otlp_headers_secret,
916        compiled_actions,
917        compiled_endpoints,
918        compiled_tasks,
919        compiled_queues,
920        endpoint_schemas,
921        task_schemas,
922        queue_schemas,
923        schemas,
924        js_escape(&endpoint),
925        js_escape(&invalidation_endpoint),
926        js_escape(&task_endpoint),
927        js_escape(&queue_drain_endpoint),
928        js_escape(base_path),
929        agent_surfaces
930            .openapi_json
931            .map(|document| format!("\"{}\"", js_escape(document)))
932            .unwrap_or_else(|| "null".into()),
933        agent_surfaces.serve_openapi,
934        agent_surfaces.mcp,
935        tracing_mode.as_str(),
936        PRINCIPAL_RUNTIME,
937        trace_runtime,
938        pubsub_runtime,
939        endpoint_multipart_limits() + &endpoint_runtime,
940        queue_runtime,
941        model_runtime,
942        agent_runtime,
943        handler_runtime,
944    );
945    let handler = format!("{handler}\nexport {{ __noxidConfiguredServerEnvironment }};\n");
946    let missing_lifecycle_exports =
947        missing_server_lifecycle_exports(&handler, runtime_options.tracing_export);
948    if !missing_lifecycle_exports.is_empty() {
949        return Err(format!(
950            "internal error: generated server handler is missing compiler-owned lifecycle exports: {}",
951            missing_lifecycle_exports.join(", ")
952        ));
953    }
954    Ok(ServerJavaScriptOutput {
955        handler,
956        manifest: program.to_json(),
957        server_actions: callable
958            .iter()
959            .filter(|boundary| boundary.target.as_str() == "server")
960            .count(),
961        edge_actions: callable
962            .iter()
963            .filter(|boundary| boundary.target.as_str() == "edge")
964            .count(),
965    })
966}
967
968/// Lower one compiler-owned remote expression to JavaScript.
969///
970/// Fail-closed: a construct with no deterministic server lowering returns a
971/// stable `error[CODE]` instead of a placeholder, so a remote body semantics
972/// failed to refuse stops the build rather than shipping `undefined`.
973pub fn compiler_body_javascript(expression: &SemanticExpr) -> Result<String, String> {
974    Ok(match &expression.kind {
975        SemanticExprKind::Int(value) => value.to_string(),
976        SemanticExprKind::Float(value) => value.to_string(),
977        SemanticExprKind::String(value) => format!("\"{}\"", js_escape(value)),
978        SemanticExprKind::Boolean(value) => value.to_string(),
979        SemanticExprKind::Array(values) => format!(
980            "[{}]",
981            values
982                .iter()
983                .map(compiler_body_javascript)
984                .collect::<Result<Vec<_>, _>>()?
985                .join(", ")
986        ),
987        SemanticExprKind::Struct { fields, .. } => format!(
988            "Object.freeze({{ {} }})",
989            fields
990                .iter()
991                .map(|field| {
992                    Ok(format!(
993                        "\"{}\": {}",
994                        js_escape(&field.name),
995                        compiler_body_javascript(&field.value)?
996                    ))
997                })
998                .collect::<Result<Vec<_>, String>>()?
999                .join(", ")
1000        ),
1001        SemanticExprKind::FieldAccess { base, name, .. } => {
1002            format!(
1003                "({})[\"{}\"]",
1004                compiler_body_javascript(base)?,
1005                js_escape(name)
1006            )
1007        }
1008        SemanticExprKind::Reference(id) => {
1009            let name = id.as_str().rsplit('.').next().unwrap_or(id.as_str());
1010            if id.as_str().starts_with("local:") {
1011                name.to_string()
1012            } else {
1013                format!("args[\"{}\"]", js_escape(name))
1014            }
1015        }
1016        SemanticExprKind::Variant {
1017            variant, payload, ..
1018        } => {
1019            let tag = variant
1020                .as_str()
1021                .rsplit('.')
1022                .next()
1023                .unwrap_or(variant.as_str());
1024            match payload {
1025                Some(payload) => format!(
1026                    "Object.freeze({{ tag: \"{}\", value: {} }})",
1027                    js_escape(tag),
1028                    compiler_body_javascript(payload)?
1029                ),
1030                None => format!("Object.freeze({{ tag: \"{}\" }})", js_escape(tag)),
1031            }
1032        }
1033        SemanticExprKind::Binary { left, op, right } => {
1034            if matches!(op, SemanticBinaryOp::Equal | SemanticBinaryOp::NotEqual)
1035                && has_language_value_equality(&left.ty)
1036            {
1037                let equality = format!(
1038                    "$noxEqual({}, {})",
1039                    compiler_body_javascript(left)?,
1040                    compiler_body_javascript(right)?
1041                );
1042                return Ok(if matches!(op, SemanticBinaryOp::NotEqual) {
1043                    format!("(!{equality})")
1044                } else {
1045                    equality
1046                });
1047            }
1048            let operator = match op {
1049                SemanticBinaryOp::Add => "+",
1050                SemanticBinaryOp::Subtract => "-",
1051                SemanticBinaryOp::Multiply => "*",
1052                SemanticBinaryOp::Divide => "/",
1053                SemanticBinaryOp::Equal => "===",
1054                SemanticBinaryOp::NotEqual => "!==",
1055                SemanticBinaryOp::Less => "<",
1056                SemanticBinaryOp::LessEqual => "<=",
1057                SemanticBinaryOp::Greater => ">",
1058                SemanticBinaryOp::GreaterEqual => ">=",
1059                SemanticBinaryOp::And => "&&",
1060                SemanticBinaryOp::Or => "||",
1061                SemanticBinaryOp::Coalesce => "??",
1062            };
1063            format!(
1064                "({} {operator} {})",
1065                compiler_body_javascript(left)?,
1066                compiler_body_javascript(right)?
1067            )
1068        }
1069        SemanticExprKind::Unary { op, operand } => {
1070            format!("({}{})", op.as_str(), compiler_body_javascript(operand)?)
1071        }
1072        SemanticExprKind::StringTemplate(parts) => {
1073            let mut pieces = vec!["\"\"".to_string()];
1074            for part in parts {
1075                pieces.push(match part {
1076                    SemanticTemplatePart::Literal(value) => format!("\"{}\"", js_escape(value)),
1077                    SemanticTemplatePart::Expression(expression) => {
1078                        format!("({})", compiler_body_javascript(expression)?)
1079                    }
1080                });
1081            }
1082            format!("({})", pieces.join(" + "))
1083        }
1084        SemanticExprKind::CollectionQuery {
1085            base,
1086            kind,
1087            field,
1088            value,
1089        } => {
1090            let value = value.as_deref().map(compiler_body_javascript).transpose()?;
1091            noxid_ir::collection_query_javascript(
1092                *kind,
1093                &compiler_body_javascript(base)?,
1094                field.as_ref().map(|segment| segment.name.as_str()),
1095                value.as_deref(),
1096                match &base.ty {
1097                    noxid_types::Type::Map(key, _) => Some(key.as_ref()),
1098                    _ => None,
1099                },
1100            )
1101        }
1102        SemanticExprKind::FunctionCall {
1103            function,
1104            name,
1105            arguments,
1106        } => match emit_distinct_identity_call(function, arguments)? {
1107            Some(javascript) => javascript,
1108            None => match emit_builtin_call(function, arguments)? {
1109                Some(javascript) => javascript,
1110                None => return Err(rejected_remote_call(name, arguments.len())),
1111            },
1112        },
1113        SemanticExprKind::Call {
1114            name, arguments, ..
1115        } => return Err(rejected_remote_call(name, arguments.len())),
1116    })
1117}
1118
1119/// One rule, one message: compiler-owned remote bodies lower only the
1120/// deterministic expression subset, and a call outside it needs a host body.
1121fn rejected_remote_call(name: &str, arity: usize) -> String {
1122    noxid_ir::emitter_rejection(
1123        "REMOTE_ACTION_CALL_UNSUPPORTED",
1124        format_args!(
1125            "the call `{name}({} argument(s))` has no compiler-owned server lowering; compiler-owned remote bodies emit only literals, typed parameters, field access, constructors, operators, templates, pure builtins, and collection queries — move this call into a host-implemented body keyed by the action or endpoint, or replace it with that subset",
1126            arity
1127        ),
1128    )
1129}
1130
1131pub fn compiler_statements_javascript(
1132    statements: &[noxid_ir::SemanticStatement],
1133    indent: usize,
1134) -> Result<String, String> {
1135    noxid_ir::computational_statements_javascript(statements, indent, &compiler_body_javascript)
1136}
1137
1138/// A `distinct` type is erased at every boundary: the wire representation is
1139/// the base type, so `UserId(value)` and `id.base()` are both the identity on
1140/// the value. The client and SSR emitters already lower them this way; the
1141/// server emitter must agree exactly, or the same expression would mean one
1142/// thing in a client action and another in a compiler-owned remote body.
1143/// `Ok(None)` means "not a distinct call".
1144fn emit_distinct_identity_call(
1145    function: &noxid_ir::SemanticId,
1146    arguments: &[SemanticExpr],
1147) -> Result<Option<String>, String> {
1148    if !function.is_distinct_call() {
1149        return Ok(None);
1150    }
1151    match arguments.first() {
1152        Some(argument) => compiler_body_javascript(argument).map(Some),
1153        None => Ok(Some("undefined".into())),
1154    }
1155}
1156
1157/// `Ok(None)` means "not a builtin at all"; `Err` means the id claims to be a
1158/// builtin the shared lowering table cannot emit, which is a compiler bug the
1159/// build must not paper over with a bare `name(args)` call.
1160fn emit_builtin_call(
1161    function: &noxid_ir::SemanticId,
1162    arguments: &[SemanticExpr],
1163) -> Result<Option<String>, String> {
1164    let Some(name) = function.as_str().strip_prefix("fn:@builtin.") else {
1165        return Ok(None);
1166    };
1167    let emitted = arguments
1168        .iter()
1169        .map(compiler_body_javascript)
1170        .collect::<Result<Vec<_>, _>>()?;
1171    noxid_ir::builtin_javascript(name, &emitted)
1172        .map(Some)
1173        .ok_or_else(|| noxid_ir::rejected_builtin_call(name, emitted.len()))
1174}
1175
1176fn has_language_value_equality(ty: &noxid_types::Type) -> bool {
1177    use noxid_types::Type;
1178    match ty {
1179        Type::Array(_)
1180        | Type::Map(_, _)
1181        | Type::MapEntry(_, _)
1182        | Type::Result(_, _)
1183        | Type::Named(_) => true,
1184        Type::Optional(inner)
1185        | Type::Static(inner)
1186        | Type::Reactive(inner)
1187        | Type::Binding(inner) => has_language_value_equality(inner),
1188        // WO-44 stage (a) exhaustiveness only: an upload body field is
1189        // already `FileRef` by the time server codegen sees it, so `File`
1190        // has no value identity here. Stage (c) owns the multipart handler.
1191        Type::File
1192        | Type::Int
1193        | Type::String
1194        | Type::Boolean
1195        | Type::Number
1196        | Type::Float
1197        | Type::Date
1198        | Type::Function(_, _)
1199        | Type::Unknown => false,
1200    }
1201}
1202
1203fn schema_javascript(boundary: &ExecutionBoundary) -> String {
1204    let parameters = boundary
1205        .parameters
1206        .iter()
1207        .map(|parameter| {
1208            format!(
1209                "Object.freeze({{ name: \"{}\", type: \"{}\", typeId: {} }})",
1210                js_escape(&parameter.name),
1211                js_escape(&parameter.ty),
1212                optional_js_string(parameter.type_id.as_ref().map(|id| id.as_str())),
1213            )
1214        })
1215        .collect::<Vec<_>>()
1216        .join(", ");
1217    let capabilities = boundary
1218        .capabilities
1219        .iter()
1220        .map(|capability| format!("\"{}\"", js_escape(capability)))
1221        .collect::<Vec<_>>()
1222        .join(", ");
1223    let route_scopes = boundary
1224        .route_scopes
1225        .iter()
1226        .map(|scope| {
1227            let middleware = scope
1228                .middleware
1229                .iter()
1230                .filter_map(|id| id.as_str().strip_prefix("middleware:"))
1231                .map(|name| format!("\"{}\"", js_escape(name)))
1232                .collect::<Vec<_>>()
1233                .join(", ");
1234            format!(
1235                "Object.freeze({{ id: \"{}\", pattern: \"{}\", middleware: Object.freeze([{}]) }})",
1236                js_escape(scope.route.as_str()),
1237                js_escape(&scope.pattern),
1238                middleware,
1239            )
1240        })
1241        .collect::<Vec<_>>()
1242        .join(", ");
1243    let invalidates = semantic_ids_javascript(&boundary.invalidates);
1244    format!(
1245        "\"{}\": Object.freeze({{ id: \"{}\", boundary: \"{}\", target: \"{}\", parameters: Object.freeze([{}]), result: Object.freeze({{ id: \"{}\", type: \"{}\", typeId: {} }}), capabilities: Object.freeze([{}]), routeScopes: Object.freeze([{}]), invalidates: Object.freeze([{}]) }})",
1246        js_escape(boundary.action.as_str()),
1247        js_escape(boundary.action.as_str()),
1248        js_escape(boundary.id.as_str()),
1249        boundary.target.as_str(),
1250        parameters,
1251        js_escape(boundary.result.id.as_str()),
1252        js_escape(&boundary.result.ty),
1253        optional_js_string(boundary.result.type_id.as_ref().map(|id| id.as_str())),
1254        capabilities,
1255        route_scopes,
1256        invalidates,
1257    )
1258}
1259
1260fn endpoint_schema_javascript(endpoint: &EndpointExecutionBoundary) -> String {
1261    let fields = |section| {
1262        endpoint
1263            .inputs
1264            .iter()
1265            .filter(|input| input.section == section)
1266            .map(|input| {
1267                let upload = input.file.as_ref().map_or_else(
1268                    || "null".to_string(),
1269                    |file| {
1270                        let types = file
1271                            .types
1272                            .iter()
1273                            .map(|media_type| format!("\"{}\"", js_escape(media_type)))
1274                            .collect::<Vec<_>>()
1275                            .join(", ");
1276                        format!(
1277                            "Object.freeze({{ maxSizeBytes: {}, types: Object.freeze([{}]), multiple: {} }})",
1278                            file.max_size_bytes, types, file.multiple,
1279                        )
1280                    },
1281                );
1282                format!(
1283                    "Object.freeze({{ name: \"{}\", type: \"{}\", typeId: {}, upload: {} }})",
1284                    js_escape(&input.name),
1285                    js_escape(&input.ty),
1286                    optional_js_string(input.type_id.as_ref().map(|id| id.as_str())),
1287                    upload,
1288                )
1289            })
1290            .collect::<Vec<_>>()
1291            .join(", ")
1292    };
1293    let capabilities = endpoint
1294        .capabilities
1295        .iter()
1296        .map(|capability| format!("\"{}\"", js_escape(capability)))
1297        .collect::<Vec<_>>()
1298        .join(", ");
1299    let middleware = endpoint
1300        .middleware
1301        .iter()
1302        .map(|name| format!("\"{}\"", js_escape(name)))
1303        .collect::<Vec<_>>()
1304        .join(", ");
1305    let limit = endpoint.limit.map_or_else(
1306        || "null".to_string(),
1307        |limit| {
1308            format!(
1309                "Object.freeze({{ requests: {}, window: \"{}\", scope: \"{}\" }})",
1310                limit.requests,
1311                match limit.window {
1312                    EndpointLimitWindow::Minute => "minute",
1313                    EndpointLimitWindow::Hour => "hour",
1314                },
1315                match limit.scope {
1316                    EndpointLimitScope::Session => "session",
1317                    EndpointLimitScope::Ip => "ip",
1318                }
1319            )
1320        },
1321    );
1322    let cache = endpoint.cache.as_ref().map_or_else(
1323        || "null".to_string(),
1324        |cache| {
1325            let tags = cache
1326                .tags
1327                .iter()
1328                .map(|tag| format!("\"{}\"", js_escape(tag)))
1329                .collect::<Vec<_>>()
1330                .join(", ");
1331            format!(
1332                "Object.freeze({{ id: \"{}\", mode: \"{}\", seconds: {}, tags: Object.freeze([{}]) }})",
1333                js_escape(cache.id.as_str()),
1334                cache.mode.as_str(),
1335                cache.seconds,
1336                tags,
1337            )
1338        },
1339    );
1340    let path = endpoint.path.as_deref().unwrap_or("");
1341    let method = endpoint
1342        .method
1343        .map(|method| method.as_str().to_uppercase())
1344        .unwrap_or_default();
1345    let result_validator = format!("validator:endpoint.{}.result", endpoint.name);
1346    let error_validator = (endpoint.kind == EndpointKind::RequestResponse
1347        && endpoint.result.ty.starts_with("Result<"))
1348    .then(|| format!("validator:endpoint.{}.error", endpoint.name));
1349    format!(
1350        "Object.freeze({{ id: \"{}\", name: \"{}\", version: {}, description: {}, kind: \"{}\", method: \"{}\", path: \"{}\", params: Object.freeze([{}]), query: Object.freeze([{}]), body: Object.freeze([{}]), result: Object.freeze({{ id: \"{}\", type: \"{}\", typeId: {}, validator: \"{}\", errorValidator: {} }}), capabilities: Object.freeze([{}]), timeoutMs: {}, limit: {}, cache: {}, idempotent: {}, middleware: Object.freeze([{}]), invalidates: Object.freeze([{}]) }})",
1351        js_escape(endpoint.id.as_str()),
1352        js_escape(&endpoint.name),
1353        endpoint.version,
1354        endpoint
1355            .description
1356            .as_deref()
1357            .map(|description| format!("\"{}\"", js_escape(description)))
1358            .unwrap_or_else(|| "null".into()),
1359        endpoint.kind.as_str(),
1360        js_escape(&method),
1361        js_escape(path),
1362        fields(EndpointInputSection::Params),
1363        fields(EndpointInputSection::Query),
1364        fields(EndpointInputSection::Body),
1365        js_escape(endpoint.result.id.as_str()),
1366        js_escape(&endpoint.result.ty),
1367        optional_js_string(endpoint.result.type_id.as_ref().map(|id| id.as_str())),
1368        js_escape(&result_validator),
1369        error_validator
1370            .as_deref()
1371            .map(|value| format!("\"{}\"", js_escape(value)))
1372            .unwrap_or_else(|| "null".into()),
1373        capabilities,
1374        endpoint.timeout_ms,
1375        limit,
1376        cache,
1377        endpoint.idempotent,
1378        middleware,
1379        semantic_ids_javascript(&endpoint.invalidates),
1380    )
1381}
1382
1383fn task_schema_javascript(task: &TaskExecutionBoundary) -> String {
1384    format!(
1385        "Object.freeze({{ id: \"{}\", name: \"{}\", schedule: \"{}\", hostKey: {} }})",
1386        js_escape(task.id.as_str()),
1387        js_escape(&task.name),
1388        js_escape(&task.schedule),
1389        optional_js_string(task.host_key.as_ref().map(|id| id.as_str())),
1390    )
1391}
1392
1393fn queue_schema_javascript(queue: &QueueExecutionBoundary) -> String {
1394    let payload = queue
1395        .payload
1396        .iter()
1397        .map(|field| {
1398            let type_ids = field
1399                .type_ids
1400                .iter()
1401                .map(|(name, id)| {
1402                    format!("\"{}\": \"{}\"", js_escape(name), js_escape(id.as_str()))
1403                })
1404                .collect::<Vec<_>>()
1405                .join(", ");
1406            format!(
1407                "Object.freeze({{ name: \"{}\", type: \"{}\", typeId: {}, typeIds: Object.freeze({{{}}}) }})",
1408                js_escape(&field.name),
1409                js_escape(&field.ty),
1410                optional_js_string(field.type_id.as_ref().map(|id| id.as_str())),
1411                type_ids,
1412            )
1413        })
1414        .collect::<Vec<_>>()
1415        .join(", ");
1416    format!(
1417        "Object.freeze({{ id: \"{}\", name: \"{}\", hostKey: {}, payload: Object.freeze([{}]), retry: {}, backoffMs: {}, invalidates: Object.freeze([{}]) }})",
1418        js_escape(queue.id.as_str()),
1419        js_escape(&queue.name),
1420        optional_js_string(queue.host_key.as_ref().map(|id| id.as_str())),
1421        payload,
1422        queue.retry,
1423        queue.backoff_ms,
1424        semantic_ids_javascript(&queue.invalidates),
1425    )
1426}
1427
1428fn semantic_ids_javascript(ids: &[SemanticId]) -> String {
1429    ids.iter()
1430        .map(|id| format!("\"{}\"", js_escape(id.as_str())))
1431        .collect::<Vec<_>>()
1432        .join(", ")
1433}
1434
1435fn optional_js_string(value: Option<&str>) -> String {
1436    value
1437        .map(|value| format!("\"{}\"", js_escape(value)))
1438        .unwrap_or_else(|| "null".into())
1439}
1440
1441const PRINCIPAL_RUNTIME: &str = r##"const __noxidPrincipalAuthority = typeof __installNoxidPrincipalAuthority === "function" ? __installNoxidPrincipalAuthority(__noxidTraceDataAccess) : null;
1442const __noxidPrincipalValues = new WeakSet();
1443function __noxidTrustedPrincipal(value) { __noxidPrincipalValues.add(value); return value; }
1444const __NOXID_SYSTEM_PRINCIPAL = __noxidTrustedPrincipal(Object.freeze({ kind: "system", canonical: "system", scope: null, agent: null }));
1445
1446function __noxidPrincipalPart(value) {
1447  return encodeURIComponent(value).replace(/[!'()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
1448}
1449
1450function __noxidPrincipal(middlewareContext, environment, agent = null) {
1451  const identity = middlewareContext?.userId ?? middlewareContext?.sessionId ?? middlewareContext?.session?.id ?? environment?.sessionId;
1452  const scope = typeof identity === "string" && identity.length > 0 ? identity : null;
1453  if (agent !== null) {
1454    const acting = scope === null ? "system" : `session:${__noxidPrincipalPart(scope)}`;
1455    return __noxidTrustedPrincipal(Object.freeze({ kind: "agent", canonical: `agent:${__noxidPrincipalPart(agent)}:acting:${acting}`, scope, agent }));
1456  }
1457  if (scope === null) return __NOXID_SYSTEM_PRINCIPAL;
1458  return __noxidTrustedPrincipal(Object.freeze({ kind: "user", canonical: `session:${__noxidPrincipalPart(scope)}`, scope, agent: null }));
1459}
1460
1461export function __noxidLiveConnectionPrincipal(middlewareContext, environment) {
1462  return __noxidPrincipal(middlewareContext, environment);
1463}
1464
1465function __noxidPrincipalFromCanonical(value) {
1466  if (value === null || value === "system") return __NOXID_SYSTEM_PRINCIPAL;
1467  if (typeof value !== "string") throw Object.assign(new Error("persisted queue principal is invalid"), { code: "QUEUE_PRINCIPAL_DRIFT" });
1468  try {
1469    if (value.startsWith("session:")) {
1470      const scope = decodeURIComponent(value.slice(8));
1471      const principal = __noxidPrincipal({ userId: scope }, null);
1472      if (principal.canonical === value) return principal;
1473    }
1474    const marker = ":acting:session:";
1475    if (value.startsWith("agent:") && value.includes(marker)) {
1476      const split = value.indexOf(marker);
1477      const agent = decodeURIComponent(value.slice(6, split));
1478      const scope = decodeURIComponent(value.slice(split + marker.length));
1479      const principal = __noxidPrincipal({ userId: scope }, null, agent);
1480      if (principal.canonical === value) return principal;
1481    }
1482    if (value.startsWith("agent:") && value.endsWith(":acting:system")) {
1483      const agent = decodeURIComponent(value.slice(6, -14));
1484      const principal = __noxidPrincipal(null, null, agent);
1485      if (principal.canonical === value) return principal;
1486    }
1487  } catch {}
1488  throw Object.assign(new Error("persisted queue principal is invalid"), { code: "QUEUE_PRINCIPAL_DRIFT" });
1489}
1490
1491const __noxidRuntimePrincipals = new WeakMap();
1492function __noxidDataContext(fields, principal) {
1493  const context = Object.freeze({ ...fields, principal });
1494  __noxidRuntimePrincipals.set(context, principal);
1495  __noxidTraceBindPrincipal(context, principal);
1496  return __noxidPrincipalAuthority === null ? context : __noxidPrincipalAuthority.bind(context, principal);
1497}
1498
1499const __noxidAgentRequests = new WeakMap();
1500function __noxidAgentForRequest(request) { return __noxidAgentRequests.get(request) ?? null; }
1501"##;
1502
1503const TRACE_RUNTIME: &str = r##"const NOXID_TRACE_SCHEMA = "noxid.trace.v1";
1504const NOXID_TRACE_ID = /^[A-Za-z0-9_-]{16,128}$/;
1505const NOXID_TRACEPARENT = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(.*)$/i;
1506const NOXID_DIAGNOSTIC_CODE = /^[A-Z][A-Z0-9_]{0,127}$/;
1507const NOXID_SEMANTIC_ID = /^[A-Za-z0-9][A-Za-z0-9._:@/+-]{0,255}$/;
1508const NOXID_CAPABILITY = /^[A-Za-z][A-Za-z0-9._:-]{0,127}$/;
1509const NOXID_ROUTE = /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/\[\]-]{0,1023}$/;
1510const NOXID_PRINCIPAL = /^(?:system|session:[A-Za-z0-9._~%!*'()-]{1,768})$/;
1511const NOXID_DATA_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_$]{0,127}$/;
1512const NOXID_MODEL_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
1513const NOXID_MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,127}$/;
1514const NOXID_MODEL_PROVIDERS = new Set(["anthropic", "openai", "openai-compatible"]);
1515const NOXID_AGENT_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
1516const NOXID_AGENT_RUN = /^[A-Za-z0-9_-]{16,128}$/;
1517const __noxidRequestTraces = new WeakMap();
1518const __noxidFailureSpans = new WeakMap();
1519let __noxidTraceCounter = 0;
1520
1521function __noxidGeneratedTraceId() {
1522  try {
1523    const value = globalThis.crypto?.randomUUID?.();
1524    if (typeof value === "string" && NOXID_TRACE_ID.test(value)) return value;
1525  } catch {}
1526  __noxidTraceCounter = (__noxidTraceCounter + 1) % Number.MAX_SAFE_INTEGER;
1527  return `noxid_${Date.now().toString(36)}_${__noxidTraceCounter.toString(36).padStart(10, "0")}`;
1528}
1529
1530function __noxidRequestTraceContext(request) {
1531  let traceparent = null;
1532  let inbound = null;
1533  try {
1534    traceparent = request.headers.get("traceparent");
1535    inbound = request.headers.get("x-noxid-trace");
1536  } catch {}
1537  const parsed = typeof traceparent === "string" ? NOXID_TRACEPARENT.exec(traceparent) : null;
1538  const futureSuffixValid = parsed !== null && (parsed[1].toLowerCase() === "00" ? parsed[5] === "" : parsed[5] === "" || /^-(?:[0-9a-f]{2})+$/i.test(parsed[5]));
1539  if (parsed !== null && parsed[1].toLowerCase() !== "ff" && futureSuffixValid && !/^0+$/.test(parsed[2]) && !/^0+$/.test(parsed[3])) {
1540    return { id: parsed[2].toLowerCase(), parentSpanId: parsed[3].toLowerCase(), traceFlags: Number.parseInt(parsed[4], 16) };
1541  }
1542  return { id: typeof inbound === "string" && NOXID_TRACE_ID.test(inbound) ? inbound : __noxidGeneratedTraceId() };
1543}
1544
1545function __noxidTraceContext(parent = null) {
1546  // One sequence counter per trace, and one deferred `request.start` per trace.
1547  // A nested scope carries a reference to the trace's shared cell instead of
1548  // instantiating its own, so `sequence` starts at one per trace and follows
1549  // emission order across every nested scope, and the agent door's deferred
1550  // start is still the trace's first record even when the first thing the trace
1551  // emits comes from a subrequest scope. docs/language-reference.md states the
1552  // contract; evaluator ruling 2026-09-03.
1553  const inherited = parent !== null && typeof parent === "object" ? parent.shared : null;
1554  const shared = inherited !== null && typeof inherited === "object" ? inherited : { sequence: 0, pendingStartMethod: null, root: null, agentSemanticId: null, actingPrincipal: null };
1555  const trace = { id: typeof parent === "string" ? parent : parent?.id ?? __noxidGeneratedTraceId(), shared };
1556  if (shared.root === null) shared.root = trace;
1557  if (typeof parent?.parentSpanId === "string") trace.parentSpanId = parent.parentSpanId;
1558  if (Number.isInteger(parent?.traceFlags)) trace.traceFlags = parent.traceFlags;
1559  return trace;
1560}
1561
1562function __noxidTraceNow() {
1563  try {
1564    const value = globalThis.performance?.now?.();
1565    if (Number.isFinite(value)) return value;
1566  } catch {}
1567  return Date.now();
1568}
1569
1570function __noxidTraceNewSpanId() {
1571  /* noxid-server:trace-span-id */
1572  return null;
1573}
1574
1575function __noxidTraceDuration(startedAt) {
1576  return Math.max(0, __noxidTraceNow() - startedAt);
1577}
1578
1579function __noxidTraceBeginSpan(trace) {
1580  if (trace === null) return null;
1581  return Object.freeze({
1582    trace,
1583    startedAt: __noxidTraceNow(),
1584    spanId: __noxidTraceNewSpanId(),
1585    parentSpanId: trace.requestSpanId ?? trace.parentSpanId,
1586  });
1587}
1588
1589function __noxidTraceFinishSpan(span, event, fields = null) {
1590  if (span === null) return;
1591  __noxidTraceEmit(span.trace, event, {
1592    ...(fields ?? Object.create(null)),
1593    durationMs: __noxidTraceDuration(span.startedAt),
1594    spanId: span.spanId,
1595    parentSpanId: span.parentSpanId,
1596  });
1597}
1598
1599function __noxidTraceEmit(trace, event, fields = null) {
1600  let developmentCapture = false;
1601  /* noxid-server:development-trace-context */
1602  const loggable = tracingMode !== "off" && (tracingMode !== "requests" || event === "request.start" || event === "request.end");
1603  if (trace === null && developmentCapture) trace = __noxidTraceContext();
1604  if (trace === null || !loggable && !developmentCapture) return;
1605  if (event !== "request.start" && typeof trace.shared.pendingStartMethod === "string") {
1606    const method = trace.shared.pendingStartMethod;
1607    trace.shared.pendingStartMethod = null;
1608    // The start belongs to the request root, not to whichever nested scope
1609    // happened to emit first.
1610    __noxidTraceEmit(trace.shared.root ?? trace, "request.start", { method });
1611  }
1612  const record = Object.create(null);
1613  record.schema = NOXID_TRACE_SCHEMA;
1614  record.traceId = trace.id;
1615  record.sequence = ++trace.shared.sequence;
1616  record.event = event;
1617  record.timestampMs = Date.now();
1618  const tracedAgentSemanticId = typeof trace.agentSemanticId === "string" ? trace.agentSemanticId : trace.shared.agentSemanticId;
1619  const tracedActingPrincipal = typeof trace.actingPrincipal === "string" ? trace.actingPrincipal : trace.shared.actingPrincipal;
1620  if (typeof tracedAgentSemanticId === "string" && NOXID_SEMANTIC_ID.test(tracedAgentSemanticId)) record.agentSemanticId = tracedAgentSemanticId;
1621  if (typeof tracedActingPrincipal === "string" && NOXID_PRINCIPAL.test(tracedActingPrincipal)) record.actingPrincipal = tracedActingPrincipal;
1622  if (fields !== null) {
1623    if (typeof fields.semanticId === "string" && NOXID_SEMANTIC_ID.test(fields.semanticId)) record.semanticId = fields.semanticId;
1624    if (typeof fields.code === "string" && NOXID_DIAGNOSTIC_CODE.test(fields.code)) record.code = fields.code;
1625    if (typeof fields.capability === "string" && NOXID_CAPABILITY.test(fields.capability)) record.capability = fields.capability;
1626    if (typeof fields.route === "string" && NOXID_ROUTE.test(fields.route)) {
1627      record.route = fields.route;
1628      trace.route = fields.route;
1629    } else if (typeof trace.route === "string") record.route = trace.route;
1630    if (Number.isInteger(fields.status) && fields.status >= 100 && fields.status <= 599) record.status = fields.status;
1631    if (typeof fields.method === "string" && /^[A-Z]{1,16}$/.test(fields.method)) record.method = fields.method;
1632    if (typeof fields.state === "string" && /^[A-Za-z][A-Za-z-]{0,31}$/.test(fields.state)) record.state = fields.state;
1633    if (typeof fields.transition === "string" && /^[A-Za-z][A-Za-z]{0,31}$/.test(fields.transition)) record.transition = fields.transition;
1634    if (typeof fields.jobId === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(fields.jobId)) record.jobId = fields.jobId;
1635    if (Number.isSafeInteger(fields.attempts) && fields.attempts >= 0) record.attempts = fields.attempts;
1636    if (Number.isFinite(fields.durationMs) && fields.durationMs >= 0) record.durationMs = fields.durationMs;
1637    if (typeof fields.spanId === "string" && /^[0-9a-f]{16}$/.test(fields.spanId) && !/^0+$/.test(fields.spanId)) record.spanId = fields.spanId;
1638    if (typeof fields.parentSpanId === "string" && /^[0-9a-f]{16}$/.test(fields.parentSpanId) && !/^0+$/.test(fields.parentSpanId)) record.parentSpanId = fields.parentSpanId;
1639    if (typeof fields.spanName === "string" && /^[A-Za-z][A-Za-z0-9._:-]{0,127}$/.test(fields.spanName)) record.spanName = fields.spanName;
1640    if (typeof fields.driver === "string" && new Set(["memory", "postgres", "redis"]).has(fields.driver)) record.driver = fields.driver;
1641    if (typeof fields.kind === "string" && new Set(["invalidation", "presence"]).has(fields.kind)) record.kind = fields.kind;
1642    if (typeof fields.agentSemanticId === "string" && NOXID_SEMANTIC_ID.test(fields.agentSemanticId)) record.agentSemanticId = fields.agentSemanticId;
1643    if (typeof fields.actingPrincipal === "string" && NOXID_PRINCIPAL.test(fields.actingPrincipal)) record.actingPrincipal = fields.actingPrincipal;
1644    if (typeof fields.dataTable === "string" && NOXID_DATA_IDENTIFIER.test(fields.dataTable)) record.dataTable = fields.dataTable;
1645    if (typeof fields.scopeColumn === "string" && NOXID_DATA_IDENTIFIER.test(fields.scopeColumn)) record.scopeColumn = fields.scopeColumn;
1646    // WO-30 model spans. Identity, tokens and retries only: a prompt or a
1647    // completion is never presented to this serializer, so it cannot leak.
1648    if (typeof fields.model === "string" && NOXID_MODEL_NAME.test(fields.model)) record.model = fields.model;
1649    if (typeof fields.modelProvider === "string" && NOXID_MODEL_PROVIDERS.has(fields.modelProvider)) record.modelProvider = fields.modelProvider;
1650    if (typeof fields.modelId === "string" && NOXID_MODEL_ID.test(fields.modelId)) record.modelId = fields.modelId;
1651    if (Number.isSafeInteger(fields.tokensInput) && fields.tokensInput >= 0) record.tokensInput = fields.tokensInput;
1652    if (Number.isSafeInteger(fields.tokensOutput) && fields.tokensOutput >= 0) record.tokensOutput = fields.tokensOutput;
1653    if (Number.isSafeInteger(fields.modelRetries) && fields.modelRetries >= 0 && fields.modelRetries <= 5) record.modelRetries = fields.modelRetries;
1654    // WO-31 agent spans. `agent` is the declared agent name (`noxid.agent`),
1655    // `agentRun` the run id (`noxid.agent.run`), `agentTurn` the zero-based
1656    // turn index (`noxid.agent.turn`), and `toolEndpoint` the dispatched
1657    // endpoint's semantic id (`noxid.tool.endpoint`). Token counts reuse the
1658    // model fields above. No prompt, instruction, argument, or tool-result
1659    // content is ever presented to this serializer.
1660    if (typeof fields.agent === "string" && NOXID_AGENT_NAME.test(fields.agent)) record.agent = fields.agent;
1661    if (typeof fields.agentRun === "string" && NOXID_AGENT_RUN.test(fields.agentRun)) record.agentRun = fields.agentRun;
1662    if (Number.isSafeInteger(fields.agentTurn) && fields.agentTurn >= 0) record.agentTurn = fields.agentTurn;
1663    if (typeof fields.toolEndpoint === "string" && NOXID_SEMANTIC_ID.test(fields.toolEndpoint)) record.toolEndpoint = fields.toolEndpoint;
1664  }
1665  if (record.parentSpanId === undefined && event !== "request.start" && event !== "request.end" && typeof trace.requestSpanId === "string") {
1666    record.parentSpanId = trace.requestSpanId;
1667  }
1668  /* noxid-server:trace-export */
1669  /* noxid-server:development-trace-capture */
1670}
1671
1672function __noxidActingPrincipal(principal) {
1673  return principal?.scope === null ? "system" : typeof principal?.scope === "string" ? `session:${__noxidPrincipalPart(principal.scope)}` : null;
1674}
1675
1676function __noxidTraceBindPrincipal(context, principal) {
1677  if (principal?.kind !== "agent" || typeof principal.agent !== "string") return;
1678  const trace = __noxidTraceForRequest(context?.request);
1679  if (trace === null) return;
1680  trace.agentSemanticId = principal.agent;
1681  trace.actingPrincipal = __noxidActingPrincipal(principal);
1682  // The acting identity belongs to the trace, not to the scope that happened to
1683  // learn it. An agent door binds inside its endpoint subrequest, so without
1684  // this the request's own start and end records would lose the identity the
1685  // door was dispatched under (evaluator ruling 2026-09-03).
1686  trace.shared.agentSemanticId = trace.agentSemanticId;
1687  trace.shared.actingPrincipal = trace.actingPrincipal;
1688}
1689
1690function __noxidTraceDataAccess(context, fields) {
1691  if (tracingMode !== "full") return;
1692  const principal = context?.principal;
1693  const requestTrace = __noxidTraceForRequest(context?.request);
1694  const trace = requestTrace ?? __noxidTraceContext(
1695    typeof context?.traceId === "string" && NOXID_TRACE_ID.test(context.traceId) ? context.traceId : null,
1696  );
1697  if (principal?.kind === "agent" && typeof principal.agent === "string") {
1698    trace.agentSemanticId = principal.agent;
1699    trace.actingPrincipal = __noxidActingPrincipal(principal);
1700  }
1701  __noxidTraceEmit(trace, "data.access", {
1702    semanticId: context?.semanticId,
1703    agentSemanticId: principal?.agent,
1704    actingPrincipal: principal?.kind === "agent" ? __noxidActingPrincipal(principal) : null,
1705    dataTable: fields?.table,
1706    scopeColumn: fields?.principalColumn,
1707  });
1708}
1709
1710function __noxidTraceForRequest(request) {
1711  return request !== null && typeof request === "object" ? __noxidRequestTraces.get(request) ?? null : null;
1712}
1713
1714function __noxidTraceRoute(request, route) {
1715  const trace = __noxidTraceForRequest(request);
1716  if (trace !== null && typeof route === "string" && NOXID_ROUTE.test(route)) trace.route = route;
1717}
1718
1719function __noxidTraceIdForRequest(request) {
1720  return __noxidTraceForRequest(request)?.id ?? null;
1721}
1722
1723export function noxidTraceId(request) {
1724  return __noxidTraceIdForRequest(request);
1725}
1726
1727export function inheritNoxidRequestTrace(parent, child, semanticId = null) {
1728  const parentTrace = __noxidTraceForRequest(parent);
1729  if (parentTrace === null || child === null || typeof child !== "object") return;
1730  const trace = __noxidTraceContext({
1731    id: parentTrace.id,
1732    // The subrequest is a nested scope of the same trace, so it shares the
1733    // parent's sequence counter and deferred start rather than starting its own.
1734    shared: parentTrace.shared,
1735    parentSpanId: parentTrace.requestSpanId ?? parentTrace.parentSpanId,
1736    traceFlags: parentTrace.traceFlags,
1737  });
1738  trace.inheritedRequest = true;
1739  if (typeof semanticId === "string" && NOXID_SEMANTIC_ID.test(semanticId)) trace.subrequestSemanticId = semanticId;
1740  if (typeof parentTrace.agentSemanticId === "string") trace.agentSemanticId = parentTrace.agentSemanticId;
1741  if (typeof parentTrace.actingPrincipal === "string") trace.actingPrincipal = parentTrace.actingPrincipal;
1742  __noxidRequestTraces.set(child, trace);
1743}
1744
1745function __noxidTraceBeginSemantic(request) {
1746  let trace = __noxidTraceForRequest(request);
1747  if (trace === null && tracingMode === "full") trace = __noxidTraceContext();
1748  return __noxidTraceBeginSpan(trace);
1749}
1750
1751function __noxidTraceFinishSemantic(span, event, semanticId, fields = null) {
1752  __noxidTraceFinishSpan(span, event, fields === null ? { semanticId } : { semanticId, ...fields });
1753}
1754
1755function __noxidTraceSemantic(request, event, semanticId, fields = null) {
1756  const span = __noxidTraceBeginSemantic(request);
1757  __noxidTraceFinishSemantic(span, event, semanticId, fields);
1758  return span?.trace?.id ?? null;
1759}
1760
1761export function traceNoxidSemantic(request, event, semanticId) {
1762  if (!new Set(["middleware", "endpoint", "action", "task"]).has(event)) return null;
1763  return __noxidTraceSemantic(request, event, semanticId);
1764}
1765
1766export function traceNoxidFailure(response, code, semanticId) {
1767  return __noxidTraceFailure(response, code, semanticId);
1768}
1769
1770function __noxidTraceFailure(response, code, semanticId, capability = null) {
1771  if (response !== null && typeof response === "object") {
1772    __noxidFailureSpans.set(response, { code, semanticId, capability, emitted: false });
1773  }
1774  return response;
1775}
1776
1777function __noxidTraceCopyFailure(source, target) {
1778  const failure = __noxidFailureSpans.get(source);
1779  if (failure !== undefined && target !== null && typeof target === "object") __noxidFailureSpans.set(target, failure);
1780  return target;
1781}
1782
1783function __noxidTraceResponseFailure(request, response) {
1784  const failure = __noxidFailureSpans.get(response);
1785  if (failure === undefined || failure.emitted) return;
1786  failure.emitted = true;
1787  const capability = (failure.code.includes("CAPABILITY") && failure.code.endsWith("_DENIED")) || failure.code === "CACHE_INVALIDATION_DENIED";
1788  __noxidTraceEmit(__noxidTraceForRequest(request), capability ? "capability.denied" : "validation.refused", failure);
1789}
1790
1791function __noxidTraceFinishRequest(trace, event, status) {
1792  __noxidTraceEmit(trace, event, {
1793    status,
1794    method: trace.requestMethod,
1795    semanticId: trace.subrequestSemanticId,
1796    durationMs: __noxidTraceDuration(trace.requestStartedAt),
1797    spanId: trace.requestSpanId,
1798    parentSpanId: trace.requestParentSpanId,
1799    spanName: event === "request.end" ? "request" : event,
1800  });
1801}
1802
1803function __noxidTraceStreamingResponse(request, trace, response, event = "request.end") {
1804  const reader = response.body.getReader();
1805  let finished = false;
1806  const finish = (status) => {
1807    if (finished) return;
1808    finished = true;
1809    __noxidTraceFinishRequest(trace, event, status);
1810    if (request !== null && typeof request === "object") __noxidRequestTraces.delete(request);
1811  };
1812  const body = new ReadableStream({
1813    async pull(controller) {
1814      try {
1815        const chunk = await reader.read();
1816        if (chunk.done) {
1817          finish(response.status);
1818          controller.close();
1819        } else {
1820          controller.enqueue(chunk.value);
1821        }
1822      } catch (cause) {
1823        finish(500);
1824        controller.error(cause);
1825      }
1826    },
1827    async cancel(reason) {
1828      try { await reader.cancel(reason); }
1829      finally { finish(response.status); }
1830    },
1831  });
1832  return __noxidTraceCopyFailure(response, new Response(body, { status: response.status, statusText: response.statusText, headers: response.headers }));
1833}
1834
1835export async function withNoxidRequestTrace(request, operation) {
1836  const inherited = __noxidTraceForRequest(request);
1837  if (inherited !== null) {
1838    if (inherited.requestActive === true || inherited.inheritedRequest !== true) {
1839      const response = await operation(inherited);
1840      __noxidTraceResponseFailure(request, response);
1841      return response;
1842    }
1843    inherited.requestActive = true;
1844    inherited.requestStartedAt = __noxidTraceNow();
1845    inherited.requestSpanId = __noxidTraceNewSpanId();
1846    inherited.requestParentSpanId = inherited.parentSpanId;
1847    try { inherited.requestMethod = request.method.toUpperCase(); } catch {}
1848    const event = typeof inherited.subrequestSemanticId === "string" && inherited.subrequestSemanticId.startsWith("route-loader:") ? "loader" : "subrequest";
1849    let streaming = false;
1850    try {
1851      const response = await operation(inherited);
1852      __noxidTraceResponseFailure(request, response);
1853      streaming = response?.body !== null && (response?.headers?.get("x-noxid-ssr-stream") === "1" || response?.headers?.get("content-type")?.toLowerCase().startsWith("text/event-stream"));
1854      if (streaming) return __noxidTraceStreamingResponse(request, inherited, response, event);
1855      __noxidTraceFinishRequest(inherited, event, response?.status);
1856      return response;
1857    } catch (cause) {
1858      __noxidTraceFinishRequest(inherited, event, 500);
1859      throw cause;
1860    } finally {
1861      if (!streaming && request !== null && typeof request === "object") __noxidRequestTraces.delete(request);
1862    }
1863  }
1864  if (tracingMode === "off") return operation(null);
1865  const trace = __noxidTraceContext(__noxidRequestTraceContext(request));
1866  trace.requestActive = true;
1867  trace.requestStartedAt = __noxidTraceNow();
1868  trace.requestSpanId = __noxidTraceNewSpanId();
1869  trace.requestParentSpanId = trace.parentSpanId;
1870  if (request !== null && typeof request === "object") __noxidRequestTraces.set(request, trace);
1871  let method = null;
1872  let deferredAgentStart = false;
1873  let streaming = false;
1874  try { method = request.method.toUpperCase(); } catch {}
1875  trace.requestMethod = method;
1876  try { deferredAgentStart = new URL(request.url).pathname.endsWith("/_noxid/mcp"); } catch {}
1877  if (deferredAgentStart) trace.shared.pendingStartMethod = method;
1878  else __noxidTraceEmit(trace, "request.start", { method });
1879  try {
1880    const response = await operation(trace);
1881    __noxidTraceResponseFailure(request, response);
1882    streaming = response?.body !== null && (response?.headers?.get("x-noxid-ssr-stream") === "1" || response?.headers?.get("content-type")?.toLowerCase().startsWith("text/event-stream"));
1883    if (streaming) return __noxidTraceStreamingResponse(request, trace, response);
1884    __noxidTraceFinishRequest(trace, "request.end", response?.status);
1885    return response;
1886  } catch (cause) {
1887    __noxidTraceFinishRequest(trace, "request.end", 500);
1888    throw cause;
1889  } finally {
1890    // Streaming responses retain request ownership until their body closes or
1891    // is cancelled; ordinary responses release it here.
1892    if (!streaming && request !== null && typeof request === "object") __noxidRequestTraces.delete(request);
1893  }
1894}
1895"##;
1896
1897const PUBSUB_RUNTIME: &str = r##"
1898const NOXID_PUBSUB_SCHEMA = "noxid.pubsub.v1";
1899const NOXID_PUBSUB_KINDS = new Set(["invalidation", "presence"]);
1900const NOXID_PUBSUB_CHANNEL = `noxid_pubsub_v1_${applicationNamespace}`;
1901const NOXID_PUBSUB_MAX_BYTES = 7_000;
1902const __noxidMemoryPubSubKey = Symbol.for("noxid.pubsub.memory.v1");
1903
1904function __noxidPubSubNow() {
1905  try { return performance.now(); } catch { return Date.now(); }
1906}
1907
1908function __noxidPubSubDuration(trace, event, startedAt, envelope, state) {
1909  __noxidTraceEmit(trace, event, {
1910    semanticId: envelope.semanticId,
1911    driver: pubSubDriver,
1912    kind: envelope.kind,
1913    state,
1914    durationMs: Math.max(0, __noxidPubSubNow() - startedAt),
1915  });
1916}
1917
1918function __noxidPubSubFailure(code, message, cause) {
1919  return Object.assign(new Error(message, cause === undefined ? undefined : { cause }), { code });
1920}
1921
1922function __noxidPubSubPrincipal(principal) {
1923  if (principal === null || typeof principal !== "object" || !__noxidPrincipalValues.has(principal)) {
1924    throw __noxidPubSubFailure("PUBSUB_PRINCIPAL_INVALID", "live events require a compiler-owned canonical principal");
1925  }
1926  let canonical;
1927  try { canonical = principal.canonical; } catch {}
1928  return __noxidPubSubCanonical(canonical);
1929}
1930
1931function __noxidPubSubCanonical(canonical) {
1932  if (typeof canonical !== "string" || canonical.length === 0 || canonical.length > 1024) {
1933    throw __noxidPubSubFailure("PUBSUB_PRINCIPAL_INVALID", "live events require the stable canonical principal serialization from the request context");
1934  }
1935  return canonical;
1936}
1937
1938function __noxidPubSubIdentity(kind, semanticId, principal) {
1939  if (!NOXID_PUBSUB_KINDS.has(kind)) throw __noxidPubSubFailure("PUBSUB_KIND_INVALID", "compiler-owned pub/sub kind is invalid");
1940  if (typeof semanticId !== "string" || !NOXID_SEMANTIC_ID.test(semanticId)) throw __noxidPubSubFailure("PUBSUB_SEMANTIC_ID_INVALID", "compiler-owned pub/sub semantic id is invalid");
1941  return Object.freeze({ kind, semanticId, principal: __noxidPubSubPrincipal(principal) });
1942}
1943
1944export function __noxidPubSubTopic(kind, semanticId, principal) {
1945  const identity = __noxidPubSubIdentity(kind, semanticId, principal);
1946  return __noxidPubSubTopicFromCanonical(identity.kind, identity.semanticId, identity.principal);
1947}
1948
1949function __noxidPubSubTopicFromCanonical(kind, semanticId, principal) {
1950  __noxidPubSubCanonical(principal);
1951  return `noxid:live:v1:${applicationNamespace}:${kind}:${encodeURIComponent(semanticId)}:${encodeURIComponent(principal)}`;
1952}
1953
1954function __noxidPubSubEventId(value) {
1955  if (value === undefined) {
1956    try { value = globalThis.crypto?.randomUUID?.().replaceAll("-", "_"); } catch {}
1957  }
1958  if (typeof value !== "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(value)) {
1959    throw __noxidPubSubFailure("PUBSUB_EVENT_ID_INVALID", "compiler-owned pub/sub event id must be a bounded stable identifier");
1960  }
1961  return value;
1962}
1963
1964export function __noxidPubSubEvent(kind, semanticId, principal, body = null, id = undefined) {
1965  const identity = __noxidPubSubIdentity(kind, semanticId, principal);
1966  return __noxidPubSubEventFromCanonical(identity.kind, identity.semanticId, identity.principal, body, id);
1967}
1968
1969function __noxidPubSubEventFromCanonical(kind, semanticId, principal, body = null, id = undefined) {
1970  if (!NOXID_PUBSUB_KINDS.has(kind) || typeof semanticId !== "string" || !NOXID_SEMANTIC_ID.test(semanticId)) {
1971    throw __noxidPubSubFailure("PUBSUB_EVENT_DRIFT", "compiler-owned pub/sub event identity is invalid");
1972  }
1973  principal = __noxidPubSubCanonical(principal);
1974  if (kind === "invalidation" && body !== null) {
1975    throw __noxidPubSubFailure("PUBSUB_INVALIDATION_PAYLOAD", "live invalidations carry no payload; publish only the resource identity and refetch through its validated path");
1976  }
1977  const event = Object.freeze({
1978    schema: NOXID_PUBSUB_SCHEMA,
1979    application: applicationNamespace,
1980    id: __noxidPubSubEventId(id),
1981    kind,
1982    semanticId,
1983    principal,
1984    body,
1985  });
1986  let encoded;
1987  try { encoded = JSON.stringify(event); }
1988  catch (cause) { throw __noxidPubSubFailure("PUBSUB_EVENT_INVALID", "compiler-owned pub/sub event must be serializable typed data", cause); }
1989  if (new TextEncoder().encode(encoded).byteLength > NOXID_PUBSUB_MAX_BYTES) throw __noxidPubSubFailure("PUBSUB_EVENT_TOO_LARGE", "compiler-owned pub/sub event exceeds the 7000-byte cross-driver bound");
1990  return event;
1991}
1992
1993function __noxidPubSubDecode(raw) {
1994  let value;
1995  try { value = typeof raw === "string" ? JSON.parse(raw) : raw; }
1996  catch (cause) { throw __noxidPubSubFailure("PUBSUB_EVENT_DRIFT", "pub/sub delivered malformed JSON", cause); }
1997  if (value === null || typeof value !== "object" || Array.isArray(value) || value.schema !== NOXID_PUBSUB_SCHEMA || value.application !== applicationNamespace) {
1998    throw __noxidPubSubFailure("PUBSUB_EVENT_DRIFT", "pub/sub delivered an unsupported event envelope");
1999  }
2000  if (!NOXID_PUBSUB_KINDS.has(value.kind) || typeof value.semanticId !== "string" || !NOXID_SEMANTIC_ID.test(value.semanticId)) {
2001    throw __noxidPubSubFailure("PUBSUB_EVENT_DRIFT", "pub/sub delivered an invalid typed event identity");
2002  }
2003  const identity = Object.freeze({ kind: value.kind, semanticId: value.semanticId, principal: __noxidPubSubCanonical(value.principal) });
2004  if (value.kind === "invalidation" && value.body !== null) throw __noxidPubSubFailure("PUBSUB_EVENT_DRIFT", "pub/sub delivered an invalidation payload");
2005  return Object.freeze({ schema: NOXID_PUBSUB_SCHEMA, application: applicationNamespace, id: __noxidPubSubEventId(value.id), ...identity, body: value.body ?? null });
2006}
2007
2008function __noxidMemoryPubSub() {
2009  let registry = globalThis[__noxidMemoryPubSubKey];
2010  if (!(registry instanceof Map)) {
2011    registry = new Map();
2012    Object.defineProperty(globalThis, __noxidMemoryPubSubKey, { value: registry, configurable: true });
2013  }
2014  return Object.freeze({
2015    async publish(topic, encoded) {
2016      for (const receive of [...(registry.get(topic) ?? [])]) receive(encoded);
2017    },
2018    async subscribe(topic, receive) {
2019      let subscribers = registry.get(topic);
2020      if (subscribers === undefined) registry.set(topic, subscribers = new Set());
2021      subscribers.add(receive);
2022      return async () => {
2023        subscribers.delete(receive);
2024        if (subscribers.size === 0) registry.delete(topic);
2025      };
2026    },
2027  });
2028}
2029
2030let __noxidPubSubPostgresPromise;
2031async function __noxidPostgresPubSub() {
2032  if (__noxidPubSubPostgresPromise !== undefined) return __noxidPubSubPostgresPromise;
2033  __noxidPubSubPostgresPromise = (async () => {
2034    const url = queueDatabaseUrl();
2035    if (url === null) throw __noxidPubSubFailure("PUBSUB_POSTGRES_URL_REQUIRED", "DATABASE_URL is required for the Postgres live publisher");
2036    let postgres;
2037    try { postgres = (await import("postgres")).default; }
2038    catch { throw __noxidPubSubFailure("PUBSUB_POSTGRES_DRIVER_MISSING", "the admitted Postgres driver is unavailable for live publishing"); }
2039    const sql = postgres(url, { max: Math.max(1, Math.min(databasePoolSize, 2)) });
2040    const subscribers = new Map();
2041    let listening;
2042    const ensureListening = async () => {
2043      if (listening === undefined) {
2044        listening = Promise.resolve(sql.listen(NOXID_PUBSUB_CHANNEL, (encoded) => {
2045          let decoded;
2046          try { decoded = __noxidPubSubDecode(encoded); } catch { return; }
2047          const topic = __noxidPubSubTopicFromCanonical(decoded.kind, decoded.semanticId, decoded.principal);
2048          for (const receive of [...(subscribers.get(topic) ?? [])]) receive(encoded);
2049        }));
2050      }
2051      await listening;
2052    };
2053    const stopListeningIfIdle = async () => {
2054      if (subscribers.size !== 0 || listening === undefined) return;
2055      const active = listening;
2056      listening = undefined;
2057      const listener = await active;
2058      if (typeof listener === "function") await listener();
2059      else if (typeof listener?.unlisten === "function") await listener.unlisten();
2060    };
2061    return Object.freeze({
2062      async publish(_topic, encoded) {
2063        await sql.notify(NOXID_PUBSUB_CHANNEL, encoded);
2064      },
2065      async subscribe(topic, receive) {
2066        await ensureListening();
2067        let listeners = subscribers.get(topic);
2068        if (listeners === undefined) subscribers.set(topic, listeners = new Set());
2069        listeners.add(receive);
2070        return async () => {
2071          listeners.delete(receive);
2072          if (listeners.size === 0) subscribers.delete(topic);
2073          await stopListeningIfIdle();
2074        };
2075      },
2076    });
2077  })();
2078  try { return await __noxidPubSubPostgresPromise; }
2079  catch (cause) { __noxidPubSubPostgresPromise = undefined; throw cause; }
2080}
2081
2082function __noxidRedisPubSub() {
2083  if (typeof __noxidRedisPubSubPublish !== "function" || typeof __noxidRedisPubSubSubscribe !== "function") {
2084    throw __noxidPubSubFailure("PUBSUB_REDIS_DRIVER_MISSING", "the admitted WO-39 RESP pub/sub extension is unavailable");
2085  }
2086  return Object.freeze({
2087    publish: (topic, encoded) => __noxidRedisPubSubPublish(topic, encoded),
2088    subscribe: (topic, receive) => __noxidRedisPubSubSubscribe(topic, receive),
2089  });
2090}
2091
2092let __noxidPubSubAdapterPromise;
2093async function __noxidPubSubAdapter() {
2094  if (__noxidPubSubAdapterPromise === undefined) {
2095    __noxidPubSubAdapterPromise = Promise.resolve(
2096      pubSubDriver === "memory" ? __noxidMemoryPubSub()
2097        : pubSubDriver === "postgres" ? __noxidPostgresPubSub()
2098          : pubSubDriver === "redis" ? __noxidRedisPubSub()
2099            : Promise.reject(__noxidPubSubFailure("PUBSUB_DRIVER_INVALID", "compiler emitted an unsupported pub/sub driver")),
2100    );
2101  }
2102  return __noxidPubSubAdapterPromise;
2103}
2104
2105export async function __noxidPubSubPublish(event) {
2106  const envelope = __noxidPubSubDecode(event);
2107  const topic = __noxidPubSubTopicFromCanonical(envelope.kind, envelope.semanticId, envelope.principal);
2108  const encoded = JSON.stringify(envelope);
2109  const trace = tracingMode === "full" ? __noxidTraceContext() : null;
2110  const startedAt = __noxidPubSubNow();
2111  try {
2112    await (await __noxidPubSubAdapter()).publish(topic, encoded);
2113    __noxidPubSubDuration(trace, "pubsub.publish", startedAt, envelope, "delivered");
2114  } catch (cause) {
2115    __noxidPubSubDuration(trace, "pubsub.publish", startedAt, envelope, "retry");
2116    throw cause;
2117  }
2118}
2119
2120export async function __noxidPubSubSubscribe(kind, semanticId, principal, deliver, options = Object.create(null)) {
2121  if (typeof deliver !== "function") throw __noxidPubSubFailure("PUBSUB_SUBSCRIBER_INVALID", "pub/sub subscriber must be a compiler-owned delivery function");
2122  const identity = __noxidPubSubIdentity(kind, semanticId, principal);
2123  const topic = __noxidPubSubTopic(kind, semanticId, principal);
2124  const schedule = typeof options.schedule === "function" ? options.schedule : globalThis.setTimeout;
2125  const cancel = typeof options.cancel === "function" ? options.cancel : globalThis.clearTimeout;
2126  const coalesceKey = typeof options.coalesceKey === "function"
2127    ? options.coalesceKey
2128    : (event) => event.kind === "invalidation" ? `${event.kind}:${event.semanticId}:${event.principal}` : event.id;
2129  const pending = new Map();
2130  let stopped = false;
2131  const run = async (key) => {
2132    const entry = pending.get(key);
2133    if (entry === undefined || stopped) return;
2134    entry.timer = null;
2135    const version = entry.version;
2136    const trace = tracingMode === "full" ? __noxidTraceContext() : null;
2137    const startedAt = __noxidPubSubNow();
2138    try {
2139      await deliver(entry.event);
2140      __noxidPubSubDuration(trace, "pubsub.deliver", startedAt, entry.event, "delivered");
2141      if (entry.version === version) pending.delete(key);
2142      else entry.timer = schedule(() => run(key), pubSubCoalescingMs);
2143    } catch {
2144      __noxidPubSubDuration(trace, "pubsub.deliver", startedAt, entry.event, "retry");
2145      if (!stopped) entry.timer = schedule(() => run(key), pubSubCoalescingMs);
2146    }
2147  };
2148  const receive = (raw) => {
2149    if (stopped) return;
2150    let event;
2151    try { event = __noxidPubSubDecode(raw); } catch { return; }
2152    if (event.kind !== identity.kind || event.semanticId !== identity.semanticId || event.principal !== identity.principal) return;
2153    let key;
2154    try { key = coalesceKey(event); } catch { return; }
2155    if (typeof key !== "string" || key.length === 0 || key.length > 2048) return;
2156    const entry = pending.get(key);
2157    if (entry === undefined) {
2158      const created = { event, version: 1, timer: null };
2159      created.timer = schedule(() => run(key), pubSubCoalescingMs);
2160      pending.set(key, created);
2161    } else {
2162      entry.event = event;
2163      entry.version += 1;
2164    }
2165  };
2166  const unsubscribe = await (await __noxidPubSubAdapter()).subscribe(topic, receive);
2167  return async () => {
2168    if (stopped) return;
2169    stopped = true;
2170    for (const entry of pending.values()) if (entry.timer !== null) cancel(entry.timer);
2171    pending.clear();
2172    await unsubscribe();
2173  };
2174}
2175"##;
2176
2177/// The multipart parser's ceilings, emitted from `noxid_ir` rather than
2178/// written twice. The security manifest publishes these same constants, so a
2179/// change to the parser's bound changes the audit artefact in the same
2180/// commit — there is no way to move one without the other.
2181fn endpoint_multipart_limits() -> String {
2182    format!(
2183        "const ENDPOINT_MULTIPART_HEADER_MAX_BYTES = {};\nconst ENDPOINT_MULTIPART_SCALAR_MAX_BYTES = {};\nconst ENDPOINT_MULTIPART_MAX_PARTS = {};\nconst ENDPOINT_UPLOAD_NAME_MAX_BYTES = {};\n",
2184        noxid_ir::MULTIPART_PART_HEADER_MAX_BYTES,
2185        noxid_ir::MULTIPART_SCALAR_MAX_BYTES,
2186        noxid_ir::MULTIPART_MAX_PARTS,
2187        noxid_ir::UPLOAD_FILENAME_MAX_BYTES,
2188    )
2189}
2190
2191const ENDPOINT_RUNTIME: &str = r##"const endpointRateStorage = typeof __noxidStorage === "function" ? __noxidStorage("noxid:endpoint-rate") : null;
2192const endpointIdempotencyStorage = typeof __noxidStorage === "function" ? __noxidStorage("noxid:endpoint-idempotency") : null;
2193const endpointRateLocks = new Map();
2194const endpointIdempotencyLocks = new Map();
2195const endpointIdempotencyInFlight = new Map();
2196const endpointStreamHistories = new Map();
2197const ENDPOINT_RATE_BUCKET_MAX_ENTRIES = 10_000;
2198const ENDPOINT_IDEMPOTENCY_MAX_ENTRIES = 1024;
2199const ENDPOINT_IDEMPOTENCY_TTL_MS = 86_400_000;
2200const ENDPOINT_STREAM_HEARTBEAT_MS = 15_000;
2201const ENDPOINT_STREAM_MAX_HISTORIES = 128;
2202const ENDPOINT_STREAM_MAX_EVENTS = 256;
2203const ENDPOINT_STREAM_MAX_EVENT_BYTES = 262_144;
2204const ENDPOINT_STREAM_MAX_HISTORY_BYTES = 1_048_576;
2205
2206function streamError(schema, code, message, details = null) {
2207  return Object.freeze({ ok: false, error: Object.freeze({ code, message, semanticId: schema.id, details }) });
2208}
2209
2210function streamFrame(event, data, id = null) {
2211  const lines = [];
2212  if (id !== null) lines.push(`id: ${id}`);
2213  lines.push(`event: ${event}`);
2214  const encoded = JSON.stringify(data);
2215  for (const line of encoded.split("\n")) lines.push(`data: ${line}`);
2216  return `${lines.join("\n")}\n\n`;
2217}
2218
2219function streamErrorFrame(schema, code, message, details = null) {
2220  return streamFrame("noxid-error", streamError(schema, code, message, details));
2221}
2222
2223function parseStreamResumeId(value) {
2224  if (value === null) return null;
2225  if (value.length > 256) return Object.freeze({ invalid: true });
2226  const match = /^([A-Za-z0-9_-]{16,128}):([1-9][0-9]{0,15})$/.exec(value);
2227  if (match === null) return Object.freeze({ invalid: true });
2228  const sequence = Number(match[2]);
2229  return Number.isSafeInteger(sequence)
2230    ? Object.freeze({ token: match[1], sequence })
2231    : Object.freeze({ invalid: true });
2232}
2233
2234function newStreamToken() {
2235  if (typeof globalThis.crypto?.randomUUID !== "function" || typeof globalThis.crypto?.subtle?.digest !== "function") return null;
2236  return globalThis.crypto.randomUUID().replace(/-/g, "_");
2237}
2238
2239function stableStreamRequestValue(value, seen = new Set()) {
2240  if (value === null) return "null";
2241  if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
2242  if (typeof value === "number" && Number.isFinite(value)) return JSON.stringify(value);
2243  if (typeof value !== "object" || seen.has(value)) throw new Error("unsupported stream request value");
2244  seen.add(value);
2245  let output;
2246  if (Array.isArray(value)) {
2247    output = `[${value.map((item) => stableStreamRequestValue(item, seen)).join(",")}]`;
2248  } else {
2249    output = `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStreamRequestValue(value[key], seen)}`).join(",")}}`;
2250  }
2251  seen.delete(value);
2252  return output;
2253}
2254
2255function endpointStreamPolicyIdentity(request, environment, middlewareContext) {
2256  const session = middlewareContext?.sessionId ?? middlewareContext?.session?.id ?? environment?.sessionId;
2257  if (typeof session === "string" && session.length > 0) return `session:${session}`;
2258  const ip = environment?.requestIdentity?.ip ?? environment?.ip ?? request.headers.get("cf-connecting-ip") ?? request.headers.get("x-real-ip");
2259  return typeof ip === "string" && ip.length > 0 ? `ip:${ip}` : null;
2260}
2261
2262async function endpointStreamRequestBinding(token, request, args, environment, middlewareContext) {
2263  const identity = endpointStreamPolicyIdentity(request, environment, middlewareContext);
2264  const contract = stableStreamRequestValue(args);
2265  const bytes = new TextEncoder().encode(`${token}\n${identity ?? "token-possession"}\n${contract}`);
2266  const digest = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", bytes));
2267  let binary = "";
2268  for (const byte of digest) binary += String.fromCharCode(byte);
2269  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2270}
2271
2272function reserveStreamHistory(schema, token, requestBinding) {
2273  while (endpointStreamHistories.size >= ENDPOINT_STREAM_MAX_HISTORIES) {
2274    const removable = [...endpointStreamHistories.values()].find((history) => history.completed);
2275    if (!removable) return null;
2276    endpointStreamHistories.delete(removable.token);
2277  }
2278  const history = { token, schemaId: schema.id, requestBinding, events: [], bytes: 0, nextSequence: 1, completed: false, terminal: null };
2279  endpointStreamHistories.set(token, history);
2280  return history;
2281}
2282
2283function recordStreamEvent(history, frame, bytes) {
2284  const sequence = history.nextSequence;
2285  history.nextSequence += 1;
2286  history.events.push(Object.freeze({ sequence, frame, bytes }));
2287  history.bytes += bytes;
2288  while (history.events.length > ENDPOINT_STREAM_MAX_EVENTS || history.bytes > ENDPOINT_STREAM_MAX_HISTORY_BYTES) {
2289    const removed = history.events.shift();
2290    history.bytes -= removed.bytes;
2291  }
2292  return sequence;
2293}
2294
2295function endpointFullPath(schema) {
2296  return applicationBasePath === "/" ? schema.path : `${applicationBasePath.replace(/\/$/, "")}${schema.path}`;
2297}
2298
2299function endpointSegments(pathname) {
2300  const segments = pathname.split("/");
2301  if (segments[0] === "") segments.shift();
2302  return segments;
2303}
2304
2305function matchEndpointPath(schema, pathname) {
2306  const expected = endpointSegments(endpointFullPath(schema));
2307  const actual = endpointSegments(pathname);
2308  if (expected.length !== actual.length) return Object.freeze({ kind: "miss" });
2309  const params = Object.create(null);
2310  let malformed = false;
2311  for (let index = 0; index < expected.length; index += 1) {
2312    const segment = expected[index];
2313    if (segment.startsWith("[") && segment.endsWith("]")) {
2314      let value;
2315      try { value = decodeURIComponent(actual[index]); }
2316      catch { malformed = true; continue; }
2317      if (value.length === 0) return Object.freeze({ kind: "miss" });
2318      params[segment.slice(1, -1)] = value;
2319    } else {
2320      let value;
2321      try { value = decodeURIComponent(actual[index]); }
2322      catch { return Object.freeze({ kind: "miss" }); }
2323      if (value !== segment) return Object.freeze({ kind: "miss" });
2324    }
2325  }
2326  return malformed
2327    ? Object.freeze({ kind: "malformed" })
2328    : Object.freeze({ kind: "match", params: Object.freeze(params) });
2329}
2330
2331function endpointMatches(pathname) {
2332  const matches = [];
2333  const malformed = [];
2334  for (const schema of endpointSchemas) {
2335    const candidate = matchEndpointPath(schema, pathname);
2336    if (candidate.kind === "match") matches.push(Object.freeze({ schema, params: candidate.params }));
2337    if (candidate.kind === "malformed") malformed.push(schema);
2338  }
2339  matches.sort((left, right) => {
2340    const leftDynamic = (left.schema.path.match(/\[/g) ?? []).length;
2341    const rightDynamic = (right.schema.path.match(/\[/g) ?? []).length;
2342    return leftDynamic - rightDynamic || left.schema.path.localeCompare(right.schema.path) || left.schema.method.localeCompare(right.schema.method);
2343  });
2344  malformed.sort((left, right) => left.path.localeCompare(right.path) || left.method.localeCompare(right.method));
2345  return Object.freeze({ matches: Object.freeze(matches), malformed: Object.freeze(malformed) });
2346}
2347
2348function endpointValidator(id) {
2349  const validator = typeValidators[id];
2350  return typeof validator === "function" ? validator : null;
2351}
2352
2353function isStrictUtcIsoDate(value) {
2354  if (typeof value !== "string") return false;
2355  const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?Z$/.exec(value);
2356  if (match === null) return false;
2357  const year = Number(match[1]);
2358  const month = Number(match[2]);
2359  const day = Number(match[3]);
2360  const hour = Number(match[4]);
2361  const minute = Number(match[5]);
2362  const second = Number(match[6]);
2363  if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59) return false;
2364  const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
2365  const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
2366  return day >= 1 && day <= days[month - 1];
2367}
2368
2369function optionalWireType(type) {
2370  return type.startsWith("Optional<") && type.endsWith(">") ? type.slice(9, -1) : null;
2371}
2372
2373function arrayWireType(type) {
2374  const optional = optionalWireType(type);
2375  if (optional !== null) return arrayWireType(optional);
2376  return type.startsWith("Array<") && type.endsWith(">") ? type.slice(6, -1) : null;
2377}
2378
2379function decodeEndpointScalar(raw, type) {
2380  const optional = optionalWireType(type);
2381  if (optional !== null) return decodeEndpointScalar(raw, optional);
2382  if (type === "String") return raw;
2383  if (type === "Date") {
2384    if (!isStrictUtcIsoDate(raw)) throw new Error("Date");
2385    return raw;
2386  }
2387  if (type === "Boolean") {
2388    if (raw === "true") return true;
2389    if (raw === "false") return false;
2390    throw new Error("Boolean");
2391  }
2392  if (type === "Int") {
2393    if (!/^-?(0|[1-9][0-9]*)$/.test(raw)) throw new Error("Int");
2394    const value = Number(raw);
2395    if (!Number.isSafeInteger(value)) throw new Error("Int");
2396    return value;
2397  }
2398  if (type === "Float") {
2399    if (!/^-?(0|[1-9][0-9]*)\.[0-9]+$/.test(raw)) throw new Error("Float");
2400    const value = Number(raw);
2401    if (!Number.isFinite(value)) throw new Error("Float");
2402    return value;
2403  }
2404  if (type === "Number") {
2405    if (!/^-?(0|[1-9][0-9]*)(\.[0-9]+)?$/.test(raw)) throw new Error("Number");
2406    const value = Number(raw);
2407    if (!Number.isFinite(value)) throw new Error("Number");
2408    return value;
2409  }
2410  try { return JSON.parse(raw); } catch { throw new Error(type); }
2411}
2412
2413function validateEndpointSection(schema, section, value) {
2414  const validator = endpointValidator(`validator:endpoint.${schema.name}.${section}`);
2415  if (validator === null) return Object.freeze(value);
2416  try { return validator(value, true); }
2417  catch (cause) {
2418    throw Object.assign(new Error(`Endpoint ${section} violates its declared schema`), {
2419      code: "ENDPOINT_INPUT_TYPE",
2420      details: typeof cause?.toJSON === "function" ? cause.toJSON() : null,
2421    });
2422  }
2423}
2424
2425function decodeEndpointParams(schema, raw) {
2426  const values = Object.create(null);
2427  for (const field of schema.params) {
2428    if (!Object.hasOwn(raw, field.name)) throw Object.assign(new Error(`Missing path parameter ${field.name}`), { code: "ENDPOINT_PARAM_MISSING" });
2429    try { values[field.name] = decodeEndpointScalar(raw[field.name], field.type); }
2430    catch { throw Object.assign(new Error(`Path parameter ${field.name} must be ${field.type}`), { code: "ENDPOINT_PARAM_TYPE", details: { field: field.name, expected: field.type } }); }
2431  }
2432  return validateEndpointSection(schema, "params", values);
2433}
2434
2435function decodeEndpointQueryPart(raw) {
2436  return decodeURIComponent(raw.replace(/\+/g, " "));
2437}
2438
2439function endpointQueryTransport(url) {
2440  const grouped = Object.create(null);
2441  let malformed = false;
2442  const search = url.search.startsWith("?") ? url.search.slice(1) : url.search;
2443  if (search.length > 0) {
2444    for (const pair of search.split("&")) {
2445      if (pair.length === 0) continue;
2446      const separator = pair.indexOf("=");
2447      const rawName = separator < 0 ? pair : pair.slice(0, separator);
2448      const rawValue = separator < 0 ? "" : pair.slice(separator + 1);
2449      let name, value;
2450      try { name = decodeEndpointQueryPart(rawName); }
2451      catch { name = rawName; malformed = true; }
2452      try { value = decodeEndpointQueryPart(rawValue); }
2453      catch { value = rawValue; malformed = true; }
2454      const previous = grouped[name];
2455      if (previous === undefined) grouped[name] = value;
2456      else if (Array.isArray(previous)) grouped[name] = Object.freeze([...previous, value]);
2457      else grouped[name] = Object.freeze([previous, value]);
2458    }
2459  }
2460  return Object.freeze({ malformed, values: Object.freeze(grouped) });
2461}
2462
2463function decodeEndpointQuery(schema, transport) {
2464  if (transport.malformed) throw Object.assign(new Error("Endpoint query contains invalid percent encoding or UTF-8"), { status: 400, code: "ENDPOINT_QUERY_ENCODING_INVALID" });
2465  const expected = new Set(schema.query.map((field) => field.name));
2466  for (const name of Object.keys(transport.values)) {
2467    if (!expected.has(name)) throw Object.assign(new Error(`Unknown query field ${name}`), { code: "ENDPOINT_QUERY_UNKNOWN", details: { field: name } });
2468  }
2469  const values = Object.create(null);
2470  for (const field of schema.query) {
2471    const candidate = transport.values[field.name];
2472    const raw = candidate === undefined ? [] : Array.isArray(candidate) ? candidate : [candidate];
2473    const optional = optionalWireType(field.type) !== null;
2474    const inner = arrayWireType(field.type);
2475    if (raw.length === 0) {
2476      if (optional) { values[field.name] = null; continue; }
2477      if (inner !== null) { values[field.name] = Object.freeze([]); continue; }
2478      throw Object.assign(new Error(`Missing query field ${field.name}`), { code: "ENDPOINT_QUERY_MISSING", details: { field: field.name } });
2479    }
2480    try {
2481      if (inner !== null) {
2482        if (raw.length !== 1) throw new Error("repeated-array");
2483        const decoded = JSON.parse(raw[0]);
2484        if (!Array.isArray(decoded)) throw new Error("array");
2485        values[field.name] = decoded;
2486      } else {
2487        if (raw.length !== 1) throw new Error("repeated");
2488        values[field.name] = decodeEndpointScalar(raw[0], field.type);
2489      }
2490    } catch {
2491      throw Object.assign(new Error(`Query field ${field.name} must be ${field.type}`), { code: "ENDPOINT_QUERY_TYPE", details: { field: field.name, expected: field.type } });
2492    }
2493  }
2494  return validateEndpointSection(schema, "query", values);
2495}
2496
2497function endpointTimeoutFailure(schema) {
2498  return failure(504, "ENDPOINT_TIMEOUT", "Endpoint exceeded its declared timeout", schema.id, { timeoutMs: schema.timeoutMs });
2499}
2500
2501function endpointTimeoutError(schema) {
2502  return Object.assign(new Error("Endpoint exceeded its declared timeout"), { status: 504, code: "ENDPOINT_TIMEOUT", details: { timeoutMs: schema.timeoutMs } });
2503}
2504
2505async function readEndpointBodyBytes(request, schema, signal) {
2506  if (request.body === null) return new Uint8Array();
2507  const reader = request.body.getReader();
2508  const chunks = [];
2509  let total = 0;
2510  const cancel = () => { void reader.cancel("endpoint timeout").catch(() => {}); };
2511  signal.addEventListener("abort", cancel, { once: true });
2512  try {
2513    while (true) {
2514      if (signal.aborted) throw endpointTimeoutError(schema);
2515      const { done, value } = await reader.read();
2516      if (signal.aborted) throw endpointTimeoutError(schema);
2517      if (done) break;
2518      if (!(value instanceof Uint8Array)) throw Object.assign(new Error("Endpoint request body stream did not yield bytes"), { status: 400, code: "ENDPOINT_BODY_INVALID" });
2519      total += value.byteLength;
2520      if (total > 1_048_576) {
2521        await reader.cancel("endpoint body too large").catch(() => {});
2522        throw Object.assign(new Error("Endpoint request body exceeds 1 MiB"), { status: 413, code: "ENDPOINT_BODY_TOO_LARGE" });
2523      }
2524      chunks.push(value);
2525    }
2526  } finally {
2527    signal.removeEventListener("abort", cancel);
2528    try { reader.releaseLock(); } catch {}
2529  }
2530  const bytes = new Uint8Array(total);
2531  let offset = 0;
2532  for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
2533  return bytes;
2534}
2535
2536const endpointUploadCleanups = new WeakMap();
2537
2538function multipartFailure(status, code, message, details = null) {
2539  return Object.assign(new Error(message), { status, code, details });
2540}
2541
2542function multipartConcat(left, right) {
2543  if (left.byteLength === 0) return right.slice();
2544  if (right.byteLength === 0) return left;
2545  const joined = new Uint8Array(left.byteLength + right.byteLength);
2546  joined.set(left);
2547  joined.set(right, left.byteLength);
2548  return joined;
2549}
2550
2551function multipartIndexOf(haystack, needle, start = 0) {
2552  outer: for (let index = start; index + needle.byteLength <= haystack.byteLength; index += 1) {
2553    for (let offset = 0; offset < needle.byteLength; offset += 1) if (haystack[index + offset] !== needle[offset]) continue outer;
2554    return index;
2555  }
2556  return -1;
2557}
2558
2559function multipartParameters(value) {
2560  if (typeof value !== "string" || /[\r\n]/.test(value)) throw multipartFailure(400, "MULTIPART_HEADER_INVALID", "Multipart header parameters are malformed");
2561  const entries = [];
2562  let current = "";
2563  let quoted = false;
2564  let escaped = false;
2565  for (const character of value) {
2566    if (escaped) { current += character; escaped = false; continue; }
2567    if (quoted && character === "\\") { current += character; escaped = true; continue; }
2568    if (character === '"') { quoted = !quoted; current += character; continue; }
2569    if (character === ";" && !quoted) { entries.push(current.trim()); current = ""; continue; }
2570    current += character;
2571  }
2572  if (quoted || escaped) throw multipartFailure(400, "MULTIPART_HEADER_INVALID", "Multipart header quoting is incomplete");
2573  entries.push(current.trim());
2574  const kind = entries.shift()?.toLowerCase() ?? "";
2575  const parameters = Object.create(null);
2576  for (const entry of entries) {
2577    const separator = entry.indexOf("=");
2578    if (separator <= 0) throw multipartFailure(400, "MULTIPART_HEADER_INVALID", "Multipart header parameter requires a name and value");
2579    const name = entry.slice(0, separator).trim().toLowerCase();
2580    let parameter = entry.slice(separator + 1).trim();
2581    if (parameter.startsWith('"')) {
2582      if (!parameter.endsWith('"')) throw multipartFailure(400, "MULTIPART_HEADER_INVALID", "Multipart quoted parameter is incomplete");
2583      parameter = parameter.slice(1, -1).replace(/\\(.)/g, "$1");
2584    }
2585    if (name.length === 0 || Object.hasOwn(parameters, name)) throw multipartFailure(400, "MULTIPART_HEADER_INVALID", "Multipart header parameter is duplicated or unnamed");
2586    parameters[name] = parameter;
2587  }
2588  return { kind, parameters };
2589}
2590
2591function endpointMultipartBoundary(contentType) {
2592  const parsed = multipartParameters(contentType);
2593  const boundary = parsed.parameters.boundary;
2594  if (parsed.kind !== "multipart/form-data" || typeof boundary !== "string" || !/^[0-9A-Za-z'()+_,.\/:=?-]{1,70}$/.test(boundary)) {
2595    throw multipartFailure(415, "ENDPOINT_MULTIPART_BOUNDARY", "multipart/form-data requires one valid boundary parameter of at most 70 ASCII characters");
2596  }
2597  return boundary;
2598}
2599
2600function endpointMultipartHeaders(bytes) {
2601  let text;
2602  try { text = new TextDecoder("utf-8", { fatal: true }).decode(bytes); }
2603  catch { throw multipartFailure(400, "MULTIPART_HEADER_INVALID", "Multipart part headers are not valid UTF-8"); }
2604  const headers = Object.create(null);
2605  for (const line of text.split("\r\n")) {
2606    const separator = line.indexOf(":");
2607    if (separator <= 0) throw multipartFailure(400, "MULTIPART_HEADER_INVALID", "Multipart part header requires `name: value`");
2608    const name = line.slice(0, separator).trim().toLowerCase();
2609    const value = line.slice(separator + 1).trim();
2610    if (!/^[a-z0-9-]+$/.test(name) || Object.hasOwn(headers, name)) throw multipartFailure(400, "MULTIPART_HEADER_INVALID", "Multipart part header is duplicated or malformed");
2611    headers[name] = value;
2612  }
2613  return headers;
2614}
2615
2616// `filenameMaxBytes` is published in bytes, so it is enforced in bytes. The
2617// cut is moved back to the nearest code-point boundary — UTF-8 continuation
2618// bytes are 10xxxxxx — so a truncated name is never half a sequence and never
2619// half a surrogate pair: an astral character is kept whole or dropped whole.
2620// The limit is a ceiling on metadata, never a refusal.
2621function truncateUploadNameBytes(value) {
2622  const bytes = new TextEncoder().encode(value);
2623  if (bytes.byteLength <= ENDPOINT_UPLOAD_NAME_MAX_BYTES) return value;
2624  let end = ENDPOINT_UPLOAD_NAME_MAX_BYTES;
2625  while (end > 0 && (bytes[end] & 0xc0) === 0x80) end -= 1;
2626  return new TextDecoder("utf-8").decode(bytes.subarray(0, end));
2627}
2628
2629function sanitizeUploadName(value) {
2630  const leaf = String(value).split(/[\\/]/).pop() ?? "";
2631  // Normalize before measuring. NFKC folds compatibility spellings to one
2632  // canonically composed form, so the bytes counted against the ceiling are
2633  // the bytes the name is finally reported as.
2634  const sanitized = truncateUploadNameBytes(leaf.normalize("NFKC").replace(/[\u0000-\u001f\u007f]/g, "_").replace(/^\.+/, ""));
2635  return sanitized.length === 0 ? "upload" : sanitized;
2636}
2637
2638function decodeMultipartScalar(bytes, field) {
2639  let text;
2640  try { text = new TextDecoder("utf-8", { fatal: true }).decode(bytes); }
2641  catch { throw multipartFailure(400, "ENDPOINT_BODY_INVALID", `Multipart body field ${field.name} is not valid UTF-8`, { field: field.name }); }
2642  let wireType = field.type;
2643  while (optionalWireType(wireType) !== null) wireType = optionalWireType(wireType);
2644  if (wireType === "String") return text;
2645  if (["Int", "Number", "Float", "Boolean", "Date"].includes(wireType)) {
2646    try { return decodeEndpointScalar(text, wireType); }
2647    catch { throw multipartFailure(422, "ENDPOINT_BODY_TYPE", `Body field ${field.name} must be ${field.type}`, { field: field.name, expected: field.type }); }
2648  }
2649  try { return JSON.parse(text); }
2650  catch { throw multipartFailure(422, "ENDPOINT_BODY_TYPE", `Body field ${field.name} must contain JSON matching ${field.type}`, { field: field.name, expected: field.type }); }
2651}
2652
2653async function decodeEndpointMultipartBody(request, schema, signal, contentType) {
2654  const boundary = endpointMultipartBoundary(contentType);
2655  if (request.body === null) throw multipartFailure(400, "ENDPOINT_BODY_MISSING", "Multipart endpoint request body is missing");
2656  const encoder = new TextEncoder();
2657  const firstBoundary = encoder.encode(`--${boundary}`);
2658  const bodyBoundary = encoder.encode(`\r\n--${boundary}`);
2659  const headerTerminator = Uint8Array.from([13, 10, 13, 10]);
2660  const fields = new Map(schema.body.map((field) => [field.name, field]));
2661  const values = Object.create(null);
2662  const seen = new Set();
2663  const activeSinks = new Set();
2664  const completedAccess = [];
2665  let pending = new Uint8Array();
2666  let phase = "start";
2667  let part = null;
2668  let parts = 0;
2669  let scalarBytes = 0;
2670
2671  const beginPart = async (headerBytes) => {
2672    // One completed header block is one part, counted before the block is
2673    // parsed, so the count is a property of the body rather than of how the
2674    // body was delivered.
2675    parts += 1;
2676    if (parts > ENDPOINT_MULTIPART_MAX_PARTS) throw multipartFailure(413, "MULTIPART_PART_LIMIT", `Multipart request exceeds ${ENDPOINT_MULTIPART_MAX_PARTS} parts`);
2677    const headers = endpointMultipartHeaders(headerBytes);
2678    const disposition = multipartParameters(headers["content-disposition"]);
2679    const name = disposition.parameters.name;
2680    if (disposition.kind !== "form-data" || typeof name !== "string" || name.length === 0) throw multipartFailure(400, "MULTIPART_DISPOSITION_INVALID", "Multipart part requires Content-Disposition: form-data with a name");
2681    const field = fields.get(name);
2682    if (!field) throw multipartFailure(400, "ENDPOINT_BODY_UNKNOWN", `Unknown body field ${name}`, { field: name });
2683    const filename = disposition.parameters.filename;
2684    if (field.upload !== null) {
2685      if (typeof filename !== "string") throw multipartFailure(400, "MULTIPART_FILE_REQUIRED", `Body field ${name} requires a file part`, { field: name });
2686      if (!field.upload.multiple && seen.has(name)) throw multipartFailure(400, "ENDPOINT_BODY_DUPLICATE", `Body field ${name} may appear only once`, { field: name });
2687      const sink = await __noxidCreateUploadSink(field.upload.maxSizeBytes);
2688      activeSinks.add(sink);
2689      part = {
2690        kind: "file",
2691        field,
2692        name: sanitizeUploadName(filename),
2693        sink,
2694        validation: __noxidCreateFileValidationState(field.upload.maxSizeBytes, field.upload.types, schema.id, [name]),
2695      };
2696    } else {
2697      if (filename !== undefined) throw multipartFailure(400, "MULTIPART_SCALAR_REQUIRED", `Body field ${name} is scalar and cannot receive a file part`, { field: name });
2698      if (seen.has(name)) throw multipartFailure(400, "ENDPOINT_BODY_DUPLICATE", `Body field ${name} may appear only once`, { field: name });
2699      part = { kind: "scalar", field, chunks: [], size: 0 };
2700    }
2701  };
2702
2703  const writePart = async (chunk) => {
2704    if (chunk.byteLength === 0) return;
2705    if (part.kind === "file") {
2706      __noxidValidateFileChunk(part.validation, chunk);
2707      await part.sink.write(chunk);
2708      return;
2709    }
2710    part.size += chunk.byteLength;
2711    // A running total over the decoded span, so the verdict is the same
2712    // whether the field arrived in one read or a thousand.
2713    scalarBytes += chunk.byteLength;
2714    if (scalarBytes > ENDPOINT_MULTIPART_SCALAR_MAX_BYTES) throw multipartFailure(413, "ENDPOINT_BODY_TOO_LARGE", `Multipart scalar fields exceed ${ENDPOINT_MULTIPART_SCALAR_MAX_BYTES} bytes`);
2715    part.chunks.push(chunk.slice());
2716  };
2717
2718  const finishPart = async () => {
2719    const name = part.field.name;
2720    if (part.kind === "file") {
2721      const verdict = __noxidFinalizeFileValidation(part.validation);
2722      const staged = await part.sink.finish();
2723      activeSinks.delete(part.sink);
2724      completedAccess.push(staged.access);
2725      const file = __noxidCreateFileRef(
2726        { sniffedType: verdict.sniffedType, size: verdict.size, sha256: staged.sha256, name: part.name, maxSizeBytes: part.field.upload.maxSizeBytes },
2727        { stream: () => staged.access.stream(), bytes: () => staged.access.bytes(), store: (namespace) => staged.access.store(namespace) },
2728      );
2729      if (part.field.upload.multiple) {
2730        if (!Object.hasOwn(values, name)) values[name] = [];
2731        values[name].push(file);
2732      } else {
2733        values[name] = file;
2734      }
2735    } else {
2736      const bytes = new Uint8Array(part.size);
2737      let offset = 0;
2738      for (const chunk of part.chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
2739      values[name] = decodeMultipartScalar(bytes, part.field);
2740    }
2741    seen.add(name);
2742    part = null;
2743  };
2744
2745  const validBoundaryAt = (index) => {
2746    const suffix = index + bodyBoundary.byteLength;
2747    if (pending.byteLength < suffix + 2) return false;
2748    return (pending[suffix] === 13 && pending[suffix + 1] === 10) || (pending[suffix] === 45 && pending[suffix + 1] === 45);
2749  };
2750
2751  const processPending = async (eof) => {
2752    while (true) {
2753      if (phase === "done") {
2754        if (pending.byteLength === 0 || (pending.byteLength === 2 && pending[0] === 13 && pending[1] === 10)) { pending = new Uint8Array(); return; }
2755        throw multipartFailure(400, "MULTIPART_EPILOGUE_INVALID", "Multipart body contains bytes after the closing boundary");
2756      }
2757      if (phase === "start") {
2758        if (pending.byteLength < firstBoundary.byteLength + 2) { if (eof) throw multipartFailure(400, "MULTIPART_TRUNCATED", "Multipart body ended before its first boundary"); return; }
2759        // RFC 2046 permits a preamble before the first boundary. It belongs to
2760        // no part, carries no field, and is discarded — but it is still bytes
2761        // a client can send, so it is bounded exactly like a part header block
2762        // and refused past that with its own code rather than being read
2763        // forever.
2764        if (multipartIndexOf(pending, firstBoundary) !== 0) {
2765          const delimiter = multipartIndexOf(pending, bodyBoundary);
2766          // Measure the span, never the buffer. Once the delimiter is found
2767          // the preamble is exactly `delimiter` bytes; while it is absent
2768          // every buffered byte is preamble except a possible partial
2769          // delimiter at the tail, so the decided-so-far length is
2770          // `byteLength - (needle - 1)`. Counting the whole buffer instead
2771          // would refuse a preamble at the ceiling whenever a read happened
2772          // to stop inside the delimiter.
2773          const preambleBytes = delimiter < 0 ? pending.byteLength - (bodyBoundary.byteLength - 1) : delimiter;
2774          if (preambleBytes > ENDPOINT_MULTIPART_HEADER_MAX_BYTES) throw multipartFailure(413, "MULTIPART_PREAMBLE_TOO_LARGE", `Multipart preamble exceeds ${ENDPOINT_MULTIPART_HEADER_MAX_BYTES} bytes`);
2775          if (delimiter < 0) {
2776            if (eof) throw multipartFailure(400, "MULTIPART_BOUNDARY_INVALID", "Multipart body contains no declared boundary");
2777            return;
2778          }
2779          pending = pending.slice(delimiter + 2);
2780          continue;
2781        }
2782        if (pending[firstBoundary.byteLength] !== 13 || pending[firstBoundary.byteLength + 1] !== 10) throw multipartFailure(400, "MULTIPART_BOUNDARY_INVALID", "Multipart body does not start with its declared boundary");
2783        pending = pending.slice(firstBoundary.byteLength + 2);
2784        phase = "headers";
2785        continue;
2786      }
2787      if (phase === "headers") {
2788        const end = multipartIndexOf(pending, headerTerminator);
2789        // The ceiling belongs to the header block, not to whatever the
2790        // transport handed us. A found terminator makes the block exactly
2791        // `end` bytes, and it is measured before `beginPart` sees it: an
2792        // oversized block that arrives with its terminator in one read is
2793        // the same block as one that arrives in pieces.
2794        const headerBytes = end < 0 ? pending.byteLength - (headerTerminator.byteLength - 1) : end;
2795        if (headerBytes > ENDPOINT_MULTIPART_HEADER_MAX_BYTES) throw multipartFailure(413, "MULTIPART_HEADERS_TOO_LARGE", `Multipart part headers exceed ${ENDPOINT_MULTIPART_HEADER_MAX_BYTES} bytes`);
2796        if (end < 0) {
2797          if (eof) throw multipartFailure(400, "MULTIPART_TRUNCATED", "Multipart body ended inside part headers");
2798          return;
2799        }
2800        await beginPart(pending.subarray(0, end));
2801        pending = pending.slice(end + headerTerminator.byteLength);
2802        phase = "body";
2803        continue;
2804      }
2805      let boundaryIndex = multipartIndexOf(pending, bodyBoundary);
2806      while (boundaryIndex >= 0 && !validBoundaryAt(boundaryIndex)) boundaryIndex = multipartIndexOf(pending, bodyBoundary, boundaryIndex + 1);
2807      if (boundaryIndex < 0) {
2808        const retain = bodyBoundary.byteLength + 2;
2809        const flush = Math.max(0, pending.byteLength - retain);
2810        if (flush > 0) { await writePart(pending.subarray(0, flush)); pending = pending.slice(flush); }
2811        if (eof) throw multipartFailure(400, "MULTIPART_TRUNCATED", "Multipart body ended before a closing boundary");
2812        return;
2813      }
2814      await writePart(pending.subarray(0, boundaryIndex));
2815      await finishPart();
2816      const suffix = boundaryIndex + bodyBoundary.byteLength;
2817      const closing = pending[suffix] === 45;
2818      pending = pending.slice(suffix + 2);
2819      phase = closing ? "done" : "headers";
2820    }
2821  };
2822
2823  const reader = request.body.getReader();
2824  const cancel = () => { void reader.cancel("endpoint timeout").catch(() => {}); };
2825  signal.addEventListener("abort", cancel, { once: true });
2826  try {
2827    while (true) {
2828      if (signal.aborted) throw endpointTimeoutError(schema);
2829      const { done, value } = await reader.read();
2830      if (done) break;
2831      if (!(value instanceof Uint8Array)) throw multipartFailure(400, "ENDPOINT_BODY_INVALID", "Endpoint request body stream did not yield bytes");
2832      for (let offset = 0; offset < value.byteLength; offset += 65_536) {
2833        pending = multipartConcat(pending, value.subarray(offset, Math.min(value.byteLength, offset + 65_536)));
2834        await processPending(false);
2835      }
2836    }
2837    await processPending(true);
2838    for (const field of schema.body) {
2839      if (!Object.hasOwn(values, field.name) && optionalWireType(field.type) === null) throw multipartFailure(400, "ENDPOINT_BODY_MISSING", `Missing body field ${field.name}`, { field: field.name });
2840      if (field.upload?.multiple) values[field.name] = Object.freeze(values[field.name]);
2841    }
2842    endpointUploadCleanups.set(request, async () => {
2843      for (const access of completedAccess) await access.dispose();
2844    });
2845    return validateEndpointSection(schema, "body", values);
2846  } catch (cause) {
2847    await reader.cancel(cause?.code ?? "multipart refused").catch(() => {});
2848    for (const sink of activeSinks) await sink.abort().catch(() => {});
2849    for (const access of completedAccess) await access.dispose().catch(() => {});
2850    throw cause;
2851  } finally {
2852    signal.removeEventListener("abort", cancel);
2853    try { reader.releaseLock(); } catch {}
2854  }
2855}
2856
2857async function releaseEndpointUploads(request) {
2858  const cleanup = endpointUploadCleanups.get(request);
2859  endpointUploadCleanups.delete(request);
2860  if (cleanup) await cleanup();
2861}
2862
2863async function decodeEndpointBody(request, schema, signal) {
2864  if (schema.body.length === 0 && (schema.method === "GET" || request.body === null)) return Object.freeze(Object.create(null));
2865  const contentTypeHeader = request.headers.get("content-type") ?? "";
2866  const contentType = contentTypeHeader.split(";", 1)[0].trim().toLowerCase();
2867  const uploadFields = schema.body.filter((field) => field.upload !== null);
2868  if (contentType === "multipart/form-data") {
2869    if (uploadFields.length === 0) throw Object.assign(new Error("multipart/form-data is accepted only by endpoints declaring a File body field"), { status: 415, code: "ENDPOINT_MULTIPART_UNDECLARED" });
2870    return decodeEndpointMultipartBody(request, schema, signal, contentTypeHeader);
2871  }
2872  if (uploadFields.length !== 0) throw Object.assign(new Error("Endpoint upload body requires multipart/form-data"), { status: 415, code: "ENDPOINT_CONTENT_TYPE" });
2873  if (contentType !== "application/json") throw Object.assign(new Error("Endpoint request body requires application/json"), { status: 415, code: "ENDPOINT_CONTENT_TYPE" });
2874  const declaredLength = Number(request.headers.get("content-length") ?? 0);
2875  if (Number.isFinite(declaredLength) && declaredLength > 1_048_576) throw Object.assign(new Error("Endpoint request body exceeds 1 MiB"), { status: 413, code: "ENDPOINT_BODY_TOO_LARGE" });
2876  const bytes = await readEndpointBodyBytes(request, schema, signal);
2877  let text;
2878  try { text = new TextDecoder("utf-8", { fatal: true }).decode(bytes); }
2879  catch { throw Object.assign(new Error("Endpoint request body is not valid UTF-8"), { status: 400, code: "ENDPOINT_BODY_INVALID" }); }
2880  let body;
2881  try { body = JSON.parse(text); } catch { throw Object.assign(new Error("Endpoint request body is not valid JSON"), { status: 400, code: "ENDPOINT_BODY_INVALID" }); }
2882  if (!body || typeof body !== "object" || Array.isArray(body)) throw Object.assign(new Error("Endpoint request body must be a JSON object"), { status: 400, code: "ENDPOINT_BODY_INVALID" });
2883  const fields = new Map(schema.body.map((field) => [field.name, field]));
2884  for (const name of Object.keys(body)) if (!fields.has(name)) throw Object.assign(new Error(`Unknown body field ${name}`), { status: 400, code: "ENDPOINT_BODY_UNKNOWN", details: { field: name } });
2885  for (const field of schema.body) if (!Object.hasOwn(body, field.name) && optionalWireType(field.type) === null) throw Object.assign(new Error(`Missing body field ${field.name}`), { status: 400, code: "ENDPOINT_BODY_MISSING", details: { field: field.name } });
2886  return validateEndpointSection(schema, "body", body);
2887}
2888
2889function endpointResponseWithHeaders(response, pairs) {
2890  if (!pairs || pairs.length === 0) return response;
2891  const headers = new Headers(response.headers);
2892  for (const [name, value] of pairs) headers.append(name, value);
2893  return __noxidTraceCopyFailure(response, new Response(response.body, { status: response.status, headers }));
2894}
2895
2896function endpointCacheResponse(response, schema) {
2897  if (schema.cache === null || response.status !== 200) return response;
2898  const headers = new Headers(response.headers);
2899  const seconds = String(schema.cache.seconds);
2900  headers.set("cache-control", schema.cache.mode === "swr"
2901    ? `public, s-maxage=${seconds}, stale-while-revalidate=${seconds}`
2902    : `public, s-maxage=${seconds}, must-revalidate`);
2903  headers.set("x-noxid-cache-mode", schema.cache.mode);
2904  headers.set("x-noxid-cache-revalidate", seconds);
2905  headers.set("x-noxid-cache-stale", schema.cache.mode === "swr" ? seconds : "0");
2906  headers.set("x-noxid-cache-tags", schema.cache.tags.join(","));
2907  return new Response(response.body, { status: response.status, headers });
2908}
2909
2910function endpointRedirect(value, external) {
2911  if (external === true) {
2912    if (typeof value !== "string" || !/^https:\/\/[^\s]+$/.test(value) || /[\r\n]/.test(value)) throw new Error("external");
2913    return value;
2914  }
2915  if (typeof value !== "string" || !value.startsWith("/") || value.startsWith("//") || /[\r\n]/.test(value)) throw new Error("relative");
2916  if (applicationBasePath === "/" || value === applicationBasePath || value.startsWith(`${applicationBasePath}/`)) return value;
2917  return `${applicationBasePath.replace(/\/$/, "")}${value}`;
2918}
2919
2920async function applyEndpointMiddleware(request, schema, params, query, environment, executionContext, signal) {
2921  const headers = [];
2922  const context = Object.create(null);
2923  const chain = [
2924    ...globalMiddleware.map((name) => Object.freeze({ name, handle: globalMiddlewareHandlers[name] })),
2925    ...schema.middleware.map((name) => Object.freeze({ name, handle: middlewareHandlers[name] })),
2926  ];
2927  const route = Object.freeze({ id: schema.id, pattern: schema.path, method: schema.method, middleware: Object.freeze(chain.map((entry) => entry.name)) });
2928  __noxidTraceRoute(request, route.pattern);
2929  for (const { name, handle } of chain) {
2930    if (signal.aborted) return { response: endpointTimeoutFailure(schema), headers };
2931    if (typeof handle !== "function") return { response: failure(500, "ENDPOINT_MIDDLEWARE_MISSING", "Required endpoint middleware is not available", schema.id, { middleware: name }), headers };
2932    let result;
2933    try {
2934      result = await handle(__noxidDataContext({ middleware: name, semanticId: schema.id, traceId: __noxidTraceIdForRequest(request), boundary: `boundary:endpoint.${schema.name}.request`, target: "endpoint", route, request, url: new URL(request.url), params, query, host: hostModule, environment, executionContext, signal, context: Object.freeze({ ...context }), middlewareContext: Object.freeze({ ...context }) }, __noxidPrincipal(context, environment, __noxidAgentForRequest(request))));
2935    } catch {
2936      __noxidTraceBindPrincipal({ request }, __noxidPrincipal(context, environment, __noxidAgentForRequest(request)));
2937      __noxidTraceSemantic(request, "middleware", name.startsWith("middleware:") ? name : `middleware:${name}`);
2938      return { response: signal.aborted ? endpointTimeoutFailure(schema) : failure(500, "ENDPOINT_MIDDLEWARE_FAILED", "Endpoint middleware failed", schema.id, { middleware: name }), headers };
2939    }
2940    if (signal.aborted) return { response: endpointTimeoutFailure(schema), headers };
2941    const normalized = __noxidNormalizeMiddlewareResult(result);
2942    if (normalized.issue !== null) {
2943      __noxidTraceBindPrincipal({ request }, __noxidPrincipal(context, environment, __noxidAgentForRequest(request)));
2944      __noxidTraceSemantic(request, "middleware", name.startsWith("middleware:") ? name : `middleware:${name}`);
2945      return { response: failure(500, `ENDPOINT_MIDDLEWARE_${normalized.issue.toUpperCase()}`, "Endpoint middleware returned an invalid boundary result", schema.id, { middleware: name, validation: normalized.detail }), headers };
2946    }
2947    headers.push(...normalized.headers);
2948    if (normalized.context !== null) Object.assign(context, normalized.context);
2949    __noxidTraceBindPrincipal({ request }, __noxidPrincipal(context, environment, __noxidAgentForRequest(request)));
2950    __noxidTraceSemantic(request, "middleware", name.startsWith("middleware:") ? name : `middleware:${name}`);
2951    if (normalized.respond !== null) {
2952      const directHeaders = new Headers({ "content-type": `${normalized.respond.contentType}; charset=utf-8`, "cache-control": "no-store", "x-content-type-options": "nosniff" });
2953      for (const [header, value] of headers) directHeaders.append(header, value);
2954      return { response: new Response(normalized.respond.body, { status: normalized.respond.status, headers: directHeaders }), headers: [] };
2955    }
2956    if (normalized.redirect !== null) {
2957      let location;
2958      try { location = endpointRedirect(normalized.redirect, normalized.external); }
2959      catch { return { response: failure(500, "ENDPOINT_MIDDLEWARE_REDIRECT_INVALID", "Endpoint middleware returned an unsafe redirect", schema.id, { middleware: name }), headers }; }
2960      const redirect = new Response(null, { status: 307, headers: { location } });
2961      return { response: endpointResponseWithHeaders(redirect, headers), headers: [] };
2962    }
2963    if (!normalized.allow) return { response: failure(403, "ENDPOINT_MIDDLEWARE_DENIED", "Endpoint middleware denied the request", schema.id, { middleware: name }), headers };
2964  }
2965  return { response: null, headers, context: Object.freeze({ ...context }), route };
2966}
2967
2968async function authorizeEndpoint(request, schema, route, environment, executionContext, signal) {
2969  if (schema.capabilities.length === 0) return null;
2970  if (typeof authorize !== "function") return failure(500, "ENDPOINT_AUTHORIZER_MISSING", "Endpoint authorization is not configured", schema.id);
2971  for (const capability of schema.capabilities) {
2972    if (signal.aborted) return endpointTimeoutFailure(schema);
2973    let allowed = false;
2974    try { allowed = await authorize(Object.freeze({ capability, semanticId: schema.id, traceId: __noxidTraceIdForRequest(request), target: "endpoint", route, request, environment, executionContext, signal })) === true; } catch {}
2975    if (signal.aborted) return endpointTimeoutFailure(schema);
2976    if (!allowed) return failure(403, "ENDPOINT_CAPABILITY_DENIED", "Endpoint capability was denied", schema.id, { capability });
2977  }
2978  return null;
2979}
2980
2981function endpointRateIdentity(schema, request, environment, middlewareContext) {
2982  if (schema.limit === null) return { identity: null };
2983  if (schema.limit.scope === "session") {
2984    const identity = middlewareContext?.sessionId ?? middlewareContext?.session?.id ?? environment?.sessionId;
2985    return typeof identity === "string" && identity.length > 0 ? { identity: `session:${identity}` } : { error: failure(403, "ENDPOINT_RATE_IDENTITY_REQUIRED", "Session-scoped endpoint limit requires an authenticated session identity", schema.id) };
2986  }
2987  const forwarded = request.headers.get("cf-connecting-ip") ?? request.headers.get("x-real-ip");
2988  const identity = environment?.requestIdentity?.ip ?? environment?.ip ?? forwarded;
2989  return typeof identity === "string" && identity.length > 0 ? { identity: `ip:${identity}` } : { error: failure(403, "ENDPOINT_RATE_IDENTITY_REQUIRED", "IP-scoped endpoint limit requires a trusted client identity", schema.id) };
2990}
2991
2992async function withEndpointRateLock(key, operation) {
2993  const previous = endpointRateLocks.get(key) ?? Promise.resolve();
2994  let release;
2995  const current = new Promise((resolve) => { release = resolve; });
2996  endpointRateLocks.set(key, current);
2997  await previous;
2998  try { return await operation(); }
2999  finally {
3000    release();
3001    if (endpointRateLocks.get(key) === current) endpointRateLocks.delete(key);
3002  }
3003}
3004
3005function validEndpointRateBucket(bucket) {
3006  return bucket !== null && typeof bucket === "object"
3007    && typeof bucket.started === "number" && Number.isFinite(bucket.started) && bucket.started >= 0 && bucket.started <= Date.now()
3008    && typeof bucket.count === "number" && Number.isSafeInteger(bucket.count) && bucket.count >= 0
3009    && (bucket.windowMs === 60_000 || bucket.windowMs === 3_600_000);
3010}
3011
3012async function reserveEndpointRateCapacity(currentKey) {
3013  const keys = await endpointRateStorage.list();
3014  if (keys.includes(currentKey) || keys.length < ENDPOINT_RATE_BUCKET_MAX_ENTRIES) return;
3015  const loaded = await Promise.all(keys.map(async (key) => ({ key, bucket: await endpointRateStorage.get(key) })));
3016  for (const entry of loaded) if (!validEndpointRateBucket(entry.bucket)) await endpointRateStorage.delete(entry.key);
3017  const entries = loaded
3018    .filter((entry) => validEndpointRateBucket(entry.bucket))
3019    .sort((left, right) => left.bucket.started - right.bucket.started || left.key.localeCompare(right.key));
3020  while (entries.length >= ENDPOINT_RATE_BUCKET_MAX_ENTRIES) {
3021    const oldest = entries.shift();
3022    if (oldest) await endpointRateStorage.delete(oldest.key);
3023  }
3024}
3025
3026async function enforceEndpointRateLimit(schema, identity) {
3027  if (schema.limit === null) return null;
3028  const windowMs = schema.limit.window === "minute" ? 60_000 : 3_600_000;
3029  const key = `${schema.id}\n${identity}`;
3030  if (typeof __noxidSharedRateLimit === "function") {
3031    const retryAfter = await __noxidSharedRateLimit(key, schema.limit.requests, windowMs);
3032    return retryAfter === null
3033      ? null
3034      : failure(429, "ENDPOINT_RATE_LIMITED", "Endpoint rate limit exceeded", schema.id, { limit: schema.limit.requests, window: schema.limit.window }, { "retry-after": String(retryAfter) });
3035  }
3036  return withEndpointRateLock(key, async () => {
3037    const now = Date.now();
3038    let bucket = await endpointRateStorage.get(key);
3039    if (!validEndpointRateBucket(bucket) || bucket.count > schema.limit.requests || now - bucket.started >= windowMs || bucket.windowMs !== windowMs) {
3040      if (bucket !== null) await endpointRateStorage.delete(key);
3041      await reserveEndpointRateCapacity(key);
3042      bucket = { started: now, count: 0, windowMs };
3043    }
3044    if (bucket.count >= schema.limit.requests) {
3045      const retryAfter = Math.max(1, Math.ceil((bucket.started + windowMs - now) / 1000));
3046      return failure(429, "ENDPOINT_RATE_LIMITED", "Endpoint rate limit exceeded", schema.id, { limit: schema.limit.requests, window: schema.limit.window }, { "retry-after": String(retryAfter) });
3047    }
3048    const updated = { started: bucket.started, count: bucket.count + 1, windowMs };
3049    await endpointRateStorage.set(key, updated, { ttl: Math.max(0, (bucket.started + windowMs - now) / 1000) });
3050    return null;
3051  });
3052}
3053
3054async function endpointResponseSnapshot(response) {
3055  const failure = __noxidFailureSpans.get(response);
3056  const failureSnapshot = failure === undefined
3057    ? null
3058    : Object.freeze({ code: failure.code, semanticId: failure.semanticId });
3059  const copy = response.clone();
3060  let headers = [...copy.headers];
3061  if (typeof copy.headers.getSetCookie === "function") {
3062    headers = headers.filter(([name]) => name.toLowerCase() !== "set-cookie");
3063    headers.push(...copy.headers.getSetCookie().map((value) => ["set-cookie", value]));
3064  }
3065  const bytes = new Uint8Array(await copy.arrayBuffer());
3066  let binary = "";
3067  for (let offset = 0; offset < bytes.length; offset += 0x8000) binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
3068  return Object.freeze({ created: Date.now(), status: copy.status, headers: Object.freeze(headers.map((entry) => Object.freeze(entry))), body: btoa(binary), failure: failureSnapshot });
3069}
3070
3071function replayEndpointResponse(snapshot) {
3072  const binary = atob(snapshot.body);
3073  const body = new Uint8Array(binary.length);
3074  for (let index = 0; index < binary.length; index += 1) body[index] = binary.charCodeAt(index);
3075  const response = new Response(body, { status: snapshot.status, headers: snapshot.headers });
3076  return snapshot.failure === null || snapshot.failure === undefined
3077    ? response
3078    : __noxidTraceFailure(response, snapshot.failure.code, snapshot.failure.semanticId);
3079}
3080
3081function validEndpointFailureSnapshot(failure) {
3082  if (failure === null) return true;
3083  return typeof failure === "object"
3084    && Object.keys(failure).length === 2
3085    && typeof failure.code === "string" && NOXID_DIAGNOSTIC_CODE.test(failure.code)
3086    && (failure.semanticId === null || (typeof failure.semanticId === "string" && NOXID_SEMANTIC_ID.test(failure.semanticId)));
3087}
3088
3089function validEndpointSnapshotBase(snapshot) {
3090  return snapshot !== null && typeof snapshot === "object"
3091    && typeof snapshot.created === "number" && Number.isFinite(snapshot.created) && snapshot.created >= 0 && snapshot.created <= Date.now() && Date.now() - snapshot.created < ENDPOINT_IDEMPOTENCY_TTL_MS
3092    && typeof snapshot.status === "number" && Number.isSafeInteger(snapshot.status) && snapshot.status >= 100 && snapshot.status <= 599
3093    && Array.isArray(snapshot.headers) && snapshot.headers.every((entry) => Array.isArray(entry) && entry.length === 2 && entry.every((value) => typeof value === "string"))
3094    && typeof snapshot.body === "string";
3095}
3096
3097function validEndpointSnapshot(snapshot) {
3098  return validEndpointSnapshotBase(snapshot)
3099    && Object.hasOwn(snapshot, "failure") && validEndpointFailureSnapshot(snapshot.failure);
3100}
3101
3102function endpointSnapshotMatchesSchema(snapshot, schema) {
3103  if (!validEndpointSnapshot(snapshot)) return false;
3104  let body;
3105  try {
3106    const binary = atob(snapshot.body);
3107    const bytes = Uint8Array.from(binary, (value) => value.charCodeAt(0));
3108    body = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
3109  } catch { return false; }
3110  const code = body?.error?.code;
3111  const semanticId = body?.error?.semanticId;
3112  if (snapshot.failure === null) {
3113    return !new Set(["ENDPOINT_IMPLEMENTATION_MISSING", "ENDPOINT_TIMEOUT", "ENDPOINT_RESULT_TYPE", "ENDPOINT_RESULT_VALIDATOR_MISSING"]).has(code);
3114  }
3115  const expected = snapshot.failure.code === "ENDPOINT_RESULT_TYPE" || snapshot.failure.code === "ENDPOINT_RESULT_VALIDATOR_MISSING"
3116    ? Object.freeze({ semanticId: schema.result.id, status: 500 })
3117    : snapshot.failure.code === "ENDPOINT_TIMEOUT"
3118      ? Object.freeze({ semanticId: schema.id, status: 504 })
3119      : snapshot.failure.code === "ENDPOINT_IMPLEMENTATION_MISSING"
3120        ? Object.freeze({ semanticId: schema.id, status: 501 })
3121        : null;
3122  return expected !== null
3123    && snapshot.status === expected.status
3124    && snapshot.failure.semanticId === expected.semanticId
3125    && code === snapshot.failure.code
3126    && semanticId === snapshot.failure.semanticId;
3127}
3128
3129async function reserveEndpointIdempotencySlot(currentKey) {
3130  const keys = await endpointIdempotencyStorage.list();
3131  if (keys.includes(currentKey) || keys.length + endpointIdempotencyInFlight.size < ENDPOINT_IDEMPOTENCY_MAX_ENTRIES) return;
3132  const loaded = await Promise.all(keys.map(async (key) => ({ key, snapshot: await endpointIdempotencyStorage.get(key) })));
3133  for (const entry of loaded) if (!validEndpointSnapshot(entry.snapshot)) await endpointIdempotencyStorage.delete(entry.key);
3134  const entries = loaded
3135    .filter((entry) => validEndpointSnapshot(entry.snapshot))
3136    .sort((left, right) => left.snapshot.created - right.snapshot.created || left.key.localeCompare(right.key));
3137  while (entries.length + endpointIdempotencyInFlight.size >= ENDPOINT_IDEMPOTENCY_MAX_ENTRIES) {
3138    const oldestStored = entries.shift();
3139    if (oldestStored) await endpointIdempotencyStorage.delete(oldestStored.key);
3140    else endpointIdempotencyInFlight.delete(endpointIdempotencyInFlight.keys().next().value);
3141  }
3142}
3143
3144async function withEndpointIdempotencyLock(key, operation) {
3145  const previous = endpointIdempotencyLocks.get(key) ?? Promise.resolve();
3146  let release;
3147  const current = new Promise((resolve) => { release = resolve; });
3148  endpointIdempotencyLocks.set(key, current);
3149  await previous;
3150  try { return await operation(); }
3151  finally {
3152    release();
3153    if (endpointIdempotencyLocks.get(key) === current) endpointIdempotencyLocks.delete(key);
3154  }
3155}
3156
3157function endpointIdempotencyIdentity(request, environment, middlewareContext) {
3158  const session = middlewareContext?.sessionId ?? middlewareContext?.session?.id ?? environment?.sessionId;
3159  if (typeof session === "string" && session.length > 0) return `session:${session}`;
3160  const ip = environment?.requestIdentity?.ip ?? environment?.ip ?? request.headers.get("cf-connecting-ip") ?? request.headers.get("x-real-ip");
3161  return typeof ip === "string" && ip.length > 0 ? `ip:${ip}` : null;
3162}
3163
3164function endpointStorageFailure(schema, cause) {
3165  if (cause?.code === "SERVER_STORAGE_REDIS_UNAVAILABLE") {
3166    return failure(503, "ENDPOINT_STORAGE_UNAVAILABLE", "Endpoint operational storage is temporarily unavailable; retry the request", schema.id, null, { "retry-after": "1" });
3167  }
3168  return failure(500, "ENDPOINT_STORAGE_FAILED", "Endpoint operational storage is unavailable", schema.id);
3169}
3170
3171function sharedEndpointIdempotencyAvailable() {
3172  return typeof __noxidSharedIdempotencyPrepare === "function"
3173    && typeof __noxidSharedIdempotencyComplete === "function"
3174    && typeof __noxidSharedIdempotencyRelease === "function";
3175}
3176
3177async function prepareSharedEndpointIdempotency(schema, key, signal, deadlineAt) {
3178  if (typeof globalThis.crypto?.randomUUID !== "function") throw new Error("secure idempotency claims require crypto.randomUUID");
3179  const claim = globalThis.crypto.randomUUID();
3180  for (;;) {
3181    if (signal.aborted) return { response: endpointTimeoutFailure(schema) };
3182    const leaseMs = Math.max(1, Math.ceil(deadlineAt - Date.now() + 1000));
3183    const prepared = await __noxidSharedIdempotencyPrepare(key, claim, leaseMs);
3184    if (prepared?.state === "owner") return { claim };
3185    if (prepared?.state === "stored") {
3186      const existing = prepared.value;
3187      if (endpointSnapshotMatchesSchema(existing, schema)) return { snapshot: existing };
3188      const untraceableSnapshot = validEndpointSnapshotBase(existing);
3189      await endpointIdempotencyStorage.delete(key);
3190      if (untraceableSnapshot) return { response: endpointStorageFailure(schema) };
3191      continue;
3192    }
3193    if (prepared?.state !== "pending") throw new Error("invalid shared idempotency preparation result");
3194    await new Promise((resolve) => setTimeout(resolve, Math.min(10, Math.max(1, deadlineAt - Date.now()))));
3195  }
3196}
3197
3198function endpointStreamHeaders() {
3199  return {
3200    "content-type": "text/event-stream; charset=utf-8",
3201    "cache-control": "no-store",
3202    "connection": "keep-alive",
3203    "x-accel-buffering": "no",
3204    "x-content-type-options": "nosniff",
3205  };
3206}
3207
3208function endpointImmediateStreamFailure(schema, code, message, details = null) {
3209  return new Response(streamErrorFrame(schema, code, message, details), { status: 200, headers: endpointStreamHeaders() });
3210}
3211
3212function endpointClosedStreamResponse() {
3213  return new Response(null, { status: 200, headers: endpointStreamHeaders() });
3214}
3215
3216function resumeEndpointStream(schema, resume, requestBinding) {
3217  if (resume.invalid) return endpointImmediateStreamFailure(schema, "STREAM_RESUME_UNAVAILABLE", "Last-Event-ID is malformed or exceeds the supported length", { reason: "malformed" });
3218  const history = endpointStreamHistories.get(resume.token);
3219  if (!history || history.schemaId !== schema.id) return endpointImmediateStreamFailure(schema, "STREAM_RESUME_UNAVAILABLE", "Last-Event-ID does not name retained history for this stream endpoint", { reason: "unknown" });
3220  if (history.requestBinding !== requestBinding) return endpointImmediateStreamFailure(schema, "STREAM_RESUME_UNAVAILABLE", "Last-Event-ID belongs to a different stream request contract or request identity", { reason: "request-mismatch" });
3221  if (!history.completed) return endpointImmediateStreamFailure(schema, "STREAM_RESUME_UNAVAILABLE", "The named stream history is still active; reconnect after the prior connection closes", { reason: "active" });
3222  const firstRetained = history.events.length > 0 ? history.events[0].sequence : history.nextSequence;
3223  if (resume.sequence >= history.nextSequence) return endpointImmediateStreamFailure(schema, "STREAM_RESUME_UNAVAILABLE", "Last-Event-ID is ahead of the recorded stream", { reason: "future", nextSequence: history.nextSequence });
3224  if (resume.sequence < firstRetained - 1) return endpointImmediateStreamFailure(schema, "STREAM_RESUME_UNAVAILABLE", "Last-Event-ID is older than the bounded replay history", { reason: "evicted", firstRetained });
3225  const frames = history.events.filter((event) => event.sequence > resume.sequence).map((event) => event.frame);
3226  if (history.terminal !== null) frames.push(history.terminal);
3227  return new Response(frames.join(""), { status: 200, headers: endpointStreamHeaders() });
3228}
3229
3230async function invokeStreamEndpoint(request, schema, args, middleware, environment, executionContext, parentSignal, deadlineAt, implementation) {
3231  const traceId = __noxidTraceIdForRequest(request);
3232  if (request.signal.aborted) return endpointResponseWithHeaders(endpointClosedStreamResponse(), middleware.headers);
3233  const resume = parseStreamResumeId(request.headers.get("last-event-id"));
3234  if (resume !== null) {
3235    if (resume.invalid) return endpointResponseWithHeaders(resumeEndpointStream(schema, resume, null), middleware.headers);
3236    let requestBinding;
3237    try { requestBinding = await endpointStreamRequestBinding(resume.token, request, args, environment, middleware.context); }
3238    catch { return endpointResponseWithHeaders(endpointImmediateStreamFailure(schema, "STREAM_RESUME_BINDING_UNAVAILABLE", "The stream request contract cannot be bound safely for resume"), middleware.headers); }
3239    if (parentSignal.aborted) return endpointResponseWithHeaders(endpointTimeoutFailure(schema), middleware.headers);
3240    if (request.signal.aborted) return endpointResponseWithHeaders(endpointClosedStreamResponse(), middleware.headers);
3241    return endpointResponseWithHeaders(resumeEndpointStream(schema, resume, requestBinding), middleware.headers);
3242  }
3243  const token = newStreamToken();
3244  if (token === null) return endpointResponseWithHeaders(endpointImmediateStreamFailure(schema, "STREAM_RESUME_TOKEN_UNAVAILABLE", "A secure stream resume token cannot be created in this runtime"), middleware.headers);
3245  let requestBinding;
3246  try { requestBinding = await endpointStreamRequestBinding(token, request, args, environment, middleware.context); }
3247  catch { return endpointResponseWithHeaders(endpointImmediateStreamFailure(schema, "STREAM_RESUME_BINDING_UNAVAILABLE", "The stream request contract cannot be bound safely for resume"), middleware.headers); }
3248  if (parentSignal.aborted) return endpointResponseWithHeaders(endpointTimeoutFailure(schema), middleware.headers);
3249  if (request.signal.aborted) return endpointResponseWithHeaders(endpointClosedStreamResponse(), middleware.headers);
3250  const history = reserveStreamHistory(schema, token, requestBinding);
3251  if (history === null) return endpointResponseWithHeaders(endpointImmediateStreamFailure(schema, "STREAM_REPLAY_CAPACITY", "All bounded stream replay histories are active; retry after a connection closes", { maxHistories: ENDPOINT_STREAM_MAX_HISTORIES }), middleware.headers);
3252  const validator = endpointValidator(schema.result.validator);
3253  if (validator === null) {
3254    history.completed = true;
3255    history.terminal = streamErrorFrame(schema, "STREAM_EVENT_VALIDATOR_MISSING", "Stream event validator is unavailable", { validator: schema.result.validator });
3256    return endpointResponseWithHeaders(new Response(history.terminal, { status: 200, headers: endpointStreamHeaders() }), middleware.headers);
3257  }
3258
3259  const encoder = new TextEncoder();
3260  const controller = new AbortController();
3261  let abortKind = null;
3262  let iterator = null;
3263  let streamController = null;
3264  let open = true;
3265  let timer = null;
3266  const abort = (kind) => {
3267    if (controller.signal.aborted) return;
3268    abortKind = kind;
3269    controller.abort(kind);
3270  };
3271  const onParentAbort = () => abort("timeout");
3272  const onRequestAbort = () => abort("disconnect");
3273  parentSignal.addEventListener("abort", onParentAbort, { once: true });
3274  request.signal.addEventListener("abort", onRequestAbort, { once: true });
3275  if (parentSignal.aborted) abort("timeout");
3276  else if (request.signal.aborted) abort("disconnect");
3277  const remaining = Math.max(0, deadlineAt - Date.now());
3278  timer = setTimeout(() => abort("timeout"), remaining);
3279
3280  const enqueue = (text) => {
3281    if (!open) return false;
3282    try { streamController.enqueue(encoder.encode(text)); return true; }
3283    catch { open = false; abort("disconnect"); return false; }
3284  };
3285  const finish = async (terminal = null) => {
3286    if (history.completed) return;
3287    history.completed = true;
3288    history.terminal = terminal;
3289    if (terminal !== null) enqueue(terminal);
3290    open = false;
3291    try { streamController.close(); } catch {}
3292    if (iterator && typeof iterator.return === "function") {
3293      try { await iterator.return(); } catch {}
3294    }
3295  };
3296  const abortOutcome = controller.signal.aborted
3297    ? Promise.resolve(Object.freeze({ kind: "abort" }))
3298    : new Promise((resolve) => controller.signal.addEventListener("abort", () => resolve(Object.freeze({ kind: "abort" })), { once: true }));
3299  const waitWithHeartbeats = async (promise) => {
3300    const pending = Promise.resolve(promise).then(
3301      (value) => Object.freeze({ kind: "value", value }),
3302      (cause) => Object.freeze({ kind: "error", cause }),
3303    );
3304    while (true) {
3305      let heartbeatTimer;
3306      const heartbeat = new Promise((resolve) => { heartbeatTimer = setTimeout(() => resolve(Object.freeze({ kind: "heartbeat" })), ENDPOINT_STREAM_HEARTBEAT_MS); });
3307      const outcome = await Promise.race([pending, abortOutcome, heartbeat]);
3308      clearTimeout(heartbeatTimer);
3309      if (outcome.kind !== "heartbeat") return outcome;
3310      if (!enqueue(": noxid-heartbeat\n\n")) return Object.freeze({ kind: "abort" });
3311    }
3312  };
3313
3314  const body = new ReadableStream({
3315    async start(readableController) {
3316      streamController = readableController;
3317      try {
3318        if (controller.signal.aborted) { await finish(); return; }
3319        const middlewareContext = middleware.context ?? EMPTY_MIDDLEWARE_CONTEXT;
3320        const context = __noxidDataContext({ request, environment, executionContext, signal: controller.signal, semanticId: schema.id, traceId, target: "endpoint", route: middleware.route, capabilities: schema.capabilities, middlewareContext }, __noxidPrincipal(middlewareContext, environment, __noxidAgentForRequest(request)));
3321        const implementationOutcome = await waitWithHeartbeats(Promise.resolve().then(() => implementation(args, context)));
3322        if (implementationOutcome.kind === "abort") {
3323          if (abortKind === "timeout") await finish(streamErrorFrame(schema, "ENDPOINT_TIMEOUT", "Endpoint exceeded its declared timeout", { timeoutMs: schema.timeoutMs }));
3324          else await finish();
3325          return;
3326        }
3327        if (implementationOutcome.kind === "error") {
3328          const cause = implementationOutcome.cause;
3329          const code = typeof cause?.code === "string" ? cause.code : "STREAM_EXECUTION_FAILED";
3330          const message = cause?.expose === true && typeof cause?.message === "string" ? cause.message : "Stream endpoint execution failed";
3331          await finish(streamErrorFrame(schema, code, message));
3332          return;
3333        }
3334        const iterable = implementationOutcome.value;
3335        if (iterable === null || iterable === undefined || typeof iterable[Symbol.asyncIterator] !== "function") {
3336          await finish(streamErrorFrame(schema, "STREAM_ITERABLE_REQUIRED", "Stream endpoint implementation must return an AsyncIterable"));
3337          return;
3338        }
3339        iterator = iterable[Symbol.asyncIterator]();
3340        while (open) {
3341          const next = await waitWithHeartbeats(Promise.resolve().then(() => iterator.next()));
3342          if (next.kind === "abort") {
3343            if (abortKind === "timeout") await finish(streamErrorFrame(schema, "ENDPOINT_TIMEOUT", "Endpoint exceeded its declared timeout", { timeoutMs: schema.timeoutMs }));
3344            else await finish();
3345            return;
3346          }
3347          if (next.kind === "error") {
3348            const cause = next.cause;
3349            const code = typeof cause?.code === "string" ? cause.code : "STREAM_EXECUTION_FAILED";
3350            const message = cause?.expose === true && typeof cause?.message === "string" ? cause.message : "Stream endpoint execution failed";
3351            await finish(streamErrorFrame(schema, code, message));
3352            return;
3353          }
3354          if (!next.value || typeof next.value !== "object" || typeof next.value.done !== "boolean") {
3355            await finish(streamErrorFrame(schema, "STREAM_ITERATOR_RESULT_INVALID", "Stream endpoint iterator returned an invalid result"));
3356            return;
3357          }
3358          if (next.value.done) { await finish(); return; }
3359          let trusted;
3360          try { trusted = validator(next.value.value); }
3361          catch (cause) {
3362            await finish(streamErrorFrame(schema, "STREAM_EVENT_TYPE", "Stream endpoint yielded an event that violates its declared type", { validation: typeof cause?.toJSON === "function" ? cause.toJSON() : null }));
3363            return;
3364          }
3365          const eventValue = trusted === undefined ? null : trusted;
3366          const serialized = JSON.stringify(eventValue);
3367          const dataBytes = encoder.encode(serialized).byteLength;
3368          if (dataBytes > ENDPOINT_STREAM_MAX_EVENT_BYTES) {
3369            await finish(streamErrorFrame(schema, "STREAM_EVENT_TOO_LARGE", "Stream endpoint event exceeds the bounded replay size", { maxBytes: ENDPOINT_STREAM_MAX_EVENT_BYTES, actualBytes: dataBytes }));
3370            return;
3371          }
3372          const sequence = history.nextSequence;
3373          const frame = streamFrame("message", eventValue, `${history.token}:${sequence}`);
3374          recordStreamEvent(history, frame, encoder.encode(frame).byteLength);
3375          if (!enqueue(frame)) { await finish(); return; }
3376        }
3377      } catch {
3378        await finish(streamErrorFrame(schema, "STREAM_EXECUTION_FAILED", "Stream endpoint execution failed"));
3379      } finally {
3380        clearTimeout(timer);
3381        parentSignal.removeEventListener("abort", onParentAbort);
3382        request.signal.removeEventListener("abort", onRequestAbort);
3383      }
3384    },
3385    async cancel() {
3386      open = false;
3387      abort("disconnect");
3388      history.completed = true;
3389      if (iterator && typeof iterator.return === "function") {
3390        try { await iterator.return(); } catch {}
3391      }
3392    },
3393  });
3394  return endpointResponseWithHeaders(new Response(body, { status: 200, headers: endpointStreamHeaders() }), middleware.headers);
3395}
3396
3397function endpointResultResponse(schema, value) {
3398  const isResult = schema.result.errorValidator !== null;
3399  if (isResult) {
3400    if (!value || typeof value !== "object" || !matchesResultTag(value.tag)) return failure(500, "ENDPOINT_RESULT_TYPE", "Endpoint returned a value that violates its declared Result type", schema.result.id);
3401    const validatorId = value.tag === "Err" ? schema.result.errorValidator : schema.result.validator;
3402    const validator = endpointValidator(validatorId);
3403    if (validator === null) return failure(500, "ENDPOINT_RESULT_VALIDATOR_MISSING", "Endpoint result validator is unavailable", schema.result.id);
3404    let trusted;
3405    try { trusted = validator(value.value); }
3406    catch (cause) { return failure(500, "ENDPOINT_RESULT_TYPE", "Endpoint returned a value that violates its declared result type", schema.result.id, { validation: typeof cause?.toJSON === "function" ? cause.toJSON() : null }); }
3407    return value.tag === "Err"
3408      ? json(422, { ok: false, error: { code: "ENDPOINT_RESULT_ERR", message: "Endpoint returned its declared error result", semanticId: schema.result.id, value: trusted } })
3409      : json(200, { ok: true, value: trusted === undefined ? null : trusted });
3410  }
3411  const validator = endpointValidator(schema.result.validator);
3412  if (validator === null) return failure(500, "ENDPOINT_RESULT_VALIDATOR_MISSING", "Endpoint result validator is unavailable", schema.result.id);
3413  try {
3414    const trusted = validator(value);
3415    return json(200, { ok: true, value: trusted === undefined ? null : trusted });
3416  } catch (cause) { return failure(500, "ENDPOINT_RESULT_TYPE", "Endpoint returned a value that violates its declared result type", schema.result.id, { validation: typeof cause?.toJSON === "function" ? cause.toJSON() : null }); }
3417}
3418
3419function matchesResultTag(tag) { return tag === "Ok" || tag === "Err"; }
3420
3421function endpointAbortResponse(signal, schema) {
3422  if (signal.aborted) return Promise.resolve(endpointTimeoutFailure(schema));
3423  return new Promise((resolve) => signal.addEventListener("abort", () => resolve(endpointTimeoutFailure(schema)), { once: true }));
3424}
3425
3426async function invokeEndpoint(request, schema, args, middleware, environment, executionContext, signal, deadlineAt) {
3427  const middlewareContext = middleware.context ?? EMPTY_MIDDLEWARE_CONTEXT;
3428  const endpointPrincipal = __noxidPrincipal(middlewareContext, environment, __noxidAgentForRequest(request));
3429  __noxidTraceBindPrincipal({ request }, endpointPrincipal);
3430  const endpointSpan = __noxidTraceBeginSemantic(request);
3431  try {
3432    const implementation = compiledEndpoints[schema.id] ?? hostEndpoints[schema.id];
3433    if (typeof implementation !== "function") {
3434      return schema.kind === "stream"
3435        ? endpointResponseWithHeaders(endpointImmediateStreamFailure(schema, "STREAM_IMPLEMENTATION_MISSING", `No implementation is registered for ${schema.id}`), middleware.headers)
3436        : failure(501, "ENDPOINT_IMPLEMENTATION_MISSING", `No implementation is registered for ${schema.id}`, schema.id);
3437    }
3438    if (signal.aborted) return endpointResponseWithHeaders(endpointTimeoutFailure(schema), middleware.headers);
3439    if (schema.kind === "stream") return invokeStreamEndpoint(request, schema, args, middleware, environment, executionContext, signal, deadlineAt, implementation);
3440    const execution = (async () => {
3441      try {
3442        const context = __noxidDataContext({ request, environment, executionContext, signal, semanticId: schema.id, traceId: __noxidTraceIdForRequest(request), target: "endpoint", route: middleware.route, capabilities: schema.capabilities, middlewareContext }, endpointPrincipal);
3443        const value = await implementation(args, context);
3444        if (signal.aborted) return endpointTimeoutFailure(schema);
3445        const validatedResponse = endpointResultResponse(schema, value);
3446        if (validatedResponse.ok) await __noxidPublishLiveInvalidations(schema.invalidates, endpointPrincipal);
3447        return endpointCacheResponse(validatedResponse, schema);
3448      } catch (cause) {
3449        if (signal.aborted) return endpointTimeoutFailure(schema);
3450        const code = typeof cause?.code === "string" ? cause.code : "ENDPOINT_EXECUTION_FAILED";
3451        const message = cause?.expose === true && typeof cause?.message === "string" ? cause.message : "Endpoint execution failed";
3452        return failure(500, code, message, schema.id, null, {}, false);
3453      }
3454    })();
3455    const response = await Promise.race([execution, endpointAbortResponse(signal, schema)]);
3456    return endpointResponseWithHeaders(response, middleware.headers);
3457  } finally {
3458    __noxidTraceFinishSemantic(endpointSpan, "endpoint", schema.id, { route: middleware.route?.pattern });
3459  }
3460}
3461
3462async function withEndpointDeadline(schema, operation) {
3463  const controller = new AbortController();
3464  if (schema.timeoutMs === 0) {
3465    controller.abort();
3466    return endpointTimeoutFailure(schema);
3467  }
3468  let timer;
3469  const deadlineAt = Date.now() + schema.timeoutMs;
3470  const timeout = new Promise((resolve) => {
3471    timer = setTimeout(() => {
3472      controller.abort();
3473      resolve(endpointTimeoutFailure(schema));
3474    }, schema.timeoutMs);
3475  });
3476  try {
3477    return await Promise.race([Promise.resolve().then(() => operation(controller.signal, deadlineAt)), timeout]);
3478  } finally {
3479    clearTimeout(timer);
3480  }
3481}
3482
3483async function handleEndpointRequest(request, url, environment, executionContext) {
3484  const candidates = endpointMatches(url.pathname);
3485  const matches = candidates.matches;
3486  if (matches.length === 0 && candidates.malformed.length === 0) return null;
3487  const methodMatches = matches.filter((candidate) => candidate.schema.method === request.method.toUpperCase());
3488  const malformedMethodMatches = candidates.malformed.filter((schema) => schema.method === request.method.toUpperCase());
3489  if (methodMatches.length === 0 && malformedMethodMatches.length > 0) {
3490    const schema = malformedMethodMatches[0];
3491    return failure(400, "ENDPOINT_PATH_ENCODING_INVALID", "Endpoint path contains invalid percent encoding; percent-encode one valid UTF-8 path value", schema.id);
3492  }
3493  if (methodMatches.length === 0) {
3494    const allow = [...new Set([...matches.map((candidate) => candidate.schema.method), ...candidates.malformed.map((schema) => schema.method)])].sort().join(", ");
3495    return failure(405, "ENDPOINT_METHOD_NOT_ALLOWED", "Endpoint path does not accept this method", null, null, { allow });
3496  }
3497  const { schema, params: rawParams } = methodMatches[0];
3498  return withEndpointDeadline(schema, async (signal, deadlineAt) => {
3499  try {
3500  const queryTransport = endpointQueryTransport(url);
3501  const middleware = await applyEndpointMiddleware(request, schema, rawParams, queryTransport.values, environment, executionContext, signal);
3502  if (signal.aborted) return endpointTimeoutFailure(schema);
3503  if (middleware.response) return endpointResponseWithHeaders(middleware.response, middleware.headers);
3504  const authorizationFailure = await authorizeEndpoint(request, schema, middleware.route, environment, executionContext, signal);
3505  if (signal.aborted) return endpointResponseWithHeaders(endpointTimeoutFailure(schema), middleware.headers);
3506  if (authorizationFailure) return endpointResponseWithHeaders(authorizationFailure, middleware.headers);
3507  let params, query, body;
3508  try {
3509    params = decodeEndpointParams(schema, rawParams);
3510    query = decodeEndpointQuery(schema, queryTransport);
3511    body = await decodeEndpointBody(request, schema, signal);
3512  } catch (cause) {
3513    if (signal.aborted) return endpointResponseWithHeaders(endpointTimeoutFailure(schema), middleware.headers);
3514    const response = failure(cause?.status ?? (cause?.code === "ENDPOINT_INPUT_TYPE" || cause?.code?.endsWith("_TYPE") ? 422 : 400), cause?.code ?? "ENDPOINT_INPUT_INVALID", cause?.message ?? "Endpoint request is invalid", schema.id, cause?.details ?? null);
3515    return endpointResponseWithHeaders(response, middleware.headers);
3516  }
3517  if (signal.aborted) return endpointResponseWithHeaders(endpointTimeoutFailure(schema), middleware.headers);
3518  const argumentsRecord = Object.freeze(Object.assign(Object.create(null), params, query, body));
3519  if (schema.kind === "stream" && (schema.cache !== null || schema.idempotent)) return endpointResponseWithHeaders(failure(500, "STREAM_OPERATION_POLICY_INVALID", "Stream endpoints cannot use response cache or idempotent replay policies", schema.id), middleware.headers);
3520  const rateIdentity = endpointRateIdentity(schema, request, environment, middleware.context);
3521  if (rateIdentity.error) return endpointResponseWithHeaders(rateIdentity.error, middleware.headers);
3522  let idempotencyMapKey = null;
3523  if (schema.idempotent) {
3524    const idempotencyKey = request.headers.get("idempotency-key");
3525    if (typeof idempotencyKey !== "string" || idempotencyKey.length === 0 || idempotencyKey.length > 256 || !/^[\x21-\x7e]+$/.test(idempotencyKey)) return endpointResponseWithHeaders(failure(400, "ENDPOINT_IDEMPOTENCY_KEY_REQUIRED", "Idempotent endpoint requests require a valid Idempotency-Key header", schema.id), middleware.headers);
3526    const replayIdentity = endpointIdempotencyIdentity(request, environment, middleware.context);
3527    if (replayIdentity === null) return endpointResponseWithHeaders(failure(403, "ENDPOINT_IDEMPOTENCY_IDENTITY_REQUIRED", "Idempotent endpoint replay requires a session or trusted client identity", schema.id), middleware.headers);
3528    idempotencyMapKey = `${schema.id}\n${replayIdentity}\n${idempotencyKey}`;
3529  }
3530  if (idempotencyMapKey !== null) {
3531    try {
3532      const prepared = await withEndpointIdempotencyLock(idempotencyMapKey, async () => {
3533        if (signal.aborted) return { response: endpointTimeoutFailure(schema) };
3534        const inFlight = endpointIdempotencyInFlight.get(idempotencyMapKey);
3535        if (inFlight) return { promise: inFlight };
3536        const shared = sharedEndpointIdempotencyAvailable();
3537        let sharedClaim = null;
3538        if (shared) {
3539          const distributed = await prepareSharedEndpointIdempotency(schema, idempotencyMapKey, signal, deadlineAt);
3540          if (distributed.response) return { response: endpointResponseWithHeaders(distributed.response, middleware.headers) };
3541          if (distributed.snapshot) return { snapshot: distributed.snapshot };
3542          sharedClaim = distributed.claim;
3543        } else {
3544          const existing = await endpointIdempotencyStorage.get(idempotencyMapKey);
3545          if (existing !== null) {
3546            if (endpointSnapshotMatchesSchema(existing, schema)) return { snapshot: existing };
3547            const untraceableSnapshot = validEndpointSnapshotBase(existing);
3548            await endpointIdempotencyStorage.delete(idempotencyMapKey);
3549            if (untraceableSnapshot) return { response: endpointResponseWithHeaders(endpointStorageFailure(schema), middleware.headers) };
3550          }
3551        }
3552        const rateFailure = await enforceEndpointRateLimit(schema, rateIdentity.identity);
3553        if (rateFailure) {
3554          if (sharedClaim !== null) await __noxidSharedIdempotencyRelease(idempotencyMapKey, sharedClaim);
3555          return { response: endpointResponseWithHeaders(rateFailure, middleware.headers) };
3556        }
3557        if (!shared) await reserveEndpointIdempotencySlot(idempotencyMapKey);
3558        let promise;
3559        promise = (async () => {
3560          try {
3561            const response = await invokeEndpoint(request, schema, argumentsRecord, middleware, environment, executionContext, signal, deadlineAt);
3562            const snapshot = await endpointResponseSnapshot(response);
3563            if (sharedClaim === null) {
3564              await endpointIdempotencyStorage.set(idempotencyMapKey, snapshot, { ttl: ENDPOINT_IDEMPOTENCY_TTL_MS / 1000 });
3565            } else {
3566              await __noxidSharedIdempotencyComplete(idempotencyMapKey, sharedClaim, snapshot, ENDPOINT_IDEMPOTENCY_TTL_MS / 1000);
3567            }
3568            return snapshot;
3569          } catch (cause) {
3570            if (sharedClaim !== null) await __noxidSharedIdempotencyRelease(idempotencyMapKey, sharedClaim);
3571            throw cause;
3572          } finally {
3573            if (endpointIdempotencyInFlight.get(idempotencyMapKey) === promise) endpointIdempotencyInFlight.delete(idempotencyMapKey);
3574          }
3575        })();
3576        endpointIdempotencyInFlight.set(idempotencyMapKey, promise);
3577        return { promise };
3578      });
3579      if (prepared.response) return prepared.response;
3580      if (prepared.snapshot) return replayEndpointResponse(prepared.snapshot);
3581      const snapshot = await Promise.race([prepared.promise, endpointAbortResponse(signal, schema).then(endpointResponseSnapshot)]);
3582      return replayEndpointResponse(snapshot);
3583    } catch (cause) { return endpointResponseWithHeaders(endpointStorageFailure(schema, cause), middleware.headers); }
3584  }
3585  let rateFailure;
3586  try { rateFailure = await enforceEndpointRateLimit(schema, rateIdentity.identity); }
3587  catch (cause) { return endpointResponseWithHeaders(endpointStorageFailure(schema, cause), middleware.headers); }
3588  if (rateFailure) return endpointResponseWithHeaders(rateFailure, middleware.headers);
3589  return await invokeEndpoint(request, schema, argumentsRecord, middleware, environment, executionContext, signal, deadlineAt);
3590  } finally {
3591    await releaseEndpointUploads(request);
3592  }
3593  });
3594}
3595
3596const MCP_PROTOCOL_VERSIONS = Object.freeze(["2026-07-28", "2025-11-25", "2025-06-18", "2025-03-26"]);
3597const MCP_REQUEST_MAX_BYTES = 1_048_576;
3598const MCP_TOOL_RESULT_MAX_BYTES = 1_048_576;
3599const MCP_STREAM_MAX_EVENTS = 256;
3600
3601const mcpOpenApiDocument = openapiDocument === null ? null : JSON.parse(openapiDocument);
3602
3603function mcpOpenApiOperation(schema) {
3604  for (const pathItem of Object.values(mcpOpenApiDocument?.paths ?? {})) {
3605    const operation = pathItem?.[schema.method.toLowerCase()];
3606    if (operation?.["x-noxid-endpoint-id"] === schema.id && operation?.operationId === schema.name) return operation;
3607  }
3608  throw Object.assign(new Error(`OpenAPI is missing the exact routed operation for ${schema.id}`), { code: "MCP_OPENAPI_OPERATION_MISSING" });
3609}
3610
3611function mcpRewriteOpenApiSchema(value) {
3612  if (Array.isArray(value)) return value.map(mcpRewriteOpenApiSchema);
3613  if (value === null || typeof value !== "object") return value;
3614  const output = Object.create(null);
3615  for (const [key, nested] of Object.entries(value)) {
3616    output[key] = key === "$ref" && typeof nested === "string" && nested.startsWith("#/components/schemas/")
3617      ? `#/$defs/${nested.slice("#/components/schemas/".length)}`
3618      : mcpRewriteOpenApiSchema(nested);
3619  }
3620  return output;
3621}
3622
3623function mcpSchemaDefinitions() {
3624  const definitions = Object.create(null);
3625  for (const [name, schema] of Object.entries(mcpOpenApiDocument?.components?.schemas ?? {})) definitions[name] = mcpRewriteOpenApiSchema(schema);
3626  return definitions;
3627}
3628
3629function mcpAttachDefinitions(schema) {
3630  return Object.freeze({ ...schema, $defs: Object.freeze(mcpSchemaDefinitions()) });
3631}
3632
3633function mcpInputSchema(operation) {
3634  const properties = Object.create(null);
3635  const required = [];
3636  for (const parameter of operation.parameters ?? []) {
3637    const parameterSchema = parameter.schema ?? parameter.content?.["application/json"]?.schema;
3638    if (!parameterSchema || typeof parameter.name !== "string") throw Object.assign(new Error("OpenAPI endpoint parameter is missing its schema"), { code: "MCP_OPENAPI_SCHEMA_MISSING" });
3639    properties[parameter.name] = mcpRewriteOpenApiSchema(parameterSchema);
3640    if (parameter.required === true) required.push(parameter.name);
3641  }
3642  const bodySchema = operation.requestBody?.content?.["application/json"]?.schema;
3643  if (bodySchema) {
3644    const rewritten = mcpRewriteOpenApiSchema(bodySchema);
3645    for (const [name, schema] of Object.entries(rewritten.properties ?? {})) properties[name] = schema;
3646    for (const name of rewritten.required ?? []) if (!required.includes(name)) required.push(name);
3647  }
3648  return mcpAttachDefinitions({ type: "object", properties: Object.freeze(properties), required: Object.freeze(required), additionalProperties: false });
3649}
3650
3651function mcpOutputSchema(schema, operation) {
3652  const response = operation.responses?.["200"];
3653  let body;
3654  if (schema.kind === "stream") {
3655    const eventSchema = response?.content?.["text/event-stream"]?.["x-noxid-event-schema"];
3656    if (!eventSchema) throw Object.assign(new Error(`OpenAPI is missing the stream event schema for ${schema.id}`), { code: "MCP_OPENAPI_SCHEMA_MISSING" });
3657    const errorSchema = mcpOpenApiDocument?.components?.schemas?.NoxidErrorResponse?.properties?.error ?? {};
3658    body = { type: "object", properties: { ok: { type: "boolean" }, events: { type: "array", items: mcpRewriteOpenApiSchema(eventSchema) }, error: mcpRewriteOpenApiSchema(errorSchema) }, required: ["ok", "events"], additionalProperties: false };
3659  } else {
3660    const resultSchema = response?.content?.["application/json"]?.schema;
3661    if (!resultSchema) throw Object.assign(new Error(`OpenAPI is missing the result schema for ${schema.id}`), { code: "MCP_OPENAPI_SCHEMA_MISSING" });
3662    body = mcpRewriteOpenApiSchema(resultSchema);
3663  }
3664  return mcpAttachDefinitions({ type: "object", properties: { status: { type: "integer" }, body }, required: ["status", "body"], additionalProperties: false });
3665}
3666
3667const mcpEndpointSchemas = Object.freeze(endpointSchemas.filter((schema) => schema.path.length > 0 && schema.method.length > 0));
3668const mcpTools = Object.freeze((mcpEnabled ? mcpEndpointSchemas : []).map((schema) => {
3669  const operation = mcpOpenApiOperation(schema);
3670  const tool = { name: schema.name, inputSchema: mcpInputSchema(operation), outputSchema: mcpOutputSchema(schema, operation), "x-noxid-endpoint": Object.freeze({ semanticId: schema.id, version: schema.version, method: schema.method, path: schema.path, kind: schema.kind, signature: operation["x-noxid-signature"] }) };
3671  if (typeof operation.description === "string") tool.description = operation.description;
3672  return Object.freeze(tool);
3673}));
3674
3675function mcpRpcResult(id, result) {
3676  return Object.freeze({ jsonrpc: "2.0", id, result });
3677}
3678
3679function mcpRpcError(id, code, message, data = null) {
3680  const error = { code, message };
3681  if (data !== null) error.data = data;
3682  return Object.freeze({ jsonrpc: "2.0", id, error: Object.freeze(error) });
3683}
3684
3685function mcpJsonResponse(status, payload, headers = {}) {
3686  return new Response(payload === null ? null : JSON.stringify(payload), {
3687    status,
3688    headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "x-content-type-options": "nosniff", ...headers },
3689  });
3690}
3691
3692async function mcpRequestText(request) {
3693  const declared = Number(request.headers.get("content-length") ?? 0);
3694  if (Number.isFinite(declared) && declared > MCP_REQUEST_MAX_BYTES) throw Object.assign(new Error("MCP request body exceeds 1 MiB"), { code: "MCP_REQUEST_TOO_LARGE" });
3695  if (request.body === null) return "";
3696  const reader = request.body.getReader();
3697  const chunks = [];
3698  let total = 0;
3699  try {
3700    while (true) {
3701      const chunk = await reader.read();
3702      if (chunk.done) break;
3703      if (!(chunk.value instanceof Uint8Array)) throw new Error("MCP request body did not yield bytes");
3704      total += chunk.value.byteLength;
3705      if (total > MCP_REQUEST_MAX_BYTES) {
3706        await reader.cancel("MCP request body too large").catch(() => {});
3707        throw Object.assign(new Error("MCP request body exceeds 1 MiB"), { code: "MCP_REQUEST_TOO_LARGE" });
3708      }
3709      chunks.push(chunk.value);
3710    }
3711  } finally {
3712    try { reader.releaseLock(); } catch {}
3713  }
3714  const bytes = new Uint8Array(total);
3715  let offset = 0;
3716  for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
3717  try { return new TextDecoder("utf-8", { fatal: true }).decode(bytes); }
3718  catch { throw Object.assign(new Error("MCP request body must be UTF-8"), { code: "MCP_REQUEST_UTF8" }); }
3719}
3720
3721function mcpWireValue(value, type) {
3722  if (arrayWireType(type) !== null) return JSON.stringify(value);
3723  if (typeof value === "string") return value;
3724  if (typeof value === "number" || typeof value === "boolean") return String(value);
3725  return JSON.stringify(value);
3726}
3727
3728function mcpEndpointRequest(outerRequest, schema, args) {
3729  const validArguments = args !== null && typeof args === "object" && !Array.isArray(args) && Object.getPrototypeOf(args) === Object.prototype;
3730  const values = validArguments ? args : Object.freeze({});
3731  const declared = new Set([...schema.params, ...schema.query, ...schema.body].map((field) => field.name));
3732  const unknown = Object.keys(values).filter((name) => !declared.has(name));
3733  const transportErrors = validArguments ? [] : ["__noxid_mcp_arguments_must_be_object__"];
3734  let path = endpointFullPath(schema);
3735  for (const field of schema.params) {
3736    const present = Object.hasOwn(values, field.name);
3737    if (!present) transportErrors.push(`__noxid_mcp_missing_${field.name}__`);
3738    path = path.replace(`[${field.name}]`, encodeURIComponent(present ? mcpWireValue(values[field.name], field.type) : "__noxid_mcp_missing__"));
3739  }
3740  const url = new URL(outerRequest.url);
3741  url.pathname = path;
3742  url.search = "";
3743  for (const field of schema.query) {
3744    if (!Object.hasOwn(values, field.name) || values[field.name] === null) continue;
3745    url.searchParams.set(field.name, mcpWireValue(values[field.name], field.type));
3746  }
3747  const body = Object.create(null);
3748  for (const field of schema.body) if (Object.hasOwn(values, field.name)) body[field.name] = values[field.name];
3749  if (schema.body.length > 0) for (const name of unknown) body[name] = values[name];
3750  else for (const name of unknown) url.searchParams.set(name, mcpWireValue(values[name], "String"));
3751  for (const name of transportErrors) url.searchParams.set(name, "invalid");
3752  const headers = new Headers(outerRequest.headers);
3753  for (const name of ["accept", "content-length", "content-type", "last-event-id", "mcp-protocol-version", "mcp-session-id"]) headers.delete(name);
3754  const hasBody = schema.body.length > 0;
3755  if (hasBody) headers.set("content-type", "application/json");
3756  return new Request(url, { method: schema.method, headers, body: hasBody ? JSON.stringify(body) : undefined, signal: outerRequest.signal });
3757}
3758
3759async function mcpBoundedResponseText(response) {
3760  if (response.body === null) return "";
3761  const reader = response.body.getReader();
3762  const chunks = [];
3763  let total = 0;
3764  try {
3765    while (true) {
3766      const chunk = await reader.read();
3767      if (chunk.done) break;
3768      total += chunk.value.byteLength;
3769      if (total > MCP_TOOL_RESULT_MAX_BYTES) {
3770        await reader.cancel("MCP tool result too large").catch(() => {});
3771        throw Object.assign(new Error("MCP tool result exceeds the 1 MiB agent-surface limit"), { code: "MCP_TOOL_RESULT_TOO_LARGE" });
3772      }
3773      chunks.push(chunk.value);
3774    }
3775  } finally {
3776    try { reader.releaseLock(); } catch {}
3777  }
3778  const bytes = new Uint8Array(total);
3779  let offset = 0;
3780  for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
3781  try { return new TextDecoder("utf-8", { fatal: true }).decode(bytes); }
3782  catch { throw Object.assign(new Error("MCP tool response is not UTF-8"), { code: "MCP_TOOL_RESULT_UTF8" }); }
3783}
3784
3785function mcpStreamBody(text) {
3786  const events = [];
3787  let terminal = null;
3788  for (const block of text.replace(/\r\n/g, "\n").split("\n\n")) {
3789    if (block.length === 0 || block.split("\n").every((line) => line.length === 0 || line.startsWith(":"))) continue;
3790    let event = "message";
3791    const data = [];
3792    for (const line of block.split("\n")) {
3793      if (line.startsWith("event:")) event = line.slice(6).trimStart();
3794      if (line.startsWith("data:")) data.push(line.slice(5).replace(/^ /, ""));
3795    }
3796    if (data.length === 0) continue;
3797    let value;
3798    try { value = JSON.parse(data.join("\n")); }
3799    catch { throw Object.assign(new Error("Stream endpoint emitted invalid JSON through MCP"), { code: "MCP_STREAM_EVENT_INVALID" }); }
3800    if (event === "message") {
3801      events.push(value);
3802      if (events.length > MCP_STREAM_MAX_EVENTS) throw Object.assign(new Error("Stream endpoint exceeded the 256-event MCP result limit"), { code: "MCP_STREAM_EVENT_LIMIT" });
3803    } else if (event === "noxid-error") terminal = value?.error ?? Object.freeze({ code: "MCP_STREAM_FAILED", message: "Stream endpoint failed" });
3804    else throw Object.assign(new Error(`Stream endpoint emitted unsupported SSE event ${event}`), { code: "MCP_STREAM_EVENT_INVALID" });
3805  }
3806  return Object.freeze({ ok: terminal === null, events: Object.freeze(events), ...(terminal === null ? {} : { error: terminal }) });
3807}
3808
3809function mcpToolResult(status, body, isError) {
3810  const structuredContent = Object.freeze({ status, body });
3811  return Object.freeze({
3812    content: Object.freeze([Object.freeze({ type: "text", text: JSON.stringify(structuredContent) })]),
3813    structuredContent,
3814    isError,
3815  });
3816}
3817
3818async function callMcpEndpointTool(outerRequest, schema, args, environment, executionContext) {
3819  let endpointRequest;
3820  try { endpointRequest = mcpEndpointRequest(outerRequest, schema, args); }
3821  catch (cause) {
3822    const error = Object.freeze({ ok: false, error: Object.freeze({ code: cause?.code ?? "MCP_ARGUMENTS_INVALID", message: cause?.message ?? "MCP tool arguments are invalid", semanticId: schema.id, details: cause?.argument ? { argument: cause.argument } : null }) });
3823    return mcpToolResult(400, error, true);
3824  }
3825  inheritNoxidRequestTrace(outerRequest, endpointRequest);
3826  __noxidAgentRequests.set(endpointRequest, schema.id);
3827  const response = await withNoxidRequestTrace(endpointRequest, () => handleEndpointRequest(endpointRequest, new URL(endpointRequest.url), environment, executionContext));
3828  if (response === null) return mcpToolResult(500, Object.freeze({ ok: false, error: Object.freeze({ code: "MCP_ENDPOINT_DISPATCH_FAILED", message: "MCP tool did not resolve to its declared HTTP endpoint", semanticId: schema.id, details: null }) }), true);
3829  __noxidTraceResponseFailure(endpointRequest, response);
3830  try {
3831    const text = await mcpBoundedResponseText(response);
3832    if (schema.kind === "stream") return mcpToolResult(response.status, mcpStreamBody(text), text.includes("event: noxid-error"));
3833    let body;
3834    try { body = text.length === 0 ? null : JSON.parse(text); }
3835    catch { body = Object.freeze({ contentType: response.headers.get("content-type"), text }); }
3836    return mcpToolResult(response.status, body, !response.ok || body?.ok === false);
3837  } catch (cause) {
3838    return mcpToolResult(500, Object.freeze({ ok: false, error: Object.freeze({ code: cause?.code ?? "MCP_TOOL_RESULT_FAILED", message: cause?.message ?? "MCP tool result could not be represented safely", semanticId: schema.id, details: null }) }), true);
3839  }
3840}
3841
3842async function handleMcpRequest(request, environment, executionContext) {
3843  if (request.method !== "POST") return mcpJsonResponse(405, mcpRpcError(null, -32600, "The MCP endpoint accepts POST only"), { allow: "POST" });
3844  const contentType = request.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase();
3845  if (contentType !== "application/json") return mcpJsonResponse(415, mcpRpcError(null, -32600, "Content-Type must be application/json"));
3846  const accept = request.headers.get("accept") ?? "";
3847  if (!(accept.includes("application/json") && accept.includes("text/event-stream"))) return mcpJsonResponse(406, mcpRpcError(null, -32600, "Accept must include application/json and text/event-stream"));
3848  let payload;
3849  try { payload = JSON.parse(await mcpRequestText(request)); }
3850  catch (cause) { return mcpJsonResponse(cause?.code === "MCP_REQUEST_TOO_LARGE" ? 413 : 400, mcpRpcError(null, -32700, cause?.message ?? "MCP request must be valid JSON")); }
3851  if (payload === null || typeof payload !== "object" || Array.isArray(payload) || payload.jsonrpc !== "2.0" || typeof payload.method !== "string") return mcpJsonResponse(400, mcpRpcError(payload?.id ?? null, -32600, "Invalid JSON-RPC request"));
3852  const notification = !Object.hasOwn(payload, "id");
3853  if (notification) {
3854    return payload.method === "notifications/initialized" || payload.method === "notifications/cancelled"
3855      ? mcpJsonResponse(202, null)
3856      : mcpJsonResponse(400, mcpRpcError(null, -32600, "Unsupported MCP notification"));
3857  }
3858  const id = payload.id;
3859  const headerVersion = request.headers.get("mcp-protocol-version");
3860  if (headerVersion !== null && !MCP_PROTOCOL_VERSIONS.includes(headerVersion)) return mcpJsonResponse(400, mcpRpcError(id, -32022, `Unsupported MCP protocol version ${headerVersion}`));
3861  if (payload.method === "initialize") {
3862    const version = payload.params?.protocolVersion;
3863    if (typeof version !== "string" || !MCP_PROTOCOL_VERSIONS.includes(version)) return mcpJsonResponse(400, mcpRpcError(id, -32602, "initialize requires a supported params.protocolVersion"));
3864    if (headerVersion !== null && headerVersion !== version) return mcpJsonResponse(400, mcpRpcError(id, -32020, "MCP-Protocol-Version must match initialize params.protocolVersion"));
3865    return mcpJsonResponse(200, mcpRpcResult(id, Object.freeze({ protocolVersion: version, serverInfo: Object.freeze({ name: "noxid-endpoints", version: "0.1.0" }), capabilities: Object.freeze({ tools: Object.freeze({ listChanged: false }) }) })));
3866  }
3867  if (payload.method === "ping") return mcpJsonResponse(200, mcpRpcResult(id, Object.freeze({})));
3868  if (payload.method === "tools/list") return mcpJsonResponse(200, mcpRpcResult(id, Object.freeze({ tools: mcpTools })));
3869  if (payload.method === "tools/call") {
3870    const name = payload.params?.name;
3871    if (typeof name !== "string") return mcpJsonResponse(400, mcpRpcError(id, -32602, "tools/call requires params.name"));
3872    const schema = mcpEndpointSchemas.find((candidate) => candidate.name === name);
3873    if (!schema) return mcpJsonResponse(404, mcpRpcError(id, -32601, `Unknown endpoint tool ${name}`));
3874    const result = await callMcpEndpointTool(request, schema, payload.params?.arguments ?? Object.freeze({}), environment, executionContext);
3875    return mcpJsonResponse(200, mcpRpcResult(id, result));
3876  }
3877  return mcpJsonResponse(404, mcpRpcError(id, -32601, `Unknown MCP method ${payload.method}`));
3878}
3879
3880async function handleAgentSurfaceRequest(request, url, environment, executionContext) {
3881  const prefix = applicationBasePath === "/" ? "" : applicationBasePath.replace(/\/$/, "");
3882  /* noxid-server:agent-run-request */
3883  if (url.pathname === `${prefix}/_noxid/openapi.json`) {
3884    if (!openapiEnabled || openapiDocument === null) return failure(404, "AGENT_SURFACE_DISABLED", "OpenAPI serving is disabled");
3885    if (request.method !== "GET") return failure(405, "OPENAPI_METHOD", "OpenAPI serving requires GET", null, null, { allow: "GET" });
3886    return new Response(openapiDocument, { status: 200, headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "x-content-type-options": "nosniff" } });
3887  }
3888  if (url.pathname === `${prefix}/_noxid/mcp`) {
3889    if (!mcpEnabled) return failure(404, "AGENT_SURFACE_DISABLED", "MCP serving is disabled");
3890    return handleMcpRequest(request, environment, executionContext);
3891  }
3892  return null;
3893}
3894
3895"##;
3896
3897const QUEUE_RUNTIME: &str = r##"
3898let queueDatabasePromise;
3899let queueDatabase;
3900const QUEUE_POLL_INTERVAL_MS = 250;
3901
3902function queueSchemaByName(name) {
3903  return queueSchemas.find((schema) => schema.name === name) ?? null;
3904}
3905
3906function queueDatabaseUrl() {
3907  const node = globalThis.process?.env?.DATABASE_URL;
3908  if (typeof node === "string" && node.length > 0) return node;
3909  try {
3910    const deno = globalThis.Deno?.env?.get?.("DATABASE_URL");
3911    if (typeof deno === "string" && deno.length > 0) return deno;
3912  } catch {}
3913  return null;
3914}
3915
3916async function queueSql() {
3917  if (queueDatabasePromise !== undefined) return queueDatabasePromise;
3918  queueDatabasePromise = (async () => {
3919    const url = queueDatabaseUrl();
3920    if (url === null) throw Object.assign(new Error("DATABASE_URL is required for durable queues"), { code: "QUEUE_DATABASE_URL_REQUIRED" });
3921    let postgres;
3922    try { postgres = (await import("postgres")).default; }
3923    catch { throw Object.assign(new Error("the admitted postgres driver is unavailable"), { code: "QUEUE_POSTGRES_DRIVER_MISSING" }); }
3924    const sql = postgres(url, { max: databasePoolSize });
3925    await sql.unsafe(`CREATE TABLE IF NOT EXISTS _noxid_jobs (
3926      id text PRIMARY KEY,
3927      queue text NOT NULL,
3928      payload jsonb NOT NULL,
3929      principal text,
3930      state text NOT NULL CHECK (state IN ('pending','running','completed','dead-letter')),
3931      attempts integer NOT NULL DEFAULT 0 CHECK (attempts >= 0),
3932      run_at timestamptz NOT NULL,
3933      locked_by text,
3934      locked_at timestamptz,
3935      last_error text,
3936      created_at timestamptz NOT NULL DEFAULT now(),
3937      updated_at timestamptz NOT NULL DEFAULT now()
3938    )`);
3939    await sql`ALTER TABLE _noxid_jobs ADD COLUMN IF NOT EXISTS principal text`;
3940    await sql.unsafe("CREATE INDEX IF NOT EXISTS _noxid_jobs_claim ON _noxid_jobs (queue, state, run_at, created_at)");
3941    queueDatabase = sql;
3942    return sql;
3943  })();
3944  try { return await queueDatabasePromise; }
3945  catch (error) { queueDatabasePromise = undefined; throw error; }
3946}
3947
3948function validateQueuePayload(schema, payload, phase) {
3949  let keys;
3950  try {
3951    if (payload === null || typeof payload !== "object" || Array.isArray(payload)) throw new Error("shape");
3952    const prototype = Object.getPrototypeOf(payload);
3953    if (prototype !== Object.prototype && prototype !== null) throw new Error("prototype");
3954    keys = Reflect.ownKeys(payload);
3955  } catch {
3956    throw Object.assign(new TypeError(`queue ${schema.name} ${phase} payload must be an ordinary object`), { code: phase === "claim" ? "QUEUE_PAYLOAD_DRIFT" : "QUEUE_PAYLOAD_TYPE", semanticId: schema.id });
3957  }
3958  if (keys.some((key) => typeof key !== "string") || keys.some((key) => !schema.payload.some((field) => field.name === key))) {
3959    throw Object.assign(new TypeError(`queue ${schema.name} ${phase} payload has undeclared fields`), { code: phase === "claim" ? "QUEUE_PAYLOAD_DRIFT" : "QUEUE_PAYLOAD_TYPE", semanticId: schema.id });
3960  }
3961  const trusted = Object.create(null);
3962  for (const field of schema.payload) {
3963    let descriptor;
3964    try { descriptor = Object.getOwnPropertyDescriptor(payload, field.name); } catch {}
3965    const optional = field.type.startsWith("Optional<");
3966    if (descriptor === undefined) {
3967      if (optional) { trusted[field.name] = null; continue; }
3968      throw Object.assign(new TypeError(`queue ${schema.name} ${phase} payload is missing ${field.name}`), { code: phase === "claim" ? "QUEUE_PAYLOAD_DRIFT" : "QUEUE_PAYLOAD_TYPE", semanticId: schema.id });
3969    }
3970    if (!("value" in descriptor) || !descriptor.enumerable) {
3971      throw Object.assign(new TypeError(`queue ${schema.name} ${phase} payload field ${field.name} is not ordinary data`), { code: phase === "claim" ? "QUEUE_PAYLOAD_DRIFT" : "QUEUE_PAYLOAD_TYPE", semanticId: schema.id });
3972    }
3973    const result = validateType(field.type, descriptor.value, `payload.${field.name}`, field.typeIds, true);
3974    if (result.issue) throw Object.assign(new TypeError(result.issue), { code: phase === "claim" ? "QUEUE_PAYLOAD_DRIFT" : "QUEUE_PAYLOAD_TYPE", semanticId: schema.id, details: result.details ?? null });
3975    trusted[field.name] = result.value;
3976  }
3977  return Object.freeze(trusted);
3978}
3979
3980function queueUtcInstant(value) {
3981  if (typeof value !== "string") return null;
3982  const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?Z$/.exec(value);
3983  if (match === null) return null;
3984  const instant = new Date(value);
3985  const milliseconds = Number((match[7] ?? "").padEnd(3, "0"));
3986  return !Number.isNaN(instant.getTime())
3987      && instant.getUTCFullYear() === Number(match[1])
3988      && instant.getUTCMonth() + 1 === Number(match[2])
3989      && instant.getUTCDate() === Number(match[3])
3990      && instant.getUTCHours() === Number(match[4])
3991      && instant.getUTCMinutes() === Number(match[5])
3992      && instant.getUTCSeconds() === Number(match[6])
3993      && instant.getUTCMilliseconds() === milliseconds
3994    ? instant
3995    : null;
3996}
3997
3998function queueRunAt(value) {
3999  if (value === undefined) return new Date();
4000  if (typeof value === "string") {
4001    const instant = queueUtcInstant(value);
4002    if (instant !== null) return instant;
4003  } else if (value !== null && typeof value === "object") {
4004    try {
4005      const milliseconds = Date.prototype.getTime.call(value);
4006      if (!Number.isNaN(milliseconds)) return new Date(milliseconds);
4007    } catch {}
4008  }
4009  throw Object.assign(new TypeError("queue runAt must be a valid Date or UTC ISO timestamp"), { code: "QUEUE_RUN_AT_INVALID" });
4010}
4011
4012function queueJobId() {
4013  if (typeof globalThis.crypto?.randomUUID === "function") return globalThis.crypto.randomUUID();
4014  throw Object.assign(new Error("durable queue enqueue requires crypto.randomUUID"), { code: "QUEUE_ID_UNAVAILABLE" });
4015}
4016
4017export async function enqueue(queue, payload, options = Object.create(null)) {
4018  if (typeof queue !== "string") throw Object.assign(new TypeError("queue name must be a string"), { code: "QUEUE_NAME_INVALID" });
4019  const schema = queueSchemaByName(queue);
4020  if (schema === null) throw Object.assign(new Error("Unknown queue " + queue), { code: "QUEUE_NOT_FOUND" });
4021  let optionKeys;
4022  let runAtDescriptor;
4023  let contextDescriptor;
4024  try {
4025    if (options === null || typeof options !== "object" || Array.isArray(options)) throw new Error("shape");
4026    const prototype = Object.getPrototypeOf(options);
4027    if (prototype !== Object.prototype && prototype !== null) throw new Error("prototype");
4028    optionKeys = Reflect.ownKeys(options);
4029    if (optionKeys.some((key) => key !== "runAt" && key !== "context")) throw new Error("field");
4030    runAtDescriptor = Object.getOwnPropertyDescriptor(options, "runAt");
4031    if (runAtDescriptor !== undefined && (!("value" in runAtDescriptor) || !runAtDescriptor.enumerable)) throw new Error("descriptor");
4032    contextDescriptor = Object.getOwnPropertyDescriptor(options, "context");
4033    if (contextDescriptor !== undefined && (!("value" in contextDescriptor) || !contextDescriptor.enumerable)) throw new Error("descriptor");
4034  }
4035  catch { throw Object.assign(new TypeError("queue options must be an ordinary object"), { code: "QUEUE_OPTIONS_INVALID" }); }
4036  const trusted = validateQueuePayload(schema, payload, "enqueue");
4037  const runAt = queueRunAt(runAtDescriptor?.value);
4038  const captured = contextDescriptor === undefined ? __NOXID_SYSTEM_PRINCIPAL : __noxidRuntimePrincipals.get(contextDescriptor.value);
4039  if (contextDescriptor !== undefined && captured === undefined) {
4040    throw Object.assign(new TypeError("queue context must be a runtime-created execution context"), { code: "QUEUE_PRINCIPAL_CONTEXT_INVALID" });
4041  }
4042  const persistedPrincipal = captured?.canonical === "system" ? null : captured?.canonical;
4043  if (persistedPrincipal !== null && typeof persistedPrincipal !== "string") throw Object.assign(new TypeError("queue context has no canonical principal"), { code: "QUEUE_PRINCIPAL_CONTEXT_INVALID" });
4044  const sql = await queueSql();
4045  const id = queueJobId();
4046  await sql`INSERT INTO _noxid_jobs (id, queue, payload, principal, state, attempts, run_at) VALUES (${id}, ${schema.name}, ${sql.json(trusted)}, ${persistedPrincipal}, 'pending', 0, ${runAt})`;
4047  return Object.freeze({ id, queue: schema.name, state: "pending", attempts: 0, runAt: runAt.toISOString() });
4048}
4049
4050async function claimQueueJob(worker, queue = null, now = new Date()) {
4051  const sql = await queueSql();
4052  return sql.begin(async (transaction) => {
4053    const rows = queue === null
4054      ? await transaction`SELECT id, queue, payload, principal, attempts, run_at FROM _noxid_jobs WHERE state = 'pending' AND run_at <= ${now} ORDER BY run_at, created_at, id FOR UPDATE SKIP LOCKED LIMIT 1`
4055      : await transaction`SELECT id, queue, payload, principal, attempts, run_at FROM _noxid_jobs WHERE state = 'pending' AND queue = ${queue} AND run_at <= ${now} ORDER BY run_at, created_at, id FOR UPDATE SKIP LOCKED LIMIT 1`;
4056    const row = rows[0];
4057    if (row === undefined) return null;
4058    const schema = queueSchemaByName(row.queue);
4059    if (schema === null) {
4060      await transaction`UPDATE _noxid_jobs SET state = 'dead-letter', last_error = 'QUEUE_DECLARATION_MISSING', locked_by = NULL, locked_at = NULL, updated_at = now() WHERE id = ${row.id}`;
4061      return Object.freeze({ drift: true, id: row.id, code: "QUEUE_DECLARATION_MISSING" });
4062    }
4063    let payload;
4064    try { payload = validateQueuePayload(schema, row.payload, "claim"); }
4065    catch (cause) {
4066      await transaction`UPDATE _noxid_jobs SET state = 'dead-letter', last_error = 'QUEUE_PAYLOAD_DRIFT', locked_by = NULL, locked_at = NULL, updated_at = now() WHERE id = ${row.id}`;
4067      return Object.freeze({ drift: true, id: row.id, queue: schema.name, code: cause?.code ?? "QUEUE_PAYLOAD_DRIFT" });
4068    }
4069    const attempts = Number(row.attempts) + 1;
4070    await transaction`UPDATE _noxid_jobs SET state = 'running', attempts = ${attempts}, locked_by = ${worker}, locked_at = ${now}, updated_at = now() WHERE id = ${row.id}`;
4071    let principal;
4072    try { principal = __noxidPrincipalFromCanonical(row.principal); }
4073    catch (cause) {
4074      await transaction`UPDATE _noxid_jobs SET state = 'dead-letter', last_error = 'QUEUE_PRINCIPAL_DRIFT', locked_by = NULL, locked_at = NULL, updated_at = now() WHERE id = ${row.id}`;
4075      return Object.freeze({ drift: true, id: row.id, queue: schema.name, code: cause?.code ?? "QUEUE_PRINCIPAL_DRIFT" });
4076    }
4077    return Object.freeze({ id: row.id, queue: schema.name, schema, payload, principal, attempts, runAt: new Date(row.run_at).toISOString() });
4078  });
4079}
4080
4081async function completeQueueJob(id) {
4082  const sql = await queueSql();
4083  await sql`UPDATE _noxid_jobs SET state = 'completed', locked_by = NULL, locked_at = NULL, last_error = NULL, updated_at = now() WHERE id = ${id}`;
4084}
4085
4086async function failQueueJob(job, cause, now) {
4087  const sql = await queueSql();
4088  const message = typeof cause?.message === "string" ? cause.message.slice(0, 4096) : "Queue handler failed";
4089  if (job.attempts <= job.schema.retry) {
4090    const next = new Date(now.getTime() + job.schema.backoffMs);
4091    await sql`UPDATE _noxid_jobs SET state = 'pending', run_at = ${next}, locked_by = NULL, locked_at = NULL, last_error = ${message}, updated_at = now() WHERE id = ${job.id}`;
4092    return Object.freeze({ id: job.id, queue: job.queue, state: "pending", attempts: job.attempts, runAt: next.toISOString() });
4093  }
4094  await sql`UPDATE _noxid_jobs SET state = 'dead-letter', locked_by = NULL, locked_at = NULL, last_error = ${message}, updated_at = now() WHERE id = ${job.id}`;
4095  return Object.freeze({ id: job.id, queue: job.queue, state: "dead-letter", attempts: job.attempts, runAt: job.runAt });
4096}
4097
4098function queueWorkerFailure(message) {
4099  throw Object.assign(new TypeError(message), { code: "QUEUE_CLOCK_INVALID" });
4100}
4101
4102function queueWorkerOptions(options, allowed) {
4103  let trustedOptions;
4104  try {
4105    if (options === null || typeof options !== "object" || Array.isArray(options)) throw new Error("shape");
4106    const prototype = Object.getPrototypeOf(options);
4107    if (prototype !== Object.prototype && prototype !== null) throw new Error("prototype");
4108    trustedOptions = Object.create(null);
4109    for (const key of Reflect.ownKeys(options)) {
4110      if (typeof key !== "string" || !allowed.has(key)) throw new Error("field");
4111      const descriptor = Object.getOwnPropertyDescriptor(options, key);
4112      if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) throw new Error("descriptor");
4113      trustedOptions[key] = descriptor.value;
4114    }
4115    Object.freeze(trustedOptions);
4116  } catch {
4117    throw Object.assign(new TypeError("worker clock options must be ordinary data"), { code: "QUEUE_CLOCK_INVALID" });
4118  }
4119  return trustedOptions;
4120}
4121
4122function queueWorkerIdentity(value, label) {
4123  if (value === undefined || value === null) return null;
4124  if (typeof value !== "string") queueWorkerFailure(`worker ${label} must be a string`);
4125  return value;
4126}
4127
4128function queueWorkerClock(value) {
4129  if (value === undefined) return new Date();
4130  if (typeof value === "string") {
4131    const instant = queueUtcInstant(value);
4132    if (instant !== null) return instant;
4133  } else if (value !== null && typeof value === "object") {
4134    try {
4135      const milliseconds = Date.prototype.getTime.call(value);
4136      if (!Number.isNaN(milliseconds)) return new Date(milliseconds);
4137    } catch {}
4138  }
4139  queueWorkerFailure("worker clock must be a valid Date or UTC ISO timestamp");
4140}
4141
4142async function workQueueOnceWithLifecycle(options, onRunning) {
4143  const trustedOptions = queueWorkerOptions(options, new Set(["queue", "worker", "now"]));
4144  const queue = queueWorkerIdentity(trustedOptions.queue, "queue");
4145  if (queue !== null && queueSchemaByName(queue) === null) throw Object.assign(new Error("Unknown queue " + queue), { code: "QUEUE_NOT_FOUND" });
4146  const now = queueWorkerClock(trustedOptions.now);
4147  const worker = queueWorkerIdentity(trustedOptions.worker, "identity") ?? `noxid-${globalThis.process?.pid ?? "worker"}-${queueJobId()}`;
4148  const job = await claimQueueJob(worker, queue, now);
4149  if (job === null) return null;
4150  if (job.drift) {
4151    const refusalTrace = tracingMode === "full" ? __noxidTraceContext() : null;
4152    __noxidTraceEmit(refusalTrace, "validation.refused", { code: job.code, jobId: job.id });
4153    throw Object.assign(new Error("Persisted queue payload refused at claim"), { code: job.code, jobId: job.id });
4154  }
4155  const queueSpan = __noxidTraceBeginSemantic(null);
4156  const traceId = queueSpan?.trace?.id ?? null;
4157  const implementation = compiledQueues[job.schema.id] ?? hostQueues[job.schema.id];
4158  if (typeof implementation !== "function") {
4159    __noxidTraceFinishSemantic(queueSpan, "queue", job.schema.id, { jobId: job.id, attempts: job.attempts, spanName: "queue.run" });
4160    return failQueueJob(job, Object.assign(new Error("Queue implementation is missing"), { code: "QUEUE_IMPLEMENTATION_MISSING" }), now);
4161  }
4162  if (onRunning !== null) onRunning(job);
4163  let completed = false;
4164  try {
4165    const value = await implementation(job.payload, __noxidDataContext({ semanticId: job.schema.id, traceId, queue: job.queue, jobId: job.id, attempts: job.attempts, runAt: job.runAt }, job.principal));
4166    await completeQueueJob(job.id);
4167    completed = true;
4168    await __noxidPublishLiveInvalidations(job.schema.invalidates, job.principal);
4169    return Object.freeze({ id: job.id, queue: job.queue, state: "completed", attempts: job.attempts, runAt: job.runAt, value: value === undefined ? null : value });
4170  } catch (cause) {
4171    if (completed) throw cause;
4172    return failQueueJob(job, cause, now);
4173  } finally {
4174    __noxidTraceFinishSemantic(queueSpan, "queue", job.schema.id, { jobId: job.id, attempts: job.attempts, spanName: "queue.run" });
4175  }
4176}
4177
4178export async function workQueueOnce(options = Object.create(null)) {
4179  return workQueueOnceWithLifecycle(options, null);
4180}
4181
4182const QUEUE_DRAIN_SEMANTIC_ID = "queue-drain:on-demand";
4183const QUEUE_DRAIN_DEFAULT_BUDGET_MS = 25_000;
4184const QUEUE_DRAIN_MAX_BUDGET_MS = 300_000;
4185
4186function queueDrainBudget(executionContext) {
4187  let configured;
4188  try { configured = executionContext?.queueDrainBudgetMs; }
4189  catch { return null; }
4190  if (configured === undefined) return QUEUE_DRAIN_DEFAULT_BUDGET_MS;
4191  return Number.isSafeInteger(configured) && configured > 0 && configured <= QUEUE_DRAIN_MAX_BUDGET_MS
4192    ? configured
4193    : null;
4194}
4195
4196async function handleQueueDrainRequest(request, url, environment, executionContext) {
4197  if (url.pathname !== queueDrainPath) return null;
4198  let enabled = false;
4199  try { enabled = executionContext?.noxidQueueDrain === true; } catch {}
4200  if (!enabled) return failure(404, "QUEUE_DRAIN_DISABLED", "Queue draining is not enabled for this deployment", QUEUE_DRAIN_SEMANTIC_ID);
4201  if (request.method !== "POST") return failure(405, "QUEUE_DRAIN_METHOD", "Queue draining requires POST", QUEUE_DRAIN_SEMANTIC_ID, null, { allow: "POST" });
4202  const budgetMs = queueDrainBudget(executionContext);
4203  if (budgetMs === null) return failure(500, "QUEUE_DRAIN_BUDGET_INVALID", "The deployment supplied an invalid queue drain budget; configure a positive duration no greater than 300000ms", QUEUE_DRAIN_SEMANTIC_ID);
4204  if (queueSchemas.length === 0) return failure(501, "QUEUE_DRAIN_UNAVAILABLE", "Queue draining requires at least one declared queue", QUEUE_DRAIN_SEMANTIC_ID);
4205  if (typeof authorize !== "function") return failure(500, "QUEUE_DRAIN_AUTHORIZER_MISSING", "Queue draining requires a host authorizer for the queue.drain capability", QUEUE_DRAIN_SEMANTIC_ID, { capability: "queue.drain" });
4206  let allowed = false;
4207  try {
4208    allowed = await authorize(Object.freeze({
4209      capability: "queue.drain",
4210      semanticId: QUEUE_DRAIN_SEMANTIC_ID,
4211      traceId: __noxidTraceIdForRequest(request),
4212      target: "server",
4213      route: null,
4214      request,
4215      environment,
4216      executionContext,
4217    })) === true;
4218  } catch {}
4219  if (!allowed) return failure(403, "QUEUE_DRAIN_CAPABILITY_DENIED", "Queue drain capability was denied", QUEUE_DRAIN_SEMANTIC_ID, { capability: "queue.drain" });
4220
4221  const startedAt = Date.now();
4222  const counts = { claimed: 0, completed: 0, retried: 0, deadLettered: 0 };
4223  try {
4224    while (Date.now() - startedAt < budgetMs) {
4225      const result = await workQueueOnce();
4226      if (result === null) break;
4227      counts.claimed += 1;
4228      if (result.state === "completed") counts.completed += 1;
4229      else if (result.state === "pending") counts.retried += 1;
4230      else if (result.state === "dead-letter") counts.deadLettered += 1;
4231    }
4232  } catch (cause) {
4233    const causeCode = typeof cause?.code === "string" ? cause.code : "QUEUE_DRAIN_EXECUTION_FAILED";
4234    return failure(500, "QUEUE_DRAIN_FAILED", "Queue draining failed; inspect the durable queue database and handler configuration", QUEUE_DRAIN_SEMANTIC_ID, { causeCode });
4235  }
4236  return json(200, {
4237    ok: true,
4238    budgetMs,
4239    elapsedMs: Math.max(0, Date.now() - startedAt),
4240    counts: Object.freeze(counts),
4241  });
4242}
4243
4244const QUEUE_WORKER_STATE_NAMES = Object.freeze(["Idle", "Scheduled", "Claiming", "Running", "Stopping", "Stopped", "Failed"]);
4245const QUEUE_WORKER_EVENT_NAMES = Object.freeze(["Start", "Arm", "Deliver", "Claimed", "Settle", "Stop", "ArmFailed", "Notify"]);
4246const QUEUE_WORKER_TRANSITIONS = Object.freeze({
4247  Idle: Object.freeze({ Start: "Claiming", Arm: "Scheduled", Deliver: "Idle", Claimed: "Idle", Settle: "Idle", Stop: "Stopped", ArmFailed: "Idle", Notify: "Idle" }),
4248  Scheduled: Object.freeze({ Start: "Scheduled", Arm: "Scheduled", Deliver: "Claiming", Claimed: "Scheduled", Settle: "Scheduled", Stop: "Stopped", ArmFailed: "Failed", Notify: "Scheduled" }),
4249  Claiming: Object.freeze({ Start: "Claiming", Arm: "Claiming", Deliver: "Claiming", Claimed: "Running", Settle: "Idle", Stop: "Stopping", ArmFailed: "Failed", Notify: "Claiming" }),
4250  Running: Object.freeze({ Start: "Running", Arm: "Running", Deliver: "Running", Claimed: "Running", Settle: "Idle", Stop: "Stopping", ArmFailed: "Running", Notify: "Running" }),
4251  Stopping: Object.freeze({ Start: "Stopping", Arm: "Stopping", Deliver: "Stopping", Claimed: "Stopping", Settle: "Stopped", Stop: "Stopping", ArmFailed: "Stopping", Notify: "Stopping" }),
4252  Stopped: Object.freeze({ Start: "Stopped", Arm: "Stopped", Deliver: "Stopped", Claimed: "Stopped", Settle: "Stopped", Stop: "Stopped", ArmFailed: "Stopped", Notify: "Stopped" }),
4253  Failed: Object.freeze({ Start: "Failed", Arm: "Failed", Deliver: "Failed", Claimed: "Failed", Settle: "Failed", Stop: "Failed", ArmFailed: "Failed", Notify: "Failed" }),
4254});
4255
4256function queueWorkerState(name, detail = null) {
4257  if (!QUEUE_WORKER_STATE_NAMES.includes(name)) throw new Error(`Unknown queue worker state ${name}`);
4258  return Object.freeze({ name, detail });
4259}
4260
4261function queueWorkerEvent(name, detail = null) {
4262  if (!QUEUE_WORKER_EVENT_NAMES.includes(name)) throw new Error(`Unknown queue worker event ${name}`);
4263  return Object.freeze({ name, detail });
4264}
4265
4266function queueWorkerTransition(state, event) {
4267  const target = QUEUE_WORKER_TRANSITIONS[state.name]?.[event.name];
4268  if (target === undefined) throw new Error(`Unknown queue worker transition ${state.name} x ${event.name}`);
4269  if (state.name === "Scheduled" && (event.name === "Deliver" || event.name === "ArmFailed") && state.detail !== event.detail?.token) return state;
4270  if (target === state.name) return state;
4271  if (target === "Scheduled") return queueWorkerState(target, event.detail?.token ?? null);
4272  if (target === "Running") return queueWorkerState(target, event.detail?.job ?? null);
4273  if (target === "Failed") return queueWorkerState(target, event.detail?.error ?? null);
4274  return queueWorkerState(target);
4275}
4276
4277export function startQueueWorker(options = Object.create(null)) {
4278  const trustedOptions = queueWorkerOptions(options, new Set(["queue", "worker", "now", "setTimeout", "clearTimeout", "pollIntervalMs", "onError"]));
4279  const queue = queueWorkerIdentity(trustedOptions.queue, "queue");
4280  if (queue !== null && queueSchemaByName(queue) === null) throw Object.assign(new Error("Unknown queue " + queue), { code: "QUEUE_NOT_FOUND" });
4281  const worker = queueWorkerIdentity(trustedOptions.worker, "identity");
4282  const claimOptions = Object.create(null);
4283  if (queue !== null) claimOptions.queue = queue;
4284  if (worker !== null) claimOptions.worker = worker;
4285  if (trustedOptions.now !== undefined) claimOptions.now = queueWorkerClock(trustedOptions.now);
4286  Object.freeze(claimOptions);
4287  const setTimer = trustedOptions.setTimeout ?? globalThis.setTimeout;
4288  const clearTimer = trustedOptions.clearTimeout ?? globalThis.clearTimeout;
4289  const interval = trustedOptions.pollIntervalMs ?? QUEUE_POLL_INTERVAL_MS;
4290  const onError = trustedOptions.onError ?? console.error;
4291  if (typeof setTimer !== "function") queueWorkerFailure("worker setTimeout must be callable");
4292  if (typeof clearTimer !== "function") queueWorkerFailure("worker clearTimeout must be callable");
4293  if (typeof onError !== "function") queueWorkerFailure("worker onError must be callable");
4294  if (!Number.isSafeInteger(interval) || interval <= 0) queueWorkerFailure("worker pollIntervalMs must be a positive integer");
4295  let state = queueWorkerState("Idle");
4296  let queueSemanticId = queue === null ? null : queueSchemaByName(queue).id;
4297  const workerTrace = tracingMode === "full" ? __noxidTraceContext() : null;
4298  const transition = (event) => {
4299    const previous = state;
4300    const next = queueWorkerTransition(state, event);
4301    if (next === previous) return next;
4302    if (event.name === "Claimed" && typeof event.detail?.job?.schema?.id === "string") queueSemanticId = event.detail.job.schema.id;
4303    __noxidTraceEmit(workerTrace, "queue.state", {
4304      semanticId: queueSemanticId,
4305      state: next.name,
4306      transition: event.name,
4307      jobId: event.detail?.job?.id,
4308      attempts: event.detail?.job?.attempts,
4309    });
4310    return next;
4311  };
4312  let nextTimerToken = 0;
4313  let activeAttempt = null;
4314  let stopJoin = null;
4315  const settledJoin = Promise.resolve();
4316  const hookFailure = (cause) => { try { console.error(cause); } catch {} };
4317  const observeHookResult = (result) => {
4318    Promise.resolve(result).catch(hookFailure);
4319  };
4320  const clearToken = (token) => {
4321    if (token.delivered || token.clearInvoked) return;
4322    token.cancelled = true;
4323    token.consumed = true;
4324    if (!token.handleReady) return;
4325    token.clearInvoked = true;
4326    try { observeHookResult(clearTimer(token.handle)); }
4327    catch (cause) { hookFailure(cause); }
4328  };
4329  const notify = (error) => {
4330    try { observeHookResult(onError(error)); }
4331    catch (cause) { hookFailure(cause); }
4332  };
4333  const settleAttempt = (attempt, error) => {
4334    if (activeAttempt !== attempt || attempt.settled) return;
4335    attempt.settled = true;
4336    state = transition(queueWorkerEvent("Settle"));
4337    activeAttempt = null;
4338    if (error !== null) notify(error);
4339    if (state.name === "Idle") arm();
4340    attempt.resolveJoin();
4341  };
4342  const reserveAttempt = () => {
4343    let resolveJoin;
4344    const attempt = { settled: false, join: new Promise((resolve) => { resolveJoin = resolve; }), resolveJoin: null };
4345    attempt.resolveJoin = resolveJoin;
4346    activeAttempt = attempt;
4347    return attempt;
4348  };
4349  const launchAttempt = (attempt) => {
4350    if (attempt.settled) return;
4351    let result;
4352    try {
4353      result = workQueueOnceWithLifecycle(claimOptions, (job) => {
4354        if (activeAttempt === attempt && !attempt.settled) state = transition(queueWorkerEvent("Claimed", { job }));
4355      });
4356    } catch (cause) {
4357      settleAttempt(attempt, cause);
4358      return;
4359    }
4360    Promise.resolve(result).then(
4361      () => settleAttempt(attempt, null),
4362      (cause) => settleAttempt(attempt, cause),
4363    );
4364  };
4365  const beginAttempt = (event, deferLaunch = false) => {
4366    const previous = state;
4367    state = transition(event);
4368    if (previous === state || state.name !== "Claiming") return null;
4369    const attempt = reserveAttempt();
4370    if (!deferLaunch) launchAttempt(attempt);
4371    return attempt;
4372  };
4373  function arm() {
4374    if (state.name !== "Idle") return;
4375    const token = { id: ++nextTimerToken, consumed: false, delivered: false, cancelled: false, handleReady: false, handle: null, clearInvoked: false };
4376    state = transition(queueWorkerEvent("Arm", { token }));
4377    let synchronousAttempt = null;
4378    let arming = true;
4379    const fire = () => {
4380      if (token.consumed || state.name !== "Scheduled" || state.detail !== token) return;
4381      token.consumed = true;
4382      token.delivered = true;
4383      const attempt = beginAttempt(queueWorkerEvent("Deliver", { token }), arming);
4384      if (arming) synchronousAttempt = attempt;
4385    };
4386    let handle;
4387    try {
4388      handle = setTimer(fire, interval);
4389      observeHookResult(handle);
4390    } catch (cause) {
4391      arming = false;
4392      token.consumed = true;
4393      state = transition(queueWorkerEvent("ArmFailed", { token, error: cause }));
4394      if (synchronousAttempt !== null && state.name === "Failed") {
4395        synchronousAttempt.settled = true;
4396        activeAttempt = null;
4397        synchronousAttempt.resolveJoin();
4398        synchronousAttempt = null;
4399      }
4400      hookFailure(cause);
4401      if (synchronousAttempt !== null) launchAttempt(synchronousAttempt);
4402      return;
4403    }
4404    arming = false;
4405    token.handle = handle;
4406    token.handleReady = true;
4407    if (token.cancelled) clearToken(token);
4408    if (synchronousAttempt !== null) launchAttempt(synchronousAttempt);
4409  }
4410  /* noxid-server:agent-run-reconcile */
4411  beginAttempt(queueWorkerEvent("Start"));
4412  return Object.freeze({
4413    stop() {
4414      if (state.name === "Stopping" || state.name === "Stopped" || state.name === "Failed") return stopJoin ?? settledJoin;
4415      if (state.name === "Scheduled") {
4416        const token = state.detail;
4417        state = transition(queueWorkerEvent("Stop"));
4418        clearToken(token);
4419        stopJoin = settledJoin;
4420        return stopJoin;
4421      }
4422      if (state.name === "Idle") {
4423        state = transition(queueWorkerEvent("Stop"));
4424        stopJoin = settledJoin;
4425        return stopJoin;
4426      }
4427      if (state.name === "Claiming" || state.name === "Running") {
4428        state = transition(queueWorkerEvent("Stop"));
4429        stopJoin = activeAttempt?.join ?? settledJoin;
4430        return stopJoin;
4431      }
4432      return settledJoin;
4433    },
4434  });
4435}
4436
4437export async function queueStatus(queue = null) {
4438  if (queue !== null && queueSchemaByName(queue) === null) throw Object.assign(new Error("Unknown queue " + queue), { code: "QUEUE_NOT_FOUND" });
4439  const sql = await queueSql();
4440  const rows = queue === null
4441    ? await sql`SELECT queue, state, count(*)::int AS count FROM _noxid_jobs GROUP BY queue, state ORDER BY queue, state`
4442    : await sql`SELECT queue, state, count(*)::int AS count FROM _noxid_jobs WHERE queue = ${queue} GROUP BY queue, state ORDER BY queue, state`;
4443  return Object.freeze(rows.map((row) => Object.freeze({ queue: row.queue, state: row.state, count: Number(row.count) })));
4444}
4445
4446export async function closeQueueDatabase() {
4447  if (queueDatabase !== undefined) await queueDatabase.end({ timeout: 1 });
4448  queueDatabase = undefined;
4449  queueDatabasePromise = undefined;
4450}
4451
4452globalThis.__NOXID_QUEUE_ENQUEUE__ = enqueue;
4453"##;
4454
4455/// The WO-30 model boundary.
4456///
4457/// Everything the provider call needs is a compile-time constant emitted above
4458/// this block: the transport, the model id, the retry cap, the request schema.
4459/// The only runtime lookup is the declared secret, read one name at a time so a
4460/// missing model credential fails exactly the request that reached for it
4461/// instead of every request that touches `environment.secrets`.
4462///
4463/// No SDK: two hand-written HTTPS clients over `fetch`, which is the whole
4464/// dependency surface. Prompt and completion text never reach a span, and a
4465/// provider error body never reaches an error message.
4466const MODEL_RUNTIME: &str = r##"
4467const MODEL_ANTHROPIC_VERSION = "2023-06-01";
4468const MODEL_DEFAULT_MAX_TOKENS = 1024;
4469const MODEL_PROVIDER_CODE = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/;
4470const MODEL_STRUCTURED_TOOL = "noxid_structured_result";
4471
4472function modelError(code, message, detail = null) {
4473  const error = new Error(message);
4474  error.code = code;
4475  if (detail !== null) error.detail = detail;
4476  return error;
4477}
4478
4479function modelDefinitionFor(handle) {
4480  const name = typeof handle === "string" ? handle : handle?.model ?? null;
4481  if (typeof name === "string" && Object.hasOwn(modelDeclarations, name)) return modelDeclarations[name];
4482  throw modelError(
4483    "MODEL_NOT_DECLARED",
4484    `no model \`${typeof name === "string" ? name : String(handle)}\` is declared; import { models } from "noxid:server" and pass models.<Name> for a model declared under server/models/<name>.nox`,
4485  );
4486}
4487
4488// One declared name at a time, never the whole allowlist: an unrelated missing
4489// secret must not fail a request that never asked for it.
4490function modelSecret(definition, name) {
4491  const value = globalThis.process?.env?.[name];
4492  if (typeof value !== "string" || value.length === 0) {
4493    throw modelError(
4494      "MODEL_SECRET_MISSING",
4495      `model \`${definition.name}\` requires the declared secret ${name}, which is absent from the environment; only requests that call this model fail`,
4496    );
4497  }
4498  return value;
4499}
4500
4501function modelBaseUrl(definition) {
4502  const declared = definition.baseUrl.kind === "secret"
4503    ? modelSecret(definition, definition.baseUrl.value)
4504    : definition.baseUrl.value;
4505  let parsed;
4506  try { parsed = new URL(declared); } catch {
4507    throw modelError("MODEL_PROVIDER_ERROR", `model \`${definition.name}\` has a base URL that is not a valid absolute URL`);
4508  }
4509  const loopback = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]" || parsed.hostname === "::1";
4510  if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && loopback)) {
4511    throw modelError("MODEL_PROVIDER_ERROR", `model \`${definition.name}\` resolved a non-https base URL; a model credential may only travel over https, or over http to a loopback host`);
4512  }
4513  return `${parsed.origin}${parsed.pathname.replace(/\/$/, "")}`;
4514}
4515
4516function modelTypeSchemaFor(definition, handle) {
4517  const name = typeof handle === "string" ? handle : handle?.type ?? null;
4518  const key = typeof name === "string" ? `type:${name}` : null;
4519  const entry = key !== null && Object.hasOwn(modelTypeSchemas, key) ? modelTypeSchemas[key] : null;
4520  if (entry === null) {
4521    throw modelError(
4522      "MODEL_OUTPUT_SCHEMA_UNSUPPORTED",
4523      `model \`${definition.name}\` cannot generate \`${typeof name === "string" ? name : String(handle)}\`: it is not a declared type with a strict structured-output schema; import { types } from "noxid:server" and pass types.<TypeName> for a type this build declares`,
4524    );
4525  }
4526  const validator = typeValidators[`type:${name}`] ?? typeValidators[`validator:${name}`] ?? null;
4527  if (validator === null) {
4528    throw modelError(
4529      "MODEL_OUTPUT_SCHEMA_UNSUPPORTED",
4530      `model \`${definition.name}\` cannot generate \`${name}\`: this build emits no boundary validator for it, and model output is never accepted unvalidated`,
4531    );
4532  }
4533  return Object.freeze({ name, schema: entry.schema, validator });
4534}
4535
4536function modelUsage(input, output) {
4537  return Object.freeze({
4538    inputTokens: Number.isSafeInteger(input) && input >= 0 ? input : 0,
4539    outputTokens: Number.isSafeInteger(output) && output >= 0 ? output : 0,
4540  });
4541}
4542
4543function modelTraceFields(definition, usage, retries, durationMs, code) {
4544  const fields = {
4545    semanticId: definition.id,
4546    model: definition.name,
4547    modelProvider: definition.provider,
4548    modelId: definition.modelId,
4549    tokensInput: usage.inputTokens,
4550    tokensOutput: usage.outputTokens,
4551    modelRetries: retries,
4552    durationMs,
4553  };
4554  if (code !== null) fields.code = code;
4555  return fields;
4556}
4557
4558// Prompts, options, and completions are never presented to the serializer:
4559// only the allowlisted identity, token, retry, and latency fields above.
4560function modelTrace(request, definition, usage, retries, durationMs, code = null) {
4561  let trace = __noxidTraceForRequest(request ?? null);
4562  if (trace === null && tracingMode === "full") trace = __noxidTraceContext();
4563  __noxidTraceEmit(trace, "model.generate", modelTraceFields(definition, usage, retries, durationMs, code));
4564}
4565
4566function modelOptions(options) {
4567  if (options === undefined || options === null) return Object.freeze({});
4568  if (typeof options !== "object") throw modelError("MODEL_OPTIONS_INVALID", "model options must be an object with optional temperature, maxTokens, system, signal, and request");
4569  const unknown = Object.keys(options).filter((key) => !["temperature", "maxTokens", "system", "signal", "request"].includes(key));
4570  if (unknown.length !== 0) throw modelError("MODEL_OPTIONS_INVALID", `model option \`${unknown[0]}\` is unknown; use temperature, maxTokens, system, signal, or request`);
4571  if (options.temperature !== undefined && (typeof options.temperature !== "number" || !Number.isFinite(options.temperature) || options.temperature < 0 || options.temperature > 2)) {
4572    throw modelError("MODEL_OPTIONS_INVALID", "model option `temperature` must be a number between 0 and 2");
4573  }
4574  if (options.maxTokens !== undefined && (!Number.isSafeInteger(options.maxTokens) || options.maxTokens <= 0)) {
4575    throw modelError("MODEL_OPTIONS_INVALID", "model option `maxTokens` must be a positive whole number");
4576  }
4577  if (options.system !== undefined && typeof options.system !== "string") {
4578    throw modelError("MODEL_OPTIONS_INVALID", "model option `system` must be a string");
4579  }
4580  return options;
4581}
4582
4583function modelPrompt(definition, prompt) {
4584  if (typeof prompt !== "string" || prompt.length === 0) {
4585    throw modelError("MODEL_PROMPT_INVALID", `model \`${definition.name}\` requires a non-empty string prompt; message-array prompts are not part of this boundary`);
4586  }
4587  return prompt;
4588}
4589
4590function modelTemperature(definition, options) {
4591  if (options.temperature !== undefined) return options.temperature;
4592  return definition.temperature === null ? undefined : definition.temperature;
4593}
4594
4595function modelMaxTokens(definition, options) {
4596  if (options.maxTokens !== undefined) return options.maxTokens;
4597  return definition.maxTokens === null ? MODEL_DEFAULT_MAX_TOKENS : definition.maxTokens;
4598}
4599
4600// Each provider names its own failures in a different field: Anthropic in
4601// `error.type`, OpenAI-compatible endpoints in `error.code`. Only a value that
4602// looks like a code survives — an error *message* may quote the prompt back.
4603function modelProviderCode(definition, payload) {
4604  const code = definition.provider === "anthropic"
4605    ? payload?.error?.type ?? null
4606    : payload?.error?.code ?? payload?.error?.type ?? null;
4607  return typeof code === "string" && MODEL_PROVIDER_CODE.test(code) ? code : null;
4608}
4609
4610// The provider's response body may quote the prompt back at us, so it never
4611// reaches the error: status plus the provider's own error code, nothing else.
4612async function modelRefuseResponse(definition, response) {
4613  let code = null;
4614  try { code = modelProviderCode(definition, await response.json()); } catch {}
4615  throw modelError(
4616    "MODEL_PROVIDER_ERROR",
4617    `model \`${definition.name}\` was refused by its ${definition.provider} endpoint with status ${response.status}${code === null ? "" : ` (${code})`}`,
4618  );
4619}
4620
4621async function modelSend(definition, path, headers, body, signal, stream = false) {
4622  const url = `${modelBaseUrl(definition)}${path}`;
4623  let response;
4624  try {
4625    // `globalThis.fetch` explicitly: this module exports its own `fetch` as
4626    // the request handler, and a bare call would reach that instead.
4627    response = await globalThis.fetch(url, {
4628      method: "POST",
4629      headers: { "content-type": "application/json", ...headers },
4630      body: JSON.stringify(body),
4631      signal: signal ?? undefined,
4632    });
4633  } catch (cause) {
4634    if (cause?.name === "AbortError" || cause?.name === "TimeoutError") {
4635      throw modelError("MODEL_TIMEOUT", `model \`${definition.name}\` was cancelled before its ${definition.provider} call completed`);
4636    }
4637    throw modelError("MODEL_PROVIDER_ERROR", `model \`${definition.name}\` could not reach its ${definition.provider} endpoint`);
4638  }
4639  if (!response.ok) await modelRefuseResponse(definition, response);
4640  if (stream) return response;
4641  try { return await response.json(); } catch {
4642    throw modelError("MODEL_PROVIDER_ERROR", `model \`${definition.name}\` returned a body its ${definition.provider} transport could not decode as JSON`);
4643  }
4644}
4645
4646function anthropicHeaders(definition) {
4647  return { "x-api-key": modelSecret(definition, definition.secret), "anthropic-version": MODEL_ANTHROPIC_VERSION };
4648}
4649
4650function openaiHeaders(definition) {
4651  return { authorization: `Bearer ${modelSecret(definition, definition.secret)}` };
4652}
4653
4654function anthropicBody(definition, prompt, options, stream) {
4655  const body = {
4656    model: definition.modelId,
4657    max_tokens: modelMaxTokens(definition, options),
4658    messages: [{ role: "user", content: prompt }],
4659  };
4660  const temperature = modelTemperature(definition, options);
4661  if (temperature !== undefined) body.temperature = temperature;
4662  if (typeof options.system === "string") body.system = options.system;
4663  if (stream) body.stream = true;
4664  return body;
4665}
4666
4667function openaiBody(definition, prompt, options, stream) {
4668  const messages = [];
4669  if (typeof options.system === "string") messages.push({ role: "system", content: options.system });
4670  messages.push({ role: "user", content: prompt });
4671  const body = { model: definition.modelId, messages, max_tokens: modelMaxTokens(definition, options) };
4672  const temperature = modelTemperature(definition, options);
4673  if (temperature !== undefined) body.temperature = temperature;
4674  if (stream) {
4675    body.stream = true;
4676    body.stream_options = { include_usage: true };
4677  }
4678  return body;
4679}
4680
4681function anthropicText(payload) {
4682  const blocks = Array.isArray(payload?.content) ? payload.content : [];
4683  return blocks.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("");
4684}
4685
4686function anthropicToolInput(payload) {
4687  const blocks = Array.isArray(payload?.content) ? payload.content : [];
4688  const block = blocks.find((candidate) => candidate?.type === "tool_use" && candidate?.name === MODEL_STRUCTURED_TOOL);
4689  return block === undefined ? undefined : block.input;
4690}
4691
4692function anthropicUsage(payload) {
4693  return modelUsage(payload?.usage?.input_tokens, payload?.usage?.output_tokens);
4694}
4695
4696function openaiUsage(payload) {
4697  return modelUsage(payload?.usage?.prompt_tokens, payload?.usage?.completion_tokens);
4698}
4699
4700async function modelProviderText(definition, prompt, options) {
4701  if (definition.provider === "anthropic") {
4702    const payload = await modelSend(definition, "/v1/messages", anthropicHeaders(definition), anthropicBody(definition, prompt, options, false), options.signal);
4703    return Object.freeze({ text: anthropicText(payload), usage: anthropicUsage(payload) });
4704  }
4705  const payload = await modelSend(definition, "/v1/chat/completions", openaiHeaders(definition), openaiBody(definition, prompt, options, false), options.signal);
4706  const content = payload?.choices?.[0]?.message?.content;
4707  return Object.freeze({ text: typeof content === "string" ? content : "", usage: openaiUsage(payload) });
4708}
4709
4710// Structured output is a forced tool on Anthropic and a strict `json_schema`
4711// response format on OpenAI-compatible endpoints. Both are requests, not
4712// guarantees: the answer still goes through the boundary validator.
4713async function modelProviderObject(definition, prompt, schema, options) {
4714  if (definition.provider === "anthropic") {
4715    const body = anthropicBody(definition, prompt, options, false);
4716    body.tools = [{ name: MODEL_STRUCTURED_TOOL, description: `Return the ${schema.name} result.`, input_schema: schema.schema }];
4717    body.tool_choice = { type: "tool", name: MODEL_STRUCTURED_TOOL };
4718    const payload = await modelSend(definition, "/v1/messages", anthropicHeaders(definition), body, options.signal);
4719    return Object.freeze({ value: anthropicToolInput(payload), usage: anthropicUsage(payload) });
4720  }
4721  const body = openaiBody(definition, prompt, options, false);
4722  body.response_format = { type: "json_schema", json_schema: { name: schema.name, strict: true, schema: schema.schema } };
4723  const payload = await modelSend(definition, "/v1/chat/completions", openaiHeaders(definition), body, options.signal);
4724  const content = payload?.choices?.[0]?.message?.content;
4725  let value;
4726  try { value = typeof content === "string" ? JSON.parse(content) : undefined; } catch { value = undefined; }
4727  return Object.freeze({ value, usage: openaiUsage(payload) });
4728}
4729
4730async function* modelSseEvents(definition, response, signal) {
4731  const reader = response.body?.getReader();
4732  if (reader === undefined) throw modelError("MODEL_PROVIDER_ERROR", `model \`${definition.name}\` returned a stream with no readable body`);
4733  const decoder = new TextDecoder();
4734  let buffer = "";
4735  try {
4736    for (;;) {
4737      if (signal?.aborted) throw modelError("MODEL_TIMEOUT", `model \`${definition.name}\` stream was cancelled`);
4738      const chunk = await reader.read();
4739      if (chunk.done) break;
4740      buffer += decoder.decode(chunk.value, { stream: true });
4741      let boundary = buffer.indexOf("\n\n");
4742      while (boundary !== -1) {
4743        const frame = buffer.slice(0, boundary);
4744        buffer = buffer.slice(boundary + 2);
4745        const data = frame.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("");
4746        if (data.length !== 0 && data !== "[DONE]") {
4747          try { yield JSON.parse(data); } catch {}
4748        }
4749        boundary = buffer.indexOf("\n\n");
4750      }
4751    }
4752  } finally {
4753    try { await reader.cancel(); } catch {}
4754  }
4755}
4756
4757async function* modelProviderStream(definition, prompt, options) {
4758  let input = 0;
4759  let output = 0;
4760  if (definition.provider === "anthropic") {
4761    const response = await modelSend(definition, "/v1/messages", anthropicHeaders(definition), anthropicBody(definition, prompt, options, true), options.signal, true);
4762    for await (const event of modelSseEvents(definition, response, options.signal)) {
4763      if (event?.type === "message_start") input = event?.message?.usage?.input_tokens ?? input;
4764      if (event?.type === "content_block_delta" && typeof event?.delta?.text === "string") yield Object.freeze({ delta: event.delta.text });
4765      if (event?.type === "message_delta") output = event?.usage?.output_tokens ?? output;
4766    }
4767    return modelUsage(input, output);
4768  }
4769  const response = await modelSend(definition, "/v1/chat/completions", openaiHeaders(definition), openaiBody(definition, prompt, options, true), options.signal, true);
4770  for await (const event of modelSseEvents(definition, response, options.signal)) {
4771    const delta = event?.choices?.[0]?.delta?.content;
4772    if (typeof delta === "string" && delta.length !== 0) yield Object.freeze({ delta });
4773    if (event?.usage) {
4774      input = event.usage.prompt_tokens ?? input;
4775      output = event.usage.completion_tokens ?? output;
4776    }
4777  }
4778  return modelUsage(input, output);
4779}
4780
4781async function modelGenerateText(handle, prompt, options) {
4782  const definition = modelDefinitionFor(handle);
4783  const resolved = modelOptions(options);
4784  const text = modelPrompt(definition, prompt);
4785  const started = Date.now();
4786  const controller = modelScenarioController();
4787  try {
4788    const result = controller === null
4789      ? await modelProviderText(definition, text, resolved)
4790      : controller.text(definition);
4791    modelTrace(resolved.request, definition, result.usage, 0, Date.now() - started);
4792    return Object.freeze({ text: result.text, usage: result.usage });
4793  } catch (cause) {
4794    modelTrace(resolved.request, definition, modelUsage(0, 0), 0, Date.now() - started, cause?.code ?? "MODEL_PROVIDER_ERROR");
4795    throw cause;
4796  }
4797}
4798
4799async function modelGenerateObject(handle, prompt, type, options) {
4800  const definition = modelDefinitionFor(handle);
4801  const resolved = modelOptions(options);
4802  const text = modelPrompt(definition, prompt);
4803  const schema = modelTypeSchemaFor(definition, type);
4804  const started = Date.now();
4805  const controller = modelScenarioController();
4806  let input = 0;
4807  let output = 0;
4808  let retries = 0;
4809  let detail = null;
4810  try {
4811    for (;;) {
4812      const result = controller === null
4813        ? await modelProviderObject(definition, text, schema, resolved)
4814        : controller.object(definition);
4815      input += result.usage.inputTokens;
4816      output += result.usage.outputTokens;
4817      try {
4818        const value = schema.validator(result.value, true);
4819        const usage = modelUsage(input, output);
4820        modelTrace(resolved.request, definition, usage, retries, Date.now() - started);
4821        return Object.freeze({ value, usage });
4822      } catch (cause) {
4823        detail = cause?.message ?? String(cause);
4824        // The retry cap is declared, not adaptive: past it the shape is wrong,
4825        // not unlucky, and looping would only spend tokens. `retries: N` counts
4826        // re-attempts *after* the first attempt, so a call makes at most N + 1
4827        // provider attempts, and the refusal states both numbers rather than
4828        // leaving the reader to guess which one `retries` meant.
4829        if (retries >= definition.retries) {
4830          const attempts = retries + 1;
4831          throw modelError(
4832            "MODEL_OUTPUT_INVALID",
4833            `model \`${definition.name}\` returned output that failed \`${schema.name}\` validation after ${retries} ${retries === 1 ? "retry" : "retries"} (${attempts} ${attempts === 1 ? "attempt" : "attempts"}); the boundary validator refused it`,
4834            detail,
4835          );
4836        }
4837        retries += 1;
4838      }
4839    }
4840  } catch (cause) {
4841    modelTrace(resolved.request, definition, modelUsage(input, output), retries, Date.now() - started, cause?.code ?? "MODEL_PROVIDER_ERROR");
4842    throw cause;
4843  }
4844}
4845
4846function modelStreamText(handle, prompt, options) {
4847  const definition = modelDefinitionFor(handle);
4848  const resolved = modelOptions(options);
4849  const text = modelPrompt(definition, prompt);
4850  const controller = modelScenarioController();
4851  return {
4852    async *[Symbol.asyncIterator]() {
4853      const started = Date.now();
4854      try {
4855        const source = controller === null
4856          ? modelProviderStream(definition, text, resolved)
4857          : controller.tokens(definition);
4858        let usage = modelUsage(0, 0);
4859        for (;;) {
4860          const step = await source.next();
4861          if (step.done) {
4862            usage = step.value ?? usage;
4863            break;
4864          }
4865          yield step.value;
4866        }
4867        modelTrace(resolved.request, definition, usage, 0, Date.now() - started);
4868        yield Object.freeze({ usage });
4869      } catch (cause) {
4870        modelTrace(resolved.request, definition, modelUsage(0, 0), 0, Date.now() - started, cause?.code ?? "MODEL_PROVIDER_ERROR");
4871        throw cause;
4872      }
4873    },
4874  };
4875}
4876
4877globalThis.__NOXID_MODEL_RUNTIME__ = Object.freeze({
4878  generateText: modelGenerateText,
4879  generateObject: modelGenerateObject,
4880  streamText: modelStreamText,
4881  declarations: modelDeclarations,
4882});
4883"##;
4884
4885/// The scenario side of the boundary. Under `noxid test` the harness installs
4886/// `globalThis.__NOXID_MODEL_SCENARIO__`; while it is installed the model
4887/// runtime performs no I/O at all, and a call with no stub fails closed with
4888/// the stubbing syntax rather than reaching a provider.
4889const MODEL_SCENARIO_RUNTIME: &str = r##"
4890function modelScenarioController() {
4891  const controller = globalThis.__NOXID_MODEL_SCENARIO__;
4892  if (controller === undefined || controller === null) return null;
4893  // The refusal names the call site, not just the model: one endpoint may call
4894  // several models, and the same model several times, so "which call" is the
4895  // part a reader cannot reconstruct from the message alone.
4896  const site = typeof controller.callSite === "string" && controller.callSite.length !== 0
4897    ? ` at call site \`${controller.callSite}\``
4898    : "";
4899  const take = (definition, kind) => {
4900    const stub = typeof controller.take === "function" ? controller.take(definition.name, kind) : null;
4901    if (stub === null || stub === undefined) {
4902      throw modelError(
4903        "MODEL_STUB_REQUIRED",
4904        `scenario called model \`${definition.name}\`${site} with no stub left; add \`given model ${definition.name} = text "..."\`, \`= object <Type>(field = value)\`, \`= tokens ["a", "b"]\`, or \`= fails MODEL_PROVIDER_ERROR\` to the scenario`,
4905      );
4906    }
4907    if (stub.kind === "fails") throw modelError(stub.code, `scenario stub for model \`${definition.name}\`${site} fails with ${stub.code}`);
4908    if (stub.kind !== kind) {
4909      throw modelError(
4910        "MODEL_STUB_REQUIRED",
4911        `scenario stubbed model \`${definition.name}\`${site} with a ${stub.kind} stub, but the call needs a ${kind} stub; declare the stub shape the call actually consumes`,
4912      );
4913    }
4914    return stub;
4915  };
4916  return Object.freeze({
4917    text(definition) {
4918      const stub = take(definition, "text");
4919      return Object.freeze({ text: stub.text, usage: modelUsage(stub.inputTokens, stub.outputTokens) });
4920    },
4921    object(definition) {
4922      const stub = take(definition, "object");
4923      return Object.freeze({ value: stub.value, usage: modelUsage(stub.inputTokens, stub.outputTokens) });
4924    },
4925    async *tokens(definition) {
4926      const stub = take(definition, "tokens");
4927      for (const token of stub.tokens) yield Object.freeze({ delta: token });
4928      return modelUsage(stub.inputTokens, stub.outputTokens);
4929    },
4930  });
4931}
4932"##;
4933
4934const LIVE_RESOURCE_RUNTIME: &str = r##"
4935const LIVE_RESOURCE_SCHEMA = Object.freeze({
4936  id: "live-resource:connection",
4937  name: "LiveResourceConnection",
4938  path: liveResourcePath,
4939  method: "GET",
4940  middleware: Object.freeze([]),
4941  timeoutMs: 30_000,
4942});
4943const LIVE_RESOURCE_MAX_HISTORIES = 128;
4944const LIVE_RESOURCE_MAX_EVENTS = 256;
4945const LIVE_RESOURCE_MAX_HISTORY_BYTES = 1_048_576;
4946const LIVE_RESOURCE_TOKEN = /^[A-Za-z0-9_-]{16,128}$/;
4947const liveResourceSchemaById = new Map(liveResourceSchemas.map((schema) => [schema.id, schema]));
4948const presenceSchemaById = new Map(presenceSchemas.map((schema) => [schema.id, schema]));
4949const liveResourceHistories = new Map();
4950const liveResourceEncoder = new TextEncoder();
4951
4952function liveResourceResetResponse(headers = []) {
4953  const responseHeaders = new Headers({
4954    "content-type": "text/event-stream; charset=utf-8",
4955    "cache-control": "no-store",
4956    "x-content-type-options": "nosniff",
4957  });
4958  for (const [name, value] of headers) responseHeaders.append(name, value);
4959  return new Response("event: noxid-live-reset\ndata: null\n\n", { status: 200, headers: responseHeaders });
4960}
4961
4962function liveResourceCursor(value) {
4963  if (typeof value !== "string" || value.length === 0 || value.length > 160) return null;
4964  const separator = value.lastIndexOf(":");
4965  if (separator < 0) return null;
4966  const token = value.slice(0, separator);
4967  const sequenceText = value.slice(separator + 1);
4968  if (!LIVE_RESOURCE_TOKEN.test(token) || !/^[1-9]\d{0,15}$/.test(sequenceText)) return null;
4969  const sequence = Number(sequenceText);
4970  return Number.isSafeInteger(sequence) ? Object.freeze({ token, sequence }) : null;
4971}
4972
4973function liveResourceToken() {
4974  let token;
4975  try { token = globalThis.crypto?.randomUUID?.().replaceAll("-", "_"); } catch {}
4976  if (typeof token !== "string" || !LIVE_RESOURCE_TOKEN.test(token)) {
4977    token = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}_${Math.random().toString(36).slice(2)}`;
4978  }
4979  return token.padEnd(16, "0").slice(0, 128);
4980}
4981
4982const PRESENCE_CREDENTIAL = /^[A-Za-z0-9_-]{16,128}$/;
4983const PRESENCE_STORAGE_SCHEMA = "noxid.presence.member.v1";
4984const PRESENCE_INDEX_SCHEMA = "noxid.presence.expiry.v1";
4985const PRESENCE_MAX_MEMBERS = 256;
4986const PRESENCE_JOINS_PER_MINUTE = 120;
4987const PRESENCE_MEMBER_WRITES_PER_MINUTE = 600;
4988const PRESENCE_ABUSE_ATTEMPTS_PER_MINUTE = 1200;
4989const PRESENCE_MAX_DELTA_BYTES = 6_000;
4990const PRESENCE_MAX_SNAPSHOT_BYTES = 65_536;
4991const PRESENCE_SWEEP_BATCH = 128;
4992const presenceMemberStorage = presenceSchemas.length === 0 ? null : __noxidStorage(`noxid:presence:${applicationNamespace}:members`);
4993const presenceExpiryStorage = presenceSchemas.length === 0 ? null : __noxidStorage(`noxid:presence:${applicationNamespace}:expiry`);
4994const presenceLocalLocks = new Map();
4995const presenceLocalRates = new Map();
4996
4997function presenceSseFrame(schema, event) {
4998  return `event: noxid-presence-event\ndata: ${JSON.stringify({ presence: schema.id, event })}\n\n`;
4999}
5000
5001function presenceSseFrameBytes(schema, event) {
5002  return liveResourceEncoder.encode(presenceSseFrame(schema, event)).byteLength;
5003}
5004
5005async function presenceDigest(...parts) {
5006  if (typeof globalThis.crypto?.subtle?.digest !== "function") {
5007    throw Object.assign(new Error("Presence credentials require Web Crypto SHA-256"), { code: "PRESENCE_CRYPTO_UNAVAILABLE" });
5008  }
5009  const bytes = await globalThis.crypto.subtle.digest("SHA-256", liveResourceEncoder.encode([applicationNamespace, ...parts].join("\n")));
5010  return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
5011}
5012
5013function presenceDigestEqual(left, right) {
5014  if (typeof left !== "string" || typeof right !== "string" || left.length !== right.length) return false;
5015  let difference = 0;
5016  for (let index = 0; index < left.length; index += 1) difference |= left.charCodeAt(index) ^ right.charCodeAt(index);
5017  return difference === 0;
5018}
5019
5020async function presenceLocalLock(key, operation, signal = null) {
5021  const previous = presenceLocalLocks.get(key) ?? Promise.resolve();
5022  let release;
5023  const current = new Promise((resolve) => { release = resolve; });
5024  presenceLocalLocks.set(key, current);
5025  const releaseAfterPrevious = () => previous.finally(() => {
5026    release();
5027    if (presenceLocalLocks.get(key) === current) presenceLocalLocks.delete(key);
5028  });
5029  if (signal?.aborted) {
5030    void releaseAfterPrevious();
5031    throw presenceFailure("PRESENCE_OPERATION_ABORTED", "Presence operation was cancelled while waiting for its partition");
5032  }
5033  if (signal !== null) {
5034    let onAbort;
5035    const aborted = new Promise((resolve) => {
5036      onAbort = () => resolve(false);
5037      signal.addEventListener("abort", onAbort, { once: true });
5038    });
5039    const acquired = await Promise.race([previous.then(() => true), aborted]);
5040    signal.removeEventListener("abort", onAbort);
5041    if (!acquired) {
5042      void releaseAfterPrevious();
5043      throw presenceFailure("PRESENCE_OPERATION_ABORTED", "Presence operation was cancelled while waiting for its partition");
5044    }
5045  } else {
5046    await previous;
5047  }
5048  try { return await operation(); }
5049  finally {
5050    release();
5051    if (presenceLocalLocks.get(key) === current) presenceLocalLocks.delete(key);
5052  }
5053}
5054
5055async function presencePartitionLock(schema, canonical, routeId, routePath, operation, signal = null) {
5056  const digest = await presenceDigest(schema.id, canonical, routeId, routePath);
5057  const key = `presence-lock:${digest}`;
5058  return presenceLocalLock(key, async () => {
5059    if (signal?.aborted) throw presenceFailure("PRESENCE_OPERATION_ABORTED", "Presence operation exceeded its bounded lifetime");
5060    if (typeof __noxidSharedIdempotencyPrepare !== "function" || typeof __noxidSharedIdempotencyRelease !== "function") {
5061      const value = await operation();
5062      if (signal?.aborted) throw presenceFailure("PRESENCE_OPERATION_ABORTED", "Presence operation exceeded its bounded lifetime");
5063      return value;
5064    }
5065    if (typeof globalThis.crypto?.randomUUID !== "function") throw presenceFailure("PRESENCE_CRYPTO_UNAVAILABLE", "Presence partition locks require secure random claims");
5066    const claim = globalThis.crypto.randomUUID();
5067    const leaseMilliseconds = LIVE_RESOURCE_SCHEMA.timeoutMs + 5_000;
5068    for (;;) {
5069      if (signal?.aborted) throw presenceFailure("PRESENCE_OPERATION_ABORTED", "Presence operation exceeded its bounded lifetime");
5070      const prepared = await __noxidSharedIdempotencyPrepare(key, claim, leaseMilliseconds);
5071      if (prepared?.state === "owner") break;
5072      if (prepared?.state !== "pending") throw presenceFailure("PRESENCE_LOCK_INVALID", "Presence partition lock returned an invalid shared state");
5073      await new Promise((resolve) => setTimeout(resolve, 5));
5074    }
5075    let lost = false;
5076    let renewing = false;
5077    let renewalPromise = Promise.resolve();
5078    const renewal = setInterval(() => {
5079      if (renewing || lost) return;
5080      renewing = true;
5081      renewalPromise = __noxidSharedIdempotencyPrepare(key, claim, leaseMilliseconds)
5082        .then((value) => { if (value?.state !== "owner") lost = true; }, () => { lost = true; })
5083        .finally(() => { renewing = false; });
5084    }, Math.floor(leaseMilliseconds / 3));
5085    renewal?.unref?.();
5086    try {
5087      const value = await operation();
5088      if (lost || signal?.aborted) throw presenceFailure("PRESENCE_OPERATION_ABORTED", "Presence partition lock ownership was not retained through commit");
5089      return value;
5090    } finally {
5091      clearInterval(renewal);
5092      await renewalPromise;
5093      await __noxidSharedIdempotencyRelease(key, claim);
5094    }
5095  }, signal);
5096}
5097
5098async function presencePartition(schema, canonical, routeId, routePath) {
5099  return `partition:${await presenceDigest(schema.id, canonical, routeId, routePath)}:`;
5100}
5101
5102async function presenceMemberKey(schema, canonical, routeId, routePath, memberId) {
5103  return `member:${await presencePartition(schema, canonical, routeId, routePath)}${memberId}`;
5104}
5105
5106function presenceExpiryKey(memberKey) {
5107  return `expiry:${memberKey}`;
5108}
5109
5110function presenceFailure(code, message, cause = undefined) {
5111  return Object.assign(new Error(message, cause === undefined ? undefined : { cause }), { code });
5112}
5113
5114async function presenceAdmitRate(identity, kind, budget, parts = []) {
5115  const key = `presence-${kind}-rate:${await presenceDigest(identity, ...parts)}`;
5116  if (typeof __noxidSharedRateLimit === "function") {
5117    let retryAfter;
5118    try { retryAfter = await __noxidSharedRateLimit(key, budget, 60_000); }
5119    catch (cause) { throw presenceFailure("PRESENCE_STORAGE_UNAVAILABLE", "Presence operational rate storage is temporarily unavailable", cause); }
5120    if (retryAfter !== null && retryAfter !== undefined) throw Object.assign(presenceFailure("PRESENCE_RATE_LIMITED", "Presence write rate exceeded its compiler-owned per-principal budget"), { retryAfter });
5121    return;
5122  }
5123  const now = Date.now();
5124  let bucket = presenceLocalRates.get(key);
5125  if (bucket === undefined || now - bucket.started >= 60_000) bucket = { started: now, count: 0 };
5126  if (bucket.count >= budget) throw Object.assign(presenceFailure("PRESENCE_RATE_LIMITED", "Presence write rate exceeded its compiler-owned membership budget"), { retryAfter: Math.max(1, Math.ceil((bucket.started + 60_000 - now) / 1000)) });
5127  bucket.count += 1;
5128  presenceLocalRates.delete(key);
5129  presenceLocalRates.set(key, bucket);
5130  while (presenceLocalRates.size > 1024) presenceLocalRates.delete(presenceLocalRates.keys().next().value);
5131}
5132
5133function presenceOperationalIdentity(environment, middlewareContext, principal) {
5134  const session = middlewareContext?.sessionId ?? middlewareContext?.session?.id ?? environment?.sessionId;
5135  if (typeof session === "string" && session.length > 0 && session.length <= 256 && !/[\u0000-\u001f\u007f]/.test(session)) return `session:${session}`;
5136  const trusted = environment?.requestIdentity;
5137  const requestIdentity = trusted?.id ?? trusted?.ip;
5138  if (typeof requestIdentity === "string" && requestIdentity.length > 0 && requestIdentity.length <= 256 && !/[\u0000-\u001f\u007f]/.test(requestIdentity)) return `request:${requestIdentity}`;
5139  return principal.canonical === "system" ? null : `principal:${principal.canonical}`;
5140}
5141
5142function presenceValidateType(typeId, value, code) {
5143  const validator = typeValidators[typeId];
5144  if (typeof validator !== "function") throw presenceFailure("PRESENCE_VALIDATOR_MISSING", `Compiled presence validator ${typeId} is unavailable`);
5145  try { return validator(value, true); }
5146  catch (cause) { throw presenceFailure(code, "Presence data did not match its compiler-generated closed type", cause); }
5147}
5148
5149function presenceValidateStored(schema, value, key) {
5150  if (value === null || typeof value !== "object" || Array.isArray(value)
5151    || value.schema !== PRESENCE_STORAGE_SCHEMA || value.presence !== schema.id
5152    || typeof value.principal !== "string" || typeof value.routeId !== "string" || typeof value.routePath !== "string"
5153    || typeof value.memberId !== "string" || !PRESENCE_CREDENTIAL.test(value.memberId)
5154    || typeof value.nonce !== "string" || !PRESENCE_CREDENTIAL.test(value.nonce)
5155    || typeof value.tokenDigest !== "string" || !/^[a-f0-9]{64}$/.test(value.tokenDigest)
5156    || !Number.isSafeInteger(value.version) || value.version < 1
5157    || !Number.isSafeInteger(value.expiresAt) || value.expiresAt < 1
5158    || Object.keys(value).sort().join("\n") !== "expiresAt\nmemberId\nnonce\npresence\nprincipal\nrecord\nrouteId\nroutePath\nschema\ntokenDigest\nversion") {
5159    throw presenceFailure("PRESENCE_STORAGE_INVALID", `Presence storage record ${key} failed its closed compiler-owned schema`);
5160  }
5161  const record = presenceValidateType(schema.recordType, value.record, "PRESENCE_STORAGE_INVALID");
5162  presenceValidateType(schema.memberType, Object.freeze({ id: value.memberId, ...record }), "PRESENCE_STORAGE_INVALID");
5163  return Object.freeze({ ...value, record });
5164}
5165
5166async function presenceWriteStored(schema, stored, memberKey) {
5167  const graceMilliseconds = Math.max(schema.ttlMilliseconds, 30_000);
5168  const ttl = Math.max(1, Math.ceil((schema.ttlMilliseconds + graceMilliseconds) / 1000));
5169  await presenceExpiryStorage.set(presenceExpiryKey(memberKey), Object.freeze({
5170    schema: PRESENCE_INDEX_SCHEMA,
5171    key: memberKey,
5172    presence: schema.id,
5173    principal: stored.principal,
5174    routeId: stored.routeId,
5175    routePath: stored.routePath,
5176    memberId: stored.memberId,
5177    version: stored.version,
5178    expiresAt: stored.expiresAt,
5179    state: "active",
5180  }), { ttl });
5181  await presenceMemberStorage.set(memberKey, stored, { ttl });
5182}
5183
5184function presenceValidateIndex(value, indexKey) {
5185  if (value === null || typeof value !== "object" || Array.isArray(value)
5186    || value.schema !== PRESENCE_INDEX_SCHEMA || typeof value.key !== "string" || presenceExpiryKey(value.key) !== indexKey
5187    || typeof value.presence !== "string" || typeof value.principal !== "string"
5188    || typeof value.memberId !== "string" || !PRESENCE_CREDENTIAL.test(value.memberId)
5189    || !Number.isSafeInteger(value.version) || value.version < 1
5190    || !Number.isSafeInteger(value.expiresAt) || value.expiresAt < 1
5191    || typeof value.routeId !== "string" || typeof value.routePath !== "string" || !["active", "left", "left-published"].includes(value.state)
5192    || Object.keys(value).sort().join("\n") !== "expiresAt\nkey\nmemberId\npresence\nprincipal\nrouteId\nroutePath\nschema\nstate\nversion") {
5193    throw presenceFailure("PRESENCE_INDEX_INVALID", "Presence expiry index failed its metadata-only schema");
5194  }
5195  return Object.freeze(value);
5196}
5197
5198async function presenceTopicId(schema, routeId, routePath) {
5199  return `presence-topic:${await presenceDigest(schema.id, routeId, routePath)}`;
5200}
5201
5202async function presencePublish(schema, principal, body) {
5203  const startedAt = __noxidPubSubNow();
5204  let state = "delivered";
5205  let code = null;
5206  try { await __noxidPubSubPublish(__noxidPubSubEvent("presence", await presenceTopicId(schema, body.routeId, body.routePath), principal, body)); }
5207  catch (cause) { state = "failed"; code = cause?.code ?? "PRESENCE_PUBLISH_FAILED"; throw cause; }
5208  finally { __noxidTraceEmit(tracingMode === "full" ? __noxidTraceContext() : null, "presence.publish", { semanticId: schema.id, state, event: body.tag, code, durationMs: Math.max(0, __noxidPubSubNow() - startedAt) }); }
5209}
5210
5211async function presencePublishCanonical(schema, canonical, body) {
5212  const startedAt = __noxidPubSubNow();
5213  let state = "delivered";
5214  let code = null;
5215  try { await __noxidPubSubPublish(__noxidPubSubEventFromCanonical("presence", await presenceTopicId(schema, body.routeId, body.routePath), canonical, body)); }
5216  catch (cause) { state = "failed"; code = cause?.code ?? "PRESENCE_PUBLISH_FAILED"; throw cause; }
5217  finally { __noxidTraceEmit(tracingMode === "full" ? __noxidTraceContext() : null, "presence.publish", { semanticId: schema.id, state, event: body.tag, code, durationMs: Math.max(0, __noxidPubSubNow() - startedAt) }); }
5218}
5219
5220async function presenceClaimExpired(indexKey, expected = null) {
5221  const rawIndex = expected ?? await presenceExpiryStorage.get(indexKey);
5222  if (rawIndex === null) return false;
5223  const index = presenceValidateIndex(rawIndex, indexKey);
5224  const schema = presenceSchemaById.get(index.presence);
5225  if (schema === undefined) throw presenceFailure("PRESENCE_INDEX_INVALID", "Presence expiry index named an unknown compiler contract");
5226  let tombstone = index;
5227  if (index.state === "left-published") return false;
5228  if (index.state === "active") {
5229    if (index.expiresAt > Date.now()) return false;
5230    tombstone = await presencePartitionLock(schema, index.principal, index.routeId, index.routePath, async () => {
5231      const currentIndex = await presenceExpiryStorage.get(indexKey);
5232      if (currentIndex === null || currentIndex.state !== "active" || currentIndex.version !== index.version || currentIndex.expiresAt > Date.now()) return null;
5233      const raw = await presenceMemberStorage.get(index.key);
5234      if (raw === null) {
5235        await presenceExpiryStorage.delete(indexKey);
5236        return null;
5237      }
5238      const stored = presenceValidateStored(schema, raw, index.key);
5239      if (stored.version !== currentIndex.version || stored.expiresAt > Date.now()) return null;
5240      const left = Object.freeze({ ...currentIndex, version: stored.version + 1, state: "left" });
5241      await presenceExpiryStorage.set(indexKey, left);
5242      await presenceMemberStorage.delete(index.key);
5243      return left;
5244    });
5245    if (tombstone === null) return false;
5246  } else if (index.state === "left") {
5247    tombstone = await presencePartitionLock(schema, index.principal, index.routeId, index.routePath, async () => {
5248      const rawCurrent = await presenceExpiryStorage.get(indexKey);
5249      if (rawCurrent === null) return null;
5250      const current = presenceValidateIndex(rawCurrent, indexKey);
5251      if (current.state !== "left" || current.version !== index.version) return null;
5252      const raw = await presenceMemberStorage.get(current.key);
5253      if (raw !== null) {
5254        const stored = presenceValidateStored(schema, raw, current.key);
5255        if (stored.principal !== current.principal || stored.routeId !== current.routeId || stored.routePath !== current.routePath || stored.memberId !== current.memberId || stored.version >= current.version) throw presenceFailure("PRESENCE_STORAGE_INVALID", "Pending Left conflicts with its authored member record");
5256        await presenceMemberStorage.delete(current.key);
5257      }
5258      return current;
5259    });
5260    if (tombstone === null) return false;
5261  }
5262  await presencePublishCanonical(schema, tombstone.principal, Object.freeze({ tag: "Left", value: tombstone.memberId, memberId: tombstone.memberId, version: tombstone.version, routeId: tombstone.routeId, routePath: tombstone.routePath }));
5263  await presencePartitionLock(schema, tombstone.principal, tombstone.routeId, tombstone.routePath, async () => {
5264    const current = await presenceExpiryStorage.get(indexKey);
5265    if (current?.state === "left" && current.version === tombstone.version) {
5266      await presenceExpiryStorage.set(indexKey, Object.freeze({ ...current, state: "left-published" }), { ttl: Math.max(60, Math.ceil(schema.ttlMilliseconds * 2 / 1000)) });
5267    }
5268  });
5269  return true;
5270}
5271
5272async function presenceSweep() {
5273  if (presenceExpiryStorage === null) return;
5274  const startedAt = __noxidPubSubNow();
5275  let state = "delivered";
5276  let code = null;
5277  let claim = null;
5278  let ownsClaim = false;
5279  try {
5280    if (typeof __noxidSharedIdempotencyPrepare === "function") {
5281      claim = globalThis.crypto?.randomUUID?.();
5282      if (typeof claim !== "string") throw presenceFailure("PRESENCE_CRYPTO_UNAVAILABLE", "Presence sweep ownership requires secure random claims");
5283      const prepared = await __noxidSharedIdempotencyPrepare(`presence-sweep:${applicationNamespace}`, claim, LIVE_RESOURCE_SCHEMA.timeoutMs + 5_000);
5284      if (prepared?.state === "pending") { state = "retry"; return; }
5285      if (prepared?.state !== "owner") throw presenceFailure("PRESENCE_SWEEP_LOCK_INVALID", "Presence sweep lock returned an invalid shared state");
5286      ownsClaim = true;
5287    }
5288    const keys = await presenceExpiryStorage.list("expiry:");
5289    const cursor = await presenceExpiryStorage.get("sweep:cursor");
5290    const start = typeof cursor?.key === "string" ? Math.max(0, keys.findIndex((key) => key > cursor.key)) : 0;
5291    const batch = [...keys.slice(start), ...keys.slice(0, start)].slice(0, PRESENCE_SWEEP_BATCH);
5292    for (const key of batch) {
5293      try {
5294        const raw = await presenceExpiryStorage.get(key);
5295        if (raw === null) continue;
5296        const index = presenceValidateIndex(raw, key);
5297        if (index.state === "left" || index.state === "active" && index.expiresAt <= Date.now()) await presenceClaimExpired(key, index);
5298      } catch (cause) {
5299        __noxidTraceEmit(null, "presence.sweep.item", { semanticId: null, state: "failed", code: cause?.code ?? "PRESENCE_SWEEP_ITEM_FAILED" });
5300      }
5301    }
5302    if (batch.length > 0) await presenceExpiryStorage.set("sweep:cursor", Object.freeze({ key: batch.at(-1) }));
5303  } catch (cause) {
5304    state = "failed";
5305    code = cause?.code ?? "PRESENCE_SWEEP_FAILED";
5306  } finally {
5307    if (ownsClaim && claim !== null && typeof __noxidSharedIdempotencyRelease === "function") await __noxidSharedIdempotencyRelease(`presence-sweep:${applicationNamespace}`, claim).catch((cause) => { state = "failed"; code = cause?.code ?? "PRESENCE_SWEEP_RELEASE_FAILED"; });
5308    __noxidTraceEmit(null, "presence.sweep", { semanticId: null, state, code, durationMs: Math.max(0, __noxidPubSubNow() - startedAt) });
5309  }
5310}
5311
5312if (presenceSchemas.length > 0) {
5313  const interval = Math.max(1000, Math.min(...presenceSchemas.map((schema) => schema.heartbeatMilliseconds)));
5314  const schedule = () => {
5315    const timer = setTimeout(async () => { await presenceSweep(); schedule(); }, interval + Math.floor(Math.random() * Math.max(1, Math.floor(interval / 3))));
5316    timer?.unref?.();
5317  };
5318  schedule();
5319}
5320
5321async function presenceSnapshot(schema, principal, routeId, routePath, signal) {
5322  return presencePartitionLock(schema, principal.canonical, routeId, routePath, async () => {
5323    const members = [];
5324    const versions = new Map();
5325    const prefix = `member:${await presencePartition(schema, principal.canonical, routeId, routePath)}`;
5326    for (const key of await presenceMemberStorage.list(prefix)) {
5327      const raw = await presenceMemberStorage.get(key);
5328      if (raw === null) continue;
5329      const stored = presenceValidateStored(schema, raw, key);
5330      if (stored.principal !== principal.canonical || stored.routeId !== routeId || stored.routePath !== routePath) throw presenceFailure("PRESENCE_STORAGE_INVALID", "Presence storage crossed its canonical principal or route partition");
5331      const indexKey = presenceExpiryKey(key);
5332      const rawIndex = await presenceExpiryStorage.get(indexKey);
5333      const index = rawIndex === null ? null : presenceValidateIndex(rawIndex, indexKey);
5334      if (index?.state === "left" || index?.state === "left-published" || stored.expiresAt <= Date.now()) continue;
5335      if (index === null || index.state !== "active" || index.version !== stored.version || index.expiresAt !== stored.expiresAt || index.principal !== stored.principal || index.routeId !== stored.routeId || index.routePath !== stored.routePath) {
5336        await presenceWriteStored(schema, stored, key);
5337      }
5338      const member = presenceValidateType(schema.memberType, Object.freeze({ id: stored.memberId, ...stored.record }), "PRESENCE_STORAGE_INVALID");
5339      members.push(member);
5340      versions.set(stored.memberId, stored.version);
5341      if (members.length > PRESENCE_MAX_MEMBERS) throw presenceFailure("PRESENCE_CAPACITY_EXCEEDED", "Presence snapshot exceeded its bounded compiler-owned member capacity");
5342    }
5343    members.sort((left, right) => left.id.localeCompare(right.id));
5344    const value = presenceValidateType(schema.snapshotType, Object.freeze({ members: Object.freeze(members) }), "PRESENCE_STORAGE_INVALID");
5345    if (presenceSseFrameBytes(schema, Object.freeze({ tag: "Snapshot", value })) > PRESENCE_MAX_SNAPSHOT_BYTES) throw presenceFailure("PRESENCE_SNAPSHOT_TOO_LARGE", "Presence snapshot exceeded its bounded SSE transport budget");
5346    return Object.freeze({ value, versions });
5347  }, signal);
5348}
5349
5350function liveRouteSegments(path) {
5351  const normalized = path.length > 1 ? path.replace(/\/+$/, "") : path;
5352  if (normalized === "/") return [];
5353  const values = [];
5354  for (const encoded of normalized.slice(1).split("/")) {
5355    try { values.push(decodeURIComponent(encoded)); }
5356    catch { return null; }
5357  }
5358  return values;
5359}
5360
5361function liveRoutePatternSegments(pattern) {
5362  if (pattern === "/") return [];
5363  return pattern.slice(1).split("/");
5364}
5365
5366function liveRouteParameter(value, type) {
5367  if (type === "String") return value;
5368  if (type === "Int" && /^-?\d+$/.test(value)) {
5369    const parsed = Number(value);
5370    return Number.isSafeInteger(parsed) ? parsed : null;
5371  }
5372  if (type === "Boolean" && (value === "true" || value === "false")) return value === "true";
5373  return null;
5374}
5375
5376function liveRouteInstance(scope, supplied) {
5377  if (typeof supplied !== "string" || !supplied.startsWith("/") || supplied.length > 2048
5378    || liveResourceEncoder.encode(supplied).byteLength > 2048 || supplied.includes("?") || supplied.includes(String.fromCharCode(35))
5379    || /[\u0000-\u001f\u007f]/.test(supplied)) return null;
5380  const actual = liveRouteSegments(supplied);
5381  const expected = liveRoutePatternSegments(scope.pattern);
5382  if (actual === null || expected === null) return null;
5383  if (actual.some((segment) => segment === "." || segment === ".." || /[\u0000-\u001f\u007f]/.test(segment))) return null;
5384  const catchAllIndex = expected.findIndex((segment) => segment.startsWith("{*") && segment.endsWith("}"));
5385  if (catchAllIndex === -1 && actual.length !== expected.length) return null;
5386  if (catchAllIndex !== -1 && (catchAllIndex !== expected.length - 1 || actual.length < expected.length)) return null;
5387  const parameters = new Map(scope.parameters.map((parameter) => [parameter.name, parameter]));
5388  const params = Object.create(null);
5389  const canonicalSegments = [];
5390  for (let index = 0; index < expected.length; index += 1) {
5391    const segment = expected[index];
5392    if (segment.startsWith("{*") && segment.endsWith("}")) {
5393      const name = segment.slice(2, -1);
5394      const parameter = parameters.get(name);
5395      if (!parameter?.catchAll || parameter.type !== "Array<String>") return null;
5396      params[name] = Object.freeze(actual.slice(index));
5397      canonicalSegments.push(...actual.slice(index));
5398      break;
5399    }
5400    if (!segment.startsWith("{") || !segment.endsWith("}")) {
5401      if (segment !== actual[index]) return null;
5402      canonicalSegments.push(actual[index]);
5403      continue;
5404    }
5405    const name = segment.slice(1, -1);
5406    const parameter = parameters.get(name);
5407    if (!parameter || parameter.catchAll) return null;
5408    const converted = liveRouteParameter(actual[index], parameter.type);
5409    if (converted === null) return null;
5410    params[name] = converted;
5411    canonicalSegments.push(parameter.type === "String" ? converted : String(converted));
5412  }
5413  const path = canonicalSegments.length === 0 ? "/" : `/${canonicalSegments.map((segment) => encodeURIComponent(segment)).join("/")}`;
5414  if (liveResourceEncoder.encode(path).byteLength > 2048) return null;
5415  return Object.freeze({ path, params: Object.freeze(params) });
5416}
5417
5418function liveResourceSubscription(request, url) {
5419  if (request.method !== "GET") return { response: failure(405, "LIVE_RESOURCE_METHOD", "Live resource connections require GET", LIVE_RESOURCE_SCHEMA.id, null, { allow: "GET" }) };
5420  const accept = request.headers.get("accept") ?? "";
5421  if (!accept.toLowerCase().split(",").some((value) => value.trim().startsWith("text/event-stream"))) {
5422    return { response: failure(406, "LIVE_RESOURCE_ACCEPT_REQUIRED", "Live resource connections require Accept: text/event-stream", LIVE_RESOURCE_SCHEMA.id) };
5423  }
5424  for (const key of url.searchParams.keys()) {
5425    if (key !== "resource" && key !== "presence") return { response: failure(400, "LIVE_RESOURCE_QUERY_INVALID", "Live connections accept only repeated compiler-owned resource and presence query fields", LIVE_RESOURCE_SCHEMA.id) };
5426  }
5427  const requested = url.searchParams.getAll("resource");
5428  const requestedPresence = url.searchParams.getAll("presence");
5429  if (requested.length > liveResourceSchemas.length || new Set(requested).size !== requested.length
5430    || requestedPresence.length > presenceSchemas.length || new Set(requestedPresence).size !== requestedPresence.length
5431    || requested.length + requestedPresence.length === 0) {
5432    return { response: failure(400, "LIVE_RESOURCE_SET_INVALID", "Live connections require one bounded duplicate-free compiler-owned resource or presence set", LIVE_RESOURCE_SCHEMA.id) };
5433  }
5434  const schemas = [];
5435  const routeId = request.headers.get("x-noxid-route-id");
5436  if (typeof routeId !== "string" || routeId.length === 0 || liveResourceEncoder.encode(routeId).byteLength > 512 || /[\u0000-\u001f\u007f]/.test(routeId)) {
5437    return { response: failure(400, "LIVE_RESOURCE_ROUTE_REQUIRED", "Live resource connections require the compiler-selected route identity", LIVE_RESOURCE_SCHEMA.id) };
5438  }
5439  let routeScope = null;
5440  for (const semanticId of requested) {
5441    const schema = liveResourceSchemaById.get(semanticId);
5442    if (schema === undefined) return { response: failure(403, "LIVE_RESOURCE_NOT_DECLARED", "Live resource subscription is not present in the compiled manifest", semanticId) };
5443    const candidate = schema.routeScopes.find((scope) => scope.id === routeId);
5444    if (candidate === undefined) return { response: failure(403, "LIVE_RESOURCE_ROUTE_DENIED", "Live resource is not exposed by the compiler-selected route", semanticId, { route: routeId }) };
5445    if (routeScope === null) routeScope = candidate;
5446    else if (routeScope.pattern !== candidate.pattern || JSON.stringify(routeScope.parameters) !== JSON.stringify(candidate.parameters) || routeScope.middleware.join("\n") !== candidate.middleware.join("\n")) {
5447      return { response: failure(500, "LIVE_RESOURCE_ROUTE_DRIFT", "Compiled live resources disagree on the selected route middleware contract", semanticId) };
5448    }
5449    schemas.push(schema);
5450  }
5451  const selectedPresence = [];
5452  for (const semanticId of requestedPresence) {
5453    const schema = presenceSchemaById.get(semanticId);
5454    if (schema === undefined) return { response: failure(403, "PRESENCE_NOT_DECLARED", "Presence subscription is not present in the compiled manifest", semanticId) };
5455    const candidate = schema.routeScopes.find((scope) => scope.id === routeId);
5456    if (candidate === undefined) return { response: failure(403, "PRESENCE_ROUTE_DENIED", "Presence is not exposed by the compiler-selected route", semanticId, { route: routeId }) };
5457    if (routeScope === null) routeScope = candidate;
5458    else if (routeScope.pattern !== candidate.pattern || JSON.stringify(routeScope.parameters) !== JSON.stringify(candidate.parameters) || routeScope.middleware.join("\n") !== candidate.middleware.join("\n")) {
5459      return { response: failure(500, "LIVE_RESOURCE_ROUTE_DRIFT", "Compiled live subscriptions disagree on the selected route middleware contract", semanticId) };
5460    }
5461    schemas.push(schema);
5462    selectedPresence.push(schema);
5463  }
5464  schemas.sort((left, right) => left.id.localeCompare(right.id));
5465  selectedPresence.sort((left, right) => left.id.localeCompare(right.id));
5466  const resourceSchemas = Object.freeze(requested.map((id) => liveResourceSchemaById.get(id)).sort((left, right) => left.id.localeCompare(right.id)));
5467  const ids = Object.freeze(resourceSchemas.map((schema) => schema.id));
5468  const presenceIds = Object.freeze(selectedPresence.map((schema) => schema.id));
5469  const route = liveRouteInstance(routeScope, request.headers.get("x-noxid-route-path"));
5470  if (route === null) return { response: failure(400, "LIVE_RESOURCE_ROUTE_PATH_INVALID", "Live connection route path must exactly match its compiled route pattern and typed parameters", LIVE_RESOURCE_SCHEMA.id, { route: routeId }) };
5471  return { schemas: Object.freeze(schemas), resourceSchemas, presenceSchemas: Object.freeze(selectedPresence), ids, presenceIds, routeScope, routePath: route.path, params: route.params, key: `${routeId}\n${route.path}\nresources:${ids.join("\n")}\npresences:${presenceIds.join("\n")}` };
5472}
5473
5474function liveResourcePruneHistories() {
5475  if (liveResourceHistories.size < LIVE_RESOURCE_MAX_HISTORIES) return true;
5476  for (const [token, history] of liveResourceHistories) {
5477    if (!history.completed) continue;
5478    liveResourceHistories.delete(token);
5479    if (liveResourceHistories.size < LIVE_RESOURCE_MAX_HISTORIES) return true;
5480  }
5481  return false;
5482}
5483
5484function liveResourceFreshHistory(principal, subscriptionKey) {
5485  if (!liveResourcePruneHistories()) return null;
5486  let token = liveResourceToken();
5487  while (liveResourceHistories.has(token)) token = liveResourceToken();
5488  const history = { token, principal, subscriptionKey, events: [], bytes: 0, nextSequence: 1, completed: false };
5489  liveResourceHistories.set(token, history);
5490  return history;
5491}
5492
5493function liveResourceResumeHistory(lastEventId, principal, subscriptionKey) {
5494  const cursor = liveResourceCursor(lastEventId);
5495  if (cursor === null) return null;
5496  const history = liveResourceHistories.get(cursor.token);
5497  if (history === undefined || !history.completed || history.principal !== principal || history.subscriptionKey !== subscriptionKey) return null;
5498  const earliest = history.events[0]?.sequence ?? history.nextSequence;
5499  if (cursor.sequence < earliest - 1 || cursor.sequence >= history.nextSequence) return null;
5500  history.completed = false;
5501  liveResourceHistories.delete(history.token);
5502  liveResourceHistories.set(history.token, history);
5503  return Object.freeze({ history, replay: history.events.filter((event) => event.sequence > cursor.sequence) });
5504}
5505
5506function liveResourceRecord(history, semanticId) {
5507  const sequence = history.nextSequence++;
5508  const frame = `id: ${history.token}:${sequence}\nevent: noxid-live-invalidation\ndata: ${JSON.stringify(semanticId)}\n\n`;
5509  const bytes = liveResourceEncoder.encode(frame).byteLength;
5510  const event = Object.freeze({ sequence, semanticId, frame, bytes });
5511  history.events.push(event);
5512  history.bytes += bytes;
5513  while (history.events.length > LIVE_RESOURCE_MAX_EVENTS || history.bytes > LIVE_RESOURCE_MAX_HISTORY_BYTES) {
5514    history.bytes -= history.events.shift().bytes;
5515  }
5516  return event;
5517}
5518
5519async function liveResourceAuthorize(request, schemas, route, environment, executionContext, signal) {
5520  for (const schema of schemas) {
5521    if (signal.aborted) return endpointTimeoutFailure(LIVE_RESOURCE_SCHEMA);
5522    if (schema.capabilities.length > 0 && typeof authorize !== "function") {
5523      return failure(500, "LIVE_RESOURCE_AUTHORIZER_MISSING", "Live resource authorization is not configured", schema.id);
5524    }
5525    for (const capability of schema.capabilities) {
5526      let allowed = false;
5527      try {
5528        allowed = await authorize(Object.freeze({ capability, semanticId: schema.id, traceId: __noxidTraceIdForRequest(request), target: "live-resource", route, request, environment, executionContext, signal })) === true;
5529      } catch {}
5530      if (signal.aborted) return endpointTimeoutFailure(LIVE_RESOURCE_SCHEMA);
5531      if (!allowed) return failure(403, "LIVE_RESOURCE_CAPABILITY_DENIED", "Live resource capability was denied", schema.id, { capability });
5532    }
5533  }
5534  return null;
5535}
5536
5537function presenceValidateEvent(schema, envelope, routeId, routePath) {
5538  const body = envelope?.body;
5539  if (body === null || typeof body !== "object" || Array.isArray(body)
5540    || !["Joined", "Updated", "Left"].includes(body.tag)
5541    || typeof body.memberId !== "string" || !PRESENCE_CREDENTIAL.test(body.memberId)
5542    || !Number.isSafeInteger(body.version) || body.version < 1
5543    || body.routeId !== routeId || body.routePath !== routePath
5544    || Object.keys(body).sort().join("\n") !== "memberId\nrouteId\nroutePath\ntag\nvalue\nversion") {
5545    throw presenceFailure("PRESENCE_EVENT_INVALID", "Presence pub/sub delivered an invalid compiler-owned event");
5546  }
5547  if (body.tag === "Left") {
5548    if (typeof body.value !== "string" || !PRESENCE_CREDENTIAL.test(body.value)) throw presenceFailure("PRESENCE_EVENT_INVALID", "Presence Left requires one opaque member identity");
5549    if (body.memberId !== body.value) throw presenceFailure("PRESENCE_EVENT_INVALID", "Presence Left identity metadata drifted");
5550    return Object.freeze({ event: Object.freeze({ tag: "Left", value: body.value }), memberId: body.memberId, version: body.version });
5551  }
5552  const value = presenceValidateType(schema.memberType, body.value, "PRESENCE_EVENT_INVALID");
5553  if (value.id !== body.memberId) throw presenceFailure("PRESENCE_EVENT_INVALID", "Presence event identity metadata drifted");
5554  return Object.freeze({ event: Object.freeze({ tag: body.tag, value }), memberId: body.memberId, version: body.version });
5555}
5556
5557function liveResourceStream(request, resourceSchemas, presenceStreamSchemas, principal, routeId, routePath, history, replay, middlewareHeaders, deadline) {
5558  let closed = false;
5559  let controller = null;
5560  let heartbeatTimer = null;
5561  const pending = new Set();
5562  const pendingPresence = [];
5563  const replayQueue = [...replay];
5564  let syncPending = false;
5565  const unsubscribe = [];
5566  const trace = tracingMode === "full" ? __noxidTraceContext() : null;
5567
5568  const cleanup = async (closeStream) => {
5569    if (closed) return;
5570    closed = true;
5571    history.completed = true;
5572    if (heartbeatTimer !== null) clearInterval(heartbeatTimer);
5573    deadline.signal.removeEventListener("abort", onAbort);
5574    deadline.release();
5575    await Promise.allSettled(unsubscribe.splice(0).map((stop) => stop()));
5576    if (closeStream) { try { controller?.close(); } catch {} }
5577  };
5578  const send = (event) => {
5579    const startedAt = __noxidPubSubNow();
5580    controller.enqueue(liveResourceEncoder.encode(event.frame));
5581    __noxidTraceEmit(trace, "live.deliver", { semanticId: event.semanticId, state: "delivered", durationMs: Math.max(0, __noxidPubSubNow() - startedAt) });
5582  };
5583  const sendPresence = (entry) => {
5584    const startedAt = __noxidPubSubNow();
5585    let state = "delivered";
5586    let code = null;
5587    try {
5588      const frame = presenceSseFrame(entry.schema, entry.event);
5589      if (liveResourceEncoder.encode(frame).byteLength > PRESENCE_MAX_SNAPSHOT_BYTES) throw presenceFailure("PRESENCE_SNAPSHOT_TOO_LARGE", "Presence event exceeded its bounded SSE transport budget");
5590      controller.enqueue(liveResourceEncoder.encode(frame));
5591    } catch (cause) {
5592      state = "failed";
5593      code = cause?.code ?? "PRESENCE_DELIVERY_FAILED";
5594      throw cause;
5595    } finally {
5596      __noxidTraceEmit(trace, "presence.deliver", { semanticId: entry.schema.id, state, event: entry.event.tag, code, durationMs: Math.max(0, __noxidPubSubNow() - startedAt) });
5597    }
5598  };
5599  const queuePresence = (schema, event) => {
5600    if (event.tag === "Updated") {
5601      let same = -1;
5602      for (let index = pendingPresence.length - 1; index >= 0; index -= 1) {
5603        const entry = pendingPresence[index];
5604        if (entry.schema.id !== schema.id) continue;
5605        const memberId = entry.event.tag === "Left" ? entry.event.value : entry.event.value?.id;
5606        if ((entry.event.tag === "Joined" || entry.event.tag === "Left") && memberId === event.value.id) break;
5607        if (entry.event.tag === "Updated" && memberId === event.value.id) { same = index; break; }
5608      }
5609      if (same >= 0) { pendingPresence[same] = Object.freeze({ schema, event }); flush(); return; }
5610    }
5611    if (pendingPresence.length >= PRESENCE_MAX_MEMBERS) throw presenceFailure("PRESENCE_DELIVERY_CAPACITY", "Presence delivery exceeded its bounded queue; reconnect for a fresh Snapshot");
5612    pendingPresence.push(Object.freeze({ schema, event }));
5613    flush();
5614  };
5615  const flush = () => {
5616    if (closed || controller === null) return;
5617    try {
5618      while ((controller.desiredSize ?? 1) > 0 && replayQueue.length > 0) send(replayQueue.shift());
5619      while ((controller.desiredSize ?? 1) > 0 && pendingPresence.length > 0) sendPresence(pendingPresence.shift());
5620      if ((controller.desiredSize ?? 1) > 0 && syncPending) {
5621        syncPending = false;
5622        controller.enqueue(liveResourceEncoder.encode("event: noxid-live-sync\ndata: null\n\n"));
5623      }
5624      while ((controller.desiredSize ?? 1) > 0 && pending.size > 0) {
5625        const semanticId = pending.values().next().value;
5626        pending.delete(semanticId);
5627        send(liveResourceRecord(history, semanticId));
5628      }
5629    } catch { void cleanup(false); }
5630  };
5631  const onAbort = () => { void cleanup(true); };
5632
5633  const stream = new ReadableStream({
5634    async start(streamController) {
5635      controller = streamController;
5636      deadline.signal.addEventListener("abort", onAbort, { once: true });
5637      heartbeatTimer = setInterval(() => {
5638        if (closed || (controller.desiredSize ?? 1) <= 0) return;
5639        try { controller.enqueue(liveResourceEncoder.encode(": heartbeat\n\n")); } catch { void cleanup(false); }
5640      }, ENDPOINT_STREAM_HEARTBEAT_MS);
5641      heartbeatTimer?.unref?.();
5642      flush();
5643      try {
5644        for (const schema of resourceSchemas) {
5645          const stop = await __noxidPubSubSubscribe("invalidation", schema.id, principal, () => {
5646            pending.add(schema.id);
5647            flush();
5648          });
5649          if (closed) await stop();
5650          else unsubscribe.push(stop);
5651        }
5652        for (const schema of presenceStreamSchemas) {
5653          const buffered = [];
5654          const versions = new Map();
5655          const present = new Set();
5656          let ready = false;
5657          const reconcileDelta = (delta) => {
5658            const known = versions.get(delta.memberId);
5659            if (known !== undefined && delta.version <= known) return null;
5660            versions.set(delta.memberId, delta.version);
5661            if (delta.event.tag === "Left") {
5662              return present.delete(delta.memberId) ? delta.event : null;
5663            }
5664            if (delta.event.tag === "Joined" && present.has(delta.memberId)) {
5665              throw presenceFailure("PRESENCE_RESYNC_REQUIRED", "Presence structural ordering was incomplete; reconnect for a fresh Snapshot");
5666            }
5667            if (delta.event.tag === "Updated" && !present.has(delta.memberId)) {
5668              present.add(delta.memberId);
5669              return Object.freeze({ tag: "Joined", value: delta.event.value });
5670            }
5671            present.add(delta.memberId);
5672            return delta.event;
5673          };
5674          const topicId = await presenceTopicId(schema, routeId, routePath);
5675          const stop = await __noxidPubSubSubscribe("presence", topicId, principal, (envelope) => {
5676            try {
5677              if (envelope?.body?.routeId !== routeId || envelope?.body?.routePath !== routePath) return;
5678              const delta = presenceValidateEvent(schema, envelope, routeId, routePath);
5679              if (!ready) {
5680                if (delta.event.tag === "Updated") {
5681                  let same = -1;
5682                  for (let index = buffered.length - 1; index >= 0; index -= 1) {
5683                    const item = buffered[index];
5684                    if ((item.event.tag === "Joined" || item.event.tag === "Left") && item.memberId === delta.memberId) break;
5685                    if (item.event.tag === "Updated" && item.memberId === delta.memberId) { same = index; break; }
5686                  }
5687                  if (same >= 0 && buffered[same].version <= delta.version) { buffered[same] = delta; return; }
5688                }
5689                if (buffered.length >= PRESENCE_MAX_MEMBERS) throw presenceFailure("PRESENCE_SNAPSHOT_RACE_CAPACITY", "Presence changed too quickly while constructing Snapshot; reconnect to retry");
5690                buffered.push(delta);
5691              } else {
5692                const event = reconcileDelta(delta);
5693                if (event !== null) queuePresence(schema, event);
5694              }
5695            } catch (cause) {
5696              void cleanup(false);
5697              try { controller.error(cause); } catch {}
5698            }
5699          });
5700          if (closed) { await stop(); continue; }
5701          unsubscribe.push(stop);
5702          const snapshot = await presenceSnapshot(schema, principal, routeId, routePath, deadline.signal);
5703          for (const [memberId, version] of snapshot.versions) { versions.set(memberId, version); present.add(memberId); }
5704          queuePresence(schema, Object.freeze({ tag: "Snapshot", value: snapshot.value }));
5705          ready = true;
5706          for (const delta of buffered) {
5707            const event = reconcileDelta(delta);
5708            if (event !== null) queuePresence(schema, event);
5709          }
5710        }
5711        // This fence closes both initial-fetch -> first-subscribe and
5712        // disconnect -> resumed-subscribe gaps. Replay remains strictly after
5713        // the supplied cursor; the fence carries no cursor and asks the client
5714        // to validate current state once after every exact set is installed.
5715        syncPending = resourceSchemas.length > 0;
5716        flush();
5717      } catch (cause) {
5718        await cleanup(false);
5719        try { controller.error(cause); } catch {}
5720      }
5721    },
5722    pull() { flush(); },
5723    cancel() { return cleanup(false); },
5724  });
5725  const headers = new Headers({
5726    "content-type": "text/event-stream; charset=utf-8",
5727    "cache-control": "no-store",
5728    "connection": "keep-alive",
5729    "x-accel-buffering": "no",
5730    "x-content-type-options": "nosniff",
5731  });
5732  for (const [name, value] of middlewareHeaders) headers.append(name, value);
5733  return new Response(stream, { status: 200, headers });
5734}
5735
5736function presenceRequestShape(value, operation) {
5737  if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
5738  const allowed = operation === "join"
5739    ? "nonce\noperation\npresence\nrecord\ntoken"
5740    : operation === "update"
5741      ? "memberId\noperation\npresence\nrecord\ntoken"
5742      : "memberId\noperation\npresence\ntoken";
5743  return Object.keys(value).sort().join("\n") === allowed ? value : null;
5744}
5745
5746async function presenceReadRequest(request, signal) {
5747  if (request.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase() !== "application/json") {
5748    throw presenceFailure("PRESENCE_CONTENT_TYPE_REQUIRED", "Presence writes require Content-Type: application/json");
5749  }
5750  const declared = request.headers.get("content-length");
5751  if (declared !== null && (!/^\d+$/.test(declared) || Number(declared) > 12_288)) throw presenceFailure("PRESENCE_BODY_TOO_LARGE", "Presence writes are bounded to 12288 bytes");
5752  if (request.body === null) throw presenceFailure("PRESENCE_BODY_INVALID", "Presence write body must be valid JSON");
5753  const reader = request.body.getReader();
5754  const chunks = [];
5755  let total = 0;
5756  const cancel = () => { void reader.cancel("presence request cancelled").catch(() => {}); };
5757  signal.addEventListener("abort", cancel, { once: true });
5758  try {
5759    while (true) {
5760      if (signal.aborted) throw presenceFailure("PRESENCE_OPERATION_ABORTED", "Presence request exceeded its bounded lifetime");
5761      const chunk = await reader.read();
5762      if (signal.aborted) throw presenceFailure("PRESENCE_OPERATION_ABORTED", "Presence request exceeded its bounded lifetime");
5763      if (chunk.done) break;
5764      if (!(chunk.value instanceof Uint8Array)) throw presenceFailure("PRESENCE_BODY_INVALID", "Presence request body stream did not yield bytes");
5765      total += chunk.value.byteLength;
5766      if (total > 12_288) {
5767        await reader.cancel("presence request body too large").catch(() => {});
5768        throw presenceFailure("PRESENCE_BODY_TOO_LARGE", "Presence writes are bounded to 12288 bytes");
5769      }
5770      chunks.push(chunk.value);
5771    }
5772  } finally {
5773    signal.removeEventListener("abort", cancel);
5774    try { reader.releaseLock(); } catch {}
5775  }
5776  const bytes = new Uint8Array(total);
5777  let offset = 0;
5778  for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
5779  let text;
5780  try { text = new TextDecoder("utf-8", { fatal: true }).decode(bytes); }
5781  catch (cause) { throw presenceFailure("PRESENCE_BODY_INVALID", "Presence write body must be UTF-8", cause); }
5782  let value;
5783  try { value = JSON.parse(text); }
5784  catch (cause) { throw presenceFailure("PRESENCE_BODY_INVALID", "Presence write body must be valid JSON", cause); }
5785  const operation = value?.operation;
5786  if (!["join", "update", "heartbeat", "leave"].includes(operation) || presenceRequestShape(value, operation) === null) {
5787    throw presenceFailure("PRESENCE_BODY_INVALID", "Presence writes use only the compiler-owned join, update, heartbeat, and leave shapes");
5788  }
5789  return value;
5790}
5791
5792async function presenceEnsureAggregate(schema, canonical, routeId, routePath, candidateId, candidateRecord) {
5793  const members = [];
5794  const prefix = `member:${await presencePartition(schema, canonical, routeId, routePath)}`;
5795  for (const key of await presenceMemberStorage.list(prefix)) {
5796    const raw = await presenceMemberStorage.get(key);
5797    if (raw === null) continue;
5798    const stored = presenceValidateStored(schema, raw, key);
5799    if (stored.principal !== canonical || stored.routeId !== routeId || stored.routePath !== routePath) throw presenceFailure("PRESENCE_STORAGE_INVALID", "Presence aggregate crossed its canonical principal or route partition");
5800    if (stored.expiresAt <= Date.now() || stored.memberId === candidateId) continue;
5801    members.push(presenceValidateType(schema.memberType, Object.freeze({ id: stored.memberId, ...stored.record }), "PRESENCE_STORAGE_INVALID"));
5802  }
5803  members.push(presenceValidateType(schema.memberType, Object.freeze({ id: candidateId, ...candidateRecord }), "PRESENCE_RECORD_INVALID"));
5804  if (members.length > PRESENCE_MAX_MEMBERS) throw presenceFailure("PRESENCE_CAPACITY_EXCEEDED", "Presence partition reached its bounded member capacity");
5805  const snapshot = presenceValidateType(schema.snapshotType, Object.freeze({ members: Object.freeze(members) }), "PRESENCE_RECORD_INVALID");
5806  if (presenceSseFrameBytes(schema, Object.freeze({ tag: "Snapshot", value: snapshot })) > PRESENCE_MAX_SNAPSHOT_BYTES) {
5807    throw presenceFailure("PRESENCE_SNAPSHOT_TOO_LARGE", "Presence write would exceed the bounded aggregate Snapshot transport budget");
5808  }
5809}
5810
5811async function presenceDelta(schema, tag, stored) {
5812  const value = tag === "Left"
5813    ? stored.memberId
5814    : presenceValidateType(schema.memberType, Object.freeze({ id: stored.memberId, ...stored.record }), "PRESENCE_STORAGE_INVALID");
5815  const body = Object.freeze({ tag, value, memberId: stored.memberId, version: stored.version, routeId: stored.routeId, routePath: stored.routePath });
5816  if (liveResourceEncoder.encode(JSON.stringify(body)).byteLength > PRESENCE_MAX_DELTA_BYTES) {
5817    throw presenceFailure("PRESENCE_RECORD_TOO_LARGE", "Presence record exceeds the bounded cross-driver delta budget");
5818  }
5819  try { __noxidPubSubEventFromCanonical("presence", await presenceTopicId(schema, stored.routeId, stored.routePath), stored.principal, body, "presence_size_probe"); }
5820  catch (cause) {
5821    if (cause?.code === "PUBSUB_EVENT_TOO_LARGE") throw presenceFailure("PRESENCE_RECORD_TOO_LARGE", "Presence record exceeds the bounded cross-driver event budget", cause);
5822    throw cause;
5823  }
5824  return body;
5825}
5826
5827async function presencePublishAndClearTombstone(schema, tombstone, signal = null) {
5828  await presencePublishCanonical(schema, tombstone.principal, Object.freeze({ tag: "Left", value: tombstone.memberId, memberId: tombstone.memberId, version: tombstone.version, routeId: tombstone.routeId, routePath: tombstone.routePath }));
5829  await presencePartitionLock(schema, tombstone.principal, tombstone.routeId, tombstone.routePath, async () => {
5830    const key = presenceExpiryKey(tombstone.key);
5831    const current = await presenceExpiryStorage.get(key);
5832    if (current?.state === "left" && current.version === tombstone.version) {
5833      await presenceExpiryStorage.set(key, Object.freeze({ ...current, state: "left-published" }), { ttl: Math.max(60, Math.ceil(schema.ttlMilliseconds * 2 / 1000)) });
5834    }
5835  }, signal);
5836}
5837
5838async function presenceMutate(schema, principal, operationalIdentity, routeId, routePath, body, signal) {
5839  const canonical = principal.canonical;
5840  if (body.operation === "join") {
5841    if (!PRESENCE_CREDENTIAL.test(body.nonce) || !PRESENCE_CREDENTIAL.test(body.token)) throw presenceFailure("PRESENCE_CREDENTIAL_INVALID", "Presence join requires bounded cryptographic nonce and credential fields");
5842    const record = presenceValidateType(schema.recordType, body.record, "PRESENCE_RECORD_INVALID");
5843    const memberId = `member_${(await presenceDigest(schema.id, canonical, routePath, body.nonce)).slice(0, 48)}`;
5844    const tokenDigest = await presenceDigest(schema.id, canonical, routePath, memberId, body.token);
5845    for (;;) {
5846      const outcome = await presencePartitionLock(schema, canonical, routeId, routePath, async () => {
5847        const memberKey = await presenceMemberKey(schema, canonical, routeId, routePath, memberId);
5848        const indexKey = presenceExpiryKey(memberKey);
5849        const marker = await presenceExpiryStorage.get(indexKey);
5850        const trustedMarker = marker === null ? null : presenceValidateIndex(marker, indexKey);
5851        if (trustedMarker !== null && (trustedMarker.principal !== canonical || trustedMarker.routeId !== routeId || trustedMarker.routePath !== routePath || trustedMarker.presence !== schema.id || trustedMarker.memberId !== memberId)) throw presenceFailure("PRESENCE_INDEX_INVALID", "Presence join marker crossed its compiler-owned partition");
5852        if (trustedMarker?.state === "left") return Object.freeze({ tombstone: trustedMarker });
5853        const version = trustedMarker?.state === "left-published" ? trustedMarker.version + 1 : trustedMarker?.state === "active" ? trustedMarker.version : 1;
5854        const raw = await presenceMemberStorage.get(memberKey);
5855        if (raw !== null) {
5856          const stored = presenceValidateStored(schema, raw, memberKey);
5857          if (stored.principal !== canonical || stored.routeId !== routeId || stored.routePath !== routePath || stored.nonce !== body.nonce || !presenceDigestEqual(stored.tokenDigest, tokenDigest)) throw presenceFailure("PRESENCE_CREDENTIAL_DENIED", "Presence join credentials do not match the compiler-owned membership");
5858          if (stored.expiresAt <= Date.now()) {
5859            const tombstone = Object.freeze({ schema: PRESENCE_INDEX_SCHEMA, key: memberKey, presence: schema.id, principal: canonical, routeId, routePath, memberId, version: stored.version + 1, expiresAt: stored.expiresAt, state: "left" });
5860            await presenceExpiryStorage.set(presenceExpiryKey(memberKey), tombstone);
5861            await presenceMemberStorage.delete(memberKey);
5862            return Object.freeze({ tombstone, nextVersion: tombstone.version + 1 });
5863          }
5864          await presenceEnsureAggregate(schema, canonical, routeId, routePath, memberId, record);
5865          const changed = JSON.stringify(record) !== JSON.stringify(stored.record);
5866          const resumed = trustedMarker?.state === "left-published";
5867          const interrupted = trustedMarker?.state === "active" && trustedMarker.version > stored.version;
5868          const nextVersion = Math.max(interrupted ? trustedMarker.version : 0, resumed ? version : changed ? stored.version + 1 : stored.version);
5869          const publishTag = resumed || interrupted ? "Joined" : changed ? "Updated" : "Joined";
5870          const next = Object.freeze({ ...stored, record, version: nextVersion, expiresAt: Date.now() + schema.ttlMilliseconds });
5871          await presenceDelta(schema, publishTag, next);
5872          await presenceWriteStored(schema, next, memberKey);
5873          return Object.freeze({ stored: next, publishTag });
5874        }
5875        await presenceEnsureAggregate(schema, canonical, routeId, routePath, memberId, record);
5876        const stored = Object.freeze({ schema: PRESENCE_STORAGE_SCHEMA, presence: schema.id, principal: canonical, routeId, routePath, memberId, nonce: body.nonce, tokenDigest, record, version, expiresAt: Date.now() + schema.ttlMilliseconds });
5877        await presenceDelta(schema, "Joined", stored);
5878        await presenceWriteStored(schema, stored, memberKey);
5879        return Object.freeze({ stored, publishTag: "Joined" });
5880      }, signal);
5881      if (outcome.tombstone) {
5882        await presencePublishAndClearTombstone(schema, outcome.tombstone, signal);
5883        continue;
5884      }
5885      await presencePublish(schema, principal, await presenceDelta(schema, outcome.publishTag, outcome.stored));
5886      return Object.freeze({ memberId });
5887    }
5888  }
5889
5890  if (!PRESENCE_CREDENTIAL.test(body.memberId) || !PRESENCE_CREDENTIAL.test(body.token)) throw presenceFailure("PRESENCE_CREDENTIAL_INVALID", "Presence mutation requires bounded opaque member credentials");
5891  const memberKey = await presenceMemberKey(schema, canonical, routeId, routePath, body.memberId);
5892  const tokenDigest = await presenceDigest(schema.id, canonical, routePath, body.memberId, body.token);
5893  const outcome = await presencePartitionLock(schema, canonical, routeId, routePath, async () => {
5894    const indexKey = presenceExpiryKey(memberKey);
5895    const rawIndex = await presenceExpiryStorage.get(indexKey);
5896    const index = rawIndex === null ? null : presenceValidateIndex(rawIndex, indexKey);
5897    if (index !== null && (index.principal !== canonical || index.routeId !== routeId || index.routePath !== routePath || index.presence !== schema.id || index.memberId !== body.memberId)) throw presenceFailure("PRESENCE_INDEX_INVALID", "Presence mutation marker crossed its compiler-owned partition");
5898    const raw = await presenceMemberStorage.get(memberKey);
5899    if (index?.state === "left" || index?.state === "left-published") {
5900      if (raw !== null) {
5901        const stale = presenceValidateStored(schema, raw, memberKey);
5902        if (!presenceDigestEqual(stale.tokenDigest, tokenDigest)) throw presenceFailure("PRESENCE_CREDENTIAL_DENIED", "Presence credentials do not own this route-bound membership");
5903        await presenceMemberStorage.delete(memberKey);
5904      }
5905      return Object.freeze({ expired: true, tombstone: index.state === "left" ? index : null });
5906    }
5907    if (raw === null) return Object.freeze({ expired: true });
5908    const stored = presenceValidateStored(schema, raw, memberKey);
5909    if (stored.principal !== canonical || stored.routeId !== routeId || stored.routePath !== routePath || !presenceDigestEqual(stored.tokenDigest, tokenDigest)) throw presenceFailure("PRESENCE_CREDENTIAL_DENIED", "Presence credentials do not own this route-bound membership");
5910    await presenceAdmitRate(operationalIdentity, "member", PRESENCE_MEMBER_WRITES_PER_MINUTE, [schema.id, routeId, routePath, stored.memberId]);
5911    if (stored.expiresAt <= Date.now()) {
5912      const tombstone = Object.freeze({ schema: PRESENCE_INDEX_SCHEMA, key: memberKey, presence: schema.id, principal: canonical, routeId, routePath, memberId: stored.memberId, version: stored.version + 1, expiresAt: stored.expiresAt, state: "left" });
5913      await presenceExpiryStorage.set(presenceExpiryKey(memberKey), tombstone);
5914      await presenceMemberStorage.delete(memberKey);
5915      return Object.freeze({ expired: true, tombstone });
5916    }
5917    if (body.operation === "leave") {
5918      const tombstone = Object.freeze({ schema: PRESENCE_INDEX_SCHEMA, key: memberKey, presence: schema.id, principal: canonical, routeId, routePath, memberId: stored.memberId, version: stored.version + 1, expiresAt: stored.expiresAt, state: "left" });
5919      await presenceExpiryStorage.set(presenceExpiryKey(memberKey), tombstone);
5920      await presenceMemberStorage.delete(memberKey);
5921      return Object.freeze({ tombstone });
5922    }
5923    const nextRecord = body.operation === "update"
5924      ? presenceValidateType(schema.recordType, body.record, "PRESENCE_RECORD_INVALID")
5925      : stored.record;
5926    if (body.operation === "update") await presenceEnsureAggregate(schema, canonical, routeId, routePath, stored.memberId, nextRecord);
5927    const unchanged = body.operation === "update" && JSON.stringify(nextRecord) === JSON.stringify(stored.record);
5928    const next = Object.freeze({ ...stored, record: nextRecord, version: unchanged ? stored.version : stored.version + 1, expiresAt: Date.now() + schema.ttlMilliseconds });
5929    if (body.operation === "update") await presenceDelta(schema, "Updated", next);
5930    await presenceWriteStored(schema, next, memberKey);
5931    return Object.freeze({ stored: next, publish: body.operation === "update" });
5932  }, signal);
5933  if (outcome.tombstone) await presencePublishAndClearTombstone(schema, outcome.tombstone, signal);
5934  if (outcome.expired) throw presenceFailure("PRESENCE_MEMBERSHIP_EXPIRED", "Presence membership expired; reconnect for a fresh Snapshot and join again");
5935  if (outcome.publish) await presencePublish(schema, principal, await presenceDelta(schema, "Updated", outcome.stored));
5936  return Object.freeze({});
5937}
5938
5939async function handlePresenceWriteRequest(request, url, environment, executionContext) {
5940  if (request.method !== "POST") return failure(405, "PRESENCE_METHOD", "Presence writes require POST", LIVE_RESOURCE_SCHEMA.id, null, { allow: "POST" });
5941  if (url.search !== "") return failure(400, "PRESENCE_QUERY_INVALID", "Presence writes do not accept query fields", LIVE_RESOURCE_SCHEMA.id);
5942  const controller = new AbortController();
5943  const abortPresenceRequest = () => controller.abort("Presence request was cancelled");
5944  request.signal.addEventListener("abort", abortPresenceRequest, { once: true });
5945  if (request.signal.aborted) abortPresenceRequest();
5946  const timer = setTimeout(() => controller.abort("Presence write timed out"), LIVE_RESOURCE_SCHEMA.timeoutMs);
5947  timer?.unref?.();
5948  let schema = null;
5949  try {
5950    const body = await presenceReadRequest(request, controller.signal);
5951    schema = presenceSchemaById.get(body.presence) ?? null;
5952    if (schema === null) return failure(403, "PRESENCE_NOT_DECLARED", "Presence write is not present in the compiled manifest", body.presence ?? null);
5953    const routeId = request.headers.get("x-noxid-route-id");
5954    if (typeof routeId !== "string" || routeId.length === 0 || liveResourceEncoder.encode(routeId).byteLength > 512 || /[\u0000-\u001f\u007f]/.test(routeId)) return failure(400, "LIVE_RESOURCE_ROUTE_REQUIRED", "Presence writes require the compiler-selected route identity", schema.id);
5955    const routeScope = schema.routeScopes.find((scope) => scope.id === routeId);
5956    if (routeScope === undefined) return failure(403, "PRESENCE_ROUTE_DENIED", "Presence is not exposed by the compiler-selected route", schema.id, { route: routeId });
5957    const route = liveRouteInstance(routeScope, request.headers.get("x-noxid-route-path"));
5958    if (route === null) return failure(400, "LIVE_RESOURCE_ROUTE_PATH_INVALID", "Presence route path must exactly match its compiled route pattern and typed parameters", schema.id, { route: routeId });
5959    const routeSchema = Object.freeze({ ...LIVE_RESOURCE_SCHEMA, id: routeScope.id, path: routeScope.pattern, method: "POST", middleware: routeScope.middleware });
5960    const middleware = await applyEndpointMiddleware(request, routeSchema, route.params, Object.freeze({ presence: schema.id, operation: body.operation }), environment, executionContext, controller.signal);
5961    if (controller.signal.aborted) return endpointTimeoutFailure(LIVE_RESOURCE_SCHEMA);
5962    if (middleware.response) return endpointResponseWithHeaders(middleware.response, middleware.headers);
5963    const denied = await liveResourceAuthorize(request, [schema], middleware.route, environment, executionContext, controller.signal);
5964    if (denied) return endpointResponseWithHeaders(denied, middleware.headers);
5965    const principal = __noxidLiveConnectionPrincipal(middleware.context, environment);
5966    const operationalIdentity = presenceOperationalIdentity(environment, middleware.context, principal);
5967    if (operationalIdentity === null) return failure(403, "PRESENCE_RATE_IDENTITY_REQUIRED", "Public presence writes require a trusted request identity for operational abuse controls", schema.id);
5968    await presenceAdmitRate(operationalIdentity, "abuse", PRESENCE_ABUSE_ATTEMPTS_PER_MINUTE);
5969    if (body.operation === "join") await presenceAdmitRate(operationalIdentity, "join", PRESENCE_JOINS_PER_MINUTE);
5970    const result = await presenceMutate(schema, principal, operationalIdentity, routeId, route.path, body, controller.signal);
5971    if (controller.signal.aborted) return endpointTimeoutFailure(LIVE_RESOURCE_SCHEMA);
5972    return endpointResponseWithHeaders(json(200, Object.freeze({ ok: true, ...result })), middleware.headers);
5973  } catch (cause) {
5974    if (controller.signal.aborted || cause?.code === "PRESENCE_OPERATION_ABORTED") return endpointTimeoutFailure(LIVE_RESOURCE_SCHEMA);
5975    const clientCodes = new Set(["PRESENCE_CREDENTIAL_INVALID", "PRESENCE_RECORD_INVALID", "PRESENCE_RECORD_TOO_LARGE", "PRESENCE_SNAPSHOT_TOO_LARGE", "PRESENCE_CAPACITY_EXCEEDED"]);
5976    const status = cause?.code === "PRESENCE_BODY_TOO_LARGE" ? 413
5977      : cause?.code === "PRESENCE_BODY_INVALID" || cause?.code === "PRESENCE_CONTENT_TYPE_REQUIRED" ? 400
5978      : cause?.code === "PRESENCE_CREDENTIAL_DENIED" ? 403
5979      : cause?.code === "PRESENCE_MEMBERSHIP_EXPIRED" ? 409
5980      : cause?.code === "PRESENCE_RATE_LIMITED" ? 429
5981      : cause?.code === "PRESENCE_STORAGE_UNAVAILABLE" ? 503
5982      : clientCodes.has(cause?.code) ? 422 : 500;
5983    return failure(status, cause?.code ?? "PRESENCE_WRITE_FAILED", cause?.message ?? "Presence write failed", schema?.id ?? LIVE_RESOURCE_SCHEMA.id, null, cause?.retryAfter === undefined ? {} : { "retry-after": String(cause.retryAfter) });
5984  } finally {
5985    clearTimeout(timer);
5986    request.signal.removeEventListener("abort", abortPresenceRequest);
5987  }
5988}
5989
5990async function handleLiveResourceRequest(request, url, environment, executionContext) {
5991  if (url.pathname === presenceWritePath) return handlePresenceWriteRequest(request, url, environment, executionContext);
5992  if (url.pathname !== liveResourcePath) return null;
5993  const subscription = liveResourceSubscription(request, url);
5994  if (subscription.response) return subscription.response;
5995  const deadlineController = new AbortController();
5996  let deadlineReleased = false;
5997  let streamOwnsDeadline = false;
5998  let resolveDeadline;
5999  const deadlineResponse = new Promise((resolve) => { resolveDeadline = resolve; });
6000  const expire = () => {
6001    if (deadlineReleased) return;
6002    deadlineController.abort("Live resource connection lifetime ended");
6003    resolveDeadline(endpointTimeoutFailure(LIVE_RESOURCE_SCHEMA));
6004  };
6005  const deadlineTimer = setTimeout(expire, LIVE_RESOURCE_SCHEMA.timeoutMs);
6006  deadlineTimer?.unref?.();
6007  request.signal.addEventListener("abort", expire, { once: true });
6008  const deadline = Object.freeze({
6009    signal: deadlineController.signal,
6010    release() {
6011      if (deadlineReleased) return;
6012      deadlineReleased = true;
6013      clearTimeout(deadlineTimer);
6014      request.signal.removeEventListener("abort", expire);
6015    },
6016  });
6017  const setup = async () => {
6018    const routeSchema = Object.freeze({ ...LIVE_RESOURCE_SCHEMA, id: subscription.routeScope.id, path: subscription.routeScope.pattern, middleware: subscription.routeScope.middleware });
6019    const middleware = await applyEndpointMiddleware(request, routeSchema, subscription.params, Object.freeze({ resource: subscription.ids, presence: subscription.presenceIds }), environment, executionContext, deadline.signal);
6020    if (deadline.signal.aborted) return endpointTimeoutFailure(LIVE_RESOURCE_SCHEMA);
6021    if (middleware.response) return endpointResponseWithHeaders(middleware.response, middleware.headers);
6022    const authorizationFailure = await liveResourceAuthorize(request, subscription.schemas, middleware.route, environment, executionContext, deadline.signal);
6023    if (deadline.signal.aborted) return endpointTimeoutFailure(LIVE_RESOURCE_SCHEMA);
6024    if (authorizationFailure) return endpointResponseWithHeaders(authorizationFailure, middleware.headers);
6025    const principal = __noxidLiveConnectionPrincipal(middleware.context, environment);
6026    const suppliedCursor = request.headers.get("last-event-id");
6027    if (suppliedCursor !== null) {
6028      const resumed = liveResourceResumeHistory(suppliedCursor, principal.canonical, subscription.key);
6029      if (resumed === null) return liveResourceResetResponse(middleware.headers);
6030      const response = liveResourceStream(request, subscription.resourceSchemas, subscription.presenceSchemas, principal, subscription.routeScope.id, subscription.routePath, resumed.history, resumed.replay, middleware.headers, deadline);
6031      streamOwnsDeadline = true;
6032      return response;
6033    }
6034    const history = liveResourceFreshHistory(principal.canonical, subscription.key);
6035    if (history === null) return failure(503, "LIVE_RESOURCE_CAPACITY", "Live resource replay capacity is temporarily exhausted", LIVE_RESOURCE_SCHEMA.id);
6036    const response = liveResourceStream(request, subscription.resourceSchemas, subscription.presenceSchemas, principal, subscription.routeScope.id, subscription.routePath, history, [], middleware.headers, deadline);
6037    streamOwnsDeadline = true;
6038    return response;
6039  };
6040  try { return await Promise.race([setup(), deadlineResponse]); }
6041  finally { if (!streamOwnsDeadline) deadline.release(); }
6042}
6043"##;
6044
6045const HANDLER_RUNTIME: &str = r#"/* noxid-server:live-invalidation */
6046
6047function json(status, body, headers = {}) {
6048  return new Response(JSON.stringify(body), {
6049    status,
6050    headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "x-content-type-options": "nosniff", ...headers },
6051  });
6052}
6053
6054function failure(status, code, message, semanticId = null, details = null, headers = {}, traceable = true) {
6055  const response = json(status, { ok: false, error: { code, message, semanticId, details } }, headers);
6056  return traceable ? __noxidTraceFailure(response, code, semanticId, details?.capability) : response;
6057}
6058
6059const SSR_MIDDLEWARE_STATE = Symbol.for("noxid.ssr.middleware.state");
6060const EMPTY_MIDDLEWARE_CONTEXT = Object.freeze({});
6061function inheritedMiddlewareState(scope, executionContext) {
6062  const state = executionContext?.[SSR_MIDDLEWARE_STATE];
6063  if (!state || state.routeId !== scope.id || !Array.isArray(state.applied)) return null;
6064  const expected = [...globalMiddleware, ...scope.middleware];
6065  if (state.applied.length !== expected.length || !expected.every((name, index) => state.applied[index] === name)) return null;
6066  return state;
6067}
6068
6069function splitGeneric(type, prefix) {
6070  if (!type.startsWith(`${prefix}<`) || !type.endsWith(">")) return null;
6071  return type.slice(prefix.length + 1, -1);
6072}
6073
6074function splitGenericPair(type, prefix) {
6075  const body = splitGeneric(type, prefix);
6076  if (body === null) return null;
6077  let depth = 0;
6078  for (let index = 0; index < body.length; index += 1) {
6079    if (body[index] === "<") depth += 1;
6080    else if (body[index] === ">") depth -= 1;
6081    else if (body[index] === "," && depth === 0) return [body.slice(0, index).trim(), body.slice(index + 1).trim()];
6082  }
6083  return null;
6084}
6085
6086function valid(value) { return { value }; }
6087function invalid(issue, details = null) { return { issue, details }; }
6088
6089function validateType(type, value, path, typeId = null, external = false, ancestors = new WeakSet()) {
6090  if (type === "String") return typeof value === "string" ? valid(value) : invalid(`${path} must be String`);
6091  if (type === "Boolean") return typeof value === "boolean" ? valid(value) : invalid(`${path} must be Boolean`);
6092  if (type === "Int") return Number.isSafeInteger(value) ? valid(value) : invalid(`${path} must be Int`);
6093  if (type === "Number" || type === "Float") return typeof value === "number" && Number.isFinite(value) ? valid(value) : invalid(`${path} must be ${type}`);
6094  if (type === "Date") return typeof value === "string" && (external ? queueUtcInstant(value) !== null : !Number.isNaN(Date.parse(value))) ? valid(value) : invalid(`${path} must be a UTC ISO Date string`);
6095  const optional = splitGeneric(type, "Optional");
6096  if (optional !== null) return value == null ? valid(external ? null : value) : validateType(optional, value, path, typeId, external, ancestors);
6097  const array = splitGeneric(type, "Array");
6098  if (array !== null) {
6099    if (!Array.isArray(value)) return invalid(`${path} must be ${type}`);
6100    if (ancestors.has(value)) return invalid(`${path} must be acyclic ordinary data`);
6101    ancestors.add(value);
6102    const trusted = [];
6103    try {
6104      const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
6105      if (!lengthDescriptor || !("value" in lengthDescriptor) || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0) return invalid(`${path} must be an ordinary dense Array`);
6106      const length = lengthDescriptor.value;
6107      const keys = Reflect.ownKeys(value);
6108      if (external && keys.some((key) => key !== "length" && (typeof key !== "string" || !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= length))) return invalid(`${path} must not contain undeclared array properties`);
6109      for (let index = 0; index < length; index += 1) {
6110        const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
6111        if (descriptor !== undefined && (!("value" in descriptor) || !descriptor.enumerable)) return invalid(`${path}[${index}] must be own enumerable array data`);
6112        const result = validateType(array, descriptor?.value, `${path}[${index}]`, typeId, external, ancestors);
6113        if (result.issue) return result;
6114        trusted.push(result.value);
6115      }
6116      return valid(Object.freeze(trusted));
6117    } catch {
6118      return invalid(`${path} must be an ordinary dense Array`);
6119    } finally {
6120      ancestors.delete(value);
6121    }
6122  }
6123  const map = splitGenericPair(type, "Map");
6124  if (map !== null && map[0] === "String") {
6125    let keys;
6126    try {
6127      if (value === null || typeof value !== "object" || Array.isArray(value)) return invalid(`${path} must be ${type}`);
6128      const prototype = Object.getPrototypeOf(value);
6129      if (prototype !== Object.prototype && prototype !== null) return invalid(`${path} must be ${type}`);
6130      keys = Reflect.ownKeys(value);
6131    } catch { return invalid(`${path} must be ${type}`); }
6132    if (keys.some((key) => typeof key !== "string")) return invalid(`${path} must contain String keys`);
6133    const trusted = Object.create(null);
6134    for (const key of keys.sort()) {
6135      if (key === "__proto__" || key === "constructor" || key === "prototype") return invalid(`${path}.${key} must use a safe String map key`);
6136      let descriptor;
6137      try { descriptor = Object.getOwnPropertyDescriptor(value, key); } catch {}
6138      if (descriptor === undefined || !("value" in descriptor) || !descriptor.enumerable) return invalid(`${path}.${key} must be ordinary data`);
6139      const result = validateType(map[1], descriptor.value, `${path}.${key}`, typeId, external, ancestors);
6140      if (result.issue) return result;
6141      trusted[key] = result.value;
6142    }
6143    return valid(Object.freeze(trusted));
6144  }
6145  const resultType = splitGenericPair(type, "Result");
6146  if (resultType !== null) {
6147    let keys, tag, payload;
6148    try {
6149      if (value === null || typeof value !== "object" || Array.isArray(value)) return invalid(`${path} must be ${type}`);
6150      const prototype = Object.getPrototypeOf(value);
6151      if (prototype !== Object.prototype && prototype !== null) return invalid(`${path} must be ${type}`);
6152      keys = Reflect.ownKeys(value);
6153      const tagDescriptor = Object.getOwnPropertyDescriptor(value, "tag");
6154      const valueDescriptor = Object.getOwnPropertyDescriptor(value, "value");
6155      if (!tagDescriptor || !("value" in tagDescriptor) || !tagDescriptor.enumerable || !valueDescriptor || !("value" in valueDescriptor) || !valueDescriptor.enumerable) return invalid(`${path} must be an Ok(value) or Err(error) result`);
6156      tag = tagDescriptor.value;
6157      payload = valueDescriptor.value;
6158    } catch { return invalid(`${path} must be ${type}`); }
6159    if (!keys.every((key) => key === "tag" || key === "value") || (tag !== "Ok" && tag !== "Err")) return invalid(`${path} must be an Ok(value) or Err(error) result`);
6160    const inner = tag === "Ok" ? resultType[0] : resultType[1];
6161    const checked = validateType(inner, payload, `${path}.${tag}`, typeId, external, ancestors);
6162    return checked.issue ? checked : valid(Object.freeze({ tag, value: checked.value }));
6163  }
6164  const validatorId = typeId !== null && typeof typeId === "object" ? typeId[type] ?? null : typeId;
6165  const validator = validatorId === null ? null : typeValidators[validatorId];
6166  if (typeof validator === "function") {
6167    try { return valid(validator(value, external)); }
6168    catch (cause) {
6169      return invalid(`${path} must satisfy ${type}`, typeof cause?.toJSON === "function" ? cause.toJSON() : null);
6170    }
6171  }
6172  return invalid(`${path} uses unsupported boundary type ${type}`);
6173}
6174
6175const ACTION_BODY_MAX_BYTES = 1_048_576;
6176
6177// The action body is read off the stream with a hard byte ceiling, never
6178// buffered whole and measured afterwards. `Content-Length` is a claim the
6179// caller makes: it can be absent (a chunked request) or a lie, so it is only
6180// ever an early refusal and never the enforcement. Reading stops the moment
6181// one byte past the cap arrives, and the reader is cancelled, so a request
6182// that promises 10 bytes and sends 96 MiB costs the cap, not the body.
6183async function readBoundedActionBody(request) {
6184  if (request.body === null) return { text: "" };
6185  const reader = request.body.getReader();
6186  const chunks = [];
6187  let total = 0;
6188  try {
6189    while (true) {
6190      const chunk = await reader.read();
6191      if (chunk.done) break;
6192      if (!(chunk.value instanceof Uint8Array)) return { invalid: true };
6193      total += chunk.value.byteLength;
6194      if (total > ACTION_BODY_MAX_BYTES) return { tooLarge: true };
6195      chunks.push(chunk.value);
6196    }
6197  }
6198  catch { return { invalid: true }; }
6199  // Never awaited: when the caller cloned the request the body is a `tee`
6200  // branch, and a branch's `cancel()` does not settle until every branch has
6201  // let go. Awaiting it here deadlocks the refusal it exists to deliver.
6202  finally { try { void reader.cancel().catch(() => {}); } catch {} }
6203  const bytes = new Uint8Array(total);
6204  let offset = 0;
6205  for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
6206  return { text: new TextDecoder().decode(bytes) };
6207}
6208
6209async function decodeArguments(request, schema) {
6210  const contentType = request.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase();
6211  if (contentType !== "application/json") return { error: failure(415, "BOUNDARY_CONTENT_TYPE", "Noxid action requests require application/json", schema.id) };
6212  const declaredLength = Number(request.headers.get("content-length") ?? 0);
6213  if (Number.isFinite(declaredLength) && declaredLength > ACTION_BODY_MAX_BYTES) return { error: failure(413, "BOUNDARY_BODY_TOO_LARGE", "Noxid action request exceeds 1 MiB", schema.id) };
6214  const bounded = await readBoundedActionBody(request);
6215  if (bounded.tooLarge === true) return { error: failure(413, "BOUNDARY_BODY_TOO_LARGE", "Noxid action request exceeds 1 MiB", schema.id) };
6216  if (bounded.invalid === true) return { error: failure(400, "BOUNDARY_BODY_INVALID", "Request body is not valid JSON", schema.id) };
6217  let body;
6218  try { body = JSON.parse(bounded.text); }
6219  catch { return { error: failure(400, "BOUNDARY_BODY_INVALID", "Request body is not valid JSON", schema.id) }; }
6220  if (!body || typeof body !== "object" || Array.isArray(body) || !body.arguments || typeof body.arguments !== "object" || Array.isArray(body.arguments)) {
6221    return { error: failure(400, "BOUNDARY_BODY_INVALID", "Request body must contain an arguments object", schema.id) };
6222  }
6223  const expected = new Set(schema.parameters.map((parameter) => parameter.name));
6224  const unexpected = Object.keys(body.arguments).filter((name) => !expected.has(name));
6225  if (unexpected.length) return { error: failure(400, "BOUNDARY_ARGUMENT_UNKNOWN", `Unknown action argument: ${unexpected[0]}`, schema.id, { argument: unexpected[0] }) };
6226  const values = Object.create(null);
6227  for (const parameter of schema.parameters) {
6228    if (!Object.hasOwn(body.arguments, parameter.name)) return { error: failure(400, "BOUNDARY_ARGUMENT_MISSING", `Missing action argument: ${parameter.name}`, schema.id, { argument: parameter.name }) };
6229    const result = validateType(parameter.type, body.arguments[parameter.name], `arguments.${parameter.name}`, parameter.typeId);
6230    if (result.issue) return { error: failure(422, result.issue.includes("unsupported boundary type") ? "BOUNDARY_SCHEMA_UNSUPPORTED" : "BOUNDARY_ARGUMENT_TYPE", result.issue, schema.id, { argument: parameter.name, expected: parameter.type, validation: result.details }) };
6231    values[parameter.name] = result.value;
6232  }
6233  return { arguments: Object.freeze(values) };
6234}
6235
6236function resolveRouteScope(request, schema) {
6237  const routeId = request.headers.get("x-noxid-route-id");
6238  if (typeof routeId !== "string" || routeId.length === 0) {
6239    return { error: failure(400, "BOUNDARY_ROUTE_REQUIRED", "Noxid action requests require a compiled route identity", schema.id) };
6240  }
6241  const scope = schema.routeScopes.find((candidate) => candidate.id === routeId);
6242  if (!scope) {
6243    return { error: failure(403, "BOUNDARY_ROUTE_DENIED", "Action is not exposed by the requested route", schema.id, { route: routeId }) };
6244  }
6245  return { scope };
6246}
6247
6248function withMiddlewareHeaders(response, pairs) {
6249  if (!pairs || pairs.length === 0) return response;
6250  const headers = new Headers(response.headers);
6251  for (const [name, value] of pairs) headers.append(name, value);
6252  return __noxidTraceCopyFailure(response, new Response(response.body, { status: response.status, headers }));
6253}
6254async function applyMiddleware(request, schema, scope, environment, executionContext) {
6255  const headers = [];
6256  const inherited = inheritedMiddlewareState(scope, executionContext);
6257  if (inherited) return { failure: null, headers, context: inherited.context };
6258  const context = Object.create(null);
6259  const chain = [
6260    ...globalMiddleware.map((name) => Object.freeze({ name, handle: globalMiddlewareHandlers[name] })),
6261    ...scope.middleware.map((name) => Object.freeze({ name, handle: middlewareHandlers[name] })),
6262  ];
6263  const names = chain.map((entry) => entry.name);
6264  const route = Object.freeze({ ...scope, middleware: Object.freeze(names) });
6265  __noxidTraceRoute(request, route.pattern);
6266  for (const { name, handle } of chain) {
6267    __noxidTraceSemantic(request, "middleware", name.startsWith("middleware:") ? name : `middleware:${name}`);
6268    if (typeof handle !== "function") {
6269      return { failure: failure(500, "BOUNDARY_MIDDLEWARE_MISSING", "Required server middleware is not available", schema.id, { middleware: name, route: scope.id }), headers };
6270    }
6271    let result;
6272    try {
6273      result = await handle(__noxidDataContext({
6274        middleware: name,
6275        semanticId: schema.id,
6276        traceId: __noxidTraceIdForRequest(request),
6277        boundary: schema.boundary,
6278        target: schema.target,
6279        route,
6280        request,
6281        url: new URL(request.url),
6282        host: hostModule,
6283        environment,
6284        executionContext,
6285        context: Object.freeze({ ...context }),
6286        middlewareContext: Object.freeze({ ...context }),
6287      }, __noxidPrincipal(context, environment, __noxidAgentForRequest(request))));
6288    } catch {
6289      return { failure: failure(500, "BOUNDARY_MIDDLEWARE_FAILED", "Server middleware failed", schema.id, { middleware: name, route: scope.id }), headers };
6290    }
6291    const normalized = __noxidNormalizeMiddlewareResult(result);
6292    if (normalized.issue === "headers") {
6293      return { failure: failure(500, "BOUNDARY_MIDDLEWARE_HEADERS", "Server middleware returned a disallowed response header", schema.id, { middleware: name, route: scope.id, header: normalized.detail }), headers: [] };
6294    }
6295    if (normalized.issue === "response") {
6296      return { failure: failure(500, "BOUNDARY_MIDDLEWARE_RESPONSE", "Server middleware returned an invalid direct response", schema.id, { middleware: name, route: scope.id, validation: normalized.detail }), headers };
6297    }
6298    if (normalized.issue === "context") {
6299      return { failure: failure(500, "BOUNDARY_MIDDLEWARE_CONTEXT", "Server middleware returned invalid context", schema.id, { middleware: name, route: scope.id, validation: normalized.detail }), headers };
6300    }
6301    headers.push(...normalized.headers);
6302    if (normalized.respond !== null) {
6303      return { failure: failure(409, "BOUNDARY_MIDDLEWARE_RESPONSE", "Server action middleware cannot produce a direct response; respond belongs to SSR routes", schema.id, { middleware: name, route: scope.id }), headers };
6304    }
6305    if (normalized.redirect !== null) {
6306      return { failure: failure(409, "BOUNDARY_MIDDLEWARE_REDIRECT", "Server action middleware requested navigation", schema.id, { middleware: name, route: scope.id, redirect: normalized.redirect }), headers };
6307    }
6308    if (!normalized.allow) {
6309      return { failure: failure(403, "BOUNDARY_MIDDLEWARE_DENIED", "Server middleware denied the action", schema.id, { middleware: name, route: scope.id }), headers };
6310    }
6311    if (normalized.context !== null) Object.assign(context, normalized.context);
6312  }
6313  return { failure: null, headers, context: Object.freeze({ ...context }) };
6314}
6315
6316async function authorizeAction(request, schema, scope, environment, executionContext) {
6317  if (schema.capabilities.length === 0) return null;
6318  if (typeof authorize !== "function") {
6319    return failure(500, "BOUNDARY_AUTHORIZER_MISSING", "Action authorization is not configured", schema.id);
6320  }
6321  for (const capability of schema.capabilities) {
6322    let allowed = false;
6323    try {
6324      allowed = await authorize(Object.freeze({
6325        capability,
6326        semanticId: schema.id,
6327        traceId: __noxidTraceIdForRequest(request),
6328        target: schema.target,
6329        route: scope,
6330        request,
6331        environment,
6332        executionContext,
6333      })) === true;
6334    } catch {}
6335    if (!allowed) {
6336      return failure(403, "BOUNDARY_CAPABILITY_DENIED", "Action capability was denied", schema.id, { capability });
6337    }
6338  }
6339  return null;
6340}
6341
6342async function handleInvalidation(request, environment, executionContext) {
6343  if (request.method !== "POST") return failure(405, "CACHE_INVALIDATION_METHOD", "Cache invalidation requires POST", null, null, { allow: "POST" });
6344  const contentType = request.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase();
6345  if (contentType !== "application/json") return failure(415, "CACHE_INVALIDATION_CONTENT_TYPE", "Cache invalidation body requires application/json", "cache-invalidation:on-demand");
6346  const declaredLength = Number(request.headers.get("content-length") ?? 0);
6347  if (Number.isFinite(declaredLength) && declaredLength > 1_048_576) return failure(413, "CACHE_INVALIDATION_BODY_TOO_LARGE", "Cache invalidation body exceeds 1 MiB", "cache-invalidation:on-demand");
6348  const chunks = [];
6349  let total = 0;
6350  if (request.body !== null) {
6351    let reader;
6352    try {
6353      reader = request.body.getReader();
6354      while (true) {
6355        const { done, value } = await reader.read();
6356        if (done) break;
6357        if (!(value instanceof Uint8Array)) return failure(400, "CACHE_INVALIDATION_BODY_INVALID", "Cache invalidation body stream did not yield bytes", "cache-invalidation:on-demand");
6358        total += value.byteLength;
6359        if (total > 1_048_576) {
6360          await reader.cancel("cache invalidation body too large").catch(() => {});
6361          return failure(413, "CACHE_INVALIDATION_BODY_TOO_LARGE", "Cache invalidation body exceeds 1 MiB", "cache-invalidation:on-demand");
6362        }
6363        chunks.push(value);
6364      }
6365    } catch {
6366      return failure(400, "CACHE_INVALIDATION_BODY_INVALID", "Cache invalidation body stream could not be read", "cache-invalidation:on-demand");
6367    } finally {
6368      try { reader?.releaseLock(); } catch {}
6369    }
6370  }
6371  const bytes = new Uint8Array(total);
6372  let offset = 0;
6373  for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
6374  let text;
6375  try { text = new TextDecoder("utf-8", { fatal: true }).decode(bytes); }
6376  catch { return failure(400, "CACHE_INVALIDATION_BODY_INVALID", "Cache invalidation body is not valid UTF-8", "cache-invalidation:on-demand"); }
6377  let body;
6378  try { body = JSON.parse(text); }
6379  catch { return failure(400, "CACHE_INVALIDATION_BODY_INVALID", "Cache invalidation body must be JSON", "cache-invalidation:on-demand"); }
6380  let bodyKeys;
6381  let tagsDescriptor;
6382  try {
6383    if (body === null || typeof body !== "object" || Array.isArray(body) || Object.getPrototypeOf(body) !== Object.prototype) {
6384      return failure(400, "CACHE_INVALIDATION_BODY_INVALID", "Cache invalidation body must be an ordinary JSON object containing only tags", "cache-invalidation:on-demand");
6385    }
6386    bodyKeys = Reflect.ownKeys(body);
6387    tagsDescriptor = Object.getOwnPropertyDescriptor(body, "tags");
6388  } catch {
6389    return failure(400, "CACHE_INVALIDATION_BODY_INVALID", "Cache invalidation body shape could not be inspected safely", "cache-invalidation:on-demand");
6390  }
6391  if (bodyKeys.length !== 1 || bodyKeys[0] !== "tags" || tagsDescriptor === undefined || !("value" in tagsDescriptor) || tagsDescriptor.enumerable !== true || tagsDescriptor.configurable !== true || tagsDescriptor.writable !== true) {
6392    return failure(400, "CACHE_INVALIDATION_BODY_INVALID", "Cache invalidation body must declare only an ordinary own tags field", "cache-invalidation:on-demand");
6393  }
6394  const rawTags = tagsDescriptor.value;
6395  if (!Array.isArray(rawTags) || rawTags.length === 0 || rawTags.length > 32) return failure(400, "CACHE_INVALIDATION_TAGS_INVALID", "Cache invalidation requires 1 to 32 tags");
6396  const tags = [...new Set(rawTags)];
6397  if (tags.some((tag) => typeof tag !== "string" || tag.length === 0 || tag.length > 128 || !/^[a-zA-Z0-9_.:@-]+$/.test(tag))) return failure(400, "CACHE_INVALIDATION_TAGS_INVALID", "Cache invalidation tags contain unsafe values");
6398  if (typeof authorize !== "function") return failure(500, "CACHE_INVALIDATION_AUTHORIZER_MISSING", "Cache invalidation authorization is not configured");
6399  let allowed = false;
6400  try {
6401    allowed = await authorize(Object.freeze({ capability: "cache.invalidate", semanticId: "cache-invalidation:on-demand", traceId: __noxidTraceIdForRequest(request), target: "server", route: null, request, environment, executionContext })) === true;
6402  } catch {}
6403  if (!allowed) return failure(403, "CACHE_INVALIDATION_DENIED", "Cache invalidation capability was denied", "cache-invalidation:on-demand");
6404  if (typeof invalidateCache !== "function") return failure(501, "CACHE_INVALIDATION_UNAVAILABLE", "The deployment host has no cache invalidation adapter", "cache-invalidation:on-demand");
6405  try {
6406    const result = await invalidateCache(Object.freeze(tags), Object.freeze({ request, environment, executionContext, semanticId: "cache-invalidation:on-demand", traceId: __noxidTraceIdForRequest(request) }));
6407    return json(200, { ok: true, tags, result: result ?? null });
6408  } catch { return failure(500, "CACHE_INVALIDATION_FAILED", "The deployment cache invalidation adapter failed", "cache-invalidation:on-demand"); }
6409}
6410
6411function taskSchemaByName(name) {
6412  return taskSchemas.find((schema) => schema.name === name) ?? null;
6413}
6414
6415async function invokeTask(name, context = Object.create(null)) {
6416  const schema = taskSchemaByName(name);
6417  if (schema === null) throw Object.assign(new Error("Unknown scheduled task " + name), { code: "TASK_NOT_FOUND", semanticId: null });
6418  const implementation = compiledTasks[schema.id] ?? hostTasks[schema.id];
6419  if (typeof implementation !== "function") {
6420    throw Object.assign(new Error("No host implementation is registered for " + schema.id), {
6421      code: "TASK_IMPLEMENTATION_MISSING",
6422      semanticId: schema.id,
6423    });
6424  }
6425  const taskSpan = __noxidTraceBeginSemantic(context.request ?? null);
6426  const traceId = taskSpan?.trace?.id ?? null;
6427  try {
6428    return await implementation(
6429      Object.freeze(Object.create(null)),
6430      __noxidDataContext({ ...context, semanticId: schema.id, traceId, task: schema.name, schedule: schema.schedule }, __NOXID_SYSTEM_PRINCIPAL),
6431    );
6432  } finally {
6433    __noxidTraceFinishSemantic(taskSpan, "task", schema.id);
6434  }
6435}
6436
6437async function handleTaskRequest(request, url, environment, executionContext) {
6438  if (!url.pathname.startsWith(taskPrefix)) return null;
6439  if (request.method !== "POST") return failure(405, "TASK_METHOD", "Scheduled task triggers require POST", null, null, { allow: "POST" });
6440  let name;
6441  try { name = decodeURIComponent(url.pathname.slice(taskPrefix.length)); }
6442  catch { return failure(400, "TASK_NAME_INVALID", "Task name is not valid URL encoding"); }
6443  const schema = taskSchemaByName(name);
6444  if (schema === null) return failure(404, "TASK_NOT_FOUND", "Unknown scheduled task " + name);
6445  if (typeof authorize !== "function") return failure(500, "TASK_AUTHORIZER_MISSING", "Scheduled task authorization is not configured", schema.id, { capability: "tasks.run" });
6446  let allowed = false;
6447  try {
6448    allowed = await authorize(Object.freeze({
6449      capability: "tasks.run",
6450      semanticId: schema.id,
6451      traceId: __noxidTraceIdForRequest(request),
6452      target: "server",
6453      route: null,
6454      request,
6455      environment,
6456      executionContext,
6457    })) === true;
6458  } catch {}
6459  if (!allowed) return failure(403, "TASK_CAPABILITY_DENIED", "Scheduled task capability was denied", schema.id, { capability: "tasks.run" });
6460  try {
6461    const value = await invokeTask(name, { request, environment, executionContext });
6462    return json(200, { ok: true, value: value === undefined ? null : value });
6463  } catch (cause) {
6464    if (cause?.code === "TASK_IMPLEMENTATION_MISSING") {
6465      return failure(501, cause.code, "Scheduled task host implementation is missing", schema.id);
6466    }
6467    return failure(500, "TASK_EXECUTION_FAILED", "Scheduled task execution failed", schema.id);
6468  }
6469}
6470
6471function cronPartMatches(part, value, minimum, maximum) {
6472  const [base, rawStep] = part.split("/");
6473  const step = rawStep === undefined ? 1 : Number(rawStep);
6474  let start = minimum;
6475  let end = maximum;
6476  if (base !== "*") {
6477    if (base.includes("-")) [start, end] = base.split("-").map(Number);
6478    else {
6479      start = Number(base);
6480      end = rawStep === undefined ? start : maximum;
6481    }
6482  }
6483  return value >= start && value <= end && (value - start) % step === 0;
6484}
6485
6486function cronFieldMatches(field, value, minimum, maximum) {
6487  return field.split(",").some((part) => cronPartMatches(part, value, minimum, maximum));
6488}
6489
6490function cronMatches(schedule, date) {
6491  const fields = schedule.trim().split(/\s+/);
6492  const minute = cronFieldMatches(fields[0], date.getUTCMinutes(), 0, 59);
6493  const hour = cronFieldMatches(fields[1], date.getUTCHours(), 0, 23);
6494  const month = cronFieldMatches(fields[3], date.getUTCMonth() + 1, 1, 12);
6495  const dayOfMonth = cronFieldMatches(fields[2], date.getUTCDate(), 1, 31);
6496  const weekday = date.getUTCDay();
6497  const dayOfWeek = cronFieldMatches(fields[4], weekday, 0, 7)
6498    || (weekday === 0 && cronFieldMatches(fields[4], 7, 0, 7));
6499  const anyDayOfMonth = fields[2] === "*";
6500  const anyDayOfWeek = fields[4] === "*";
6501  const day = anyDayOfMonth || anyDayOfWeek
6502    ? dayOfMonth && dayOfWeek
6503    : dayOfMonth || dayOfWeek;
6504  return minute && hour && month && day;
6505}
6506
6507export function startTaskScheduler(environment = Object.create(null), executionContext = Object.create(null), options = Object.create(null)) {
6508  environment = __noxidConfiguredServerEnvironment(environment);
6509  const setTimer = options.setTimeout ?? globalThis.setTimeout;
6510  const clearTimer = options.clearTimeout ?? globalThis.clearTimeout;
6511  const now = options.now ?? (() => new Date());
6512  const origin = options.origin ?? "http://noxid.local";
6513  const report = options.onError ?? ((task, error) => console.error("Noxid scheduled task " + task + " failed", error));
6514  const running = new Map();
6515  let timer = null;
6516  let stopped = false;
6517  let stopJoin = null;
6518  const runDue = (instant) => {
6519    for (const task of taskSchedules) {
6520      if (!cronMatches(task.schedule, instant) || running.has(task.name)) continue;
6521      const request = new Request(origin + taskPrefix + encodeURIComponent(task.name), { method: "POST" });
6522      const execution = Promise.resolve(fetch(request, environment, executionContext))
6523        .then(async (response) => {
6524          if (!response.ok) {
6525            let detail = null;
6526            try { detail = await response.json(); } catch {}
6527            throw Object.assign(new Error("task trigger returned " + response.status), { response: detail });
6528          }
6529        })
6530        .catch((error) => report(task.name, error))
6531        .finally(() => running.delete(task.name));
6532      running.set(task.name, execution);
6533    }
6534  };
6535  const scheduleNext = () => {
6536    if (stopped || taskSchedules.length === 0) return;
6537    const instant = now();
6538    const nextMinute = new Date(Math.floor(instant.getTime() / 60_000) * 60_000 + 60_000);
6539    timer = setTimer(() => {
6540      if (stopped) return;
6541      runDue(nextMinute);
6542      scheduleNext();
6543    }, Math.max(0, nextMinute.getTime() - instant.getTime()));
6544  };
6545  scheduleNext();
6546  return Object.freeze({
6547    stop() {
6548      if (stopJoin !== null) return stopJoin;
6549      stopped = true;
6550      if (timer !== null) clearTimer(timer);
6551      timer = null;
6552      stopJoin = Promise.allSettled([...running.values()]);
6553      return stopJoin;
6554    },
6555  });
6556}
6557
6558/* noxid-server:live-resource-transport-runtime */
6559
6560export async function fetchEndpoint(request, environment = Object.create(null), executionContext = Object.create(null)) {
6561  environment = __noxidConfiguredServerEnvironment(environment);
6562  return withNoxidRequestTrace(request, async () => {
6563    /* noxid-server:startup */
6564    const url = new URL(request.url);
6565    /* noxid-server:live-resource-request */
6566    const queueDrainResponse = await handleQueueDrainRequest(request, url, environment, executionContext);
6567    if (queueDrainResponse !== null) return queueDrainResponse;
6568    const agentSurfaceResponse = await handleAgentSurfaceRequest(request, url, environment, executionContext);
6569    if (agentSurfaceResponse !== null) return agentSurfaceResponse;
6570    return handleEndpointRequest(request, url, environment, executionContext);
6571  });
6572}
6573
6574export async function fetch(request, environment = Object.create(null), executionContext = Object.create(null)) {
6575  environment = __noxidConfiguredServerEnvironment(environment);
6576  return withNoxidRequestTrace(request, async () => {
6577  const url = new URL(request.url);
6578  const endpointResponse = await fetchEndpoint(request, environment, executionContext);
6579  if (endpointResponse !== null) return endpointResponse;
6580  const taskResponse = await handleTaskRequest(request, url, environment, executionContext);
6581  if (taskResponse !== null) return taskResponse;
6582  if (url.pathname === invalidationEndpoint) return handleInvalidation(request, environment, executionContext);
6583  if (!url.pathname.startsWith(endpointPrefix)) return failure(404, "BOUNDARY_NOT_FOUND", "No Noxid action exists at this path");
6584  if (request.method !== "POST") return failure(405, "BOUNDARY_METHOD", "Noxid actions require POST", null, null, { allow: "POST" });
6585  let actionId;
6586  try { actionId = decodeURIComponent(url.pathname.slice(endpointPrefix.length)); }
6587  catch { return failure(400, "BOUNDARY_ID_INVALID", "Action identifier is not valid URL encoding"); }
6588  const schema = schemas[actionId];
6589  if (!schema) return failure(404, "BOUNDARY_NOT_FOUND", `Unknown Noxid action ${actionId}`, actionId);
6590  const implementation = compiledActions[actionId] ?? hostActions[actionId];
6591  if (typeof implementation !== "function") return failure(501, "BOUNDARY_IMPLEMENTATION_MISSING", `No host implementation is registered for ${actionId}`, actionId);
6592  const resolvedRoute = resolveRouteScope(request, schema);
6593  if (resolvedRoute.error) return resolvedRoute.error;
6594  const scope = resolvedRoute.scope;
6595  const middlewareOutcome = await applyMiddleware(request, schema, scope, environment, executionContext);
6596  const middlewareHeaders = middlewareOutcome.headers;
6597  if (middlewareOutcome.failure) return withMiddlewareHeaders(middlewareOutcome.failure, middlewareHeaders);
6598  const authorizationFailure = await authorizeAction(request, schema, scope, environment, executionContext);
6599  if (authorizationFailure) return withMiddlewareHeaders(authorizationFailure, middlewareHeaders);
6600  const decoded = await decodeArguments(request, schema);
6601  if (decoded.error) return withMiddlewareHeaders(decoded.error, middlewareHeaders);
6602  const actionSpan = __noxidTraceBeginSemantic(request);
6603  try {
6604    const middlewareContext = middlewareOutcome.context ?? EMPTY_MIDDLEWARE_CONTEXT;
6605    const actionPrincipal = __noxidPrincipal(middlewareContext, environment, __noxidAgentForRequest(request));
6606    const value = await implementation(decoded.arguments, __noxidDataContext({ request, environment, executionContext, semanticId: schema.id, traceId: __noxidTraceIdForRequest(request), target: schema.target, route: scope, capabilities: schema.capabilities, middlewareContext }, actionPrincipal));
6607    const result = validateType(schema.result.type, value, "result", schema.result.typeId);
6608    if (result.issue) return withMiddlewareHeaders(failure(500, "BOUNDARY_RESULT_TYPE", "Action returned a value that violates its declared result type", schema.result.id, { expected: schema.result.type, validation: result.details }), middlewareHeaders);
6609    await __noxidPublishLiveInvalidations(schema.invalidates, actionPrincipal);
6610    return withMiddlewareHeaders(json(200, { ok: true, value: result.value === undefined ? null : result.value }), middlewareHeaders);
6611  } catch (cause) {
6612    const code = typeof cause?.code === "string" ? cause.code : "BOUNDARY_EXECUTION_FAILED";
6613    const message = cause?.expose === true && typeof cause?.message === "string" ? cause.message : "Action execution failed";
6614    return withMiddlewareHeaders(failure(500, code, message, schema.id, null, {}, false), middlewareHeaders);
6615  } finally {
6616    __noxidTraceFinishSemantic(actionSpan, "action", schema.id, { route: scope?.pattern });
6617  }
6618  });
6619}
6620
6621globalThis.__NOXID_FETCH_HANDLER__ = fetch;
6622export default Object.freeze({ fetch });
6623"#;
6624
6625#[cfg(test)]
6626mod tests {
6627    use super::*;
6628    use noxid_execution_ir::{
6629        EndpointExecutionBoundary, EndpointExecutionInput, ExecutionBoundary, ExecutionParameter,
6630        ExecutionResult, ExecutionRouteScope, LiveResourceExecutionContract,
6631        PresenceExecutionContract, PresenceExecutionField, QueueExecutionField,
6632    };
6633    use noxid_ir::{
6634        EndpointCacheMode, EndpointCachePolicy, EndpointInputSection, EndpointKind,
6635        EndpointLimitPolicy, EndpointLimitScope, EndpointLimitWindow, EndpointMethod,
6636        ExecutionTarget, SemanticBinaryOp, SemanticExpr, SemanticExprKind, SemanticId,
6637    };
6638    use noxid_source::Span;
6639    use noxid_types::Type;
6640    use std::process::Command;
6641    use std::time::{SystemTime, UNIX_EPOCH};
6642
6643    // These tests all pass bodies the emitter can lower; a failure here is a
6644    // regression in emission itself, so unwrap at the seam and keep the
6645    // assertions about the generated handler rather than about the Result.
6646    fn generate(
6647        program: &ExecutionProgram,
6648        host_import: &str,
6649        validator_import: &str,
6650        middleware_import: &str,
6651        base_path: &str,
6652        server_secrets: &[String],
6653    ) -> ServerJavaScriptOutput {
6654        super::generate(
6655            program,
6656            host_import,
6657            validator_import,
6658            middleware_import,
6659            base_path,
6660            server_secrets,
6661        )
6662        .expect("every fixture body has a compiler-owned server lowering")
6663    }
6664
6665    #[allow(clippy::too_many_arguments)]
6666    fn generate_with_agent_surfaces(
6667        program: &ExecutionProgram,
6668        host_import: &str,
6669        validator_import: &str,
6670        middleware_import: &str,
6671        startup_import: Option<&str>,
6672        base_path: &str,
6673        server_secrets: &[String],
6674        agent_surfaces: AgentSurfaceOptions<'_>,
6675    ) -> ServerJavaScriptOutput {
6676        super::generate_with_agent_surfaces(
6677            program,
6678            host_import,
6679            validator_import,
6680            middleware_import,
6681            startup_import,
6682            base_path,
6683            server_secrets,
6684            agent_surfaces,
6685        )
6686        .expect("every fixture body has a compiler-owned server lowering")
6687    }
6688
6689    #[allow(clippy::too_many_arguments)]
6690    fn generate_with_runtime_options(
6691        program: &ExecutionProgram,
6692        host_import: &str,
6693        validator_import: &str,
6694        middleware_import: &str,
6695        startup_import: Option<&str>,
6696        base_path: &str,
6697        server_secrets: &[String],
6698        agent_surfaces: AgentSurfaceOptions<'_>,
6699        tracing_mode: ServerTracingMode,
6700        runtime_options: ServerRuntimeOptions,
6701    ) -> ServerJavaScriptOutput {
6702        super::generate_with_runtime_options(
6703            program,
6704            host_import,
6705            validator_import,
6706            middleware_import,
6707            startup_import,
6708            base_path,
6709            server_secrets,
6710            agent_surfaces,
6711            tracing_mode,
6712            runtime_options,
6713        )
6714        .expect("every fixture body has a compiler-owned server lowering")
6715    }
6716
6717    // WO-45 phase 2: a `distinct` type is erased at every boundary, so the
6718    // wire representation is the base value. The client and SSR emitters
6719    // lower construction and `.base()` to the identity; the server emitter
6720    // must produce the byte-identical lowering, or the same expression would
6721    // mean one thing in a client action and another in a compiler-owned
6722    // remote body. The sibling tests in codegen-js and codegen-ssr-js assert
6723    // these same two strings.
6724    #[test]
6725    fn distinct_construct_and_unwrap_erase_to_the_same_plain_server_value() {
6726        let literal = SemanticExpr {
6727            kind: SemanticExprKind::String("u-1".into()),
6728            ty: Type::String,
6729            span: Span::new(0, 1),
6730        };
6731        let constructed = SemanticExpr {
6732            kind: SemanticExprKind::FunctionCall {
6733                function: SemanticId::distinct_construct("UserId"),
6734                name: "UserId".into(),
6735                arguments: vec![literal],
6736            },
6737            ty: Type::Named("UserId".into()),
6738            span: Span::new(0, 1),
6739        };
6740        let unwrapped = SemanticExpr {
6741            kind: SemanticExprKind::FunctionCall {
6742                function: SemanticId::distinct_unwrap("UserId"),
6743                name: "UserId.base".into(),
6744                arguments: vec![constructed.clone()],
6745            },
6746            ty: Type::String,
6747            span: Span::new(0, 1),
6748        };
6749        assert_eq!(
6750            compiler_body_javascript(&constructed).expect("construction lowers"),
6751            "\"u-1\""
6752        );
6753        assert_eq!(
6754            compiler_body_javascript(&unwrapped).expect("unwrap lowers"),
6755            "\"u-1\""
6756        );
6757    }
6758
6759    fn server_builtin(name: &str, arguments: Vec<SemanticExpr>, ty: Type) -> SemanticExpr {
6760        SemanticExpr {
6761            kind: SemanticExprKind::FunctionCall {
6762                function: SemanticId::function(&format!("@builtin.{name}")),
6763                name: name.into(),
6764                arguments,
6765            },
6766            ty,
6767            span: Span::new(0, 1),
6768        }
6769    }
6770
6771    fn endpoint_boundary(
6772        name: &str,
6773        method: EndpointMethod,
6774        path: &str,
6775        inputs: Vec<EndpointExecutionInput>,
6776        result: &str,
6777    ) -> EndpointExecutionBoundary {
6778        EndpointExecutionBoundary {
6779            id: SemanticId::endpoint(name),
6780            kind: EndpointKind::RequestResponse,
6781            host_key: Some(SemanticId::endpoint(name)),
6782            name: name.into(),
6783            version: 1,
6784            description: None,
6785            method: Some(method),
6786            path: Some(path.into()),
6787            inputs,
6788            result: ExecutionResult {
6789                id: SemanticId::endpoint_result(name),
6790                ty: result.into(),
6791                type_id: None,
6792            },
6793            statements: vec![],
6794            capabilities: vec![],
6795            timeout_ms: 30_000,
6796            limit: None,
6797            cache: None,
6798            idempotent: false,
6799            middleware: vec![],
6800            invalidates: vec![],
6801            span: Span::new(0, 1),
6802        }
6803    }
6804
6805    fn endpoint_input(
6806        endpoint: &str,
6807        section: EndpointInputSection,
6808        name: &str,
6809        ty: &str,
6810    ) -> EndpointExecutionInput {
6811        EndpointExecutionInput {
6812            id: SemanticId::endpoint_field(endpoint, section, name),
6813            section,
6814            name: name.into(),
6815            ty: ty.into(),
6816            type_id: None,
6817            file: None,
6818        }
6819    }
6820
6821    fn presence_program() -> ExecutionProgram {
6822        ExecutionProgram {
6823            presences: vec![PresenceExecutionContract {
6824                id: SemanticId::presence("Cursor"),
6825                component: SemanticId::component("Cursor"),
6826                component_name: "Cursor".into(),
6827                stream: SemanticId::presence_stream("Cursor"),
6828                record_type: SemanticId::type_definition("Cursor", "CursorPresenceRecord"),
6829                member_type: SemanticId::type_definition("Cursor", "CursorPresenceMember"),
6830                snapshot_type: SemanticId::type_definition("Cursor", "CursorPresenceSnapshot"),
6831                fields: vec![PresenceExecutionField {
6832                    id: SemanticId::presence_field("Cursor", "name"),
6833                    name: "name".into(),
6834                    ty: "String".into(),
6835                    type_id: None,
6836                }],
6837                capabilities: vec![],
6838                route_scopes: vec![
6839                    ExecutionRouteScope {
6840                        route: SemanticId::route("/room"),
6841                        pattern: "/room".into(),
6842                        parameters: vec![],
6843                        middleware: vec![],
6844                    },
6845                    ExecutionRouteScope {
6846                        route: SemanticId::route("/other"),
6847                        pattern: "/other".into(),
6848                        parameters: vec![],
6849                        middleware: vec![],
6850                    },
6851                ],
6852                ttl_ms: 5_000,
6853                heartbeat_ms: 1_000,
6854            }],
6855            ..ExecutionProgram::default()
6856        }
6857    }
6858
6859    #[test]
6860    fn generated_presence_handler_is_valid_and_uses_the_shared_live_surface() {
6861        let generated = generate_with_runtime_options(
6862            &presence_program(),
6863            "./host.mjs",
6864            "./validators.mjs",
6865            "./middleware.mjs",
6866            None,
6867            "/app",
6868            &[],
6869            AgentSurfaceOptions::default(),
6870            ServerTracingMode::Requests,
6871            ServerRuntimeOptions {
6872                db_pool: 10,
6873                pubsub: Some(PubSubRuntimeOptions {
6874                    driver: PubSubDriver::Memory,
6875                    coalescing_ms: 1,
6876                }),
6877                development_trace_capture: true,
6878                application_namespace: Some("codegen_test".into()),
6879                ..ServerRuntimeOptions::default()
6880            },
6881        );
6882        assert!(generated.handler.contains("/app/_noxid/live"));
6883        assert!(generated.handler.contains("/app/_noxid/presence"));
6884        assert!(
6885            generated
6886                .handler
6887                .contains("__noxidPubSubEvent(\"presence\"")
6888        );
6889        assert!(generated.handler.contains("developmentCapture = true"));
6890        let root = std::env::temp_dir().join(format!(
6891            "noxid-presence-syntax-{}-{}",
6892            std::process::id(),
6893            SystemTime::now()
6894                .duration_since(UNIX_EPOCH)
6895                .unwrap()
6896                .as_nanos()
6897        ));
6898        std::fs::create_dir_all(&root).unwrap();
6899        let handler = root.join("handler.mjs");
6900        std::fs::write(&handler, generated.handler).unwrap();
6901        let output = Command::new("node")
6902            .args(["--check", handler.to_str().unwrap()])
6903            .output()
6904            .expect("Node.js is required for generated presence syntax tests");
6905        let _ = std::fs::remove_dir_all(root);
6906        assert!(
6907            output.status.success(),
6908            "{}",
6909            String::from_utf8_lossy(&output.stderr)
6910        );
6911    }
6912
6913    #[test]
6914    fn emits_every_scalar_builtin_in_compiler_owned_remote_bodies() {
6915        let string = || SemanticExpr {
6916            kind: SemanticExprKind::Reference(SemanticId::action_parameter("C", "run", "name")),
6917            ty: Type::String,
6918            span: Span::new(0, 1),
6919        };
6920        let int = |value| SemanticExpr {
6921            kind: SemanticExprKind::Int(value),
6922            ty: Type::Int,
6923            span: Span::new(0, 1),
6924        };
6925        let float = |value| SemanticExpr {
6926            kind: SemanticExprKind::Float(value),
6927            ty: Type::Float,
6928            span: Span::new(0, 1),
6929        };
6930        let cases = [
6931            (
6932                server_builtin("len", vec![string()], Type::Int),
6933                "args[\"name\"].length",
6934            ),
6935            (
6936                server_builtin(
6937                    "contains",
6938                    vec![
6939                        string(),
6940                        SemanticExpr {
6941                            kind: SemanticExprKind::String("a".into()),
6942                            ty: Type::String,
6943                            span: Span::new(0, 1),
6944                        },
6945                    ],
6946                    Type::Boolean,
6947                ),
6948                "args[\"name\"].includes(\"a\")",
6949            ),
6950            (
6951                server_builtin(
6952                    "startsWith",
6953                    vec![
6954                        string(),
6955                        SemanticExpr {
6956                            kind: SemanticExprKind::String("A".into()),
6957                            ty: Type::String,
6958                            span: Span::new(0, 1),
6959                        },
6960                    ],
6961                    Type::Boolean,
6962                ),
6963                "args[\"name\"].startsWith(\"A\")",
6964            ),
6965            (
6966                server_builtin("trim", vec![string()], Type::String),
6967                "args[\"name\"].trim()",
6968            ),
6969            (
6970                server_builtin("lower", vec![string()], Type::String),
6971                "args[\"name\"].toLowerCase()",
6972            ),
6973            (
6974                server_builtin("upper", vec![string()], Type::String),
6975                "args[\"name\"].toUpperCase()",
6976            ),
6977            (
6978                server_builtin("min", vec![int(2), int(3)], Type::Int),
6979                "Math.min(2, 3)",
6980            ),
6981            (
6982                server_builtin("max", vec![float(2.5), float(3.5)], Type::Float),
6983                "Math.max(2.5, 3.5)",
6984            ),
6985            (
6986                server_builtin("abs", vec![int(-2)], Type::Int),
6987                "Math.abs(-2)",
6988            ),
6989            (
6990                server_builtin("round", vec![float(2.5)], Type::Int),
6991                "Math.round(2.5)",
6992            ),
6993            (
6994                server_builtin("floor", vec![float(2.5)], Type::Int),
6995                "Math.floor(2.5)",
6996            ),
6997            (
6998                server_builtin("ceil", vec![float(2.5)], Type::Int),
6999                "Math.ceil(2.5)",
7000            ),
7001            (server_builtin("toFloat", vec![int(2)], Type::Float), "(2)"),
7002            (
7003                server_builtin("toInt", vec![float(2.5)], Type::Int),
7004                "Math.trunc(2.5)",
7005            ),
7006            (
7007                server_builtin("toString", vec![int(2)], Type::String),
7008                "String(2)",
7009            ),
7010        ];
7011        for (expression, expected) in cases {
7012            assert_eq!(
7013                compiler_body_javascript(&expression).expect("builtin body emits"),
7014                expected
7015            );
7016        }
7017
7018        let user_function = SemanticExpr {
7019            kind: SemanticExprKind::FunctionCall {
7020                function: SemanticId::function("len"),
7021                name: "len".into(),
7022                arguments: vec![string()],
7023            },
7024            ty: Type::Int,
7025            span: Span::new(0, 1),
7026        };
7027        let error = compiler_body_javascript(&user_function)
7028            .expect_err("a user function call has no compiler-owned server lowering");
7029        assert!(
7030            error.starts_with(
7031                "error[REMOTE_ACTION_CALL_UNSUPPORTED]: the call `len(1 argument(s))`"
7032            ),
7033            "{error}"
7034        );
7035        assert!(error.contains("host-implemented body"), "{error}");
7036    }
7037
7038    /// An external JavaScript call reaching the server emitter used to become
7039    /// the literal string `undefined /* rejected remote call */`, which is
7040    /// valid JavaScript and therefore a silently wrong compile. It now fails
7041    /// closed with the same code the semantic guard uses.
7042    #[test]
7043    fn an_external_call_fails_closed_instead_of_emitting_undefined() {
7044        let call = SemanticExpr {
7045            kind: SemanticExprKind::Call {
7046                function: SemanticId::external_function("./format.js", "shout"),
7047                name: "shout".into(),
7048                arguments: vec![SemanticExpr {
7049                    kind: SemanticExprKind::Reference(SemanticId::action_parameter(
7050                        "C", "save", "note",
7051                    )),
7052                    ty: Type::String,
7053                    span: Span::new(0, 1),
7054                }],
7055            },
7056            ty: Type::String,
7057            span: Span::new(0, 1),
7058        };
7059        let error = compiler_body_javascript(&call)
7060            .expect_err("an external JavaScript call has no compiler-owned server lowering");
7061        assert!(
7062            error.starts_with(
7063                "error[REMOTE_ACTION_CALL_UNSUPPORTED]: the call `shout(1 argument(s))`"
7064            ),
7065            "{error}"
7066        );
7067        assert!(error.contains("host-implemented body"), "{error}");
7068
7069        // The same rule holds one level up, through the statement emitter.
7070        let error = compiler_statements_javascript(
7071            &[noxid_ir::SemanticStatement::Return {
7072                value: call,
7073                span: Span::new(0, 1),
7074            }],
7075            2,
7076        )
7077        .expect_err("the statement emitter propagates the expression rejection");
7078        assert!(
7079            error.starts_with("error[REMOTE_ACTION_CALL_UNSUPPORTED]"),
7080            "{error}"
7081        );
7082    }
7083
7084    /// Semantics refuses a mis-arity builtin with BUILTIN_OVERLOAD_MISMATCH
7085    /// before emission, so this is a defensive backstop: if one ever reaches
7086    /// the emitter, it must not become a bare `len(a, b)` call.
7087    #[test]
7088    fn a_builtin_with_no_lowering_fails_closed_in_the_server_emitter() {
7089        let call = SemanticExpr {
7090            kind: SemanticExprKind::FunctionCall {
7091                function: SemanticId::function("@builtin.len"),
7092                name: "len".into(),
7093                arguments: vec![
7094                    SemanticExpr {
7095                        kind: SemanticExprKind::String("a".into()),
7096                        ty: Type::String,
7097                        span: Span::new(0, 1),
7098                    },
7099                    SemanticExpr {
7100                        kind: SemanticExprKind::String("b".into()),
7101                        ty: Type::String,
7102                        span: Span::new(0, 1),
7103                    },
7104                ],
7105            },
7106            ty: Type::Int,
7107            span: Span::new(0, 1),
7108        };
7109        let error =
7110            compiler_body_javascript(&call).expect_err("a two-argument `len` has no lowering");
7111        assert!(
7112            error.starts_with("error[BUILTIN_OVERLOAD_MISMATCH]: builtin `len`"),
7113            "{error}"
7114        );
7115    }
7116
7117    #[test]
7118    fn collection_queries_use_shared_emission_in_remote_bodies() {
7119        let query = SemanticExpr {
7120            kind: SemanticExprKind::CollectionQuery {
7121                base: Box::new(SemanticExpr {
7122                    kind: SemanticExprKind::Reference(SemanticId::state("C", "items")),
7123                    ty: Type::Array(Box::new(Type::Int)),
7124                    span: Span::new(0, 1),
7125                }),
7126                kind: noxid_ir::CollectionQueryKind::Count,
7127                field: None,
7128                value: None,
7129            },
7130            ty: Type::Int,
7131            span: Span::new(0, 1),
7132        };
7133        assert_eq!(
7134            compiler_body_javascript(&query).expect("collection query emits"),
7135            "(args[\"items\"]).length"
7136        );
7137    }
7138
7139    #[test]
7140    fn map_queries_use_typed_sorted_shared_emission_in_remote_bodies() {
7141        let query = SemanticExpr {
7142            kind: SemanticExprKind::CollectionQuery {
7143                base: Box::new(SemanticExpr {
7144                    kind: SemanticExprKind::Reference(SemanticId::action_parameter(
7145                        "C", "read", "counts",
7146                    )),
7147                    ty: Type::Map(Box::new(Type::Boolean), Box::new(Type::Int)),
7148                    span: Span::new(0, 1),
7149                }),
7150                kind: noxid_ir::CollectionQueryKind::MapEntries,
7151                field: None,
7152                value: None,
7153            },
7154            ty: Type::Array(Box::new(Type::MapEntry(
7155                Box::new(Type::Boolean),
7156                Box::new(Type::Int),
7157            ))),
7158            span: Span::new(0, 1),
7159        };
7160        let javascript = compiler_body_javascript(&query).expect("map query emits");
7161        let script = format!(
7162            "const args = {{ counts: Object.freeze({{true: 2, false: 1}}) }}; const result = {javascript}; if (JSON.stringify(result) !== JSON.stringify([{{key:false,value:1}},{{key:true,value:2}}])) process.exit(1);"
7163        );
7164        let output = Command::new("node")
7165            .args(["--input-type=module", "-e", &script])
7166            .output()
7167            .expect("node must execute server map query");
7168        assert!(
7169            output.status.success(),
7170            "{}",
7171            String::from_utf8_lossy(&output.stderr)
7172        );
7173    }
7174
7175    #[test]
7176    fn optional_coalescing_lowers_in_compiler_owned_remote_bodies() {
7177        let expression = SemanticExpr {
7178            kind: SemanticExprKind::Binary {
7179                left: Box::new(SemanticExpr {
7180                    kind: SemanticExprKind::Reference(SemanticId::action_parameter(
7181                        "C", "read", "value",
7182                    )),
7183                    ty: Type::Optional(Box::new(Type::Int)),
7184                    span: Span::new(0, 1),
7185                }),
7186                op: SemanticBinaryOp::Coalesce,
7187                right: Box::new(SemanticExpr {
7188                    kind: SemanticExprKind::Int(1),
7189                    ty: Type::Int,
7190                    span: Span::new(0, 1),
7191                }),
7192            },
7193            ty: Type::Int,
7194            span: Span::new(0, 1),
7195        };
7196        assert_eq!(
7197            compiler_body_javascript(&expression).expect("coalescing body emits"),
7198            "(args[\"value\"] ?? 1)"
7199        );
7200    }
7201
7202    #[test]
7203    fn server_compound_equality_matches_recursive_language_values() {
7204        let ready = |value| SemanticExpr {
7205            kind: SemanticExprKind::Variant {
7206                machine: SemanticId::machine("C", "Phase"),
7207                variant: SemanticId::variant("C", "Phase", "Ready"),
7208                payload: Some(Box::new(SemanticExpr {
7209                    kind: SemanticExprKind::Int(value),
7210                    ty: Type::Int,
7211                    span: Span::new(0, 1),
7212                })),
7213            },
7214            ty: Type::Named("Phase".into()),
7215            span: Span::new(0, 1),
7216        };
7217        let expression = SemanticExpr {
7218            kind: SemanticExprKind::Binary {
7219                left: Box::new(SemanticExpr {
7220                    kind: SemanticExprKind::Array(vec![ready(1), ready(2)]),
7221                    ty: Type::Array(Box::new(Type::Named("Phase".into()))),
7222                    span: Span::new(0, 1),
7223                }),
7224                op: SemanticBinaryOp::Equal,
7225                right: Box::new(SemanticExpr {
7226                    kind: SemanticExprKind::Array(vec![ready(1), ready(2)]),
7227                    ty: Type::Array(Box::new(Type::Named("Phase".into()))),
7228                    span: Span::new(0, 1),
7229                }),
7230            },
7231            ty: Type::Boolean,
7232            span: Span::new(0, 1),
7233        };
7234        let script = format!(
7235            "{}\nif (!({})) process.exit(1);",
7236            LANGUAGE_VALUE_EQUALITY_FUNCTION,
7237            compiler_body_javascript(&expression).expect("compound equality body emits")
7238        );
7239        let output = std::process::Command::new("node")
7240            .args(["--input-type=module", "-e", &script])
7241            .output()
7242            .expect("node must execute server compound equality");
7243        assert!(
7244            output.status.success(),
7245            "{}",
7246            String::from_utf8_lossy(&output.stderr)
7247        );
7248    }
7249
7250    #[test]
7251    fn server_handler_uses_fetch_and_never_embeds_host_source() {
7252        let program = ExecutionProgram {
7253            live_resources: vec![],
7254            presences: vec![],
7255            boundaries: vec![ExecutionBoundary {
7256                id: SemanticId::execution_boundary("Account", "save", ExecutionTarget::Server),
7257                action: SemanticId::action("Account", "save"),
7258                component: SemanticId::component("Account"),
7259                component_name: "Account".into(),
7260                action_name: "save".into(),
7261                target: ExecutionTarget::Server,
7262                parameters: vec![ExecutionParameter {
7263                    id: SemanticId::action_parameter("Account", "save", "id"),
7264                    name: "id".into(),
7265                    ty: "Int".into(),
7266                    type_id: None,
7267                }],
7268                result: ExecutionResult {
7269                    id: SemanticId::action_result("Account", "save"),
7270                    ty: "Boolean".into(),
7271                    type_id: None,
7272                },
7273                body: Some(SemanticExpr {
7274                    kind: SemanticExprKind::Binary {
7275                        left: Box::new(SemanticExpr {
7276                            kind: SemanticExprKind::Reference(SemanticId::action_parameter(
7277                                "Account", "save", "id",
7278                            )),
7279                            ty: Type::Int,
7280                            span: Span::new(0, 1),
7281                        }),
7282                        op: SemanticBinaryOp::Add,
7283                        right: Box::new(SemanticExpr {
7284                            kind: SemanticExprKind::Int(1),
7285                            ty: Type::Int,
7286                            span: Span::new(0, 1),
7287                        }),
7288                    },
7289                    ty: Type::Int,
7290                    span: Span::new(0, 1),
7291                }),
7292                capabilities: vec!["account.write".into()],
7293                route_scopes: vec![ExecutionRouteScope {
7294                    route: SemanticId::route("/accounts"),
7295                    pattern: "/accounts".into(),
7296                    parameters: vec![],
7297                    middleware: vec![SemanticId::middleware("session")],
7298                }],
7299                invalidates: vec![],
7300                span: Span::new(0, 1),
7301            }],
7302            endpoints: vec![],
7303            tasks: vec![],
7304            queues: vec![],
7305        };
7306        let output = generate(
7307            &program,
7308            "./host.js",
7309            "./validators.js",
7310            "./middleware.js",
7311            "/console",
7312            &[],
7313        );
7314        assert!(
7315            output
7316                .handler
7317                .contains("export async function fetch(request")
7318        );
7319        assert!(output.handler.contains("/console/_noxid/actions/"));
7320        assert!(output.handler.contains("BOUNDARY_ARGUMENT_TYPE"));
7321        assert!(output.handler.contains("BOUNDARY_RESULT_TYPE"));
7322        assert!(output.handler.contains("BOUNDARY_CAPABILITY_DENIED"));
7323        assert!(output.handler.contains("BOUNDARY_MIDDLEWARE_DENIED"));
7324        assert!(output.handler.contains("account.write"));
7325        assert!(output.handler.contains("action:Account.save"));
7326        assert!(output.handler.contains("compiledActions"));
7327        assert!(output.handler.contains("args[\"id\"] + 1"));
7328        assert!(
7329            !output
7330                .handler
7331                .contains("import * as __noxidServerStorageRuntime")
7332        );
7333        assert_eq!(output.server_actions, 1);
7334        assert_eq!(output.edge_actions, 0);
7335    }
7336
7337    #[test]
7338    fn queue_drain_is_adapter_activated_capability_guarded_and_budgeted() {
7339        let queue_id = SemanticId::queue("Drain");
7340        let live_id = SemanticId::resource("DrainStatus");
7341        let program = ExecutionProgram {
7342            live_resources: vec![LiveResourceExecutionContract {
7343                id: live_id.clone(),
7344                name: "DrainStatus".into(),
7345                capabilities: vec![],
7346                route_scopes: vec![],
7347            }],
7348            presences: vec![],
7349            boundaries: vec![],
7350            endpoints: vec![],
7351            tasks: vec![],
7352            queues: vec![QueueExecutionBoundary {
7353                id: queue_id.clone(),
7354                host_key: Some(queue_id),
7355                name: "Drain".into(),
7356                payload: vec![QueueExecutionField {
7357                    id: SemanticId::queue_payload("Drain", "fail"),
7358                    name: "fail".into(),
7359                    ty: "Boolean".into(),
7360                    type_id: None,
7361                    type_ids: vec![],
7362                }],
7363                retry: 1,
7364                backoff_ms: 60_000,
7365                statements: vec![],
7366                invalidates: vec![live_id],
7367                span: Span::new(0, 1),
7368            }],
7369        };
7370        let generated = generate_with_runtime_options(
7371            &program,
7372            "./host.mjs",
7373            "./validators.mjs",
7374            "./middleware.mjs",
7375            None,
7376            "/console",
7377            &[],
7378            AgentSurfaceOptions::default(),
7379            ServerTracingMode::Requests,
7380            ServerRuntimeOptions {
7381                db_pool: 10,
7382                pubsub: Some(PubSubRuntimeOptions {
7383                    driver: PubSubDriver::Memory,
7384                    coalescing_ms: 1,
7385                }),
7386                development_trace_capture: false,
7387                application_namespace: Some("codegen_test".into()),
7388                ..ServerRuntimeOptions::default()
7389            },
7390        );
7391        let root = std::env::temp_dir().join(format!(
7392            "noxid-queue-drain-handler-{}-{}",
7393            std::process::id(),
7394            SystemTime::now()
7395                .duration_since(UNIX_EPOCH)
7396                .unwrap()
7397                .as_nanos()
7398        ));
7399        std::fs::create_dir_all(root.join("node_modules/postgres")).unwrap();
7400        std::fs::write(root.join("package.json"), "{\"type\":\"module\"}\n").unwrap();
7401        std::fs::write(root.join("handler.mjs"), generated.handler).unwrap();
7402        std::fs::write(
7403            root.join("validators.mjs"),
7404            "export const typeValidators = Object.freeze({});\n",
7405        )
7406        .unwrap();
7407        std::fs::write(
7408            root.join("middleware.mjs"),
7409            "export const globalMiddleware = Object.freeze([]);\nexport const middleware = Object.freeze({});\n",
7410        )
7411        .unwrap();
7412        std::fs::write(
7413            root.join("host.mjs"),
7414            r#"export const queues = Object.freeze({
7415  "queue:Drain": async ({ fail }) => {
7416    await new Promise((resolve) => setTimeout(resolve, 12));
7417    if (fail) throw new Error("retry me");
7418    return "done";
7419  },
7420});
7421export async function authorize({ capability, environment }) {
7422  environment.authorized = (environment.authorized ?? 0) + 1;
7423  environment.capability = capability;
7424  return environment.allow === true;
7425}
7426"#,
7427        )
7428        .unwrap();
7429        std::fs::write(
7430            root.join("node_modules/postgres/package.json"),
7431            "{\"name\":\"postgres\",\"type\":\"module\",\"exports\":\"./index.js\"}\n",
7432        )
7433        .unwrap();
7434        std::fs::write(
7435            root.join("node_modules/postgres/index.js"),
7436            r#"const jobs = [
7437  { id: "one", queue: "Drain", payload: { fail: false }, principal: "system", attempts: 0, run_at: new Date(0) },
7438  { id: "two", queue: "Drain", payload: { fail: true }, principal: "system", attempts: 0, run_at: new Date(0) },
7439];
7440let claims = 0;
7441const text = (strings) => strings.join("?").replace(/\s+/g, " ").trim();
7442export function observedClaims() { return claims; }
7443export default function postgres() {
7444  const sql = async () => [];
7445  sql.unsafe = async () => [];
7446  sql.json = (value) => value;
7447  sql.begin = async (operation) => operation(async (strings) => {
7448    if (!text(strings).startsWith("SELECT ")) return [];
7449    const job = jobs.shift();
7450    if (job === undefined) return [];
7451    claims += 1;
7452    return [job];
7453  });
7454  return sql;
7455}
7456"#,
7457        )
7458        .unwrap();
7459        let script = r#"import { fetch as handle, fetchEndpoint, __noxidLiveConnectionPrincipal, __noxidPubSubSubscribe } from "./handler.mjs";
7460import { observedClaims } from "postgres";
7461const url = "http://noxid.test/console/_noxid/queue/drain";
7462const principal = __noxidLiveConnectionPrincipal(Object.create(null), Object.create(null));
7463const events = [];
7464const stop = await __noxidPubSubSubscribe("invalidation", "resource:DrainStatus", principal, (event) => events.push(event.semanticId), { schedule: (run) => { queueMicrotask(run); return 1; }, cancel: () => {} });
7465
7466let environment = { allow: true };
7467let response = await fetchEndpoint(new Request(url, { method: "POST" }), environment);
7468let body = await response.json();
7469if (response.status !== 404 || body.error?.code !== "QUEUE_DRAIN_DISABLED" || environment.authorized !== undefined || observedClaims() !== 0) throw new Error(`disabled door was exposed ${response.status} ${JSON.stringify(body)}`);
7470
7471environment = { allow: true };
7472response = await handle(new Request(url), environment, { noxidQueueDrain: true });
7473body = await response.json();
7474if (response.status !== 405 || response.headers.get("allow") !== "POST" || environment.authorized !== undefined || observedClaims() !== 0) throw new Error(`method guard failed ${response.status} ${JSON.stringify(body)}`);
7475
7476environment = { allow: true };
7477response = await fetchEndpoint(new Request(url, { method: "POST" }), environment, { noxidQueueDrain: true, queueDrainBudgetMs: 0 });
7478body = await response.json();
7479if (response.status !== 500 || body.error?.code !== "QUEUE_DRAIN_BUDGET_INVALID" || environment.authorized !== undefined || observedClaims() !== 0) throw new Error(`invalid provider budget did not fail closed ${response.status} ${JSON.stringify(body)}`);
7480
7481environment = { allow: false };
7482response = await handle(new Request(url, { method: "POST" }), environment, { noxidQueueDrain: true, queueDrainBudgetMs: 50 });
7483body = await response.json();
7484if (response.status !== 403 || body.error?.code !== "QUEUE_DRAIN_CAPABILITY_DENIED" || environment.capability !== "queue.drain" || observedClaims() !== 0) throw new Error(`capability guard failed ${response.status} ${JSON.stringify(body)}`);
7485
7486environment = { allow: true };
7487response = await handle(new Request(url, { method: "POST" }), environment, { noxidQueueDrain: true, queueDrainBudgetMs: 5 });
7488body = await response.json();
7489if (response.status !== 200 || body.budgetMs !== 5 || body.counts?.claimed !== 1 || body.counts?.completed !== 1 || body.counts?.retried !== 0 || observedClaims() !== 1) throw new Error(`time budget did not stop new claims ${response.status} ${JSON.stringify(body)}`);
7490await new Promise((resolve) => setTimeout(resolve, 5));
7491if (JSON.stringify(events) !== JSON.stringify(["resource:DrainStatus"])) throw new Error(`queue completion did not publish ${events}`);
7492
7493environment = { allow: true };
7494response = await handle(new Request(url, { method: "POST" }), environment, { noxidQueueDrain: true });
7495body = await response.json();
7496if (response.status !== 200 || body.budgetMs !== 25000 || body.counts?.claimed !== 1 || body.counts?.completed !== 0 || body.counts?.retried !== 1 || body.counts?.deadLettered !== 0 || observedClaims() !== 2) throw new Error(`default budget or retry counts failed ${response.status} ${JSON.stringify(body)}`);
7497await new Promise((resolve) => setTimeout(resolve, 5));
7498if (events.length !== 1) throw new Error(`failed queue delivery published ${events}`);
7499await stop();
7500"#;
7501        let output = Command::new("node")
7502            .args(["--input-type=module", "-e", script])
7503            .current_dir(&root)
7504            .env("DATABASE_URL", "postgres://noxid.test/fake")
7505            .output()
7506            .expect("Node.js is required for generated queue-drain tests");
7507        let _ = std::fs::remove_dir_all(&root);
7508        assert!(
7509            output.status.success(),
7510            "{}\n{}",
7511            String::from_utf8_lossy(&output.stdout),
7512            String::from_utf8_lossy(&output.stderr)
7513        );
7514    }
7515
7516    #[test]
7517    fn generated_handler_enforces_capabilities_and_result_types() {
7518        let program = ExecutionProgram {
7519            live_resources: vec![],
7520            presences: vec![],
7521            boundaries: vec![ExecutionBoundary {
7522                id: SemanticId::execution_boundary("Account", "save", ExecutionTarget::Server),
7523                action: SemanticId::action("Account", "save"),
7524                component: SemanticId::component("Account"),
7525                component_name: "Account".into(),
7526                action_name: "save".into(),
7527                target: ExecutionTarget::Server,
7528                parameters: vec![ExecutionParameter {
7529                    id: SemanticId::action_parameter("Account", "save", "id"),
7530                    name: "id".into(),
7531                    ty: "Int".into(),
7532                    type_id: None,
7533                }],
7534                result: ExecutionResult {
7535                    id: SemanticId::action_result("Account", "save"),
7536                    ty: "Boolean".into(),
7537                    type_id: None,
7538                },
7539                body: None,
7540                capabilities: vec!["account.write".into()],
7541                route_scopes: vec![ExecutionRouteScope {
7542                    route: SemanticId::route("/accounts"),
7543                    pattern: "/accounts".into(),
7544                    parameters: vec![],
7545                    middleware: vec![SemanticId::middleware("session")],
7546                }],
7547                invalidates: vec![],
7548                span: Span::new(0, 1),
7549            }],
7550            endpoints: vec![],
7551            tasks: vec![],
7552            queues: vec![],
7553        };
7554        let output = generate(
7555            &program,
7556            "./host.mjs",
7557            "./validators.mjs",
7558            "./middleware.mjs",
7559            "/console",
7560            &[],
7561        );
7562        let no_authorizer = generate(
7563            &program,
7564            "./host-no-authorizer.mjs",
7565            "./validators.mjs",
7566            "./middleware.mjs",
7567            "/console",
7568            &[],
7569        );
7570        let unique = std::time::SystemTime::now()
7571            .duration_since(std::time::UNIX_EPOCH)
7572            .unwrap()
7573            .as_nanos();
7574        let root = std::env::temp_dir().join(format!(
7575            "noxid-server-handler-{}-{unique}",
7576            std::process::id()
7577        ));
7578        std::fs::create_dir_all(&root).unwrap();
7579        std::fs::write(root.join("handler.mjs"), output.handler).unwrap();
7580        std::fs::write(
7581            root.join("validators.mjs"),
7582            "export const typeValidators = Object.freeze({});\n",
7583        )
7584        .unwrap();
7585        std::fs::write(
7586            root.join("middleware.mjs"),
7587            r#"export const middleware = Object.freeze({
7588  session: async ({ request, environment, host }) => {
7589    environment.trace.push("middleware:session");
7590    if (typeof host?.sessionUser !== "function") throw new Error("action middleware lost the generated host registry");
7591    const userId = host.sessionUser(request);
7592    return userId === null ? { allow: false } : { allow: true, context: { userId } };
7593  },
7594});"#,
7595        )
7596        .unwrap();
7597        std::fs::write(
7598            root.join("handler-no-authorizer.mjs"),
7599            no_authorizer.handler,
7600        )
7601        .unwrap();
7602        std::fs::write(
7603            root.join("host.mjs"),
7604            r#"export const actions = Object.freeze({
7605  "action:Account.save": async ({ id }, context) => {
7606    if (!Object.isFrozen(context.capabilities)) throw new Error("capabilities were mutable");
7607    if (!Object.isFrozen(context) || context.principal?.kind !== "user" || context.principal?.scope !== "user-a") throw new Error("action context lost the sealed middleware principal");
7608    context.environment.trace.push("action");
7609    return id === 1 ? true : "invalid";
7610  },
7611});
7612export function sessionUser(request) { return request.headers.get("x-session") === "active" ? "user-a" : null; }
7613export async function authorize({ capability, request, environment }) {
7614  environment.trace.push(`capability:${capability}`);
7615  return request.headers.get("x-capability") === capability;
7616}
7617export async function invalidateCache(tags, context) {
7618  context.environment.trace.push(`invalidate:${tags.join(",")}`);
7619  return { invalidated: tags.length };
7620}
7621"#,
7622        )
7623        .unwrap();
7624        std::fs::write(
7625            root.join("host-no-authorizer.mjs"),
7626            r#"export const actions = Object.freeze({ "action:Account.save": async () => true });
7627export function sessionUser(request) { return request.headers.get("x-session") === "active" ? "user-a" : null; }"#,
7628        )
7629        .unwrap();
7630        let script = r#"import { fetch as handle } from "./handler.mjs";
7631import { fetch as handleWithoutAuthorizer } from "./handler-no-authorizer.mjs";
7632const endpoint = "http://noxid.test/console/_noxid/actions/action%3AAccount.save";
7633function request(id, allowed = false, session = true, route = "route:/accounts") {
7634  const headers = { "content-type": "application/json" };
7635  if (route !== null) headers["x-noxid-route-id"] = route;
7636  if (session) headers["x-session"] = "active";
7637  if (allowed) headers["x-capability"] = "account.write";
7638  return new Request(endpoint, { method: "POST", headers, body: JSON.stringify({ arguments: { id } }) });
7639}
7640let environment = { trace: [] };
7641let response = await handle(request(1, false, true, null), environment);
7642let body = await response.json();
7643if (response.status !== 400 || body.error.code !== "BOUNDARY_ROUTE_REQUIRED") throw new Error("missing route did not fail closed");
7644environment = { trace: [] };
7645response = await handle(request(1, false, true, "route:/other"), environment);
7646body = await response.json();
7647if (response.status !== 403 || body.error.code !== "BOUNDARY_ROUTE_DENIED" || environment.trace.length !== 0) throw new Error("forged route did not fail closed");
7648environment = { trace: [] };
7649response = await handle(request(1, false, false), environment);
7650body = await response.json();
7651if (response.status !== 403 || body.error.code !== "BOUNDARY_MIDDLEWARE_DENIED") throw new Error("middleware denial failed");
7652environment = { trace: [] };
7653response = await handle(request(1), environment);
7654body = await response.json();
7655if (response.status !== 403 || body.error.code !== "BOUNDARY_CAPABILITY_DENIED") throw new Error("denial failed");
7656environment = { trace: [] };
7657response = await handle(request(1, true), environment);
7658body = await response.json();
7659if (response.status !== 200 || body.value !== true) throw new Error("valid result failed");
7660if (JSON.stringify(environment.trace) !== JSON.stringify(["middleware:session", "capability:account.write", "action"])) throw new Error(`wrong execution order: ${JSON.stringify(environment.trace)}`);
7661response = await handle(request(2, true), { trace: [] });
7662body = await response.json();
7663if (response.status !== 500 || body.error.code !== "BOUNDARY_RESULT_TYPE" || body.error.semanticId !== "result:Account.save") throw new Error("result validation failed");
7664response = await handleWithoutAuthorizer(request(1, true), { trace: [] });
7665body = await response.json();
7666if (response.status !== 500 || body.error.code !== "BOUNDARY_AUTHORIZER_MISSING") throw new Error("missing authorizer did not fail closed");
7667const invalidationUrl = "http://noxid.test/console/_noxid/revalidate";
7668response = await handle(new Request(invalidationUrl, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ tags: ["account"] }) }), { trace: [] });
7669body = await response.json();
7670if (response.status !== 403 || body.error.code !== "CACHE_INVALIDATION_DENIED") throw new Error("cache invalidation did not require authority");
7671environment = { trace: [] };
7672response = await handle(new Request(invalidationUrl, { method: "POST", headers: { "content-type": "application/json", "x-capability": "cache.invalidate" }, body: JSON.stringify({ tags: ["account", "account"] }) }), environment);
7673body = await response.json();
7674if (response.status !== 200 || body.tags.length !== 1 || body.result.invalidated !== 1) throw new Error("cache invalidation failed");
7675if (JSON.stringify(environment.trace) !== JSON.stringify(["capability:cache.invalidate", "invalidate:account"])) throw new Error("cache invalidation order failed");
7676for (const invalidBody of [
7677  "null",
7678  "[]",
7679  JSON.stringify({ tags: ["account"], capability: "admin" }),
7680  '{"tags":["account"],"__proto__":{"tags":["forged"]}}',
7681]) {
7682  environment = { trace: [] };
7683  response = await handle(new Request(invalidationUrl, { method: "POST", headers: { "content-type": "application/json", "x-capability": "cache.invalidate" }, body: invalidBody }), environment);
7684  body = await response.json();
7685  if (response.status !== 400 || body.error.code !== "CACHE_INVALIDATION_BODY_INVALID" || environment.trace.length !== 0) throw new Error(`non-exact cache invalidation body crossed the boundary: ${invalidBody} ${response.status} ${JSON.stringify(body)} ${JSON.stringify(environment.trace)}`);
7686}
7687const nativeJsonParse = JSON.parse;
7688for (const hostileBody of [
7689  () => Object.create({ tags: ["forged"] }),
7690  () => Object.defineProperty({}, "tags", { enumerable: true, configurable: true, get() { throw new Error("CACHE_INVALIDATION_ACCESSOR_RAN"); } }),
7691  () => new Proxy({}, { getPrototypeOf() { throw new Error("CACHE_INVALIDATION_PROXY_TRAP"); } }),
7692]) {
7693  environment = { trace: [] };
7694  JSON.parse = hostileBody;
7695  try {
7696    response = await handle(new Request(invalidationUrl, { method: "POST", headers: { "content-type": "application/json", "x-capability": "cache.invalidate" }, body: "{}" }), environment);
7697  } finally {
7698    JSON.parse = nativeJsonParse;
7699  }
7700  body = await response.json();
7701  if (response.status !== 400 || body.error.code !== "CACHE_INVALIDATION_BODY_INVALID" || environment.trace.length !== 0) throw new Error(`hostile cache invalidation shape crossed the boundary: ${response.status} ${JSON.stringify(body)} ${JSON.stringify(environment.trace)}`);
7702}
7703environment = { trace: [] };
7704const failedInvalidationStream = new ReadableStream({
7705  start(controller) { controller.error(new Error("transport failed")); },
7706});
7707response = await handle(new Request(invalidationUrl, { method: "POST", headers: { "content-type": "application/json", "x-capability": "cache.invalidate" }, body: failedInvalidationStream, duplex: "half" }), environment);
7708body = await response.json();
7709if (response.status !== 400 || body.error.code !== "CACHE_INVALIDATION_BODY_INVALID" || environment.trace.length !== 0) throw new Error(`failed cache invalidation stream escaped the structured boundary: ${response.status} ${JSON.stringify(body)} ${JSON.stringify(environment.trace)}`);
7710environment = { trace: [] };
7711response = await handle(new Request(invalidationUrl, { method: "POST", headers: { "content-type": "application/json", "x-capability": "cache.invalidate" }, body: JSON.stringify({ tags: ["../../unsafe"] }) }), environment);
7712body = await response.json();
7713if (response.status !== 400 || body.error.code !== "CACHE_INVALIDATION_TAGS_INVALID" || environment.trace.length !== 0) throw new Error("unsafe cache tag crossed authorization");
7714"#;
7715        let status = std::process::Command::new("node")
7716            .args(["--input-type=module", "-e", script])
7717            .current_dir(&root)
7718            .status()
7719            .expect("Node.js is required for generated server handler tests");
7720        let _ = std::fs::remove_dir_all(&root);
7721        assert!(status.success());
7722    }
7723
7724    #[test]
7725    fn generated_endpoint_handler_enforces_typed_routing_limits_timeout_and_replay() {
7726        let mut read = endpoint_boundary(
7727            "ReadItem",
7728            EndpointMethod::Get,
7729            "/api/items/[id]",
7730            vec![
7731                endpoint_input("ReadItem", EndpointInputSection::Params, "id", "Int"),
7732                endpoint_input(
7733                    "ReadItem",
7734                    EndpointInputSection::Query,
7735                    "tags",
7736                    "Optional<Array<Int>>",
7737                ),
7738                endpoint_input(
7739                    "ReadItem",
7740                    EndpointInputSection::Query,
7741                    "required",
7742                    "Array<String>",
7743                ),
7744            ],
7745            "String",
7746        );
7747        read.capabilities = vec!["items.read".into()];
7748        read.middleware = vec!["audit".into()];
7749        read.limit = Some(EndpointLimitPolicy {
7750            requests: 3,
7751            window: EndpointLimitWindow::Minute,
7752            scope: EndpointLimitScope::Session,
7753        });
7754        read.cache = Some(EndpointCachePolicy {
7755            id: SemanticId::endpoint_cache("ReadItem"),
7756            mode: EndpointCacheMode::Swr,
7757            seconds: 60,
7758            tags: vec!["endpoint:ReadItem@1".into()],
7759            span: Span::new(0, 1),
7760        });
7761        let mut save = endpoint_boundary(
7762            "SaveItem",
7763            EndpointMethod::Post,
7764            "/api/items",
7765            vec![endpoint_input(
7766                "SaveItem",
7767                EndpointInputSection::Body,
7768                "value",
7769                "Int",
7770            )],
7771            "Result<String, String>",
7772        );
7773        save.idempotent = true;
7774        save.middleware = vec!["audit".into()];
7775        let mut slow = endpoint_boundary(
7776            "SlowItem",
7777            EndpointMethod::Delete,
7778            "/api/items/[id]",
7779            vec![endpoint_input(
7780                "SlowItem",
7781                EndpointInputSection::Params,
7782                "id",
7783                "String",
7784            )],
7785            "Boolean",
7786        );
7787        slow.timeout_ms = 10;
7788        let mut middleware_timeout = endpoint_boundary(
7789            "MiddlewareTimeout",
7790            EndpointMethod::Get,
7791            "/api/middleware-timeout",
7792            vec![],
7793            "Boolean",
7794        );
7795        middleware_timeout.timeout_ms = 10;
7796        middleware_timeout.middleware = vec!["slow".into()];
7797        let upload = endpoint_boundary(
7798            "Upload",
7799            EndpointMethod::Post,
7800            "/api/upload",
7801            vec![endpoint_input(
7802                "Upload",
7803                EndpointInputSection::Body,
7804                "text",
7805                "String",
7806            )],
7807            "Int",
7808        );
7809        let bodyless_mutation = endpoint_boundary(
7810            "BodylessMutation",
7811            EndpointMethod::Post,
7812            "/api/bodyless",
7813            vec![],
7814            "Boolean",
7815        );
7816        let measure = endpoint_boundary(
7817            "Measure",
7818            EndpointMethod::Get,
7819            "/api/measure",
7820            vec![endpoint_input(
7821                "Measure",
7822                EndpointInputSection::Query,
7823                "value",
7824                "Float",
7825            )],
7826            "Float",
7827        );
7828        let count = endpoint_boundary(
7829            "Count",
7830            EndpointMethod::Get,
7831            "/api/count",
7832            vec![endpoint_input(
7833                "Count",
7834                EndpointInputSection::Query,
7835                "value",
7836                "Int",
7837            )],
7838            "Int",
7839        );
7840        let mut fast_replay = endpoint_boundary(
7841            "FastReplay",
7842            EndpointMethod::Post,
7843            "/api/fast-replay",
7844            vec![],
7845            "Int",
7846        );
7847        fast_replay.idempotent = true;
7848        let events = endpoint_boundary(
7849            "Events",
7850            EndpointMethod::Get,
7851            "/api/events",
7852            vec![endpoint_input(
7853                "Events",
7854                EndpointInputSection::Query,
7855                "since",
7856                "Date",
7857            )],
7858            "Date",
7859        );
7860        let optional_echo = endpoint_boundary(
7861            "OptionalEcho",
7862            EndpointMethod::Post,
7863            "/api/optional",
7864            vec![endpoint_input(
7865                "OptionalEcho",
7866                EndpointInputSection::Body,
7867                "value",
7868                "Optional<String>",
7869            )],
7870            "OptionalReply",
7871        );
7872        let mut rate_save = endpoint_boundary(
7873            "RateSave",
7874            EndpointMethod::Post,
7875            "/api/rate-save",
7876            vec![endpoint_input(
7877                "RateSave",
7878                EndpointInputSection::Body,
7879                "value",
7880                "Int",
7881            )],
7882            "Int",
7883        );
7884        rate_save.limit = Some(EndpointLimitPolicy {
7885            requests: 1,
7886            window: EndpointLimitWindow::Minute,
7887            scope: EndpointLimitScope::Ip,
7888        });
7889        let put = endpoint_boundary(
7890            "PutItem",
7891            EndpointMethod::Put,
7892            "/api/put",
7893            vec![],
7894            "Boolean",
7895        );
7896        let patch = endpoint_boundary(
7897            "PatchItem",
7898            EndpointMethod::Patch,
7899            "/api/patch",
7900            vec![],
7901            "Boolean",
7902        );
7903        let mut direct = endpoint_boundary(
7904            "DirectResponse",
7905            EndpointMethod::Get,
7906            "/health",
7907            vec![],
7908            "String",
7909        );
7910        direct.middleware = vec!["direct".into()];
7911        let mut redirect = endpoint_boundary(
7912            "RedirectResponse",
7913            EndpointMethod::Get,
7914            "/login",
7915            vec![],
7916            "String",
7917        );
7918        redirect.middleware = vec!["redirect".into()];
7919        let mut denied = endpoint_boundary(
7920            "DeniedWrite",
7921            EndpointMethod::Post,
7922            "/private/[id]",
7923            vec![
7924                endpoint_input("DeniedWrite", EndpointInputSection::Params, "id", "Int"),
7925                endpoint_input("DeniedWrite", EndpointInputSection::Query, "page", "Int"),
7926                endpoint_input("DeniedWrite", EndpointInputSection::Body, "value", "Int"),
7927            ],
7928            "Int",
7929        );
7930        denied.middleware = vec!["deny".into()];
7931        let program = ExecutionProgram {
7932            live_resources: vec![],
7933            presences: vec![],
7934            boundaries: vec![],
7935            endpoints: vec![
7936                read,
7937                save,
7938                slow,
7939                middleware_timeout,
7940                upload,
7941                bodyless_mutation,
7942                measure,
7943                count,
7944                fast_replay,
7945                events,
7946                optional_echo,
7947                rate_save,
7948                put,
7949                patch,
7950                direct,
7951                redirect,
7952                denied,
7953            ],
7954            tasks: vec![],
7955            queues: vec![],
7956        };
7957        let generated = generate(
7958            &program,
7959            "./host.mjs",
7960            "./validators.mjs",
7961            "./middleware.mjs",
7962            "/console",
7963            &[],
7964        );
7965        for method in ["GET", "POST", "PUT", "PATCH", "DELETE"] {
7966            assert!(generated.handler.contains(&format!("method: \"{method}\"")));
7967        }
7968        assert!(
7969            generated
7970                .handler
7971                .contains("import * as __noxidServerStorageRuntime")
7972        );
7973        assert!(
7974            generated
7975                .handler
7976                .contains("const __noxidStorage = __noxidServerStorageRuntime.storage")
7977        );
7978        let root = std::env::temp_dir().join(format!(
7979            "noxid-endpoint-handler-{}-{}",
7980            std::process::id(),
7981            SystemTime::now()
7982                .duration_since(UNIX_EPOCH)
7983                .unwrap()
7984                .as_nanos()
7985        ));
7986        std::fs::create_dir_all(&root).unwrap();
7987        std::fs::write(root.join("package.json"), "{\"type\":\"module\"}\n").unwrap();
7988        std::fs::write(root.join("handler.mjs"), generated.handler).unwrap();
7989        std::fs::write(
7990            root.join("noxid-server.js"),
7991            r#"const namespaces = new Map();
7992export function storage(namespace) {
7993  if (!namespaces.has(namespace)) namespaces.set(namespace, new Map());
7994  const records = namespaces.get(namespace);
7995  const read = (key) => {
7996    const record = records.get(key);
7997    if (!record) return null;
7998    if (record.expiresAt !== null && record.expiresAt <= Date.now()) { records.delete(key); return null; }
7999    return structuredClone(record.value);
8000  };
8001  return Object.freeze({
8002    async get(key) { return read(key); },
8003    async set(key, value, options) { records.set(key, { value: structuredClone(value), expiresAt: options?.ttl === undefined ? null : Date.now() + options.ttl * 1000 }); },
8004    async delete(key) { return records.delete(key); },
8005    async list(prefix = "") { for (const key of records.keys()) read(key); return Object.freeze([...records.keys()].filter((key) => key.startsWith(prefix)).sort()); },
8006  });
8007}
8008"#,
8009        )
8010        .unwrap();
8011        std::fs::write(
8012            root.join("validators.mjs"),
8013            r#"function record(fields) { return (value) => {
8014  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("record");
8015  const trusted = Object.create(null);
8016  for (const [name, validate] of Object.entries(fields)) trusted[name] = validate(value[name]);
8017  return Object.freeze(trusted);
8018}; }
8019const int = (value) => { if (!Number.isSafeInteger(value)) throw new Error("int"); return value; };
8020const float = (value) => { if (typeof value !== "number" || !Number.isFinite(value)) throw new Error("float"); return value; };
8021const string = (value) => { if (typeof value !== "string") throw new Error("string"); return value; };
8022const boolean = (value) => { if (typeof value !== "boolean") throw new Error("boolean"); return value; };
8023const optional = (validate) => (value) => value == null ? null : validate(value);
8024const array = (validate) => (value) => {
8025  if (!Array.isArray(value)) throw new Error("array");
8026  const trusted = [];
8027  for (let index = 0; index < value.length; index += 1) trusted.push(validate(value[index]));
8028  return Object.freeze(trusted);
8029};
8030const map = (validate) => (value) => {
8031  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("map");
8032  const trusted = Object.create(null);
8033  for (const key of Object.keys(value)) trusted[key] = validate(value[key]);
8034  return Object.freeze(trusted);
8035};
8036const date = (value) => { if (typeof value !== "string") throw new Error("date"); return value; };
8037export const typeValidators = Object.freeze({
8038  "validator:endpoint.ReadItem.params": record({ id: int }),
8039  "validator:endpoint.ReadItem.query": record({ tags: (value) => { if (value !== null && (!Array.isArray(value) || value.some((item) => !Number.isSafeInteger(item)))) throw new Error("tags"); return value === null ? null : Object.freeze([...value]); }, required: (value) => { if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new Error("required"); return Object.freeze([...value]); } }),
8040  "validator:endpoint.ReadItem.result": string,
8041  "validator:endpoint.SaveItem.body": record({ value: int }),
8042  "validator:endpoint.SaveItem.result": string,
8043  "validator:endpoint.SaveItem.error": string,
8044  "validator:endpoint.SlowItem.params": record({ id: string }),
8045  "validator:endpoint.SlowItem.result": boolean,
8046  "validator:endpoint.MiddlewareTimeout.result": boolean,
8047  "validator:endpoint.Upload.body": record({ text: string }),
8048  "validator:endpoint.Upload.result": int,
8049  "validator:endpoint.BodylessMutation.result": boolean,
8050  "validator:endpoint.Measure.query": record({ value: float }),
8051  "validator:endpoint.Measure.result": float,
8052  "validator:endpoint.Count.query": record({ value: int }),
8053  "validator:endpoint.Count.result": int,
8054  "validator:endpoint.FastReplay.result": int,
8055  "validator:endpoint.Events.query": record({ since: date }),
8056  "validator:endpoint.Events.result": date,
8057  "validator:endpoint.OptionalEcho.body": record({ value: optional(string) }),
8058  "validator:endpoint.OptionalEcho.result": record({ value: optional(string), nested: record({ missing: optional(string) }), values: array(optional(string)), labels: map(optional(string)) }),
8059  "validator:endpoint.RateSave.body": record({ value: int }),
8060  "validator:endpoint.RateSave.result": int,
8061  "validator:endpoint.PutItem.result": boolean,
8062  "validator:endpoint.PatchItem.result": boolean,
8063  "validator:endpoint.DirectResponse.result": string,
8064  "validator:endpoint.RedirectResponse.result": string,
8065  "validator:endpoint.DeniedWrite.body": record({ value: int }),
8066  "validator:endpoint.DeniedWrite.params": record({ id: int }),
8067  "validator:endpoint.DeniedWrite.query": record({ page: int }),
8068  "validator:endpoint.DeniedWrite.result": int,
8069});
8070"#,
8071        )
8072        .unwrap();
8073        std::fs::write(
8074            root.join("middleware.mjs"),
8075            r#"export const globalMiddleware = Object.freeze(["global"]);
8076export const globalMiddlewareHandlers = Object.freeze({ global: async ({ environment }) => { environment.trace.push("global"); return { allow: true, headers: { "x-global": "yes", "set-cookie": ["a=1; Path=/; HttpOnly", "b=2; Path=/; HttpOnly"] }, context: { sessionId: "ctx-session" } }; } });
8077export const middleware = Object.freeze({
8078  audit: async ({ environment }) => { environment.trace.push("audit"); return { allow: true, headers: { "x-audit": "yes" } }; },
8079  slow: async ({ environment }) => { environment.trace.push("slow:start"); await new Promise((resolve) => setTimeout(resolve, 60)); environment.trace.push("slow:end"); return { allow: true }; },
8080  direct: async ({ environment, host }) => { environment.trace.push("direct"); return { respond: { status: 202, contentType: "text/plain", body: await host.endpointHealth() }, headers: { "x-direct": "yes" } }; },
8081  redirect: async ({ environment, host }) => { environment.trace.push("redirect"); return { redirect: await host.afterLoginPath(), headers: { "x-redirect": "yes" } }; },
8082  deny: async ({ environment, params, query }) => { if (params.id !== "not-int" || query.unknown !== "yes") throw new Error("raw candidates missing"); environment.trace.push("deny"); return { allow: false, headers: { "x-deny": "yes" } }; },
8083});
8084"#,
8085        )
8086        .unwrap();
8087        std::fs::write(
8088            root.join("host.mjs"),
8089            r#"let saves = 0;
8090let fastReplays = 0;
8091export const endpoints = Object.freeze({
8092  "endpoint:ReadItem@1": async ({ id, tags, required }, { environment, signal }) => { if (!(signal instanceof AbortSignal)) throw new Error("signal"); environment.trace.push("read"); return `${id}:${JSON.stringify(tags)}:${JSON.stringify(required)}`; },
8093  "endpoint:SaveItem@1": async ({ value }, { environment }) => { saves += 1; environment.trace.push(`save:${saves}`); await new Promise((resolve) => setTimeout(resolve, 20)); return { tag: value < 0 ? "Err" : "Ok", value: value === 999 ? 999 : value < 0 ? "negative" : `saved:${value}:${saves}` }; },
8094  "endpoint:SlowItem@1": async (_args, { signal, environment }) => new Promise(() => { signal.addEventListener("abort", () => { environment.aborted = signal.aborted; }, { once: true }); }),
8095  "endpoint:MiddlewareTimeout@1": async (_args, { environment }) => { environment.trace.push("middleware-timeout-host"); return true; },
8096  "endpoint:Upload@1": async ({ text }, { environment }) => { environment.uploads = (environment.uploads ?? 0) + 1; return text.length; },
8097  "endpoint:BodylessMutation@1": async (_args, { environment }) => { environment.bodylessCalls = (environment.bodylessCalls ?? 0) + 1; return true; },
8098  "endpoint:Measure@1": async ({ value }, { environment }) => { environment.numericCalls = (environment.numericCalls ?? 0) + 1; return value; },
8099  "endpoint:Count@1": async ({ value }, { environment }) => { environment.numericCalls = (environment.numericCalls ?? 0) + 1; return value; },
8100  "endpoint:FastReplay@1": async () => { fastReplays += 1; return fastReplays; },
8101  "endpoint:Events@1": async ({ since }, { environment }) => { environment.dateCalls = (environment.dateCalls ?? 0) + 1; return since; },
8102  "endpoint:OptionalEcho@1": async ({ value }, { environment }) => { environment.optionalObserved = value; return { value, nested: { missing: undefined }, values: new Array(1), labels: { missing: undefined } }; },
8103  "endpoint:RateSave@1": async ({ value }, { environment }) => { environment.rateCalls = (environment.rateCalls ?? 0) + 1; return value; },
8104  "endpoint:PutItem@1": async () => true,
8105  "endpoint:PatchItem@1": async () => true,
8106  "endpoint:DeniedWrite@1": async () => { throw new Error("DENIED_HOST_MUST_NOT_RUN"); },
8107});
8108export async function authorize({ capability, environment }) { environment.trace.push(`cap:${capability}`); return environment.deny !== true; }
8109export async function endpointHealth() { return "ready-from-host"; }
8110export async function afterLoginPath() { return "/dashboard"; }
8111export async function invalidateCache(tags, { environment }) { environment.invalidated = [...tags]; return { invalidated: tags.length }; }
8112export function fastReplayCalls() { return fastReplays; }
8113"#,
8114        )
8115        .unwrap();
8116        let script = r#"import { fetch as handle } from "./handler.mjs";
8117import { fastReplayCalls } from "./host.mjs";
8118import { storage as testStorage } from "./noxid-server.js";
8119const base = "http://noxid.test/console";
8120let env = { trace: [] };
8121for (const path of ["/console/api//items/7", "/console/api/items//7", "//console/api/items/7", "/console/api/items/7/", "/console/api/items/7///", "/console/health/"]) {
8122  env = { trace: [] };
8123  const unmatched = await handle(new Request(`http://noxid.test${path}`), env);
8124  const unmatchedBody = await unmatched.json();
8125  if (unmatched.status !== 404 || unmatchedBody.error?.code !== "BOUNDARY_NOT_FOUND" || env.trace.length !== 0) throw new Error(`empty path segment aliased an endpoint ${path}: ${unmatched.status} ${JSON.stringify(unmatchedBody)} ${JSON.stringify(env.trace)}`);
8126}
8127env = { trace: [] };
8128let response = await handle(new Request(`${base}/private/not-int?unknown=yes`, { method: "POST", body: "not-json" }), env);
8129let body = await response.json();
8130if (response.status !== 403 || body.error.code !== "ENDPOINT_MIDDLEWARE_DENIED" || response.headers.get("x-deny") !== "yes") throw new Error(`middleware denial lost to body decoding ${response.status} ${JSON.stringify(body)}`);
8131if (JSON.stringify(env.trace) !== JSON.stringify(["global", "deny"])) throw new Error(`deny-before-body order failed ${JSON.stringify(env.trace)}`);
8132env = { trace: [] };
8133response = await handle(new Request(`${base}/private/not-int?unknown=yes&bad=%FF`, { method: "POST", body: "not-json" }), env);
8134body = await response.json();
8135if (response.status !== 403 || body.error.code !== "ENDPOINT_MIDDLEWARE_DENIED" || JSON.stringify(env.trace) !== JSON.stringify(["global", "deny"])) throw new Error(`middleware denial lost to malformed query ${response.status} ${JSON.stringify(body)} ${JSON.stringify(env.trace)}`);
8136env = { trace: [] };
8137response = await handle(new Request(`${base}/api/items/7`, { headers: { "x-noxid-session-id": "one" } }), env);
8138body = await response.json();
8139if (response.status !== 200 || body.value !== "7:null:[]") throw new Error(`optional/empty arrays failed ${response.status} ${JSON.stringify(body)}`);
8140if (response.headers.get("x-global") !== "yes" || response.headers.get("x-audit") !== "yes") throw new Error("middleware headers missing");
8141if (response.headers.get("x-noxid-cache-mode") !== "swr" || response.headers.get("x-noxid-cache-revalidate") !== "60" || response.headers.get("x-noxid-cache-stale") !== "60" || response.headers.get("x-noxid-cache-tags") !== "endpoint:ReadItem@1" || !response.headers.get("cache-control")?.includes("stale-while-revalidate=60")) throw new Error("endpoint cache headers missing");
8142if (JSON.stringify(env.trace) !== JSON.stringify(["global", "audit", "cap:items.read", "read"])) throw new Error(`order ${JSON.stringify(env.trace)}`);
8143env = { trace: [] };
8144response = await handle(new Request(`${base}/_noxid/revalidate`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ tags: ["endpoint:ReadItem@1"] }) }), env);
8145body = await response.json();
8146if (response.status !== 200 || body.result?.invalidated !== 1 || JSON.stringify(env.invalidated) !== JSON.stringify(["endpoint:ReadItem@1"])) throw new Error(`endpoint tag revalidation failed ${response.status} ${JSON.stringify(body)}`);
8147response = await handle(new Request(`${base}/_noxid/revalidate`, { method: "POST", headers: { "content-type": "text/plain" }, body: JSON.stringify({ tags: ["endpoint:ReadItem@1"] }) }), env);
8148body = await response.json();
8149if (response.status !== 415 || body.error?.code !== "CACHE_INVALIDATION_CONTENT_TYPE") throw new Error(`invalidation media type boundary failed ${response.status} ${JSON.stringify(body)}`);
8150response = await handle(new Request(`${base}/_noxid/revalidate`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ tags: ["endpoint:ReadItem@1"], padding: "x".repeat(1_048_576) }) }), env);
8151body = await response.json();
8152if (response.status !== 413 || body.error?.code !== "CACHE_INVALIDATION_BODY_TOO_LARGE") throw new Error(`invalidation byte boundary failed ${response.status} ${JSON.stringify(body)}`);
8153env = { trace: [] };
8154response = await handle(new Request(`${base}/api/items/8?tags=%5B%5D&required=%5B%5D`, { headers: { "x-noxid-session-id": "one" } }), env);
8155body = await response.json();
8156if (response.status !== 200 || body.value !== "8:[]:[]") throw new Error("optional Some(empty array) collapsed into None");
8157response = await handle(new Request(`${base}/api/items/9?tags=%5B1%2C2%5D&required=%5B%22%22%5D`, { headers: { "x-noxid-session-id": "one" } }), env);
8158body = await response.json();
8159if (response.status !== 200 || body.value !== "9:[1,2]:[\"\"]") throw new Error("JSON array query decoding failed");
8160for (const query of ["required=%FF", "%FF=value"]) {
8161  env = { trace: [] };
8162  response = await handle(new Request(`${base}/api/items/12?${query}`), env);
8163  body = await response.json();
8164  if (response.status !== 400 || body.error.code !== "ENDPOINT_QUERY_ENCODING_INVALID" || JSON.stringify(env.trace) !== JSON.stringify(["global", "audit", "cap:items.read"])) throw new Error(`malformed query crossed endpoint boundary ${query} ${response.status} ${JSON.stringify(body)} ${JSON.stringify(env.trace)}`);
8165}
8166response = await handle(new Request(`${base}/api/items/11`, { headers: { "x-noxid-session-id": "denied" } }), { trace: [], deny: true });
8167if (response.status !== 403) throw new Error("capability denial failed");
8168response = await handle(new Request(`${base}/api/items/10`, { headers: { "x-noxid-session-id": "one" } }), { trace: [] });
8169if (response.status !== 429 || response.headers.get("retry-after") === null) throw new Error("rate limit failed");
8170response = await handle(new Request(`${base}/api/items/7`, { method: "POST" }), { trace: [] });
8171if (response.status !== 405 || response.headers.get("allow") !== "DELETE, GET") throw new Error(`method routing failed ${response.status} ${response.headers.get("allow")}`);
8172const saveRequest = () => new Request(`${base}/api/items`, { method: "POST", headers: { "content-type": "application/json", "idempotency-key": "same" }, body: JSON.stringify({ value: 4 }) });
8173env = { trace: [] };
8174response = await handle(new Request(`${base}/api/items`, { method: "POST", body: JSON.stringify({ value: 4 }) }), env);
8175if (response.status !== 415) throw new Error("content type refusal failed");
8176if (JSON.stringify(env.trace) !== JSON.stringify(["global", "audit"])) throw new Error(`allowed middleware did not precede body validation ${JSON.stringify(env.trace)}`);
8177response = await handle(new Request(`${base}/api/items`, { method: "POST", headers: { "content-type": "application/json", "content-length": "1048577", "idempotency-key": "large" }, body: "{}" }), { trace: [] });
8178if (response.status !== 413) throw new Error("body size refusal failed");
8179response = await handle(new Request(`${base}/api/items`, { method: "POST", headers: { "content-type": "application/json", "idempotency-key": "typed" }, body: JSON.stringify({ value: "wrong" }) }), { trace: [] });
8180if (response.status !== 422) throw new Error("body type refusal failed");
8181response = await handle(new Request(`${base}/api/items`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ value: 4 }) }), { trace: [] });
8182if (response.status !== 400) throw new Error("idempotency key refusal failed");
8183env = { trace: [] };
8184const [first, second] = await Promise.all([handle(saveRequest(), env), handle(saveRequest(), env)]);
8185const [firstText, secondText] = await Promise.all([first.text(), second.text()]);
8186if (first.status !== 200 || second.status !== 200 || firstText !== secondText) throw new Error("concurrent replay body/status failed");
8187if (JSON.parse(firstText).value !== "saved:4:1") throw new Error(`body refusals invoked host before allowed request ${firstText}`);
8188if ([...first.headers].toString() !== [...second.headers].toString() || first.headers.get("x-global") !== "yes" || first.headers.get("x-audit") !== "yes") throw new Error("full replay headers failed");
8189if (typeof first.headers.getSetCookie === "function" && (first.headers.getSetCookie().length !== 2 || second.headers.getSetCookie().length !== 2)) throw new Error("full replay set-cookie headers failed");
8190if (env.trace.filter((entry) => entry.startsWith("save:")).length !== 1) throw new Error(`duplicate implementation ran ${JSON.stringify(env.trace)}`);
8191response = await handle(saveRequest(), { trace: [] });
8192if (await response.text() !== firstText) throw new Error("sequential replay failed");
8193response = await handle(new Request(`${base}/api/items`, { method: "POST", headers: { "content-type": "application/json", "idempotency-key": "err" }, body: JSON.stringify({ value: -1 }) }), { trace: [] });
8194body = await response.json();
8195if (response.status !== 422 || body.error.value !== "negative") throw new Error("typed Result Err failed");
8196response = await handle(new Request(`${base}/api/items`, { method: "POST", headers: { "content-type": "application/json", "idempotency-key": "bad-result" }, body: JSON.stringify({ value: 999 }) }), { trace: [] });
8197body = await response.json();
8198if (response.status !== 500 || body.error.code !== "ENDPOINT_RESULT_TYPE") throw new Error("result validation refusal failed");
8199env = { trace: [] };
8200response = await handle(new Request(`${base}/api/items/a`, { method: "DELETE" }), env);
8201body = await response.json();
8202if (response.status !== 504 || body.error.code !== "ENDPOINT_TIMEOUT" || env.aborted !== true) throw new Error(`timeout did not abort signal ${response.status} ${JSON.stringify(body)} ${env.aborted}`);
8203env = { trace: [] };
8204const middlewareStarted = Date.now();
8205response = await handle(new Request(`${base}/api/middleware-timeout`), env);
8206body = await response.json();
8207const middlewareElapsed = Date.now() - middlewareStarted;
8208if (response.status !== 504 || body.error.code !== "ENDPOINT_TIMEOUT" || middlewareElapsed >= 50) throw new Error(`middleware escaped deadline ${response.status} ${middlewareElapsed} ${JSON.stringify(body)}`);
8209await new Promise((resolve) => setTimeout(resolve, 70));
8210if (env.trace.includes("middleware-timeout-host")) throw new Error(`host ran after middleware timeout ${JSON.stringify(env.trace)}`);
8211const oversizedText = "é".repeat(600_000);
8212const oversizedWire = JSON.stringify({ text: oversizedText });
8213if (new TextEncoder().encode(oversizedWire).byteLength <= 1_048_576 || oversizedWire.length >= 1_048_576) throw new Error("UTF-8 byte fixture is invalid");
8214env = { trace: [], uploads: 0 };
8215response = await handle(new Request(`${base}/api/upload`, { method: "POST", headers: { "content-type": "application/json" }, body: oversizedWire }), env);
8216body = await response.json();
8217if (response.status !== 413 || body.error.code !== "ENDPOINT_BODY_TOO_LARGE" || env.uploads !== 0) throw new Error(`UTF-8 byte limit failed ${response.status} ${JSON.stringify(body)} ${env.uploads}`);
8218const oversizedUndeclaredWire = JSON.stringify({ ignored: "x".repeat(1_048_576) });
8219env = { trace: [], bodylessCalls: 0 };
8220response = await handle(new Request(`${base}/api/bodyless`, { method: "POST", headers: { "content-type": "application/json" }, body: oversizedUndeclaredWire }), env);
8221body = await response.json();
8222if (response.status !== 413 || body.error.code !== "ENDPOINT_BODY_TOO_LARGE" || env.bodylessCalls !== 0) throw new Error(`bodyless mutation skipped transport cap ${response.status} ${JSON.stringify(body)} ${env.bodylessCalls}`);
8223response = await handle(new Request(`${base}/api/bodyless`, { method: "POST" }), env);
8224if (response.status !== 200 || env.bodylessCalls !== 1) throw new Error(`absent empty body was not accepted ${response.status} ${env.bodylessCalls}`);
8225response = await handle(new Request(`${base}/api/bodyless`, { method: "POST", headers: { "content-type": "application/json" }, body: "{}" }), env);
8226if (response.status !== 200 || env.bodylessCalls !== 2) throw new Error(`declared empty JSON body was not accepted ${response.status} ${env.bodylessCalls}`);
8227response = await handle(new Request(`${base}/api/bodyless`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ ignored: true }) }), env);
8228body = await response.json();
8229if (response.status !== 400 || body.error.code !== "ENDPOINT_BODY_UNKNOWN" || env.bodylessCalls !== 2) throw new Error(`undeclared body field reached host ${response.status} ${JSON.stringify(body)} ${env.bodylessCalls}`);
8230for (const value of ["0x10", "0b10", "0o10", "Infinity", "1e3", "+1.0", "1", ".5", "1.", "01.5", " "]) {
8231  env = { trace: [], numericCalls: 0 };
8232  response = await handle(new Request(`${base}/api/measure?value=${encodeURIComponent(value)}`), env);
8233  body = await response.json();
8234  if (response.status !== 422 || body.error.code !== "ENDPOINT_QUERY_TYPE" || env.numericCalls !== 0) throw new Error(`noncanonical Float passed ${JSON.stringify(value)} ${response.status} ${JSON.stringify(body)} ${env.numericCalls}`);
8235}
8236env = { trace: [], numericCalls: 0 };
8237response = await handle(new Request(`${base}/api/measure?value=-1.25`), env);
8238body = await response.json();
8239if (response.status !== 200 || body.value !== -1.25 || env.numericCalls !== 1) throw new Error(`canonical Float failed ${response.status} ${JSON.stringify(body)} ${env.numericCalls}`);
8240for (const value of ["0x10", "1.0", "1e2", "+1", "01", " "]) {
8241  env = { trace: [], numericCalls: 0 };
8242  response = await handle(new Request(`${base}/api/count?value=${encodeURIComponent(value)}`), env);
8243  body = await response.json();
8244  if (response.status !== 422 || body.error.code !== "ENDPOINT_QUERY_TYPE" || env.numericCalls !== 0) throw new Error(`noncanonical Int passed ${JSON.stringify(value)} ${response.status} ${JSON.stringify(body)} ${env.numericCalls}`);
8245}
8246env = { trace: [], numericCalls: 0 };
8247response = await handle(new Request(`${base}/api/count?value=-12`), env);
8248body = await response.json();
8249if (response.status !== 200 || body.value !== -12 || env.numericCalls !== 1) throw new Error(`canonical Int failed ${response.status} ${JSON.stringify(body)} ${env.numericCalls}`);
8250for (const since of ["01/02/2020", "2021-02-29T00:00:00Z", "2020-01-01T24:00:00Z", "2020-01-01T00:00:00+00:00"]) {
8251  env = { trace: [], dateCalls: 0 };
8252  response = await handle(new Request(`${base}/api/events?since=${encodeURIComponent(since)}`), env);
8253  body = await response.json();
8254  if (response.status !== 422 || body.error.code !== "ENDPOINT_QUERY_TYPE" || env.dateCalls !== 0) throw new Error(`invalid Date crossed endpoint boundary ${since} ${response.status} ${JSON.stringify(body)} ${env.dateCalls}`);
8255}
8256env = { trace: [], dateCalls: 0 };
8257response = await handle(new Request(`${base}/api/events?since=${encodeURIComponent("2020-02-29T23:59:59.123Z")}`), env);
8258body = await response.json();
8259if (response.status !== 200 || body.value !== "2020-02-29T23:59:59.123Z" || env.dateCalls !== 1) throw new Error(`valid UTC Date failed ${response.status} ${JSON.stringify(body)} ${env.dateCalls}`);
8260env = { trace: [], optionalObserved: "unset" };
8261response = await handle(new Request(`${base}/api/optional`, { method: "POST", headers: { "content-type": "application/json" }, body: "{}" }), env);
8262body = await response.json();
8263if (response.status !== 200 || env.optionalObserved !== null || !Object.hasOwn(body.value, "value") || body.value.value !== null || body.value.nested.missing !== null || body.value.values[0] !== null || body.value.labels.missing !== null) throw new Error(`recursive Optional None was not serialized as null ${response.status} ${JSON.stringify(body)} ${String(env.optionalObserved)}`);
8264env = { trace: [], rateCalls: 0 };
8265response = await handle(new Request(`${base}/api/rate-save`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ value: "wrong" }) }), env);
8266body = await response.json();
8267if (response.status !== 422 || body.error.code !== "ENDPOINT_INPUT_TYPE" || env.rateCalls !== 0) throw new Error(`rate identity preempted invalid input ${response.status} ${JSON.stringify(body)} ${env.rateCalls}`);
8268response = await handle(new Request(`${base}/api/rate-save`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ value: 1 }) }), env);
8269body = await response.json();
8270if (response.status !== 403 || body.error.code !== "ENDPOINT_RATE_IDENTITY_REQUIRED" || env.rateCalls !== 0) throw new Error(`valid input did not reach rate identity boundary ${response.status} ${JSON.stringify(body)} ${env.rateCalls}`);
8271env = { trace: [] };
8272response = await handle(new Request(`${base}/api/items/%FF`), env);
8273body = await response.json();
8274if (response.status !== 400 || body.error.code !== "ENDPOINT_PATH_ENCODING_INVALID" || env.trace.length !== 0) throw new Error(`malformed dynamic path escaped endpoint ownership ${response.status} ${JSON.stringify(body)} ${JSON.stringify(env.trace)}`);
8275const fastReplayRequest = (key) => new Request(`${base}/api/fast-replay`, { method: "POST", headers: { "idempotency-key": key } });
8276for (let index = 0; index < 1024; index += 1) {
8277  response = await handle(fastReplayRequest(`capacity-${index}`), { trace: [] });
8278  if (response.status !== 200) throw new Error(`idempotency capacity fill failed ${index} ${response.status}`);
8279}
8280if (fastReplayCalls() !== 1024) throw new Error(`capacity fill executed wrong count ${fastReplayCalls()}`);
8281response = await handle(fastReplayRequest("capacity-0"), { trace: [] });
8282body = await response.json();
8283if (response.status !== 200 || body.value !== 1 || fastReplayCalls() !== 1024) throw new Error(`capacity replay evicted itself ${response.status} ${JSON.stringify(body)} ${fastReplayCalls()}`);
8284response = await handle(fastReplayRequest("capacity-new"), { trace: [] });
8285if (response.status !== 200 || fastReplayCalls() !== 1025) throw new Error(`new-key capacity admission failed ${response.status} ${fastReplayCalls()}`);
8286const idempotencyStorage = testStorage("noxid:endpoint-idempotency");
8287for (const key of await idempotencyStorage.list()) await idempotencyStorage.delete(key);
8288for (let index = 0; index < 1024; index += 1) await idempotencyStorage.set(`corrupt-${index}`, { malformed: true });
8289response = await handle(fastReplayRequest("after-corruption"), { trace: [] });
8290if (response.status !== 200 || (await idempotencyStorage.list()).length !== 1) throw new Error(`corrupt records defeated idempotency capacity ${response.status} ${(await idempotencyStorage.list()).length}`);
8291for (const key of await idempotencyStorage.list()) await idempotencyStorage.delete(key);
8292await idempotencyStorage.set("endpoint:SaveItem@1\nsession:ctx-session\ncorrupt-snapshot", { created: Date.now(), status: 200, headers: [], body: "%%%" });
8293response = await handle(new Request(`${base}/api/items`, { method: "POST", headers: { "content-type": "application/json", "idempotency-key": "corrupt-snapshot" }, body: JSON.stringify({ value: 10 }) }), { trace: [] });
8294body = await response.json();
8295if (response.status !== 500 || body.error.code !== "ENDPOINT_STORAGE_FAILED") throw new Error(`invalid stored response escaped containment ${response.status} ${JSON.stringify(body)}`);
8296response = await handle(new Request(`${base}/api/put`, { method: "PUT" }), { trace: [] });
8297if (response.status !== 200) throw new Error("put failed");
8298response = await handle(new Request(`${base}/api/patch`, { method: "PATCH" }), { trace: [] });
8299if (response.status !== 200) throw new Error("patch failed");
8300response = await handle(new Request(`${base}/api/items/nope`, { method: "DELETE" }), { trace: [] });
8301if (response.status !== 504) throw new Error("delete matcher failed");
8302response = await handle(new Request(`${base}/api/missing`), { trace: [] });
8303if (response.status !== 404) throw new Error("unknown endpoint failed");
8304env = { trace: [] };
8305response = await handle(new Request(`${base}/health`), env);
8306if (response.status !== 202 || await response.text() !== "ready-from-host" || response.headers.get("x-global") !== "yes" || response.headers.get("x-direct") !== "yes") throw new Error("host-backed direct middleware response/headers failed");
8307if (JSON.stringify(env.trace) !== JSON.stringify(["global", "direct"])) throw new Error(`direct middleware order failed ${JSON.stringify(env.trace)}`);
8308env = { trace: [] };
8309response = await handle(new Request(`${base}/login`), env);
8310if (response.status !== 307 || response.headers.get("location") !== "/console/dashboard" || response.headers.get("x-global") !== "yes" || response.headers.get("x-redirect") !== "yes") throw new Error("host-backed middleware redirect/headers failed");
8311if (JSON.stringify(env.trace) !== JSON.stringify(["global", "redirect"])) throw new Error(`redirect middleware order failed ${JSON.stringify(env.trace)}`);
8312"#;
8313        let output = Command::new("node")
8314            .args(["--input-type=module", "-e", script])
8315            .current_dir(&root)
8316            .output()
8317            .expect("Node.js is required for generated endpoint handler tests");
8318        let _ = std::fs::remove_dir_all(&root);
8319        assert!(
8320            output.status.success(),
8321            "{}\n{}",
8322            String::from_utf8_lossy(&output.stdout),
8323            String::from_utf8_lossy(&output.stderr)
8324        );
8325    }
8326
8327    #[test]
8328    fn generated_stream_endpoint_validates_frames_resumes_times_out_and_cancels() {
8329        let mut events = endpoint_boundary(
8330            "Events",
8331            EndpointMethod::Get,
8332            "/api/events",
8333            vec![endpoint_input(
8334                "Events",
8335                EndpointInputSection::Query,
8336                "project",
8337                "String",
8338            )],
8339            "Int",
8340        );
8341        events.kind = EndpointKind::Stream;
8342        events.capabilities = vec!["events.read".into()];
8343        events.timeout_ms = 500;
8344        let mut invalid = endpoint_boundary(
8345            "InvalidEvents",
8346            EndpointMethod::Get,
8347            "/api/invalid-events",
8348            vec![],
8349            "Int",
8350        );
8351        invalid.kind = EndpointKind::Stream;
8352        invalid.timeout_ms = 500;
8353        let mut slow = endpoint_boundary(
8354            "SlowEvents",
8355            EndpointMethod::Get,
8356            "/api/slow-events",
8357            vec![],
8358            "Int",
8359        );
8360        slow.kind = EndpointKind::Stream;
8361        slow.timeout_ms = 20;
8362        let mut cancellable = endpoint_boundary(
8363            "CancellableEvents",
8364            EndpointMethod::Get,
8365            "/api/cancellable-events",
8366            vec![],
8367            "Int",
8368        );
8369        cancellable.kind = EndpointKind::Stream;
8370        cancellable.timeout_ms = 500;
8371        let mut missing = endpoint_boundary(
8372            "MissingEvents",
8373            EndpointMethod::Get,
8374            "/api/missing-events",
8375            vec![],
8376            "Int",
8377        );
8378        missing.kind = EndpointKind::Stream;
8379        missing.timeout_ms = 500;
8380        let mut result_events = endpoint_boundary(
8381            "ResultEvents",
8382            EndpointMethod::Get,
8383            "/api/result-events",
8384            vec![],
8385            "Result<Int, String>",
8386        );
8387        result_events.kind = EndpointKind::Stream;
8388        result_events.timeout_ms = 500;
8389        let program = ExecutionProgram {
8390            live_resources: vec![],
8391            presences: vec![],
8392            boundaries: vec![],
8393            endpoints: vec![events, invalid, slow, cancellable, missing, result_events],
8394            tasks: vec![],
8395            queues: vec![],
8396        };
8397        let output = generate(
8398            &program,
8399            "./host.mjs",
8400            "./validators.mjs",
8401            "./middleware.mjs",
8402            "/",
8403            &[],
8404        );
8405        assert!(
8406            output
8407                .handler
8408                .contains("ENDPOINT_STREAM_HEARTBEAT_MS = 15_000")
8409        );
8410        assert!(output.handler.contains("ENDPOINT_STREAM_MAX_EVENTS = 256"));
8411        assert!(
8412            !output
8413                .handler
8414                .contains("validator:endpoint.ResultEvents.error")
8415        );
8416
8417        let unique = SystemTime::now()
8418            .duration_since(UNIX_EPOCH)
8419            .unwrap()
8420            .as_nanos();
8421        let root = std::env::temp_dir().join(format!(
8422            "noxid-stream-endpoint-handler-{}-{unique}",
8423            std::process::id()
8424        ));
8425        std::fs::create_dir_all(&root).unwrap();
8426        std::fs::write(root.join("handler.mjs"), output.handler).unwrap();
8427        std::fs::write(
8428            root.join("validators.mjs"),
8429            r#"const int = (value) => { if (!Number.isSafeInteger(value)) throw new Error("Int"); return value; };
8430export const typeValidators = Object.freeze({
8431  "validator:endpoint.Events.query": (value) => { if (!value || typeof value.project !== "string") throw new Error("project"); return Object.freeze({ project: value.project }); },
8432  "validator:endpoint.Events.result": int,
8433  "validator:endpoint.InvalidEvents.result": int,
8434  "validator:endpoint.SlowEvents.result": int,
8435  "validator:endpoint.CancellableEvents.result": int,
8436  "validator:endpoint.MissingEvents.result": int,
8437});
8438"#,
8439        )
8440        .unwrap();
8441        std::fs::write(
8442            root.join("middleware.mjs"),
8443            "export const middleware = Object.freeze({});\nexport const globalMiddleware = Object.freeze([]);\n",
8444        )
8445        .unwrap();
8446        std::fs::write(
8447            root.join("host.mjs"),
8448            r#"export const endpoints = Object.freeze({
8449  "endpoint:Events@1": async function* (_args, context) {
8450    context.environment.eventCalls = (context.environment.eventCalls ?? 0) + 1;
8451    yield 1; yield 2; yield 3;
8452  },
8453  "endpoint:InvalidEvents@1": async function* () { yield 1; yield "bad"; yield 3; },
8454  "endpoint:SlowEvents@1": async function* () { yield 1; await new Promise((resolve) => setTimeout(resolve, 100)); yield 2; },
8455  "endpoint:CancellableEvents@1": async function* (_args, context) {
8456    try { yield 1; await new Promise((resolve) => setTimeout(resolve, 100)); yield 2; }
8457    finally { context.environment.cancelled = (context.environment.cancelled ?? 0) + 1; }
8458  },
8459});
8460export async function authorize({ capability, request }) {
8461  return capability === "events.read" && request.headers.get("x-events") === "yes";
8462}
8463"#,
8464        )
8465        .unwrap();
8466        let script = r#"import { fetch as handle } from "./handler.mjs";
8467const base = "http://noxid.test";
8468let environment = { eventCalls: 0 };
8469environment.sessionId = "session-a";
8470let response = await handle(new Request(`${base}/api/events?project=A`), environment);
8471let denied = await response.json();
8472if (response.status !== 403 || denied.error.code !== "ENDPOINT_CAPABILITY_DENIED" || environment.eventCalls !== 0) throw new Error(`stream authorization failed ${response.status} ${JSON.stringify(denied)}`);
8473
8474response = await handle(new Request(`${base}/api/events?project=A`, { headers: { "x-events": "yes" } }), environment);
8475if (response.status !== 200 || response.headers.get("content-type") !== "text/event-stream; charset=utf-8" || response.headers.get("cache-control") !== "no-store") throw new Error("SSE headers failed");
8476const first = await response.text();
8477if (environment.eventCalls !== 1) throw new Error(`host call count ${environment.eventCalls}`);
8478const ids = [...first.matchAll(/^id: ([^\n]+)$/gm)].map((match) => match[1]);
8479if (ids.length !== 3 || !ids.every((id, index) => id.endsWith(`:${index + 1}`))) throw new Error(`SSE ids failed ${JSON.stringify(ids)} ${first}`);
8480if (!first.includes("event: message\ndata: 1\n\n") || !first.includes("event: message\ndata: 3\n\n")) throw new Error(`typed SSE frames failed ${first}`);
8481
8482response = await handle(new Request(`${base}/api/events?project=A`, { headers: { "x-events": "yes", "last-event-id": ids[0] } }), environment);
8483const replay = await response.text();
8484if (environment.eventCalls !== 1 || replay.includes("data: 1\n") || !replay.includes("data: 2\n") || !replay.includes("data: 3\n")) throw new Error(`resume failed calls=${environment.eventCalls} ${replay}`);
8485response = await handle(new Request(`${base}/api/events?project=B`, { headers: { "x-events": "yes", "last-event-id": ids[0] } }), environment);
8486const crossArgument = await response.text();
8487if (!crossArgument.includes("STREAM_RESUME_UNAVAILABLE") || !crossArgument.includes("request-mismatch") || environment.eventCalls !== 1 || crossArgument.includes("data: 2\n")) throw new Error(`cross-argument replay escaped isolation ${crossArgument}`);
8488const otherIdentity = { eventCalls: 0, sessionId: "session-b" };
8489response = await handle(new Request(`${base}/api/events?project=A`, { headers: { "x-events": "yes", "last-event-id": ids[0] } }), otherIdentity);
8490const crossIdentity = await response.text();
8491if (!crossIdentity.includes("STREAM_RESUME_UNAVAILABLE") || !crossIdentity.includes("request-mismatch") || otherIdentity.eventCalls !== 0 || crossIdentity.includes("data: 2\n")) throw new Error(`cross-identity replay escaped isolation ${crossIdentity}`);
8492const disconnected = new AbortController();
8493disconnected.abort("already disconnected");
8494const disconnectedEnvironment = { eventCalls: 0, sessionId: "session-a" };
8495response = await handle(new Request(`${base}/api/events?project=A`, { headers: { "x-events": "yes" }, signal: disconnected.signal }), disconnectedEnvironment);
8496if (response.status !== 200 || await response.text() !== "" || disconnectedEnvironment.eventCalls !== 0) throw new Error(`already-aborted request invoked stream host ${disconnectedEnvironment.eventCalls}`);
8497response = await handle(new Request(`${base}/api/events?project=A`, { headers: { "x-events": "yes", "last-event-id": "forged" } }), environment);
8498const malformed = await response.text();
8499if (!malformed.includes("event: noxid-error") || !malformed.includes("STREAM_RESUME_UNAVAILABLE") || environment.eventCalls !== 1) throw new Error(`malformed resume did not fail closed ${malformed}`);
8500
8501response = await handle(new Request(`${base}/api/invalid-events`));
8502const invalid = await response.text();
8503if (!invalid.includes("data: 1\n") || invalid.includes("data: 3\n") || !invalid.includes("event: noxid-error") || !invalid.includes("STREAM_EVENT_TYPE")) throw new Error(`invalid event did not terminate ${invalid}`);
8504
8505response = await handle(new Request(`${base}/api/missing-events`));
8506const missing = await response.text();
8507if (response.status !== 200 || !missing.includes("event: noxid-error") || !missing.includes("STREAM_IMPLEMENTATION_MISSING")) throw new Error(`missing implementation was not structured SSE ${response.status} ${missing}`);
8508
8509const started = Date.now();
8510response = await handle(new Request(`${base}/api/slow-events`));
8511const timedOut = await response.text();
8512const elapsed = Date.now() - started;
8513if (!timedOut.includes("data: 1\n") || timedOut.includes("data: 2\n") || !timedOut.includes("ENDPOINT_TIMEOUT") || elapsed >= 80) throw new Error(`connection timeout failed elapsed=${elapsed} ${timedOut}`);
8514
8515environment = { cancelled: 0 };
8516response = await handle(new Request(`${base}/api/cancellable-events`), environment);
8517const reader = response.body.getReader();
8518const firstChunk = await reader.read();
8519if (firstChunk.done || !new TextDecoder().decode(firstChunk.value).includes("data: 1")) throw new Error("cancellable stream did not start");
8520await reader.cancel("client disconnected");
8521await new Promise((resolve) => setTimeout(resolve, 10));
8522if (environment.cancelled !== 1) throw new Error(`disconnect did not cancel iterator ${environment.cancelled}`);
8523"#;
8524        let output = Command::new("node")
8525            .args(["--input-type=module", "-e", script])
8526            .current_dir(&root)
8527            .output()
8528            .expect("Node.js is required for generated stream endpoint tests");
8529        let _ = std::fs::remove_dir_all(&root);
8530        assert!(
8531            output.status.success(),
8532            "{}\n{}",
8533            String::from_utf8_lossy(&output.stdout),
8534            String::from_utf8_lossy(&output.stderr)
8535        );
8536    }
8537
8538    #[test]
8539    fn generated_mcp_surface_lists_exact_openapi_tools_and_reuses_endpoint_pipeline() {
8540        let mut widget = endpoint_boundary(
8541            "GetWidget",
8542            EndpointMethod::Post,
8543            "/api/widgets/[id]",
8544            vec![
8545                endpoint_input("GetWidget", EndpointInputSection::Params, "id", "String"),
8546                endpoint_input(
8547                    "GetWidget",
8548                    EndpointInputSection::Query,
8549                    "tags",
8550                    "Optional<Array<String>>",
8551                ),
8552                endpoint_input(
8553                    "GetWidget",
8554                    EndpointInputSection::Body,
8555                    "input",
8556                    "WidgetInput",
8557                ),
8558            ],
8559            "Result<Widget, String>",
8560        );
8561        widget.description = Some("Fetch one typed widget".into());
8562        widget.capabilities = vec!["widgets.read".into()];
8563        widget.middleware = vec!["audit".into()];
8564        widget.inputs[2].type_id = Some(SemanticId::type_definition("Api", "WidgetInput"));
8565        widget.result.type_id = Some(SemanticId::type_definition("Api", "Widget"));
8566        let mut events = endpoint_boundary(
8567            "WidgetEvents",
8568            EndpointMethod::Get,
8569            "/api/widget-events",
8570            vec![],
8571            "Widget",
8572        );
8573        events.kind = EndpointKind::Stream;
8574        events.description = Some("Watch typed widgets".into());
8575        events.timeout_ms = 200;
8576        events.result.type_id = Some(SemanticId::type_definition("Api", "Widget"));
8577        let program = ExecutionProgram {
8578            live_resources: vec![],
8579            presences: vec![],
8580            boundaries: vec![],
8581            endpoints: vec![widget, events],
8582            tasks: vec![],
8583            queues: vec![],
8584        };
8585        let openapi = r##"{"openapi":"3.1.0","info":{"title":"MCP fixture","version":"0.1.0"},"paths":{"/api/widgets/{id}":{"post":{"operationId":"GetWidget","description":"Fetch one typed widget","x-noxid-endpoint-id":"endpoint:GetWidget@1","x-noxid-signature":"GetWidget(params { id: String }, query { tags: Optional<Array<String>> }, body { input: WidgetInput }) -> Result<Widget, String>","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"tags","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"input":{"$ref":"#/components/schemas/WidgetInput"}},"required":["input"],"additionalProperties":false}}}},"responses":{"200":{"description":"Typed endpoint result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"const":true},"value":{"$ref":"#/components/schemas/Widget"}},"required":["ok","value"],"additionalProperties":false}}}}}}},"/api/widget-events":{"get":{"operationId":"WidgetEvents","description":"Watch typed widgets","x-noxid-endpoint-id":"endpoint:WidgetEvents@1","x-noxid-signature":"WidgetEvents() -> Stream<Widget>","responses":{"200":{"description":"SSE event stream","content":{"text/event-stream":{"schema":{"type":"string"},"x-noxid-event-schema":{"$ref":"#/components/schemas/Widget"}}}}}}}},"components":{"schemas":{"Nested":{"type":"object","properties":{"scores":{"type":"array","items":{"type":"integer"}}},"required":["scores"],"additionalProperties":false},"WidgetInput":{"type":"object","properties":{"label":{"type":"string"},"note":{"anyOf":[{"type":"string"},{"type":"null"}]},"nested":{"$ref":"#/components/schemas/Nested"}},"required":["label","nested"],"additionalProperties":false},"Widget":{"type":"object","properties":{"name":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"nested":{"$ref":"#/components/schemas/Nested"}},"required":["name","tags","nested"],"additionalProperties":false},"NoxidErrorResponse":{"type":"object","properties":{"ok":{"const":false},"error":{"type":"object"}},"required":["ok","error"]}}}}"##;
8586        let enabled = generate_with_agent_surfaces(
8587            &program,
8588            "./host.mjs",
8589            "./validators.mjs",
8590            "./middleware.mjs",
8591            None,
8592            "/console",
8593            &[],
8594            AgentSurfaceOptions {
8595                openapi_json: Some(openapi),
8596                serve_openapi: false,
8597                mcp: true,
8598                principal_authority_import: None,
8599            },
8600        );
8601        let docs = generate_with_agent_surfaces(
8602            &program,
8603            "./host.mjs",
8604            "./validators.mjs",
8605            "./middleware.mjs",
8606            None,
8607            "/console",
8608            &[],
8609            AgentSurfaceOptions {
8610                openapi_json: Some(openapi),
8611                serve_openapi: true,
8612                mcp: false,
8613                principal_authority_import: None,
8614            },
8615        );
8616        let off = generate(
8617            &program,
8618            "./host.mjs",
8619            "./validators.mjs",
8620            "./middleware.mjs",
8621            "/console",
8622            &[],
8623        );
8624
8625        let unique = SystemTime::now()
8626            .duration_since(UNIX_EPOCH)
8627            .unwrap()
8628            .as_nanos();
8629        let root = std::env::temp_dir().join(format!(
8630            "noxid-mcp-endpoint-handler-{}-{unique}",
8631            std::process::id()
8632        ));
8633        std::fs::create_dir_all(&root).unwrap();
8634        std::fs::write(root.join("handler.mjs"), enabled.handler).unwrap();
8635        std::fs::write(root.join("handler-docs.mjs"), docs.handler).unwrap();
8636        std::fs::write(root.join("handler-off.mjs"), off.handler).unwrap();
8637        std::fs::write(root.join("openapi.json"), openapi).unwrap();
8638        std::fs::write(
8639            root.join("validators.mjs"),
8640            r#"const fail = (message) => { throw new Error(message); };
8641const nested = (value) => value && Array.isArray(value.scores) && value.scores.every(Number.isSafeInteger) ? Object.freeze({ scores: Object.freeze([...value.scores]) }) : fail("Nested");
8642const input = (value) => value && typeof value.label === "string" && (value.note === null || value.note === undefined || typeof value.note === "string") ? Object.freeze({ label: value.label, note: value.note ?? null, nested: nested(value.nested) }) : fail("WidgetInput");
8643const widget = (value) => value && typeof value.name === "string" && Array.isArray(value.tags) && value.tags.every((tag) => typeof tag === "string") ? Object.freeze({ name: value.name, tags: Object.freeze([...value.tags]), nested: nested(value.nested) }) : fail("Widget");
8644export const typeValidators = Object.freeze({
8645  "validator:endpoint.GetWidget.params": (value) => typeof value.id === "string" ? Object.freeze({ id: value.id }) : fail("params"),
8646  "validator:endpoint.GetWidget.query": (value) => value.tags === null || (Array.isArray(value.tags) && value.tags.every((tag) => typeof tag === "string")) ? Object.freeze({ tags: value.tags === null ? null : Object.freeze([...value.tags]) }) : fail("query"),
8647  "validator:endpoint.GetWidget.body": (value) => Object.freeze({ input: input(value.input) }),
8648  "validator:endpoint.GetWidget.result": widget,
8649  "validator:endpoint.GetWidget.error": (value) => typeof value === "string" ? value : fail("error"),
8650  "validator:endpoint.WidgetEvents.result": widget,
8651});
8652"#,
8653        )
8654        .unwrap();
8655        std::fs::write(
8656            root.join("middleware.mjs"),
8657            r#"export const globalMiddleware = Object.freeze([]);
8658export const middleware = Object.freeze({ audit: async ({ request, environment }) => {
8659  environment.trace.push(`middleware:${new URL(request.url).pathname}:${request.headers.get("x-session")}`);
8660  return { allow: true, context: { sessionId: request.headers.get("x-session") } };
8661} });
8662"#,
8663        )
8664        .unwrap();
8665        std::fs::write(
8666            root.join("host.mjs"),
8667            r#"export const endpoints = Object.freeze({
8668  "endpoint:GetWidget@1": async ({ id, tags, input }, context) => {
8669    context.environment.trace.push(`host:${id}:${context.middlewareContext.sessionId}`);
8670    context.environment.hostCalls = (context.environment.hostCalls ?? 0) + 1;
8671    return { tag: "Ok", value: { name: input.label, tags: tags ?? [], nested: input.nested } };
8672  },
8673  "endpoint:WidgetEvents@1": async function* () {
8674    yield { name: "one", tags: ["live"], nested: { scores: [1] } };
8675    yield { name: "two", tags: [], nested: { scores: [2, 3] } };
8676  },
8677});
8678export async function authorize({ capability, request, environment }) {
8679  environment.trace.push(`authorize:${capability}`);
8680  return request.headers.get("x-capability") === capability;
8681}
8682"#,
8683        )
8684        .unwrap();
8685        let script = r##"import { readFile } from "node:fs/promises";
8686import { fetch as handle } from "./handler.mjs";
8687import { fetch as handleDocs } from "./handler-docs.mjs";
8688import { fetch as handleOff } from "./handler-off.mjs";
8689const expectedOpenApi = await readFile("./openapi.json", "utf8");
8690const url = "http://noxid.test/console/_noxid/mcp";
8691const request = (id, method, params = {}, headers = {}) => new Request(url, {
8692  method: "POST",
8693  headers: { "content-type": "application/json", accept: "application/json, text/event-stream", "mcp-protocol-version": "2025-11-25", ...headers },
8694  body: JSON.stringify({ jsonrpc: "2.0", id, method, params }),
8695});
8696
8697let response = await handleOff(request(1, "tools/list"), { trace: [] });
8698if (response.status !== 404 || (await response.json()).error.code !== "AGENT_SURFACE_DISABLED") throw new Error("default-off MCP door did not 404");
8699response = await handleOff(new Request("http://noxid.test/console/_noxid/openapi.json"), { trace: [] });
8700if (response.status !== 404) throw new Error("default-off OpenAPI door did not 404");
8701response = await handle(new Request("http://noxid.test/console/_noxid/openapi.json"), { trace: [] });
8702if (response.status !== 404) throw new Error("MCP-only build exposed OpenAPI");
8703response = await handleDocs(new Request("http://noxid.test/console/_noxid/openapi.json"), { trace: [] });
8704if (response.status !== 200 || await response.text() !== expectedOpenApi) throw new Error("OpenAPI document was not served exactly");
8705
8706response = await handle(request(2, "initialize", { protocolVersion: "2025-11-25", capabilities: {}, clientInfo: { name: "test", version: "1" } }), { trace: [] });
8707let rpc = await response.json();
8708if (response.status !== 200 || rpc.result.protocolVersion !== "2025-11-25" || rpc.result.serverInfo.name !== "noxid-endpoints") throw new Error(`initialize failed ${JSON.stringify(rpc)}`);
8709response = await handle(request(3, "tools/list"), { trace: [] });
8710rpc = await response.json();
8711const tools = rpc.result.tools;
8712if (tools.length !== 2 || new Set(tools.map((tool) => tool.name)).size !== 2 || tools.map((tool) => tool.name).sort().join(",") !== "GetWidget,WidgetEvents") throw new Error(`tool list diverged ${JSON.stringify(tools)}`);
8713const get = tools.find((tool) => tool.name === "GetWidget");
8714if (get.description !== "Fetch one typed widget" || get["x-noxid-endpoint"].signature.includes("Result<Widget, String>") !== true) throw new Error("description/signature missing");
8715if (get.inputSchema.properties.input.$ref !== "#/$defs/WidgetInput" || get.inputSchema.$defs.WidgetInput.properties.nested.$ref !== "#/$defs/Nested" || get.inputSchema.$defs.Nested.properties.scores.items.type !== "integer") throw new Error(`nested input schema drifted ${JSON.stringify(get.inputSchema)}`);
8716if (get.inputSchema.properties.tags.anyOf[0].items.type !== "string" || !get.inputSchema.required.includes("input") || get.inputSchema.required.includes("tags")) throw new Error("optional/array schema drifted");
8717if (get.outputSchema.properties.body.properties.value.$ref !== "#/$defs/Widget" || get.outputSchema.$defs.Widget.properties.nested.$ref !== "#/$defs/Nested") throw new Error("Result success schema drifted");
8718const stream = tools.find((tool) => tool.name === "WidgetEvents");
8719if (stream.outputSchema.properties.body.properties.events.items.$ref !== "#/$defs/Widget") throw new Error("stream event schema drifted");
8720
8721let environment = { trace: [], hostCalls: 0, sessionId: "environment-session" };
8722const args = { id: "w-1", tags: ["a", "b"], input: { label: "ready", note: null, nested: { scores: [7, 8] } } };
8723response = await handle(request(4, "tools/call", { name: "GetWidget", arguments: args }, { "x-session": "mcp-session" }), environment);
8724rpc = await response.json();
8725if (rpc.result.isError !== true || rpc.result.structuredContent.body.error.code !== "ENDPOINT_CAPABILITY_DENIED" || environment.hostCalls !== 0) throw new Error(`capability denial did not reuse endpoint pipeline ${JSON.stringify(rpc)} ${JSON.stringify(environment)}`);
8726if (JSON.stringify(environment.trace) !== JSON.stringify(["middleware:/console/api/widgets/w-1:mcp-session", "authorize:widgets.read"])) throw new Error(`denial order diverged ${JSON.stringify(environment.trace)}`);
8727environment = { trace: [], hostCalls: 0 };
8728response = await handle(request(41, "tools/call", { name: "GetWidget", arguments: {} }, { "x-session": "mcp-session" }), environment);
8729rpc = await response.json();
8730if (rpc.result.structuredContent.body.error.code !== "ENDPOINT_CAPABILITY_DENIED" || environment.hostCalls !== 0 || environment.trace[1] !== "authorize:widgets.read") throw new Error(`malformed unauthorized MCP call bypassed endpoint capability order ${JSON.stringify(rpc)} ${JSON.stringify(environment)}`);
8731
8732environment = { trace: [], hostCalls: 0 };
8733response = await handle(request(5, "tools/call", { name: "GetWidget", arguments: args }, { "x-session": "mcp-session", "x-capability": "widgets.read" }), environment);
8734rpc = await response.json();
8735if (rpc.result.isError !== false || rpc.result.structuredContent.status !== 200 || rpc.result.structuredContent.body.value.name !== "ready" || rpc.result.structuredContent.body.value.nested.scores[1] !== 8 || environment.hostCalls !== 1) throw new Error(`typed tool roundtrip failed ${JSON.stringify(rpc)} ${JSON.stringify(environment)}`);
8736if (JSON.stringify(environment.trace) !== JSON.stringify(["middleware:/console/api/widgets/w-1:mcp-session", "authorize:widgets.read", "host:w-1:mcp-session"])) throw new Error(`MCP pipeline order/session diverged ${JSON.stringify(environment.trace)}`);
8737
8738environment = { trace: [], hostCalls: 0 };
8739const invalid = { ...args, input: { ...args.input, nested: { scores: ["bad"] } } };
8740response = await handle(request(6, "tools/call", { name: "GetWidget", arguments: invalid }, { "x-session": "mcp-session", "x-capability": "widgets.read" }), environment);
8741rpc = await response.json();
8742if (rpc.result.isError !== true || rpc.result.structuredContent.status !== 422 || rpc.result.structuredContent.body.error.code !== "ENDPOINT_INPUT_TYPE" || environment.hostCalls !== 0) throw new Error(`MCP input escaped endpoint validation ${JSON.stringify(rpc)}`);
8743
8744response = await handle(request(7, "tools/call", { name: "WidgetEvents", arguments: {} }), { trace: [] });
8745rpc = await response.json();
8746if (rpc.result.isError !== false || rpc.result.structuredContent.body.events.length !== 2 || rpc.result.structuredContent.body.events[1].nested.scores[1] !== 3) throw new Error(`stream endpoint tool mapping failed ${JSON.stringify(rpc)}`);
8747"##;
8748        let output = Command::new("node")
8749            .args(["--input-type=module", "-e", script])
8750            .current_dir(&root)
8751            .output()
8752            .expect("Node.js is required for generated MCP endpoint tests");
8753        let _ = std::fs::remove_dir_all(&root);
8754        assert!(
8755            output.status.success(),
8756            "{}\n{}",
8757            String::from_utf8_lossy(&output.stdout),
8758            String::from_utf8_lossy(&output.stderr)
8759        );
8760    }
8761}