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
|
@@ -17,6 +17,10 @@ const CLAIM_LIMIT = 16;
|
|
|
17
17
|
const CLAIM_LEASE_MS = 30_000;
|
|
18
18
|
const RETRY_MS = 1_000;
|
|
19
19
|
const REAUTHORIZE_MS = 30_000;
|
|
20
|
+
const REPO_OBJECT_REQUEST_TIMEOUT_MS = 15_000;
|
|
21
|
+
const COOKIE_PRIVATE_CONTRACT = 'browser-cookie-jar@1';
|
|
22
|
+
const USER_HOME_PRIVATE_CONTRACT = 'amalgm-user-home@1';
|
|
23
|
+
const ENTITY_REGISTRY_PRIVATE_CONTRACT = 'amalgm-entity-registry@1';
|
|
20
24
|
|
|
21
25
|
function createSharedRealtimeTunnel({
|
|
22
26
|
send,
|
|
@@ -24,8 +28,11 @@ function createSharedRealtimeTunnel({
|
|
|
24
28
|
localPort,
|
|
25
29
|
localHeaders,
|
|
26
30
|
warn,
|
|
31
|
+
repoObjectRequestTimeoutMs = REPO_OBJECT_REQUEST_TIMEOUT_MS,
|
|
27
32
|
}) {
|
|
28
33
|
const mutationInflight = new Map();
|
|
34
|
+
const entityMutationInflight = new Map();
|
|
35
|
+
const entityContentInflight = new Map();
|
|
29
36
|
const promotionInflight = new Map();
|
|
30
37
|
const attachmentInflight = new Map();
|
|
31
38
|
const privateEnsureInflight = new Map();
|
|
@@ -37,19 +44,28 @@ function createSharedRealtimeTunnel({
|
|
|
37
44
|
let enabled = false;
|
|
38
45
|
let presenceEnabled = false;
|
|
39
46
|
let mutationDrain = null;
|
|
47
|
+
let entityMutationDrain = null;
|
|
48
|
+
let entityContentDrain = null;
|
|
40
49
|
let promotionDrain = null;
|
|
41
50
|
let attachmentDrain = null;
|
|
42
51
|
let wakeRequest = null;
|
|
43
52
|
let wakeRetryTimer = null;
|
|
44
53
|
let mutationRetryTimer = null;
|
|
54
|
+
let entityMutationRetryTimer = null;
|
|
55
|
+
let entityContentRetryTimer = null;
|
|
45
56
|
let promotionRetryTimer = null;
|
|
46
57
|
let attachmentRetryTimer = null;
|
|
47
58
|
let reauthorizeTimer = null;
|
|
48
59
|
let repoObjectTimer = null;
|
|
49
60
|
let localFailures = 0;
|
|
61
|
+
let userHomeReady = false;
|
|
62
|
+
|
|
63
|
+
function privateInflightFor(contract) {
|
|
64
|
+
return [...privateEnsureInflight.values()].some((entry) => entry.contract === contract);
|
|
65
|
+
}
|
|
50
66
|
|
|
51
67
|
async function ensurePrivateCookieResource() {
|
|
52
|
-
if (!enabled ||
|
|
68
|
+
if (!enabled || privateInflightFor(COOKIE_PRIVATE_CONTRACT)) return;
|
|
53
69
|
try {
|
|
54
70
|
const bootstrap = await localStateJson('GET', '/browser/cookie-jar/cloud/bootstrap');
|
|
55
71
|
if (!bootstrap?.enabled || !bootstrap.resourceId) return;
|
|
@@ -67,12 +83,131 @@ function createSharedRealtimeTunnel({
|
|
|
67
83
|
scheduleMutationDrain(RETRY_MS);
|
|
68
84
|
return;
|
|
69
85
|
}
|
|
70
|
-
privateEnsureInflight.set(requestId,
|
|
86
|
+
privateEnsureInflight.set(requestId, {
|
|
87
|
+
resourceId: bootstrap.resourceId,
|
|
88
|
+
contract: COOKIE_PRIVATE_CONTRACT,
|
|
89
|
+
phase: 'ensure',
|
|
90
|
+
});
|
|
71
91
|
} catch (error) {
|
|
72
92
|
warn(`private cookie bootstrap unavailable: ${error?.message || error}`);
|
|
73
93
|
}
|
|
74
94
|
}
|
|
75
95
|
|
|
96
|
+
async function ensurePrivateUserHome() {
|
|
97
|
+
if (!enabled || privateInflightFor(USER_HOME_PRIVATE_CONTRACT)) return;
|
|
98
|
+
try {
|
|
99
|
+
const identity = await localStateJson('GET', '/entities/user-home/cloud/identity');
|
|
100
|
+
if (!identity?.enabled || !identity.resourceId) return;
|
|
101
|
+
const requestId = `private_home_lookup_${crypto.randomBytes(12).toString('hex')}`;
|
|
102
|
+
if (!sendLarge({
|
|
103
|
+
type: 'private.resource.lookup',
|
|
104
|
+
request_id: requestId,
|
|
105
|
+
resource_id: identity.resourceId,
|
|
106
|
+
contract: identity.contract,
|
|
107
|
+
})) {
|
|
108
|
+
scheduleMutationDrain(RETRY_MS);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
privateEnsureInflight.set(requestId, {
|
|
112
|
+
resourceId: identity.resourceId,
|
|
113
|
+
contract: USER_HOME_PRIVATE_CONTRACT,
|
|
114
|
+
phase: 'lookup',
|
|
115
|
+
});
|
|
116
|
+
} catch (error) {
|
|
117
|
+
warn(`private user-home lookup unavailable: ${error?.message || error}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function createPrivateUserHome(resourceId) {
|
|
122
|
+
try {
|
|
123
|
+
const bootstrap = await localStateJson('GET', '/entities/user-home/cloud/bootstrap');
|
|
124
|
+
if (!bootstrap?.enabled || bootstrap.resourceId !== resourceId) {
|
|
125
|
+
throw new Error('local user-home bootstrap identity changed');
|
|
126
|
+
}
|
|
127
|
+
const requestId = `private_home_ensure_${crypto.randomBytes(12).toString('hex')}`;
|
|
128
|
+
if (!sendLarge({
|
|
129
|
+
type: 'private.resource.ensure',
|
|
130
|
+
request_id: requestId,
|
|
131
|
+
resource_id: bootstrap.resourceId,
|
|
132
|
+
contract: bootstrap.contract,
|
|
133
|
+
schema_version: bootstrap.schemaVersion,
|
|
134
|
+
snapshot_b64: bootstrap.snapshotBase64,
|
|
135
|
+
snapshot_checksum: bootstrap.snapshotChecksum,
|
|
136
|
+
snapshot_bytes: bootstrap.snapshotBytes,
|
|
137
|
+
})) {
|
|
138
|
+
scheduleMutationDrain(RETRY_MS);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
privateEnsureInflight.set(requestId, {
|
|
142
|
+
resourceId: bootstrap.resourceId,
|
|
143
|
+
contract: USER_HOME_PRIVATE_CONTRACT,
|
|
144
|
+
phase: 'ensure',
|
|
145
|
+
});
|
|
146
|
+
} catch (error) {
|
|
147
|
+
warn(`private user-home bootstrap unavailable: ${error?.message || error}`);
|
|
148
|
+
setTimeout(() => enabled && void ensurePrivateUserHome(), RETRY_MS).unref?.();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function ensurePrivateEntityRegistry() {
|
|
153
|
+
// The user-home snapshot is the root UUID handshake. Creating the
|
|
154
|
+
// universal graph before it lands risks minting a competing bootstrap
|
|
155
|
+
// graph on a newly reinstalled machine.
|
|
156
|
+
if (!enabled || !userHomeReady || privateInflightFor(ENTITY_REGISTRY_PRIVATE_CONTRACT)) return;
|
|
157
|
+
try {
|
|
158
|
+
const identity = await localStateJson('GET', '/entities/cloud/identity');
|
|
159
|
+
if (!identity?.enabled || !identity.resourceId) return;
|
|
160
|
+
const requestId = `private_entities_lookup_${crypto.randomBytes(12).toString('hex')}`;
|
|
161
|
+
if (!sendLarge({
|
|
162
|
+
type: 'private.resource.lookup',
|
|
163
|
+
request_id: requestId,
|
|
164
|
+
resource_id: identity.resourceId,
|
|
165
|
+
contract: identity.contract,
|
|
166
|
+
})) {
|
|
167
|
+
scheduleEntityMutationDrain(RETRY_MS);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
privateEnsureInflight.set(requestId, {
|
|
171
|
+
resourceId: identity.resourceId,
|
|
172
|
+
contract: ENTITY_REGISTRY_PRIVATE_CONTRACT,
|
|
173
|
+
phase: 'lookup',
|
|
174
|
+
});
|
|
175
|
+
} catch (error) {
|
|
176
|
+
warn(`private entity registry lookup unavailable: ${error?.message || error}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function createPrivateEntityRegistry(resourceId) {
|
|
181
|
+
try {
|
|
182
|
+
const bootstrap = await localStateJson('GET', '/entities/cloud/bootstrap');
|
|
183
|
+
if (!bootstrap?.enabled || bootstrap.resourceId !== resourceId) {
|
|
184
|
+
throw new Error('local entity registry bootstrap identity changed');
|
|
185
|
+
}
|
|
186
|
+
const requestId = `private_entities_ensure_${crypto.randomBytes(12).toString('hex')}`;
|
|
187
|
+
if (!sendLarge({
|
|
188
|
+
type: 'private.resource.ensure',
|
|
189
|
+
request_id: requestId,
|
|
190
|
+
resource_id: bootstrap.resourceId,
|
|
191
|
+
contract: bootstrap.contract,
|
|
192
|
+
schema_version: bootstrap.schemaVersion,
|
|
193
|
+
snapshot_b64: bootstrap.snapshotBase64,
|
|
194
|
+
snapshot_checksum: bootstrap.snapshotChecksum,
|
|
195
|
+
snapshot_bytes: bootstrap.snapshotBytes,
|
|
196
|
+
})) {
|
|
197
|
+
scheduleEntityMutationDrain(RETRY_MS);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
privateEnsureInflight.set(requestId, {
|
|
201
|
+
resourceId: bootstrap.resourceId,
|
|
202
|
+
contract: ENTITY_REGISTRY_PRIVATE_CONTRACT,
|
|
203
|
+
phase: 'ensure',
|
|
204
|
+
});
|
|
205
|
+
} catch (error) {
|
|
206
|
+
warn(`private entity registry bootstrap unavailable: ${error?.message || error}`);
|
|
207
|
+
setTimeout(() => enabled && void ensurePrivateEntityRegistry(), RETRY_MS).unref?.();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
76
211
|
function localStateJson(method, requestPath, body) {
|
|
77
212
|
const payload = body === undefined ? null : Buffer.from(JSON.stringify(body));
|
|
78
213
|
return new Promise((resolve, reject) => {
|
|
@@ -124,6 +259,24 @@ function createSharedRealtimeTunnel({
|
|
|
124
259
|
mutationRetryTimer.unref?.();
|
|
125
260
|
}
|
|
126
261
|
|
|
262
|
+
function scheduleEntityMutationDrain(delay = 0) {
|
|
263
|
+
if (!enabled || entityMutationRetryTimer) return;
|
|
264
|
+
entityMutationRetryTimer = setTimeout(() => {
|
|
265
|
+
entityMutationRetryTimer = null;
|
|
266
|
+
void drainEntityCloudMutations();
|
|
267
|
+
}, delay);
|
|
268
|
+
entityMutationRetryTimer.unref?.();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function scheduleEntityContentDrain(delay = 0) {
|
|
272
|
+
if (!enabled || entityContentRetryTimer) return;
|
|
273
|
+
entityContentRetryTimer = setTimeout(() => {
|
|
274
|
+
entityContentRetryTimer = null;
|
|
275
|
+
void drainEntityContent();
|
|
276
|
+
}, delay);
|
|
277
|
+
entityContentRetryTimer.unref?.();
|
|
278
|
+
}
|
|
279
|
+
|
|
127
280
|
function schedulePromotionDrain(delay = 0) {
|
|
128
281
|
if (!enabled || promotionRetryTimer) return;
|
|
129
282
|
promotionRetryTimer = setTimeout(() => {
|
|
@@ -142,10 +295,6 @@ function createSharedRealtimeTunnel({
|
|
|
142
295
|
attachmentRetryTimer.unref?.();
|
|
143
296
|
}
|
|
144
297
|
|
|
145
|
-
function alreadyInflight(inflight, matches) {
|
|
146
|
-
return [...inflight.values()].some(matches);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
298
|
async function drainMutations() {
|
|
150
299
|
if (!enabled || mutationDrain) return;
|
|
151
300
|
mutationDrain = (async () => {
|
|
@@ -156,10 +305,6 @@ function createSharedRealtimeTunnel({
|
|
|
156
305
|
for (const claim of Array.isArray(result?.mutations) ? result.mutations : []) {
|
|
157
306
|
const envelope = claim?.envelope;
|
|
158
307
|
if (!envelope?.resourceId || !envelope?.mutationId) continue;
|
|
159
|
-
if (alreadyInflight(mutationInflight, (entry) => (
|
|
160
|
-
entry.resourceId === envelope.resourceId
|
|
161
|
-
&& entry.mutationId === envelope.mutationId
|
|
162
|
-
))) continue;
|
|
163
308
|
const requestId = `shared_${crypto.randomBytes(12).toString('hex')}`;
|
|
164
309
|
if (!sendLarge({ type: 'shared.mutation.submit', request_id: requestId, envelope })) {
|
|
165
310
|
await failMutationClaim(claim, 'shared tunnel backpressure: mutation was not queued');
|
|
@@ -184,6 +329,103 @@ function createSharedRealtimeTunnel({
|
|
|
184
329
|
await mutationDrain;
|
|
185
330
|
}
|
|
186
331
|
|
|
332
|
+
async function drainEntityCloudMutations() {
|
|
333
|
+
if (!enabled || entityMutationDrain) return;
|
|
334
|
+
entityMutationDrain = (async () => {
|
|
335
|
+
const result = await localStateJson('POST', '/entities/cloud/mutations/claim', {
|
|
336
|
+
limit: CLAIM_LIMIT,
|
|
337
|
+
leaseMs: CLAIM_LEASE_MS,
|
|
338
|
+
});
|
|
339
|
+
for (const envelope of Array.isArray(result?.mutations) ? result.mutations : []) {
|
|
340
|
+
if (!envelope?.resourceId || !envelope?.mutationId) continue;
|
|
341
|
+
const requestId = `entities_${crypto.randomBytes(12).toString('hex')}`;
|
|
342
|
+
if (!sendLarge({ type: 'shared.mutation.submit', request_id: requestId, envelope })) {
|
|
343
|
+
await failEntityMutation(envelope, 'shared tunnel backpressure: entity mutation was not queued');
|
|
344
|
+
scheduleEntityMutationDrain(RETRY_MS);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
entityMutationInflight.set(requestId, {
|
|
348
|
+
resourceId: envelope.resourceId,
|
|
349
|
+
mutationId: envelope.mutationId,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
localFailures = 0;
|
|
353
|
+
})().catch((error) => {
|
|
354
|
+
localFailures += 1;
|
|
355
|
+
if (localFailures === 1 || localFailures % 20 === 0) {
|
|
356
|
+
warn(`entity outbox unavailable (attempt ${localFailures}): ${error?.message || error}`);
|
|
357
|
+
}
|
|
358
|
+
scheduleEntityMutationDrain(RETRY_MS);
|
|
359
|
+
}).finally(() => {
|
|
360
|
+
entityMutationDrain = null;
|
|
361
|
+
});
|
|
362
|
+
await entityMutationDrain;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function drainEntityContent() {
|
|
366
|
+
if (!enabled || entityContentDrain) return;
|
|
367
|
+
entityContentDrain = (async () => {
|
|
368
|
+
const result = await localStateJson('POST', '/entities/cloud/content/claim', {
|
|
369
|
+
leaseMs: CLAIM_LEASE_MS,
|
|
370
|
+
});
|
|
371
|
+
const upload = result?.upload;
|
|
372
|
+
if (!upload?.resourceId || !upload?.contentHash || !upload?.kind) return;
|
|
373
|
+
const requestId = `entity_content_${crypto.randomBytes(12).toString('hex')}`;
|
|
374
|
+
if (!sendLarge({
|
|
375
|
+
type: 'private.entity-content.put',
|
|
376
|
+
request_id: requestId,
|
|
377
|
+
resource_id: upload.resourceId,
|
|
378
|
+
content_hash: upload.contentHash,
|
|
379
|
+
kind: upload.kind,
|
|
380
|
+
part_index: upload.partIndex,
|
|
381
|
+
part_count: upload.partCount,
|
|
382
|
+
sha256: upload.sha256,
|
|
383
|
+
bytes: upload.bytes,
|
|
384
|
+
data_b64: upload.dataBase64,
|
|
385
|
+
})) {
|
|
386
|
+
await failEntityContent(upload, 'shared tunnel backpressure: entity content was not queued');
|
|
387
|
+
scheduleEntityContentDrain(RETRY_MS);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
entityContentInflight.set(requestId, upload);
|
|
391
|
+
})().catch((error) => {
|
|
392
|
+
localFailures += 1;
|
|
393
|
+
if (localFailures === 1 || localFailures % 20 === 0) {
|
|
394
|
+
warn(`entity content outbox unavailable (attempt ${localFailures}): ${error?.message || error}`);
|
|
395
|
+
}
|
|
396
|
+
scheduleEntityContentDrain(RETRY_MS);
|
|
397
|
+
}).finally(() => {
|
|
398
|
+
entityContentDrain = null;
|
|
399
|
+
});
|
|
400
|
+
await entityContentDrain;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function failEntityContent(upload, message) {
|
|
404
|
+
try {
|
|
405
|
+
await localStateJson('POST', '/entities/cloud/content/fail', {
|
|
406
|
+
resourceId: upload.resourceId,
|
|
407
|
+
contentHash: upload.contentHash,
|
|
408
|
+
message,
|
|
409
|
+
});
|
|
410
|
+
} catch {
|
|
411
|
+
// The durable local content ledger leaves the upload claimable after
|
|
412
|
+
// its lease, even if this failure response cannot reach the runtime.
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function failEntityMutation(envelope, message) {
|
|
417
|
+
try {
|
|
418
|
+
await localStateJson('POST', '/entities/cloud/mutations/fail', {
|
|
419
|
+
resourceId: envelope.resourceId,
|
|
420
|
+
mutationId: envelope.mutationId,
|
|
421
|
+
message,
|
|
422
|
+
retryable: true,
|
|
423
|
+
});
|
|
424
|
+
} catch {
|
|
425
|
+
// The durable identity-db lease makes the row claimable after expiry.
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
187
429
|
async function failMutationClaim(claim, message) {
|
|
188
430
|
try {
|
|
189
431
|
await localStateJson('POST', '/state/mutations/fail', {
|
|
@@ -207,9 +449,6 @@ function createSharedRealtimeTunnel({
|
|
|
207
449
|
for (const claim of Array.isArray(result?.promotions) ? result.promotions : []) {
|
|
208
450
|
const promotion = claim?.frame;
|
|
209
451
|
if (!promotion?.promotionId || !promotion?.resourceId) continue;
|
|
210
|
-
if (alreadyInflight(promotionInflight, (entry) => (
|
|
211
|
-
entry.promotionId === promotion.promotionId
|
|
212
|
-
))) continue;
|
|
213
452
|
const requestId = `promote_${crypto.randomBytes(12).toString('hex')}`;
|
|
214
453
|
const queued = sendLarge({
|
|
215
454
|
type: 'shared.resource.promote',
|
|
@@ -256,9 +495,6 @@ function createSharedRealtimeTunnel({
|
|
|
256
495
|
});
|
|
257
496
|
for (const attachment of Array.isArray(result?.attachments) ? result.attachments : []) {
|
|
258
497
|
if (!attachment?.attachmentId || !attachment?.resourceId) continue;
|
|
259
|
-
if (alreadyInflight(attachmentInflight, (entry) => (
|
|
260
|
-
entry.attachmentId === attachment.attachmentId
|
|
261
|
-
))) continue;
|
|
262
498
|
const requestId = `attach_${crypto.randomBytes(12).toString('hex')}`;
|
|
263
499
|
if (!send({
|
|
264
500
|
type: 'shared.resource.attach',
|
|
@@ -291,10 +527,17 @@ function createSharedRealtimeTunnel({
|
|
|
291
527
|
repoObjectTimer.unref?.();
|
|
292
528
|
}
|
|
293
529
|
|
|
530
|
+
function expireRepoObjectRequests(now = Date.now()) {
|
|
531
|
+
for (const inflight of [repoUploadInflight, repoDownloadInflight]) {
|
|
532
|
+
for (const [requestId, object] of inflight) {
|
|
533
|
+
if (now - object.sentAt >= repoObjectRequestTimeoutMs) inflight.delete(requestId);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
294
538
|
async function drainRepoObjects() {
|
|
295
539
|
if (!enabled) return;
|
|
296
|
-
|
|
297
|
-
// clears this map and the durable local queue resends after reconnect.
|
|
540
|
+
expireRepoObjectRequests();
|
|
298
541
|
if (repoUploadInflight.size === 0) {
|
|
299
542
|
const result = await localStateJson('POST', '/state/repo-objects/uploads/claim', {});
|
|
300
543
|
const upload = result?.upload;
|
|
@@ -311,6 +554,7 @@ function createSharedRealtimeTunnel({
|
|
|
311
554
|
repoUploadInflight.set(requestId, {
|
|
312
555
|
resourceId: upload.resourceId,
|
|
313
556
|
sha256: upload.sha256,
|
|
557
|
+
sentAt: Date.now(),
|
|
314
558
|
});
|
|
315
559
|
}
|
|
316
560
|
}
|
|
@@ -329,6 +573,7 @@ function createSharedRealtimeTunnel({
|
|
|
329
573
|
repoDownloadInflight.set(requestId, {
|
|
330
574
|
resourceId: download.resourceId,
|
|
331
575
|
sha256: download.sha256,
|
|
576
|
+
sentAt: Date.now(),
|
|
332
577
|
});
|
|
333
578
|
}
|
|
334
579
|
}
|
|
@@ -470,10 +715,14 @@ function createSharedRealtimeTunnel({
|
|
|
470
715
|
if (!enabled) return;
|
|
471
716
|
startWakeStream();
|
|
472
717
|
void ensurePrivateCookieResource();
|
|
718
|
+
void ensurePrivateUserHome();
|
|
719
|
+
if (userHomeReady) void ensurePrivateEntityRegistry();
|
|
473
720
|
void joinReplicas();
|
|
474
721
|
scheduleAttachmentDrain();
|
|
475
722
|
schedulePromotionDrain();
|
|
476
723
|
scheduleMutationDrain();
|
|
724
|
+
scheduleEntityContentDrain();
|
|
725
|
+
scheduleEntityMutationDrain();
|
|
477
726
|
scheduleRepoObjects();
|
|
478
727
|
}
|
|
479
728
|
|
|
@@ -521,6 +770,26 @@ function createSharedRealtimeTunnel({
|
|
|
521
770
|
});
|
|
522
771
|
}
|
|
523
772
|
|
|
773
|
+
function handlePrivateEntityContentStored(frame) {
|
|
774
|
+
const upload = entityContentInflight.get(frame.request_id);
|
|
775
|
+
if (!upload || upload.contentHash !== frame.content_hash || upload.kind !== frame.kind
|
|
776
|
+
|| upload.partIndex !== frame.part_index) return;
|
|
777
|
+
void localStateJson('POST', '/entities/cloud/content/ack', {
|
|
778
|
+
resourceId: upload.resourceId,
|
|
779
|
+
contentHash: upload.contentHash,
|
|
780
|
+
kind: upload.kind,
|
|
781
|
+
partIndex: upload.partIndex,
|
|
782
|
+
}).then(() => {
|
|
783
|
+
entityContentInflight.delete(frame.request_id);
|
|
784
|
+
// A completed manifest may unblock the first entity mutation that
|
|
785
|
+
// names this exact head, so advance both rails immediately.
|
|
786
|
+
scheduleEntityContentDrain();
|
|
787
|
+
scheduleEntityMutationDrain();
|
|
788
|
+
}).catch(() => {
|
|
789
|
+
setTimeout(() => enabled && handlePrivateEntityContentStored(frame), RETRY_MS).unref?.();
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
|
|
524
793
|
function handleAttached(frame) {
|
|
525
794
|
const identity = attachmentInflight.get(frame.request_id);
|
|
526
795
|
if (!identity) return;
|
|
@@ -545,13 +814,9 @@ function createSharedRealtimeTunnel({
|
|
|
545
814
|
void localStateJson('POST', '/state/shared/attachments/fail', {
|
|
546
815
|
attachmentId: identity.attachmentId,
|
|
547
816
|
message: error?.message || 'local snapshot materialization failed',
|
|
548
|
-
|
|
549
|
-
// A local materialization failure is weather (busy disk, concurrent
|
|
550
|
-
// save, restart), never proof that the binding is permanently bad.
|
|
551
|
-
retryable: true,
|
|
817
|
+
retryable: false,
|
|
552
818
|
}).catch(() => {});
|
|
553
819
|
attachmentInflight.delete(frame.request_id);
|
|
554
|
-
scheduleAttachmentDrain(RETRY_MS);
|
|
555
820
|
});
|
|
556
821
|
}
|
|
557
822
|
|
|
@@ -576,9 +841,17 @@ function createSharedRealtimeTunnel({
|
|
|
576
841
|
}
|
|
577
842
|
|
|
578
843
|
function handlePrivateReady(frame) {
|
|
579
|
-
const
|
|
580
|
-
if (!
|
|
581
|
-
|
|
844
|
+
const inflight = privateEnsureInflight.get(frame.request_id);
|
|
845
|
+
if (!inflight || inflight.resourceId !== frame.resource_id) return;
|
|
846
|
+
const installPath = inflight.contract === COOKIE_PRIVATE_CONTRACT
|
|
847
|
+
? '/browser/cookie-jar/cloud/install'
|
|
848
|
+
: inflight.contract === USER_HOME_PRIVATE_CONTRACT
|
|
849
|
+
? '/entities/user-home/cloud/install'
|
|
850
|
+
: inflight.contract === ENTITY_REGISTRY_PRIVATE_CONTRACT
|
|
851
|
+
? '/entities/cloud/install'
|
|
852
|
+
: null;
|
|
853
|
+
if (!installPath) return;
|
|
854
|
+
void localStateJson('POST', installPath, {
|
|
582
855
|
resourceId: frame.resource_id,
|
|
583
856
|
authorityEpoch: frame.authority_epoch,
|
|
584
857
|
headVersion: frame.head_version,
|
|
@@ -589,15 +862,47 @@ function createSharedRealtimeTunnel({
|
|
|
589
862
|
schemaVersion: frame.schema_version,
|
|
590
863
|
}).then(() => {
|
|
591
864
|
privateEnsureInflight.delete(frame.request_id);
|
|
865
|
+
if (inflight.contract === USER_HOME_PRIVATE_CONTRACT) {
|
|
866
|
+
userHomeReady = true;
|
|
867
|
+
void ensurePrivateEntityRegistry();
|
|
868
|
+
}
|
|
592
869
|
void joinReplicas();
|
|
593
870
|
scheduleMutationDrain();
|
|
871
|
+
scheduleEntityContentDrain();
|
|
872
|
+
scheduleEntityMutationDrain();
|
|
594
873
|
}).catch((error) => {
|
|
595
|
-
warn(`private
|
|
874
|
+
warn(`private ${inflight.contract} snapshot was not installed: ${error?.message || error}`);
|
|
596
875
|
setTimeout(() => enabled && handlePrivateReady(frame), RETRY_MS).unref?.();
|
|
597
876
|
});
|
|
598
877
|
}
|
|
599
878
|
|
|
879
|
+
function handlePrivateMissing(frame) {
|
|
880
|
+
const inflight = privateEnsureInflight.get(frame.request_id);
|
|
881
|
+
if (!inflight || inflight.resourceId !== frame.resource_id || inflight.phase !== 'lookup') return;
|
|
882
|
+
privateEnsureInflight.delete(frame.request_id);
|
|
883
|
+
if (inflight.contract === USER_HOME_PRIVATE_CONTRACT) {
|
|
884
|
+
void createPrivateUserHome(inflight.resourceId);
|
|
885
|
+
} else if (inflight.contract === ENTITY_REGISTRY_PRIVATE_CONTRACT) {
|
|
886
|
+
void createPrivateEntityRegistry(inflight.resourceId);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
600
890
|
function handleAck(frame) {
|
|
891
|
+
const entityIdentity = entityMutationInflight.get(frame.request_id);
|
|
892
|
+
if (entityIdentity) {
|
|
893
|
+
void localStateJson('POST', '/entities/cloud/mutations/ack', {
|
|
894
|
+
resourceId: entityIdentity.resourceId,
|
|
895
|
+
mutationId: entityIdentity.mutationId,
|
|
896
|
+
version: frame.version,
|
|
897
|
+
committedAt: frame.committed_at,
|
|
898
|
+
}).then(() => {
|
|
899
|
+
entityMutationInflight.delete(frame.request_id);
|
|
900
|
+
scheduleEntityMutationDrain();
|
|
901
|
+
}).catch(() => {
|
|
902
|
+
setTimeout(() => enabled && handleAck(frame), RETRY_MS).unref?.();
|
|
903
|
+
});
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
601
906
|
const identity = mutationInflight.get(frame.request_id) || {
|
|
602
907
|
resourceId: frame.resource_id,
|
|
603
908
|
mutationId: frame.mutation_id,
|
|
@@ -618,18 +923,9 @@ function createSharedRealtimeTunnel({
|
|
|
618
923
|
}
|
|
619
924
|
|
|
620
925
|
function handleError(frame) {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
resourceId: failedUpload.resourceId,
|
|
625
|
-
sha256: failedUpload.sha256,
|
|
626
|
-
retryable: frame.retryable !== false,
|
|
627
|
-
code: frame.code,
|
|
628
|
-
message: frame.message,
|
|
629
|
-
}).finally(() => {
|
|
630
|
-
repoUploadInflight.delete(frame.request_id);
|
|
631
|
-
scheduleRepoObjects();
|
|
632
|
-
});
|
|
926
|
+
if (repoUploadInflight.has(frame.request_id)) {
|
|
927
|
+
repoUploadInflight.delete(frame.request_id);
|
|
928
|
+
scheduleRepoObjects(RETRY_MS);
|
|
633
929
|
return;
|
|
634
930
|
}
|
|
635
931
|
if (repoDownloadInflight.has(frame.request_id)) {
|
|
@@ -637,12 +933,26 @@ function createSharedRealtimeTunnel({
|
|
|
637
933
|
scheduleRepoObjects(RETRY_MS);
|
|
638
934
|
return;
|
|
639
935
|
}
|
|
936
|
+
const content = entityContentInflight.get(frame.request_id);
|
|
937
|
+
if (content) {
|
|
938
|
+
void failEntityContent(content, `${frame.code || 'shared_error'}: ${frame.message || 'entity content failed'}`)
|
|
939
|
+
.finally(() => {
|
|
940
|
+
entityContentInflight.delete(frame.request_id);
|
|
941
|
+
if (frame.retryable !== false) scheduleEntityContentDrain(RETRY_MS);
|
|
942
|
+
});
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
640
945
|
const privateResource = privateEnsureInflight.get(frame.request_id);
|
|
641
946
|
if (privateResource) {
|
|
642
947
|
privateEnsureInflight.delete(frame.request_id);
|
|
643
|
-
warn(`private
|
|
948
|
+
warn(`private ${privateResource.contract} bootstrap failed for ${privateResource.resourceId}: ${frame.code || 'shared_error'}: ${frame.message || ''}`);
|
|
644
949
|
if (frame.retryable !== false) {
|
|
645
|
-
|
|
950
|
+
const retry = privateResource.contract === USER_HOME_PRIVATE_CONTRACT
|
|
951
|
+
? ensurePrivateUserHome
|
|
952
|
+
: privateResource.contract === ENTITY_REGISTRY_PRIVATE_CONTRACT
|
|
953
|
+
? ensurePrivateEntityRegistry
|
|
954
|
+
: ensurePrivateCookieResource;
|
|
955
|
+
setTimeout(() => enabled && void retry(), RETRY_MS).unref?.();
|
|
646
956
|
}
|
|
647
957
|
return;
|
|
648
958
|
}
|
|
@@ -702,6 +1012,20 @@ function createSharedRealtimeTunnel({
|
|
|
702
1012
|
return;
|
|
703
1013
|
}
|
|
704
1014
|
|
|
1015
|
+
const entityIdentity = entityMutationInflight.get(frame.request_id);
|
|
1016
|
+
if (entityIdentity) {
|
|
1017
|
+
void localStateJson('POST', '/entities/cloud/mutations/fail', {
|
|
1018
|
+
resourceId: entityIdentity.resourceId,
|
|
1019
|
+
mutationId: entityIdentity.mutationId,
|
|
1020
|
+
message: `${frame.code || 'shared_error'}: ${frame.message || 'entity cloud mutation failed'}`,
|
|
1021
|
+
retryable: frame.retryable !== false,
|
|
1022
|
+
}).finally(() => {
|
|
1023
|
+
entityMutationInflight.delete(frame.request_id);
|
|
1024
|
+
if (frame.retryable !== false) scheduleEntityMutationDrain(RETRY_MS);
|
|
1025
|
+
});
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
705
1029
|
const identity = mutationInflight.get(frame.request_id) || {
|
|
706
1030
|
resourceId: frame.resource_id,
|
|
707
1031
|
mutationId: frame.mutation_id,
|
|
@@ -710,7 +1034,6 @@ function createSharedRealtimeTunnel({
|
|
|
710
1034
|
void localStateJson('POST', '/state/mutations/fail', {
|
|
711
1035
|
resourceId: identity.resourceId,
|
|
712
1036
|
mutationId: identity.mutationId,
|
|
713
|
-
code: frame.code,
|
|
714
1037
|
message: `${frame.code || 'shared_error'}: ${frame.message || 'shared mutation failed'}`,
|
|
715
1038
|
retryable: frame.retryable !== false,
|
|
716
1039
|
}).finally(() => {
|
|
@@ -722,8 +1045,11 @@ function createSharedRealtimeTunnel({
|
|
|
722
1045
|
function handleCommitted(frame) {
|
|
723
1046
|
const resourceId = frame.resource_id || frame.envelope?.resourceId;
|
|
724
1047
|
if (!resourceId) return;
|
|
1048
|
+
const endpoint = frame.envelope?.contract === ENTITY_REGISTRY_PRIVATE_CONTRACT
|
|
1049
|
+
? '/entities/cloud/mutations/ingest'
|
|
1050
|
+
: '/state/mutations/ingest';
|
|
725
1051
|
const prior = inboundChains.get(resourceId) || Promise.resolve();
|
|
726
|
-
const next = prior.then(() => localStateJson('POST',
|
|
1052
|
+
const next = prior.then(() => localStateJson('POST', endpoint, {
|
|
727
1053
|
envelope: frame.envelope,
|
|
728
1054
|
version: frame.version,
|
|
729
1055
|
committedAt: frame.committed_at,
|
|
@@ -803,6 +1129,12 @@ function createSharedRealtimeTunnel({
|
|
|
803
1129
|
case 'private.resource.ready':
|
|
804
1130
|
handlePrivateReady(frame);
|
|
805
1131
|
return true;
|
|
1132
|
+
case 'private.resource.missing':
|
|
1133
|
+
handlePrivateMissing(frame);
|
|
1134
|
+
return true;
|
|
1135
|
+
case 'private.entity-content.stored':
|
|
1136
|
+
handlePrivateEntityContentStored(frame);
|
|
1137
|
+
return true;
|
|
806
1138
|
case 'repo.object.stored':
|
|
807
1139
|
handleRepoObjectStored(frame);
|
|
808
1140
|
return true;
|
|
@@ -828,6 +1160,8 @@ function createSharedRealtimeTunnel({
|
|
|
828
1160
|
enabled = false;
|
|
829
1161
|
presenceEnabled = false;
|
|
830
1162
|
mutationInflight.clear();
|
|
1163
|
+
entityMutationInflight.clear();
|
|
1164
|
+
entityContentInflight.clear();
|
|
831
1165
|
promotionInflight.clear();
|
|
832
1166
|
attachmentInflight.clear();
|
|
833
1167
|
privateEnsureInflight.clear();
|
|
@@ -839,12 +1173,19 @@ function createSharedRealtimeTunnel({
|
|
|
839
1173
|
wakeRequest = null;
|
|
840
1174
|
clearTimeout(wakeRetryTimer);
|
|
841
1175
|
clearTimeout(mutationRetryTimer);
|
|
1176
|
+
clearTimeout(entityMutationRetryTimer);
|
|
1177
|
+
clearTimeout(entityContentRetryTimer);
|
|
842
1178
|
clearTimeout(promotionRetryTimer);
|
|
843
1179
|
clearTimeout(attachmentRetryTimer);
|
|
844
1180
|
clearTimeout(reauthorizeTimer);
|
|
845
1181
|
clearTimeout(repoObjectTimer);
|
|
846
1182
|
wakeRetryTimer = null;
|
|
847
1183
|
mutationRetryTimer = null;
|
|
1184
|
+
entityMutationRetryTimer = null;
|
|
1185
|
+
entityMutationDrain = null;
|
|
1186
|
+
entityContentRetryTimer = null;
|
|
1187
|
+
entityContentDrain = null;
|
|
1188
|
+
userHomeReady = false;
|
|
848
1189
|
promotionRetryTimer = null;
|
|
849
1190
|
attachmentRetryTimer = null;
|
|
850
1191
|
reauthorizeTimer = null;
|
package/lib/supervisor.js
CHANGED
|
@@ -174,8 +174,6 @@ const SAFE_PROCESS_ENV_KEYS = [
|
|
|
174
174
|
'LANG',
|
|
175
175
|
'LC_ALL',
|
|
176
176
|
'LOGNAME',
|
|
177
|
-
'AMALGM_AUTO_UPDATE',
|
|
178
|
-
'AMALGM_DISABLE_AUTO_UPDATE',
|
|
179
177
|
'AMALGM_NATIVE_NODE_MODULES',
|
|
180
178
|
'AMALGM_SKIP_NATIVE_INSTALL',
|
|
181
179
|
'ELECTRON_RUN_AS_NODE',
|
|
@@ -1345,6 +1343,5 @@ module.exports = {
|
|
|
1345
1343
|
liveDeclaredRuntimePorts,
|
|
1346
1344
|
runtimeStateFilesUnder,
|
|
1347
1345
|
runtimeStateSearchRoots,
|
|
1348
|
-
safeBaseProcessEnv,
|
|
1349
1346
|
},
|
|
1350
1347
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amalgm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.246",
|
|
4
4
|
"description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"sync-runtime": "node ../../scripts/sync-npm-package-runtime.mjs",
|
|
18
18
|
"prepack": "node ../../scripts/sync-npm-package-runtime.mjs",
|
|
19
19
|
"pack:dry": "npm pack --dry-run",
|
|
20
|
-
"check": "node --check bin/amalgm.js && node --check lib/app-chat.js && node --check lib/react.js && node --check lib/auth-store.js && node --check lib/cli.js && node --check lib/identity-adopt.js && node --check lib/layout.js && node --check lib/migrate-layout.js && node --check lib/paths.js && node --check lib/process-cleanup.js && node --check lib/runtime-identity.js && node --check lib/runtime-manifest.js && node --check lib/service.js && node --check lib/state-migration.js && node --check lib/supervisor.js && node --check lib/tunnel-chat.js && node --check lib/tunnel-events.js && node --check lib/updater.js && node --check runtime/lib/runtime-manifest.js && node --check runtime/scripts/runtime-auth.js && node --check runtime/scripts/proxy-token-store.js && node --check runtime/scripts/local-gateway.js && node --check runtime/scripts/port-monitor.js && node --check runtime/scripts/chat-server.js && node --check runtime/scripts/chat-server/index.js && node --check runtime/scripts/chat-server/config.js && node --check runtime/scripts/chat-core/tooling/native-binaries.js && node --check runtime/scripts/chat-core/tooling/package-import.js && node --check runtime/scripts/chat-core/tooling/runtime-home.js && node --check runtime/scripts/amalgm-mcp/index.js && node --check runtime/scripts/amalgm-mcp/config.js && node --check runtime/scripts/amalgm-mcp/events/pr-check-runner.js && node --check runtime/scripts/lib/project-paths.js && node --check runtime/scripts/lib/runtime-paths.js"
|
|
20
|
+
"check": "node --check bin/amalgm.js && node --check lib/app-chat.js && node --check lib/react.js && node --check lib/auth-store.js && node --check lib/cli.js && node --check lib/identity-adopt.js && node --check lib/layout.js && node --check lib/migrate-layout.js && node --check lib/paths.js && node --check lib/process-cleanup.js && node --check lib/runtime-identity.js && node --check lib/runtime-manifest.js && node --check lib/service.js && node --check lib/state-migration.js && node --check lib/supervisor.js && node --check lib/tunnel-chat.js && node --check lib/tunnel-events.js && node --check lib/updater.js && node --check runtime/lib/runtime-manifest.js && node --check runtime/scripts/runtime-auth.js && node --check runtime/scripts/proxy-token-store.js && node --check runtime/scripts/local-gateway.js && node --check runtime/scripts/port-monitor.js && node --check runtime/scripts/chat-server.js && node --check runtime/scripts/chat-server/index.js && node --check runtime/scripts/chat-server/config.js && node --check runtime/scripts/chat-core/tooling/native-binaries.js && node --check runtime/scripts/chat-core/tooling/package-import.js && node --check runtime/scripts/chat-core/tooling/runtime-home.js && node --check runtime/scripts/amalgm-mcp/index.js && node --check runtime/scripts/amalgm-mcp/config.js && node --check runtime/scripts/amalgm-mcp/events/pr-check-runner.js && node --check runtime/scripts/amalgm-mcp/registration/entity-cloud.js && node --check runtime/scripts/amalgm-mcp/registration/entity-content.js && node --check runtime/scripts/amalgm-mcp/registration/service.js && node --check runtime/scripts/amalgm-mcp/server/routes/entities.js && node --check runtime/scripts/lib/project-paths.js && node --check runtime/scripts/lib/runtime-paths.js"
|
|
21
21
|
},
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=20"
|