@camstack/addon-waste-collections 0.1.35 → 0.1.37

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.
@@ -6,7 +6,7 @@ let node_fs = require("node:fs");
6
6
  let node_path = require("node:path");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_url = require("node:url");
9
- //#region ../types/dist/event-category-BmPm98n0.mjs
9
+ //#region ../types/dist/event-category-78tcBemo.mjs
10
10
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
11
11
  EventCategory["SystemBoot"] = "system.boot";
12
12
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -127,6 +127,23 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
127
127
  */
128
128
  EventCategory["AddonLoadRecovered"] = "addon.load-recovered";
129
129
  /**
130
+ * An addon is RUNNING although its installed package is below what the
131
+ * running server requires (`camstack.requires.addons` of the server, or the
132
+ * addon's own `requires.server`). Raised once per streak by the unsupported-
133
+ * addon tracker, on the hub and on agents; AlertCenter turns it into one
134
+ * operator alert per node and package. The addon is never held back for it.
135
+ *
136
+ * Payload: `{ packageName, addonId, nodeId, installedVersion, requiredMinimum, serverVersion, unmet }`.
137
+ */
138
+ EventCategory["AddonUnsupportedVersion"] = "addon.unsupported-version";
139
+ /**
140
+ * The package of an {@link AddonUnsupportedVersion} alert reached a supported
141
+ * version, or is no longer installed on the node. AlertCenter dismisses the matching alert.
142
+ *
143
+ * Payload: `{ packageName, addonId, nodeId }`.
144
+ */
145
+ EventCategory["AddonUnsupportedResolved"] = "addon.unsupported-resolved";
146
+ /**
130
147
  * Monitor scheduled the next retry for a failed addon. Transient —
131
148
  * surfaced to the UI for live-updating the "next retry in Ns"
132
149
  * countdown on the Addons page row, NOT persisted as an alert.
@@ -237,7 +254,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
237
254
  EventCategory["ProcessRestarted"] = "process.restarted";
238
255
  /**
239
256
  * The SET of storage locations changed — one was created, edited, enabled,
240
- * disabled or deleted through `storage.upsertLocation` / `deleteLocation`.
257
+ * disabled or deleted through the location instances or the forced-delete action.
241
258
  *
242
259
  * Telemetry, not a transaction (D8/D11): every consumer that re-resolves on
243
260
  * it must also converge on its own periodic path, because a dropped event
@@ -5988,7 +6005,7 @@ var ZodIssueCode = {
5988
6005
  var ZodFirstPartyTypeKind;
5989
6006
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5990
6007
  //#endregion
5991
- //#region ../types/dist/sleep-CdKka2ri.mjs
6008
+ //#region ../types/dist/sleep-AEnTN3b4.mjs
5992
6009
  /**
5993
6010
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5994
6011
  * window to float samples (D455).
@@ -6083,16 +6100,6 @@ var AddonMigrationsBlockedError = class extends Error {
6083
6100
  this.migrationIds = migrationIds;
6084
6101
  }
6085
6102
  };
6086
- /**
6087
- * The value a redacted secret is replaced with on every read surface.
6088
- *
6089
- * It is also the WRITE-BACK token: an `upsertLocation` that sends this value
6090
- * back for a key means "keep what is stored", which is what lets an operator
6091
- * rename a location without retyping its password. A literal an operator could
6092
- * plausibly choose as a real password would turn that convenience into a way
6093
- * to lock yourself out, hence the sentinel shape.
6094
- */
6095
- var REDACTED_SECRET = "__camstack_redacted__";
6096
6103
  /** Is this field's stored value a credential? */
6097
6104
  function isSecretConfigField(field) {
6098
6105
  if (field.type === "password") return true;
@@ -6734,10 +6741,33 @@ var RECORDS_ERROR_CODES = [
6734
6741
  "transact-refused"
6735
6742
  ];
6736
6743
  var PREFIX = "records/";
6744
+ /**
6745
+ * Cross-realm brand. An addon bundles its own copy of this module (D15) while
6746
+ * `ctx.records` is the HOST's door, so an error thrown by the host is an
6747
+ * instance of the host's classes, not of the addon's. The brand plus the code
6748
+ * make `instanceof` answer by identity of the failure, not of the copy.
6749
+ */
6750
+ var RECORDS_ERROR_BRAND = Symbol.for("camstack.records-error");
6751
+ function carriesBrand(value) {
6752
+ return typeof value === "object" && value !== null && Reflect.get(value, RECORDS_ERROR_BRAND) === true;
6753
+ }
6737
6754
  /** The base of every records failure. `detail` is the human part of the message. */
6738
6755
  var RecordsError = class extends Error {
6756
+ /** The code a subclass stands for; the base stands for every code. */
6757
+ static code = void 0;
6758
+ /**
6759
+ * `instanceof` across copies of this module: any branded error is a
6760
+ * `RecordsError`, and is an instance of a subclass only when its code is the
6761
+ * subclass's code. Same-copy answers are unchanged.
6762
+ */
6763
+ static [Symbol.hasInstance](value) {
6764
+ if (Function.prototype[Symbol.hasInstance].call(this, value)) return true;
6765
+ if (!carriesBrand(value)) return false;
6766
+ return this.code === void 0 || value.code === this.code;
6767
+ }
6739
6768
  code;
6740
6769
  detail;
6770
+ [RECORDS_ERROR_BRAND] = true;
6741
6771
  constructor(code, detail) {
6742
6772
  super(`${PREFIX}${code}: ${detail}`);
6743
6773
  this.name = "RecordsError";
@@ -6746,96 +6776,110 @@ var RecordsError = class extends Error {
6746
6776
  }
6747
6777
  };
6748
6778
  /** A write guarded by `ifRev` found another revision (or an existing row for `ifRev: 0`). */
6749
- var RevConflictError = class extends RecordsError {
6779
+ var RevConflictError = class RevConflictError extends RecordsError {
6780
+ static code = "rev-conflict";
6750
6781
  constructor(detail) {
6751
- super("rev-conflict", detail);
6782
+ super(RevConflictError.code, detail);
6752
6783
  this.name = "RevConflictError";
6753
6784
  }
6754
6785
  };
6755
6786
  /** `data` exceeds the kind's `maxDataBytes`. Bytes are never record fields (D613). */
6756
- var RecordTooLargeError = class extends RecordsError {
6787
+ var RecordTooLargeError = class RecordTooLargeError extends RecordsError {
6788
+ static code = "too-large";
6757
6789
  constructor(detail) {
6758
- super("too-large", detail);
6790
+ super(RecordTooLargeError.code, detail);
6759
6791
  this.name = "RecordTooLargeError";
6760
6792
  }
6761
6793
  };
6762
6794
  /** A filter, order or aggregate names a field no declared index serves. */
6763
- var UnindexedFilterError = class extends RecordsError {
6795
+ var UnindexedFilterError = class UnindexedFilterError extends RecordsError {
6796
+ static code = "unindexed-filter";
6764
6797
  constructor(detail) {
6765
- super("unindexed-filter", detail);
6798
+ super(UnindexedFilterError.code, detail);
6766
6799
  this.name = "UnindexedFilterError";
6767
6800
  }
6768
6801
  };
6769
6802
  /** A bulk delete with no predicate at all. */
6770
- var FilterRequiredError = class extends RecordsError {
6803
+ var FilterRequiredError = class FilterRequiredError extends RecordsError {
6804
+ static code = "filter-required";
6771
6805
  constructor(detail) {
6772
- super("filter-required", detail);
6806
+ super(FilterRequiredError.code, detail);
6773
6807
  this.name = "FilterRequiredError";
6774
6808
  }
6775
6809
  };
6776
6810
  /** The caller declared no such kind (another addon's kind is indistinguishable from none). */
6777
- var UnknownRecordKindError = class extends RecordsError {
6811
+ var UnknownRecordKindError = class UnknownRecordKindError extends RecordsError {
6812
+ static code = "unknown-kind";
6778
6813
  constructor(detail) {
6779
- super("unknown-kind", detail);
6814
+ super(UnknownRecordKindError.code, detail);
6780
6815
  this.name = "UnknownRecordKindError";
6781
6816
  }
6782
6817
  };
6783
- var RecordNotFoundError = class extends RecordsError {
6818
+ var RecordNotFoundError = class RecordNotFoundError extends RecordsError {
6819
+ static code = "not-found";
6784
6820
  constructor(detail) {
6785
- super("not-found", detail);
6821
+ super(RecordNotFoundError.code, detail);
6786
6822
  this.name = "RecordNotFoundError";
6787
6823
  }
6788
6824
  };
6789
6825
  /** A unique index refused the write. */
6790
- var RecordUniqueViolationError = class extends RecordsError {
6826
+ var RecordUniqueViolationError = class RecordUniqueViolationError extends RecordsError {
6827
+ static code = "unique-violation";
6791
6828
  constructor(detail) {
6792
- super("unique-violation", detail);
6829
+ super(RecordUniqueViolationError.code, detail);
6793
6830
  this.name = "RecordUniqueViolationError";
6794
6831
  }
6795
6832
  };
6796
6833
  /** A secret field held a plain string where only a keychain marker may be stored. */
6797
- var PlaintextSecretError = class extends RecordsError {
6834
+ var PlaintextSecretError = class PlaintextSecretError extends RecordsError {
6835
+ static code = "plaintext-secret";
6798
6836
  constructor(detail) {
6799
- super("plaintext-secret", detail);
6837
+ super(PlaintextSecretError.code, detail);
6800
6838
  this.name = "PlaintextSecretError";
6801
6839
  }
6802
6840
  };
6803
6841
  /** A marker whose ref is not the row's own (the D878 rule). */
6804
- var ForeignSecretMarkerError = class extends RecordsError {
6842
+ var ForeignSecretMarkerError = class ForeignSecretMarkerError extends RecordsError {
6843
+ static code = "foreign-marker";
6805
6844
  constructor(detail) {
6806
- super("foreign-marker", detail);
6845
+ super(ForeignSecretMarkerError.code, detail);
6807
6846
  this.name = "ForeignSecretMarkerError";
6808
6847
  }
6809
6848
  };
6810
6849
  /** The keychain could not store or serve a value: explicit, never an empty string. */
6811
- var RecordSecretUnavailableError = class extends RecordsError {
6850
+ var RecordSecretUnavailableError = class RecordSecretUnavailableError extends RecordsError {
6851
+ static code = "secret-unavailable";
6812
6852
  constructor(detail) {
6813
- super("secret-unavailable", detail);
6853
+ super(RecordSecretUnavailableError.code, detail);
6814
6854
  this.name = "RecordSecretUnavailableError";
6815
6855
  }
6816
6856
  };
6817
- var InvalidRecordError = class extends RecordsError {
6857
+ var InvalidRecordError = class InvalidRecordError extends RecordsError {
6858
+ static code = "invalid-record";
6818
6859
  constructor(detail) {
6819
- super("invalid-record", detail);
6860
+ super(InvalidRecordError.code, detail);
6820
6861
  this.name = "InvalidRecordError";
6821
6862
  }
6822
6863
  };
6823
6864
  /** A kind declaration the engine refuses (non-additive change, bad shape, wrong file). */
6824
- var DeclarationRefusedError = class extends RecordsError {
6865
+ var DeclarationRefusedError = class DeclarationRefusedError extends RecordsError {
6866
+ static code = "declaration-refused";
6825
6867
  constructor(detail) {
6826
- super("declaration-refused", detail);
6868
+ super(DeclarationRefusedError.code, detail);
6827
6869
  this.name = "DeclarationRefusedError";
6828
6870
  }
6829
6871
  };
6830
- var NoCallerError = class extends RecordsError {
6872
+ var NoCallerError = class NoCallerError extends RecordsError {
6873
+ static code = "no-caller";
6831
6874
  constructor(detail) {
6832
- super("no-caller", detail);
6875
+ super(NoCallerError.code, detail);
6833
6876
  this.name = "NoCallerError";
6834
6877
  }
6835
6878
  };
6836
- var BatchTooLargeError = class extends RecordsError {
6879
+ var BatchTooLargeError = class BatchTooLargeError extends RecordsError {
6880
+ static code = "batch-too-large";
6837
6881
  constructor(detail) {
6838
- super("batch-too-large", detail);
6882
+ super(BatchTooLargeError.code, detail);
6839
6883
  this.name = "BatchTooLargeError";
6840
6884
  }
6841
6885
  };
@@ -6844,9 +6888,10 @@ var BatchTooLargeError = class extends RecordsError {
6844
6888
  * caller did not declare in ONE file and ONE `transactFamily`. The detail names
6845
6889
  * which rule (`cross-file`, `cross-family`, `no-family`); it is never a silent split.
6846
6890
  */
6847
- var TransactRefusedError = class extends RecordsError {
6891
+ var TransactRefusedError = class TransactRefusedError extends RecordsError {
6892
+ static code = "transact-refused";
6848
6893
  constructor(detail) {
6849
- super("transact-refused", detail);
6894
+ super(TransactRefusedError.code, detail);
6850
6895
  this.name = "TransactRefusedError";
6851
6896
  }
6852
6897
  };
@@ -7365,17 +7410,35 @@ var RecordIntegrationInstances = class {
7365
7410
  ...kind !== void 0 ? { kind } : {}
7366
7411
  });
7367
7412
  if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
7413
+ const initial = this.hooks.initialBody(name, kind);
7414
+ const patch = this.patchOf(values ?? {});
7415
+ if (Object.keys(patch).length > 0) Object.assign(patch, await this.hooks.validateWrite?.({
7416
+ kind,
7417
+ body: {
7418
+ ...initial,
7419
+ ...patch
7420
+ }
7421
+ }));
7368
7422
  await this.handle.put({
7369
7423
  id: minted,
7370
- data: this.hooks.initialBody(name, kind)
7424
+ data: initial
7371
7425
  }, { ifRev: 0 });
7372
- const patch = this.patchOf(values ?? {});
7373
7426
  if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
7374
7427
  await this.hooks.onChanged?.(minted, "created");
7375
7428
  return { id: minted };
7376
7429
  };
7377
7430
  updateInstance = async ({ id, name, values }) => {
7378
7431
  const patch = this.patchOf(values);
7432
+ if (this.hooks.validateWrite !== void 0 && Object.keys(patch).length > 0) {
7433
+ const stored = await this.handle.get(id);
7434
+ if (stored !== null) Object.assign(patch, await this.hooks.validateWrite({
7435
+ kind: void 0,
7436
+ body: {
7437
+ ...stored.data,
7438
+ ...patch
7439
+ }
7440
+ }));
7441
+ }
7379
7442
  if (name !== void 0) patch[this.nameField] = name;
7380
7443
  await this.handle.patch(id, patch);
7381
7444
  await this.hooks.onChanged?.(id, "updated");
@@ -10739,7 +10802,6 @@ var CAP_NAMES = Object.freeze([
10739
10802
  "notifier",
10740
10803
  "numeric-sensor",
10741
10804
  "oauth-integration",
10742
- "oidc-connections",
10743
10805
  "osd",
10744
10806
  "osd-manager",
10745
10807
  "pet-feeder",
@@ -12631,8 +12693,7 @@ Object.freeze({
12631
12693
  "device-provider": { "reloadDevice": 18e4 },
12632
12694
  "llm": {
12633
12695
  "generate": 6e5,
12634
- "generateVision": 6e5,
12635
- "testProfile": 6e5
12696
+ "generateVision": 6e5
12636
12697
  },
12637
12698
  "llm-runtime": {
12638
12699
  "complete": 6e5,
@@ -14035,17 +14096,18 @@ var IntegrationInstanceRefSchema = object({
14035
14096
  * message to what the kind supports — callers never special-case a service.
14036
14097
  *
14037
14098
  * DESIGN DECISIONS (locked):
14038
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
14039
- * `setTargetEnabled`), each provider persisting via the `settings-store`
14040
- * cap. Rationale: the admin UI needs one uniform surface across the
14041
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
14042
- * alternative would fork the UI per addon and cannot host the
14043
- * discovery→adopt flow.
14044
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
14045
- * the generated cap-mount auto-`concatCollection`-fans them across every
14046
- * registered provider (notifiers addon + HA addon) so one catalog is
14047
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
14048
- * `addonId` the generated collection router extracts from the call input.
14099
+ * - A target is an INSTANCE of this function cap (epic #440, step 6): the
14100
+ * providers serve their targets as `integration-instances` records, one
14101
+ * record kind per target kind, and the admin lists, adds, edits, switches
14102
+ * and deletes them through that cap. This cap keeps what an instance cannot
14103
+ * say: the kind catalogue (`listTargetKinds`: capabilities, delivery
14104
+ * parameters, brand icon), dispatch (`send`, `testTarget`) and the form
14105
+ * helpers (`discoverTargets`, `probeTargetConfig`).
14106
+ * - `listTargetKinds` / `discoverTargets` return arrays → the generated
14107
+ * cap-mount auto-`concatCollection`-fans them across every registered
14108
+ * provider so one catalog is routable. `send` / `testTarget` /
14109
+ * `probeTargetConfig` route to ONE provider by the `addonId` the generated
14110
+ * collection router extracts from the call input.
14049
14111
  * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
14050
14112
  * transformer) + UDS MsgPack both round-trip typed arrays — already used by
14051
14113
  * `storage` / `storage-provider` / `recording` caps over the same path. No
@@ -14290,7 +14352,7 @@ var TargetKindCapsSchema = object({
14290
14352
  * the union is large and not meant for runtime validation here; the exported
14291
14353
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
14292
14354
  */
14293
- var ConfigSchemaPassthrough$4 = unknown();
14355
+ var ConfigSchemaPassthrough$3 = unknown();
14294
14356
  var TargetKindSchema = object({
14295
14357
  kind: string(),
14296
14358
  label: string(),
@@ -14327,7 +14389,7 @@ var TargetKindSchema = object({
14327
14389
  * its raster path, which is the safe default for an unknown image.
14328
14390
  */
14329
14391
  iconMediaType: string().optional(),
14330
- configSchema: ConfigSchemaPassthrough$4,
14392
+ configSchema: ConfigSchemaPassthrough$3,
14331
14393
  /**
14332
14394
  * The config keys that decide WHERE this kind's secrets are sent (server,
14333
14395
  * base URL, instance, chat, recipient). A save that changes any of them
@@ -14349,9 +14411,11 @@ var TargetKindSchema = object({
14349
14411
  caps: TargetKindCapsSchema
14350
14412
  });
14351
14413
  /**
14352
- * A persisted target. `config` holds secrets; providers REDACT secret fields
14353
- * (return a presence marker only) when serving `listTargets` — never
14354
- * round-trip a stored secret to the UI.
14414
+ * A target as a provider's own code sees it (the notification-center bridge, the
14415
+ * send path): the flat record body split into the name, the switch and the
14416
+ * kind's fields. `config` holds secrets; a reader that serves it to a client
14417
+ * REDACTS secret fields (a presence marker only) — never round-trip a stored
14418
+ * secret to the UI.
14355
14419
  */
14356
14420
  var TargetSchema = object({
14357
14421
  id: string(),
@@ -14394,13 +14458,13 @@ var ProbeTargetConfigResultSchema = object({
14394
14458
  stateInstance: string().optional(),
14395
14459
  error: string().optional()
14396
14460
  });
14397
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
14461
+ method(object({}), array(TargetKindSchema)), method(object({
14398
14462
  kind: string(),
14399
14463
  config: record(string(), unknown()).optional(),
14400
14464
  /**
14401
14465
  * When editing / duplicating, the blank password field never round-trips
14402
14466
  * the stored token. Pass the source target id so the provider can restore
14403
- * secrets server-side before dialling (same merge as upsert).
14467
+ * secrets server-side before dialling.
14404
14468
  */
14405
14469
  targetId: string().optional()
14406
14470
  }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
@@ -14423,27 +14487,6 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
14423
14487
  }), TestResultSchema, {
14424
14488
  kind: "mutation",
14425
14489
  auth: "admin"
14426
- }), method(object({
14427
- target: TargetSchema,
14428
- /**
14429
- * Duplicate flow: create a NEW target id while copying secrets from an
14430
- * existing row. The UI never holds the stored token; blank / redacted
14431
- * password fields are restored from this source (D634 still applies if
14432
- * destinations diverge without a re-typed secret).
14433
- */
14434
- cloneSecretsFrom: string().optional()
14435
- }), TargetSchema, {
14436
- kind: "mutation",
14437
- auth: "admin"
14438
- }), method(object({ targetId: string() }), _void(), {
14439
- kind: "mutation",
14440
- auth: "admin"
14441
- }), method(object({
14442
- targetId: string(),
14443
- enabled: boolean()
14444
- }), _void(), {
14445
- kind: "mutation",
14446
- auth: "admin"
14447
14490
  });
14448
14491
  looseObject({
14449
14492
  name: string().min(1),
@@ -22701,7 +22744,6 @@ var SYSTEM_CAP_VOCABULARY = {
22701
22744
  "notification-output": system("notification delivery — reached through notifier devices and rules"),
22702
22745
  "notification-rules": system("notification rules — authored on their own page"),
22703
22746
  "oauth-integration": system(IDENTITY),
22704
- "oidc-connections": system(IDENTITY),
22705
22747
  "osd-manager": system(PIPELINE),
22706
22748
  "pipeline-executor": system(PIPELINE),
22707
22749
  "pipeline-orchestrator": system(PIPELINE),
@@ -54389,7 +54431,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
54389
54431
  * or the redaction token KEEPS the stored secret; `{ $secretClear: true }`
54390
54432
  * clears it.
54391
54433
  */
54392
- var ConfigSchemaPassthrough$3 = custom();
54434
+ var ConfigSchemaPassthrough$2 = custom();
54393
54435
  var IntegrationInstanceIdSchema = string().min(1);
54394
54436
  var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
54395
54437
  /**
@@ -54435,7 +54477,16 @@ var IntegrationInstanceKindSchema = object({
54435
54477
  /** Relative hub path to start an interactive account link of an instance of this kind. */
54436
54478
  connectPath: string().min(1).optional(),
54437
54479
  /** Relative hub path to disconnect that account. */
54438
- disconnectPath: string().min(1).optional()
54480
+ disconnectPath: string().min(1).optional(),
54481
+ /**
54482
+ * Parts of the edit dialog the flat form cannot draw (a node picker, a model source), named by a
54483
+ * GENERIC widget id the client maps to a component; a client that knows none of them shows none.
54484
+ * `connection` widgets write connection values, `model` widgets own the model choice.
54485
+ */
54486
+ widgets: array(object({
54487
+ id: string().min(1),
54488
+ placement: _enum(["connection", "model"])
54489
+ })).optional()
54439
54490
  });
54440
54491
  /**
54441
54492
  * How devices of an instance are found. `none`: the operator adds them by hand; `lan`: the addon
@@ -54540,7 +54591,7 @@ var integrationInstancesCapability = {
54540
54591
  id: IntegrationInstanceIdSchema.nullable(),
54541
54592
  /** The kind of the create form (`id: null`) of a provider with several. */
54542
54593
  kind: string().min(1).optional()
54543
- }), ConfigSchemaPassthrough$3, { auth: "admin" }),
54594
+ }), ConfigSchemaPassthrough$2, { auth: "admin" }),
54544
54595
  listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
54545
54596
  getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
54546
54597
  /**
@@ -55491,12 +55542,12 @@ var LlmModelInfoSchema = object({
55491
55542
  });
55492
55543
  /**
55493
55544
  * The provider-specific part of a profile, validated by the provider that owns
55494
- * the kind (`upsertProfile` refuses what it does not accept). A field its
55545
+ * the kind (saving an instance refuses what it does not accept). A field its
55495
55546
  * `connectionSchema` declares `type: 'password'` is a secret: it is held in the
55496
55547
  * keychain, never in the row, and reads back as the redaction marker.
55497
55548
  */
55498
55549
  var LlmProviderConfigSchema = record(string(), unknown());
55499
- var LlmProfileSchema = object({
55550
+ object({
55500
55551
  id: string(),
55501
55552
  name: string(),
55502
55553
  /** The provider-declared kind (`LlmProfileKindDescriptor.kind`). */
@@ -55590,8 +55641,8 @@ var LlmProviderWidgetSchema = object({
55590
55641
  /** ConfigUISchema tree passed through untyped on the wire (the
55591
55642
  * notification-output `ConfigSchemaPassthrough` precedent at
55592
55643
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
55593
- var ConfigSchemaPassthrough$2 = unknown();
55594
- var LlmProfileKindDescriptorSchema = object({
55644
+ var ConfigSchemaPassthrough$1 = unknown();
55645
+ object({
55595
55646
  kind: LlmProfileKindSchema,
55596
55647
  label: string(),
55597
55648
  /** A generic icon name (`cloud`, `plug`, `cpu`) — never a glyph or a logo. */
@@ -55599,9 +55650,9 @@ var LlmProfileKindDescriptorSchema = object({
55599
55650
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
55600
55651
  addonId: string(),
55601
55652
  /** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
55602
- connectionSchema: ConfigSchemaPassthrough$2,
55653
+ connectionSchema: ConfigSchemaPassthrough$1,
55603
55654
  /** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
55604
- advancedSchema: ConfigSchemaPassthrough$2.optional(),
55655
+ advancedSchema: ConfigSchemaPassthrough$1.optional(),
55605
55656
  /** The kind's defaults; a model from `listModels` may override any of them. */
55606
55657
  features: LlmModelFeaturesSchema,
55607
55658
  /** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
@@ -55730,20 +55781,7 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
55730
55781
  }), method(object({
55731
55782
  addonId: string().optional(),
55732
55783
  requestId: string()
55733
- }), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
55734
- kind: "mutation",
55735
- auth: "admin"
55736
- }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
55737
- kind: "mutation",
55738
- auth: "admin"
55739
- }), method(ProfileRefInputSchema, _void(), {
55740
- kind: "mutation",
55741
- auth: "admin"
55742
- }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
55743
- kind: "mutation",
55744
- auth: "admin",
55745
- timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
55746
- }), method(LlmListModelsInputSchema, LlmListModelsResultSchema, {
55784
+ }), _void(), { kind: "mutation" }), method(LlmListModelsInputSchema, LlmListModelsResultSchema, {
55747
55785
  kind: "mutation",
55748
55786
  auth: "admin"
55749
55787
  }), method(object({}), array(LlmDefaultSchema)), method(object({
@@ -56738,20 +56776,7 @@ method(_void(), MeshStatusSchema), method(object({
56738
56776
  /** Optional hostname override the host should advertise once joined. */
56739
56777
  hostname: string().optional() }), object({
56740
56778
  /** Authentication URL the operator should open in a browser. */
56741
- loginUrl: string() }), { kind: "mutation" }), method(_void(), object({ left: literal(true) }), { kind: "mutation" }), method(_void(), object({ loggedOut: literal(true) }), { kind: "mutation" }), method(_void(), object({ peers: array(MeshPeerSchema).readonly() })), method(object({
56742
- /** Optional auth key — when provided, probes the key validity
56743
- * against the provider's API. Omit when already joined to
56744
- * just ping the daemon. */
56745
- authKey: string().optional() }), object({
56746
- ok: boolean(),
56747
- /** Provider-side identifier resolved by the probe (tailnet
56748
- * name for Tailscale, network id for ZeroTier, etc.). */
56749
- tenant: string().optional(),
56750
- /** Daemon binary version, when reachable. */
56751
- daemonVersion: string().optional(),
56752
- /** Human-readable error when `ok: false`. */
56753
- error: string().optional()
56754
- }), { kind: "mutation" });
56779
+ loginUrl: string() }), { kind: "mutation" }), method(_void(), object({ left: literal(true) }), { kind: "mutation" }), method(_void(), object({ loggedOut: literal(true) }), { kind: "mutation" }), method(_void(), object({ peers: array(MeshPeerSchema).readonly() }));
56755
56780
  object({
56756
56781
  value: number().nullable().meta(readingMeta("device", { label: "Total" })),
56757
56782
  lastFetchedAt: number()
@@ -66409,61 +66434,6 @@ var OauthIntegrationDescriptorSchema = object({
66409
66434
  });
66410
66435
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
66411
66436
  /**
66412
- * `oidc-connections` — CRUD for IdP rows under `auth-oidc` (D828 sibling).
66413
- *
66414
- * One hub-only singleton (`auth-oidc`). The Settings UI is a DataTable +
66415
- * ItemFormDialog; there is no `editable-array` in `globalSettingsSchema`.
66416
- * Operator-chosen `id` is the URL slug (`/addon/auth-oidc/<id>/…`) and is
66417
- * immutable after create.
66418
- */
66419
- var ConfigSchemaPassthrough$1 = unknown();
66420
- var OidcConnectionSchema = object({
66421
- id: string().min(1),
66422
- displayName: string(),
66423
- icon: string(),
66424
- issuerUrl: string(),
66425
- clientId: string(),
66426
- /** Client id + secret present — never the secret value. */
66427
- configured: boolean(),
66428
- /**
66429
- * Values for the connection form schema. `clientSecret` is a keychain
66430
- * {@link SecretFieldView} or `null` / `''`, never plaintext (D789).
66431
- */
66432
- formValues: record(string(), unknown()).optional()
66433
- });
66434
- /**
66435
- * Patch one connection. Omitted or empty `clientSecret` keeps the stored
66436
- * secret (D787). `{ $secretClear: true }` clears it. `id` is never renamed.
66437
- */
66438
- var OidcConnectionUpdateInputSchema = object({
66439
- id: string().min(1),
66440
- displayName: string().min(1),
66441
- icon: string().optional(),
66442
- issuerUrl: string().optional(),
66443
- clientId: string().optional(),
66444
- clientSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
66445
- redirectUri: string().optional(),
66446
- scopes: string().optional(),
66447
- usernameClaim: _enum([
66448
- "preferred_username",
66449
- "email",
66450
- "sub"
66451
- ]).optional()
66452
- });
66453
- method(object({}), ConfigSchemaPassthrough$1, { auth: "admin" }), method(object({}), array(OidcConnectionSchema).readonly(), { auth: "admin" }), method(object({
66454
- id: string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug"),
66455
- displayName: string().min(1)
66456
- }), object({ id: string() }), {
66457
- kind: "mutation",
66458
- auth: "admin"
66459
- }), method(OidcConnectionUpdateInputSchema, object({ ok: literal(true) }), {
66460
- kind: "mutation",
66461
- auth: "admin"
66462
- }), method(object({ id: string().min(1) }), object({ ok: literal(true) }), {
66463
- kind: "mutation",
66464
- auth: "admin"
66465
- });
66466
- /**
66467
66437
  * Generic on-screen-display (video overlay) cap. Each camera exposes
66468
66438
  * a firmware-defined list of overlays (channel name, timestamp,
66469
66439
  * watermark, logo, …); the cap models them as a homogeneous array of
@@ -72636,21 +72606,6 @@ method(object({
72636
72606
  */
72637
72607
  ttlSec: union([number().int().positive(), literal("never")]).optional()
72638
72608
  }), object({ token: string() }), { auth: "admin" }), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable(), { auth: "admin" });
72639
- var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
72640
- providerId: string().min(1),
72641
- displayName: string().min(1),
72642
- configSchema: unknown(),
72643
- nodeLocal: boolean(),
72644
- shouldSaveDiskSpace: literal(true),
72645
- minFreePercent: number().min(0).max(100)
72646
- }), object({
72647
- providerId: string().min(1),
72648
- displayName: string().min(1),
72649
- configSchema: unknown(),
72650
- nodeLocal: boolean(),
72651
- shouldSaveDiskSpace: literal(false),
72652
- minFreePercent: literal(null)
72653
- })]);
72654
72609
  method(object({
72655
72610
  location: StorageLocationRefSchema,
72656
72611
  relativePath: string()
@@ -72712,42 +72667,7 @@ method(object({
72712
72667
  }), _instanceof(Uint8Array), { auth: "admin" }), method(object({ downloadId: string() }), _void(), {
72713
72668
  kind: "mutation",
72714
72669
  auth: "admin"
72715
- }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
72716
- createdAt: true,
72717
- updatedAt: true
72718
- }), StorageLocationSchema, {
72719
- kind: "mutation",
72720
- auth: "admin"
72721
- }), method(object({
72722
- id: string(),
72723
- force: boolean().optional()
72724
- }), _void(), {
72725
- kind: "mutation",
72726
- auth: "admin"
72727
- }), method(_void(), array(StorageDrainProgressSchema).readonly()), method(object({ id: string() }), object({
72728
- ok: boolean(),
72729
- error: string().optional()
72730
- }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
72731
- providerId: string(),
72732
- /**
72733
- * The location this config is an UNSAVED edit of, when there is one.
72734
- *
72735
- * `listLocations` replaces every declared secret with the redaction
72736
- * sentinel, so the edit modal's form state holds the sentinel for any
72737
- * credential the operator did not retype — and posting that here
72738
- * without a way to resolve it makes the provider try to authenticate
72739
- * as `__camstack_redacted__` and report the operator's own working
72740
- * password as wrong. Given this id, the orchestrator restores each
72741
- * sentinel from the stored config (same rule as `upsertLocation`)
72742
- * before dispatching. Omitted by the "Add location" wizard, where
72743
- * every value was typed just now and nothing is stored yet.
72744
- */
72745
- locationId: string().optional(),
72746
- config: record(string(), unknown())
72747
- }), object({
72748
- ok: boolean(),
72749
- error: string().optional()
72750
- }), { auth: "admin" });
72670
+ }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(_void(), array(StorageDrainProgressSchema).readonly());
72751
72671
  /** One table of the settings database. */
72752
72672
  var StorageTableSchema = object({
72753
72673
  /** The table's full name, e.g. `recorder:addon-settings` or `addon-device-settings`. */
@@ -80340,13 +80260,6 @@ Object.freeze({
80340
80260
  access: "delete",
80341
80261
  role: "admin"
80342
80262
  },
80343
- "llm.deleteProfile": {
80344
- capName: "llm",
80345
- capScope: "system",
80346
- addonId: null,
80347
- access: "delete",
80348
- role: "admin"
80349
- },
80350
80263
  "llm.generate": {
80351
80264
  capName: "llm",
80352
80265
  capScope: "system",
@@ -80410,20 +80323,6 @@ Object.freeze({
80410
80323
  access: "view",
80411
80324
  role: "admin"
80412
80325
  },
80413
- "llm.listProfileKinds": {
80414
- capName: "llm",
80415
- capScope: "system",
80416
- addonId: null,
80417
- access: "view",
80418
- role: "admin"
80419
- },
80420
- "llm.listProfiles": {
80421
- capName: "llm",
80422
- capScope: "system",
80423
- addonId: null,
80424
- access: "view",
80425
- role: "admin"
80426
- },
80427
80326
  "llm.listRuntimeNodes": {
80428
80327
  capName: "llm",
80429
80328
  capScope: "system",
@@ -80431,13 +80330,6 @@ Object.freeze({
80431
80330
  access: "view",
80432
80331
  role: "admin"
80433
80332
  },
80434
- "llm.migrateRawSecrets": {
80435
- capName: "llm",
80436
- capScope: "system",
80437
- addonId: null,
80438
- access: "create",
80439
- role: "admin"
80440
- },
80441
80333
  "llm.resolveModelRef": {
80442
80334
  capName: "llm",
80443
80335
  capScope: "system",
@@ -80466,20 +80358,6 @@ Object.freeze({
80466
80358
  access: "create",
80467
80359
  role: "admin"
80468
80360
  },
80469
- "llm.testProfile": {
80470
- capName: "llm",
80471
- capScope: "system",
80472
- addonId: null,
80473
- access: "create",
80474
- role: "admin"
80475
- },
80476
- "llm.upsertProfile": {
80477
- capName: "llm",
80478
- capScope: "system",
80479
- addonId: null,
80480
- access: "create",
80481
- role: "admin"
80482
- },
80483
80361
  "llmRuntime.appleAvailability": {
80484
80362
  capName: "llm-runtime",
80485
80363
  capScope: "system",
@@ -80984,13 +80862,6 @@ Object.freeze({
80984
80862
  access: "create",
80985
80863
  role: "admin"
80986
80864
  },
80987
- "meshNetwork.testConnection": {
80988
- capName: "mesh-network",
80989
- capScope: "system",
80990
- addonId: null,
80991
- access: "create",
80992
- role: "admin"
80993
- },
80994
80865
  "meter.getStatus": {
80995
80866
  capName: "meter",
80996
80867
  capScope: "device",
@@ -81649,13 +81520,6 @@ Object.freeze({
81649
81520
  access: "create",
81650
81521
  role: "admin"
81651
81522
  },
81652
- "notificationOutput.deleteTarget": {
81653
- capName: "notification-output",
81654
- capScope: "system",
81655
- addonId: null,
81656
- access: "delete",
81657
- role: "admin"
81658
- },
81659
81523
  "notificationOutput.discoverTargets": {
81660
81524
  capName: "notification-output",
81661
81525
  capScope: "system",
@@ -81663,13 +81527,6 @@ Object.freeze({
81663
81527
  access: "view",
81664
81528
  role: "admin"
81665
81529
  },
81666
- "notificationOutput.getTarget": {
81667
- capName: "notification-output",
81668
- capScope: "system",
81669
- addonId: null,
81670
- access: "view",
81671
- role: "admin"
81672
- },
81673
81530
  "notificationOutput.listTargetKinds": {
81674
81531
  capName: "notification-output",
81675
81532
  capScope: "system",
@@ -81677,13 +81534,6 @@ Object.freeze({
81677
81534
  access: "view",
81678
81535
  role: "admin"
81679
81536
  },
81680
- "notificationOutput.listTargets": {
81681
- capName: "notification-output",
81682
- capScope: "system",
81683
- addonId: null,
81684
- access: "view",
81685
- role: "admin"
81686
- },
81687
81537
  "notificationOutput.probeTargetConfig": {
81688
81538
  capName: "notification-output",
81689
81539
  capScope: "system",
@@ -81698,13 +81548,6 @@ Object.freeze({
81698
81548
  access: "create",
81699
81549
  role: "admin"
81700
81550
  },
81701
- "notificationOutput.setTargetEnabled": {
81702
- capName: "notification-output",
81703
- capScope: "system",
81704
- addonId: null,
81705
- access: "create",
81706
- role: "admin"
81707
- },
81708
81551
  "notificationOutput.testTarget": {
81709
81552
  capName: "notification-output",
81710
81553
  capScope: "system",
@@ -81712,13 +81555,6 @@ Object.freeze({
81712
81555
  access: "create",
81713
81556
  role: "admin"
81714
81557
  },
81715
- "notificationOutput.upsertTarget": {
81716
- capName: "notification-output",
81717
- capScope: "system",
81718
- addonId: null,
81719
- access: "create",
81720
- role: "admin"
81721
- },
81722
81558
  "notificationRules.activateAlarmProfile": {
81723
81559
  capName: "notification-rules",
81724
81560
  capScope: "system",
@@ -81978,41 +81814,6 @@ Object.freeze({
81978
81814
  access: "view",
81979
81815
  role: "admin"
81980
81816
  },
81981
- "oidcConnections.connectionFormSchema": {
81982
- capName: "oidc-connections",
81983
- capScope: "system",
81984
- addonId: null,
81985
- access: "view",
81986
- role: "admin"
81987
- },
81988
- "oidcConnections.createConnection": {
81989
- capName: "oidc-connections",
81990
- capScope: "system",
81991
- addonId: null,
81992
- access: "create",
81993
- role: "admin"
81994
- },
81995
- "oidcConnections.deleteConnection": {
81996
- capName: "oidc-connections",
81997
- capScope: "system",
81998
- addonId: null,
81999
- access: "delete",
82000
- role: "admin"
82001
- },
82002
- "oidcConnections.listConnections": {
82003
- capName: "oidc-connections",
82004
- capScope: "system",
82005
- addonId: null,
82006
- access: "view",
82007
- role: "admin"
82008
- },
82009
- "oidcConnections.updateConnection": {
82010
- capName: "oidc-connections",
82011
- capScope: "system",
82012
- addonId: null,
82013
- access: "create",
82014
- role: "admin"
82015
- },
82016
81817
  "osd.getStatus": {
82017
81818
  capName: "osd",
82018
81819
  capScope: "device",
@@ -85044,13 +84845,6 @@ Object.freeze({
85044
84845
  access: "delete",
85045
84846
  role: "admin"
85046
84847
  },
85047
- "storage.deleteLocation": {
85048
- capName: "storage",
85049
- capScope: "system",
85050
- addonId: null,
85051
- access: "delete",
85052
- role: "admin"
85053
- },
85054
84848
  "storage.endDownload": {
85055
84849
  capName: "storage",
85056
84850
  capScope: "system",
@@ -85107,13 +84901,6 @@ Object.freeze({
85107
84901
  access: "view",
85108
84902
  role: "admin"
85109
84903
  },
85110
- "storage.listProviders": {
85111
- capName: "storage",
85112
- capScope: "system",
85113
- addonId: null,
85114
- access: "view",
85115
- role: "admin"
85116
- },
85117
84904
  "storage.read": {
85118
84905
  capName: "storage",
85119
84906
  capScope: "system",
@@ -85135,27 +84922,6 @@ Object.freeze({
85135
84922
  access: "view",
85136
84923
  role: "admin"
85137
84924
  },
85138
- "storage.testConfig": {
85139
- capName: "storage",
85140
- capScope: "system",
85141
- addonId: null,
85142
- access: "view",
85143
- role: "admin"
85144
- },
85145
- "storage.testLocation": {
85146
- capName: "storage",
85147
- capScope: "system",
85148
- addonId: null,
85149
- access: "view",
85150
- role: "admin"
85151
- },
85152
- "storage.upsertLocation": {
85153
- capName: "storage",
85154
- capScope: "system",
85155
- addonId: null,
85156
- access: "create",
85157
- role: "admin"
85158
- },
85159
84925
  "storage.write": {
85160
84926
  capName: "storage",
85161
84927
  capScope: "system",
@@ -91308,20 +91074,6 @@ object({
91308
91074
  });
91309
91075
  //#endregion
91310
91076
  //#region src/custom-actions.ts
91311
- var SourceIdSchema = string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug");
91312
- /**
91313
- * A source as the admin list shows it. No `args`: a credential never leaves the
91314
- * addon - `formValues` carries the redaction token where one is stored (D880).
91315
- */
91316
- var WasteSourceRowSchema = object({
91317
- id: string(),
91318
- label: string(),
91319
- module: string(),
91320
- enabled: boolean(),
91321
- formValues: record(string(), unknown()).optional(),
91322
- serviceId: string().optional(),
91323
- countryId: string().optional()
91324
- });
91325
91077
  var wasteCollectionsActions = defineCustomActions({
91326
91078
  fetchNow: customAction(object({
91327
91079
  /** When set, refresh only this configured source id; otherwise all enabled. */
@@ -91336,10 +91088,6 @@ sourceId: string().min(1).optional() }).optional(), object({
91336
91088
  kind: "mutation",
91337
91089
  auth: "admin"
91338
91090
  }),
91339
- listSources: customAction(object({}), array(WasteSourceRowSchema).readonly(), {
91340
- kind: "mutation",
91341
- auth: "admin"
91342
- }),
91343
91091
  listCountries: customAction(object({}), array(object({
91344
91092
  id: string(),
91345
91093
  label: string()
@@ -91357,43 +91105,6 @@ sourceId: string().min(1).optional() }).optional(), object({
91357
91105
  })).readonly(), {
91358
91106
  kind: "mutation",
91359
91107
  auth: "admin"
91360
- }),
91361
- serviceFormSchema: customAction(object({ serviceId: string().min(1) }), unknown(), {
91362
- kind: "mutation",
91363
- auth: "admin"
91364
- }),
91365
- createSource: customAction(object({
91366
- /** Optional override; when omitted the id is derived from `label`. */
91367
- id: SourceIdSchema.optional(),
91368
- label: string().min(1),
91369
- serviceId: string().min(1),
91370
- args: record(string(), unknown()).default({}),
91371
- enabled: boolean().optional()
91372
- }), object({ id: string() }), {
91373
- kind: "mutation",
91374
- auth: "admin"
91375
- }),
91376
- updateSource: customAction(object({
91377
- id: string().min(1),
91378
- label: string().min(1),
91379
- serviceId: string().min(1).optional(),
91380
- args: record(string(), unknown()).optional(),
91381
- enabled: boolean().optional()
91382
- }), object({ ok: literal(true) }), {
91383
- kind: "mutation",
91384
- auth: "admin"
91385
- }),
91386
- /**
91387
- * D880: rewrite every legacy source (free `argsJson`) as typed `args`, moving its
91388
- * credentials into the keychain. Dry run unless `apply`; idempotent; counts only.
91389
- */
91390
- migrateSources: customAction(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
91391
- kind: "mutation",
91392
- auth: "admin"
91393
- }),
91394
- deleteSource: customAction(object({ id: string().min(1) }), object({ ok: literal(true) }), {
91395
- kind: "mutation",
91396
- auth: "admin"
91397
91108
  })
91398
91109
  });
91399
91110
  //#endregion
@@ -91427,33 +91138,6 @@ var WASTE_SECRET_STATE_PATHS = SECRET_ARG_KEYS.map((key) => `sources[id].args.${
91427
91138
  function isSecretArg(key) {
91428
91139
  return SECRET_ARG_KEYS.includes(key.toLowerCase());
91429
91140
  }
91430
- /** Is a credential stored for this argument (a value, or a marker the keychain could not serve)? */
91431
- function isSet(value) {
91432
- return isSecretMarker(value) || typeof value === "string" && value.length > 0;
91433
- }
91434
- /** The arguments as a read surface may show them: a stored credential is the redaction token. */
91435
- function redactArgs(args) {
91436
- const out = {};
91437
- for (const [key, value] of Object.entries(args)) out[key] = isSecretArg(key) && isSet(value) ? REDACTED_SECRET : value;
91438
- return out;
91439
- }
91440
- /**
91441
- * The write-back of {@link redactArgs}: a credential sent back as the redaction
91442
- * token means "keep what is stored", so it takes the stored (resolved) value; one
91443
- * with nothing stored is dropped, never saved as the token.
91444
- */
91445
- function restoreRedactedArgs(incoming, stored) {
91446
- const out = {};
91447
- for (const [key, value] of Object.entries(incoming)) {
91448
- if (isSecretArg(key) && value === "__camstack_redacted__") {
91449
- const previous = stored[key];
91450
- if (previous !== void 0) out[key] = previous;
91451
- continue;
91452
- }
91453
- out[key] = value;
91454
- }
91455
- return out;
91456
- }
91457
91141
  /**
91458
91142
  * Does this argument bag hold a credential that is NOT a usable value: a MARKER the keychain did
91459
91143
  * not resolve, or the redaction token (a row read without its credential)? Either would reach a
@@ -91462,10 +91146,6 @@ function restoreRedactedArgs(incoming, stored) {
91462
91146
  function hasUnresolvedCredential(args) {
91463
91147
  return Object.entries(args).some(([key, value]) => isSecretArg(key) && (isSecretMarker(value) || value === "__camstack_redacted__"));
91464
91148
  }
91465
- /** Does it hold a non-empty credential (any form)? */
91466
- function hasCredential(args) {
91467
- return Object.entries(args).some(([key, value]) => isSecretArg(key) && isSet(value));
91468
- }
91469
91149
  //#endregion
91470
91150
  //#region src/waste-types.ts
91471
91151
  /**
@@ -91930,35 +91610,6 @@ function reportFailed(log, failures) {
91930
91610
  };
91931
91611
  }
91932
91612
  //#endregion
91933
- //#region src/source-id.ts
91934
- /**
91935
- * Stable calendar source ids derived from the operator label.
91936
- * Pattern: `^[a-z0-9][a-z0-9-]{0,63}$` (max 64 chars).
91937
- */
91938
- var SOURCE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
91939
- /** Slugify a display label into a candidate source id (may be empty). */
91940
- function slugifySourceLabel(label) {
91941
- const s = label.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
91942
- if (s.length === 0) return "";
91943
- return s.slice(0, 64);
91944
- }
91945
- /**
91946
- * Pick an unused id from `label`. Collisions append `-2`, `-3`, …
91947
- * (truncated so the result stays within the 64-char pattern).
91948
- */
91949
- function allocateSourceId(label, existingIds) {
91950
- const taken = existingIds instanceof Set ? existingIds : new Set(existingIds);
91951
- const baseRaw = slugifySourceLabel(label);
91952
- const base = baseRaw.length > 0 ? baseRaw : "source";
91953
- if (!taken.has(base) && SOURCE_ID_PATTERN.test(base)) return base;
91954
- for (let n = 2; n < 1e4; n++) {
91955
- const suffix = `-${n}`;
91956
- const candidate = `${base.slice(0, Math.max(1, 64 - suffix.length)).replace(/-+$/g, "")}${suffix}`;
91957
- if (SOURCE_ID_PATTERN.test(candidate) && !taken.has(candidate)) return candidate;
91958
- }
91959
- throw new Error("could not allocate a unique source id");
91960
- }
91961
- //#endregion
91962
91613
  //#region src/waste-calendar-source.ts
91963
91614
  function startOfUtcDayMs(isoDate) {
91964
91615
  const [y, m, d] = isoDate.split("-").map((p) => Number(p));
@@ -178003,6 +177654,35 @@ function mergeServiceArgs(service, formValues) {
178003
177654
  return out;
178004
177655
  }
178005
177656
  //#endregion
177657
+ //#region src/source-id.ts
177658
+ /**
177659
+ * Stable calendar source ids derived from the operator label.
177660
+ * Pattern: `^[a-z0-9][a-z0-9-]{0,63}$` (max 64 chars).
177661
+ */
177662
+ var SOURCE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
177663
+ /** Slugify a display label into a candidate source id (may be empty). */
177664
+ function slugifySourceLabel(label) {
177665
+ const s = label.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
177666
+ if (s.length === 0) return "";
177667
+ return s.slice(0, 64);
177668
+ }
177669
+ /**
177670
+ * Pick an unused id from `label`. Collisions append `-2`, `-3`, …
177671
+ * (truncated so the result stays within the 64-char pattern).
177672
+ */
177673
+ function allocateSourceId(label, existingIds) {
177674
+ const taken = existingIds instanceof Set ? existingIds : new Set(existingIds);
177675
+ const baseRaw = slugifySourceLabel(label);
177676
+ const base = baseRaw.length > 0 ? baseRaw : "source";
177677
+ if (!taken.has(base) && SOURCE_ID_PATTERN.test(base)) return base;
177678
+ for (let n = 2; n < 1e4; n++) {
177679
+ const suffix = `-${n}`;
177680
+ const candidate = `${base.slice(0, Math.max(1, 64 - suffix.length)).replace(/-+$/g, "")}${suffix}`;
177681
+ if (SOURCE_ID_PATTERN.test(candidate) && !taken.has(candidate)) return candidate;
177682
+ }
177683
+ throw new Error("could not allocate a unique source id");
177684
+ }
177685
+ //#endregion
178006
177686
  //#region src/waste-source-store.ts
178007
177687
  /**
178008
177688
  * A stored body as a source. A row read while the keychain is locked still carries its markers
@@ -178506,23 +178186,13 @@ var WasteCollectionsAddon = class extends BaseAddon {
178506
178186
  customActions: wasteCollectionsActions,
178507
178187
  actionHandlers: {
178508
178188
  fetchNow: async (input) => this.fetchNow(input),
178509
- listSources: async () => this.listSources(),
178510
178189
  listCountries: async () => listCatalogCountries(),
178511
178190
  listServices: async (input) => listCatalogServices(input.countryId).map((s) => ({
178512
178191
  id: s.id,
178513
178192
  label: serviceDisplayLabel(s),
178514
178193
  title: s.label,
178515
178194
  module: s.module
178516
- })),
178517
- serviceFormSchema: async (input) => {
178518
- const service = findCatalogService(input.serviceId);
178519
- if (!service) throw new Error(`unknown service '${input.serviceId}'`);
178520
- return serviceFormSchema(service);
178521
- },
178522
- createSource: async (input) => this.createSource(input),
178523
- updateSource: async (input) => this.updateSource(input),
178524
- deleteSource: async (input) => this.deleteSource(input),
178525
- migrateSources: async (input) => this.migrateSources(input.apply)
178195
+ }))
178526
178196
  }
178527
178197
  };
178528
178198
  }
@@ -178589,17 +178259,6 @@ var WasteCollectionsAddon = class extends BaseAddon {
178589
178259
  if (!this.legacyServed) return records;
178590
178260
  return withUnmigratedLegacyRows(records, this.parseConfigSources(this.config.sources));
178591
178261
  }
178592
- async persistSources(sources) {
178593
- this.activeSources = [...sources];
178594
- const store = this.sourceStore;
178595
- if (store !== null) {
178596
- await store.replaceAll(sources);
178597
- const kept = new Set(sources.map((s) => s.id));
178598
- await this.pruneLegacyRows((legacyId) => kept.has(legacyId));
178599
- return;
178600
- }
178601
- await this.updateGlobalSettings({ sources: [...sources] });
178602
- }
178603
178262
  /**
178604
178263
  * A source deleted at the records leaves the legacy setting too, or it would reappear beside the
178605
178264
  * records (while the migration is pending) or be copied back by it.
@@ -178614,195 +178273,6 @@ var WasteCollectionsAddon = class extends BaseAddon {
178614
178273
  if (this.activeSources.length > 0 || this.sourceStore !== null) return this.activeSources;
178615
178274
  return this.parseConfigSources(this.config.sources);
178616
178275
  }
178617
- listSources() {
178618
- return this.configuredSources().map((s) => {
178619
- let args = {};
178620
- try {
178621
- args = sourceArgs(s);
178622
- } catch {
178623
- args = { ...s.args };
178624
- }
178625
- const matched = matchCatalogService(s.module, args);
178626
- const formValues = {
178627
- label: s.label,
178628
- enabled: s.enabled,
178629
- ...redactArgs(args)
178630
- };
178631
- if (matched) for (const key of Object.keys(matched.defaultArgs)) delete formValues[key];
178632
- return {
178633
- id: s.id,
178634
- label: s.label,
178635
- module: s.module,
178636
- enabled: s.enabled,
178637
- formValues,
178638
- serviceId: matched?.id,
178639
- countryId: matched?.countryId
178640
- };
178641
- });
178642
- }
178643
- async createSource(input) {
178644
- const existing = this.configuredSources().map((s) => s.id);
178645
- let id;
178646
- if (input.id !== void 0) {
178647
- if (!SOURCE_ID_PATTERN.test(input.id)) throw new Error("id must be a lowercase slug (e.g. ics-home, junker)");
178648
- if (existing.includes(input.id)) throw new Error(`source '${input.id}' already exists`);
178649
- id = input.id;
178650
- } else id = allocateSourceId(input.label, existing);
178651
- const service = findCatalogService(input.serviceId);
178652
- if (!service) throw new Error(`unknown service '${input.serviceId}'`);
178653
- const row = WasteSourceConfigSchema.parse({
178654
- id,
178655
- label: input.label,
178656
- module: service.module,
178657
- args: this.typedArgs(mergeServiceArgs(service, restoreRedactedArgs(input.args, {}))),
178658
- enabled: input.enabled ?? true
178659
- });
178660
- await this.persistSources([...this.configuredSources(), row]);
178661
- return { id };
178662
- }
178663
- async updateSource(input) {
178664
- const sources = this.configuredSources();
178665
- const idx = sources.findIndex((s) => s.id === input.id);
178666
- if (idx < 0) throw new Error(`source '${input.id}' not found`);
178667
- const prev = sources[idx];
178668
- let prevArgs = {};
178669
- try {
178670
- prevArgs = sourceArgs(prev);
178671
- } catch {
178672
- prevArgs = { ...prev.args };
178673
- }
178674
- let module = prev.module;
178675
- let args = prevArgs;
178676
- if (input.serviceId !== void 0) {
178677
- const service = findCatalogService(input.serviceId);
178678
- if (!service) throw new Error(`unknown service '${input.serviceId}'`);
178679
- module = service.module;
178680
- args = mergeServiceArgs(service, restoreRedactedArgs(input.args ?? {}, prevArgs));
178681
- } else if (input.args !== void 0) {
178682
- const sent = restoreRedactedArgs(input.args, prevArgs);
178683
- const matched = matchCatalogService(prev.module, prevArgs);
178684
- args = matched ? mergeServiceArgs(matched, sent) : {
178685
- ...prevArgs,
178686
- ...sent
178687
- };
178688
- }
178689
- const next = WasteSourceConfigSchema.parse({
178690
- id: prev.id,
178691
- label: input.label,
178692
- module,
178693
- args: this.typedArgs(args),
178694
- enabled: input.enabled ?? prev.enabled
178695
- });
178696
- await this.persistSources(sources.map((s, i) => i === idx ? next : s));
178697
- return { ok: true };
178698
- }
178699
- /** Validate an argument bag against the typed value schema (a nested object is refused). */
178700
- typedArgs(args) {
178701
- const out = {};
178702
- for (const [key, value] of Object.entries(args)) out[key] = WasteArgValueSchema.parse(value);
178703
- return out;
178704
- }
178705
- /**
178706
- * D880: rewrite every legacy source (free `argsJson`) as typed `args`; its
178707
- * credentials go to the keychain through the normal save path. Then every
178708
- * credential is read back and compared with what the legacy row held; on any
178709
- * difference the legacy rows are written back, so nothing is lost. Dry run
178710
- * unless `apply`. Idempotent. Counts only - never an argument, a ref or an id.
178711
- */
178712
- async migrateSources(apply) {
178713
- const sources = this.configuredSources();
178714
- const legacy = sources.filter((s) => s.argsJson !== void 0);
178715
- const rawRows = legacy.filter((s) => {
178716
- try {
178717
- return hasCredential(sourceArgs(s));
178718
- } catch {
178719
- return false;
178720
- }
178721
- });
178722
- const markerRows = sources.filter((s) => s.argsJson === void 0 && hasCredential(s.args)).length;
178723
- const report = (over) => ({
178724
- apply,
178725
- rows: sources.length,
178726
- raw: rawRows.length,
178727
- markers: markerRows,
178728
- migrated: 0,
178729
- failed: 0,
178730
- remainingRaw: rawRows.length,
178731
- unresolvedMarkers: this.unresolvedSecrets.length,
178732
- redundantCopies: legacy.length,
178733
- verified: rawRows.length === 0 && this.unresolvedSecrets.length === 0,
178734
- ...over
178735
- });
178736
- if (!apply || legacy.length === 0) return report({});
178737
- const expected = /* @__PURE__ */ new Map();
178738
- const unconverted = /* @__PURE__ */ new Set();
178739
- const migrated = sources.map((s) => {
178740
- if (s.argsJson === void 0) return s;
178741
- try {
178742
- const args = sourceArgs(s);
178743
- const row = WasteSourceConfigSchema.parse({
178744
- id: s.id,
178745
- label: s.label,
178746
- module: s.module,
178747
- args: this.typedArgs(args),
178748
- enabled: s.enabled
178749
- });
178750
- expected.set(s.id, args);
178751
- return row;
178752
- } catch {
178753
- unconverted.add(s.id);
178754
- return s;
178755
- }
178756
- });
178757
- const failedRows = unconverted.size;
178758
- if (expected.size === 0) return report({
178759
- failed: failedRows,
178760
- remainingRaw: legacy.length
178761
- });
178762
- try {
178763
- await this.persistSources(migrated);
178764
- } catch {
178765
- return report({ failed: legacy.length });
178766
- }
178767
- const back = new Map(this.configuredSources().map((s) => [s.id, s]));
178768
- if (![...expected].every(([id, args]) => {
178769
- const row = back.get(id);
178770
- return row !== void 0 && row.argsJson === void 0 && JSON.stringify(sortedEntries(row.args)) === JSON.stringify(sortedEntries(args));
178771
- }) || this.unresolvedSecrets.length > 0) {
178772
- await this.rollbackMigration(sources, expected);
178773
- return report({ failed: legacy.length });
178774
- }
178775
- return report({
178776
- migrated: expected.size,
178777
- failed: failedRows,
178778
- remainingRaw: failedRows,
178779
- unresolvedMarkers: 0,
178780
- redundantCopies: legacy.length,
178781
- verified: failedRows === 0
178782
- });
178783
- }
178784
- /**
178785
- * Put the legacy rows back. A converted row's `args` is written as `null`, which
178786
- * the save path reads as "this object is gone": it deletes the keychain refs the
178787
- * migration created (a plain `{}` would keep them). Never throws - the report
178788
- * says failed either way, and the legacy JSON stays authoritative.
178789
- */
178790
- async rollbackMigration(sources, converted) {
178791
- try {
178792
- await this.updateGlobalSettingsAsOperator({ sources: sources.map((s) => converted.has(s.id) ? {
178793
- ...s,
178794
- args: null
178795
- } : s) });
178796
- } catch {
178797
- this.ctx.logger.error("waste-collections: the migration rollback failed - keychain refs may be orphaned (System -> Secrets)");
178798
- }
178799
- }
178800
- async deleteSource(input) {
178801
- const sources = this.configuredSources();
178802
- if (!sources.some((s) => s.id === input.id)) throw new Error(`source '${input.id}' not found`);
178803
- await this.persistSources(sources.filter((s) => s.id !== input.id));
178804
- return { ok: true };
178805
- }
178806
178276
  async fetchNow(input) {
178807
178277
  const targets = this.configuredSources().filter((s) => {
178808
178278
  if (!s.enabled) return false;
@@ -178903,33 +178373,11 @@ var WasteCollectionsAddon = class extends BaseAddon {
178903
178373
  this.ctx.logger.warn("waste-collections: cache persist failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
178904
178374
  }
178905
178375
  }
178906
- globalSettingsSchema() {
178907
- return this.schema({ sections: [{
178908
- id: "refresh",
178909
- title: "Refresh",
178910
- description: "Sources are managed in the table above (Add / Edit): pick a country and a service, then fill only that service’s fields. Each source becomes one calendar under System → Calendars.",
178911
- columns: 2,
178912
- fields: [{
178913
- type: "addon-action-button",
178914
- key: "_fetchNow",
178915
- label: "Refresh",
178916
- buttonLabel: "Fetch now",
178917
- description: "Sources refresh once at boot and then every 24 hours; this refreshes every enabled source immediately.",
178918
- addonId: "waste-collections",
178919
- action: "fetchNow",
178920
- variant: "primary",
178921
- paramsStatic: {}
178922
- }]
178923
- }] });
178924
- }
178925
178376
  };
178926
178377
  /** Re-parse helper for tests. */
178927
178378
  function parseWasteConfig(raw) {
178928
178379
  return WasteConfigSchema.parse(raw);
178929
178380
  }
178930
- function sortedEntries(args) {
178931
- return Object.entries(args).toSorted(([a], [b]) => a < b ? -1 : 1);
178932
- }
178933
178381
  //#endregion
178934
178382
  exports.DEFAULT_WASTE_CONFIG = DEFAULT_WASTE_CONFIG;
178935
178383
  exports.WasteCollectionsAddon = WasteCollectionsAddon;