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
|
@@ -13,10 +13,7 @@
|
|
|
13
13
|
* real local edit or revert is one new transition.
|
|
14
14
|
*
|
|
15
15
|
* Checkpoint files below are local capture cache only. Their operations ride
|
|
16
|
-
* inside the
|
|
17
|
-
*
|
|
18
|
-
* Axiom 4: only an exact local observation may create or update intent.
|
|
19
|
-
* Proven by workspace-local-intent.test.js.
|
|
16
|
+
* inside the machine wall, so the entire repo has one ordered stream.
|
|
20
17
|
*/
|
|
21
18
|
|
|
22
19
|
const crypto = require('crypto');
|
|
@@ -26,10 +23,9 @@ const { AMALGM_DIR } = require('../config');
|
|
|
26
23
|
const { appendStateEvent, subscribeStateEvents } = require('../state/events');
|
|
27
24
|
const { machineId } = require('./identity');
|
|
28
25
|
const { listRepoCards } = require('./cards');
|
|
29
|
-
const
|
|
30
|
-
const { describeBytes, manifestChecksum, sha256 } = require('./content');
|
|
26
|
+
const { captureCheckpoint } = require('./git');
|
|
31
27
|
|
|
32
|
-
const CHECKPOINT_CONTRACT = 'repo-checkpoint@
|
|
28
|
+
const CHECKPOINT_CONTRACT = 'repo-checkpoint@8';
|
|
33
29
|
const MAX_INLINE_DIFF_BYTES = 512 * 1024;
|
|
34
30
|
const PUBLISH_QUIET_MS = 25;
|
|
35
31
|
const RECONCILE_INTERVAL_MS = 30_000;
|
|
@@ -58,75 +54,15 @@ function checkpointFile(machine, repoKey) {
|
|
|
58
54
|
.update(`${CHECKPOINT_CONTRACT}|${machine}|${repoKey}`)
|
|
59
55
|
.digest('hex')
|
|
60
56
|
.slice(0, 24);
|
|
61
|
-
return path.join(AMALGM_DIR, 'state', 'repo-checkpoints-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Git can print the same tree in different file order depending on whether
|
|
66
|
-
* a path is tracked or still untracked. File order is transport formatting,
|
|
67
|
-
* not content identity, so sort complete file patches before hashing them.
|
|
68
|
-
*/
|
|
69
|
-
function patchSections(diff) {
|
|
70
|
-
if (!diff) return [];
|
|
71
|
-
if (!diff.startsWith('diff --git ')) return [diff];
|
|
72
|
-
const sections = [];
|
|
73
|
-
let start = 0;
|
|
74
|
-
for (;;) {
|
|
75
|
-
const next = diff.indexOf('\ndiff --git ', start + 1);
|
|
76
|
-
if (next === -1) {
|
|
77
|
-
sections.push(diff.slice(start));
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
sections.push(diff.slice(start, next + 1));
|
|
81
|
-
start = next + 1;
|
|
82
|
-
}
|
|
83
|
-
return sections.sort((left, right) => (
|
|
84
|
-
left < right ? -1 : left > right ? 1 : 0
|
|
85
|
-
));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function canonicalPatch(diff) {
|
|
89
|
-
return patchSections(diff).join('');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function sectionIdentity(text) {
|
|
93
|
-
return manifestChecksum(describeBytes(Buffer.from(text, 'utf8')));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function revisionOfSections(baseOid, indexSections, worktreeSections) {
|
|
97
|
-
const index = [...indexSections].sort();
|
|
98
|
-
const worktree = [...worktreeSections].sort();
|
|
99
|
-
if (index.length === 0 && worktree.length === 0) return null;
|
|
100
|
-
return sha256(Buffer.from(JSON.stringify({ baseOid, index, worktree }), 'utf8'));
|
|
57
|
+
return path.join(AMALGM_DIR, 'state', 'repo-checkpoints-v8', `${digest}.json`);
|
|
101
58
|
}
|
|
102
59
|
|
|
103
60
|
function revisionOf(baseOid, indexDiff, worktreeDiff = '') {
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function revisionOfValue(baseOid, value) {
|
|
112
|
-
if (
|
|
113
|
-
typeof value?.indexDiff === 'string'
|
|
114
|
-
&& typeof value?.worktreeDiff === 'string'
|
|
115
|
-
) return revisionOf(baseOid, value.indexDiff, value.worktreeDiff);
|
|
116
|
-
if (!value?.sections) throw new Error('checkpoint value has no content');
|
|
117
|
-
const identities = (layer) => {
|
|
118
|
-
if (!Array.isArray(value.sections[layer])) throw new Error('checkpoint sections are invalid');
|
|
119
|
-
return value.sections[layer].map((section) => {
|
|
120
|
-
if (typeof section?.text === 'string' && section.text.length > 0) {
|
|
121
|
-
return sectionIdentity(section.text);
|
|
122
|
-
}
|
|
123
|
-
if (/^[0-9a-f]{64}$/.test(String(section?.payload?.checksum || ''))) {
|
|
124
|
-
return section.payload.checksum;
|
|
125
|
-
}
|
|
126
|
-
throw new Error('checkpoint section has no content identity');
|
|
127
|
-
});
|
|
128
|
-
};
|
|
129
|
-
return revisionOfSections(baseOid, identities('index'), identities('worktree'));
|
|
61
|
+
return indexDiff === '' && worktreeDiff === ''
|
|
62
|
+
? null
|
|
63
|
+
: crypto.createHash('sha256')
|
|
64
|
+
.update(`${baseOid}\nindex\n${indexDiff}\nworktree\n${worktreeDiff}`, 'utf8')
|
|
65
|
+
.digest('hex');
|
|
130
66
|
}
|
|
131
67
|
|
|
132
68
|
function parseCheckpoint(text) {
|
|
@@ -153,16 +89,6 @@ function readCheckpoint(machine, repoKey) {
|
|
|
153
89
|
}
|
|
154
90
|
}
|
|
155
91
|
|
|
156
|
-
function stampCheckpointMutation(machine, repoKey, checkpoint, mutationId) {
|
|
157
|
-
if (!mutationId) return false;
|
|
158
|
-
const existing = readCheckpoint(machine, repoKey);
|
|
159
|
-
if (!existing || !sameTransition(existing, checkpoint)) return false;
|
|
160
|
-
if (existing.mutationId === mutationId) return true;
|
|
161
|
-
const file = checkpointFile(machine, repoKey);
|
|
162
|
-
durable.writeJson(file, { ...existing, mutationId });
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
92
|
function valueIdentity(value) {
|
|
167
93
|
return JSON.stringify({
|
|
168
94
|
revision: value?.revision ?? null,
|
|
@@ -170,502 +96,81 @@ function valueIdentity(value) {
|
|
|
170
96
|
});
|
|
171
97
|
}
|
|
172
98
|
|
|
99
|
+
function parkedIdentity(parked) {
|
|
100
|
+
return JSON.stringify((parked || []).map((entry) => {
|
|
101
|
+
const stable = { ...entry };
|
|
102
|
+
delete stable.version;
|
|
103
|
+
return stable;
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
|
|
173
107
|
function sameTransition(left, right) {
|
|
174
108
|
return left
|
|
175
109
|
&& left.baseOid === right.baseOid
|
|
176
110
|
&& left.branch === right.branch
|
|
177
111
|
&& valueIdentity(left.from) === valueIdentity(right.from)
|
|
178
|
-
&& valueIdentity(left.to) === valueIdentity(right.to)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
function sectionEntry(machine, repoKey, baseOid, layer, text, external) {
|
|
182
|
-
if (!external) return { text };
|
|
183
|
-
const digest = crypto.createHash('sha256').update(text, 'utf8').digest('hex');
|
|
184
|
-
return {
|
|
185
|
-
payload: require('./objects').publishPayload(
|
|
186
|
-
`${CHECKPOINT_CONTRACT}\n${machine}\n${repoKey}\n${baseOid}\n${layer}\n${digest}`,
|
|
187
|
-
Buffer.from(text, 'utf8'),
|
|
188
|
-
repoKey,
|
|
189
|
-
),
|
|
190
|
-
};
|
|
112
|
+
&& valueIdentity(left.to) === valueIdentity(right.to)
|
|
113
|
+
&& parkedIdentity(left.parked) === parkedIdentity(right.parked);
|
|
191
114
|
}
|
|
192
115
|
|
|
193
116
|
function checkpointValue(machine, repoKey, baseOid, indexDiff, worktreeDiff) {
|
|
194
117
|
const revision = revisionOf(baseOid, indexDiff, worktreeDiff);
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
const bytes = Buffer.from(
|
|
198
|
-
JSON.stringify({ indexDiff: canonicalIndex, worktreeDiff: canonicalWorktree }),
|
|
199
|
-
'utf8',
|
|
200
|
-
);
|
|
201
|
-
if (bytes.length <= MAX_INLINE_DIFF_BYTES) {
|
|
202
|
-
return { revision, indexDiff: canonicalIndex, worktreeDiff: canonicalWorktree };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const sections = [
|
|
206
|
-
...patchSections(canonicalIndex).map((text, index) => ({
|
|
207
|
-
key: `index:${index}`,
|
|
208
|
-
layer: 'index',
|
|
209
|
-
text,
|
|
210
|
-
bytes: Buffer.byteLength(text, 'utf8'),
|
|
211
|
-
})),
|
|
212
|
-
...patchSections(canonicalWorktree).map((text, index) => ({
|
|
213
|
-
key: `worktree:${index}`,
|
|
214
|
-
layer: 'worktree',
|
|
215
|
-
text,
|
|
216
|
-
bytes: Buffer.byteLength(text, 'utf8'),
|
|
217
|
-
})),
|
|
218
|
-
];
|
|
219
|
-
const external = new Set(
|
|
220
|
-
sections.filter((section) => section.bytes > MAX_INLINE_DIFF_BYTES)
|
|
221
|
-
.map((section) => section.key),
|
|
222
|
-
);
|
|
223
|
-
let inlineBytes = sections
|
|
224
|
-
.filter((section) => !external.has(section.key))
|
|
225
|
-
.reduce((total, section) => total + section.bytes, 0);
|
|
226
|
-
for (const section of sections
|
|
227
|
-
.filter((candidate) => !external.has(candidate.key))
|
|
228
|
-
.sort((left, right) => right.bytes - left.bytes || left.key.localeCompare(right.key))) {
|
|
229
|
-
if (inlineBytes <= MAX_INLINE_DIFF_BYTES) break;
|
|
230
|
-
external.add(section.key);
|
|
231
|
-
inlineBytes -= section.bytes;
|
|
232
|
-
}
|
|
233
|
-
|
|
118
|
+
const bytes = Buffer.from(JSON.stringify({ indexDiff, worktreeDiff }), 'utf8');
|
|
119
|
+
if (bytes.length <= MAX_INLINE_DIFF_BYTES) return { revision, indexDiff, worktreeDiff };
|
|
234
120
|
return {
|
|
235
121
|
revision,
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
repoKey,
|
|
242
|
-
baseOid,
|
|
243
|
-
section.layer,
|
|
244
|
-
section.text,
|
|
245
|
-
external.has(section.key),
|
|
246
|
-
)),
|
|
247
|
-
worktree: sections
|
|
248
|
-
.filter((section) => section.layer === 'worktree')
|
|
249
|
-
.map((section) => sectionEntry(
|
|
250
|
-
machine,
|
|
251
|
-
repoKey,
|
|
252
|
-
baseOid,
|
|
253
|
-
section.layer,
|
|
254
|
-
section.text,
|
|
255
|
-
external.has(section.key),
|
|
256
|
-
)),
|
|
257
|
-
},
|
|
122
|
+
payload: require('./objects').publishPayload(
|
|
123
|
+
`${CHECKPOINT_CONTRACT}\n${machine}\n${repoKey}\n${baseOid}\n${revision || 'clean'}`,
|
|
124
|
+
bytes,
|
|
125
|
+
repoKey,
|
|
126
|
+
),
|
|
258
127
|
};
|
|
259
128
|
}
|
|
260
129
|
|
|
261
130
|
function checkpointValueReady(value) {
|
|
262
|
-
if (value?.sections) {
|
|
263
|
-
return ['index', 'worktree'].every((layer) => (
|
|
264
|
-
Array.isArray(value.sections[layer])
|
|
265
|
-
&& value.sections[layer].every((section) => (
|
|
266
|
-
typeof section?.text === 'string'
|
|
267
|
-
|| section?.payload?.kind === 'chunks'
|
|
268
|
-
))
|
|
269
|
-
));
|
|
270
|
-
}
|
|
271
131
|
return !value?.payload || value.payload.kind === 'chunks';
|
|
272
132
|
}
|
|
273
133
|
|
|
274
|
-
function
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
objects: branch.objects,
|
|
286
|
-
})).sort((left, right) => left.name.localeCompare(right.name)),
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function intentBase(state) {
|
|
291
|
-
if (
|
|
292
|
-
state?.verificationContract !== 'verified-repo-state@1'
|
|
293
|
-
|| state.materialized !== true
|
|
294
|
-
|| !Number.isInteger(state.appliedThrough)
|
|
295
|
-
|| state.appliedThrough < 0
|
|
296
|
-
|| !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(String(state.targetCard?.oid || ''))
|
|
297
|
-
|| !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(String(state.heldIndexTree || ''))
|
|
298
|
-
|| !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(String(state.heldTree || ''))
|
|
299
|
-
|| !Array.isArray(state.branches)
|
|
300
|
-
) return null;
|
|
301
|
-
return {
|
|
302
|
-
through: state.appliedThrough,
|
|
303
|
-
card: {
|
|
304
|
-
head: {
|
|
305
|
-
branch: state.targetCard.branch ?? null,
|
|
306
|
-
oid: state.targetCard.oid,
|
|
307
|
-
detached: !state.targetCard.branch,
|
|
308
|
-
},
|
|
309
|
-
branches: state.branches.map((branch) => ({
|
|
310
|
-
name: branch.name,
|
|
311
|
-
oid: branch.oid,
|
|
312
|
-
})).sort((left, right) => left.name.localeCompare(right.name)),
|
|
313
|
-
},
|
|
314
|
-
indexTree: state.heldIndexTree,
|
|
315
|
-
worktreeTree: state.heldTree,
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function hasIntentEffect(base, local) {
|
|
320
|
-
const card = require('./intent')._test.cardState;
|
|
321
|
-
return JSON.stringify(card(base.card)) !== JSON.stringify(card(local.card))
|
|
322
|
-
|| base.indexTree !== local.indexTree
|
|
323
|
-
|| base.worktreeTree !== local.worktreeTree;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
async function initialIntentBase(project, card) {
|
|
327
|
-
if (project.ownerUserId !== require('./identity').userId()) return null;
|
|
328
|
-
const replica = require('../state/replicas').getReplicaByResourceId(project.resourceId);
|
|
329
|
-
if (!replica || replica.appliedVersion !== 0) return null;
|
|
330
|
-
const snapshot = require('./project-stream').readSnapshot(project.file, project.resourceId);
|
|
331
|
-
if (snapshot.version !== 0 || snapshot.card !== null || snapshot.checkpoint !== null) return null;
|
|
332
|
-
const emptyTree = await require('./transition').emptyTree(card.path);
|
|
333
|
-
return {
|
|
334
|
-
through: 0,
|
|
335
|
-
card: null,
|
|
336
|
-
indexTree: emptyTree,
|
|
337
|
-
worktreeTree: emptyTree,
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
async function gitlinkPaths(card, observed) {
|
|
342
|
-
const merge = require('./merge');
|
|
343
|
-
const trees = new Set([
|
|
344
|
-
observed.value.indexTree,
|
|
345
|
-
observed.value.worktreeTree,
|
|
346
|
-
card.head.oid,
|
|
347
|
-
...(card.refs || card.branches || []).map((branch) => branch.oid),
|
|
348
|
-
].filter(Boolean));
|
|
349
|
-
const paths = new Set();
|
|
350
|
-
for (const tree of trees) {
|
|
351
|
-
for (const entry of await merge.gitlinksInTree(card.path, tree)) paths.add(entry);
|
|
134
|
+
function checkpointForSharedState(existing, {
|
|
135
|
+
machine,
|
|
136
|
+
repoKey,
|
|
137
|
+
branch,
|
|
138
|
+
baseOid,
|
|
139
|
+
sharedValue,
|
|
140
|
+
parked,
|
|
141
|
+
capturedAt = new Date().toISOString(),
|
|
142
|
+
}) {
|
|
143
|
+
if (existing?.baseOid === baseOid && existing.branch === branch) {
|
|
144
|
+
return { ...existing, parked };
|
|
352
145
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Capture is the only place physical state may become local authorship.
|
|
358
|
-
* It requires a verified baseline (or an already-owned intent) and runs only
|
|
359
|
-
* outside the reducer's materialization section.
|
|
360
|
-
*/
|
|
361
|
-
async function recordObservedIntent(
|
|
362
|
-
project,
|
|
363
|
-
card,
|
|
364
|
-
checkpoint,
|
|
365
|
-
base,
|
|
366
|
-
observed,
|
|
367
|
-
options = {},
|
|
368
|
-
) {
|
|
369
|
-
const intents = require('./intent');
|
|
370
|
-
const local = {
|
|
371
|
-
card: exactCard(card),
|
|
372
|
-
checkpoint: {
|
|
373
|
-
checkpoint: CHECKPOINT_CONTRACT,
|
|
374
|
-
baseOid: card.head.oid,
|
|
375
|
-
branch: card.head.branch ?? null,
|
|
376
|
-
value: checkpoint.to,
|
|
377
|
-
},
|
|
378
|
-
indexTree: observed.value.indexTree,
|
|
379
|
-
worktreeTree: observed.value.worktreeTree,
|
|
380
|
-
physicalDigest: observed.digest,
|
|
381
|
-
};
|
|
382
|
-
const links = await gitlinkPaths(card, observed);
|
|
383
|
-
const terminal = links.length > 0
|
|
384
|
-
? {
|
|
385
|
-
code: 'unsupported_gitlink',
|
|
386
|
-
message: `unsupported gitlink/submodule at ${links[0]}`,
|
|
387
|
-
}
|
|
388
|
-
: null;
|
|
389
|
-
const previous = intents.readIntent(project);
|
|
390
|
-
const previousSuccessor = previous?.successor || null;
|
|
391
|
-
if (previous?.terminal && observed.digest === previous.local.physicalDigest) {
|
|
392
|
-
return {
|
|
393
|
-
state: 'rejected',
|
|
394
|
-
reason: previous.terminal.code,
|
|
395
|
-
intent: previous,
|
|
396
|
-
unchanged: true,
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
if (
|
|
400
|
-
previousSuccessor?.terminal
|
|
401
|
-
&& observed.digest === previousSuccessor.local.physicalDigest
|
|
402
|
-
) {
|
|
403
|
-
return {
|
|
404
|
-
state: 'rejected',
|
|
405
|
-
reason: previousSuccessor.terminal.code,
|
|
406
|
-
intent: previous,
|
|
407
|
-
successor: previousSuccessor,
|
|
408
|
-
unchanged: true,
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
const nextSemanticDigest = intents._test.semanticDigest({ local });
|
|
412
|
-
const acknowledgement = previous
|
|
413
|
-
? intents.officialAcknowledgement(project, previous)
|
|
414
|
-
: null;
|
|
415
|
-
if (
|
|
416
|
-
acknowledgement
|
|
417
|
-
&& previous.semanticDigest === nextSemanticDigest
|
|
418
|
-
&& base.through <= acknowledgement.version
|
|
419
|
-
) {
|
|
420
|
-
if (previousSuccessor) intents.clearSuccessor(project, previous);
|
|
421
|
-
return { state: 'silent', reason: 'officially-acknowledged' };
|
|
422
|
-
}
|
|
423
|
-
const mayReplaceAnchored = Boolean(
|
|
424
|
-
previous?.publication
|
|
425
|
-
&& (
|
|
426
|
-
previous.semanticDigest === options.replaceAnchoredIntent
|
|
427
|
-
|| (acknowledgement && acknowledgement.version <= base.through)
|
|
428
|
-
|| intents.publicationMayRebase(project, previous, base.through)
|
|
429
|
-
)
|
|
430
|
-
);
|
|
431
|
-
if (
|
|
432
|
-
previous?.publication
|
|
433
|
-
&& previous.semanticDigest !== nextSemanticDigest
|
|
434
|
-
&& !mayReplaceAnchored
|
|
435
|
-
) {
|
|
436
|
-
const successor = intents.writeSuccessor(project, previous, {
|
|
437
|
-
local,
|
|
438
|
-
terminal,
|
|
439
|
-
});
|
|
440
|
-
if (!successor) {
|
|
441
|
-
return {
|
|
442
|
-
state: 'waiting',
|
|
443
|
-
reason: 'local-intent-moving',
|
|
444
|
-
intent: previous,
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
return {
|
|
448
|
-
state: terminal ? 'rejected' : 'captured',
|
|
449
|
-
reason: terminal ? terminal.code : 'successor-captured',
|
|
450
|
-
intent: previous,
|
|
451
|
-
successor,
|
|
452
|
-
mayMaterialize: true,
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
if (!terminal && !hasIntentEffect(base, local)) {
|
|
456
|
-
if (
|
|
457
|
-
options.preserveNoEffectIntent === true
|
|
458
|
-
&& previous
|
|
459
|
-
&& !previous.publication
|
|
460
|
-
) {
|
|
461
|
-
const saved = intents.writeIntent(project, {
|
|
462
|
-
base,
|
|
463
|
-
local,
|
|
464
|
-
terminal: null,
|
|
465
|
-
publication: null,
|
|
466
|
-
birthMutationId: previous.birthMutationId,
|
|
467
|
-
preserveBirthMutationId: true,
|
|
468
|
-
});
|
|
469
|
-
return {
|
|
470
|
-
state: 'captured',
|
|
471
|
-
reason: 'identity-confirmation',
|
|
472
|
-
intent: saved,
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
if (!previous?.publication || mayReplaceAnchored) {
|
|
476
|
-
intents.clearIntent(project);
|
|
477
|
-
return { state: 'silent', reason: 'no-local-effect' };
|
|
478
|
-
}
|
|
479
|
-
if (acknowledgement && acknowledgement.version <= base.through) {
|
|
480
|
-
return { state: 'silent', reason: 'officially-acknowledged' };
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
const candidate = {
|
|
484
|
-
base,
|
|
485
|
-
local,
|
|
486
|
-
terminal,
|
|
487
|
-
successor: null,
|
|
488
|
-
publication: (
|
|
489
|
-
previous?.semanticDigest === nextSemanticDigest
|
|
490
|
-
&& previous.publication?.baseThrough === base.through
|
|
491
|
-
)
|
|
492
|
-
? previous.publication
|
|
493
|
-
: null,
|
|
494
|
-
birthMutationId: options.preserveBirthMutationId === true
|
|
495
|
-
? previous?.birthMutationId
|
|
496
|
-
: mayReplaceAnchored
|
|
497
|
-
? previousSuccessor?.birthMutationId
|
|
498
|
-
: null,
|
|
499
|
-
preserveBirthMutationId: options.preserveBirthMutationId === true,
|
|
500
|
-
};
|
|
501
|
-
const saved = intents.writeIntent(project, candidate);
|
|
502
|
-
return terminal
|
|
503
|
-
? { state: 'rejected', reason: terminal.code, intent: saved }
|
|
504
|
-
: { state: 'captured', intent: saved };
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
async function captureIntentObservationWithinTurn(project, card, checkpoint) {
|
|
508
|
-
const reducer = require('./reducer');
|
|
509
|
-
const state = reducer.readRepoState(project.repoKey, project.resourceId);
|
|
510
|
-
const base = intentBase(state) || await initialIntentBase(project, card);
|
|
511
|
-
if (!base) {
|
|
512
|
-
return { state: 'silent', reason: 'no-verified-baseline' };
|
|
513
|
-
}
|
|
514
|
-
const observed = await reducer.observePhysicalRepo(card.path);
|
|
515
|
-
if (observed.state !== 'ready') return { state: 'waiting', reason: 'local-state-moving' };
|
|
516
|
-
const observedRevision = revisionOf(
|
|
517
|
-
observed.captured.baseOid,
|
|
518
|
-
observed.captured.indexDiff,
|
|
519
|
-
observed.captured.worktreeDiff,
|
|
520
|
-
);
|
|
521
|
-
const observedCard = {
|
|
522
|
-
head: {
|
|
523
|
-
branch: observed.value.head.branch ?? null,
|
|
524
|
-
oid: observed.value.head.oid,
|
|
525
|
-
detached: !observed.value.head.branch,
|
|
526
|
-
},
|
|
527
|
-
branches: observed.value.branches,
|
|
528
|
-
};
|
|
529
|
-
const intents = require('./intent');
|
|
530
|
-
if (
|
|
531
|
-
observed.captured.baseOid !== card.head.oid
|
|
532
|
-
|| (
|
|
533
|
-
checkpoint
|
|
534
|
-
&& (
|
|
535
|
-
checkpoint.to?.revision !== observedRevision
|
|
536
|
-
|| checkpoint.baseOid !== observed.captured.baseOid
|
|
537
|
-
|| (checkpoint.branch ?? null) !== (observed.value.head.branch ?? null)
|
|
538
|
-
)
|
|
539
|
-
)
|
|
540
|
-
|| JSON.stringify(intents._test.cardState(exactCard(card)))
|
|
541
|
-
!== JSON.stringify(intents._test.cardState(observedCard))
|
|
542
|
-
) {
|
|
543
|
-
return { state: 'waiting', reason: 'local-state-moving' };
|
|
544
|
-
}
|
|
545
|
-
const to = checkpointValue(
|
|
546
|
-
machineId(),
|
|
547
|
-
project.repoKey,
|
|
548
|
-
observed.captured.baseOid,
|
|
549
|
-
observed.captured.indexDiff,
|
|
550
|
-
observed.captured.worktreeDiff,
|
|
551
|
-
);
|
|
552
|
-
if (!checkpointValueReady(to)) {
|
|
553
|
-
return { state: 'waiting', reason: 'checkpoint-payload-pending' };
|
|
554
|
-
}
|
|
555
|
-
const capturedCheckpoint = {
|
|
146
|
+
if (parked.length === 0) return null;
|
|
147
|
+
return {
|
|
556
148
|
checkpoint: CHECKPOINT_CONTRACT,
|
|
557
|
-
machine
|
|
558
|
-
repoKey
|
|
559
|
-
branch
|
|
560
|
-
baseOid
|
|
561
|
-
from:
|
|
562
|
-
to,
|
|
563
|
-
|
|
149
|
+
machine,
|
|
150
|
+
repoKey,
|
|
151
|
+
branch,
|
|
152
|
+
baseOid,
|
|
153
|
+
from: sharedValue,
|
|
154
|
+
to: sharedValue,
|
|
155
|
+
parked,
|
|
156
|
+
capturedAt,
|
|
564
157
|
};
|
|
565
|
-
if (!intents.readIntent(project) && observed.digest === state?.observedPhysicalDigest) {
|
|
566
|
-
return {
|
|
567
|
-
state: 'silent',
|
|
568
|
-
reason: 'verified-state',
|
|
569
|
-
checkpoint: capturedCheckpoint,
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
const result = await recordObservedIntent(
|
|
573
|
-
project,
|
|
574
|
-
card,
|
|
575
|
-
capturedCheckpoint,
|
|
576
|
-
base,
|
|
577
|
-
observed,
|
|
578
|
-
);
|
|
579
|
-
return { ...result, checkpoint: capturedCheckpoint };
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
function captureIntentObservation(project, card, checkpoint) {
|
|
583
|
-
return require('./reducer').withRepoTurn(
|
|
584
|
-
project.repoKey,
|
|
585
|
-
'capture',
|
|
586
|
-
() => captureIntentObservationWithinTurn(project, card, checkpoint),
|
|
587
|
-
);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
async function captureLocalIntent(project, card, checkpoint) {
|
|
591
|
-
const { checkpoint: capturedCheckpoint, ...outcome } = await captureIntentObservation(
|
|
592
|
-
project,
|
|
593
|
-
card,
|
|
594
|
-
checkpoint,
|
|
595
|
-
);
|
|
596
|
-
return outcome;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* A valid apply WAL is also an exact baseline. Recovery first resolves every
|
|
601
|
-
* source/target component to target while retaining values that equal
|
|
602
|
-
* neither. Only then does this function durably name the retained state as
|
|
603
|
-
* local intent over the official WAL target.
|
|
604
|
-
*/
|
|
605
|
-
async function captureIntentAgainst(project, card, base, observed, options = {}) {
|
|
606
|
-
if (!base || observed?.state !== 'ready' || !observed.captured) {
|
|
607
|
-
return { state: 'waiting', reason: 'local-state-moving' };
|
|
608
|
-
}
|
|
609
|
-
const to = checkpointValue(
|
|
610
|
-
machineId(),
|
|
611
|
-
project.repoKey,
|
|
612
|
-
card.head.oid,
|
|
613
|
-
observed.captured.indexDiff,
|
|
614
|
-
observed.captured.worktreeDiff,
|
|
615
|
-
);
|
|
616
|
-
if (!checkpointValueReady(to)) {
|
|
617
|
-
return { state: 'waiting', reason: 'checkpoint-payload-pending' };
|
|
618
|
-
}
|
|
619
|
-
return recordObservedIntent(project, card, { to }, base, observed, options);
|
|
620
158
|
}
|
|
621
159
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
return { state: 'silent', reason: 'no-registered-card' };
|
|
632
|
-
}
|
|
633
|
-
return captureLocalIntent(project, card, null);
|
|
160
|
+
function localParkedEntries(existing, reduced, card) {
|
|
161
|
+
const held = existing?.baseOid === card.head?.oid
|
|
162
|
+
&& existing.branch === card.head?.branch
|
|
163
|
+
? existing.parked || []
|
|
164
|
+
: [];
|
|
165
|
+
const unpublished = (reduced?.parked || []).filter((entry) => (
|
|
166
|
+
!Number.isInteger(entry.version) || entry.version < 1
|
|
167
|
+
));
|
|
168
|
+
return [...new Map([...held, ...unpublished].map((entry) => [entry.oid, entry])).values()];
|
|
634
169
|
}
|
|
635
170
|
|
|
636
171
|
function readCheckpointValue(baseOid, value) {
|
|
637
172
|
let indexDiff = typeof value?.indexDiff === 'string' ? value.indexDiff : '';
|
|
638
173
|
let worktreeDiff = typeof value?.worktreeDiff === 'string' ? value.worktreeDiff : '';
|
|
639
|
-
if (value?.sections) {
|
|
640
|
-
const readSections = (layer) => {
|
|
641
|
-
if (!Array.isArray(value.sections[layer])) return { state: 'payload-invalid', text: null };
|
|
642
|
-
const parts = [];
|
|
643
|
-
for (const section of value.sections[layer]) {
|
|
644
|
-
if (typeof section?.text === 'string') {
|
|
645
|
-
parts.push(section.text);
|
|
646
|
-
continue;
|
|
647
|
-
}
|
|
648
|
-
if (section?.payload?.kind === 'pending') {
|
|
649
|
-
return { state: 'payload-pending', text: null };
|
|
650
|
-
}
|
|
651
|
-
if (!section?.payload) return { state: 'payload-invalid', text: null };
|
|
652
|
-
const payload = require('./objects').readPayload(section.payload);
|
|
653
|
-
if (payload.state !== 'ready') return { state: payload.state, text: null };
|
|
654
|
-
parts.push(payload.bytes.toString('utf8'));
|
|
655
|
-
}
|
|
656
|
-
return { state: 'ready', text: parts.join('') };
|
|
657
|
-
};
|
|
658
|
-
const index = readSections('index');
|
|
659
|
-
if (index.state !== 'ready') {
|
|
660
|
-
return { state: index.state, indexDiff: null, worktreeDiff: null };
|
|
661
|
-
}
|
|
662
|
-
const worktree = readSections('worktree');
|
|
663
|
-
if (worktree.state !== 'ready') {
|
|
664
|
-
return { state: worktree.state, indexDiff: null, worktreeDiff: null };
|
|
665
|
-
}
|
|
666
|
-
indexDiff = index.text;
|
|
667
|
-
worktreeDiff = worktree.text;
|
|
668
|
-
}
|
|
669
174
|
if (value?.payload) {
|
|
670
175
|
if (value.payload.kind === 'pending') {
|
|
671
176
|
return { state: 'payload-pending', indexDiff: null, worktreeDiff: null };
|
|
@@ -692,6 +197,17 @@ function readCheckpointValue(baseOid, value) {
|
|
|
692
197
|
return { state: 'ready', indexDiff, worktreeDiff };
|
|
693
198
|
}
|
|
694
199
|
|
|
200
|
+
function checkpointBaseReady(card, reduced, publication) {
|
|
201
|
+
return Boolean(
|
|
202
|
+
reduced
|
|
203
|
+
&& publication?.current
|
|
204
|
+
&& reduced.baseOid === card.head?.oid
|
|
205
|
+
&& reduced.branch === card.head?.branch
|
|
206
|
+
&& typeof reduced.indexDiff === 'string'
|
|
207
|
+
&& typeof reduced.worktreeDiff === 'string'
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
695
211
|
function checkpointMayPublish(repoKey) {
|
|
696
212
|
return !require('./reducer').isMaterializing(repoKey);
|
|
697
213
|
}
|
|
@@ -702,62 +218,108 @@ async function publishPass(replicaId = null) {
|
|
|
702
218
|
for (const card of listRepoCards()) {
|
|
703
219
|
if (replicaId && card.replica !== replicaId) continue;
|
|
704
220
|
if (card.health !== 'healthy' || card.kind !== 'primary' || card.operation !== 'none') continue;
|
|
705
|
-
const
|
|
706
|
-
const project = wall.projectForPath(card.path);
|
|
707
|
-
const repoKey = project?.repoKey;
|
|
221
|
+
const repoKey = repoKeyOfCard(card);
|
|
708
222
|
if (!repoKey || !card.head?.oid) continue;
|
|
709
223
|
if (!checkpointMayPublish(repoKey)) {
|
|
710
224
|
run.waiting += 1;
|
|
711
225
|
continue;
|
|
712
226
|
}
|
|
713
227
|
try {
|
|
714
|
-
const
|
|
715
|
-
|
|
228
|
+
const reduced = require('./reducer').readRepoState(repoKey);
|
|
229
|
+
const wall = require('./wall');
|
|
230
|
+
if (!checkpointBaseReady(card, reduced, wall.publicationStatus(repoKey, reduced))) {
|
|
716
231
|
run.waiting += 1;
|
|
717
232
|
continue;
|
|
718
233
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
}
|
|
724
|
-
if (!intent.checkpoint) {
|
|
725
|
-
run.skips += 1;
|
|
234
|
+
|
|
235
|
+
const captured = await captureCheckpoint(card.path);
|
|
236
|
+
if (captured.indexDiff === null || captured.baseOid !== card.head.oid) {
|
|
237
|
+
run.waiting += 1;
|
|
726
238
|
continue;
|
|
727
239
|
}
|
|
240
|
+
const sharedRevision = revisionOf(card.head.oid, reduced.indexDiff, reduced.worktreeDiff);
|
|
241
|
+
const localRevision = revisionOf(card.head.oid, captured.indexDiff, captured.worktreeDiff);
|
|
728
242
|
const file = checkpointFile(machine, repoKey);
|
|
729
243
|
const existing = readCheckpoint(machine, repoKey);
|
|
730
|
-
const
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
if (
|
|
744
|
-
|
|
745
|
-
|
|
244
|
+
const parked = await Promise.all(localParkedEntries(existing, reduced, card).map(async (entry) => ({
|
|
245
|
+
...entry,
|
|
246
|
+
objects: await require('./objects').bundleSourceFor(card.path, repoKey, entry.oid),
|
|
247
|
+
})));
|
|
248
|
+
if (!wall.publicationStatus(repoKey, reduced).current) {
|
|
249
|
+
run.waiting += 1;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Exact equality means the working tree merely materialized the shared
|
|
254
|
+
// checkpoint. Keep the writer's last transition because it remains part
|
|
255
|
+
// of the reducer input. A newly parked commit still rides as metadata on
|
|
256
|
+
// a no-op transition; the working tree itself is never echoed.
|
|
257
|
+
if (sharedRevision === localRevision) {
|
|
258
|
+
const refreshed = checkpointForSharedState(existing, {
|
|
259
|
+
machine,
|
|
260
|
+
repoKey,
|
|
261
|
+
branch: card.head.branch,
|
|
262
|
+
baseOid: card.head.oid,
|
|
263
|
+
sharedValue: checkpointValue(
|
|
264
|
+
machine,
|
|
265
|
+
repoKey,
|
|
266
|
+
card.head.oid,
|
|
267
|
+
reduced.indexDiff,
|
|
268
|
+
reduced.worktreeDiff,
|
|
269
|
+
),
|
|
270
|
+
parked,
|
|
271
|
+
});
|
|
272
|
+
if (refreshed && !sameTransition(existing, refreshed)) {
|
|
273
|
+
fs.writeFileSync(file, `${JSON.stringify(refreshed)}\n`);
|
|
746
274
|
appendStateEvent({
|
|
747
275
|
resource: 'repo-checkpoints',
|
|
748
|
-
op: '
|
|
276
|
+
op: 'update',
|
|
749
277
|
id: card.replica,
|
|
750
|
-
|
|
278
|
+
value: refreshed,
|
|
279
|
+
source: 'repo-checkpoint:parked-objects',
|
|
751
280
|
});
|
|
281
|
+
run.published += 1;
|
|
282
|
+
continue;
|
|
752
283
|
}
|
|
753
284
|
run.skips += 1;
|
|
754
285
|
continue;
|
|
755
286
|
}
|
|
287
|
+
|
|
288
|
+
const from = checkpointValue(
|
|
289
|
+
machine,
|
|
290
|
+
repoKey,
|
|
291
|
+
card.head.oid,
|
|
292
|
+
reduced.indexDiff,
|
|
293
|
+
reduced.worktreeDiff,
|
|
294
|
+
);
|
|
295
|
+
const to = checkpointValue(
|
|
296
|
+
machine,
|
|
297
|
+
repoKey,
|
|
298
|
+
card.head.oid,
|
|
299
|
+
captured.indexDiff,
|
|
300
|
+
captured.worktreeDiff,
|
|
301
|
+
);
|
|
302
|
+
if (!checkpointValueReady(from) || !checkpointValueReady(to)) {
|
|
303
|
+
run.waiting += 1;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const envelope = {
|
|
307
|
+
checkpoint: CHECKPOINT_CONTRACT,
|
|
308
|
+
machine,
|
|
309
|
+
repoKey,
|
|
310
|
+
branch: card.head.branch,
|
|
311
|
+
baseOid: card.head.oid,
|
|
312
|
+
from,
|
|
313
|
+
to,
|
|
314
|
+
parked,
|
|
315
|
+
capturedAt: new Date().toISOString(),
|
|
316
|
+
};
|
|
756
317
|
if (sameTransition(existing, envelope)) {
|
|
757
318
|
run.skips += 1;
|
|
758
319
|
continue;
|
|
759
320
|
}
|
|
760
|
-
|
|
321
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
322
|
+
fs.writeFileSync(file, `${JSON.stringify(envelope)}\n`);
|
|
761
323
|
appendStateEvent({
|
|
762
324
|
resource: 'repo-checkpoints',
|
|
763
325
|
op: existing ? 'update' : 'insert',
|
|
@@ -845,7 +407,10 @@ function checkpointStats() {
|
|
|
845
407
|
|
|
846
408
|
module.exports = {
|
|
847
409
|
_test: {
|
|
410
|
+
checkpointBaseReady,
|
|
411
|
+
checkpointForSharedState,
|
|
848
412
|
checkpointMayPublish,
|
|
413
|
+
localParkedEntries,
|
|
849
414
|
},
|
|
850
415
|
CHECKPOINT_CONTRACT,
|
|
851
416
|
MAX_INLINE_DIFF_BYTES,
|
|
@@ -853,18 +418,13 @@ module.exports = {
|
|
|
853
418
|
checkpointStats,
|
|
854
419
|
checkpointValue,
|
|
855
420
|
checkpointValueReady,
|
|
856
|
-
captureIntentAgainst,
|
|
857
|
-
captureIntentForCard,
|
|
858
|
-
captureLocalIntent,
|
|
859
421
|
parseCheckpoint,
|
|
860
422
|
publishCheckpoints,
|
|
861
423
|
readCheckpoint,
|
|
862
424
|
readCheckpointValue,
|
|
863
425
|
repoKeyOfCard,
|
|
864
426
|
revisionOf,
|
|
865
|
-
revisionOfValue,
|
|
866
427
|
sameTransition,
|
|
867
|
-
stampCheckpointMutation,
|
|
868
428
|
startCheckpointService,
|
|
869
429
|
stopCheckpointService,
|
|
870
430
|
};
|