@lunora/platform 1.0.0-alpha.6 → 1.0.0-alpha.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- export { type D as DirectShardDirectory, type S as ScheduleOptions, type a as ScheduledJob, type b as ScheduledJobStatus, type c as SchedulerHost, type d as ShardAlarms, type e as ShardAsyncSqlExec, type f as ShardDirectory, type g as ShardHost, type h as ShardJurisdiction, type i as ShardKvListOptions, type j as ShardKvStore, type k as ShardSqlCursor, type l as ShardSqlExec, type m as ShardStub, type n as SocketHandle, type o as SocketHost, type p as SqlRow, type T as TwoStepShardDirectory, r as resolveShard } from "./packem_shared/socket-host.d-DCVFvmXn.mjs";
1
+ export { type D as DirectShardDirectory, L as LUNORA_ROLE_PREFIX, type S as ScheduleOptions, type a as ScheduledJob, type b as ScheduledJobStatus, type c as SchedulerHost, type d as ShardAlarms, type e as ShardDirectory, type f as ShardHost, type g as ShardJurisdiction, type h as ShardKvListOptions, type i as ShardKvStore, type j as ShardRegionHint, type k as ShardSqlCursor, type l as ShardSqlExec, type m as ShardStub, type n as SocketHandle, type o as SocketHost, type p as SqlRow, type T as TwoStepShardDirectory, r as resolveShard } from "./packem_shared/socket-host.d-VUTw8RC_.mjs";
2
2
  /**
3
3
  * The subset of the Cloudflare `ExecutionContext` the Lunora worker entry and
4
4
  * the framework mount seams rely on — `waitUntil` for fire-and-forget work that
@@ -22,15 +22,70 @@ export { type D as DirectShardDirectory, type S as ScheduleOptions, type a as Sc
22
22
  * fall back to {@link NOOP_EXECUTION_CONTEXT}.
23
23
  */
24
24
  interface ExecutionContextLike {
25
+ /**
26
+ * Present only when Cloudflare Access authenticated the request against a
27
+ * policy attached to the **Worker** (rather than to a hostname). `undefined`
28
+ * on every unauthenticated request, so its presence is itself the "Access
29
+ * authorized this caller" signal — see {@link AccessContextLike}.
30
+ */
31
+ access?: AccessContextLike;
25
32
  cache?: {
26
33
  purge: (options: {
27
34
  purgeEverything?: boolean;
28
35
  tags?: string[];
29
36
  }) => Promise<unknown>;
30
37
  };
38
+ /**
39
+ * Loopback bindings to the Worker's own top-level exports — where Cloudflare
40
+ * exposes the workflows and agents declared in wrangler `exports`, keyed by
41
+ * class name (see `shared/workflow-binding.ts`).
42
+ */
43
+ exports?: unknown;
31
44
  passThroughOnException?: () => void;
32
45
  waitUntil?: (promise: Promise<unknown>) => void;
33
46
  }
47
+ /**
48
+ * The identity Cloudflare Access attaches to a Worker-protected request.
49
+ *
50
+ * Shape follows the Access application-token payload: `sub` is the stable per-user
51
+ * id, `email` the verified address, `common_name` the service-token name (machine
52
+ * callers, whose `sub` is empty), and `exp` the credential expiry in epoch
53
+ * **seconds**. Group membership is whatever the Access policy emits — a list of
54
+ * names, or of `{ id, name }` objects — hence `unknown`; normalize before use.
55
+ *
56
+ * Cloudflare may add further fields, so the index signature keeps them rather
57
+ * than dropping them: this is a view of a payload we do not own.
58
+ */
59
+ interface AccessIdentityLike {
60
+ [claim: string]: unknown;
61
+ /** Service-token name. Present for non-interactive (machine) callers instead of `email`. */
62
+ common_name?: string;
63
+ /** Verified user email. Present for interactive (SSO) callers. */
64
+ email?: string;
65
+ /** Credential expiry, epoch **seconds**. */
66
+ exp?: number;
67
+ /** IdP group membership — names or `{ id, name }` objects, depending on the policy. */
68
+ groups?: unknown;
69
+ /** Display name from the identity provider, when it emits one. */
70
+ name?: string;
71
+ /** Stable per-user id, and what consumers key a user on. Empty for service tokens. */
72
+ sub?: string;
73
+ /** Cloudflare's per-user UUID. Carried through, but deliberately not used as an id — only this path emits it, so keying on it would not match the JWT path. */
74
+ user_uuid?: string;
75
+ }
76
+ /**
77
+ * The `ctx.access` facade Cloudflare exposes on a Worker protected by Access.
78
+ *
79
+ * Reading the identity from here is preferable to verifying the
80
+ * `Cf-Access-Jwt-Assertion` header: the platform has already authenticated the
81
+ * caller, so there is no JWKS fetch, no audience check to get wrong, and nothing
82
+ * a request can forge — the field simply does not exist unless Access authorized
83
+ * the call. The header path remains the fallback for hostname-scoped Access
84
+ * applications, which do not populate this.
85
+ */
86
+ interface AccessContextLike {
87
+ getIdentity: () => AccessIdentityLike | null | undefined | Promise<AccessIdentityLike | null | undefined>;
88
+ }
34
89
  /**
35
90
  * No-op `ExecutionContext` used when the host runtime didn't supply one (a
36
91
  * non-Cloudflare preview, or a unit test), so the worker's `fetch` always
@@ -65,6 +120,35 @@ interface QueueMessageLike<Body = unknown> {
65
120
  }) => void;
66
121
  readonly timestamp: Date;
67
122
  }
123
+ /** Options accepted by {@link HttpCacheLike.match} and {@link HttpCacheLike.delete}. */
124
+ interface HttpCacheQueryOptions {
125
+ /** Match a non-`GET` request against a stored `GET` entry. */
126
+ ignoreMethod?: boolean;
127
+ }
128
+ /**
129
+ * Minimal projection of one Web Cache API cache — the store a host puts in front
130
+ * of the app, reached on Cloudflare as `caches.default` (the colo cache).
131
+ *
132
+ * Only the three calls Lunora makes are declared, so a host that has a cache but
133
+ * not the whole `Cache` interface still satisfies it, and a unit test can pass a
134
+ * plain object double. This is a **host** primitive, not a binding: it is reached
135
+ * through a runtime global rather than `env`, and a target without one leaves it
136
+ * `undefined` rather than shipping a fake — see `httpCache` in
137
+ * `PlatformCapabilities`.
138
+ *
139
+ * The stored entry is keyed by the request, so a caller that needs `Vary`
140
+ * semantics must fold the varying header values into the key itself: Cloudflare's
141
+ * cache honours `Vary` for `Accept-Encoding` only, and a projection cannot make
142
+ * that portable.
143
+ */
144
+ interface HttpCacheLike {
145
+ /** Evict the entry stored under `request`. Resolves `true` when something was removed. */
146
+ delete: (request: Request | string, options?: HttpCacheQueryOptions) => Promise<boolean>;
147
+ /** The stored response for `request`, or `undefined` on a miss. */
148
+ match: (request: Request | string, options?: HttpCacheQueryOptions) => Promise<Response | undefined>;
149
+ /** Store `response` under `request`. Rejects for a `206`, a `Vary: *`, or a `Set-Cookie`-bearing response. */
150
+ put: (request: Request | string, response: Response) => Promise<void>;
151
+ }
68
152
  /** A single vector match. */
69
153
  interface VectorMatchLike {
70
154
  id: string;
@@ -85,6 +169,7 @@ interface AnalyticsEngineDataPointLike {
85
169
  }
86
170
  interface D1PreparedStatementLike {
87
171
  all: <T = unknown>() => Promise<{
172
+ meta?: Record<string, unknown>;
88
173
  results: T[];
89
174
  success: boolean;
90
175
  }>;
@@ -214,30 +299,53 @@ type KvNamespaceListResult<Metadata = unknown> = {
214
299
  list_complete: true;
215
300
  };
216
301
  /**
217
- * Minimal structural projection of `VectorizeIndex` so unit tests can pass a
302
+ * Minimal structural projection of `Vectorize` so unit tests can pass a
218
303
  * plain-object double and the real Cloudflare binding satisfies the same shape.
219
304
  * Mirrors the surface documented at
220
305
  * https://developers.cloudflare.com/vectorize/reference/client-api/.
306
+ *
307
+ * **Method** syntax, not arrow properties: `ReadonlyArray` is the correct (wider)
308
+ * parameter type, but an arrow property makes parameters strictly contravariant
309
+ * under `strictFunctionTypes`, and Cloudflare declares `deleteByIds(ids: string[])`
310
+ * — so the real binding did not satisfy this interface at all. Methods are checked
311
+ * bivariantly, which admits both it and a `readonly`-clean double.
312
+ * `binding-assignability.test-d.ts` in `@lunora/bindings` pins that against the
313
+ * published types. `this: void` keeps the members usable as bare references, which
314
+ * is what every `expect(double.query)` assertion in those tests does.
221
315
  */
222
316
  interface VectorizeIndexLike {
223
- deleteByIds: (ids: ReadonlyArray<string>) => Promise<VectorizeDeleteMutation>;
224
- describe?: () => Promise<VectorizeIndexDetails>;
225
- getByIds: (ids: ReadonlyArray<string>) => Promise<ReadonlyArray<VectorizeVector>>;
226
- insert: (vectors: ReadonlyArray<VectorizeVector>) => Promise<VectorizeUpsertMutation>;
227
- query: (vector: ReadonlyArray<number>, options?: VectorizeQueryOptions) => Promise<VectorizeMatches>;
228
- upsert: (vectors: ReadonlyArray<VectorizeVector>) => Promise<VectorizeUpsertMutation>;
317
+ deleteByIds(this: void, ids: ReadonlyArray<string>): Promise<VectorizeDeleteMutation>;
318
+ describe?(this: void): Promise<VectorizeIndexDetails>;
319
+ getByIds(this: void, ids: ReadonlyArray<string>): Promise<ReadonlyArray<VectorizeVector>>;
320
+ insert(this: void, vectors: ReadonlyArray<VectorizeVector>): Promise<VectorizeUpsertMutation>;
321
+ query(this: void, vector: VectorValues, options?: VectorizeQueryOptions): Promise<VectorizeMatches>;
322
+ upsert(this: void, vectors: ReadonlyArray<VectorizeVector>): Promise<VectorizeUpsertMutation>;
229
323
  }
230
324
  type VectorMetric = "cosine" | "euclidean" | "dot-product";
325
+ /**
326
+ * An embedding, as either a plain array or one of the typed arrays Vectorize
327
+ * accepts. The typed-array arms are not a convenience: Cloudflare's own
328
+ * `VectorizeVector.values` is `VectorFloatArray | number[]`, so a projection
329
+ * limited to `ReadonlyArray<number>` excluded every vector the real binding
330
+ * returns. Read one with `Array.from(...)` rather than an array method.
331
+ */
332
+ type VectorValues = Float32Array | Float64Array | ReadonlyArray<number>;
231
333
  interface VectorizeVector {
232
334
  id: string;
233
335
  metadata?: Record<string, unknown>;
234
336
  namespace?: string;
235
- values: ReadonlyArray<number>;
337
+ values: VectorValues;
236
338
  }
339
+ /**
340
+ * Query options, kept a superset of Cloudflare's own `VectorizeQueryOptions` so
341
+ * the real binding satisfies {@link VectorizeIndexLike}. `returnMetadata` carries
342
+ * the legacy `boolean` arm for that reason - pass one of the three levels; the
343
+ * boolean is what Cloudflare still accepts, not what callers should write.
344
+ */
237
345
  interface VectorizeQueryOptions {
238
346
  filter?: Record<string, unknown>;
239
347
  namespace?: string;
240
- returnMetadata?: "none" | "indexed" | "all";
348
+ returnMetadata?: "none" | "indexed" | "all" | boolean;
241
349
  returnValues?: boolean;
242
350
  topK?: number;
243
351
  }
@@ -246,7 +354,7 @@ interface VectorizeMatch {
246
354
  metadata?: Record<string, unknown>;
247
355
  namespace?: string;
248
356
  score: number;
249
- values?: ReadonlyArray<number>;
357
+ values?: VectorValues;
250
358
  }
251
359
  interface VectorizeMatches {
252
360
  count: number;
@@ -259,11 +367,24 @@ interface VectorizeDeleteMutation {
259
367
  count?: number;
260
368
  mutationId: string;
261
369
  }
370
+ /**
371
+ * What `describe()` reports. Both spellings of the row count are optional because
372
+ * Cloudflare renamed it between API generations — the beta `VectorizeIndex`
373
+ * returns `vectorsCount`, the current `Vectorize` returns `vectorCount` — and a
374
+ * projection that required either one excluded a real binding. Read them as
375
+ * `vectorCount ?? vectorsCount`.
376
+ *
377
+ * `processedUpTo*` are typed loosely for the same reason: documented as ISO 8601
378
+ * strings, typed as `number` in `@cloudflare/workers-types`.
379
+ */
262
380
  interface VectorizeIndexDetails {
263
381
  dimensions: number;
264
- processedUpToDatetime?: string;
265
- processedUpToMutation?: string;
266
- vectorsCount: number;
382
+ processedUpToDatetime?: number | string;
383
+ processedUpToMutation?: number | string;
384
+ /** The current `Vectorize.describe()` spelling. */
385
+ vectorCount?: number;
386
+ /** The beta `VectorizeIndex.describe()` spelling. */
387
+ vectorsCount?: number;
267
388
  }
268
389
  /**
269
390
  * A single-range read against R2: an `{ offset, length }` window (at least one
@@ -296,7 +417,8 @@ interface R2BucketLike {
296
417
  contentType?: string;
297
418
  };
298
419
  }) => Promise<R2MultipartUploadLike>;
299
- delete: (key: string) => Promise<void>;
420
+ /** Delete one key, or up to 1000 in one call (R2's bulk delete). Absent keys are a no-op. */
421
+ delete: (keys: string | string[]) => Promise<void>;
300
422
  get: (key: string, options?: {
301
423
  range?: R2RangeLike;
302
424
  }) => Promise<R2ObjectBodyLike | null>;
@@ -310,22 +432,53 @@ interface R2BucketLike {
310
432
  list: (options?: {
311
433
  cursor?: string;
312
434
  delimiter?: string;
435
+ include?: ("customMetadata" | "httpMetadata")[];
313
436
  limit?: number;
314
437
  prefix?: string;
438
+ startAfter?: string;
315
439
  }) => Promise<{
316
440
  cursor?: string;
441
+ delimitedPrefixes?: string[];
317
442
  objects: R2ObjectLike[];
318
443
  truncated?: boolean;
319
444
  }>;
320
- put: (key: string, body: ReadableStream | ArrayBuffer | Blob | string | null, options?: {
321
- customMetadata?: Record<string, string>;
322
- httpMetadata?: {
323
- contentType?: string;
324
- };
325
- }) => Promise<R2ObjectLike>;
445
+ put: {
446
+ (key: string, body: R2PutBodyLike, options: R2PutOptionsLike & {
447
+ onlyIf: R2ConditionalLike;
448
+ }): Promise<R2ObjectLike | null>;
449
+ (key: string, body: R2PutBodyLike, options?: R2PutOptionsLike): Promise<R2ObjectLike>;
450
+ };
326
451
  /** Resume an in-progress multipart upload by id (R2 `resumeMultipartUpload`). Optional; see {@link Storage.resumeMultipartUpload}. */
327
452
  resumeMultipartUpload?: (key: string, uploadId: string) => R2MultipartUploadLike;
328
453
  }
454
+ /**
455
+ * What `R2BucketLike.put` stores bytes from. `ArrayBufferView` is in the real
456
+ * `R2Bucket.put` signature and in `uploadPart`; a `Uint8Array` is the most
457
+ * natural thing to hand a byte store.
458
+ */
459
+ type R2PutBodyLike = ReadableStream | ArrayBuffer | ArrayBufferView | Blob | string | null;
460
+ /**
461
+ * The put options `R2BucketLike.put` accepts. `sha256` is in the real
462
+ * `R2Bucket.put` signature and `@lunora/storage`'s `upload` has always passed
463
+ * it: R2 verifies the digest on write and records it, and a recorded digest is
464
+ * the only reason `list()`/`head()` can report `checksums.sha256` at all.
465
+ */
466
+ interface R2PutOptionsLike {
467
+ customMetadata?: Record<string, string>;
468
+ httpMetadata?: {
469
+ contentType?: string;
470
+ };
471
+ sha256?: ArrayBuffer | string;
472
+ }
473
+ /**
474
+ * A put precondition (R2's `R2Conditional`, the etag half). `etagMatches` is
475
+ * `If-Match`; `etagDoesNotMatch: "*"` is `If-None-Match: *`, "only if the key
476
+ * does not exist yet".
477
+ */
478
+ interface R2ConditionalLike {
479
+ etagDoesNotMatch?: string;
480
+ etagMatches?: string;
481
+ }
329
482
  /** One uploaded multipart part — returned by `uploadPart`, required to `complete`. Mirrors R2's `R2UploadedPart`. */
330
483
  interface R2UploadedPartLike {
331
484
  etag: string;
@@ -455,9 +608,112 @@ interface QueueRetryOptions {
455
608
  * Lunora features a target platform supports natively, emulates, or cannot
456
609
  * support at all.
457
610
  *
458
- * Codegen consumes this matrix to omit unsupported `ctx.*` surfaces from
459
- * emitted types and to emit diagnostics for features that need emulation.
460
- * Docs and Studio also read it to show parity per target.
611
+ * # Who reads it
612
+ *
613
+ * **`@lunora/codegen` is the only gating consumer.** `gateAgainstMatrix`
614
+ * (`packages/codegen/src/platform-target.ts`) intersects an app's detected
615
+ * feature usage with the target's matrix and diagnoses exactly two states:
616
+ * `unsupported` (`platform_unsupported_feature`) and a key missing from the
617
+ * matrix altogether (`platform_undeclared_feature`, the fail-closed arm).
618
+ * `native` and `emulated` are emitted identically, with no diagnostic between
619
+ * them — that distinction exists for honest parity reporting, not for codegen.
620
+ *
621
+ * The generated worker also reports its target's levels to `@lunora/studio`
622
+ * (the `platform` field of `__lunora_admin__:studioFeatures`), which marks a
623
+ * page whose feature the target rates `unsupported` as unavailable. Studio
624
+ * still imports nothing from here — the worker tells it. The per-feature table
625
+ * in `packages/platform-node/docs/index.mdx` is a hand-written copy held
626
+ * verbatim by `pnpm run lint:node-capabilities-docs`: change a rating or a note
627
+ * here first, then that table, or the check fails.
628
+ *
629
+ * # Gate-bearing keys
630
+ *
631
+ * A rating only gates something if `@lunora/codegen` reads it — either through a
632
+ * usage key mapped onto the feature (`CAPABILITY_ROWS` + `CAPABILITY_TO_FEATURE`,
633
+ * for an app-imported `ctx.*` module) or through a `PlatformSignals` entry (for
634
+ * something the app declares in its schema or a declaration file). The
635
+ * gate-bearing keys are:
636
+ *
637
+ * `agents`, `ai`, `aiSearch`, `analytics`, `analyticsSql`, `artifacts`, `browser`, `commitOrderedTables`,
638
+ * `containerEgressPolicy`, `containerRuntimeScheduling`, `containerSandboxTools`, `containers`,
639
+ * `cronTriggers`, `crossShardFanout`,
640
+ * `durableStreams`, `globalTables`, `hyperdrive`, `images`, `keyValueStore`,
641
+ * `mail`, `objectStorage`, `pipelines`, `queues`, `relationGraph`,
642
+ * `scheduler`, `secrets`, `services`, `topics`, `vectorStore`, `workerLoaders`, `workflowRollback`,
643
+ * `workflowSchedules`, `workflows`.
644
+ *
645
+ * Every other key here — `authJurisdictionMove`, `conditionalWrites`, `edgeRequestMetadata`, `hostTraceFusion`, `httpCache`,
646
+ * `identityProxy`, `localSql`, `logArchive`, `memoryTables`,
647
+ * `objectColumnPaths`, `objectStorageBackups`, `objectStorageCdcArchive`, `pointInTimeRecovery`, `queryExplain`,
648
+ * `serverReactors`,
649
+ * `shardAlarms`, `shardedState`, `shardPlacement`, `shardReadReplicas`,
650
+ * `websocketHibernation` — is
651
+ * **advisory**: rating one `unsupported` omits no surface and warns nobody. It
652
+ * still records parity honestly, which is its job; it is not a gate.
653
+ *
654
+ * # Advisory is not one thing — there are two reasons, and only one is final
655
+ *
656
+ * `conditionalWrites` is advisory by nature as well: it is one `ctx.db` method
657
+ * (`patchIf`), and every backend that runs it decides in the database's own
658
+ * statement, so no target needs codegen to withhold it. A backend without one
659
+ * refuses the call at first use instead of reading and then writing.
660
+ * `authJurisdictionMove` is advisory by nature too: an app never declares it.
661
+ * Codegen wires the admin ops whenever DO-backed auth is pinned to a
662
+ * jurisdiction, so the thing a host would have to refuse is the jurisdiction.
663
+ *
664
+ * Most advisory keys are advisory *by nature*: the feature is engine-internal
665
+ * (`shardAlarms`, `shardedState`, `shardPlacement`, `shardReadReplicas`,
666
+ * `websocketHibernation`, `localSql`, `serverReactors`) or degrades honestly on
667
+ * its own (`httpCache` falls back to headers-only, `identityProxy` to header
668
+ * verification). There is nothing an app declares for codegen to notice, so
669
+ * there is nothing to gate. These stay ratings, permanently.
670
+ *
671
+ * The three telemetry keys — `edgeRequestMetadata`, `hostTraceFusion`,
672
+ * `logArchive` — are advisory by nature for a third reason worth naming, since
673
+ * it is not obvious: each is configured through a `createWorker` argument or an
674
+ * `ObservabilitySink` field (`trustInboundTraceContext`, `fuseCloudflareTraces`,
675
+ * `logArchive`), and codegen reads neither. There is no app-side DECLARATION to
676
+ * gate on, so promoting them would mean inventing one. Where that silence
677
+ * actually bites — `trustInboundTraceContext: "mtls"` collapsing to never-trust
678
+ * off Cloudflare — the runtime warns once instead, from the only tier that can
679
+ * observe it (`createDroppedTraceNotice` in `@lunora/runtime`).
680
+ *
681
+ * The rest of the telemetry pipeline — `ctx.log`, `ctx.trace`, `ctx.span`,
682
+ * `ctx.metrics`, traced `ctx.fetch`, automatic `ctx.db` / `ctx.sql` database
683
+ * instrumentation (a wrapper over the client the target already rates, so it
684
+ * follows `hyperdrive`'s rating rather than needing its own), and W3C trace
685
+ * propagation — deliberately
686
+ * has NO key: it is sink callbacks over the `fetch` global, needs no host
687
+ * primitive, and a key every target must rate `native` forever is paperwork, not
688
+ * a control.
689
+ *
690
+ * The rest are advisory only because nobody wired them, and they are the ones
691
+ * to watch: an app DOES declare the feature, codegen CAN see the declaration,
692
+ * and the rating is still consulted by nothing. Codegen already has the shape
693
+ * for exactly this — `PlatformSignals` in `platform-target.ts`, the second gate
694
+ * pass that diagnoses app-declared features with no `ctx.*` capability row
695
+ * (`agents`, `commitOrderedTables`, `containerEgressPolicy`,
696
+ * `containerRuntimeScheduling`, `containerSandboxTools`, `cronTriggers`,
697
+ * `crossShardFanout`, `durableStreams`, `globalTables`, `queues`,
698
+ * `relationGraph`, `secrets`, `services`, `topics`, `vectorStore`, `workflowRollback`,
699
+ * `workflowSchedules`).
700
+ * Promoting one is three lines there: a `PlatformSignals` field, plus its entry
701
+ * in that module's signal-key list and its human-readable label — and then
702
+ * setting the signal from the IR.
703
+ *
704
+ * `commitOrderedTables` was promoted that way: `TableIR.commitOrdered` sits in
705
+ * the same IR that feeds `globalTables`, and until it was read a host rating it
706
+ * `unsupported` emitted the full `.commitOrdered()` surface with no diagnostic
707
+ * and silently lost commit ordering — the one guarantee the feature is.
708
+ * `memoryTables`, `objectStorageBackups` and `objectStorageCdcArchive` remain
709
+ * weaker instances of the same shape, still unpromoted.
710
+ *
711
+ * **Adding a feature key is therefore half a change.** The other half is a row
712
+ * in `CAPABILITY_ROWS` and an entry in `CAPABILITY_TO_FEATURE` (for an
713
+ * app-imported `ctx.*` module), or a `PlatformSignals` entry (for something the
714
+ * app declares in its schema), or a deliberate decision that the key is advisory
715
+ * by nature — recorded here. Silence means the rating ships as documentation
716
+ * while the surface it describes is emitted anyway.
461
717
  */
462
718
  /** Support level for a single feature on a target platform. */
463
719
  type CapabilityLevel = "native" | "emulated" | "unsupported";
@@ -475,52 +731,528 @@ interface Capability {
475
731
  interface PlatformCapabilities {
476
732
  /** Feature-level capabilities. */
477
733
  features: {
478
- /** AI inference (Workers AI / Bedrock / OpenAI). */
734
+ /**
735
+ * Durable agents — a `defineAgent` export in `lunora/agents.ts`.
736
+ *
737
+ * Its own key rather than a facet of `workflows` or `ai`, because an
738
+ * agent needs BOTH and neither implies the other: the generated class
739
+ * compiles onto the host's workflow engine as a `ctx.exports.<Class>` workflow
740
+ * the emitted context resolves off `env`, and the loop it runs there
741
+ * calls model inference. A host that emulates workflows but has no
742
+ * inference (or no way to mount a generated class into its engine) can
743
+ * rate `workflows` honestly and still not run an agent.
744
+ */
745
+ agents?: Capability;
746
+ /**
747
+ * AI inference — `ctx.ai` (Workers AI / Bedrock / OpenAI).
748
+ *
749
+ * Covers every model id `ctx.ai.model(...)` resolves, including
750
+ * `"<provider>/<model>"` catalog slugs and `dynamic/<route>` ids, which
751
+ * route through Cloudflare AI Gateway over the same `AI` binding rather
752
+ * than a binding of their own. A host rating this `native` must say how
753
+ * slugs resolve there; one without a gateway is honest to rate the
754
+ * Workers AI half and note the slug half missing. Usage accounting rides
755
+ * `ctx.trace` / `ctx.metrics`, which need no key.
756
+ */
479
757
  ai?: Capability;
758
+ /**
759
+ * Cloudflare AI Search (formerly AutoRAG) — `ctx.aiSearch`, the raw
760
+ * `ai_search_namespaces` binding passed through as-is, on ActionCtx only.
761
+ *
762
+ * Its own key rather than a facet of `ai` or `vectorStore`: it is a
763
+ * managed retrieval service (chunking, embedding, the hybrid index and
764
+ * reranking all live on the instance), not inference and not a vector
765
+ * index a host could stand in for. No Lunora host contract carries it —
766
+ * a second host would have to supply an `AiSearchNamespace` look-alike.
767
+ * Gate-bearing through `CAPABILITY_TO_FEATURE`.
768
+ */
769
+ aiSearch?: Capability;
480
770
  /** Analytics / observability sinks. */
481
771
  analytics?: Capability;
772
+ /**
773
+ * Read-only SQL over Cloudflare's Analytics SQL API through the Analytics
774
+ * SQL binding (wrangler `analytics` key) — `ctx.analyticsSql`, on
775
+ * ActionCtx only.
776
+ *
777
+ * Its own key rather than a facet of `analytics`: that one rates the
778
+ * write-only Analytics Engine sink (`ctx.analytics`, every ctx), a
779
+ * different binding a host could stand in for independently. No Lunora
780
+ * host contract carries this one — a second host would have to supply a
781
+ * `query({ query, params })` look-alike over its own analytics store.
782
+ * Gate-bearing through `CAPABILITY_TO_FEATURE`.
783
+ */
784
+ analyticsSql?: Capability;
785
+ /** Git-backed versioned repos via an Artifacts binding (`ctx.artifacts`). */
786
+ artifacts?: Capability;
787
+ /**
788
+ * Copying DO-backed auth from its un-pinned object into the
789
+ * jurisdiction-pinned one, and purging the un-pinned copy afterwards
790
+ * (the worker's `copyAuthToJurisdiction` / `purgeUnpinnedAuth` admin
791
+ * ops). Advisory by nature: it exists only once a schema pins auth with
792
+ * `.jurisdiction(…)`, which is itself the thing a host without
793
+ * jurisdictions cannot run.
794
+ */
795
+ authJurisdictionMove?: Capability;
482
796
  /** Browser rendering / headless browser. */
483
797
  browser?: Capability;
484
- /** Container execution (Cloudflare Containers / Fargate). */
798
+ /**
799
+ * `.commitOrdered()` tables — the `_commitSeq` system field: a per-shard
800
+ * integer allocated once per mutation and strictly increasing in commit
801
+ * order.
802
+ *
803
+ * Listed as a capability rather than assumed, because the ordering
804
+ * guarantee is not the engine's to give. It rests on two things the HOST
805
+ * provides: an atomic write boundary the counter bump shares with the
806
+ * rows it stamps, and serialized execution so two mutations cannot
807
+ * interleave their allocations. A host that offers neither can still
808
+ * create the counter and hand out increasing numbers — they just would
809
+ * not order commits, which is the whole contract.
810
+ *
811
+ * Gate-bearing: `TableIR.commitOrdered` feeds the `PlatformSignals`
812
+ * pass off the same IR the `globalTables` signal reads, so a host
813
+ * rating this `unsupported` refuses the app rather than emitting the
814
+ * full `.commitOrdered()` surface and silently dropping the ordering
815
+ * guarantee — which is the only thing the feature is.
816
+ */
817
+ commitOrderedTables?: Capability;
818
+ /**
819
+ * Atomic conditional writes — `ctx.db.patchIf(table, id, { where, patch })`,
820
+ * which applies a patch only while the stored row matches `where`, decided
821
+ * in the same step as the write. What makes a read-decide-write (a rate
822
+ * limiter's admission, a compare-and-set) safe against concurrent callers.
823
+ *
824
+ * Carried by no host contract: it is SQL the engines already run. A
825
+ * shard-local table decides it in one synchronous run over `localSql`; a
826
+ * `.global()` table in one conditional `UPDATE` through `@lunora/sql-store`
827
+ * on the target's `globalTables` backend. A writer that cannot decide it
828
+ * atomically omits the method and the call is refused, never emulated with
829
+ * a read followed by a write. Advisory by nature (see the module header).
830
+ */
831
+ conditionalWrites?: Capability;
832
+ /**
833
+ * An outbound-traffic policy on a container — `defineContainer({
834
+ * allowedHosts | deniedHosts | interceptHttps })`, which
835
+ * `LunoraContainer` applies through `@cloudflare/containers`'
836
+ * outbound interception.
837
+ *
838
+ * Rated apart from `containers` because a host can run a container and
839
+ * still not police its egress; the policy is then not merely absent,
840
+ * the container refuses to start. Gate-bearing: codegen sets the
841
+ * `containerEgressPolicy` `PlatformSignals` flag when a
842
+ * `defineContainer` call carries one of those keys, so the gap is a
843
+ * build-time diagnostic rather than a container that fails on first use.
844
+ */
845
+ containerEgressPolicy?: Capability;
846
+ /**
847
+ * The `durable_object` container scheduling policy —
848
+ * `defineContainer({ schedulingPolicy: "durable_object" })`: each
849
+ * instance picks its image and size at `start()`, and can save and
850
+ * restore its filesystem (`snapshot()` / `start({ snapshot })`).
851
+ *
852
+ * One key for both halves because snapshots only exist under this
853
+ * policy — there is no app declaration for a snapshot that codegen
854
+ * could gate apart from the policy. Rated apart from `containers`
855
+ * because a host can run a container from one configured image and
856
+ * still have no way to start a chosen image or restore a filesystem.
857
+ * Gate-bearing: codegen sets the `containerRuntimeScheduling`
858
+ * `PlatformSignals` flag off `ContainerIR.schedulingPolicy`.
859
+ */
860
+ containerRuntimeScheduling?: Capability;
861
+ /**
862
+ * Container execution (Cloudflare Containers / Fargate), including
863
+ * `ctx.containers.<name>.exec`. Deliberately one rating rather than two:
864
+ * `exec` is a method on the accessor this key already gates, not a
865
+ * separate app-imported surface, so there is no usage signal codegen
866
+ * could gate it on independently and nothing that could act on a second
867
+ * rating. A host that can reach a container but cannot carry a command
868
+ * result back should say so in this note.
869
+ */
485
870
  containers?: Capability;
871
+ /**
872
+ * The Sandbox SDK helpers on a `defineContainer({ sandbox: true })`
873
+ * container: structured file operations (`Files`), directory backups to
874
+ * R2 (`DirectoryBackup`) and S3-compatible bucket mounts (`S3Mount`),
875
+ * from `@cloudflare/sandbox`. All three run the `sandbox-shim` helper
876
+ * through the native `ctx.container.exec()`, and the backup and mount
877
+ * helpers route the container's storage traffic through Worker
878
+ * entrypoints via `interceptOutboundHttp`. A host that runs a container
879
+ * but has neither cannot carry them. Gate-bearing: codegen sets the
880
+ * `containerSandboxTools` `PlatformSignals` flag off `ContainerIR.sandbox`.
881
+ */
882
+ containerSandboxTools?: Capability;
883
+ /**
884
+ * DECLARED cron triggers — the `cronJobs()` registrations codegen lifts
885
+ * into `LUNORA_CRONS`, dispatched by whatever the host wakes on a
886
+ * schedule.
887
+ *
888
+ * Separate from {@link PlatformCapabilities.features.scheduler}, which
889
+ * rates the imperative surface (`ctx.scheduler.runAfter/runAt`, a job
890
+ * the app enqueues at runtime). The two are genuinely independent: a
891
+ * host can dispatch enqueued jobs perfectly and still walk nothing into
892
+ * its declared crons, in which case an app's `crons.daily(...)` never
893
+ * fires. One rating covering both is how that shipped as green.
894
+ */
895
+ cronTriggers?: Capability;
486
896
  /** Cross-shard fan-out queries. */
487
897
  crossShardFanout?: Capability;
898
+ /**
899
+ * Durable streams: a `.stream()` run whose chunks are persisted and
900
+ * whose producer outlives the socket that opened it, so a reconnecting
901
+ * or second client resumes the same transcript.
902
+ */
903
+ durableStreams?: Capability;
904
+ /**
905
+ * Platform-injected PER-REQUEST metadata the runtime reads off the
906
+ * request object itself rather than a header — Cloudflare's `request.cf`.
907
+ *
908
+ * Two telemetry surfaces stand on it, and both degrade SILENTLY without
909
+ * it, which is why it is rated rather than assumed: the
910
+ * `trustInboundTraceContext: "mtls"` trust signal reads
911
+ * `cf.tlsClientAuth.certVerified` (absent ⇒ no caller is ever trusted, so
912
+ * every inbound `traceparent` is dropped), and the OTLP resource detector
913
+ * reads the colo/country placement attributes (absent ⇒ the spans carry
914
+ * no placement resource).
915
+ *
916
+ * What makes it a capability and not a header check is unforgeability:
917
+ * the platform sets it, so a caller cannot write it. A host that merely
918
+ * stamps a header carries no such proof and should rate this
919
+ * `unsupported`.
920
+ *
921
+ * Advisory by nature: both consumers are `createWorker` options, which
922
+ * codegen never sees, so there is no app-side declaration to gate on. The
923
+ * runtime warns once instead when a dropped trace proves the signal is
924
+ * undeliverable — see `createDroppedTraceNotice` in `@lunora/runtime`.
925
+ */
926
+ edgeRequestMetadata?: Capability;
488
927
  /** Global (replicated) tables backed by a SQL store. */
489
928
  globalTables?: Capability;
929
+ /**
930
+ * Merging Lunora's spans into the HOST's own trace tree, so its native
931
+ * tracing shows one nested tree instead of two unrelated ones — the
932
+ * sink's `fuseCloudflareTraces` opt-in, which reaches `cloudflare:workers`'
933
+ * `tracing.enterSpan` (plus, where present, `getActiveSpan` for the
934
+ * invocation root and a span's `setAttributes` / `recordException` /
935
+ * `setStatus`).
936
+ *
937
+ * Rated because it is the one telemetry surface that reaches past
938
+ * `ShardHost` into a provider API. Everything else in the pipeline is
939
+ * engine-level (a sink callback), so it runs anywhere; this needs the
940
+ * host to HAVE a trace tree and to expose a way to enter a span in it.
941
+ *
942
+ * `unsupported` costs nothing: the feature is already capability-probed
943
+ * at runtime and no-ops where the import is unavailable. The rating is
944
+ * what makes that a stated fact rather than something a second host
945
+ * discovers.
946
+ *
947
+ * Advisory by nature: the flag lives on the sink object passed to
948
+ * `createWorker`/`createShardDO`, which codegen never sees.
949
+ */
950
+ hostTraceFusion?: Capability;
951
+ /**
952
+ * A shared HTTP cache in front of the app that the runtime can READ AND
953
+ * WRITE — the Web Cache API (`caches.default` on Cloudflare), projected
954
+ * as `HttpCacheLike`.
955
+ *
956
+ * Rated separately from the app merely emitting `Cache-Control`, because
957
+ * only this half needs a host primitive. Emitting the header is portable
958
+ * by construction: any host that returns an HTTP response can do it, and
959
+ * browsers and downstream CDNs honour it wherever the app runs. What is
960
+ * not portable is a store the Worker itself can `match`/`put` against,
961
+ * which is why `@lunora/runtime`'s REST edge cache degrades to
962
+ * headers-only on a target rated `unsupported` rather than failing.
963
+ */
964
+ httpCache?: Capability;
490
965
  /** BYO database via connection pooling (Hyperdrive / RDS Proxy). */
491
966
  hyperdrive?: Capability;
967
+ /**
968
+ * An identity-aware proxy in front of the app that authenticates the
969
+ * caller before the request reaches it, and hands the runtime a verified
970
+ * identity **out-of-band** — on the execution context rather than on the
971
+ * request (Cloudflare Access attached to a Worker; IAP; an ALB OIDC
972
+ * action).
973
+ *
974
+ * Rated separately from the header-stamping form of the same product
975
+ * because only this one needs a host primitive. An identity-aware proxy
976
+ * that merely adds a signed header is portable by construction: any host
977
+ * that receives an HTTP request can verify it, which is why
978
+ * `@lunora/cloudflare-access` still works on a target rated
979
+ * `unsupported` here (it falls back to the `Cf-Access-Jwt-Assertion`
980
+ * JWT). What is not portable is the identity arriving beside the
981
+ * request, which is why `ExecutionContextLike.access` is a projection a
982
+ * host either populates or does not.
983
+ */
984
+ identityProxy?: Capability;
985
+ /** Image transforms (resize/format/optimize) via an Images binding. */
986
+ images?: Capability;
492
987
  /** Key-value storage (KV / Redis / DynamoDB). */
493
988
  keyValueStore?: Capability;
494
989
  /** Local SQL execution inside a shard. */
495
990
  localSql?: Capability;
991
+ /**
992
+ * Reading back the DURABLE `ctx.log` archive — the `logArchive` worker
993
+ * option behind the studio Logs panel's Archive feed and `lunora logs
994
+ * --durable`.
995
+ *
996
+ * Distinct from `pipelines` (which writes the records) because the read
997
+ * side needs two more things the write side does not: an Iceberg catalog
998
+ * over the object store, and an SQL engine that can query it
999
+ * (R2 Data Catalog + R2 SQL). A host that can ship log records to cold
1000
+ * storage but cannot query them back should say `unsupported` here.
1001
+ *
1002
+ * Advisory by nature: the archive table is named by a `createWorker`
1003
+ * option or the `LUNORA_LOG_ARCHIVE_TABLE` env var, neither of which
1004
+ * codegen sees. Both fail closed at runtime with a single
1005
+ * `LOG_ARCHIVE_NOT_CONFIGURED`, which the panel renders as an empty
1006
+ * state, so an unsupported host degrades visibly rather than silently.
1007
+ */
1008
+ logArchive?: Capability;
496
1009
  /** Email sending (Resend / SES / etc). */
497
1010
  mail?: Capability;
498
- /** Object storage (R2 / S3 / MinIO). */
1011
+ /**
1012
+ * `.memory()` tables — the ephemeral tier: rows cleared on every shard
1013
+ * cold start, never written to the CDC changelog, refilled by
1014
+ * `onShardInit`.
1015
+ *
1016
+ * The rating answers "does a memory table avoid durable storage on this
1017
+ * host", NOT "does it work". The lifetime semantics are the engine's and
1018
+ * hold everywhere; whether the rows actually stay out of the durable
1019
+ * store depends on the host offering a second, memory-backed SQL handle,
1020
+ * which is a per-target fact.
1021
+ */
1022
+ memoryTables?: Capability;
1023
+ /**
1024
+ * Paths into an object column — `where: { "profile.plan": "pro" }` and
1025
+ * `patch(id, { "profile.plan": "pro" })`.
1026
+ *
1027
+ * Carried by no host contract: a shard-local table reads into its JSON document with SQLite's
1028
+ * `json_extract`, and a `.global()` table does the same over its JSON-text column through
1029
+ * `@lunora/sql-store`, so it follows the `globalTables` backend. Postgres and MySQL behind
1030
+ * Hyperdrive read the same paths with their own JSON operators (`->` and `JSON_EXTRACT`), verified
1031
+ * against PGlite (Postgres 18) and MySQL 8.0; Postgres needs 16 or later for `pg_input_is_valid`. Every
1032
+ * path form works on all three, `contains` included, with no refusal. The partial update is made in the
1033
+ * engine and works everywhere. Advisory by nature (see the module header).
1034
+ */
1035
+ objectColumnPaths?: Capability;
1036
+ /**
1037
+ * Object storage (R2 / S3 / MinIO).
1038
+ *
1039
+ * `ctx.storage.deleteAfterCommit(key)` rides on this rating and gets no
1040
+ * key of its own: it needs no host primitive beyond the bucket. The
1041
+ * post-commit flush uses `ShardHost.waitUntil` where the host has one and
1042
+ * is awaited inline where it does not, so a host that can serve
1043
+ * `objectStorage` serves the deferral at the same level.
1044
+ */
499
1045
  objectStorage?: Capability;
1046
+ /**
1047
+ * Snapshot backups kept in object storage rather than on the machine
1048
+ * that took them — `lunora backup create|list|restore --bucket`, and
1049
+ * the platform's own `backupCron`. Distinct from
1050
+ * `objectStorage` above because it needs three things a
1051
+ * bucket alone does not imply: an admin-gated read of one object
1052
+ * (`GET /_lunora/admin/storage/object`), a checksum-verified write, and
1053
+ * a scheduler to run the unattended half.
1054
+ */
1055
+ objectStorageBackups?: Capability;
1056
+ /**
1057
+ * The CDC changelog's cold tier: rows a retention sweep is about to
1058
+ * destroy are written to an object-storage bucket first
1059
+ * (`LUNORA_CDC_ARCHIVE`), and a consumer whose cursor has fallen below
1060
+ * the retained window is served from there instead of being told to
1061
+ * re-seed.
1062
+ *
1063
+ * Distinct from `objectStorage` because it needs the bucket to do one
1064
+ * thing a plain byte store need not: resume a key-ordered listing from a
1065
+ * position (`list({ startAfter })`). Without it the read-back re-lists
1066
+ * the prefix from the front every time and stops finding the range it
1067
+ * needs once enough segments precede the cursor — which fails as a
1068
+ * refusal rather than a gap, but fails permanently and silently, so a
1069
+ * host that cannot seek should say `unsupported` here rather than
1070
+ * inherit `objectStorage`'s rating.
1071
+ */
1072
+ objectStorageCdcArchive?: Capability;
500
1073
  /** Pipelines / streaming data. */
501
1074
  pipelines?: Capability;
1075
+ /**
1076
+ * In-place point-in-time recovery of a shard — the `getPitrBookmark` /
1077
+ * `pitrRestore` admin ops and the studio page that drives them. They
1078
+ * need the host's own change log: a bookmark names a moment in it and a
1079
+ * restore rewinds the shard's database to one. Nothing in `ShardHost`
1080
+ * carries that, so a host without it answers `PITR_UNAVAILABLE`.
1081
+ *
1082
+ * Advisory for codegen by nature — an app declares nothing to gate on;
1083
+ * the admin ops are always wired. Studio reads it to mark the page
1084
+ * unavailable on a host that cannot serve it. The off-platform tier
1085
+ * (`lunora backup`) is {@link PlatformCapabilities.features.objectStorageBackups}.
1086
+ */
1087
+ pointInTimeRecovery?: Capability;
1088
+ /**
1089
+ * Query plans — `ctx.db.<table>.explain(args)`, which returns the SQL a `findMany`
1090
+ * compiles to and the database's own plan for it.
1091
+ *
1092
+ * Carried by no host contract: it prefixes a statement the engines already build with
1093
+ * `EXPLAIN QUERY PLAN` (SQLite, D1) or `EXPLAIN` (Postgres, MySQL), on `localSql` for a
1094
+ * shard-local table and through `@lunora/sql-store` on the `globalTables` backend. The plan
1095
+ * text is the database's own, so its wording differs by backend. Advisory by nature (see the
1096
+ * module header).
1097
+ */
1098
+ queryExplain?: Capability;
502
1099
  /** Queue-backed workpools. */
503
1100
  queues?: Capability;
1101
+ /**
1102
+ * `ctx.db.related(...)` — breadth-first traversal of the foreign-key
1103
+ * graph the schema's `v.id("target")` columns describe, returning each
1104
+ * reached row with its depth, the edge names walked to reach it, and a
1105
+ * depth-decaying score.
1106
+ *
1107
+ * Rated on its own key rather than folded into `localSql`, because the
1108
+ * two answer different questions: `localSql` says a shard can run SQL,
1109
+ * while this says a host can serve the traversal's read SHAPE — an
1110
+ * id lookup per out-edge and a batched `WHERE fk IN (...)` per in-edge,
1111
+ * repeated per hop within one request. A host whose reads are remote
1112
+ * enough that a multi-hop expansion cannot finish inside a request
1113
+ * should say `unsupported` here even though every individual read works.
1114
+ *
1115
+ * Gate-bearing: codegen sets the `relationGraph` `PlatformSignals` flag
1116
+ * from the schema IR's `v.id` columns, so a host rating it
1117
+ * `unsupported` refuses the app rather than emitting a `related` that
1118
+ * throws (or worse, silently returns nothing) on the first hop.
1119
+ */
1120
+ relationGraph?: Capability;
1121
+ /**
1122
+ * The origin a request reached the app on, forwarded to the shard with the
1123
+ * dispatch: `ctx.origin` in mutations and actions, and the base
1124
+ * `ctx.storage` signs URLs against when no `publicBaseUrl` is declared.
1125
+ *
1126
+ * The runtime reads it off `request.url`, never off a client header, so
1127
+ * the rating answers whether the HOST vouches for that URL's host. An app
1128
+ * signing URLs against it may hand them to other callers, and a host that
1129
+ * builds the URL from a caller-typed `Host` header lets that caller choose
1130
+ * where they point.
1131
+ *
1132
+ * Advisory by nature: every target populates it, so codegen has nothing
1133
+ * to omit. Where the host does not vouch for it, configure a base
1134
+ * (`publicBaseUrl`, or the storage registry item's
1135
+ * `STORAGE_PUBLIC_BASE_URL`), which always wins over the origin.
1136
+ */
1137
+ requestOrigin?: Capability;
504
1138
  /** Cron triggers / scheduled functions. */
505
1139
  scheduler?: Capability;
506
1140
  /** Secrets management. */
507
1141
  secrets?: Capability;
1142
+ /**
1143
+ * `onQueryChange` reactors — server-side reactivity: a subscriber that is
1144
+ * not a socket, woken after a write flush when a watched read's result
1145
+ * changed.
1146
+ *
1147
+ * Host-dependent because the whole mechanism rests on the host being able
1148
+ * to run work AFTER a write commits, on the same shard, without a client
1149
+ * connection to hang it off — and on that work being serialized against
1150
+ * further writes so a reactor's own writes cascade deterministically
1151
+ * rather than interleaving.
1152
+ */
1153
+ serverReactors?: Capability;
1154
+ /**
1155
+ * Service bindings to sibling Workers — `lunora.config.*` `services` →
1156
+ * `ctx.services.<name>` on actions (plan 457): a fetch `Fetcher`, or an
1157
+ * RPC stub for a `WorkerEntrypoint`. Gate-bearing: codegen sets the
1158
+ * `services` `PlatformSignals` flag on a declared service.
1159
+ */
1160
+ services?: Capability;
508
1161
  /** Alarms / scheduled wakeup inside a shard. */
509
1162
  shardAlarms?: Capability;
510
1163
  /** Durable Object-style sharded state. */
511
1164
  shardedState?: Capability;
1165
+ /** Geographic placement of a shard (`ShardPlacement.locationHint`). */
1166
+ shardPlacement?: Capability;
1167
+ /** Region-local read replicas of a shard, for one-shot queries. */
1168
+ shardReadReplicas?: Capability;
1169
+ /**
1170
+ * Pub/Sub topics — `defineTopic` / `defineSubscription` → `ctx.topics`.
1171
+ * Each subscription deploys as its own queue and a publish sends to all
1172
+ * of them, so this is never better than `queues` on the same host.
1173
+ * Gate-bearing: codegen sets the `topics` `PlatformSignals` flag on a
1174
+ * `defineTopic` export.
1175
+ */
1176
+ topics?: Capability;
512
1177
  /** Vector database (Vectorize / pgvector / Pinecone). */
513
1178
  vectorStore?: Capability;
514
1179
  /** Hibernated WebSocket subscriptions. */
515
1180
  websocketHibernation?: Capability;
1181
+ /**
1182
+ * Worker Loaders (Dynamic Workers): load code supplied at runtime into
1183
+ * its own isolate with an `env`, egress and CPU budget the loader
1184
+ * chooses. `@lunora/agent`'s `jsCodeTool` runs model-written scripts
1185
+ * through it. Gate-bearing: codegen sets the `workerLoaders`
1186
+ * `PlatformSignals` flag on a `jsCodeTool` import in `lunora/`.
1187
+ */
1188
+ workerLoaders?: Capability;
1189
+ /**
1190
+ * Compensation for a failed workflow — `defineStep({ rollback })`,
1191
+ * which `@lunora/workflow` forwards to the host's `step.do` rollback
1192
+ * option.
1193
+ *
1194
+ * Rated apart from `workflows` because a host can run steps, sleeps and
1195
+ * events and still not implement rollback, and there the step does not
1196
+ * run without it — it fails. Gate-bearing: codegen sets the
1197
+ * `workflowRollback` `PlatformSignals` flag when a `defineStep` call
1198
+ * declares a `rollback`, so the gap is a build-time diagnostic rather
1199
+ * than a step that fails on first use.
1200
+ */
1201
+ workflowRollback?: Capability;
516
1202
  /** Durable workflows (step-based). */
517
1203
  workflows?: Capability;
1204
+ /**
1205
+ * Cron-started workflow instances — `defineWorkflow({ schedules })`,
1206
+ * written to the wrangler `workflows[].schedules` list, where the host
1207
+ * itself creates an instance on each tick (no `scheduled()` handler).
1208
+ *
1209
+ * Rated apart from `workflows` and from `cronTriggers` because it is
1210
+ * neither: a host can run workflows it is asked to create, and dispatch
1211
+ * declared crons to `scheduled()`, and still never start a workflow off
1212
+ * its own schedule list. That host would build the app green and the
1213
+ * workflow would simply never run. Gate-bearing: codegen sets the
1214
+ * `workflowSchedules` `PlatformSignals` flag when a workflow declares
1215
+ * `schedules`.
1216
+ */
1217
+ workflowSchedules?: Capability;
518
1218
  };
519
1219
  /** Platform identifier used in codegen and config (e.g. "cloudflare", "aws"). */
520
1220
  id: string;
521
1221
  /** Human-readable platform name (e.g. "Cloudflare", "AWS", "Rivet"). */
522
1222
  name: string;
523
1223
  }
1224
+ /**
1225
+ * The celld capability matrix — `@lunora/platform-celld`'s honest self-rating.
1226
+ *
1227
+ * celld (github.com/denoland/celld) is a self-hosted, distributed Durable
1228
+ * Objects daemon: each node embeds V8, executes Wrangler bundles, and
1229
+ * coordinates ownership through an S3-compatible (or GCS / Azure Blob) bucket
1230
+ * instead of a control plane. Because it implements the Workers/Durable Object
1231
+ * API itself, the Cloudflare host adapters ARE the celld host adapters — what
1232
+ * differs is which primitives exist, and that difference is exactly this
1233
+ * matrix.
1234
+ *
1235
+ * Ratings track celld **v0.6.1** and derive from its documented compatibility
1236
+ * surface (`docs/cloudflare-compat.md`, `docs/services/*.md`,
1237
+ * `docs/limitations.md` in the celld repo; v0.6.0 was celld's first beta). The host contracts
1238
+ * behind `shardedState`, `localSql`, `shardAlarms`, `commitOrderedTables` and
1239
+ * `websocketHibernation` are also exercised by the conformance TCK against a
1240
+ * live single-node celld (`@lunora/platform-celld`'s `celld` vitest project),
1241
+ * which also drives D1, KV, R2, Queues, Workflows and Cron Triggers through
1242
+ * Lunora's own adapters and runs a `LunoraContainer`, and a two-node fleet
1243
+ * test covers routing, crash takeover and rebalancing. celld's own rule is
1244
+ * that an unsupported configuration or API must fail at deploy or first use,
1245
+ * so "Partial" there means a listed set of gaps rather than silent
1246
+ * degradation; the gaps that bite Lunora are named per key below.
1247
+ *
1248
+ * `shardPlacement` and `shardReadReplicas` stay `unsupported`, and by celld's
1249
+ * own design rather than a gap: its Durable Object docs state it "makes no
1250
+ * placement, migration, or jurisdiction promise". Nodes carry no region, and
1251
+ * rebalancing moves hibernated cells toward each node's `CELLD_PLACEMENT_WEIGHT`
1252
+ * share, not toward a reader — so there is no location to hint at and no region
1253
+ * to place a read replica in. Revisit only if celld grows node regions.
1254
+ */
1255
+ declare const CELLD_CAPABILITIES: PlatformCapabilities;
524
1256
  /**
525
1257
  * The Cloudflare capability matrix — the reference implementation.
526
1258
  *
@@ -535,33 +1267,72 @@ declare const CLOUDFLARE_CAPABILITIES: PlatformCapabilities;
535
1267
  * The Node capability matrix — `@lunora/platform-node`'s honest self-rating
536
1268
  * (plan 234).
537
1269
  *
538
- * `@lunora/platform-node` is a spike: a `ShardHost`/`SocketHost`/
539
- * `ShardDirectory`/`ShardKvStore`/`SchedulerHost` implementation over
540
- * `better-sqlite3` and an in-process registry, built to run the conformance
541
- * TCK against a second host and discover what the contracts under-specify.
542
- * It is a single Node process with no distributed placement, no host-level
543
- * scheduler to re-arm timers after a restart, and no bindings at all for the
544
- * Cloudflare-specific products (R2, Vectorize, Workers AI, Queues,
545
- * Workflows, Containers, Browser Rendering, Analytics Engine, Secrets Store,
546
- * Hyperdrive) most `ctx.*` surfaces are built on. Every one of those is
547
- * rated `"unsupported"` here rather than left undeclared — see
548
- * `gateAgainstMatrix` in `@lunora/codegen`, whose fail-closed gate (plan
549
- * 229) treats an undeclared feature as unsupported anyway, but under a
550
- * different diagnostic name than an honest, explicit rating.
551
- *
552
- * Two features are rated `"emulated"` rather than `"native"` even though
553
- * this package fully implements their contract, because "native" would
554
- * overstate what a bare Node process provides on its own: `keyValueStore` is
555
- * a SQL table wearing a KV-shaped API, not a dedicated KV product, and
556
- * `websocketHibernation` never actually evicts a socket to save memory — it
557
- * only proves the attachment/tag durability half of the contract, not real
558
- * hibernation. `scheduler` and `shardAlarms` are rated `"unsupported"`, not
559
- * `"emulated"`: the Node host stores and times both, but its timer body only
560
- * clears bookkeeping — nothing dispatches the scheduled function or wakes the
561
- * alarm callback. `"emulated"` means built on lower-level primitives and
562
- * working; never-dispatched is not that (plan 267). `globalTables` is also
563
- * `"unsupported"` — no replicated SQL store is implemented. All ratings are
564
- * argued in detail in `plans/234-node-host-findings.md`.
1270
+ * `@lunora/platform-node` implements every contract in this package
1271
+ * (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
1272
+ * `SchedulerHost`) over `better-sqlite3` and an in-process registry, plus the
1273
+ * `.global()` table backend via `@lunora/sql-store`. It began as a spike to run
1274
+ * the conformance TCK against a second host; the durability gaps that spike
1275
+ * surfaced — alarms and scheduler jobs that were persisted but never re-armed,
1276
+ * socket attachments that lived only in memory — are closed, and each is now
1277
+ * pinned by a restart test rather than only by a simulated recycle.
1278
+ *
1279
+ * `scheduler` and `shardAlarms` were rated `"unsupported"` under plan 267, on
1280
+ * the grounds that the host stored and timed both while its timer body only
1281
+ * cleared bookkeeping — nothing dispatched the scheduled function or woke the
1282
+ * alarm. That rating was correct for the code it described, and the code is
1283
+ * what changed: both now dispatch (through `onDispatch` / `onAlarm`) and both
1284
+ * re-arm from their durable rows on construction, so `"emulated"` — built on
1285
+ * lower-level primitives and *working* — is now the honest reading.
1286
+ *
1287
+ * What remains genuinely absent is everything a single Node process cannot
1288
+ * distribute: placement across nodes, failover, and most Cloudflare-specific
1289
+ * product bindings (Vectorize, Workers AI, Containers, Browser Rendering,
1290
+ * Analytics Engine, Secrets Store, Hyperdrive). Workflows, object storage and
1291
+ * queues are the three that CAN be emulated locally — `defineWorkflow` handlers
1292
+ * compile onto the `@visulima/workflow` engine, R2 becomes a filesystem bucket,
1293
+ * and Queues becomes a durable table with the same batch/ack/retry/dead-letter
1294
+ * semantics — so those three are rated `"emulated"`; the rest of the Cloudflare
1295
+ * products most `ctx.*` surfaces are built on are rated `"unsupported"` here
1296
+ * rather than left undeclared — see `gateAgainstMatrix` in `@lunora/codegen`,
1297
+ * whose fail-closed gate (plan 229) treats an undeclared feature as unsupported
1298
+ * anyway, but under a different diagnostic name than an honest, explicit rating.
1299
+ *
1300
+ * Almost nothing here is rated `"native"`, and that is the matrix's own
1301
+ * definition doing its job rather than a hedge: `native` means the platform
1302
+ * itself provides the feature, and a bare Node process provides essentially
1303
+ * none of them — Lunora builds alarms out of `setTimeout` plus a durable row,
1304
+ * a KV store out of a SQL table, and `.global()` tables out of a second SQLite
1305
+ * file. `localSql` is the exception, because SQLite genuinely is the platform
1306
+ * primitive there. The ratings say who does the work; the notes say how well.
1307
+ * Both are argued in detail in `plans/234-node-host-findings.md`.
565
1308
  */
566
1309
  declare const NODE_CAPABILITIES: PlatformCapabilities;
567
- export { type AnalyticsEngineDataPoint, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, CLOUDFLARE_CAPABILITIES, type Capability, type CapabilityLevel, type D1DatabaseLike, type D1PreparedStatementLike, type D1SessionLike, type ExecutionContextLike, type KVNamespaceLike, type KvGetOptions, type KvListKey, type KvNamespaceListResult, type KvNamespacePutOptions, type KvValue, type KvValueType, type MessageBatchLike, type MessageLike, type MessageSendRequestLike, NODE_CAPABILITIES, NOOP_EXECUTION_CONTEXT, type PlatformCapabilities, type QueueBindingLike, type QueueContentType, type QueueMessageLike, type QueueRetryOptions, type QueueSendBatchOptions, type QueueSendOptions, type QueueSendOptionsLike, type QueueSendRequestLike, type R2BucketLike, type R2MultipartUploadLike, type R2ObjectBodyLike, type R2ObjectLike, type R2RangeLike, type R2UploadedPartLike, type VectorMatchLike, type VectorMetric, type VectorRecordLike, type VectorizeDeleteMutation, type VectorizeIndexDetails, type VectorizeIndexLike, type VectorizeMatch, type VectorizeMatches, type VectorizeQueryOptions, type VectorizeUpsertMutation, type VectorizeVector };
1310
+ export { type AnalyticsEngineDataPoint, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, CELLD_CAPABILITIES, CLOUDFLARE_CAPABILITIES, type Capability, type CapabilityLevel, type D1DatabaseLike, type D1PreparedStatementLike, type D1SessionLike,
1311
+ /**
1312
+ * `@lunora/platform` — provider-neutral host contracts for Lunora.
1313
+ *
1314
+ * This package defines the structural interfaces that separate the Lunora
1315
+ * engine from any specific host (Cloudflare Workers, AWS, Rivet, Node, etc.).
1316
+ * It contains **types and capability metadata only** — near-zero runtime code.
1317
+ *
1318
+ * The contracts fall into four groups:
1319
+ *
1320
+ * 1. **Shard host** (`ShardHost`) — single-writer execution, transactions,
1321
+ * local SQL, alarms, and background continuation per shard key.
1322
+ * 2. **Socket host** (`SocketHost`) — hibernated WebSocket subscriptions with
1323
+ * durable attachments and tagged fan-out.
1324
+ * 3. **Shard directory** (`ShardDirectory`) — deterministic placement and RPC
1325
+ * dispatch from shard keys to stubs.
1326
+ * 4. **Scheduler host** (`SchedulerHost`) — durable delayed jobs, cron, and
1327
+ * at-least-once dispatch.
1328
+ *
1329
+ * Plus canonical binding projections (`KVNamespaceLike`, `R2BucketLike`,
1330
+ * `QueueBindingLike`, `D1DatabaseLike`, `VectorizeIndexLike`, …) and the
1331
+ * `PlatformCapabilities` matrix that codegen uses to tailor emitted types per
1332
+ * target.
1333
+ *
1334
+ * This package is **zero-dependency** and safe on every runtime (browser,
1335
+ * workerd, Node). It is intended to be the leaf dependency every other
1336
+ * `@lunora/*` package can import without creating cycles.
1337
+ */
1338
+ type ExecutionContextLike, type HttpCacheLike, type HttpCacheQueryOptions, type KVNamespaceLike, type KvGetOptions, type KvListKey, type KvNamespaceListResult, type KvNamespacePutOptions, type KvValue, type KvValueType, type MessageBatchLike, type MessageLike, type MessageSendRequestLike, NODE_CAPABILITIES, NOOP_EXECUTION_CONTEXT, type PlatformCapabilities, type QueueBindingLike, type QueueContentType, type QueueMessageLike, type QueueRetryOptions, type QueueSendBatchOptions, type QueueSendOptions, type QueueSendOptionsLike, type QueueSendRequestLike, type R2BucketLike, type R2ConditionalLike, type R2MultipartUploadLike, type R2ObjectBodyLike, type R2ObjectLike, type R2PutBodyLike, type R2PutOptionsLike, type R2RangeLike, type R2UploadedPartLike, type VectorMatchLike, type VectorMetric, type VectorRecordLike, type VectorValues, type VectorizeDeleteMutation, type VectorizeIndexDetails, type VectorizeIndexLike, type VectorizeMatch, type VectorizeMatches, type VectorizeQueryOptions, type VectorizeUpsertMutation, type VectorizeVector };