@camstack/system 1.2.307 → 1.2.309

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/autotrack/index.js +1 -1
  8. package/dist/builtins/autotrack/index.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/camera-grid/index.js +1 -1
  12. package/dist/builtins/camera-grid/index.mjs +1 -1
  13. package/dist/builtins/composer/composed-device.d.ts +26 -0
  14. package/dist/builtins/composer/composer.addon.d.ts +12 -0
  15. package/dist/builtins/composer/composer.addon.js +1682 -0
  16. package/dist/builtins/composer/composer.addon.mjs +1677 -0
  17. package/dist/builtins/composer/composer.d.ts +103 -0
  18. package/dist/builtins/composer/composition-declarations.d.ts +53 -0
  19. package/dist/builtins/composer/composition-runtime.d.ts +88 -0
  20. package/dist/builtins/composer/deadline-timer.d.ts +18 -0
  21. package/dist/builtins/composer/index.d.ts +1 -0
  22. package/dist/builtins/composer/index.js +8 -0
  23. package/dist/builtins/composer/index.mjs +2 -0
  24. package/dist/builtins/composer/source-tracker.d.ts +78 -0
  25. package/dist/builtins/console-logging/index.js +1 -1
  26. package/dist/builtins/console-logging/index.mjs +1 -1
  27. package/dist/builtins/core-blocks/block-supervisor.d.ts +32 -1
  28. package/dist/builtins/core-blocks/blocks-integration.d.ts +5 -0
  29. package/dist/builtins/core-blocks/composition-api.d.ts +25 -0
  30. package/dist/builtins/core-blocks/composition-sources.d.ts +10 -0
  31. package/dist/builtins/core-blocks/core-block-store.d.ts +34 -2
  32. package/dist/builtins/core-blocks/core-blocks.addon.d.ts +6 -0
  33. package/dist/builtins/core-blocks/core-blocks.addon.js +504 -11
  34. package/dist/builtins/core-blocks/core-blocks.addon.mjs +504 -11
  35. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  36. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  37. package/dist/builtins/doorbell/virtual-doorbell.addon.js +10 -31
  38. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +9 -30
  39. package/dist/builtins/hub-forwarder/index.js +1 -1
  40. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  41. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  42. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  43. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  44. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  45. package/dist/builtins/local-network/local-network.addon.js +1 -1
  46. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  47. package/dist/builtins/loki-logging/index.js +1 -1
  48. package/dist/builtins/loki-logging/index.mjs +1 -1
  49. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  50. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  51. package/dist/builtins/platform-probe/index.js +1 -1
  52. package/dist/builtins/platform-probe/index.mjs +1 -1
  53. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  54. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  55. package/dist/builtins/shared/device-state-changed.d.ts +17 -0
  56. package/dist/builtins/snapshot/index.js +1 -1
  57. package/dist/builtins/snapshot/index.mjs +1 -1
  58. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  59. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  60. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  61. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  62. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  63. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  64. package/dist/builtins/system-config/system-config.addon.js +1 -1
  65. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  66. package/dist/builtins/winston-logging/index.js +1 -1
  67. package/dist/builtins/winston-logging/index.mjs +1 -1
  68. package/dist/device-state-changed-Bk7cvTnz.js +31 -0
  69. package/dist/device-state-changed-DoBbkyEI.mjs +20 -0
  70. package/dist/{dist-DdfJa8Zn.mjs → dist-BEuQPDk_.mjs} +1333 -83
  71. package/dist/{dist-D3ch99WD.js → dist-BhO9eYKO.js} +1422 -82
  72. package/dist/index.d.ts +1 -0
  73. package/dist/index.js +4 -1
  74. package/dist/index.mjs +4 -2
  75. package/dist/{retired-settings-keys-BJNhhXap.mjs → retired-settings-keys-CQvG6B-Q.mjs} +1 -1
  76. package/dist/{retired-settings-keys-DG_LEPqC.js → retired-settings-keys-DHgnJqS3.js} +1 -1
  77. package/package.json +20 -1
@@ -0,0 +1,103 @@
1
+ import { CapabilityLookup, ComposerApplyInput, ComposerApplyResult, CompositionBlockState, DeviceDeclaration, DeviceType, IScopedLogger } from '@camstack/types';
2
+ import { DeviceStateChangedData } from '../shared/device-state-changed.js';
3
+ import { ComposedDeviceHandle } from './composed-device.js';
4
+ import { PersistedComposedRow } from './composition-declarations.js';
5
+ import { DeadlineTimer } from './composition-runtime.js';
6
+ import { SourceBindingsChangeReason, SourceTracker } from './source-tracker.js';
7
+ /** What the composer reads of a `device.bindings-changed` payload; the typed `EventCatalog` payload is assignable to it. */
8
+ export interface DeviceBindingsChangedData {
9
+ readonly deviceId: number;
10
+ readonly capName: string;
11
+ readonly reason: SourceBindingsChangeReason;
12
+ }
13
+ export interface ComposerDerivedSpec {
14
+ readonly type: DeviceType;
15
+ readonly role?: string;
16
+ }
17
+ export interface ComposerDevicePorts {
18
+ setName(deviceId: number, name: string): Promise<void>;
19
+ decommission(deviceId: number): Promise<void>;
20
+ applyDerivedSpec(deviceId: number, derived: ComposerDerivedSpec): Promise<void>;
21
+ }
22
+ export interface ComposerDeclareResult {
23
+ /** Live composed devices by stableId. */
24
+ readonly devices: ReadonlyMap<string, ComposedDeviceHandle>;
25
+ }
26
+ export interface ComposerDeps {
27
+ readonly logger: IScopedLogger;
28
+ readonly tracker: SourceTracker;
29
+ readonly lookupCap: CapabilityLookup;
30
+ /** The composed rows that already exist (`listOwnDevices`), read once before the first withdrawal decision. */
31
+ readonly readPersisted: () => Promise<readonly PersistedComposedRow[]>;
32
+ readonly declare: (integrationId: string, devices: readonly DeviceDeclaration[]) => Promise<ComposerDeclareResult>;
33
+ readonly devices: ComposerDevicePorts;
34
+ readonly now: () => number;
35
+ readonly newTimer: () => DeadlineTimer;
36
+ }
37
+ export declare class Composer {
38
+ private readonly deps;
39
+ private live;
40
+ private devices;
41
+ /** StableIds whose row exists: the seeded persisted rows, then what every declare kept. */
42
+ private rows;
43
+ private seeded;
44
+ /** The previous seed read answered `[]`: one more `[]` confirms it (D49). */
45
+ private emptySeedSeen;
46
+ /** Consecutive applies refused because `core_blocks` had not loaded: warned once per run. */
47
+ private storeWaitApplies;
48
+ /** Consecutive applies refused because the Blocks integration had not reconciled: warned once per run. */
49
+ private integrationWaitApplies;
50
+ private declaredFingerprint;
51
+ private readonly gate;
52
+ /** Applies never interleave: each one reads and replaces the whole live set. */
53
+ private applyChain;
54
+ constructor(deps: ComposerDeps);
55
+ apply(input: ComposerApplyInput): Promise<ComposerApplyResult>;
56
+ getStates(blockIds: readonly string[]): readonly CompositionBlockState[];
57
+ /** Runs on the tracker's one chain (R3): resolves once the change reached every runtime. */
58
+ onStateChanged(data: DeviceStateChangedData): Promise<void>;
59
+ onDeviceUnregistered(deviceId: number): Promise<void>;
60
+ onBindingsChanged(data: DeviceBindingsChangedData): Promise<void>;
61
+ shutdown(): void;
62
+ private applyNow;
63
+ private waiting;
64
+ /**
65
+ * Seed the withdrawal gate with the rows that exist, ONCE, before its first
66
+ * `filter`; answers null once seeded, else why this apply must wait.
67
+ *
68
+ * An EMPTY answer is the destroying direction: a cold registry answers `[]`
69
+ * too, and `DeclaredDevices.reconcile` sweeps from its OWN later read — so a
70
+ * cold seed followed by a warm sweep would delete the row of a block that no
71
+ * longer plans, with no placeholder to hold it. `[]` is therefore trusted
72
+ * only when the next apply's read agrees (D49); that apply comes from the
73
+ * supervisor's existing converge, never a timer here (D3). A failed read
74
+ * changes nothing and breaks the agreement.
75
+ */
76
+ private ensureSeeded;
77
+ /**
78
+ * A row that exists but does not parse is held exactly like a composition
79
+ * that stopped planning: its device (if it has a row) stays as an offline
80
+ * placeholder, and the block fails naming the parse error. Absent, it would
81
+ * be withdrawn on the second apply that omitted it (D49).
82
+ */
83
+ private planUnreadable;
84
+ private plan;
85
+ /** One line when a block starts or stops planning — never one per apply. */
86
+ private logPlanTransitions;
87
+ /**
88
+ * A changed type/role/cap set (or a switch to or from a placeholder) needs a
89
+ * NEW device object: decommission, and let the reconcile adopt it with the
90
+ * new class. A failure keeps the old device and is retried next apply.
91
+ */
92
+ private rebuildShapes;
93
+ private reconcileDevices;
94
+ private rebuildRuntimes;
95
+ private newRuntime;
96
+ private applyName;
97
+ private syncSources;
98
+ /** A failed sync leaves the tracker as it was: the runtimes read what it holds. */
99
+ private syncTracker;
100
+ private dispatch;
101
+ private tagsOf;
102
+ private verdict;
103
+ }
@@ -0,0 +1,53 @@
1
+ import { CapabilityDefinition, CompositionNewTarget, DeviceDeclaration, IScopedLogger } from '@camstack/types';
2
+ export interface ComposerDeclarationInput {
3
+ readonly blockId: string;
4
+ readonly name: string;
5
+ readonly target: CompositionNewTarget;
6
+ readonly caps: readonly CapabilityDefinition[];
7
+ }
8
+ export declare function declarationFor(input: ComposerDeclarationInput): DeviceDeclaration;
9
+ /** What forces a rebuild of the live device: type, role, cap set. Never the name. */
10
+ export declare function shapeOf(input: ComposerDeclarationInput): string;
11
+ /**
12
+ * The numeric id of the live composed device behind a stableId, or null when
13
+ * none was ever built (its declare failed). Every gate line carries it as
14
+ * `tags.deviceId` — the id the operator asks about (CLAUDE.md "Logging").
15
+ */
16
+ export type ComposedDeviceIdOf = (stableId: string) => number | null;
17
+ /**
18
+ * The declaration that holds a persisted composed row whose block has no plan
19
+ * in this apply. `name` and `type` are used only if the row has to be CREATED,
20
+ * which `DeclaredDevices` does not do for a row it finds: it adopts it, and the
21
+ * row's own name and type stay canonical (`ComposedPlaceholderDevice`).
22
+ */
23
+ export declare function placeholderDeclaration(stableId: string): DeviceDeclaration;
24
+ /** A composed device row that already exists — `DeclaredDevicePorts.listOwnDevices()` answers these. */
25
+ export interface PersistedComposedRow {
26
+ readonly id: number;
27
+ readonly stableId: string;
28
+ }
29
+ export declare class WithdrawalGate {
30
+ private readonly logger;
31
+ private readonly deviceIdOf;
32
+ private known;
33
+ private misses;
34
+ private persistedIds;
35
+ constructor(logger: IScopedLogger, deviceIdOf: ComposedDeviceIdOf);
36
+ /**
37
+ * The rows that exist before this process applied anything. Called once at
38
+ * boot, before the first `filter`: without it the mirror starts empty and a
39
+ * row missing from the first apply after a restart (a release, a block that
40
+ * no longer plans) is swept at once — one apply, not two. A seeded row
41
+ * missing from that apply counts as its FIRST miss and is held by
42
+ * `placeholderDeclaration`. A row the gate already knows keeps its entry and
43
+ * its miss count.
44
+ */
45
+ seed(rows: readonly PersistedComposedRow[]): void;
46
+ filter(current: readonly DeviceDeclaration[]): readonly DeviceDeclaration[];
47
+ /**
48
+ * The live device's id, else the persisted row's. A declaration whose device
49
+ * was never built and never persisted has no numeric id to carry: the
50
+ * stableId in `meta` is then all there is.
51
+ */
52
+ private tagsOf;
53
+ }
@@ -0,0 +1,88 @@
1
+ import { CompositionFieldPlan, CompositionFieldState, ExpressionValue, IScopedLogger, SourceReader } from '@camstack/types';
2
+ export interface WriteOk {
3
+ readonly ok: true;
4
+ }
5
+ export interface WriteRefused {
6
+ readonly ok: false;
7
+ readonly error: string;
8
+ }
9
+ export type WriteOutcome = WriteOk | WriteRefused;
10
+ /**
11
+ * The composed device. A write answers `{ ok: false }` rather than throwing,
12
+ * and `setAvailable` does not throw. The runtime survives either anyway: a
13
+ * throwing write is a failed write, a throwing `setAvailable` is retried on
14
+ * the next pass.
15
+ */
16
+ export interface ComposedWriteTarget {
17
+ readonly id: number;
18
+ seed(capName: string, slice: Readonly<Record<string, ExpressionValue>>): WriteOutcome;
19
+ patch(capName: string, patch: Readonly<Record<string, ExpressionValue>>): WriteOutcome;
20
+ setAvailable(available: boolean): void;
21
+ }
22
+ export interface DeadlineTimer {
23
+ arm(at: number, fire: () => void): void;
24
+ disarm(): void;
25
+ }
26
+ export interface CompositionRuntimeDeps {
27
+ readonly blockId: string;
28
+ readonly fields: readonly CompositionFieldPlan[];
29
+ readonly target: ComposedWriteTarget;
30
+ readonly read: SourceReader;
31
+ readonly now: () => number;
32
+ readonly timer: DeadlineTimer;
33
+ readonly logger: IScopedLogger;
34
+ }
35
+ export interface CompositionRuntimeState {
36
+ readonly failure: string | null;
37
+ readonly available: boolean;
38
+ readonly fields: readonly CompositionFieldState[];
39
+ }
40
+ export declare class CompositionRuntime {
41
+ private readonly deps;
42
+ private records;
43
+ private seeded;
44
+ private available;
45
+ private failure;
46
+ private disabledReason;
47
+ private stopped;
48
+ /** The last `pass threw` logged (`during: message`); cleared by a pass that completes. */
49
+ private lastPassError;
50
+ private pending;
51
+ private heldBack;
52
+ constructor(deps: CompositionRuntimeDeps);
53
+ start(): void;
54
+ onSourcesChanged(sliceKeys: ReadonlySet<string>): void;
55
+ disable(reason: string): void;
56
+ /** Terminal: no write, no evaluation and no timer after this. */
57
+ stop(): void;
58
+ state(): CompositionRuntimeState;
59
+ /**
60
+ * Every entry point runs here: a no-op once stopped, nothing thrown escapes,
61
+ * and the one timer is re-armed from the stored deadlines whatever happened.
62
+ */
63
+ private guarded;
64
+ /** Once per distinct failure, not once per pass: a reader that keeps throwing logs one line. */
65
+ private logPassError;
66
+ private pass;
67
+ private evaluate;
68
+ private recordsOf;
69
+ /** `retry`: write-pending caps this pass re-writes WHOLE, whether or not a value changed. */
70
+ private writeCaps;
71
+ private seedCap;
72
+ private patchCap;
73
+ /** After a failed write nothing is known about what landed: send every current value. */
74
+ private patchWholeCap;
75
+ private writableEntries;
76
+ private holdBackSeed;
77
+ /** One write. A refusal or a throw marks the cap write-pending; true when it landed. */
78
+ private write;
79
+ private clearPending;
80
+ private markPending;
81
+ private markWritten;
82
+ private updateAvailability;
83
+ private updateFailure;
84
+ /** `after`: arm only deadlines later than this instant; null arms the earliest stored one. */
85
+ private armDeadline;
86
+ private onDeadline;
87
+ private logTransition;
88
+ }
@@ -0,0 +1,18 @@
1
+ import { DeadlineTimer } from './composition-runtime.js';
2
+ /** Node's largest `setTimeout` delay; anything longer is clamped to 1 ms (`TimeoutOverflowWarning`). */
3
+ export declare const MAX_TIMER_DELAY_MS = 2147483647;
4
+ /** One pending wake-up per composition, at its earliest expression deadline. Never a poll. */
5
+ export declare class NodeDeadlineTimer implements DeadlineTimer {
6
+ private readonly now;
7
+ private handle;
8
+ constructor(now: () => number);
9
+ arm(at: number, fire: () => void): void;
10
+ /**
11
+ * A deadline further away than the largest delay is reached in legs: the
12
+ * clamped wake-up is NOT the deadline, so it re-arms the remainder instead of
13
+ * firing — firing early would re-arm the same deadline and, unclamped, Node
14
+ * turns it into a 1 ms spin (D3).
15
+ */
16
+ private schedule;
17
+ disarm(): void;
18
+ }
@@ -0,0 +1 @@
1
+ export { ComposerAddon as default, ComposerAddon } from './composer.addon.js';
@@ -0,0 +1,8 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ require("../../chunk-Cek0wNdY.js");
6
+ const require_builtins_composer_composer_addon = require("./composer.addon.js");
7
+ exports.ComposerAddon = require_builtins_composer_composer_addon.ComposerAddon;
8
+ exports.default = require_builtins_composer_composer_addon.ComposerAddon;
@@ -0,0 +1,2 @@
1
+ import { ComposerAddon } from "./composer.addon.mjs";
2
+ export { ComposerAddon, ComposerAddon as default };
@@ -0,0 +1,78 @@
1
+ import { CompositionFieldRead, CompositionSourceRef, EventCatalog, IScopedLogger, SourceReading } from '@camstack/types';
2
+ export interface SourceRow {
3
+ readonly id: number;
4
+ readonly stableId: string;
5
+ }
6
+ export interface SourceRowsRead {
7
+ readonly kind: 'rows';
8
+ readonly rows: readonly SourceRow[];
9
+ }
10
+ export interface SourceUnreadable {
11
+ readonly kind: 'unreadable';
12
+ readonly error: string;
13
+ }
14
+ export type SourceListRead = SourceRowsRead | SourceUnreadable;
15
+ export interface SliceFound {
16
+ readonly kind: 'slice';
17
+ readonly slice: Readonly<Record<string, unknown>>;
18
+ }
19
+ export interface SliceNone {
20
+ readonly kind: 'none';
21
+ }
22
+ export type SliceRead = SliceFound | SliceNone | SourceUnreadable;
23
+ export interface BoundCaps {
24
+ readonly kind: 'caps';
25
+ readonly caps: ReadonlySet<string>;
26
+ }
27
+ export type BoundCapsRead = BoundCaps | SourceUnreadable;
28
+ export interface SourceTrackerPorts {
29
+ readonly logger: IScopedLogger;
30
+ /** `deviceManager.listAll({ addonId, projection: 'slim' })`: one call per owning addon per pass. */
31
+ readonly listDevicesOf: (addonId: string) => Promise<SourceListRead>;
32
+ readonly readSlice: (deviceId: number, capName: string) => Promise<SliceRead>;
33
+ readonly readBoundCaps: (deviceId: number) => Promise<BoundCapsRead>;
34
+ }
35
+ /** Why a `device.bindings-changed` fired — the catalog's own closed union. */
36
+ export type SourceBindingsChangeReason = EventCatalog['device.bindings-changed']['reason'];
37
+ export interface TrackedSourceRequest {
38
+ readonly ref: CompositionSourceRef;
39
+ readonly caps: readonly string[];
40
+ }
41
+ export declare class SourceTracker {
42
+ private readonly ports;
43
+ private sources;
44
+ /** The one chain every entry point joins (R3); the pattern is `BlockSupervisor.inFlight`. */
45
+ private chain;
46
+ constructor(ports: SourceTrackerPorts);
47
+ /**
48
+ * Track exactly `requests`. Hydrate what is new or unresolved; keep what is
49
+ * known. `reset` names source keys an OPERATOR action re-opens (a block edit
50
+ * or restart): only those lose a sticky `missing`.
51
+ */
52
+ sync(requests: readonly TrackedSourceRequest[], reset: ReadonlySet<string>): Promise<ReadonlySet<string>>;
53
+ onStateChanged(deviceId: number, capName: string, slice: Readonly<Record<string, unknown>>): Promise<ReadonlySet<string>>;
54
+ onDeviceUnregistered(deviceId: number): Promise<ReadonlySet<string>>;
55
+ onBindingsChanged(deviceId: number, capName: string, reason: SourceBindingsChangeReason): Promise<ReadonlySet<string>>;
56
+ /** Run `work` after everything already queued, whether that settled or failed; never let one failure stall the chain. */
57
+ private serialized;
58
+ private syncNow;
59
+ read(read: CompositionFieldRead): SourceReading;
60
+ private applyStateChanged;
61
+ private confirmUnregistered;
62
+ private applyBindingsChanged;
63
+ /** A source found under a NEW id is a re-registration: read it there now (R7c). */
64
+ private rereadUnderNewId;
65
+ private hydrate;
66
+ /** Read `toRead` (bindings first, then slices); every other requested cap keeps what `known` holds. */
67
+ private readCaps;
68
+ private resolveConfirmed;
69
+ /**
70
+ * One bindings read. A second one would not confirm anything: back to back
71
+ * they are one correlated read, and "unbound" no longer destroys work — it is
72
+ * a non-fatal `none` that `native-registered` heals.
73
+ */
74
+ private readBindings;
75
+ private readCap;
76
+ private logHealed;
77
+ private readOnline;
78
+ }
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-D3ch99WD.js");
6
+ const require_dist = require("../../dist-BhO9eYKO.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/console-logging/console-destination.ts
9
9
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { gn as BaseAddon, jt as logDestinationCapability } from "../../dist-DdfJa8Zn.mjs";
1
+ import { Ut as logDestinationCapability, jn as BaseAddon } from "../../dist-BEuQPDk_.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/console-logging/console-destination.ts
4
4
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { CoreBlock, IScopedLogger } from '@camstack/types';
1
+ import { ComposerApplyInput, ComposerApplyResult, ComposerUnreadableBlock, CoreBlock, IScopedLogger } from '@camstack/types';
2
2
  /** The runner lifecycle states `$process.list` reports. `stopping`/`stopped`
3
3
  * are transient states of a runner on its way out and are treated as absent. */
4
4
  export type BlockRunnerState = 'starting' | 'running' | 'stopping' | 'stopped' | 'crashed' | 'failed';
@@ -28,6 +28,10 @@ export interface BlockCompileOutcome {
28
28
  readonly error?: string;
29
29
  readonly js?: string;
30
30
  }
31
+ /** The composer's one verb, as the supervisor needs it. */
32
+ export interface ComposerPort {
33
+ apply(input: ComposerApplyInput): Promise<ComposerApplyResult>;
34
+ }
31
35
  export interface BlockSupervisorDeps {
32
36
  /** This node's id, already stripped of any `/<addon>` runner suffix. */
33
37
  readonly nodeId: string;
@@ -37,11 +41,21 @@ export interface BlockSupervisorDeps {
37
41
  readonly host: BlockProcessHost;
38
42
  readonly compile: (code: string) => Promise<BlockCompileOutcome>;
39
43
  readonly logger: IScopedLogger;
44
+ /** Null when this node exposes no composer, and then every composition says so. */
45
+ readonly composer: ComposerPort | null;
46
+ /** The shared Blocks integration, null until it has reconciled. */
47
+ readonly integrationId: () => string | null;
48
+ /** False while the `core_blocks` read has not succeeded — the composer then withdraws nothing (D49). */
49
+ readonly storeLoaded: () => boolean;
50
+ /** Re-read `core_blocks`. Called by a converge while `storeLoaded()` is false — the converge IS the retry (D3). Never throws. */
51
+ readonly loadStore: () => Promise<void>;
40
52
  }
41
53
  /** The slice of `CoreBlockStore` the supervisor uses. */
42
54
  export interface BlockStoreView {
43
55
  list(): readonly CoreBlock[];
44
56
  get(blockId: string): CoreBlock | null;
57
+ /** Stored composition rows that do not parse: sent to the composer so their devices are held (D49). */
58
+ unreadableCompositions(): readonly ComposerUnreadableBlock[];
45
59
  setStatus(blockId: string, status: CoreBlock['status'], error?: string): Promise<void>;
46
60
  }
47
61
  export declare class BlockSupervisor {
@@ -57,6 +71,16 @@ export declare class BlockSupervisor {
57
71
  /** Serialises passes so a timer tick cannot interleave with a write-driven
58
72
  * converge and spawn the same runner twice. */
59
73
  private inFlight;
74
+ /** Composition restarts not yet delivered to the composer. Stores nothing durable (D62). */
75
+ private pendingCompositionRestarts;
76
+ /**
77
+ * Empty applies still owed: the composer withdraws only after TWO agree
78
+ * (D49), and a boot sends one. Only an apply the composer says it ACTED on
79
+ * (`result.acted`) pays one off — it can refuse with the store loaded.
80
+ */
81
+ private emptyAppliesOwed;
82
+ /** Consecutive applies that could not reach the composer: warned once per run. */
83
+ private failedApplies;
60
84
  constructor(deps: BlockSupervisorDeps);
61
85
  /** Start the reconcile timer. The first pass runs immediately. */
62
86
  start(intervalMs: number): void;
@@ -82,6 +106,13 @@ export declare class BlockSupervisor {
82
106
  private runRestart;
83
107
  private runPass;
84
108
  private reconcile;
109
+ /**
110
+ * Send every composition placed here to the composer and mirror its verdicts
111
+ * into the block status, reason verbatim. A `starting` verdict (the composer
112
+ * does not trust its store or seed yet) is mirrored as `starting`; the next
113
+ * pass of this same converge is the retry — no timer of its own (D3).
114
+ */
115
+ private convergeCompositions;
85
116
  /** Compile, write the package, spawn. Each failure is reported ON the block
86
117
  * — the author asked for this to run, so they are owed the reason it does
87
118
  * not. */
@@ -20,6 +20,11 @@ export type BlocksIntegrationPorts = Omit<DeclaredDevicePorts, 'addonId'>;
20
20
  * with every device on the cluster, and the sweep would delete the blocks'), or
21
21
  * make `core-blocks` declare a device of its own. Neither is worth the
22
22
  * convenience; the spec test pins both.
23
+ *
24
+ * A SECOND owner hangs devices here since D659: `composer` (every composed
25
+ * device). It is equally safe for the same reason — its rows are owned by
26
+ * `composer`, never by `core-blocks`, and its own withdrawal sweep is scoped to
27
+ * `composer`.
23
28
  */
24
29
  export declare function reconcileBlocksIntegration(ports: BlocksIntegrationPorts): Promise<string | null>;
25
30
  /** What {@link armBlocksIntegration} needs, and the seam its spec drives. */
@@ -0,0 +1,25 @@
1
+ import { CapabilityLookup, CompositionBlockInput, CompositionBlockPatch, CompositionBlockState, CompositionSourceDescription, CompositionSourceRef, CoreBlock, ICoreBlocksProvider, IScopedLogger } from '@camstack/types';
2
+ export interface CompositionStoreView {
3
+ /** False until the table has been READ; a verdict against an empty peer set would hide a cycle. */
4
+ isLoaded(): boolean;
5
+ list(): readonly CoreBlock[];
6
+ get(blockId: string): CoreBlock | null;
7
+ createComposition(input: CompositionBlockInput, createdBy: string): Promise<CoreBlock>;
8
+ updateComposition(blockId: string, patch: CompositionBlockPatch): Promise<CoreBlock>;
9
+ }
10
+ export interface CompositionApiDeps {
11
+ readonly store: CompositionStoreView;
12
+ readonly describeSources: (refs: readonly CompositionSourceRef[]) => Promise<ReadonlyMap<string, CompositionSourceDescription>>;
13
+ readonly lookupCap: CapabilityLookup;
14
+ readonly composerStates: (blockIds: readonly string[]) => Promise<readonly CompositionBlockState[]>;
15
+ readonly convergeSoon: () => void;
16
+ readonly logger: IScopedLogger;
17
+ }
18
+ export type CompositionApi = Pick<ICoreBlocksProvider, 'createComposition' | 'updateComposition' | 'getComposition' | 'listCompositions' | 'validateComposition'>;
19
+ /**
20
+ * Every write runs validate-then-persist on ONE chain. Validation awaits a
21
+ * source read before the save lands; two unserialized saves (A reads B, B
22
+ * reads A) would each be judged against a store without the other and BOTH
23
+ * pass — a stored cycle nothing downstream catches.
24
+ */
25
+ export declare function buildCompositionApi(deps: CompositionApiDeps): CompositionApi;
@@ -0,0 +1,10 @@
1
+ import { CompositionSourceDescription, CompositionSourceRef } from '@camstack/types';
2
+ export interface SourceRowView {
3
+ readonly id: number;
4
+ readonly stableId: string;
5
+ }
6
+ export interface CompositionSourcePorts {
7
+ readonly listByAddon: (addonId: string) => Promise<readonly SourceRowView[]>;
8
+ readonly boundCaps: (deviceId: number) => Promise<readonly string[]>;
9
+ }
10
+ export declare function describeCompositionSources(ports: CompositionSourcePorts, refs: readonly CompositionSourceRef[]): Promise<ReadonlyMap<string, CompositionSourceDescription>>;
@@ -1,4 +1,4 @@
1
- import { CoreBlock, CoreBlockInput, IScopedLogger, SettingsStoreClient } from '@camstack/types';
1
+ import { ComposerUnreadableBlock, CompositionBlockInput, CompositionBlockPatch, CoreBlock, CoreBlockInput, IScopedLogger, SettingsStoreClient } from '@camstack/types';
2
2
  /**
3
3
  * @durable class=config owner=core-blocks
4
4
  * write="`create` and `update` persist the operator's block after it compiles;
@@ -41,6 +41,12 @@ export declare const CORE_BLOCKS_INDEXES: readonly [{
41
41
  readonly name: "idx_core_blocks_enabled";
42
42
  readonly columns: readonly ["enabled"];
43
43
  }];
44
+ /**
45
+ * Rows one read asks for. A read that ANSWERS this many is not a loaded table:
46
+ * rows past the limit would be absent, and an absent composition reads as a
47
+ * deleted one (D49).
48
+ */
49
+ export declare const CORE_BLOCKS_LOAD_LIMIT = 1000;
44
50
  export interface CoreBlockStoreDeps {
45
51
  readonly store: SettingsStoreClient;
46
52
  readonly logger: IScopedLogger;
@@ -49,18 +55,32 @@ export interface CoreBlockStoreDeps {
49
55
  }
50
56
  export declare class CoreBlockStore {
51
57
  private readonly byId;
58
+ /** Rows that exist but do not parse and may be compositions — held for the composer, never dropped (D49). */
59
+ private unreadable;
52
60
  private readonly store;
53
61
  private readonly logger;
54
62
  private readonly now;
55
63
  private readonly newId;
64
+ private loaded;
65
+ /** Consecutive failed reads — the warn is once per run of them, not once per retry. */
66
+ private failedLoads;
56
67
  constructor(deps: CoreBlockStoreDeps);
57
68
  static declare(store: SettingsStoreClient): Promise<void>;
69
+ /** True only after a `load()` that READ the table. The composer withdraws nothing otherwise (D49). */
70
+ isLoaded(): boolean;
71
+ /** Stored composition rows that do not parse, by id — the composer holds their devices. */
72
+ unreadableCompositions(): readonly ComposerUnreadableBlock[];
58
73
  /**
59
74
  * Hydrate every block.
60
75
  *
61
76
  * A row whose JSON no longer validates is SKIPPED with a warning rather than
62
77
  * dropped or repaired: it is somebody's code, and the honest answer to "I
63
- * cannot read this" is to leave it on disk and say so.
78
+ * cannot read this" is to leave it on disk and say so. A skipped row that may
79
+ * be a COMPOSITION is also kept in `unreadableCompositions()`: absent from the
80
+ * composer's input it would read as deleted, and its device would go (D49).
81
+ *
82
+ * `source` is parsed here, and a build older than D659 strips `source` and
83
+ * rewrites the row — rolling back below D659 erases compositions.
64
84
  */
65
85
  load(): Promise<void>;
66
86
  list(): readonly CoreBlock[];
@@ -70,6 +90,13 @@ export declare class CoreBlockStore {
70
90
  create(input: CoreBlockInput, createdBy: string): Promise<CoreBlock>;
71
91
  /** Apply a partial patch. Immutable: returns the NEW block. */
72
92
  update(blockId: string, patch: Partial<CoreBlockInput>): Promise<CoreBlock>;
93
+ /**
94
+ * Store a composition block (D659): hub-placed, no code, never `running` on
95
+ * creation — the composer's verdict, mirrored by the supervisor, says that.
96
+ */
97
+ createComposition(input: CompositionBlockInput, createdBy: string): Promise<CoreBlock>;
98
+ /** Patch a composition block. Immutable: returns the NEW block. Refuses a code block by name. */
99
+ updateComposition(blockId: string, patch: CompositionBlockPatch): Promise<CoreBlock>;
73
100
  /**
74
101
  * Record what the runner did with this block.
75
102
  *
@@ -78,6 +105,11 @@ export declare class CoreBlockStore {
78
105
  * a block is running because somebody renamed it.
79
106
  */
80
107
  setStatus(blockId: string, status: CoreBlock['status'], error?: string): Promise<void>;
108
+ /**
109
+ * A write before the table has been read would be wiped from memory by the
110
+ * load that follows (it clears and refills), so it is refused by name.
111
+ */
112
+ private requireLoaded;
81
113
  /** Idempotent delete — an unknown id is a no-op. */
82
114
  delete(blockId: string): Promise<void>;
83
115
  private persist;
@@ -55,5 +55,11 @@ export declare class CoreBlocksAddon extends BaseAddon {
55
55
  */
56
56
  private gate;
57
57
  private buildProvider;
58
+ /**
59
+ * The composition methods (D659), flat. Sources are read over `ctx.api` at
60
+ * SAVE time — one listing per owning addon, one binding read per found
61
+ * device — and a read that fails refuses the save rather than skipping it.
62
+ */
63
+ private buildCompositionApi;
58
64
  }
59
65
  export type { CoreBlock };