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
|
@@ -1,771 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The one authoritative stream for a code project.
|
|
5
|
-
*
|
|
6
|
-
* Clients publish deltas. Only an officially numbered cloud mutation changes
|
|
7
|
-
* this snapshot. A local outbox row is intent, never authority.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const crypto = require('crypto');
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const { appendStateEvent } = require('../state/events');
|
|
13
|
-
const { openLocalDb } = require('../state/db');
|
|
14
|
-
const durable = require('./durable');
|
|
15
|
-
|
|
16
|
-
const STREAM_CONTRACT = 'code-project-stream@2';
|
|
17
|
-
const WALL_CONTRACT = 'code-project@7';
|
|
18
|
-
const CARD_CONTRACT = 'repo-card@3';
|
|
19
|
-
const CHECKPOINT_CONTRACT = 'repo-checkpoint@11';
|
|
20
|
-
const BUNDLE_CONTRACT = 'git-bundle-chunks@2';
|
|
21
|
-
const PAYLOAD_CONTRACT = 'opaque-chunks@2';
|
|
22
|
-
const LOCAL_RESOURCE_PREFIX = 'code-project:';
|
|
23
|
-
const SHA = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
24
|
-
const SHA256 = /^[0-9a-f]{64}$/;
|
|
25
|
-
const MUTATION_ID = /^[A-Za-z0-9_-]{16,160}$/;
|
|
26
|
-
const MAX_CHUNKS = 4096;
|
|
27
|
-
const MAX_CHUNK_BYTES = 512 * 1024;
|
|
28
|
-
const MAX_BRANCHES = 4096;
|
|
29
|
-
|
|
30
|
-
function invalid(message, statusCode = 400, code = null) {
|
|
31
|
-
const error = Object.assign(new Error(message), { statusCode });
|
|
32
|
-
if (code) error.code = code;
|
|
33
|
-
return error;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function exactObject(input, keys, label) {
|
|
37
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
38
|
-
throw invalid(`${label} is invalid`, 409);
|
|
39
|
-
}
|
|
40
|
-
const actual = Object.keys(input).sort();
|
|
41
|
-
const expected = [...keys].sort();
|
|
42
|
-
if (
|
|
43
|
-
actual.length !== expected.length
|
|
44
|
-
|| actual.some((key, index) => key !== expected[index])
|
|
45
|
-
) throw invalid(`${label} has unexpected fields`, 409);
|
|
46
|
-
return input;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function nonEmptyString(value, label) {
|
|
50
|
-
if (typeof value !== 'string' || !value) throw invalid(`${label} is invalid`, 409);
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function branchName(value) {
|
|
55
|
-
const components = typeof value === 'string' ? value.split('/') : [];
|
|
56
|
-
if (
|
|
57
|
-
typeof value !== 'string'
|
|
58
|
-
|| value.length < 1
|
|
59
|
-
|| value.length > 1024
|
|
60
|
-
|| value === '@'
|
|
61
|
-
|| value === 'HEAD'
|
|
62
|
-
|| value.startsWith('-')
|
|
63
|
-
|| value.endsWith('.')
|
|
64
|
-
|| value.includes('..')
|
|
65
|
-
|| value.includes('@{')
|
|
66
|
-
|| value.includes('[')
|
|
67
|
-
|| /[\u0000-\u0020\u007f~^:?*\\]/.test(value)
|
|
68
|
-
|| components.some((component) => (
|
|
69
|
-
!component
|
|
70
|
-
|| component.startsWith('.')
|
|
71
|
-
|| component.endsWith('.lock')
|
|
72
|
-
))
|
|
73
|
-
) throw invalid('branch name is invalid', 409);
|
|
74
|
-
return value;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function mutationId(value, label) {
|
|
78
|
-
if (!MUTATION_ID.test(String(value || ''))) throw invalid(`${label} is invalid`, 409);
|
|
79
|
-
return value;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function normalizeChunks(input, resourceId, kind, contract) {
|
|
83
|
-
exactObject(
|
|
84
|
-
input,
|
|
85
|
-
['kind', 'contract', 'resourceId', 'checksum', 'bytes', 'chunks'],
|
|
86
|
-
`${kind} locator`,
|
|
87
|
-
);
|
|
88
|
-
if (
|
|
89
|
-
input.kind !== kind
|
|
90
|
-
|| input.contract !== contract
|
|
91
|
-
|| input.resourceId !== resourceId
|
|
92
|
-
|| !SHA256.test(String(input.checksum || ''))
|
|
93
|
-
|| !Number.isSafeInteger(input.bytes)
|
|
94
|
-
|| input.bytes < 1
|
|
95
|
-
|| !Array.isArray(input.chunks)
|
|
96
|
-
|| input.chunks.length < 1
|
|
97
|
-
|| input.chunks.length > MAX_CHUNKS
|
|
98
|
-
) throw invalid(`${kind} locator is invalid`, 409);
|
|
99
|
-
const chunks = input.chunks.map((chunk) => {
|
|
100
|
-
exactObject(chunk, ['sha256', 'bytes'], `${kind} chunk`);
|
|
101
|
-
if (
|
|
102
|
-
!SHA256.test(String(chunk.sha256 || ''))
|
|
103
|
-
|| !Number.isSafeInteger(chunk.bytes)
|
|
104
|
-
|| chunk.bytes < 1
|
|
105
|
-
|| chunk.bytes > MAX_CHUNK_BYTES
|
|
106
|
-
) throw invalid(`${kind} chunk is invalid`, 409);
|
|
107
|
-
return { sha256: chunk.sha256, bytes: chunk.bytes };
|
|
108
|
-
});
|
|
109
|
-
if (chunks.reduce((sum, chunk) => sum + chunk.bytes, 0) !== input.bytes) {
|
|
110
|
-
throw invalid(`${kind} locator byte count is invalid`, 409);
|
|
111
|
-
}
|
|
112
|
-
const checksum = crypto.createHash('sha256').update(Buffer.from(JSON.stringify(chunks))).digest('hex');
|
|
113
|
-
if (checksum !== input.checksum) throw invalid(`${kind} locator checksum is invalid`, 409);
|
|
114
|
-
return {
|
|
115
|
-
kind,
|
|
116
|
-
contract,
|
|
117
|
-
resourceId,
|
|
118
|
-
checksum: input.checksum,
|
|
119
|
-
bytes: input.bytes,
|
|
120
|
-
chunks,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function normalizeBundle(input, resourceId, oid) {
|
|
125
|
-
exactObject(
|
|
126
|
-
input,
|
|
127
|
-
['kind', 'contract', 'resourceId', 'oid', 'ref', 'checksum', 'bytes', 'chunks'],
|
|
128
|
-
'bundle locator',
|
|
129
|
-
);
|
|
130
|
-
if (
|
|
131
|
-
input.kind !== 'bundle'
|
|
132
|
-
|| input.contract !== BUNDLE_CONTRACT
|
|
133
|
-
|| input.resourceId !== resourceId
|
|
134
|
-
|| input.oid !== oid
|
|
135
|
-
|| input.ref !== `refs/amalgm/exports/${oid}`
|
|
136
|
-
) throw invalid('bundle locator is invalid', 409);
|
|
137
|
-
const common = normalizeChunks(
|
|
138
|
-
{
|
|
139
|
-
kind: 'bundle',
|
|
140
|
-
contract: BUNDLE_CONTRACT,
|
|
141
|
-
resourceId: input.resourceId,
|
|
142
|
-
checksum: input.checksum,
|
|
143
|
-
bytes: input.bytes,
|
|
144
|
-
chunks: input.chunks,
|
|
145
|
-
},
|
|
146
|
-
resourceId,
|
|
147
|
-
'bundle',
|
|
148
|
-
BUNDLE_CONTRACT,
|
|
149
|
-
);
|
|
150
|
-
return { ...common, oid, ref: input.ref };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function normalizePayload(input, resourceId) {
|
|
154
|
-
return normalizeChunks(input, resourceId, 'chunks', PAYLOAD_CONTRACT);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function normalizeCheckpointValue(input, resourceId, baseOid = null) {
|
|
158
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
159
|
-
throw invalid('checkpoint value is invalid', 409);
|
|
160
|
-
}
|
|
161
|
-
const revision = input.revision;
|
|
162
|
-
if (!(revision === null || SHA256.test(String(revision || '')))) {
|
|
163
|
-
throw invalid('checkpoint revision is invalid', 409);
|
|
164
|
-
}
|
|
165
|
-
if (Object.hasOwn(input, 'sections')) {
|
|
166
|
-
exactObject(input, ['revision', 'sections'], 'checkpoint value');
|
|
167
|
-
exactObject(input.sections, ['index', 'worktree'], 'checkpoint sections');
|
|
168
|
-
const normalizeLayer = (layer) => {
|
|
169
|
-
if (!Array.isArray(input.sections[layer])) {
|
|
170
|
-
throw invalid('checkpoint sections are invalid', 409);
|
|
171
|
-
}
|
|
172
|
-
return input.sections[layer].map((section) => {
|
|
173
|
-
if (Object.hasOwn(section || {}, 'text')) {
|
|
174
|
-
exactObject(section, ['text'], 'checkpoint section');
|
|
175
|
-
if (typeof section.text !== 'string' || section.text.length === 0) {
|
|
176
|
-
throw invalid('checkpoint section is invalid', 409);
|
|
177
|
-
}
|
|
178
|
-
return { text: section.text };
|
|
179
|
-
}
|
|
180
|
-
exactObject(section, ['payload'], 'checkpoint section');
|
|
181
|
-
return { payload: normalizePayload(section.payload, resourceId) };
|
|
182
|
-
});
|
|
183
|
-
};
|
|
184
|
-
const normalized = {
|
|
185
|
-
revision,
|
|
186
|
-
sections: {
|
|
187
|
-
index: normalizeLayer('index'),
|
|
188
|
-
worktree: normalizeLayer('worktree'),
|
|
189
|
-
},
|
|
190
|
-
};
|
|
191
|
-
if (require('./checkpoint').revisionOfValue(baseOid, normalized) !== revision) {
|
|
192
|
-
throw invalid('checkpoint revision is invalid', 409);
|
|
193
|
-
}
|
|
194
|
-
return normalized;
|
|
195
|
-
}
|
|
196
|
-
exactObject(input, ['revision', 'indexDiff', 'worktreeDiff'], 'checkpoint value');
|
|
197
|
-
if (typeof input.indexDiff !== 'string' || typeof input.worktreeDiff !== 'string') {
|
|
198
|
-
throw invalid('checkpoint value is invalid', 409);
|
|
199
|
-
}
|
|
200
|
-
if (
|
|
201
|
-
baseOid
|
|
202
|
-
&& require('./checkpoint').revisionOfValue(baseOid, input) !== revision
|
|
203
|
-
) throw invalid('checkpoint revision is invalid', 409);
|
|
204
|
-
return {
|
|
205
|
-
revision,
|
|
206
|
-
indexDiff: input.indexDiff,
|
|
207
|
-
worktreeDiff: input.worktreeDiff,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function normalizeHead(input) {
|
|
212
|
-
exactObject(input, ['branch', 'oid', 'detached'], 'card head');
|
|
213
|
-
if (
|
|
214
|
-
!(input.branch === null || branchName(input.branch))
|
|
215
|
-
|| !SHA.test(String(input.oid || ''))
|
|
216
|
-
|| typeof input.detached !== 'boolean'
|
|
217
|
-
|| (input.detached && input.branch !== null)
|
|
218
|
-
|| (!input.detached && !input.branch)
|
|
219
|
-
) throw invalid('card head is invalid', 409);
|
|
220
|
-
return { branch: input.branch, oid: input.oid, detached: input.detached };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function normalizeBranch(input, resourceId) {
|
|
224
|
-
exactObject(input, ['name', 'oid', 'objects'], 'card branch');
|
|
225
|
-
const name = branchName(input.name);
|
|
226
|
-
if (!SHA.test(String(input.oid || ''))) throw invalid('branch oid is invalid', 409);
|
|
227
|
-
return {
|
|
228
|
-
name,
|
|
229
|
-
oid: input.oid,
|
|
230
|
-
objects: normalizeBundle(input.objects, resourceId, input.oid),
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function normalizeCheckpoint(input, resourceId, expectedHead = null) {
|
|
235
|
-
exactObject(input, ['checkpoint', 'baseOid', 'branch', 'value'], 'card checkpoint');
|
|
236
|
-
if (
|
|
237
|
-
input.checkpoint !== CHECKPOINT_CONTRACT
|
|
238
|
-
|| !SHA.test(String(input.baseOid || ''))
|
|
239
|
-
|| !(input.branch === null || branchName(input.branch))
|
|
240
|
-
|| (expectedHead && (
|
|
241
|
-
input.baseOid !== expectedHead.oid
|
|
242
|
-
|| input.branch !== expectedHead.branch
|
|
243
|
-
))
|
|
244
|
-
) throw invalid('card checkpoint is invalid', 409);
|
|
245
|
-
return {
|
|
246
|
-
checkpoint: CHECKPOINT_CONTRACT,
|
|
247
|
-
baseOid: input.baseOid,
|
|
248
|
-
branch: input.branch,
|
|
249
|
-
value: normalizeCheckpointValue(input.value, resourceId, input.baseOid),
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function normalizeCardSet(input, resourceId) {
|
|
254
|
-
exactObject(input, ['kind', 'card', 'head', 'objects', 'branches', 'checkpoint'], 'card.set');
|
|
255
|
-
if (input.kind !== 'card.set' || input.card !== CARD_CONTRACT) {
|
|
256
|
-
throw invalid('card.set is invalid', 409);
|
|
257
|
-
}
|
|
258
|
-
const head = normalizeHead(input.head);
|
|
259
|
-
const branches = Array.isArray(input.branches)
|
|
260
|
-
? input.branches.map((branch) => normalizeBranch(branch, resourceId))
|
|
261
|
-
: null;
|
|
262
|
-
if (!branches) throw invalid('card branches are invalid', 409);
|
|
263
|
-
if (branches.length > MAX_BRANCHES) throw invalid('card has too many branches', 409);
|
|
264
|
-
const names = new Set();
|
|
265
|
-
for (const branch of branches) {
|
|
266
|
-
if (names.has(branch.name)) throw invalid('card repeats a branch', 409);
|
|
267
|
-
names.add(branch.name);
|
|
268
|
-
}
|
|
269
|
-
if (!head.detached && !branches.some((branch) => (
|
|
270
|
-
branch.name === head.branch && branch.oid === head.oid
|
|
271
|
-
))) throw invalid('card branch map does not contain HEAD', 409);
|
|
272
|
-
return {
|
|
273
|
-
kind: 'card.set',
|
|
274
|
-
card: CARD_CONTRACT,
|
|
275
|
-
head,
|
|
276
|
-
objects: normalizeBundle(input.objects, resourceId, head.oid),
|
|
277
|
-
branches: branches.sort((left, right) => left.name.localeCompare(right.name)),
|
|
278
|
-
checkpoint: normalizeCheckpoint(input.checkpoint, resourceId, head),
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function normalizeCheckpointSet(input, resourceId) {
|
|
283
|
-
exactObject(
|
|
284
|
-
input,
|
|
285
|
-
['kind', 'checkpoint', 'baseOid', 'branch', 'from', 'to'],
|
|
286
|
-
'checkpoint.set',
|
|
287
|
-
);
|
|
288
|
-
if (
|
|
289
|
-
input.kind !== 'checkpoint.set'
|
|
290
|
-
|| input.checkpoint !== CHECKPOINT_CONTRACT
|
|
291
|
-
|| !SHA.test(String(input.baseOid || ''))
|
|
292
|
-
|| !(input.branch === null || branchName(input.branch))
|
|
293
|
-
) throw invalid('checkpoint.set is invalid', 409);
|
|
294
|
-
return {
|
|
295
|
-
kind: 'checkpoint.set',
|
|
296
|
-
checkpoint: CHECKPOINT_CONTRACT,
|
|
297
|
-
baseOid: input.baseOid,
|
|
298
|
-
branch: input.branch,
|
|
299
|
-
from: normalizeCheckpointValue(input.from, resourceId, input.baseOid),
|
|
300
|
-
to: normalizeCheckpointValue(input.to, resourceId, input.baseOid),
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function normalizeDelta(input, resourceId) {
|
|
305
|
-
let normalized;
|
|
306
|
-
if (input?.kind === 'card.set') normalized = normalizeCardSet(input, resourceId);
|
|
307
|
-
else if (input?.kind === 'checkpoint.set') normalized = normalizeCheckpointSet(input, resourceId);
|
|
308
|
-
else throw invalid('code project delta is invalid', 409);
|
|
309
|
-
const chunks = new Map();
|
|
310
|
-
const visit = (value) => {
|
|
311
|
-
if (!value || typeof value !== 'object') return;
|
|
312
|
-
if (Array.isArray(value)) {
|
|
313
|
-
value.forEach(visit);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
if (Array.isArray(value.chunks)) {
|
|
317
|
-
value.chunks.forEach((chunk) => {
|
|
318
|
-
const previous = chunks.get(chunk.sha256);
|
|
319
|
-
if (previous !== undefined && previous !== chunk.bytes) {
|
|
320
|
-
throw invalid('one code chunk claims two sizes', 409);
|
|
321
|
-
}
|
|
322
|
-
chunks.set(chunk.sha256, chunk.bytes);
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
Object.values(value).forEach(visit);
|
|
326
|
-
};
|
|
327
|
-
visit(normalized);
|
|
328
|
-
if (chunks.size > MAX_CHUNKS) throw invalid('code publication has too many chunks', 409);
|
|
329
|
-
return normalized;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function normalizeOperation(input, resourceId) {
|
|
333
|
-
exactObject(input, ['kind', 'project', 'delta'], 'code.publish operation');
|
|
334
|
-
if (input.kind !== 'code.publish' || input.project !== resourceId) {
|
|
335
|
-
throw invalid('code.publish operation is invalid', 409);
|
|
336
|
-
}
|
|
337
|
-
return {
|
|
338
|
-
kind: 'code.publish',
|
|
339
|
-
project: resourceId,
|
|
340
|
-
delta: normalizeDelta(input.delta, resourceId),
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function stampCard(delta, stamp) {
|
|
345
|
-
return {
|
|
346
|
-
card: CARD_CONTRACT,
|
|
347
|
-
head: delta.head,
|
|
348
|
-
objects: delta.objects,
|
|
349
|
-
branches: delta.branches,
|
|
350
|
-
...stamp,
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function stampCheckpoint(checkpoint, stamp) {
|
|
355
|
-
return { ...checkpoint, ...stamp };
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function sameValue(left, right) {
|
|
359
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function fold(snapshotInput, operationInput, stampInput) {
|
|
363
|
-
const snapshot = normalizeSnapshot(snapshotInput, operationInput?.project, true);
|
|
364
|
-
const operation = normalizeOperation(operationInput, snapshot.project);
|
|
365
|
-
const version = Number(stampInput?.version);
|
|
366
|
-
const machine = nonEmptyString(stampInput?.machine, 'mutation machine');
|
|
367
|
-
const officialMutationId = mutationId(stampInput?.mutationId, 'mutation id');
|
|
368
|
-
if (!Number.isSafeInteger(version) || version < 1) {
|
|
369
|
-
throw invalid('mutation version is invalid', 409);
|
|
370
|
-
}
|
|
371
|
-
// Snapshot writes precede cursor writes. Replaying after a crash is silent.
|
|
372
|
-
if (snapshot.version >= version) return snapshot;
|
|
373
|
-
if (version !== snapshot.version + 1) {
|
|
374
|
-
throw invalid(
|
|
375
|
-
`code project gap: expected ${snapshot.version + 1}, received ${version}`,
|
|
376
|
-
409,
|
|
377
|
-
'shared_mutation_gap',
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
const stamp = { machine, mutationId: officialMutationId, version };
|
|
381
|
-
if (operation.delta.kind === 'card.set') {
|
|
382
|
-
return {
|
|
383
|
-
schemaVersion: 2,
|
|
384
|
-
project: snapshot.project,
|
|
385
|
-
version,
|
|
386
|
-
card: stampCard(operation.delta, stamp),
|
|
387
|
-
checkpoint: stampCheckpoint(operation.delta.checkpoint, stamp),
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
const delta = operation.delta;
|
|
391
|
-
if (
|
|
392
|
-
!snapshot.card
|
|
393
|
-
|| snapshot.card.head.oid !== delta.baseOid
|
|
394
|
-
|| snapshot.card.head.branch !== delta.branch
|
|
395
|
-
|| !snapshot.checkpoint
|
|
396
|
-
|| snapshot.checkpoint.baseOid !== delta.baseOid
|
|
397
|
-
|| snapshot.checkpoint.branch !== delta.branch
|
|
398
|
-
|| !sameValue(snapshot.checkpoint.value, delta.from)
|
|
399
|
-
) throw invalid('code_project_stale_base', 409, 'code_project_stale_base');
|
|
400
|
-
return {
|
|
401
|
-
...snapshot,
|
|
402
|
-
version,
|
|
403
|
-
checkpoint: stampCheckpoint({
|
|
404
|
-
checkpoint: CHECKPOINT_CONTRACT,
|
|
405
|
-
baseOid: delta.baseOid,
|
|
406
|
-
branch: delta.branch,
|
|
407
|
-
value: delta.to,
|
|
408
|
-
}, stamp),
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
function emptySnapshot(resourceId) {
|
|
413
|
-
return {
|
|
414
|
-
schemaVersion: 2,
|
|
415
|
-
project: resourceId,
|
|
416
|
-
version: 0,
|
|
417
|
-
card: null,
|
|
418
|
-
checkpoint: null,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function normalizeStampedCard(input, resourceId, snapshotVersion) {
|
|
423
|
-
exactObject(
|
|
424
|
-
input,
|
|
425
|
-
['card', 'head', 'objects', 'branches', 'machine', 'mutationId', 'version'],
|
|
426
|
-
'official card',
|
|
427
|
-
);
|
|
428
|
-
const version = Number(input.version);
|
|
429
|
-
if (
|
|
430
|
-
input.card !== CARD_CONTRACT
|
|
431
|
-
|| !Number.isSafeInteger(version)
|
|
432
|
-
|| version < 1
|
|
433
|
-
|| version > snapshotVersion
|
|
434
|
-
) throw invalid('official card stamp is invalid', 409);
|
|
435
|
-
const delta = normalizeCardSet({
|
|
436
|
-
kind: 'card.set',
|
|
437
|
-
card: input.card,
|
|
438
|
-
head: input.head,
|
|
439
|
-
objects: input.objects,
|
|
440
|
-
branches: input.branches,
|
|
441
|
-
checkpoint: {
|
|
442
|
-
checkpoint: CHECKPOINT_CONTRACT,
|
|
443
|
-
baseOid: input.head.oid,
|
|
444
|
-
branch: input.head.branch,
|
|
445
|
-
value: { revision: null, indexDiff: '', worktreeDiff: '' },
|
|
446
|
-
},
|
|
447
|
-
}, resourceId);
|
|
448
|
-
return {
|
|
449
|
-
card: CARD_CONTRACT,
|
|
450
|
-
head: delta.head,
|
|
451
|
-
objects: delta.objects,
|
|
452
|
-
branches: delta.branches,
|
|
453
|
-
machine: nonEmptyString(input.machine, 'official card machine'),
|
|
454
|
-
mutationId: mutationId(input.mutationId, 'official card mutation'),
|
|
455
|
-
version,
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function normalizeStampedCheckpoint(input, resourceId, snapshotVersion) {
|
|
460
|
-
exactObject(
|
|
461
|
-
input,
|
|
462
|
-
['checkpoint', 'baseOid', 'branch', 'value', 'machine', 'mutationId', 'version'],
|
|
463
|
-
'official checkpoint',
|
|
464
|
-
);
|
|
465
|
-
const version = Number(input.version);
|
|
466
|
-
if (
|
|
467
|
-
!Number.isSafeInteger(version)
|
|
468
|
-
|| version < 1
|
|
469
|
-
|| version > snapshotVersion
|
|
470
|
-
) throw invalid('official checkpoint stamp is invalid', 409);
|
|
471
|
-
return {
|
|
472
|
-
...normalizeCheckpoint({
|
|
473
|
-
checkpoint: input.checkpoint,
|
|
474
|
-
baseOid: input.baseOid,
|
|
475
|
-
branch: input.branch,
|
|
476
|
-
value: input.value,
|
|
477
|
-
}, resourceId),
|
|
478
|
-
machine: nonEmptyString(input.machine, 'official checkpoint machine'),
|
|
479
|
-
mutationId: mutationId(input.mutationId, 'official checkpoint mutation'),
|
|
480
|
-
version,
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
function normalizeSnapshot(input, resourceId = null, official = false) {
|
|
485
|
-
exactObject(input, ['schemaVersion', 'project', 'version', 'card', 'checkpoint'], 'code project snapshot');
|
|
486
|
-
if (
|
|
487
|
-
input.schemaVersion !== 2
|
|
488
|
-
|| typeof input.project !== 'string'
|
|
489
|
-
|| !input.project
|
|
490
|
-
|| !require('./tree/identity').isUuid(input.project)
|
|
491
|
-
|| (resourceId && input.project !== resourceId)
|
|
492
|
-
|| !Number.isSafeInteger(Number(input.version))
|
|
493
|
-
|| Number(input.version) < 0
|
|
494
|
-
) throw invalid('code project snapshot is invalid', 409);
|
|
495
|
-
const version = Number(input.version);
|
|
496
|
-
if (version === 0) {
|
|
497
|
-
if (input.card !== null || input.checkpoint !== null) {
|
|
498
|
-
throw invalid('empty code project snapshot is invalid', 409);
|
|
499
|
-
}
|
|
500
|
-
return emptySnapshot(input.project);
|
|
501
|
-
}
|
|
502
|
-
if (!official || !input.card || !input.checkpoint) {
|
|
503
|
-
throw invalid('official code project snapshot is incomplete', 409);
|
|
504
|
-
}
|
|
505
|
-
const card = normalizeStampedCard(input.card, input.project, version);
|
|
506
|
-
const checkpoint = normalizeStampedCheckpoint(input.checkpoint, input.project, version);
|
|
507
|
-
if (
|
|
508
|
-
checkpoint.version !== version
|
|
509
|
-
|| card.version > checkpoint.version
|
|
510
|
-
|| checkpoint.baseOid !== card.head.oid
|
|
511
|
-
|| checkpoint.branch !== card.head.branch
|
|
512
|
-
) throw invalid('official checkpoint does not match the card', 409);
|
|
513
|
-
return {
|
|
514
|
-
schemaVersion: 2,
|
|
515
|
-
project: input.project,
|
|
516
|
-
version,
|
|
517
|
-
card,
|
|
518
|
-
checkpoint,
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
function readSnapshot(file, resourceId) {
|
|
523
|
-
if (!fs.existsSync(file) || fs.statSync(file).size === 0) return emptySnapshot(resourceId);
|
|
524
|
-
try {
|
|
525
|
-
return normalizeSnapshot(JSON.parse(fs.readFileSync(file, 'utf8')), resourceId, true);
|
|
526
|
-
} catch (error) {
|
|
527
|
-
if (error?.statusCode) throw error;
|
|
528
|
-
throw invalid('code project snapshot is not JSON', 409);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function writeSnapshot(file, snapshot) {
|
|
533
|
-
durable.writeJson(file, normalizeSnapshot(snapshot, snapshot.project, true));
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function publish(project, deltaInput, mutationIdInput) {
|
|
537
|
-
const replica = require('../state/replicas').getReplicaByResourceId(project.resourceId);
|
|
538
|
-
if (!replica || replica.contract !== STREAM_CONTRACT) {
|
|
539
|
-
throw invalid('code project stream is not attached', 409);
|
|
540
|
-
}
|
|
541
|
-
const operation = normalizeOperation({
|
|
542
|
-
kind: 'code.publish',
|
|
543
|
-
project: project.resourceId,
|
|
544
|
-
delta: deltaInput,
|
|
545
|
-
}, project.resourceId);
|
|
546
|
-
const publicationMutationId = mutationId(mutationIdInput, 'publication mutation');
|
|
547
|
-
return require('../state/mutations').commitLocalMutation({
|
|
548
|
-
channelId: `resource:${project.resourceId}`,
|
|
549
|
-
localResource: `${LOCAL_RESOURCE_PREFIX}${project.resourceId}`,
|
|
550
|
-
sharedResourceId: project.resourceId,
|
|
551
|
-
mutationId: publicationMutationId,
|
|
552
|
-
deviceId: require('./identity').machineId(),
|
|
553
|
-
authorityEpoch: replica.authorityEpoch,
|
|
554
|
-
baseCloudVersion: replica.appliedVersion,
|
|
555
|
-
contract: STREAM_CONTRACT,
|
|
556
|
-
schemaVersion: 2,
|
|
557
|
-
operationKind: 'code.publish',
|
|
558
|
-
operation,
|
|
559
|
-
}, {
|
|
560
|
-
// Outbound intent deliberately does not mutate the official snapshot.
|
|
561
|
-
event: {
|
|
562
|
-
resource: `${LOCAL_RESOURCE_PREFIX}${project.resourceId}`,
|
|
563
|
-
op: 'update',
|
|
564
|
-
id: project.file,
|
|
565
|
-
sharedResourceId: project.resourceId,
|
|
566
|
-
value: operation.delta,
|
|
567
|
-
source: 'code-project:outbox',
|
|
568
|
-
},
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
function decodeSnapshot(input) {
|
|
573
|
-
if (input?.contract !== STREAM_CONTRACT || Number(input?.schemaVersion) !== 2) {
|
|
574
|
-
throw invalid('unsupported code project stream', 409);
|
|
575
|
-
}
|
|
576
|
-
const bytes = Buffer.from(String(input.snapshotBase64 || ''), 'base64');
|
|
577
|
-
if (
|
|
578
|
-
!bytes.length
|
|
579
|
-
|| crypto.createHash('sha256').update(bytes).digest('hex') !== input.snapshotChecksum
|
|
580
|
-
) throw invalid('code project snapshot checksum mismatch', 409);
|
|
581
|
-
let parsed;
|
|
582
|
-
try {
|
|
583
|
-
parsed = JSON.parse(bytes.toString('utf8'));
|
|
584
|
-
} catch {
|
|
585
|
-
throw invalid('code project snapshot must be JSON', 409);
|
|
586
|
-
}
|
|
587
|
-
const snapshot = normalizeSnapshot(parsed, input.resourceId, true);
|
|
588
|
-
if (snapshot.version !== Number(input.snapshotVersion)) {
|
|
589
|
-
throw invalid('code project snapshot version mismatch', 409);
|
|
590
|
-
}
|
|
591
|
-
return { snapshot };
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
function installSnapshot(file, input, options = {}) {
|
|
595
|
-
const database = options.database || openLocalDb();
|
|
596
|
-
const replicas = require('../state/replicas');
|
|
597
|
-
const envelope = replicas.validateSnapshotEnvelope(input, {
|
|
598
|
-
contract: STREAM_CONTRACT,
|
|
599
|
-
schemaVersion: 2,
|
|
600
|
-
});
|
|
601
|
-
const { snapshot } = decodeSnapshot(input);
|
|
602
|
-
const existing = replicas.getReplicaByResourceId(input.resourceId, { database });
|
|
603
|
-
if (existing && existing.localPath !== file) {
|
|
604
|
-
throw invalid('code project stream is bound to another local file', 409);
|
|
605
|
-
}
|
|
606
|
-
const current = fs.existsSync(file)
|
|
607
|
-
? readSnapshot(file, input.resourceId)
|
|
608
|
-
: emptySnapshot(input.resourceId);
|
|
609
|
-
if (snapshot.version < current.version) {
|
|
610
|
-
const checked = existing
|
|
611
|
-
? replicas.advanceReplicaSnapshot({
|
|
612
|
-
resourceId: envelope.resourceId,
|
|
613
|
-
snapshotVersion: envelope.snapshotVersion,
|
|
614
|
-
snapshotChecksum: input.snapshotChecksum,
|
|
615
|
-
authorityEpoch: envelope.authorityEpoch,
|
|
616
|
-
}, { database })
|
|
617
|
-
: existing;
|
|
618
|
-
return { replica: checked, duplicate: true, ignored: true };
|
|
619
|
-
}
|
|
620
|
-
if (snapshot.version === current.version) {
|
|
621
|
-
if (JSON.stringify(snapshot) !== JSON.stringify(current)) {
|
|
622
|
-
throw invalid(
|
|
623
|
-
'equal code project snapshot versions contain different state',
|
|
624
|
-
409,
|
|
625
|
-
'code_project_snapshot_mismatch',
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
|
-
if (existing) {
|
|
629
|
-
const replica = replicas.advanceReplicaSnapshot({
|
|
630
|
-
resourceId: input.resourceId,
|
|
631
|
-
snapshotVersion: envelope.snapshotVersion,
|
|
632
|
-
snapshotChecksum: input.snapshotChecksum,
|
|
633
|
-
authorityEpoch: envelope.authorityEpoch,
|
|
634
|
-
}, { database });
|
|
635
|
-
completeInstalledSnapshot(input.resourceId, snapshot.version, database);
|
|
636
|
-
return { replica, duplicate: true };
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
writeSnapshot(file, snapshot);
|
|
640
|
-
const registered = existing
|
|
641
|
-
? {
|
|
642
|
-
replica: replicas.advanceReplicaSnapshot({
|
|
643
|
-
resourceId: input.resourceId,
|
|
644
|
-
snapshotVersion: envelope.snapshotVersion,
|
|
645
|
-
snapshotChecksum: input.snapshotChecksum,
|
|
646
|
-
authorityEpoch: envelope.authorityEpoch,
|
|
647
|
-
}, { database }),
|
|
648
|
-
}
|
|
649
|
-
: replicas.registerReplica({
|
|
650
|
-
resourceId: input.resourceId,
|
|
651
|
-
localResource: `${LOCAL_RESOURCE_PREFIX}${input.resourceId}`,
|
|
652
|
-
localPath: file,
|
|
653
|
-
contract: STREAM_CONTRACT,
|
|
654
|
-
schemaVersion: 2,
|
|
655
|
-
authorityEpoch: envelope.authorityEpoch,
|
|
656
|
-
appliedVersion: envelope.snapshotVersion,
|
|
657
|
-
snapshotVersion: envelope.snapshotVersion,
|
|
658
|
-
snapshotChecksum: input.snapshotChecksum,
|
|
659
|
-
}, { database });
|
|
660
|
-
completeInstalledSnapshot(input.resourceId, snapshot.version, database);
|
|
661
|
-
appendStateEvent({
|
|
662
|
-
resource: `${LOCAL_RESOURCE_PREFIX}${input.resourceId}`,
|
|
663
|
-
op: 'update',
|
|
664
|
-
id: file,
|
|
665
|
-
sharedResourceId: input.resourceId,
|
|
666
|
-
resourceVersion: envelope.snapshotVersion,
|
|
667
|
-
source: 'code-project:snapshot',
|
|
668
|
-
});
|
|
669
|
-
return registered;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
function completeInstalledSnapshot(resourceId, through, database) {
|
|
673
|
-
require('../state/mutations').completeOfficialSnapshotFold(
|
|
674
|
-
resourceId,
|
|
675
|
-
through,
|
|
676
|
-
{ database },
|
|
677
|
-
);
|
|
678
|
-
const project = require('./wall').projectForResource(resourceId);
|
|
679
|
-
if (!project) return;
|
|
680
|
-
const intents = require('./intent');
|
|
681
|
-
const held = intents.readIntent(project);
|
|
682
|
-
const acknowledgement = held
|
|
683
|
-
? intents.officialAcknowledgement(project, held)
|
|
684
|
-
: null;
|
|
685
|
-
if (acknowledgement && acknowledgement.version <= through) {
|
|
686
|
-
intents.markPublicationFolded(
|
|
687
|
-
project,
|
|
688
|
-
acknowledgement.mutationId,
|
|
689
|
-
acknowledgement.version,
|
|
690
|
-
held.publication.numberedAt,
|
|
691
|
-
);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
function applyCloudMutation(input, options = {}) {
|
|
696
|
-
const envelope = input?.envelope || {};
|
|
697
|
-
if (
|
|
698
|
-
envelope.contract !== STREAM_CONTRACT
|
|
699
|
-
|| Number(envelope.schemaVersion) !== 2
|
|
700
|
-
|| envelope.operationKind !== 'code.publish'
|
|
701
|
-
) throw invalid('invalid code project cloud mutation', 409);
|
|
702
|
-
const operation = normalizeOperation(envelope.operation, envelope.resourceId);
|
|
703
|
-
const database = options.database || openLocalDb();
|
|
704
|
-
const replica = require('../state/replicas').getReplicaByResourceId(
|
|
705
|
-
envelope.resourceId,
|
|
706
|
-
{ database },
|
|
707
|
-
);
|
|
708
|
-
if (!replica || replica.contract !== STREAM_CONTRACT) {
|
|
709
|
-
throw invalid('unknown code project stream', 404);
|
|
710
|
-
}
|
|
711
|
-
return require('../state/mutations').ingestCloudMutation({
|
|
712
|
-
...input,
|
|
713
|
-
localResource: `${LOCAL_RESOURCE_PREFIX}${envelope.resourceId}`,
|
|
714
|
-
}, {
|
|
715
|
-
database,
|
|
716
|
-
requireOfficialMaterialization: true,
|
|
717
|
-
materialize() {
|
|
718
|
-
const next = fold(
|
|
719
|
-
readSnapshot(replica.localPath, envelope.resourceId),
|
|
720
|
-
operation,
|
|
721
|
-
{
|
|
722
|
-
machine: envelope.deviceId,
|
|
723
|
-
mutationId: envelope.mutationId,
|
|
724
|
-
version: Number(input.version),
|
|
725
|
-
},
|
|
726
|
-
);
|
|
727
|
-
writeSnapshot(replica.localPath, next);
|
|
728
|
-
const project = require('./wall').projectForResource(envelope.resourceId);
|
|
729
|
-
if (project) {
|
|
730
|
-
require('./intent').markPublicationFolded(
|
|
731
|
-
project,
|
|
732
|
-
envelope.mutationId,
|
|
733
|
-
Number(input.version),
|
|
734
|
-
input.committedAt,
|
|
735
|
-
);
|
|
736
|
-
}
|
|
737
|
-
},
|
|
738
|
-
event: {
|
|
739
|
-
resource: `${LOCAL_RESOURCE_PREFIX}${envelope.resourceId}`,
|
|
740
|
-
op: 'update',
|
|
741
|
-
id: replica.localPath,
|
|
742
|
-
sharedResourceId: envelope.resourceId,
|
|
743
|
-
resourceVersion: Number(input.version),
|
|
744
|
-
value: operation.delta,
|
|
745
|
-
source: 'code-project:cloud',
|
|
746
|
-
},
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
module.exports = {
|
|
751
|
-
BUNDLE_CONTRACT,
|
|
752
|
-
CARD_CONTRACT,
|
|
753
|
-
CHECKPOINT_CONTRACT,
|
|
754
|
-
LOCAL_RESOURCE_PREFIX,
|
|
755
|
-
PAYLOAD_CONTRACT,
|
|
756
|
-
STREAM_CONTRACT,
|
|
757
|
-
WALL_CONTRACT,
|
|
758
|
-
_test: {
|
|
759
|
-
emptySnapshot,
|
|
760
|
-
fold,
|
|
761
|
-
normalizeDelta,
|
|
762
|
-
normalizeOperation,
|
|
763
|
-
normalizeSnapshot,
|
|
764
|
-
},
|
|
765
|
-
applyCloudMutation,
|
|
766
|
-
emptySnapshot,
|
|
767
|
-
installSnapshot,
|
|
768
|
-
publish,
|
|
769
|
-
readSnapshot,
|
|
770
|
-
writeSnapshot,
|
|
771
|
-
};
|