@camstack/system 1.2.441 → 1.2.443
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/addon-runner.js +72 -2
- package/dist/addon-runner.mjs +72 -2
- package/dist/addon-utils.js +1 -1
- package/dist/addon-utils.mjs +1 -1
- package/dist/auth/totp-manager.d.ts +46 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.d.ts +41 -1
- package/dist/builtins/alerts/alerts.addon.js +638 -26
- package/dist/builtins/alerts/alerts.addon.mjs +638 -26
- package/dist/builtins/alerts/critical-push.d.ts +86 -0
- package/dist/builtins/alerts/runner-alert.d.ts +71 -0
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/block-package.d.ts +4 -2
- package/dist/builtins/core-blocks/block-settings-store.d.ts +65 -19
- package/dist/builtins/core-blocks/core-blocks.addon.js +365 -38
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +365 -38
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.js +3 -3
- package/dist/builtins/keychain/index.mjs +3 -3
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/auth-schema.d.ts +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +226 -17
- package/dist/builtins/local-auth/local-auth.addon.mjs +226 -17
- package/dist/builtins/local-network/local-network.addon.js +2 -2
- package/dist/builtins/local-network/local-network.addon.mjs +2 -2
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +2 -2
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +2 -2
- package/dist/builtins/platform-probe/index.js +2 -2
- package/dist/builtins/platform-probe/index.mjs +2 -2
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/index.js +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +2 -2
- package/dist/builtins/system-config/system-config.addon.mjs +2 -2
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{composition-sources-BG-W2U0d.js → composition-sources-BUXX1e8j.js} +1 -1
- package/dist/{composition-sources-ByNISOnW.mjs → composition-sources-C8tUG2uL.mjs} +1 -1
- package/dist/{constants-DJILnUSb.mjs → constants-CGwVaAV3.mjs} +1 -1
- package/dist/{constants-UojhAJcm.js → constants-DWmm7_of.js} +1 -1
- package/dist/{device-ca-D8Ri9TVz.mjs → device-ca-DBfiJ_SB.mjs} +1 -1
- package/dist/{device-ca-196DAEZT.js → device-ca-DGayRmC0.js} +1 -1
- package/dist/{dist-BWvkuc31.mjs → dist-Bxt_S-9a.mjs} +180 -13
- package/dist/{dist-BiTCcaf-.js → dist-D9qT3jHr.js} +203 -12
- package/dist/file-host-worker.js +1 -1
- package/dist/file-host-worker.mjs +1 -1
- package/dist/{hub-mdns-ciao-DOBTq8qr.js → hub-mdns-ciao-B-wk9nzx.js} +1 -1
- package/dist/{hub-mdns-ciao-Cnd0bTB9.mjs → hub-mdns-ciao-DpPcQDg-.mjs} +1 -1
- package/dist/index.js +145 -16
- package/dist/index.mjs +140 -17
- package/dist/kernel/addon-refused-error.d.ts +30 -0
- package/dist/kernel/index.d.ts +3 -0
- package/dist/kernel/legacy-blob-guard.d.ts +6 -1
- package/dist/kernel/moleculer/process-service.d.ts +16 -1
- package/dist/kernel/moleculer/runner-refusal.d.ts +27 -0
- package/dist/{restore-archive-4ziluTU3.mjs → restore-archive-9CGgeBzD.mjs} +1 -1
- package/dist/{restore-archive-CfSSjKnA.js → restore-archive-BMNXwWWD.js} +1 -1
- package/dist/{wal-maintenance-CgZTNWAz.js → wal-maintenance-cRHR4rUY.js} +0 -0
- package/dist/{wal-maintenance-B57SAp4a.mjs → wal-maintenance-sIB32ONp.mjs} +0 -0
- package/package.json +1 -1
- package/dist/{custom-action-registry-BKmoaqG0.js → custom-action-registry-COyb49zm.js} +1 -1
- package/dist/{custom-action-registry-CyUuhATc.mjs → custom-action-registry-stpC3Zy-.mjs} +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
4
|
-
const require_composition_sources = require("../../composition-sources-
|
|
3
|
+
const require_dist = require("../../dist-D9qT3jHr.js");
|
|
4
|
+
const require_composition_sources = require("../../composition-sources-BUXX1e8j.js");
|
|
5
5
|
const require_prefix_range = require("../../prefix-range-D_KueXbQ.js");
|
|
6
6
|
const require_prefix_walk = require("../../prefix-walk-Dv6MLZlM.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
@@ -10,6 +10,7 @@ let node_fs_promises = require("node:fs/promises");
|
|
|
10
10
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
11
11
|
let node_path = require("node:path");
|
|
12
12
|
node_path = require_chunk.__toESM(node_path);
|
|
13
|
+
let node_util = require("node:util");
|
|
13
14
|
let node_module = require("node:module");
|
|
14
15
|
let node_os = require("node:os");
|
|
15
16
|
node_os = require_chunk.__toESM(node_os);
|
|
@@ -75,6 +76,10 @@ import * as blockModule from './block.js'
|
|
|
75
76
|
|
|
76
77
|
const BLOCK_ID = __BLOCK_ID__
|
|
77
78
|
const BLOCK_NAME = __BLOCK_NAME__
|
|
79
|
+
// The block's settings form, embedded so the SDK derives the keychain secret
|
|
80
|
+
// tree from it (a password field is stored as a marker and read back resolved,
|
|
81
|
+
// D880). null = the block declares none.
|
|
82
|
+
const BLOCK_SETTINGS_SCHEMA = __BLOCK_SETTINGS_SCHEMA__
|
|
78
83
|
const blockMain = blockModule.default
|
|
79
84
|
|
|
80
85
|
export default class CoreBlockAddon extends BaseAddon {
|
|
@@ -86,6 +91,10 @@ export default class CoreBlockAddon extends BaseAddon {
|
|
|
86
91
|
this.disposer = null
|
|
87
92
|
}
|
|
88
93
|
|
|
94
|
+
globalSettingsSchema() {
|
|
95
|
+
return BLOCK_SETTINGS_SCHEMA
|
|
96
|
+
}
|
|
97
|
+
|
|
89
98
|
async onInitialize() {
|
|
90
99
|
if (typeof blockMain !== 'function') {
|
|
91
100
|
throw new Error(
|
|
@@ -113,8 +122,8 @@ export default class CoreBlockAddon extends BaseAddon {
|
|
|
113
122
|
`;
|
|
114
123
|
/** Build the wrapper for one block. Exported for the test that asserts the
|
|
115
124
|
* generated source actually carries the block's identity. */
|
|
116
|
-
function renderBlockWrapper(blockId, blockName) {
|
|
117
|
-
return WRAPPER_SOURCE.replace("__BLOCK_ID__", JSON.stringify(blockId)).replace("__BLOCK_NAME__", JSON.stringify(blockName));
|
|
125
|
+
function renderBlockWrapper(blockId, blockName, settingsSchema = void 0) {
|
|
126
|
+
return WRAPPER_SOURCE.replace("__BLOCK_ID__", () => JSON.stringify(blockId)).replace("__BLOCK_NAME__", () => JSON.stringify(blockName)).replace("__BLOCK_SETTINGS_SCHEMA__", () => JSON.stringify(settingsSchema ?? null));
|
|
118
127
|
}
|
|
119
128
|
/** The package manifest the addon runner reads to find the entry point. */
|
|
120
129
|
function renderBlockManifest(blockId, blockName) {
|
|
@@ -163,7 +172,7 @@ async function materializeBlock(input) {
|
|
|
163
172
|
await Promise.all([
|
|
164
173
|
node_fs_promises.writeFile(node_path.join(addonDir, "package.json"), renderBlockManifest(input.blockId, input.blockName), "utf-8"),
|
|
165
174
|
node_fs_promises.writeFile(node_path.join(distDir, "block.js"), input.js, "utf-8"),
|
|
166
|
-
node_fs_promises.writeFile(node_path.join(distDir, "index.js"), renderBlockWrapper(input.blockId, input.blockName), "utf-8")
|
|
175
|
+
node_fs_promises.writeFile(node_path.join(distDir, "index.js"), renderBlockWrapper(input.blockId, input.blockName, input.settingsSchema), "utf-8")
|
|
167
176
|
]);
|
|
168
177
|
return {
|
|
169
178
|
addonDir,
|
|
@@ -426,7 +435,8 @@ var BlockSupervisor = class {
|
|
|
426
435
|
blockId: block.id,
|
|
427
436
|
blockName: block.name,
|
|
428
437
|
js: compiled.js,
|
|
429
|
-
files
|
|
438
|
+
files,
|
|
439
|
+
settingsSchema: block.settingsSchema
|
|
430
440
|
});
|
|
431
441
|
await this.deps.host.spawn({
|
|
432
442
|
runnerId: pkg.addonId,
|
|
@@ -560,9 +570,33 @@ function createAddonSettingsStore(door, addonId) {
|
|
|
560
570
|
//#endregion
|
|
561
571
|
//#region src/builtins/core-blocks/block-settings-store.ts
|
|
562
572
|
/**
|
|
573
|
+
* Preference values for core blocks.
|
|
574
|
+
*
|
|
575
|
+
* Schema lives on the block record (`settingsSchema`, extracted from the
|
|
576
|
+
* compiled entry's named export or set by the editor). The VALUES live where a
|
|
577
|
+
* running script reads them and nowhere else: the block addon's own
|
|
578
|
+
* `addon-settings` rows (`core-block-<id>.<field>`, D870), so
|
|
579
|
+
* `ctx.settings.readAddonStore()` answers them like any addon's settings and a
|
|
580
|
+
* settings patch never rewrites the operator's source.
|
|
581
|
+
*
|
|
582
|
+
* A field the block's `settingsSchema` declares `password` / `secret: true` is
|
|
583
|
+
* keychain-backed (D783 / D787), owner = the block's addon id, scope `global`:
|
|
584
|
+
* the row holds the marker, the value lives in the keychain. The block's
|
|
585
|
+
* generated wrapper declares the same schema, so the running script reads the
|
|
586
|
+
* resolved VALUE. A marker is honoured only for its own ref (an incoming marker
|
|
587
|
+
* is never stored - the write path treats it as "keep"), a REDACTED / view
|
|
588
|
+
* value keeps what is stored, and a keychain that cannot take a write refuses
|
|
589
|
+
* it rather than storing plaintext.
|
|
590
|
+
*
|
|
591
|
+
* Before D880 the values were ALSO kept as one blob per block in
|
|
592
|
+
* `core-blocks:settings`: two raw copies of every password. That collection is
|
|
593
|
+
* now LEGACY - read only by {@link BlockSettingsStore.migrateRawSecrets}, which
|
|
594
|
+
* moves the secrets, copies any key the rows lack, and deletes the blob.
|
|
595
|
+
*/
|
|
596
|
+
/**
|
|
563
597
|
* @durable class=config owner=core-blocks
|
|
564
|
-
* write="
|
|
565
|
-
* retention="
|
|
598
|
+
* write="NOTHING since D880 - the values live in the block addon's `addon-settings` rows. Only the one-shot `migrateRawSecrets` reads it, then deletes each row."
|
|
599
|
+
* retention="legacy: every row is deleted by the verified operator migration; the declaration goes with it once every hub ran it."
|
|
566
600
|
*/
|
|
567
601
|
var CORE_BLOCK_SETTINGS_COLLECTION = "core-blocks:settings";
|
|
568
602
|
var CORE_BLOCK_SETTINGS_COLUMNS = [{
|
|
@@ -576,54 +610,335 @@ var CORE_BLOCK_SETTINGS_COLUMNS = [{
|
|
|
576
610
|
notNull: true,
|
|
577
611
|
defaultValue: "{}"
|
|
578
612
|
}];
|
|
613
|
+
/** The block declares a secret, but this process has no keychain door to put it in. */
|
|
614
|
+
var BlockSecretsUnavailableError = class extends Error {
|
|
615
|
+
blockId;
|
|
616
|
+
constructor(blockId) {
|
|
617
|
+
super(`core block "${blockId}" declares a password field, but this process has no keychain door - nothing was saved. Unlock the keychain (System -> Secrets) and save again.`);
|
|
618
|
+
this.blockId = blockId;
|
|
619
|
+
this.name = "BlockSecretsUnavailableError";
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
/** The block addon's settings rows could not be read: never answered as "empty". */
|
|
623
|
+
var BlockSettingsUnreadableError = class extends Error {
|
|
624
|
+
blockId;
|
|
625
|
+
constructor(blockId, reason) {
|
|
626
|
+
super(`core block "${blockId}": its settings rows cannot be read (${reason})`);
|
|
627
|
+
this.blockId = blockId;
|
|
628
|
+
this.name = "BlockSettingsUnreadableError";
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
function isRecord(value) {
|
|
632
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
633
|
+
}
|
|
634
|
+
/** Walk `blob` along the secret `tree`: how many declared secret values are raw / markers. */
|
|
635
|
+
function countSecretLeaves(tree, blob) {
|
|
636
|
+
const counts = {
|
|
637
|
+
raw: 0,
|
|
638
|
+
markers: 0
|
|
639
|
+
};
|
|
640
|
+
if (!isRecord(blob)) return counts;
|
|
641
|
+
const visit = (node, value) => {
|
|
642
|
+
if (node.kind === "leaf") {
|
|
643
|
+
if (require_dist.isSecretMarker(value)) counts.markers += 1;
|
|
644
|
+
else if (typeof value === "string" && value.length > 0) counts.raw += 1;
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (node.kind === "object") {
|
|
648
|
+
if (isRecord(value)) for (const [key, child] of node.children) visit(child, value[key]);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (Array.isArray(value)) for (const row of value) {
|
|
652
|
+
if (!isRecord(row)) continue;
|
|
653
|
+
for (const [key, child] of node.item) visit(child, row[key]);
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
for (const [key, node] of tree) visit(node, blob[key]);
|
|
657
|
+
return counts;
|
|
658
|
+
}
|
|
659
|
+
/** Only the top-level keys the secret tree names (and, when given, `only` holds). */
|
|
660
|
+
function secretKeysOf(tree, blob, only) {
|
|
661
|
+
const out = {};
|
|
662
|
+
for (const key of tree.keys()) if (key in blob && (only === void 0 || key in only)) out[key] = blob[key];
|
|
663
|
+
return out;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Names that LOOK like a credential. Only used on a block whose schema does not
|
|
667
|
+
* declare the field (none, or a partial one): such a value cannot be moved by type,
|
|
668
|
+
* so it is FLAGGED, never copied and never silently dropped.
|
|
669
|
+
*/
|
|
670
|
+
var CREDENTIAL_NAME = /(password|passwd|passphrase|secret|token|api_?key|private_?key|access_?code|credential|cookie|pincode|pin$)/i;
|
|
671
|
+
var NOT_A_VALUE_SUFFIX = /(hash|hashed|fingerprint|count|length|expires|expiry|type|kind|version|status|label|ref|path|id|name)$/i;
|
|
672
|
+
function holdsRawCredential(value, depth = 0) {
|
|
673
|
+
if (depth > 8) return false;
|
|
674
|
+
if (Array.isArray(value)) return value.some((v) => holdsRawCredential(v, depth + 1));
|
|
675
|
+
if (!isRecord(value) || require_dist.isSecretMarker(value)) return false;
|
|
676
|
+
return Object.entries(value).some(([key, child]) => CREDENTIAL_NAME.test(key) && !NOT_A_VALUE_SUFFIX.test(key) ? typeof child === "string" && child.length > 0 || holdsRawCredential(child, depth + 1) : holdsRawCredential(child, depth + 1));
|
|
677
|
+
}
|
|
678
|
+
/** Top-level keys the schema does not declare secret that nevertheless hold a raw credential-named value. */
|
|
679
|
+
function untrackedCredentialKeys(tree, blob) {
|
|
680
|
+
const out = /* @__PURE__ */ new Set();
|
|
681
|
+
for (const [key, value] of Object.entries(blob)) {
|
|
682
|
+
if (tree.has(key)) continue;
|
|
683
|
+
if (CREDENTIAL_NAME.test(key) && !NOT_A_VALUE_SUFFIX.test(key) && typeof value === "string" && value.length > 0 || !require_dist.isSecretMarker(value) && holdsRawCredential(value)) out.add(key);
|
|
684
|
+
}
|
|
685
|
+
return out;
|
|
686
|
+
}
|
|
687
|
+
/** The legacy row's values, tolerating the old bare-blob shape. */
|
|
688
|
+
function legacyValuesOf(row) {
|
|
689
|
+
if (!isRecord(row)) return {};
|
|
690
|
+
const values = row["values"];
|
|
691
|
+
if (isRecord(values)) return { ...values };
|
|
692
|
+
return "values" in row ? {} : { ...row };
|
|
693
|
+
}
|
|
579
694
|
var BlockSettingsStore = class {
|
|
580
695
|
store;
|
|
696
|
+
keychainFor;
|
|
697
|
+
onOrphaned;
|
|
581
698
|
constructor(deps) {
|
|
582
699
|
this.store = deps.store;
|
|
700
|
+
this.keychainFor = deps.keychainFor;
|
|
701
|
+
this.onOrphaned = deps.onOrphaned ?? (() => void 0);
|
|
583
702
|
}
|
|
703
|
+
/** Declares the LEGACY collection, so the migration can read it. */
|
|
584
704
|
static async declare(store) {
|
|
585
705
|
await store.declareCollection.mutate({
|
|
586
706
|
collection: CORE_BLOCK_SETTINGS_COLLECTION,
|
|
587
707
|
columns: [...CORE_BLOCK_SETTINGS_COLUMNS]
|
|
588
708
|
});
|
|
589
709
|
}
|
|
590
|
-
|
|
591
|
-
const
|
|
710
|
+
rows(blockId) {
|
|
711
|
+
const store = this.store;
|
|
712
|
+
return createAddonSettingsStore({
|
|
713
|
+
query: (input) => store.query.query(input),
|
|
714
|
+
get: (input) => store.get.query(input),
|
|
715
|
+
transact: (input) => store.transact.mutate(input)
|
|
716
|
+
}, require_dist.coreBlockAddonId(blockId));
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* The block's stored values - markers, NOT resolved. For the admin form
|
|
720
|
+
* (`hydrateSchema` turns a marker into a "set" view) and as the `stored`
|
|
721
|
+
* side of a write. Absent answers `{}`; an unreadable store THROWS.
|
|
722
|
+
*/
|
|
723
|
+
async readStored(blockId) {
|
|
724
|
+
const read = await this.rows(blockId).read();
|
|
725
|
+
if (read.kind === "unreadable") throw new BlockSettingsUnreadableError(blockId, read.error);
|
|
726
|
+
return read.kind === "present" ? { ...read.value } : {};
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Patch the block's values. Secret fields (by the schema TYPE) go to the
|
|
730
|
+
* keychain first and the row gets the marker; a keychain that cannot take
|
|
731
|
+
* them refuses the whole patch.
|
|
732
|
+
*/
|
|
733
|
+
async mergeValues(blockId, schema, patch) {
|
|
734
|
+
const tree = require_dist.secretTreeFromSchema(schema ?? null);
|
|
735
|
+
if (tree.size === 0) {
|
|
736
|
+
await this.rows(blockId).merge(patch);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const secrets = this.keychainFor(require_dist.coreBlockAddonId(blockId));
|
|
740
|
+
if (secrets === null) throw new BlockSecretsUnavailableError(blockId);
|
|
741
|
+
const result = await require_dist.transformSecretWrite({
|
|
742
|
+
tree,
|
|
743
|
+
secrets,
|
|
744
|
+
patch,
|
|
745
|
+
stored: await this.readStored(blockId),
|
|
746
|
+
operatorWrite: true
|
|
747
|
+
});
|
|
748
|
+
await this.rows(blockId).merge(result.patch);
|
|
749
|
+
const { orphaned } = await result.afterWrite();
|
|
750
|
+
if (orphaned.length > 0) this.onOrphaned(blockId, orphaned);
|
|
751
|
+
}
|
|
752
|
+
/** The block is gone: its rows, its keychain values and the legacy blob go with it. */
|
|
753
|
+
async delete(blockId) {
|
|
754
|
+
const stored = await this.readStored(blockId);
|
|
755
|
+
const owner = require_dist.coreBlockAddonId(blockId);
|
|
756
|
+
const refs = require_dist.collectSecretMarkerRefs(stored);
|
|
757
|
+
const removal = {};
|
|
758
|
+
for (const key of Object.keys(stored)) removal[key] = void 0;
|
|
759
|
+
await this.rows(blockId).merge(removal);
|
|
760
|
+
await this.store.delete.mutate({
|
|
592
761
|
collection: CORE_BLOCK_SETTINGS_COLLECTION,
|
|
593
762
|
key: blockId
|
|
594
763
|
});
|
|
595
|
-
if (
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
764
|
+
if (refs.length === 0) return;
|
|
765
|
+
const secrets = this.keychainFor(owner);
|
|
766
|
+
try {
|
|
767
|
+
await secrets?.delete(refs);
|
|
768
|
+
if (secrets === null) this.onOrphaned(blockId, refs);
|
|
769
|
+
} catch {
|
|
770
|
+
this.onOrphaned(blockId, refs);
|
|
771
|
+
}
|
|
600
772
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
773
|
+
/**
|
|
774
|
+
* D880: move every plaintext secret a block's `settingsSchema` declares into
|
|
775
|
+
* the keychain, and delete the legacy `core-blocks:settings` blob.
|
|
776
|
+
*
|
|
777
|
+
* Per block: gather the values (the addon-settings rows win; a key only the
|
|
778
|
+
* blob holds is carried over), put each secret, READ IT BACK and compare,
|
|
779
|
+
* and only then write the marker. The legacy row is deleted only after the
|
|
780
|
+
* block's rows hold every key it had and every secret resolves. A block that
|
|
781
|
+
* fails stays exactly as it was. Dry run unless `apply`; idempotent; counts
|
|
782
|
+
* only - never a value, a ref or a block id.
|
|
783
|
+
*/
|
|
784
|
+
async migrateRawSecrets(blocks, apply) {
|
|
785
|
+
const legacyRows = await this.store.query.query({ collection: CORE_BLOCK_SETTINGS_COLLECTION });
|
|
786
|
+
const legacyById = /* @__PURE__ */ new Map();
|
|
787
|
+
for (const row of legacyRows) legacyById.set(row.id, legacyValuesOf(row.data));
|
|
788
|
+
const tally = {
|
|
789
|
+
rows: 0,
|
|
790
|
+
raw: 0,
|
|
791
|
+
markers: 0,
|
|
792
|
+
migrated: 0,
|
|
793
|
+
failed: 0,
|
|
794
|
+
redundant: 0
|
|
605
795
|
};
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
796
|
+
const verifyAfter = [];
|
|
797
|
+
for (const block of blocks) {
|
|
798
|
+
const tree = require_dist.secretTreeFromSchema(block.settingsSchema ?? null);
|
|
799
|
+
const legacy = legacyById.get(block.id);
|
|
800
|
+
const stored = await this.readStored(block.id);
|
|
801
|
+
const effective = {
|
|
802
|
+
...legacy,
|
|
803
|
+
...stored
|
|
804
|
+
};
|
|
805
|
+
const counts = countSecretLeaves(tree, effective);
|
|
806
|
+
const untracked = untrackedCredentialKeys(tree, effective);
|
|
807
|
+
if (tree.size > 0 || untracked.size > 0) {
|
|
808
|
+
tally.rows += 1;
|
|
809
|
+
if (counts.raw > 0 || untracked.size > 0) tally.raw += 1;
|
|
810
|
+
else if (counts.markers > 0) tally.markers += 1;
|
|
811
|
+
verifyAfter.push(block);
|
|
812
|
+
}
|
|
813
|
+
const hasLegacy = legacy !== void 0;
|
|
814
|
+
if (hasLegacy) tally.redundant += 1;
|
|
815
|
+
if (!apply) continue;
|
|
816
|
+
const carry = this.carryable(block.id, effective, stored, tree, untracked);
|
|
817
|
+
if (carry.foreign > 0) tally.failed += 1;
|
|
818
|
+
if (counts.raw > 0) if (await this.moveBlock(block, tree, carry.rest, effective, stored)) tally.migrated += 1;
|
|
819
|
+
else {
|
|
820
|
+
tally.failed += 1;
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
else if (hasLegacy && Object.keys(carry.rest).length > 0) await this.rows(block.id).merge(carry.rest);
|
|
824
|
+
const legacyUntracked = untrackedCredentialKeys(tree, legacy ?? {});
|
|
825
|
+
if (hasLegacy && legacyUntracked.size === 0) await this.dropLegacy(block.id);
|
|
826
|
+
}
|
|
827
|
+
const known = new Set(blocks.map((b) => b.id));
|
|
828
|
+
for (const id of legacyById.keys()) {
|
|
829
|
+
if (known.has(id)) continue;
|
|
830
|
+
tally.redundant += 1;
|
|
831
|
+
if (apply) await this.dropLegacy(id);
|
|
832
|
+
}
|
|
833
|
+
return this.report(apply, tally, verifyAfter);
|
|
613
834
|
}
|
|
614
|
-
|
|
835
|
+
/**
|
|
836
|
+
* The keys of the legacy blob that may be copied into the block's rows: not in
|
|
837
|
+
* the rows already, not a secret the tree moves, not a credential-named value no
|
|
838
|
+
* schema declares (flagged instead), and not a marker that points at another
|
|
839
|
+
* owner's secret (counted as `foreign`, never copied).
|
|
840
|
+
*/
|
|
841
|
+
carryable(blockId, effective, stored, tree, untracked) {
|
|
842
|
+
const owner = require_dist.coreBlockAddonId(blockId);
|
|
843
|
+
const rest = {};
|
|
844
|
+
let foreign = 0;
|
|
845
|
+
for (const [key, value] of Object.entries(effective)) {
|
|
846
|
+
if (key in stored || tree.has(key) || untracked.has(key)) continue;
|
|
847
|
+
if (require_dist.collectSecretMarkerRefs(value).some((ref) => !require_dist.isSecretRefOwnedBy(ref, owner))) {
|
|
848
|
+
foreign += 1;
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
rest[key] = value;
|
|
852
|
+
}
|
|
853
|
+
return {
|
|
854
|
+
rest,
|
|
855
|
+
foreign
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
async dropLegacy(blockId) {
|
|
615
859
|
await this.store.delete.mutate({
|
|
616
860
|
collection: CORE_BLOCK_SETTINGS_COLLECTION,
|
|
617
861
|
key: blockId
|
|
618
862
|
});
|
|
619
863
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
864
|
+
/** put -> read back -> compare -> only then the marker rows. */
|
|
865
|
+
async moveBlock(block, tree, rest, effective, stored) {
|
|
866
|
+
const secrets = this.keychainFor(require_dist.coreBlockAddonId(block.id));
|
|
867
|
+
if (secrets === null) return false;
|
|
868
|
+
const original = secretKeysOf(tree, effective);
|
|
869
|
+
let written = null;
|
|
870
|
+
try {
|
|
871
|
+
written = await require_dist.transformSecretWrite({
|
|
872
|
+
tree,
|
|
873
|
+
secrets,
|
|
874
|
+
patch: original,
|
|
875
|
+
stored: secretKeysOf(tree, stored),
|
|
876
|
+
operatorWrite: true
|
|
877
|
+
});
|
|
878
|
+
const back = await require_dist.resolveSecretMarkers(written.patch, secrets, { unresolved: "omit" });
|
|
879
|
+
const expected = await require_dist.resolveSecretMarkers(secretKeysOf(tree, original, written.patch), secrets, { unresolved: "omit" });
|
|
880
|
+
if (back.unresolved.length > 0 || expected.unresolved.length > 0 || !(0, node_util.isDeepStrictEqual)(back.value, expected.value)) {
|
|
881
|
+
await this.discard(secrets, written.patch, stored);
|
|
882
|
+
return false;
|
|
883
|
+
}
|
|
884
|
+
await this.rows(block.id).merge({
|
|
885
|
+
...rest,
|
|
886
|
+
...written.patch
|
|
887
|
+
});
|
|
888
|
+
await written.afterWrite();
|
|
889
|
+
return true;
|
|
890
|
+
} catch {
|
|
891
|
+
if (written !== null) await this.discard(secrets, written.patch, stored);
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
/** Remove the values a failed move put, never one the row already pointed at. */
|
|
896
|
+
async discard(secrets, patch, stored) {
|
|
897
|
+
const had = new Set(require_dist.collectSecretMarkerRefs(stored));
|
|
898
|
+
const fresh = require_dist.collectSecretMarkerRefs(patch).filter((ref) => !had.has(ref));
|
|
899
|
+
if (fresh.length === 0) return;
|
|
900
|
+
try {
|
|
901
|
+
await secrets.delete(fresh);
|
|
902
|
+
} catch {}
|
|
903
|
+
}
|
|
904
|
+
async report(apply, tally, subjects) {
|
|
905
|
+
let remainingRaw = 0;
|
|
906
|
+
let unresolvedMarkers = 0;
|
|
907
|
+
const legacyById = /* @__PURE__ */ new Map();
|
|
908
|
+
for (const row of await this.store.query.query({ collection: CORE_BLOCK_SETTINGS_COLLECTION })) legacyById.set(row.id, legacyValuesOf(row.data));
|
|
909
|
+
for (const block of subjects) {
|
|
910
|
+
const tree = require_dist.secretTreeFromSchema(block.settingsSchema ?? null);
|
|
911
|
+
const after = await this.readStored(block.id);
|
|
912
|
+
const counts = countSecretLeaves(tree, after);
|
|
913
|
+
remainingRaw += counts.raw;
|
|
914
|
+
const legacyKeys = legacyById.get(block.id) ?? {};
|
|
915
|
+
for (const key of untrackedCredentialKeys(tree, after)) if (!(key in legacyKeys)) remainingRaw += 1;
|
|
916
|
+
if (counts.markers === 0) continue;
|
|
917
|
+
const secrets = this.keychainFor(require_dist.coreBlockAddonId(block.id));
|
|
918
|
+
if (secrets === null) {
|
|
919
|
+
unresolvedMarkers += counts.markers;
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
const resolved = await require_dist.resolveSecretMarkers(secretKeysOf(tree, after), secrets, { unresolved: "omit" });
|
|
923
|
+
unresolvedMarkers += resolved.unresolved.length;
|
|
924
|
+
}
|
|
925
|
+
const bySchema = new Map(subjects.map((b) => [b.id, b.settingsSchema]));
|
|
926
|
+
for (const [id, values] of legacyById) {
|
|
927
|
+
const tree = require_dist.secretTreeFromSchema(bySchema.get(id) ?? null);
|
|
928
|
+
remainingRaw += countSecretLeaves(tree, values).raw + untrackedCredentialKeys(tree, values).size;
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
apply,
|
|
932
|
+
rows: tally.rows,
|
|
933
|
+
raw: tally.raw,
|
|
934
|
+
markers: tally.markers,
|
|
935
|
+
migrated: tally.migrated,
|
|
936
|
+
failed: tally.failed,
|
|
937
|
+
remainingRaw,
|
|
938
|
+
unresolvedMarkers,
|
|
939
|
+
redundantCopies: tally.redundant,
|
|
940
|
+
verified: remainingRaw === 0 && unresolvedMarkers === 0
|
|
941
|
+
};
|
|
627
942
|
}
|
|
628
943
|
};
|
|
629
944
|
//#endregion
|
|
@@ -1953,7 +2268,14 @@ var CoreBlocksAddon = class extends require_dist.BaseAddon {
|
|
|
1953
2268
|
store: settingsStore,
|
|
1954
2269
|
logger: this.ctx.logger.child("store")
|
|
1955
2270
|
});
|
|
1956
|
-
this.prefs = new BlockSettingsStore({
|
|
2271
|
+
this.prefs = new BlockSettingsStore({
|
|
2272
|
+
store: settingsStore,
|
|
2273
|
+
keychainFor: (owner) => this.ctx.secrets?.forOwner?.(owner) ?? null,
|
|
2274
|
+
onOrphaned: (blockId, refs) => this.ctx.logger.warn("core block settings: dropped keychain refs could not be deleted", { meta: {
|
|
2275
|
+
blockId,
|
|
2276
|
+
refs: [...refs]
|
|
2277
|
+
} })
|
|
2278
|
+
});
|
|
1957
2279
|
await this.store.load();
|
|
1958
2280
|
this.transpiler = await loadTranspiler(this.ctx.logger);
|
|
1959
2281
|
armBlocksIntegration({
|
|
@@ -2213,13 +2535,18 @@ var CoreBlocksAddon = class extends require_dist.BaseAddon {
|
|
|
2213
2535
|
if (block === null) return null;
|
|
2214
2536
|
const schema = block.settingsSchema;
|
|
2215
2537
|
if (schema === void 0) return null;
|
|
2216
|
-
return require_dist.hydrateSchema(schema, await this.requirePrefs().
|
|
2538
|
+
return require_dist.hydrateSchema(schema, await this.requirePrefs().readStored(blockId), { inlineSecretsAsViews: true });
|
|
2217
2539
|
},
|
|
2218
2540
|
updateBlockSettings: async ({ blockId, patch }) => {
|
|
2219
|
-
|
|
2220
|
-
|
|
2541
|
+
const block = this.requireStore().get(blockId);
|
|
2542
|
+
if (block === null) throw new Error(`core-blocks: no block "${blockId}"`);
|
|
2543
|
+
await this.requirePrefs().mergeValues(blockId, block.settingsSchema, patch);
|
|
2221
2544
|
return { success: true };
|
|
2222
2545
|
},
|
|
2546
|
+
migrateRawSecrets: async ({ apply }) => this.requirePrefs().migrateRawSecrets(this.requireStore().list().map((b) => ({
|
|
2547
|
+
id: b.id,
|
|
2548
|
+
settingsSchema: b.settingsSchema
|
|
2549
|
+
})), apply),
|
|
2223
2550
|
getTypeDefs: async () => {
|
|
2224
2551
|
this.typeDefs ??= await loadBlockTypeDefs(this.ctx.logger);
|
|
2225
2552
|
return { libs: this.typeDefs.map((l) => ({
|