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.
Files changed (157) hide show
  1. package/README.md +12 -0
  2. package/lib/cli.js +146 -8
  3. package/lib/layout.js +89 -4
  4. package/lib/shared-realtime-tunnel.js +384 -43
  5. package/lib/supervisor.js +0 -3
  6. package/package.json +2 -2
  7. package/runtime/scripts/amalgm-mcp/adapters/contract.js +197 -0
  8. package/runtime/scripts/amalgm-mcp/adapters/filesystem.js +135 -0
  9. package/runtime/scripts/amalgm-mcp/adapters/git.js +84 -0
  10. package/runtime/scripts/amalgm-mcp/adapters/index.js +43 -0
  11. package/runtime/scripts/amalgm-mcp/adapters/reference.js +31 -0
  12. package/runtime/scripts/amalgm-mcp/adapters/truth.js +19 -0
  13. package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +20 -68
  14. package/runtime/scripts/amalgm-mcp/index.js +4 -0
  15. package/runtime/scripts/amalgm-mcp/lib/layout.js +89 -4
  16. package/runtime/scripts/amalgm-mcp/observer/README.md +259 -0
  17. package/runtime/scripts/amalgm-mcp/observer/apply.js +150 -0
  18. package/runtime/scripts/amalgm-mcp/observer/continuity.js +111 -0
  19. package/runtime/scripts/amalgm-mcp/observer/edges.js +111 -0
  20. package/runtime/scripts/amalgm-mcp/observer/index.js +1146 -0
  21. package/runtime/scripts/amalgm-mcp/observer/scan.js +387 -0
  22. package/runtime/scripts/amalgm-mcp/observer/store.js +262 -0
  23. package/runtime/scripts/amalgm-mcp/observer/verify.js +200 -0
  24. package/runtime/scripts/amalgm-mcp/observer/watch.js +62 -0
  25. package/runtime/scripts/amalgm-mcp/project-context/store.js +19 -87
  26. package/runtime/scripts/amalgm-mcp/registration/classify.js +33 -0
  27. package/runtime/scripts/amalgm-mcp/registration/entity-cloud.js +456 -0
  28. package/runtime/scripts/amalgm-mcp/registration/entity-content.js +345 -0
  29. package/runtime/scripts/amalgm-mcp/registration/index.js +752 -0
  30. package/runtime/scripts/amalgm-mcp/registration/refusal.js +42 -0
  31. package/runtime/scripts/amalgm-mcp/registration/repo-followers.js +127 -0
  32. package/runtime/scripts/amalgm-mcp/registration/repo-states.js +109 -0
  33. package/runtime/scripts/amalgm-mcp/registration/service.js +310 -0
  34. package/runtime/scripts/amalgm-mcp/registration/tree.js +178 -0
  35. package/runtime/scripts/amalgm-mcp/registry/evidence.js +826 -0
  36. package/runtime/scripts/amalgm-mcp/registry/index.js +666 -0
  37. package/runtime/scripts/amalgm-mcp/registry/store.js +290 -0
  38. package/runtime/scripts/amalgm-mcp/repocard/README.md +103 -0
  39. package/runtime/scripts/amalgm-mcp/repocard/apply.js +111 -0
  40. package/runtime/scripts/amalgm-mcp/repocard/capture-worker.js +94 -0
  41. package/runtime/scripts/amalgm-mcp/repocard/capture.js +149 -0
  42. package/runtime/scripts/amalgm-mcp/repocard/follow.js +310 -0
  43. package/runtime/scripts/amalgm-mcp/repocard/git.js +48 -0
  44. package/runtime/scripts/amalgm-mcp/repocard/index.js +48 -0
  45. package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
  46. package/runtime/scripts/amalgm-mcp/server/routes/entities.js +287 -0
  47. package/runtime/scripts/amalgm-mcp/server/routes/state.js +0 -13
  48. package/runtime/scripts/amalgm-mcp/state/attachments.js +11 -45
  49. package/runtime/scripts/amalgm-mcp/state/db.js +46 -215
  50. package/runtime/scripts/amalgm-mcp/state/docs.js +301 -1101
  51. package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +0 -99
  52. package/runtime/scripts/amalgm-mcp/state/mutations.js +130 -507
  53. package/runtime/scripts/amalgm-mcp/state/promotions.js +1 -21
  54. package/runtime/scripts/amalgm-mcp/state/replicas.js +13 -134
  55. package/runtime/scripts/amalgm-mcp/state/shared-rest.js +3 -46
  56. package/runtime/scripts/amalgm-mcp/tests/adapters.test.js +379 -0
  57. package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +0 -193
  58. package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.life.test.js +449 -0
  59. package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.rig.js +107 -0
  60. package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.watch.test.js +213 -0
  61. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/adapter.js +72 -0
  62. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/assert.js +497 -0
  63. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/bind.js +91 -0
  64. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/graph.js +1031 -0
  65. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/ledger.js +234 -0
  66. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/referee.js +118 -0
  67. package/runtime/scripts/amalgm-mcp/tests/entity.oracle.test.js +1365 -0
  68. package/runtime/scripts/amalgm-mcp/tests/entity.registry.test.js +967 -0
  69. package/runtime/scripts/amalgm-mcp/tests/entity.rig.js +239 -0
  70. package/runtime/scripts/amalgm-mcp/tests/entity.storm.test.js +486 -0
  71. package/runtime/scripts/amalgm-mcp/tests/fake-watch.js +35 -0
  72. package/runtime/scripts/amalgm-mcp/tests/observer.links.test.js +289 -0
  73. package/runtime/scripts/amalgm-mcp/tests/observer.rig.js +103 -0
  74. package/runtime/scripts/amalgm-mcp/tests/observer.roots.test.js +505 -0
  75. package/runtime/scripts/amalgm-mcp/tests/observer.storm.test.js +389 -0
  76. package/runtime/scripts/amalgm-mcp/tests/observer.territory.test.js +845 -0
  77. package/runtime/scripts/amalgm-mcp/tests/observer.test.js +657 -0
  78. package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +17 -32
  79. package/runtime/scripts/amalgm-mcp/tests/registration.service.test.js +256 -0
  80. package/runtime/scripts/amalgm-mcp/tests/registration.test.js +931 -0
  81. package/runtime/scripts/amalgm-mcp/tests/repocard.storm.test.js +284 -0
  82. package/runtime/scripts/amalgm-mcp/tests/repocard.test.js +371 -0
  83. package/runtime/scripts/amalgm-mcp/tests/repofollow.storm.test.js +435 -0
  84. package/runtime/scripts/amalgm-mcp/tests/repofollow.test.js +675 -0
  85. package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +0 -84
  86. package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +13 -376
  87. package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +0 -309
  88. package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +30 -758
  89. package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +3 -72
  90. package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +1 -89
  91. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +24 -55
  92. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +141 -424
  93. package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +3 -171
  94. package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +7 -220
  95. package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +236 -179
  96. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +326 -2228
  97. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +3 -5319
  98. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +1 -191
  99. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +17 -3140
  100. package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +740 -137
  101. package/runtime/scripts/amalgm-mcp/workspace/access-store.js +1 -5
  102. package/runtime/scripts/amalgm-mcp/workspace/card.js +11 -35
  103. package/runtime/scripts/amalgm-mcp/workspace/cards.js +14 -39
  104. package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +147 -587
  105. package/runtime/scripts/amalgm-mcp/workspace/git.js +34 -406
  106. package/runtime/scripts/amalgm-mcp/workspace/merge.js +181 -102
  107. package/runtime/scripts/amalgm-mcp/workspace/objects.js +56 -221
  108. package/runtime/scripts/amalgm-mcp/workspace/reducer.js +538 -1964
  109. package/runtime/scripts/amalgm-mcp/workspace/rest.js +28 -49
  110. package/runtime/scripts/amalgm-mcp/workspace/store.js +4 -77
  111. package/runtime/scripts/amalgm-mcp/workspace/transition.js +192 -1734
  112. package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +1 -7
  113. package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +61 -73
  114. package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +6 -51
  115. package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +8 -79
  116. package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +146 -3461
  117. package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +71 -899
  118. package/runtime/scripts/amalgm-mcp/workspace/wall.js +538 -370
  119. package/runtime/scripts/local-gateway.js +1 -0
  120. package/runtime/scripts/amalgm-mcp/state/doc-disk.js +0 -550
  121. package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +0 -399
  122. package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +0 -1
  123. package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +0 -24
  124. package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +0 -1999
  125. package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +0 -364
  126. package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +0 -185
  127. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +0 -26
  128. package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +0 -119
  129. package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +0 -1446
  130. package/runtime/scripts/amalgm-mcp/workspace/content.js +0 -32
  131. package/runtime/scripts/amalgm-mcp/workspace/durable.js +0 -45
  132. package/runtime/scripts/amalgm-mcp/workspace/intent.js +0 -535
  133. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +0 -31
  134. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
  135. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
  136. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +0 -72
  137. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +0 -165
  138. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +0 -5
  139. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +0 -2
  140. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +0 -2201
  141. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +0 -19
  142. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +0 -18
  143. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +0 -18
  144. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +0 -33
  145. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +0 -47
  146. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +0 -43
  147. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +0 -114
  148. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +0 -84
  149. package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +0 -771
  150. package/runtime/scripts/amalgm-mcp/workspace/provenance.js +0 -240
  151. package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +0 -240
  152. package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +0 -100
  153. package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +0 -127
  154. package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +0 -12
  155. package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +0 -125
  156. package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +0 -76
  157. package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +0 -3766
@@ -1,32 +0,0 @@
1
- 'use strict';
2
-
3
- const crypto = require('crypto');
4
-
5
- const CHUNK_BYTES = 512 * 1024;
6
-
7
- function sha256(bytes) {
8
- return crypto.createHash('sha256').update(bytes).digest('hex');
9
- }
10
-
11
- function describeBytes(bytesInput) {
12
- const bytes = Buffer.isBuffer(bytesInput) ? bytesInput : Buffer.from(bytesInput);
13
- const chunks = [];
14
- for (let offset = 0; offset < bytes.length; offset += CHUNK_BYTES) {
15
- const part = bytes.subarray(offset, Math.min(offset + CHUNK_BYTES, bytes.length));
16
- chunks.push({ sha256: sha256(part), bytes: part.length });
17
- }
18
- return chunks;
19
- }
20
-
21
- function manifestChecksum(chunks) {
22
- return sha256(Buffer.from(JSON.stringify(
23
- chunks.map((chunk) => ({ sha256: chunk.sha256, bytes: chunk.bytes })),
24
- )));
25
- }
26
-
27
- module.exports = {
28
- CHUNK_BYTES,
29
- describeBytes,
30
- manifestChecksum,
31
- sha256,
32
- };
@@ -1,45 +0,0 @@
1
- 'use strict';
2
-
3
- const crypto = require('crypto');
4
- const fs = require('fs');
5
- const path = require('path');
6
-
7
- function syncDirectory(directory) {
8
- const descriptor = fs.openSync(directory, 'r');
9
- try {
10
- fs.fsyncSync(descriptor);
11
- } finally {
12
- fs.closeSync(descriptor);
13
- }
14
- }
15
-
16
- /**
17
- * Durable sync state is replaced, never edited in place. After a process
18
- * death, readers therefore see either the complete old value or the complete
19
- * new value.
20
- */
21
- function writeFile(file, content, mode = 0o600) {
22
- fs.mkdirSync(path.dirname(file), { recursive: true });
23
- const temporary = path.join(
24
- path.dirname(file),
25
- `.${path.basename(file)}.${process.pid}.${crypto.randomBytes(8).toString('hex')}.tmp`,
26
- );
27
- let descriptor = fs.openSync(temporary, 'wx', mode);
28
- try {
29
- fs.writeFileSync(descriptor, content);
30
- fs.fsyncSync(descriptor);
31
- fs.closeSync(descriptor);
32
- descriptor = null;
33
- fs.renameSync(temporary, file);
34
- syncDirectory(path.dirname(file));
35
- } finally {
36
- if (descriptor !== null) fs.closeSync(descriptor);
37
- if (fs.existsSync(temporary)) fs.rmSync(temporary, { force: true });
38
- }
39
- }
40
-
41
- function writeJson(file, value, { pretty = false } = {}) {
42
- writeFile(file, `${JSON.stringify(value, null, pretty ? 2 : 0)}\n`);
43
- }
44
-
45
- module.exports = { writeFile, writeJson };
@@ -1,535 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Durable local authorship for one registered code folder.
5
- *
6
- * The official stream still has only two values: card and checkpoint. This
7
- * file is not shared state. It records the exact local state a real file/Git
8
- * observation saw, relative to the last verified official state on this
9
- * machine. The reducer may preserve only this record; a physical mismatch by
10
- * itself never creates authorship.
11
- *
12
- * Axiom 4: a reserved predecessor is immutable and later work occupies one
13
- * successor pinned to its exact content. Proven by workspace-local-intent.test.js.
14
- */
15
-
16
- const crypto = require('crypto');
17
- const fs = require('fs');
18
- const path = require('path');
19
- const { AMALGM_DIR } = require('../config');
20
- const durable = require('./durable');
21
- const provenance = require('./provenance');
22
-
23
- const INTENT_CONTRACT = 'repo-local-intent@4';
24
- const LEGACY_INTENT_CONTRACTS = new Set([
25
- 'repo-local-intent@2',
26
- 'repo-local-intent@3',
27
- ]);
28
- const SUCCESSOR_CONTRACT = 'repo-local-intent-successor@2';
29
- const LEGACY_SUCCESSOR_CONTRACT = 'repo-local-intent-successor@1';
30
- const INTENT_DIR = path.join(AMALGM_DIR, 'state', 'repo-local-intent');
31
- const SHA = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
32
- const MUTATION_ID = /^[A-Za-z0-9_-]{16,160}$/;
33
-
34
- function newMutationId() {
35
- return `mut_${crypto.randomBytes(18).toString('base64url')}`;
36
- }
37
-
38
- function intentFile(resourceId) {
39
- const digest = crypto.createHash('sha256').update(resourceId).digest('hex').slice(0, 24);
40
- return path.join(INTENT_DIR, `${digest}.json`);
41
- }
42
-
43
- function canonicalPath(localPath) {
44
- const resolved = path.resolve(localPath);
45
- const tail = [];
46
- let existing = resolved;
47
- while (!fs.existsSync(existing)) {
48
- const parent = path.dirname(existing);
49
- if (parent === existing) return resolved;
50
- tail.unshift(path.basename(existing));
51
- existing = parent;
52
- }
53
- return path.join(fs.realpathSync.native(existing), ...tail);
54
- }
55
-
56
- function branchState(branches) {
57
- return (branches || [])
58
- .map((branch) => ({ name: branch.name, oid: branch.oid ?? null }))
59
- .sort((left, right) => left.name.localeCompare(right.name));
60
- }
61
-
62
- function cardState(card) {
63
- return {
64
- head: {
65
- branch: card?.head?.branch ?? null,
66
- oid: card?.head?.oid ?? null,
67
- detached: Boolean(card?.head?.detached),
68
- },
69
- branches: branchState(card?.branches || card?.refs),
70
- };
71
- }
72
-
73
- function semanticState(value) {
74
- return {
75
- card: cardState(value?.local?.card),
76
- checkpoint: {
77
- baseOid: value?.local?.checkpoint?.baseOid ?? null,
78
- branch: value?.local?.checkpoint?.branch ?? null,
79
- revision: value?.local?.checkpoint?.value?.revision ?? null,
80
- },
81
- };
82
- }
83
-
84
- function semanticDigest(value) {
85
- return crypto.createHash('sha256')
86
- .update(JSON.stringify(semanticState(value)))
87
- .digest('hex');
88
- }
89
-
90
- function validTree(value) {
91
- return SHA.test(String(value || ''));
92
- }
93
-
94
- function validInitialBase(base) {
95
- return Boolean(
96
- base?.through === 0
97
- && base.card === null
98
- && validTree(base.indexTree)
99
- && base.indexTree === base.worktreeTree
100
- );
101
- }
102
-
103
- function validLocal(local) {
104
- return Boolean(
105
- validTree(local?.card?.head?.oid)
106
- && Array.isArray(local?.card?.branches)
107
- && validTree(local?.indexTree)
108
- && validTree(local?.worktreeTree)
109
- && local?.checkpoint?.baseOid === local.card.head.oid
110
- && typeof local?.physicalDigest === 'string'
111
- );
112
- }
113
-
114
- function sameCard(left, right) {
115
- return JSON.stringify(cardState(left)) === JSON.stringify(cardState(right));
116
- }
117
-
118
- function validSuccessor(value, predecessor) {
119
- const currentContract = value?.contract === SUCCESSOR_CONTRACT;
120
- const legacyContract = value?.contract === LEGACY_SUCCESSOR_CONTRACT;
121
- return Boolean(
122
- value
123
- && (currentContract || legacyContract)
124
- && (!currentContract || MUTATION_ID.test(value.birthMutationId))
125
- && value.predecessorSemanticDigest === predecessor.semanticDigest
126
- && value.base?.semanticDigest === predecessor.semanticDigest
127
- && sameCard(value.base?.card, predecessor.local.card)
128
- && value.base?.indexTree === predecessor.local.indexTree
129
- && value.base?.worktreeTree === predecessor.local.worktreeTree
130
- && value.base?.physicalDigest === predecessor.local.physicalDigest
131
- && validLocal(value.local)
132
- && value.semanticDigest === semanticDigest(value)
133
- && typeof value.capturedAt === 'string'
134
- );
135
- }
136
-
137
- function validIntent(value, project) {
138
- const initial = validInitialBase(value?.base);
139
- const currentContract = value?.contract === INTENT_CONTRACT;
140
- const legacyContract = LEGACY_INTENT_CONTRACTS.has(value?.contract)
141
- && (
142
- value.contract !== 'repo-local-intent@2'
143
- || value.successor === undefined
144
- || value.successor === null
145
- );
146
- return Boolean(
147
- value
148
- && (currentContract || legacyContract)
149
- && (!currentContract || MUTATION_ID.test(value.birthMutationId))
150
- && value.resourceId === project.resourceId
151
- && value.ownerUserId === project.ownerUserId
152
- && canonicalPath(value.path) === canonicalPath(project.path)
153
- && Number.isInteger(value.base?.through)
154
- && value.base.through >= 0
155
- && (
156
- initial
157
- || (
158
- validTree(value.base?.card?.head?.oid)
159
- && Array.isArray(value.base?.card?.branches)
160
- && validTree(value.base?.indexTree)
161
- && validTree(value.base?.worktreeTree)
162
- )
163
- )
164
- && validLocal(value.local)
165
- && value.semanticDigest === semanticDigest(value)
166
- && (
167
- value.successor === undefined
168
- || value.successor === null
169
- || validSuccessor(value.successor, value)
170
- )
171
- && (
172
- value.publication === null
173
- || (
174
- typeof value.publication?.mutationId === 'string'
175
- && (
176
- !currentContract
177
- || value.publication.mutationId === value.birthMutationId
178
- )
179
- && value.publication.semanticDigest === value.semanticDigest
180
- && value.publication.baseThrough === value.base.through
181
- && (
182
- (
183
- value.publication.numberedVersion === null
184
- && value.publication.numberedAt === null
185
- && value.publication.foldedAt === null
186
- )
187
- || (
188
- Number.isInteger(value.publication.numberedVersion)
189
- && value.publication.numberedVersion > 0
190
- && typeof value.publication.numberedAt === 'string'
191
- && (
192
- value.publication.foldedAt === null
193
- || typeof value.publication.foldedAt === 'string'
194
- )
195
- )
196
- )
197
- )
198
- )
199
- );
200
- }
201
-
202
- function persistIntent(project, value) {
203
- if (!validIntent(value, project)) throw new Error('local code intent is invalid');
204
- durable.writeJson(intentFile(project.resourceId), value, { pretty: true });
205
- return value;
206
- }
207
-
208
- function readIntent(project) {
209
- if (!project?.resourceId || !project?.path || !project?.ownerUserId) return null;
210
- const file = intentFile(project.resourceId);
211
- let raw;
212
- try {
213
- raw = fs.readFileSync(file, 'utf8');
214
- } catch (error) {
215
- if (error?.code === 'ENOENT') return null;
216
- throw new Error(`cannot read local code intent ${file}`, { cause: error });
217
- }
218
- let value;
219
- try {
220
- value = JSON.parse(raw);
221
- } catch (error) {
222
- throw new Error(`local code intent ${file} is not valid JSON`, { cause: error });
223
- }
224
- if (!validIntent(value, project)) {
225
- throw new Error(`local code intent ${file} has an invalid or unsupported contract`);
226
- }
227
- return value;
228
- }
229
-
230
- function writeIntent(project, input) {
231
- const held = readIntent(project);
232
- const nextSemanticDigest = semanticDigest({ local: input.local });
233
- const samePredecessor = held
234
- && held.semanticDigest === nextSemanticDigest
235
- && JSON.stringify(held.base) === JSON.stringify(input.base);
236
- let birthMutationId = MUTATION_ID.test(String(input.birthMutationId || ''))
237
- ? input.birthMutationId
238
- : null;
239
- if (
240
- !birthMutationId
241
- && (
242
- input.preserveBirthMutationId === true
243
- || samePredecessor
244
- )
245
- && MUTATION_ID.test(String(held?.birthMutationId || ''))
246
- ) birthMutationId = held.birthMutationId;
247
- if (
248
- !birthMutationId
249
- && MUTATION_ID.test(String(input.publication?.mutationId || ''))
250
- ) birthMutationId = input.publication.mutationId;
251
- if (!birthMutationId) birthMutationId = newMutationId();
252
- const next = {
253
- contract: INTENT_CONTRACT,
254
- birthMutationId,
255
- resourceId: project.resourceId,
256
- ownerUserId: project.ownerUserId,
257
- path: canonicalPath(project.path),
258
- base: input.base,
259
- local: input.local,
260
- publication: input.publication || null,
261
- terminal: input.terminal || null,
262
- capturedAt: new Date().toISOString(),
263
- };
264
- next.semanticDigest = nextSemanticDigest;
265
- next.successor = input.successor === undefined && samePredecessor
266
- ? held.successor || null
267
- : input.successor || null;
268
- return persistIntent(project, next);
269
- }
270
-
271
- /**
272
- * Freeze the published intent and atomically replace only its open successor.
273
- *
274
- * A successor is a final desk state, not an edit log. Its base is the exact
275
- * content identity of the frozen predecessor, so repeated saves coalesce
276
- * without ever changing the bytes or publication identity already in flight.
277
- */
278
- function writeSuccessor(project, predecessor, input) {
279
- let held = readIntent(project);
280
- if (!held || held.semanticDigest !== predecessor?.semanticDigest) return null;
281
- if (
282
- held.contract !== INTENT_CONTRACT
283
- || !MUTATION_ID.test(String(held.birthMutationId || ''))
284
- ) {
285
- held = writeIntent(project, {
286
- base: held.base,
287
- local: held.local,
288
- terminal: held.terminal,
289
- successor: held.successor,
290
- publication: held.publication,
291
- });
292
- }
293
- const prior = held.successor || null;
294
- const successor = {
295
- contract: SUCCESSOR_CONTRACT,
296
- birthMutationId: null,
297
- predecessorSemanticDigest: held.semanticDigest,
298
- base: {
299
- semanticDigest: held.semanticDigest,
300
- card: held.local.card,
301
- indexTree: held.local.indexTree,
302
- worktreeTree: held.local.worktreeTree,
303
- physicalDigest: held.local.physicalDigest,
304
- },
305
- local: input.local,
306
- terminal: input.terminal || null,
307
- capturedAt: new Date().toISOString(),
308
- };
309
- successor.semanticDigest = semanticDigest(successor);
310
- successor.birthMutationId = (
311
- prior?.semanticDigest === successor.semanticDigest
312
- && MUTATION_ID.test(String(prior.birthMutationId || ''))
313
- ) ? prior.birthMutationId : newMutationId();
314
- persistIntent(project, { ...held, contract: INTENT_CONTRACT, successor });
315
- return successor;
316
- }
317
-
318
- function readSuccessor(project) {
319
- return readIntent(project)?.successor || null;
320
- }
321
-
322
- function clearSuccessor(project, predecessor = null) {
323
- let held = readIntent(project);
324
- if (
325
- !held?.successor
326
- || (
327
- predecessor
328
- && held.semanticDigest !== predecessor.semanticDigest
329
- )
330
- ) return false;
331
- if (
332
- held.contract !== INTENT_CONTRACT
333
- || !MUTATION_ID.test(String(held.birthMutationId || ''))
334
- ) {
335
- held = writeIntent(project, {
336
- base: held.base,
337
- local: held.local,
338
- terminal: held.terminal,
339
- successor: held.successor,
340
- publication: held.publication,
341
- });
342
- }
343
- persistIntent(project, { ...held, contract: INTENT_CONTRACT, successor: null });
344
- return true;
345
- }
346
-
347
- function clearIntent(project) {
348
- if (!project?.resourceId) return false;
349
- const file = intentFile(project.resourceId);
350
- if (!fs.existsSync(file)) return false;
351
- fs.rmSync(file, { force: true });
352
- return true;
353
- }
354
-
355
- function stampPublication(project, intent, mutationId) {
356
- const held = readIntent(project);
357
- if (
358
- !held
359
- || held.semanticDigest !== intent?.semanticDigest
360
- || mutationId !== held.birthMutationId
361
- ) return null;
362
- return writeIntent(project, {
363
- birthMutationId: held.birthMutationId,
364
- base: held.base,
365
- local: held.local,
366
- terminal: held.terminal,
367
- publication: {
368
- mutationId,
369
- semanticDigest: held.semanticDigest,
370
- baseThrough: held.base.through,
371
- numberedVersion: null,
372
- numberedAt: null,
373
- foldedAt: null,
374
- },
375
- });
376
- }
377
-
378
- function reservePublication(project, intent) {
379
- let held = readIntent(project);
380
- if (!held || held.semanticDigest !== intent?.semanticDigest) return null;
381
- if (!MUTATION_ID.test(String(held.birthMutationId || ''))) {
382
- held = writeIntent(project, {
383
- base: held.base,
384
- local: held.local,
385
- terminal: held.terminal,
386
- successor: held.successor,
387
- publication: held.publication,
388
- });
389
- }
390
- if (
391
- held.publication
392
- && held.publication.semanticDigest === held.semanticDigest
393
- && held.publication.baseThrough === held.base.through
394
- && held.publication.numberedVersion === null
395
- ) return held;
396
- provenance.requireTransition(
397
- provenance.publicationState(held.publication),
398
- provenance.ACTION.RESERVE,
399
- provenance.STATE.RESERVED,
400
- ['intent-record', 'publication-id'],
401
- );
402
- return stampPublication(
403
- project,
404
- held,
405
- held.birthMutationId,
406
- );
407
- }
408
-
409
- function markPublicationNumbered(project, mutationId, version, numberedAt = null) {
410
- const held = readIntent(project);
411
- if (!held || held.publication?.mutationId !== mutationId) return null;
412
- if (!Number.isInteger(version) || version < 1) {
413
- throw new Error('numbered publication version is invalid');
414
- }
415
- if (
416
- held.publication.numberedVersion !== null
417
- && held.publication.numberedVersion !== version
418
- ) throw new Error('publication received a different official number');
419
- if (held.publication.numberedVersion === version) return held;
420
- provenance.requireTransition(
421
- provenance.publicationState(held.publication),
422
- provenance.ACTION.NUMBER,
423
- provenance.STATE.NUMBERED,
424
- ['publication-id', 'official-version', 'server-receipt'],
425
- );
426
- return writeIntent(project, {
427
- base: held.base,
428
- local: held.local,
429
- terminal: held.terminal,
430
- publication: {
431
- ...held.publication,
432
- numberedVersion: version,
433
- numberedAt: numberedAt || new Date().toISOString(),
434
- },
435
- });
436
- }
437
-
438
- function markPublicationFolded(project, mutationId, version, numberedAt = null) {
439
- const numbered = markPublicationNumbered(project, mutationId, version, numberedAt);
440
- if (!numbered || numbered.publication.foldedAt !== null) return numbered;
441
- provenance.requireTransition(
442
- provenance.publicationState(numbered.publication),
443
- provenance.ACTION.FOLD,
444
- provenance.STATE.FOLDED,
445
- ['predecessor-folded', 'official-event', 'official-snapshot'],
446
- );
447
- return writeIntent(project, {
448
- base: numbered.base,
449
- local: numbered.local,
450
- terminal: numbered.terminal,
451
- publication: {
452
- ...numbered.publication,
453
- foldedAt: new Date().toISOString(),
454
- },
455
- });
456
- }
457
-
458
- function officialAcknowledgement(project, intent) {
459
- if (!intent?.publication?.mutationId) return null;
460
- if (
461
- Number.isInteger(intent.publication.numberedVersion)
462
- && intent.publication.numberedVersion > 0
463
- && typeof intent.publication.numberedAt === 'string'
464
- ) {
465
- return {
466
- mutationId: intent.publication.mutationId,
467
- version: intent.publication.numberedVersion,
468
- };
469
- }
470
- const row = require('../state/mutations').getMutation(
471
- `resource:${project.resourceId}`,
472
- intent.publication.mutationId,
473
- { revealOperation: true },
474
- );
475
- return (
476
- row
477
- && row.state === 'synced'
478
- && Number.isInteger(row.officialCloudVersion)
479
- && row.officialCloudVersion > 0
480
- && intent.publication.semanticDigest === intent.semanticDigest
481
- ) ? {
482
- mutationId: row.mutationId,
483
- version: row.officialCloudVersion,
484
- } : null;
485
- }
486
-
487
- function publicationMayRebase(project, intent, through) {
488
- if (
489
- !intent?.publication?.mutationId
490
- || intent.publication.numberedVersion !== null
491
- || !Number.isInteger(through)
492
- || through <= intent.base.through
493
- ) return false;
494
- const row = require('../state/mutations').getMutation(
495
- `resource:${project.resourceId}`,
496
- intent.publication.mutationId,
497
- );
498
- return !row || (
499
- row.state === 'rejected'
500
- && row.failureCode === 'code_project_stale_base'
501
- );
502
- }
503
-
504
- function readOwnedIntent(project) {
505
- const intent = readIntent(project);
506
- if (!intent) return null;
507
- return officialAcknowledgement(project, intent) ? null : intent;
508
- }
509
-
510
- module.exports = {
511
- INTENT_CONTRACT,
512
- SUCCESSOR_CONTRACT,
513
- _test: {
514
- canonicalPath,
515
- cardState,
516
- semanticDigest,
517
- validInitialBase,
518
- validIntent,
519
- validSuccessor,
520
- },
521
- clearIntent,
522
- clearSuccessor,
523
- intentFile,
524
- markPublicationFolded,
525
- markPublicationNumbered,
526
- officialAcknowledgement,
527
- publicationMayRebase,
528
- readIntent,
529
- readOwnedIntent,
530
- readSuccessor,
531
- reservePublication,
532
- stampPublication,
533
- writeIntent,
534
- writeSuccessor,
535
- };
@@ -1,31 +0,0 @@
1
- # Native workspace file transaction
2
-
3
- This helper is the only code allowed to observe or cross an ordered file or
4
- Git-metadata mutation. JavaScript cannot express
5
- `openat`/`renameat`/`linkat`, so path confinement at that layer has an
6
- unavoidable check/use race. The helper opens each parent directory with
7
- `O_NOFOLLOW` and performs every observation and mutation relative to those
8
- held descriptors.
9
-
10
- `main.go` owns the transaction and crash ordering. `move_*.go` consumes a
11
- prepared target without replacing a concurrent destination; its disappearance
12
- is the crossing receipt. `writers_*.go` proves that an extracted source inode
13
- has no pre-existing writer before it can be retired. The checked-in binaries
14
- are required because managed runtimes do not include a Go compiler.
15
- `manifest.json` binds them to the exact source bytes.
16
-
17
- The `git-lock` backend prepares index or HEAD bytes inside the linked
18
- worktree's private Git directory, hard-links them to Git's canonical lock, and
19
- renames that lock over the destination. The retained hard link identifies the
20
- crossing even after later Git activity changes the destination or creates a
21
- new lock.
22
-
23
- Rebuild and verify both release platforms:
24
-
25
- ```sh
26
- node build.mjs
27
- node verify.mjs
28
- ```
29
-
30
- The ordinary JavaScript transition tests execute the host binary against real
31
- Git repositories, crashes, open descriptors, and directory-swap races.