@camstack/addon-mqtt-broker 1.2.125 → 1.2.126
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mqtt-broker.addon.js +1671 -76
- package/dist/mqtt-broker.addon.mjs +1671 -76
- package/package.json +1 -1
|
@@ -39,7 +39,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
39
39
|
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
40
40
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
41
41
|
//#endregion
|
|
42
|
-
//#region ../types/dist/event-category-
|
|
42
|
+
//#region ../types/dist/event-category-BvOFi6PY.mjs
|
|
43
43
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
44
44
|
EventCategory["SystemBoot"] = "system.boot";
|
|
45
45
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -103,6 +103,24 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
103
103
|
* consumes this category and maps it to the `addon-crash-loop` kind.
|
|
104
104
|
*/
|
|
105
105
|
EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
|
|
106
|
+
/**
|
|
107
|
+
* A runner whose addon refused to start with a typed reason (D877) - parked
|
|
108
|
+
* in `refused`, never counted by the crash breaker, retried on operator
|
|
109
|
+
* action. One event per refusal.
|
|
110
|
+
*/
|
|
111
|
+
EventCategory["AddonRunnerRefused"] = "addon.runner-refused";
|
|
112
|
+
/**
|
|
113
|
+
* A runner settled on a state (D877): `running` (READY), `crashed`, `failed` or
|
|
114
|
+
* `refused`. In-process telemetry for the boot check (re-evaluate while any
|
|
115
|
+
* runner is still pending) and the alert centre (close a recovered runner).
|
|
116
|
+
*/
|
|
117
|
+
EventCategory["ProcessStateChanged"] = "process.state-changed";
|
|
118
|
+
/**
|
|
119
|
+
* The node's one-shot runner boot check (D877): planned runners against the
|
|
120
|
+
* process service, `degraded` when any is failed, refused or missing. The
|
|
121
|
+
* alert centre turns it into one alert and a direct critical push.
|
|
122
|
+
*/
|
|
123
|
+
EventCategory["SystemRunnerCheck"] = "system.runner-check";
|
|
106
124
|
EventCategory["AddonError"] = "addon.error";
|
|
107
125
|
EventCategory["AddonPageReady"] = "addon.page-ready";
|
|
108
126
|
EventCategory["AddonWidgetReady"] = "addon.widget-ready";
|
|
@@ -4145,6 +4163,12 @@ var numberProcessor = (schema, ctx, _json, _params) => {
|
|
|
4145
4163
|
var booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
4146
4164
|
json.type = "boolean";
|
|
4147
4165
|
};
|
|
4166
|
+
var bigintProcessor = (_schema, ctx, _json, _params) => {
|
|
4167
|
+
if (ctx.unrepresentable === "throw") throw new Error("BigInt cannot be represented in JSON Schema");
|
|
4168
|
+
};
|
|
4169
|
+
var symbolProcessor = (_schema, ctx, _json, _params) => {
|
|
4170
|
+
if (ctx.unrepresentable === "throw") throw new Error("Symbols cannot be represented in JSON Schema");
|
|
4171
|
+
};
|
|
4148
4172
|
var nullProcessor = (_schema, ctx, json, _params) => {
|
|
4149
4173
|
if (ctx.target === "openapi-3.0") {
|
|
4150
4174
|
json.type = "string";
|
|
@@ -4152,12 +4176,17 @@ var nullProcessor = (_schema, ctx, json, _params) => {
|
|
|
4152
4176
|
json.enum = [null];
|
|
4153
4177
|
} else json.type = "null";
|
|
4154
4178
|
};
|
|
4179
|
+
var undefinedProcessor = (_schema, ctx, _json, _params) => {
|
|
4180
|
+
if (ctx.unrepresentable === "throw") throw new Error("Undefined cannot be represented in JSON Schema");
|
|
4181
|
+
};
|
|
4155
4182
|
var voidProcessor = (_schema, ctx, _json, _params) => {
|
|
4156
4183
|
if (ctx.unrepresentable === "throw") throw new Error("Void cannot be represented in JSON Schema");
|
|
4157
4184
|
};
|
|
4158
4185
|
var neverProcessor = (_schema, _ctx, json, _params) => {
|
|
4159
4186
|
json.not = {};
|
|
4160
4187
|
};
|
|
4188
|
+
var anyProcessor = (_schema, _ctx, _json, _params) => {};
|
|
4189
|
+
var unknownProcessor = (_schema, _ctx, _json, _params) => {};
|
|
4161
4190
|
var dateProcessor = (_schema, ctx, _json, _params) => {
|
|
4162
4191
|
if (ctx.unrepresentable === "throw") throw new Error("Date cannot be represented in JSON Schema");
|
|
4163
4192
|
};
|
|
@@ -4189,12 +4218,53 @@ var literalProcessor = (schema, ctx, json, _params) => {
|
|
|
4189
4218
|
json.enum = vals;
|
|
4190
4219
|
}
|
|
4191
4220
|
};
|
|
4221
|
+
var nanProcessor = (_schema, ctx, _json, _params) => {
|
|
4222
|
+
if (ctx.unrepresentable === "throw") throw new Error("NaN cannot be represented in JSON Schema");
|
|
4223
|
+
};
|
|
4224
|
+
var templateLiteralProcessor = (schema, _ctx, json, _params) => {
|
|
4225
|
+
const _json = json;
|
|
4226
|
+
const pattern = schema._zod.pattern;
|
|
4227
|
+
if (!pattern) throw new Error("Pattern not found in template literal");
|
|
4228
|
+
_json.type = "string";
|
|
4229
|
+
_json.pattern = pattern.source;
|
|
4230
|
+
};
|
|
4231
|
+
var fileProcessor = (schema, _ctx, json, _params) => {
|
|
4232
|
+
const _json = json;
|
|
4233
|
+
const file = {
|
|
4234
|
+
type: "string",
|
|
4235
|
+
format: "binary",
|
|
4236
|
+
contentEncoding: "binary"
|
|
4237
|
+
};
|
|
4238
|
+
const { minimum, maximum, mime } = schema._zod.bag;
|
|
4239
|
+
if (minimum !== void 0) file.minLength = minimum;
|
|
4240
|
+
if (maximum !== void 0) file.maxLength = maximum;
|
|
4241
|
+
if (mime) if (mime.length === 1) {
|
|
4242
|
+
file.contentMediaType = mime[0];
|
|
4243
|
+
Object.assign(_json, file);
|
|
4244
|
+
} else {
|
|
4245
|
+
Object.assign(_json, file);
|
|
4246
|
+
_json.anyOf = mime.map((m) => ({ contentMediaType: m }));
|
|
4247
|
+
}
|
|
4248
|
+
else Object.assign(_json, file);
|
|
4249
|
+
};
|
|
4250
|
+
var successProcessor = (_schema, _ctx, json, _params) => {
|
|
4251
|
+
json.type = "boolean";
|
|
4252
|
+
};
|
|
4192
4253
|
var customProcessor = (_schema, ctx, _json, _params) => {
|
|
4193
4254
|
if (ctx.unrepresentable === "throw") throw new Error("Custom types cannot be represented in JSON Schema");
|
|
4194
4255
|
};
|
|
4256
|
+
var functionProcessor = (_schema, ctx, _json, _params) => {
|
|
4257
|
+
if (ctx.unrepresentable === "throw") throw new Error("Function types cannot be represented in JSON Schema");
|
|
4258
|
+
};
|
|
4195
4259
|
var transformProcessor = (_schema, ctx, _json, _params) => {
|
|
4196
4260
|
if (ctx.unrepresentable === "throw") throw new Error("Transforms cannot be represented in JSON Schema");
|
|
4197
4261
|
};
|
|
4262
|
+
var mapProcessor = (_schema, ctx, _json, _params) => {
|
|
4263
|
+
if (ctx.unrepresentable === "throw") throw new Error("Map cannot be represented in JSON Schema");
|
|
4264
|
+
};
|
|
4265
|
+
var setProcessor = (_schema, ctx, _json, _params) => {
|
|
4266
|
+
if (ctx.unrepresentable === "throw") throw new Error("Set cannot be represented in JSON Schema");
|
|
4267
|
+
};
|
|
4198
4268
|
var arrayProcessor = (schema, ctx, _json, params) => {
|
|
4199
4269
|
const json = _json;
|
|
4200
4270
|
const def = schema._zod.def;
|
|
@@ -4399,6 +4469,12 @@ var readonlyProcessor = (schema, ctx, json, params) => {
|
|
|
4399
4469
|
seen.ref = def.innerType;
|
|
4400
4470
|
json.readOnly = true;
|
|
4401
4471
|
};
|
|
4472
|
+
var promiseProcessor = (schema, ctx, _json, params) => {
|
|
4473
|
+
const def = schema._zod.def;
|
|
4474
|
+
process$1(def.innerType, ctx, params);
|
|
4475
|
+
const seen = ctx.seen.get(schema);
|
|
4476
|
+
seen.ref = def.innerType;
|
|
4477
|
+
};
|
|
4402
4478
|
var optionalProcessor = (schema, ctx, _json, params) => {
|
|
4403
4479
|
const def = schema._zod.def;
|
|
4404
4480
|
process$1(def.innerType, ctx, params);
|
|
@@ -4411,6 +4487,81 @@ var lazyProcessor = (schema, ctx, _json, params) => {
|
|
|
4411
4487
|
const seen = ctx.seen.get(schema);
|
|
4412
4488
|
seen.ref = innerType;
|
|
4413
4489
|
};
|
|
4490
|
+
var allProcessors = {
|
|
4491
|
+
string: stringProcessor,
|
|
4492
|
+
number: numberProcessor,
|
|
4493
|
+
boolean: booleanProcessor,
|
|
4494
|
+
bigint: bigintProcessor,
|
|
4495
|
+
symbol: symbolProcessor,
|
|
4496
|
+
null: nullProcessor,
|
|
4497
|
+
undefined: undefinedProcessor,
|
|
4498
|
+
void: voidProcessor,
|
|
4499
|
+
never: neverProcessor,
|
|
4500
|
+
any: anyProcessor,
|
|
4501
|
+
unknown: unknownProcessor,
|
|
4502
|
+
date: dateProcessor,
|
|
4503
|
+
enum: enumProcessor,
|
|
4504
|
+
literal: literalProcessor,
|
|
4505
|
+
nan: nanProcessor,
|
|
4506
|
+
template_literal: templateLiteralProcessor,
|
|
4507
|
+
file: fileProcessor,
|
|
4508
|
+
success: successProcessor,
|
|
4509
|
+
custom: customProcessor,
|
|
4510
|
+
function: functionProcessor,
|
|
4511
|
+
transform: transformProcessor,
|
|
4512
|
+
map: mapProcessor,
|
|
4513
|
+
set: setProcessor,
|
|
4514
|
+
array: arrayProcessor,
|
|
4515
|
+
object: objectProcessor,
|
|
4516
|
+
union: unionProcessor,
|
|
4517
|
+
intersection: intersectionProcessor,
|
|
4518
|
+
tuple: tupleProcessor,
|
|
4519
|
+
record: recordProcessor,
|
|
4520
|
+
nullable: nullableProcessor,
|
|
4521
|
+
nonoptional: nonoptionalProcessor,
|
|
4522
|
+
default: defaultProcessor,
|
|
4523
|
+
prefault: prefaultProcessor,
|
|
4524
|
+
catch: catchProcessor,
|
|
4525
|
+
pipe: pipeProcessor,
|
|
4526
|
+
readonly: readonlyProcessor,
|
|
4527
|
+
promise: promiseProcessor,
|
|
4528
|
+
optional: optionalProcessor,
|
|
4529
|
+
lazy: lazyProcessor
|
|
4530
|
+
};
|
|
4531
|
+
function toJSONSchema(input, params) {
|
|
4532
|
+
if ("_idmap" in input) {
|
|
4533
|
+
const registry = input;
|
|
4534
|
+
const ctx = initializeContext({
|
|
4535
|
+
...params,
|
|
4536
|
+
processors: allProcessors
|
|
4537
|
+
});
|
|
4538
|
+
const defs = {};
|
|
4539
|
+
for (const entry of registry._idmap.entries()) {
|
|
4540
|
+
const [_, schema] = entry;
|
|
4541
|
+
process$1(schema, ctx);
|
|
4542
|
+
}
|
|
4543
|
+
const schemas = {};
|
|
4544
|
+
ctx.external = {
|
|
4545
|
+
registry,
|
|
4546
|
+
uri: params?.uri,
|
|
4547
|
+
defs
|
|
4548
|
+
};
|
|
4549
|
+
for (const entry of registry._idmap.entries()) {
|
|
4550
|
+
const [key, schema] = entry;
|
|
4551
|
+
extractDefs(ctx, schema);
|
|
4552
|
+
schemas[key] = finalize(ctx, schema);
|
|
4553
|
+
}
|
|
4554
|
+
if (Object.keys(defs).length > 0) schemas.__shared = { [ctx.target === "draft-2020-12" ? "$defs" : "definitions"]: defs };
|
|
4555
|
+
return { schemas };
|
|
4556
|
+
}
|
|
4557
|
+
const ctx = initializeContext({
|
|
4558
|
+
...params,
|
|
4559
|
+
processors: allProcessors
|
|
4560
|
+
});
|
|
4561
|
+
process$1(input, ctx);
|
|
4562
|
+
extractDefs(ctx, input);
|
|
4563
|
+
return finalize(ctx, input);
|
|
4564
|
+
}
|
|
4414
4565
|
//#endregion
|
|
4415
4566
|
//#region ../../node_modules/zod/v4/classic/iso.js
|
|
4416
4567
|
var ZodISODateTime = /*@__PURE__*/ $constructor("ZodISODateTime", (inst, def) => {
|
|
@@ -5427,7 +5578,7 @@ var ZodIssueCode = {
|
|
|
5427
5578
|
var ZodFirstPartyTypeKind;
|
|
5428
5579
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5429
5580
|
//#endregion
|
|
5430
|
-
//#region ../types/dist/sleep-
|
|
5581
|
+
//#region ../types/dist/sleep-bZIhUvjd.mjs
|
|
5431
5582
|
/**
|
|
5432
5583
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5433
5584
|
* window to float samples (D455).
|
|
@@ -5544,7 +5695,7 @@ function isKeychainStoredField(field) {
|
|
|
5544
5695
|
var SECRET_CLEAR = { $secretClear: true };
|
|
5545
5696
|
/** The `'***'` token D634 redaction writes for non-admins. */
|
|
5546
5697
|
var LEGACY_DEVICE_REDACTION = "***";
|
|
5547
|
-
function isRecord$
|
|
5698
|
+
function isRecord$5(value) {
|
|
5548
5699
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5549
5700
|
}
|
|
5550
5701
|
function makeSecretMarker(ref) {
|
|
@@ -5555,12 +5706,12 @@ function makeSecretMarker(ref) {
|
|
|
5555
5706
|
}
|
|
5556
5707
|
/** A stored marker: an object whose `$secret` is a non-empty string. */
|
|
5557
5708
|
function isSecretMarker(value) {
|
|
5558
|
-
if (!isRecord$
|
|
5709
|
+
if (!isRecord$5(value)) return false;
|
|
5559
5710
|
const ref = value["$secret"];
|
|
5560
5711
|
return typeof ref === "string" && ref.length > 0;
|
|
5561
5712
|
}
|
|
5562
5713
|
function isSecretClear(value) {
|
|
5563
|
-
return isRecord$
|
|
5714
|
+
return isRecord$5(value) && value["$secretClear"] === true;
|
|
5564
5715
|
}
|
|
5565
5716
|
/**
|
|
5566
5717
|
* The UI view of a stored secret field value: a marker becomes "set", inline
|
|
@@ -6170,7 +6321,7 @@ var KeychainWriteRefusedError = class extends Error {
|
|
|
6170
6321
|
}
|
|
6171
6322
|
};
|
|
6172
6323
|
var KEEP = { kind: "keep" };
|
|
6173
|
-
function isRecord$
|
|
6324
|
+
function isRecord$4(value) {
|
|
6174
6325
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6175
6326
|
}
|
|
6176
6327
|
function defaultMintId() {
|
|
@@ -6183,7 +6334,7 @@ function collectSecretMarkerRefs(value, out = []) {
|
|
|
6183
6334
|
return out;
|
|
6184
6335
|
}
|
|
6185
6336
|
if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
|
|
6186
|
-
else if (isRecord$
|
|
6337
|
+
else if (isRecord$4(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
|
|
6187
6338
|
return out;
|
|
6188
6339
|
}
|
|
6189
6340
|
async function storedValueEquals(secrets, marker, candidate) {
|
|
@@ -6238,7 +6389,7 @@ function rowId(row, idKey) {
|
|
|
6238
6389
|
return null;
|
|
6239
6390
|
}
|
|
6240
6391
|
async function writeArray(ctx, node, incoming, stored, fieldPath) {
|
|
6241
|
-
const storedRows = Array.isArray(stored) ? stored.filter(isRecord$
|
|
6392
|
+
const storedRows = Array.isArray(stored) ? stored.filter(isRecord$4) : [];
|
|
6242
6393
|
if (!Array.isArray(incoming)) {
|
|
6243
6394
|
ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
|
|
6244
6395
|
return {
|
|
@@ -6254,7 +6405,7 @@ async function writeArray(ctx, node, incoming, stored, fieldPath) {
|
|
|
6254
6405
|
const kept = /* @__PURE__ */ new Set();
|
|
6255
6406
|
const rows = [];
|
|
6256
6407
|
for (const row of incoming) {
|
|
6257
|
-
if (!isRecord$
|
|
6408
|
+
if (!isRecord$4(row)) {
|
|
6258
6409
|
rows.push(row);
|
|
6259
6410
|
continue;
|
|
6260
6411
|
}
|
|
@@ -6279,7 +6430,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
|
|
|
6279
6430
|
case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
|
|
6280
6431
|
case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
|
|
6281
6432
|
case "object": {
|
|
6282
|
-
if (!isRecord$
|
|
6433
|
+
if (!isRecord$4(incoming)) {
|
|
6283
6434
|
if (incoming === void 0) return KEEP;
|
|
6284
6435
|
ctx.deletes.push(...collectSecretMarkerRefs(stored));
|
|
6285
6436
|
return {
|
|
@@ -6287,7 +6438,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
|
|
|
6287
6438
|
value: incoming
|
|
6288
6439
|
};
|
|
6289
6440
|
}
|
|
6290
|
-
const storedObj = isRecord$
|
|
6441
|
+
const storedObj = isRecord$4(stored) ? stored : {};
|
|
6291
6442
|
return {
|
|
6292
6443
|
kind: "value",
|
|
6293
6444
|
value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
|
|
@@ -6410,7 +6561,7 @@ async function resolveSecretMarkers(value, secrets, options = {}) {
|
|
|
6410
6561
|
value: items
|
|
6411
6562
|
};
|
|
6412
6563
|
}
|
|
6413
|
-
if (isRecord$
|
|
6564
|
+
if (isRecord$4(v)) {
|
|
6414
6565
|
const out = {};
|
|
6415
6566
|
for (const [k, child] of Object.entries(v)) {
|
|
6416
6567
|
const s = substitute(child);
|
|
@@ -6442,7 +6593,7 @@ var SecretOverwriteRefusedError = class extends Error {
|
|
|
6442
6593
|
this.name = "SecretOverwriteRefusedError";
|
|
6443
6594
|
}
|
|
6444
6595
|
};
|
|
6445
|
-
function isRecord(value) {
|
|
6596
|
+
function isRecord$3(value) {
|
|
6446
6597
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6447
6598
|
}
|
|
6448
6599
|
function resolvedValue(pass, marker) {
|
|
@@ -6471,7 +6622,7 @@ function inArray(pass, incoming, stored) {
|
|
|
6471
6622
|
return incoming.map((item, index) => {
|
|
6472
6623
|
const twin = stored[index];
|
|
6473
6624
|
if (isSecretMarker(twin)) return typeof item === "string" && item === resolvedValue(pass, twin) ? twin : item;
|
|
6474
|
-
if (isRecord(item) && isRecord(twin)) return inArrayRow(pass, item, twin);
|
|
6625
|
+
if (isRecord$3(item) && isRecord$3(twin)) return inArrayRow(pass, item, twin);
|
|
6475
6626
|
if (Array.isArray(item) && Array.isArray(twin)) return inArray(pass, item, twin);
|
|
6476
6627
|
return item;
|
|
6477
6628
|
});
|
|
@@ -6482,7 +6633,7 @@ function inArrayRow(pass, incoming, stored) {
|
|
|
6482
6633
|
const twin = stored[key];
|
|
6483
6634
|
if (isSecretMarker(twin)) {
|
|
6484
6635
|
if (typeof value === "string" && value === resolvedValue(pass, twin)) out[key] = twin;
|
|
6485
|
-
} else if (isRecord(value) && isRecord(twin)) out[key] = inArrayRow(pass, value, twin);
|
|
6636
|
+
} else if (isRecord$3(value) && isRecord$3(twin)) out[key] = inArrayRow(pass, value, twin);
|
|
6486
6637
|
else if (Array.isArray(value) && Array.isArray(twin)) out[key] = inArray(pass, value, twin);
|
|
6487
6638
|
}
|
|
6488
6639
|
return out;
|
|
@@ -6490,7 +6641,7 @@ function inArrayRow(pass, incoming, stored) {
|
|
|
6490
6641
|
async function walk(pass, incoming, stored) {
|
|
6491
6642
|
if (isSecretMarker(stored)) return atMarker(pass, incoming, stored);
|
|
6492
6643
|
if (Array.isArray(incoming) && Array.isArray(stored)) return inArray(pass, incoming, stored);
|
|
6493
|
-
if (!isRecord(incoming) || !isRecord(stored)) return incoming;
|
|
6644
|
+
if (!isRecord$3(incoming) || !isRecord$3(stored)) return incoming;
|
|
6494
6645
|
const out = { ...incoming };
|
|
6495
6646
|
for (const [key, value] of Object.entries(incoming)) out[key] = await walk(pass, value, stored[key]);
|
|
6496
6647
|
return out;
|
|
@@ -6521,7 +6672,7 @@ async function restoreUnchangedSecrets(input) {
|
|
|
6521
6672
|
function isJsonEqual(a, b) {
|
|
6522
6673
|
if (Object.is(a, b)) return true;
|
|
6523
6674
|
if (Array.isArray(a)) return Array.isArray(b) && a.length === b.length && a.every((v, i) => isJsonEqual(v, b[i]));
|
|
6524
|
-
if (isRecord(a) && isRecord(b)) {
|
|
6675
|
+
if (isRecord$3(a) && isRecord$3(b)) {
|
|
6525
6676
|
const keys = Object.keys(a);
|
|
6526
6677
|
if (keys.length !== Object.keys(b).length) return false;
|
|
6527
6678
|
return keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && isJsonEqual(a[k], b[k]));
|
|
@@ -6545,7 +6696,7 @@ function markerKeysOf(blob, owner) {
|
|
|
6545
6696
|
}
|
|
6546
6697
|
function nodeFromStored(value, owner) {
|
|
6547
6698
|
if (isSecretMarker(value)) return isSecretRefOwnedBy(value.$secret, owner) ? { kind: "leaf" } : null;
|
|
6548
|
-
if (!isRecord(value)) return null;
|
|
6699
|
+
if (!isRecord$3(value)) return null;
|
|
6549
6700
|
const children = /* @__PURE__ */ new Map();
|
|
6550
6701
|
for (const [key, child] of Object.entries(value)) {
|
|
6551
6702
|
const node = nodeFromStored(child, owner);
|
|
@@ -6678,7 +6829,6 @@ function createHandle(raw, opts) {
|
|
|
6678
6829
|
const writeAddon = (patch) => write("addon", opts.addonTree(), void 0, patch, () => readStoredAddonBlob(raw), (p) => raw.writeAddonStore(p));
|
|
6679
6830
|
const readAddonStoreResult = raw.readAddonStoreResult;
|
|
6680
6831
|
const readAddonStoreKeyResult = raw.readAddonStoreKeyResult;
|
|
6681
|
-
const readDeviceStoreBatch = raw.readDeviceStoreBatch;
|
|
6682
6832
|
const overrides = {
|
|
6683
6833
|
readAddonStore: async () => resolveRead("addon", await raw.readAddonStore()),
|
|
6684
6834
|
...readAddonStoreResult !== void 0 ? { readAddonStoreResult: async () => {
|
|
@@ -6702,12 +6852,12 @@ function createHandle(raw, opts) {
|
|
|
6702
6852
|
};
|
|
6703
6853
|
} } : {},
|
|
6704
6854
|
readDeviceStore: async (deviceId) => resolveRead(`device:${deviceId}`, await raw.readDeviceStore(deviceId)),
|
|
6705
|
-
|
|
6706
|
-
const rows = await readDeviceStoreBatch
|
|
6855
|
+
readDeviceStoreBatch: async (deviceIds) => {
|
|
6856
|
+
const rows = await raw.readDeviceStoreBatch(deviceIds);
|
|
6707
6857
|
const out = /* @__PURE__ */ new Map();
|
|
6708
6858
|
for (const [deviceId, blob] of rows) out.set(deviceId, await resolveRead(`device:${deviceId}`, blob));
|
|
6709
6859
|
return out;
|
|
6710
|
-
}
|
|
6860
|
+
},
|
|
6711
6861
|
writeAddonStore: async (patch) => {
|
|
6712
6862
|
await writeAddon(patch);
|
|
6713
6863
|
},
|
|
@@ -7334,7 +7484,7 @@ var BaseAddon = class {
|
|
|
7334
7484
|
if (!ctx) return;
|
|
7335
7485
|
const addonId = ctx.id;
|
|
7336
7486
|
setImmediate(() => {
|
|
7337
|
-
ctx.api.addons
|
|
7487
|
+
ctx.api.addons.restartAddon.mutate({ addonId }).then(() => {
|
|
7338
7488
|
ctx.logger.info("addon auto-restart triggered by restart-required setting change", { meta: { changedFields: patchKeys.filter((k) => restartKeys.has(k)) } });
|
|
7339
7489
|
}).catch((err) => {
|
|
7340
7490
|
ctx.logger.error("addon auto-restart failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
@@ -7534,7 +7684,7 @@ var BaseAddon = class {
|
|
|
7534
7684
|
return this._addonSecretTree;
|
|
7535
7685
|
}
|
|
7536
7686
|
deviceSecretTree() {
|
|
7537
|
-
this._deviceSecretTree ??= secretTreeFromSchema(this.deviceSettingsSchema());
|
|
7687
|
+
this._deviceSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.deviceSettingsSchema()), this.secretStatePaths);
|
|
7538
7688
|
return this._deviceSecretTree;
|
|
7539
7689
|
}
|
|
7540
7690
|
/**
|
|
@@ -7610,42 +7760,135 @@ var BaseAddon = class {
|
|
|
7610
7760
|
this[timerOf] = timer;
|
|
7611
7761
|
}
|
|
7612
7762
|
/**
|
|
7613
|
-
* Wrap `ctx.settings.readAddonStore()` with a
|
|
7614
|
-
*
|
|
7615
|
-
* swap, or any race where the SqliteSettingsBackend is between shutdown and
|
|
7616
|
-
* re-initialize) doesn't propagate up into `initialize()` and leave the
|
|
7763
|
+
* Wrap `ctx.settings.readAddonStore()` with a retry budget so a transient
|
|
7764
|
+
* settings-store outage doesn't propagate up into `initialize()` and leave the
|
|
7617
7765
|
* addon permanently broken.
|
|
7618
7766
|
*
|
|
7619
|
-
*
|
|
7620
|
-
*
|
|
7621
|
-
* `
|
|
7767
|
+
* Two kinds of outage, two budgets:
|
|
7768
|
+
* - the store's own restart fingerprints (`SqliteSettingsBackend not
|
|
7769
|
+
* initialized`, `provider not available`): a short budget, then the addon
|
|
7770
|
+
* falls back to `{}` (defaults) - its first successful patch rehydrates;
|
|
7771
|
+
* - a TRANSPORT failure (the hub is restarting while this runner boots:
|
|
7772
|
+
* `discovery deadline exceeded`, `parent could not route`, a refused or
|
|
7773
|
+
* timed-out connection). Since D870 a runner's settings read THROWS instead
|
|
7774
|
+
* of answering `{}`, so without a budget a hub restart ends in a respawn
|
|
7775
|
+
* loop. A long, bounded budget; only after it the error propagates - the
|
|
7776
|
+
* addon never runs on defaults for a store it could not ask.
|
|
7777
|
+
* Anything else propagates immediately so real bugs surface.
|
|
7622
7778
|
*/
|
|
7623
7779
|
async readAddonStoreWithRetry() {
|
|
7624
7780
|
const settings = this.rawSettings();
|
|
7625
7781
|
if (!settings) return {};
|
|
7626
|
-
const delaysMs = [
|
|
7627
|
-
150,
|
|
7628
|
-
350,
|
|
7629
|
-
600,
|
|
7630
|
-
900
|
|
7631
|
-
];
|
|
7632
7782
|
let lastErr;
|
|
7633
|
-
for (let attempt = 0
|
|
7783
|
+
for (let attempt = 0;; attempt++) try {
|
|
7634
7784
|
const stored = await settings.readAddonStore() ?? {};
|
|
7635
7785
|
this.settingsStoreReady = true;
|
|
7636
7786
|
return stored;
|
|
7637
7787
|
} catch (err) {
|
|
7638
7788
|
lastErr = err;
|
|
7639
|
-
const
|
|
7640
|
-
if (
|
|
7641
|
-
|
|
7642
|
-
|
|
7789
|
+
const kind = classifyStoreError(err);
|
|
7790
|
+
if (kind === "permanent") throw err;
|
|
7791
|
+
const delay = (kind === "transport" ? TRANSPORT_RETRY_DELAYS_MS : RESTART_RETRY_DELAYS_MS)[attempt];
|
|
7792
|
+
if (delay === void 0) {
|
|
7793
|
+
if (kind === "transport") throw err;
|
|
7794
|
+
break;
|
|
7795
|
+
}
|
|
7796
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
7643
7797
|
}
|
|
7644
7798
|
this.settingsStoreReady = false;
|
|
7645
7799
|
this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
|
|
7646
7800
|
return {};
|
|
7647
7801
|
}
|
|
7648
7802
|
};
|
|
7803
|
+
/** The settings store's own restart fingerprints. */
|
|
7804
|
+
var RESTART_RETRY_DELAYS_MS = [
|
|
7805
|
+
150,
|
|
7806
|
+
350,
|
|
7807
|
+
600,
|
|
7808
|
+
900
|
|
7809
|
+
];
|
|
7810
|
+
/** A hub restarting under a booting runner: ~100 s in total. */
|
|
7811
|
+
var TRANSPORT_RETRY_DELAYS_MS = [
|
|
7812
|
+
150,
|
|
7813
|
+
350,
|
|
7814
|
+
600,
|
|
7815
|
+
900,
|
|
7816
|
+
2e3,
|
|
7817
|
+
5e3,
|
|
7818
|
+
1e4,
|
|
7819
|
+
2e4,
|
|
7820
|
+
3e4,
|
|
7821
|
+
3e4
|
|
7822
|
+
];
|
|
7823
|
+
var RESTART_FINGERPRINTS = ["SqliteSettingsBackend not initialized", "provider not available"];
|
|
7824
|
+
/** Node / socket error codes of a connection that did not happen or broke. */
|
|
7825
|
+
var TRANSPORT_CODES = new Set([
|
|
7826
|
+
"ECONNREFUSED",
|
|
7827
|
+
"ECONNRESET",
|
|
7828
|
+
"ETIMEDOUT",
|
|
7829
|
+
"EPIPE",
|
|
7830
|
+
"EHOSTUNREACH",
|
|
7831
|
+
"ENETUNREACH"
|
|
7832
|
+
]);
|
|
7833
|
+
/** tRPC error codes that mean "the other side is not answering now", not "your call is wrong". */
|
|
7834
|
+
var UNAVAILABLE_TRPC_CODES = new Set([
|
|
7835
|
+
"TIMEOUT",
|
|
7836
|
+
"SERVICE_UNAVAILABLE",
|
|
7837
|
+
"BAD_GATEWAY",
|
|
7838
|
+
"GATEWAY_TIMEOUT"
|
|
7839
|
+
]);
|
|
7840
|
+
/** The exact phrases OUR transport raises when it cannot reach the hub. */
|
|
7841
|
+
var TRANSPORT_PHRASES = [
|
|
7842
|
+
"discovery deadline exceeded",
|
|
7843
|
+
"parent could not route child unowned cap call",
|
|
7844
|
+
"UDS request timed out after",
|
|
7845
|
+
"channel closed"
|
|
7846
|
+
];
|
|
7847
|
+
/** Moleculer error `type`s of a call that could not be served right now. */
|
|
7848
|
+
var MOLECULER_TRANSIENT_TYPES = new Set([
|
|
7849
|
+
"REQUEST_TIMEOUT",
|
|
7850
|
+
"SERVICE_NOT_FOUND",
|
|
7851
|
+
"SERVICE_NOT_AVAILABLE",
|
|
7852
|
+
"REQUEST_REJECTED",
|
|
7853
|
+
"BROKER_DISCONNECTED"
|
|
7854
|
+
]);
|
|
7855
|
+
function stringProp(value, key) {
|
|
7856
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
7857
|
+
const found = Reflect.get(value, key);
|
|
7858
|
+
return typeof found === "string" ? found : void 0;
|
|
7859
|
+
}
|
|
7860
|
+
/** The code an error carries: its own, its cause's, or a tRPC `data.code`. */
|
|
7861
|
+
function errorCode(err) {
|
|
7862
|
+
const out = {};
|
|
7863
|
+
const cause = typeof err === "object" && err !== null ? Reflect.get(err, "cause") : void 0;
|
|
7864
|
+
const data = typeof err === "object" && err !== null ? Reflect.get(err, "data") : void 0;
|
|
7865
|
+
const node = stringProp(err, "code") ?? stringProp(cause, "code");
|
|
7866
|
+
if (node !== void 0) out.node = node;
|
|
7867
|
+
const trpc = stringProp(data, "code");
|
|
7868
|
+
if (trpc !== void 0) out.trpc = trpc;
|
|
7869
|
+
return out;
|
|
7870
|
+
}
|
|
7871
|
+
/**
|
|
7872
|
+
* Is a settings read failure worth retrying, and with which budget? Only
|
|
7873
|
+
* EXPLICIT signals count: a socket error code, a tRPC "unavailable" code, or
|
|
7874
|
+
* one of the exact phrases our own transport raises. A message that merely
|
|
7875
|
+
* contains "timed out" or "unreachable" is not evidence of anything, and
|
|
7876
|
+
* `settings store not wired` is a wiring bug that retrying cannot fix.
|
|
7877
|
+
*/
|
|
7878
|
+
function classifyStoreError(err) {
|
|
7879
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7880
|
+
if (RESTART_FINGERPRINTS.some((f) => message.includes(f))) return "restart";
|
|
7881
|
+
const code = errorCode(err);
|
|
7882
|
+
if (code.node !== void 0 && TRANSPORT_CODES.has(code.node)) return "transport";
|
|
7883
|
+
if (code.trpc !== void 0 && UNAVAILABLE_TRPC_CODES.has(code.trpc)) return "transport";
|
|
7884
|
+
if (typeof err === "object" && err !== null) {
|
|
7885
|
+
if (Reflect.get(err, "retryable") === true) return "transport";
|
|
7886
|
+
const type = stringProp(err, "type");
|
|
7887
|
+
if (type !== void 0 && MOLECULER_TRANSIENT_TYPES.has(type)) return "transport";
|
|
7888
|
+
}
|
|
7889
|
+
if (TRANSPORT_PHRASES.some((f) => message.includes(f))) return "transport";
|
|
7890
|
+
return "permanent";
|
|
7891
|
+
}
|
|
7649
7892
|
/**
|
|
7650
7893
|
* Collect the keys of every field marked `perNode: true`, recursing into
|
|
7651
7894
|
* layout containers (`group` fields and `sub-tabs` tabs) the same way
|
|
@@ -7735,6 +7978,365 @@ var PeerBytesTicketSchema = object({
|
|
|
7735
7978
|
*/
|
|
7736
7979
|
declaredBytes: number().int().nonnegative().nullable()
|
|
7737
7980
|
});
|
|
7981
|
+
/** Limits and names of `ctx.records` (D881). One authority; the engine and the facade read it. */
|
|
7982
|
+
/** The files a kind may live in. `history` is device-history's (D850), never records. */
|
|
7983
|
+
var RECORD_DATABASES = ["settings", "events"];
|
|
7984
|
+
/** The classes of a kind: the `@durable` vocabulary, one authority (replaces the annotation). */
|
|
7985
|
+
var RECORD_CLASSES = [
|
|
7986
|
+
"config",
|
|
7987
|
+
"ledger",
|
|
7988
|
+
"audit",
|
|
7989
|
+
"mirror",
|
|
7990
|
+
"ephemeral"
|
|
7991
|
+
];
|
|
7992
|
+
var RECORD_DEFAULT_MAX_DATA_BYTES = 64 * 1024;
|
|
7993
|
+
var RECORD_MAX_DATA_BYTES = 1024 * 1024;
|
|
7994
|
+
var RECORD_BATCH_MAX = 1e3;
|
|
7995
|
+
var RECORD_QUERY_MAX = 1e3;
|
|
7996
|
+
/** A kind in the `settings` file may not bound itself above this (D447: high volume is `events`). */
|
|
7997
|
+
var RECORD_SETTINGS_MAX_ROWS = 5e4;
|
|
7998
|
+
var PREFIX = "records/";
|
|
7999
|
+
/** The base of every records failure. `detail` is the human part of the message. */
|
|
8000
|
+
var RecordsError = class extends Error {
|
|
8001
|
+
code;
|
|
8002
|
+
detail;
|
|
8003
|
+
constructor(code, detail) {
|
|
8004
|
+
super(`${PREFIX}${code}: ${detail}`);
|
|
8005
|
+
this.name = "RecordsError";
|
|
8006
|
+
this.code = code;
|
|
8007
|
+
this.detail = detail;
|
|
8008
|
+
}
|
|
8009
|
+
};
|
|
8010
|
+
/** The keychain could not store or serve a value: explicit, never an empty string. */
|
|
8011
|
+
var RecordSecretUnavailableError = class extends RecordsError {
|
|
8012
|
+
constructor(detail) {
|
|
8013
|
+
super("secret-unavailable", detail);
|
|
8014
|
+
this.name = "RecordSecretUnavailableError";
|
|
8015
|
+
}
|
|
8016
|
+
};
|
|
8017
|
+
var InvalidRecordError = class extends RecordsError {
|
|
8018
|
+
constructor(detail) {
|
|
8019
|
+
super("invalid-record", detail);
|
|
8020
|
+
this.name = "InvalidRecordError";
|
|
8021
|
+
}
|
|
8022
|
+
};
|
|
8023
|
+
/** A kind declaration the engine refuses (non-additive change, bad shape, wrong file). */
|
|
8024
|
+
var DeclarationRefusedError = class extends RecordsError {
|
|
8025
|
+
constructor(detail) {
|
|
8026
|
+
super("declaration-refused", detail);
|
|
8027
|
+
this.name = "DeclarationRefusedError";
|
|
8028
|
+
}
|
|
8029
|
+
};
|
|
8030
|
+
function defOf(schema) {
|
|
8031
|
+
const zodBag = Reflect.get(schema, "_zod");
|
|
8032
|
+
if (typeof zodBag !== "object" || zodBag === null) return {};
|
|
8033
|
+
const def = Reflect.get(zodBag, "def");
|
|
8034
|
+
if (typeof def !== "object" || def === null) return {};
|
|
8035
|
+
return def;
|
|
8036
|
+
}
|
|
8037
|
+
function declaredHere(schema) {
|
|
8038
|
+
return globalRegistry.get(schema)?.secret === true;
|
|
8039
|
+
}
|
|
8040
|
+
/** Wrapper kinds whose `innerType` is the field itself. */
|
|
8041
|
+
var WRAPPERS = new Set([
|
|
8042
|
+
"optional",
|
|
8043
|
+
"nullable",
|
|
8044
|
+
"default",
|
|
8045
|
+
"prefault",
|
|
8046
|
+
"catch",
|
|
8047
|
+
"readonly",
|
|
8048
|
+
"nonoptional"
|
|
8049
|
+
]);
|
|
8050
|
+
/**
|
|
8051
|
+
* The schema layers that make up ONE field: the schema itself and every
|
|
8052
|
+
* wrapper below it (optional / default / nullable / readonly / catch), plus
|
|
8053
|
+
* both sides of a pipe (`z.preprocess` puts the real schema on `out`,
|
|
8054
|
+
* `.transform` on `in`).
|
|
8055
|
+
*/
|
|
8056
|
+
function layersOf(schema) {
|
|
8057
|
+
const out = [];
|
|
8058
|
+
const queue = [schema];
|
|
8059
|
+
while (queue.length > 0) {
|
|
8060
|
+
const s = queue.shift();
|
|
8061
|
+
if (!s || out.includes(s)) continue;
|
|
8062
|
+
out.push(s);
|
|
8063
|
+
const def = defOf(s);
|
|
8064
|
+
if (def.type !== void 0 && WRAPPERS.has(def.type) && def.innerType) queue.push(def.innerType);
|
|
8065
|
+
if (def.type === "pipe") {
|
|
8066
|
+
if (def.in) queue.push(def.in);
|
|
8067
|
+
if (def.out) queue.push(def.out);
|
|
8068
|
+
}
|
|
8069
|
+
}
|
|
8070
|
+
return out;
|
|
8071
|
+
}
|
|
8072
|
+
/** True when any layer of this field declares `.meta({ secret: true })`. */
|
|
8073
|
+
function isZodSecret(schema) {
|
|
8074
|
+
return layersOf(schema).some(declaredHere);
|
|
8075
|
+
}
|
|
8076
|
+
/**
|
|
8077
|
+
* Every declared secret path in a config schema: dotted keys, `[]` for "each
|
|
8078
|
+
* element of this array" (`connection.password`, `rows[].token`). A declared
|
|
8079
|
+
* object or array is reported as one path and not descended into — everything
|
|
8080
|
+
* under it is secret.
|
|
8081
|
+
*/
|
|
8082
|
+
function collectZodSecretPaths(schema, prefix = "") {
|
|
8083
|
+
if (isZodSecret(schema)) return prefix.length > 0 ? [prefix] : [];
|
|
8084
|
+
const paths = [];
|
|
8085
|
+
for (const layer of layersOf(schema)) {
|
|
8086
|
+
const def = defOf(layer);
|
|
8087
|
+
if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
|
|
8088
|
+
else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
|
|
8089
|
+
else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
|
|
8090
|
+
else if (def.type === "intersection") {
|
|
8091
|
+
if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
|
|
8092
|
+
if (def.right) paths.push(...collectZodSecretPaths(def.right, prefix));
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
return [...new Set(paths)];
|
|
8096
|
+
}
|
|
8097
|
+
/**
|
|
8098
|
+
* Secret fields of a record, addressed the way a Zod schema declares them
|
|
8099
|
+
* (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
|
|
8100
|
+
* (`auth.accessToken`, `headers[].value`).
|
|
8101
|
+
*
|
|
8102
|
+
* Array elements are matched by a DECLARED id key (`secretArrayKeys`:
|
|
8103
|
+
* `{ headers: 'name' }`), never by index, so reordering a list keeps every
|
|
8104
|
+
* keychain ref (the same rule as `arrayItemFieldPath`). The concrete path of a
|
|
8105
|
+
* leaf names its element: `headers[X-Api].value`.
|
|
8106
|
+
*
|
|
8107
|
+
* Pure and browser-safe. Nothing here mutates: every transform returns a new value.
|
|
8108
|
+
*/
|
|
8109
|
+
/** The array prefixes (declared form, e.g. `headers` or `a.headers`) of a path. */
|
|
8110
|
+
function parseSecretPath(path) {
|
|
8111
|
+
if (path.length === 0) throw new InvalidRecordError("an empty secret path");
|
|
8112
|
+
return path.split(".").map((raw) => {
|
|
8113
|
+
const each = raw.endsWith("[]");
|
|
8114
|
+
const key = each ? raw.slice(0, -2) : raw;
|
|
8115
|
+
if (key.length === 0) throw new InvalidRecordError(`secret path "${path}" has an empty segment`);
|
|
8116
|
+
return {
|
|
8117
|
+
key,
|
|
8118
|
+
each
|
|
8119
|
+
};
|
|
8120
|
+
});
|
|
8121
|
+
}
|
|
8122
|
+
/** The declared array prefixes of `path` that need an id key (`headers`, `a.rows`). */
|
|
8123
|
+
function secretArrayPrefixes(path) {
|
|
8124
|
+
const segments = parseSecretPath(path);
|
|
8125
|
+
const prefixes = [];
|
|
8126
|
+
const walked = [];
|
|
8127
|
+
for (const segment of segments) {
|
|
8128
|
+
walked.push(segment.key);
|
|
8129
|
+
if (segment.each) prefixes.push(walked.join("."));
|
|
8130
|
+
}
|
|
8131
|
+
return prefixes;
|
|
8132
|
+
}
|
|
8133
|
+
/**
|
|
8134
|
+
* Record kinds (D881): the declaration an addon makes of one kind of fact, the
|
|
8135
|
+
* Zod row schema that describes it, and the helper that turns the two into the
|
|
8136
|
+
* JSON the engine stores.
|
|
8137
|
+
*
|
|
8138
|
+
* A kind is declared ONCE, next to the code that owns it, and the engine's
|
|
8139
|
+
* reconciler creates its indexes - nothing creates DDL at runtime from a write.
|
|
8140
|
+
* Everything the generic machinery needs (secret fields, the entry descriptor)
|
|
8141
|
+
* is DERIVED from the Zod schema: `.meta({ secret: true })` marks a credential,
|
|
8142
|
+
* never a field name (D879).
|
|
8143
|
+
*/
|
|
8144
|
+
/** A JSON field path: `trackId`, `auth.accessToken`. Charset-restricted: it is rendered into DDL. */
|
|
8145
|
+
var RecordFieldPathSchema = string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}(\.[A-Za-z_][A-Za-z0-9_]{0,63}){0,3}$/);
|
|
8146
|
+
var RecordIndexSchema = object({
|
|
8147
|
+
name: string().regex(/^[a-z][a-z0-9_]{0,31}$/),
|
|
8148
|
+
fields: array(RecordFieldPathSchema).min(1).max(4),
|
|
8149
|
+
unique: boolean().optional()
|
|
8150
|
+
}).strict();
|
|
8151
|
+
var RecordRetentionSchema = union([
|
|
8152
|
+
object({
|
|
8153
|
+
ttlMs: number().int().positive(),
|
|
8154
|
+
from: _enum(["at", "write"])
|
|
8155
|
+
}).strict(),
|
|
8156
|
+
object({
|
|
8157
|
+
maxRows: number().int().positive().optional(),
|
|
8158
|
+
maxRowsPerDevice: number().int().positive().optional()
|
|
8159
|
+
}).strict().refine((r) => r.maxRows !== void 0 || r.maxRowsPerDevice !== void 0, { message: "name maxRows, maxRowsPerDevice or both" }),
|
|
8160
|
+
object({
|
|
8161
|
+
owner: string().min(1),
|
|
8162
|
+
reason: string().min(1)
|
|
8163
|
+
}).strict(),
|
|
8164
|
+
object({ none: string().min(1) }).strict()
|
|
8165
|
+
]);
|
|
8166
|
+
/** The descriptor a generic entries UI reads (D881 "generic entries"). */
|
|
8167
|
+
var EntryDescriptorSchema = object({
|
|
8168
|
+
title: string().min(1),
|
|
8169
|
+
/** The record body field shown as the entry's name. */
|
|
8170
|
+
nameField: string().min(1),
|
|
8171
|
+
/** JSON Schema of the entry body; `secret: true` marks a password field. */
|
|
8172
|
+
fields: record(string(), unknown())
|
|
8173
|
+
}).strict();
|
|
8174
|
+
/** What crosses the wire and sits in the engine's catalog: JSON only, no Zod. */
|
|
8175
|
+
var RecordKindDeclarationSchema = object({
|
|
8176
|
+
database: _enum(RECORD_DATABASES).default("settings"),
|
|
8177
|
+
class: _enum(RECORD_CLASSES),
|
|
8178
|
+
writeTrigger: string().min(1),
|
|
8179
|
+
device: _enum([
|
|
8180
|
+
"required",
|
|
8181
|
+
"optional",
|
|
8182
|
+
"none"
|
|
8183
|
+
]).default("none"),
|
|
8184
|
+
at: _enum(["required", "none"]).default("none"),
|
|
8185
|
+
indexes: array(RecordIndexSchema).max(8).default([]),
|
|
8186
|
+
retention: RecordRetentionSchema,
|
|
8187
|
+
deviceRemoval: _enum(["delete", "none"]).default("none"),
|
|
8188
|
+
maxDataBytes: number().int().positive().max(RECORD_MAX_DATA_BYTES).default(RECORD_DEFAULT_MAX_DATA_BYTES),
|
|
8189
|
+
/** Derived from the Zod schema; never written by hand. */
|
|
8190
|
+
secretPaths: array(string().min(1)).default([]),
|
|
8191
|
+
/** The id key of each array that carries a secret (`{ headers: 'name' }`). */
|
|
8192
|
+
secretArrayKeys: record(string(), string()).default({}),
|
|
8193
|
+
/** The legacy table this kind adopts from; the boot guard reads it (D853: no migration code). */
|
|
8194
|
+
adoptsFrom: string().min(1).optional(),
|
|
8195
|
+
/** Kinds that must agree through `transact` sit in one file under one family name. */
|
|
8196
|
+
transactFamily: string().min(1).optional(),
|
|
8197
|
+
entry: EntryDescriptorSchema.optional()
|
|
8198
|
+
}).strict();
|
|
8199
|
+
/** The kind names: lower-case, dash separated, short enough for an index name. */
|
|
8200
|
+
var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
|
|
8201
|
+
/** Cross-field rules a declaration must satisfy. Throws {@link DeclarationRefusedError}. */
|
|
8202
|
+
function assertValidDeclaration(name, decl) {
|
|
8203
|
+
const refuse = (why) => {
|
|
8204
|
+
throw new DeclarationRefusedError(`kind "${name}": ${why}`);
|
|
8205
|
+
};
|
|
8206
|
+
if (!RecordKindNameSchema.safeParse(name).success) refuse("the name must be lower-case-dashed");
|
|
8207
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8208
|
+
for (const index of decl.indexes) {
|
|
8209
|
+
if (seen.has(index.name)) refuse(`index name "${index.name}" is declared twice`);
|
|
8210
|
+
seen.add(index.name);
|
|
8211
|
+
}
|
|
8212
|
+
const { retention } = decl;
|
|
8213
|
+
const hasSecrets = decl.secretPaths.length > 0;
|
|
8214
|
+
if ("ttlMs" in retention) {
|
|
8215
|
+
if (retention.from === "at" && decl.at !== "required") refuse("a ttl from \"at\" needs at: \"required\"");
|
|
8216
|
+
if (hasSecrets) refuse("a kind with secret fields cannot use engine TTL (its keychain values would be orphaned)");
|
|
8217
|
+
}
|
|
8218
|
+
if ("maxRows" in retention || "maxRowsPerDevice" in retention) {
|
|
8219
|
+
if (retention.maxRowsPerDevice !== void 0 && decl.device !== "required") refuse("maxRowsPerDevice needs device: \"required\"");
|
|
8220
|
+
if (decl.at !== "required") refuse("a row bound trims the oldest \"at\": it needs at: \"required\"");
|
|
8221
|
+
if (hasSecrets) refuse("a kind with secret fields cannot use an engine row bound");
|
|
8222
|
+
const bound = Math.max(retention.maxRows ?? 0, retention.maxRowsPerDevice ?? 0);
|
|
8223
|
+
if (decl.database === "settings" && bound > 5e4) refuse(`a row bound above ${RECORD_SETTINGS_MAX_ROWS} belongs in database: "events" (a big delete on the settings thread stalls every configuration read)`);
|
|
8224
|
+
}
|
|
8225
|
+
if (decl.deviceRemoval === "delete" && decl.device === "none") refuse("deviceRemoval: \"delete\" needs a device: \"required\" or \"optional\"");
|
|
8226
|
+
for (const path of decl.secretPaths) for (const prefix of secretArrayPrefixes(path)) if (decl.secretArrayKeys[prefix] === void 0) refuse(`secret path "${path}" crosses the array "${prefix}": declare its id key in secretArrayKeys`);
|
|
8227
|
+
}
|
|
8228
|
+
function bodyFieldsOf(schema) {
|
|
8229
|
+
return { ...toJSONSchema(schema, {
|
|
8230
|
+
unrepresentable: "any",
|
|
8231
|
+
io: "input"
|
|
8232
|
+
}) };
|
|
8233
|
+
}
|
|
8234
|
+
function isRecordLike(value) {
|
|
8235
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8236
|
+
}
|
|
8237
|
+
/** Does every branch of the body schema carry `field`? (A discriminated union has several.) */
|
|
8238
|
+
function hasNameField(json, field) {
|
|
8239
|
+
const branches = json["oneOf"] ?? json["anyOf"];
|
|
8240
|
+
if (Array.isArray(branches)) return branches.length > 0 && branches.every((b) => isRecordLike(b) && hasNameField(b, field));
|
|
8241
|
+
const properties = json["properties"];
|
|
8242
|
+
return isRecordLike(properties) && Object.prototype.hasOwnProperty.call(properties, field);
|
|
8243
|
+
}
|
|
8244
|
+
/**
|
|
8245
|
+
* Declare the kinds of a package. The keys are the kind names. The result is
|
|
8246
|
+
* what `ctx.records.declare` sends to the engine and what `ctx.records.kind`
|
|
8247
|
+
* types its handle from.
|
|
8248
|
+
*/
|
|
8249
|
+
function defineRecordKinds(specs) {
|
|
8250
|
+
const out = {};
|
|
8251
|
+
for (const [name, spec] of Object.entries(specs)) {
|
|
8252
|
+
const secretPaths = collectZodSecretPaths(spec.schema);
|
|
8253
|
+
const nameField = spec.entry?.nameField ?? "name";
|
|
8254
|
+
const fields = bodyFieldsOf(spec.schema);
|
|
8255
|
+
if (spec.entry !== void 0 && !hasNameField(fields, nameField)) throw new DeclarationRefusedError(`kind "${name}": an entry kind's schema needs a "${nameField}" field`);
|
|
8256
|
+
const parsed = RecordKindDeclarationSchema.parse({
|
|
8257
|
+
database: spec.database,
|
|
8258
|
+
class: spec.class,
|
|
8259
|
+
writeTrigger: spec.writeTrigger,
|
|
8260
|
+
device: spec.device,
|
|
8261
|
+
at: spec.at,
|
|
8262
|
+
indexes: spec.indexes === void 0 ? void 0 : [...spec.indexes],
|
|
8263
|
+
retention: spec.retention,
|
|
8264
|
+
deviceRemoval: spec.deviceRemoval,
|
|
8265
|
+
maxDataBytes: spec.maxDataBytes,
|
|
8266
|
+
secretPaths: [...secretPaths],
|
|
8267
|
+
secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
|
|
8268
|
+
adoptsFrom: spec.adoptsFrom,
|
|
8269
|
+
transactFamily: spec.transactFamily,
|
|
8270
|
+
entry: spec.entry === void 0 ? void 0 : {
|
|
8271
|
+
title: spec.entry.title,
|
|
8272
|
+
nameField,
|
|
8273
|
+
fields
|
|
8274
|
+
}
|
|
8275
|
+
});
|
|
8276
|
+
assertValidDeclaration(name, parsed);
|
|
8277
|
+
out[name] = {
|
|
8278
|
+
name,
|
|
8279
|
+
schema: spec.schema,
|
|
8280
|
+
declaration: parsed
|
|
8281
|
+
};
|
|
8282
|
+
}
|
|
8283
|
+
return out;
|
|
8284
|
+
}
|
|
8285
|
+
/**
|
|
8286
|
+
* Entry kinds (D881): every list of user-created entries (notification
|
|
8287
|
+
* targets, brokers, LLM profiles) is ONE record kind with an id, a name and a
|
|
8288
|
+
* kind-specific body, and the admin UI lists/adds/edits/deletes all of them
|
|
8289
|
+
* through the `record-entries` capability with password fields derived from
|
|
8290
|
+
* the schema (`.meta({ secret: true })`).
|
|
8291
|
+
*/
|
|
8292
|
+
/** The body of a plain entry: a name and the fields the kind adds. */
|
|
8293
|
+
function entryBody(shape) {
|
|
8294
|
+
return object({
|
|
8295
|
+
name: string().min(1),
|
|
8296
|
+
...shape
|
|
8297
|
+
});
|
|
8298
|
+
}
|
|
8299
|
+
/**
|
|
8300
|
+
* Declare one entry kind. An entry is configuration: never trimmed by the
|
|
8301
|
+
* engine, never aged, belonging to no device.
|
|
8302
|
+
*/
|
|
8303
|
+
function defineEntryKind(name, spec) {
|
|
8304
|
+
const defined = defineRecordKinds({ [name]: {
|
|
8305
|
+
schema: spec.schema,
|
|
8306
|
+
class: "config",
|
|
8307
|
+
writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
|
|
8308
|
+
retention: { none: "an entry goes only when the operator deletes it" },
|
|
8309
|
+
...spec.database === void 0 ? {} : { database: spec.database },
|
|
8310
|
+
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
8311
|
+
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
8312
|
+
...spec.adoptsFrom === void 0 ? {} : { adoptsFrom: spec.adoptsFrom },
|
|
8313
|
+
entry: {
|
|
8314
|
+
title: spec.title,
|
|
8315
|
+
...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
|
|
8316
|
+
}
|
|
8317
|
+
} })[name];
|
|
8318
|
+
if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
|
|
8319
|
+
return defined;
|
|
8320
|
+
}
|
|
8321
|
+
/**
|
|
8322
|
+
* Legacy source for broker entry adoption (B2): brokers live in addon-settings
|
|
8323
|
+
* as one row `<addonId>.brokers` whose value is an array until records-adopt
|
|
8324
|
+
* copies them into the entry kind.
|
|
8325
|
+
*/
|
|
8326
|
+
var SETTINGS_BROKERS_ADOPTS_PREFIX = "@settings-brokers/";
|
|
8327
|
+
/** The `adoptsFrom` value for an addon's broker entry kind. */
|
|
8328
|
+
function settingsBrokersAdoptsFrom(addonId) {
|
|
8329
|
+
return `${SETTINGS_BROKERS_ADOPTS_PREFIX}${addonId}`;
|
|
8330
|
+
}
|
|
8331
|
+
/**
|
|
8332
|
+
* Legacy source for entry adoption (B3): a list stored under addon-settings
|
|
8333
|
+
* `<addonId>.<key>` until records-adopt copies rows into the entry kind.
|
|
8334
|
+
*/
|
|
8335
|
+
var SETTINGS_ARRAY_ADOPTS_PREFIX = "@settings-array/";
|
|
8336
|
+
/** The `adoptsFrom` value for an addon-settings array field. */
|
|
8337
|
+
function settingsArrayAdoptsFrom(addonId, settingsKey) {
|
|
8338
|
+
return `${SETTINGS_ARRAY_ADOPTS_PREFIX}${addonId}/${settingsKey}`;
|
|
8339
|
+
}
|
|
7738
8340
|
/** Shared Zod schemas used across streaming capabilities. */
|
|
7739
8341
|
var CamProfileSchema = _enum([
|
|
7740
8342
|
"high",
|
|
@@ -8200,7 +8802,7 @@ object({
|
|
|
8200
8802
|
params: record(string(), union([string(), number()])).optional()
|
|
8201
8803
|
});
|
|
8202
8804
|
//#endregion
|
|
8203
|
-
//#region ../types/dist/err-msg
|
|
8805
|
+
//#region ../types/dist/err-msg--2RrYiLO.mjs
|
|
8204
8806
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
8205
8807
|
DeviceType["Camera"] = "camera";
|
|
8206
8808
|
DeviceType["Hub"] = "hub";
|
|
@@ -8469,6 +9071,9 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
|
8469
9071
|
/** Virtual HA toggle (input_boolean.*) — distinguishable from a
|
|
8470
9072
|
* real Switch device for UI rendering / export adapters. */
|
|
8471
9073
|
DeviceRole["BinaryHelper"] = "binary-helper";
|
|
9074
|
+
/** A smart plug: a `switch` whose source says it is an outlet (HA
|
|
9075
|
+
* `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
|
|
9076
|
+
DeviceRole["Outlet"] = "outlet";
|
|
8472
9077
|
/** Generic motion / occupancy / moving event source. Distinct from
|
|
8473
9078
|
* the camera accessory PirSensor role: that one is a camera child;
|
|
8474
9079
|
* this is a standalone HA / 3rd-party motion sensor. */
|
|
@@ -8536,7 +9141,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
|
8536
9141
|
return DeviceRole;
|
|
8537
9142
|
}({});
|
|
8538
9143
|
//#endregion
|
|
8539
|
-
//#region ../types/dist/composition-
|
|
9144
|
+
//#region ../types/dist/composition-DOwQB_Ua.mjs
|
|
8540
9145
|
/**
|
|
8541
9146
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
8542
9147
|
*
|
|
@@ -11740,11 +12345,10 @@ var ClipModelMetaSchema = object({
|
|
|
11740
12345
|
/**
|
|
11741
12346
|
* The `vector-store` index this model's vectors live in. ONE feature space
|
|
11742
12347
|
* per index: two models share an index only when their vectors are ranked
|
|
11743
|
-
* against each other, which for CLIP means never across families.
|
|
11744
|
-
*
|
|
11745
|
-
* `modelId`); any other model gets its own.
|
|
12348
|
+
* against each other, which for CLIP means never across families. Every
|
|
12349
|
+
* model gets its own.
|
|
11746
12350
|
*/
|
|
11747
|
-
vectorIndex: string().regex(/^object-clip
|
|
12351
|
+
vectorIndex: string().regex(/^object-clip-[a-z0-9][a-z0-9-]*$/),
|
|
11748
12352
|
/**
|
|
11749
12353
|
* The text encoder's tokenizer, a declared SIBLING file of its onnx. The name
|
|
11750
12354
|
* must be unique per tokenizer: every model's siblings land in ONE flat
|
|
@@ -15420,6 +16024,41 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
|
|
|
15420
16024
|
keyField: string()
|
|
15421
16025
|
}).optional()
|
|
15422
16026
|
})) });
|
|
16027
|
+
/**
|
|
16028
|
+
* The report of a one-shot "move this owner's plaintext secrets into the
|
|
16029
|
+
* keychain" run (D878). COUNTS only - never a value, never a ref.
|
|
16030
|
+
*
|
|
16031
|
+
* The owner runs it on its own rows, through its own write path; an operator
|
|
16032
|
+
* script (`scripts/hub/migrate-raw-secrets.ts`) calls the owner's cap method.
|
|
16033
|
+
* Dry run (`apply: false`) reports what WOULD move and changes nothing.
|
|
16034
|
+
*/
|
|
16035
|
+
var RawSecretMigrationInputSchema = object({
|
|
16036
|
+
/** `false` (the default of the script) = report only. */
|
|
16037
|
+
apply: boolean() });
|
|
16038
|
+
var RawSecretMigrationReportSchema = object({
|
|
16039
|
+
apply: boolean(),
|
|
16040
|
+
/** Rows that can carry a secret (a profile, an enrolment). */
|
|
16041
|
+
rows: number().int(),
|
|
16042
|
+
/** Rows whose secret was plaintext when the run started. */
|
|
16043
|
+
raw: number().int(),
|
|
16044
|
+
/** Rows that already held a keychain marker when the run started. */
|
|
16045
|
+
markers: number().int(),
|
|
16046
|
+
/** Plaintext rows moved by THIS run (always 0 on a dry run). */
|
|
16047
|
+
migrated: number().int(),
|
|
16048
|
+
/** Plaintext rows left in place because the move or its read-back failed. */
|
|
16049
|
+
failed: number().int(),
|
|
16050
|
+
/** After the run: rows still holding plaintext. */
|
|
16051
|
+
remainingRaw: number().int(),
|
|
16052
|
+
/** After the run: markers the keychain could not resolve right now. */
|
|
16053
|
+
unresolvedMarkers: number().int(),
|
|
16054
|
+
/**
|
|
16055
|
+
* A second, redundant copy of the secrets' rows that the run found (and, on
|
|
16056
|
+
* `apply`, deleted after verifying the first). Owners with a single copy omit it.
|
|
16057
|
+
*/
|
|
16058
|
+
redundantCopies: number().int().optional(),
|
|
16059
|
+
/** True when nothing is raw and every marker resolves. */
|
|
16060
|
+
verified: boolean()
|
|
16061
|
+
});
|
|
15423
16062
|
/** Where a block runs. The operator chooses — a block driving a device on an
|
|
15424
16063
|
* agent is the reason placement is not fixed to the hub. */
|
|
15425
16064
|
var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
|
|
@@ -15646,6 +16285,9 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
15646
16285
|
}), SettingsUpdateResultSchema, {
|
|
15647
16286
|
kind: "mutation",
|
|
15648
16287
|
auth: "admin"
|
|
16288
|
+
}), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
|
|
16289
|
+
kind: "mutation",
|
|
16290
|
+
auth: "admin"
|
|
15649
16291
|
}), method(object({}), object({ libs: array(object({
|
|
15650
16292
|
filePath: string(),
|
|
15651
16293
|
content: string()
|
|
@@ -15909,6 +16551,347 @@ id: union([string(), number()]) }), {
|
|
|
15909
16551
|
auth: "admin"
|
|
15910
16552
|
});
|
|
15911
16553
|
/**
|
|
16554
|
+
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
16555
|
+
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
16556
|
+
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
16557
|
+
*
|
|
16558
|
+
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
16559
|
+
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
16560
|
+
* that with the addon id closed over). It is deliberately NOT in any input
|
|
16561
|
+
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
16562
|
+
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
16563
|
+
* addons go through the owner's capability.
|
|
16564
|
+
*
|
|
16565
|
+
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
16566
|
+
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
16567
|
+
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
16568
|
+
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
16569
|
+
* row's own ref.
|
|
16570
|
+
*
|
|
16571
|
+
* Internal: addons reach it through `ctx.records`, never directly.
|
|
16572
|
+
*/
|
|
16573
|
+
var RecordIdSchema = string().min(1).max(256);
|
|
16574
|
+
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
16575
|
+
var RecordBodySchema = record(string(), unknown());
|
|
16576
|
+
var RecordInputSchema = object({
|
|
16577
|
+
id: RecordIdSchema,
|
|
16578
|
+
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
16579
|
+
deviceId: number().int().optional(),
|
|
16580
|
+
/** The domain clock, ms: observed / created / opened. */
|
|
16581
|
+
at: number().int().optional(),
|
|
16582
|
+
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
16583
|
+
expiresAt: number().int().optional(),
|
|
16584
|
+
data: RecordBodySchema
|
|
16585
|
+
});
|
|
16586
|
+
var RecordViewSchema = object({
|
|
16587
|
+
id: string(),
|
|
16588
|
+
deviceId: number().int().nullable(),
|
|
16589
|
+
at: number().nullable(),
|
|
16590
|
+
expiresAt: number().nullable(),
|
|
16591
|
+
rev: number().int(),
|
|
16592
|
+
updatedAt: number(),
|
|
16593
|
+
data: RecordBodySchema
|
|
16594
|
+
});
|
|
16595
|
+
/** Declared index fields only. Anything else does not compile, by design. */
|
|
16596
|
+
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
16597
|
+
string(),
|
|
16598
|
+
number(),
|
|
16599
|
+
boolean(),
|
|
16600
|
+
object({ in: array(union([
|
|
16601
|
+
string(),
|
|
16602
|
+
number(),
|
|
16603
|
+
boolean()
|
|
16604
|
+
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
16605
|
+
object({
|
|
16606
|
+
gt: union([string(), number()]).optional(),
|
|
16607
|
+
gte: union([string(), number()]).optional(),
|
|
16608
|
+
lt: union([string(), number()]).optional(),
|
|
16609
|
+
lte: union([string(), number()]).optional()
|
|
16610
|
+
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
16611
|
+
]));
|
|
16612
|
+
var KindShape = { kind: RecordKindNameSchema };
|
|
16613
|
+
var TimeWindowShape = {
|
|
16614
|
+
deviceId: number().int().optional(),
|
|
16615
|
+
atFrom: number().optional(),
|
|
16616
|
+
atTo: number().optional()
|
|
16617
|
+
};
|
|
16618
|
+
var RecordQueryInputSchema = object({
|
|
16619
|
+
...KindShape,
|
|
16620
|
+
...TimeWindowShape,
|
|
16621
|
+
where: RecordWhereSchema.optional(),
|
|
16622
|
+
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
16623
|
+
order: _enum(["at", "id"]).optional(),
|
|
16624
|
+
desc: boolean().optional(),
|
|
16625
|
+
limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
16626
|
+
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
16627
|
+
cursor: string().min(1).optional()
|
|
16628
|
+
});
|
|
16629
|
+
var RecordQueryResultSchema = object({
|
|
16630
|
+
records: array(RecordViewSchema),
|
|
16631
|
+
nextCursor: string().nullable()
|
|
16632
|
+
});
|
|
16633
|
+
var RecordKindStateSchema = _enum([
|
|
16634
|
+
"created",
|
|
16635
|
+
"unchanged",
|
|
16636
|
+
"updated"
|
|
16637
|
+
]);
|
|
16638
|
+
var DeclareKindResultSchema = object({
|
|
16639
|
+
kindId: number().int(),
|
|
16640
|
+
state: RecordKindStateSchema,
|
|
16641
|
+
createdIndexes: array(string()),
|
|
16642
|
+
/** The boot guard: the kind is empty while its legacy table still holds rows. */
|
|
16643
|
+
adoptionPending: object({
|
|
16644
|
+
legacyTable: string(),
|
|
16645
|
+
legacyRows: number().int()
|
|
16646
|
+
}).nullable()
|
|
16647
|
+
});
|
|
16648
|
+
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
16649
|
+
var SecretRefsList = array(string());
|
|
16650
|
+
var RecordSecretRefsSchema = record(string(), record(string(), string()));
|
|
16651
|
+
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
|
|
16652
|
+
kind: "mutation",
|
|
16653
|
+
auth: "admin"
|
|
16654
|
+
}), method(object({
|
|
16655
|
+
...KindShape,
|
|
16656
|
+
id: RecordIdSchema
|
|
16657
|
+
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
16658
|
+
...KindShape,
|
|
16659
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
16660
|
+
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
16661
|
+
...KindShape,
|
|
16662
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
16663
|
+
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
16664
|
+
...KindShape,
|
|
16665
|
+
record: RecordInputSchema,
|
|
16666
|
+
/** Compare-and-set. `0` means "the row must not exist". */
|
|
16667
|
+
ifRev: number().int().min(0).optional()
|
|
16668
|
+
}), object({
|
|
16669
|
+
rev: number().int(),
|
|
16670
|
+
releasedRefs: SecretRefsList
|
|
16671
|
+
}), {
|
|
16672
|
+
kind: "mutation",
|
|
16673
|
+
auth: "admin"
|
|
16674
|
+
}), method(object({
|
|
16675
|
+
...KindShape,
|
|
16676
|
+
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
16677
|
+
}), object({
|
|
16678
|
+
count: number().int(),
|
|
16679
|
+
releasedRefs: SecretRefsList
|
|
16680
|
+
}), {
|
|
16681
|
+
kind: "mutation",
|
|
16682
|
+
auth: "admin"
|
|
16683
|
+
}), method(object({
|
|
16684
|
+
...KindShape,
|
|
16685
|
+
id: RecordIdSchema,
|
|
16686
|
+
data: RecordBodySchema,
|
|
16687
|
+
at: number().int().nullable().optional(),
|
|
16688
|
+
expiresAt: number().int().nullable().optional(),
|
|
16689
|
+
ifRev: number().int().min(1).optional()
|
|
16690
|
+
}), object({
|
|
16691
|
+
rev: number().int(),
|
|
16692
|
+
releasedRefs: SecretRefsList
|
|
16693
|
+
}), {
|
|
16694
|
+
kind: "mutation",
|
|
16695
|
+
auth: "admin"
|
|
16696
|
+
}), method(object({
|
|
16697
|
+
...KindShape,
|
|
16698
|
+
id: RecordIdSchema
|
|
16699
|
+
}), object({
|
|
16700
|
+
deleted: boolean(),
|
|
16701
|
+
secretRefs: SecretRefsList
|
|
16702
|
+
}), {
|
|
16703
|
+
kind: "mutation",
|
|
16704
|
+
auth: "admin"
|
|
16705
|
+
}), method(object({
|
|
16706
|
+
...KindShape,
|
|
16707
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
16708
|
+
}), object({
|
|
16709
|
+
deleted: number().int(),
|
|
16710
|
+
secretRefs: SecretRefsList
|
|
16711
|
+
}), {
|
|
16712
|
+
kind: "mutation",
|
|
16713
|
+
auth: "admin"
|
|
16714
|
+
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
16715
|
+
...KindShape,
|
|
16716
|
+
...TimeWindowShape,
|
|
16717
|
+
where: RecordWhereSchema.optional()
|
|
16718
|
+
}), object({ count: number().int() }), { auth: "admin" }), method(object({
|
|
16719
|
+
...KindShape,
|
|
16720
|
+
deviceId: number().int().optional(),
|
|
16721
|
+
from: number(),
|
|
16722
|
+
to: number(),
|
|
16723
|
+
bucketMs: number().int().positive()
|
|
16724
|
+
}), object({ buckets: array(object({
|
|
16725
|
+
bucketStart: number(),
|
|
16726
|
+
count: number().int()
|
|
16727
|
+
})) }), { auth: "admin" }), method(object({
|
|
16728
|
+
...KindShape,
|
|
16729
|
+
op: _enum([
|
|
16730
|
+
"sum",
|
|
16731
|
+
"min",
|
|
16732
|
+
"max",
|
|
16733
|
+
"count"
|
|
16734
|
+
]),
|
|
16735
|
+
field: RecordFieldPathSchema.optional(),
|
|
16736
|
+
groupBy: RecordFieldPathSchema.optional(),
|
|
16737
|
+
where: RecordWhereSchema.optional()
|
|
16738
|
+
}), object({ rows: array(object({
|
|
16739
|
+
group: union([
|
|
16740
|
+
string(),
|
|
16741
|
+
number(),
|
|
16742
|
+
_null()
|
|
16743
|
+
]),
|
|
16744
|
+
value: number().nullable()
|
|
16745
|
+
})) }), { auth: "admin" }), method(object({
|
|
16746
|
+
...KindShape,
|
|
16747
|
+
deviceId: number().int().optional(),
|
|
16748
|
+
atBefore: number().optional(),
|
|
16749
|
+
where: RecordWhereSchema.optional()
|
|
16750
|
+
}), object({
|
|
16751
|
+
deleted: number().int(),
|
|
16752
|
+
secretRefs: SecretRefsList
|
|
16753
|
+
}), {
|
|
16754
|
+
kind: "mutation",
|
|
16755
|
+
auth: "admin"
|
|
16756
|
+
}), method(object({
|
|
16757
|
+
deviceId: number().int(),
|
|
16758
|
+
kinds: array(RecordKindNameSchema).optional()
|
|
16759
|
+
}), object({
|
|
16760
|
+
deleted: record(string(), number().int()),
|
|
16761
|
+
secretRefs: SecretRefsList
|
|
16762
|
+
}), {
|
|
16763
|
+
kind: "mutation",
|
|
16764
|
+
auth: "admin"
|
|
16765
|
+
}), method(object(KindShape), object({ id: number().int() }), {
|
|
16766
|
+
kind: "mutation",
|
|
16767
|
+
auth: "admin"
|
|
16768
|
+
});
|
|
16769
|
+
/** One table of the settings database. */
|
|
16770
|
+
var StorageTableSchema = object({
|
|
16771
|
+
/** The table's full name, e.g. `recorder:addon-settings` or `addon-device-settings`. */
|
|
16772
|
+
name: string(),
|
|
16773
|
+
/** The owner prefix of a scoped name (`recorder` for `recorder:addon-settings`), else null. */
|
|
16774
|
+
owner: string().nullable(),
|
|
16775
|
+
rows: number().int().nonnegative(),
|
|
16776
|
+
/** Bytes the table's pages occupy (`dbstat`); null when the engine cannot measure. */
|
|
16777
|
+
bytes: number().int().nonnegative().nullable(),
|
|
16778
|
+
/** Declared by an addon since this engine process started (a running addon claims it). */
|
|
16779
|
+
declaredThisBoot: boolean()
|
|
16780
|
+
});
|
|
16781
|
+
/** One declared record kind (D881) as System -> Data shows it. */
|
|
16782
|
+
var StorageKindSchema = object({
|
|
16783
|
+
owner: string(),
|
|
16784
|
+
kind: string(),
|
|
16785
|
+
database: _enum(["settings", "events"]),
|
|
16786
|
+
class: string(),
|
|
16787
|
+
state: _enum([
|
|
16788
|
+
"declared",
|
|
16789
|
+
"orphaned",
|
|
16790
|
+
"dropped"
|
|
16791
|
+
]),
|
|
16792
|
+
/** `null` when the caller asked for no counts (`counts: false`). */
|
|
16793
|
+
rows: number().int().nonnegative().nullable(),
|
|
16794
|
+
/** Rows x the average size of a sample; null for an empty kind. */
|
|
16795
|
+
bytesEstimate: number().int().nonnegative().nullable(),
|
|
16796
|
+
writeTrigger: string(),
|
|
16797
|
+
retention: RecordRetentionSchema,
|
|
16798
|
+
/** `true` when the kind is an entry kind (the generic entries UI lists it). */
|
|
16799
|
+
entry: boolean(),
|
|
16800
|
+
/** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
|
|
16801
|
+
declaration: RecordKindDeclarationSchema
|
|
16802
|
+
});
|
|
16803
|
+
method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: array(StorageTableSchema) }), { auth: "admin" }), method(object({
|
|
16804
|
+
collection: string().min(1),
|
|
16805
|
+
expectRows: number().int().nonnegative()
|
|
16806
|
+
}), object({
|
|
16807
|
+
dropped: literal(true),
|
|
16808
|
+
rows: number().int().nonnegative()
|
|
16809
|
+
}), {
|
|
16810
|
+
kind: "mutation",
|
|
16811
|
+
auth: "admin"
|
|
16812
|
+
}), method(object({
|
|
16813
|
+
owner: string().min(1).optional(),
|
|
16814
|
+
/** `false` skips the per-kind row count (a catalog read only). Default true. */
|
|
16815
|
+
counts: boolean().optional()
|
|
16816
|
+
}), object({ kinds: array(StorageKindSchema) }), { auth: "admin" }), method(RecordQueryInputSchema.extend({
|
|
16817
|
+
owner: string().min(1),
|
|
16818
|
+
kind: RecordKindNameSchema
|
|
16819
|
+
}), RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
16820
|
+
owner: string().min(1),
|
|
16821
|
+
kind: RecordKindNameSchema,
|
|
16822
|
+
records: array(RecordInputSchema).max(RECORD_BATCH_MAX),
|
|
16823
|
+
/** The LAST batch: the copy is complete, the boot guard stops asking. */
|
|
16824
|
+
complete: boolean().optional()
|
|
16825
|
+
}).refine((input) => input.records.length > 0 || input.complete === true, { message: "records must be non-empty unless complete is true" }), object({ adopted: number().int() }), {
|
|
16826
|
+
kind: "mutation",
|
|
16827
|
+
auth: "admin"
|
|
16828
|
+
}), method(object({
|
|
16829
|
+
collection: string().min(1),
|
|
16830
|
+
cursor: string().optional(),
|
|
16831
|
+
limit: number().int().min(1).max(1e3).optional()
|
|
16832
|
+
}), object({
|
|
16833
|
+
rows: array(object({
|
|
16834
|
+
id: string(),
|
|
16835
|
+
data: record(string(), unknown())
|
|
16836
|
+
})),
|
|
16837
|
+
nextCursor: string().nullable()
|
|
16838
|
+
}), { auth: "admin" }), method(object({ collection: string().min(1) }), object({ adopted: boolean() }), { auth: "admin" });
|
|
16839
|
+
/**
|
|
16840
|
+
* record-entries - the ONE admin surface for every list of user-created entries
|
|
16841
|
+
* (D881): notification targets, brokers, LLM profiles, anything an addon
|
|
16842
|
+
* declares as an ENTRY kind (`entry: { title }` in `defineRecordKinds`).
|
|
16843
|
+
*
|
|
16844
|
+
* The UI is generic. `listEntryKinds` hands it, per kind, the JSON Schema of
|
|
16845
|
+
* the entry body with `secret: true` on every credential field, so a password
|
|
16846
|
+
* field is derived from the schema and never from a name. `putEntry` takes the
|
|
16847
|
+
* redaction token for an untouched secret (the stored value is kept), a string
|
|
16848
|
+
* for a new one, and the clear instruction to remove one. A read never carries
|
|
16849
|
+
* a secret value, and a locked keychain refuses the write with
|
|
16850
|
+
* `records/secret-unavailable` instead of storing plaintext.
|
|
16851
|
+
*
|
|
16852
|
+
* Hub-only, served in-process (it acts for each entry's owner on the keychain).
|
|
16853
|
+
*/
|
|
16854
|
+
var EntryKindInfoSchema = object({
|
|
16855
|
+
owner: string(),
|
|
16856
|
+
kind: RecordKindNameSchema,
|
|
16857
|
+
title: string(),
|
|
16858
|
+
nameField: string(),
|
|
16859
|
+
/** JSON Schema of the body; `secret: true` marks a password field. */
|
|
16860
|
+
fields: record(string(), unknown()),
|
|
16861
|
+
secretPaths: array(string()),
|
|
16862
|
+
rows: number().int().nonnegative()
|
|
16863
|
+
});
|
|
16864
|
+
var EntryViewSchema = object({
|
|
16865
|
+
id: RecordIdSchema,
|
|
16866
|
+
name: string(),
|
|
16867
|
+
rev: number().int(),
|
|
16868
|
+
updatedAt: number(),
|
|
16869
|
+
/** Secret fields hold the redaction token. */
|
|
16870
|
+
data: RecordBodySchema
|
|
16871
|
+
});
|
|
16872
|
+
var EntryAddress = {
|
|
16873
|
+
owner: string().min(1),
|
|
16874
|
+
kind: RecordKindNameSchema
|
|
16875
|
+
};
|
|
16876
|
+
method(object({}), object({ kinds: array(EntryKindInfoSchema) }), { auth: "admin" }), method(object({ ...EntryAddress }), object({ entries: array(EntryViewSchema) }), { auth: "admin" }), method(object({
|
|
16877
|
+
...EntryAddress,
|
|
16878
|
+
id: RecordIdSchema.optional(),
|
|
16879
|
+
data: RecordBodySchema,
|
|
16880
|
+
ifRev: number().int().min(1).optional()
|
|
16881
|
+
}), object({
|
|
16882
|
+
id: string(),
|
|
16883
|
+
rev: number().int()
|
|
16884
|
+
}), {
|
|
16885
|
+
kind: "mutation",
|
|
16886
|
+
auth: "admin"
|
|
16887
|
+
}), method(object({
|
|
16888
|
+
...EntryAddress,
|
|
16889
|
+
id: RecordIdSchema
|
|
16890
|
+
}), object({ deleted: boolean() }), {
|
|
16891
|
+
kind: "mutation",
|
|
16892
|
+
auth: "admin"
|
|
16893
|
+
});
|
|
16894
|
+
/**
|
|
15912
16895
|
* `keychain` — the hub's encrypted store of secret values (D787).
|
|
15913
16896
|
*
|
|
15914
16897
|
* Settings blobs never hold a secret: they hold a marker
|
|
@@ -16449,6 +17432,16 @@ method(object({}), KeychainStatusSchema, { auth: "admin" }), method(object({}),
|
|
|
16449
17432
|
kind: "mutation",
|
|
16450
17433
|
auth: "admin",
|
|
16451
17434
|
caller: "required"
|
|
17435
|
+
}), method(object({
|
|
17436
|
+
ref: string().min(1),
|
|
17437
|
+
value: string().min(1)
|
|
17438
|
+
}), object({
|
|
17439
|
+
ref: string(),
|
|
17440
|
+
version: number().int()
|
|
17441
|
+
}), {
|
|
17442
|
+
kind: "mutation",
|
|
17443
|
+
auth: "admin",
|
|
17444
|
+
caller: "required"
|
|
16452
17445
|
}), method(object({ refs: array(string().min(1)).max(1e3) }), object({
|
|
16453
17446
|
deleted: array(string()).readonly(),
|
|
16454
17447
|
kept: array(object({
|
|
@@ -38918,6 +39911,9 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
|
|
|
38918
39911
|
}), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
|
|
38919
39912
|
kind: "mutation",
|
|
38920
39913
|
auth: "admin"
|
|
39914
|
+
}), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
|
|
39915
|
+
kind: "mutation",
|
|
39916
|
+
auth: "admin"
|
|
38921
39917
|
}), method(ProfileRefInputSchema, _void(), {
|
|
38922
39918
|
kind: "mutation",
|
|
38923
39919
|
auth: "admin"
|
|
@@ -49360,6 +50356,41 @@ var ServerRollbackInfoSchema = object({
|
|
|
49360
50356
|
atMs: number(),
|
|
49361
50357
|
reason: string()
|
|
49362
50358
|
});
|
|
50359
|
+
/**
|
|
50360
|
+
* The runner boot check (D877): the runners this node PLANNED (hub: one per
|
|
50361
|
+
* `resolveRunnerId` over its forked addons; agent: the runners its process
|
|
50362
|
+
* service holds, because only the hub plans) against the states the process
|
|
50363
|
+
* service reports. Kept in memory, recomputed by the same one-shot check after
|
|
50364
|
+
* every boot, served on `getServerPackageStatus`.
|
|
50365
|
+
*
|
|
50366
|
+
* `failed` is the D6 breaker's terminal state, `refused` a deterministic Init
|
|
50367
|
+
* refusal (an addon that said "I will not start, and here is why"), `missing` a
|
|
50368
|
+
* planned runner the process service has no entry for.
|
|
50369
|
+
*/
|
|
50370
|
+
var RunnerRefusalSchema = object({
|
|
50371
|
+
runnerId: string(),
|
|
50372
|
+
addonIds: array(string()),
|
|
50373
|
+
reason: string()
|
|
50374
|
+
});
|
|
50375
|
+
var RunnerBootCheckSchema = object({
|
|
50376
|
+
checkedAtMs: number(),
|
|
50377
|
+
/** Runners planned for this node. */
|
|
50378
|
+
expected: number(),
|
|
50379
|
+
/** Planned runners whose process is `running`. */
|
|
50380
|
+
running: number(),
|
|
50381
|
+
failed: array(string()),
|
|
50382
|
+
refused: array(RunnerRefusalSchema),
|
|
50383
|
+
missing: array(string()),
|
|
50384
|
+
/**
|
|
50385
|
+
* Runners still on their way: `starting`, or `crashed` waiting on a backoff
|
|
50386
|
+
* respawn. The check is FINAL only when this is empty; until then nothing is
|
|
50387
|
+
* pushed and the verdict is re-evaluated on the next runner state change.
|
|
50388
|
+
* Optional for version skew (absent = settled).
|
|
50389
|
+
*/
|
|
50390
|
+
pending: array(string()).optional(),
|
|
50391
|
+
/** True when the plan is the hub's (`missing` is then meaningful); false on an agent. */
|
|
50392
|
+
planKnown: boolean()
|
|
50393
|
+
});
|
|
49363
50394
|
var ServerPackageStatusSchema = object({
|
|
49364
50395
|
/** Root package name (`@camstack/server` on the hub). */
|
|
49365
50396
|
packageName: string(),
|
|
@@ -49422,7 +50453,14 @@ var ServerPackageStatusSchema = object({
|
|
|
49422
50453
|
* for version skew: an older provider's payload simply omits it, and an
|
|
49423
50454
|
* absent wrapper is unknown — never `native`.
|
|
49424
50455
|
*/
|
|
49425
|
-
wrapper: WrapperIdentitySchema.optional()
|
|
50456
|
+
wrapper: WrapperIdentitySchema.optional(),
|
|
50457
|
+
/**
|
|
50458
|
+
* What this node PLANNED to run against what its process service reports
|
|
50459
|
+
* (D877). Absent until the one-shot boot check has run (~60 s after boot) and
|
|
50460
|
+
* on a provider that predates it. Absent is "not checked yet", never "all
|
|
50461
|
+
* well": a consumer that needs the answer treats it as pending.
|
|
50462
|
+
*/
|
|
50463
|
+
runners: RunnerBootCheckSchema.optional()
|
|
49426
50464
|
});
|
|
49427
50465
|
var ServerUpdateCheckResultSchema = object({
|
|
49428
50466
|
packageName: string(),
|
|
@@ -49473,6 +50511,13 @@ var ServerUpdateOutcomeSchema = _enum([
|
|
|
49473
50511
|
"refused",
|
|
49474
50512
|
"failed"
|
|
49475
50513
|
]);
|
|
50514
|
+
/** One addon the pre-flight found the target version would refuse (D877). */
|
|
50515
|
+
var UpdateBlockerSchema = object({
|
|
50516
|
+
addonId: string(),
|
|
50517
|
+
reason: string(),
|
|
50518
|
+
/** What the operator does about it. */
|
|
50519
|
+
remedy: string()
|
|
50520
|
+
});
|
|
49476
50521
|
var ServerUpdateActionResultSchema = object({
|
|
49477
50522
|
accepted: boolean(),
|
|
49478
50523
|
targetVersion: string().nullable(),
|
|
@@ -49490,14 +50535,27 @@ var ServerUpdateActionResultSchema = object({
|
|
|
49490
50535
|
* The failure itself, verbatim enough to act on (the npm output for a
|
|
49491
50536
|
* staging install). Non-null only for `failed`.
|
|
49492
50537
|
*/
|
|
49493
|
-
error: string().nullable().optional()
|
|
50538
|
+
error: string().nullable().optional(),
|
|
50539
|
+
/**
|
|
50540
|
+
* The addons the target version would refuse to start on this node's live
|
|
50541
|
+
* store (D877), present when the pre-flight refused the update
|
|
50542
|
+
* (`reason: 'addon-would-refuse'`). Optional for version skew.
|
|
50543
|
+
*/
|
|
50544
|
+
blockers: array(UpdateBlockerSchema).optional()
|
|
49494
50545
|
});
|
|
49495
50546
|
method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
|
|
49496
50547
|
kind: "mutation",
|
|
49497
50548
|
auth: "admin"
|
|
49498
|
-
}), method(object({
|
|
49499
|
-
/** Explicit target version; omitted = latest from the registry. */
|
|
49500
|
-
version: string().optional()
|
|
50549
|
+
}), method(object({
|
|
50550
|
+
/** Explicit target version; omitted = latest from the registry. */
|
|
50551
|
+
version: string().optional(),
|
|
50552
|
+
/**
|
|
50553
|
+
* Apply even though the pre-flight (D877) found addons the target would
|
|
50554
|
+
* refuse to start. Off by default: those addons stay down until the
|
|
50555
|
+
* remedy is run, which is the incident this gate exists to prevent.
|
|
50556
|
+
*/
|
|
50557
|
+
acknowledgeRefusals: boolean().optional()
|
|
50558
|
+
}), ServerUpdateActionResultSchema, {
|
|
49501
50559
|
kind: "mutation",
|
|
49502
50560
|
auth: "admin",
|
|
49503
50561
|
timeoutMs: 16 * 6e4
|
|
@@ -51910,7 +52968,9 @@ var ManifestShapeSchema = object({
|
|
|
51910
52968
|
var AddonProcessSchema = object({
|
|
51911
52969
|
pid: number().optional(),
|
|
51912
52970
|
mode: string(),
|
|
51913
|
-
state: string()
|
|
52971
|
+
state: string(),
|
|
52972
|
+
/** Why the runner is `refused` (an addon's own words), when it is (D877). */
|
|
52973
|
+
reason: string().optional()
|
|
51914
52974
|
});
|
|
51915
52975
|
/**
|
|
51916
52976
|
* Per-addon health snapshot from the kernel's AddonHealthMonitor.
|
|
@@ -52913,6 +53973,12 @@ object({
|
|
|
52913
53973
|
/** Horizontal louver swing on/off. Null when the device has no
|
|
52914
53974
|
* horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
|
|
52915
53975
|
swingHorizontal: boolean().nullable(),
|
|
53976
|
+
/** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
|
|
53977
|
+
minTemp: number().nullable().optional(),
|
|
53978
|
+
/** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
|
|
53979
|
+
maxTemp: number().nullable().optional(),
|
|
53980
|
+
/** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
|
|
53981
|
+
tempStep: number().positive().nullable().optional(),
|
|
52916
53982
|
/** Ms epoch when the slice was last updated (push or command). */
|
|
52917
53983
|
lastFetchedAt: number()
|
|
52918
53984
|
});
|
|
@@ -54733,7 +55799,19 @@ var TlsStatusSchema = object({
|
|
|
54733
55799
|
sans: array(string()),
|
|
54734
55800
|
caCertPem: string().nullable(),
|
|
54735
55801
|
reissueError: string().nullable(),
|
|
54736
|
-
restartRequired: boolean()
|
|
55802
|
+
restartRequired: boolean(),
|
|
55803
|
+
/** An uploaded certificate is served but no CA is kept for it and it is not publicly signed (D873). */
|
|
55804
|
+
caMissing: boolean(),
|
|
55805
|
+
/** Does the hub's own generated CA carry a critical name constraint? `null` while it has none. */
|
|
55806
|
+
localCaConstrained: boolean().nullable(),
|
|
55807
|
+
/** ISO time the generated CA was replaced by a constrained one; devices must re-download it. */
|
|
55808
|
+
caReplacedAt: string().nullable(),
|
|
55809
|
+
/** The uploaded certificate kept on disk, selectable again after "Revert to generated". */
|
|
55810
|
+
storedUpload: object({
|
|
55811
|
+
leafFingerprintSha256: string(),
|
|
55812
|
+
validTo: string(),
|
|
55813
|
+
hasCa: boolean()
|
|
55814
|
+
}).nullable()
|
|
54737
55815
|
});
|
|
54738
55816
|
method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
|
|
54739
55817
|
/**
|
|
@@ -54789,6 +55867,9 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
|
|
|
54789
55867
|
}), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
|
|
54790
55868
|
kind: "mutation",
|
|
54791
55869
|
auth: "admin"
|
|
55870
|
+
}), method(_void(), TlsStatusSchema, {
|
|
55871
|
+
kind: "mutation",
|
|
55872
|
+
auth: "admin"
|
|
54792
55873
|
});
|
|
54793
55874
|
object({
|
|
54794
55875
|
/** Lifecycle state of the lock. `jammed` means the motor reported
|
|
@@ -59918,7 +60999,11 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
|
|
|
59918
60999
|
kind: "mutation",
|
|
59919
61000
|
auth: "admin",
|
|
59920
61001
|
access: "delete"
|
|
59921
|
-
}), method(object({ userId: string() }), TotpStatusSchema, { auth: "admin" }), method(
|
|
61002
|
+
}), method(object({ userId: string() }), TotpStatusSchema, { auth: "admin" }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
|
|
61003
|
+
kind: "mutation",
|
|
61004
|
+
auth: "admin",
|
|
61005
|
+
access: "create"
|
|
61006
|
+
}), method(object({
|
|
59922
61007
|
userId: string(),
|
|
59923
61008
|
code: string()
|
|
59924
61009
|
}), object({ valid: boolean() }), {
|
|
@@ -62119,6 +63204,12 @@ Object.freeze({
|
|
|
62119
63204
|
addonId: null,
|
|
62120
63205
|
access: "view"
|
|
62121
63206
|
},
|
|
63207
|
+
"coreBlocks.migrateRawSecrets": {
|
|
63208
|
+
capName: "core-blocks",
|
|
63209
|
+
capScope: "system",
|
|
63210
|
+
addonId: null,
|
|
63211
|
+
access: "create"
|
|
63212
|
+
},
|
|
62122
63213
|
"coreBlocks.restart": {
|
|
62123
63214
|
capName: "core-blocks",
|
|
62124
63215
|
capScope: "system",
|
|
@@ -63805,6 +64896,12 @@ Object.freeze({
|
|
|
63805
64896
|
addonId: null,
|
|
63806
64897
|
access: "create"
|
|
63807
64898
|
},
|
|
64899
|
+
"keychainAdmin.importSecret": {
|
|
64900
|
+
capName: "keychain-admin",
|
|
64901
|
+
capScope: "system",
|
|
64902
|
+
addonId: null,
|
|
64903
|
+
access: "create"
|
|
64904
|
+
},
|
|
63808
64905
|
"keychainAdmin.listAudit": {
|
|
63809
64906
|
capName: "keychain-admin",
|
|
63810
64907
|
capScope: "system",
|
|
@@ -63973,6 +65070,12 @@ Object.freeze({
|
|
|
63973
65070
|
addonId: null,
|
|
63974
65071
|
access: "view"
|
|
63975
65072
|
},
|
|
65073
|
+
"llm.migrateRawSecrets": {
|
|
65074
|
+
capName: "llm",
|
|
65075
|
+
capScope: "system",
|
|
65076
|
+
addonId: null,
|
|
65077
|
+
access: "create"
|
|
65078
|
+
},
|
|
63976
65079
|
"llm.resolveModelRef": {
|
|
63977
65080
|
capName: "llm",
|
|
63978
65081
|
capScope: "system",
|
|
@@ -64201,6 +65304,12 @@ Object.freeze({
|
|
|
64201
65304
|
addonId: null,
|
|
64202
65305
|
access: "create"
|
|
64203
65306
|
},
|
|
65307
|
+
"localNetwork.useStoredUploadedCertificate": {
|
|
65308
|
+
capName: "local-network",
|
|
65309
|
+
capScope: "system",
|
|
65310
|
+
addonId: null,
|
|
65311
|
+
access: "create"
|
|
65312
|
+
},
|
|
64204
65313
|
"lockControl.getStatus": {
|
|
64205
65314
|
capName: "lock-control",
|
|
64206
65315
|
capScope: "device",
|
|
@@ -66853,6 +67962,30 @@ Object.freeze({
|
|
|
66853
67962
|
addonId: null,
|
|
66854
67963
|
access: "create"
|
|
66855
67964
|
},
|
|
67965
|
+
"recordEntries.deleteEntry": {
|
|
67966
|
+
capName: "record-entries",
|
|
67967
|
+
capScope: "system",
|
|
67968
|
+
addonId: null,
|
|
67969
|
+
access: "delete"
|
|
67970
|
+
},
|
|
67971
|
+
"recordEntries.listEntries": {
|
|
67972
|
+
capName: "record-entries",
|
|
67973
|
+
capScope: "system",
|
|
67974
|
+
addonId: null,
|
|
67975
|
+
access: "view"
|
|
67976
|
+
},
|
|
67977
|
+
"recordEntries.listEntryKinds": {
|
|
67978
|
+
capName: "record-entries",
|
|
67979
|
+
capScope: "system",
|
|
67980
|
+
addonId: null,
|
|
67981
|
+
access: "view"
|
|
67982
|
+
},
|
|
67983
|
+
"recordEntries.putEntry": {
|
|
67984
|
+
capName: "record-entries",
|
|
67985
|
+
capScope: "system",
|
|
67986
|
+
addonId: null,
|
|
67987
|
+
access: "create"
|
|
67988
|
+
},
|
|
66856
67989
|
"recording.getAvailability": {
|
|
66857
67990
|
capName: "recording",
|
|
66858
67991
|
capScope: "device",
|
|
@@ -67153,6 +68286,102 @@ Object.freeze({
|
|
|
67153
68286
|
addonId: null,
|
|
67154
68287
|
access: "view"
|
|
67155
68288
|
},
|
|
68289
|
+
"records.aggregate": {
|
|
68290
|
+
capName: "records",
|
|
68291
|
+
capScope: "system",
|
|
68292
|
+
addonId: null,
|
|
68293
|
+
access: "view"
|
|
68294
|
+
},
|
|
68295
|
+
"records.allocateId": {
|
|
68296
|
+
capName: "records",
|
|
68297
|
+
capScope: "system",
|
|
68298
|
+
addonId: null,
|
|
68299
|
+
access: "create"
|
|
68300
|
+
},
|
|
68301
|
+
"records.count": {
|
|
68302
|
+
capName: "records",
|
|
68303
|
+
capScope: "system",
|
|
68304
|
+
addonId: null,
|
|
68305
|
+
access: "view"
|
|
68306
|
+
},
|
|
68307
|
+
"records.declareKinds": {
|
|
68308
|
+
capName: "records",
|
|
68309
|
+
capScope: "system",
|
|
68310
|
+
addonId: null,
|
|
68311
|
+
access: "create"
|
|
68312
|
+
},
|
|
68313
|
+
"records.delete": {
|
|
68314
|
+
capName: "records",
|
|
68315
|
+
capScope: "system",
|
|
68316
|
+
addonId: null,
|
|
68317
|
+
access: "delete"
|
|
68318
|
+
},
|
|
68319
|
+
"records.deleteDevice": {
|
|
68320
|
+
capName: "records",
|
|
68321
|
+
capScope: "system",
|
|
68322
|
+
addonId: null,
|
|
68323
|
+
access: "delete"
|
|
68324
|
+
},
|
|
68325
|
+
"records.deleteMany": {
|
|
68326
|
+
capName: "records",
|
|
68327
|
+
capScope: "system",
|
|
68328
|
+
addonId: null,
|
|
68329
|
+
access: "delete"
|
|
68330
|
+
},
|
|
68331
|
+
"records.deleteWhere": {
|
|
68332
|
+
capName: "records",
|
|
68333
|
+
capScope: "system",
|
|
68334
|
+
addonId: null,
|
|
68335
|
+
access: "delete"
|
|
68336
|
+
},
|
|
68337
|
+
"records.get": {
|
|
68338
|
+
capName: "records",
|
|
68339
|
+
capScope: "system",
|
|
68340
|
+
addonId: null,
|
|
68341
|
+
access: "view"
|
|
68342
|
+
},
|
|
68343
|
+
"records.getMany": {
|
|
68344
|
+
capName: "records",
|
|
68345
|
+
capScope: "system",
|
|
68346
|
+
addonId: null,
|
|
68347
|
+
access: "view"
|
|
68348
|
+
},
|
|
68349
|
+
"records.histogram": {
|
|
68350
|
+
capName: "records",
|
|
68351
|
+
capScope: "system",
|
|
68352
|
+
addonId: null,
|
|
68353
|
+
access: "view"
|
|
68354
|
+
},
|
|
68355
|
+
"records.patch": {
|
|
68356
|
+
capName: "records",
|
|
68357
|
+
capScope: "system",
|
|
68358
|
+
addonId: null,
|
|
68359
|
+
access: "create"
|
|
68360
|
+
},
|
|
68361
|
+
"records.put": {
|
|
68362
|
+
capName: "records",
|
|
68363
|
+
capScope: "system",
|
|
68364
|
+
addonId: null,
|
|
68365
|
+
access: "create"
|
|
68366
|
+
},
|
|
68367
|
+
"records.putMany": {
|
|
68368
|
+
capName: "records",
|
|
68369
|
+
capScope: "system",
|
|
68370
|
+
addonId: null,
|
|
68371
|
+
access: "create"
|
|
68372
|
+
},
|
|
68373
|
+
"records.query": {
|
|
68374
|
+
capName: "records",
|
|
68375
|
+
capScope: "system",
|
|
68376
|
+
addonId: null,
|
|
68377
|
+
access: "view"
|
|
68378
|
+
},
|
|
68379
|
+
"records.secretRefs": {
|
|
68380
|
+
capName: "records",
|
|
68381
|
+
capScope: "system",
|
|
68382
|
+
addonId: null,
|
|
68383
|
+
access: "view"
|
|
68384
|
+
},
|
|
67156
68385
|
"sceneMonitor.captureReference": {
|
|
67157
68386
|
capName: "scene-monitor",
|
|
67158
68387
|
capScope: "device",
|
|
@@ -67585,6 +68814,48 @@ Object.freeze({
|
|
|
67585
68814
|
addonId: null,
|
|
67586
68815
|
access: "create"
|
|
67587
68816
|
},
|
|
68817
|
+
"storageAdmin.adoptRecords": {
|
|
68818
|
+
capName: "storage-admin",
|
|
68819
|
+
capScope: "system",
|
|
68820
|
+
addonId: null,
|
|
68821
|
+
access: "create"
|
|
68822
|
+
},
|
|
68823
|
+
"storageAdmin.dropCollection": {
|
|
68824
|
+
capName: "storage-admin",
|
|
68825
|
+
capScope: "system",
|
|
68826
|
+
addonId: null,
|
|
68827
|
+
access: "delete"
|
|
68828
|
+
},
|
|
68829
|
+
"storageAdmin.isAdoptedLegacyTable": {
|
|
68830
|
+
capName: "storage-admin",
|
|
68831
|
+
capScope: "system",
|
|
68832
|
+
addonId: null,
|
|
68833
|
+
access: "view"
|
|
68834
|
+
},
|
|
68835
|
+
"storageAdmin.listKinds": {
|
|
68836
|
+
capName: "storage-admin",
|
|
68837
|
+
capScope: "system",
|
|
68838
|
+
addonId: null,
|
|
68839
|
+
access: "view"
|
|
68840
|
+
},
|
|
68841
|
+
"storageAdmin.listTables": {
|
|
68842
|
+
capName: "storage-admin",
|
|
68843
|
+
capScope: "system",
|
|
68844
|
+
addonId: null,
|
|
68845
|
+
access: "view"
|
|
68846
|
+
},
|
|
68847
|
+
"storageAdmin.readLegacyCollection": {
|
|
68848
|
+
capName: "storage-admin",
|
|
68849
|
+
capScope: "system",
|
|
68850
|
+
addonId: null,
|
|
68851
|
+
access: "view"
|
|
68852
|
+
},
|
|
68853
|
+
"storageAdmin.readRecords": {
|
|
68854
|
+
capName: "storage-admin",
|
|
68855
|
+
capScope: "system",
|
|
68856
|
+
addonId: null,
|
|
68857
|
+
access: "view"
|
|
68858
|
+
},
|
|
67588
68859
|
"storageEvictable.evict": {
|
|
67589
68860
|
capName: "storage-evictable",
|
|
67590
68861
|
capScope: "system",
|
|
@@ -68497,6 +69768,12 @@ Object.freeze({
|
|
|
68497
69768
|
addonId: null,
|
|
68498
69769
|
access: "view"
|
|
68499
69770
|
},
|
|
69771
|
+
"userManagement.migrateRawSecrets": {
|
|
69772
|
+
capName: "user-management",
|
|
69773
|
+
capScope: "system",
|
|
69774
|
+
addonId: null,
|
|
69775
|
+
access: "create"
|
|
69776
|
+
},
|
|
68500
69777
|
"userManagement.oauthExchangeCode": {
|
|
68501
69778
|
capName: "user-management",
|
|
68502
69779
|
capScope: "system",
|
|
@@ -71173,6 +72450,37 @@ Object.freeze({
|
|
|
71173
72450
|
form: "single",
|
|
71174
72451
|
optional: false
|
|
71175
72452
|
}],
|
|
72453
|
+
"records.count": [{
|
|
72454
|
+
name: "deviceId",
|
|
72455
|
+
form: "single",
|
|
72456
|
+
optional: true
|
|
72457
|
+
}],
|
|
72458
|
+
"records.deleteDevice": [{
|
|
72459
|
+
name: "deviceId",
|
|
72460
|
+
form: "single",
|
|
72461
|
+
optional: false
|
|
72462
|
+
}],
|
|
72463
|
+
"records.deleteWhere": [{
|
|
72464
|
+
name: "deviceId",
|
|
72465
|
+
form: "single",
|
|
72466
|
+
optional: true
|
|
72467
|
+
}],
|
|
72468
|
+
"records.histogram": [{
|
|
72469
|
+
name: "deviceId",
|
|
72470
|
+
form: "single",
|
|
72471
|
+
optional: true
|
|
72472
|
+
}],
|
|
72473
|
+
"records.putMany": [{
|
|
72474
|
+
name: "records",
|
|
72475
|
+
form: "object-array",
|
|
72476
|
+
optional: false,
|
|
72477
|
+
itemField: "deviceId"
|
|
72478
|
+
}],
|
|
72479
|
+
"records.query": [{
|
|
72480
|
+
name: "deviceId",
|
|
72481
|
+
form: "single",
|
|
72482
|
+
optional: true
|
|
72483
|
+
}],
|
|
71176
72484
|
"sceneMonitor.captureReference": [{
|
|
71177
72485
|
name: "deviceId",
|
|
71178
72486
|
form: "single",
|
|
@@ -71289,6 +72597,17 @@ Object.freeze({
|
|
|
71289
72597
|
form: "single",
|
|
71290
72598
|
optional: false
|
|
71291
72599
|
}],
|
|
72600
|
+
"storageAdmin.adoptRecords": [{
|
|
72601
|
+
name: "records",
|
|
72602
|
+
form: "object-array",
|
|
72603
|
+
optional: false,
|
|
72604
|
+
itemField: "deviceId"
|
|
72605
|
+
}],
|
|
72606
|
+
"storageAdmin.readRecords": [{
|
|
72607
|
+
name: "deviceId",
|
|
72608
|
+
form: "single",
|
|
72609
|
+
optional: true
|
|
72610
|
+
}],
|
|
71292
72611
|
"streamBroker.acquireEgressTranscode": [{
|
|
71293
72612
|
name: "deviceId",
|
|
71294
72613
|
form: "single",
|
|
@@ -104872,6 +106191,205 @@ function retiredCiphertextPaths(config) {
|
|
|
104872
106191
|
return out;
|
|
104873
106192
|
}
|
|
104874
106193
|
//#endregion
|
|
106194
|
+
//#region src/mqtt-external-broker-entry.ts
|
|
106195
|
+
var mqttOnboardDescriptorSchema = object({
|
|
106196
|
+
port: number().int().min(1).max(65535),
|
|
106197
|
+
allowAnonymous: boolean()
|
|
106198
|
+
});
|
|
106199
|
+
var MqttExternalBrokerEntrySchema = object({
|
|
106200
|
+
id: string().min(1),
|
|
106201
|
+
name: string().min(1),
|
|
106202
|
+
url: string().min(1),
|
|
106203
|
+
username: string().optional(),
|
|
106204
|
+
password: string().optional().meta({ secret: true }),
|
|
106205
|
+
clientIdPrefix: string().optional(),
|
|
106206
|
+
onboard: mqttOnboardDescriptorSchema.optional()
|
|
106207
|
+
});
|
|
106208
|
+
var mqttBrokersEntryKind = defineEntryKind("brokers", {
|
|
106209
|
+
title: "MQTT external brokers",
|
|
106210
|
+
schema: entryBody({
|
|
106211
|
+
url: string().min(1),
|
|
106212
|
+
username: string().optional(),
|
|
106213
|
+
password: string().optional().meta({ secret: true }),
|
|
106214
|
+
clientIdPrefix: string().optional(),
|
|
106215
|
+
onboard: mqttOnboardDescriptorSchema.optional()
|
|
106216
|
+
}),
|
|
106217
|
+
writeTrigger: "an operator adds or removes an external MQTT broker entry",
|
|
106218
|
+
adoptsFrom: settingsBrokersAdoptsFrom("mqtt-broker")
|
|
106219
|
+
});
|
|
106220
|
+
//#endregion
|
|
106221
|
+
//#region src/mqtt-entry-record-kinds.ts
|
|
106222
|
+
var aclAccessSchema = _enum([
|
|
106223
|
+
"sub",
|
|
106224
|
+
"pub",
|
|
106225
|
+
"both"
|
|
106226
|
+
]);
|
|
106227
|
+
var MqttClientBodySchema = entryBody({ passwordHash: string() });
|
|
106228
|
+
var mqttClientEntryKind = defineEntryKind("mqtt-client", {
|
|
106229
|
+
title: "MQTT embedded clients",
|
|
106230
|
+
schema: MqttClientBodySchema,
|
|
106231
|
+
writeTrigger: "an operator adds or removes an embedded MQTT client credential",
|
|
106232
|
+
adoptsFrom: settingsArrayAdoptsFrom("mqtt-broker", "embeddedClients")
|
|
106233
|
+
});
|
|
106234
|
+
var MqttAclBodySchema = entryBody({
|
|
106235
|
+
username: string().min(1),
|
|
106236
|
+
topicPattern: string().min(1),
|
|
106237
|
+
access: aclAccessSchema
|
|
106238
|
+
});
|
|
106239
|
+
var mqttAclEntryKind = defineEntryKind("mqtt-acl-rule", {
|
|
106240
|
+
title: "MQTT ACL rules",
|
|
106241
|
+
schema: MqttAclBodySchema,
|
|
106242
|
+
writeTrigger: "an operator edits embedded broker ACL rules",
|
|
106243
|
+
adoptsFrom: settingsArrayAdoptsFrom("mqtt-broker", "embeddedAcl")
|
|
106244
|
+
});
|
|
106245
|
+
//#endregion
|
|
106246
|
+
//#region src/mqtt-broker-store.ts
|
|
106247
|
+
function rowToEntry(id, data) {
|
|
106248
|
+
const parsed = MqttExternalBrokerEntrySchema.safeParse({
|
|
106249
|
+
id,
|
|
106250
|
+
name: data.name,
|
|
106251
|
+
url: data.url,
|
|
106252
|
+
username: data.username,
|
|
106253
|
+
password: data.password,
|
|
106254
|
+
clientIdPrefix: data.clientIdPrefix,
|
|
106255
|
+
onboard: data.onboard
|
|
106256
|
+
});
|
|
106257
|
+
return parsed.success ? parsed.data : null;
|
|
106258
|
+
}
|
|
106259
|
+
var MqttBrokerStore = class MqttBrokerStore {
|
|
106260
|
+
kind;
|
|
106261
|
+
constructor(kind) {
|
|
106262
|
+
this.kind = kind;
|
|
106263
|
+
}
|
|
106264
|
+
static of(records) {
|
|
106265
|
+
return new MqttBrokerStore(records.kind(mqttBrokersEntryKind));
|
|
106266
|
+
}
|
|
106267
|
+
async listResolved() {
|
|
106268
|
+
const page = await this.kind.query({ limit: 1e3 });
|
|
106269
|
+
const out = [];
|
|
106270
|
+
for (const record of page.records) {
|
|
106271
|
+
let data = record.data;
|
|
106272
|
+
try {
|
|
106273
|
+
const resolved = await this.kind.getResolved(record.id);
|
|
106274
|
+
if (resolved !== null) data = resolved.data;
|
|
106275
|
+
} catch (err) {
|
|
106276
|
+
if (!(err instanceof RecordSecretUnavailableError)) throw err;
|
|
106277
|
+
}
|
|
106278
|
+
const entry = rowToEntry(record.id, data);
|
|
106279
|
+
if (entry !== null) out.push(entry);
|
|
106280
|
+
}
|
|
106281
|
+
return out;
|
|
106282
|
+
}
|
|
106283
|
+
async replaceAll(entries) {
|
|
106284
|
+
const page = await this.kind.query({ limit: 1e3 });
|
|
106285
|
+
const nextIds = new Set(entries.map((e) => e.id));
|
|
106286
|
+
for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
|
|
106287
|
+
for (const entry of entries) {
|
|
106288
|
+
const { id, name, url, username, password, clientIdPrefix, onboard } = entry;
|
|
106289
|
+
const body = {
|
|
106290
|
+
name,
|
|
106291
|
+
url,
|
|
106292
|
+
...username === void 0 ? {} : { username },
|
|
106293
|
+
...password === void 0 ? {} : { password },
|
|
106294
|
+
...clientIdPrefix === void 0 ? {} : { clientIdPrefix },
|
|
106295
|
+
...onboard === void 0 ? {} : { onboard }
|
|
106296
|
+
};
|
|
106297
|
+
const parsed = mqttBrokersEntryKind.schema.safeParse(body);
|
|
106298
|
+
if (!parsed.success) throw new Error(`invalid MQTT broker '${id}': ${parsed.error.message}`);
|
|
106299
|
+
await this.kind.put({
|
|
106300
|
+
id,
|
|
106301
|
+
data: parsed.data
|
|
106302
|
+
});
|
|
106303
|
+
}
|
|
106304
|
+
}
|
|
106305
|
+
};
|
|
106306
|
+
//#endregion
|
|
106307
|
+
//#region src/mqtt-entry-store.ts
|
|
106308
|
+
function mqttAclRecordId(rule) {
|
|
106309
|
+
return `${rule.username}\u0000${rule.topicPattern}`;
|
|
106310
|
+
}
|
|
106311
|
+
var MqttClientStore = class MqttClientStore {
|
|
106312
|
+
kind;
|
|
106313
|
+
constructor(kind) {
|
|
106314
|
+
this.kind = kind;
|
|
106315
|
+
}
|
|
106316
|
+
static of(records) {
|
|
106317
|
+
return new MqttClientStore(records.kind(mqttClientEntryKind));
|
|
106318
|
+
}
|
|
106319
|
+
async listResolved() {
|
|
106320
|
+
const page = await this.kind.query({ limit: 1e3 });
|
|
106321
|
+
const out = [];
|
|
106322
|
+
for (const record of page.records) {
|
|
106323
|
+
const parsed = MqttClientBodySchema.safeParse(record.data);
|
|
106324
|
+
if (!parsed.success) continue;
|
|
106325
|
+
out.push({
|
|
106326
|
+
username: record.id,
|
|
106327
|
+
passwordHash: parsed.data.passwordHash
|
|
106328
|
+
});
|
|
106329
|
+
}
|
|
106330
|
+
return out;
|
|
106331
|
+
}
|
|
106332
|
+
async replaceAll(entries) {
|
|
106333
|
+
const page = await this.kind.query({ limit: 1e3 });
|
|
106334
|
+
const nextIds = new Set(entries.map((e) => e.username));
|
|
106335
|
+
for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
|
|
106336
|
+
for (const entry of entries) {
|
|
106337
|
+
const body = {
|
|
106338
|
+
name: entry.username,
|
|
106339
|
+
passwordHash: entry.passwordHash
|
|
106340
|
+
};
|
|
106341
|
+
const parsed = mqttClientEntryKind.schema.safeParse(body);
|
|
106342
|
+
if (!parsed.success) throw new Error(`invalid MQTT client '${entry.username}': ${parsed.error.message}`);
|
|
106343
|
+
await this.kind.put({
|
|
106344
|
+
id: entry.username,
|
|
106345
|
+
data: parsed.data
|
|
106346
|
+
});
|
|
106347
|
+
}
|
|
106348
|
+
}
|
|
106349
|
+
};
|
|
106350
|
+
var MqttAclStore = class MqttAclStore {
|
|
106351
|
+
kind;
|
|
106352
|
+
constructor(kind) {
|
|
106353
|
+
this.kind = kind;
|
|
106354
|
+
}
|
|
106355
|
+
static of(records) {
|
|
106356
|
+
return new MqttAclStore(records.kind(mqttAclEntryKind));
|
|
106357
|
+
}
|
|
106358
|
+
async listResolved() {
|
|
106359
|
+
const page = await this.kind.query({ limit: 1e3 });
|
|
106360
|
+
const out = [];
|
|
106361
|
+
for (const record of page.records) {
|
|
106362
|
+
const parsed = MqttAclBodySchema.safeParse(record.data);
|
|
106363
|
+
if (!parsed.success) continue;
|
|
106364
|
+
out.push({
|
|
106365
|
+
username: parsed.data.username,
|
|
106366
|
+
topicPattern: parsed.data.topicPattern,
|
|
106367
|
+
access: parsed.data.access
|
|
106368
|
+
});
|
|
106369
|
+
}
|
|
106370
|
+
return out;
|
|
106371
|
+
}
|
|
106372
|
+
async replaceAll(entries) {
|
|
106373
|
+
const page = await this.kind.query({ limit: 1e3 });
|
|
106374
|
+
const nextIds = new Set(entries.map((e) => mqttAclRecordId(e)));
|
|
106375
|
+
for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
|
|
106376
|
+
for (const entry of entries) {
|
|
106377
|
+
const body = {
|
|
106378
|
+
name: `${entry.username} · ${entry.topicPattern}`,
|
|
106379
|
+
username: entry.username,
|
|
106380
|
+
topicPattern: entry.topicPattern,
|
|
106381
|
+
access: entry.access
|
|
106382
|
+
};
|
|
106383
|
+
const parsed = mqttAclEntryKind.schema.safeParse(body);
|
|
106384
|
+
if (!parsed.success) throw new Error(`invalid MQTT ACL '${entry.username}/${entry.topicPattern}': ${parsed.error.message}`);
|
|
106385
|
+
await this.kind.put({
|
|
106386
|
+
id: mqttAclRecordId(entry),
|
|
106387
|
+
data: parsed.data
|
|
106388
|
+
});
|
|
106389
|
+
}
|
|
106390
|
+
}
|
|
106391
|
+
};
|
|
106392
|
+
//#endregion
|
|
104875
106393
|
//#region src/mqtt-broker.addon.ts
|
|
104876
106394
|
/**
|
|
104877
106395
|
* MQTT Broker addon — broker REGISTRY (not a pub/sub proxy).
|
|
@@ -104905,7 +106423,7 @@ function retiredCiphertextPaths(config) {
|
|
|
104905
106423
|
* integration-type addon.
|
|
104906
106424
|
*
|
|
104907
106425
|
* Lifecycle:
|
|
104908
|
-
* - External broker entries are persisted in `this.
|
|
106426
|
+
* - External broker entries are persisted in `this.activeExternalBrokers`.
|
|
104909
106427
|
* - The embedded broker is persisted as flat `embedded*` config keys
|
|
104910
106428
|
* (`embeddedEnabled`, `embeddedPort`, …) so the standard settings
|
|
104911
106429
|
* form can drive it; {@link MqttBrokerAddon.deriveEmbeddedEntry}
|
|
@@ -105024,11 +106542,86 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105024
106542
|
/** Per-entry onboard `aedes` runtimes, keyed by `ExternalBrokerEntry.id`. */
|
|
105025
106543
|
onboardRuntimes = /* @__PURE__ */ new Map();
|
|
105026
106544
|
probeCache = null;
|
|
106545
|
+
brokerStore = null;
|
|
106546
|
+
clientStore = null;
|
|
106547
|
+
aclStore = null;
|
|
106548
|
+
/** External broker rows (records when B2 is live, else settings mirror). */
|
|
106549
|
+
activeExternalBrokers = [];
|
|
106550
|
+
activeEmbeddedClients = [];
|
|
106551
|
+
activeEmbeddedAcl = [];
|
|
105027
106552
|
constructor() {
|
|
105028
106553
|
super({ ...DEFAULT_CONFIG });
|
|
105029
106554
|
}
|
|
106555
|
+
async loadPersistedExternalBrokers() {
|
|
106556
|
+
const store = this.brokerStore;
|
|
106557
|
+
if (store !== null) {
|
|
106558
|
+
const fromRecords = await store.listResolved();
|
|
106559
|
+
if (fromRecords.length > 0) return fromRecords;
|
|
106560
|
+
}
|
|
106561
|
+
return this.config.brokers;
|
|
106562
|
+
}
|
|
106563
|
+
async persistExternalBrokers(entries) {
|
|
106564
|
+
this.activeExternalBrokers = [...entries];
|
|
106565
|
+
const store = this.brokerStore;
|
|
106566
|
+
if (store !== null) {
|
|
106567
|
+
await store.replaceAll(entries);
|
|
106568
|
+
return;
|
|
106569
|
+
}
|
|
106570
|
+
await this.updateGlobalSettings({ brokers: entries });
|
|
106571
|
+
}
|
|
106572
|
+
async loadEmbeddedClients() {
|
|
106573
|
+
const store = this.clientStore;
|
|
106574
|
+
if (store !== null) {
|
|
106575
|
+
const fromRecords = await store.listResolved();
|
|
106576
|
+
if (fromRecords.length > 0) return fromRecords;
|
|
106577
|
+
}
|
|
106578
|
+
return this.config.embeddedClients;
|
|
106579
|
+
}
|
|
106580
|
+
async loadEmbeddedAcl() {
|
|
106581
|
+
const store = this.aclStore;
|
|
106582
|
+
if (store !== null) {
|
|
106583
|
+
const fromRecords = await store.listResolved();
|
|
106584
|
+
if (fromRecords.length > 0) return fromRecords;
|
|
106585
|
+
}
|
|
106586
|
+
return this.config.embeddedAcl;
|
|
106587
|
+
}
|
|
106588
|
+
async syncEmbeddedEntriesFromConfig() {
|
|
106589
|
+
const clients = [...this.config.embeddedClients];
|
|
106590
|
+
const acl = [...this.config.embeddedAcl];
|
|
106591
|
+
this.activeEmbeddedClients = clients;
|
|
106592
|
+
this.activeEmbeddedAcl = acl;
|
|
106593
|
+
const clientStore = this.clientStore;
|
|
106594
|
+
const aclStore = this.aclStore;
|
|
106595
|
+
if (clientStore !== null) await clientStore.replaceAll(clients);
|
|
106596
|
+
if (aclStore !== null) await aclStore.replaceAll(acl);
|
|
106597
|
+
}
|
|
105030
106598
|
async onInitialize() {
|
|
105031
106599
|
this.reportRetiredCiphertext();
|
|
106600
|
+
const records = this.ctx.records;
|
|
106601
|
+
if (records === void 0) throw new Error("mqtt-broker: ctx.records is required (hub-only addon)");
|
|
106602
|
+
await records.declare({
|
|
106603
|
+
brokers: mqttBrokersEntryKind,
|
|
106604
|
+
"mqtt-client": mqttClientEntryKind,
|
|
106605
|
+
"mqtt-acl-rule": mqttAclEntryKind
|
|
106606
|
+
});
|
|
106607
|
+
this.brokerStore = MqttBrokerStore.of(records);
|
|
106608
|
+
this.clientStore = MqttClientStore.of(records);
|
|
106609
|
+
this.aclStore = MqttAclStore.of(records);
|
|
106610
|
+
this.activeExternalBrokers = await this.loadPersistedExternalBrokers();
|
|
106611
|
+
if ((await this.brokerStore.listResolved()).length === 0 && this.config.brokers.length > 0) {
|
|
106612
|
+
await this.brokerStore.replaceAll(this.config.brokers);
|
|
106613
|
+
this.activeExternalBrokers = await this.loadPersistedExternalBrokers();
|
|
106614
|
+
}
|
|
106615
|
+
this.activeEmbeddedClients = await this.loadEmbeddedClients();
|
|
106616
|
+
this.activeEmbeddedAcl = await this.loadEmbeddedAcl();
|
|
106617
|
+
if ((await this.clientStore.listResolved()).length === 0 && this.config.embeddedClients.length > 0) {
|
|
106618
|
+
await this.clientStore.replaceAll(this.config.embeddedClients);
|
|
106619
|
+
this.activeEmbeddedClients = await this.loadEmbeddedClients();
|
|
106620
|
+
}
|
|
106621
|
+
if ((await this.aclStore.listResolved()).length === 0 && this.config.embeddedAcl.length > 0) {
|
|
106622
|
+
await this.aclStore.replaceAll(this.config.embeddedAcl);
|
|
106623
|
+
this.activeEmbeddedAcl = await this.loadEmbeddedAcl();
|
|
106624
|
+
}
|
|
105032
106625
|
this.probeCache = new BrokerProbeCache({
|
|
105033
106626
|
intervalMs: PROBE_INTERVAL_MS,
|
|
105034
106627
|
onError: (id, err) => {
|
|
@@ -105074,7 +106667,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105074
106667
|
startEmbeddedBroker: async (input) => this.startEmbeddedBroker(input),
|
|
105075
106668
|
stopEmbeddedBroker: async ({ id }) => this.stopEmbeddedBroker(id),
|
|
105076
106669
|
getStatus: async () => ({
|
|
105077
|
-
brokerCount: this.
|
|
106670
|
+
brokerCount: this.activeExternalBrokers.length + (this.config.embeddedEnabled ? 1 : 0),
|
|
105078
106671
|
embeddedRunning: this.embeddedRuntime !== null
|
|
105079
106672
|
})
|
|
105080
106673
|
};
|
|
@@ -105107,7 +106700,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105107
106700
|
error: info.error ?? null
|
|
105108
106701
|
});
|
|
105109
106702
|
const owns = (id) => {
|
|
105110
|
-
if (this.
|
|
106703
|
+
if (this.activeExternalBrokers.some((b) => b.id === id)) return true;
|
|
105111
106704
|
const embedded = this.deriveEmbeddedEntry();
|
|
105112
106705
|
return embedded !== null && embedded.id === id;
|
|
105113
106706
|
};
|
|
@@ -105201,7 +106794,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105201
106794
|
getStatus: async () => {
|
|
105202
106795
|
const all = await this.listBrokers();
|
|
105203
106796
|
return {
|
|
105204
|
-
brokerCount: this.
|
|
106797
|
+
brokerCount: this.activeExternalBrokers.length + (this.config.embeddedEnabled ? 1 : 0),
|
|
105205
106798
|
connectedCount: all.filter((b) => b.status === "connected").length
|
|
105206
106799
|
};
|
|
105207
106800
|
}
|
|
@@ -105213,6 +106806,8 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105213
106806
|
await this.shutdownAllOnboard();
|
|
105214
106807
|
}
|
|
105215
106808
|
async onConfigChanged() {
|
|
106809
|
+
this.activeExternalBrokers = await this.loadPersistedExternalBrokers();
|
|
106810
|
+
await this.syncEmbeddedEntriesFromConfig();
|
|
105216
106811
|
await this.reconcileOnboardRuntimes();
|
|
105217
106812
|
const desired = this.deriveEmbeddedEntry();
|
|
105218
106813
|
const running = this.embeddedRuntime?.entry ?? null;
|
|
@@ -105239,7 +106834,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105239
106834
|
}
|
|
105240
106835
|
async listBrokers() {
|
|
105241
106836
|
const out = [];
|
|
105242
|
-
for (const entry of this.
|
|
106837
|
+
for (const entry of this.activeExternalBrokers) {
|
|
105243
106838
|
if (entry.onboard) {
|
|
105244
106839
|
const running = this.onboardRuntimes.get(entry.id) ?? null;
|
|
105245
106840
|
const status = running !== null ? "connected" : "disconnected";
|
|
@@ -105288,7 +106883,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105288
106883
|
return out;
|
|
105289
106884
|
}
|
|
105290
106885
|
getBrokerConfig(id) {
|
|
105291
|
-
const external = this.
|
|
106886
|
+
const external = this.activeExternalBrokers.find((b) => b.id === id);
|
|
105292
106887
|
if (external) {
|
|
105293
106888
|
const password = this.storedPassword(external.password);
|
|
105294
106889
|
return {
|
|
@@ -105319,8 +106914,8 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105319
106914
|
...input.password !== void 0 ? { password: input.password } : {},
|
|
105320
106915
|
...input.clientIdPrefix !== void 0 ? { clientIdPrefix: input.clientIdPrefix } : {}
|
|
105321
106916
|
};
|
|
105322
|
-
const next = [...this.
|
|
105323
|
-
await this.
|
|
106917
|
+
const next = [...this.activeExternalBrokers, entry];
|
|
106918
|
+
await this.persistExternalBrokers(next);
|
|
105324
106919
|
this.ctx.logger.info("mqtt broker added", { meta: {
|
|
105325
106920
|
id,
|
|
105326
106921
|
url: entry.url
|
|
@@ -105349,8 +106944,8 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105349
106944
|
...built.password !== void 0 ? { password: built.password } : {},
|
|
105350
106945
|
onboard: built.onboard
|
|
105351
106946
|
};
|
|
105352
|
-
const next = [...this.
|
|
105353
|
-
await this.
|
|
106947
|
+
const next = [...this.activeExternalBrokers, entry];
|
|
106948
|
+
await this.persistExternalBrokers(next);
|
|
105354
106949
|
const spawn = this.onboardSpawnEntry(entry);
|
|
105355
106950
|
if (spawn) await this.spawnOnboard(spawn);
|
|
105356
106951
|
this.ctx.logger.info("onboard mqtt broker added", {
|
|
@@ -105366,10 +106961,10 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105366
106961
|
return { id };
|
|
105367
106962
|
}
|
|
105368
106963
|
async removeBroker(id) {
|
|
105369
|
-
const removed = this.
|
|
106964
|
+
const removed = this.activeExternalBrokers.find((b) => b.id === id);
|
|
105370
106965
|
if (removed) {
|
|
105371
|
-
const next = this.
|
|
105372
|
-
await this.
|
|
106966
|
+
const next = this.activeExternalBrokers.filter((b) => b.id !== id);
|
|
106967
|
+
await this.persistExternalBrokers(next);
|
|
105373
106968
|
this.probeCache?.evict(id);
|
|
105374
106969
|
if (removed.onboard) await this.shutdownOnboard(id);
|
|
105375
106970
|
this.ctx.logger.info("mqtt broker removed", { meta: { id } });
|
|
@@ -105590,7 +107185,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105590
107185
|
* entry disappeared, and respawn runtimes whose port changed.
|
|
105591
107186
|
*/
|
|
105592
107187
|
async reconcileOnboardRuntimes() {
|
|
105593
|
-
const desiredEntries = this.
|
|
107188
|
+
const desiredEntries = this.activeExternalBrokers.filter(hasOnboard);
|
|
105594
107189
|
const desired = desiredEntries.map((b) => ({
|
|
105595
107190
|
id: b.id,
|
|
105596
107191
|
port: b.onboard.port
|
|
@@ -105631,7 +107226,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105631
107226
|
done(null, allowAnonymous);
|
|
105632
107227
|
return;
|
|
105633
107228
|
}
|
|
105634
|
-
const match = this.
|
|
107229
|
+
const match = this.activeEmbeddedClients.find((c) => c.username === username);
|
|
105635
107230
|
if (match) {
|
|
105636
107231
|
done(null, bcryptCompareSafe(passwordStr, match.passwordHash));
|
|
105637
107232
|
return;
|
|
@@ -105671,7 +107266,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105671
107266
|
* requested topic and access mode.
|
|
105672
107267
|
*/
|
|
105673
107268
|
isAclAllowed(client, topic, access) {
|
|
105674
|
-
const acl = this.
|
|
107269
|
+
const acl = this.activeEmbeddedAcl;
|
|
105675
107270
|
if (acl.length === 0) return true;
|
|
105676
107271
|
const username = readClientUsername(client);
|
|
105677
107272
|
if (!username) return true;
|
|
@@ -105698,7 +107293,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105698
107293
|
runtime.aedes.authorizeSubscribe = this.makeSubscribeAuthorizer();
|
|
105699
107294
|
runtime.credentialsSnapshot = nextSnapshot;
|
|
105700
107295
|
if (affectedUsers.length === 0) {
|
|
105701
|
-
this.ctx.logger.debug("mqtt embedded hot-reload — no password changes", { meta: { aclRules: this.
|
|
107296
|
+
this.ctx.logger.debug("mqtt embedded hot-reload — no password changes", { meta: { aclRules: this.activeEmbeddedAcl.length } });
|
|
105702
107297
|
return;
|
|
105703
107298
|
}
|
|
105704
107299
|
const clients = readClientMap(runtime.aedes);
|
|
@@ -105724,7 +107319,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105724
107319
|
}
|
|
105725
107320
|
buildCredentialsSnapshot() {
|
|
105726
107321
|
const m = /* @__PURE__ */ new Map();
|
|
105727
|
-
for (const c of this.
|
|
107322
|
+
for (const c of this.activeEmbeddedClients) m.set(c.username, c.passwordHash);
|
|
105728
107323
|
return m;
|
|
105729
107324
|
}
|
|
105730
107325
|
/**
|
|
@@ -105749,7 +107344,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105749
107344
|
}
|
|
105750
107345
|
collectExternalProbeInputs() {
|
|
105751
107346
|
const out = [];
|
|
105752
|
-
for (const b of this.
|
|
107347
|
+
for (const b of this.activeExternalBrokers) {
|
|
105753
107348
|
if (b.onboard !== void 0) continue;
|
|
105754
107349
|
let password;
|
|
105755
107350
|
try {
|
|
@@ -105786,7 +107381,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
105786
107381
|
const paths = retiredCiphertextPaths(this.config);
|
|
105787
107382
|
if (paths.length === 0) return;
|
|
105788
107383
|
const unreadable = [];
|
|
105789
|
-
const values = [...this.
|
|
107384
|
+
const values = [...this.activeExternalBrokers.map((b) => ({
|
|
105790
107385
|
id: `brokers[${b.id}].password`,
|
|
105791
107386
|
v: b.password
|
|
105792
107387
|
})), {
|