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,287 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The local entity API — the runtime half of `amalgm register`. Authenticated
|
|
5
|
+
* by the same boundary as every other local route (server/http-service.js
|
|
6
|
+
* authorizes before dispatch), so this file is only the shape of the
|
|
7
|
+
* request and the shape of the failure.
|
|
8
|
+
*
|
|
9
|
+
* Two failures, two meanings, and the boundary itself says which:
|
|
10
|
+
*
|
|
11
|
+
* 400 a REFUSAL — the request named ground amalgm will not take
|
|
12
|
+
* (registration/refusal.js). The message IS the reason and is
|
|
13
|
+
* meant to be shown to whoever asked.
|
|
14
|
+
* --- anything else is a FAULT: a torn identity store, an unusable
|
|
15
|
+
* database, a bug. The request was fine; the installation is
|
|
16
|
+
* not. It travels on to the server's fault handler, which is
|
|
17
|
+
* what already reports every other route's faults.
|
|
18
|
+
*
|
|
19
|
+
* The split is never made by reading an error's wording. A message is
|
|
20
|
+
* for people; it changes. Deciding on it would mean that the day the
|
|
21
|
+
* identity store tears, the user is told their path is bad.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const crypto = require('crypto');
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
USER_HOME_CLOUD_CONTRACT,
|
|
28
|
+
USER_HOME_CLOUD_SCHEMA_VERSION,
|
|
29
|
+
registrationService,
|
|
30
|
+
} = require('../../registration/service');
|
|
31
|
+
const { isRefusal } = require('../../registration/refusal');
|
|
32
|
+
|
|
33
|
+
function privateUserHomeResourceId(userId) {
|
|
34
|
+
return `pvr_${crypto
|
|
35
|
+
.createHash('sha256')
|
|
36
|
+
.update(`private-user:${userId}:${USER_HOME_CLOUD_CONTRACT}`)
|
|
37
|
+
.digest('base64url')
|
|
38
|
+
.slice(0, 32)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function userHomeCloudIdentity() {
|
|
42
|
+
const { userId } = require('../../workspace/identity');
|
|
43
|
+
const ownerUserId = userId();
|
|
44
|
+
if (!ownerUserId) return { enabled: false, reason: 'runtime is not enrolled to a user' };
|
|
45
|
+
return {
|
|
46
|
+
enabled: true,
|
|
47
|
+
resourceId: privateUserHomeResourceId(ownerUserId),
|
|
48
|
+
contract: USER_HOME_CLOUD_CONTRACT,
|
|
49
|
+
schemaVersion: USER_HOME_CLOUD_SCHEMA_VERSION,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function snapshotBytes(value) {
|
|
54
|
+
const bytes = Buffer.from(JSON.stringify(value), 'utf8');
|
|
55
|
+
return {
|
|
56
|
+
snapshotBase64: bytes.toString('base64'),
|
|
57
|
+
snapshotBytes: bytes.length,
|
|
58
|
+
snapshotChecksum: crypto.createHash('sha256').update(bytes).digest('hex'),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function handleEntityRoutes(ctx) {
|
|
63
|
+
if (ctx.pathname === '/entities/user-home/cloud/identity' && ctx.method === 'GET') {
|
|
64
|
+
ctx.res.setHeader('Cache-Control', 'no-store');
|
|
65
|
+
ctx.sendJson(200, userHomeCloudIdentity());
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (ctx.pathname === '/entities/user-home/cloud/bootstrap' && ctx.method === 'GET') {
|
|
70
|
+
ctx.res.setHeader('Cache-Control', 'no-store');
|
|
71
|
+
const identity = userHomeCloudIdentity();
|
|
72
|
+
if (!identity.enabled) {
|
|
73
|
+
ctx.sendJson(200, identity);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const home = await registrationService().bootstrapUserHome();
|
|
77
|
+
ctx.sendJson(200, { ...identity, ...snapshotBytes(home.cloudSnapshot) });
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (ctx.pathname === '/entities/user-home/cloud/install' && ctx.method === 'POST') {
|
|
82
|
+
ctx.res.setHeader('Cache-Control', 'no-store');
|
|
83
|
+
const identity = userHomeCloudIdentity();
|
|
84
|
+
if (!identity.enabled) {
|
|
85
|
+
ctx.sendJson(409, identity);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
const body = await ctx.readJsonBody();
|
|
89
|
+
if (
|
|
90
|
+
body?.resourceId !== identity.resourceId
|
|
91
|
+
|| body?.contract !== identity.contract
|
|
92
|
+
|| Number(body?.schemaVersion) !== identity.schemaVersion
|
|
93
|
+
) {
|
|
94
|
+
ctx.sendJson(400, { error: 'cloud user-home identity does not belong to this runtime user' });
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
const bytes = Buffer.from(String(body.snapshotBase64 || ''), 'base64');
|
|
98
|
+
const checksum = crypto.createHash('sha256').update(bytes).digest('hex');
|
|
99
|
+
if (checksum !== body.snapshotChecksum) {
|
|
100
|
+
ctx.sendJson(400, { error: 'cloud user-home snapshot checksum mismatch' });
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
let snapshot;
|
|
104
|
+
try {
|
|
105
|
+
snapshot = JSON.parse(bytes.toString('utf8'));
|
|
106
|
+
} catch {
|
|
107
|
+
ctx.sendJson(400, { error: 'cloud user-home snapshot is not JSON' });
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
const home = await registrationService().bootstrapUserHome({ identitySnapshot: snapshot });
|
|
111
|
+
ctx.sendJson(200, {
|
|
112
|
+
resourceId: identity.resourceId,
|
|
113
|
+
roots: home.roots.map(({ key, record }) => ({ key, uuid: record.uuid })),
|
|
114
|
+
exactManaged: home.exactManaged.map(({ key, record }) => ({ key, uuid: record.uuid })),
|
|
115
|
+
});
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// The universal entity graph has its own private resource. It is kept
|
|
120
|
+
// separate from the user-home declaration: the latter establishes the
|
|
121
|
+
// fixed safe roots and exact governance; this resource carries every
|
|
122
|
+
// logical entity record and its durable mutation tail.
|
|
123
|
+
if (ctx.pathname === '/entities/cloud/identity' && ctx.method === 'GET') {
|
|
124
|
+
ctx.res.setHeader('Cache-Control', 'no-store');
|
|
125
|
+
ctx.sendJson(200, registrationService().entityCloud.identity());
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (ctx.pathname === '/entities/cloud/bootstrap' && ctx.method === 'GET') {
|
|
130
|
+
ctx.res.setHeader('Cache-Control', 'no-store');
|
|
131
|
+
const service = registrationService();
|
|
132
|
+
const identity = service.entityCloud.identity();
|
|
133
|
+
if (!identity.enabled) {
|
|
134
|
+
ctx.sendJson(200, identity);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
// A missing entity resource is first created from the same declared
|
|
138
|
+
// user-home graph that login owns. This is not an alternate registrar.
|
|
139
|
+
await service.bootstrapUserHome();
|
|
140
|
+
ctx.sendJson(200, service.entityCloud.bootstrap(service.registration.registry));
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (ctx.pathname === '/entities/cloud/install' && ctx.method === 'POST') {
|
|
145
|
+
ctx.res.setHeader('Cache-Control', 'no-store');
|
|
146
|
+
const body = await ctx.readJsonBody();
|
|
147
|
+
const service = registrationService();
|
|
148
|
+
try {
|
|
149
|
+
ctx.sendJson(200, service.entityCloud.install({
|
|
150
|
+
registry: service.registration.registry,
|
|
151
|
+
resourceId: body?.resourceId,
|
|
152
|
+
authorityEpoch: body?.authorityEpoch,
|
|
153
|
+
headVersion: body?.headVersion,
|
|
154
|
+
snapshotBase64: body?.snapshotBase64,
|
|
155
|
+
snapshotChecksum: body?.snapshotChecksum,
|
|
156
|
+
}));
|
|
157
|
+
} catch (error) {
|
|
158
|
+
ctx.sendJson(409, { error: error.message });
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (ctx.pathname === '/entities/cloud/mutations/claim' && ctx.method === 'POST') {
|
|
164
|
+
const body = await ctx.readJsonBody();
|
|
165
|
+
ctx.sendJson(200, {
|
|
166
|
+
mutations: registrationService().entityCloud.claim({
|
|
167
|
+
limit: body?.limit,
|
|
168
|
+
leaseMs: body?.leaseMs,
|
|
169
|
+
}),
|
|
170
|
+
});
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Content objects are a separate ordered rail: a file record is not
|
|
175
|
+
// submitted until this endpoint has received the immutable object manifest
|
|
176
|
+
// acknowledgement for its payload head.
|
|
177
|
+
if (ctx.pathname === '/entities/cloud/content/claim' && ctx.method === 'POST') {
|
|
178
|
+
const body = await ctx.readJsonBody();
|
|
179
|
+
ctx.sendJson(200, {
|
|
180
|
+
upload: registrationService().entityCloud.claimContent({ leaseMs: body?.leaseMs }),
|
|
181
|
+
});
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (ctx.pathname === '/entities/cloud/content/ack' && ctx.method === 'POST') {
|
|
186
|
+
const body = await ctx.readJsonBody();
|
|
187
|
+
try {
|
|
188
|
+
ctx.sendJson(200, registrationService().entityCloud.acknowledgeContent({
|
|
189
|
+
resourceId: body?.resourceId,
|
|
190
|
+
contentHash: body?.contentHash,
|
|
191
|
+
kind: body?.kind,
|
|
192
|
+
partIndex: body?.partIndex,
|
|
193
|
+
}));
|
|
194
|
+
} catch (error) {
|
|
195
|
+
ctx.sendJson(400, { error: error.message });
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (ctx.pathname === '/entities/cloud/content/fail' && ctx.method === 'POST') {
|
|
201
|
+
const body = await ctx.readJsonBody();
|
|
202
|
+
try {
|
|
203
|
+
ctx.sendJson(200, registrationService().entityCloud.failContent({
|
|
204
|
+
resourceId: body?.resourceId,
|
|
205
|
+
contentHash: body?.contentHash,
|
|
206
|
+
message: body?.message,
|
|
207
|
+
}));
|
|
208
|
+
} catch (error) {
|
|
209
|
+
ctx.sendJson(400, { error: error.message });
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (ctx.pathname === '/entities/cloud/mutations/ack' && ctx.method === 'POST') {
|
|
215
|
+
const body = await ctx.readJsonBody();
|
|
216
|
+
try {
|
|
217
|
+
ctx.sendJson(200, registrationService().entityCloud.acknowledge({
|
|
218
|
+
resourceId: body?.resourceId,
|
|
219
|
+
mutationId: body?.mutationId,
|
|
220
|
+
version: body?.version,
|
|
221
|
+
committedAt: body?.committedAt,
|
|
222
|
+
}));
|
|
223
|
+
} catch (error) {
|
|
224
|
+
ctx.sendJson(400, { error: error.message });
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (ctx.pathname === '/entities/cloud/mutations/fail' && ctx.method === 'POST') {
|
|
230
|
+
const body = await ctx.readJsonBody();
|
|
231
|
+
try {
|
|
232
|
+
ctx.sendJson(200, registrationService().entityCloud.fail({
|
|
233
|
+
resourceId: body?.resourceId,
|
|
234
|
+
mutationId: body?.mutationId,
|
|
235
|
+
message: body?.message,
|
|
236
|
+
retryable: body?.retryable,
|
|
237
|
+
}));
|
|
238
|
+
} catch (error) {
|
|
239
|
+
ctx.sendJson(400, { error: error.message });
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (ctx.pathname === '/entities/cloud/mutations/ingest' && ctx.method === 'POST') {
|
|
245
|
+
const body = await ctx.readJsonBody();
|
|
246
|
+
const service = registrationService();
|
|
247
|
+
try {
|
|
248
|
+
ctx.sendJson(200, service.entityCloud.ingest({
|
|
249
|
+
registry: service.registration.registry,
|
|
250
|
+
envelope: body?.envelope,
|
|
251
|
+
version: body?.version,
|
|
252
|
+
committedAt: body?.committedAt,
|
|
253
|
+
}));
|
|
254
|
+
} catch (error) {
|
|
255
|
+
ctx.sendJson(409, { error: error.message });
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (ctx.pathname !== '/entities/register' || ctx.method !== 'POST') return false;
|
|
261
|
+
|
|
262
|
+
const body = await ctx.readJsonBody();
|
|
263
|
+
if (body?.path !== undefined && body?.paths !== undefined) {
|
|
264
|
+
ctx.sendJson(400, { error: 'send either path or paths, not both' });
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
const targets = Array.isArray(body?.paths)
|
|
268
|
+
? body.paths.map((target) => typeof target === 'string' ? target.trim() : '')
|
|
269
|
+
: [typeof body?.path === 'string' ? body.path.trim() : ''];
|
|
270
|
+
if (targets.length === 0 || targets.some((target) => !target)) {
|
|
271
|
+
ctx.sendJson(400, { error: 'path or paths is required: one or more absolute paths to register' });
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const results = await registrationService().registerMany(targets);
|
|
277
|
+
// Preserve the one-path response while exposing the full batch result to
|
|
278
|
+
// the CLI and any future SDK. One registration remains a degenerate batch.
|
|
279
|
+
ctx.sendJson(200, results.length === 1 ? { ...results[0], results } : { results });
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (!isRefusal(error)) throw error;
|
|
282
|
+
ctx.sendJson(400, { error: error.message });
|
|
283
|
+
}
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = { handleEntityRoutes, privateUserHomeResourceId, userHomeCloudIdentity };
|
|
@@ -59,19 +59,6 @@ async function handleStateRoutes(ctx) {
|
|
|
59
59
|
));
|
|
60
60
|
return true;
|
|
61
61
|
}
|
|
62
|
-
if (ctx.pathname === '/state/repo-objects/uploads/fail') {
|
|
63
|
-
const body = await ctx.readJsonBody();
|
|
64
|
-
ctx.sendJson(200, require('../../workspace/objects').failUpload(
|
|
65
|
-
body?.resourceId,
|
|
66
|
-
body?.sha256,
|
|
67
|
-
{
|
|
68
|
-
retryable: body?.retryable,
|
|
69
|
-
code: body?.code,
|
|
70
|
-
message: body?.message,
|
|
71
|
-
},
|
|
72
|
-
));
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
62
|
if (ctx.pathname === '/state/repo-objects/downloads/claim') {
|
|
76
63
|
ctx.sendJson(200, { download: require('../../workspace/objects').claimDownload() });
|
|
77
64
|
return true;
|
|
@@ -4,7 +4,7 @@ const crypto = require('crypto');
|
|
|
4
4
|
|
|
5
5
|
const { cachedStatement, openLocalDb } = require('./db');
|
|
6
6
|
const { isSharedResourceId } = require('./cloud-resource-id');
|
|
7
|
-
const { appendStateEvent
|
|
7
|
+
const { appendStateEvent } = require('./events');
|
|
8
8
|
|
|
9
9
|
const ATTACHMENT_ID = /^[A-Za-z0-9_-]{16,160}$/;
|
|
10
10
|
|
|
@@ -30,10 +30,6 @@ function normalizeRow(row) {
|
|
|
30
30
|
attemptCount: Number(row.attempt_count),
|
|
31
31
|
nextAttemptAt: row.next_attempt_at || null,
|
|
32
32
|
lastError: row.last_error || null,
|
|
33
|
-
// Legacy requests predate the fence. Their target was required to be
|
|
34
|
-
// empty, so zero is the only conservative boundary: every surviving or
|
|
35
|
-
// newly captured local mutation remains overlay.
|
|
36
|
-
journalCursor: row.journal_cursor == null ? 0 : Number(row.journal_cursor),
|
|
37
33
|
createdAt: row.created_at,
|
|
38
34
|
updatedAt: row.updated_at,
|
|
39
35
|
};
|
|
@@ -58,16 +54,12 @@ function enqueueAttachment(input, options = {}) {
|
|
|
58
54
|
return { attachment, duplicate: true };
|
|
59
55
|
}
|
|
60
56
|
const now = new Date().toISOString();
|
|
61
|
-
const journalCursor = Number(
|
|
62
|
-
cachedStatement(database, 'SELECT COALESCE(MAX(journal_id), 0) AS cursor FROM mutation_journal')
|
|
63
|
-
.get().cursor,
|
|
64
|
-
);
|
|
65
57
|
try {
|
|
66
58
|
cachedStatement(database, `
|
|
67
59
|
INSERT INTO shared_resource_attachments (
|
|
68
|
-
attachment_id, resource_id, local_path,
|
|
69
|
-
) VALUES (?, ?, ?, ?,
|
|
70
|
-
`).run(attachmentId, resourceId, localPath,
|
|
60
|
+
attachment_id, resource_id, local_path, created_at, updated_at
|
|
61
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
62
|
+
`).run(attachmentId, resourceId, localPath, now, now);
|
|
71
63
|
} catch (error) {
|
|
72
64
|
if (String(error?.code || '').includes('CONSTRAINT')) {
|
|
73
65
|
throw invalid('that resource or local path already has an attachment request', 409);
|
|
@@ -78,22 +70,14 @@ function enqueueAttachment(input, options = {}) {
|
|
|
78
70
|
cachedStatement(database, 'SELECT * FROM shared_resource_attachments WHERE attachment_id = ?')
|
|
79
71
|
.get(attachmentId),
|
|
80
72
|
);
|
|
81
|
-
|
|
73
|
+
appendStateEvent({
|
|
82
74
|
resource: 'shared:attachments',
|
|
83
75
|
op: 'update',
|
|
84
76
|
id: attachmentId,
|
|
85
77
|
sharedResourceId: resourceId,
|
|
86
78
|
value: { state: 'pending' },
|
|
87
79
|
source: 'shared:attachment',
|
|
88
|
-
};
|
|
89
|
-
if (options.deferEvent === true) {
|
|
90
|
-
return {
|
|
91
|
-
attachment,
|
|
92
|
-
duplicate: false,
|
|
93
|
-
deferredEvent: insertStateEvent(database, eventInput),
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
appendStateEvent(eventInput);
|
|
80
|
+
});
|
|
97
81
|
return { attachment, duplicate: false };
|
|
98
82
|
}
|
|
99
83
|
|
|
@@ -145,16 +129,7 @@ function acknowledgeAttachment(attachmentIdInput, payload, options = {}) {
|
|
|
145
129
|
|
|
146
130
|
const installed = payload?.contract === 'workspace-tree@1'
|
|
147
131
|
? require('../workspace/tree-store').installCloudTreeSnapshot(attachment.localPath, payload, { database })
|
|
148
|
-
:
|
|
149
|
-
? require('../workspace/project-stream').installSnapshot(
|
|
150
|
-
attachment.localPath,
|
|
151
|
-
payload,
|
|
152
|
-
{ database },
|
|
153
|
-
)
|
|
154
|
-
: require('./docs').installCloudDocSnapshot(attachment.localPath, {
|
|
155
|
-
...payload,
|
|
156
|
-
attachmentJournalCursor: attachment.journalCursor,
|
|
157
|
-
});
|
|
132
|
+
: require('./docs').installCloudDocSnapshot(attachment.localPath, payload);
|
|
158
133
|
const now = new Date().toISOString();
|
|
159
134
|
cachedStatement(database, `
|
|
160
135
|
UPDATE shared_resource_attachments
|
|
@@ -219,30 +194,21 @@ function requestRehydration(input, options = {}) {
|
|
|
219
194
|
let attachmentId;
|
|
220
195
|
if (existing) {
|
|
221
196
|
attachmentId = existing.attachment_id;
|
|
222
|
-
const journalCursor = Number(
|
|
223
|
-
cachedStatement(database, 'SELECT COALESCE(MAX(journal_id), 0) AS cursor FROM mutation_journal')
|
|
224
|
-
.get().cursor,
|
|
225
|
-
);
|
|
226
197
|
cachedStatement(database, `
|
|
227
198
|
UPDATE shared_resource_attachments
|
|
228
199
|
SET attachment_state = 'pending',
|
|
229
200
|
next_attempt_at = NULL,
|
|
230
201
|
last_error = NULL,
|
|
231
|
-
journal_cursor = ?,
|
|
232
202
|
updated_at = ?
|
|
233
203
|
WHERE attachment_id = ?
|
|
234
|
-
`).run(
|
|
204
|
+
`).run(now, attachmentId);
|
|
235
205
|
} else {
|
|
236
206
|
attachmentId = `att_${crypto.randomBytes(16).toString('base64url')}`;
|
|
237
|
-
const journalCursor = Number(
|
|
238
|
-
cachedStatement(database, 'SELECT COALESCE(MAX(journal_id), 0) AS cursor FROM mutation_journal')
|
|
239
|
-
.get().cursor,
|
|
240
|
-
);
|
|
241
207
|
cachedStatement(database, `
|
|
242
208
|
INSERT INTO shared_resource_attachments (
|
|
243
|
-
attachment_id, resource_id, local_path,
|
|
244
|
-
) VALUES (?, ?, ?, ?,
|
|
245
|
-
`).run(attachmentId, resourceId, replica.localPath,
|
|
209
|
+
attachment_id, resource_id, local_path, created_at, updated_at
|
|
210
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
211
|
+
`).run(attachmentId, resourceId, replica.localPath, now, now);
|
|
246
212
|
}
|
|
247
213
|
const attachment = normalizeRow(
|
|
248
214
|
cachedStatement(database, 'SELECT * FROM shared_resource_attachments WHERE attachment_id = ?')
|