@klum-db/lobby 0.2.0-pre.27 → 0.2.0-pre.28

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.
@@ -0,0 +1,763 @@
1
+ import { Vault as Vault$1, IndexDef, Operator, LiveQuery, LiveAggregation, AggregateSpec, AggregateResult, Collection, Noydb, Query, JoinStrategy, ChangeEvent } from '@noy-db/hub/kernel';
2
+ import { Vault } from '@noy-db/hub';
3
+ import { DecryptedRecord } from '@noy-db/hub/bundle';
4
+
5
+ /**
6
+ * @klum-db/lobby interchange — field-authority conflict resolution (FR-4).
7
+ *
8
+ * Pure functions: given an app-supplied per-field policy and both sides'
9
+ * RECORD-level provenance (FR-5 `_source`/`_sourceTs`), decide field-by-field
10
+ * whether the incoming value wins. No vault I/O — unit-testable in isolation.
11
+ * @module
12
+ */
13
+ /** How a single field's authority is decided on merge. */
14
+ type FieldAuthorityRule = {
15
+ readonly authority: 'source-newest';
16
+ } | {
17
+ readonly authority: 'owner';
18
+ readonly ownerSource: string;
19
+ } | {
20
+ readonly authority: 'fixed-source';
21
+ readonly source: string;
22
+ };
23
+ /** Per-collection field → rule map. Fields not listed default to keep-local. */
24
+ type FieldAuthorityPolicy = Record<string, FieldAuthorityRule>;
25
+ /** Record-level provenance for both sides (shared across all fields — Q3 defer). */
26
+ interface FieldAuthorityInputs {
27
+ readonly incomingSource?: string;
28
+ readonly incomingSourceTs?: string;
29
+ readonly localSource?: string;
30
+ readonly localSourceTs?: string;
31
+ }
32
+ /** Thrown when a collection resolves to `field-authority` but no policy is supplied for it. */
33
+ declare class FieldAuthorityPolicyMissingError extends Error {
34
+ constructor(collection: string);
35
+ }
36
+ /** Decide whether the INCOMING value of one field wins. Pure. Defaults never clobber on ambiguity. */
37
+ declare function resolveFieldAuthority(rule: FieldAuthorityRule, io: FieldAuthorityInputs): 'incoming' | 'local';
38
+ /**
39
+ * Build the merged record per policy, starting from the local (`before`) copy
40
+ * and overlaying only the changed fields whose rule resolves to `incoming`.
41
+ * Returns the merged record plus a per-field decision map (for the audit report).
42
+ * Pure — no I/O.
43
+ */
44
+ declare function resolveRecordByFieldAuthority(policy: FieldAuthorityPolicy, before: Record<string, unknown>, incoming: Record<string, unknown>, changedFields: readonly string[], io: FieldAuthorityInputs): {
45
+ merged: Record<string, unknown>;
46
+ decisions: Record<string, 'incoming' | 'local'>;
47
+ };
48
+
49
+ /**
50
+ * @klum-db/lobby interchange — reconcile an incoming extracted-partition
51
+ * compartment into an existing receiver vault (FR-3).
52
+ *
53
+ * `mergeCompartment(receiver, compartmentBytes, opts)` → `MergeReport`:
54
+ * 1. Decrypt the incoming bytes via hub's `decryptExtractedPartition`.
55
+ * 2. `diffVault(receiver, incoming)` classifies records as added / modified
56
+ * / deleted (deleted = receiver-only slice, ignored per FR-3 semantics).
57
+ * 3. Resolve each `modified` entry per the per-collection strategy.
58
+ * 4. Apply writes via `collection.put(id, rec, { reason })` (unless dryRun).
59
+ *
60
+ * @module
61
+ */
62
+
63
+ /** Per-collection conflict strategy. `field-level` is a deprecated alias for `field-authority`. */
64
+ type MergeStrategy = 'take-incoming' | 'keep-local' | 'lww-by-ts' | 'manual-queue' | 'field-authority'
65
+ /** @deprecated Use `field-authority` instead. */
66
+ | 'field-level';
67
+ /** Options for the decrypted-records merge path (no bundle, no transfer key). */
68
+ interface DecryptedMergeOptions {
69
+ readonly strategy: MergeStrategy | (Record<string, MergeStrategy> & {
70
+ default?: MergeStrategy;
71
+ });
72
+ readonly dryRun?: boolean;
73
+ /** Audit reason stamped on every write. Defaults to `'merge:compartment'`. */
74
+ readonly reason?: string;
75
+ /** Per-collection field→authority policy. Required for any collection using `field-authority`. */
76
+ readonly fieldAuthority?: Record<string, FieldAuthorityPolicy>;
77
+ }
78
+ /** Options for the bundle merge path — adds the transfer key for decryption. */
79
+ interface MergeCompartmentOptions extends DecryptedMergeOptions {
80
+ readonly transferKey: Uint8Array;
81
+ }
82
+ interface MergeConflict {
83
+ readonly collection: string;
84
+ readonly id: string;
85
+ readonly strategy: MergeStrategy;
86
+ readonly resolution: 'incoming' | 'local' | 'queued' | 'field-merged';
87
+ }
88
+ interface MergeReport {
89
+ readonly vault: string;
90
+ readonly dryRun: boolean;
91
+ readonly summary: {
92
+ readonly inserted: number;
93
+ readonly updated: number;
94
+ readonly skipped: number;
95
+ readonly queued: number;
96
+ readonly total: number;
97
+ };
98
+ readonly byCollection: Record<string, {
99
+ readonly inserted: number;
100
+ readonly updated: number;
101
+ readonly skipped: number;
102
+ readonly queued: number;
103
+ }>;
104
+ /**
105
+ * One entry per `modified` (id-collision) record, regardless of outcome —
106
+ * including `take-incoming` overwrites (`resolution: 'incoming'`). This is a
107
+ * full audit trail of every conflict the merge encountered and how it was
108
+ * resolved, not just the ones that were skipped or queued.
109
+ */
110
+ readonly conflicts: readonly MergeConflict[];
111
+ }
112
+ /**
113
+ * @deprecated No longer thrown — `field-level` is now a deprecated alias for
114
+ * `field-authority` and resolves via the field-authority resolver (FR-4).
115
+ * Kept for backwards compatibility of existing imports.
116
+ */
117
+ declare class FieldLevelDeferredError extends Error {
118
+ constructor(collection: string);
119
+ }
120
+ /**
121
+ * Merge an already-decrypted record set into the receiver. The shared core of
122
+ * `mergeCompartment` (decrypt → this) and `migrateThenMerge` (decrypt → migrate → this).
123
+ * `decrypted` is keyed by collection; each record carries id/record/ts/source/sourceTs.
124
+ *
125
+ * Semantics:
126
+ * - **added**: in incoming, not in receiver → always insert (every strategy).
127
+ * - **modified**: in both, body differs → resolve per collection strategy.
128
+ * - **deleted**: in receiver, not in incoming → IGNORE (incoming is a slice;
129
+ * never delete receiver rows).
130
+ * - **unchanged**: no-op.
131
+ *
132
+ * Returns a {@link MergeReport} describing what was (or would be) written.
133
+ * When `dryRun: true` the report is fully computed but no `put()` is called.
134
+ *
135
+ * Writes are applied sequentially and **non-transactionally**: a `put()`
136
+ * failure mid-loop (e.g. schema mismatch or a storage error) rejects the
137
+ * returned promise but leaves the receiver partially merged. Use `dryRun`
138
+ * first to validate the plan when partial application is unacceptable.
139
+ */
140
+ declare function mergeDecryptedRecords(receiver: Vault, decrypted: Record<string, readonly DecryptedRecord[]>, opts: DecryptedMergeOptions): Promise<MergeReport>;
141
+ /**
142
+ * Reconcile an incoming extracted-partition compartment into a receiver vault.
143
+ * Decrypts the compartment bytes then delegates to {@link mergeDecryptedRecords}.
144
+ *
145
+ * See {@link mergeDecryptedRecords} for full semantics, dryRun behaviour, and
146
+ * the non-transactional write caveat.
147
+ */
148
+ declare function mergeCompartment(receiver: Vault, compartmentBytes: Uint8Array, opts: MergeCompartmentOptions): Promise<MergeReport>;
149
+
150
+ /**
151
+ * @category capability
152
+ * Multi-vault partition federation (MVF) — public types for VaultGroup
153
+ * transparent shard routing. See
154
+ * docs/superpowers/specs/2026-06-07-mvf-vaultgroup-routing-mvp-design.md.
155
+ */
156
+
157
+ /**
158
+ * A schema blueprint for a class of shard vaults. `configure` is
159
+ * re-applied to every shard handle so all shards are configured
160
+ * identically (collections, indexes, schemas). `version` is recorded
161
+ * into each shard's registry row and drives the fan-out
162
+ * `minVersion` guard.
163
+ */
164
+ interface VaultTemplate {
165
+ readonly version: number;
166
+ readonly configure: (vault: Vault$1) => void;
167
+ }
168
+ /** One row in the StateManagement `vault-registry` collection. */
169
+ interface VaultRegistryRow {
170
+ readonly vaultId: string;
171
+ readonly partitionKey: string;
172
+ readonly templateName: string;
173
+ readonly schemaVersion: number;
174
+ readonly createdAt: number;
175
+ /** Which VaultGroup this shard belongs to (registry is shared across groups). */
176
+ readonly group: string;
177
+ }
178
+ /** How a VaultGroup maps records to shards. */
179
+ interface ShardingConfig<T> {
180
+ /** Extract the partition key from a record. */
181
+ readonly keyOf: (record: T) => string;
182
+ /** Name of the template (registered via `withVaultTemplate`) shards are stamped from. */
183
+ readonly vaultTemplate: string;
184
+ /** When a write targets an unknown partition key, stamp a shard inline. Default `true`. */
185
+ readonly autoCreate?: boolean;
186
+ /**
187
+ * Data-residency guard (#271): the geographic region a record's shard must
188
+ * live in (e.g. `'eu'`). When set, `createShard` resolves the candidate
189
+ * backend (via `routeStore`'s vault-prefix routing) and throws
190
+ * `DataResidencyError` if its `capabilities.region` doesn't match — so a
191
+ * shard never lands on a non-compliant backend. Advisory until a region is
192
+ * declared on the backing store; pair with `routeStore({ vaultRoutes })`
193
+ * and a region-encoded partition key (e.g. `eu-acme` → `firm--eu-`).
194
+ */
195
+ readonly regionOf?: (record: T) => string;
196
+ }
197
+ /** Options for `Noydb.openVaultGroup`. */
198
+ interface VaultGroupOptions<T> {
199
+ /**
200
+ * The `vault-registry` collection (source of truth for shard discovery).
201
+ * Optional: when omitted, the reserved StateManagement vault's registry
202
+ * is auto-opened and used.
203
+ */
204
+ readonly registry?: Collection<VaultRegistryRow>;
205
+ readonly sharding: ShardingConfig<T>;
206
+ /**
207
+ * Lazy migrate-on-open (#271 fleet migration). When `true`, opening a shard
208
+ * whose registry `schemaVersion` is behind the template's version runs that
209
+ * shard's cutover inline (via `migrateShard`) before surfacing the handle.
210
+ * Zero cost for shards never opened. Default `false` (use `migrateFleet`).
211
+ */
212
+ readonly migrateOnOpen?: boolean;
213
+ }
214
+ /** Result of `VaultGroup.migrateFleet` (#271 active batch runner). */
215
+ interface FleetMigrationResult {
216
+ /** The version migrated toward (the template's current version). */
217
+ readonly target: number;
218
+ /** vaultIds successfully migrated (or already current). */
219
+ readonly migrated: string[];
220
+ /** vaultIds whose cutover failed, with the error message. */
221
+ readonly failed: {
222
+ readonly vaultId: string;
223
+ readonly error: string;
224
+ }[];
225
+ }
226
+ /** Options for a cross-shard fan-out read. */
227
+ interface FanoutQueryOptions {
228
+ /** Skip shards whose registry `schemaVersion` is below this. */
229
+ readonly minVersion?: number;
230
+ /** Max shards queried in parallel (passed to queryAcross). Default 1. */
231
+ readonly concurrency?: number;
232
+ }
233
+ /** A shard excluded from a fan-out result, with the reason. */
234
+ interface SkippedVault {
235
+ readonly vaultId: string;
236
+ readonly reason: 'schema-drift' | 'error' | 'no-grant';
237
+ readonly error?: Error;
238
+ }
239
+ /** The result of a cross-shard fan-out read. */
240
+ interface FanoutResult<R> {
241
+ readonly results: R[];
242
+ readonly skippedVaults: SkippedVault[];
243
+ }
244
+ /** A single captured where-clause, replayed inside each shard. */
245
+ interface WhereClause {
246
+ readonly field: string;
247
+ readonly op: Operator;
248
+ readonly value: unknown;
249
+ }
250
+ /** Options for the live/aggregate fan-out (extends the one-shot opts). */
251
+ interface LiveQueryOptions extends FanoutQueryOptions {
252
+ /** Coalesce window before recompute. Default 0 (microtask). */
253
+ readonly debounceMs?: number;
254
+ }
255
+ /** A grouped aggregate output row: the grouped field + the reduced spec result. */
256
+ type GroupedRow<F extends string, Spec extends AggregateSpec> = {
257
+ readonly [K in F]: unknown;
258
+ } & AggregateResult<Spec>;
259
+ /** Reactive cross-shard record (or grouped-row) query — array-shaped, mirrors LiveQuery<T>. */
260
+ interface CrossVaultLiveQuery<T> extends LiveQuery<T> {
261
+ readonly skippedVaults: readonly SkippedVault[];
262
+ readonly ready: Promise<void>;
263
+ }
264
+ /** Reactive cross-shard scalar aggregate — mirrors LiveAggregation<R>. */
265
+ interface CrossVaultLiveAggregation<R> extends LiveAggregation<R> {
266
+ readonly skippedVaults: readonly SkippedVault[];
267
+ readonly ready: Promise<void>;
268
+ }
269
+ /**
270
+ * Context passed to a cross-vault `derive` callback (#271 Insight Vault).
271
+ * One call per shard; identifies which shard the records came from.
272
+ */
273
+ interface CrossVaultDerivationContext {
274
+ /** The shard's vault id (e.g. `firm-clients--acme`). */
275
+ readonly vaultId: string;
276
+ /** The shard's partition key (e.g. `acme`). */
277
+ readonly partitionKey: string;
278
+ /** The shard's schema/template version, from its registry row. */
279
+ readonly schemaVersion: number;
280
+ }
281
+ /**
282
+ * A push-model cross-vault derivation (#271, Insight Vault — Layer 4).
283
+ *
284
+ * For each eligible shard, `refreshInsights()` reads the shard's `source`
285
+ * collection, runs `derive` on that shard's records, and writes the returned
286
+ * summary row into a separate analytics ("Insight") vault — keyed by partition
287
+ * key, one row per shard. The summary is re-encrypted under the Insight Vault's
288
+ * own DEK; the shard's ciphertext never leaves its DEK boundary (the push model
289
+ * that resolves the cross-vault DEK conflict). See the ZK note in the spec —
290
+ * the Insight Vault backend sees aggregated structure across shards, a weaker
291
+ * profile than per-shard vaults; opt-in.
292
+ */
293
+ interface CrossVaultDerivationSpec<R = Record<string, unknown>, S = Record<string, unknown>> {
294
+ /** Collection read from each shard. */
295
+ readonly source: string;
296
+ /** Destination Insight Vault + collection for the per-shard summary rows. */
297
+ readonly target: {
298
+ readonly vault: string;
299
+ readonly collection: string;
300
+ };
301
+ /** Per-shard reducer: that shard's source records + context → one summary row. */
302
+ readonly derive: (records: R[], ctx: CrossVaultDerivationContext) => S;
303
+ /**
304
+ * Opt in to auto-push-on-write (#12): when a write lands on a shard's
305
+ * `source` collection, recompute and push that shard's summary
306
+ * automatically (coalesced per microtask). Default off — without this the
307
+ * derivation is explicit-refresh-only (drive it with `refreshInsights()`).
308
+ */
309
+ readonly autoPush?: boolean;
310
+ }
311
+ /** The result of `refreshInsights()`. */
312
+ interface RefreshInsightsResult {
313
+ /** Number of summary rows written (one per eligible shard × registered derivation). */
314
+ readonly written: number;
315
+ /** Shards excluded (schema-drift, unprovisioned, or read error). */
316
+ readonly skippedVaults: SkippedVault[];
317
+ }
318
+ /** A serializable blueprint captured from a VaultTemplate.configure run. */
319
+ interface CapturedBlueprint {
320
+ /** Sorted collection names declared by the template. */
321
+ readonly collections: string[];
322
+ /** Per-collection index defs (key order canonicalized). */
323
+ readonly indexes: Record<string, IndexDef[]>;
324
+ /** Collections that declared `persistJsonSchema: true`. */
325
+ readonly persistJsonSchema: string[];
326
+ }
327
+ /** One row in the StateManagement `schema-manifest` collection, keyed by `${templateName}:${version}`. */
328
+ interface SchemaManifestRow {
329
+ readonly templateName: string;
330
+ readonly version: number;
331
+ readonly collections: string[];
332
+ readonly indexes: Record<string, IndexDef[]>;
333
+ readonly persistJsonSchema: string[];
334
+ /** sha256 over the canonicalized serializable blueprint. */
335
+ readonly fingerprint: string;
336
+ readonly recordedAt: number;
337
+ }
338
+ /** One row in the append-only StateManagement `deployment-events` collection. */
339
+ interface DeploymentEvent {
340
+ readonly id: string;
341
+ readonly ts: number;
342
+ readonly type: 'shard-created' | 'manifest-recorded' | 'group-opened' | 'migration-started' | 'migration-completed' | 'migration-failed' | 'unit-graduated';
343
+ readonly group: string;
344
+ readonly vaultId?: string;
345
+ readonly templateName?: string;
346
+ readonly version?: number;
347
+ readonly actor?: string;
348
+ /** Free-form detail (e.g. migration error message). */
349
+ readonly detail?: string;
350
+ }
351
+ /**
352
+ * One row in the StateManagement `migration-status` collection (#271 fleet
353
+ * schema-migration runner), keyed by `vaultId`. Tracks each shard's progress
354
+ * toward the template's current version so the active batch runner is
355
+ * resumable and the staged rollout can verify a cohort before proceeding.
356
+ */
357
+ interface MigrationStatusRow {
358
+ readonly vaultId: string;
359
+ readonly group: string;
360
+ /** The shard's registry schemaVersion at the time of this status. */
361
+ readonly currentVersion: number;
362
+ /** The version the runner is moving this shard to (the template's version). */
363
+ readonly targetVersion: number;
364
+ readonly status: 'pending' | 'running' | 'done' | 'failed';
365
+ readonly startedAt?: number;
366
+ readonly finishedAt?: number;
367
+ /** Records migrated by the per-shard cutover (when status `done`). */
368
+ readonly migrated?: number;
369
+ readonly error?: string;
370
+ }
371
+ /** Which direction the sync flows across the surface boundary. */
372
+ type SurfaceDirection = 'push' | 'pull' | 'bidi';
373
+ /** Lifecycle state of a bilateral surface agreement. */
374
+ type SurfaceStatus = 'proposed' | 'agreed' | 'suspended';
375
+ /**
376
+ * Conflict resolution policy for a Surface.
377
+ * `strategy` may be a single MergeStrategy applied to all collections, or a
378
+ * per-collection map (with an optional `default` fallback).
379
+ */
380
+ interface SurfaceConflictPolicy {
381
+ readonly strategy: MergeStrategy | (Record<string, MergeStrategy> & {
382
+ default?: MergeStrategy;
383
+ });
384
+ readonly fieldAuthority?: Record<string, FieldAuthorityPolicy>;
385
+ }
386
+ /**
387
+ * Persisted row in the StateManagementVault `surfaces` collection (FR-7).
388
+ * Describes a bilaterally-agreed subset of collections/fields that two parties
389
+ * sync in a given direction with a given conflict policy.
390
+ */
391
+ interface SurfaceRow {
392
+ readonly id: string;
393
+ /** Collection names included in this surface. */
394
+ readonly collections: readonly string[];
395
+ /** Per-collection field allow-lists (omit = all fields). */
396
+ readonly fields?: Record<string, readonly string[]>;
397
+ readonly direction: SurfaceDirection;
398
+ readonly conflictPolicy: SurfaceConflictPolicy;
399
+ /** Sync cadence in milliseconds (undefined = manual only). */
400
+ readonly cadenceMs?: number;
401
+ readonly status: SurfaceStatus;
402
+ readonly proposedBy: string;
403
+ readonly agreedBy?: string;
404
+ readonly createdAt: number;
405
+ readonly lastSyncAt?: number;
406
+ readonly nextSyncDueAt?: number;
407
+ }
408
+
409
+ /**
410
+ * @category capability
411
+ * StateManagement Vault — federation control plane (registry +
412
+ * schema-manifest + append-only deployment-events). See
413
+ * docs/superpowers/specs/2026-06-08-statemanagement-vault-design.md.
414
+ */
415
+
416
+ declare class StateManagementVault {
417
+ #private;
418
+ readonly registry: Collection<VaultRegistryRow>;
419
+ readonly schemaManifest: Collection<SchemaManifestRow>;
420
+ private constructor();
421
+ /** Idempotently open the reserved state vault and bind the control-plane collections. */
422
+ static open(db: Noydb): Promise<StateManagementVault>;
423
+ /** Read one shard's migration status (or null). */
424
+ getMigrationStatus(vaultId: string): Promise<MigrationStatusRow | null>;
425
+ /** All migration-status rows (hydrates first). */
426
+ listMigrationStatus(): Promise<MigrationStatusRow[]>;
427
+ /** Upsert one shard's migration status (keyed by vaultId). */
428
+ upsertMigrationStatus(row: MigrationStatusRow): Promise<void>;
429
+ /** Persist a new Surface row (keyed by `row.id`). */
430
+ createSurface(row: SurfaceRow): Promise<void>;
431
+ /** Read one Surface row by id, or null if absent. */
432
+ getSurface(id: string): Promise<SurfaceRow | null>;
433
+ /** All persisted Surface rows (hydrates first). */
434
+ listSurfaces(): Promise<SurfaceRow[]>;
435
+ /**
436
+ * Merge `patch` into the existing Surface row keyed by `id`, persist the
437
+ * result, and return it. Mirrors the migrationStatus upsert pattern but
438
+ * returns the merged row for convenience.
439
+ */
440
+ updateSurface(id: string, patch: Partial<SurfaceRow>): Promise<SurfaceRow>;
441
+ /** Read-only query over the append-only deployment-events log. */
442
+ queryEvents(): Query<DeploymentEvent>;
443
+ /**
444
+ * Append a deployment event with a fresh unique (ULID) id. This is the
445
+ * only write path to the events log; no update/delete is exposed.
446
+ * Callers should treat failures as non-fatal — this method does not
447
+ * swallow errors, so wrap the call site in try/catch where appropriate.
448
+ */
449
+ appendEvent(event: Omit<DeploymentEvent, 'id' | 'ts'> & {
450
+ ts?: number;
451
+ }): Promise<void>;
452
+ /**
453
+ * Ensure a manifest row exists for `(templateName, template.version)`.
454
+ * Safe to call repeatedly: the `fingerprint` is a deterministic hash of
455
+ * the template's declared shape (stable across calls), though each call
456
+ * refreshes `recordedAt`.
457
+ */
458
+ recordManifest(templateName: string, template: VaultTemplate): Promise<string>;
459
+ /**
460
+ * True when `template`'s current declared shape does not match the recorded
461
+ * manifest for `(templateName, template.version)`. Because shards carry no
462
+ * schema state independent of their template, this catches "a template's
463
+ * shape changed without bumping `version`" — not independent per-shard drift.
464
+ * A missing manifest is treated as drift (nothing to verify against).
465
+ */
466
+ detectDrift(templateName: string, template: VaultTemplate): Promise<boolean>;
467
+ }
468
+
469
+ /** Public options for `ShardedQuery.crossShardJoin`. */
470
+ interface CrossShardJoinOptions {
471
+ /** Alias key under which the joined same-shard record attaches. */
472
+ readonly as: string;
473
+ /** Per-shard row ceiling override (default DEFAULT_JOIN_MAX_ROWS). */
474
+ readonly maxRows?: number;
475
+ /** Planner strategy override, passed through to intra-vault `.join()`. */
476
+ readonly strategy?: JoinStrategy;
477
+ }
478
+ /**
479
+ * Minimal structural shape of a broadcast dimension source. A
480
+ * `Collection` satisfies this natively: `list()` hydrates and returns
481
+ * the decoded records. Kept as a one-method interface so plain test
482
+ * sources are trivial to construct.
483
+ */
484
+ interface BroadcastSource {
485
+ list(): Promise<readonly unknown[]>;
486
+ }
487
+ /** Public options for `ShardedQuery.broadcastJoin`. */
488
+ interface BroadcastJoinOptions {
489
+ /** Alias key under which the dimension record attaches. */
490
+ readonly as: string;
491
+ /** The shared dimension collection (an opened handle in another vault). */
492
+ readonly from: BroadcastSource;
493
+ /** Right-side key to match `field` against. Default 'id'. */
494
+ readonly on?: string;
495
+ /** Miss behavior. 'warn' (default) attaches null + one-shot warning; 'cascade' is silent. */
496
+ readonly mode?: 'warn' | 'cascade';
497
+ }
498
+ /** Internal co-partitioned leg carried on ShardedQuery. */
499
+ interface CoPartitionedLeg {
500
+ readonly field: string;
501
+ readonly as: string;
502
+ readonly maxRows: number | undefined;
503
+ readonly strategy: JoinStrategy | undefined;
504
+ }
505
+ /** Internal broadcast leg carried on ShardedQuery. */
506
+ interface BroadcastLeg {
507
+ readonly field: string;
508
+ readonly as: string;
509
+ readonly from: BroadcastSource;
510
+ readonly on: string;
511
+ readonly mode: 'warn' | 'cascade';
512
+ }
513
+
514
+ /** A source that can fan out records across shards. Satisfied by ShardedQuery. */
515
+ interface FanoutRecordSource<R> {
516
+ fanoutRecords(options: FanoutQueryOptions): Promise<{
517
+ records: R[];
518
+ skippedVaults: SkippedVault[];
519
+ }>;
520
+ }
521
+ /** Live-binding hooks (change subscription + relevance) threaded from ShardedQuery. */
522
+ interface LiveBinding {
523
+ subscribeToChanges: (handler: (e: ChangeEvent) => void) => () => void;
524
+ isRelevant: (e: ChangeEvent) => boolean;
525
+ }
526
+ /**
527
+ * One-shot cross-vault aggregate. Concatenates all shard records and runs a
528
+ * single central reduce, ensuring correct avg/mean values.
529
+ */
530
+ declare class CrossVaultAggregation<R, Spec extends AggregateSpec> {
531
+ private readonly src;
532
+ private readonly spec;
533
+ private readonly bind?;
534
+ constructor(src: FanoutRecordSource<R>, spec: Spec, bind?: LiveBinding | undefined);
535
+ run(options?: FanoutQueryOptions): Promise<{
536
+ result: AggregateResult<Spec>;
537
+ skippedVaults: SkippedVault[];
538
+ }>;
539
+ live(options?: LiveQueryOptions): CrossVaultLiveAggregation<AggregateResult<Spec>>;
540
+ }
541
+ /**
542
+ * One-shot cross-vault grouped aggregate. Concatenates all shard records and
543
+ * runs a single central group-and-reduce, emitting one row per bucket.
544
+ */
545
+ declare class CrossVaultGroupedAggregation<R, F extends string, Spec extends AggregateSpec> {
546
+ private readonly src;
547
+ private readonly field;
548
+ private readonly spec;
549
+ private readonly bind?;
550
+ constructor(src: FanoutRecordSource<R>, field: F, spec: Spec, bind?: LiveBinding | undefined);
551
+ run(options?: FanoutQueryOptions): Promise<{
552
+ results: GroupedRow<F, Spec>[];
553
+ skippedVaults: SkippedVault[];
554
+ }>;
555
+ live(options?: LiveQueryOptions): CrossVaultLiveQuery<GroupedRow<F, Spec>>;
556
+ }
557
+
558
+ /**
559
+ * @category capability
560
+ * Multi-vault partition federation — VaultGroup transparent shard
561
+ * routing. Spec:
562
+ * docs/superpowers/specs/2026-06-07-mvf-vaultgroup-routing-mvp-design.md.
563
+ */
564
+
565
+ declare class VaultGroup<T> {
566
+ /** @internal */ readonly db: Noydb;
567
+ /** @internal */ readonly name: string;
568
+ /** @internal */ readonly registry: Collection<VaultRegistryRow>;
569
+ /** @internal */ readonly sharding: ShardingConfig<T>;
570
+ /** @internal */ readonly template: VaultTemplate;
571
+ /** @internal — lazy migrate-on-open (#271). */ readonly migrateOnOpen: boolean;
572
+ constructor(
573
+ /** @internal */ db: Noydb,
574
+ /** @internal */ name: string,
575
+ /** @internal */ registry: Collection<VaultRegistryRow>,
576
+ /** @internal */ sharding: ShardingConfig<T>,
577
+ /** @internal */ template: VaultTemplate,
578
+ /** @internal — lazy migrate-on-open (#271). */ migrateOnOpen?: boolean);
579
+ /** @internal — set when the group is managed (no explicit registry). */
580
+ private stateVault;
581
+ /** @internal */
582
+ _attachStateVault(sv: StateManagementVault): void;
583
+ /** Deterministic vault name for a partition key, namespaced by the group. */
584
+ shardVaultId(partitionKey: string): string;
585
+ /**
586
+ * @internal — group-qualified registry record key (avoids cross-group key
587
+ * collisions). Identical to the shard vault id by design — the registry row
588
+ * for a shard is keyed by that shard's vault id — so it delegates to
589
+ * `shardVaultId`, reusing its partition-key validation.
590
+ */
591
+ registryId(partitionKey: string): string;
592
+ /**
593
+ * Registry rows for THIS group (hydrates the registry collection first).
594
+ * The registry may be shared across groups (the auto-wired StateManagement
595
+ * vault holds one `vaultRegistry` for the whole instance), so rows are
596
+ * filtered by `group` — without this, a group's fan-out reads would leak
597
+ * across into other groups' shards. Mirrors the `${group}--` scoping that
598
+ * `liveBinding().isRelevant` already applies to the reactive path.
599
+ */
600
+ allRows(): Promise<VaultRegistryRow[]>;
601
+ /**
602
+ * Open an existing shard and apply the template. When `migrateOnOpen` is set
603
+ * (#271) and the shard's registry version is behind the template, its cutover
604
+ * runs inline first — so a behind shard never surfaces a stale handle.
605
+ */
606
+ openShard(partitionKey: string): Promise<Vault$1>;
607
+ /** @internal — open + configure with no migrate-on-open hook (used by the migration path itself to avoid recursion). */
608
+ private _openShardRaw;
609
+ /**
610
+ * Idempotently provision a shard for `partitionKey`. Returns the
611
+ * configured vault handle.
612
+ *
613
+ * - row + vault present → no-op, return handle
614
+ * - row present, vault gone → ShardProvisioningError
615
+ * - row absent (vault present or not) → open-or-create, configure, write row
616
+ *
617
+ * When `region` is given (the routing `put` passes `sharding.regionOf(record)`),
618
+ * the candidate backend's `capabilities.region` must match or this throws
619
+ * `DataResidencyError` BEFORE provisioning (#271 data-residency guard).
620
+ */
621
+ createShard(partitionKey: string, region?: string): Promise<Vault$1>;
622
+ /**
623
+ * Drill down to a single shard's full Collection API. Throws if the shard is unknown.
624
+ * Also throws ShardProvisioningError if the registry row exists but the vault has been deleted
625
+ * (registry/store divergence).
626
+ */
627
+ shard(partitionKey: string): Promise<Vault$1>;
628
+ /** A sharded view over one logical collection across all shards. */
629
+ collection<R = T>(collectionName: string): ShardedCollection<T, R>;
630
+ /** @internal — eligible (openable-candidate) rows + drift/divergence skips. */
631
+ resolveEligible(options?: {
632
+ minVersion?: number;
633
+ }): Promise<{
634
+ eligible: VaultRegistryRow[];
635
+ skipped: SkippedVault[];
636
+ }>;
637
+ /** @internal — registered push-model cross-vault derivations (#271 Insight Vault). */
638
+ private readonly crossVaultDerivations;
639
+ /** @internal — auto-push controller; created (and the change-subscription armed) when the first autoPush derivation registers. */
640
+ private insightAutoPush?;
641
+ /**
642
+ * Register a push-model cross-vault derivation — the Insight Vault pattern
643
+ * (#271, Layer 4). Drive it with {@link refreshInsights}.
644
+ *
645
+ * For each shard, `derive(records, ctx)` runs on that shard's `source`
646
+ * records and its return value is written into the analytics
647
+ * (`target.vault` / `target.collection`) vault, keyed by partition key —
648
+ * one summary row per shard. The derivation runs in-process under THIS
649
+ * group's `Noydb` (which already holds both the shard and Insight Vault
650
+ * keyrings); the shard's decrypted records are reduced to a summary that is
651
+ * re-encrypted under the Insight Vault's own DEK, so no shard ciphertext
652
+ * crosses a DEK boundary.
653
+ *
654
+ * **Zero-knowledge note:** the Insight Vault backend sees aggregated
655
+ * structure (totals, counts, timestamps) drawn from many shards — a weaker
656
+ * ZK profile than the per-shard vaults. Opt-in; keep summaries to aggregate
657
+ * scalars (no embeddings / no raw records).
658
+ *
659
+ * v1 is explicit-refresh (no write-path push); call `refreshInsights()`
660
+ * after a batch of writes, or on a schedule.
661
+ *
662
+ * The `target.vault` must NOT be the group itself or one of its shards —
663
+ * a summary writing back into client-shard data would breach the Insight
664
+ * Vault's separate-DEK-boundary contract. Such a target throws a
665
+ * `ValidationError` at registration (#271 Insight-write isolation).
666
+ */
667
+ withCrossVaultDerivation<R = Record<string, unknown>, S = Record<string, unknown>>(spec: CrossVaultDerivationSpec<R, S>): void;
668
+ /**
669
+ * Run every registered {@link withCrossVaultDerivation}: read each eligible
670
+ * shard's source records, derive a per-shard summary, and write it into the
671
+ * Insight Vault keyed by partition key. Shards behind `minVersion`,
672
+ * unprovisioned, or whose read errors are reported in `skippedVaults` and
673
+ * are not written (a stale summary is never left behind for a failed shard).
674
+ */
675
+ refreshInsights(options?: {
676
+ minVersion?: number;
677
+ concurrency?: number;
678
+ }): Promise<RefreshInsightsResult>;
679
+ /** @internal — re-derive + push every autoPush derivation's summary for one shard. */
680
+ private _recomputeShardInsights;
681
+ /**
682
+ * Await any pending Insight auto-push flush (#12). Resolves immediately when
683
+ * no autoPush derivation is registered or nothing is pending. Use after a
684
+ * batch of writes to observe the Insight Vault, or in tests.
685
+ */
686
+ whenInsightsSettled(): Promise<void>;
687
+ /** @internal — the control-plane vault for migration status; lazily opened. */
688
+ private ensureStateVault;
689
+ /**
690
+ * Migrate ONE shard to the template's current version (#271 fleet runner,
691
+ * per-shard step). Opens the shard (applying the template, which arms the
692
+ * M12 cutover), drains schema-write detection, runs `vault.runSchemaCutover()`
693
+ * (the per-vault drain-barrier-transform protocol), then advances the
694
+ * registry row's `schemaVersion` and records `migration-status`. A shard
695
+ * already at the template version is a no-op (`status: 'done'`, migrated 0).
696
+ * Never throws on a cutover failure — it records `status: 'failed'` and
697
+ * returns the row, so a fleet run continues past a bad shard.
698
+ */
699
+ migrateShard(partitionKey: string): Promise<MigrationStatusRow>;
700
+ /**
701
+ * Active batch runner (#271): migrate every shard behind the template version
702
+ * to it, in controlled batches. **Resumable + crash-safe** — shards already at
703
+ * the target are skipped (the registry version is the source of truth), so a
704
+ * re-run after a crash only picks up the unfinished + previously-failed shards.
705
+ *
706
+ * - `cohort` — restrict to these partition keys (the staged / canary rollout:
707
+ * migrate a small cohort, verify the Insight Vault, then run the rest).
708
+ * - `batchSize` — max shards migrated concurrently per batch (back-pressure).
709
+ * Default 4. Batches run sequentially; shards within a batch run in parallel.
710
+ */
711
+ migrateFleet(options?: {
712
+ cohort?: readonly string[];
713
+ batchSize?: number;
714
+ }): Promise<FleetMigrationResult>;
715
+ }
716
+ declare class ShardedCollection<T, R = T> {
717
+ private readonly group;
718
+ private readonly collectionName;
719
+ constructor(group: VaultGroup<T>, collectionName: string);
720
+ /** Route a write to the shard owning `keyOf(record)`. */
721
+ put(id: string, record: T): Promise<void>;
722
+ /** Begin a cross-shard fan-out query. */
723
+ query(): ShardedQuery<T, R>;
724
+ }
725
+ declare class ShardedQuery<T, R = T> {
726
+ private readonly group;
727
+ private readonly collectionName;
728
+ private readonly clauses;
729
+ private readonly coPartitionedLegs;
730
+ private readonly broadcastLegs;
731
+ constructor(group: VaultGroup<T>, collectionName: string, clauses: readonly WhereClause[], coPartitionedLegs?: readonly CoPartitionedLeg[], broadcastLegs?: readonly BroadcastLeg[]);
732
+ where(field: string, op: WhereClause['op'], value: unknown): ShardedQuery<T, R>;
733
+ /** Co-partitioned join: each shard joins its own same-vault right collection (resolved via ref()), then union. */
734
+ crossShardJoin(field: string, opts: CrossShardJoinOptions): ShardedQuery<T, R>;
735
+ /** Broadcast dimension join: enrich every merged row from a single shared collection. */
736
+ broadcastJoin(field: string, opts: BroadcastJoinOptions): ShardedQuery<T, R>;
737
+ /** @internal — fan out the where-filtered records across eligible shards. */
738
+ fanoutRecords(options?: FanoutQueryOptions): Promise<{
739
+ records: R[];
740
+ skippedVaults: SkippedVault[];
741
+ }>;
742
+ /** Fan out across eligible shards, merge, then apply any broadcast dimension legs. */
743
+ toArray(options?: FanoutQueryOptions): Promise<FanoutResult<R>>;
744
+ /** @internal — build the change-subscription + relevance binding for this query's group+collection. */
745
+ liveBinding(): LiveBinding;
746
+ /** @internal — joined queries don't support reactive/aggregate surfaces in v1. */
747
+ private assertNoJoinLegs;
748
+ /** Returns a reactive cross-shard live query — a facade over CrossVaultLive. */
749
+ live(options?: LiveQueryOptions): CrossVaultLiveQuery<R>;
750
+ /** One-shot distributed aggregate — central reduce over all shard records. */
751
+ aggregate<Spec extends AggregateSpec>(spec: Spec): CrossVaultAggregation<R, Spec>;
752
+ /** Begin a grouped cross-shard aggregate. */
753
+ groupBy<F extends string>(field: F): ShardedGroupedQuery<T, R, F>;
754
+ }
755
+ /** Grouped cross-shard query — intermediate after `.groupBy(field)`, terminates with `.aggregate(spec)`. */
756
+ declare class ShardedGroupedQuery<T, R, F extends string> {
757
+ private readonly query;
758
+ private readonly field;
759
+ constructor(query: ShardedQuery<T, R>, field: F);
760
+ aggregate<Spec extends AggregateSpec>(spec: Spec): CrossVaultGroupedAggregation<R, F, Spec>;
761
+ }
762
+
763
+ export { ShardedGroupedQuery as A, ShardedQuery as B, type CapturedBlueprint as C, type DecryptedMergeOptions as D, type ShardingConfig as E, type FanoutQueryOptions as F, type GroupedRow as G, type SurfaceStatus as H, type VaultRegistryRow as I, mergeCompartment as J, mergeDecryptedRecords as K, type LiveQueryOptions as L, type MergeCompartmentOptions as M, resolveFieldAuthority as N, resolveRecordByFieldAuthority as O, type RefreshInsightsResult as R, StateManagementVault as S, VaultGroup as V, type VaultTemplate as a, type SkippedVault as b, type MergeReport as c, type SurfaceDirection as d, type SurfaceConflictPolicy as e, type SurfaceRow as f, type VaultGroupOptions as g, CrossVaultAggregation as h, type CrossVaultDerivationContext as i, type CrossVaultDerivationSpec as j, CrossVaultGroupedAggregation as k, type CrossVaultLiveAggregation as l, type CrossVaultLiveQuery as m, type DeploymentEvent as n, type FanoutResult as o, type FieldAuthorityInputs as p, type FieldAuthorityPolicy as q, FieldAuthorityPolicyMissingError as r, type FieldAuthorityRule as s, FieldLevelDeferredError as t, type FleetMigrationResult as u, type MergeConflict as v, type MergeStrategy as w, type MigrationStatusRow as x, type SchemaManifestRow as y, ShardedCollection as z };