@camstack/system 1.2.424 → 1.2.426

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/autotrack/index.js +1 -1
  8. package/dist/builtins/autotrack/index.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-databases.d.ts +51 -0
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +19 -0
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +268 -17
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +268 -17
  13. package/dist/builtins/backup-orchestrator/manifest-store.d.ts +23 -3
  14. package/dist/builtins/backup-orchestrator/run-queue.d.ts +3 -0
  15. package/dist/builtins/backup-orchestrator/schedule-store.d.ts +7 -1
  16. package/dist/builtins/backup-orchestrator/seed-marker-store.d.ts +34 -0
  17. package/dist/builtins/calendars/calendars.addon.js +1 -1
  18. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  19. package/dist/builtins/camera-grid/index.js +1 -1
  20. package/dist/builtins/camera-grid/index.mjs +1 -1
  21. package/dist/builtins/composer/composer.addon.js +2 -2
  22. package/dist/builtins/composer/composer.addon.mjs +2 -2
  23. package/dist/builtins/console-logging/index.js +1 -1
  24. package/dist/builtins/console-logging/index.mjs +1 -1
  25. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  26. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  27. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  28. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  29. package/dist/builtins/hub-forwarder/index.js +1 -1
  30. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  31. package/dist/builtins/keychain/index.js +3 -3
  32. package/dist/builtins/keychain/index.mjs +3 -3
  33. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  34. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  35. package/dist/builtins/local-auth/local-auth.addon.js +2 -2
  36. package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
  37. package/dist/builtins/local-network/address-book.d.ts +21 -0
  38. package/dist/builtins/local-network/local-network.addon.d.ts +6 -0
  39. package/dist/builtins/local-network/local-network.addon.js +48 -7
  40. package/dist/builtins/local-network/local-network.addon.mjs +48 -7
  41. package/dist/builtins/loki-logging/index.js +1 -1
  42. package/dist/builtins/loki-logging/index.mjs +1 -1
  43. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  44. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  45. package/dist/builtins/platform-probe/index.js +1 -1
  46. package/dist/builtins/platform-probe/index.mjs +1 -1
  47. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  48. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  49. package/dist/builtins/snapshot/index.js +1 -1
  50. package/dist/builtins/snapshot/index.mjs +1 -1
  51. package/dist/builtins/sqlite-storage/database-snapshot-provider.d.ts +18 -0
  52. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  53. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  54. package/dist/builtins/sqlite-storage/index.js +1 -1
  55. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  56. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +49 -7
  57. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +49 -7
  58. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  59. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  60. package/dist/builtins/system-backup/system-backup.service.d.ts +48 -15
  61. package/dist/builtins/system-config/system-config.addon.js +2 -2
  62. package/dist/builtins/system-config/system-config.addon.mjs +2 -2
  63. package/dist/builtins/winston-logging/index.js +1 -1
  64. package/dist/builtins/winston-logging/index.mjs +1 -1
  65. package/dist/{composition-sources-BWNs-ZeH.js → composition-sources-60tuHQ9N.js} +1 -1
  66. package/dist/{composition-sources-tRG7nKWK.mjs → composition-sources-gxVpghXh.mjs} +1 -1
  67. package/dist/{constants-DHhhVjkt.mjs → constants-B6YTf0v2.mjs} +1 -1
  68. package/dist/{constants-BBWZlnpQ.js → constants-CO5zpJQY.js} +1 -1
  69. package/dist/{dist-B-NmfvVC.mjs → dist-CGz9u2HD.mjs} +869 -800
  70. package/dist/{dist-CiUKtVkX.js → dist-K3WFop9Q.js} +869 -800
  71. package/dist/file-host-worker.js +1 -1
  72. package/dist/file-host-worker.mjs +1 -1
  73. package/dist/index.js +272 -198
  74. package/dist/index.mjs +272 -198
  75. package/dist/{jwt-signing-key-nwRpRgf_.js → jwt-signing-key-BdDy9ZJN.js} +1 -1
  76. package/dist/{jwt-signing-key-CUpweD22.mjs → jwt-signing-key-C_w5ielw.mjs} +1 -1
  77. package/dist/kernel/moleculer/process-service.d.ts +1 -110
  78. package/dist/kernel/moleculer/runner-heap-flags.d.ts +228 -0
  79. package/dist/{restore-archive-DA-aJtg7.js → restore-archive-BJp4Llea.js} +156 -65
  80. package/dist/{restore-archive-CFmPVlWQ.mjs → restore-archive-CZyUM8Qf.mjs} +151 -66
  81. package/dist/{retired-settings-keys-D62kN_hT.js → retired-settings-keys-Dr2AyhsD.js} +1 -1
  82. package/dist/{retired-settings-keys-DCGEI4Vi.mjs → retired-settings-keys-Dut6QWh6.mjs} +1 -1
  83. package/dist/{wal-maintenance-BlqNqaQ3.mjs → wal-maintenance-BZhzoxqA.mjs} +0 -0
  84. package/dist/{wal-maintenance-BzdCQGjH.js → wal-maintenance-Bzm3O2Nj.js} +0 -0
  85. package/package.json +1 -1
@@ -10880,6 +10880,568 @@ var authProviderCapability = {
10880
10880
  mount: { kind: "skip" }
10881
10881
  };
10882
10882
  /**
10883
+ * Query filter for settings-store collections.
10884
+ */
10885
+ var QueryFilterSchema = zod.z.object({
10886
+ where: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
10887
+ whereIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
10888
+ whereBetween: zod.z.record(zod.z.string(), zod.z.tuple([zod.z.unknown(), zod.z.unknown()])).optional(),
10889
+ /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
10890
+ whereNot: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
10891
+ /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
10892
+ * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
10893
+ whereNotIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
10894
+ orderBy: zod.z.object({
10895
+ field: zod.z.string(),
10896
+ direction: zod.z.enum(["asc", "desc"])
10897
+ }).optional(),
10898
+ limit: zod.z.number().optional(),
10899
+ offset: zod.z.number().optional()
10900
+ });
10901
+ /**
10902
+ * The predicate half of a filter, for BULK MUTATIONS.
10903
+ *
10904
+ * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
10905
+ * meaning for a statement that rewrites a set, and accepting them would invite
10906
+ * a caller to believe `limit` bounds the damage. Every field is optional here
10907
+ * only so the shape stays composable — the implementation REJECTS a filter
10908
+ * that compiles to no predicate, because that is the whole collection.
10909
+ */
10910
+ var MutationFilterSchema = zod.z.object({
10911
+ where: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
10912
+ whereIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
10913
+ whereBetween: zod.z.record(zod.z.string(), zod.z.tuple([zod.z.unknown(), zod.z.unknown()])).optional(),
10914
+ whereNot: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
10915
+ whereNotIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional()
10916
+ });
10917
+ /**
10918
+ * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
10919
+ *
10920
+ * `as` names the slot in the result, so the SAME column may be asked twice with
10921
+ * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
10922
+ * a `Record<column, op>` shape could not express.
10923
+ */
10924
+ var AggregateFieldSchema = zod.z.object({
10925
+ /** Result key. */
10926
+ as: zod.z.string().min(1),
10927
+ /** Column to aggregate. Must be a real column of a declared collection. */
10928
+ field: zod.z.string().min(1),
10929
+ op: zod.z.enum([
10930
+ "sum",
10931
+ "min",
10932
+ "max"
10933
+ ])
10934
+ });
10935
+ /**
10936
+ * `COUNT(*)` plus one number per requested field.
10937
+ *
10938
+ * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
10939
+ * that really is 0 are different facts, and an accounting caller that renders
10940
+ * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
10941
+ */
10942
+ var AggregateResultSchema = zod.z.object({
10943
+ count: zod.z.number().int(),
10944
+ values: zod.z.record(zod.z.string(), zod.z.number().nullable())
10945
+ });
10946
+ /** A single stored record: `{ id, data }`. */
10947
+ var SettingsRecordSchema = zod.z.object({
10948
+ id: zod.z.string(),
10949
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
10950
+ });
10951
+ /**
10952
+ * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
10953
+ * optional.
10954
+ *
10955
+ * A separate schema rather than loosening the shared one: every other method
10956
+ * on this cap addresses a row BY its id, and making that field optional
10957
+ * everywhere would turn a forgotten key into a silently generated one on
10958
+ * `update` and `delete` as well.
10959
+ */
10960
+ var BulkRecordSchema = zod.z.object({
10961
+ id: zod.z.string().optional(),
10962
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
10963
+ });
10964
+ /**
10965
+ * The database FILES one `sqlite-settings` engine process owns (D851).
10966
+ * Closed on purpose: a file is a thing the engine opens, backs up and gives a
10967
+ * serving thread, so a new one is a code change, never a string a caller
10968
+ * invents. `settings` is `camstack.db` and the default for every collection
10969
+ * that names no file.
10970
+ */
10971
+ var DataStoreDatabaseSchema = zod.z.enum([
10972
+ "settings",
10973
+ "events",
10974
+ "history"
10975
+ ]);
10976
+ /** Every database file, in a stable order (settings first). */
10977
+ var DATA_STORE_DATABASES = DataStoreDatabaseSchema.options;
10978
+ /**
10979
+ * One write inside a {@link settingsStoreCapability.methods.transact} call.
10980
+ * The three that a "replace this set, bump that revision" write needs, plus
10981
+ * the batched upsert a rollup writer needs.
10982
+ */
10983
+ var TransactOpSchema = zod.z.discriminatedUnion("op", [
10984
+ zod.z.object({
10985
+ op: zod.z.literal("deleteWhere"),
10986
+ collection: zod.z.string(),
10987
+ filter: MutationFilterSchema
10988
+ }),
10989
+ zod.z.object({
10990
+ op: zod.z.literal("insertMany"),
10991
+ collection: zod.z.string(),
10992
+ records: zod.z.array(BulkRecordSchema).readonly()
10993
+ }),
10994
+ zod.z.object({
10995
+ op: zod.z.literal("update"),
10996
+ collection: zod.z.string(),
10997
+ id: zod.z.string(),
10998
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
10999
+ }),
11000
+ zod.z.object({
11001
+ op: zod.z.literal("upsertMany"),
11002
+ collection: zod.z.string(),
11003
+ conflict: zod.z.array(zod.z.string()).readonly(),
11004
+ records: zod.z.array(BulkRecordSchema).readonly()
11005
+ })
11006
+ ]);
11007
+ /**
11008
+ * The precondition of a transaction: the row `key` of `collection` must hold
11009
+ * `equals` in `column`, read INSIDE the transaction.
11010
+ */
11011
+ var TransactGuardSchema = zod.z.object({
11012
+ collection: zod.z.string(),
11013
+ key: zod.z.string(),
11014
+ column: zod.z.string(),
11015
+ equals: zod.z.unknown()
11016
+ });
11017
+ /**
11018
+ * Column declaration for a structured (SQL-backed) collection.
11019
+ *
11020
+ * Logical types — the backend translates each to the matching SQLite
11021
+ * storage class and handles per-type marshaling:
11022
+ * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
11023
+ * - `JSON` — TEXT under the hood; serialised on write, parsed on read
11024
+ * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
11025
+ */
11026
+ var CollectionColumnSchema = zod.z.object({
11027
+ name: zod.z.string(),
11028
+ type: zod.z.enum([
11029
+ "TEXT",
11030
+ "INTEGER",
11031
+ "REAL",
11032
+ "JSON",
11033
+ "BOOLEAN"
11034
+ ]),
11035
+ primaryKey: zod.z.boolean().optional(),
11036
+ notNull: zod.z.boolean().optional(),
11037
+ unique: zod.z.boolean().optional(),
11038
+ /**
11039
+ * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
11040
+ * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
11041
+ * collection surface simply cannot express their existing tables — which is
11042
+ * why they were still on the uncapped `table*` API.
11043
+ */
11044
+ defaultValue: zod.z.union([
11045
+ zod.z.string(),
11046
+ zod.z.number(),
11047
+ zod.z.boolean()
11048
+ ]).optional()
11049
+ });
11050
+ var CollectionIndexSchema = zod.z.object({
11051
+ name: zod.z.string(),
11052
+ columns: zod.z.array(zod.z.string()).readonly(),
11053
+ unique: zod.z.boolean().optional()
11054
+ });
11055
+ /**
11056
+ * The input of `declareCollection` — ONE schema for the door and for the
11057
+ * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
11058
+ * the payload untouched).
11059
+ *
11060
+ * - `database` — the file the collection lives in (D851). Absent means
11061
+ * `settings`. A declaration naming a file other than the one the table is
11062
+ * already in is REFUSED by the engine, never moved.
11063
+ * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
11064
+ * as `primaryKey: true` on that column. A composite-keyed collection has no
11065
+ * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
11066
+ * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
11067
+ * address one row by id are refused.
11068
+ * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
11069
+ */
11070
+ var DeclareCollectionInputSchema = zod.z.object({
11071
+ namespace: zod.z.string().optional(),
11072
+ collection: zod.z.string(),
11073
+ columns: zod.z.array(CollectionColumnSchema).readonly(),
11074
+ indexes: zod.z.array(CollectionIndexSchema).readonly().optional(),
11075
+ database: DataStoreDatabaseSchema.optional(),
11076
+ primaryKey: zod.z.array(zod.z.string()).readonly().optional(),
11077
+ withoutRowid: zod.z.boolean().optional()
11078
+ });
11079
+ /**
11080
+ * settings-store — singleton capability for addon-scoped persistence.
11081
+ *
11082
+ * Every method operates within a `collection`. An optional `namespace`
11083
+ * field provides access to additional data spaces beyond the default
11084
+ * addon settings — useful for business data (events, tracks, faces, etc.).
11085
+ *
11086
+ * **Scoping is the CALLER's, and it is opt-in.** The table is
11087
+ * `namespace ? `${namespace}:${collection}` : collection` — nothing
11088
+ * consults the identity of the caller. An earlier version of this comment
11089
+ * claimed the implementation prefixes every collection with the calling
11090
+ * addon's ID and that "addons never see each other's data"; that was never
11091
+ * true, and this same file contradicted it under `declareCollection`. What
11092
+ * exists is `addon-context-factory`, which passes `namespace: addonId` ON
11093
+ * THE ADDON'S BEHALF for the `addon-settings` / `addon-devices` paths only.
11094
+ * Business collections use bare names, and any caller may name any
11095
+ * namespace, or none. Making the door enforce it is tracked separately —
11096
+ * it renames tables, so it needs a migration.
11097
+ *
11098
+ * **So every method is `auth: 'admin'` (D634).** Because nothing scopes a
11099
+ * call to its caller, a `protected` method here was a raw, unredacted read
11100
+ * (and write) of every addon's collections for any role holding
11101
+ * `category:system` — which is every role: a `viewer` could read every
11102
+ * notifier token in plaintext from `notifiers:notification-targets`. No
11103
+ * browser or app client calls this cap. Addons are not affected: an
11104
+ * addon's `ctx.api` cap call is resolved to the provider directly and never
11105
+ * enters the tRPC router (so the tier gates sessions and tokens, never
11106
+ * addons), and the hub itself calls it through the capability registry.
11107
+ *
11108
+ * - `{ collection: 'addon-settings' }` → table `addon-settings`
11109
+ * - `{ namespace: 'my-addon', collection: 'addon-settings' }` →
11110
+ * table `my-addon:addon-settings`
11111
+ *
11112
+ * Served by the **storage-orchestrator** builtin, which dispatches to the
11113
+ * `data-store-provider` engine registered for the collection
11114
+ * (`sqlite-settings`, a SQLite WAL backend, today). One addon owns the
11115
+ * data door; engines sit behind it
11116
+ * ([D44](../../../../docs/decisions/adr-0044.md)).
11117
+ * Addons access it via `ctx.api.settingsStore.*`.
11118
+ */
11119
+ var settingsStoreCapability = {
11120
+ name: "settings-store",
11121
+ scope: "system",
11122
+ mode: "singleton",
11123
+ methods: {
11124
+ /** Get a single value by key from a collection. */
11125
+ get: method(zod.z.object({
11126
+ namespace: zod.z.string().optional(),
11127
+ collection: zod.z.string(),
11128
+ key: zod.z.string()
11129
+ }), zod.z.unknown(), { auth: "admin" }),
11130
+ /** Set a value by key in a collection (upsert). */
11131
+ set: method(zod.z.object({
11132
+ namespace: zod.z.string().optional(),
11133
+ collection: zod.z.string(),
11134
+ key: zod.z.string(),
11135
+ value: zod.z.unknown()
11136
+ }), zod.z.void(), {
11137
+ kind: "mutation",
11138
+ auth: "admin"
11139
+ }),
11140
+ /**
11141
+ * Rows matching a filter.
11142
+ *
11143
+ * `columns` is a SQL-level projection: the engine SELECTs only those
11144
+ * columns (plus the primary key) instead of the whole row. It is the only
11145
+ * way to stop paying disk for a column that is discarded — a heavy JSON
11146
+ * column dominates the row, and dropping it in JS after the read pays every
11147
+ * byte first. Measured on the live `pipeline-analytics:tracks` table: a
11148
+ * 501-row page is 4.6 MB whole and 0.11 MB projected, same index, same
11149
+ * plan.
11150
+ *
11151
+ * The engine REFUSES a column it cannot serve rather than omitting it —
11152
+ * see `SettingsQueryInput.columns` for why a projection may not be
11153
+ * forgiving the way a predicate is.
11154
+ */
11155
+ query: method(zod.z.object({
11156
+ namespace: zod.z.string().optional(),
11157
+ collection: zod.z.string(),
11158
+ filter: QueryFilterSchema.optional(),
11159
+ columns: zod.z.array(zod.z.string()).readonly().optional()
11160
+ }), zod.z.array(SettingsRecordSchema).readonly(), { auth: "admin" }),
11161
+ /**
11162
+ * Make a SLOT hold this row — insert it, or update whatever already
11163
+ * occupies the slot — and answer with the id the row HAS.
11164
+ *
11165
+ * ## Why the store needed this
11166
+ *
11167
+ * {@link set} is already an upsert, but its slot is the PRIMARY KEY: the
11168
+ * caller has to know the id to address the row. That is exactly what a
11169
+ * collection keyed by an `INTEGER` rowid alias cannot do — SQLite assigns
11170
+ * the id, so a writer whose uniqueness rule is "one row per
11171
+ * `(ownerKind, ownerId, kind, slot)`" has no id to name and no way to
11172
+ * express the rule. {@link insert} can express it (a UNIQUE index
11173
+ * arbitrates) but only by THROWING at the loser of a race, and the loser
11174
+ * here is a legitimate re-capture, not an error.
11175
+ *
11176
+ * Getting there without this method means read-then-write: query the slot,
11177
+ * then `insert` or `update`. Two statements instead of one, a race between
11178
+ * them, and a recovery path that has to recognise a UNIQUE violation by its
11179
+ * MESSAGE — across a process boundary, because the store lives behind an
11180
+ * RPC door. A string match would be the whole gate, and a store that
11181
+ * rephrased its error would lose the row silently. One statement has no
11182
+ * such gate (D482).
11183
+ *
11184
+ * ## The contract
11185
+ *
11186
+ * `conflict` names the DECLARED columns that identify the slot. They must
11187
+ * be covered by a UNIQUE index on the collection — SQLite resolves an
11188
+ * `ON CONFLICT` target against its indexes and REFUSES the statement when
11189
+ * none matches, so a wrong list is a loud error on the first write, never a
11190
+ * silently duplicated row. An empty list is refused rather than passed on:
11191
+ * `ON CONFLICT()` is not valid SQL, and the intent it would most likely
11192
+ * encode ("no slot, just insert") is {@link insert}.
11193
+ *
11194
+ * Row shape is {@link insert}'s: `record.id` is used when the collection
11195
+ * needs a supplied key and IGNORED when the primary key is an `INTEGER`
11196
+ * rowid alias — exactly as `insert` and `insertMany` already treat it.
11197
+ *
11198
+ * Returns the id either way: the one SQLite assigned on an insert, or the
11199
+ * one the row it updated already had. A caller cannot tell which happened,
11200
+ * and deliberately — every caller wants the slot's id, and one that needed
11201
+ * to distinguish creation from replacement would have to read first anyway.
11202
+ */
11203
+ upsert: method(zod.z.object({
11204
+ namespace: zod.z.string().optional(),
11205
+ collection: zod.z.string(),
11206
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
11207
+ * index on the collection. Never empty. */
11208
+ conflict: zod.z.array(zod.z.string()).readonly(),
11209
+ record: BulkRecordSchema
11210
+ }), zod.z.object({
11211
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
11212
+ * row's own id on an update. */
11213
+ id: zod.z.union([zod.z.string(), zod.z.number()]) }), {
11214
+ kind: "mutation",
11215
+ auth: "admin"
11216
+ }),
11217
+ /**
11218
+ * {@link upsert} for a BATCH, in ONE transaction on ONE prepared statement.
11219
+ *
11220
+ * The write-side twin of {@link insertMany} for rows that may already
11221
+ * exist: a rollup writer recomputes a bucket and must overwrite it, and
11222
+ * N round trips with N COMMITs is the cost {@link insertMany} was added to
11223
+ * avoid. `conflict` has the meaning it has on {@link upsert} (declared
11224
+ * columns covered by a UNIQUE index or the primary key, never empty).
11225
+ *
11226
+ * Every record of a batch must carry the SAME set of declared columns and
11227
+ * every `conflict` column: one batch is one statement shape, and a record
11228
+ * that silently bound NULL over a column it did not mention would erase
11229
+ * it. A heterogeneous batch is refused whole. **All or nothing.**
11230
+ */
11231
+ upsertMany: method(zod.z.object({
11232
+ namespace: zod.z.string().optional(),
11233
+ collection: zod.z.string(),
11234
+ conflict: zod.z.array(zod.z.string()).readonly(),
11235
+ records: zod.z.array(BulkRecordSchema).readonly()
11236
+ }), zod.z.object({ upserted: zod.z.number().int() }), {
11237
+ kind: "mutation",
11238
+ auth: "admin"
11239
+ }),
11240
+ /** Insert a new record. */
11241
+ insert: method(zod.z.object({
11242
+ namespace: zod.z.string().optional(),
11243
+ collection: zod.z.string(),
11244
+ record: zod.z.object({
11245
+ id: zod.z.string().optional(),
11246
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
11247
+ })
11248
+ }), zod.z.object({
11249
+ /**
11250
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
11251
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
11252
+ * primary key — which is the only place an auto key is knowable.
11253
+ */
11254
+ id: zod.z.union([zod.z.string(), zod.z.number()]) }), {
11255
+ kind: "mutation",
11256
+ auth: "admin"
11257
+ }),
11258
+ /**
11259
+ * Insert MANY records in ONE transaction, returning how many landed.
11260
+ *
11261
+ * The write-side twin of {@link deleteWhere}, and it exists for the same
11262
+ * reason: without it, appending a batch is N round trips and N COMMITs on
11263
+ * the single shared connection that also serves every cluster-wide
11264
+ * configuration read. The durable load series writes one process row per
11265
+ * process per sample — 76 rows every 10 s on the live fleet — and the
11266
+ * operator's rule for it is *one transaction per sample, never one per
11267
+ * row*. `insert` cannot express that; nothing else could.
11268
+ *
11269
+ * **All or nothing.** A batch that fails on its fifth row leaves none of
11270
+ * the five behind. A half-written sample is worse than a missing one: the
11271
+ * missing one reads as "nobody reported", which is true, while the half
11272
+ * one reads as "these were the only processes running", which is not.
11273
+ *
11274
+ * `id` is OPTIONAL per record, and that is the difference from
11275
+ * {@link insert}. A collection whose primary key is an `INTEGER` rowid
11276
+ * alias has no id to supply — SQLite assigns it, for free, and inventing a
11277
+ * `randomUUID()` for such a column would write a 36-character string into
11278
+ * an integer key. Omitted on a TEXT key, a uuid is generated exactly as
11279
+ * `insert` does.
11280
+ */
11281
+ insertMany: method(zod.z.object({
11282
+ namespace: zod.z.string().optional(),
11283
+ collection: zod.z.string(),
11284
+ records: zod.z.array(BulkRecordSchema).readonly()
11285
+ }), zod.z.object({ inserted: zod.z.number().int() }), {
11286
+ kind: "mutation",
11287
+ auth: "admin"
11288
+ }),
11289
+ /**
11290
+ * Several writes, ONE transaction, behind an optional precondition.
11291
+ *
11292
+ * The multi-collection twin of {@link insertMany}: a write that has to move
11293
+ * a set of rows in one collection AND a revision in another cannot be
11294
+ * expressed as separate calls without a window in which a reader (or a
11295
+ * crash) sees half of it. The retrain annotation save replaces a frame's
11296
+ * rows, its dismissed set and its revision this way.
11297
+ *
11298
+ * `guard`, when given, is read inside the transaction: if the row is
11299
+ * missing or its column differs from `equals`, NOTHING is written and the
11300
+ * answer is `{ applied: false, current }` with what was found — the
11301
+ * compare-and-set a revisioned write needs, with no second reader able to
11302
+ * slip between the check and the writes.
11303
+ *
11304
+ * **All or nothing**, like {@link insertMany}: a throw in any op rolls the
11305
+ * whole call back. Ops run in the order given (delete before insert to
11306
+ * replace a set). `namespace` applies to every collection named.
11307
+ */
11308
+ transact: method(zod.z.object({
11309
+ namespace: zod.z.string().optional(),
11310
+ guard: TransactGuardSchema.optional(),
11311
+ ops: zod.z.array(TransactOpSchema).readonly()
11312
+ }), zod.z.object({
11313
+ applied: zod.z.boolean(),
11314
+ current: zod.z.unknown().optional()
11315
+ }), {
11316
+ kind: "mutation",
11317
+ auth: "admin"
11318
+ }),
11319
+ /** Update an existing record by ID. */
11320
+ update: method(zod.z.object({
11321
+ namespace: zod.z.string().optional(),
11322
+ collection: zod.z.string(),
11323
+ id: zod.z.string(),
11324
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
11325
+ }), zod.z.void(), {
11326
+ kind: "mutation",
11327
+ auth: "admin"
11328
+ }),
11329
+ /** Delete a record by key/ID. */
11330
+ delete: method(zod.z.object({
11331
+ namespace: zod.z.string().optional(),
11332
+ collection: zod.z.string(),
11333
+ key: zod.z.string()
11334
+ }), zod.z.void(), {
11335
+ kind: "mutation",
11336
+ auth: "admin"
11337
+ }),
11338
+ /**
11339
+ * Delete every record matching `filter`, in ONE statement, returning how
11340
+ * many rows went. This exists because its absence made every retention
11341
+ * path in the system an N+1 drain loop: `delete` takes a key, so a sweep
11342
+ * had to SELECT a page of full rows — every column, including the fat
11343
+ * ones — purely to learn their ids, then issue one call per row.
11344
+ *
11345
+ * **The filter is required and must resolve.** A predicate naming
11346
+ * something the collection cannot express is an ERROR here, not a
11347
+ * widening as it is on `query`, and a filter with no predicates is an
11348
+ * error rather than "every row". Deleting a whole collection is a
11349
+ * legitimate intent, but it must be asked for by name — not reached by
11350
+ * an empty object.
11351
+ */
11352
+ deleteWhere: method(zod.z.object({
11353
+ namespace: zod.z.string().optional(),
11354
+ collection: zod.z.string(),
11355
+ filter: MutationFilterSchema
11356
+ }), zod.z.object({ deleted: zod.z.number().int() }), {
11357
+ kind: "mutation",
11358
+ auth: "admin"
11359
+ }),
11360
+ /**
11361
+ * Apply `data` to every record matching `filter`, in one statement,
11362
+ * returning how many rows changed. Same filter contract as
11363
+ * {@link deleteWhere} — an unresolvable predicate is an error.
11364
+ */
11365
+ updateWhere: method(zod.z.object({
11366
+ namespace: zod.z.string().optional(),
11367
+ collection: zod.z.string(),
11368
+ filter: MutationFilterSchema,
11369
+ data: zod.z.record(zod.z.string(), zod.z.unknown())
11370
+ }), zod.z.object({ updated: zod.z.number().int() }), {
11371
+ kind: "mutation",
11372
+ auth: "admin"
11373
+ }),
11374
+ /** Count entries in a collection, optionally filtered. */
11375
+ count: method(zod.z.object({
11376
+ namespace: zod.z.string().optional(),
11377
+ collection: zod.z.string(),
11378
+ filter: QueryFilterSchema.optional()
11379
+ }), zod.z.number(), { auth: "admin" }),
11380
+ /**
11381
+ * `COUNT(*)` and one `SUM` / `MIN` / `MAX` per requested field, in ONE
11382
+ * statement, over the rows `filter` selects.
11383
+ *
11384
+ * Exists because "how much is there" was being answered by materialising
11385
+ * "what is there". The recorder's storage-pressure sweep asked its in-RAM
11386
+ * footage index for bytes/count/oldest/newest across a set of storage
11387
+ * locations twice a minute, and the only way to answer that from a map is
11388
+ * to visit every row — 7.1 M of them on the live hub, ~15 M row visits a
11389
+ * minute on the main thread, which is also why the whole archive had to
11390
+ * stay resident to be visited. The question is a sum; nothing needs to be
11391
+ * materialised to answer it.
11392
+ *
11393
+ * **The engine REFUSES a field it cannot serve**, exactly as
11394
+ * `query.columns` does and unlike a PREDICATE, which is skipped when
11395
+ * unresolvable. A dropped predicate over-matches and the caller sees extra
11396
+ * rows; a dropped aggregate returns a NUMBER that is wrong and looks
11397
+ * exactly like a real one. That asymmetry is what this repo has already
11398
+ * paid for once in `count`.
11399
+ */
11400
+ aggregate: method(zod.z.object({
11401
+ namespace: zod.z.string().optional(),
11402
+ collection: zod.z.string(),
11403
+ fields: zod.z.array(AggregateFieldSchema).readonly(),
11404
+ filter: QueryFilterSchema.optional()
11405
+ }), AggregateResultSchema, { auth: "admin" }),
11406
+ /** Grouped counts per ((field-origin)/bucketSize) bucket, filtered. */
11407
+ histogram: method(zod.z.object({
11408
+ namespace: zod.z.string().optional(),
11409
+ collection: zod.z.string(),
11410
+ field: zod.z.string(),
11411
+ bucketSize: zod.z.number().int().positive(),
11412
+ origin: zod.z.number().int(),
11413
+ filter: QueryFilterSchema.optional()
11414
+ }), zod.z.array(zod.z.object({
11415
+ bucket: zod.z.number().int(),
11416
+ count: zod.z.number().int()
11417
+ })).readonly(), { auth: "admin" }),
11418
+ /** Check if a collection is empty. */
11419
+ isEmpty: method(zod.z.object({
11420
+ namespace: zod.z.string().optional(),
11421
+ collection: zod.z.string()
11422
+ }), zod.z.boolean(), { auth: "admin" }),
11423
+ /**
11424
+ * Declare a typed (SQL-backed) collection with explicit columns +
11425
+ * indexes. Idempotent: re-declaring an existing collection with the
11426
+ * same shape is a no-op; shape changes (new columns, new indexes)
11427
+ * are applied additively. Subsequent `insert` / `update` / `delete`
11428
+ * / `query` calls on this collection use typed columns instead of
11429
+ * JSON-blob storage — `record.data` fields are spread across
11430
+ * columns, `query.filter.where` matches real columns (no
11431
+ * `json_extract` overhead), `orderBy` uses column indexes.
11432
+ *
11433
+ * Addons call this in `onInitialize` before their first read/write.
11434
+ * Collection names should be namespaced by addon (e.g.
11435
+ * `pipeline-analytics:object-events`) to avoid cross-addon
11436
+ * clashes — no automatic prefix is added.
11437
+ */
11438
+ declareCollection: method(DeclareCollectionInputSchema, zod.z.void(), {
11439
+ kind: "mutation",
11440
+ auth: "admin"
11441
+ })
11442
+ }
11443
+ };
11444
+ /**
10883
11445
  * The suffix of a verified snapshot, written next to the live database it
10884
11446
  * copies: `<db>.backup-snapshot`. A restore promotes it back over `<db>`
10885
11447
  * (`server/backend/src/sqlite-restore.ts` spells the same string — the
@@ -10906,7 +11468,11 @@ var DATABASE_SNAPSHOT_TIMEOUT_MS = 30 * 6e4;
10906
11468
  * database has no sidecars and is just as owned). Paths are absolute on the
10907
11469
  * owner's host.
10908
11470
  */
10909
- var DatabaseSnapshotTargetsSchema = zod.z.object({ databases: zod.z.array(zod.z.object({ livePath: zod.z.string() })) });
11471
+ var DatabaseSnapshotTargetsSchema = zod.z.object({ databases: zod.z.array(zod.z.object({
11472
+ /** Which owned file this is — the handle `writeSnapshot` takes. */
11473
+ databaseId: DataStoreDatabaseSchema,
11474
+ livePath: zod.z.string()
11475
+ })) });
10910
11476
  /** One b-tree table of the snapshot and the rows it holds. */
10911
11477
  var DatabaseSnapshotTableSchema = zod.z.object({
10912
11478
  name: zod.z.string(),
@@ -10939,6 +11505,10 @@ var DatabaseSnapshotResultSchema = zod.z.object({
10939
11505
  /** When the pinned read transaction began (ms epoch) — the snapshot's instant. */
10940
11506
  takenAt: zod.z.number()
10941
11507
  });
11508
+ /** A snapshot the owner wrote, with the file it belongs to. */
11509
+ var DatabaseSnapshotWrittenSchema = DatabaseSnapshotResultSchema.extend({ databaseId: DataStoreDatabaseSchema });
11510
+ /** Which database `writeSnapshot` copies. Required: an unstated ask is not guessed. */
11511
+ var DatabaseSnapshotRequestSchema = zod.z.object({ databaseId: DataStoreDatabaseSchema });
10942
11512
  /**
10943
11513
  * A snapshot as an ARCHIVE records it (`ArchiveManifest.sqliteSnapshots`):
10944
11514
  * the same facts, with both paths relative to the archive root (the node's
@@ -10948,7 +11518,9 @@ var ArchiveSqliteSnapshotSchema = DatabaseSnapshotResultSchema.extend({
10948
11518
  /** Archive-relative path of the live database the archive does NOT carry. */
10949
11519
  livePath: zod.z.string(),
10950
11520
  /** Archive-relative path of the verified copy it carries in its place. */
10951
- snapshotPath: zod.z.string()
11521
+ snapshotPath: zod.z.string(),
11522
+ /** Absent on an archive made before several files were owned: that was `settings`. */
11523
+ databaseId: DataStoreDatabaseSchema.optional()
10952
11524
  });
10953
11525
  /**
10954
11526
  * database-snapshot — the OWNER of a live SQLite database writes a consistent,
@@ -10973,17 +11545,21 @@ var databaseSnapshotCapability = {
10973
11545
  internal: true,
10974
11546
  methods: {
10975
11547
  /**
10976
- * Which databases this owner snapshots. Answered from memory — no file is
10977
- * touched — so a backup can ask it before deciding anything.
11548
+ * EVERY database file this owner has open, with its id. Answered from
11549
+ * memory — no file is touched — so a backup can ask it before deciding
11550
+ * anything. A file created at runtime is listed from the moment it opens,
11551
+ * so no backup can leave it out (D693).
10978
11552
  */
10979
11553
  listDatabases: method(zod.z.object({}), DatabaseSnapshotTargetsSchema, { auth: "admin" }),
10980
11554
  /**
10981
- * Write `<db>.backup-snapshot` and return what it holds. Throws — and
10982
- * leaves no file under the final name — when the copy fails
10983
- * `integrity_check` or its row counts differ from the pinned source's.
10984
- * A second call while one is running joins it.
11555
+ * Write `<db>.backup-snapshot` for ONE database and return what it holds.
11556
+ * Throws — and leaves no file under the final name — when the copy fails
11557
+ * `integrity_check` or its row counts differ from the pinned source's, or
11558
+ * when the database is not open. A second call for the SAME database while
11559
+ * one is running joins it; different databases run one after the other
11560
+ * (the disk reserve check is per copy).
10985
11561
  */
10986
- writeSnapshot: method(zod.z.object({}), DatabaseSnapshotResultSchema, {
11562
+ writeSnapshot: method(DatabaseSnapshotRequestSchema, DatabaseSnapshotWrittenSchema, {
10987
11563
  kind: "mutation",
10988
11564
  auth: "admin",
10989
11565
  timeoutMs: DATABASE_SNAPSHOT_TIMEOUT_MS
@@ -11106,7 +11682,12 @@ var BackupArchiveEntrySchema = zod.z.object({
11106
11682
  /** Top-level locations included in the archive (db, addons, tls, …). */
11107
11683
  locations: zod.z.array(zod.z.string()).readonly(),
11108
11684
  /** Present when the archive is encrypted; restore then needs that key. */
11109
- encryption: BackupArchiveEncryptionSchema.optional()
11685
+ encryption: BackupArchiveEncryptionSchema.optional(),
11686
+ /**
11687
+ * The databases the run asked to carry (its retention class). Absent on an
11688
+ * archive made before several files were owned: that was `['settings']`.
11689
+ */
11690
+ databases: zod.z.array(DataStoreDatabaseSchema).readonly().optional()
11110
11691
  });
11111
11692
  var BackupEntrySchema = zod.z.object({
11112
11693
  id: zod.z.string(),
@@ -11140,7 +11721,12 @@ var ArchiveManifestSchema = zod.z.object({
11140
11721
  * Live SQLite databases this archive carries as their OWNER's verified
11141
11722
  * snapshot instead of as files (D693). Absent on archives made before.
11142
11723
  */
11143
- sqliteSnapshots: zod.z.array(ArchiveSqliteSnapshotSchema).optional()
11724
+ sqliteSnapshots: zod.z.array(ArchiveSqliteSnapshotSchema).optional(),
11725
+ /**
11726
+ * Owned database files the run did NOT carry (not selected): left out of the
11727
+ * tar and the entry list alike. Absent when every owned file was carried.
11728
+ */
11729
+ omittedDatabases: zod.z.array(DataStoreDatabaseSchema).optional()
11144
11730
  });
11145
11731
  var LocationStatSchema = zod.z.object({
11146
11732
  name: zod.z.string(),
@@ -11262,6 +11848,12 @@ var BackupScheduleSchema = zod.z.object({
11262
11848
  /** Optional subset of source locations to include; omitted = all. */
11263
11849
  dataSources: zod.z.array(zod.z.string()).readonly().optional(),
11264
11850
  /**
11851
+ * Which database files this schedule carries. Absent = `['settings']`
11852
+ * (an unstated ask is the minimum, D382). Events and history run weekly,
11853
+ * settings nightly (D852).
11854
+ */
11855
+ databases: zod.z.array(DataStoreDatabaseSchema).readonly().optional(),
11856
+ /**
11265
11857
  * ms-epoch of the last tick that FIRED this schedule. Stamped before the
11266
11858
  * archive runs (it is the dedupe anchor), so it says "attempted", never
11267
11859
  * "succeeded" — a run refused by every destination stamps it too.
@@ -11313,7 +11905,9 @@ var backupCapability = {
11313
11905
  * retention). Omitted = each destination's own policy
11314
11906
  * retention (manual runs).
11315
11907
  */
11316
- retentionCount: zod.z.number().int().min(1).max(1e3).optional()
11908
+ retentionCount: zod.z.number().int().min(1).max(1e3).optional(),
11909
+ /** Database files to carry; omitted = `['settings']`. */
11910
+ databases: zod.z.array(DataStoreDatabaseSchema).optional()
11317
11911
  }).optional(), BackupTriggerResultSchema, {
11318
11912
  kind: "mutation",
11319
11913
  auth: "admin"
@@ -11471,6 +12065,16 @@ var backupCapability = {
11471
12065
  */
11472
12066
  listSchedules: method(zod.z.void(), zod.z.array(BackupScheduleSchema).readonly(), { auth: "admin" }),
11473
12067
  /**
12068
+ * The database files that exist on this hub right now (the owner's open
12069
+ * files), so the selection UI can offer all of them and mark a file that
12070
+ * is not created yet. `answered: false` = the owner did not answer, which
12071
+ * is not the same as "none exist".
12072
+ */
12073
+ listAvailableDatabases: method(zod.z.void(), zod.z.object({
12074
+ answered: zod.z.boolean(),
12075
+ databases: zod.z.array(DataStoreDatabaseSchema).readonly()
12076
+ }), { auth: "admin" }),
12077
+ /**
11474
12078
  * Create or replace a schedule. An empty / absent `id` mints a new
11475
12079
  * one; a present `id` replaces in place. `cron` is validated
11476
12080
  * server-side; `locationIds` must reference existing `backups`
@@ -11483,7 +12087,8 @@ var backupCapability = {
11483
12087
  enabled: zod.z.boolean(),
11484
12088
  locationIds: zod.z.array(zod.z.string()).readonly(),
11485
12089
  retentionCount: zod.z.number().int().min(1).max(1e3),
11486
- dataSources: zod.z.array(zod.z.string()).readonly().optional()
12090
+ dataSources: zod.z.array(zod.z.string()).readonly().optional(),
12091
+ databases: zod.z.array(DataStoreDatabaseSchema).readonly().optional()
11487
12092
  }), BackupScheduleSchema, {
11488
12093
  kind: "mutation",
11489
12094
  auth: "admin"
@@ -14780,835 +15385,273 @@ var CoreBlockCompileInputSchema = zod.z.object({
14780
15385
  code: zod.z.string().optional(),
14781
15386
  files: CoreBlockFilesSchema.optional(),
14782
15387
  entry: zod.z.string().min(1).max(260).optional()
14783
- }).superRefine((val, ctx) => {
14784
- if (val.files !== void 0) {
14785
- const entry = val.entry ?? "main.ts";
14786
- if (val.files[entry] === void 0) ctx.addIssue({
14787
- code: "custom",
14788
- message: `files must include the entry "${entry}"`,
14789
- path: ["files"]
14790
- });
14791
- return;
14792
- }
14793
- if (val.code === void 0) ctx.addIssue({
14794
- code: "custom",
14795
- message: "code or files is required",
14796
- path: ["code"]
14797
- });
14798
- });
14799
- var coreBlocksCapability = {
14800
- name: "core-blocks",
14801
- scope: "system",
14802
- mode: "singleton",
14803
- methods: {
14804
- list: method(zod.z.object({}), zod.z.object({ blocks: zod.z.array(CoreBlockSchema) }), { auth: "admin" }),
14805
- get: method(zod.z.object({ blockId: zod.z.string() }), zod.z.object({ block: CoreBlockSchema.nullable() }), { auth: "admin" }),
14806
- /**
14807
- * Create a block. The code is COMPILED first: storing source that does not
14808
- * compile turns an editor error into a fork failure the operator meets
14809
- * minutes later in a log.
14810
- */
14811
- create: method(zod.z.object({ block: CoreBlockInputSchema }), zod.z.object({ block: CoreBlockSchema }), {
14812
- kind: "mutation",
14813
- auth: "admin",
14814
- caller: "required"
14815
- }),
14816
- update: method(zod.z.object({
14817
- blockId: zod.z.string(),
14818
- block: CoreBlockInputFieldsSchema.partial()
14819
- }), zod.z.object({ block: CoreBlockSchema }), {
14820
- kind: "mutation",
14821
- auth: "admin",
14822
- caller: "required"
14823
- }),
14824
- delete: method(zod.z.object({ blockId: zod.z.string() }), zod.z.object({ success: zod.z.literal(true) }), {
14825
- kind: "mutation",
14826
- auth: "admin"
14827
- }),
14828
- setEnabled: method(zod.z.object({
14829
- blockId: zod.z.string(),
14830
- enabled: zod.z.boolean()
14831
- }), zod.z.object({ block: CoreBlockSchema }), {
14832
- kind: "mutation",
14833
- auth: "admin"
14834
- }),
14835
- /**
14836
- * Stop this block's runner and let the supervisor bring it back.
14837
- *
14838
- * **It stores nothing.** There is no "restart requested" flag and no second
14839
- * lifecycle field: the supervisor already stops and respawns a runner when
14840
- * the code hash changes, and this exposes that same path deliberately
14841
- * (D62 — a switch writes the authority that already owned the function).
14842
- * The next reconcile pass remains the authority, so a restart that lost a
14843
- * race is corrected within one pass rather than leaving a stale flag behind.
14844
- *
14845
- * Refused for a DISABLED block: nothing would come back, and a button that
14846
- * silently does nothing is worse than one that says why.
14847
- */
14848
- restart: method(zod.z.object({ blockId: zod.z.string() }), zod.z.object({ block: CoreBlockSchema }), {
14849
- kind: "mutation",
14850
- auth: "admin"
14851
- }),
14852
- /**
14853
- * Type-check without saving — what the editor calls as the author types, so
14854
- * the compiler's verdict is the same one the server will reach.
14855
- * Accepts `{ code }` or `{ files, entry }` (multi-file project).
14856
- */
14857
- compile: method(CoreBlockCompileInputSchema, CoreBlockCompileResultSchema, {
14858
- kind: "mutation",
14859
- auth: "admin"
14860
- }),
14861
- /**
14862
- * Hydrated settings form for one block, or null until a `settingsSchema`
14863
- * is known (extracted from the compiled entry's named export, or set on
14864
- * the block). Values live in the `core-blocks:settings` collection.
14865
- */
14866
- getBlockSettings: method(zod.z.object({ blockId: zod.z.string() }), SettingsSchemaWithValuesSchema.nullable(), { auth: "admin" }),
14867
- /**
14868
- * Patch persisted preference values for one block. The schema is not
14869
- * rewritten here — only values. Scripts read the same blob via
14870
- * `ctx.settings.readAddonStore()` (mirrored into the block addon's
14871
- * `addon-settings` namespace on every patch).
14872
- */
14873
- updateBlockSettings: method(zod.z.object({
14874
- blockId: zod.z.string(),
14875
- patch: SettingsPatchSchema
14876
- }), SettingsUpdateResultSchema, {
14877
- kind: "mutation",
14878
- auth: "admin"
14879
- }),
14880
- /**
14881
- * The declaration files the editor type-checks against.
14882
- *
14883
- * Served rather than bundled: the graph is 3.4 MB across 344 files, and
14884
- * bundling it would roughly double the admin remote. Served rather than
14885
- * hand-stubbed: a block runs with the same `ctx` an addon gets, so a stub
14886
- * would drift and the editor would confidently autocomplete methods that
14887
- * do not exist.
14888
- */
14889
- getTypeDefs: method(zod.z.object({}), zod.z.object({ libs: zod.z.array(zod.z.object({
14890
- filePath: zod.z.string(),
14891
- content: zod.z.string()
14892
- })) }), { auth: "admin" }),
14893
- /**
14894
- * Create a composition block (D659). VALIDATED first against the live
14895
- * sources and every stored composition; a refusal names every problem by
14896
- * path and reason (D391) and stores nothing.
14897
- */
14898
- createComposition: method(zod.z.object({ block: CompositionBlockInputSchema }), zod.z.object({ block: CoreBlockSchema }), {
14899
- kind: "mutation",
14900
- auth: "admin",
14901
- caller: "required"
14902
- }),
14903
- /**
14904
- * Patch a composition block. A changed `composition` is validated exactly
14905
- * as on create. `expectedUpdatedAt` is the `updatedAt` the caller READ: a
14906
- * block changed since is refused by name, never silently overwritten.
14907
- */
14908
- updateComposition: method(zod.z.object({
14909
- blockId: zod.z.string(),
14910
- expectedUpdatedAt: zod.z.number(),
14911
- block: CompositionBlockInputSchema.partial()
14912
- }), zod.z.object({ block: CoreBlockSchema }), {
14913
- kind: "mutation",
14914
- auth: "admin",
14915
- caller: "required"
14916
- }),
14917
- /** One composition and the composer's state for it, or `not-loaded` (D315). */
14918
- getComposition: method(zod.z.object({ blockId: zod.z.string() }), CompositionGetReadSchema, { auth: "admin" }),
14919
- listCompositions: method(zod.z.object({}), CompositionListReadSchema, { auth: "admin" }),
14920
- /**
14921
- * The composition that customizes an existing device (D663), found by its
14922
- * TARGET — never by the block's derived name. One per device at most.
14923
- */
14924
- getCustomization: method(zod.z.object({
14925
- addonId: zod.z.string().min(1),
14926
- stableId: zod.z.string().min(1)
14927
- }), CompositionCustomizationReadSchema, { auth: "admin" }),
14928
- /**
14929
- * The capabilities a composition may BUILD on a device of `deviceType`
14930
- * (a new device, or added to an existing one), with their fields — the
14931
- * editor's feature list and the presets' availability (G2, D676). Built
14932
- * from the validator's own blockers; a replace of a BOUND cap is not
14933
- * listed here (the device's bindings say what it has).
14934
- */
14935
- listComposableCaps: method(zod.z.object({ deviceType: zod.z.enum(DeviceType) }), ComposableCapabilitiesSchema, { auth: "admin" }),
14936
- /**
14937
- * Validate without saving — the same verdict the save reaches. A mutation
14938
- * for its body size; it writes nothing, so its access is `view`.
14939
- */
14940
- validateComposition: method(zod.z.object({
14941
- composition: CompositionSchema,
14942
- blockId: zod.z.string().optional()
14943
- }), CompositionValidationSchema, {
14944
- kind: "mutation",
14945
- auth: "admin",
14946
- access: "view"
14947
- })
14948
- }
14949
- };
14950
- /**
14951
- * `custom-model-registry` — collection cap exposing operator-registered
14952
- * custom detection models. Each provider (today: `addon-model-studio`)
14953
- * contributes a list of `CustomModelDescriptor`s; the hub auto-concatenates
14954
- * them across providers (`concatCollection`).
14955
- *
14956
- * The detection-pipeline is *aware* of this cap: when at least one provider
14957
- * exists it unions these descriptors into the per-step model picker and the
14958
- * runtime model-resolution path, alongside the static catalog. When no
14959
- * provider exists the consumer no-ops entirely (identical to the catalog-only
14960
- * behaviour).
14961
- *
14962
- * A descriptor carries a full `ModelCatalogEntry` directly — the same shape
14963
- * the static catalog uses — so the existing download/resolution code consumes
14964
- * it unchanged. `stepId` is the detection step the model targets
14965
- * (e.g. `'object-detection'`).
14966
- */
14967
- var CustomModelDescriptorSchema = zod.z.object({
14968
- stepId: zod.z.string(),
14969
- entry: ModelCatalogEntrySchema
14970
- });
14971
- /** One regular file of an imported bundle, as the producer measured it. */
14972
- var ModelBundleFileSchema = zod.z.object({
14973
- /** Relative path inside the bundle tar; an mlpackage member is `<x>.mlpackage/<member>`. */
14974
- path: zod.z.string().min(1),
14975
- bytes: zod.z.number().int().nonnegative(),
14976
- sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
14977
- });
14978
- var ImportModelBundleInputSchema = zod.z.object({
14979
- /** The registry provider that keeps the model (Model Studio). */
14980
- addonId: zod.z.string(),
14981
- stepId: zod.z.string().min(1),
14982
- /**
14983
- * The catalog rows to register, complete, every format URL already
14984
- * `camstack-local://<modelId>/<file>` naming a file of the bundle. One
14985
- * bundle may carry several rows (a base and its INT8 variant).
14986
- */
14987
- entries: zod.z.array(ModelCatalogEntrySchema).min(1).max(4),
14988
- /** The bundle tar, offered by the producer over `ctx.peerBytes` (same host). */
14989
- bundle: zod.z.object({
14990
- ticket: PeerBytesTicketSchema,
14991
- bytes: zod.z.number().int().positive(),
14992
- sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
14993
- }),
14994
- /** Every file the tar must hold; anything missing, extra or different is refused. */
14995
- files: zod.z.array(ModelBundleFileSchema).min(1).max(64),
14996
- /** Convert missing engine formats and push the model to every agent afterwards. */
14997
- distribute: zod.z.boolean()
14998
- });
14999
- var ImportModelBundleResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
15000
- kind: zod.z.literal("imported"),
15001
- modelIds: zod.z.array(zod.z.string()).readonly(),
15002
- distributed: zod.z.array(zod.z.object({
15003
- modelId: zod.z.string(),
15004
- nodeId: zod.z.string(),
15005
- format: zod.z.enum(MODEL_FORMATS),
15006
- ok: zod.z.boolean(),
15007
- error: zod.z.string().optional()
15008
- })).readonly()
15009
- }), zod.z.object({
15010
- kind: zod.z.literal("refused"),
15011
- reason: zod.z.enum([
15012
- "transfer-failed",
15013
- "checksum-mismatch",
15014
- "bundle-mismatch",
15015
- "invalid-entry",
15016
- "id-taken"
15017
- ]),
15018
- detail: zod.z.string()
15019
- })]);
15020
- var customModelRegistryCapability = {
15021
- name: "custom-model-registry",
15022
- scope: "system",
15023
- mode: "collection",
15024
- internal: true,
15025
- methods: {
15026
- /**
15027
- * `internal: true` did not gate the mount (see the 2026-08-26 note on
15028
- * `data-store-provider`) — this was reachable on the AppRouter by ANY
15029
- * authenticated session at the default `auth: 'protected'`. The only
15030
- * consumer, the detection-pipeline's model resolution
15031
- * (`addon-pipeline/src/detection-pipeline/provider.ts`), calls
15032
- * `ctx.api.customModelRegistry.listModels.query()` — UDS/Moleculer,
15033
- * never tRPC — so `auth: 'admin'` costs it nothing.
15034
- */
15035
- listModels: method(zod.z.void(), zod.z.array(CustomModelDescriptorSchema).readonly(), { auth: "admin" }),
15036
- /**
15037
- * Register a model whose files arrive as ONE bundle tar (D806): the tar
15038
- * is taken over `ctx.peerBytes`, checked against `bundle.sha256` and
15039
- * every row of `files`, unpacked into the hub's flat modelsDir (directory
15040
- * formats included), each format's `.revision` record written, the rows
15041
- * upserted under the operator's name, the hub marked available and —
15042
- * when asked — engine formats converted and the model distributed. A
15043
- * model id already registered by another source is refused, never
15044
- * overwritten.
15045
- */
15046
- importModelBundle: method(ImportModelBundleInputSchema, ImportModelBundleResultSchema, {
15047
- kind: "mutation",
15048
- auth: "admin",
15049
- timeoutMs: 30 * 6e4
15050
- })
15051
- }
15052
- };
15053
- /**
15054
- * Query filter for settings-store collections.
15055
- */
15056
- var QueryFilterSchema = zod.z.object({
15057
- where: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
15058
- whereIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
15059
- whereBetween: zod.z.record(zod.z.string(), zod.z.tuple([zod.z.unknown(), zod.z.unknown()])).optional(),
15060
- /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
15061
- whereNot: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
15062
- /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
15063
- * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
15064
- whereNotIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
15065
- orderBy: zod.z.object({
15066
- field: zod.z.string(),
15067
- direction: zod.z.enum(["asc", "desc"])
15068
- }).optional(),
15069
- limit: zod.z.number().optional(),
15070
- offset: zod.z.number().optional()
15071
- });
15072
- /**
15073
- * The predicate half of a filter, for BULK MUTATIONS.
15074
- *
15075
- * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
15076
- * meaning for a statement that rewrites a set, and accepting them would invite
15077
- * a caller to believe `limit` bounds the damage. Every field is optional here
15078
- * only so the shape stays composable — the implementation REJECTS a filter
15079
- * that compiles to no predicate, because that is the whole collection.
15080
- */
15081
- var MutationFilterSchema = zod.z.object({
15082
- where: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
15083
- whereIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
15084
- whereBetween: zod.z.record(zod.z.string(), zod.z.tuple([zod.z.unknown(), zod.z.unknown()])).optional(),
15085
- whereNot: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
15086
- whereNotIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional()
15087
- });
15088
- /**
15089
- * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
15090
- *
15091
- * `as` names the slot in the result, so the SAME column may be asked twice with
15092
- * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
15093
- * a `Record<column, op>` shape could not express.
15094
- */
15095
- var AggregateFieldSchema = zod.z.object({
15096
- /** Result key. */
15097
- as: zod.z.string().min(1),
15098
- /** Column to aggregate. Must be a real column of a declared collection. */
15099
- field: zod.z.string().min(1),
15100
- op: zod.z.enum([
15101
- "sum",
15102
- "min",
15103
- "max"
15104
- ])
15105
- });
15106
- /**
15107
- * `COUNT(*)` plus one number per requested field.
15108
- *
15109
- * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
15110
- * that really is 0 are different facts, and an accounting caller that renders
15111
- * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
15112
- */
15113
- var AggregateResultSchema = zod.z.object({
15114
- count: zod.z.number().int(),
15115
- values: zod.z.record(zod.z.string(), zod.z.number().nullable())
15116
- });
15117
- /** A single stored record: `{ id, data }`. */
15118
- var SettingsRecordSchema = zod.z.object({
15119
- id: zod.z.string(),
15120
- data: zod.z.record(zod.z.string(), zod.z.unknown())
15121
- });
15122
- /**
15123
- * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
15124
- * optional.
15125
- *
15126
- * A separate schema rather than loosening the shared one: every other method
15127
- * on this cap addresses a row BY its id, and making that field optional
15128
- * everywhere would turn a forgotten key into a silently generated one on
15129
- * `update` and `delete` as well.
15130
- */
15131
- var BulkRecordSchema = zod.z.object({
15132
- id: zod.z.string().optional(),
15133
- data: zod.z.record(zod.z.string(), zod.z.unknown())
15134
- });
15135
- /**
15136
- * The database FILES one `sqlite-settings` engine process owns (D851).
15137
- * Closed on purpose: a file is a thing the engine opens, backs up and gives a
15138
- * serving thread, so a new one is a code change, never a string a caller
15139
- * invents. `settings` is `camstack.db` and the default for every collection
15140
- * that names no file.
15141
- */
15142
- var DataStoreDatabaseSchema = zod.z.enum([
15143
- "settings",
15144
- "events",
15145
- "history"
15146
- ]);
15147
- /** Every database file, in a stable order (settings first). */
15148
- var DATA_STORE_DATABASES = DataStoreDatabaseSchema.options;
15149
- /**
15150
- * One write inside a {@link settingsStoreCapability.methods.transact} call.
15151
- * The three that a "replace this set, bump that revision" write needs, plus
15152
- * the batched upsert a rollup writer needs.
15153
- */
15154
- var TransactOpSchema = zod.z.discriminatedUnion("op", [
15155
- zod.z.object({
15156
- op: zod.z.literal("deleteWhere"),
15157
- collection: zod.z.string(),
15158
- filter: MutationFilterSchema
15159
- }),
15160
- zod.z.object({
15161
- op: zod.z.literal("insertMany"),
15162
- collection: zod.z.string(),
15163
- records: zod.z.array(BulkRecordSchema).readonly()
15164
- }),
15165
- zod.z.object({
15166
- op: zod.z.literal("update"),
15167
- collection: zod.z.string(),
15168
- id: zod.z.string(),
15169
- data: zod.z.record(zod.z.string(), zod.z.unknown())
15170
- }),
15171
- zod.z.object({
15172
- op: zod.z.literal("upsertMany"),
15173
- collection: zod.z.string(),
15174
- conflict: zod.z.array(zod.z.string()).readonly(),
15175
- records: zod.z.array(BulkRecordSchema).readonly()
15176
- })
15177
- ]);
15178
- /**
15179
- * The precondition of a transaction: the row `key` of `collection` must hold
15180
- * `equals` in `column`, read INSIDE the transaction.
15181
- */
15182
- var TransactGuardSchema = zod.z.object({
15183
- collection: zod.z.string(),
15184
- key: zod.z.string(),
15185
- column: zod.z.string(),
15186
- equals: zod.z.unknown()
15187
- });
15188
- /**
15189
- * Column declaration for a structured (SQL-backed) collection.
15190
- *
15191
- * Logical types — the backend translates each to the matching SQLite
15192
- * storage class and handles per-type marshaling:
15193
- * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
15194
- * - `JSON` — TEXT under the hood; serialised on write, parsed on read
15195
- * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
15196
- */
15197
- var CollectionColumnSchema = zod.z.object({
15198
- name: zod.z.string(),
15199
- type: zod.z.enum([
15200
- "TEXT",
15201
- "INTEGER",
15202
- "REAL",
15203
- "JSON",
15204
- "BOOLEAN"
15205
- ]),
15206
- primaryKey: zod.z.boolean().optional(),
15207
- notNull: zod.z.boolean().optional(),
15208
- unique: zod.z.boolean().optional(),
15209
- /**
15210
- * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
15211
- * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
15212
- * collection surface simply cannot express their existing tables — which is
15213
- * why they were still on the uncapped `table*` API.
15214
- */
15215
- defaultValue: zod.z.union([
15216
- zod.z.string(),
15217
- zod.z.number(),
15218
- zod.z.boolean()
15219
- ]).optional()
15220
- });
15221
- var CollectionIndexSchema = zod.z.object({
15222
- name: zod.z.string(),
15223
- columns: zod.z.array(zod.z.string()).readonly(),
15224
- unique: zod.z.boolean().optional()
15225
- });
15226
- /**
15227
- * The input of `declareCollection` — ONE schema for the door and for the
15228
- * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
15229
- * the payload untouched).
15230
- *
15231
- * - `database` — the file the collection lives in (D851). Absent means
15232
- * `settings`. A declaration naming a file other than the one the table is
15233
- * already in is REFUSED by the engine, never moved.
15234
- * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
15235
- * as `primaryKey: true` on that column. A composite-keyed collection has no
15236
- * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
15237
- * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
15238
- * address one row by id are refused.
15239
- * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
15240
- */
15241
- var DeclareCollectionInputSchema = zod.z.object({
15242
- namespace: zod.z.string().optional(),
15243
- collection: zod.z.string(),
15244
- columns: zod.z.array(CollectionColumnSchema).readonly(),
15245
- indexes: zod.z.array(CollectionIndexSchema).readonly().optional(),
15246
- database: DataStoreDatabaseSchema.optional(),
15247
- primaryKey: zod.z.array(zod.z.string()).readonly().optional(),
15248
- withoutRowid: zod.z.boolean().optional()
15249
- });
15250
- /**
15251
- * settings-store — singleton capability for addon-scoped persistence.
15252
- *
15253
- * Every method operates within a `collection`. An optional `namespace`
15254
- * field provides access to additional data spaces beyond the default
15255
- * addon settings — useful for business data (events, tracks, faces, etc.).
15256
- *
15257
- * **Scoping is the CALLER's, and it is opt-in.** The table is
15258
- * `namespace ? `${namespace}:${collection}` : collection` — nothing
15259
- * consults the identity of the caller. An earlier version of this comment
15260
- * claimed the implementation prefixes every collection with the calling
15261
- * addon's ID and that "addons never see each other's data"; that was never
15262
- * true, and this same file contradicted it under `declareCollection`. What
15263
- * exists is `addon-context-factory`, which passes `namespace: addonId` ON
15264
- * THE ADDON'S BEHALF for the `addon-settings` / `addon-devices` paths only.
15265
- * Business collections use bare names, and any caller may name any
15266
- * namespace, or none. Making the door enforce it is tracked separately —
15267
- * it renames tables, so it needs a migration.
15268
- *
15269
- * **So every method is `auth: 'admin'` (D634).** Because nothing scopes a
15270
- * call to its caller, a `protected` method here was a raw, unredacted read
15271
- * (and write) of every addon's collections for any role holding
15272
- * `category:system` — which is every role: a `viewer` could read every
15273
- * notifier token in plaintext from `notifiers:notification-targets`. No
15274
- * browser or app client calls this cap. Addons are not affected: an
15275
- * addon's `ctx.api` cap call is resolved to the provider directly and never
15276
- * enters the tRPC router (so the tier gates sessions and tokens, never
15277
- * addons), and the hub itself calls it through the capability registry.
15278
- *
15279
- * - `{ collection: 'addon-settings' }` → table `addon-settings`
15280
- * - `{ namespace: 'my-addon', collection: 'addon-settings' }` →
15281
- * table `my-addon:addon-settings`
15282
- *
15283
- * Served by the **storage-orchestrator** builtin, which dispatches to the
15284
- * `data-store-provider` engine registered for the collection
15285
- * (`sqlite-settings`, a SQLite WAL backend, today). One addon owns the
15286
- * data door; engines sit behind it
15287
- * ([D44](../../../../docs/decisions/adr-0044.md)).
15288
- * Addons access it via `ctx.api.settingsStore.*`.
15289
- */
15290
- var settingsStoreCapability = {
15291
- name: "settings-store",
15388
+ }).superRefine((val, ctx) => {
15389
+ if (val.files !== void 0) {
15390
+ const entry = val.entry ?? "main.ts";
15391
+ if (val.files[entry] === void 0) ctx.addIssue({
15392
+ code: "custom",
15393
+ message: `files must include the entry "${entry}"`,
15394
+ path: ["files"]
15395
+ });
15396
+ return;
15397
+ }
15398
+ if (val.code === void 0) ctx.addIssue({
15399
+ code: "custom",
15400
+ message: "code or files is required",
15401
+ path: ["code"]
15402
+ });
15403
+ });
15404
+ var coreBlocksCapability = {
15405
+ name: "core-blocks",
15292
15406
  scope: "system",
15293
15407
  mode: "singleton",
15294
15408
  methods: {
15295
- /** Get a single value by key from a collection. */
15296
- get: method(zod.z.object({
15297
- namespace: zod.z.string().optional(),
15298
- collection: zod.z.string(),
15299
- key: zod.z.string()
15300
- }), zod.z.unknown(), { auth: "admin" }),
15301
- /** Set a value by key in a collection (upsert). */
15302
- set: method(zod.z.object({
15303
- namespace: zod.z.string().optional(),
15304
- collection: zod.z.string(),
15305
- key: zod.z.string(),
15306
- value: zod.z.unknown()
15307
- }), zod.z.void(), {
15409
+ list: method(zod.z.object({}), zod.z.object({ blocks: zod.z.array(CoreBlockSchema) }), { auth: "admin" }),
15410
+ get: method(zod.z.object({ blockId: zod.z.string() }), zod.z.object({ block: CoreBlockSchema.nullable() }), { auth: "admin" }),
15411
+ /**
15412
+ * Create a block. The code is COMPILED first: storing source that does not
15413
+ * compile turns an editor error into a fork failure the operator meets
15414
+ * minutes later in a log.
15415
+ */
15416
+ create: method(zod.z.object({ block: CoreBlockInputSchema }), zod.z.object({ block: CoreBlockSchema }), {
15417
+ kind: "mutation",
15418
+ auth: "admin",
15419
+ caller: "required"
15420
+ }),
15421
+ update: method(zod.z.object({
15422
+ blockId: zod.z.string(),
15423
+ block: CoreBlockInputFieldsSchema.partial()
15424
+ }), zod.z.object({ block: CoreBlockSchema }), {
15425
+ kind: "mutation",
15426
+ auth: "admin",
15427
+ caller: "required"
15428
+ }),
15429
+ delete: method(zod.z.object({ blockId: zod.z.string() }), zod.z.object({ success: zod.z.literal(true) }), {
15308
15430
  kind: "mutation",
15309
15431
  auth: "admin"
15310
15432
  }),
15311
- /**
15312
- * Rows matching a filter.
15313
- *
15314
- * `columns` is a SQL-level projection: the engine SELECTs only those
15315
- * columns (plus the primary key) instead of the whole row. It is the only
15316
- * way to stop paying disk for a column that is discarded — a heavy JSON
15317
- * column dominates the row, and dropping it in JS after the read pays every
15318
- * byte first. Measured on the live `pipeline-analytics:tracks` table: a
15319
- * 501-row page is 4.6 MB whole and 0.11 MB projected, same index, same
15320
- * plan.
15321
- *
15322
- * The engine REFUSES a column it cannot serve rather than omitting it —
15323
- * see `SettingsQueryInput.columns` for why a projection may not be
15324
- * forgiving the way a predicate is.
15325
- */
15326
- query: method(zod.z.object({
15327
- namespace: zod.z.string().optional(),
15328
- collection: zod.z.string(),
15329
- filter: QueryFilterSchema.optional(),
15330
- columns: zod.z.array(zod.z.string()).readonly().optional()
15331
- }), zod.z.array(SettingsRecordSchema).readonly(), { auth: "admin" }),
15332
- /**
15333
- * Make a SLOT hold this row — insert it, or update whatever already
15334
- * occupies the slot — and answer with the id the row HAS.
15335
- *
15336
- * ## Why the store needed this
15337
- *
15338
- * {@link set} is already an upsert, but its slot is the PRIMARY KEY: the
15339
- * caller has to know the id to address the row. That is exactly what a
15340
- * collection keyed by an `INTEGER` rowid alias cannot do — SQLite assigns
15341
- * the id, so a writer whose uniqueness rule is "one row per
15342
- * `(ownerKind, ownerId, kind, slot)`" has no id to name and no way to
15343
- * express the rule. {@link insert} can express it (a UNIQUE index
15344
- * arbitrates) but only by THROWING at the loser of a race, and the loser
15345
- * here is a legitimate re-capture, not an error.
15346
- *
15347
- * Getting there without this method means read-then-write: query the slot,
15348
- * then `insert` or `update`. Two statements instead of one, a race between
15349
- * them, and a recovery path that has to recognise a UNIQUE violation by its
15350
- * MESSAGE — across a process boundary, because the store lives behind an
15351
- * RPC door. A string match would be the whole gate, and a store that
15352
- * rephrased its error would lose the row silently. One statement has no
15353
- * such gate (D482).
15354
- *
15355
- * ## The contract
15356
- *
15357
- * `conflict` names the DECLARED columns that identify the slot. They must
15358
- * be covered by a UNIQUE index on the collection — SQLite resolves an
15359
- * `ON CONFLICT` target against its indexes and REFUSES the statement when
15360
- * none matches, so a wrong list is a loud error on the first write, never a
15361
- * silently duplicated row. An empty list is refused rather than passed on:
15362
- * `ON CONFLICT()` is not valid SQL, and the intent it would most likely
15363
- * encode ("no slot, just insert") is {@link insert}.
15364
- *
15365
- * Row shape is {@link insert}'s: `record.id` is used when the collection
15366
- * needs a supplied key and IGNORED when the primary key is an `INTEGER`
15367
- * rowid alias — exactly as `insert` and `insertMany` already treat it.
15368
- *
15369
- * Returns the id either way: the one SQLite assigned on an insert, or the
15370
- * one the row it updated already had. A caller cannot tell which happened,
15371
- * and deliberately — every caller wants the slot's id, and one that needed
15372
- * to distinguish creation from replacement would have to read first anyway.
15373
- */
15374
- upsert: method(zod.z.object({
15375
- namespace: zod.z.string().optional(),
15376
- collection: zod.z.string(),
15377
- /** Declared columns identifying the slot; must be covered by a UNIQUE
15378
- * index on the collection. Never empty. */
15379
- conflict: zod.z.array(zod.z.string()).readonly(),
15380
- record: BulkRecordSchema
15381
- }), zod.z.object({
15382
- /** The id the row HAS: assigned by SQLite on an insert, or the existing
15383
- * row's own id on an update. */
15384
- id: zod.z.union([zod.z.string(), zod.z.number()]) }), {
15433
+ setEnabled: method(zod.z.object({
15434
+ blockId: zod.z.string(),
15435
+ enabled: zod.z.boolean()
15436
+ }), zod.z.object({ block: CoreBlockSchema }), {
15385
15437
  kind: "mutation",
15386
15438
  auth: "admin"
15387
15439
  }),
15388
15440
  /**
15389
- * {@link upsert} for a BATCH, in ONE transaction on ONE prepared statement.
15441
+ * Stop this block's runner and let the supervisor bring it back.
15390
15442
  *
15391
- * The write-side twin of {@link insertMany} for rows that may already
15392
- * exist: a rollup writer recomputes a bucket and must overwrite it, and
15393
- * N round trips with N COMMITs is the cost {@link insertMany} was added to
15394
- * avoid. `conflict` has the meaning it has on {@link upsert} (declared
15395
- * columns covered by a UNIQUE index or the primary key, never empty).
15443
+ * **It stores nothing.** There is no "restart requested" flag and no second
15444
+ * lifecycle field: the supervisor already stops and respawns a runner when
15445
+ * the code hash changes, and this exposes that same path deliberately
15446
+ * (D62 — a switch writes the authority that already owned the function).
15447
+ * The next reconcile pass remains the authority, so a restart that lost a
15448
+ * race is corrected within one pass rather than leaving a stale flag behind.
15396
15449
  *
15397
- * Every record of a batch must carry the SAME set of declared columns and
15398
- * every `conflict` column: one batch is one statement shape, and a record
15399
- * that silently bound NULL over a column it did not mention would erase
15400
- * it. A heterogeneous batch is refused whole. **All or nothing.**
15450
+ * Refused for a DISABLED block: nothing would come back, and a button that
15451
+ * silently does nothing is worse than one that says why.
15401
15452
  */
15402
- upsertMany: method(zod.z.object({
15403
- namespace: zod.z.string().optional(),
15404
- collection: zod.z.string(),
15405
- conflict: zod.z.array(zod.z.string()).readonly(),
15406
- records: zod.z.array(BulkRecordSchema).readonly()
15407
- }), zod.z.object({ upserted: zod.z.number().int() }), {
15453
+ restart: method(zod.z.object({ blockId: zod.z.string() }), zod.z.object({ block: CoreBlockSchema }), {
15408
15454
  kind: "mutation",
15409
15455
  auth: "admin"
15410
15456
  }),
15411
- /** Insert a new record. */
15412
- insert: method(zod.z.object({
15413
- namespace: zod.z.string().optional(),
15414
- collection: zod.z.string(),
15415
- record: zod.z.object({
15416
- id: zod.z.string().optional(),
15417
- data: zod.z.record(zod.z.string(), zod.z.unknown())
15418
- })
15419
- }), zod.z.object({
15420
15457
  /**
15421
- * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
15422
- * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
15423
- * primary key — which is the only place an auto key is knowable.
15458
+ * Type-check without saving — what the editor calls as the author types, so
15459
+ * the compiler's verdict is the same one the server will reach.
15460
+ * Accepts `{ code }` or `{ files, entry }` (multi-file project).
15424
15461
  */
15425
- id: zod.z.union([zod.z.string(), zod.z.number()]) }), {
15462
+ compile: method(CoreBlockCompileInputSchema, CoreBlockCompileResultSchema, {
15426
15463
  kind: "mutation",
15427
15464
  auth: "admin"
15428
15465
  }),
15429
15466
  /**
15430
- * Insert MANY records in ONE transaction, returning how many landed.
15431
- *
15432
- * The write-side twin of {@link deleteWhere}, and it exists for the same
15433
- * reason: without it, appending a batch is N round trips and N COMMITs on
15434
- * the single shared connection that also serves every cluster-wide
15435
- * configuration read. The durable load series writes one process row per
15436
- * process per sample — 76 rows every 10 s on the live fleet — and the
15437
- * operator's rule for it is *one transaction per sample, never one per
15438
- * row*. `insert` cannot express that; nothing else could.
15439
- *
15440
- * **All or nothing.** A batch that fails on its fifth row leaves none of
15441
- * the five behind. A half-written sample is worse than a missing one: the
15442
- * missing one reads as "nobody reported", which is true, while the half
15443
- * one reads as "these were the only processes running", which is not.
15444
- *
15445
- * `id` is OPTIONAL per record, and that is the difference from
15446
- * {@link insert}. A collection whose primary key is an `INTEGER` rowid
15447
- * alias has no id to supply — SQLite assigns it, for free, and inventing a
15448
- * `randomUUID()` for such a column would write a 36-character string into
15449
- * an integer key. Omitted on a TEXT key, a uuid is generated exactly as
15450
- * `insert` does.
15467
+ * Hydrated settings form for one block, or null until a `settingsSchema`
15468
+ * is known (extracted from the compiled entry's named export, or set on
15469
+ * the block). Values live in the `core-blocks:settings` collection.
15451
15470
  */
15452
- insertMany: method(zod.z.object({
15453
- namespace: zod.z.string().optional(),
15454
- collection: zod.z.string(),
15455
- records: zod.z.array(BulkRecordSchema).readonly()
15456
- }), zod.z.object({ inserted: zod.z.number().int() }), {
15471
+ getBlockSettings: method(zod.z.object({ blockId: zod.z.string() }), SettingsSchemaWithValuesSchema.nullable(), { auth: "admin" }),
15472
+ /**
15473
+ * Patch persisted preference values for one block. The schema is not
15474
+ * rewritten here — only values. Scripts read the same blob via
15475
+ * `ctx.settings.readAddonStore()` (mirrored into the block addon's
15476
+ * `addon-settings` namespace on every patch).
15477
+ */
15478
+ updateBlockSettings: method(zod.z.object({
15479
+ blockId: zod.z.string(),
15480
+ patch: SettingsPatchSchema
15481
+ }), SettingsUpdateResultSchema, {
15457
15482
  kind: "mutation",
15458
15483
  auth: "admin"
15459
15484
  }),
15460
15485
  /**
15461
- * Several writes, ONE transaction, behind an optional precondition.
15462
- *
15463
- * The multi-collection twin of {@link insertMany}: a write that has to move
15464
- * a set of rows in one collection AND a revision in another cannot be
15465
- * expressed as separate calls without a window in which a reader (or a
15466
- * crash) sees half of it. The retrain annotation save replaces a frame's
15467
- * rows, its dismissed set and its revision this way.
15468
- *
15469
- * `guard`, when given, is read inside the transaction: if the row is
15470
- * missing or its column differs from `equals`, NOTHING is written and the
15471
- * answer is `{ applied: false, current }` with what was found — the
15472
- * compare-and-set a revisioned write needs, with no second reader able to
15473
- * slip between the check and the writes.
15486
+ * The declaration files the editor type-checks against.
15474
15487
  *
15475
- * **All or nothing**, like {@link insertMany}: a throw in any op rolls the
15476
- * whole call back. Ops run in the order given (delete before insert to
15477
- * replace a set). `namespace` applies to every collection named.
15488
+ * Served rather than bundled: the graph is 3.4 MB across 344 files, and
15489
+ * bundling it would roughly double the admin remote. Served rather than
15490
+ * hand-stubbed: a block runs with the same `ctx` an addon gets, so a stub
15491
+ * would drift and the editor would confidently autocomplete methods that
15492
+ * do not exist.
15478
15493
  */
15479
- transact: method(zod.z.object({
15480
- namespace: zod.z.string().optional(),
15481
- guard: TransactGuardSchema.optional(),
15482
- ops: zod.z.array(TransactOpSchema).readonly()
15483
- }), zod.z.object({
15484
- applied: zod.z.boolean(),
15485
- current: zod.z.unknown().optional()
15486
- }), {
15487
- kind: "mutation",
15488
- auth: "admin"
15489
- }),
15490
- /** Update an existing record by ID. */
15491
- update: method(zod.z.object({
15492
- namespace: zod.z.string().optional(),
15493
- collection: zod.z.string(),
15494
- id: zod.z.string(),
15495
- data: zod.z.record(zod.z.string(), zod.z.unknown())
15496
- }), zod.z.void(), {
15497
- kind: "mutation",
15498
- auth: "admin"
15499
- }),
15500
- /** Delete a record by key/ID. */
15501
- delete: method(zod.z.object({
15502
- namespace: zod.z.string().optional(),
15503
- collection: zod.z.string(),
15504
- key: zod.z.string()
15505
- }), zod.z.void(), {
15494
+ getTypeDefs: method(zod.z.object({}), zod.z.object({ libs: zod.z.array(zod.z.object({
15495
+ filePath: zod.z.string(),
15496
+ content: zod.z.string()
15497
+ })) }), { auth: "admin" }),
15498
+ /**
15499
+ * Create a composition block (D659). VALIDATED first against the live
15500
+ * sources and every stored composition; a refusal names every problem by
15501
+ * path and reason (D391) and stores nothing.
15502
+ */
15503
+ createComposition: method(zod.z.object({ block: CompositionBlockInputSchema }), zod.z.object({ block: CoreBlockSchema }), {
15506
15504
  kind: "mutation",
15507
- auth: "admin"
15505
+ auth: "admin",
15506
+ caller: "required"
15508
15507
  }),
15509
15508
  /**
15510
- * Delete every record matching `filter`, in ONE statement, returning how
15511
- * many rows went. This exists because its absence made every retention
15512
- * path in the system an N+1 drain loop: `delete` takes a key, so a sweep
15513
- * had to SELECT a page of full rows — every column, including the fat
15514
- * ones — purely to learn their ids, then issue one call per row.
15515
- *
15516
- * **The filter is required and must resolve.** A predicate naming
15517
- * something the collection cannot express is an ERROR here, not a
15518
- * widening as it is on `query`, and a filter with no predicates is an
15519
- * error rather than "every row". Deleting a whole collection is a
15520
- * legitimate intent, but it must be asked for by name — not reached by
15521
- * an empty object.
15509
+ * Patch a composition block. A changed `composition` is validated exactly
15510
+ * as on create. `expectedUpdatedAt` is the `updatedAt` the caller READ: a
15511
+ * block changed since is refused by name, never silently overwritten.
15522
15512
  */
15523
- deleteWhere: method(zod.z.object({
15524
- namespace: zod.z.string().optional(),
15525
- collection: zod.z.string(),
15526
- filter: MutationFilterSchema
15527
- }), zod.z.object({ deleted: zod.z.number().int() }), {
15513
+ updateComposition: method(zod.z.object({
15514
+ blockId: zod.z.string(),
15515
+ expectedUpdatedAt: zod.z.number(),
15516
+ block: CompositionBlockInputSchema.partial()
15517
+ }), zod.z.object({ block: CoreBlockSchema }), {
15528
15518
  kind: "mutation",
15529
- auth: "admin"
15519
+ auth: "admin",
15520
+ caller: "required"
15530
15521
  }),
15522
+ /** One composition and the composer's state for it, or `not-loaded` (D315). */
15523
+ getComposition: method(zod.z.object({ blockId: zod.z.string() }), CompositionGetReadSchema, { auth: "admin" }),
15524
+ listCompositions: method(zod.z.object({}), CompositionListReadSchema, { auth: "admin" }),
15531
15525
  /**
15532
- * Apply `data` to every record matching `filter`, in one statement,
15533
- * returning how many rows changed. Same filter contract as
15534
- * {@link deleteWhere} — an unresolvable predicate is an error.
15526
+ * The composition that customizes an existing device (D663), found by its
15527
+ * TARGET — never by the block's derived name. One per device at most.
15535
15528
  */
15536
- updateWhere: method(zod.z.object({
15537
- namespace: zod.z.string().optional(),
15538
- collection: zod.z.string(),
15539
- filter: MutationFilterSchema,
15540
- data: zod.z.record(zod.z.string(), zod.z.unknown())
15541
- }), zod.z.object({ updated: zod.z.number().int() }), {
15529
+ getCustomization: method(zod.z.object({
15530
+ addonId: zod.z.string().min(1),
15531
+ stableId: zod.z.string().min(1)
15532
+ }), CompositionCustomizationReadSchema, { auth: "admin" }),
15533
+ /**
15534
+ * The capabilities a composition may BUILD on a device of `deviceType`
15535
+ * (a new device, or added to an existing one), with their fields — the
15536
+ * editor's feature list and the presets' availability (G2, D676). Built
15537
+ * from the validator's own blockers; a replace of a BOUND cap is not
15538
+ * listed here (the device's bindings say what it has).
15539
+ */
15540
+ listComposableCaps: method(zod.z.object({ deviceType: zod.z.enum(DeviceType) }), ComposableCapabilitiesSchema, { auth: "admin" }),
15541
+ /**
15542
+ * Validate without saving — the same verdict the save reaches. A mutation
15543
+ * for its body size; it writes nothing, so its access is `view`.
15544
+ */
15545
+ validateComposition: method(zod.z.object({
15546
+ composition: CompositionSchema,
15547
+ blockId: zod.z.string().optional()
15548
+ }), CompositionValidationSchema, {
15542
15549
  kind: "mutation",
15543
- auth: "admin"
15544
- }),
15545
- /** Count entries in a collection, optionally filtered. */
15546
- count: method(zod.z.object({
15547
- namespace: zod.z.string().optional(),
15548
- collection: zod.z.string(),
15549
- filter: QueryFilterSchema.optional()
15550
- }), zod.z.number(), { auth: "admin" }),
15550
+ auth: "admin",
15551
+ access: "view"
15552
+ })
15553
+ }
15554
+ };
15555
+ /**
15556
+ * `custom-model-registry` — collection cap exposing operator-registered
15557
+ * custom detection models. Each provider (today: `addon-model-studio`)
15558
+ * contributes a list of `CustomModelDescriptor`s; the hub auto-concatenates
15559
+ * them across providers (`concatCollection`).
15560
+ *
15561
+ * The detection-pipeline is *aware* of this cap: when at least one provider
15562
+ * exists it unions these descriptors into the per-step model picker and the
15563
+ * runtime model-resolution path, alongside the static catalog. When no
15564
+ * provider exists the consumer no-ops entirely (identical to the catalog-only
15565
+ * behaviour).
15566
+ *
15567
+ * A descriptor carries a full `ModelCatalogEntry` directly — the same shape
15568
+ * the static catalog uses — so the existing download/resolution code consumes
15569
+ * it unchanged. `stepId` is the detection step the model targets
15570
+ * (e.g. `'object-detection'`).
15571
+ */
15572
+ var CustomModelDescriptorSchema = zod.z.object({
15573
+ stepId: zod.z.string(),
15574
+ entry: ModelCatalogEntrySchema
15575
+ });
15576
+ /** One regular file of an imported bundle, as the producer measured it. */
15577
+ var ModelBundleFileSchema = zod.z.object({
15578
+ /** Relative path inside the bundle tar; an mlpackage member is `<x>.mlpackage/<member>`. */
15579
+ path: zod.z.string().min(1),
15580
+ bytes: zod.z.number().int().nonnegative(),
15581
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
15582
+ });
15583
+ var ImportModelBundleInputSchema = zod.z.object({
15584
+ /** The registry provider that keeps the model (Model Studio). */
15585
+ addonId: zod.z.string(),
15586
+ stepId: zod.z.string().min(1),
15587
+ /**
15588
+ * The catalog rows to register, complete, every format URL already
15589
+ * `camstack-local://<modelId>/<file>` naming a file of the bundle. One
15590
+ * bundle may carry several rows (a base and its INT8 variant).
15591
+ */
15592
+ entries: zod.z.array(ModelCatalogEntrySchema).min(1).max(4),
15593
+ /** The bundle tar, offered by the producer over `ctx.peerBytes` (same host). */
15594
+ bundle: zod.z.object({
15595
+ ticket: PeerBytesTicketSchema,
15596
+ bytes: zod.z.number().int().positive(),
15597
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
15598
+ }),
15599
+ /** Every file the tar must hold; anything missing, extra or different is refused. */
15600
+ files: zod.z.array(ModelBundleFileSchema).min(1).max(64),
15601
+ /** Convert missing engine formats and push the model to every agent afterwards. */
15602
+ distribute: zod.z.boolean()
15603
+ });
15604
+ var ImportModelBundleResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
15605
+ kind: zod.z.literal("imported"),
15606
+ modelIds: zod.z.array(zod.z.string()).readonly(),
15607
+ distributed: zod.z.array(zod.z.object({
15608
+ modelId: zod.z.string(),
15609
+ nodeId: zod.z.string(),
15610
+ format: zod.z.enum(MODEL_FORMATS),
15611
+ ok: zod.z.boolean(),
15612
+ error: zod.z.string().optional()
15613
+ })).readonly()
15614
+ }), zod.z.object({
15615
+ kind: zod.z.literal("refused"),
15616
+ reason: zod.z.enum([
15617
+ "transfer-failed",
15618
+ "checksum-mismatch",
15619
+ "bundle-mismatch",
15620
+ "invalid-entry",
15621
+ "id-taken"
15622
+ ]),
15623
+ detail: zod.z.string()
15624
+ })]);
15625
+ var customModelRegistryCapability = {
15626
+ name: "custom-model-registry",
15627
+ scope: "system",
15628
+ mode: "collection",
15629
+ internal: true,
15630
+ methods: {
15551
15631
  /**
15552
- * `COUNT(*)` and one `SUM` / `MIN` / `MAX` per requested field, in ONE
15553
- * statement, over the rows `filter` selects.
15554
- *
15555
- * Exists because "how much is there" was being answered by materialising
15556
- * "what is there". The recorder's storage-pressure sweep asked its in-RAM
15557
- * footage index for bytes/count/oldest/newest across a set of storage
15558
- * locations twice a minute, and the only way to answer that from a map is
15559
- * to visit every row — 7.1 M of them on the live hub, ~15 M row visits a
15560
- * minute on the main thread, which is also why the whole archive had to
15561
- * stay resident to be visited. The question is a sum; nothing needs to be
15562
- * materialised to answer it.
15563
- *
15564
- * **The engine REFUSES a field it cannot serve**, exactly as
15565
- * `query.columns` does and unlike a PREDICATE, which is skipped when
15566
- * unresolvable. A dropped predicate over-matches and the caller sees extra
15567
- * rows; a dropped aggregate returns a NUMBER that is wrong and looks
15568
- * exactly like a real one. That asymmetry is what this repo has already
15569
- * paid for once in `count`.
15632
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
15633
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
15634
+ * authenticated session at the default `auth: 'protected'`. The only
15635
+ * consumer, the detection-pipeline's model resolution
15636
+ * (`addon-pipeline/src/detection-pipeline/provider.ts`), calls
15637
+ * `ctx.api.customModelRegistry.listModels.query()` — UDS/Moleculer,
15638
+ * never tRPC — so `auth: 'admin'` costs it nothing.
15570
15639
  */
15571
- aggregate: method(zod.z.object({
15572
- namespace: zod.z.string().optional(),
15573
- collection: zod.z.string(),
15574
- fields: zod.z.array(AggregateFieldSchema).readonly(),
15575
- filter: QueryFilterSchema.optional()
15576
- }), AggregateResultSchema, { auth: "admin" }),
15577
- /** Grouped counts per ((field-origin)/bucketSize) bucket, filtered. */
15578
- histogram: method(zod.z.object({
15579
- namespace: zod.z.string().optional(),
15580
- collection: zod.z.string(),
15581
- field: zod.z.string(),
15582
- bucketSize: zod.z.number().int().positive(),
15583
- origin: zod.z.number().int(),
15584
- filter: QueryFilterSchema.optional()
15585
- }), zod.z.array(zod.z.object({
15586
- bucket: zod.z.number().int(),
15587
- count: zod.z.number().int()
15588
- })).readonly(), { auth: "admin" }),
15589
- /** Check if a collection is empty. */
15590
- isEmpty: method(zod.z.object({
15591
- namespace: zod.z.string().optional(),
15592
- collection: zod.z.string()
15593
- }), zod.z.boolean(), { auth: "admin" }),
15640
+ listModels: method(zod.z.void(), zod.z.array(CustomModelDescriptorSchema).readonly(), { auth: "admin" }),
15594
15641
  /**
15595
- * Declare a typed (SQL-backed) collection with explicit columns +
15596
- * indexes. Idempotent: re-declaring an existing collection with the
15597
- * same shape is a no-op; shape changes (new columns, new indexes)
15598
- * are applied additively. Subsequent `insert` / `update` / `delete`
15599
- * / `query` calls on this collection use typed columns instead of
15600
- * JSON-blob storage — `record.data` fields are spread across
15601
- * columns, `query.filter.where` matches real columns (no
15602
- * `json_extract` overhead), `orderBy` uses column indexes.
15603
- *
15604
- * Addons call this in `onInitialize` before their first read/write.
15605
- * Collection names should be namespaced by addon (e.g.
15606
- * `pipeline-analytics:object-events`) to avoid cross-addon
15607
- * clashes — no automatic prefix is added.
15642
+ * Register a model whose files arrive as ONE bundle tar (D806): the tar
15643
+ * is taken over `ctx.peerBytes`, checked against `bundle.sha256` and
15644
+ * every row of `files`, unpacked into the hub's flat modelsDir (directory
15645
+ * formats included), each format's `.revision` record written, the rows
15646
+ * upserted under the operator's name, the hub marked available and —
15647
+ * when asked — engine formats converted and the model distributed. A
15648
+ * model id already registered by another source is refused, never
15649
+ * overwritten.
15608
15650
  */
15609
- declareCollection: method(DeclareCollectionInputSchema, zod.z.void(), {
15651
+ importModelBundle: method(ImportModelBundleInputSchema, ImportModelBundleResultSchema, {
15610
15652
  kind: "mutation",
15611
- auth: "admin"
15653
+ auth: "admin",
15654
+ timeoutMs: 30 * 6e4
15612
15655
  })
15613
15656
  }
15614
15657
  };
@@ -37572,6 +37615,18 @@ var CalendarInfoSchema = zod.z.object({
37572
37615
  source: CalendarSourceKindSchema,
37573
37616
  enabled: zod.z.boolean()
37574
37617
  });
37618
+ /**
37619
+ * Time contract of {@link CalendarEvent}.
37620
+ *
37621
+ * - Timed event: `startMs` / `endMs` are real instants; `endMs` is exclusive.
37622
+ * - All-day event (`allDay: true`): the event is a run of calendar DATES, not
37623
+ * instants. `startMs` is UTC midnight of the first date and `endMs` is UTC
37624
+ * midnight of the day AFTER the last date (RFC 5545 exclusive DTEND), so a
37625
+ * one-day event spans exactly 24 h. Every provider produces that and no
37626
+ * consumer may read the instants in a local timezone: use
37627
+ * {@link calendarEventLocalSpan}. Reading UTC midnight as local time put an
37628
+ * all-day event on the following day for any viewer ahead of UTC.
37629
+ */
37575
37630
  var CalendarEventSchema = zod.z.object({
37576
37631
  id: zod.z.string().min(1),
37577
37632
  calendarId: zod.z.string().min(1),
@@ -40757,7 +40812,15 @@ var HubAddressBookSchema = zod.z.object({
40757
40812
  publicEndpoint: HubAddressRoleResolutionSchema
40758
40813
  }),
40759
40814
  /** The `hub=` an app link carries (D709), or null. */
40760
- appLinkHub: zod.z.string().nullable()
40815
+ appLinkHub: zod.z.string().nullable(),
40816
+ /**
40817
+ * Every origin a browser can really reach this hub at, in the scheme each
40818
+ * listener serves: `https://<lan>:<httpsPort>`, and `http://<lan>:<httpPort>`
40819
+ * while LAN HTTP is on, plus the public and custom addresses. Distinct from
40820
+ * `rows`, which show `http://` only while LAN HTTP is on. Never loopback,
40821
+ * never the app link. Absent on a hub older than D794's amendment.
40822
+ */
40823
+ listenerOrigins: zod.z.array(zod.z.string()).readonly().optional()
40761
40824
  });
40762
40825
  var AllowedAddressesSchema = zod.z.object({
40763
40826
  /**
@@ -52446,6 +52509,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
52446
52509
  addonId: null,
52447
52510
  access: "view"
52448
52511
  },
52512
+ "backup.listAvailableDatabases": {
52513
+ capName: "backup",
52514
+ capScope: "system",
52515
+ addonId: null,
52516
+ access: "view"
52517
+ },
52449
52518
  "backup.listDestinations": {
52450
52519
  capName: "backup",
52451
52520
  capScope: "system",