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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { isUuid } = require('./identity');
|
|
4
4
|
const { KINDS, normalizeNodeName } = require('./operations');
|
|
5
|
+
const { validateLiveSymlinkTargets } = require('./symlinks');
|
|
5
6
|
|
|
6
7
|
function invalid(message) {
|
|
7
8
|
throw Object.assign(new Error(message), { statusCode: 400 });
|
|
@@ -35,6 +36,12 @@ function normalizeTreeSnapshot(input) {
|
|
|
35
36
|
}
|
|
36
37
|
const parentId = value.parentId == null ? null : String(value.parentId).toLowerCase();
|
|
37
38
|
if (parentId && !isUuid(parentId)) invalid('workspace tree parent identity is invalid');
|
|
39
|
+
const trashComponentId = value.trashComponentId == null
|
|
40
|
+
? null
|
|
41
|
+
: String(value.trashComponentId).toLowerCase();
|
|
42
|
+
if (trashComponentId && !isUuid(trashComponentId)) {
|
|
43
|
+
invalid('workspace tree trash component identity is invalid');
|
|
44
|
+
}
|
|
38
45
|
const createdAt = timestamp(value.createdAt, new Date().toISOString());
|
|
39
46
|
const updatedAt = timestamp(value.updatedAt, createdAt);
|
|
40
47
|
if (value.kind === 'symlink' && (typeof value.symlinkTarget !== 'string' || !value.symlinkTarget)) {
|
|
@@ -48,6 +55,7 @@ function normalizeTreeSnapshot(input) {
|
|
|
48
55
|
name: normalizeNodeName(value.name),
|
|
49
56
|
symlinkTarget: value.kind === 'symlink' ? value.symlinkTarget : null,
|
|
50
57
|
trashedAt: value.trashedAt ? timestamp(value.trashedAt) : null,
|
|
58
|
+
trashComponentId,
|
|
51
59
|
purgedAt: value.purgedAt ? timestamp(value.purgedAt) : null,
|
|
52
60
|
createdAt,
|
|
53
61
|
updatedAt,
|
|
@@ -58,17 +66,30 @@ function normalizeTreeSnapshot(input) {
|
|
|
58
66
|
if (!root || root.parentId !== null || root.kind !== 'folder') {
|
|
59
67
|
invalid('workspace tree requires one folder root whose id is the workspace id');
|
|
60
68
|
}
|
|
69
|
+
const ancestry = buildAncestryIndex(nodes, workspaceId);
|
|
61
70
|
const siblingNames = new Set();
|
|
62
71
|
for (const node of nodes.values()) {
|
|
63
72
|
if (node.id === workspaceId) continue;
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
if (node.purgedAt) {
|
|
74
|
+
if (node.trashComponentId) {
|
|
75
|
+
invalid('a purged workspace tree node cannot name a physical trash component');
|
|
76
|
+
}
|
|
77
|
+
} else if (node.trashedAt) {
|
|
78
|
+
if (!node.trashComponentId) {
|
|
79
|
+
invalid('an active workspace tree tombstone requires explicit trash provenance');
|
|
80
|
+
}
|
|
81
|
+
const component = nodes.get(node.trashComponentId);
|
|
82
|
+
if (
|
|
83
|
+
!component
|
|
84
|
+
|| !component.trashedAt
|
|
85
|
+
|| component.purgedAt
|
|
86
|
+
|| component.trashComponentId !== component.id
|
|
87
|
+
|| !isAncestor(ancestry, component.id, node.id)
|
|
88
|
+
) {
|
|
89
|
+
invalid('workspace tree trash component is not an ancestor');
|
|
90
|
+
}
|
|
91
|
+
} else if (node.trashComponentId) {
|
|
92
|
+
invalid('a live workspace tree node cannot name a trash component');
|
|
72
93
|
}
|
|
73
94
|
if (!node.trashedAt && !node.purgedAt) {
|
|
74
95
|
const key = `${node.parentId}\0${node.name}`;
|
|
@@ -76,7 +97,57 @@ function normalizeTreeSnapshot(input) {
|
|
|
76
97
|
siblingNames.add(key);
|
|
77
98
|
}
|
|
78
99
|
}
|
|
100
|
+
validateLiveSymlinkTargets(nodes, workspaceId);
|
|
79
101
|
return { schemaVersion: 1, workspaceId, version: input.version, nodes };
|
|
80
102
|
}
|
|
81
103
|
|
|
104
|
+
function buildAncestryIndex(nodes, rootId) {
|
|
105
|
+
const children = new Map();
|
|
106
|
+
for (const node of nodes.values()) {
|
|
107
|
+
if (node.id === rootId) continue;
|
|
108
|
+
if (!node.parentId || !nodes.has(node.parentId)) invalid('workspace tree node parent is missing');
|
|
109
|
+
const siblings = children.get(node.parentId) || [];
|
|
110
|
+
siblings.push(node.id);
|
|
111
|
+
children.set(node.parentId, siblings);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const entered = new Map();
|
|
115
|
+
const exited = new Map();
|
|
116
|
+
const stack = [{ id: rootId, exit: false }];
|
|
117
|
+
let clock = 0;
|
|
118
|
+
while (stack.length) {
|
|
119
|
+
const frame = stack.pop();
|
|
120
|
+
if (frame.exit) {
|
|
121
|
+
exited.set(frame.id, clock);
|
|
122
|
+
clock += 1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (entered.has(frame.id)) invalid('workspace tree contains a cycle');
|
|
126
|
+
entered.set(frame.id, clock);
|
|
127
|
+
clock += 1;
|
|
128
|
+
stack.push({ id: frame.id, exit: true });
|
|
129
|
+
const descendants = children.get(frame.id) || [];
|
|
130
|
+
for (let index = descendants.length - 1; index >= 0; index -= 1) {
|
|
131
|
+
stack.push({ id: descendants[index], exit: false });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (entered.size !== nodes.size) invalid('workspace tree contains a cycle');
|
|
135
|
+
return { entered, exited };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isAncestor(index, ancestorId, nodeId) {
|
|
139
|
+
const ancestorEntered = index.entered.get(ancestorId);
|
|
140
|
+
const ancestorExited = index.exited.get(ancestorId);
|
|
141
|
+
const nodeEntered = index.entered.get(nodeId);
|
|
142
|
+
const nodeExited = index.exited.get(nodeId);
|
|
143
|
+
return (
|
|
144
|
+
ancestorEntered !== undefined
|
|
145
|
+
&& ancestorExited !== undefined
|
|
146
|
+
&& nodeEntered !== undefined
|
|
147
|
+
&& nodeExited !== undefined
|
|
148
|
+
&& ancestorEntered <= nodeEntered
|
|
149
|
+
&& nodeExited <= ancestorExited
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
82
153
|
module.exports = { normalizeTreeSnapshot };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function invalidSymlink(node, message) {
|
|
4
|
+
throw Object.assign(new Error(message), {
|
|
5
|
+
code: 'WORKSPACE_TREE_SYMLINK_ESCAPE',
|
|
6
|
+
reason: 'symlink-escapes-workspace',
|
|
7
|
+
statusCode: 409,
|
|
8
|
+
nodeId: node?.id || null,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function portableTargetSegments(target, node) {
|
|
13
|
+
if (
|
|
14
|
+
typeof target !== 'string'
|
|
15
|
+
|| !target
|
|
16
|
+
|| target.includes('\0')
|
|
17
|
+
|| /^[\\/]/.test(target)
|
|
18
|
+
|| /^[A-Za-z]:/.test(target)
|
|
19
|
+
) {
|
|
20
|
+
invalidSymlink(node, 'workspace tree symlink target must be relative');
|
|
21
|
+
}
|
|
22
|
+
return target.split(/[\\/]+/);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function liveNode(node) {
|
|
26
|
+
return Boolean(node && !node.trashedAt && !node.purgedAt);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function logicalParentSegments(nodes, rootId, node) {
|
|
30
|
+
const segments = [];
|
|
31
|
+
const seen = new Set([node.id]);
|
|
32
|
+
let currentId = node.parentId;
|
|
33
|
+
while (currentId && currentId !== rootId) {
|
|
34
|
+
if (seen.has(currentId)) {
|
|
35
|
+
invalidSymlink(node, 'workspace tree symlink ancestry contains a cycle');
|
|
36
|
+
}
|
|
37
|
+
seen.add(currentId);
|
|
38
|
+
const current = nodes.get(currentId);
|
|
39
|
+
if (!current) {
|
|
40
|
+
invalidSymlink(node, 'workspace tree symlink parent is missing');
|
|
41
|
+
}
|
|
42
|
+
segments.push(current.name);
|
|
43
|
+
currentId = current.parentId;
|
|
44
|
+
}
|
|
45
|
+
if (currentId !== rootId) {
|
|
46
|
+
invalidSymlink(node, 'workspace tree symlink is outside its workspace');
|
|
47
|
+
}
|
|
48
|
+
return segments.reverse();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateLiveSymlinkTargets(inputNodes, rootId) {
|
|
52
|
+
const nodes = inputNodes instanceof Map
|
|
53
|
+
? inputNodes
|
|
54
|
+
: new Map((inputNodes || []).map((node) => [node.id, node]));
|
|
55
|
+
for (const node of nodes.values()) {
|
|
56
|
+
if (node.kind !== 'symlink' || !liveNode(node)) continue;
|
|
57
|
+
const resolved = logicalParentSegments(nodes, rootId, node);
|
|
58
|
+
for (const segment of portableTargetSegments(node.symlinkTarget, node)) {
|
|
59
|
+
if (!segment || segment === '.') continue;
|
|
60
|
+
if (segment === '..') {
|
|
61
|
+
if (!resolved.length) {
|
|
62
|
+
invalidSymlink(
|
|
63
|
+
node,
|
|
64
|
+
`workspace tree symlink ${node.id} escapes its workspace`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
resolved.pop();
|
|
68
|
+
} else {
|
|
69
|
+
resolved.push(segment);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return nodes;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { validateLiveSymlinkTargets };
|