@medicine-wheel/infra 0.5.7 → 0.5.8
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/README.md +92 -15
- package/dist/index.d.ts +14 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -5
- package/dist/index.js.map +1 -1
- package/dist/preconditions.d.ts +198 -0
- package/dist/preconditions.d.ts.map +1 -0
- package/dist/preconditions.js +181 -0
- package/dist/preconditions.js.map +1 -0
- package/dist/reconcile.d.ts +111 -0
- package/dist/reconcile.d.ts.map +1 -0
- package/dist/reconcile.js +130 -0
- package/dist/reconcile.js.map +1 -0
- package/dist/schemas.d.ts +406 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +71 -2
- package/dist/schemas.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconciliation — level-triggered, so `satisfied` cannot age into a lie.
|
|
3
|
+
*
|
|
4
|
+
* The outage this answers (jgwill/gaia#74) was not caused by a missing fact. It
|
|
5
|
+
* was caused by a fact that had been true. Something was recorded as running,
|
|
6
|
+
* the recording was never re-read, and every downstream decision was made
|
|
7
|
+
* against a snapshot.
|
|
8
|
+
*
|
|
9
|
+
* **Level-triggered** is the whole design: `reconcile()` compares the current
|
|
10
|
+
* declared set against the current observed set, every time, and remembers no
|
|
11
|
+
* transitions. There is no "it was converged a minute ago" state to go stale,
|
|
12
|
+
* because there is no state at all — only the two levels and the comparison.
|
|
13
|
+
*
|
|
14
|
+
* Four drift states, and the two asymmetric ones carry the most information:
|
|
15
|
+
*
|
|
16
|
+
* - `converged` — declared and observed agree on every compared field.
|
|
17
|
+
* - `drifted` — both sides exist and disagree. The differences are named.
|
|
18
|
+
* - `unrealized` — declared, not observed. Written down and not running.
|
|
19
|
+
* - `undeclared` — observed, not declared. **Running and nobody wrote it down**
|
|
20
|
+
* — the state that finds the service somebody started by hand
|
|
21
|
+
* at 2am, which is exactly the service holding the port the
|
|
22
|
+
* next tenant is about to be given.
|
|
23
|
+
*
|
|
24
|
+
* A pure function over plain data. No I/O, no clock: `now` is passed in, so two
|
|
25
|
+
* runs over the same input are identical.
|
|
26
|
+
*
|
|
27
|
+
* Spec: /opt/eury/rispecs/foundations/02-specifications.md §S6.
|
|
28
|
+
*/
|
|
29
|
+
import type { MetisHold, NodeId, PortConflict, ServiceFacet, TenantFacet } from './types';
|
|
30
|
+
export type DriftState = 'converged' | 'drifted' | 'unrealized' | 'undeclared';
|
|
31
|
+
/** Every drift state, in the order a report should read them: agreement, then disagreement, then the two absences. */
|
|
32
|
+
export declare const DRIFT_STATES: readonly DriftState[];
|
|
33
|
+
/**
|
|
34
|
+
* A reading of a host, taken at a moment, by someone.
|
|
35
|
+
*
|
|
36
|
+
* `observedBy` and `observedAt` are required rather than optional. An
|
|
37
|
+
* observation with no reader and no time is a rumour, and reconciling against a
|
|
38
|
+
* rumour is how the snapshot problem comes back wearing a different hat.
|
|
39
|
+
*/
|
|
40
|
+
export interface ObservedState {
|
|
41
|
+
/** → `HostFacet.nodeId` — the host this reading was taken on */
|
|
42
|
+
observedBy: NodeId;
|
|
43
|
+
/** ISO 8601 */
|
|
44
|
+
observedAt: string;
|
|
45
|
+
services: ServiceFacet[];
|
|
46
|
+
tenants?: TenantFacet[];
|
|
47
|
+
}
|
|
48
|
+
export interface FieldDifference {
|
|
49
|
+
field: string;
|
|
50
|
+
declared?: unknown;
|
|
51
|
+
observed?: unknown;
|
|
52
|
+
}
|
|
53
|
+
export interface DriftRow {
|
|
54
|
+
/** → `ServiceFacet.nodeId` */
|
|
55
|
+
nodeId: NodeId;
|
|
56
|
+
/** the unit name from whichever side has it, so a row is readable without a join */
|
|
57
|
+
unit: string;
|
|
58
|
+
state: DriftState;
|
|
59
|
+
/** present only when `state` is `drifted` */
|
|
60
|
+
differences?: FieldDifference[];
|
|
61
|
+
/**
|
|
62
|
+
* Métis from **both** sides, carried through untouched.
|
|
63
|
+
*
|
|
64
|
+
* An array, not a single hold, and not a merge. Two sides holding different
|
|
65
|
+
* tacit notes is not drift — it is two people knowing different things. Keeping
|
|
66
|
+
* only the declared side would delete the observing agent's note; merging the
|
|
67
|
+
* two into one `MetisHold` would concatenate two people into one `heldBy`
|
|
68
|
+
* string and lose attribution. Both are the same erasure in different shapes,
|
|
69
|
+
* and this field exists to refuse it.
|
|
70
|
+
*
|
|
71
|
+
* Ordered declared-first, so a reader sees the recorded knowledge before the
|
|
72
|
+
* observed knowledge.
|
|
73
|
+
*/
|
|
74
|
+
metis?: MetisHold[];
|
|
75
|
+
}
|
|
76
|
+
export interface ReconcileResult {
|
|
77
|
+
observedBy: NodeId;
|
|
78
|
+
observedAt: string;
|
|
79
|
+
/**
|
|
80
|
+
* Milliseconds between `observedAt` and `now`, when `now` was given.
|
|
81
|
+
*
|
|
82
|
+
* Exposed so a caller can refuse to act on an old reading. Negative when the
|
|
83
|
+
* observation is stamped in the future — surfaced rather than clamped, because
|
|
84
|
+
* a future timestamp means clock skew and that is worth seeing.
|
|
85
|
+
*/
|
|
86
|
+
observationAgeMs?: number;
|
|
87
|
+
rows: DriftRow[];
|
|
88
|
+
/** counts per drift state; every state present, including the zeroes */
|
|
89
|
+
summary: Record<DriftState, number>;
|
|
90
|
+
/**
|
|
91
|
+
* Port conflicts over declared ∪ observed — the §S3 traversal folded in here
|
|
92
|
+
* because this is the one place both sides are in hand at once.
|
|
93
|
+
*/
|
|
94
|
+
conflicts: PortConflict[];
|
|
95
|
+
/** true when nothing drifted, nothing is unrealized, nothing is undeclared, and no port collides */
|
|
96
|
+
converged: boolean;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Compare what was declared against what was observed, right now.
|
|
100
|
+
*
|
|
101
|
+
* Rows are keyed by `nodeId` and sorted by it, so two runs over the same input
|
|
102
|
+
* are byte-identical and a caller can diff them.
|
|
103
|
+
*
|
|
104
|
+
* @param declared desired state, authored by eury
|
|
105
|
+
* @param observed a reading taken on a host by gaia, with its provenance
|
|
106
|
+
* @param opts.now ISO 8601 — supplied by the caller so this stays clock-free
|
|
107
|
+
*/
|
|
108
|
+
export declare function reconcile(declared: ServiceFacet[], observed: ObservedState, opts?: {
|
|
109
|
+
now?: string;
|
|
110
|
+
}): ReconcileResult;
|
|
111
|
+
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAe,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEvG,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,YAAY,CAAC;AAE/E,sHAAsH;AACtH,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAAyD,CAAC;AAExG;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACpC;;;OAGG;IACH,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,oGAAoG;IACpG,SAAS,EAAE,OAAO,CAAC;CACpB;AA2CD;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,YAAY,EAAE,EACxB,QAAQ,EAAE,aAAa,EACvB,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1B,eAAe,CA+DjB"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Reconciliation — level-triggered, so `satisfied` cannot age into a lie.
|
|
4
|
+
*
|
|
5
|
+
* The outage this answers (jgwill/gaia#74) was not caused by a missing fact. It
|
|
6
|
+
* was caused by a fact that had been true. Something was recorded as running,
|
|
7
|
+
* the recording was never re-read, and every downstream decision was made
|
|
8
|
+
* against a snapshot.
|
|
9
|
+
*
|
|
10
|
+
* **Level-triggered** is the whole design: `reconcile()` compares the current
|
|
11
|
+
* declared set against the current observed set, every time, and remembers no
|
|
12
|
+
* transitions. There is no "it was converged a minute ago" state to go stale,
|
|
13
|
+
* because there is no state at all — only the two levels and the comparison.
|
|
14
|
+
*
|
|
15
|
+
* Four drift states, and the two asymmetric ones carry the most information:
|
|
16
|
+
*
|
|
17
|
+
* - `converged` — declared and observed agree on every compared field.
|
|
18
|
+
* - `drifted` — both sides exist and disagree. The differences are named.
|
|
19
|
+
* - `unrealized` — declared, not observed. Written down and not running.
|
|
20
|
+
* - `undeclared` — observed, not declared. **Running and nobody wrote it down**
|
|
21
|
+
* — the state that finds the service somebody started by hand
|
|
22
|
+
* at 2am, which is exactly the service holding the port the
|
|
23
|
+
* next tenant is about to be given.
|
|
24
|
+
*
|
|
25
|
+
* A pure function over plain data. No I/O, no clock: `now` is passed in, so two
|
|
26
|
+
* runs over the same input are identical.
|
|
27
|
+
*
|
|
28
|
+
* Spec: /opt/eury/rispecs/foundations/02-specifications.md §S6.
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.DRIFT_STATES = void 0;
|
|
32
|
+
exports.reconcile = reconcile;
|
|
33
|
+
const ports_1 = require("./ports");
|
|
34
|
+
/** Every drift state, in the order a report should read them: agreement, then disagreement, then the two absences. */
|
|
35
|
+
exports.DRIFT_STATES = ['converged', 'drifted', 'unrealized', 'undeclared'];
|
|
36
|
+
/** Fields compared between a declared and an observed service. `metis` is deliberately absent. */
|
|
37
|
+
const COMPARED_FIELDS = [
|
|
38
|
+
'unit',
|
|
39
|
+
'scope',
|
|
40
|
+
'ownedBy',
|
|
41
|
+
'workingDirectory',
|
|
42
|
+
'execStop',
|
|
43
|
+
];
|
|
44
|
+
/** `host|proto|port` — the identity of a claim, matching the slot key `detectPortConflicts` uses. */
|
|
45
|
+
function portKey(binding) {
|
|
46
|
+
return `${binding.host} ${binding.proto ?? 'tcp'} ${binding.port}`;
|
|
47
|
+
}
|
|
48
|
+
/** Ports compared as sets of claims, so ordering differences between systemd and a manifest are not drift. */
|
|
49
|
+
function portsDiffer(declared, observed) {
|
|
50
|
+
const a = [...new Set(declared.map(portKey))].sort();
|
|
51
|
+
const b = [...new Set(observed.map(portKey))].sort();
|
|
52
|
+
return a.length !== b.length || a.some((k, i) => k !== b[i]);
|
|
53
|
+
}
|
|
54
|
+
function diffService(declared, observed) {
|
|
55
|
+
const differences = [];
|
|
56
|
+
for (const field of COMPARED_FIELDS) {
|
|
57
|
+
if (declared[field] !== observed[field]) {
|
|
58
|
+
differences.push({ field, declared: declared[field], observed: observed[field] });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (portsDiffer(declared.ports, observed.ports)) {
|
|
62
|
+
differences.push({
|
|
63
|
+
field: 'ports',
|
|
64
|
+
declared: [...declared.ports].map(portKey).sort(),
|
|
65
|
+
observed: [...observed.ports].map(portKey).sort(),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return differences;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Compare what was declared against what was observed, right now.
|
|
72
|
+
*
|
|
73
|
+
* Rows are keyed by `nodeId` and sorted by it, so two runs over the same input
|
|
74
|
+
* are byte-identical and a caller can diff them.
|
|
75
|
+
*
|
|
76
|
+
* @param declared desired state, authored by eury
|
|
77
|
+
* @param observed a reading taken on a host by gaia, with its provenance
|
|
78
|
+
* @param opts.now ISO 8601 — supplied by the caller so this stays clock-free
|
|
79
|
+
*/
|
|
80
|
+
function reconcile(declared, observed, opts = {}) {
|
|
81
|
+
const declaredById = new Map(declared.map((s) => [s.nodeId, s]));
|
|
82
|
+
const observedById = new Map(observed.services.map((s) => [s.nodeId, s]));
|
|
83
|
+
const rows = [];
|
|
84
|
+
/** Both sides' holds, declared first, with the empty case left undefined. */
|
|
85
|
+
const heldBy = (...sides) => {
|
|
86
|
+
const held = sides.filter((m) => m !== undefined);
|
|
87
|
+
return held.length > 0 ? held : undefined;
|
|
88
|
+
};
|
|
89
|
+
for (const [nodeId, d] of declaredById) {
|
|
90
|
+
const o = observedById.get(nodeId);
|
|
91
|
+
if (!o) {
|
|
92
|
+
rows.push({ nodeId, unit: d.unit, state: 'unrealized', metis: heldBy(d.metis) });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const differences = diffService(d, o);
|
|
96
|
+
const metis = heldBy(d.metis, o.metis);
|
|
97
|
+
rows.push(differences.length === 0
|
|
98
|
+
? { nodeId, unit: d.unit, state: 'converged', metis }
|
|
99
|
+
: { nodeId, unit: d.unit, state: 'drifted', differences, metis });
|
|
100
|
+
}
|
|
101
|
+
for (const [nodeId, o] of observedById) {
|
|
102
|
+
if (declaredById.has(nodeId))
|
|
103
|
+
continue;
|
|
104
|
+
rows.push({ nodeId, unit: o.unit, state: 'undeclared', metis: heldBy(o.metis) });
|
|
105
|
+
}
|
|
106
|
+
rows.sort((a, b) => a.nodeId.localeCompare(b.nodeId));
|
|
107
|
+
const summary = exports.DRIFT_STATES.reduce((acc, state) => ({ ...acc, [state]: rows.filter((r) => r.state === state).length }), {});
|
|
108
|
+
const conflicts = (0, ports_1.detectPortConflicts)([
|
|
109
|
+
...declared.flatMap((s) => s.ports),
|
|
110
|
+
...observed.services.flatMap((s) => s.ports),
|
|
111
|
+
]);
|
|
112
|
+
const result = {
|
|
113
|
+
observedBy: observed.observedBy,
|
|
114
|
+
observedAt: observed.observedAt,
|
|
115
|
+
rows,
|
|
116
|
+
summary,
|
|
117
|
+
conflicts,
|
|
118
|
+
converged: summary.drifted === 0 &&
|
|
119
|
+
summary.unrealized === 0 &&
|
|
120
|
+
summary.undeclared === 0 &&
|
|
121
|
+
conflicts.length === 0,
|
|
122
|
+
};
|
|
123
|
+
if (opts.now) {
|
|
124
|
+
const age = Date.parse(opts.now) - Date.parse(observed.observedAt);
|
|
125
|
+
if (!Number.isNaN(age))
|
|
126
|
+
result.observationAgeMs = age;
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=reconcile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"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"}
|