@amalgm/live 0.1.0
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/PURPOSE.md +103 -0
- package/README.md +45 -0
- package/dist/contracts/binding.d.ts +49 -0
- package/dist/contracts/binding.d.ts.map +1 -0
- package/dist/contracts/binding.js +23 -0
- package/dist/contracts/binding.js.map +1 -0
- package/dist/contracts/content.d.ts +42 -0
- package/dist/contracts/content.d.ts.map +1 -0
- package/dist/contracts/content.js +72 -0
- package/dist/contracts/content.js.map +1 -0
- package/dist/contracts/docs.d.ts +64 -0
- package/dist/contracts/docs.d.ts.map +1 -0
- package/dist/contracts/docs.js +51 -0
- package/dist/contracts/docs.js.map +1 -0
- package/dist/contracts/events.d.ts +45 -0
- package/dist/contracts/events.d.ts.map +1 -0
- package/dist/contracts/events.js +38 -0
- package/dist/contracts/events.js.map +1 -0
- package/dist/contracts/frames/gateway.d.ts +83 -0
- package/dist/contracts/frames/gateway.d.ts.map +1 -0
- package/dist/contracts/frames/gateway.js +35 -0
- package/dist/contracts/frames/gateway.js.map +1 -0
- package/dist/contracts/frames/stream.d.ts +59 -0
- package/dist/contracts/frames/stream.d.ts.map +1 -0
- package/dist/contracts/frames/stream.js +17 -0
- package/dist/contracts/frames/stream.js.map +1 -0
- package/dist/contracts/frames/tunnel.d.ts +78 -0
- package/dist/contracts/frames/tunnel.d.ts.map +1 -0
- package/dist/contracts/frames/tunnel.js +55 -0
- package/dist/contracts/frames/tunnel.js.map +1 -0
- package/dist/contracts/index.d.ts +19 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +19 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/mutation.d.ts +51 -0
- package/dist/contracts/mutation.d.ts.map +1 -0
- package/dist/contracts/mutation.js +55 -0
- package/dist/contracts/mutation.js.map +1 -0
- package/dist/contracts/presence.d.ts +46 -0
- package/dist/contracts/presence.d.ts.map +1 -0
- package/dist/contracts/presence.js +34 -0
- package/dist/contracts/presence.js.map +1 -0
- package/dist/contracts/resources.d.ts +52 -0
- package/dist/contracts/resources.d.ts.map +1 -0
- package/dist/contracts/resources.js +105 -0
- package/dist/contracts/resources.js.map +1 -0
- package/dist/contracts/snapshot.d.ts +24 -0
- package/dist/contracts/snapshot.d.ts.map +1 -0
- package/dist/contracts/snapshot.js +28 -0
- package/dist/contracts/snapshot.js.map +1 -0
- package/dist/contracts/validate.d.ts +32 -0
- package/dist/contracts/validate.d.ts.map +1 -0
- package/dist/contracts/validate.js +25 -0
- package/dist/contracts/validate.js.map +1 -0
- package/dist/detection/adapter-contract.d.ts +134 -0
- package/dist/detection/adapter-contract.d.ts.map +1 -0
- package/dist/detection/adapter-contract.js +162 -0
- package/dist/detection/adapter-contract.js.map +1 -0
- package/dist/detection/classify.d.ts +31 -0
- package/dist/detection/classify.d.ts.map +1 -0
- package/dist/detection/classify.js +28 -0
- package/dist/detection/classify.js.map +1 -0
- package/dist/detection/continuity.d.ts +54 -0
- package/dist/detection/continuity.d.ts.map +1 -0
- package/dist/detection/continuity.js +85 -0
- package/dist/detection/continuity.js.map +1 -0
- package/dist/detection/coverage.d.ts +43 -0
- package/dist/detection/coverage.d.ts.map +1 -0
- package/dist/detection/coverage.js +60 -0
- package/dist/detection/coverage.js.map +1 -0
- package/dist/detection/diff.d.ts +144 -0
- package/dist/detection/diff.d.ts.map +1 -0
- package/dist/detection/diff.js +187 -0
- package/dist/detection/diff.js.map +1 -0
- package/dist/detection/enrollment.d.ts +16 -0
- package/dist/detection/enrollment.d.ts.map +1 -0
- package/dist/detection/enrollment.js +70 -0
- package/dist/detection/enrollment.js.map +1 -0
- package/dist/detection/index.d.ts +29 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +29 -0
- package/dist/detection/index.js.map +1 -0
- package/dist/detection/refusal.d.ts +33 -0
- package/dist/detection/refusal.d.ts.map +1 -0
- package/dist/detection/refusal.js +40 -0
- package/dist/detection/refusal.js.map +1 -0
- package/dist/entities/bytes.d.ts +18 -0
- package/dist/entities/bytes.d.ts.map +1 -0
- package/dist/entities/bytes.js +61 -0
- package/dist/entities/bytes.js.map +1 -0
- package/dist/entities/cloud.d.ts +82 -0
- package/dist/entities/cloud.d.ts.map +1 -0
- package/dist/entities/cloud.js +136 -0
- package/dist/entities/cloud.js.map +1 -0
- package/dist/entities/index.d.ts +23 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +23 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/moves.d.ts +42 -0
- package/dist/entities/moves.d.ts.map +1 -0
- package/dist/entities/moves.js +82 -0
- package/dist/entities/moves.js.map +1 -0
- package/dist/entities/repo-states.d.ts +121 -0
- package/dist/entities/repo-states.d.ts.map +1 -0
- package/dist/entities/repo-states.js +127 -0
- package/dist/entities/repo-states.js.map +1 -0
- package/dist/entities/rules.d.ts +104 -0
- package/dist/entities/rules.d.ts.map +1 -0
- package/dist/entities/rules.js +200 -0
- package/dist/entities/rules.js.map +1 -0
- package/dist/entities/types.d.ts +94 -0
- package/dist/entities/types.d.ts.map +1 -0
- package/dist/entities/types.js +40 -0
- package/dist/entities/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/machines/arbitration.d.ts +36 -0
- package/dist/machines/arbitration.d.ts.map +1 -0
- package/dist/machines/arbitration.js +37 -0
- package/dist/machines/arbitration.js.map +1 -0
- package/dist/machines/backoff.d.ts +36 -0
- package/dist/machines/backoff.d.ts.map +1 -0
- package/dist/machines/backoff.js +34 -0
- package/dist/machines/backoff.js.map +1 -0
- package/dist/machines/bootstrap.d.ts +65 -0
- package/dist/machines/bootstrap.d.ts.map +1 -0
- package/dist/machines/bootstrap.js +105 -0
- package/dist/machines/bootstrap.js.map +1 -0
- package/dist/machines/chunks.d.ts +51 -0
- package/dist/machines/chunks.d.ts.map +1 -0
- package/dist/machines/chunks.js +67 -0
- package/dist/machines/chunks.js.map +1 -0
- package/dist/machines/index.d.ts +14 -0
- package/dist/machines/index.d.ts.map +1 -0
- package/dist/machines/index.js +14 -0
- package/dist/machines/index.js.map +1 -0
- package/dist/machines/journal.d.ts +63 -0
- package/dist/machines/journal.d.ts.map +1 -0
- package/dist/machines/journal.js +71 -0
- package/dist/machines/journal.js.map +1 -0
- package/dist/machines/ordering.d.ts +53 -0
- package/dist/machines/ordering.d.ts.map +1 -0
- package/dist/machines/ordering.js +87 -0
- package/dist/machines/ordering.js.map +1 -0
- package/dist/machines/presence.d.ts +26 -0
- package/dist/machines/presence.d.ts.map +1 -0
- package/dist/machines/presence.js +49 -0
- package/dist/machines/presence.js.map +1 -0
- package/dist/merge/index.d.ts +9 -0
- package/dist/merge/index.d.ts.map +1 -0
- package/dist/merge/index.js +9 -0
- package/dist/merge/index.js.map +1 -0
- package/dist/merge/merge3.d.ts +39 -0
- package/dist/merge/merge3.d.ts.map +1 -0
- package/dist/merge/merge3.js +188 -0
- package/dist/merge/merge3.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The registry's pure rule surface — the laws of the entity model (engine
|
|
3
|
+
* docs/entity-model.md) with every store lookup behind the injected
|
|
4
|
+
* {@link EntityReadPort} and every hash behind {@link Sha256Hex}:
|
|
5
|
+
*
|
|
6
|
+
* - the ratified version formula: version = hash(type, parentUUID, name,
|
|
7
|
+
* status, adapterPayloadVersion) — DERIVED, never stored;
|
|
8
|
+
* - container payloads as sorted active direct membership;
|
|
9
|
+
* - lifecycle inheritance DOWN the ancestor chain as derived effective
|
|
10
|
+
* status (deleted > trashed > own stored status);
|
|
11
|
+
* - one active owner per (parentUUID, name) slot; only containers hold
|
|
12
|
+
* active children; the parent graph is acyclic;
|
|
13
|
+
* - what an attestation may say (leaf ground attests bytes; membership and
|
|
14
|
+
* reference identity are never observed);
|
|
15
|
+
* - the record / syncRecord shapes and the cloud-record normalizer.
|
|
16
|
+
*
|
|
17
|
+
* Refusal messages are ported verbatim from the engine registry — they are
|
|
18
|
+
* the ratified refusal text its exams pin.
|
|
19
|
+
*/
|
|
20
|
+
import { CONTAINERS, DERIVED_PAYLOAD, ROOT_TYPES, TYPES, UUID_PATTERN, } from './types.js';
|
|
21
|
+
/** The row a UUID must name; absence is the caller's bug, said plainly. */
|
|
22
|
+
export function mustGet(uuid, byUuid) {
|
|
23
|
+
const row = byUuid(uuid);
|
|
24
|
+
if (!row)
|
|
25
|
+
throw new Error(`no entity ${uuid}`);
|
|
26
|
+
return row;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What an attestation may say — asked of both the type a record wears now
|
|
30
|
+
* and the type its new bytes claim, because an attestation may carry a
|
|
31
|
+
* record across leaf kinds (a pointer replaced by bytes) and both ends
|
|
32
|
+
* must be ground an observation can speak for.
|
|
33
|
+
*/
|
|
34
|
+
export function attestable(type) {
|
|
35
|
+
if (DERIVED_PAYLOAD.has(type)) {
|
|
36
|
+
throw new Error(`${type} payload is derived membership, never attested`);
|
|
37
|
+
}
|
|
38
|
+
if (type === 'reference') {
|
|
39
|
+
throw new Error('a reference\'s payload is the target\'s identity, set by the deliberate act that created it — never an attestation; the rendered symlink is derived state, not evidence');
|
|
40
|
+
}
|
|
41
|
+
if (!TYPES.has(type))
|
|
42
|
+
throw new Error(`unknown entity type ${JSON.stringify(type)}`);
|
|
43
|
+
}
|
|
44
|
+
/** The ratified formula: version = hash(type, parentUUID, name, status, payloadVersion). */
|
|
45
|
+
export function canonicalVersion({ type, parentUUID = null, name, status, payloadVersion = null }, sha256Hex) {
|
|
46
|
+
return sha256Hex(JSON.stringify([type, parentUUID, name, status, payloadVersion]));
|
|
47
|
+
}
|
|
48
|
+
/** A name is one non-empty path segment: never '', '.', '..', a '/' or NUL. */
|
|
49
|
+
export function validName(name) {
|
|
50
|
+
if (typeof name !== 'string' || name === '' || name === '.' || name === '..'
|
|
51
|
+
|| name.includes('/') || name.includes('\0')) {
|
|
52
|
+
throw new Error(`invalid entity name ${JSON.stringify(name)}: a name is one non-empty path segment`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** A UUID may arrive only through a trusted identity binding (for example
|
|
56
|
+
* a cloud user-home bootstrap). The registry still owns the actual insert:
|
|
57
|
+
* accepting a caller-supplied UUID is not accepting a caller-supplied
|
|
58
|
+
* record, parent, status, or version. */
|
|
59
|
+
export function mintOrValidateUuid(uuid, ports) {
|
|
60
|
+
if (uuid === undefined || uuid === null)
|
|
61
|
+
return ports.mintUuid();
|
|
62
|
+
if (typeof uuid !== 'string' || !UUID_PATTERN.test(uuid)) {
|
|
63
|
+
throw new Error(`entity identity must be a UUID, got ${JSON.stringify(uuid)}`);
|
|
64
|
+
}
|
|
65
|
+
if (ports.byUuid(uuid))
|
|
66
|
+
throw new Error(`entity identity ${uuid} is already registered`);
|
|
67
|
+
return uuid.toLowerCase();
|
|
68
|
+
}
|
|
69
|
+
/** Ancestor uuids from the given parent up to the root. */
|
|
70
|
+
export function ancestorsOf(parentUuid, byUuid) {
|
|
71
|
+
const chain = [];
|
|
72
|
+
const seen = new Set();
|
|
73
|
+
let cursor = parentUuid;
|
|
74
|
+
while (cursor !== null) {
|
|
75
|
+
if (seen.has(cursor))
|
|
76
|
+
throw new Error(`registry corrupt: parent cycle at ${cursor}`);
|
|
77
|
+
seen.add(cursor);
|
|
78
|
+
chain.push(cursor);
|
|
79
|
+
cursor = mustGet(cursor, byUuid).parentUuid;
|
|
80
|
+
}
|
|
81
|
+
return chain;
|
|
82
|
+
}
|
|
83
|
+
/** Lifecycle inherits down: deleted if any ancestor is deleted, trashed if
|
|
84
|
+
* any is trashed, otherwise the entity's own stored status — one flip on a
|
|
85
|
+
* folder, no descendant record rewritten. */
|
|
86
|
+
export function effectiveStatusOf(row, byUuid) {
|
|
87
|
+
const statuses = [row.status, ...ancestorsOf(row.parentUuid, byUuid)
|
|
88
|
+
.map((uuid) => mustGet(uuid, byUuid).status)];
|
|
89
|
+
if (statuses.includes('deleted'))
|
|
90
|
+
return 'deleted';
|
|
91
|
+
if (statuses.includes('trashed'))
|
|
92
|
+
return 'trashed';
|
|
93
|
+
return row.status;
|
|
94
|
+
}
|
|
95
|
+
const byUuidOrder = (left, right) => (left < right ? -1 : left > right ? 1 : 0);
|
|
96
|
+
/**
|
|
97
|
+
* Container payload: the sorted active direct membership (childUUID, name).
|
|
98
|
+
* Children are sorted here by uuid, codepoint order — the same order the
|
|
99
|
+
* engine's `ORDER BY uuid` (BINARY collation over lowercase-hex uuids)
|
|
100
|
+
* produces — so the hash input is byte-identical however the port returns
|
|
101
|
+
* them. Membership counts direct children by their STORED status, never
|
|
102
|
+
* effective status: effective-status membership would ripple a version
|
|
103
|
+
* change through the entire subtree and defeat the one-flip lifecycle.
|
|
104
|
+
*/
|
|
105
|
+
export function membershipHash(activeChildren, sha256Hex) {
|
|
106
|
+
const pairs = [...activeChildren]
|
|
107
|
+
.sort((left, right) => byUuidOrder(left.uuid, right.uuid))
|
|
108
|
+
.map((child) => [child.uuid, child.name]);
|
|
109
|
+
return sha256Hex(JSON.stringify(pairs));
|
|
110
|
+
}
|
|
111
|
+
const payloadFor = (row, ports) => (DERIVED_PAYLOAD.has(row.type) ? ports.membershipHashOf(row.uuid) : row.payloadVersion);
|
|
112
|
+
/** The public record: exactly the six ratified fields, version derived. */
|
|
113
|
+
export function entityRecordOf(row, ports) {
|
|
114
|
+
return {
|
|
115
|
+
uuid: row.uuid,
|
|
116
|
+
type: row.type,
|
|
117
|
+
parentUUID: row.parentUuid,
|
|
118
|
+
name: row.name,
|
|
119
|
+
version: canonicalVersion({
|
|
120
|
+
type: row.type, parentUUID: row.parentUuid, name: row.name,
|
|
121
|
+
status: row.status, payloadVersion: payloadFor(row, ports),
|
|
122
|
+
}, ports.sha256Hex),
|
|
123
|
+
status: row.status,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** The cloud form contains identity and the content/head token, never an
|
|
127
|
+
* address, watcher, inode, or SQLite bytes. `payloadVersion` is the
|
|
128
|
+
* unambiguous current head for leaf types; containers derive theirs from
|
|
129
|
+
* membership and retain null here; a repo.git retains its immutable
|
|
130
|
+
* Card/Checkpoint head. */
|
|
131
|
+
export function syncRecordOf(row, ports) {
|
|
132
|
+
return {
|
|
133
|
+
...entityRecordOf(row, ports),
|
|
134
|
+
payloadVersion: row.payloadVersion,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** Validate and normalize a record arriving from the cloud: six fields plus
|
|
138
|
+
* payloadVersion, UUIDs lowercased, root laws enforced. */
|
|
139
|
+
export function normalizeCloudRecord(value) {
|
|
140
|
+
const record = value;
|
|
141
|
+
if (!record || typeof record !== 'object'
|
|
142
|
+
|| typeof record.uuid !== 'string' || !UUID_PATTERN.test(record.uuid)
|
|
143
|
+
|| typeof record.type !== 'string' || !TYPES.has(record.type)
|
|
144
|
+
|| (record.parentUUID !== null && (typeof record.parentUUID !== 'string' || !UUID_PATTERN.test(record.parentUUID)))
|
|
145
|
+
|| typeof record.name !== 'string'
|
|
146
|
+
|| !['active', 'trashed', 'deleted'].includes(record.status)
|
|
147
|
+
|| (record.payloadVersion !== null && typeof record.payloadVersion !== 'string')
|
|
148
|
+
|| typeof record.version !== 'string' || !/^[0-9a-f]{64}$/i.test(record.version)) {
|
|
149
|
+
throw new Error('cloud entity operation has an invalid record');
|
|
150
|
+
}
|
|
151
|
+
validName(record.name);
|
|
152
|
+
if (record.parentUUID === null && !ROOT_TYPES.has(record.type)) {
|
|
153
|
+
throw new Error(`cloud entity ${record.uuid} is not a valid tree root`);
|
|
154
|
+
}
|
|
155
|
+
if (record.parentUUID !== null && record.type === 'workspace') {
|
|
156
|
+
throw new Error('cloud entity workspace is the root type only');
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
uuid: record.uuid.toLowerCase(),
|
|
160
|
+
type: record.type,
|
|
161
|
+
parentUuid: record.parentUUID === null ? null : record.parentUUID.toLowerCase(),
|
|
162
|
+
name: record.name,
|
|
163
|
+
status: record.status,
|
|
164
|
+
payloadVersion: record.payloadVersion,
|
|
165
|
+
expectedVersion: record.version.toLowerCase(),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/** Equality over the five stored facts plus payload — everything the
|
|
169
|
+
* version fingerprints except the fingerprint itself. */
|
|
170
|
+
export function sameLogicalRecord(left, right) {
|
|
171
|
+
return left.uuid === right.uuid
|
|
172
|
+
&& left.type === right.type
|
|
173
|
+
&& left.parentUuid === right.parentUuid
|
|
174
|
+
&& left.name === right.name
|
|
175
|
+
&& left.status === right.status
|
|
176
|
+
&& left.payloadVersion === right.payloadVersion;
|
|
177
|
+
}
|
|
178
|
+
/** Only containers — workspace, folder, repo.git — hold active children,
|
|
179
|
+
* and only while effectively active themselves. */
|
|
180
|
+
export function requireActiveContainerParent(parentUuid, port) {
|
|
181
|
+
const parent = mustGet(parentUuid, port.byUuid.bind(port));
|
|
182
|
+
if (!CONTAINERS.has(parent.type)) {
|
|
183
|
+
throw new Error(`only containers hold active children: ${parentUuid} is ${parent.type}, not workspace/folder/repo.git`);
|
|
184
|
+
}
|
|
185
|
+
if (effectiveStatusOf(parent, port.byUuid.bind(port)) !== 'active') {
|
|
186
|
+
throw new Error(`parent ${parentUuid} is not effectively active — restore it before putting entities under it`);
|
|
187
|
+
}
|
|
188
|
+
return parent;
|
|
189
|
+
}
|
|
190
|
+
/** One active owner per (parentUUID, name) slot. Parentless records are
|
|
191
|
+
* exempt: a top-level tree is placed by its machine binding, never by name. */
|
|
192
|
+
export function requireFreeSlot(parentUuid, name, port, forUuid = null) {
|
|
193
|
+
if (parentUuid === null)
|
|
194
|
+
return; // top level: address places a tree, never its name
|
|
195
|
+
const occupant = port.activeChildByName(parentUuid, name);
|
|
196
|
+
if (occupant && occupant.uuid !== forUuid) {
|
|
197
|
+
throw new Error(`slot taken: active entity ${occupant.uuid} already owns (${parentUuid}, ${name}) — move or trash it first`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/entities/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACL,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,GAG7D,MAAM,YAAY,CAAC;AAEpB,2EAA2E;AAC3E,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,MAAgC;IACpE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;IAC/C,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,gDAAgD,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,yKAAyK,CAAC,CAAC;IAC7L,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACvF,CAAC;AAUD,4FAA4F;AAC5F,MAAM,UAAU,gBAAgB,CAC9B,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,EAAyB,EACvF,SAAoB;IAEpB,OAAO,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;AACrF,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,SAAS,CAAC,IAAa;IACrC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI;WACvE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;IACvG,CAAC;AACH,CAAC;AAQD;;;yCAGyC;AACzC,MAAM,UAAU,kBAAkB,CAAC,IAA+B,EAAE,KAAgB;IAClF,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;IACjE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,uCAAuC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,wBAAwB,CAAC,CAAC;IACzF,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC5B,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,WAAW,CAAC,UAAyB,EAAE,MAAgC;IACrF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,IAAI,MAAM,GAAG,UAAU,CAAC;IACxB,OAAO,MAAM,KAAK,IAAI,EAAE,CAAC;QACvB,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,MAAM,EAAE,CAAC,CAAC;QACrF,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC;IAC9C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;6CAE6C;AAC7C,MAAM,UAAU,iBAAiB,CAC/B,GAA6C,EAC7C,MAAgC;IAEhC,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC;aACjE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,IAAI,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IACnD,IAAI,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IACnD,OAAO,GAAG,CAAC,MAAM,CAAC;AACpB,CAAC;AAED,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE,CAC1D,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAC5B,cAA2D,EAC3D,SAAoB;IAEpB,MAAM,KAAK,GAAG,CAAC,GAAG,cAAc,CAAC;SAC9B,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;SACzD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5C,OAAO,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AAC1C,CAAC;AASD,MAAM,UAAU,GAAG,CAAC,GAAc,EAAE,KAAkB,EAAiB,EAAE,CACvE,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAE1F,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,GAAc,EAAE,KAAkB;IAC/D,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,OAAO,EAAE,gBAAgB,CAAC;YACxB,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI;YAC1D,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC;SAC3D,EAAE,KAAK,CAAC,SAAS,CAAC;QACnB,MAAM,EAAE,GAAG,CAAC,MAAM;KACnB,CAAC;AACJ,CAAC;AAED;;;;2BAI2B;AAC3B,MAAM,UAAU,YAAY,CAAC,GAAc,EAAE,KAAkB;IAC7D,OAAO;QACL,GAAG,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC;QAC7B,cAAc,EAAE,GAAG,CAAC,cAAc;KACnC,CAAC;AACJ,CAAC;AAcD;2DAC2D;AAC3D,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,MAAM,MAAM,GAAG,KAGP,CAAC;IACT,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;WACpC,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;WAClE,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;WAC1D,CAAC,MAAM,CAAC,UAAU,KAAK,IAAI,IAAI,CAAC,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;WAChH,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;WAC/B,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAgB,CAAC;WACnE,CAAC,MAAM,CAAC,cAAc,KAAK,IAAI,IAAI,OAAO,MAAM,CAAC,cAAc,KAAK,QAAQ,CAAC;WAC7E,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QACnF,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IACD,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CAAC,gBAAgB,MAAM,CAAC,IAAI,2BAA2B,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC9D,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IACD,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE;QAC/B,IAAI,EAAE,MAAM,CAAC,IAAkB;QAC/B,UAAU,EAAE,MAAM,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,MAAM,CAAC,UAAqB,CAAC,WAAW,EAAE;QAC3F,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,MAAM,EAAE,MAAM,CAAC,MAAsB;QACrC,cAAc,EAAE,MAAM,CAAC,cAA+B;QACtD,eAAe,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE;KAC9C,CAAC;AACJ,CAAC;AAED;yDACyD;AACzD,MAAM,UAAU,iBAAiB,CAC/B,IAA4F,EAC5F,KAA6F;IAE7F,OAAO,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI;WAC1B,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI;WACxB,IAAI,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU;WACpC,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI;WACxB,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;WAC5B,IAAI,CAAC,cAAc,KAAK,KAAK,CAAC,cAAc,CAAC;AACpD,CAAC;AAED;mDACmD;AACnD,MAAM,UAAU,4BAA4B,CAAC,UAAkB,EAAE,IAAoB;IACnF,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3D,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,yCAAyC,UAAU,OAAO,MAAM,CAAC,IAAI,iCAAiC,CAAC,CAAC;IAC1H,CAAC;IACD,IAAI,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QACnE,MAAM,IAAI,KAAK,CAAC,UAAU,UAAU,0EAA0E,CAAC,CAAC;IAClH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;+EAC+E;AAC/E,MAAM,UAAU,eAAe,CAC7B,UAAyB,EACzB,IAAY,EACZ,IAAoB,EACpB,UAAyB,IAAI;IAE7B,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,CAAC,mDAAmD;IACpF,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAC1D,IAAI,QAAQ,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,CAAC,IAAI,kBAAkB,UAAU,KAAK,IAAI,4BAA4B,CAAC,CAAC;IAC/H,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The entity alphabet and the six-field universal record — the value
|
|
3
|
+
* vocabulary of the entity model (engine docs/entity-model.md, "The entity
|
|
4
|
+
* record"). Every enrolled entity, of every type, is exactly six fields:
|
|
5
|
+
* UUID, type, parentUUID, name, version, status. Paths are derived from the
|
|
6
|
+
* parentUUID + name chain and are never identity; version is derived on
|
|
7
|
+
* read, never stored.
|
|
8
|
+
*
|
|
9
|
+
* The alphabet is PHYSICAL kinds only. Constructs — agents, apps,
|
|
10
|
+
* automations, tools, skills — are folder-and-file ground with a known
|
|
11
|
+
* structure, their meaning layered ABOVE the physical type, never encoded
|
|
12
|
+
* as one.
|
|
13
|
+
*
|
|
14
|
+
* This module also names the two ports the pure rules receive instead of a
|
|
15
|
+
* database or node:crypto: a read port over the host's identity store, and
|
|
16
|
+
* a SHA-256 hex function.
|
|
17
|
+
*/
|
|
18
|
+
/** `link` is a filesystem symlink: payload = the literal target text.
|
|
19
|
+
* `reference` is the OTHER link kind, never conflated: Amalgm-created by a
|
|
20
|
+
* deliberate act, its payload the TARGET ENTITY'S UUID — identity, not an
|
|
21
|
+
* address. */
|
|
22
|
+
export type EntityType = 'workspace' | 'folder' | 'repo.git' | 'file.text' | 'file.binary' | 'link' | 'reference';
|
|
23
|
+
export type ContainerType = 'workspace' | 'folder' | 'repo.git';
|
|
24
|
+
/** active — exists normally; trashed — recoverable, UUID survives;
|
|
25
|
+
* deleted — content permanently gone, the record IS the tombstone. */
|
|
26
|
+
export type EntityStatus = 'active' | 'trashed' | 'deleted';
|
|
27
|
+
export declare const TYPES: ReadonlySet<string>;
|
|
28
|
+
/** Only containers hold active children. */
|
|
29
|
+
export declare const CONTAINERS: ReadonlySet<string>;
|
|
30
|
+
/** The types a parentless record (a tree root) may wear. `folder` is
|
|
31
|
+
* deliberately absent: a folder inside no workspace is not a thing amalgm
|
|
32
|
+
* registers. `reference` is absent too — a managed pointer is always
|
|
33
|
+
* created inside a tree. */
|
|
34
|
+
export declare const ROOT_TYPES: ReadonlySet<string>;
|
|
35
|
+
/** Types whose payload IS their membership: derived from the records
|
|
36
|
+
* beneath them, so no observation may attest one. */
|
|
37
|
+
export declare const DERIVED_PAYLOAD: ReadonlySet<string>;
|
|
38
|
+
/** The one entity alphabet, published sorted for the layers that map onto
|
|
39
|
+
* it — read from here, never restated, so it cannot drift. */
|
|
40
|
+
export declare const ENTITY_TYPES: readonly EntityType[];
|
|
41
|
+
export declare const isEntityType: (value: unknown) => value is EntityType;
|
|
42
|
+
/** RFC-4122 UUID shape (versions 1–8), the permanent identity and realtime
|
|
43
|
+
* channel of every enrolled entity. */
|
|
44
|
+
export declare const UUID_PATTERN: RegExp;
|
|
45
|
+
/**
|
|
46
|
+
* A stored identity row as the host's store holds it — the six fields with
|
|
47
|
+
* the payload attestation beside them and no derived version. The rules in
|
|
48
|
+
* this layer read rows through {@link EntityReadPort} and never write one.
|
|
49
|
+
*/
|
|
50
|
+
export interface EntityRow {
|
|
51
|
+
readonly uuid: string;
|
|
52
|
+
readonly type: EntityType;
|
|
53
|
+
readonly parentUuid: string | null;
|
|
54
|
+
readonly name: string;
|
|
55
|
+
readonly status: EntityStatus;
|
|
56
|
+
readonly payloadVersion: string | null;
|
|
57
|
+
}
|
|
58
|
+
/** The public record: exactly the six ratified fields, version derived by
|
|
59
|
+
* the canonical formula. Payload attestations are evidence inputs, never a
|
|
60
|
+
* seventh public field. */
|
|
61
|
+
export interface EntityRecord {
|
|
62
|
+
readonly uuid: string;
|
|
63
|
+
readonly type: EntityType;
|
|
64
|
+
readonly parentUUID: string | null;
|
|
65
|
+
readonly name: string;
|
|
66
|
+
readonly version: string;
|
|
67
|
+
readonly status: EntityStatus;
|
|
68
|
+
}
|
|
69
|
+
/** The cloud form: the record plus the unambiguous current head for leaf
|
|
70
|
+
* types. Containers derive their payload from membership and retain null
|
|
71
|
+
* here; a repo.git retains its immutable Card/Checkpoint stateId. */
|
|
72
|
+
export interface EntitySyncRecord extends EntityRecord {
|
|
73
|
+
readonly payloadVersion: string | null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* SHA-256 as lowercase hex over the given bytes; string input is hashed as
|
|
77
|
+
* its UTF-8 encoding (exactly node:crypto's `createHash('sha256')
|
|
78
|
+
* .update(input).digest('hex')`). Injected by the host — src/ owns hash
|
|
79
|
+
* INPUTS byte for byte, never the hash implementation.
|
|
80
|
+
*/
|
|
81
|
+
export type Sha256Hex = (input: Uint8Array | string) => string;
|
|
82
|
+
/**
|
|
83
|
+
* The minimal read surface the rules need over the host's identity store.
|
|
84
|
+
* Lookups are by permanent identity or by (parentUUID, name) slot; the
|
|
85
|
+
* rules never enumerate, never write, and never see a path.
|
|
86
|
+
*/
|
|
87
|
+
export interface EntityReadPort {
|
|
88
|
+
/** The row wearing this UUID, or null/undefined when none does. */
|
|
89
|
+
byUuid(uuid: string): EntityRow | null | undefined;
|
|
90
|
+
/** The ACTIVE row owning the (parentUuid, name) slot, or null/undefined
|
|
91
|
+
* when the slot is free. */
|
|
92
|
+
activeChildByName(parentUuid: string, name: string): EntityRow | null | undefined;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/entities/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;cAGc;AACd,MAAM,MAAM,UAAU,GAClB,WAAW,GAAG,QAAQ,GAAG,UAAU,GACnC,WAAW,GAAG,aAAa,GAC3B,MAAM,GAAG,WAAW,CAAC;AAEzB,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEhE;sEACsE;AACtE,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAO5D,eAAO,MAAM,KAAK,EAAE,WAAW,CAAC,MAAM,CAAqB,CAAC;AAE5D,4CAA4C;AAC5C,eAAO,MAAM,UAAU,EAAE,WAAW,CAAC,MAAM,CAE1C,CAAC;AAEF;;;4BAG4B;AAC5B,eAAO,MAAM,UAAU,EAAE,WAAW,CAAC,MAAM,CAE1C,CAAC;AAEF;qDACqD;AACrD,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,CAE/C,CAAC;AAEF;8DAC8D;AAC9D,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAAwC,CAAC;AAEvF,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,UACR,CAAC;AAEhD;uCACuC;AACvC,eAAO,MAAM,YAAY,QACqD,CAAC;AAE/E;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED;;2BAE2B;AAC3B,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;CAC/B;AAED;;qEAEqE;AACrE,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,KAAK,MAAM,CAAC;AAE/D;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,SAAS,CAAC;IACnD;gCAC4B;IAC5B,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,SAAS,CAAC;CACnF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The entity alphabet and the six-field universal record — the value
|
|
3
|
+
* vocabulary of the entity model (engine docs/entity-model.md, "The entity
|
|
4
|
+
* record"). Every enrolled entity, of every type, is exactly six fields:
|
|
5
|
+
* UUID, type, parentUUID, name, version, status. Paths are derived from the
|
|
6
|
+
* parentUUID + name chain and are never identity; version is derived on
|
|
7
|
+
* read, never stored.
|
|
8
|
+
*
|
|
9
|
+
* The alphabet is PHYSICAL kinds only. Constructs — agents, apps,
|
|
10
|
+
* automations, tools, skills — are folder-and-file ground with a known
|
|
11
|
+
* structure, their meaning layered ABOVE the physical type, never encoded
|
|
12
|
+
* as one.
|
|
13
|
+
*
|
|
14
|
+
* This module also names the two ports the pure rules receive instead of a
|
|
15
|
+
* database or node:crypto: a read port over the host's identity store, and
|
|
16
|
+
* a SHA-256 hex function.
|
|
17
|
+
*/
|
|
18
|
+
const ALPHABET = [
|
|
19
|
+
'workspace', 'folder', 'repo.git', 'file.text', 'file.binary',
|
|
20
|
+
'link', 'reference',
|
|
21
|
+
];
|
|
22
|
+
export const TYPES = new Set(ALPHABET);
|
|
23
|
+
/** Only containers hold active children. */
|
|
24
|
+
export const CONTAINERS = new Set(['workspace', 'folder', 'repo.git']);
|
|
25
|
+
/** The types a parentless record (a tree root) may wear. `folder` is
|
|
26
|
+
* deliberately absent: a folder inside no workspace is not a thing amalgm
|
|
27
|
+
* registers. `reference` is absent too — a managed pointer is always
|
|
28
|
+
* created inside a tree. */
|
|
29
|
+
export const ROOT_TYPES = new Set(['workspace', 'repo.git', 'file.text', 'file.binary', 'link']);
|
|
30
|
+
/** Types whose payload IS their membership: derived from the records
|
|
31
|
+
* beneath them, so no observation may attest one. */
|
|
32
|
+
export const DERIVED_PAYLOAD = new Set(['workspace', 'folder']);
|
|
33
|
+
/** The one entity alphabet, published sorted for the layers that map onto
|
|
34
|
+
* it — read from here, never restated, so it cannot drift. */
|
|
35
|
+
export const ENTITY_TYPES = Object.freeze([...ALPHABET].sort());
|
|
36
|
+
export const isEntityType = (value) => typeof value === 'string' && TYPES.has(value);
|
|
37
|
+
/** RFC-4122 UUID shape (versions 1–8), the permanent identity and realtime
|
|
38
|
+
* channel of every enrolled entity. */
|
|
39
|
+
export const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
40
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/entities/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAiBH,MAAM,QAAQ,GAA0B;IACtC,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa;IAC7D,MAAM,EAAE,WAAW;CACpB,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAwB,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;AAE5D,4CAA4C;AAC5C,MAAM,CAAC,MAAM,UAAU,GAAwB,IAAI,GAAG,CACpD,CAAC,WAAW,EAAE,QAAQ,EAAE,UAAU,CAA2B,CAC9D,CAAC;AAEF;;;4BAG4B;AAC5B,MAAM,CAAC,MAAM,UAAU,GAAwB,IAAI,GAAG,CACpD,CAAC,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,CAAwB,CACrF,CAAC;AAEF;qDACqD;AACrD,MAAM,CAAC,MAAM,eAAe,GAAwB,IAAI,GAAG,CACzD,CAAC,WAAW,EAAE,QAAQ,CAAwB,CAC/C,CAAC;AAEF;8DAC8D;AAC9D,MAAM,CAAC,MAAM,YAAY,GAA0B,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAEvF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAc,EAAuB,EAAE,CAClE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAEhD;uCACuC;AACvC,MAAM,CAAC,MAAM,YAAY,GACvB,4EAA4E,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @amalgm/live — the portable realtime SDK for the Amalgm platform.
|
|
3
|
+
*
|
|
4
|
+
* Three layers, one rule each:
|
|
5
|
+
*
|
|
6
|
+
* - `contracts/` every wire shape and identity rule, as types + validators
|
|
7
|
+
* - `machines/` the protocol's behavior, as pure state machines
|
|
8
|
+
* - `adapters/` the ports a host injects (transport, journal, clock, crypto)
|
|
9
|
+
*
|
|
10
|
+
* Start at PURPOSE.md for the axioms; docs/ holds the normative specs.
|
|
11
|
+
*/
|
|
12
|
+
export * from './contracts/index.js';
|
|
13
|
+
export * from './machines/index.js';
|
|
14
|
+
export * from './entities/index.js';
|
|
15
|
+
export * from './detection/index.js';
|
|
16
|
+
export * from './merge/index.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @amalgm/live — the portable realtime SDK for the Amalgm platform.
|
|
3
|
+
*
|
|
4
|
+
* Three layers, one rule each:
|
|
5
|
+
*
|
|
6
|
+
* - `contracts/` every wire shape and identity rule, as types + validators
|
|
7
|
+
* - `machines/` the protocol's behavior, as pure state machines
|
|
8
|
+
* - `adapters/` the ports a host injects (transport, journal, clock, crypto)
|
|
9
|
+
*
|
|
10
|
+
* Start at PURPOSE.md for the axioms; docs/ holds the normative specs.
|
|
11
|
+
*/
|
|
12
|
+
export * from './contracts/index.js';
|
|
13
|
+
export * from './machines/index.js';
|
|
14
|
+
export * from './entities/index.js';
|
|
15
|
+
export * from './detection/index.js';
|
|
16
|
+
export * from './merge/index.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tunnel slot arbitration — who owns `(computer_id, runtime_label)`.
|
|
3
|
+
*
|
|
4
|
+
* The gateway holds one slot per runtime identity; a newcomer's first
|
|
5
|
+
* identity-bearing hello decides the winner (local-live-protocol.md §5).
|
|
6
|
+
* This is the pure decision. The gateway supplies sockets and timers; the
|
|
7
|
+
* fencing rule — mark the loser superseded BEFORE closing it, drop its
|
|
8
|
+
* in-flight frames — is part of the contract and encoded in the verdict.
|
|
9
|
+
*/
|
|
10
|
+
import { CLOSE_CODES, type TunnelHello } from '../contracts/frames/tunnel.js';
|
|
11
|
+
/** A newcomer with a holder present buffers unrouted until its first identity-bearing hello, bounded by this deadline (AMALGM_TUNNEL_HELLO_ARBITRATION_MS). */
|
|
12
|
+
export declare const ARBITRATION_DEADLINE_MS = 3000;
|
|
13
|
+
export type SlotIdentity = Pick<TunnelHello, 'instance_id' | 'boot_ts'>;
|
|
14
|
+
export type SlotVerdict =
|
|
15
|
+
/** Free slot, or the newcomer won: install it. `fenceHolder` demands the holder be superseded-then-closed (4000) with its frames dropped. */
|
|
16
|
+
{
|
|
17
|
+
readonly install: true;
|
|
18
|
+
readonly fenceHolder: boolean;
|
|
19
|
+
readonly rule: 'free-slot' | 'same-instance-reconnect' | 'newer-boot' | 'legacy-last-connect-wins';
|
|
20
|
+
}
|
|
21
|
+
/** The newcomer lost: refuse with 4009. The holder is untouched. */
|
|
22
|
+
| {
|
|
23
|
+
readonly install: false;
|
|
24
|
+
readonly closeCode: typeof CLOSE_CODES.supersededInstance;
|
|
25
|
+
readonly rule: 'older-or-equal-boot';
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Decide a contested slot.
|
|
29
|
+
*
|
|
30
|
+
* - same `instance_id` → newer socket wins (the same process reconnecting)
|
|
31
|
+
* - different `instance_id` → strictly newer `boot_ts` wins; older or EQUAL loses
|
|
32
|
+
* - unusable identity on either side (or the deadline expired — pass
|
|
33
|
+
* `holder: null` identity via `legacyFallback`) → last-connect-wins, logged as such by the caller
|
|
34
|
+
*/
|
|
35
|
+
export declare function decideSlot(holder: SlotIdentity | null, newcomer: Partial<SlotIdentity>): SlotVerdict;
|
|
36
|
+
//# sourceMappingURL=arbitration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arbitration.d.ts","sourceRoot":"","sources":["../../src/machines/arbitration.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAqB,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjG,+JAA+J;AAC/J,eAAO,MAAM,uBAAuB,OAAQ,CAAC;AAE7C,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,aAAa,GAAG,SAAS,CAAC,CAAC;AAExE,MAAM,MAAM,WAAW;AACrB,6IAA6I;AAC3I;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,yBAAyB,GAAG,YAAY,GAAG,0BAA0B,CAAA;CAAE;AAC/J,oEAAoE;GAClE;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,WAAW,CAAC,kBAAkB,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAA;CAAE,CAAC;AAEjI;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,WAAW,CAepG"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tunnel slot arbitration — who owns `(computer_id, runtime_label)`.
|
|
3
|
+
*
|
|
4
|
+
* The gateway holds one slot per runtime identity; a newcomer's first
|
|
5
|
+
* identity-bearing hello decides the winner (local-live-protocol.md §5).
|
|
6
|
+
* This is the pure decision. The gateway supplies sockets and timers; the
|
|
7
|
+
* fencing rule — mark the loser superseded BEFORE closing it, drop its
|
|
8
|
+
* in-flight frames — is part of the contract and encoded in the verdict.
|
|
9
|
+
*/
|
|
10
|
+
import { CLOSE_CODES, hasUsableIdentity } from '../contracts/frames/tunnel.js';
|
|
11
|
+
/** A newcomer with a holder present buffers unrouted until its first identity-bearing hello, bounded by this deadline (AMALGM_TUNNEL_HELLO_ARBITRATION_MS). */
|
|
12
|
+
export const ARBITRATION_DEADLINE_MS = 3_000;
|
|
13
|
+
/**
|
|
14
|
+
* Decide a contested slot.
|
|
15
|
+
*
|
|
16
|
+
* - same `instance_id` → newer socket wins (the same process reconnecting)
|
|
17
|
+
* - different `instance_id` → strictly newer `boot_ts` wins; older or EQUAL loses
|
|
18
|
+
* - unusable identity on either side (or the deadline expired — pass
|
|
19
|
+
* `holder: null` identity via `legacyFallback`) → last-connect-wins, logged as such by the caller
|
|
20
|
+
*/
|
|
21
|
+
export function decideSlot(holder, newcomer) {
|
|
22
|
+
if (holder === null) {
|
|
23
|
+
return { install: true, fenceHolder: false, rule: 'free-slot' };
|
|
24
|
+
}
|
|
25
|
+
if (!hasUsableIdentity(newcomer) || !hasUsableIdentity(holder)) {
|
|
26
|
+
return { install: true, fenceHolder: true, rule: 'legacy-last-connect-wins' };
|
|
27
|
+
}
|
|
28
|
+
const contender = newcomer;
|
|
29
|
+
if (contender.instance_id === holder.instance_id) {
|
|
30
|
+
return { install: true, fenceHolder: true, rule: 'same-instance-reconnect' };
|
|
31
|
+
}
|
|
32
|
+
if (contender.boot_ts > holder.boot_ts) {
|
|
33
|
+
return { install: true, fenceHolder: true, rule: 'newer-boot' };
|
|
34
|
+
}
|
|
35
|
+
return { install: false, closeCode: CLOSE_CODES.supersededInstance, rule: 'older-or-equal-boot' };
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=arbitration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arbitration.js","sourceRoot":"","sources":["../../src/machines/arbitration.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAoB,MAAM,+BAA+B,CAAC;AAEjG,+JAA+J;AAC/J,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAC;AAU7C;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,MAA2B,EAAE,QAA+B;IACrF,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,0BAA0B,EAAE,CAAC;IAChF,CAAC;IACD,MAAM,SAAS,GAAG,QAAwB,CAAC;IAC3C,IAAI,SAAS,CAAC,WAAW,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC;IAC/E,CAAC;IACD,IAAI,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;QACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAClE,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,CAAC,kBAAkB,EAAE,IAAI,EAAE,qBAAqB,EAAE,CAAC;AACpG,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tunnel reconnect policy (local-live-protocol.md §5, runtime client
|
|
3
|
+
* behavior).
|
|
4
|
+
*
|
|
5
|
+
* Two rules keep a two-instance fight from becoming a reconnect war:
|
|
6
|
+
*
|
|
7
|
+
* 1. Exponential backoff resets to 1s only after a connection SURVIVED the
|
|
8
|
+
* stable window; a connection that dies younger keeps (and grows) the
|
|
9
|
+
* previous backoff.
|
|
10
|
+
* 2. A 4009 refusal ("superseded instance") retries after a fixed long
|
|
11
|
+
* delay — never a full stop, because a wrongly-refused instance must
|
|
12
|
+
* still recover once the true holder dies.
|
|
13
|
+
*/
|
|
14
|
+
/** AMALGM_TUNNEL_SUPERSEDED_RETRY_MS */
|
|
15
|
+
export declare const SUPERSEDED_RETRY_MS: number;
|
|
16
|
+
/** AMALGM_TUNNEL_STABLE_RESET_MS — survival time that earns a reset to the base delay. */
|
|
17
|
+
export declare const STABLE_RESET_MS = 30000;
|
|
18
|
+
export declare const BASE_DELAY_MS = 1000;
|
|
19
|
+
export declare const MAX_DELAY_MS = 60000;
|
|
20
|
+
export interface BackoffState {
|
|
21
|
+
/** The delay the NEXT failure will use. */
|
|
22
|
+
readonly delayMs: number;
|
|
23
|
+
}
|
|
24
|
+
export declare const initialBackoff: BackoffState;
|
|
25
|
+
export interface Disconnect {
|
|
26
|
+
readonly closeCode?: number;
|
|
27
|
+
/** How long the connection lived before dying. */
|
|
28
|
+
readonly connectedForMs: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ReconnectPlan {
|
|
31
|
+
readonly waitMs: number;
|
|
32
|
+
readonly next: BackoffState;
|
|
33
|
+
readonly rule: 'superseded-fixed-retry' | 'stable-reset' | 'exponential';
|
|
34
|
+
}
|
|
35
|
+
export declare function planReconnect(state: BackoffState, disconnect: Disconnect): ReconnectPlan;
|
|
36
|
+
//# sourceMappingURL=backoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backoff.d.ts","sourceRoot":"","sources":["../../src/machines/backoff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,wCAAwC;AACxC,eAAO,MAAM,mBAAmB,QAAa,CAAC;AAC9C,0FAA0F;AAC1F,eAAO,MAAM,eAAe,QAAS,CAAC;AACtC,eAAO,MAAM,aAAa,OAAQ,CAAC;AACnC,eAAO,MAAM,YAAY,QAAS,CAAC;AAEnC,MAAM,WAAW,YAAY;IAC3B,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,cAAc,EAAE,YAAyC,CAAC;AAEvE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,wBAAwB,GAAG,cAAc,GAAG,aAAa,CAAC;CAC1E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,GAAG,aAAa,CAWxF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tunnel reconnect policy (local-live-protocol.md §5, runtime client
|
|
3
|
+
* behavior).
|
|
4
|
+
*
|
|
5
|
+
* Two rules keep a two-instance fight from becoming a reconnect war:
|
|
6
|
+
*
|
|
7
|
+
* 1. Exponential backoff resets to 1s only after a connection SURVIVED the
|
|
8
|
+
* stable window; a connection that dies younger keeps (and grows) the
|
|
9
|
+
* previous backoff.
|
|
10
|
+
* 2. A 4009 refusal ("superseded instance") retries after a fixed long
|
|
11
|
+
* delay — never a full stop, because a wrongly-refused instance must
|
|
12
|
+
* still recover once the true holder dies.
|
|
13
|
+
*/
|
|
14
|
+
import { CLOSE_CODES } from '../contracts/frames/tunnel.js';
|
|
15
|
+
/** AMALGM_TUNNEL_SUPERSEDED_RETRY_MS */
|
|
16
|
+
export const SUPERSEDED_RETRY_MS = 5 * 60_000;
|
|
17
|
+
/** AMALGM_TUNNEL_STABLE_RESET_MS — survival time that earns a reset to the base delay. */
|
|
18
|
+
export const STABLE_RESET_MS = 30_000;
|
|
19
|
+
export const BASE_DELAY_MS = 1_000;
|
|
20
|
+
export const MAX_DELAY_MS = 60_000;
|
|
21
|
+
export const initialBackoff = { delayMs: BASE_DELAY_MS };
|
|
22
|
+
export function planReconnect(state, disconnect) {
|
|
23
|
+
if (disconnect.closeCode === CLOSE_CODES.supersededInstance) {
|
|
24
|
+
// Fixed, slow, and does not disturb the exponential state: an instance
|
|
25
|
+
// alternating between refusals and crashes must not launder its backoff.
|
|
26
|
+
return { waitMs: SUPERSEDED_RETRY_MS, next: state, rule: 'superseded-fixed-retry' };
|
|
27
|
+
}
|
|
28
|
+
if (disconnect.connectedForMs >= STABLE_RESET_MS) {
|
|
29
|
+
return { waitMs: BASE_DELAY_MS, next: initialBackoff, rule: 'stable-reset' };
|
|
30
|
+
}
|
|
31
|
+
const grown = Math.min(state.delayMs * 2, MAX_DELAY_MS);
|
|
32
|
+
return { waitMs: state.delayMs, next: { delayMs: grown }, rule: 'exponential' };
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=backoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backoff.js","sourceRoot":"","sources":["../../src/machines/backoff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAE5D,wCAAwC;AACxC,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAG,MAAM,CAAC;AAC9C,0FAA0F;AAC1F,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AACtC,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC;AACnC,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC;AAOnC,MAAM,CAAC,MAAM,cAAc,GAAiB,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;AAcvE,MAAM,UAAU,aAAa,CAAC,KAAmB,EAAE,UAAsB;IACvE,IAAI,UAAU,CAAC,SAAS,KAAK,WAAW,CAAC,kBAAkB,EAAE,CAAC;QAC5D,uEAAuE;QACvE,yEAAyE;QACzE,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,wBAAwB,EAAE,CAAC;IACtF,CAAC;IACD,IAAI,UAAU,CAAC,cAAc,IAAI,eAAe,EAAE,CAAC;QACjD,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IAC/E,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,YAAY,CAAC,CAAC;IACxD,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;AAClF,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bootstrap machine — how a client reaches `live` truthfully.
|
|
3
|
+
*
|
|
4
|
+
* Ordered-bootstrap mode (local-live-protocol.md §3): the snapshot is the
|
|
5
|
+
* first data frame and everything after it is ordered on the same channel,
|
|
6
|
+
* so the machine simply installs and follows. Legacy mode: the snapshot
|
|
7
|
+
* travels out-of-band, so the machine holds events while the snapshot body
|
|
8
|
+
* is in flight and replays only those past its seq — the full-snapshot
|
|
9
|
+
* barrier (§6).
|
|
10
|
+
*
|
|
11
|
+
* Feature detection (§3): a bootstrap request against a legacy engine
|
|
12
|
+
* yields no `snapshot` frame. The client falls back to the legacy path
|
|
13
|
+
* when the first data frame is a `state` event, a `ping` arrives, or the
|
|
14
|
+
* timeout passes — whichever comes first.
|
|
15
|
+
*/
|
|
16
|
+
import type { StateEvent } from '../contracts/events.js';
|
|
17
|
+
import type { Snapshot } from '../contracts/snapshot.js';
|
|
18
|
+
import { type StreamFrame } from '../contracts/frames/stream.js';
|
|
19
|
+
export type BootstrapCommand =
|
|
20
|
+
/** Install the snapshot, then apply the events in order, then follow live from the snapshot's seq. */
|
|
21
|
+
{
|
|
22
|
+
readonly command: 'install';
|
|
23
|
+
readonly snapshot: Snapshot;
|
|
24
|
+
readonly replay: readonly StateEvent[];
|
|
25
|
+
}
|
|
26
|
+
/** The peer is legacy: fetch a REST snapshot and run the full-snapshot barrier. Buffered events are handed back. */
|
|
27
|
+
| {
|
|
28
|
+
readonly command: 'fallback-legacy';
|
|
29
|
+
readonly held: readonly StateEvent[];
|
|
30
|
+
readonly reason: 'state-before-snapshot' | 'ping-before-snapshot' | 'timeout';
|
|
31
|
+
}
|
|
32
|
+
/** Bootstrap snapshot build failed server-side; the stream is ending. Fall back to the REST path. */
|
|
33
|
+
| {
|
|
34
|
+
readonly command: 'fallback-error';
|
|
35
|
+
readonly status?: number;
|
|
36
|
+
} | {
|
|
37
|
+
readonly command: 'none';
|
|
38
|
+
};
|
|
39
|
+
/** One instance per bootstrap-mode connection attempt; discard after it resolves. */
|
|
40
|
+
export declare class BootstrapMachine {
|
|
41
|
+
#private;
|
|
42
|
+
readonly timeoutMs = 5000;
|
|
43
|
+
/** Feed each stream frame until a command other than `none` resolves the machine. */
|
|
44
|
+
accept(frame: StreamFrame): BootstrapCommand;
|
|
45
|
+
/** The host's timer fired without a snapshot frame. */
|
|
46
|
+
expire(): BootstrapCommand;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The legacy full-snapshot barrier (§6): hold events while a full snapshot
|
|
50
|
+
* body is in flight; on arrival, replay only those past its seq. Partial
|
|
51
|
+
* snapshots (resources added on a live connection) never pass through
|
|
52
|
+
* here — they hold per-resource and never bump the connection seq.
|
|
53
|
+
*/
|
|
54
|
+
export declare class SnapshotBarrier {
|
|
55
|
+
#private;
|
|
56
|
+
get inFlight(): boolean;
|
|
57
|
+
begin(): void;
|
|
58
|
+
/** Returns true when the event was held (a snapshot body is in flight). */
|
|
59
|
+
hold(event: StateEvent): boolean;
|
|
60
|
+
/** The snapshot body landed: install it, then apply the returned events in order. */
|
|
61
|
+
complete(snapshot: Snapshot): StateEvent[];
|
|
62
|
+
/** The snapshot fetch failed: release nothing; the caller re-syncs. */
|
|
63
|
+
abandon(): void;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/machines/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAiC,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEhG,MAAM,MAAM,gBAAgB;AAC1B,sGAAsG;AACpG;IAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAA;CAAE;AACtG,oHAAoH;GAClH;IAAE,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,UAAU,EAAE,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,GAAG,sBAAsB,GAAG,SAAS,CAAA;CAAE;AAC9J,qGAAqG;GACnG;IAAE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,qFAAqF;AACrF,qBAAa,gBAAgB;;IAI3B,QAAQ,CAAC,SAAS,QAAiC;IAEnD,qFAAqF;IACrF,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB;IAoC5C,uDAAuD;IACvD,MAAM,IAAI,gBAAgB;CAO3B;AAED;;;;;GAKG;AACH,qBAAa,eAAe;;IAI1B,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,KAAK,IAAI,IAAI;IAKb,2EAA2E;IAC3E,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO;IAMhC,qFAAqF;IACrF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,UAAU,EAAE;IAO1C,uEAAuE;IACvE,OAAO,IAAI,IAAI;CAIhB"}
|