amalgm 0.1.245 → 0.1.246
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 +12 -0
- package/lib/cli.js +146 -8
- package/lib/layout.js +89 -4
- package/lib/shared-realtime-tunnel.js +384 -43
- package/lib/supervisor.js +0 -3
- package/package.json +2 -2
- package/runtime/scripts/amalgm-mcp/adapters/contract.js +197 -0
- package/runtime/scripts/amalgm-mcp/adapters/filesystem.js +135 -0
- package/runtime/scripts/amalgm-mcp/adapters/git.js +84 -0
- package/runtime/scripts/amalgm-mcp/adapters/index.js +43 -0
- package/runtime/scripts/amalgm-mcp/adapters/reference.js +31 -0
- package/runtime/scripts/amalgm-mcp/adapters/truth.js +19 -0
- package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +20 -68
- package/runtime/scripts/amalgm-mcp/index.js +4 -0
- package/runtime/scripts/amalgm-mcp/lib/layout.js +89 -4
- package/runtime/scripts/amalgm-mcp/observer/README.md +259 -0
- package/runtime/scripts/amalgm-mcp/observer/apply.js +150 -0
- package/runtime/scripts/amalgm-mcp/observer/continuity.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/edges.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/index.js +1146 -0
- package/runtime/scripts/amalgm-mcp/observer/scan.js +387 -0
- package/runtime/scripts/amalgm-mcp/observer/store.js +262 -0
- package/runtime/scripts/amalgm-mcp/observer/verify.js +200 -0
- package/runtime/scripts/amalgm-mcp/observer/watch.js +62 -0
- package/runtime/scripts/amalgm-mcp/project-context/store.js +19 -87
- package/runtime/scripts/amalgm-mcp/registration/classify.js +33 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-cloud.js +456 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-content.js +345 -0
- package/runtime/scripts/amalgm-mcp/registration/index.js +752 -0
- package/runtime/scripts/amalgm-mcp/registration/refusal.js +42 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-followers.js +127 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-states.js +109 -0
- package/runtime/scripts/amalgm-mcp/registration/service.js +310 -0
- package/runtime/scripts/amalgm-mcp/registration/tree.js +178 -0
- package/runtime/scripts/amalgm-mcp/registry/evidence.js +826 -0
- package/runtime/scripts/amalgm-mcp/registry/index.js +666 -0
- package/runtime/scripts/amalgm-mcp/registry/store.js +290 -0
- package/runtime/scripts/amalgm-mcp/repocard/README.md +103 -0
- package/runtime/scripts/amalgm-mcp/repocard/apply.js +111 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture-worker.js +94 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture.js +149 -0
- package/runtime/scripts/amalgm-mcp/repocard/follow.js +310 -0
- package/runtime/scripts/amalgm-mcp/repocard/git.js +48 -0
- package/runtime/scripts/amalgm-mcp/repocard/index.js +48 -0
- package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
- package/runtime/scripts/amalgm-mcp/server/routes/entities.js +287 -0
- package/runtime/scripts/amalgm-mcp/server/routes/state.js +0 -13
- package/runtime/scripts/amalgm-mcp/state/attachments.js +11 -45
- package/runtime/scripts/amalgm-mcp/state/db.js +46 -215
- package/runtime/scripts/amalgm-mcp/state/docs.js +301 -1101
- package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +0 -99
- package/runtime/scripts/amalgm-mcp/state/mutations.js +130 -507
- package/runtime/scripts/amalgm-mcp/state/promotions.js +1 -21
- package/runtime/scripts/amalgm-mcp/state/replicas.js +13 -134
- package/runtime/scripts/amalgm-mcp/state/shared-rest.js +3 -46
- package/runtime/scripts/amalgm-mcp/tests/adapters.test.js +379 -0
- package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +0 -193
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.life.test.js +449 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.rig.js +107 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.watch.test.js +213 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/adapter.js +72 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/assert.js +497 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/bind.js +91 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/graph.js +1031 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/ledger.js +234 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/referee.js +118 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.oracle.test.js +1365 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.registry.test.js +967 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.rig.js +239 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.storm.test.js +486 -0
- package/runtime/scripts/amalgm-mcp/tests/fake-watch.js +35 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.links.test.js +289 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.rig.js +103 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.roots.test.js +505 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.storm.test.js +389 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.territory.test.js +845 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.test.js +657 -0
- package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +17 -32
- package/runtime/scripts/amalgm-mcp/tests/registration.service.test.js +256 -0
- package/runtime/scripts/amalgm-mcp/tests/registration.test.js +931 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.storm.test.js +284 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.test.js +371 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.storm.test.js +435 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.test.js +675 -0
- package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +0 -84
- package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +13 -376
- package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +0 -309
- package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +30 -758
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +3 -72
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +1 -89
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +24 -55
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +141 -424
- package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +3 -171
- package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +7 -220
- package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +236 -179
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +326 -2228
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +3 -5319
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +1 -191
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +17 -3140
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +740 -137
- package/runtime/scripts/amalgm-mcp/workspace/access-store.js +1 -5
- package/runtime/scripts/amalgm-mcp/workspace/card.js +11 -35
- package/runtime/scripts/amalgm-mcp/workspace/cards.js +14 -39
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +147 -587
- package/runtime/scripts/amalgm-mcp/workspace/git.js +34 -406
- package/runtime/scripts/amalgm-mcp/workspace/merge.js +181 -102
- package/runtime/scripts/amalgm-mcp/workspace/objects.js +56 -221
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +538 -1964
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +28 -49
- package/runtime/scripts/amalgm-mcp/workspace/store.js +4 -77
- package/runtime/scripts/amalgm-mcp/workspace/transition.js +192 -1734
- package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +1 -7
- package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +61 -73
- package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +6 -51
- package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +8 -79
- package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +146 -3461
- package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +71 -899
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +538 -370
- package/runtime/scripts/local-gateway.js +1 -0
- package/runtime/scripts/amalgm-mcp/state/doc-disk.js +0 -550
- package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +0 -399
- package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +0 -1
- package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +0 -24
- package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +0 -1999
- package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +0 -364
- package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +0 -185
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +0 -26
- package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +0 -119
- package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +0 -1446
- package/runtime/scripts/amalgm-mcp/workspace/content.js +0 -32
- package/runtime/scripts/amalgm-mcp/workspace/durable.js +0 -45
- package/runtime/scripts/amalgm-mcp/workspace/intent.js +0 -535
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +0 -31
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +0 -72
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +0 -165
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +0 -5
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +0 -2
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +0 -2201
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +0 -19
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +0 -33
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +0 -47
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +0 -43
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +0 -114
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +0 -84
- package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +0 -771
- package/runtime/scripts/amalgm-mcp/workspace/provenance.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +0 -100
- package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +0 -127
- package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +0 -12
- package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +0 -125
- package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +0 -76
- package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +0 -3766
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The adapter contract: what it means to serve an entity type.
|
|
5
|
+
*
|
|
6
|
+
* An adapter is how a KIND of entity rides the seven realtime rails —
|
|
7
|
+
* watch, detect, record, send, receive, apply, verify
|
|
8
|
+
* (docs/realtime-rails.md §2). The entity's `type` selects its adapter;
|
|
9
|
+
* the rails themselves never change shape per type.
|
|
10
|
+
*
|
|
11
|
+
* Axioms:
|
|
12
|
+
*
|
|
13
|
+
* 1. ONE ADAPTER PER TYPE, EVERY TYPE ASSIGNED. The alphabet belongs
|
|
14
|
+
* to the registry (`ENTITY_TYPES`); the adapter map covers it
|
|
15
|
+
* exactly — no type unassigned, no type claimed twice. Adapters
|
|
16
|
+
* read the alphabet, never restate it, so the map cannot drift
|
|
17
|
+
* from the registry that enforces it. Assigned is not yet
|
|
18
|
+
* operational: operational is earned rail by rail, and absence is
|
|
19
|
+
* honest.
|
|
20
|
+
*
|
|
21
|
+
* 2. ADAPTERS OWN BEHAVIOR; THE REGISTRY OWNS IDENTITY. An adapter
|
|
22
|
+
* decides how truth is read and applied — never which UUID it
|
|
23
|
+
* belongs to. A lawful type transition that crosses adapters
|
|
24
|
+
* (folder becoming repo.git) is a handoff on the same UUID, never
|
|
25
|
+
* a rebirth; and the kinds that transition into one another on a
|
|
26
|
+
* single piece of ground (file ↔ link) live inside ONE adapter,
|
|
27
|
+
* so their transitions never even change hands.
|
|
28
|
+
*
|
|
29
|
+
* 3. ONE SOCKET PER RAIL. A rail's callable surfaces — their names
|
|
30
|
+
* and their shapes — are ratified HERE, once, for every adapter
|
|
31
|
+
* (`SOCKETS`). A generic caller performs the same conceptual
|
|
32
|
+
* operation through the same call on every type; it never branches
|
|
33
|
+
* on an adapter-specific function name. An adapter binds a rail's
|
|
34
|
+
* whole socket or binds the rail not at all; a rail whose socket
|
|
35
|
+
* is not yet ratified cannot be bound by anyone. The sockets are
|
|
36
|
+
* derived from the real consumer — the registration boundary and
|
|
37
|
+
* its evidence lenses — never invented ahead of one, and never for
|
|
38
|
+
* symmetry (axiom 7 decides which rails earn one at all).
|
|
39
|
+
*
|
|
40
|
+
* 4. A TRUTH READ NEVER LIES. Every assigned type names its source of
|
|
41
|
+
* truth in one sentence, and reading it produces exactly one of
|
|
42
|
+
* three honest answers: PRESENT (the complete truth behind a
|
|
43
|
+
* version — equal versions mean equal truth, and equal truth is
|
|
44
|
+
* silence), ABSENT (the ground genuinely is not there — a fact,
|
|
45
|
+
* not an error), or UNSETTLED (changing or unreadable right now;
|
|
46
|
+
* read again). Partial truth is never passed off as complete, and
|
|
47
|
+
* nothing is silently omitted — a read is complete or it is
|
|
48
|
+
* unsettled.
|
|
49
|
+
*
|
|
50
|
+
* 5. RAILS ARE HONEST. A rail an adapter binds is built and proven;
|
|
51
|
+
* an unbound rail is absent — never stubbed, never defaulted, so
|
|
52
|
+
* nothing downstream can mistake a placeholder for a capability.
|
|
53
|
+
*
|
|
54
|
+
* 6. THE CONTRACT IS FROZEN GROUND. A descriptor is immutable from
|
|
55
|
+
* the moment it loads; behavior changes by shipping a new adapter,
|
|
56
|
+
* never by mutating a live one.
|
|
57
|
+
*
|
|
58
|
+
* 7. THE BASE ORCHESTRATES; THE ADAPTER SUPPLIES TYPE BEHAVIOR. The
|
|
59
|
+
* universal machinery owns orchestration and identity: what is
|
|
60
|
+
* enrolled, when work happens, retries, routing, and which UUID
|
|
61
|
+
* owns the result. An adapter supplies only the type-specific
|
|
62
|
+
* behavior a real rail requires. A rail therefore earns a socket
|
|
63
|
+
* only when a real consumer proves that behavior differs by type —
|
|
64
|
+
* never for symmetry. Behavior that survives an adapter handoff
|
|
65
|
+
* unchanged was never that adapter's behavior.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
const RAILS = Object.freeze([
|
|
69
|
+
'watch', 'detect', 'record', 'send', 'receive', 'apply', 'verify',
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The ratified sockets — for each rail that has one, the exact surfaces
|
|
74
|
+
* every binding adapter implements. Same names, same shapes, every
|
|
75
|
+
* type. ONE socket is ratified today, derived from what production
|
|
76
|
+
* detection actually asks of a type (the observer's content decision,
|
|
77
|
+
* the registration boundary's lenses): read the current truth and
|
|
78
|
+
* compare — equal truth is silence.
|
|
79
|
+
*
|
|
80
|
+
* detect.truth(path) →
|
|
81
|
+
* { truth: 'present', version, state } the complete current
|
|
82
|
+
* truth, fingerprinted — equal versions mean equal truth
|
|
83
|
+
* { truth: 'absent' } the ground genuinely is
|
|
84
|
+
* not there — a fact, not an error
|
|
85
|
+
* { truth: 'unsettled', reason } changing or unreadable
|
|
86
|
+
* right now; read again — never a partial answer
|
|
87
|
+
*
|
|
88
|
+
* No other rail has a socket, and WATCH AND RECORD EARN NONE AT THIS
|
|
89
|
+
* BAR — adjudicated 2026-08-03 under axiom 7, on the filesystem
|
|
90
|
+
* alphabet we actually serve. The observer's doorbell is chosen by
|
|
91
|
+
* physical shape (one piece of ground watches its parent; a tree
|
|
92
|
+
* watches itself): identical for a folder and a repo, which are
|
|
93
|
+
* different adapters; different inside one adapter's own types; and
|
|
94
|
+
* untouched when a live root crosses `files` ↔ `repo` on the same
|
|
95
|
+
* UUID. It survives the adapter handoff, so it was never adapter
|
|
96
|
+
* behavior. Record is the registry's by axiom 2 — identity,
|
|
97
|
+
* hierarchy, versions, lifecycle. A type whose truth is NOT filesystem
|
|
98
|
+
* ground — an API, a database — would be new evidence, and axiom 7
|
|
99
|
+
* decides it then, on that consumer, exactly as it decided this one.
|
|
100
|
+
*
|
|
101
|
+
* Apply and verify remain what they are today: proven MODULE surfaces
|
|
102
|
+
* (repocard's materialize-and-prove and re-read-and-compare), joining
|
|
103
|
+
* the contract only when a real multi-type consumer — the
|
|
104
|
+
* Send/Receive rails — exists to derive their socket from, by the same
|
|
105
|
+
* law that made detect's. Send is where the next socket is expected,
|
|
106
|
+
* because there the cargo itself differs by type: bytes, membership,
|
|
107
|
+
* written target, repository state. A socket is ratified from a real
|
|
108
|
+
* consumer or it is not ratified at all.
|
|
109
|
+
*/
|
|
110
|
+
const SOCKETS = Object.freeze({
|
|
111
|
+
detect: Object.freeze(['truth']),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Convict a malformed adapter descriptor loudly; return it unchanged
|
|
116
|
+
* when lawful. The shape is deliberately tiny:
|
|
117
|
+
*
|
|
118
|
+
* name — the adapter's one name
|
|
119
|
+
* types — { entityType: 'one-sentence source of truth', ... }
|
|
120
|
+
* rails — { railName: { socket surfaces, complete }, ... } — only
|
|
121
|
+
* rails whose ratified socket the adapter fully implements
|
|
122
|
+
*/
|
|
123
|
+
function assertAdapter(adapter) {
|
|
124
|
+
const where = adapter && adapter.name ? `adapter ${JSON.stringify(adapter.name)}` : 'adapter';
|
|
125
|
+
if (!adapter || typeof adapter !== 'object' || !Object.isFrozen(adapter)) {
|
|
126
|
+
throw new Error(`${where}: a descriptor is a frozen object`);
|
|
127
|
+
}
|
|
128
|
+
if (typeof adapter.name !== 'string' || adapter.name.length === 0) {
|
|
129
|
+
throw new Error('adapter: name is a non-empty string');
|
|
130
|
+
}
|
|
131
|
+
const { types, rails } = adapter;
|
|
132
|
+
if (!types || typeof types !== 'object' || !Object.isFrozen(types) || Object.keys(types).length === 0) {
|
|
133
|
+
throw new Error(`${where}: types is a frozen, non-empty map of entity type → declared truth`);
|
|
134
|
+
}
|
|
135
|
+
for (const [type, truth] of Object.entries(types)) {
|
|
136
|
+
if (typeof truth !== 'string' || truth.length === 0) {
|
|
137
|
+
throw new Error(`${where}: type ${JSON.stringify(type)} must declare its source of truth in one sentence`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!rails || typeof rails !== 'object' || !Object.isFrozen(rails)) {
|
|
141
|
+
throw new Error(`${where}: rails is a frozen map (empty is honest; a stub is not)`);
|
|
142
|
+
}
|
|
143
|
+
for (const [rail, surfaces] of Object.entries(rails)) {
|
|
144
|
+
if (!RAILS.includes(rail)) {
|
|
145
|
+
throw new Error(`${where}: ${JSON.stringify(rail)} is not a rail — the rails are ${RAILS.join(', ')}`);
|
|
146
|
+
}
|
|
147
|
+
const socket = SOCKETS[rail];
|
|
148
|
+
if (!socket) {
|
|
149
|
+
throw new Error(`${where}: rail ${rail} has no ratified socket yet — a socket is ratified in the contract before anyone binds it`);
|
|
150
|
+
}
|
|
151
|
+
if (!surfaces || typeof surfaces !== 'object' || !Object.isFrozen(surfaces)) {
|
|
152
|
+
throw new Error(`${where}: rail ${rail} must bind a frozen map of its socket's surfaces`);
|
|
153
|
+
}
|
|
154
|
+
const bound = Object.keys(surfaces).sort();
|
|
155
|
+
if (bound.join(',') !== [...socket].sort().join(',')) {
|
|
156
|
+
throw new Error(`${where}: rail ${rail} binds [${bound.join(', ')}] but its socket is [${socket.join(', ')}] — an adapter implements a rail's whole socket or binds the rail not at all`);
|
|
157
|
+
}
|
|
158
|
+
for (const [surface, fn] of Object.entries(surfaces)) {
|
|
159
|
+
if (typeof fn !== 'function') {
|
|
160
|
+
throw new Error(`${where}: ${rail}.${surface} must be a function — an unbuilt surface is left absent, never stubbed`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return adapter;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Build the one type → adapter map, convicting every violation of
|
|
169
|
+
* axiom 1: an adapter claiming a type outside the alphabet, two
|
|
170
|
+
* adapters claiming one type, or an alphabet type left unassigned.
|
|
171
|
+
* Pure — callers own where the alphabet and adapters come from.
|
|
172
|
+
*/
|
|
173
|
+
function buildTypeMap(adapters, alphabet) {
|
|
174
|
+
const known = new Set(alphabet);
|
|
175
|
+
const byType = new Map();
|
|
176
|
+
for (const adapter of adapters) {
|
|
177
|
+
assertAdapter(adapter);
|
|
178
|
+
for (const type of Object.keys(adapter.types)) {
|
|
179
|
+
if (!known.has(type)) {
|
|
180
|
+
throw new Error(`adapter ${adapter.name} claims ${JSON.stringify(type)}, which is not in the registry's alphabet`);
|
|
181
|
+
}
|
|
182
|
+
const holder = byType.get(type);
|
|
183
|
+
if (holder) {
|
|
184
|
+
throw new Error(`entity type ${JSON.stringify(type)} is claimed by both ${holder.name} and ${adapter.name} — one adapter per type`);
|
|
185
|
+
}
|
|
186
|
+
byType.set(type, adapter);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (const type of known) {
|
|
190
|
+
if (!byType.has(type)) {
|
|
191
|
+
throw new Error(`entity type ${JSON.stringify(type)} has no adapter — every type is assigned`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return byType;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = { RAILS, SOCKETS, assertAdapter, buildTypeMap };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The filesystem adapter: every kind whose truth IS ordinary filesystem
|
|
5
|
+
* ground — folders, files, symlinks — served by ONE adapter on purpose.
|
|
6
|
+
* These kinds transition into one another on the same UUID (a file
|
|
7
|
+
* becomes a link on the same slot, text becomes binary, a workspace
|
|
8
|
+
* sheds to folder on adoption), and one adapter means those transitions
|
|
9
|
+
* never cross a behavioral boundary where identity could be fumbled.
|
|
10
|
+
*
|
|
11
|
+
* Constructs — agents, apps, automations, tools, skills — are NOT
|
|
12
|
+
* types here or anywhere: physically they are folders and files with a
|
|
13
|
+
* known structure, served by this adapter like any other ground, and
|
|
14
|
+
* their construct meaning is layered above the physical type. If one
|
|
15
|
+
* ever truly travels or applies differently, promoting it is a
|
|
16
|
+
* deliberate future ratification — never a default.
|
|
17
|
+
*
|
|
18
|
+
* One rail bound: detect, through the ratified socket, and production
|
|
19
|
+
* consumes it — the observer's content reads (observer/index.js
|
|
20
|
+
* scanContent and safeContent) speak through this exact function. The
|
|
21
|
+
* content primitive lives HERE: one detector, owned by the adapter, and
|
|
22
|
+
* the dependency points one way — the observer reads truth through the
|
|
23
|
+
* socket, never the other way around. Watch and record stay
|
|
24
|
+
* orchestrated in the observer until their sockets are ratified from
|
|
25
|
+
* that seam; apply and verify for filesystem ground ride the cloud
|
|
26
|
+
* rails. All honestly absent.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const crypto = require('crypto');
|
|
30
|
+
const fs = require('fs');
|
|
31
|
+
const path = require('path');
|
|
32
|
+
|
|
33
|
+
const { lookFailed } = require('./truth');
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A file's bytes, read ONCE, answering both questions they support:
|
|
37
|
+
* their sha256 and whether any of them is a NUL. A hash and a type that
|
|
38
|
+
* came from different reads can disagree — the file may change between
|
|
39
|
+
* them — and a type is a claim ABOUT the bytes behind the hash, so the
|
|
40
|
+
* two are one answer or they are not honest. Streaming, so large files
|
|
41
|
+
* never load whole into memory; the NUL is looked for chunk by chunk.
|
|
42
|
+
*/
|
|
43
|
+
function readFile(absPath) {
|
|
44
|
+
const hash = crypto.createHash('sha256');
|
|
45
|
+
let binary = false;
|
|
46
|
+
const fd = fs.openSync(absPath, 'r');
|
|
47
|
+
try {
|
|
48
|
+
const buffer = Buffer.allocUnsafe(1 << 20);
|
|
49
|
+
let read;
|
|
50
|
+
while ((read = fs.readSync(fd, buffer, 0, buffer.length)) > 0) {
|
|
51
|
+
const chunk = buffer.subarray(0, read);
|
|
52
|
+
hash.update(chunk);
|
|
53
|
+
if (!binary && chunk.includes(0)) binary = true;
|
|
54
|
+
}
|
|
55
|
+
} finally {
|
|
56
|
+
fs.closeSync(fd);
|
|
57
|
+
}
|
|
58
|
+
return { sha256: hash.digest('hex'), binary };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const present = (parts, state) => ({
|
|
62
|
+
truth: 'present',
|
|
63
|
+
version: crypto.createHash('sha256').update(JSON.stringify(parts)).digest('hex'),
|
|
64
|
+
state,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const KIND = (stat) => (stat.isSymbolicLink() ? 'link' : stat.isDirectory() ? 'dir' : 'file');
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The detect socket: the ground's whole current truth, by its physical
|
|
71
|
+
* kind — the caller never says which kind it expects, disk decides
|
|
72
|
+
* (exactly how a file becomes a link on the same slot without anyone
|
|
73
|
+
* branching).
|
|
74
|
+
*
|
|
75
|
+
* link → its written target text, exactly as disk holds it
|
|
76
|
+
* dir → its direct membership: sorted names with their kinds
|
|
77
|
+
* (folder truth is membership; content belongs to members)
|
|
78
|
+
* file → its bytes, as a streaming sha256, plus whether any of
|
|
79
|
+
* those same bytes is a NUL — one read, so the fingerprint
|
|
80
|
+
* and the text/bytes claim can never describe different ground
|
|
81
|
+
*
|
|
82
|
+
* Three honest answers, never a fourth: ground that is not there is
|
|
83
|
+
* ABSENT — a fact, not an error — and ground that moved between looks
|
|
84
|
+
* or refuses to be read completely is UNSETTLED: the whole read
|
|
85
|
+
* succeeds or the whole read says "read again"; a member is never
|
|
86
|
+
* silently dropped from a membership that claims to be complete.
|
|
87
|
+
*
|
|
88
|
+
* Physical truth only: enrollment policy — what deserves to be shared —
|
|
89
|
+
* is the caller's injected question, asked before this one.
|
|
90
|
+
*/
|
|
91
|
+
function truth(absPath) {
|
|
92
|
+
let stat;
|
|
93
|
+
try {
|
|
94
|
+
stat = fs.lstatSync(absPath);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return lookFailed(error);
|
|
97
|
+
}
|
|
98
|
+
const kind = KIND(stat);
|
|
99
|
+
try {
|
|
100
|
+
if (kind === 'link') {
|
|
101
|
+
const target = fs.readlinkSync(absPath);
|
|
102
|
+
return present(['link', target], { kind, target });
|
|
103
|
+
}
|
|
104
|
+
if (kind === 'dir') {
|
|
105
|
+
const members = fs.readdirSync(absPath).sort().map((name) => ({
|
|
106
|
+
name, kind: KIND(fs.lstatSync(path.join(absPath, name))),
|
|
107
|
+
}));
|
|
108
|
+
return present(['dir', members], { kind, members });
|
|
109
|
+
}
|
|
110
|
+
// `binary` rides the state but NOT the version: it is derived from
|
|
111
|
+
// the very bytes the sha256 fingerprints, so it can never vary
|
|
112
|
+
// while the hash holds. Adding it would fingerprint the same fact twice.
|
|
113
|
+
const { sha256, binary } = readFile(absPath);
|
|
114
|
+
return present(['file', sha256], { kind, sha256, binary });
|
|
115
|
+
} catch (error) {
|
|
116
|
+
// The ground changed between looks: a complete read was impossible.
|
|
117
|
+
return { truth: 'unsettled', reason: error.code ?? 'changed-mid-read' };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = Object.freeze({
|
|
122
|
+
name: 'filesystem',
|
|
123
|
+
|
|
124
|
+
types: Object.freeze({
|
|
125
|
+
workspace: 'its active direct membership — folder truth; the collaboration boundary it marks is authority meaning, layered above detection',
|
|
126
|
+
folder: 'its active direct membership',
|
|
127
|
+
'file.text': 'its bytes',
|
|
128
|
+
'file.binary': 'its bytes',
|
|
129
|
+
link: 'its written target text — the literal address, never what it resolves to',
|
|
130
|
+
}),
|
|
131
|
+
|
|
132
|
+
rails: Object.freeze({
|
|
133
|
+
detect: Object.freeze({ truth }),
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Git adapter: a repository is ONE entity — its committed state
|
|
5
|
+
* (the card) plus its working checkpoint — never thousands of unrelated
|
|
6
|
+
* files. One logical operation (a checkout, a rebase) is one change on
|
|
7
|
+
* one UUID, not meaningless bulk churn.
|
|
8
|
+
*
|
|
9
|
+
* One rail bound: detect, through the ratified socket. Truth
|
|
10
|
+
* translates repocard's proven capture into the three-state answer:
|
|
11
|
+
* ground with no repository on it is ABSENT (a vanished .git is how a
|
|
12
|
+
* repository leaves an address — the territory law), a repo caught
|
|
13
|
+
* mid-operation is UNSETTLED (capture's busy markers — MERGE_HEAD, an
|
|
14
|
+
* active rebase — mean the truth is changing under the read), and a
|
|
15
|
+
* present truth's version is the stateId: one fingerprint over cardId
|
|
16
|
+
* and checkpointId. Detection's truth is deliberately bundle-free:
|
|
17
|
+
* comparing is constant work and packing is neither cheap nor
|
|
18
|
+
* deterministic, so the TRANSPORTED head — card with its bundle of
|
|
19
|
+
* objects — is the Send rail's packaging act, not detect's.
|
|
20
|
+
*
|
|
21
|
+
* Repocard's apply and verify remain proven MODULE surfaces
|
|
22
|
+
* (materialize-and-prove, re-read-and-compare); they join the contract
|
|
23
|
+
* when a real multi-type Apply consumer — the Send/Receive rails —
|
|
24
|
+
* exists to derive their socket from. Watching a repo (the follower,
|
|
25
|
+
* the capture worker) stays orchestrated by the observer until the
|
|
26
|
+
* watch socket is ratified from that seam.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const fs = require('fs');
|
|
30
|
+
const path = require('path');
|
|
31
|
+
|
|
32
|
+
const { capture } = require('../repocard');
|
|
33
|
+
const { lookFailed } = require('./truth');
|
|
34
|
+
|
|
35
|
+
/** The detect socket over capture: same call, same shape as every type. */
|
|
36
|
+
function truth(repoPath) {
|
|
37
|
+
try {
|
|
38
|
+
fs.lstatSync(repoPath);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return lookFailed(error);
|
|
41
|
+
}
|
|
42
|
+
// No .git (directory or worktree file) means no repository holds this
|
|
43
|
+
// ground: the REPOSITORY is absent, whatever else stands at the path.
|
|
44
|
+
// The look is error-preserving: a .git that cannot be inspected still
|
|
45
|
+
// exists — that read is unsettled, never absence.
|
|
46
|
+
try {
|
|
47
|
+
fs.lstatSync(path.join(repoPath, '.git'));
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return lookFailed(error);
|
|
50
|
+
}
|
|
51
|
+
let captured;
|
|
52
|
+
try {
|
|
53
|
+
captured = capture(repoPath, { withBundle: false });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// A git subprocess that failed is a repository that could not be
|
|
56
|
+
// inspected — unsettled, never a fourth answer. Only failures git
|
|
57
|
+
// itself reported are classified; a programming error propagates.
|
|
58
|
+
if (!error.gitFailed) throw error;
|
|
59
|
+
return { truth: 'unsettled', reason: error.gitFailed };
|
|
60
|
+
}
|
|
61
|
+
if (captured.busy) return { truth: 'unsettled', reason: captured.busy };
|
|
62
|
+
return {
|
|
63
|
+
truth: 'present',
|
|
64
|
+
version: captured.stateId,
|
|
65
|
+
state: {
|
|
66
|
+
card: captured.card,
|
|
67
|
+
checkpoint: captured.checkpoint,
|
|
68
|
+
cardId: captured.cardId,
|
|
69
|
+
checkpointId: captured.checkpointId,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = Object.freeze({
|
|
75
|
+
name: 'git',
|
|
76
|
+
|
|
77
|
+
types: Object.freeze({
|
|
78
|
+
'repo.git': 'its card — the committed state — plus its working checkpoint',
|
|
79
|
+
}),
|
|
80
|
+
|
|
81
|
+
rails: Object.freeze({
|
|
82
|
+
detect: Object.freeze({ truth }),
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The adapter layer's one door. Three adapters cover the registry's
|
|
5
|
+
* whole physical alphabet:
|
|
6
|
+
*
|
|
7
|
+
* filesystem — workspace, folder, file.text, file.binary, link:
|
|
8
|
+
* ordinary filesystem ground, one adapter so same-ground
|
|
9
|
+
* type transitions never change hands (constructs —
|
|
10
|
+
* agents, automations, and kin — are this ground too,
|
|
11
|
+
* their meaning layered above the physical type)
|
|
12
|
+
* git — repo.git: card + checkpoint as one entity
|
|
13
|
+
* reference — reference: a UUID-targeting managed link (assigned,
|
|
14
|
+
* not yet operational)
|
|
15
|
+
*
|
|
16
|
+
* The map is built against the registry's published alphabet at load —
|
|
17
|
+
* an unassigned type, a double claim, or a type the registry doesn't
|
|
18
|
+
* know refuses to load at all, so a drifted map cannot exist at
|
|
19
|
+
* runtime. A generic caller selects with `adapterFor(type)` and speaks
|
|
20
|
+
* only the ratified sockets — the same calls for every type, never an
|
|
21
|
+
* adapter-specific name. Every surface — SDK, CLI, MCP, REST — reaches
|
|
22
|
+
* type behavior through this door; none re-implements it.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const { ENTITY_TYPES } = require('../registry');
|
|
26
|
+
const { RAILS, SOCKETS, assertAdapter, buildTypeMap } = require('./contract');
|
|
27
|
+
const filesystem = require('./filesystem');
|
|
28
|
+
const git = require('./git');
|
|
29
|
+
const reference = require('./reference');
|
|
30
|
+
|
|
31
|
+
const ADAPTERS = Object.freeze([filesystem, git, reference]);
|
|
32
|
+
const byType = buildTypeMap(ADAPTERS, ENTITY_TYPES);
|
|
33
|
+
|
|
34
|
+
/** The one selection: entity type → its adapter. Unknown types convict. */
|
|
35
|
+
function adapterFor(type) {
|
|
36
|
+
const adapter = byType.get(type);
|
|
37
|
+
if (!adapter) {
|
|
38
|
+
throw new Error(`no adapter serves entity type ${JSON.stringify(type)} — the alphabet is ${ENTITY_TYPES.join(', ')}`);
|
|
39
|
+
}
|
|
40
|
+
return adapter;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { ADAPTERS, RAILS, SOCKETS, adapterFor, assertAdapter, buildTypeMap };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The managed-reference adapter: Amalgm's OWN link kind, never conflated
|
|
5
|
+
* with a filesystem symlink. Its payload is the target entity's UUID —
|
|
6
|
+
* identity, not an address — and its local rendering as an ordinary
|
|
7
|
+
* symlink is DERIVED state: canonical-truth-projected on every boundary
|
|
8
|
+
* pass, missing renderings recreated, drifted ones rewritten, real
|
|
9
|
+
* ground on the slot never overwritten, dangling honestly while the
|
|
10
|
+
* target is unavailable.
|
|
11
|
+
*
|
|
12
|
+
* ASSIGNED, NOT YET OPERATIONAL — and that absence is the honest
|
|
13
|
+
* truth: no rail is bound because the projection lives inside the
|
|
14
|
+
* registration boundary today (registration/index.js maintains
|
|
15
|
+
* renderings as part of every pass), proven by the wall but not yet
|
|
16
|
+
* standing as socket surfaces. A reference's truth is registry
|
|
17
|
+
* identity, not disk — its detect socket is ratified from that seam
|
|
18
|
+
* when the boundary's projection is extracted, never invented ahead
|
|
19
|
+
* of it. Send-side resolution of the target UUID on other machines
|
|
20
|
+
* rides the cloud rails.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
module.exports = Object.freeze({
|
|
24
|
+
name: 'reference',
|
|
25
|
+
|
|
26
|
+
types: Object.freeze({
|
|
27
|
+
reference: "its target entity's UUID — identity, not an address; the rendered symlink is derived state",
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
rails: Object.freeze({}),
|
|
31
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The three-state vocabulary's one shared judgment: what a FAILED look
|
|
5
|
+
* at ground means. ENOENT and ENOTDIR are the filesystem itself saying
|
|
6
|
+
* "not there" — honest ABSENCE, a fact. Every other failure means the
|
|
7
|
+
* LOOK failed, not the ground — UNSETTLED: no conclusion, read again.
|
|
8
|
+
*
|
|
9
|
+
* Every truth reader classifies through this one function so no reader
|
|
10
|
+
* can invent its own idea of absence: mistaking "could not look" for
|
|
11
|
+
* "not there" is the exact lie the axiom forbids. Only present or
|
|
12
|
+
* absent may produce a conclusion.
|
|
13
|
+
*/
|
|
14
|
+
const lookFailed = (error) =>
|
|
15
|
+
error.code === 'ENOENT' || error.code === 'ENOTDIR'
|
|
16
|
+
? { truth: 'absent' }
|
|
17
|
+
: { truth: 'unsettled', reason: error.code };
|
|
18
|
+
|
|
19
|
+
module.exports = { lookFailed };
|
|
@@ -599,26 +599,18 @@ function getCookieCloudBootstrap(options = {}) {
|
|
|
599
599
|
|
|
600
600
|
function installCookieCloudBootstrap(input = {}, options = {}) {
|
|
601
601
|
const database = options.database || require('../state/db').openLocalDb();
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
contract: COOKIE_CLOUD_CONTRACT,
|
|
605
|
-
schemaVersion: COOKIE_JAR_SCHEMA_VERSION,
|
|
606
|
-
});
|
|
607
|
-
const expectedResourceId = privateCookieResourceId(
|
|
608
|
-
require('../workspace/identity').userId(),
|
|
609
|
-
);
|
|
610
|
-
if (!expectedResourceId || envelope.resourceId !== expectedResourceId) {
|
|
602
|
+
const replica = cookieCloudReplica({ database });
|
|
603
|
+
if (!replica || input.resourceId !== replica.resourceId) {
|
|
611
604
|
throw new Error('Private cookie resource does not belong to this runtime user');
|
|
612
605
|
}
|
|
606
|
+
if (input.contract !== COOKIE_CLOUD_CONTRACT || Number(input.schemaVersion) !== COOKIE_JAR_SCHEMA_VERSION) {
|
|
607
|
+
throw new Error('Private cookie resource contract changed');
|
|
608
|
+
}
|
|
613
609
|
const bytes = Buffer.from(String(input.snapshotBase64 || ''), 'base64');
|
|
614
610
|
const checksum = crypto.createHash('sha256').update(bytes).digest('hex');
|
|
615
611
|
if (checksum !== input.snapshotChecksum) throw new Error('Private cookie snapshot checksum mismatch');
|
|
616
612
|
const local = loadInternalCookieJar();
|
|
617
613
|
const remote = normalizeCloudSnapshot(JSON.parse(bytes.toString('utf8')));
|
|
618
|
-
if (remote.version !== envelope.snapshotVersion) {
|
|
619
|
-
throw new Error('Private cookie snapshot version mismatch');
|
|
620
|
-
}
|
|
621
|
-
const replica = cookieCloudReplica({ database });
|
|
622
614
|
const pending = require('../state/mutations').listPendingCloudMutations(replica.resourceId, { database });
|
|
623
615
|
const pendingKeys = new Set(pending.map((mutation) => mutation.operation?.key).filter(Boolean));
|
|
624
616
|
// A base snapshot behind the official head is not the current cloud jar.
|
|
@@ -626,62 +618,28 @@ function installCookieCloudBootstrap(input = {}, options = {}) {
|
|
|
626
618
|
// as pre-cloud work and create an echo loop on every reconnect. The gateway
|
|
627
619
|
// normally compacts private resources to head before bootstrap; this guard
|
|
628
620
|
// makes the runtime fail closed if an older gateway sends a stale base.
|
|
629
|
-
const snapshotIsCurrent =
|
|
621
|
+
const snapshotIsCurrent = Number(input.snapshotVersion) >= Number(input.headVersion);
|
|
630
622
|
const bootstrapOperations = snapshotIsCurrent
|
|
631
623
|
? localBootstrapOperations(local, remote, pendingKeys)
|
|
632
624
|
: [];
|
|
633
625
|
|
|
634
|
-
if (!snapshotIsCurrent) {
|
|
635
|
-
// A stale prefix is useful only as an identity check for a prefix this
|
|
636
|
-
// machine has already applied. Advancing to a newer stale prefix without
|
|
637
|
-
// installing its bytes would make the cursor lie; installing it would
|
|
638
|
-
// overwrite local legacy state before a head snapshot can classify it.
|
|
639
|
-
const currentReplica = replicas.refreshReplicaAuthority({
|
|
640
|
-
resourceId: replica.resourceId,
|
|
641
|
-
authorityEpoch: envelope.authorityEpoch,
|
|
642
|
-
headVersion: envelope.headVersion,
|
|
643
|
-
contract: COOKIE_CLOUD_CONTRACT,
|
|
644
|
-
schemaVersion: COOKIE_JAR_SCHEMA_VERSION,
|
|
645
|
-
}, { database });
|
|
646
|
-
return {
|
|
647
|
-
replica: currentReplica,
|
|
648
|
-
jar: readCookieJar(),
|
|
649
|
-
bootstrappedMutations: 0,
|
|
650
|
-
bootstrapDeferred: true,
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// Legacy local values become durable mutations before the official
|
|
655
|
-
// snapshot can replace the physical jar. If the process dies after the
|
|
656
|
-
// replacement, restart can replay these rows; in-memory bootstrapOperations
|
|
657
|
-
// are never the only copy of user state.
|
|
658
|
-
let currentReplica = replica;
|
|
659
|
-
for (const operation of bootstrapOperations) {
|
|
660
|
-
commitCookieOperation(operation, currentReplica, { database });
|
|
661
|
-
currentReplica = cookieCloudReplica({ database });
|
|
662
|
-
}
|
|
663
|
-
const durablePending = require('../state/mutations')
|
|
664
|
-
.listPendingCloudMutations(replica.resourceId, { database });
|
|
665
|
-
|
|
666
626
|
persistCloudSnapshot({
|
|
667
627
|
schemaVersion: COOKIE_JAR_SCHEMA_VERSION,
|
|
668
628
|
version: remote.version,
|
|
669
629
|
records: [...remote.records.values()],
|
|
670
630
|
tombstones: [...remote.tombstones.values()],
|
|
671
631
|
});
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
snapshotChecksum: checksum,
|
|
679
|
-
authorityEpoch: envelope.authorityEpoch,
|
|
632
|
+
const { advanceReplicaSnapshot, refreshReplicaAuthority } = require('../state/replicas');
|
|
633
|
+
advanceReplicaSnapshot({
|
|
634
|
+
resourceId: replica.resourceId,
|
|
635
|
+
snapshotVersion: Number(input.snapshotVersion) || 0,
|
|
636
|
+
snapshotChecksum: checksum,
|
|
637
|
+
authorityEpoch: Number(input.authorityEpoch) || 1,
|
|
680
638
|
}, { database });
|
|
681
|
-
|
|
639
|
+
refreshReplicaAuthority({
|
|
682
640
|
resourceId: replica.resourceId,
|
|
683
|
-
authorityEpoch:
|
|
684
|
-
headVersion:
|
|
641
|
+
authorityEpoch: Number(input.authorityEpoch) || 1,
|
|
642
|
+
headVersion: Number(input.headVersion) || 0,
|
|
685
643
|
contract: COOKIE_CLOUD_CONTRACT,
|
|
686
644
|
schemaVersion: COOKIE_JAR_SCHEMA_VERSION,
|
|
687
645
|
}, { database });
|
|
@@ -689,18 +647,12 @@ function installCookieCloudBootstrap(input = {}, options = {}) {
|
|
|
689
647
|
// Snapshot installation never discards local work. Already-journaled edits
|
|
690
648
|
// replay from their durable rows; pre-cloud legacy entries become ordinary
|
|
691
649
|
// new mutations through the same commit path.
|
|
692
|
-
for (const mutation of
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
mutation.mutationId,
|
|
698
|
-
cookieEvent(mutation.operation, mutation.mutationId),
|
|
699
|
-
{ database },
|
|
700
|
-
);
|
|
701
|
-
}
|
|
650
|
+
for (const mutation of pending) materializeCookieOperation(mutation.operation);
|
|
651
|
+
let currentReplica = cookieCloudReplica({ database });
|
|
652
|
+
for (const operation of bootstrapOperations) {
|
|
653
|
+
commitCookieOperation(operation, currentReplica, { database });
|
|
654
|
+
currentReplica = cookieCloudReplica({ database });
|
|
702
655
|
}
|
|
703
|
-
currentReplica = cookieCloudReplica({ database });
|
|
704
656
|
return {
|
|
705
657
|
replica: currentReplica,
|
|
706
658
|
jar: readCookieJar(),
|
|
@@ -127,6 +127,10 @@ async function boot() {
|
|
|
127
127
|
ensureAppsDirs();
|
|
128
128
|
startProjectContextService();
|
|
129
129
|
|
|
130
|
+
// The authenticated tunnel coordinates user-home bootstrap after it asks
|
|
131
|
+
// the gateway whether this user already has a cloud UUID graph. Runtime
|
|
132
|
+
// startup must not mint a competing graph before that answer arrives.
|
|
133
|
+
|
|
130
134
|
require('./state/ports').startPortsBridge();
|
|
131
135
|
require('./workspace/cards').startRepoCardService();
|
|
132
136
|
require('./workspace/tree-store').startTreeEnrollmentService();
|