@camstack/types 1.2.360 → 1.2.362

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.js CHANGED
@@ -8326,6 +8326,570 @@ var authProviderCapability = {
8326
8326
  mount: { kind: "skip" }
8327
8327
  };
8328
8328
  //#endregion
8329
+ //#region src/capabilities/settings-store.cap.ts
8330
+ /**
8331
+ * Query filter for settings-store collections.
8332
+ */
8333
+ var QueryFilterSchema = zod.z.object({
8334
+ where: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
8335
+ whereIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
8336
+ whereBetween: zod.z.record(zod.z.string(), zod.z.tuple([zod.z.unknown(), zod.z.unknown()])).optional(),
8337
+ /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
8338
+ whereNot: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
8339
+ /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
8340
+ * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
8341
+ whereNotIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
8342
+ orderBy: zod.z.object({
8343
+ field: zod.z.string(),
8344
+ direction: zod.z.enum(["asc", "desc"])
8345
+ }).optional(),
8346
+ limit: zod.z.number().optional(),
8347
+ offset: zod.z.number().optional()
8348
+ });
8349
+ /**
8350
+ * The predicate half of a filter, for BULK MUTATIONS.
8351
+ *
8352
+ * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
8353
+ * meaning for a statement that rewrites a set, and accepting them would invite
8354
+ * a caller to believe `limit` bounds the damage. Every field is optional here
8355
+ * only so the shape stays composable — the implementation REJECTS a filter
8356
+ * that compiles to no predicate, because that is the whole collection.
8357
+ */
8358
+ var MutationFilterSchema = zod.z.object({
8359
+ where: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
8360
+ whereIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
8361
+ whereBetween: zod.z.record(zod.z.string(), zod.z.tuple([zod.z.unknown(), zod.z.unknown()])).optional(),
8362
+ whereNot: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
8363
+ whereNotIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional()
8364
+ });
8365
+ /**
8366
+ * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
8367
+ *
8368
+ * `as` names the slot in the result, so the SAME column may be asked twice with
8369
+ * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
8370
+ * a `Record<column, op>` shape could not express.
8371
+ */
8372
+ var AggregateFieldSchema = zod.z.object({
8373
+ /** Result key. */
8374
+ as: zod.z.string().min(1),
8375
+ /** Column to aggregate. Must be a real column of a declared collection. */
8376
+ field: zod.z.string().min(1),
8377
+ op: zod.z.enum([
8378
+ "sum",
8379
+ "min",
8380
+ "max"
8381
+ ])
8382
+ });
8383
+ /**
8384
+ * `COUNT(*)` plus one number per requested field.
8385
+ *
8386
+ * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
8387
+ * that really is 0 are different facts, and an accounting caller that renders
8388
+ * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
8389
+ */
8390
+ var AggregateResultSchema = zod.z.object({
8391
+ count: zod.z.number().int(),
8392
+ values: zod.z.record(zod.z.string(), zod.z.number().nullable())
8393
+ });
8394
+ /** A single stored record: `{ id, data }`. */
8395
+ var SettingsRecordSchema = zod.z.object({
8396
+ id: zod.z.string(),
8397
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
8398
+ });
8399
+ /**
8400
+ * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
8401
+ * optional.
8402
+ *
8403
+ * A separate schema rather than loosening the shared one: every other method
8404
+ * on this cap addresses a row BY its id, and making that field optional
8405
+ * everywhere would turn a forgotten key into a silently generated one on
8406
+ * `update` and `delete` as well.
8407
+ */
8408
+ var BulkRecordSchema = zod.z.object({
8409
+ id: zod.z.string().optional(),
8410
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
8411
+ });
8412
+ /**
8413
+ * The database FILES one `sqlite-settings` engine process owns (D851).
8414
+ * Closed on purpose: a file is a thing the engine opens, backs up and gives a
8415
+ * serving thread, so a new one is a code change, never a string a caller
8416
+ * invents. `settings` is `camstack.db` and the default for every collection
8417
+ * that names no file.
8418
+ */
8419
+ var DataStoreDatabaseSchema = zod.z.enum([
8420
+ "settings",
8421
+ "events",
8422
+ "history"
8423
+ ]);
8424
+ /** Every database file, in a stable order (settings first). */
8425
+ var DATA_STORE_DATABASES = DataStoreDatabaseSchema.options;
8426
+ /**
8427
+ * One write inside a {@link settingsStoreCapability.methods.transact} call.
8428
+ * The three that a "replace this set, bump that revision" write needs, plus
8429
+ * the batched upsert a rollup writer needs.
8430
+ */
8431
+ var TransactOpSchema = zod.z.discriminatedUnion("op", [
8432
+ zod.z.object({
8433
+ op: zod.z.literal("deleteWhere"),
8434
+ collection: zod.z.string(),
8435
+ filter: MutationFilterSchema
8436
+ }),
8437
+ zod.z.object({
8438
+ op: zod.z.literal("insertMany"),
8439
+ collection: zod.z.string(),
8440
+ records: zod.z.array(BulkRecordSchema).readonly()
8441
+ }),
8442
+ zod.z.object({
8443
+ op: zod.z.literal("update"),
8444
+ collection: zod.z.string(),
8445
+ id: zod.z.string(),
8446
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
8447
+ }),
8448
+ zod.z.object({
8449
+ op: zod.z.literal("upsertMany"),
8450
+ collection: zod.z.string(),
8451
+ conflict: zod.z.array(zod.z.string()).readonly(),
8452
+ records: zod.z.array(BulkRecordSchema).readonly()
8453
+ })
8454
+ ]);
8455
+ /**
8456
+ * The precondition of a transaction: the row `key` of `collection` must hold
8457
+ * `equals` in `column`, read INSIDE the transaction.
8458
+ */
8459
+ var TransactGuardSchema = zod.z.object({
8460
+ collection: zod.z.string(),
8461
+ key: zod.z.string(),
8462
+ column: zod.z.string(),
8463
+ equals: zod.z.unknown()
8464
+ });
8465
+ /**
8466
+ * Column declaration for a structured (SQL-backed) collection.
8467
+ *
8468
+ * Logical types — the backend translates each to the matching SQLite
8469
+ * storage class and handles per-type marshaling:
8470
+ * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
8471
+ * - `JSON` — TEXT under the hood; serialised on write, parsed on read
8472
+ * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
8473
+ */
8474
+ var CollectionColumnSchema = zod.z.object({
8475
+ name: zod.z.string(),
8476
+ type: zod.z.enum([
8477
+ "TEXT",
8478
+ "INTEGER",
8479
+ "REAL",
8480
+ "JSON",
8481
+ "BOOLEAN"
8482
+ ]),
8483
+ primaryKey: zod.z.boolean().optional(),
8484
+ notNull: zod.z.boolean().optional(),
8485
+ unique: zod.z.boolean().optional(),
8486
+ /**
8487
+ * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
8488
+ * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
8489
+ * collection surface simply cannot express their existing tables — which is
8490
+ * why they were still on the uncapped `table*` API.
8491
+ */
8492
+ defaultValue: zod.z.union([
8493
+ zod.z.string(),
8494
+ zod.z.number(),
8495
+ zod.z.boolean()
8496
+ ]).optional()
8497
+ });
8498
+ var CollectionIndexSchema = zod.z.object({
8499
+ name: zod.z.string(),
8500
+ columns: zod.z.array(zod.z.string()).readonly(),
8501
+ unique: zod.z.boolean().optional()
8502
+ });
8503
+ /**
8504
+ * The input of `declareCollection` — ONE schema for the door and for the
8505
+ * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
8506
+ * the payload untouched).
8507
+ *
8508
+ * - `database` — the file the collection lives in (D851). Absent means
8509
+ * `settings`. A declaration naming a file other than the one the table is
8510
+ * already in is REFUSED by the engine, never moved.
8511
+ * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
8512
+ * as `primaryKey: true` on that column. A composite-keyed collection has no
8513
+ * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
8514
+ * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
8515
+ * address one row by id are refused.
8516
+ * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
8517
+ */
8518
+ var DeclareCollectionInputSchema = zod.z.object({
8519
+ namespace: zod.z.string().optional(),
8520
+ collection: zod.z.string(),
8521
+ columns: zod.z.array(CollectionColumnSchema).readonly(),
8522
+ indexes: zod.z.array(CollectionIndexSchema).readonly().optional(),
8523
+ database: DataStoreDatabaseSchema.optional(),
8524
+ primaryKey: zod.z.array(zod.z.string()).readonly().optional(),
8525
+ withoutRowid: zod.z.boolean().optional()
8526
+ });
8527
+ /**
8528
+ * settings-store — singleton capability for addon-scoped persistence.
8529
+ *
8530
+ * Every method operates within a `collection`. An optional `namespace`
8531
+ * field provides access to additional data spaces beyond the default
8532
+ * addon settings — useful for business data (events, tracks, faces, etc.).
8533
+ *
8534
+ * **Scoping is the CALLER's, and it is opt-in.** The table is
8535
+ * `namespace ? `${namespace}:${collection}` : collection` — nothing
8536
+ * consults the identity of the caller. An earlier version of this comment
8537
+ * claimed the implementation prefixes every collection with the calling
8538
+ * addon's ID and that "addons never see each other's data"; that was never
8539
+ * true, and this same file contradicted it under `declareCollection`. What
8540
+ * exists is `addon-context-factory`, which passes `namespace: addonId` ON
8541
+ * THE ADDON'S BEHALF for the `addon-settings` / `addon-devices` paths only.
8542
+ * Business collections use bare names, and any caller may name any
8543
+ * namespace, or none. Making the door enforce it is tracked separately —
8544
+ * it renames tables, so it needs a migration.
8545
+ *
8546
+ * **So every method is `auth: 'admin'` (D634).** Because nothing scopes a
8547
+ * call to its caller, a `protected` method here was a raw, unredacted read
8548
+ * (and write) of every addon's collections for any role holding
8549
+ * `category:system` — which is every role: a `viewer` could read every
8550
+ * notifier token in plaintext from `notifiers:notification-targets`. No
8551
+ * browser or app client calls this cap. Addons are not affected: an
8552
+ * addon's `ctx.api` cap call is resolved to the provider directly and never
8553
+ * enters the tRPC router (so the tier gates sessions and tokens, never
8554
+ * addons), and the hub itself calls it through the capability registry.
8555
+ *
8556
+ * - `{ collection: 'addon-settings' }` → table `addon-settings`
8557
+ * - `{ namespace: 'my-addon', collection: 'addon-settings' }` →
8558
+ * table `my-addon:addon-settings`
8559
+ *
8560
+ * Served by the **storage-orchestrator** builtin, which dispatches to the
8561
+ * `data-store-provider` engine registered for the collection
8562
+ * (`sqlite-settings`, a SQLite WAL backend, today). One addon owns the
8563
+ * data door; engines sit behind it
8564
+ * ([D44](../../../../docs/decisions/adr-0044.md)).
8565
+ * Addons access it via `ctx.api.settingsStore.*`.
8566
+ */
8567
+ var settingsStoreCapability = {
8568
+ name: "settings-store",
8569
+ scope: "system",
8570
+ mode: "singleton",
8571
+ methods: {
8572
+ /** Get a single value by key from a collection. */
8573
+ get: require_sleep.method(zod.z.object({
8574
+ namespace: zod.z.string().optional(),
8575
+ collection: zod.z.string(),
8576
+ key: zod.z.string()
8577
+ }), zod.z.unknown(), { auth: "admin" }),
8578
+ /** Set a value by key in a collection (upsert). */
8579
+ set: require_sleep.method(zod.z.object({
8580
+ namespace: zod.z.string().optional(),
8581
+ collection: zod.z.string(),
8582
+ key: zod.z.string(),
8583
+ value: zod.z.unknown()
8584
+ }), zod.z.void(), {
8585
+ kind: "mutation",
8586
+ auth: "admin"
8587
+ }),
8588
+ /**
8589
+ * Rows matching a filter.
8590
+ *
8591
+ * `columns` is a SQL-level projection: the engine SELECTs only those
8592
+ * columns (plus the primary key) instead of the whole row. It is the only
8593
+ * way to stop paying disk for a column that is discarded — a heavy JSON
8594
+ * column dominates the row, and dropping it in JS after the read pays every
8595
+ * byte first. Measured on the live `pipeline-analytics:tracks` table: a
8596
+ * 501-row page is 4.6 MB whole and 0.11 MB projected, same index, same
8597
+ * plan.
8598
+ *
8599
+ * The engine REFUSES a column it cannot serve rather than omitting it —
8600
+ * see `SettingsQueryInput.columns` for why a projection may not be
8601
+ * forgiving the way a predicate is.
8602
+ */
8603
+ query: require_sleep.method(zod.z.object({
8604
+ namespace: zod.z.string().optional(),
8605
+ collection: zod.z.string(),
8606
+ filter: QueryFilterSchema.optional(),
8607
+ columns: zod.z.array(zod.z.string()).readonly().optional()
8608
+ }), zod.z.array(SettingsRecordSchema).readonly(), { auth: "admin" }),
8609
+ /**
8610
+ * Make a SLOT hold this row — insert it, or update whatever already
8611
+ * occupies the slot — and answer with the id the row HAS.
8612
+ *
8613
+ * ## Why the store needed this
8614
+ *
8615
+ * {@link set} is already an upsert, but its slot is the PRIMARY KEY: the
8616
+ * caller has to know the id to address the row. That is exactly what a
8617
+ * collection keyed by an `INTEGER` rowid alias cannot do — SQLite assigns
8618
+ * the id, so a writer whose uniqueness rule is "one row per
8619
+ * `(ownerKind, ownerId, kind, slot)`" has no id to name and no way to
8620
+ * express the rule. {@link insert} can express it (a UNIQUE index
8621
+ * arbitrates) but only by THROWING at the loser of a race, and the loser
8622
+ * here is a legitimate re-capture, not an error.
8623
+ *
8624
+ * Getting there without this method means read-then-write: query the slot,
8625
+ * then `insert` or `update`. Two statements instead of one, a race between
8626
+ * them, and a recovery path that has to recognise a UNIQUE violation by its
8627
+ * MESSAGE — across a process boundary, because the store lives behind an
8628
+ * RPC door. A string match would be the whole gate, and a store that
8629
+ * rephrased its error would lose the row silently. One statement has no
8630
+ * such gate (D482).
8631
+ *
8632
+ * ## The contract
8633
+ *
8634
+ * `conflict` names the DECLARED columns that identify the slot. They must
8635
+ * be covered by a UNIQUE index on the collection — SQLite resolves an
8636
+ * `ON CONFLICT` target against its indexes and REFUSES the statement when
8637
+ * none matches, so a wrong list is a loud error on the first write, never a
8638
+ * silently duplicated row. An empty list is refused rather than passed on:
8639
+ * `ON CONFLICT()` is not valid SQL, and the intent it would most likely
8640
+ * encode ("no slot, just insert") is {@link insert}.
8641
+ *
8642
+ * Row shape is {@link insert}'s: `record.id` is used when the collection
8643
+ * needs a supplied key and IGNORED when the primary key is an `INTEGER`
8644
+ * rowid alias — exactly as `insert` and `insertMany` already treat it.
8645
+ *
8646
+ * Returns the id either way: the one SQLite assigned on an insert, or the
8647
+ * one the row it updated already had. A caller cannot tell which happened,
8648
+ * and deliberately — every caller wants the slot's id, and one that needed
8649
+ * to distinguish creation from replacement would have to read first anyway.
8650
+ */
8651
+ upsert: require_sleep.method(zod.z.object({
8652
+ namespace: zod.z.string().optional(),
8653
+ collection: zod.z.string(),
8654
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
8655
+ * index on the collection. Never empty. */
8656
+ conflict: zod.z.array(zod.z.string()).readonly(),
8657
+ record: BulkRecordSchema
8658
+ }), zod.z.object({
8659
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
8660
+ * row's own id on an update. */
8661
+ id: zod.z.union([zod.z.string(), zod.z.number()]) }), {
8662
+ kind: "mutation",
8663
+ auth: "admin"
8664
+ }),
8665
+ /**
8666
+ * {@link upsert} for a BATCH, in ONE transaction on ONE prepared statement.
8667
+ *
8668
+ * The write-side twin of {@link insertMany} for rows that may already
8669
+ * exist: a rollup writer recomputes a bucket and must overwrite it, and
8670
+ * N round trips with N COMMITs is the cost {@link insertMany} was added to
8671
+ * avoid. `conflict` has the meaning it has on {@link upsert} (declared
8672
+ * columns covered by a UNIQUE index or the primary key, never empty).
8673
+ *
8674
+ * Every record of a batch must carry the SAME set of declared columns and
8675
+ * every `conflict` column: one batch is one statement shape, and a record
8676
+ * that silently bound NULL over a column it did not mention would erase
8677
+ * it. A heterogeneous batch is refused whole. **All or nothing.**
8678
+ */
8679
+ upsertMany: require_sleep.method(zod.z.object({
8680
+ namespace: zod.z.string().optional(),
8681
+ collection: zod.z.string(),
8682
+ conflict: zod.z.array(zod.z.string()).readonly(),
8683
+ records: zod.z.array(BulkRecordSchema).readonly()
8684
+ }), zod.z.object({ upserted: zod.z.number().int() }), {
8685
+ kind: "mutation",
8686
+ auth: "admin"
8687
+ }),
8688
+ /** Insert a new record. */
8689
+ insert: require_sleep.method(zod.z.object({
8690
+ namespace: zod.z.string().optional(),
8691
+ collection: zod.z.string(),
8692
+ record: zod.z.object({
8693
+ id: zod.z.string().optional(),
8694
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
8695
+ })
8696
+ }), zod.z.object({
8697
+ /**
8698
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
8699
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
8700
+ * primary key — which is the only place an auto key is knowable.
8701
+ */
8702
+ id: zod.z.union([zod.z.string(), zod.z.number()]) }), {
8703
+ kind: "mutation",
8704
+ auth: "admin"
8705
+ }),
8706
+ /**
8707
+ * Insert MANY records in ONE transaction, returning how many landed.
8708
+ *
8709
+ * The write-side twin of {@link deleteWhere}, and it exists for the same
8710
+ * reason: without it, appending a batch is N round trips and N COMMITs on
8711
+ * the single shared connection that also serves every cluster-wide
8712
+ * configuration read. The durable load series writes one process row per
8713
+ * process per sample — 76 rows every 10 s on the live fleet — and the
8714
+ * operator's rule for it is *one transaction per sample, never one per
8715
+ * row*. `insert` cannot express that; nothing else could.
8716
+ *
8717
+ * **All or nothing.** A batch that fails on its fifth row leaves none of
8718
+ * the five behind. A half-written sample is worse than a missing one: the
8719
+ * missing one reads as "nobody reported", which is true, while the half
8720
+ * one reads as "these were the only processes running", which is not.
8721
+ *
8722
+ * `id` is OPTIONAL per record, and that is the difference from
8723
+ * {@link insert}. A collection whose primary key is an `INTEGER` rowid
8724
+ * alias has no id to supply — SQLite assigns it, for free, and inventing a
8725
+ * `randomUUID()` for such a column would write a 36-character string into
8726
+ * an integer key. Omitted on a TEXT key, a uuid is generated exactly as
8727
+ * `insert` does.
8728
+ */
8729
+ insertMany: require_sleep.method(zod.z.object({
8730
+ namespace: zod.z.string().optional(),
8731
+ collection: zod.z.string(),
8732
+ records: zod.z.array(BulkRecordSchema).readonly()
8733
+ }), zod.z.object({ inserted: zod.z.number().int() }), {
8734
+ kind: "mutation",
8735
+ auth: "admin"
8736
+ }),
8737
+ /**
8738
+ * Several writes, ONE transaction, behind an optional precondition.
8739
+ *
8740
+ * The multi-collection twin of {@link insertMany}: a write that has to move
8741
+ * a set of rows in one collection AND a revision in another cannot be
8742
+ * expressed as separate calls without a window in which a reader (or a
8743
+ * crash) sees half of it. The retrain annotation save replaces a frame's
8744
+ * rows, its dismissed set and its revision this way.
8745
+ *
8746
+ * `guard`, when given, is read inside the transaction: if the row is
8747
+ * missing or its column differs from `equals`, NOTHING is written and the
8748
+ * answer is `{ applied: false, current }` with what was found — the
8749
+ * compare-and-set a revisioned write needs, with no second reader able to
8750
+ * slip between the check and the writes.
8751
+ *
8752
+ * **All or nothing**, like {@link insertMany}: a throw in any op rolls the
8753
+ * whole call back. Ops run in the order given (delete before insert to
8754
+ * replace a set). `namespace` applies to every collection named.
8755
+ */
8756
+ transact: require_sleep.method(zod.z.object({
8757
+ namespace: zod.z.string().optional(),
8758
+ guard: TransactGuardSchema.optional(),
8759
+ ops: zod.z.array(TransactOpSchema).readonly()
8760
+ }), zod.z.object({
8761
+ applied: zod.z.boolean(),
8762
+ current: zod.z.unknown().optional()
8763
+ }), {
8764
+ kind: "mutation",
8765
+ auth: "admin"
8766
+ }),
8767
+ /** Update an existing record by ID. */
8768
+ update: require_sleep.method(zod.z.object({
8769
+ namespace: zod.z.string().optional(),
8770
+ collection: zod.z.string(),
8771
+ id: zod.z.string(),
8772
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
8773
+ }), zod.z.void(), {
8774
+ kind: "mutation",
8775
+ auth: "admin"
8776
+ }),
8777
+ /** Delete a record by key/ID. */
8778
+ delete: require_sleep.method(zod.z.object({
8779
+ namespace: zod.z.string().optional(),
8780
+ collection: zod.z.string(),
8781
+ key: zod.z.string()
8782
+ }), zod.z.void(), {
8783
+ kind: "mutation",
8784
+ auth: "admin"
8785
+ }),
8786
+ /**
8787
+ * Delete every record matching `filter`, in ONE statement, returning how
8788
+ * many rows went. This exists because its absence made every retention
8789
+ * path in the system an N+1 drain loop: `delete` takes a key, so a sweep
8790
+ * had to SELECT a page of full rows — every column, including the fat
8791
+ * ones — purely to learn their ids, then issue one call per row.
8792
+ *
8793
+ * **The filter is required and must resolve.** A predicate naming
8794
+ * something the collection cannot express is an ERROR here, not a
8795
+ * widening as it is on `query`, and a filter with no predicates is an
8796
+ * error rather than "every row". Deleting a whole collection is a
8797
+ * legitimate intent, but it must be asked for by name — not reached by
8798
+ * an empty object.
8799
+ */
8800
+ deleteWhere: require_sleep.method(zod.z.object({
8801
+ namespace: zod.z.string().optional(),
8802
+ collection: zod.z.string(),
8803
+ filter: MutationFilterSchema
8804
+ }), zod.z.object({ deleted: zod.z.number().int() }), {
8805
+ kind: "mutation",
8806
+ auth: "admin"
8807
+ }),
8808
+ /**
8809
+ * Apply `data` to every record matching `filter`, in one statement,
8810
+ * returning how many rows changed. Same filter contract as
8811
+ * {@link deleteWhere} — an unresolvable predicate is an error.
8812
+ */
8813
+ updateWhere: require_sleep.method(zod.z.object({
8814
+ namespace: zod.z.string().optional(),
8815
+ collection: zod.z.string(),
8816
+ filter: MutationFilterSchema,
8817
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
8818
+ }), zod.z.object({ updated: zod.z.number().int() }), {
8819
+ kind: "mutation",
8820
+ auth: "admin"
8821
+ }),
8822
+ /** Count entries in a collection, optionally filtered. */
8823
+ count: require_sleep.method(zod.z.object({
8824
+ namespace: zod.z.string().optional(),
8825
+ collection: zod.z.string(),
8826
+ filter: QueryFilterSchema.optional()
8827
+ }), zod.z.number(), { auth: "admin" }),
8828
+ /**
8829
+ * `COUNT(*)` and one `SUM` / `MIN` / `MAX` per requested field, in ONE
8830
+ * statement, over the rows `filter` selects.
8831
+ *
8832
+ * Exists because "how much is there" was being answered by materialising
8833
+ * "what is there". The recorder's storage-pressure sweep asked its in-RAM
8834
+ * footage index for bytes/count/oldest/newest across a set of storage
8835
+ * locations twice a minute, and the only way to answer that from a map is
8836
+ * to visit every row — 7.1 M of them on the live hub, ~15 M row visits a
8837
+ * minute on the main thread, which is also why the whole archive had to
8838
+ * stay resident to be visited. The question is a sum; nothing needs to be
8839
+ * materialised to answer it.
8840
+ *
8841
+ * **The engine REFUSES a field it cannot serve**, exactly as
8842
+ * `query.columns` does and unlike a PREDICATE, which is skipped when
8843
+ * unresolvable. A dropped predicate over-matches and the caller sees extra
8844
+ * rows; a dropped aggregate returns a NUMBER that is wrong and looks
8845
+ * exactly like a real one. That asymmetry is what this repo has already
8846
+ * paid for once in `count`.
8847
+ */
8848
+ aggregate: require_sleep.method(zod.z.object({
8849
+ namespace: zod.z.string().optional(),
8850
+ collection: zod.z.string(),
8851
+ fields: zod.z.array(AggregateFieldSchema).readonly(),
8852
+ filter: QueryFilterSchema.optional()
8853
+ }), AggregateResultSchema, { auth: "admin" }),
8854
+ /** Grouped counts per ((field-origin)/bucketSize) bucket, filtered. */
8855
+ histogram: require_sleep.method(zod.z.object({
8856
+ namespace: zod.z.string().optional(),
8857
+ collection: zod.z.string(),
8858
+ field: zod.z.string(),
8859
+ bucketSize: zod.z.number().int().positive(),
8860
+ origin: zod.z.number().int(),
8861
+ filter: QueryFilterSchema.optional()
8862
+ }), zod.z.array(zod.z.object({
8863
+ bucket: zod.z.number().int(),
8864
+ count: zod.z.number().int()
8865
+ })).readonly(), { auth: "admin" }),
8866
+ /** Check if a collection is empty. */
8867
+ isEmpty: require_sleep.method(zod.z.object({
8868
+ namespace: zod.z.string().optional(),
8869
+ collection: zod.z.string()
8870
+ }), zod.z.boolean(), { auth: "admin" }),
8871
+ /**
8872
+ * Declare a typed (SQL-backed) collection with explicit columns +
8873
+ * indexes. Idempotent: re-declaring an existing collection with the
8874
+ * same shape is a no-op; shape changes (new columns, new indexes)
8875
+ * are applied additively. Subsequent `insert` / `update` / `delete`
8876
+ * / `query` calls on this collection use typed columns instead of
8877
+ * JSON-blob storage — `record.data` fields are spread across
8878
+ * columns, `query.filter.where` matches real columns (no
8879
+ * `json_extract` overhead), `orderBy` uses column indexes.
8880
+ *
8881
+ * Addons call this in `onInitialize` before their first read/write.
8882
+ * Collection names should be namespaced by addon (e.g.
8883
+ * `pipeline-analytics:object-events`) to avoid cross-addon
8884
+ * clashes — no automatic prefix is added.
8885
+ */
8886
+ declareCollection: require_sleep.method(DeclareCollectionInputSchema, zod.z.void(), {
8887
+ kind: "mutation",
8888
+ auth: "admin"
8889
+ })
8890
+ }
8891
+ };
8892
+ //#endregion
8329
8893
  //#region src/capabilities/database-snapshot.cap.ts
8330
8894
  /**
8331
8895
  * The suffix of a verified snapshot, written next to the live database it
@@ -8354,7 +8918,11 @@ var DATABASE_SNAPSHOT_TIMEOUT_MS = 30 * 6e4;
8354
8918
  * database has no sidecars and is just as owned). Paths are absolute on the
8355
8919
  * owner's host.
8356
8920
  */
8357
- var DatabaseSnapshotTargetsSchema = zod.z.object({ databases: zod.z.array(zod.z.object({ livePath: zod.z.string() })) });
8921
+ var DatabaseSnapshotTargetsSchema = zod.z.object({ databases: zod.z.array(zod.z.object({
8922
+ /** Which owned file this is — the handle `writeSnapshot` takes. */
8923
+ databaseId: DataStoreDatabaseSchema,
8924
+ livePath: zod.z.string()
8925
+ })) });
8358
8926
  /** One b-tree table of the snapshot and the rows it holds. */
8359
8927
  var DatabaseSnapshotTableSchema = zod.z.object({
8360
8928
  name: zod.z.string(),
@@ -8387,6 +8955,10 @@ var DatabaseSnapshotResultSchema = zod.z.object({
8387
8955
  /** When the pinned read transaction began (ms epoch) — the snapshot's instant. */
8388
8956
  takenAt: zod.z.number()
8389
8957
  });
8958
+ /** A snapshot the owner wrote, with the file it belongs to. */
8959
+ var DatabaseSnapshotWrittenSchema = DatabaseSnapshotResultSchema.extend({ databaseId: DataStoreDatabaseSchema });
8960
+ /** Which database `writeSnapshot` copies. Required: an unstated ask is not guessed. */
8961
+ var DatabaseSnapshotRequestSchema = zod.z.object({ databaseId: DataStoreDatabaseSchema });
8390
8962
  /**
8391
8963
  * A snapshot as an ARCHIVE records it (`ArchiveManifest.sqliteSnapshots`):
8392
8964
  * the same facts, with both paths relative to the archive root (the node's
@@ -8396,7 +8968,9 @@ var ArchiveSqliteSnapshotSchema = DatabaseSnapshotResultSchema.extend({
8396
8968
  /** Archive-relative path of the live database the archive does NOT carry. */
8397
8969
  livePath: zod.z.string(),
8398
8970
  /** Archive-relative path of the verified copy it carries in its place. */
8399
- snapshotPath: zod.z.string()
8971
+ snapshotPath: zod.z.string(),
8972
+ /** Absent on an archive made before several files were owned: that was `settings`. */
8973
+ databaseId: DataStoreDatabaseSchema.optional()
8400
8974
  });
8401
8975
  /**
8402
8976
  * database-snapshot — the OWNER of a live SQLite database writes a consistent,
@@ -8421,17 +8995,21 @@ var databaseSnapshotCapability = {
8421
8995
  internal: true,
8422
8996
  methods: {
8423
8997
  /**
8424
- * Which databases this owner snapshots. Answered from memory — no file is
8425
- * touched — so a backup can ask it before deciding anything.
8998
+ * EVERY database file this owner has open, with its id. Answered from
8999
+ * memory — no file is touched — so a backup can ask it before deciding
9000
+ * anything. A file created at runtime is listed from the moment it opens,
9001
+ * so no backup can leave it out (D693).
8426
9002
  */
8427
9003
  listDatabases: require_sleep.method(zod.z.object({}), DatabaseSnapshotTargetsSchema, { auth: "admin" }),
8428
9004
  /**
8429
- * Write `<db>.backup-snapshot` and return what it holds. Throws — and
8430
- * leaves no file under the final name — when the copy fails
8431
- * `integrity_check` or its row counts differ from the pinned source's.
8432
- * A second call while one is running joins it.
9005
+ * Write `<db>.backup-snapshot` for ONE database and return what it holds.
9006
+ * Throws — and leaves no file under the final name — when the copy fails
9007
+ * `integrity_check` or its row counts differ from the pinned source's, or
9008
+ * when the database is not open. A second call for the SAME database while
9009
+ * one is running joins it; different databases run one after the other
9010
+ * (the disk reserve check is per copy).
8433
9011
  */
8434
- writeSnapshot: require_sleep.method(zod.z.object({}), DatabaseSnapshotResultSchema, {
9012
+ writeSnapshot: require_sleep.method(DatabaseSnapshotRequestSchema, DatabaseSnapshotWrittenSchema, {
8435
9013
  kind: "mutation",
8436
9014
  auth: "admin",
8437
9015
  timeoutMs: DATABASE_SNAPSHOT_TIMEOUT_MS
@@ -8554,7 +9132,12 @@ var BackupArchiveEntrySchema = zod.z.object({
8554
9132
  /** Top-level locations included in the archive (db, addons, tls, …). */
8555
9133
  locations: zod.z.array(zod.z.string()).readonly(),
8556
9134
  /** Present when the archive is encrypted; restore then needs that key. */
8557
- encryption: BackupArchiveEncryptionSchema.optional()
9135
+ encryption: BackupArchiveEncryptionSchema.optional(),
9136
+ /**
9137
+ * The databases the run asked to carry (its retention class). Absent on an
9138
+ * archive made before several files were owned: that was `['settings']`.
9139
+ */
9140
+ databases: zod.z.array(DataStoreDatabaseSchema).readonly().optional()
8558
9141
  });
8559
9142
  var BackupEntrySchema = zod.z.object({
8560
9143
  id: zod.z.string(),
@@ -8588,7 +9171,12 @@ var ArchiveManifestSchema = zod.z.object({
8588
9171
  * Live SQLite databases this archive carries as their OWNER's verified
8589
9172
  * snapshot instead of as files (D693). Absent on archives made before.
8590
9173
  */
8591
- sqliteSnapshots: zod.z.array(ArchiveSqliteSnapshotSchema).optional()
9174
+ sqliteSnapshots: zod.z.array(ArchiveSqliteSnapshotSchema).optional(),
9175
+ /**
9176
+ * Owned database files the run did NOT carry (not selected): left out of the
9177
+ * tar and the entry list alike. Absent when every owned file was carried.
9178
+ */
9179
+ omittedDatabases: zod.z.array(DataStoreDatabaseSchema).optional()
8592
9180
  });
8593
9181
  var LocationStatSchema = zod.z.object({
8594
9182
  name: zod.z.string(),
@@ -8710,6 +9298,12 @@ var BackupScheduleSchema = zod.z.object({
8710
9298
  /** Optional subset of source locations to include; omitted = all. */
8711
9299
  dataSources: zod.z.array(zod.z.string()).readonly().optional(),
8712
9300
  /**
9301
+ * Which database files this schedule carries. Absent = `['settings']`
9302
+ * (an unstated ask is the minimum, D382). Events and history run weekly,
9303
+ * settings nightly (D852).
9304
+ */
9305
+ databases: zod.z.array(DataStoreDatabaseSchema).readonly().optional(),
9306
+ /**
8713
9307
  * ms-epoch of the last tick that FIRED this schedule. Stamped before the
8714
9308
  * archive runs (it is the dedupe anchor), so it says "attempted", never
8715
9309
  * "succeeded" — a run refused by every destination stamps it too.
@@ -8761,7 +9355,9 @@ var backupCapability = {
8761
9355
  * retention). Omitted = each destination's own policy
8762
9356
  * retention (manual runs).
8763
9357
  */
8764
- retentionCount: zod.z.number().int().min(1).max(1e3).optional()
9358
+ retentionCount: zod.z.number().int().min(1).max(1e3).optional(),
9359
+ /** Database files to carry; omitted = `['settings']`. */
9360
+ databases: zod.z.array(DataStoreDatabaseSchema).optional()
8765
9361
  }).optional(), BackupTriggerResultSchema, {
8766
9362
  kind: "mutation",
8767
9363
  auth: "admin"
@@ -8931,7 +9527,8 @@ var backupCapability = {
8931
9527
  enabled: zod.z.boolean(),
8932
9528
  locationIds: zod.z.array(zod.z.string()).readonly(),
8933
9529
  retentionCount: zod.z.number().int().min(1).max(1e3),
8934
- dataSources: zod.z.array(zod.z.string()).readonly().optional()
9530
+ dataSources: zod.z.array(zod.z.string()).readonly().optional(),
9531
+ databases: zod.z.array(DataStoreDatabaseSchema).readonly().optional()
8935
9532
  }), BackupScheduleSchema, {
8936
9533
  kind: "mutation",
8937
9534
  auth: "admin"
@@ -12305,780 +12902,279 @@ var CoreBlockCompileResultSchema = zod.z.object({
12305
12902
  * Compile input: legacy single-file `{ code }`, or a multi-file project.
12306
12903
  * When `files` is present, it wins over `code`.
12307
12904
  */
12308
- var CoreBlockCompileInputSchema = zod.z.object({
12309
- code: zod.z.string().optional(),
12310
- files: CoreBlockFilesSchema.optional(),
12311
- entry: zod.z.string().min(1).max(260).optional()
12312
- }).superRefine((val, ctx) => {
12313
- if (val.files !== void 0) {
12314
- const entry = val.entry ?? "main.ts";
12315
- if (val.files[entry] === void 0) ctx.addIssue({
12316
- code: "custom",
12317
- message: `files must include the entry "${entry}"`,
12318
- path: ["files"]
12319
- });
12320
- return;
12321
- }
12322
- if (val.code === void 0) ctx.addIssue({
12323
- code: "custom",
12324
- message: "code or files is required",
12325
- path: ["code"]
12326
- });
12327
- });
12328
- var coreBlocksCapability = {
12329
- name: "core-blocks",
12330
- scope: "system",
12331
- mode: "singleton",
12332
- methods: {
12333
- list: require_sleep.method(zod.z.object({}), zod.z.object({ blocks: zod.z.array(CoreBlockSchema) }), { auth: "admin" }),
12334
- get: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), zod.z.object({ block: CoreBlockSchema.nullable() }), { auth: "admin" }),
12335
- /**
12336
- * Create a block. The code is COMPILED first: storing source that does not
12337
- * compile turns an editor error into a fork failure the operator meets
12338
- * minutes later in a log.
12339
- */
12340
- create: require_sleep.method(zod.z.object({ block: CoreBlockInputSchema }), zod.z.object({ block: CoreBlockSchema }), {
12341
- kind: "mutation",
12342
- auth: "admin",
12343
- caller: "required"
12344
- }),
12345
- update: require_sleep.method(zod.z.object({
12346
- blockId: zod.z.string(),
12347
- block: CoreBlockInputFieldsSchema.partial()
12348
- }), zod.z.object({ block: CoreBlockSchema }), {
12349
- kind: "mutation",
12350
- auth: "admin",
12351
- caller: "required"
12352
- }),
12353
- delete: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), zod.z.object({ success: zod.z.literal(true) }), {
12354
- kind: "mutation",
12355
- auth: "admin"
12356
- }),
12357
- setEnabled: require_sleep.method(zod.z.object({
12358
- blockId: zod.z.string(),
12359
- enabled: zod.z.boolean()
12360
- }), zod.z.object({ block: CoreBlockSchema }), {
12361
- kind: "mutation",
12362
- auth: "admin"
12363
- }),
12364
- /**
12365
- * Stop this block's runner and let the supervisor bring it back.
12366
- *
12367
- * **It stores nothing.** There is no "restart requested" flag and no second
12368
- * lifecycle field: the supervisor already stops and respawns a runner when
12369
- * the code hash changes, and this exposes that same path deliberately
12370
- * (D62 — a switch writes the authority that already owned the function).
12371
- * The next reconcile pass remains the authority, so a restart that lost a
12372
- * race is corrected within one pass rather than leaving a stale flag behind.
12373
- *
12374
- * Refused for a DISABLED block: nothing would come back, and a button that
12375
- * silently does nothing is worse than one that says why.
12376
- */
12377
- restart: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), zod.z.object({ block: CoreBlockSchema }), {
12378
- kind: "mutation",
12379
- auth: "admin"
12380
- }),
12381
- /**
12382
- * Type-check without saving — what the editor calls as the author types, so
12383
- * the compiler's verdict is the same one the server will reach.
12384
- * Accepts `{ code }` or `{ files, entry }` (multi-file project).
12385
- */
12386
- compile: require_sleep.method(CoreBlockCompileInputSchema, CoreBlockCompileResultSchema, {
12387
- kind: "mutation",
12388
- auth: "admin"
12389
- }),
12390
- /**
12391
- * Hydrated settings form for one block, or null until a `settingsSchema`
12392
- * is known (extracted from the compiled entry's named export, or set on
12393
- * the block). Values live in the `core-blocks:settings` collection.
12394
- */
12395
- getBlockSettings: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), SettingsSchemaWithValuesSchema.nullable(), { auth: "admin" }),
12396
- /**
12397
- * Patch persisted preference values for one block. The schema is not
12398
- * rewritten here — only values. Scripts read the same blob via
12399
- * `ctx.settings.readAddonStore()` (mirrored into the block addon's
12400
- * `addon-settings` namespace on every patch).
12401
- */
12402
- updateBlockSettings: require_sleep.method(zod.z.object({
12403
- blockId: zod.z.string(),
12404
- patch: SettingsPatchSchema
12405
- }), SettingsUpdateResultSchema, {
12406
- kind: "mutation",
12407
- auth: "admin"
12408
- }),
12409
- /**
12410
- * The declaration files the editor type-checks against.
12411
- *
12412
- * Served rather than bundled: the graph is 3.4 MB across 344 files, and
12413
- * bundling it would roughly double the admin remote. Served rather than
12414
- * hand-stubbed: a block runs with the same `ctx` an addon gets, so a stub
12415
- * would drift and the editor would confidently autocomplete methods that
12416
- * do not exist.
12417
- */
12418
- getTypeDefs: require_sleep.method(zod.z.object({}), zod.z.object({ libs: zod.z.array(zod.z.object({
12419
- filePath: zod.z.string(),
12420
- content: zod.z.string()
12421
- })) }), { auth: "admin" }),
12422
- /**
12423
- * Create a composition block (D659). VALIDATED first against the live
12424
- * sources and every stored composition; a refusal names every problem by
12425
- * path and reason (D391) and stores nothing.
12426
- */
12427
- createComposition: require_sleep.method(zod.z.object({ block: CompositionBlockInputSchema }), zod.z.object({ block: CoreBlockSchema }), {
12428
- kind: "mutation",
12429
- auth: "admin",
12430
- caller: "required"
12431
- }),
12432
- /**
12433
- * Patch a composition block. A changed `composition` is validated exactly
12434
- * as on create. `expectedUpdatedAt` is the `updatedAt` the caller READ: a
12435
- * block changed since is refused by name, never silently overwritten.
12436
- */
12437
- updateComposition: require_sleep.method(zod.z.object({
12438
- blockId: zod.z.string(),
12439
- expectedUpdatedAt: zod.z.number(),
12440
- block: CompositionBlockInputSchema.partial()
12441
- }), zod.z.object({ block: CoreBlockSchema }), {
12442
- kind: "mutation",
12443
- auth: "admin",
12444
- caller: "required"
12445
- }),
12446
- /** One composition and the composer's state for it, or `not-loaded` (D315). */
12447
- getComposition: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), CompositionGetReadSchema, { auth: "admin" }),
12448
- listCompositions: require_sleep.method(zod.z.object({}), CompositionListReadSchema, { auth: "admin" }),
12449
- /**
12450
- * The composition that customizes an existing device (D663), found by its
12451
- * TARGET — never by the block's derived name. One per device at most.
12452
- */
12453
- getCustomization: require_sleep.method(zod.z.object({
12454
- addonId: zod.z.string().min(1),
12455
- stableId: zod.z.string().min(1)
12456
- }), CompositionCustomizationReadSchema, { auth: "admin" }),
12457
- /**
12458
- * The capabilities a composition may BUILD on a device of `deviceType`
12459
- * (a new device, or added to an existing one), with their fields — the
12460
- * editor's feature list and the presets' availability (G2, D676). Built
12461
- * from the validator's own blockers; a replace of a BOUND cap is not
12462
- * listed here (the device's bindings say what it has).
12463
- */
12464
- listComposableCaps: require_sleep.method(zod.z.object({ deviceType: zod.z.enum(require_err_msg.DeviceType) }), ComposableCapabilitiesSchema, { auth: "admin" }),
12465
- /**
12466
- * Validate without saving — the same verdict the save reaches. A mutation
12467
- * for its body size; it writes nothing, so its access is `view`.
12468
- */
12469
- validateComposition: require_sleep.method(zod.z.object({
12470
- composition: require_composition.CompositionSchema,
12471
- blockId: zod.z.string().optional()
12472
- }), CompositionValidationSchema, {
12473
- kind: "mutation",
12474
- auth: "admin",
12475
- access: "view"
12476
- })
12477
- }
12478
- };
12479
- //#endregion
12480
- //#region src/capabilities/custom-model-registry.cap.ts
12481
- /**
12482
- * `custom-model-registry` — collection cap exposing operator-registered
12483
- * custom detection models. Each provider (today: `addon-model-studio`)
12484
- * contributes a list of `CustomModelDescriptor`s; the hub auto-concatenates
12485
- * them across providers (`concatCollection`).
12486
- *
12487
- * The detection-pipeline is *aware* of this cap: when at least one provider
12488
- * exists it unions these descriptors into the per-step model picker and the
12489
- * runtime model-resolution path, alongside the static catalog. When no
12490
- * provider exists the consumer no-ops entirely (identical to the catalog-only
12491
- * behaviour).
12492
- *
12493
- * A descriptor carries a full `ModelCatalogEntry` directly — the same shape
12494
- * the static catalog uses — so the existing download/resolution code consumes
12495
- * it unchanged. `stepId` is the detection step the model targets
12496
- * (e.g. `'object-detection'`).
12497
- */
12498
- var CustomModelDescriptorSchema = zod.z.object({
12499
- stepId: zod.z.string(),
12500
- entry: ModelCatalogEntrySchema
12501
- });
12502
- /** One regular file of an imported bundle, as the producer measured it. */
12503
- var ModelBundleFileSchema = zod.z.object({
12504
- /** Relative path inside the bundle tar; an mlpackage member is `<x>.mlpackage/<member>`. */
12505
- path: zod.z.string().min(1),
12506
- bytes: zod.z.number().int().nonnegative(),
12507
- sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
12508
- });
12509
- var ImportModelBundleInputSchema = zod.z.object({
12510
- /** The registry provider that keeps the model (Model Studio). */
12511
- addonId: zod.z.string(),
12512
- stepId: zod.z.string().min(1),
12513
- /**
12514
- * The catalog rows to register, complete, every format URL already
12515
- * `camstack-local://<modelId>/<file>` naming a file of the bundle. One
12516
- * bundle may carry several rows (a base and its INT8 variant).
12517
- */
12518
- entries: zod.z.array(ModelCatalogEntrySchema).min(1).max(4),
12519
- /** The bundle tar, offered by the producer over `ctx.peerBytes` (same host). */
12520
- bundle: zod.z.object({
12521
- ticket: require_sleep.PeerBytesTicketSchema,
12522
- bytes: zod.z.number().int().positive(),
12523
- sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
12524
- }),
12525
- /** Every file the tar must hold; anything missing, extra or different is refused. */
12526
- files: zod.z.array(ModelBundleFileSchema).min(1).max(64),
12527
- /** Convert missing engine formats and push the model to every agent afterwards. */
12528
- distribute: zod.z.boolean()
12529
- });
12530
- var ImportModelBundleResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
12531
- kind: zod.z.literal("imported"),
12532
- modelIds: zod.z.array(zod.z.string()).readonly(),
12533
- distributed: zod.z.array(zod.z.object({
12534
- modelId: zod.z.string(),
12535
- nodeId: zod.z.string(),
12536
- format: zod.z.enum(require_sleep.MODEL_FORMATS),
12537
- ok: zod.z.boolean(),
12538
- error: zod.z.string().optional()
12539
- })).readonly()
12540
- }), zod.z.object({
12541
- kind: zod.z.literal("refused"),
12542
- reason: zod.z.enum([
12543
- "transfer-failed",
12544
- "checksum-mismatch",
12545
- "bundle-mismatch",
12546
- "invalid-entry",
12547
- "id-taken"
12548
- ]),
12549
- detail: zod.z.string()
12550
- })]);
12551
- var customModelRegistryCapability = {
12552
- name: "custom-model-registry",
12553
- scope: "system",
12554
- mode: "collection",
12555
- internal: true,
12556
- methods: {
12557
- /**
12558
- * `internal: true` did not gate the mount (see the 2026-08-26 note on
12559
- * `data-store-provider`) — this was reachable on the AppRouter by ANY
12560
- * authenticated session at the default `auth: 'protected'`. The only
12561
- * consumer, the detection-pipeline's model resolution
12562
- * (`addon-pipeline/src/detection-pipeline/provider.ts`), calls
12563
- * `ctx.api.customModelRegistry.listModels.query()` — UDS/Moleculer,
12564
- * never tRPC — so `auth: 'admin'` costs it nothing.
12565
- */
12566
- listModels: require_sleep.method(zod.z.void(), zod.z.array(CustomModelDescriptorSchema).readonly(), { auth: "admin" }),
12567
- /**
12568
- * Register a model whose files arrive as ONE bundle tar (D806): the tar
12569
- * is taken over `ctx.peerBytes`, checked against `bundle.sha256` and
12570
- * every row of `files`, unpacked into the hub's flat modelsDir (directory
12571
- * formats included), each format's `.revision` record written, the rows
12572
- * upserted under the operator's name, the hub marked available and —
12573
- * when asked — engine formats converted and the model distributed. A
12574
- * model id already registered by another source is refused, never
12575
- * overwritten.
12576
- */
12577
- importModelBundle: require_sleep.method(ImportModelBundleInputSchema, ImportModelBundleResultSchema, {
12578
- kind: "mutation",
12579
- auth: "admin",
12580
- timeoutMs: 30 * 6e4
12581
- })
12582
- }
12583
- };
12584
- //#endregion
12585
- //#region src/capabilities/settings-store.cap.ts
12586
- /**
12587
- * Query filter for settings-store collections.
12588
- */
12589
- var QueryFilterSchema = zod.z.object({
12590
- where: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
12591
- whereIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
12592
- whereBetween: zod.z.record(zod.z.string(), zod.z.tuple([zod.z.unknown(), zod.z.unknown()])).optional(),
12593
- /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
12594
- whereNot: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
12595
- /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
12596
- * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
12597
- whereNotIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
12598
- orderBy: zod.z.object({
12599
- field: zod.z.string(),
12600
- direction: zod.z.enum(["asc", "desc"])
12601
- }).optional(),
12602
- limit: zod.z.number().optional(),
12603
- offset: zod.z.number().optional()
12604
- });
12605
- /**
12606
- * The predicate half of a filter, for BULK MUTATIONS.
12607
- *
12608
- * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
12609
- * meaning for a statement that rewrites a set, and accepting them would invite
12610
- * a caller to believe `limit` bounds the damage. Every field is optional here
12611
- * only so the shape stays composable — the implementation REJECTS a filter
12612
- * that compiles to no predicate, because that is the whole collection.
12613
- */
12614
- var MutationFilterSchema = zod.z.object({
12615
- where: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
12616
- whereIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
12617
- whereBetween: zod.z.record(zod.z.string(), zod.z.tuple([zod.z.unknown(), zod.z.unknown()])).optional(),
12618
- whereNot: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
12619
- whereNotIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional()
12620
- });
12621
- /**
12622
- * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
12623
- *
12624
- * `as` names the slot in the result, so the SAME column may be asked twice with
12625
- * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
12626
- * a `Record<column, op>` shape could not express.
12627
- */
12628
- var AggregateFieldSchema = zod.z.object({
12629
- /** Result key. */
12630
- as: zod.z.string().min(1),
12631
- /** Column to aggregate. Must be a real column of a declared collection. */
12632
- field: zod.z.string().min(1),
12633
- op: zod.z.enum([
12634
- "sum",
12635
- "min",
12636
- "max"
12637
- ])
12638
- });
12639
- /**
12640
- * `COUNT(*)` plus one number per requested field.
12641
- *
12642
- * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
12643
- * that really is 0 are different facts, and an accounting caller that renders
12644
- * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
12645
- */
12646
- var AggregateResultSchema = zod.z.object({
12647
- count: zod.z.number().int(),
12648
- values: zod.z.record(zod.z.string(), zod.z.number().nullable())
12649
- });
12650
- /** A single stored record: `{ id, data }`. */
12651
- var SettingsRecordSchema = zod.z.object({
12652
- id: zod.z.string(),
12653
- data: zod.z.record(zod.z.string(), zod.z.unknown())
12654
- });
12655
- /**
12656
- * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
12657
- * optional.
12658
- *
12659
- * A separate schema rather than loosening the shared one: every other method
12660
- * on this cap addresses a row BY its id, and making that field optional
12661
- * everywhere would turn a forgotten key into a silently generated one on
12662
- * `update` and `delete` as well.
12663
- */
12664
- var BulkRecordSchema = zod.z.object({
12665
- id: zod.z.string().optional(),
12666
- data: zod.z.record(zod.z.string(), zod.z.unknown())
12667
- });
12668
- /**
12669
- * One write inside a {@link settingsStoreCapability.methods.transact} call.
12670
- * Only the three that a "replace this set, bump that revision" write needs.
12671
- */
12672
- var TransactOpSchema = zod.z.discriminatedUnion("op", [
12673
- zod.z.object({
12674
- op: zod.z.literal("deleteWhere"),
12675
- collection: zod.z.string(),
12676
- filter: MutationFilterSchema
12677
- }),
12678
- zod.z.object({
12679
- op: zod.z.literal("insertMany"),
12680
- collection: zod.z.string(),
12681
- records: zod.z.array(BulkRecordSchema).readonly()
12682
- }),
12683
- zod.z.object({
12684
- op: zod.z.literal("update"),
12685
- collection: zod.z.string(),
12686
- id: zod.z.string(),
12687
- data: zod.z.record(zod.z.string(), zod.z.unknown())
12688
- })
12689
- ]);
12690
- /**
12691
- * The precondition of a transaction: the row `key` of `collection` must hold
12692
- * `equals` in `column`, read INSIDE the transaction.
12693
- */
12694
- var TransactGuardSchema = zod.z.object({
12695
- collection: zod.z.string(),
12696
- key: zod.z.string(),
12697
- column: zod.z.string(),
12698
- equals: zod.z.unknown()
12699
- });
12700
- /**
12701
- * Column declaration for a structured (SQL-backed) collection.
12702
- *
12703
- * Logical types — the backend translates each to the matching SQLite
12704
- * storage class and handles per-type marshaling:
12705
- * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
12706
- * - `JSON` — TEXT under the hood; serialised on write, parsed on read
12707
- * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
12708
- */
12709
- var CollectionColumnSchema = zod.z.object({
12710
- name: zod.z.string(),
12711
- type: zod.z.enum([
12712
- "TEXT",
12713
- "INTEGER",
12714
- "REAL",
12715
- "JSON",
12716
- "BOOLEAN"
12717
- ]),
12718
- primaryKey: zod.z.boolean().optional(),
12719
- notNull: zod.z.boolean().optional(),
12720
- unique: zod.z.boolean().optional(),
12721
- /**
12722
- * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
12723
- * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
12724
- * collection surface simply cannot express their existing tables — which is
12725
- * why they were still on the uncapped `table*` API.
12726
- */
12727
- defaultValue: zod.z.union([
12728
- zod.z.string(),
12729
- zod.z.number(),
12730
- zod.z.boolean()
12731
- ]).optional()
12732
- });
12733
- var CollectionIndexSchema = zod.z.object({
12734
- name: zod.z.string(),
12735
- columns: zod.z.array(zod.z.string()).readonly(),
12736
- unique: zod.z.boolean().optional()
12737
- });
12738
- /**
12739
- * settings-store — singleton capability for addon-scoped persistence.
12740
- *
12741
- * Every method operates within a `collection`. An optional `namespace`
12742
- * field provides access to additional data spaces beyond the default
12743
- * addon settings — useful for business data (events, tracks, faces, etc.).
12744
- *
12745
- * **Scoping is the CALLER's, and it is opt-in.** The table is
12746
- * `namespace ? `${namespace}:${collection}` : collection` — nothing
12747
- * consults the identity of the caller. An earlier version of this comment
12748
- * claimed the implementation prefixes every collection with the calling
12749
- * addon's ID and that "addons never see each other's data"; that was never
12750
- * true, and this same file contradicted it under `declareCollection`. What
12751
- * exists is `addon-context-factory`, which passes `namespace: addonId` ON
12752
- * THE ADDON'S BEHALF for the `addon-settings` / `addon-devices` paths only.
12753
- * Business collections use bare names, and any caller may name any
12754
- * namespace, or none. Making the door enforce it is tracked separately —
12755
- * it renames tables, so it needs a migration.
12756
- *
12757
- * **So every method is `auth: 'admin'` (D634).** Because nothing scopes a
12758
- * call to its caller, a `protected` method here was a raw, unredacted read
12759
- * (and write) of every addon's collections for any role holding
12760
- * `category:system` — which is every role: a `viewer` could read every
12761
- * notifier token in plaintext from `notifiers:notification-targets`. No
12762
- * browser or app client calls this cap. Addons are not affected: an
12763
- * addon's `ctx.api` cap call is resolved to the provider directly and never
12764
- * enters the tRPC router (so the tier gates sessions and tokens, never
12765
- * addons), and the hub itself calls it through the capability registry.
12766
- *
12767
- * - `{ collection: 'addon-settings' }` → table `addon-settings`
12768
- * - `{ namespace: 'my-addon', collection: 'addon-settings' }` →
12769
- * table `my-addon:addon-settings`
12770
- *
12771
- * Served by the **storage-orchestrator** builtin, which dispatches to the
12772
- * `data-store-provider` engine registered for the collection
12773
- * (`sqlite-settings`, a SQLite WAL backend, today). One addon owns the
12774
- * data door; engines sit behind it
12775
- * ([D44](../../../../docs/decisions/adr-0044.md)).
12776
- * Addons access it via `ctx.api.settingsStore.*`.
12777
- */
12778
- var settingsStoreCapability = {
12779
- name: "settings-store",
12905
+ var CoreBlockCompileInputSchema = zod.z.object({
12906
+ code: zod.z.string().optional(),
12907
+ files: CoreBlockFilesSchema.optional(),
12908
+ entry: zod.z.string().min(1).max(260).optional()
12909
+ }).superRefine((val, ctx) => {
12910
+ if (val.files !== void 0) {
12911
+ const entry = val.entry ?? "main.ts";
12912
+ if (val.files[entry] === void 0) ctx.addIssue({
12913
+ code: "custom",
12914
+ message: `files must include the entry "${entry}"`,
12915
+ path: ["files"]
12916
+ });
12917
+ return;
12918
+ }
12919
+ if (val.code === void 0) ctx.addIssue({
12920
+ code: "custom",
12921
+ message: "code or files is required",
12922
+ path: ["code"]
12923
+ });
12924
+ });
12925
+ var coreBlocksCapability = {
12926
+ name: "core-blocks",
12780
12927
  scope: "system",
12781
12928
  mode: "singleton",
12782
12929
  methods: {
12783
- /** Get a single value by key from a collection. */
12784
- get: require_sleep.method(zod.z.object({
12785
- namespace: zod.z.string().optional(),
12786
- collection: zod.z.string(),
12787
- key: zod.z.string()
12788
- }), zod.z.unknown(), { auth: "admin" }),
12789
- /** Set a value by key in a collection (upsert). */
12790
- set: require_sleep.method(zod.z.object({
12791
- namespace: zod.z.string().optional(),
12792
- collection: zod.z.string(),
12793
- key: zod.z.string(),
12794
- value: zod.z.unknown()
12795
- }), zod.z.void(), {
12796
- kind: "mutation",
12797
- auth: "admin"
12798
- }),
12799
- /**
12800
- * Rows matching a filter.
12801
- *
12802
- * `columns` is a SQL-level projection: the engine SELECTs only those
12803
- * columns (plus the primary key) instead of the whole row. It is the only
12804
- * way to stop paying disk for a column that is discarded — a heavy JSON
12805
- * column dominates the row, and dropping it in JS after the read pays every
12806
- * byte first. Measured on the live `pipeline-analytics:tracks` table: a
12807
- * 501-row page is 4.6 MB whole and 0.11 MB projected, same index, same
12808
- * plan.
12809
- *
12810
- * The engine REFUSES a column it cannot serve rather than omitting it —
12811
- * see `SettingsQueryInput.columns` for why a projection may not be
12812
- * forgiving the way a predicate is.
12813
- */
12814
- query: require_sleep.method(zod.z.object({
12815
- namespace: zod.z.string().optional(),
12816
- collection: zod.z.string(),
12817
- filter: QueryFilterSchema.optional(),
12818
- columns: zod.z.array(zod.z.string()).readonly().optional()
12819
- }), zod.z.array(SettingsRecordSchema).readonly(), { auth: "admin" }),
12930
+ list: require_sleep.method(zod.z.object({}), zod.z.object({ blocks: zod.z.array(CoreBlockSchema) }), { auth: "admin" }),
12931
+ get: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), zod.z.object({ block: CoreBlockSchema.nullable() }), { auth: "admin" }),
12820
12932
  /**
12821
- * Make a SLOT hold this row — insert it, or update whatever already
12822
- * occupies the slot — and answer with the id the row HAS.
12823
- *
12824
- * ## Why the store needed this
12825
- *
12826
- * {@link set} is already an upsert, but its slot is the PRIMARY KEY: the
12827
- * caller has to know the id to address the row. That is exactly what a
12828
- * collection keyed by an `INTEGER` rowid alias cannot do — SQLite assigns
12829
- * the id, so a writer whose uniqueness rule is "one row per
12830
- * `(ownerKind, ownerId, kind, slot)`" has no id to name and no way to
12831
- * express the rule. {@link insert} can express it (a UNIQUE index
12832
- * arbitrates) but only by THROWING at the loser of a race, and the loser
12833
- * here is a legitimate re-capture, not an error.
12834
- *
12835
- * Getting there without this method means read-then-write: query the slot,
12836
- * then `insert` or `update`. Two statements instead of one, a race between
12837
- * them, and a recovery path that has to recognise a UNIQUE violation by its
12838
- * MESSAGE — across a process boundary, because the store lives behind an
12839
- * RPC door. A string match would be the whole gate, and a store that
12840
- * rephrased its error would lose the row silently. One statement has no
12841
- * such gate (D482).
12842
- *
12843
- * ## The contract
12844
- *
12845
- * `conflict` names the DECLARED columns that identify the slot. They must
12846
- * be covered by a UNIQUE index on the collection — SQLite resolves an
12847
- * `ON CONFLICT` target against its indexes and REFUSES the statement when
12848
- * none matches, so a wrong list is a loud error on the first write, never a
12849
- * silently duplicated row. An empty list is refused rather than passed on:
12850
- * `ON CONFLICT()` is not valid SQL, and the intent it would most likely
12851
- * encode ("no slot, just insert") is {@link insert}.
12852
- *
12853
- * Row shape is {@link insert}'s: `record.id` is used when the collection
12854
- * needs a supplied key and IGNORED when the primary key is an `INTEGER`
12855
- * rowid alias — exactly as `insert` and `insertMany` already treat it.
12856
- *
12857
- * Returns the id either way: the one SQLite assigned on an insert, or the
12858
- * one the row it updated already had. A caller cannot tell which happened,
12859
- * and deliberately — every caller wants the slot's id, and one that needed
12860
- * to distinguish creation from replacement would have to read first anyway.
12933
+ * Create a block. The code is COMPILED first: storing source that does not
12934
+ * compile turns an editor error into a fork failure the operator meets
12935
+ * minutes later in a log.
12861
12936
  */
12862
- upsert: require_sleep.method(zod.z.object({
12863
- namespace: zod.z.string().optional(),
12864
- collection: zod.z.string(),
12865
- /** Declared columns identifying the slot; must be covered by a UNIQUE
12866
- * index on the collection. Never empty. */
12867
- conflict: zod.z.array(zod.z.string()).readonly(),
12868
- record: BulkRecordSchema
12869
- }), zod.z.object({
12870
- /** The id the row HAS: assigned by SQLite on an insert, or the existing
12871
- * row's own id on an update. */
12872
- id: zod.z.union([zod.z.string(), zod.z.number()]) }), {
12937
+ create: require_sleep.method(zod.z.object({ block: CoreBlockInputSchema }), zod.z.object({ block: CoreBlockSchema }), {
12873
12938
  kind: "mutation",
12874
- auth: "admin"
12939
+ auth: "admin",
12940
+ caller: "required"
12875
12941
  }),
12876
- /** Insert a new record. */
12877
- insert: require_sleep.method(zod.z.object({
12878
- namespace: zod.z.string().optional(),
12879
- collection: zod.z.string(),
12880
- record: zod.z.object({
12881
- id: zod.z.string().optional(),
12882
- data: zod.z.record(zod.z.string(), zod.z.unknown())
12883
- })
12884
- }), zod.z.object({
12885
- /**
12886
- * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12887
- * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12888
- * primary key — which is the only place an auto key is knowable.
12889
- */
12890
- id: zod.z.union([zod.z.string(), zod.z.number()]) }), {
12942
+ update: require_sleep.method(zod.z.object({
12943
+ blockId: zod.z.string(),
12944
+ block: CoreBlockInputFieldsSchema.partial()
12945
+ }), zod.z.object({ block: CoreBlockSchema }), {
12946
+ kind: "mutation",
12947
+ auth: "admin",
12948
+ caller: "required"
12949
+ }),
12950
+ delete: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), zod.z.object({ success: zod.z.literal(true) }), {
12891
12951
  kind: "mutation",
12892
12952
  auth: "admin"
12893
12953
  }),
12894
- /**
12895
- * Insert MANY records in ONE transaction, returning how many landed.
12896
- *
12897
- * The write-side twin of {@link deleteWhere}, and it exists for the same
12898
- * reason: without it, appending a batch is N round trips and N COMMITs on
12899
- * the single shared connection that also serves every cluster-wide
12900
- * configuration read. The durable load series writes one process row per
12901
- * process per sample — 76 rows every 10 s on the live fleet — and the
12902
- * operator's rule for it is *one transaction per sample, never one per
12903
- * row*. `insert` cannot express that; nothing else could.
12904
- *
12905
- * **All or nothing.** A batch that fails on its fifth row leaves none of
12906
- * the five behind. A half-written sample is worse than a missing one: the
12907
- * missing one reads as "nobody reported", which is true, while the half
12908
- * one reads as "these were the only processes running", which is not.
12909
- *
12910
- * `id` is OPTIONAL per record, and that is the difference from
12911
- * {@link insert}. A collection whose primary key is an `INTEGER` rowid
12912
- * alias has no id to supply — SQLite assigns it, for free, and inventing a
12913
- * `randomUUID()` for such a column would write a 36-character string into
12914
- * an integer key. Omitted on a TEXT key, a uuid is generated exactly as
12915
- * `insert` does.
12916
- */
12917
- insertMany: require_sleep.method(zod.z.object({
12918
- namespace: zod.z.string().optional(),
12919
- collection: zod.z.string(),
12920
- records: zod.z.array(BulkRecordSchema).readonly()
12921
- }), zod.z.object({ inserted: zod.z.number().int() }), {
12954
+ setEnabled: require_sleep.method(zod.z.object({
12955
+ blockId: zod.z.string(),
12956
+ enabled: zod.z.boolean()
12957
+ }), zod.z.object({ block: CoreBlockSchema }), {
12922
12958
  kind: "mutation",
12923
12959
  auth: "admin"
12924
12960
  }),
12925
12961
  /**
12926
- * Several writes, ONE transaction, behind an optional precondition.
12927
- *
12928
- * The multi-collection twin of {@link insertMany}: a write that has to move
12929
- * a set of rows in one collection AND a revision in another cannot be
12930
- * expressed as separate calls without a window in which a reader (or a
12931
- * crash) sees half of it. The retrain annotation save replaces a frame's
12932
- * rows, its dismissed set and its revision this way.
12962
+ * Stop this block's runner and let the supervisor bring it back.
12933
12963
  *
12934
- * `guard`, when given, is read inside the transaction: if the row is
12935
- * missing or its column differs from `equals`, NOTHING is written and the
12936
- * answer is `{ applied: false, current }` with what was found — the
12937
- * compare-and-set a revisioned write needs, with no second reader able to
12938
- * slip between the check and the writes.
12964
+ * **It stores nothing.** There is no "restart requested" flag and no second
12965
+ * lifecycle field: the supervisor already stops and respawns a runner when
12966
+ * the code hash changes, and this exposes that same path deliberately
12967
+ * (D62 — a switch writes the authority that already owned the function).
12968
+ * The next reconcile pass remains the authority, so a restart that lost a
12969
+ * race is corrected within one pass rather than leaving a stale flag behind.
12939
12970
  *
12940
- * **All or nothing**, like {@link insertMany}: a throw in any op rolls the
12941
- * whole call back. Ops run in the order given (delete before insert to
12942
- * replace a set). `namespace` applies to every collection named.
12971
+ * Refused for a DISABLED block: nothing would come back, and a button that
12972
+ * silently does nothing is worse than one that says why.
12943
12973
  */
12944
- transact: require_sleep.method(zod.z.object({
12945
- namespace: zod.z.string().optional(),
12946
- guard: TransactGuardSchema.optional(),
12947
- ops: zod.z.array(TransactOpSchema).readonly()
12948
- }), zod.z.object({
12949
- applied: zod.z.boolean(),
12950
- current: zod.z.unknown().optional()
12951
- }), {
12974
+ restart: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), zod.z.object({ block: CoreBlockSchema }), {
12952
12975
  kind: "mutation",
12953
12976
  auth: "admin"
12954
12977
  }),
12955
- /** Update an existing record by ID. */
12956
- update: require_sleep.method(zod.z.object({
12957
- namespace: zod.z.string().optional(),
12958
- collection: zod.z.string(),
12959
- id: zod.z.string(),
12960
- data: zod.z.record(zod.z.string(), zod.z.unknown())
12961
- }), zod.z.void(), {
12978
+ /**
12979
+ * Type-check without saving — what the editor calls as the author types, so
12980
+ * the compiler's verdict is the same one the server will reach.
12981
+ * Accepts `{ code }` or `{ files, entry }` (multi-file project).
12982
+ */
12983
+ compile: require_sleep.method(CoreBlockCompileInputSchema, CoreBlockCompileResultSchema, {
12962
12984
  kind: "mutation",
12963
12985
  auth: "admin"
12964
12986
  }),
12965
- /** Delete a record by key/ID. */
12966
- delete: require_sleep.method(zod.z.object({
12967
- namespace: zod.z.string().optional(),
12968
- collection: zod.z.string(),
12969
- key: zod.z.string()
12970
- }), zod.z.void(), {
12987
+ /**
12988
+ * Hydrated settings form for one block, or null until a `settingsSchema`
12989
+ * is known (extracted from the compiled entry's named export, or set on
12990
+ * the block). Values live in the `core-blocks:settings` collection.
12991
+ */
12992
+ getBlockSettings: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), SettingsSchemaWithValuesSchema.nullable(), { auth: "admin" }),
12993
+ /**
12994
+ * Patch persisted preference values for one block. The schema is not
12995
+ * rewritten here — only values. Scripts read the same blob via
12996
+ * `ctx.settings.readAddonStore()` (mirrored into the block addon's
12997
+ * `addon-settings` namespace on every patch).
12998
+ */
12999
+ updateBlockSettings: require_sleep.method(zod.z.object({
13000
+ blockId: zod.z.string(),
13001
+ patch: SettingsPatchSchema
13002
+ }), SettingsUpdateResultSchema, {
12971
13003
  kind: "mutation",
12972
13004
  auth: "admin"
12973
13005
  }),
12974
13006
  /**
12975
- * Delete every record matching `filter`, in ONE statement, returning how
12976
- * many rows went. This exists because its absence made every retention
12977
- * path in the system an N+1 drain loop: `delete` takes a key, so a sweep
12978
- * had to SELECT a page of full rows — every column, including the fat
12979
- * ones — purely to learn their ids, then issue one call per row.
13007
+ * The declaration files the editor type-checks against.
12980
13008
  *
12981
- * **The filter is required and must resolve.** A predicate naming
12982
- * something the collection cannot express is an ERROR here, not a
12983
- * widening as it is on `query`, and a filter with no predicates is an
12984
- * error rather than "every row". Deleting a whole collection is a
12985
- * legitimate intent, but it must be asked for by name — not reached by
12986
- * an empty object.
13009
+ * Served rather than bundled: the graph is 3.4 MB across 344 files, and
13010
+ * bundling it would roughly double the admin remote. Served rather than
13011
+ * hand-stubbed: a block runs with the same `ctx` an addon gets, so a stub
13012
+ * would drift and the editor would confidently autocomplete methods that
13013
+ * do not exist.
12987
13014
  */
12988
- deleteWhere: require_sleep.method(zod.z.object({
12989
- namespace: zod.z.string().optional(),
12990
- collection: zod.z.string(),
12991
- filter: MutationFilterSchema
12992
- }), zod.z.object({ deleted: zod.z.number().int() }), {
13015
+ getTypeDefs: require_sleep.method(zod.z.object({}), zod.z.object({ libs: zod.z.array(zod.z.object({
13016
+ filePath: zod.z.string(),
13017
+ content: zod.z.string()
13018
+ })) }), { auth: "admin" }),
13019
+ /**
13020
+ * Create a composition block (D659). VALIDATED first against the live
13021
+ * sources and every stored composition; a refusal names every problem by
13022
+ * path and reason (D391) and stores nothing.
13023
+ */
13024
+ createComposition: require_sleep.method(zod.z.object({ block: CompositionBlockInputSchema }), zod.z.object({ block: CoreBlockSchema }), {
12993
13025
  kind: "mutation",
12994
- auth: "admin"
13026
+ auth: "admin",
13027
+ caller: "required"
12995
13028
  }),
12996
13029
  /**
12997
- * Apply `data` to every record matching `filter`, in one statement,
12998
- * returning how many rows changed. Same filter contract as
12999
- * {@link deleteWhere} — an unresolvable predicate is an error.
13030
+ * Patch a composition block. A changed `composition` is validated exactly
13031
+ * as on create. `expectedUpdatedAt` is the `updatedAt` the caller READ: a
13032
+ * block changed since is refused by name, never silently overwritten.
13000
13033
  */
13001
- updateWhere: require_sleep.method(zod.z.object({
13002
- namespace: zod.z.string().optional(),
13003
- collection: zod.z.string(),
13004
- filter: MutationFilterSchema,
13005
- data: zod.z.record(zod.z.string(), zod.z.unknown())
13006
- }), zod.z.object({ updated: zod.z.number().int() }), {
13034
+ updateComposition: require_sleep.method(zod.z.object({
13035
+ blockId: zod.z.string(),
13036
+ expectedUpdatedAt: zod.z.number(),
13037
+ block: CompositionBlockInputSchema.partial()
13038
+ }), zod.z.object({ block: CoreBlockSchema }), {
13007
13039
  kind: "mutation",
13008
- auth: "admin"
13040
+ auth: "admin",
13041
+ caller: "required"
13009
13042
  }),
13010
- /** Count entries in a collection, optionally filtered. */
13011
- count: require_sleep.method(zod.z.object({
13012
- namespace: zod.z.string().optional(),
13013
- collection: zod.z.string(),
13014
- filter: QueryFilterSchema.optional()
13015
- }), zod.z.number(), { auth: "admin" }),
13043
+ /** One composition and the composer's state for it, or `not-loaded` (D315). */
13044
+ getComposition: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), CompositionGetReadSchema, { auth: "admin" }),
13045
+ listCompositions: require_sleep.method(zod.z.object({}), CompositionListReadSchema, { auth: "admin" }),
13016
13046
  /**
13017
- * `COUNT(*)` and one `SUM` / `MIN` / `MAX` per requested field, in ONE
13018
- * statement, over the rows `filter` selects.
13019
- *
13020
- * Exists because "how much is there" was being answered by materialising
13021
- * "what is there". The recorder's storage-pressure sweep asked its in-RAM
13022
- * footage index for bytes/count/oldest/newest across a set of storage
13023
- * locations twice a minute, and the only way to answer that from a map is
13024
- * to visit every row — 7.1 M of them on the live hub, ~15 M row visits a
13025
- * minute on the main thread, which is also why the whole archive had to
13026
- * stay resident to be visited. The question is a sum; nothing needs to be
13027
- * materialised to answer it.
13028
- *
13029
- * **The engine REFUSES a field it cannot serve**, exactly as
13030
- * `query.columns` does and unlike a PREDICATE, which is skipped when
13031
- * unresolvable. A dropped predicate over-matches and the caller sees extra
13032
- * rows; a dropped aggregate returns a NUMBER that is wrong and looks
13033
- * exactly like a real one. That asymmetry is what this repo has already
13034
- * paid for once in `count`.
13047
+ * The composition that customizes an existing device (D663), found by its
13048
+ * TARGET — never by the block's derived name. One per device at most.
13035
13049
  */
13036
- aggregate: require_sleep.method(zod.z.object({
13037
- namespace: zod.z.string().optional(),
13038
- collection: zod.z.string(),
13039
- fields: zod.z.array(AggregateFieldSchema).readonly(),
13040
- filter: QueryFilterSchema.optional()
13041
- }), AggregateResultSchema, { auth: "admin" }),
13042
- /** Grouped counts per ((field-origin)/bucketSize) bucket, filtered. */
13043
- histogram: require_sleep.method(zod.z.object({
13044
- namespace: zod.z.string().optional(),
13045
- collection: zod.z.string(),
13046
- field: zod.z.string(),
13047
- bucketSize: zod.z.number().int().positive(),
13048
- origin: zod.z.number().int(),
13049
- filter: QueryFilterSchema.optional()
13050
- }), zod.z.array(zod.z.object({
13051
- bucket: zod.z.number().int(),
13052
- count: zod.z.number().int()
13053
- })).readonly(), { auth: "admin" }),
13054
- /** Check if a collection is empty. */
13055
- isEmpty: require_sleep.method(zod.z.object({
13056
- namespace: zod.z.string().optional(),
13057
- collection: zod.z.string()
13058
- }), zod.z.boolean(), { auth: "admin" }),
13050
+ getCustomization: require_sleep.method(zod.z.object({
13051
+ addonId: zod.z.string().min(1),
13052
+ stableId: zod.z.string().min(1)
13053
+ }), CompositionCustomizationReadSchema, { auth: "admin" }),
13059
13054
  /**
13060
- * Declare a typed (SQL-backed) collection with explicit columns +
13061
- * indexes. Idempotent: re-declaring an existing collection with the
13062
- * same shape is a no-op; shape changes (new columns, new indexes)
13063
- * are applied additively. Subsequent `insert` / `update` / `delete`
13064
- * / `query` calls on this collection use typed columns instead of
13065
- * JSON-blob storage — `record.data` fields are spread across
13066
- * columns, `query.filter.where` matches real columns (no
13067
- * `json_extract` overhead), `orderBy` uses column indexes.
13068
- *
13069
- * Addons call this in `onInitialize` before their first read/write.
13070
- * Collection names should be namespaced by addon (e.g.
13071
- * `pipeline-analytics:object-events`) to avoid cross-addon
13072
- * clashes — no automatic prefix is added.
13055
+ * The capabilities a composition may BUILD on a device of `deviceType`
13056
+ * (a new device, or added to an existing one), with their fields — the
13057
+ * editor's feature list and the presets' availability (G2, D676). Built
13058
+ * from the validator's own blockers; a replace of a BOUND cap is not
13059
+ * listed here (the device's bindings say what it has).
13073
13060
  */
13074
- declareCollection: require_sleep.method(zod.z.object({
13075
- namespace: zod.z.string().optional(),
13076
- collection: zod.z.string(),
13077
- columns: zod.z.array(CollectionColumnSchema).readonly(),
13078
- indexes: zod.z.array(CollectionIndexSchema).readonly().optional()
13079
- }), zod.z.void(), {
13061
+ listComposableCaps: require_sleep.method(zod.z.object({ deviceType: zod.z.enum(require_err_msg.DeviceType) }), ComposableCapabilitiesSchema, { auth: "admin" }),
13062
+ /**
13063
+ * Validate without saving — the same verdict the save reaches. A mutation
13064
+ * for its body size; it writes nothing, so its access is `view`.
13065
+ */
13066
+ validateComposition: require_sleep.method(zod.z.object({
13067
+ composition: require_composition.CompositionSchema,
13068
+ blockId: zod.z.string().optional()
13069
+ }), CompositionValidationSchema, {
13080
13070
  kind: "mutation",
13081
- auth: "admin"
13071
+ auth: "admin",
13072
+ access: "view"
13073
+ })
13074
+ }
13075
+ };
13076
+ //#endregion
13077
+ //#region src/capabilities/custom-model-registry.cap.ts
13078
+ /**
13079
+ * `custom-model-registry` — collection cap exposing operator-registered
13080
+ * custom detection models. Each provider (today: `addon-model-studio`)
13081
+ * contributes a list of `CustomModelDescriptor`s; the hub auto-concatenates
13082
+ * them across providers (`concatCollection`).
13083
+ *
13084
+ * The detection-pipeline is *aware* of this cap: when at least one provider
13085
+ * exists it unions these descriptors into the per-step model picker and the
13086
+ * runtime model-resolution path, alongside the static catalog. When no
13087
+ * provider exists the consumer no-ops entirely (identical to the catalog-only
13088
+ * behaviour).
13089
+ *
13090
+ * A descriptor carries a full `ModelCatalogEntry` directly — the same shape
13091
+ * the static catalog uses — so the existing download/resolution code consumes
13092
+ * it unchanged. `stepId` is the detection step the model targets
13093
+ * (e.g. `'object-detection'`).
13094
+ */
13095
+ var CustomModelDescriptorSchema = zod.z.object({
13096
+ stepId: zod.z.string(),
13097
+ entry: ModelCatalogEntrySchema
13098
+ });
13099
+ /** One regular file of an imported bundle, as the producer measured it. */
13100
+ var ModelBundleFileSchema = zod.z.object({
13101
+ /** Relative path inside the bundle tar; an mlpackage member is `<x>.mlpackage/<member>`. */
13102
+ path: zod.z.string().min(1),
13103
+ bytes: zod.z.number().int().nonnegative(),
13104
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
13105
+ });
13106
+ var ImportModelBundleInputSchema = zod.z.object({
13107
+ /** The registry provider that keeps the model (Model Studio). */
13108
+ addonId: zod.z.string(),
13109
+ stepId: zod.z.string().min(1),
13110
+ /**
13111
+ * The catalog rows to register, complete, every format URL already
13112
+ * `camstack-local://<modelId>/<file>` naming a file of the bundle. One
13113
+ * bundle may carry several rows (a base and its INT8 variant).
13114
+ */
13115
+ entries: zod.z.array(ModelCatalogEntrySchema).min(1).max(4),
13116
+ /** The bundle tar, offered by the producer over `ctx.peerBytes` (same host). */
13117
+ bundle: zod.z.object({
13118
+ ticket: require_sleep.PeerBytesTicketSchema,
13119
+ bytes: zod.z.number().int().positive(),
13120
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
13121
+ }),
13122
+ /** Every file the tar must hold; anything missing, extra or different is refused. */
13123
+ files: zod.z.array(ModelBundleFileSchema).min(1).max(64),
13124
+ /** Convert missing engine formats and push the model to every agent afterwards. */
13125
+ distribute: zod.z.boolean()
13126
+ });
13127
+ var ImportModelBundleResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
13128
+ kind: zod.z.literal("imported"),
13129
+ modelIds: zod.z.array(zod.z.string()).readonly(),
13130
+ distributed: zod.z.array(zod.z.object({
13131
+ modelId: zod.z.string(),
13132
+ nodeId: zod.z.string(),
13133
+ format: zod.z.enum(require_sleep.MODEL_FORMATS),
13134
+ ok: zod.z.boolean(),
13135
+ error: zod.z.string().optional()
13136
+ })).readonly()
13137
+ }), zod.z.object({
13138
+ kind: zod.z.literal("refused"),
13139
+ reason: zod.z.enum([
13140
+ "transfer-failed",
13141
+ "checksum-mismatch",
13142
+ "bundle-mismatch",
13143
+ "invalid-entry",
13144
+ "id-taken"
13145
+ ]),
13146
+ detail: zod.z.string()
13147
+ })]);
13148
+ var customModelRegistryCapability = {
13149
+ name: "custom-model-registry",
13150
+ scope: "system",
13151
+ mode: "collection",
13152
+ internal: true,
13153
+ methods: {
13154
+ /**
13155
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
13156
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
13157
+ * authenticated session at the default `auth: 'protected'`. The only
13158
+ * consumer, the detection-pipeline's model resolution
13159
+ * (`addon-pipeline/src/detection-pipeline/provider.ts`), calls
13160
+ * `ctx.api.customModelRegistry.listModels.query()` — UDS/Moleculer,
13161
+ * never tRPC — so `auth: 'admin'` costs it nothing.
13162
+ */
13163
+ listModels: require_sleep.method(zod.z.void(), zod.z.array(CustomModelDescriptorSchema).readonly(), { auth: "admin" }),
13164
+ /**
13165
+ * Register a model whose files arrive as ONE bundle tar (D806): the tar
13166
+ * is taken over `ctx.peerBytes`, checked against `bundle.sha256` and
13167
+ * every row of `files`, unpacked into the hub's flat modelsDir (directory
13168
+ * formats included), each format's `.revision` record written, the rows
13169
+ * upserted under the operator's name, the hub marked available and —
13170
+ * when asked — engine formats converted and the model distributed. A
13171
+ * model id already registered by another source is refused, never
13172
+ * overwritten.
13173
+ */
13174
+ importModelBundle: require_sleep.method(ImportModelBundleInputSchema, ImportModelBundleResultSchema, {
13175
+ kind: "mutation",
13176
+ auth: "admin",
13177
+ timeoutMs: 30 * 6e4
13082
13178
  })
13083
13179
  }
13084
13180
  };
@@ -13229,6 +13325,16 @@ id: zod.z.union([zod.z.string(), zod.z.number()]) }), {
13229
13325
  kind: "mutation",
13230
13326
  auth: "admin"
13231
13327
  }),
13328
+ /** Upsert many records in ONE transaction; see the door's `upsertMany`. */
13329
+ upsertMany: require_sleep.method(zod.z.object({
13330
+ namespace: zod.z.string().optional(),
13331
+ collection: zod.z.string(),
13332
+ conflict: zod.z.array(zod.z.string()).readonly(),
13333
+ records: zod.z.array(BulkRecordSchema).readonly()
13334
+ }), zod.z.object({ upserted: zod.z.number().int() }), {
13335
+ kind: "mutation",
13336
+ auth: "admin"
13337
+ }),
13232
13338
  /** Insert many records in ONE transaction. All or nothing. */
13233
13339
  insertMany: require_sleep.method(zod.z.object({
13234
13340
  namespace: zod.z.string().optional(),
@@ -13321,12 +13427,7 @@ id: zod.z.union([zod.z.string(), zod.z.number()]) }), {
13321
13427
  collection: zod.z.string()
13322
13428
  }), zod.z.boolean(), { auth: "admin" }),
13323
13429
  /** Declare a typed (SQL-backed) collection with columns + indexes. */
13324
- declareCollection: require_sleep.method(zod.z.object({
13325
- namespace: zod.z.string().optional(),
13326
- collection: zod.z.string(),
13327
- columns: zod.z.array(CollectionColumnSchema).readonly(),
13328
- indexes: zod.z.array(CollectionIndexSchema).readonly().optional()
13329
- }), zod.z.void(), {
13430
+ declareCollection: require_sleep.method(DeclareCollectionInputSchema, zod.z.void(), {
13330
13431
  kind: "mutation",
13331
13432
  auth: "admin"
13332
13433
  })
@@ -56052,6 +56153,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
56052
56153
  addonId: null,
56053
56154
  access: "create"
56054
56155
  },
56156
+ "dataStoreProvider.upsertMany": {
56157
+ capName: "data-store-provider",
56158
+ capScope: "system",
56159
+ addonId: null,
56160
+ access: "create"
56161
+ },
56055
56162
  "dayNight.getOptions": {
56056
56163
  capName: "day-night",
56057
56164
  capScope: "device",
@@ -60900,6 +61007,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
60900
61007
  addonId: null,
60901
61008
  access: "create"
60902
61009
  },
61010
+ "settingsStore.upsertMany": {
61011
+ capName: "settings-store",
61012
+ capScope: "system",
61013
+ addonId: null,
61014
+ access: "create"
61015
+ },
60903
61016
  "smoke.getStatus": {
60904
61017
  capName: "smoke",
60905
61018
  capScope: "device",
@@ -71131,6 +71244,7 @@ function createSystemProxy(api) {
71131
71244
  set: (input) => dispatch("settingsStore", "set", "mutation", input),
71132
71245
  query: (input) => dispatch("settingsStore", "query", "query", input),
71133
71246
  upsert: (input) => dispatch("settingsStore", "upsert", "mutation", input),
71247
+ upsertMany: (input) => dispatch("settingsStore", "upsertMany", "mutation", input),
71134
71248
  insert: (input) => dispatch("settingsStore", "insert", "mutation", input),
71135
71249
  insertMany: (input) => dispatch("settingsStore", "insertMany", "mutation", input),
71136
71250
  transact: (input) => dispatch("settingsStore", "transact", "mutation", input),
@@ -80119,6 +80233,7 @@ exports.DATABASE_SNAPSHOT_PARTIAL_SUFFIX = DATABASE_SNAPSHOT_PARTIAL_SUFFIX;
80119
80233
  exports.DATABASE_SNAPSHOT_SUFFIX = DATABASE_SNAPSHOT_SUFFIX;
80120
80234
  exports.DATABASE_SNAPSHOT_TIMEOUT_MS = DATABASE_SNAPSHOT_TIMEOUT_MS;
80121
80235
  exports.DATAPLANE_SECRET_HEADER = require_sleep.DATAPLANE_SECRET_HEADER;
80236
+ exports.DATA_STORE_DATABASES = DATA_STORE_DATABASES;
80122
80237
  exports.DAYS_PER_WEEK = DAYS_PER_WEEK;
80123
80238
  exports.DAY_NAMES = DAY_NAMES;
80124
80239
  exports.DECLARED_DEVICE_SWEEP_LIMIT = DECLARED_DEVICE_SWEEP_LIMIT;
@@ -80178,14 +80293,18 @@ exports.DISPLAY_ICON_KEYS = DISPLAY_ICON_KEYS;
80178
80293
  exports.DISPLAY_ICON_OFFERED_KEYS = DISPLAY_ICON_OFFERED_KEYS;
80179
80294
  exports.DISPLAY_ICON_VOCABULARY = DISPLAY_ICON_VOCABULARY;
80180
80295
  exports.DOORBELL_RISE_DEBOUNCE_MS = DOORBELL_RISE_DEBOUNCE_MS;
80296
+ exports.DataStoreDatabaseSchema = DataStoreDatabaseSchema;
80181
80297
  exports.DataStoreEngineInfoSchema = EngineInfoSchema;
80298
+ exports.DatabaseSnapshotRequestSchema = DatabaseSnapshotRequestSchema;
80182
80299
  exports.DatabaseSnapshotResultSchema = DatabaseSnapshotResultSchema;
80183
80300
  exports.DatabaseSnapshotTableSchema = DatabaseSnapshotTableSchema;
80184
80301
  exports.DatabaseSnapshotTargetsSchema = DatabaseSnapshotTargetsSchema;
80302
+ exports.DatabaseSnapshotWrittenSchema = DatabaseSnapshotWrittenSchema;
80185
80303
  exports.DayNightModeSchema = DayNightModeSchema;
80186
80304
  exports.DayNightOptionsSchema = DayNightOptionsSchema;
80187
80305
  exports.DayNightSettingsPatchSchema = DayNightSettingsPatchSchema;
80188
80306
  exports.DayNightStatusSchema = DayNightStatusSchema;
80307
+ exports.DeclareCollectionInputSchema = DeclareCollectionInputSchema;
80189
80308
  exports.DeclaredDevices = DeclaredDevices;
80190
80309
  exports.DecodedAudioChunkSchema = require_sleep.DecodedAudioChunkSchema;
80191
80310
  exports.DeleteIntegrationResultSchema = DeleteIntegrationResultSchema;