amalgm 0.1.238 → 0.1.240
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/lib/shared-realtime-tunnel.js +22 -14
- package/package.json +1 -1
- package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +68 -20
- package/runtime/scripts/amalgm-mcp/project-context/store.js +87 -19
- package/runtime/scripts/amalgm-mcp/state/attachments.js +40 -12
- package/runtime/scripts/amalgm-mcp/state/db.js +216 -17
- package/runtime/scripts/amalgm-mcp/state/doc-disk.js +550 -0
- package/runtime/scripts/amalgm-mcp/state/docs.js +1101 -301
- package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +99 -0
- package/runtime/scripts/amalgm-mcp/state/mutations.js +507 -130
- package/runtime/scripts/amalgm-mcp/state/promotions.js +21 -1
- package/runtime/scripts/amalgm-mcp/state/replicas.js +134 -13
- package/runtime/scripts/amalgm-mcp/state/shared-rest.js +14 -8
- package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +193 -0
- package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +313 -94
- package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +1 -0
- package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +32 -17
- package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +84 -0
- package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +376 -13
- package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +309 -0
- package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +758 -30
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +72 -3
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +33 -6
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +27 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +334 -191
- package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +24 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +1999 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +364 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +81 -3
- package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +114 -8
- package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +185 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +174 -269
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +26 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +2060 -407
- package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +119 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +1446 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +5319 -3
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +191 -1
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +3140 -17
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +135 -637
- package/runtime/scripts/amalgm-mcp/workspace/access-store.js +5 -1
- package/runtime/scripts/amalgm-mcp/workspace/card.js +7 -7
- package/runtime/scripts/amalgm-mcp/workspace/cards.js +36 -12
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +436 -173
- package/runtime/scripts/amalgm-mcp/workspace/content.js +32 -0
- package/runtime/scripts/amalgm-mcp/workspace/durable.js +45 -0
- package/runtime/scripts/amalgm-mcp/workspace/git.js +391 -37
- package/runtime/scripts/amalgm-mcp/workspace/intent.js +535 -0
- package/runtime/scripts/amalgm-mcp/workspace/merge.js +89 -6
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +31 -0
- 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 +72 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +165 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +5 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +2 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +2201 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +19 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +18 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +18 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +33 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +47 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +43 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +114 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +84 -0
- package/runtime/scripts/amalgm-mcp/workspace/objects.js +119 -54
- package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +616 -168
- package/runtime/scripts/amalgm-mcp/workspace/provenance.js +240 -0
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +1979 -570
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +49 -28
- package/runtime/scripts/amalgm-mcp/workspace/store.js +77 -4
- package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +240 -0
- package/runtime/scripts/amalgm-mcp/workspace/transition.js +1729 -192
- package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +100 -0
- package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +7 -1
- package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +73 -61
- package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +127 -0
- package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +12 -0
- package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +51 -6
- package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +125 -0
- package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +79 -8
- package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +76 -0
- package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +3480 -165
- package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +3766 -0
- package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +899 -71
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +352 -497
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Local adapter between a checkout and its one canonical project stream.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* owner grants membership. Every authorized machine is then an ordinary
|
|
10
|
-
* writer in the same stream.
|
|
6
|
+
* repoKey is only a local lookup alias. resourceId is the sole cloud key.
|
|
7
|
+
* The cloud stores exactly one card and one checkpoint; this file derives
|
|
8
|
+
* one card.set or checkpoint.set proposal from the current checkout.
|
|
11
9
|
*/
|
|
12
10
|
|
|
13
11
|
const crypto = require('crypto');
|
|
@@ -15,21 +13,20 @@ const fs = require('fs');
|
|
|
15
13
|
const path = require('path');
|
|
16
14
|
const { AMALGM_DIR } = require('../config');
|
|
17
15
|
const { subscribeStateEvents } = require('../state/events');
|
|
16
|
+
const durable = require('./durable');
|
|
18
17
|
const { machineId, userId } = require('./identity');
|
|
19
18
|
const { listRepoCards } = require('./cards');
|
|
20
19
|
const {
|
|
21
20
|
CHECKPOINT_CONTRACT,
|
|
22
|
-
checkpointValue,
|
|
23
21
|
checkpointValueReady,
|
|
24
22
|
readCheckpoint,
|
|
25
23
|
repoKeyOfCard,
|
|
26
|
-
sameTransition,
|
|
27
24
|
stampCheckpointMutation,
|
|
28
25
|
} = require('./checkpoint');
|
|
29
26
|
const projectStream = require('./project-stream');
|
|
30
27
|
|
|
31
28
|
const { WALL_CONTRACT } = projectStream;
|
|
32
|
-
const PROJECT_DIR = path.join(AMALGM_DIR, 'state', 'code-projects');
|
|
29
|
+
const PROJECT_DIR = path.join(AMALGM_DIR, 'state', 'code-projects-v2');
|
|
33
30
|
const REGISTRY_FILE = path.join(PROJECT_DIR, 'projects.json');
|
|
34
31
|
const PUBLISH_QUIET_MS = 25;
|
|
35
32
|
const ENSURE_STEP_TIMEOUT_MS = 20_000;
|
|
@@ -57,16 +54,7 @@ function readRegistry() {
|
|
|
57
54
|
}
|
|
58
55
|
|
|
59
56
|
function writeRegistry(registry) {
|
|
60
|
-
|
|
61
|
-
fs.writeFileSync(REGISTRY_FILE, `${JSON.stringify(registry, null, 2)}\n`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function projectResourceId(repoKey, ownerUserId = userId()) {
|
|
65
|
-
if (!repoKey || !ownerUserId) return null;
|
|
66
|
-
const digest = crypto.createHash('sha256')
|
|
67
|
-
.update(`amalgm-code-project@2|${ownerUserId}|${repoKey}`)
|
|
68
|
-
.digest('base64url');
|
|
69
|
-
return `shr_cp1${digest.slice(0, 40)}`;
|
|
57
|
+
durable.writeJson(REGISTRY_FILE, registry, { pretty: true });
|
|
70
58
|
}
|
|
71
59
|
|
|
72
60
|
function projectFile(resourceId) {
|
|
@@ -74,66 +62,105 @@ function projectFile(resourceId) {
|
|
|
74
62
|
return path.join(PROJECT_DIR, `${digest}.json`);
|
|
75
63
|
}
|
|
76
64
|
|
|
65
|
+
function canonicalPath(localPath) {
|
|
66
|
+
const resolved = path.resolve(localPath);
|
|
67
|
+
const tail = [];
|
|
68
|
+
let existing = resolved;
|
|
69
|
+
while (!fs.existsSync(existing)) {
|
|
70
|
+
const parent = path.dirname(existing);
|
|
71
|
+
if (parent === existing) return resolved;
|
|
72
|
+
tail.unshift(path.basename(existing));
|
|
73
|
+
existing = parent;
|
|
74
|
+
}
|
|
75
|
+
return path.join(fs.realpathSync.native(existing), ...tail);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function projectRecord(bound) {
|
|
79
|
+
if (!bound?.resourceId) return null;
|
|
80
|
+
return {
|
|
81
|
+
repoKey: `code-folder:${bound.resourceId}`,
|
|
82
|
+
resourceId: bound.resourceId,
|
|
83
|
+
ownerUserId: bound.ownerUserId || null,
|
|
84
|
+
path: bound?.path || null,
|
|
85
|
+
file: projectFile(bound.resourceId),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function projects() {
|
|
90
|
+
return Object.values(readRegistry()).map(projectRecord).filter(Boolean);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function projectForPath(localPath) {
|
|
94
|
+
if (!localPath) return null;
|
|
95
|
+
const resolved = canonicalPath(localPath);
|
|
96
|
+
return projects().find((project) => (
|
|
97
|
+
project.path && canonicalPath(project.path) === resolved
|
|
98
|
+
)) || null;
|
|
99
|
+
}
|
|
100
|
+
|
|
77
101
|
function projectForRepo(repoKey) {
|
|
78
102
|
if (!repoKey) return null;
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
return
|
|
83
|
-
repoKey,
|
|
84
|
-
resourceId,
|
|
85
|
-
ownerUserId,
|
|
86
|
-
path: bound?.path || null,
|
|
87
|
-
file: projectFile(resourceId),
|
|
88
|
-
} : null;
|
|
103
|
+
const direct = projects().find((project) => project.repoKey === repoKey);
|
|
104
|
+
if (direct) return direct;
|
|
105
|
+
const card = listRepoCards().find((candidate) => repoKeyOfCard(candidate) === repoKey);
|
|
106
|
+
return projectForPath(card?.path);
|
|
89
107
|
}
|
|
90
108
|
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
109
|
+
function projectForResource(resourceId) {
|
|
110
|
+
return projects().find((project) => project.resourceId === resourceId) || null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function bindProject(resourceId, ownerUserId = null, localPath = null) {
|
|
114
|
+
const identity = require('./tree/identity');
|
|
115
|
+
if (!identity.isUuid(String(resourceId || ''))) {
|
|
116
|
+
throw new Error('resourceId must be the code-folder UUID');
|
|
117
|
+
}
|
|
118
|
+
if (!identity.isUuid(String(ownerUserId || ''))) {
|
|
119
|
+
throw new Error('ownerUserId must be a user UUID');
|
|
95
120
|
}
|
|
96
|
-
if (
|
|
121
|
+
if (typeof localPath !== 'string' || !path.isAbsolute(localPath)) {
|
|
97
122
|
throw new Error('path must be an absolute folder path');
|
|
98
123
|
}
|
|
99
124
|
const registry = readRegistry();
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
const key = resourceId.toLowerCase();
|
|
126
|
+
const owner = ownerUserId.toLowerCase();
|
|
127
|
+
const boundPath = canonicalPath(localPath);
|
|
128
|
+
const existing = registry[key];
|
|
129
|
+
if (existing) {
|
|
130
|
+
if (
|
|
131
|
+
existing.resourceId === key
|
|
132
|
+
&& existing.ownerUserId === owner
|
|
133
|
+
&& canonicalPath(existing.path) === boundPath
|
|
134
|
+
) return projectRecord(existing);
|
|
135
|
+
throw new Error('that code-folder UUID is already bound differently');
|
|
136
|
+
}
|
|
137
|
+
const pathOwner = Object.values(registry).find((bound) => (
|
|
138
|
+
bound?.path && canonicalPath(bound.path) === boundPath
|
|
139
|
+
));
|
|
140
|
+
if (pathOwner) throw new Error('that folder path is already bound to another code-folder UUID');
|
|
141
|
+
registry[key] = {
|
|
142
|
+
resourceId: key,
|
|
143
|
+
ownerUserId: owner,
|
|
144
|
+
path: boundPath,
|
|
105
145
|
joinedAt: new Date().toISOString(),
|
|
106
146
|
};
|
|
107
147
|
writeRegistry(registry);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
148
|
+
require('./store').upsertWorkspace({
|
|
149
|
+
path: boundPath,
|
|
150
|
+
name: path.basename(boundPath),
|
|
151
|
+
source: 'computer',
|
|
152
|
+
managed: false,
|
|
153
|
+
}, { source: 'workspace:code-project-bind' });
|
|
154
|
+
const project = projectRecord(registry[key]);
|
|
116
155
|
if (service.started) {
|
|
117
|
-
ensureProjectBound(
|
|
156
|
+
ensureProjectBound(project)
|
|
118
157
|
.then(() => {
|
|
119
158
|
schedulePublish();
|
|
120
159
|
return require('./reducer').reduceNow();
|
|
121
160
|
})
|
|
122
161
|
.catch(() => {});
|
|
123
162
|
}
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function projects() {
|
|
128
|
-
const byRepo = new Map();
|
|
129
|
-
for (const card of listRepoCards()) {
|
|
130
|
-
const repoKey = repoKeyOfCard(card);
|
|
131
|
-
if (repoKey && !byRepo.has(repoKey)) byRepo.set(repoKey, projectForRepo(repoKey));
|
|
132
|
-
}
|
|
133
|
-
for (const repoKey of Object.keys(readRegistry())) {
|
|
134
|
-
if (!byRepo.has(repoKey)) byRepo.set(repoKey, projectForRepo(repoKey));
|
|
135
|
-
}
|
|
136
|
-
return [...byRepo.values()].filter(Boolean);
|
|
163
|
+
return project;
|
|
137
164
|
}
|
|
138
165
|
|
|
139
166
|
function wallReplica(project) {
|
|
@@ -141,61 +168,111 @@ function wallReplica(project) {
|
|
|
141
168
|
return replica?.contract === projectStream.STREAM_CONTRACT ? replica : null;
|
|
142
169
|
}
|
|
143
170
|
|
|
171
|
+
function outboundState(rows) {
|
|
172
|
+
const terminal = rows.find((row) => row.state === 'rejected' || row.state === 'conflicted');
|
|
173
|
+
return {
|
|
174
|
+
pending: rows.length,
|
|
175
|
+
terminalError: terminal
|
|
176
|
+
? terminal.lastError || `code publication ${terminal.mutationId} was ${terminal.state}`
|
|
177
|
+
: null,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
144
181
|
function publicationStatus(repoKey, reduced = undefined) {
|
|
145
182
|
const project = projectForRepo(repoKey);
|
|
146
183
|
const replica = project ? wallReplica(project) : null;
|
|
147
184
|
const receivedThrough = replica?.appliedVersion ?? null;
|
|
185
|
+
if (project && Number.isInteger(receivedThrough)) {
|
|
186
|
+
require('../state/mutations').retireStaleCodeMutations(
|
|
187
|
+
project.resourceId,
|
|
188
|
+
receivedThrough,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
148
191
|
const baseline = reduced === undefined
|
|
149
192
|
? require('./reducer').readRepoState(repoKey, project?.resourceId)
|
|
150
193
|
: reduced;
|
|
151
194
|
const appliedThrough = Number.isInteger(baseline?.appliedThrough)
|
|
152
195
|
? baseline.appliedThrough
|
|
153
196
|
: receivedThrough === 0 && !baseline ? 0 : null;
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
197
|
+
const mutations = require('../state/mutations');
|
|
198
|
+
const pendingRows = project
|
|
199
|
+
? mutations.listPendingCloudMutations(project.resourceId)
|
|
200
|
+
: [];
|
|
201
|
+
const officialRows = project
|
|
202
|
+
? mutations.listUnappliedOfficialMutations(project.resourceId)
|
|
203
|
+
: [];
|
|
204
|
+
const outgoing = outboundState(pendingRows);
|
|
157
205
|
return {
|
|
158
206
|
receivedThrough,
|
|
159
207
|
appliedThrough,
|
|
160
|
-
|
|
208
|
+
...outgoing,
|
|
209
|
+
awaitingOfficial: officialRows.length,
|
|
161
210
|
current: receivedThrough !== null
|
|
162
211
|
&& appliedThrough === receivedThrough
|
|
163
|
-
&& pending === 0
|
|
212
|
+
&& outgoing.pending === 0
|
|
213
|
+
&& officialRows.length === 0,
|
|
164
214
|
};
|
|
165
215
|
}
|
|
166
216
|
|
|
167
|
-
function
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
?
|
|
171
|
-
:
|
|
217
|
+
function localRepo(card, repoKey) {
|
|
218
|
+
return card?.repo || (
|
|
219
|
+
repoKey.startsWith('root:')
|
|
220
|
+
? { remote: null, rootCommit: repoKey.slice(5) }
|
|
221
|
+
: { remote: repoKey, rootCommit: null }
|
|
172
222
|
);
|
|
173
|
-
return {
|
|
174
|
-
...card,
|
|
175
|
-
positionVersion: validVersion(card.positionVersion)
|
|
176
|
-
? card.positionVersion
|
|
177
|
-
: versionFor(card.positionMutationId),
|
|
178
|
-
branches: (card.branches || []).map((ref) => ({
|
|
179
|
-
...ref,
|
|
180
|
-
version: validVersion(ref.version)
|
|
181
|
-
? ref.version
|
|
182
|
-
: versionFor(ref.mutationId),
|
|
183
|
-
})),
|
|
184
|
-
checkpoints: (card.checkpoints || []).map((checkpoint) => ({
|
|
185
|
-
...checkpoint,
|
|
186
|
-
version: validVersion(checkpoint.version)
|
|
187
|
-
? checkpoint.version
|
|
188
|
-
: versionFor(checkpoint.mutationId),
|
|
189
|
-
})),
|
|
190
|
-
};
|
|
191
223
|
}
|
|
192
224
|
|
|
225
|
+
/**
|
|
226
|
+
* The reducer still consumes its local card-shaped projection. This adapter
|
|
227
|
+
* is not shared state: it injects the local repo alias and derives one exact
|
|
228
|
+
* clean→current checkpoint transition from the canonical two values.
|
|
229
|
+
*/
|
|
193
230
|
function readProjectWall(project) {
|
|
194
|
-
|
|
195
|
-
|
|
231
|
+
const snapshot = projectStream.readSnapshot(project.file, project.resourceId);
|
|
232
|
+
if (!snapshot.card || !snapshot.checkpoint) return [];
|
|
233
|
+
const card = snapshot.card;
|
|
234
|
+
const checkpoint = snapshot.checkpoint;
|
|
235
|
+
return [{
|
|
236
|
+
wall: WALL_CONTRACT,
|
|
237
|
+
project: project.resourceId,
|
|
196
238
|
resourceId: project.resourceId,
|
|
197
|
-
|
|
198
|
-
|
|
239
|
+
machine: card.machine,
|
|
240
|
+
mutationId: card.mutationId,
|
|
241
|
+
version: snapshot.version,
|
|
242
|
+
cards: [{
|
|
243
|
+
card: projectStream.CARD_CONTRACT,
|
|
244
|
+
machine: card.machine,
|
|
245
|
+
replica: `official:${project.resourceId}`,
|
|
246
|
+
repo: localRepo(null, project.repoKey),
|
|
247
|
+
kind: 'primary',
|
|
248
|
+
health: 'healthy',
|
|
249
|
+
head: card.head,
|
|
250
|
+
objects: card.objects,
|
|
251
|
+
positionMachine: card.machine,
|
|
252
|
+
positionMutationId: card.mutationId,
|
|
253
|
+
positionVersion: card.version,
|
|
254
|
+
branches: card.branches.map((branch) => ({
|
|
255
|
+
...branch,
|
|
256
|
+
machine: card.machine,
|
|
257
|
+
mutationId: card.mutationId,
|
|
258
|
+
version: card.version,
|
|
259
|
+
})),
|
|
260
|
+
checkpoints: [{
|
|
261
|
+
checkpoint: CHECKPOINT_CONTRACT,
|
|
262
|
+
machine: checkpoint.machine,
|
|
263
|
+
baseOid: checkpoint.baseOid,
|
|
264
|
+
branch: checkpoint.branch,
|
|
265
|
+
from: {
|
|
266
|
+
revision: null,
|
|
267
|
+
indexDiff: '',
|
|
268
|
+
worktreeDiff: '',
|
|
269
|
+
},
|
|
270
|
+
to: checkpoint.value,
|
|
271
|
+
mutationId: checkpoint.mutationId,
|
|
272
|
+
version: checkpoint.version,
|
|
273
|
+
}],
|
|
274
|
+
}],
|
|
275
|
+
}];
|
|
199
276
|
}
|
|
200
277
|
|
|
201
278
|
function readWall(repoKey = null) {
|
|
@@ -203,356 +280,120 @@ function readWall(repoKey = null) {
|
|
|
203
280
|
return selected.flatMap((project) => readProjectWall(project));
|
|
204
281
|
}
|
|
205
282
|
|
|
206
|
-
function publishedCardFor(card) {
|
|
207
|
-
const repoKey = repoKeyOfCard(card);
|
|
208
|
-
if (!repoKey) return null;
|
|
209
|
-
const own = readWall(repoKey).find((entry) => entry.machine === machineId());
|
|
210
|
-
const published = own?.cards?.find((candidate) => candidate.replica === card.replica);
|
|
211
|
-
if (
|
|
212
|
-
!published
|
|
213
|
-
|| !Number.isInteger(published.positionVersion)
|
|
214
|
-
|| published.positionVersion < 1
|
|
215
|
-
|| published.head?.branch !== card.head?.branch
|
|
216
|
-
|| published.head?.oid !== card.head?.oid
|
|
217
|
-
|| Boolean(published.head?.detached) !== Boolean(card.head?.detached)
|
|
218
|
-
) return null;
|
|
219
|
-
return published;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
283
|
function samePosition(left, right) {
|
|
223
284
|
return left?.head?.branch === right?.head?.branch
|
|
224
285
|
&& left?.head?.oid === right?.head?.oid
|
|
225
286
|
&& Boolean(left?.head?.detached) === Boolean(right?.head?.detached);
|
|
226
287
|
}
|
|
227
288
|
|
|
228
|
-
function
|
|
229
|
-
return
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Publishing is a transition from the last shared state this checkout
|
|
234
|
-
* materialized. A local value equal to either that baseline or the current
|
|
235
|
-
* rail is not new work. Anything else is a local contribution.
|
|
236
|
-
*/
|
|
237
|
-
function hasLocalContribution(card, plan, baseline, checkpoint = null, previousCard = null) {
|
|
238
|
-
if (!plan?.card) return true;
|
|
239
|
-
const onRail = samePosition(card, plan.card);
|
|
240
|
-
const atBaseline = baseline
|
|
241
|
-
&& card.head?.branch === baseline.branch
|
|
242
|
-
&& card.head?.oid === baseline.baseOid
|
|
243
|
-
&& !baseline.pendingCard;
|
|
244
|
-
const alreadyPublished = validVersion(previousCard?.positionVersion)
|
|
245
|
-
&& samePosition(card, previousCard);
|
|
246
|
-
const reversesPublishedSharedPosition = previousCard
|
|
247
|
-
&& validVersion(previousCard.positionVersion)
|
|
248
|
-
&& samePosition(previousCard, plan.card)
|
|
249
|
-
&& !alreadyPublished;
|
|
250
|
-
if ((!onRail && !atBaseline && !alreadyPublished) || reversesPublishedSharedPosition) return true;
|
|
251
|
-
|
|
252
|
-
const currentRefs = oidMap(card.refs);
|
|
253
|
-
const railRefs = oidMap(plan.branches);
|
|
254
|
-
const baselineRefs = oidMap(baseline?.branches);
|
|
255
|
-
const previousRefs = new Map(
|
|
256
|
-
(previousCard?.branches || previousCard?.refs || []).map((ref) => [ref.name, ref]),
|
|
257
|
-
);
|
|
258
|
-
for (const name of new Set([
|
|
259
|
-
...currentRefs.keys(),
|
|
260
|
-
...baselineRefs.keys(),
|
|
261
|
-
...previousRefs.keys(),
|
|
262
|
-
])) {
|
|
263
|
-
const current = currentRefs.get(name) ?? null;
|
|
264
|
-
const shared = railRefs.get(name) ?? null;
|
|
265
|
-
const previous = baselineRefs.get(name) ?? null;
|
|
266
|
-
const publishedRef = previousRefs.get(name);
|
|
267
|
-
const published = publishedRef?.oid ?? null;
|
|
268
|
-
const matchesBaseline = baselineRefs.has(name) && current === previous;
|
|
269
|
-
const matchesPublished = validVersion(publishedRef?.version) && current === published;
|
|
270
|
-
const reversesPublishedSharedValue = validVersion(publishedRef?.version)
|
|
271
|
-
&& published === shared
|
|
272
|
-
&& current !== published;
|
|
273
|
-
if (
|
|
274
|
-
current !== shared
|
|
275
|
-
&& ((!matchesBaseline && !matchesPublished) || reversesPublishedSharedValue)
|
|
276
|
-
) return true;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
onRail
|
|
281
|
-
&& checkpoint
|
|
282
|
-
&& checkpoint.baseOid === card.head?.oid
|
|
283
|
-
&& checkpoint.branch === card.head?.branch
|
|
284
|
-
) {
|
|
285
|
-
const latest = [...(plan.transitions || [])]
|
|
286
|
-
.reverse()
|
|
287
|
-
.find((entry) => entry.machine === checkpoint.machine);
|
|
288
|
-
if (!sameTransition(latest, checkpoint)) return true;
|
|
289
|
-
}
|
|
290
|
-
return false;
|
|
289
|
+
function branchIdentity(refs) {
|
|
290
|
+
return JSON.stringify((refs || [])
|
|
291
|
+
.map((ref) => ({ name: ref.name, oid: ref.oid }))
|
|
292
|
+
.sort((left, right) => left.name.localeCompare(right.name)));
|
|
291
293
|
}
|
|
292
294
|
|
|
293
|
-
function
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const shared = new Map((plan.branches || []).map((ref) => [ref.name, ref]));
|
|
298
|
-
return (card.refs || []).some((ref) => {
|
|
299
|
-
const accepted = shared.get(ref.name);
|
|
300
|
-
const alreadyAvailable = accepted
|
|
301
|
-
&& validVersion(accepted.version)
|
|
302
|
-
&& (accepted.oid ?? null) === (ref.oid ?? null);
|
|
303
|
-
return !alreadyAvailable && ref.objects?.kind === 'pending';
|
|
304
|
-
});
|
|
295
|
+
function sameCardState(localCard, officialCard) {
|
|
296
|
+
return samePosition(localCard, officialCard)
|
|
297
|
+
&& branchIdentity(localCard?.refs || localCard?.branches)
|
|
298
|
+
=== branchIdentity(officialCard?.refs || officialCard?.branches);
|
|
305
299
|
}
|
|
306
300
|
|
|
307
|
-
function
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|| !Number.isInteger(baseline.appliedThrough)
|
|
313
|
-
|| baseline.appliedThrough < 0
|
|
314
|
-
) return null;
|
|
315
|
-
const value = checkpointValue(
|
|
316
|
-
machineId(),
|
|
317
|
-
project.repoKey,
|
|
318
|
-
card.head.oid,
|
|
319
|
-
baseline.indexDiff || '',
|
|
320
|
-
baseline.worktreeDiff || '',
|
|
321
|
-
);
|
|
322
|
-
if (!checkpointValueReady(value)) return null;
|
|
323
|
-
const reusableThrough = shared
|
|
324
|
-
&& shared.baseOid === card.head.oid
|
|
325
|
-
&& shared.branch === card.head.branch
|
|
326
|
-
&& shared.value?.revision === value.revision
|
|
327
|
-
&& Number.isInteger(shared.through)
|
|
328
|
-
&& shared.through >= 0;
|
|
329
|
-
return {
|
|
330
|
-
checkpoint: CHECKPOINT_CONTRACT,
|
|
331
|
-
baseOid: card.head.oid,
|
|
332
|
-
branch: card.head.branch,
|
|
333
|
-
through: reusableThrough ? shared.through : baseline.appliedThrough,
|
|
334
|
-
value,
|
|
335
|
-
};
|
|
301
|
+
function publishedCardFor(card) {
|
|
302
|
+
const project = projectForPath(card?.path);
|
|
303
|
+
if (!project) return null;
|
|
304
|
+
const projected = readProjectWall(project)[0]?.cards?.[0] || null;
|
|
305
|
+
return projected && sameCardState(card, projected) ? projected : null;
|
|
336
306
|
}
|
|
337
307
|
|
|
338
|
-
function
|
|
339
|
-
return
|
|
308
|
+
function isCommittedCard(card) {
|
|
309
|
+
return typeof card?.head?.oid === 'string'
|
|
310
|
+
&& /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(card.head.oid);
|
|
340
311
|
}
|
|
341
312
|
|
|
342
|
-
function
|
|
343
|
-
if (
|
|
344
|
-
return
|
|
313
|
+
function cardPayloadsReady(card) {
|
|
314
|
+
if (card.head?.oid && card.objects?.kind !== 'bundle') return false;
|
|
315
|
+
return (card.refs || card.branches || []).every((ref) => (
|
|
316
|
+
!ref.oid || ref.objects?.kind === 'bundle'
|
|
317
|
+
));
|
|
345
318
|
}
|
|
346
319
|
|
|
347
|
-
function
|
|
348
|
-
|
|
349
|
-
const mutation = require('../state/mutations').getMutation(`resource:${resourceId}`, mutationId);
|
|
350
|
-
return Number.isInteger(mutation?.officialCloudVersion) ? mutation.officialCloudVersion : null;
|
|
320
|
+
function hasPendingContributionObjects(card) {
|
|
321
|
+
return !cardPayloadsReady(card);
|
|
351
322
|
}
|
|
352
323
|
|
|
353
|
-
function
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (!resourceId) return null;
|
|
373
|
-
return officialVersion(resourceId, mutationId)
|
|
374
|
-
|| durableMutationVersion(repoKey ? projectForRepo(repoKey) : null, mutationId);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function sealedVersion(resourceId, mutationId, ...versions) {
|
|
378
|
-
return versions.find(validVersion)
|
|
379
|
-
|| officialVersion(resourceId, mutationId)
|
|
380
|
-
|| null;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function stampBranches(
|
|
384
|
-
refs,
|
|
385
|
-
previousBranches,
|
|
386
|
-
sharedBranches,
|
|
387
|
-
baselineBranches,
|
|
388
|
-
mutationId,
|
|
389
|
-
resourceId,
|
|
390
|
-
) {
|
|
391
|
-
const current = new Map((refs || []).map((ref) => [ref.name, ref]));
|
|
392
|
-
const previous = new Map((previousBranches || []).map((ref) => [ref.name, ref]));
|
|
393
|
-
const shared = new Map((sharedBranches || []).map((ref) => [ref.name, ref]));
|
|
394
|
-
const baseline = new Map((baselineBranches || []).map((ref) => [ref.name, ref]));
|
|
395
|
-
const names = [...new Set([...current.keys(), ...previous.keys()])].sort();
|
|
396
|
-
return names.map((name) => {
|
|
397
|
-
const observed = current.get(name);
|
|
398
|
-
const before = previous.get(name);
|
|
399
|
-
const onRail = shared.get(name);
|
|
400
|
-
const materialized = baseline.get(name);
|
|
401
|
-
const oid = observed?.oid ?? null;
|
|
402
|
-
const unchanged = before && (before.oid ?? null) === oid;
|
|
403
|
-
const previousReusable = unchanged
|
|
404
|
-
&& before.mutationId
|
|
405
|
-
&& (validVersion(before.version) || knownMutation(resourceId, before.mutationId));
|
|
406
|
-
const railReusable = onRail
|
|
407
|
-
&& (onRail.oid ?? null) === oid
|
|
408
|
-
&& onRail.mutationId
|
|
409
|
-
&& validVersion(onRail.version);
|
|
410
|
-
const baselineReusable = materialized
|
|
411
|
-
&& (materialized.oid ?? null) === oid
|
|
412
|
-
&& materialized.mutationId
|
|
413
|
-
&& validVersion(materialized.version);
|
|
414
|
-
const mutation = previousReusable
|
|
415
|
-
? before.mutationId
|
|
416
|
-
: railReusable
|
|
417
|
-
? onRail.mutationId
|
|
418
|
-
: baselineReusable ? materialized.mutationId : mutationId;
|
|
419
|
-
return {
|
|
420
|
-
name,
|
|
421
|
-
oid,
|
|
422
|
-
...(observed?.objects ? { objects: observed.objects } : {}),
|
|
423
|
-
mutationId: mutation,
|
|
424
|
-
version: sealedVersion(
|
|
425
|
-
resourceId,
|
|
426
|
-
mutation,
|
|
427
|
-
mutation === before?.mutationId ? before.version : null,
|
|
428
|
-
mutation === onRail?.mutationId ? onRail.version : null,
|
|
429
|
-
mutation === materialized?.mutationId ? materialized.version : null,
|
|
430
|
-
),
|
|
431
|
-
};
|
|
432
|
-
});
|
|
324
|
+
function checkpointForCard(card, repoKey) {
|
|
325
|
+
const checkpoint = readCheckpoint(machineId(), repoKey);
|
|
326
|
+
if (
|
|
327
|
+
checkpoint
|
|
328
|
+
&& checkpoint.checkpoint === CHECKPOINT_CONTRACT
|
|
329
|
+
&& checkpoint.baseOid === card.head?.oid
|
|
330
|
+
&& checkpoint.branch === card.head?.branch
|
|
331
|
+
&& checkpointValueReady(checkpoint.to)
|
|
332
|
+
) return checkpoint;
|
|
333
|
+
const dirty = card.dirty || {};
|
|
334
|
+
const clean = ['staged', 'unstaged', 'untracked', 'conflicted', 'submodulesChanged']
|
|
335
|
+
.every((key) => Number(dirty[key] || 0) === 0);
|
|
336
|
+
return clean ? {
|
|
337
|
+
checkpoint: CHECKPOINT_CONTRACT,
|
|
338
|
+
baseOid: card.head.oid,
|
|
339
|
+
branch: card.head.branch,
|
|
340
|
+
from: { revision: null, indexDiff: '', worktreeDiff: '' },
|
|
341
|
+
to: { revision: null, indexDiff: '', worktreeDiff: '' },
|
|
342
|
+
} : null;
|
|
433
343
|
}
|
|
434
344
|
|
|
435
|
-
function
|
|
345
|
+
function cardSet(card, checkpoint) {
|
|
436
346
|
return {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
replica: card.replica,
|
|
440
|
-
repo: card.repo,
|
|
441
|
-
objects: card.objects,
|
|
442
|
-
kind: card.kind,
|
|
347
|
+
kind: 'card.set',
|
|
348
|
+
card: projectStream.CARD_CONTRACT,
|
|
443
349
|
head: {
|
|
444
|
-
branch: card.head
|
|
445
|
-
oid: card.head
|
|
446
|
-
detached: Boolean(card.head
|
|
350
|
+
branch: card.head.branch ?? null,
|
|
351
|
+
oid: card.head.oid,
|
|
352
|
+
detached: Boolean(card.head.detached),
|
|
353
|
+
},
|
|
354
|
+
objects: card.objects,
|
|
355
|
+
branches: (card.refs || card.branches || []).map((ref) => ({
|
|
356
|
+
name: ref.name,
|
|
357
|
+
oid: ref.oid,
|
|
358
|
+
objects: ref.objects,
|
|
359
|
+
})),
|
|
360
|
+
checkpoint: {
|
|
361
|
+
checkpoint: CHECKPOINT_CONTRACT,
|
|
362
|
+
baseOid: card.head.oid,
|
|
363
|
+
branch: card.head.branch ?? null,
|
|
364
|
+
value: checkpoint.to,
|
|
447
365
|
},
|
|
448
|
-
health: card.health,
|
|
449
366
|
};
|
|
450
367
|
}
|
|
451
368
|
|
|
452
|
-
function
|
|
453
|
-
return
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const previous = previousByReplica.get(card.replica);
|
|
462
|
-
const reusablePosition = samePosition(previous, card)
|
|
463
|
-
&& previous?.positionMutationId
|
|
464
|
-
&& (
|
|
465
|
-
validVersion(previous.positionVersion)
|
|
466
|
-
|| knownMutation(project.resourceId, previous.positionMutationId)
|
|
467
|
-
);
|
|
468
|
-
const reusableRailPosition = samePosition(sharedPlan?.card, card)
|
|
469
|
-
&& sharedPlan.card.positionMutationId
|
|
470
|
-
&& validVersion(sharedPlan.card.positionVersion);
|
|
471
|
-
const reusableBaselinePosition = baseline
|
|
472
|
-
&& card.head?.branch === baseline.branch
|
|
473
|
-
&& card.head?.oid === baseline.baseOid
|
|
474
|
-
&& baseline.cardMutationId
|
|
475
|
-
&& validVersion(baseline.cardVersion);
|
|
476
|
-
const positionMutationId = reusablePosition
|
|
477
|
-
? previous.positionMutationId
|
|
478
|
-
: reusableRailPosition
|
|
479
|
-
? sharedPlan.card.positionMutationId
|
|
480
|
-
: reusableBaselinePosition ? baseline.cardMutationId : mutationId;
|
|
481
|
-
const positionVersion = sealedVersion(
|
|
482
|
-
project.resourceId,
|
|
483
|
-
positionMutationId,
|
|
484
|
-
positionMutationId === previous?.positionMutationId ? previous.positionVersion : null,
|
|
485
|
-
positionMutationId === sharedPlan?.card?.positionMutationId
|
|
486
|
-
? sharedPlan.card.positionVersion
|
|
487
|
-
: null,
|
|
488
|
-
positionMutationId === baseline?.cardMutationId ? baseline.cardVersion : null,
|
|
489
|
-
);
|
|
490
|
-
const checkpoint = readCheckpoint(machineId(), project.repoKey);
|
|
491
|
-
const currentCheckpoint = checkpoint
|
|
492
|
-
&& checkpoint.baseOid === card.head?.oid
|
|
493
|
-
&& checkpoint.branch === card.head?.branch
|
|
494
|
-
? checkpoint
|
|
495
|
-
: null;
|
|
496
|
-
const checkpointBase = checkpointBaseFor(
|
|
497
|
-
card,
|
|
498
|
-
baseline,
|
|
499
|
-
project,
|
|
500
|
-
sharedPlan?.card?.checkpointBase,
|
|
501
|
-
);
|
|
502
|
-
const sameBase = previous?.head?.oid === card.head?.oid
|
|
503
|
-
&& previous?.head?.branch === card.head?.branch;
|
|
504
|
-
const history = sameBase && Array.isArray(previous?.checkpoints)
|
|
505
|
-
? previous.checkpoints.map((entry) => ({
|
|
506
|
-
...entry,
|
|
507
|
-
version: sealedVersion(
|
|
508
|
-
project.resourceId,
|
|
509
|
-
entry.mutationId,
|
|
510
|
-
entry.version,
|
|
511
|
-
),
|
|
512
|
-
})).filter((entry) => (
|
|
513
|
-
validVersion(entry.version)
|
|
514
|
-
&& (!checkpointBase || entry.version > checkpointBase.through)
|
|
515
|
-
))
|
|
516
|
-
: [];
|
|
517
|
-
const last = history.at(-1);
|
|
518
|
-
const checkpoints = currentCheckpoint
|
|
519
|
-
? (sameTransition(last, currentCheckpoint)
|
|
520
|
-
? history
|
|
521
|
-
: [...history, { ...currentCheckpoint, mutationId }])
|
|
522
|
-
: history;
|
|
523
|
-
return {
|
|
524
|
-
...sharedCard(card),
|
|
525
|
-
positionMutationId,
|
|
526
|
-
positionVersion,
|
|
527
|
-
branches: stampBranches(
|
|
528
|
-
card.refs,
|
|
529
|
-
previous?.branches,
|
|
530
|
-
sharedPlan?.branches,
|
|
531
|
-
baseline?.branches,
|
|
532
|
-
mutationId,
|
|
533
|
-
project.resourceId,
|
|
534
|
-
),
|
|
535
|
-
...(checkpointBase ? { checkpointBase } : {}),
|
|
536
|
-
...(checkpoints.length ? { checkpoints } : {}),
|
|
537
|
-
};
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function cardsUseMutation(cards, mutationId) {
|
|
542
|
-
return cards.some((card) => (
|
|
543
|
-
card.positionMutationId === mutationId
|
|
544
|
-
|| (card.branches || []).some((ref) => ref.mutationId === mutationId)
|
|
545
|
-
|| (card.checkpoints || []).some((checkpoint) => checkpoint.mutationId === mutationId)
|
|
546
|
-
));
|
|
369
|
+
function checkpointSet(card, checkpoint, officialCheckpoint) {
|
|
370
|
+
return {
|
|
371
|
+
kind: 'checkpoint.set',
|
|
372
|
+
checkpoint: CHECKPOINT_CONTRACT,
|
|
373
|
+
baseOid: card.head.oid,
|
|
374
|
+
branch: card.head.branch ?? null,
|
|
375
|
+
from: officialCheckpoint.value,
|
|
376
|
+
to: checkpoint.to,
|
|
377
|
+
};
|
|
547
378
|
}
|
|
548
379
|
|
|
549
|
-
function
|
|
550
|
-
|
|
551
|
-
const stripTransport = ({ publishedAt, mutationId, version, ...record }) => record;
|
|
552
|
-
return JSON.stringify(stripTransport(left)) === JSON.stringify(stripTransport(right));
|
|
380
|
+
function hasLocalContribution(card, plan) {
|
|
381
|
+
return !plan?.card || !sameCardState(card, plan.card);
|
|
553
382
|
}
|
|
554
383
|
|
|
555
384
|
function publishProject(project, rawCards) {
|
|
385
|
+
const intents = require('./intent');
|
|
386
|
+
const intent = intents.readOwnedIntent(project);
|
|
387
|
+
if (intent?.terminal) {
|
|
388
|
+
service.counters.publishSkips += 1;
|
|
389
|
+
return {
|
|
390
|
+
published: false,
|
|
391
|
+
reason: 'local-intent-rejected',
|
|
392
|
+
terminalError: intent.terminal.message,
|
|
393
|
+
terminalCode: intent.terminal.code,
|
|
394
|
+
repoKey: project.repoKey,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
556
397
|
const replica = wallReplica(project);
|
|
557
398
|
if (!replica) {
|
|
558
399
|
service.counters.publishSkips += 1;
|
|
@@ -562,75 +403,73 @@ function publishProject(project, rawCards) {
|
|
|
562
403
|
service.counters.publishSkips += 1;
|
|
563
404
|
return { published: false, reason: 'repo-materializing', repoKey: project.repoKey };
|
|
564
405
|
}
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const reducer = require('./reducer');
|
|
569
|
-
const sharedPlan = reducer.reduceWall(readProjectWall(project)).get(project.repoKey);
|
|
570
|
-
const baseline = reducer.readRepoState(project.repoKey, project.resourceId);
|
|
571
|
-
const previousCards = (previousEntry?.cards || [])
|
|
572
|
-
.map((card) => sealCardVersions(project, card, previousEntry));
|
|
573
|
-
const previousReplicas = new Set(previousCards.map((card) => card.replica));
|
|
574
|
-
const currentReplicas = new Set(rawCards.map((card) => card.replica));
|
|
575
|
-
const removed = [...previousReplicas].some((replica) => !currentReplicas.has(replica));
|
|
576
|
-
const contributes = rawCards.some((card) => {
|
|
577
|
-
const checkpoint = readCheckpoint(machineId(), project.repoKey);
|
|
578
|
-
const previousCard = previousCards.find((entry) => entry.replica === card.replica);
|
|
579
|
-
return hasLocalContribution(card, sharedPlan, baseline, checkpoint, previousCard);
|
|
580
|
-
});
|
|
581
|
-
if (contributes && rawCards.some((card) => hasPendingContributionObjects(card, sharedPlan))) {
|
|
406
|
+
const status = publicationStatus(project.repoKey);
|
|
407
|
+
if (!status.current) {
|
|
582
408
|
service.counters.publishSkips += 1;
|
|
583
|
-
return {
|
|
409
|
+
return {
|
|
410
|
+
published: false,
|
|
411
|
+
reason: status.terminalError ? 'publication-rejected' : 'project-not-current',
|
|
412
|
+
mayMaterialize: true,
|
|
413
|
+
repoKey: project.repoKey,
|
|
414
|
+
...status,
|
|
415
|
+
};
|
|
584
416
|
}
|
|
585
|
-
if (!
|
|
417
|
+
if (!intent) {
|
|
586
418
|
service.counters.publishSkips += 1;
|
|
587
|
-
return {
|
|
419
|
+
return {
|
|
420
|
+
published: false,
|
|
421
|
+
reason: 'shared-state-covered',
|
|
422
|
+
mayMaterialize: true,
|
|
423
|
+
repoKey: project.repoKey,
|
|
424
|
+
};
|
|
588
425
|
}
|
|
589
|
-
|
|
590
|
-
const cards = stampCards(
|
|
591
|
-
rawCards,
|
|
592
|
-
previousCards,
|
|
593
|
-
sharedPlan,
|
|
594
|
-
baseline,
|
|
595
|
-
mutationId,
|
|
596
|
-
project,
|
|
597
|
-
);
|
|
598
|
-
const mine = {
|
|
599
|
-
wall: WALL_CONTRACT,
|
|
600
|
-
project: project.repoKey,
|
|
601
|
-
machine: machineId(),
|
|
602
|
-
mutationId,
|
|
603
|
-
publishedAt: new Date().toISOString(),
|
|
604
|
-
cards,
|
|
605
|
-
};
|
|
606
|
-
if (samePublishedRecord(previousEntry, mine)) {
|
|
426
|
+
if (intent.base.through !== status.appliedThrough) {
|
|
607
427
|
service.counters.publishSkips += 1;
|
|
608
|
-
return {
|
|
428
|
+
return {
|
|
429
|
+
published: false,
|
|
430
|
+
reason: 'local-intent-refreshing',
|
|
431
|
+
mayMaterialize: true,
|
|
432
|
+
repoKey: project.repoKey,
|
|
433
|
+
};
|
|
609
434
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
435
|
+
const card = intent.local.card;
|
|
436
|
+
const checkpoint = {
|
|
437
|
+
checkpoint: CHECKPOINT_CONTRACT,
|
|
438
|
+
baseOid: intent.local.checkpoint.baseOid,
|
|
439
|
+
branch: intent.local.checkpoint.branch,
|
|
440
|
+
from: null,
|
|
441
|
+
to: intent.local.checkpoint.value,
|
|
442
|
+
};
|
|
443
|
+
const snapshot = projectStream.readSnapshot(project.file, project.resourceId);
|
|
444
|
+
let delta = null;
|
|
445
|
+
if (!snapshot.card || !sameCardState(card, snapshot.card)) {
|
|
446
|
+
if (!cardPayloadsReady(card)) {
|
|
447
|
+
service.counters.publishSkips += 1;
|
|
448
|
+
return { published: false, reason: 'objects-preparing', repoKey: project.repoKey };
|
|
449
|
+
}
|
|
450
|
+
delta = cardSet(card, checkpoint);
|
|
451
|
+
} else if (checkpoint.to.revision !== snapshot.checkpoint?.value?.revision) {
|
|
452
|
+
delta = checkpointSet(card, checkpoint, snapshot.checkpoint);
|
|
613
453
|
}
|
|
614
|
-
|
|
615
|
-
|
|
454
|
+
if (!delta) {
|
|
455
|
+
delta = checkpointSet(card, checkpoint, snapshot.checkpoint);
|
|
456
|
+
}
|
|
457
|
+
const reserved = intents.reservePublication(project, intent);
|
|
458
|
+
if (!reserved?.publication?.mutationId) {
|
|
616
459
|
service.counters.publishSkips += 1;
|
|
617
460
|
return {
|
|
618
461
|
published: false,
|
|
619
|
-
reason: '
|
|
462
|
+
reason: 'local-intent-refreshing',
|
|
463
|
+
mayMaterialize: true,
|
|
620
464
|
repoKey: project.repoKey,
|
|
621
|
-
...status,
|
|
622
465
|
};
|
|
623
466
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
stampCheckpointMutation(machineId(), project.repoKey, checkpoint, mutationId);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
const result = projectStream.publish(project, mine);
|
|
467
|
+
const result = projectStream.publish(
|
|
468
|
+
project,
|
|
469
|
+
delta,
|
|
470
|
+
reserved.publication.mutationId,
|
|
471
|
+
);
|
|
472
|
+
stampCheckpointMutation(machineId(), project.repoKey, checkpoint, result.mutation.mutationId);
|
|
634
473
|
service.counters.publishes += 1;
|
|
635
474
|
service.lastPublishedAt = new Date().toISOString();
|
|
636
475
|
return {
|
|
@@ -638,26 +477,36 @@ function publishProject(project, rawCards) {
|
|
|
638
477
|
repoKey: project.repoKey,
|
|
639
478
|
resourceId: project.resourceId,
|
|
640
479
|
mutationId: result.mutation.mutationId,
|
|
641
|
-
|
|
480
|
+
delta: delta.kind,
|
|
642
481
|
};
|
|
643
482
|
}
|
|
644
483
|
|
|
645
484
|
function publishSelected(selected, allCards, publisher = publishProject) {
|
|
646
485
|
return selected.map((project) => {
|
|
647
486
|
try {
|
|
648
|
-
|
|
649
|
-
project
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
);
|
|
487
|
+
const cards = allCards.filter((card) => (
|
|
488
|
+
projectForPath(card.path)?.resourceId === project.resourceId
|
|
489
|
+
&& isCommittedCard(card)
|
|
490
|
+
&& card.kind === 'primary'
|
|
491
|
+
&& card.health === 'healthy'
|
|
492
|
+
&& card.operation === 'none'
|
|
493
|
+
));
|
|
494
|
+
if (cards.length === 0) {
|
|
495
|
+
service.counters.publishSkips += 1;
|
|
496
|
+
return {
|
|
497
|
+
published: false,
|
|
498
|
+
reason: 'local-checkout-unavailable',
|
|
499
|
+
repoKey: project.repoKey,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
return publisher(project, cards);
|
|
655
503
|
} catch (error) {
|
|
656
504
|
service.counters.publishFailures += 1;
|
|
657
505
|
console.warn(`[CodeProject] ${project.repoKey} publish failed:`, error.message);
|
|
658
506
|
return {
|
|
659
507
|
published: false,
|
|
660
508
|
reason: 'publish-failed',
|
|
509
|
+
terminalError: error.message,
|
|
661
510
|
repoKey: project.repoKey,
|
|
662
511
|
error: error.message,
|
|
663
512
|
};
|
|
@@ -705,9 +554,11 @@ async function ensureProject(project) {
|
|
|
705
554
|
const attachments = require('../state/attachments');
|
|
706
555
|
const promotions = require('../state/promotions');
|
|
707
556
|
fs.mkdirSync(path.dirname(project.file), { recursive: true });
|
|
708
|
-
if (!fs.existsSync(project.file))
|
|
557
|
+
if (!fs.existsSync(project.file)) {
|
|
558
|
+
projectStream.writeSnapshot(project.file, projectStream.emptySnapshot(project.resourceId));
|
|
559
|
+
}
|
|
709
560
|
|
|
710
|
-
|
|
561
|
+
const request = attachments.getAttachmentByResource(project.resourceId);
|
|
711
562
|
if (!request) {
|
|
712
563
|
await callLocal(shared.handleAttachmentStart, {
|
|
713
564
|
resourceId: project.resourceId,
|
|
@@ -715,21 +566,26 @@ async function ensureProject(project) {
|
|
|
715
566
|
contract: projectStream.STREAM_CONTRACT,
|
|
716
567
|
});
|
|
717
568
|
} else if (request.state === 'rejected') {
|
|
569
|
+
let promotion = promotions.getPromotionByResource(project.resourceId);
|
|
718
570
|
if (
|
|
719
571
|
ATTACH_REJECTED.test(request.lastError || '')
|
|
720
|
-
&& project.
|
|
721
|
-
&& !
|
|
572
|
+
&& project.ownerUserId === userId()
|
|
573
|
+
&& !promotion
|
|
722
574
|
) {
|
|
723
575
|
await callLocal(shared.handlePromotionStart, {
|
|
724
576
|
resourceId: project.resourceId,
|
|
725
577
|
path: project.file,
|
|
726
|
-
project: project.repoKey,
|
|
727
578
|
contract: projectStream.STREAM_CONTRACT,
|
|
728
|
-
schemaVersion:
|
|
579
|
+
schemaVersion: 2,
|
|
729
580
|
});
|
|
730
|
-
await waitForSettled(
|
|
581
|
+
promotion = await waitForSettled(
|
|
582
|
+
project,
|
|
583
|
+
() => promotions.getPromotionByResource(project.resourceId),
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
if (!wallReplica(project) && promotion?.state === 'promoted') {
|
|
587
|
+
attachments.retryAttachment(project.resourceId);
|
|
731
588
|
}
|
|
732
|
-
if (!wallReplica(project)) attachments.retryAttachment(project.resourceId);
|
|
733
589
|
}
|
|
734
590
|
await waitForSettled(project, () => attachments.getAttachmentByResource(project.resourceId));
|
|
735
591
|
if (wallReplica(project)) schedulePublish();
|
|
@@ -770,17 +626,12 @@ function startWallService() {
|
|
|
770
626
|
if (service.started) return;
|
|
771
627
|
service.started = true;
|
|
772
628
|
service.unsubscribe = subscribeStateEvents((event) => {
|
|
773
|
-
const acknowledgedProject = event.resource === 'shared-mutations'
|
|
774
|
-
? projects().find((project) => project.resourceId === event.sharedResourceId)
|
|
775
|
-
: null;
|
|
776
|
-
if (acknowledgedProject) {
|
|
777
|
-
projectStream.seal(acknowledgedProject, event.mutationId || event.id, event.resourceVersion);
|
|
778
|
-
}
|
|
779
629
|
if (
|
|
780
630
|
event.resource === 'repo-cards'
|
|
781
631
|
|| event.resource === 'repo-checkpoints'
|
|
782
632
|
|| event.resource === 'repo-objects'
|
|
783
|
-
||
|
|
633
|
+
|| event.resource === 'shared-mutations'
|
|
634
|
+
|| String(event.resource || '').startsWith(projectStream.LOCAL_RESOURCE_PREFIX)
|
|
784
635
|
) {
|
|
785
636
|
ensureProjects().catch(() => {});
|
|
786
637
|
schedulePublish();
|
|
@@ -809,6 +660,7 @@ function stopWallService() {
|
|
|
809
660
|
function wallStats() {
|
|
810
661
|
const rows = projects().map((project) => {
|
|
811
662
|
const replica = wallReplica(project);
|
|
663
|
+
const snapshot = projectStream.readSnapshot(project.file, project.resourceId);
|
|
812
664
|
return {
|
|
813
665
|
repoKey: project.repoKey,
|
|
814
666
|
resourceId: project.resourceId,
|
|
@@ -822,7 +674,7 @@ function wallStats() {
|
|
|
822
674
|
lastError: replica.lastError,
|
|
823
675
|
updatedAt: replica.updatedAt,
|
|
824
676
|
},
|
|
825
|
-
|
|
677
|
+
version: snapshot.version,
|
|
826
678
|
};
|
|
827
679
|
});
|
|
828
680
|
return {
|
|
@@ -838,24 +690,27 @@ function wallStats() {
|
|
|
838
690
|
|
|
839
691
|
module.exports = {
|
|
840
692
|
_test: {
|
|
841
|
-
|
|
693
|
+
cardPayloadsReady,
|
|
694
|
+
checkpointForCard,
|
|
842
695
|
hasPendingContributionObjects,
|
|
696
|
+
ensureProject,
|
|
697
|
+
outboundState,
|
|
698
|
+
publishProject,
|
|
843
699
|
publishSelected,
|
|
844
|
-
|
|
700
|
+
sameCardState,
|
|
845
701
|
},
|
|
846
702
|
WALL_CONTRACT,
|
|
847
703
|
bindProject,
|
|
848
704
|
hasLocalContribution,
|
|
849
705
|
isCommittedCard,
|
|
850
|
-
mutationVersion,
|
|
851
706
|
projectFile,
|
|
707
|
+
projectForPath,
|
|
852
708
|
projectForRepo,
|
|
853
|
-
|
|
709
|
+
projectForResource,
|
|
854
710
|
publicationStatus,
|
|
855
711
|
publishedCardFor,
|
|
856
712
|
publishWall,
|
|
857
713
|
readWall,
|
|
858
|
-
stampCards,
|
|
859
714
|
startWallService,
|
|
860
715
|
stopWallService,
|
|
861
716
|
wallStats,
|