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,1999 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const test = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const crypto = require('crypto');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const { execFileSync } = require('child_process');
|
|
10
|
-
|
|
11
|
-
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-local-intent-'));
|
|
12
|
-
process.env.AMALGM_DIR = path.join(tempRoot, '.amalgm');
|
|
13
|
-
process.env.AMALGM_WORKSPACES_DIR = path.join(process.env.AMALGM_DIR, 'workspaces');
|
|
14
|
-
process.env.AMALGM_COMPUTER_ID = 'machine-intent';
|
|
15
|
-
process.env.AMALGM_DEVICE_ID = 'machine-intent';
|
|
16
|
-
fs.mkdirSync(process.env.AMALGM_DIR, { recursive: true });
|
|
17
|
-
const OWNER = '11111111-1111-4111-8111-111111111111';
|
|
18
|
-
fs.writeFileSync(
|
|
19
|
-
path.join(process.env.AMALGM_DIR, 'computer.json'),
|
|
20
|
-
JSON.stringify({ computer_id: 'machine-intent', user_id: OWNER }),
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
const { closeLocalDb, openLocalDb } = require('../state/db');
|
|
24
|
-
const attachments = require('../state/attachments');
|
|
25
|
-
const mutations = require('../state/mutations');
|
|
26
|
-
const replicas = require('../state/replicas');
|
|
27
|
-
const checkpoint = require('../workspace/checkpoint');
|
|
28
|
-
const intent = require('../workspace/intent');
|
|
29
|
-
const merge = require('../workspace/merge');
|
|
30
|
-
const projectStream = require('../workspace/project-stream');
|
|
31
|
-
const reducer = require('../workspace/reducer');
|
|
32
|
-
const wall = require('../workspace/wall');
|
|
33
|
-
|
|
34
|
-
test.after(() => {
|
|
35
|
-
closeLocalDb();
|
|
36
|
-
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
function git(repo, ...args) {
|
|
40
|
-
return execFileSync('git', ['-C', repo, ...args], { encoding: 'utf8' }).trim();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function createRepo(name) {
|
|
44
|
-
const repo = path.join(tempRoot, name);
|
|
45
|
-
fs.mkdirSync(repo, { recursive: true });
|
|
46
|
-
git(repo, 'init', '-q');
|
|
47
|
-
git(repo, 'config', 'user.email', 'intent@example.test');
|
|
48
|
-
git(repo, 'config', 'user.name', 'Intent Test');
|
|
49
|
-
fs.writeFileSync(path.join(repo, 'file.txt'), 'base\n');
|
|
50
|
-
git(repo, 'add', '.');
|
|
51
|
-
git(repo, 'commit', '-qm', 'base');
|
|
52
|
-
return repo;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function bundle(resourceId, oid, byte = 'a') {
|
|
56
|
-
const chunks = [{ sha256: byte.repeat(64), bytes: 1 }];
|
|
57
|
-
return {
|
|
58
|
-
kind: 'bundle',
|
|
59
|
-
contract: projectStream.BUNDLE_CONTRACT,
|
|
60
|
-
resourceId,
|
|
61
|
-
oid,
|
|
62
|
-
ref: `refs/amalgm/exports/${oid}`,
|
|
63
|
-
checksum: crypto.createHash('sha256').update(JSON.stringify(chunks)).digest('hex'),
|
|
64
|
-
bytes: 1,
|
|
65
|
-
chunks,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function cardFor(project, repo, replica = `replica-${project.resourceId.slice(-4)}`) {
|
|
70
|
-
const oid = git(repo, 'rev-parse', 'HEAD');
|
|
71
|
-
const branches = git(repo, 'for-each-ref', '--format=%(refname:short) %(objectname)', 'refs/heads')
|
|
72
|
-
.split('\n')
|
|
73
|
-
.filter(Boolean)
|
|
74
|
-
.map((line, index) => {
|
|
75
|
-
const [name, branchOid] = line.split(' ');
|
|
76
|
-
return { name, oid: branchOid, objects: bundle(project.resourceId, branchOid, String(index + 1)) };
|
|
77
|
-
});
|
|
78
|
-
const branch = git(repo, 'branch', '--show-current') || null;
|
|
79
|
-
return {
|
|
80
|
-
card: projectStream.CARD_CONTRACT,
|
|
81
|
-
machine: 'machine-intent',
|
|
82
|
-
replica,
|
|
83
|
-
path: fs.realpathSync(repo),
|
|
84
|
-
kind: 'primary',
|
|
85
|
-
health: 'healthy',
|
|
86
|
-
operation: 'none',
|
|
87
|
-
head: { branch, oid, detached: branch === null },
|
|
88
|
-
objects: bundle(project.resourceId, oid),
|
|
89
|
-
refs: branches,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function checkpointFor(project, card) {
|
|
94
|
-
const captured = await require('../workspace/git').captureCheckpoint(card.path);
|
|
95
|
-
return {
|
|
96
|
-
checkpoint: checkpoint.CHECKPOINT_CONTRACT,
|
|
97
|
-
machine: 'machine-intent',
|
|
98
|
-
repoKey: project.repoKey,
|
|
99
|
-
branch: card.head.branch,
|
|
100
|
-
baseOid: card.head.oid,
|
|
101
|
-
from: { revision: null, indexDiff: '', worktreeDiff: '' },
|
|
102
|
-
to: checkpoint.checkpointValue(
|
|
103
|
-
'machine-intent',
|
|
104
|
-
project.repoKey,
|
|
105
|
-
card.head.oid,
|
|
106
|
-
captured.indexDiff,
|
|
107
|
-
captured.worktreeDiff,
|
|
108
|
-
),
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function reducerFile(repoKey) {
|
|
113
|
-
const digest = crypto.createHash('sha256').update(repoKey).digest('hex').slice(0, 24);
|
|
114
|
-
return path.join(process.env.AMALGM_DIR, 'state', 'repo-reducer', `${digest}.json`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function setVerifiedBaseline(project, card, through = 1) {
|
|
118
|
-
const observed = await reducer.observePhysicalRepo(card.path);
|
|
119
|
-
const tree = await merge.treeOf(card.path, card.head.oid);
|
|
120
|
-
const state = {
|
|
121
|
-
repoKey: project.repoKey,
|
|
122
|
-
verificationContract: 'verified-repo-state@1',
|
|
123
|
-
resourceId: project.resourceId,
|
|
124
|
-
path: card.path,
|
|
125
|
-
replica: card.replica,
|
|
126
|
-
branch: card.head.branch,
|
|
127
|
-
baseOid: card.head.oid,
|
|
128
|
-
targetCard: {
|
|
129
|
-
branch: card.head.branch,
|
|
130
|
-
oid: card.head.oid,
|
|
131
|
-
version: through,
|
|
132
|
-
mutationId: `mutation_baseline_${project.resourceId.slice(-4)}`,
|
|
133
|
-
},
|
|
134
|
-
appliedThrough: through,
|
|
135
|
-
branches: card.refs.map((branch) => ({
|
|
136
|
-
...branch,
|
|
137
|
-
version: through,
|
|
138
|
-
mutationId: `mutation_baseline_${project.resourceId.slice(-4)}`,
|
|
139
|
-
})),
|
|
140
|
-
heldIndexTree: tree,
|
|
141
|
-
heldTree: tree,
|
|
142
|
-
observedPhysicalDigest: observed.digest,
|
|
143
|
-
materialized: true,
|
|
144
|
-
};
|
|
145
|
-
fs.mkdirSync(path.dirname(reducerFile(project.repoKey)), { recursive: true });
|
|
146
|
-
fs.writeFileSync(reducerFile(project.repoKey), `${JSON.stringify(state)}\n`);
|
|
147
|
-
return state;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function officialSnapshot(project, card, version = 1, mutationId = 'mutation_official_base_01') {
|
|
151
|
-
return projectStream._test.fold(
|
|
152
|
-
projectStream.emptySnapshot(project.resourceId),
|
|
153
|
-
{
|
|
154
|
-
kind: 'code.publish',
|
|
155
|
-
project: project.resourceId,
|
|
156
|
-
delta: {
|
|
157
|
-
kind: 'card.set',
|
|
158
|
-
card: projectStream.CARD_CONTRACT,
|
|
159
|
-
head: card.head,
|
|
160
|
-
objects: card.objects,
|
|
161
|
-
branches: card.refs,
|
|
162
|
-
checkpoint: {
|
|
163
|
-
checkpoint: checkpoint.CHECKPOINT_CONTRACT,
|
|
164
|
-
baseOid: card.head.oid,
|
|
165
|
-
branch: card.head.branch,
|
|
166
|
-
value: { revision: null, indexDiff: '', worktreeDiff: '' },
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
{ machine: 'machine-peer', mutationId, version },
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async function setup(name, resourceId) {
|
|
175
|
-
const repo = createRepo(name);
|
|
176
|
-
const project = wall.bindProject(resourceId, OWNER, repo);
|
|
177
|
-
const card = cardFor(project, repo);
|
|
178
|
-
const snapshot = officialSnapshot(project, card);
|
|
179
|
-
projectStream.writeSnapshot(project.file, snapshot);
|
|
180
|
-
replicas.registerReplica({
|
|
181
|
-
resourceId,
|
|
182
|
-
localResource: `${projectStream.LOCAL_RESOURCE_PREFIX}${resourceId}`,
|
|
183
|
-
localPath: project.file,
|
|
184
|
-
contract: projectStream.STREAM_CONTRACT,
|
|
185
|
-
schemaVersion: 2,
|
|
186
|
-
authorityEpoch: 1,
|
|
187
|
-
appliedVersion: 1,
|
|
188
|
-
snapshotVersion: 1,
|
|
189
|
-
snapshotChecksum: crypto.createHash('sha256').update(JSON.stringify(snapshot)).digest('hex'),
|
|
190
|
-
});
|
|
191
|
-
await setVerifiedBaseline(project, card);
|
|
192
|
-
return { repo, project, card };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function planWithPeerCheckpoint(setupState, peerCapture, version = 2) {
|
|
196
|
-
const snapshot = projectStream.readSnapshot(
|
|
197
|
-
setupState.project.file,
|
|
198
|
-
setupState.project.resourceId,
|
|
199
|
-
);
|
|
200
|
-
const peerValue = checkpoint.checkpointValue(
|
|
201
|
-
'machine-peer',
|
|
202
|
-
setupState.project.repoKey,
|
|
203
|
-
setupState.card.head.oid,
|
|
204
|
-
peerCapture.indexDiff,
|
|
205
|
-
peerCapture.worktreeDiff,
|
|
206
|
-
);
|
|
207
|
-
return {
|
|
208
|
-
repoKey: setupState.project.repoKey,
|
|
209
|
-
card: {
|
|
210
|
-
...snapshot.card,
|
|
211
|
-
repo: { remote: null, rootCommit: setupState.card.head.oid },
|
|
212
|
-
kind: 'primary',
|
|
213
|
-
health: 'healthy',
|
|
214
|
-
positionMachine: snapshot.card.machine,
|
|
215
|
-
positionMutationId: snapshot.card.mutationId,
|
|
216
|
-
positionVersion: snapshot.card.version,
|
|
217
|
-
},
|
|
218
|
-
branches: snapshot.card.branches.map((branch) => ({
|
|
219
|
-
...branch,
|
|
220
|
-
machine: snapshot.card.machine,
|
|
221
|
-
mutationId: snapshot.card.mutationId,
|
|
222
|
-
version: snapshot.card.version,
|
|
223
|
-
})),
|
|
224
|
-
transitions: [{
|
|
225
|
-
checkpoint: checkpoint.CHECKPOINT_CONTRACT,
|
|
226
|
-
machine: 'machine-peer',
|
|
227
|
-
baseOid: setupState.card.head.oid,
|
|
228
|
-
branch: setupState.card.head.branch,
|
|
229
|
-
from: { revision: null, indexDiff: '', worktreeDiff: '' },
|
|
230
|
-
to: peerValue,
|
|
231
|
-
mutationId: `mutation_peer_checkpoint_${version}`,
|
|
232
|
-
version,
|
|
233
|
-
}],
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
test('a real local edit while behind has durable provenance and refreshes at equal dirty counts', async () => {
|
|
238
|
-
const setupState = await setup(
|
|
239
|
-
'behind-edit',
|
|
240
|
-
'018f0000-0000-7000-8000-00000000c001',
|
|
241
|
-
);
|
|
242
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'local one\n');
|
|
243
|
-
let card = cardFor(setupState.project, setupState.repo);
|
|
244
|
-
let captured = await checkpointFor(setupState.project, card);
|
|
245
|
-
const first = await checkpoint.captureLocalIntent(setupState.project, card, captured);
|
|
246
|
-
assert.equal(first.state, 'captured');
|
|
247
|
-
assert.equal(first.intent.base.through, 1);
|
|
248
|
-
|
|
249
|
-
openLocalDb().prepare(
|
|
250
|
-
'UPDATE shared_resource_replicas SET applied_version = 2 WHERE resource_id = ?',
|
|
251
|
-
).run(setupState.project.resourceId);
|
|
252
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'local two\n');
|
|
253
|
-
card = cardFor(setupState.project, setupState.repo);
|
|
254
|
-
captured = await checkpointFor(setupState.project, card);
|
|
255
|
-
const second = await checkpoint.captureLocalIntent(setupState.project, card, captured);
|
|
256
|
-
assert.equal(second.state, 'captured');
|
|
257
|
-
assert.equal(second.intent.base.through, 1);
|
|
258
|
-
assert.notEqual(second.intent.semanticDigest, first.intent.semanticDigest);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test('legacy intent state upgrades on write and unknown contracts fail loudly', async () => {
|
|
262
|
-
const setupState = await setup(
|
|
263
|
-
'intent-contract-upgrade',
|
|
264
|
-
'018f0000-0000-7000-8000-00000000c11b',
|
|
265
|
-
);
|
|
266
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'legacy intent\n');
|
|
267
|
-
const card = cardFor(setupState.project, setupState.repo);
|
|
268
|
-
const captured = await checkpoint.captureLocalIntent(
|
|
269
|
-
setupState.project,
|
|
270
|
-
card,
|
|
271
|
-
await checkpointFor(setupState.project, card),
|
|
272
|
-
);
|
|
273
|
-
const file = intent.intentFile(setupState.project.resourceId);
|
|
274
|
-
const legacy = { ...captured.intent, contract: 'repo-local-intent@2' };
|
|
275
|
-
delete legacy.birthMutationId;
|
|
276
|
-
delete legacy.successor;
|
|
277
|
-
fs.writeFileSync(file, `${JSON.stringify(legacy)}\n`);
|
|
278
|
-
assert.equal(intent.readIntent(setupState.project).contract, 'repo-local-intent@2');
|
|
279
|
-
const upgraded = intent.reservePublication(setupState.project, legacy);
|
|
280
|
-
assert.equal(upgraded.contract, intent.INTENT_CONTRACT);
|
|
281
|
-
assert.match(upgraded.birthMutationId, /^[A-Za-z0-9_-]{16,160}$/);
|
|
282
|
-
assert.equal(upgraded.publication.mutationId, upgraded.birthMutationId);
|
|
283
|
-
|
|
284
|
-
const legacyReserved = {
|
|
285
|
-
...upgraded,
|
|
286
|
-
contract: 'repo-local-intent@3',
|
|
287
|
-
};
|
|
288
|
-
delete legacyReserved.birthMutationId;
|
|
289
|
-
fs.writeFileSync(file, `${JSON.stringify(legacyReserved)}\n`);
|
|
290
|
-
const migratedReserved = intent.reservePublication(
|
|
291
|
-
setupState.project,
|
|
292
|
-
legacyReserved,
|
|
293
|
-
);
|
|
294
|
-
assert.equal(
|
|
295
|
-
migratedReserved.birthMutationId,
|
|
296
|
-
legacyReserved.publication.mutationId,
|
|
297
|
-
'an already-reserved legacy identity is its birth identity after upgrade',
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
const unknown = {
|
|
301
|
-
...intent.readIntent(setupState.project),
|
|
302
|
-
contract: 'repo-local-intent@999',
|
|
303
|
-
};
|
|
304
|
-
fs.writeFileSync(file, `${JSON.stringify(unknown)}\n`);
|
|
305
|
-
assert.throws(
|
|
306
|
-
() => intent.readIntent(setupState.project),
|
|
307
|
-
/invalid or unsupported contract/,
|
|
308
|
-
);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test('materialization output and missing reducer provenance are silent', async () => {
|
|
312
|
-
const setupState = await setup(
|
|
313
|
-
'silent-output',
|
|
314
|
-
'018f0000-0000-7000-8000-00000000c002',
|
|
315
|
-
);
|
|
316
|
-
const clean = await checkpointFor(setupState.project, setupState.card);
|
|
317
|
-
assert.equal(
|
|
318
|
-
(await checkpoint.captureLocalIntent(setupState.project, setupState.card, clean)).state,
|
|
319
|
-
'silent',
|
|
320
|
-
);
|
|
321
|
-
fs.rmSync(reducerFile(setupState.project.repoKey), { force: true });
|
|
322
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'state without provenance\n');
|
|
323
|
-
const changedCard = cardFor(setupState.project, setupState.repo);
|
|
324
|
-
assert.deepEqual(
|
|
325
|
-
await checkpoint.captureLocalIntent(
|
|
326
|
-
setupState.project,
|
|
327
|
-
changedCard,
|
|
328
|
-
await checkpointFor(setupState.project, changedCard),
|
|
329
|
-
),
|
|
330
|
-
{ state: 'silent', reason: 'no-verified-baseline' },
|
|
331
|
-
);
|
|
332
|
-
assert.equal(intent.readIntent(setupState.project), null);
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
test('missing reducer provenance blocks apply without discarding durable intent', async () => {
|
|
336
|
-
const setupState = await setup(
|
|
337
|
-
'missing-intent-provenance',
|
|
338
|
-
'018f0000-0000-7000-8000-00000000c110',
|
|
339
|
-
);
|
|
340
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'official later\n');
|
|
341
|
-
const peerCapture = await require('../workspace/git').captureCheckpoint(setupState.repo);
|
|
342
|
-
git(setupState.repo, 'reset', '--hard', 'HEAD');
|
|
343
|
-
|
|
344
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'owned local\n');
|
|
345
|
-
const localCard = cardFor(setupState.project, setupState.repo);
|
|
346
|
-
const captured = await checkpoint.captureLocalIntent(
|
|
347
|
-
setupState.project,
|
|
348
|
-
localCard,
|
|
349
|
-
await checkpointFor(setupState.project, localCard),
|
|
350
|
-
);
|
|
351
|
-
assert.equal(captured.state, 'captured');
|
|
352
|
-
fs.rmSync(reducerFile(setupState.project.repoKey), { force: true });
|
|
353
|
-
|
|
354
|
-
const outcome = await reducer.materializeRepo(
|
|
355
|
-
localCard,
|
|
356
|
-
planWithPeerCheckpoint(setupState, peerCapture),
|
|
357
|
-
2,
|
|
358
|
-
);
|
|
359
|
-
assert.deepEqual(outcome, {
|
|
360
|
-
state: 'waiting',
|
|
361
|
-
reason: 'local-intent-provenance-unverified',
|
|
362
|
-
});
|
|
363
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'file.txt'), 'utf8'), 'owned local\n');
|
|
364
|
-
assert.equal(intent.readIntent(setupState.project)?.semanticDigest, captured.intent.semanticDigest);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
test('peer-identical official bytes cannot retire an unacknowledged local mutation ID', async () => {
|
|
368
|
-
const setupState = await setup(
|
|
369
|
-
'peer-identical-intent',
|
|
370
|
-
'018f0000-0000-7000-8000-00000000c12f',
|
|
371
|
-
);
|
|
372
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'same independent bytes\n');
|
|
373
|
-
const peerCapture = await require('../workspace/git').captureCheckpoint(setupState.repo);
|
|
374
|
-
const localCard = cardFor(setupState.project, setupState.repo);
|
|
375
|
-
const captured = await checkpoint.captureLocalIntent(
|
|
376
|
-
setupState.project,
|
|
377
|
-
localCard,
|
|
378
|
-
await checkpointFor(setupState.project, localCard),
|
|
379
|
-
);
|
|
380
|
-
assert.equal(captured.state, 'captured');
|
|
381
|
-
assert.equal(captured.intent.publication, null);
|
|
382
|
-
|
|
383
|
-
const peerPlan = planWithPeerCheckpoint(setupState, peerCapture);
|
|
384
|
-
const beforePeer = projectStream.readSnapshot(
|
|
385
|
-
setupState.project.file,
|
|
386
|
-
setupState.project.resourceId,
|
|
387
|
-
);
|
|
388
|
-
projectStream.writeSnapshot(
|
|
389
|
-
setupState.project.file,
|
|
390
|
-
projectStream._test.fold(
|
|
391
|
-
beforePeer,
|
|
392
|
-
{
|
|
393
|
-
kind: 'code.publish',
|
|
394
|
-
project: setupState.project.resourceId,
|
|
395
|
-
delta: {
|
|
396
|
-
kind: 'checkpoint.set',
|
|
397
|
-
checkpoint: checkpoint.CHECKPOINT_CONTRACT,
|
|
398
|
-
baseOid: setupState.card.head.oid,
|
|
399
|
-
branch: setupState.card.head.branch,
|
|
400
|
-
from: beforePeer.checkpoint.value,
|
|
401
|
-
to: peerPlan.transitions[0].to,
|
|
402
|
-
},
|
|
403
|
-
},
|
|
404
|
-
{
|
|
405
|
-
machine: 'machine-peer',
|
|
406
|
-
mutationId: peerPlan.transitions[0].mutationId,
|
|
407
|
-
version: 2,
|
|
408
|
-
},
|
|
409
|
-
),
|
|
410
|
-
);
|
|
411
|
-
const outcome = await reducer.materializeRepo(
|
|
412
|
-
localCard,
|
|
413
|
-
peerPlan,
|
|
414
|
-
2,
|
|
415
|
-
);
|
|
416
|
-
assert.equal(outcome.state, 'in-sync', JSON.stringify(outcome));
|
|
417
|
-
const retained = intent.readIntent(setupState.project);
|
|
418
|
-
assert.equal(retained.semanticDigest, captured.intent.semanticDigest);
|
|
419
|
-
assert.equal(retained.birthMutationId, captured.intent.birthMutationId);
|
|
420
|
-
assert.equal(retained.base.through, 2, JSON.stringify(retained));
|
|
421
|
-
assert.equal(retained.publication, null);
|
|
422
|
-
assert.equal(
|
|
423
|
-
fs.readFileSync(path.join(setupState.repo, 'file.txt'), 'utf8'),
|
|
424
|
-
'same independent bytes\n',
|
|
425
|
-
);
|
|
426
|
-
openLocalDb().prepare(
|
|
427
|
-
'UPDATE shared_resource_replicas SET applied_version = 2 WHERE resource_id = ?',
|
|
428
|
-
).run(setupState.project.resourceId);
|
|
429
|
-
const currentCard = cardFor(setupState.project, setupState.repo);
|
|
430
|
-
const publication = wall._test.publishProject(setupState.project, [currentCard]);
|
|
431
|
-
assert.equal(publication.published, true, JSON.stringify(publication));
|
|
432
|
-
assert.equal(publication.mutationId, captured.intent.birthMutationId);
|
|
433
|
-
assert.equal(publication.delta, 'checkpoint.set');
|
|
434
|
-
const pending = mutations.listPendingCloudMutations(setupState.project.resourceId);
|
|
435
|
-
assert.equal(pending.length, 1);
|
|
436
|
-
assert.deepEqual(
|
|
437
|
-
pending[0].operation.delta.from,
|
|
438
|
-
pending[0].operation.delta.to,
|
|
439
|
-
'the state-neutral event exists only to number this durable local identity',
|
|
440
|
-
);
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
test('an exact official ack ends ownership and cannot re-enter from the unchanged disk', async () => {
|
|
444
|
-
const setupState = await setup(
|
|
445
|
-
'exact-ack',
|
|
446
|
-
'018f0000-0000-7000-8000-00000000c003',
|
|
447
|
-
);
|
|
448
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'owned once\n');
|
|
449
|
-
const card = cardFor(setupState.project, setupState.repo);
|
|
450
|
-
const captured = await checkpointFor(setupState.project, card);
|
|
451
|
-
await checkpoint.captureLocalIntent(setupState.project, card, captured);
|
|
452
|
-
const publication = wall._test.publishProject(setupState.project, [card]);
|
|
453
|
-
assert.equal(publication.published, true);
|
|
454
|
-
assert.equal(
|
|
455
|
-
intent.readIntent(setupState.project).publication.mutationId,
|
|
456
|
-
publication.mutationId,
|
|
457
|
-
'the publication id is durable before delivery',
|
|
458
|
-
);
|
|
459
|
-
const claimed = mutations.claimCloudMutations().find((entry) => (
|
|
460
|
-
entry.mutation.mutationId === publication.mutationId
|
|
461
|
-
));
|
|
462
|
-
assert.ok(claimed);
|
|
463
|
-
mutations.acknowledgeCloudMutation(
|
|
464
|
-
setupState.project.resourceId,
|
|
465
|
-
publication.mutationId,
|
|
466
|
-
{ version: 2, committedAt: '2026-07-27T00:00:00.000Z' },
|
|
467
|
-
);
|
|
468
|
-
assert.equal(intent.readOwnedIntent(setupState.project), null);
|
|
469
|
-
assert.deepEqual(
|
|
470
|
-
{
|
|
471
|
-
numberedVersion: intent.readIntent(setupState.project).publication.numberedVersion,
|
|
472
|
-
numberedAt: intent.readIntent(setupState.project).publication.numberedAt,
|
|
473
|
-
foldedAt: intent.readIntent(setupState.project).publication.foldedAt,
|
|
474
|
-
},
|
|
475
|
-
{
|
|
476
|
-
numberedVersion: 2,
|
|
477
|
-
numberedAt: '2026-07-27T00:00:00.000Z',
|
|
478
|
-
foldedAt: null,
|
|
479
|
-
},
|
|
480
|
-
'server numbering ends ownership without claiming an ordered fold or physical apply',
|
|
481
|
-
);
|
|
482
|
-
const before = intent.readIntent(setupState.project);
|
|
483
|
-
projectStream.applyCloudMutation({
|
|
484
|
-
version: 2,
|
|
485
|
-
committedAt: '2026-07-27T00:00:00.000Z',
|
|
486
|
-
envelope: claimed.envelope,
|
|
487
|
-
});
|
|
488
|
-
assert.equal(
|
|
489
|
-
intent.readIntent(setupState.project).publication.numberedVersion,
|
|
490
|
-
2,
|
|
491
|
-
'the official number remains durable after the ordered fold',
|
|
492
|
-
);
|
|
493
|
-
assert.equal(
|
|
494
|
-
typeof intent.readIntent(setupState.project).publication.foldedAt,
|
|
495
|
-
'string',
|
|
496
|
-
'the ordinary official tail records its fold separately from numbering',
|
|
497
|
-
);
|
|
498
|
-
openLocalDb().prepare(`
|
|
499
|
-
UPDATE mutation_journal
|
|
500
|
-
SET updated_at = '2026-07-26T00:00:00.000Z'
|
|
501
|
-
WHERE mutation_id = ?
|
|
502
|
-
`).run(publication.mutationId);
|
|
503
|
-
mutations.pruneMutationJournal({
|
|
504
|
-
cutoffIso: '2026-07-26T12:00:00.000Z',
|
|
505
|
-
batchSize: 100,
|
|
506
|
-
});
|
|
507
|
-
assert.equal(
|
|
508
|
-
mutations.getMutation(
|
|
509
|
-
`resource:${setupState.project.resourceId}`,
|
|
510
|
-
publication.mutationId,
|
|
511
|
-
),
|
|
512
|
-
null,
|
|
513
|
-
'the folded publication may leave the generic journal',
|
|
514
|
-
);
|
|
515
|
-
assert.deepEqual(intent.officialAcknowledgement(
|
|
516
|
-
setupState.project,
|
|
517
|
-
intent.readIntent(setupState.project),
|
|
518
|
-
), {
|
|
519
|
-
mutationId: publication.mutationId,
|
|
520
|
-
version: 2,
|
|
521
|
-
});
|
|
522
|
-
assert.equal(intent.readOwnedIntent(setupState.project), null);
|
|
523
|
-
const repeated = await checkpoint.captureLocalIntent(setupState.project, card, captured);
|
|
524
|
-
assert.deepEqual(repeated, { state: 'silent', reason: 'officially-acknowledged' });
|
|
525
|
-
assert.equal(intent.readIntent(setupState.project).semanticDigest, before.semanticDigest);
|
|
526
|
-
|
|
527
|
-
const plan = reducer.reduceWall(wall.readWall(setupState.project.repoKey))
|
|
528
|
-
.get(setupState.project.repoKey);
|
|
529
|
-
const applied = await reducer.materializeRepo(card, plan, 2);
|
|
530
|
-
assert.equal(applied.state, 'in-sync', JSON.stringify(applied));
|
|
531
|
-
assert.equal(
|
|
532
|
-
intent.readIntent(setupState.project),
|
|
533
|
-
null,
|
|
534
|
-
'only verified physical apply and cursor advance retire the folded intent',
|
|
535
|
-
);
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
test('a crash after verified apply cannot revive the folded intent over a later local write', async () => {
|
|
539
|
-
const setupState = await setup(
|
|
540
|
-
'folded-retirement-crash',
|
|
541
|
-
'018f0000-0000-7000-8000-00000000c111',
|
|
542
|
-
);
|
|
543
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'official local write\n');
|
|
544
|
-
const card = cardFor(setupState.project, setupState.repo);
|
|
545
|
-
await checkpoint.captureLocalIntent(
|
|
546
|
-
setupState.project,
|
|
547
|
-
card,
|
|
548
|
-
await checkpointFor(setupState.project, card),
|
|
549
|
-
);
|
|
550
|
-
const publication = wall._test.publishProject(setupState.project, [card]);
|
|
551
|
-
const claimed = mutations.claimCloudMutations().find((entry) => (
|
|
552
|
-
entry.mutation.mutationId === publication.mutationId
|
|
553
|
-
));
|
|
554
|
-
assert.ok(claimed);
|
|
555
|
-
mutations.acknowledgeCloudMutation(
|
|
556
|
-
setupState.project.resourceId,
|
|
557
|
-
publication.mutationId,
|
|
558
|
-
{ version: 2, committedAt: '2026-07-27T02:00:00.000Z' },
|
|
559
|
-
);
|
|
560
|
-
projectStream.applyCloudMutation({
|
|
561
|
-
version: 2,
|
|
562
|
-
committedAt: '2026-07-27T02:00:00.000Z',
|
|
563
|
-
envelope: claimed.envelope,
|
|
564
|
-
});
|
|
565
|
-
const foldedDigest = intent.readIntent(setupState.project).semanticDigest;
|
|
566
|
-
const plan = reducer.reduceWall(wall.readWall(setupState.project.repoKey))
|
|
567
|
-
.get(setupState.project.repoKey);
|
|
568
|
-
|
|
569
|
-
const clearIntent = intent.clearIntent;
|
|
570
|
-
let crashed = false;
|
|
571
|
-
intent.clearIntent = (...args) => {
|
|
572
|
-
if (!crashed) {
|
|
573
|
-
crashed = true;
|
|
574
|
-
throw new Error('simulated process death before folded-intent cleanup');
|
|
575
|
-
}
|
|
576
|
-
return clearIntent(...args);
|
|
577
|
-
};
|
|
578
|
-
try {
|
|
579
|
-
await assert.rejects(
|
|
580
|
-
reducer.materializeRepo(card, plan, 2),
|
|
581
|
-
/simulated process death/,
|
|
582
|
-
);
|
|
583
|
-
} finally {
|
|
584
|
-
intent.clearIntent = clearIntent;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
const applied = reducer.readRepoState(setupState.project.repoKey, setupState.project.resourceId);
|
|
588
|
-
assert.equal(applied.appliedThrough, 2);
|
|
589
|
-
assert.equal(applied.materialized, true);
|
|
590
|
-
assert.equal(intent.readIntent(setupState.project).semanticDigest, foldedDigest);
|
|
591
|
-
|
|
592
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'written after the crash\n');
|
|
593
|
-
const restarted = await reducer.materializeRepo(card, plan, 2);
|
|
594
|
-
assert.deepEqual(restarted, {
|
|
595
|
-
state: 'waiting',
|
|
596
|
-
reason: 'local-intent-capture-required',
|
|
597
|
-
});
|
|
598
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'file.txt'), 'utf8'), 'written after the crash\n');
|
|
599
|
-
assert.equal(intent.readIntent(setupState.project), null);
|
|
600
|
-
assert.equal(
|
|
601
|
-
reducer.readRepoState(setupState.project.repoKey, setupState.project.resourceId).appliedThrough,
|
|
602
|
-
2,
|
|
603
|
-
);
|
|
604
|
-
|
|
605
|
-
const changedCard = cardFor(setupState.project, setupState.repo);
|
|
606
|
-
const successor = await checkpoint.captureLocalIntent(
|
|
607
|
-
setupState.project,
|
|
608
|
-
changedCard,
|
|
609
|
-
await checkpointFor(setupState.project, changedCard),
|
|
610
|
-
);
|
|
611
|
-
assert.equal(successor.state, 'captured');
|
|
612
|
-
assert.equal(successor.intent.base.through, 2);
|
|
613
|
-
assert.equal(successor.intent.publication, null);
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
test('a write made after numbering becomes one successor over the folded official state', async () => {
|
|
617
|
-
const setupState = await setup(
|
|
618
|
-
'numbered-successor',
|
|
619
|
-
'018f0000-0000-7000-8000-00000000c112',
|
|
620
|
-
);
|
|
621
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'numbered predecessor\n');
|
|
622
|
-
const predecessorCard = cardFor(setupState.project, setupState.repo);
|
|
623
|
-
const predecessor = await checkpoint.captureLocalIntent(
|
|
624
|
-
setupState.project,
|
|
625
|
-
predecessorCard,
|
|
626
|
-
await checkpointFor(setupState.project, predecessorCard),
|
|
627
|
-
);
|
|
628
|
-
const publication = wall._test.publishProject(setupState.project, [predecessorCard]);
|
|
629
|
-
const claimed = mutations.claimCloudMutations().find((entry) => (
|
|
630
|
-
entry.mutation.mutationId === publication.mutationId
|
|
631
|
-
));
|
|
632
|
-
assert.ok(claimed);
|
|
633
|
-
mutations.acknowledgeCloudMutation(
|
|
634
|
-
setupState.project.resourceId,
|
|
635
|
-
publication.mutationId,
|
|
636
|
-
{ version: 2, committedAt: '2026-07-27T03:00:00.000Z' },
|
|
637
|
-
);
|
|
638
|
-
|
|
639
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'successor written before fold\n');
|
|
640
|
-
const successorCard = cardFor(setupState.project, setupState.repo);
|
|
641
|
-
const premature = await checkpoint.captureLocalIntent(
|
|
642
|
-
setupState.project,
|
|
643
|
-
successorCard,
|
|
644
|
-
await checkpointFor(setupState.project, successorCard),
|
|
645
|
-
);
|
|
646
|
-
assert.equal(premature.state, 'captured');
|
|
647
|
-
assert.equal(premature.reason, 'successor-captured');
|
|
648
|
-
assert.equal(
|
|
649
|
-
intent.readIntent(setupState.project).publication.mutationId,
|
|
650
|
-
publication.mutationId,
|
|
651
|
-
);
|
|
652
|
-
assert.equal(intent.readIntent(setupState.project).semanticDigest, predecessor.intent.semanticDigest);
|
|
653
|
-
assert.equal(
|
|
654
|
-
intent.readSuccessor(setupState.project).predecessorSemanticDigest,
|
|
655
|
-
predecessor.intent.semanticDigest,
|
|
656
|
-
);
|
|
657
|
-
assert.equal(
|
|
658
|
-
intent.readSuccessor(setupState.project).local.physicalDigest,
|
|
659
|
-
(await reducer.observePhysicalRepo(setupState.repo)).digest,
|
|
660
|
-
);
|
|
661
|
-
const successorBirthMutationId = intent.readSuccessor(
|
|
662
|
-
setupState.project,
|
|
663
|
-
).birthMutationId;
|
|
664
|
-
|
|
665
|
-
projectStream.applyCloudMutation({
|
|
666
|
-
version: 2,
|
|
667
|
-
committedAt: '2026-07-27T03:00:00.000Z',
|
|
668
|
-
envelope: claimed.envelope,
|
|
669
|
-
});
|
|
670
|
-
const plan = reducer.reduceWall(wall.readWall(setupState.project.repoKey))
|
|
671
|
-
.get(setupState.project.repoKey);
|
|
672
|
-
const applied = await reducer.materializeRepo(successorCard, plan, 2);
|
|
673
|
-
assert.equal(applied.state, 'in-sync', JSON.stringify(applied));
|
|
674
|
-
assert.equal(
|
|
675
|
-
fs.readFileSync(path.join(setupState.repo, 'file.txt'), 'utf8'),
|
|
676
|
-
'successor written before fold\n',
|
|
677
|
-
);
|
|
678
|
-
assert.equal(
|
|
679
|
-
reducer.readRepoState(setupState.project.repoKey, setupState.project.resourceId).appliedThrough,
|
|
680
|
-
2,
|
|
681
|
-
);
|
|
682
|
-
const successor = intent.readOwnedIntent(setupState.project);
|
|
683
|
-
assert.equal(successor.base.through, 2);
|
|
684
|
-
assert.equal(successor.publication, null);
|
|
685
|
-
assert.notEqual(successor.semanticDigest, predecessor.intent.semanticDigest);
|
|
686
|
-
assert.equal(successor.birthMutationId, successorBirthMutationId);
|
|
687
|
-
assert.equal(successor.local.physicalDigest, (
|
|
688
|
-
await reducer.observePhysicalRepo(setupState.repo)
|
|
689
|
-
).digest);
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
test('successor intent keeps only later local work while a newer remote file also applies', async () => {
|
|
693
|
-
const setupState = await setup(
|
|
694
|
-
'folded-successor-plus-remote',
|
|
695
|
-
'018f0000-0000-7000-8000-00000000c113',
|
|
696
|
-
);
|
|
697
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'official predecessor\n');
|
|
698
|
-
const predecessorCard = cardFor(setupState.project, setupState.repo);
|
|
699
|
-
await checkpoint.captureLocalIntent(
|
|
700
|
-
setupState.project,
|
|
701
|
-
predecessorCard,
|
|
702
|
-
await checkpointFor(setupState.project, predecessorCard),
|
|
703
|
-
);
|
|
704
|
-
const publication = wall._test.publishProject(setupState.project, [predecessorCard]);
|
|
705
|
-
const claimed = mutations.claimCloudMutations().find((entry) => (
|
|
706
|
-
entry.mutation.mutationId === publication.mutationId
|
|
707
|
-
));
|
|
708
|
-
mutations.acknowledgeCloudMutation(
|
|
709
|
-
setupState.project.resourceId,
|
|
710
|
-
publication.mutationId,
|
|
711
|
-
{ version: 2, committedAt: '2026-07-27T04:00:00.000Z' },
|
|
712
|
-
);
|
|
713
|
-
projectStream.applyCloudMutation({
|
|
714
|
-
version: 2,
|
|
715
|
-
committedAt: '2026-07-27T04:00:00.000Z',
|
|
716
|
-
envelope: claimed.envelope,
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
fs.writeFileSync(path.join(setupState.repo, 'remote.txt'), 'newer remote\n');
|
|
720
|
-
const remoteCapture = await require('../workspace/git').captureCheckpoint(setupState.repo);
|
|
721
|
-
fs.rmSync(path.join(setupState.repo, 'remote.txt'));
|
|
722
|
-
fs.writeFileSync(path.join(setupState.repo, 'successor.txt'), 'later local\n');
|
|
723
|
-
const successorCard = cardFor(setupState.project, setupState.repo);
|
|
724
|
-
const capturedSuccessor = await checkpoint.captureLocalIntent(
|
|
725
|
-
setupState.project,
|
|
726
|
-
successorCard,
|
|
727
|
-
await checkpointFor(setupState.project, successorCard),
|
|
728
|
-
);
|
|
729
|
-
assert.equal(capturedSuccessor.state, 'captured');
|
|
730
|
-
assert.equal(capturedSuccessor.reason, 'successor-captured');
|
|
731
|
-
|
|
732
|
-
const plan = reducer.reduceWall(wall.readWall(setupState.project.repoKey))
|
|
733
|
-
.get(setupState.project.repoKey);
|
|
734
|
-
const predecessorOperation = plan.transitions.find((entry) => entry.version === 2);
|
|
735
|
-
plan.transitions.push({
|
|
736
|
-
checkpoint: checkpoint.CHECKPOINT_CONTRACT,
|
|
737
|
-
machine: 'machine-peer',
|
|
738
|
-
repoKey: setupState.project.repoKey,
|
|
739
|
-
branch: predecessorCard.head.branch,
|
|
740
|
-
baseOid: predecessorCard.head.oid,
|
|
741
|
-
from: predecessorOperation.to,
|
|
742
|
-
to: checkpoint.checkpointValue(
|
|
743
|
-
'machine-peer',
|
|
744
|
-
setupState.project.repoKey,
|
|
745
|
-
predecessorCard.head.oid,
|
|
746
|
-
remoteCapture.indexDiff,
|
|
747
|
-
remoteCapture.worktreeDiff,
|
|
748
|
-
),
|
|
749
|
-
version: 3,
|
|
750
|
-
mutationId: 'mutation_remote_after_fold_03',
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
const applied = await reducer.materializeRepo(successorCard, plan, 3);
|
|
754
|
-
assert.equal(applied.state, 'in-sync', JSON.stringify(applied));
|
|
755
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'file.txt'), 'utf8'), 'official predecessor\n');
|
|
756
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'remote.txt'), 'utf8'), 'newer remote\n');
|
|
757
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'successor.txt'), 'utf8'), 'later local\n');
|
|
758
|
-
const successor = intent.readOwnedIntent(setupState.project);
|
|
759
|
-
assert.equal(successor.base.through, 3);
|
|
760
|
-
assert.equal(successor.publication, null);
|
|
761
|
-
assert.equal(successor.local.physicalDigest, (
|
|
762
|
-
await reducer.observePhysicalRepo(setupState.repo)
|
|
763
|
-
).digest);
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
test('restart recovers the apply WAL before replacing a folded predecessor with its successor', async () => {
|
|
767
|
-
const setupState = await setup(
|
|
768
|
-
'successor-capture-crash',
|
|
769
|
-
'018f0000-0000-7000-8000-00000000c114',
|
|
770
|
-
);
|
|
771
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'folded predecessor\n');
|
|
772
|
-
const predecessorCard = cardFor(setupState.project, setupState.repo);
|
|
773
|
-
await checkpoint.captureLocalIntent(
|
|
774
|
-
setupState.project,
|
|
775
|
-
predecessorCard,
|
|
776
|
-
await checkpointFor(setupState.project, predecessorCard),
|
|
777
|
-
);
|
|
778
|
-
const publication = wall._test.publishProject(setupState.project, [predecessorCard]);
|
|
779
|
-
const claimed = mutations.claimCloudMutations().find((entry) => (
|
|
780
|
-
entry.mutation.mutationId === publication.mutationId
|
|
781
|
-
));
|
|
782
|
-
mutations.acknowledgeCloudMutation(
|
|
783
|
-
setupState.project.resourceId,
|
|
784
|
-
publication.mutationId,
|
|
785
|
-
{ version: 2, committedAt: '2026-07-27T05:00:00.000Z' },
|
|
786
|
-
);
|
|
787
|
-
projectStream.applyCloudMutation({
|
|
788
|
-
version: 2,
|
|
789
|
-
committedAt: '2026-07-27T05:00:00.000Z',
|
|
790
|
-
envelope: claimed.envelope,
|
|
791
|
-
});
|
|
792
|
-
const foldedDigest = intent.readIntent(setupState.project).semanticDigest;
|
|
793
|
-
fs.writeFileSync(path.join(setupState.repo, 'after.txt'), 'successor survives restart\n');
|
|
794
|
-
const successorCard = cardFor(setupState.project, setupState.repo);
|
|
795
|
-
const capturedSuccessor = await checkpoint.captureLocalIntent(
|
|
796
|
-
setupState.project,
|
|
797
|
-
successorCard,
|
|
798
|
-
await checkpointFor(setupState.project, successorCard),
|
|
799
|
-
);
|
|
800
|
-
assert.equal(capturedSuccessor.state, 'captured');
|
|
801
|
-
assert.equal(capturedSuccessor.reason, 'successor-captured');
|
|
802
|
-
const plan = reducer.reduceWall(wall.readWall(setupState.project.repoKey))
|
|
803
|
-
.get(setupState.project.repoKey);
|
|
804
|
-
|
|
805
|
-
const writeIntent = intent.writeIntent;
|
|
806
|
-
let crashed = false;
|
|
807
|
-
intent.writeIntent = (project, input) => {
|
|
808
|
-
if (!crashed && input.base?.through === 2) {
|
|
809
|
-
crashed = true;
|
|
810
|
-
throw new Error('simulated process death before successor capture');
|
|
811
|
-
}
|
|
812
|
-
return writeIntent(project, input);
|
|
813
|
-
};
|
|
814
|
-
try {
|
|
815
|
-
await assert.rejects(
|
|
816
|
-
reducer.materializeRepo(successorCard, plan, 2),
|
|
817
|
-
/simulated process death/,
|
|
818
|
-
);
|
|
819
|
-
} finally {
|
|
820
|
-
intent.writeIntent = writeIntent;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
const interrupted = reducer.readRepoState(
|
|
824
|
-
setupState.project.repoKey,
|
|
825
|
-
setupState.project.resourceId,
|
|
826
|
-
);
|
|
827
|
-
assert.equal(interrupted.appliedThrough, 1);
|
|
828
|
-
assert.equal(interrupted.applying.through, 2);
|
|
829
|
-
assert.equal(interrupted.applying.predecessorIntentDigest, foldedDigest);
|
|
830
|
-
assert.equal(intent.readIntent(setupState.project).semanticDigest, foldedDigest);
|
|
831
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'after.txt'), 'utf8'), 'successor survives restart\n');
|
|
832
|
-
|
|
833
|
-
const recovered = await reducer.materializeRepo(successorCard, plan, 2);
|
|
834
|
-
assert.equal(recovered.state, 'in-sync', JSON.stringify(recovered));
|
|
835
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'after.txt'), 'utf8'), 'successor survives restart\n');
|
|
836
|
-
assert.equal(
|
|
837
|
-
reducer.readRepoState(setupState.project.repoKey, setupState.project.resourceId).appliedThrough,
|
|
838
|
-
2,
|
|
839
|
-
);
|
|
840
|
-
const successor = intent.readOwnedIntent(setupState.project);
|
|
841
|
-
assert.equal(successor.base.through, 2);
|
|
842
|
-
assert.equal(successor.publication, null);
|
|
843
|
-
assert.notEqual(successor.semanticDigest, foldedDigest);
|
|
844
|
-
});
|
|
845
|
-
|
|
846
|
-
test('a reserved publication stays immutable across restart and later local writes', async () => {
|
|
847
|
-
const setupState = await setup(
|
|
848
|
-
'reserved-publication',
|
|
849
|
-
'018f0000-0000-7000-8000-00000000c00c',
|
|
850
|
-
);
|
|
851
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'reserved work\n');
|
|
852
|
-
const card = cardFor(setupState.project, setupState.repo);
|
|
853
|
-
const captured = await checkpoint.captureLocalIntent(
|
|
854
|
-
setupState.project,
|
|
855
|
-
card,
|
|
856
|
-
await checkpointFor(setupState.project, card),
|
|
857
|
-
);
|
|
858
|
-
const first = intent.reservePublication(setupState.project, captured.intent);
|
|
859
|
-
const second = intent.reservePublication(setupState.project, captured.intent);
|
|
860
|
-
assert.equal(first.publication.mutationId, captured.intent.birthMutationId);
|
|
861
|
-
assert.equal(second.publication.mutationId, first.publication.mutationId);
|
|
862
|
-
assert.equal(
|
|
863
|
-
mutations.getMutation(
|
|
864
|
-
`resource:${setupState.project.resourceId}`,
|
|
865
|
-
first.publication.mutationId,
|
|
866
|
-
),
|
|
867
|
-
null,
|
|
868
|
-
'reserving the id does not pretend the outbox row already exists',
|
|
869
|
-
);
|
|
870
|
-
|
|
871
|
-
const published = wall._test.publishProject(setupState.project, [card]);
|
|
872
|
-
assert.equal(published.published, true);
|
|
873
|
-
assert.equal(published.mutationId, first.publication.mutationId);
|
|
874
|
-
const replay = wall._test.publishProject(setupState.project, [card]);
|
|
875
|
-
assert.equal(replay.published, false);
|
|
876
|
-
assert.equal(replay.reason, 'project-not-current');
|
|
877
|
-
assert.equal(
|
|
878
|
-
mutations.listPendingCloudMutations(setupState.project.resourceId)
|
|
879
|
-
.filter((row) => row.mutationId === first.publication.mutationId).length,
|
|
880
|
-
1,
|
|
881
|
-
);
|
|
882
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'written after reservation\n');
|
|
883
|
-
const changedCard = cardFor(setupState.project, setupState.repo);
|
|
884
|
-
const changed = await checkpoint.captureLocalIntent(
|
|
885
|
-
setupState.project,
|
|
886
|
-
changedCard,
|
|
887
|
-
await checkpointFor(setupState.project, changedCard),
|
|
888
|
-
);
|
|
889
|
-
assert.equal(changed.state, 'captured');
|
|
890
|
-
assert.equal(changed.reason, 'successor-captured');
|
|
891
|
-
assert.equal(
|
|
892
|
-
intent.readIntent(setupState.project).publication.mutationId,
|
|
893
|
-
first.publication.mutationId,
|
|
894
|
-
);
|
|
895
|
-
assert.equal(
|
|
896
|
-
intent.readIntent(setupState.project).semanticDigest,
|
|
897
|
-
first.semanticDigest,
|
|
898
|
-
);
|
|
899
|
-
assert.equal(
|
|
900
|
-
intent.readSuccessor(setupState.project).predecessorSemanticDigest,
|
|
901
|
-
first.semanticDigest,
|
|
902
|
-
);
|
|
903
|
-
});
|
|
904
|
-
|
|
905
|
-
test('a failed successor write leaves the predecessor immutable and the next capture repairs it', async () => {
|
|
906
|
-
const setupState = await setup(
|
|
907
|
-
'successor-write-crash',
|
|
908
|
-
'018f0000-0000-7000-8000-00000000c11a',
|
|
909
|
-
);
|
|
910
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'frozen predecessor\n');
|
|
911
|
-
const predecessorCard = cardFor(setupState.project, setupState.repo);
|
|
912
|
-
const predecessor = await checkpoint.captureLocalIntent(
|
|
913
|
-
setupState.project,
|
|
914
|
-
predecessorCard,
|
|
915
|
-
await checkpointFor(setupState.project, predecessorCard),
|
|
916
|
-
);
|
|
917
|
-
const reserved = intent.reservePublication(setupState.project, predecessor.intent);
|
|
918
|
-
|
|
919
|
-
fs.writeFileSync(path.join(setupState.repo, 'later.txt'), 'durable on retry\n');
|
|
920
|
-
const successorCard = cardFor(setupState.project, setupState.repo);
|
|
921
|
-
const writeSuccessor = intent.writeSuccessor;
|
|
922
|
-
intent.writeSuccessor = () => {
|
|
923
|
-
throw new Error('simulated process death during successor write');
|
|
924
|
-
};
|
|
925
|
-
try {
|
|
926
|
-
await assert.rejects(
|
|
927
|
-
checkpoint.captureLocalIntent(
|
|
928
|
-
setupState.project,
|
|
929
|
-
successorCard,
|
|
930
|
-
await checkpointFor(setupState.project, successorCard),
|
|
931
|
-
),
|
|
932
|
-
/simulated process death/,
|
|
933
|
-
);
|
|
934
|
-
} finally {
|
|
935
|
-
intent.writeSuccessor = writeSuccessor;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
const afterCrash = intent.readIntent(setupState.project);
|
|
939
|
-
assert.equal(afterCrash.semanticDigest, predecessor.intent.semanticDigest);
|
|
940
|
-
assert.equal(afterCrash.publication.mutationId, reserved.publication.mutationId);
|
|
941
|
-
assert.equal(intent.readSuccessor(setupState.project), null);
|
|
942
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'later.txt'), 'utf8'), 'durable on retry\n');
|
|
943
|
-
|
|
944
|
-
const repaired = await checkpoint.captureLocalIntent(
|
|
945
|
-
setupState.project,
|
|
946
|
-
successorCard,
|
|
947
|
-
await checkpointFor(setupState.project, successorCard),
|
|
948
|
-
);
|
|
949
|
-
assert.equal(repaired.state, 'captured');
|
|
950
|
-
assert.equal(repaired.reason, 'successor-captured');
|
|
951
|
-
assert.equal(
|
|
952
|
-
intent.readSuccessor(setupState.project).local.physicalDigest,
|
|
953
|
-
(await reducer.observePhysicalRepo(setupState.repo)).digest,
|
|
954
|
-
);
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
test('later saves coalesce into one successor pinned to the frozen predecessor', async () => {
|
|
958
|
-
const setupState = await setup(
|
|
959
|
-
'successor-coalescing',
|
|
960
|
-
'018f0000-0000-7000-8000-00000000c116',
|
|
961
|
-
);
|
|
962
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'A\n');
|
|
963
|
-
const predecessorCard = cardFor(setupState.project, setupState.repo);
|
|
964
|
-
const predecessor = await checkpoint.captureLocalIntent(
|
|
965
|
-
setupState.project,
|
|
966
|
-
predecessorCard,
|
|
967
|
-
await checkpointFor(setupState.project, predecessorCard),
|
|
968
|
-
);
|
|
969
|
-
const reserved = intent.reservePublication(setupState.project, predecessor.intent);
|
|
970
|
-
const successorDigests = [];
|
|
971
|
-
|
|
972
|
-
for (const value of ['B', 'C', 'D']) {
|
|
973
|
-
fs.writeFileSync(path.join(setupState.repo, 'later.txt'), `${value}\n`);
|
|
974
|
-
const card = cardFor(setupState.project, setupState.repo);
|
|
975
|
-
const captured = await checkpoint.captureLocalIntent(
|
|
976
|
-
setupState.project,
|
|
977
|
-
card,
|
|
978
|
-
await checkpointFor(setupState.project, card),
|
|
979
|
-
);
|
|
980
|
-
assert.equal(captured.state, 'captured');
|
|
981
|
-
assert.equal(captured.reason, 'successor-captured');
|
|
982
|
-
successorDigests.push(captured.successor.semanticDigest);
|
|
983
|
-
assert.equal(
|
|
984
|
-
captured.successor.predecessorSemanticDigest,
|
|
985
|
-
predecessor.intent.semanticDigest,
|
|
986
|
-
);
|
|
987
|
-
assert.equal(
|
|
988
|
-
captured.successor.base.physicalDigest,
|
|
989
|
-
predecessor.intent.local.physicalDigest,
|
|
990
|
-
);
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
assert.equal(new Set(successorDigests).size, 3);
|
|
994
|
-
assert.equal(intent.readIntent(setupState.project).semanticDigest, predecessor.intent.semanticDigest);
|
|
995
|
-
assert.equal(
|
|
996
|
-
intent.readIntent(setupState.project).publication.mutationId,
|
|
997
|
-
reserved.publication.mutationId,
|
|
998
|
-
);
|
|
999
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'later.txt'), 'utf8'), 'D\n');
|
|
1000
|
-
assert.equal(
|
|
1001
|
-
intent.readSuccessor(setupState.project).semanticDigest,
|
|
1002
|
-
successorDigests.at(-1),
|
|
1003
|
-
);
|
|
1004
|
-
});
|
|
1005
|
-
|
|
1006
|
-
test('a crash while numbering A keeps its successor and repairs the lifecycle stamp on retry', async () => {
|
|
1007
|
-
const setupState = await setup(
|
|
1008
|
-
'numbering-with-successor-crash',
|
|
1009
|
-
'018f0000-0000-7000-8000-00000000c117',
|
|
1010
|
-
);
|
|
1011
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'A\n');
|
|
1012
|
-
const predecessorCard = cardFor(setupState.project, setupState.repo);
|
|
1013
|
-
await checkpoint.captureLocalIntent(
|
|
1014
|
-
setupState.project,
|
|
1015
|
-
predecessorCard,
|
|
1016
|
-
await checkpointFor(setupState.project, predecessorCard),
|
|
1017
|
-
);
|
|
1018
|
-
const publication = wall._test.publishProject(setupState.project, [predecessorCard]);
|
|
1019
|
-
fs.writeFileSync(path.join(setupState.repo, 'later.txt'), 'B\n');
|
|
1020
|
-
const successorCard = cardFor(setupState.project, setupState.repo);
|
|
1021
|
-
await checkpoint.captureLocalIntent(
|
|
1022
|
-
setupState.project,
|
|
1023
|
-
successorCard,
|
|
1024
|
-
await checkpointFor(setupState.project, successorCard),
|
|
1025
|
-
);
|
|
1026
|
-
const successorDigest = intent.readSuccessor(setupState.project).semanticDigest;
|
|
1027
|
-
|
|
1028
|
-
const markPublicationNumbered = intent.markPublicationNumbered;
|
|
1029
|
-
intent.markPublicationNumbered = () => {
|
|
1030
|
-
throw new Error('simulated process death after durable cloud ack');
|
|
1031
|
-
};
|
|
1032
|
-
try {
|
|
1033
|
-
assert.throws(
|
|
1034
|
-
() => mutations.acknowledgeCloudMutation(
|
|
1035
|
-
setupState.project.resourceId,
|
|
1036
|
-
publication.mutationId,
|
|
1037
|
-
{ version: 2, committedAt: '2026-07-27T05:30:00.000Z' },
|
|
1038
|
-
),
|
|
1039
|
-
/simulated process death/,
|
|
1040
|
-
);
|
|
1041
|
-
} finally {
|
|
1042
|
-
intent.markPublicationNumbered = markPublicationNumbered;
|
|
1043
|
-
}
|
|
1044
|
-
assert.equal(intent.readIntent(setupState.project).publication.numberedVersion, null);
|
|
1045
|
-
assert.equal(intent.readSuccessor(setupState.project).semanticDigest, successorDigest);
|
|
1046
|
-
|
|
1047
|
-
mutations.acknowledgeCloudMutation(
|
|
1048
|
-
setupState.project.resourceId,
|
|
1049
|
-
publication.mutationId,
|
|
1050
|
-
{ version: 2, committedAt: '2026-07-27T05:30:00.000Z' },
|
|
1051
|
-
);
|
|
1052
|
-
assert.equal(intent.readIntent(setupState.project).publication.numberedVersion, 2);
|
|
1053
|
-
assert.equal(intent.readSuccessor(setupState.project).semanticDigest, successorDigest);
|
|
1054
|
-
});
|
|
1055
|
-
|
|
1056
|
-
test('a stale rejected A rebases together with its successor after the winning event applies', async () => {
|
|
1057
|
-
const setupState = await setup(
|
|
1058
|
-
'stale-predecessor-successor-rebase',
|
|
1059
|
-
'018f0000-0000-7000-8000-00000000c118',
|
|
1060
|
-
);
|
|
1061
|
-
fs.writeFileSync(path.join(setupState.repo, 'peer.txt'), 'peer winner\n');
|
|
1062
|
-
const peerCapture = await require('../workspace/git').captureCheckpoint(setupState.repo);
|
|
1063
|
-
fs.rmSync(path.join(setupState.repo, 'peer.txt'));
|
|
1064
|
-
|
|
1065
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'A\n');
|
|
1066
|
-
const predecessorCard = cardFor(setupState.project, setupState.repo);
|
|
1067
|
-
await checkpoint.captureLocalIntent(
|
|
1068
|
-
setupState.project,
|
|
1069
|
-
predecessorCard,
|
|
1070
|
-
await checkpointFor(setupState.project, predecessorCard),
|
|
1071
|
-
);
|
|
1072
|
-
const publication = wall._test.publishProject(setupState.project, [predecessorCard]);
|
|
1073
|
-
fs.writeFileSync(path.join(setupState.repo, 'later.txt'), 'B\n');
|
|
1074
|
-
const successorCard = cardFor(setupState.project, setupState.repo);
|
|
1075
|
-
await checkpoint.captureLocalIntent(
|
|
1076
|
-
setupState.project,
|
|
1077
|
-
successorCard,
|
|
1078
|
-
await checkpointFor(setupState.project, successorCard),
|
|
1079
|
-
);
|
|
1080
|
-
mutations.failCloudMutation(
|
|
1081
|
-
setupState.project.resourceId,
|
|
1082
|
-
publication.mutationId,
|
|
1083
|
-
{
|
|
1084
|
-
message: 'code_project_stale_base',
|
|
1085
|
-
code: 'code_project_stale_base',
|
|
1086
|
-
retryable: false,
|
|
1087
|
-
},
|
|
1088
|
-
);
|
|
1089
|
-
|
|
1090
|
-
const applied = await reducer.materializeRepo(
|
|
1091
|
-
successorCard,
|
|
1092
|
-
planWithPeerCheckpoint(setupState, peerCapture),
|
|
1093
|
-
2,
|
|
1094
|
-
);
|
|
1095
|
-
assert.equal(applied.state, 'in-sync', JSON.stringify(applied));
|
|
1096
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'peer.txt'), 'utf8'), 'peer winner\n');
|
|
1097
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'file.txt'), 'utf8'), 'A\n');
|
|
1098
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'later.txt'), 'utf8'), 'B\n');
|
|
1099
|
-
|
|
1100
|
-
const rebasedCard = cardFor(setupState.project, setupState.repo);
|
|
1101
|
-
const rebased = await checkpoint.captureLocalIntent(
|
|
1102
|
-
setupState.project,
|
|
1103
|
-
rebasedCard,
|
|
1104
|
-
await checkpointFor(setupState.project, rebasedCard),
|
|
1105
|
-
);
|
|
1106
|
-
assert.equal(rebased.state, 'captured');
|
|
1107
|
-
assert.equal(rebased.intent.base.through, 2);
|
|
1108
|
-
assert.equal(rebased.intent.publication, null);
|
|
1109
|
-
assert.equal(rebased.intent.successor, null);
|
|
1110
|
-
assert.equal(
|
|
1111
|
-
rebased.intent.local.physicalDigest,
|
|
1112
|
-
(await reducer.observePhysicalRepo(setupState.repo)).digest,
|
|
1113
|
-
);
|
|
1114
|
-
});
|
|
1115
|
-
|
|
1116
|
-
test('a successor that cancels stale A disappears after the winning floor applies', async () => {
|
|
1117
|
-
const setupState = await setup(
|
|
1118
|
-
'stale-predecessor-cancelled',
|
|
1119
|
-
'018f0000-0000-7000-8000-00000000c119',
|
|
1120
|
-
);
|
|
1121
|
-
fs.writeFileSync(path.join(setupState.repo, 'peer.txt'), 'peer winner\n');
|
|
1122
|
-
const peerCapture = await require('../workspace/git').captureCheckpoint(setupState.repo);
|
|
1123
|
-
fs.rmSync(path.join(setupState.repo, 'peer.txt'));
|
|
1124
|
-
|
|
1125
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'A\n');
|
|
1126
|
-
const predecessorCard = cardFor(setupState.project, setupState.repo);
|
|
1127
|
-
await checkpoint.captureLocalIntent(
|
|
1128
|
-
setupState.project,
|
|
1129
|
-
predecessorCard,
|
|
1130
|
-
await checkpointFor(setupState.project, predecessorCard),
|
|
1131
|
-
);
|
|
1132
|
-
const publication = wall._test.publishProject(setupState.project, [predecessorCard]);
|
|
1133
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'base\n');
|
|
1134
|
-
const cancelledCard = cardFor(setupState.project, setupState.repo);
|
|
1135
|
-
await checkpoint.captureLocalIntent(
|
|
1136
|
-
setupState.project,
|
|
1137
|
-
cancelledCard,
|
|
1138
|
-
await checkpointFor(setupState.project, cancelledCard),
|
|
1139
|
-
);
|
|
1140
|
-
assert.ok(intent.readSuccessor(setupState.project));
|
|
1141
|
-
mutations.failCloudMutation(
|
|
1142
|
-
setupState.project.resourceId,
|
|
1143
|
-
publication.mutationId,
|
|
1144
|
-
{
|
|
1145
|
-
message: 'code_project_stale_base',
|
|
1146
|
-
code: 'code_project_stale_base',
|
|
1147
|
-
retryable: false,
|
|
1148
|
-
},
|
|
1149
|
-
);
|
|
1150
|
-
|
|
1151
|
-
const applied = await reducer.materializeRepo(
|
|
1152
|
-
cancelledCard,
|
|
1153
|
-
planWithPeerCheckpoint(setupState, peerCapture),
|
|
1154
|
-
2,
|
|
1155
|
-
);
|
|
1156
|
-
assert.equal(applied.state, 'in-sync', JSON.stringify(applied));
|
|
1157
|
-
const currentCard = cardFor(setupState.project, setupState.repo);
|
|
1158
|
-
const recaptured = await checkpoint.captureLocalIntent(
|
|
1159
|
-
setupState.project,
|
|
1160
|
-
currentCard,
|
|
1161
|
-
await checkpointFor(setupState.project, currentCard),
|
|
1162
|
-
);
|
|
1163
|
-
assert.deepEqual(recaptured, { state: 'silent', reason: 'no-local-effect' });
|
|
1164
|
-
assert.equal(intent.readIntent(setupState.project), null);
|
|
1165
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'file.txt'), 'utf8'), 'base\n');
|
|
1166
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'peer.txt'), 'utf8'), 'peer winner\n');
|
|
1167
|
-
});
|
|
1168
|
-
|
|
1169
|
-
test('the same bytes are new intent after the official base has advanced', async () => {
|
|
1170
|
-
const setupState = await setup(
|
|
1171
|
-
'intentional-revert',
|
|
1172
|
-
'018f0000-0000-7000-8000-00000000c00d',
|
|
1173
|
-
);
|
|
1174
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'state A\n');
|
|
1175
|
-
let card = cardFor(setupState.project, setupState.repo);
|
|
1176
|
-
const first = await checkpoint.captureLocalIntent(
|
|
1177
|
-
setupState.project,
|
|
1178
|
-
card,
|
|
1179
|
-
await checkpointFor(setupState.project, card),
|
|
1180
|
-
);
|
|
1181
|
-
const publication = wall._test.publishProject(setupState.project, [card]);
|
|
1182
|
-
mutations.acknowledgeCloudMutation(
|
|
1183
|
-
setupState.project.resourceId,
|
|
1184
|
-
publication.mutationId,
|
|
1185
|
-
{ version: 2, committedAt: '2026-07-27T01:00:00.000Z' },
|
|
1186
|
-
);
|
|
1187
|
-
assert.equal(first.intent.semanticDigest, intent.readIntent(setupState.project).semanticDigest);
|
|
1188
|
-
|
|
1189
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'state B\n');
|
|
1190
|
-
card = cardFor(setupState.project, setupState.repo);
|
|
1191
|
-
await setVerifiedBaseline(setupState.project, card, 3);
|
|
1192
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'state A\n');
|
|
1193
|
-
card = cardFor(setupState.project, setupState.repo);
|
|
1194
|
-
const reverted = await checkpoint.captureLocalIntent(
|
|
1195
|
-
setupState.project,
|
|
1196
|
-
card,
|
|
1197
|
-
await checkpointFor(setupState.project, card),
|
|
1198
|
-
);
|
|
1199
|
-
assert.equal(reverted.state, 'captured');
|
|
1200
|
-
assert.equal(reverted.intent.base.through, 3);
|
|
1201
|
-
assert.equal(reverted.intent.semanticDigest, first.intent.semanticDigest);
|
|
1202
|
-
assert.equal(
|
|
1203
|
-
reverted.intent.publication,
|
|
1204
|
-
null,
|
|
1205
|
-
'the old official number belongs to the earlier A, not this new A',
|
|
1206
|
-
);
|
|
1207
|
-
});
|
|
1208
|
-
|
|
1209
|
-
test('the losing first writer keeps one exact empty-tree intent while event one applies', async () => {
|
|
1210
|
-
const repo = createRepo('base-zero-loser');
|
|
1211
|
-
const localBranch = git(repo, 'branch', '--show-current');
|
|
1212
|
-
git(repo, 'switch', '-qc', 'winner');
|
|
1213
|
-
fs.writeFileSync(path.join(repo, 'winner.txt'), 'official winner\n');
|
|
1214
|
-
git(repo, 'add', '.');
|
|
1215
|
-
git(repo, 'commit', '-qm', 'winner');
|
|
1216
|
-
const winnerOid = git(repo, 'rev-parse', 'HEAD');
|
|
1217
|
-
git(repo, 'switch', '-q', localBranch);
|
|
1218
|
-
const localOid = git(repo, 'rev-parse', 'HEAD');
|
|
1219
|
-
const resourceId = '018f0000-0000-7000-8000-00000000c10e';
|
|
1220
|
-
const project = wall.bindProject(resourceId, OWNER, repo);
|
|
1221
|
-
projectStream.writeSnapshot(project.file, projectStream.emptySnapshot(resourceId));
|
|
1222
|
-
replicas.registerReplica({
|
|
1223
|
-
resourceId,
|
|
1224
|
-
localResource: `${projectStream.LOCAL_RESOURCE_PREFIX}${resourceId}`,
|
|
1225
|
-
localPath: project.file,
|
|
1226
|
-
contract: projectStream.STREAM_CONTRACT,
|
|
1227
|
-
schemaVersion: 2,
|
|
1228
|
-
authorityEpoch: 1,
|
|
1229
|
-
appliedVersion: 0,
|
|
1230
|
-
snapshotVersion: 0,
|
|
1231
|
-
});
|
|
1232
|
-
const localCard = cardFor(project, repo);
|
|
1233
|
-
const captured = await checkpoint.captureLocalIntent(
|
|
1234
|
-
project,
|
|
1235
|
-
localCard,
|
|
1236
|
-
await checkpointFor(project, localCard),
|
|
1237
|
-
);
|
|
1238
|
-
assert.equal(captured.state, 'captured');
|
|
1239
|
-
assert.match(captured.intent.base.indexTree, /^[0-9a-f]{40,64}$/);
|
|
1240
|
-
assert.equal(captured.intent.base.worktreeTree, captured.intent.base.indexTree);
|
|
1241
|
-
assert.equal(captured.intent.base.card, null);
|
|
1242
|
-
|
|
1243
|
-
const winnerObjects = localCard.refs.find((branch) => branch.name === 'winner').objects;
|
|
1244
|
-
const official = {
|
|
1245
|
-
...localCard,
|
|
1246
|
-
head: { branch: localBranch, oid: winnerOid, detached: false },
|
|
1247
|
-
objects: winnerObjects,
|
|
1248
|
-
positionVersion: 1,
|
|
1249
|
-
positionMutationId: 'mutation_first_winner_01',
|
|
1250
|
-
};
|
|
1251
|
-
const outcome = await reducer.materializeRepo(localCard, {
|
|
1252
|
-
repoKey: project.repoKey,
|
|
1253
|
-
card: official,
|
|
1254
|
-
branches: [{
|
|
1255
|
-
name: localBranch,
|
|
1256
|
-
oid: winnerOid,
|
|
1257
|
-
objects: winnerObjects,
|
|
1258
|
-
version: 1,
|
|
1259
|
-
mutationId: official.positionMutationId,
|
|
1260
|
-
}],
|
|
1261
|
-
transitions: [],
|
|
1262
|
-
}, 1);
|
|
1263
|
-
assert.equal(outcome.state, 'in-sync', JSON.stringify(outcome));
|
|
1264
|
-
assert.equal(git(repo, 'rev-parse', 'HEAD'), localOid);
|
|
1265
|
-
const retained = intent.readOwnedIntent(project);
|
|
1266
|
-
assert.equal(retained?.base.through, 1);
|
|
1267
|
-
assert.equal(
|
|
1268
|
-
retained?.birthMutationId,
|
|
1269
|
-
captured.intent.birthMutationId,
|
|
1270
|
-
JSON.stringify({ captured: captured.intent, retained }),
|
|
1271
|
-
);
|
|
1272
|
-
assert.equal(reducer.readRepoState(project.repoKey, project.resourceId).appliedThrough, 1);
|
|
1273
|
-
});
|
|
1274
|
-
|
|
1275
|
-
test('a base-zero intent with a non-empty fake baseline blocks instead of being discarded', async () => {
|
|
1276
|
-
const repo = createRepo('base-zero-corrupt');
|
|
1277
|
-
const resourceId = '018f0000-0000-7000-8000-00000000c10f';
|
|
1278
|
-
const project = wall.bindProject(resourceId, OWNER, repo);
|
|
1279
|
-
projectStream.writeSnapshot(project.file, projectStream.emptySnapshot(resourceId));
|
|
1280
|
-
replicas.registerReplica({
|
|
1281
|
-
resourceId,
|
|
1282
|
-
localResource: `${projectStream.LOCAL_RESOURCE_PREFIX}${resourceId}`,
|
|
1283
|
-
localPath: project.file,
|
|
1284
|
-
contract: projectStream.STREAM_CONTRACT,
|
|
1285
|
-
schemaVersion: 2,
|
|
1286
|
-
authorityEpoch: 1,
|
|
1287
|
-
appliedVersion: 0,
|
|
1288
|
-
snapshotVersion: 0,
|
|
1289
|
-
});
|
|
1290
|
-
const localCard = cardFor(project, repo);
|
|
1291
|
-
const captured = await checkpoint.captureLocalIntent(
|
|
1292
|
-
project,
|
|
1293
|
-
localCard,
|
|
1294
|
-
await checkpointFor(project, localCard),
|
|
1295
|
-
);
|
|
1296
|
-
const nonEmptyTree = await merge.treeOf(repo, localCard.head.oid);
|
|
1297
|
-
intent.writeIntent(project, {
|
|
1298
|
-
base: {
|
|
1299
|
-
through: 0,
|
|
1300
|
-
card: null,
|
|
1301
|
-
indexTree: nonEmptyTree,
|
|
1302
|
-
worktreeTree: nonEmptyTree,
|
|
1303
|
-
},
|
|
1304
|
-
local: captured.intent.local,
|
|
1305
|
-
publication: null,
|
|
1306
|
-
});
|
|
1307
|
-
const official = {
|
|
1308
|
-
...localCard,
|
|
1309
|
-
positionVersion: 1,
|
|
1310
|
-
positionMutationId: 'mutation_initial_corrupt_01',
|
|
1311
|
-
};
|
|
1312
|
-
const outcome = await reducer.materializeRepo(localCard, {
|
|
1313
|
-
repoKey: project.repoKey,
|
|
1314
|
-
card: official,
|
|
1315
|
-
branches: localCard.refs.map((branch) => ({
|
|
1316
|
-
...branch,
|
|
1317
|
-
version: 1,
|
|
1318
|
-
mutationId: official.positionMutationId,
|
|
1319
|
-
})),
|
|
1320
|
-
transitions: [],
|
|
1321
|
-
}, 1);
|
|
1322
|
-
assert.deepEqual(outcome, {
|
|
1323
|
-
state: 'waiting',
|
|
1324
|
-
reason: 'local-intent-provenance-unverified',
|
|
1325
|
-
});
|
|
1326
|
-
assert.equal(intent.readIntent(project)?.semanticDigest, captured.intent.semanticDigest);
|
|
1327
|
-
});
|
|
1328
|
-
|
|
1329
|
-
test('inbound work arriving before the watcher captures local intent and applies underneath it', async () => {
|
|
1330
|
-
const setupState = await setup(
|
|
1331
|
-
'ordered-catch-up',
|
|
1332
|
-
'018f0000-0000-7000-8000-00000000c00a',
|
|
1333
|
-
);
|
|
1334
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'official peer\n');
|
|
1335
|
-
const peerCapture = await require('../workspace/git').captureCheckpoint(setupState.repo);
|
|
1336
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'base\n');
|
|
1337
|
-
fs.writeFileSync(path.join(setupState.repo, 'local.txt'), 'pending local\n');
|
|
1338
|
-
const localCard = cardFor(setupState.project, setupState.repo);
|
|
1339
|
-
const synchronousCapture = await checkpoint.captureIntentForCard(
|
|
1340
|
-
setupState.project,
|
|
1341
|
-
localCard,
|
|
1342
|
-
);
|
|
1343
|
-
assert.equal(synchronousCapture.state, 'captured');
|
|
1344
|
-
|
|
1345
|
-
const plan = planWithPeerCheckpoint(setupState, peerCapture);
|
|
1346
|
-
const outcome = await reducer.materializeRepo(localCard, plan, 2);
|
|
1347
|
-
assert.equal(outcome.state, 'in-sync', JSON.stringify(outcome));
|
|
1348
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'file.txt'), 'utf8'), 'official peer\n');
|
|
1349
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'local.txt'), 'utf8'), 'pending local\n');
|
|
1350
|
-
const verified = reducer.readRepoState(
|
|
1351
|
-
setupState.project.repoKey,
|
|
1352
|
-
setupState.project.resourceId,
|
|
1353
|
-
);
|
|
1354
|
-
assert.equal(verified.appliedThrough, 2);
|
|
1355
|
-
assert.equal(Object.hasOwn(verified, 'pendingCard'), false);
|
|
1356
|
-
assert.equal(Object.hasOwn(verified, 'pendingRefs'), false);
|
|
1357
|
-
assert.equal(Object.hasOwn(verified, 'pendingCheckpoint'), false);
|
|
1358
|
-
|
|
1359
|
-
const refreshedCard = cardFor(setupState.project, setupState.repo);
|
|
1360
|
-
const refreshed = await checkpoint.captureLocalIntent(
|
|
1361
|
-
setupState.project,
|
|
1362
|
-
refreshedCard,
|
|
1363
|
-
await checkpointFor(setupState.project, refreshedCard),
|
|
1364
|
-
);
|
|
1365
|
-
assert.equal(refreshed.state, 'captured');
|
|
1366
|
-
assert.equal(refreshed.intent.base.through, 2);
|
|
1367
|
-
});
|
|
1368
|
-
|
|
1369
|
-
test('one intent birth certificate cannot combine captures from two repo states', async () => {
|
|
1370
|
-
const setupState = await setup(
|
|
1371
|
-
'single-observation-capture',
|
|
1372
|
-
'018f0000-0000-7000-8000-00000000c120',
|
|
1373
|
-
);
|
|
1374
|
-
const staleCard = setupState.card;
|
|
1375
|
-
const staleCheckpoint = await checkpointFor(setupState.project, staleCard);
|
|
1376
|
-
|
|
1377
|
-
fs.writeFileSync(path.join(setupState.repo, 'committed-later.txt'), 'later commit\n');
|
|
1378
|
-
git(setupState.repo, 'add', 'committed-later.txt');
|
|
1379
|
-
git(setupState.repo, 'commit', '-qm', 'commit between captures');
|
|
1380
|
-
const laterOid = git(setupState.repo, 'rev-parse', 'HEAD');
|
|
1381
|
-
assert.notEqual(laterOid, staleCard.head.oid);
|
|
1382
|
-
|
|
1383
|
-
const mixed = await checkpoint.captureLocalIntent(
|
|
1384
|
-
setupState.project,
|
|
1385
|
-
staleCard,
|
|
1386
|
-
staleCheckpoint,
|
|
1387
|
-
);
|
|
1388
|
-
assert.deepEqual(mixed, {
|
|
1389
|
-
state: 'waiting',
|
|
1390
|
-
reason: 'local-state-moving',
|
|
1391
|
-
});
|
|
1392
|
-
assert.equal(intent.readIntent(setupState.project), null);
|
|
1393
|
-
|
|
1394
|
-
const freshCard = cardFor(setupState.project, setupState.repo);
|
|
1395
|
-
const freshCheckpoint = await checkpointFor(setupState.project, freshCard);
|
|
1396
|
-
const captured = await checkpoint.captureLocalIntent(
|
|
1397
|
-
setupState.project,
|
|
1398
|
-
freshCard,
|
|
1399
|
-
freshCheckpoint,
|
|
1400
|
-
);
|
|
1401
|
-
assert.equal(captured.state, 'captured');
|
|
1402
|
-
assert.equal(captured.intent.local.card.head.oid, laterOid);
|
|
1403
|
-
assert.equal(
|
|
1404
|
-
captured.intent.local.physicalDigest,
|
|
1405
|
-
(await reducer.observePhysicalRepo(setupState.repo)).digest,
|
|
1406
|
-
);
|
|
1407
|
-
});
|
|
1408
|
-
|
|
1409
|
-
test('one capture records card, checkpoint, trees, and digest from one observation', async () => {
|
|
1410
|
-
const setupState = await setup(
|
|
1411
|
-
'one-observation-fields',
|
|
1412
|
-
'018f0000-0000-7000-8000-00000000c121',
|
|
1413
|
-
);
|
|
1414
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'first local value\n');
|
|
1415
|
-
const card = cardFor(setupState.project, setupState.repo);
|
|
1416
|
-
const observe = reducer.observePhysicalRepo;
|
|
1417
|
-
let firstObservation = null;
|
|
1418
|
-
reducer.observePhysicalRepo = async (repo) => {
|
|
1419
|
-
firstObservation = await observe(repo);
|
|
1420
|
-
fs.writeFileSync(path.join(repo, 'file.txt'), 'second local value\n');
|
|
1421
|
-
return firstObservation;
|
|
1422
|
-
};
|
|
1423
|
-
let captured;
|
|
1424
|
-
try {
|
|
1425
|
-
captured = await checkpoint.captureIntentForCard(setupState.project, card);
|
|
1426
|
-
} finally {
|
|
1427
|
-
reducer.observePhysicalRepo = observe;
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
assert.equal(captured.state, 'captured');
|
|
1431
|
-
const held = intent.readIntent(setupState.project);
|
|
1432
|
-
assert.equal(held.local.physicalDigest, firstObservation.digest);
|
|
1433
|
-
assert.equal(held.local.indexTree, firstObservation.value.indexTree);
|
|
1434
|
-
assert.equal(held.local.worktreeTree, firstObservation.value.worktreeTree);
|
|
1435
|
-
assert.equal(
|
|
1436
|
-
held.local.checkpoint.value.revision,
|
|
1437
|
-
checkpoint.revisionOf(
|
|
1438
|
-
firstObservation.captured.baseOid,
|
|
1439
|
-
firstObservation.captured.indexDiff,
|
|
1440
|
-
firstObservation.captured.worktreeDiff,
|
|
1441
|
-
),
|
|
1442
|
-
);
|
|
1443
|
-
assert.notEqual((await reducer.observePhysicalRepo(setupState.repo)).digest, held.local.physicalDigest);
|
|
1444
|
-
|
|
1445
|
-
const successor = await checkpoint.captureIntentForCard(
|
|
1446
|
-
setupState.project,
|
|
1447
|
-
cardFor(setupState.project, setupState.repo),
|
|
1448
|
-
);
|
|
1449
|
-
assert.equal(successor.state, 'captured');
|
|
1450
|
-
assert.equal(
|
|
1451
|
-
successor.intent.local.physicalDigest,
|
|
1452
|
-
(await reducer.observePhysicalRepo(setupState.repo)).digest,
|
|
1453
|
-
);
|
|
1454
|
-
});
|
|
1455
|
-
|
|
1456
|
-
test('capture and materialization take one serialized repo-state turn', async () => {
|
|
1457
|
-
const setupState = await setup(
|
|
1458
|
-
'capture-materialization-epoch',
|
|
1459
|
-
'018f0000-0000-7000-8000-00000000c122',
|
|
1460
|
-
);
|
|
1461
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'local before pause\n');
|
|
1462
|
-
const card = cardFor(setupState.project, setupState.repo);
|
|
1463
|
-
const observe = reducer.observePhysicalRepo;
|
|
1464
|
-
let releaseObservation;
|
|
1465
|
-
let observationStarted;
|
|
1466
|
-
const started = new Promise((resolve) => { observationStarted = resolve; });
|
|
1467
|
-
const release = new Promise((resolve) => { releaseObservation = resolve; });
|
|
1468
|
-
reducer.observePhysicalRepo = async (repo) => {
|
|
1469
|
-
observationStarted();
|
|
1470
|
-
await release;
|
|
1471
|
-
return observe(repo);
|
|
1472
|
-
};
|
|
1473
|
-
let materializationEntered = false;
|
|
1474
|
-
try {
|
|
1475
|
-
const pendingCapture = checkpoint.captureIntentForCard(setupState.project, card);
|
|
1476
|
-
await started;
|
|
1477
|
-
const pendingMaterialization = reducer.withMaterialization(
|
|
1478
|
-
setupState.project.repoKey,
|
|
1479
|
-
async () => {
|
|
1480
|
-
materializationEntered = true;
|
|
1481
|
-
},
|
|
1482
|
-
);
|
|
1483
|
-
await new Promise((resolve) => { setImmediate(resolve); });
|
|
1484
|
-
assert.equal(materializationEntered, false);
|
|
1485
|
-
releaseObservation();
|
|
1486
|
-
const capture = await pendingCapture;
|
|
1487
|
-
assert.equal(capture.state, 'captured');
|
|
1488
|
-
await pendingMaterialization;
|
|
1489
|
-
assert.equal(materializationEntered, true);
|
|
1490
|
-
} finally {
|
|
1491
|
-
reducer.observePhysicalRepo = observe;
|
|
1492
|
-
releaseObservation?.();
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
assert.ok(intent.readIntent(setupState.project));
|
|
1496
|
-
});
|
|
1497
|
-
|
|
1498
|
-
test('two materializations of one repo never overlap', async () => {
|
|
1499
|
-
const repoKey = 'code-folder:serialized-materializations';
|
|
1500
|
-
let active = 0;
|
|
1501
|
-
let maximum = 0;
|
|
1502
|
-
let releaseFirst;
|
|
1503
|
-
const firstGate = new Promise((resolve) => { releaseFirst = resolve; });
|
|
1504
|
-
const first = reducer.withMaterialization(repoKey, async () => {
|
|
1505
|
-
active += 1;
|
|
1506
|
-
maximum = Math.max(maximum, active);
|
|
1507
|
-
await firstGate;
|
|
1508
|
-
active -= 1;
|
|
1509
|
-
});
|
|
1510
|
-
const second = reducer.withMaterialization(repoKey, async () => {
|
|
1511
|
-
active += 1;
|
|
1512
|
-
maximum = Math.max(maximum, active);
|
|
1513
|
-
active -= 1;
|
|
1514
|
-
});
|
|
1515
|
-
await new Promise((resolve) => { setImmediate(resolve); });
|
|
1516
|
-
assert.equal(maximum, 1);
|
|
1517
|
-
releaseFirst();
|
|
1518
|
-
await Promise.all([first, second]);
|
|
1519
|
-
assert.equal(maximum, 1);
|
|
1520
|
-
});
|
|
1521
|
-
|
|
1522
|
-
test('different repos keep independent local-state turns', async () => {
|
|
1523
|
-
let active = 0;
|
|
1524
|
-
let maximum = 0;
|
|
1525
|
-
let release;
|
|
1526
|
-
const gate = new Promise((resolve) => { release = resolve; });
|
|
1527
|
-
const enter = async () => {
|
|
1528
|
-
active += 1;
|
|
1529
|
-
maximum = Math.max(maximum, active);
|
|
1530
|
-
await gate;
|
|
1531
|
-
active -= 1;
|
|
1532
|
-
};
|
|
1533
|
-
const first = reducer.withMaterialization('code-folder:turn-a', enter);
|
|
1534
|
-
const second = reducer.withMaterialization('code-folder:turn-b', enter);
|
|
1535
|
-
await new Promise((resolve) => { setImmediate(resolve); });
|
|
1536
|
-
assert.equal(maximum, 2);
|
|
1537
|
-
release();
|
|
1538
|
-
await Promise.all([first, second]);
|
|
1539
|
-
});
|
|
1540
|
-
|
|
1541
|
-
test('an edit between capture and the apply lock stops apply, captures, then survives catch-up', async () => {
|
|
1542
|
-
const setupState = await setup(
|
|
1543
|
-
'apply-boundary-race',
|
|
1544
|
-
'018f0000-0000-7000-8000-00000000c00b',
|
|
1545
|
-
);
|
|
1546
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'official peer\n');
|
|
1547
|
-
const peerCapture = await require('../workspace/git').captureCheckpoint(setupState.repo);
|
|
1548
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'base\n');
|
|
1549
|
-
|
|
1550
|
-
assert.equal(
|
|
1551
|
-
(await checkpoint.captureIntentForCard(setupState.project, setupState.card)).state,
|
|
1552
|
-
'silent',
|
|
1553
|
-
);
|
|
1554
|
-
fs.writeFileSync(path.join(setupState.repo, 'local.txt'), 'written inside the gap\n');
|
|
1555
|
-
|
|
1556
|
-
const boundary = await reducer.withMaterialization(
|
|
1557
|
-
setupState.project.repoKey,
|
|
1558
|
-
() => reducer.verifyApplyBoundary(setupState.project, setupState.card, 2),
|
|
1559
|
-
);
|
|
1560
|
-
assert.deepEqual(boundary, {
|
|
1561
|
-
state: 'waiting',
|
|
1562
|
-
reason: 'local-intent-capture-required',
|
|
1563
|
-
});
|
|
1564
|
-
assert.equal(intent.readIntent(setupState.project), null);
|
|
1565
|
-
|
|
1566
|
-
const captured = await checkpoint.captureIntentForCard(
|
|
1567
|
-
setupState.project,
|
|
1568
|
-
setupState.card,
|
|
1569
|
-
);
|
|
1570
|
-
assert.equal(captured.state, 'captured');
|
|
1571
|
-
const outcome = await reducer.materializeRepo(
|
|
1572
|
-
setupState.card,
|
|
1573
|
-
planWithPeerCheckpoint(setupState, peerCapture),
|
|
1574
|
-
2,
|
|
1575
|
-
);
|
|
1576
|
-
assert.equal(outcome.state, 'in-sync', JSON.stringify(outcome));
|
|
1577
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'file.txt'), 'utf8'), 'official peer\n');
|
|
1578
|
-
assert.equal(
|
|
1579
|
-
fs.readFileSync(path.join(setupState.repo, 'local.txt'), 'utf8'),
|
|
1580
|
-
'written inside the gap\n',
|
|
1581
|
-
);
|
|
1582
|
-
});
|
|
1583
|
-
|
|
1584
|
-
test('a save after the boundary check cannot be blessed by the later WAL observation', async () => {
|
|
1585
|
-
const setupState = await setup(
|
|
1586
|
-
'post-boundary-save',
|
|
1587
|
-
'018f0000-0000-7000-8000-00000000c115',
|
|
1588
|
-
);
|
|
1589
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'official peer\n');
|
|
1590
|
-
const peerCapture = await require('../workspace/git').captureCheckpoint(setupState.repo);
|
|
1591
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'base\n');
|
|
1592
|
-
const plan = planWithPeerCheckpoint(setupState, peerCapture);
|
|
1593
|
-
|
|
1594
|
-
const boundary = await reducer.withMaterialization(
|
|
1595
|
-
setupState.project.repoKey,
|
|
1596
|
-
() => reducer.verifyApplyBoundary(setupState.project, setupState.card, 2),
|
|
1597
|
-
);
|
|
1598
|
-
assert.deepEqual(boundary, { state: 'ready' });
|
|
1599
|
-
fs.writeFileSync(path.join(setupState.repo, 'local.txt'), 'saved after boundary\n');
|
|
1600
|
-
|
|
1601
|
-
const blocked = await reducer.materializeRepo(setupState.card, plan, 2);
|
|
1602
|
-
assert.deepEqual(blocked, {
|
|
1603
|
-
state: 'waiting',
|
|
1604
|
-
reason: 'local-intent-capture-required',
|
|
1605
|
-
});
|
|
1606
|
-
const held = reducer.readRepoState(
|
|
1607
|
-
setupState.project.repoKey,
|
|
1608
|
-
setupState.project.resourceId,
|
|
1609
|
-
);
|
|
1610
|
-
assert.equal(held.appliedThrough, 1);
|
|
1611
|
-
assert.equal(held.applying, undefined);
|
|
1612
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'local.txt'), 'utf8'), 'saved after boundary\n');
|
|
1613
|
-
|
|
1614
|
-
const captured = await checkpoint.captureIntentForCard(
|
|
1615
|
-
setupState.project,
|
|
1616
|
-
setupState.card,
|
|
1617
|
-
);
|
|
1618
|
-
assert.equal(captured.state, 'captured');
|
|
1619
|
-
const applied = await reducer.materializeRepo(setupState.card, plan, 2);
|
|
1620
|
-
assert.equal(applied.state, 'in-sync', JSON.stringify(applied));
|
|
1621
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'file.txt'), 'utf8'), 'official peer\n');
|
|
1622
|
-
assert.equal(fs.readFileSync(path.join(setupState.repo, 'local.txt'), 'utf8'), 'saved after boundary\n');
|
|
1623
|
-
});
|
|
1624
|
-
|
|
1625
|
-
test('only a provenance record can keep an old branch from yielding to the rail', () => {
|
|
1626
|
-
const oidA = 'a'.repeat(40);
|
|
1627
|
-
const oidB = 'b'.repeat(40);
|
|
1628
|
-
const oidC = 'c'.repeat(40);
|
|
1629
|
-
const plan = {
|
|
1630
|
-
card: { head: { branch: 'main', oid: oidA } },
|
|
1631
|
-
branches: [
|
|
1632
|
-
{ name: 'main', oid: oidA },
|
|
1633
|
-
{ name: 'feature', oid: oidB },
|
|
1634
|
-
],
|
|
1635
|
-
};
|
|
1636
|
-
const current = [
|
|
1637
|
-
{ name: 'main', oid: oidA },
|
|
1638
|
-
{ name: 'feature', oid: oidC },
|
|
1639
|
-
];
|
|
1640
|
-
const owned = reducer.classifyBranchRefs(plan, current, {
|
|
1641
|
-
base: { card: { branches: [{ name: 'main', oid: oidA }, { name: 'feature', oid: oidA }] } },
|
|
1642
|
-
local: { card: { branches: current } },
|
|
1643
|
-
});
|
|
1644
|
-
assert.deepEqual(owned.changes, []);
|
|
1645
|
-
assert.deepEqual(owned.pending, [{ name: 'feature', oid: oidC }]);
|
|
1646
|
-
|
|
1647
|
-
const acknowledged = reducer.classifyBranchRefs(plan, current, null);
|
|
1648
|
-
assert.deepEqual(acknowledged.pending, []);
|
|
1649
|
-
assert.deepEqual(acknowledged.changes, [{
|
|
1650
|
-
branch: 'feature',
|
|
1651
|
-
expectedOid: oidC,
|
|
1652
|
-
oid: oidB,
|
|
1653
|
-
}]);
|
|
1654
|
-
});
|
|
1655
|
-
|
|
1656
|
-
test('a pending HEAD switch protects its attached branch but still advances official HEAD', () => {
|
|
1657
|
-
const oidA = 'a'.repeat(40);
|
|
1658
|
-
const oidB = 'b'.repeat(40);
|
|
1659
|
-
const oidF = 'f'.repeat(40);
|
|
1660
|
-
const classified = reducer.classifyBranchRefs({
|
|
1661
|
-
card: { head: { branch: 'main', oid: oidB } },
|
|
1662
|
-
branches: [{ name: 'main', oid: oidB }],
|
|
1663
|
-
}, [
|
|
1664
|
-
{ name: 'main', oid: oidA },
|
|
1665
|
-
{ name: 'feature', oid: oidF },
|
|
1666
|
-
], {
|
|
1667
|
-
base: {
|
|
1668
|
-
card: {
|
|
1669
|
-
head: { branch: 'main', oid: oidA },
|
|
1670
|
-
branches: [{ name: 'main', oid: oidA }],
|
|
1671
|
-
},
|
|
1672
|
-
},
|
|
1673
|
-
local: {
|
|
1674
|
-
card: {
|
|
1675
|
-
head: { branch: 'feature', oid: oidF },
|
|
1676
|
-
branches: [
|
|
1677
|
-
{ name: 'main', oid: oidA },
|
|
1678
|
-
{ name: 'feature', oid: oidF },
|
|
1679
|
-
],
|
|
1680
|
-
},
|
|
1681
|
-
},
|
|
1682
|
-
});
|
|
1683
|
-
assert.deepEqual(classified.changes, [{
|
|
1684
|
-
branch: 'main',
|
|
1685
|
-
expectedOid: oidA,
|
|
1686
|
-
oid: oidB,
|
|
1687
|
-
}]);
|
|
1688
|
-
assert.deepEqual(classified.pending, [{ name: 'feature', oid: oidF }]);
|
|
1689
|
-
assert.deepEqual(Object.fromEntries(classified.expected), {
|
|
1690
|
-
main: oidB,
|
|
1691
|
-
feature: oidF,
|
|
1692
|
-
});
|
|
1693
|
-
});
|
|
1694
|
-
|
|
1695
|
-
test('switching to an existing branch owns the attached ref while official main advances', () => {
|
|
1696
|
-
const oidA = 'a'.repeat(40);
|
|
1697
|
-
const oidB = 'b'.repeat(40);
|
|
1698
|
-
const oidF = 'f'.repeat(40);
|
|
1699
|
-
const classified = reducer.classifyBranchRefs({
|
|
1700
|
-
card: { head: { branch: 'main', oid: oidB } },
|
|
1701
|
-
branches: [
|
|
1702
|
-
{ name: 'main', oid: oidB },
|
|
1703
|
-
{ name: 'feature', oid: null },
|
|
1704
|
-
],
|
|
1705
|
-
}, [
|
|
1706
|
-
{ name: 'main', oid: oidA },
|
|
1707
|
-
{ name: 'feature', oid: oidF },
|
|
1708
|
-
], {
|
|
1709
|
-
base: {
|
|
1710
|
-
card: {
|
|
1711
|
-
head: { branch: 'main', oid: oidA },
|
|
1712
|
-
branches: [
|
|
1713
|
-
{ name: 'main', oid: oidA },
|
|
1714
|
-
{ name: 'feature', oid: oidF },
|
|
1715
|
-
],
|
|
1716
|
-
},
|
|
1717
|
-
},
|
|
1718
|
-
local: {
|
|
1719
|
-
card: {
|
|
1720
|
-
head: { branch: 'feature', oid: oidF },
|
|
1721
|
-
branches: [
|
|
1722
|
-
{ name: 'main', oid: oidA },
|
|
1723
|
-
{ name: 'feature', oid: oidF },
|
|
1724
|
-
],
|
|
1725
|
-
},
|
|
1726
|
-
},
|
|
1727
|
-
});
|
|
1728
|
-
assert.deepEqual(classified.changes, [{
|
|
1729
|
-
branch: 'main',
|
|
1730
|
-
expectedOid: oidA,
|
|
1731
|
-
oid: oidB,
|
|
1732
|
-
}]);
|
|
1733
|
-
assert.deepEqual(classified.pending, [{ name: 'feature', oid: oidF }]);
|
|
1734
|
-
assert.deepEqual(Object.fromEntries(classified.expected), {
|
|
1735
|
-
main: oidB,
|
|
1736
|
-
feature: oidF,
|
|
1737
|
-
});
|
|
1738
|
-
});
|
|
1739
|
-
|
|
1740
|
-
test('folder binding is an owner/resource/realpath bijection', () => {
|
|
1741
|
-
const realParent = path.join(tempRoot, 'binding-real');
|
|
1742
|
-
const aliasParent = path.join(tempRoot, 'binding-alias');
|
|
1743
|
-
fs.mkdirSync(realParent);
|
|
1744
|
-
fs.symlinkSync(realParent, aliasParent);
|
|
1745
|
-
const real = path.join(realParent, 'repo');
|
|
1746
|
-
const alias = path.join(aliasParent, 'repo');
|
|
1747
|
-
fs.mkdirSync(real);
|
|
1748
|
-
const resourceId = '018f0000-0000-7000-8000-00000000c004';
|
|
1749
|
-
const first = wall.bindProject(resourceId, OWNER, alias);
|
|
1750
|
-
assert.equal(first.path, fs.realpathSync(real));
|
|
1751
|
-
assert.equal(wall.bindProject(resourceId, OWNER, real).path, first.path);
|
|
1752
|
-
assert.throws(
|
|
1753
|
-
() => wall.bindProject('018f0000-0000-7000-8000-00000000c005', OWNER, alias),
|
|
1754
|
-
/already bound/,
|
|
1755
|
-
);
|
|
1756
|
-
assert.throws(
|
|
1757
|
-
() => wall.bindProject(resourceId, OWNER, path.join(tempRoot, 'elsewhere')),
|
|
1758
|
-
/already bound differently/,
|
|
1759
|
-
);
|
|
1760
|
-
assert.throws(
|
|
1761
|
-
() => wall.bindProject('018f0000-0000-7000-8000-00000000c006', 'not-a-user', path.join(tempRoot, 'other')),
|
|
1762
|
-
/ownerUserId/,
|
|
1763
|
-
);
|
|
1764
|
-
});
|
|
1765
|
-
|
|
1766
|
-
test('snapshot install ignores older state, accepts exact equality, and rejects unequal equality', async () => {
|
|
1767
|
-
const setupState = await setup(
|
|
1768
|
-
'snapshot-order',
|
|
1769
|
-
'018f0000-0000-7000-8000-00000000c007',
|
|
1770
|
-
);
|
|
1771
|
-
const input = (snapshot) => {
|
|
1772
|
-
const bytes = Buffer.from(JSON.stringify(snapshot));
|
|
1773
|
-
return {
|
|
1774
|
-
resourceId: setupState.project.resourceId,
|
|
1775
|
-
contract: projectStream.STREAM_CONTRACT,
|
|
1776
|
-
schemaVersion: 2,
|
|
1777
|
-
authorityEpoch: 1,
|
|
1778
|
-
headVersion: snapshot.version,
|
|
1779
|
-
snapshotVersion: snapshot.version,
|
|
1780
|
-
snapshotChecksum: crypto.createHash('sha256').update(bytes).digest('hex'),
|
|
1781
|
-
snapshotBase64: bytes.toString('base64'),
|
|
1782
|
-
};
|
|
1783
|
-
};
|
|
1784
|
-
const current = projectStream.readSnapshot(
|
|
1785
|
-
setupState.project.file,
|
|
1786
|
-
setupState.project.resourceId,
|
|
1787
|
-
);
|
|
1788
|
-
const older = projectStream.installSnapshot(
|
|
1789
|
-
setupState.project.file,
|
|
1790
|
-
input(projectStream.emptySnapshot(setupState.project.resourceId)),
|
|
1791
|
-
);
|
|
1792
|
-
assert.equal(older.ignored, true);
|
|
1793
|
-
assert.equal(projectStream.installSnapshot(
|
|
1794
|
-
setupState.project.file,
|
|
1795
|
-
input(current),
|
|
1796
|
-
).duplicate, true);
|
|
1797
|
-
|
|
1798
|
-
const otherRepo = createRepo('snapshot-other');
|
|
1799
|
-
fs.writeFileSync(path.join(otherRepo, 'other.txt'), 'other\n');
|
|
1800
|
-
git(otherRepo, 'add', '.');
|
|
1801
|
-
git(otherRepo, 'commit', '-qm', 'other');
|
|
1802
|
-
const otherCard = cardFor(setupState.project, otherRepo, 'replica-other');
|
|
1803
|
-
const unequal = officialSnapshot(
|
|
1804
|
-
setupState.project,
|
|
1805
|
-
otherCard,
|
|
1806
|
-
1,
|
|
1807
|
-
'mutation_official_other_01',
|
|
1808
|
-
);
|
|
1809
|
-
assert.throws(
|
|
1810
|
-
() => projectStream.installSnapshot(setupState.project.file, input(unequal)),
|
|
1811
|
-
(error) => error.code === 'code_project_snapshot_mismatch',
|
|
1812
|
-
);
|
|
1813
|
-
|
|
1814
|
-
const newer = projectStream._test.fold(
|
|
1815
|
-
current,
|
|
1816
|
-
{
|
|
1817
|
-
kind: 'code.publish',
|
|
1818
|
-
project: setupState.project.resourceId,
|
|
1819
|
-
delta: {
|
|
1820
|
-
kind: 'checkpoint.set',
|
|
1821
|
-
checkpoint: checkpoint.CHECKPOINT_CONTRACT,
|
|
1822
|
-
baseOid: current.card.head.oid,
|
|
1823
|
-
branch: current.card.head.branch,
|
|
1824
|
-
from: current.checkpoint.value,
|
|
1825
|
-
to: {
|
|
1826
|
-
revision: checkpoint.revisionOf(current.card.head.oid, '', 'diff --git a/new b/new\n'),
|
|
1827
|
-
indexDiff: '',
|
|
1828
|
-
worktreeDiff: 'diff --git a/new b/new\n',
|
|
1829
|
-
},
|
|
1830
|
-
},
|
|
1831
|
-
},
|
|
1832
|
-
{ machine: 'machine-peer', mutationId: 'mutation_snapshot_newer_01', version: 2 },
|
|
1833
|
-
);
|
|
1834
|
-
const beforeFuture = fs.readFileSync(setupState.project.file, 'utf8');
|
|
1835
|
-
assert.throws(
|
|
1836
|
-
() => projectStream.installSnapshot(
|
|
1837
|
-
setupState.project.file,
|
|
1838
|
-
{ ...input(newer), headVersion: 1 },
|
|
1839
|
-
),
|
|
1840
|
-
/cannot exceed headVersion/,
|
|
1841
|
-
);
|
|
1842
|
-
assert.equal(fs.readFileSync(setupState.project.file, 'utf8'), beforeFuture);
|
|
1843
|
-
assert.equal(
|
|
1844
|
-
projectStream.installSnapshot(setupState.project.file, input(newer)).replica.appliedVersion,
|
|
1845
|
-
2,
|
|
1846
|
-
);
|
|
1847
|
-
assert.equal(
|
|
1848
|
-
projectStream.readSnapshot(setupState.project.file, setupState.project.resourceId).version,
|
|
1849
|
-
2,
|
|
1850
|
-
);
|
|
1851
|
-
|
|
1852
|
-
openLocalDb().prepare(`
|
|
1853
|
-
UPDATE shared_resource_replicas
|
|
1854
|
-
SET applied_version = 1, snapshot_version = 1
|
|
1855
|
-
WHERE resource_id = ?
|
|
1856
|
-
`).run(setupState.project.resourceId);
|
|
1857
|
-
const replay = projectStream.installSnapshot(
|
|
1858
|
-
setupState.project.file,
|
|
1859
|
-
input(newer),
|
|
1860
|
-
);
|
|
1861
|
-
assert.equal(replay.duplicate, true);
|
|
1862
|
-
assert.equal(replay.replica.appliedVersion, 2);
|
|
1863
|
-
assert.equal(replay.replica.snapshotVersion, 2);
|
|
1864
|
-
});
|
|
1865
|
-
|
|
1866
|
-
test('an equal snapshot retry repairs numbered work through fold and physical apply', async () => {
|
|
1867
|
-
const setupState = await setup(
|
|
1868
|
-
'snapshot-fold-recovery',
|
|
1869
|
-
'018f0000-0000-7000-8000-00000000c00e',
|
|
1870
|
-
);
|
|
1871
|
-
fs.writeFileSync(path.join(setupState.repo, 'file.txt'), 'numbered by snapshot\n');
|
|
1872
|
-
const card = cardFor(setupState.project, setupState.repo);
|
|
1873
|
-
await checkpoint.captureLocalIntent(
|
|
1874
|
-
setupState.project,
|
|
1875
|
-
card,
|
|
1876
|
-
await checkpointFor(setupState.project, card),
|
|
1877
|
-
);
|
|
1878
|
-
const published = wall._test.publishProject(setupState.project, [card]);
|
|
1879
|
-
const claimed = mutations.claimCloudMutations().find((entry) => (
|
|
1880
|
-
entry.mutation.mutationId === published.mutationId
|
|
1881
|
-
));
|
|
1882
|
-
mutations.acknowledgeCloudMutation(
|
|
1883
|
-
setupState.project.resourceId,
|
|
1884
|
-
published.mutationId,
|
|
1885
|
-
{ version: 2, committedAt: '2026-07-27T02:00:00.000Z' },
|
|
1886
|
-
);
|
|
1887
|
-
const next = projectStream._test.fold(
|
|
1888
|
-
projectStream.readSnapshot(
|
|
1889
|
-
setupState.project.file,
|
|
1890
|
-
setupState.project.resourceId,
|
|
1891
|
-
),
|
|
1892
|
-
claimed.envelope.operation,
|
|
1893
|
-
{
|
|
1894
|
-
machine: claimed.envelope.deviceId,
|
|
1895
|
-
mutationId: claimed.envelope.mutationId,
|
|
1896
|
-
version: 2,
|
|
1897
|
-
},
|
|
1898
|
-
);
|
|
1899
|
-
const bytes = Buffer.from(JSON.stringify(next));
|
|
1900
|
-
const snapshotInput = {
|
|
1901
|
-
resourceId: setupState.project.resourceId,
|
|
1902
|
-
contract: projectStream.STREAM_CONTRACT,
|
|
1903
|
-
schemaVersion: 2,
|
|
1904
|
-
authorityEpoch: 1,
|
|
1905
|
-
headVersion: 2,
|
|
1906
|
-
snapshotVersion: 2,
|
|
1907
|
-
snapshotChecksum: crypto.createHash('sha256').update(bytes).digest('hex'),
|
|
1908
|
-
snapshotBase64: bytes.toString('base64'),
|
|
1909
|
-
};
|
|
1910
|
-
projectStream.installSnapshot(setupState.project.file, snapshotInput);
|
|
1911
|
-
|
|
1912
|
-
const numbered = intent.readIntent(setupState.project);
|
|
1913
|
-
intent.writeIntent(setupState.project, {
|
|
1914
|
-
base: numbered.base,
|
|
1915
|
-
local: numbered.local,
|
|
1916
|
-
terminal: numbered.terminal,
|
|
1917
|
-
publication: { ...numbered.publication, foldedAt: null },
|
|
1918
|
-
});
|
|
1919
|
-
openLocalDb().prepare(`
|
|
1920
|
-
UPDATE mutation_journal
|
|
1921
|
-
SET official_projected_at = NULL
|
|
1922
|
-
WHERE mutation_id = ?
|
|
1923
|
-
`).run(published.mutationId);
|
|
1924
|
-
|
|
1925
|
-
const retried = projectStream.installSnapshot(
|
|
1926
|
-
setupState.project.file,
|
|
1927
|
-
snapshotInput,
|
|
1928
|
-
);
|
|
1929
|
-
assert.equal(retried.duplicate, true);
|
|
1930
|
-
assert.equal(
|
|
1931
|
-
typeof intent.readIntent(setupState.project).publication.foldedAt,
|
|
1932
|
-
'string',
|
|
1933
|
-
'equal snapshot retry restores the durable folded-intent fact',
|
|
1934
|
-
);
|
|
1935
|
-
assert.equal(
|
|
1936
|
-
typeof mutations.getMutation(
|
|
1937
|
-
`resource:${setupState.project.resourceId}`,
|
|
1938
|
-
published.mutationId,
|
|
1939
|
-
).officialProjectedAt,
|
|
1940
|
-
'string',
|
|
1941
|
-
'equal snapshot retry restores the generic official-fold fact',
|
|
1942
|
-
);
|
|
1943
|
-
|
|
1944
|
-
const plan = reducer.reduceWall(wall.readWall(setupState.project.repoKey))
|
|
1945
|
-
.get(setupState.project.repoKey);
|
|
1946
|
-
const applied = await reducer.materializeRepo(card, plan, 2);
|
|
1947
|
-
assert.equal(applied.state, 'in-sync', JSON.stringify(applied));
|
|
1948
|
-
assert.equal(intent.readIntent(setupState.project), null);
|
|
1949
|
-
assert.equal(
|
|
1950
|
-
reducer.readRepoState(
|
|
1951
|
-
setupState.project.repoKey,
|
|
1952
|
-
setupState.project.resourceId,
|
|
1953
|
-
).appliedThrough,
|
|
1954
|
-
2,
|
|
1955
|
-
);
|
|
1956
|
-
});
|
|
1957
|
-
|
|
1958
|
-
test('an unauthorized rejected attachment remains terminal', async () => {
|
|
1959
|
-
const repo = path.join(tempRoot, 'terminal-attachment');
|
|
1960
|
-
fs.mkdirSync(repo);
|
|
1961
|
-
const resourceId = '018f0000-0000-7000-8000-00000000c008';
|
|
1962
|
-
const project = wall.bindProject(
|
|
1963
|
-
resourceId,
|
|
1964
|
-
'22222222-2222-4222-8222-222222222222',
|
|
1965
|
-
repo,
|
|
1966
|
-
);
|
|
1967
|
-
const request = attachments.enqueueAttachment({
|
|
1968
|
-
attachmentId: 'att_terminal_code_project_01',
|
|
1969
|
-
resourceId,
|
|
1970
|
-
localPath: project.file,
|
|
1971
|
-
}).attachment;
|
|
1972
|
-
attachments.failAttachment(request.attachmentId, {
|
|
1973
|
-
message: 'shared_resource_read_forbidden',
|
|
1974
|
-
retryable: false,
|
|
1975
|
-
});
|
|
1976
|
-
await wall._test.ensureProject(project);
|
|
1977
|
-
assert.equal(attachments.getAttachmentByResource(resourceId).state, 'rejected');
|
|
1978
|
-
});
|
|
1979
|
-
|
|
1980
|
-
test('a gitlink is a typed local terminal and never enters the outbox', async () => {
|
|
1981
|
-
const setupState = await setup(
|
|
1982
|
-
'gitlink-terminal',
|
|
1983
|
-
'018f0000-0000-7000-8000-00000000c009',
|
|
1984
|
-
);
|
|
1985
|
-
const oid = setupState.card.head.oid;
|
|
1986
|
-
git(setupState.repo, 'update-index', '--add', '--cacheinfo', `160000,${oid},vendor`);
|
|
1987
|
-
const card = cardFor(setupState.project, setupState.repo);
|
|
1988
|
-
const captured = await checkpoint.captureLocalIntent(
|
|
1989
|
-
setupState.project,
|
|
1990
|
-
card,
|
|
1991
|
-
await checkpointFor(setupState.project, card),
|
|
1992
|
-
);
|
|
1993
|
-
assert.equal(captured.state, 'rejected');
|
|
1994
|
-
assert.equal(captured.intent.terminal.code, 'unsupported_gitlink');
|
|
1995
|
-
const publication = wall._test.publishProject(setupState.project, [card]);
|
|
1996
|
-
assert.equal(publication.published, false);
|
|
1997
|
-
assert.equal(publication.terminalCode, 'unsupported_gitlink');
|
|
1998
|
-
assert.equal(mutations.listPendingCloudMutations(setupState.project.resourceId).length, 0);
|
|
1999
|
-
});
|