amalgm 0.1.245 → 0.1.246
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/lib/cli.js +146 -8
- package/lib/layout.js +89 -4
- package/lib/shared-realtime-tunnel.js +384 -43
- package/lib/supervisor.js +0 -3
- package/package.json +2 -2
- package/runtime/scripts/amalgm-mcp/adapters/contract.js +197 -0
- package/runtime/scripts/amalgm-mcp/adapters/filesystem.js +135 -0
- package/runtime/scripts/amalgm-mcp/adapters/git.js +84 -0
- package/runtime/scripts/amalgm-mcp/adapters/index.js +43 -0
- package/runtime/scripts/amalgm-mcp/adapters/reference.js +31 -0
- package/runtime/scripts/amalgm-mcp/adapters/truth.js +19 -0
- package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +20 -68
- package/runtime/scripts/amalgm-mcp/index.js +4 -0
- package/runtime/scripts/amalgm-mcp/lib/layout.js +89 -4
- package/runtime/scripts/amalgm-mcp/observer/README.md +259 -0
- package/runtime/scripts/amalgm-mcp/observer/apply.js +150 -0
- package/runtime/scripts/amalgm-mcp/observer/continuity.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/edges.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/index.js +1146 -0
- package/runtime/scripts/amalgm-mcp/observer/scan.js +387 -0
- package/runtime/scripts/amalgm-mcp/observer/store.js +262 -0
- package/runtime/scripts/amalgm-mcp/observer/verify.js +200 -0
- package/runtime/scripts/amalgm-mcp/observer/watch.js +62 -0
- package/runtime/scripts/amalgm-mcp/project-context/store.js +19 -87
- package/runtime/scripts/amalgm-mcp/registration/classify.js +33 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-cloud.js +456 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-content.js +345 -0
- package/runtime/scripts/amalgm-mcp/registration/index.js +752 -0
- package/runtime/scripts/amalgm-mcp/registration/refusal.js +42 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-followers.js +127 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-states.js +109 -0
- package/runtime/scripts/amalgm-mcp/registration/service.js +310 -0
- package/runtime/scripts/amalgm-mcp/registration/tree.js +178 -0
- package/runtime/scripts/amalgm-mcp/registry/evidence.js +826 -0
- package/runtime/scripts/amalgm-mcp/registry/index.js +666 -0
- package/runtime/scripts/amalgm-mcp/registry/store.js +290 -0
- package/runtime/scripts/amalgm-mcp/repocard/README.md +103 -0
- package/runtime/scripts/amalgm-mcp/repocard/apply.js +111 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture-worker.js +94 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture.js +149 -0
- package/runtime/scripts/amalgm-mcp/repocard/follow.js +310 -0
- package/runtime/scripts/amalgm-mcp/repocard/git.js +48 -0
- package/runtime/scripts/amalgm-mcp/repocard/index.js +48 -0
- package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
- package/runtime/scripts/amalgm-mcp/server/routes/entities.js +287 -0
- package/runtime/scripts/amalgm-mcp/server/routes/state.js +0 -13
- package/runtime/scripts/amalgm-mcp/state/attachments.js +11 -45
- package/runtime/scripts/amalgm-mcp/state/db.js +46 -215
- package/runtime/scripts/amalgm-mcp/state/docs.js +301 -1101
- package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +0 -99
- package/runtime/scripts/amalgm-mcp/state/mutations.js +130 -507
- package/runtime/scripts/amalgm-mcp/state/promotions.js +1 -21
- package/runtime/scripts/amalgm-mcp/state/replicas.js +13 -134
- package/runtime/scripts/amalgm-mcp/state/shared-rest.js +3 -46
- package/runtime/scripts/amalgm-mcp/tests/adapters.test.js +379 -0
- package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +0 -193
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.life.test.js +449 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.rig.js +107 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.watch.test.js +213 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/adapter.js +72 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/assert.js +497 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/bind.js +91 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/graph.js +1031 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/ledger.js +234 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/referee.js +118 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.oracle.test.js +1365 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.registry.test.js +967 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.rig.js +239 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.storm.test.js +486 -0
- package/runtime/scripts/amalgm-mcp/tests/fake-watch.js +35 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.links.test.js +289 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.rig.js +103 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.roots.test.js +505 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.storm.test.js +389 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.territory.test.js +845 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.test.js +657 -0
- package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +17 -32
- package/runtime/scripts/amalgm-mcp/tests/registration.service.test.js +256 -0
- package/runtime/scripts/amalgm-mcp/tests/registration.test.js +931 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.storm.test.js +284 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.test.js +371 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.storm.test.js +435 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.test.js +675 -0
- package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +0 -84
- package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +13 -376
- package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +0 -309
- package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +30 -758
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +3 -72
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +1 -89
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +24 -55
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +141 -424
- package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +3 -171
- package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +7 -220
- package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +236 -179
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +326 -2228
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +3 -5319
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +1 -191
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +17 -3140
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +740 -137
- package/runtime/scripts/amalgm-mcp/workspace/access-store.js +1 -5
- package/runtime/scripts/amalgm-mcp/workspace/card.js +11 -35
- package/runtime/scripts/amalgm-mcp/workspace/cards.js +14 -39
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +147 -587
- package/runtime/scripts/amalgm-mcp/workspace/git.js +34 -406
- package/runtime/scripts/amalgm-mcp/workspace/merge.js +181 -102
- package/runtime/scripts/amalgm-mcp/workspace/objects.js +56 -221
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +538 -1964
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +28 -49
- package/runtime/scripts/amalgm-mcp/workspace/store.js +4 -77
- package/runtime/scripts/amalgm-mcp/workspace/transition.js +192 -1734
- package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +1 -7
- package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +61 -73
- package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +6 -51
- package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +8 -79
- package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +146 -3461
- package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +71 -899
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +538 -370
- package/runtime/scripts/local-gateway.js +1 -0
- package/runtime/scripts/amalgm-mcp/state/doc-disk.js +0 -550
- package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +0 -399
- package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +0 -1
- package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +0 -24
- package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +0 -1999
- package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +0 -364
- package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +0 -185
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +0 -26
- package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +0 -119
- package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +0 -1446
- package/runtime/scripts/amalgm-mcp/workspace/content.js +0 -32
- package/runtime/scripts/amalgm-mcp/workspace/durable.js +0 -45
- package/runtime/scripts/amalgm-mcp/workspace/intent.js +0 -535
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +0 -31
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +0 -72
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +0 -165
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +0 -5
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +0 -2
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +0 -2201
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +0 -19
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +0 -33
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +0 -47
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +0 -43
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +0 -114
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +0 -84
- package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +0 -771
- package/runtime/scripts/amalgm-mcp/workspace/provenance.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +0 -100
- package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +0 -127
- package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +0 -12
- package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +0 -125
- package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +0 -76
- package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +0 -3766
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The complete provenance transition relation for managed repo bytes.
|
|
5
|
-
*
|
|
6
|
-
* This is deliberately data, not a second reconciler. Runtime code may name
|
|
7
|
-
* one of these transitions, and tests exhaust the whole relation.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const CLASS = Object.freeze({
|
|
11
|
-
OFFICIAL: 'officially-applied',
|
|
12
|
-
INTENT: 'journaled-local-intent',
|
|
13
|
-
UNKNOWN: 'unknown-provenance',
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const STATE = Object.freeze({
|
|
17
|
-
OFFICIAL: CLASS.OFFICIAL,
|
|
18
|
-
UNKNOWN: CLASS.UNKNOWN,
|
|
19
|
-
CAPTURED: `${CLASS.INTENT}:captured`,
|
|
20
|
-
RESERVED: `${CLASS.INTENT}:reserved`,
|
|
21
|
-
NUMBERED: `${CLASS.INTENT}:numbered`,
|
|
22
|
-
FOLDED: `${CLASS.INTENT}:folded`,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const ACTION = Object.freeze({
|
|
26
|
-
SAVE: 'save',
|
|
27
|
-
VERIFY: 'verify',
|
|
28
|
-
CAPTURE: 'capture',
|
|
29
|
-
RESERVE: 'reserve',
|
|
30
|
-
RETRY: 'retry',
|
|
31
|
-
NUMBER: 'number',
|
|
32
|
-
FOLD: 'fold',
|
|
33
|
-
APPLY: 'apply',
|
|
34
|
-
SNAPSHOT: 'snapshot',
|
|
35
|
-
COMPACT: 'compact',
|
|
36
|
-
PRUNE: 'prune',
|
|
37
|
-
RECOVER: 'recover',
|
|
38
|
-
OBSERVE_UNKNOWN: 'observe-unknown',
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const allStates = Object.freeze(Object.values(STATE));
|
|
42
|
-
|
|
43
|
-
function transition(from, action, to, facts) {
|
|
44
|
-
return Object.freeze({
|
|
45
|
-
from,
|
|
46
|
-
action,
|
|
47
|
-
to,
|
|
48
|
-
facts: Object.freeze(facts),
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const transitions = [
|
|
53
|
-
transition(STATE.OFFICIAL, ACTION.SAVE, STATE.UNKNOWN, [
|
|
54
|
-
'previous-provenance-preserved',
|
|
55
|
-
]),
|
|
56
|
-
...[
|
|
57
|
-
STATE.CAPTURED,
|
|
58
|
-
STATE.RESERVED,
|
|
59
|
-
STATE.NUMBERED,
|
|
60
|
-
STATE.FOLDED,
|
|
61
|
-
].map((from) => transition(from, ACTION.SAVE, STATE.UNKNOWN, [
|
|
62
|
-
'previous-provenance-preserved',
|
|
63
|
-
])),
|
|
64
|
-
transition(STATE.UNKNOWN, ACTION.OBSERVE_UNKNOWN, STATE.UNKNOWN, [
|
|
65
|
-
'exact-physical-observation',
|
|
66
|
-
]),
|
|
67
|
-
transition(STATE.UNKNOWN, ACTION.VERIFY, STATE.OFFICIAL, [
|
|
68
|
-
'official-target',
|
|
69
|
-
'physical-verification',
|
|
70
|
-
'applied-cursor',
|
|
71
|
-
]),
|
|
72
|
-
transition(STATE.UNKNOWN, ACTION.CAPTURE, STATE.CAPTURED, [
|
|
73
|
-
'verified-base',
|
|
74
|
-
'exact-local-state',
|
|
75
|
-
'physical-digest',
|
|
76
|
-
'intent-record',
|
|
77
|
-
]),
|
|
78
|
-
transition(STATE.CAPTURED, ACTION.RESERVE, STATE.RESERVED, [
|
|
79
|
-
'intent-record',
|
|
80
|
-
'publication-id',
|
|
81
|
-
]),
|
|
82
|
-
transition(STATE.RESERVED, ACTION.RESERVE, STATE.RESERVED, [
|
|
83
|
-
'same-publication-id',
|
|
84
|
-
'same-envelope',
|
|
85
|
-
]),
|
|
86
|
-
transition(STATE.RESERVED, ACTION.RETRY, STATE.RESERVED, [
|
|
87
|
-
'same-publication-id',
|
|
88
|
-
'same-envelope',
|
|
89
|
-
]),
|
|
90
|
-
transition(STATE.RESERVED, ACTION.NUMBER, STATE.NUMBERED, [
|
|
91
|
-
'publication-id',
|
|
92
|
-
'official-version',
|
|
93
|
-
'server-receipt',
|
|
94
|
-
]),
|
|
95
|
-
transition(STATE.NUMBERED, ACTION.NUMBER, STATE.NUMBERED, [
|
|
96
|
-
'same-publication-id',
|
|
97
|
-
'same-official-version',
|
|
98
|
-
]),
|
|
99
|
-
transition(STATE.NUMBERED, ACTION.FOLD, STATE.FOLDED, [
|
|
100
|
-
'predecessor-folded',
|
|
101
|
-
'official-event',
|
|
102
|
-
'official-snapshot',
|
|
103
|
-
]),
|
|
104
|
-
transition(STATE.FOLDED, ACTION.FOLD, STATE.FOLDED, [
|
|
105
|
-
'same-official-version',
|
|
106
|
-
'official-snapshot',
|
|
107
|
-
]),
|
|
108
|
-
transition(STATE.FOLDED, ACTION.APPLY, STATE.OFFICIAL, [
|
|
109
|
-
'official-target',
|
|
110
|
-
'apply-wal',
|
|
111
|
-
'physical-verification',
|
|
112
|
-
'applied-cursor',
|
|
113
|
-
'intent-retired',
|
|
114
|
-
]),
|
|
115
|
-
transition(STATE.OFFICIAL, ACTION.APPLY, STATE.OFFICIAL, [
|
|
116
|
-
'contiguous-official-target',
|
|
117
|
-
'apply-wal',
|
|
118
|
-
'physical-verification',
|
|
119
|
-
'applied-cursor',
|
|
120
|
-
]),
|
|
121
|
-
...[
|
|
122
|
-
STATE.CAPTURED,
|
|
123
|
-
STATE.RESERVED,
|
|
124
|
-
STATE.NUMBERED,
|
|
125
|
-
].map((state) => transition(state, ACTION.APPLY, state, [
|
|
126
|
-
'contiguous-official-target',
|
|
127
|
-
'apply-wal',
|
|
128
|
-
'pending-intent-preserved',
|
|
129
|
-
'physical-verification',
|
|
130
|
-
'applied-cursor',
|
|
131
|
-
])),
|
|
132
|
-
transition(STATE.OFFICIAL, ACTION.SNAPSHOT, STATE.OFFICIAL, [
|
|
133
|
-
'verified-snapshot',
|
|
134
|
-
'apply-wal',
|
|
135
|
-
'physical-verification',
|
|
136
|
-
'applied-cursor',
|
|
137
|
-
]),
|
|
138
|
-
transition(STATE.UNKNOWN, ACTION.SNAPSHOT, STATE.OFFICIAL, [
|
|
139
|
-
'empty-path',
|
|
140
|
-
'verified-snapshot',
|
|
141
|
-
'apply-wal',
|
|
142
|
-
'physical-verification',
|
|
143
|
-
'applied-cursor',
|
|
144
|
-
]),
|
|
145
|
-
transition(STATE.UNKNOWN, ACTION.SNAPSHOT, STATE.UNKNOWN, [
|
|
146
|
-
'unknown-bytes-preserved',
|
|
147
|
-
]),
|
|
148
|
-
...[
|
|
149
|
-
STATE.CAPTURED,
|
|
150
|
-
STATE.RESERVED,
|
|
151
|
-
].map((state) => transition(state, ACTION.SNAPSHOT, state, [
|
|
152
|
-
'verified-snapshot',
|
|
153
|
-
'apply-wal',
|
|
154
|
-
'pending-intent-preserved',
|
|
155
|
-
'physical-verification',
|
|
156
|
-
'applied-cursor',
|
|
157
|
-
])),
|
|
158
|
-
...[
|
|
159
|
-
STATE.NUMBERED,
|
|
160
|
-
STATE.FOLDED,
|
|
161
|
-
].map((state) => transition(state, ACTION.SNAPSHOT, STATE.OFFICIAL, [
|
|
162
|
-
'numbered-event-in-snapshot',
|
|
163
|
-
'verified-snapshot',
|
|
164
|
-
'apply-wal',
|
|
165
|
-
'physical-verification',
|
|
166
|
-
'applied-cursor',
|
|
167
|
-
'intent-retired',
|
|
168
|
-
])),
|
|
169
|
-
transition(STATE.OFFICIAL, ACTION.COMPACT, STATE.OFFICIAL, [
|
|
170
|
-
'retained-server-receipt',
|
|
171
|
-
'verified-server-snapshot',
|
|
172
|
-
]),
|
|
173
|
-
transition(STATE.FOLDED, ACTION.PRUNE, STATE.FOLDED, [
|
|
174
|
-
'official-fold',
|
|
175
|
-
'durable-folded-intent',
|
|
176
|
-
]),
|
|
177
|
-
transition(STATE.OFFICIAL, ACTION.PRUNE, STATE.OFFICIAL, [
|
|
178
|
-
'official-fold',
|
|
179
|
-
'physical-verification',
|
|
180
|
-
'applied-cursor',
|
|
181
|
-
]),
|
|
182
|
-
...allStates.map((state) => transition(state, ACTION.RECOVER, state, [
|
|
183
|
-
'durable-provenance-record',
|
|
184
|
-
'apply-wal-before-destructive-write',
|
|
185
|
-
])),
|
|
186
|
-
...[
|
|
187
|
-
STATE.OFFICIAL,
|
|
188
|
-
STATE.CAPTURED,
|
|
189
|
-
STATE.RESERVED,
|
|
190
|
-
STATE.NUMBERED,
|
|
191
|
-
STATE.FOLDED,
|
|
192
|
-
].map((state) => transition(state, ACTION.RECOVER, STATE.UNKNOWN, [
|
|
193
|
-
'apply-wal',
|
|
194
|
-
'exact-third-value-observation',
|
|
195
|
-
'previous-provenance-preserved',
|
|
196
|
-
])),
|
|
197
|
-
];
|
|
198
|
-
|
|
199
|
-
const TRANSITIONS = Object.freeze(transitions);
|
|
200
|
-
const transitionByKey = new Map(
|
|
201
|
-
TRANSITIONS.map((entry) => [`${entry.from}\u0000${entry.action}\u0000${entry.to}`, entry]),
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
function provenanceClass(state) {
|
|
205
|
-
if (state === STATE.OFFICIAL) return CLASS.OFFICIAL;
|
|
206
|
-
if (state === STATE.UNKNOWN) return CLASS.UNKNOWN;
|
|
207
|
-
if (typeof state === 'string' && state.startsWith(`${CLASS.INTENT}:`)) {
|
|
208
|
-
return CLASS.INTENT;
|
|
209
|
-
}
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function publicationState(publication) {
|
|
214
|
-
if (!publication) return STATE.CAPTURED;
|
|
215
|
-
if (publication.foldedAt != null) return STATE.FOLDED;
|
|
216
|
-
if (publication.numberedVersion != null) return STATE.NUMBERED;
|
|
217
|
-
return STATE.RESERVED;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function requireTransition(from, action, to, availableFacts = []) {
|
|
221
|
-
const entry = transitionByKey.get(`${from}\u0000${action}\u0000${to}`);
|
|
222
|
-
if (!entry) throw new Error(`illegal provenance transition: ${from} --${action}--> ${to}`);
|
|
223
|
-
const available = new Set(availableFacts);
|
|
224
|
-
const missing = entry.facts.filter((fact) => !available.has(fact));
|
|
225
|
-
if (missing.length > 0) {
|
|
226
|
-
throw new Error(`provenance transition ${action} is missing: ${missing.join(', ')}`);
|
|
227
|
-
}
|
|
228
|
-
return entry;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
module.exports = {
|
|
232
|
-
ACTION,
|
|
233
|
-
CLASS,
|
|
234
|
-
STATE,
|
|
235
|
-
TRANSITIONS,
|
|
236
|
-
allStates,
|
|
237
|
-
provenanceClass,
|
|
238
|
-
publicationState,
|
|
239
|
-
requireTransition,
|
|
240
|
-
};
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Referee projection for the floor, physical verification, and local overlay.
|
|
5
|
-
*
|
|
6
|
-
* Referee axiom: a gate must see every active or successor intent. Proven by
|
|
7
|
-
* workspace-sync-stats.test.js and workspace-sync-harness-core.test.mjs.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const crypto = require('crypto');
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const { listRepoCards, repoCardServiceStats } = require('./cards');
|
|
14
|
-
const { repoKeyOfCard } = require('./checkpoint');
|
|
15
|
-
const { machineId, userId } = require('./identity');
|
|
16
|
-
const projectStream = require('./project-stream');
|
|
17
|
-
const reducer = require('./reducer');
|
|
18
|
-
const { listWorkspaces } = require('./store');
|
|
19
|
-
const wall = require('./wall');
|
|
20
|
-
const runtimeVersion = require('../../../../package.json').version;
|
|
21
|
-
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
|
|
22
|
-
const RUNTIME_ROOT = path.resolve(__dirname, '..');
|
|
23
|
-
|
|
24
|
-
function filesUnder(root) {
|
|
25
|
-
return fs.readdirSync(root, { withFileTypes: true })
|
|
26
|
-
.flatMap((entry) => {
|
|
27
|
-
const file = path.join(root, entry.name);
|
|
28
|
-
if (entry.isDirectory()) return filesUnder(file);
|
|
29
|
-
return entry.isFile() ? [file] : [];
|
|
30
|
-
})
|
|
31
|
-
.sort();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// The sync identity follows the architecture tree, not a hand-maintained
|
|
35
|
-
// dependency list. New state/materialization modules and native workspace
|
|
36
|
-
// helpers therefore enter the release identity automatically.
|
|
37
|
-
const SYNC_FILES = [
|
|
38
|
-
path.join(RUNTIME_ROOT, 'state'),
|
|
39
|
-
path.join(RUNTIME_ROOT, 'workspace'),
|
|
40
|
-
].flatMap(filesUnder);
|
|
41
|
-
|
|
42
|
-
function fileFingerprint(files) {
|
|
43
|
-
const hash = crypto.createHash('sha256');
|
|
44
|
-
for (const file of files.filter((candidate) => fs.existsSync(candidate)).sort()) {
|
|
45
|
-
hash.update(path.relative(PACKAGE_ROOT, file));
|
|
46
|
-
hash.update('\0');
|
|
47
|
-
hash.update(fs.readFileSync(file));
|
|
48
|
-
hash.update('\0');
|
|
49
|
-
}
|
|
50
|
-
return `sha256:${hash.digest('hex')}`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function sharedTunnelFiles() {
|
|
54
|
-
return [
|
|
55
|
-
path.join(PACKAGE_ROOT, 'lib', 'shared-realtime-tunnel.js'),
|
|
56
|
-
path.join(PACKAGE_ROOT, 'packages', 'amalgm', 'lib', 'shared-realtime-tunnel.js'),
|
|
57
|
-
].filter((file) => fs.existsSync(file));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const runtimeBuildFingerprint = fileFingerprint([
|
|
61
|
-
path.join(PACKAGE_ROOT, 'package.json'),
|
|
62
|
-
...filesUnder(RUNTIME_ROOT),
|
|
63
|
-
...sharedTunnelFiles(),
|
|
64
|
-
]);
|
|
65
|
-
const runtimeSyncFingerprint = fileFingerprint([
|
|
66
|
-
...SYNC_FILES,
|
|
67
|
-
...sharedTunnelFiles(),
|
|
68
|
-
]);
|
|
69
|
-
|
|
70
|
-
function canonical(value) {
|
|
71
|
-
if (Array.isArray(value)) return value.map(canonical);
|
|
72
|
-
if (!value || typeof value !== 'object') return value;
|
|
73
|
-
return Object.fromEntries(
|
|
74
|
-
Object.keys(value).sort().map((key) => [key, canonical(value[key])]),
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function digest(value) {
|
|
79
|
-
return crypto.createHash('sha256')
|
|
80
|
-
.update(JSON.stringify(canonical(value)))
|
|
81
|
-
.digest('hex');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function samePath(left, right) {
|
|
85
|
-
return Boolean(left && right && path.resolve(left) === path.resolve(right));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function folderFor(project, cards, workspaces) {
|
|
89
|
-
const card = cards.find((entry) => (
|
|
90
|
-
entry.kind === 'primary'
|
|
91
|
-
&& repoKeyOfCard(entry) === project.repoKey
|
|
92
|
-
));
|
|
93
|
-
const folderPath = project.path || card?.path || null;
|
|
94
|
-
const workspace = workspaces.find((entry) => samePath(entry.path, folderPath));
|
|
95
|
-
return {
|
|
96
|
-
card,
|
|
97
|
-
folderPath,
|
|
98
|
-
folderUuid: workspace?.id || null,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function waitingFor(state, outcome) {
|
|
103
|
-
const waiting = [];
|
|
104
|
-
if (outcome?.state === 'waiting' || outcome?.state === 'error') {
|
|
105
|
-
waiting.push({
|
|
106
|
-
state: outcome.state,
|
|
107
|
-
reason: outcome.reason || null,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
return waiting;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function verificationFor(state, outcome, pendingIntent = null) {
|
|
114
|
-
return {
|
|
115
|
-
contract: state?.verificationContract || null,
|
|
116
|
-
appliedThrough: Number.isSafeInteger(state?.appliedThrough)
|
|
117
|
-
? state.appliedThrough
|
|
118
|
-
: null,
|
|
119
|
-
materialized: state?.materialized === true,
|
|
120
|
-
targetDigest: state?.targetDigest || null,
|
|
121
|
-
expectedPhysicalDigest: state?.expectedPhysicalDigest || null,
|
|
122
|
-
observedPhysicalDigest: state?.observedPhysicalDigest || null,
|
|
123
|
-
pendingIntent,
|
|
124
|
-
waiting: waitingFor(state, outcome),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function pendingIntentValue(value, owned) {
|
|
129
|
-
const successor = value?.successor || null;
|
|
130
|
-
if (!owned && !successor) return null;
|
|
131
|
-
const result = {
|
|
132
|
-
baseThrough: value.base.through,
|
|
133
|
-
semanticDigest: value.semanticDigest,
|
|
134
|
-
mutationId: value.birthMutationId || value.publication?.mutationId || null,
|
|
135
|
-
terminalCode: value.terminal?.code || null,
|
|
136
|
-
};
|
|
137
|
-
if (successor) {
|
|
138
|
-
result.successor = {
|
|
139
|
-
predecessorSemanticDigest: successor.predecessorSemanticDigest,
|
|
140
|
-
semanticDigest: successor.semanticDigest,
|
|
141
|
-
mutationId: successor.birthMutationId || null,
|
|
142
|
-
terminalCode: successor.terminal?.code || null,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
return result;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function pendingIntentFor(project) {
|
|
149
|
-
const intents = require('./intent');
|
|
150
|
-
const value = intents.readIntent(project);
|
|
151
|
-
return pendingIntentValue(
|
|
152
|
-
value,
|
|
153
|
-
Boolean(value && !intents.officialAcknowledgement(project, value)),
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function autoUpdateDisabled(env = process.env) {
|
|
158
|
-
const disabled = String(env.AMALGM_DISABLE_AUTO_UPDATE || '').trim().toLowerCase();
|
|
159
|
-
if (['1', 'true', 'yes', 'on'].includes(disabled)) return true;
|
|
160
|
-
const enabled = String(env.AMALGM_AUTO_UPDATE || '').trim().toLowerCase();
|
|
161
|
-
return ['0', 'false', 'no', 'off'].includes(enabled);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function projectObservation({
|
|
165
|
-
project,
|
|
166
|
-
cards,
|
|
167
|
-
workspaces,
|
|
168
|
-
reducerRepos,
|
|
169
|
-
}) {
|
|
170
|
-
const folder = folderFor(project, cards, workspaces);
|
|
171
|
-
const state = reducer.readRepoState(project.repoKey, project.resourceId);
|
|
172
|
-
const exactState = (
|
|
173
|
-
state
|
|
174
|
-
&& folder.folderPath
|
|
175
|
-
&& samePath(state.path, folder.folderPath)
|
|
176
|
-
&& (!folder.card || state.replica === folder.card.replica)
|
|
177
|
-
) ? state : null;
|
|
178
|
-
const snapshot = projectStream.readSnapshot(project.file, project.resourceId);
|
|
179
|
-
const publication = wall.publicationStatus(project.repoKey, exactState);
|
|
180
|
-
const outcome = reducerRepos.find((entry) => (
|
|
181
|
-
entry.repoKey === project.repoKey
|
|
182
|
-
&& (!folder.folderPath || !entry.path || samePath(entry.path, folder.folderPath))
|
|
183
|
-
)) || null;
|
|
184
|
-
return {
|
|
185
|
-
resourceId: project.resourceId,
|
|
186
|
-
repoKey: project.repoKey,
|
|
187
|
-
folderPath: folder.folderPath,
|
|
188
|
-
folderUuid: folder.folderUuid,
|
|
189
|
-
receivedThrough: publication.receivedThrough,
|
|
190
|
-
snapshotThrough: snapshot.version,
|
|
191
|
-
snapshotDigest: digest(snapshot),
|
|
192
|
-
pendingOutbound: publication.pending,
|
|
193
|
-
verification: verificationFor(exactState, outcome, pendingIntentFor(project)),
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function workspaceSyncStats() {
|
|
198
|
-
const cards = listRepoCards();
|
|
199
|
-
const wallState = wall.wallStats();
|
|
200
|
-
const reducerState = reducer.reducerStats();
|
|
201
|
-
const workspaces = listWorkspaces();
|
|
202
|
-
const projects = wallState.projects.map((entry) => projectObservation({
|
|
203
|
-
project: wall.projectForRepo(entry.repoKey),
|
|
204
|
-
cards,
|
|
205
|
-
workspaces,
|
|
206
|
-
reducerRepos: reducerState.repos,
|
|
207
|
-
}));
|
|
208
|
-
const health = { healthy: 0, missing: 0, broken: 0, inaccessible: 0 };
|
|
209
|
-
for (const card of cards) health[card.health] = (health[card.health] || 0) + 1;
|
|
210
|
-
return {
|
|
211
|
-
machine: machineId(),
|
|
212
|
-
user: userId(),
|
|
213
|
-
runtimeVersion,
|
|
214
|
-
runtime: {
|
|
215
|
-
buildFingerprint: runtimeBuildFingerprint,
|
|
216
|
-
syncFingerprint: runtimeSyncFingerprint,
|
|
217
|
-
autoUpdateDisabled: autoUpdateDisabled(),
|
|
218
|
-
},
|
|
219
|
-
projects,
|
|
220
|
-
cards: { total: cards.length, ...health },
|
|
221
|
-
service: repoCardServiceStats(),
|
|
222
|
-
wall: wallState,
|
|
223
|
-
checkpoints: require('./checkpoint').checkpointStats(),
|
|
224
|
-
reducer: reducerState,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
module.exports = {
|
|
229
|
-
_test: {
|
|
230
|
-
digest,
|
|
231
|
-
folderFor,
|
|
232
|
-
autoUpdateDisabled,
|
|
233
|
-
fileFingerprint,
|
|
234
|
-
syncFingerprintFiles: SYNC_FILES.map((file) => path.relative(RUNTIME_ROOT, file)),
|
|
235
|
-
pendingIntentFor,
|
|
236
|
-
pendingIntentValue,
|
|
237
|
-
verificationFor,
|
|
238
|
-
},
|
|
239
|
-
workspaceSyncStats,
|
|
240
|
-
};
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The official workspace-tree floor.
|
|
5
|
-
*
|
|
6
|
-
* `workspace_nodes` is the physical view (official floor plus durable local
|
|
7
|
-
* overlay), so it cannot also answer what an incoming official operation is
|
|
8
|
-
* allowed to reduce against. This table keeps that one separate fact.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const { openLocalDb } = require('../../state/db');
|
|
12
|
-
const { normalizeTreeSnapshot } = require('./snapshot');
|
|
13
|
-
|
|
14
|
-
function ensureSchema(database = openLocalDb()) {
|
|
15
|
-
database.exec(`
|
|
16
|
-
CREATE TABLE IF NOT EXISTS workspace_tree_floors (
|
|
17
|
-
resource_id TEXT PRIMARY KEY,
|
|
18
|
-
rail_version INTEGER NOT NULL CHECK (rail_version >= 0),
|
|
19
|
-
authority_epoch INTEGER NOT NULL CHECK (authority_epoch > 0),
|
|
20
|
-
snapshot_json TEXT NOT NULL,
|
|
21
|
-
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
22
|
-
);
|
|
23
|
-
`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function snapshotRecord(snapshot) {
|
|
27
|
-
return {
|
|
28
|
-
schemaVersion: 1,
|
|
29
|
-
workspaceId: snapshot.workspaceId,
|
|
30
|
-
version: snapshot.version,
|
|
31
|
-
nodes: Array.from(snapshot.nodes.values()),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function normalize(row) {
|
|
36
|
-
if (!row) return null;
|
|
37
|
-
const snapshot = normalizeTreeSnapshot(JSON.parse(row.snapshot_json));
|
|
38
|
-
if (
|
|
39
|
-
snapshot.workspaceId !== row.resource_id
|
|
40
|
-
|| snapshot.version !== Number(row.rail_version)
|
|
41
|
-
) {
|
|
42
|
-
throw new Error(`workspace tree floor ${row.resource_id} is inconsistent`);
|
|
43
|
-
}
|
|
44
|
-
return {
|
|
45
|
-
resourceId: row.resource_id,
|
|
46
|
-
version: Number(row.rail_version),
|
|
47
|
-
authorityEpoch: Number(row.authority_epoch),
|
|
48
|
-
snapshot,
|
|
49
|
-
updatedAt: row.updated_at,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function read(resourceId, options = {}) {
|
|
54
|
-
const database = options.database || openLocalDb();
|
|
55
|
-
ensureSchema(database);
|
|
56
|
-
return normalize(database.prepare(`
|
|
57
|
-
SELECT * FROM workspace_tree_floors WHERE resource_id = ?
|
|
58
|
-
`).get(resourceId));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function write(snapshotInput, authorityEpochInput, options = {}) {
|
|
62
|
-
const database = options.database || openLocalDb();
|
|
63
|
-
ensureSchema(database);
|
|
64
|
-
const snapshot = normalizeTreeSnapshot(snapshotRecord(snapshotInput));
|
|
65
|
-
const authorityEpoch = Number(authorityEpochInput);
|
|
66
|
-
if (!Number.isSafeInteger(authorityEpoch) || authorityEpoch < 1) {
|
|
67
|
-
throw new Error('workspace tree floor authority epoch is invalid');
|
|
68
|
-
}
|
|
69
|
-
database.prepare(`
|
|
70
|
-
INSERT INTO workspace_tree_floors (
|
|
71
|
-
resource_id, rail_version, authority_epoch, snapshot_json, updated_at
|
|
72
|
-
) VALUES (?, ?, ?, ?, ?)
|
|
73
|
-
ON CONFLICT(resource_id) DO UPDATE SET
|
|
74
|
-
rail_version = excluded.rail_version,
|
|
75
|
-
authority_epoch = excluded.authority_epoch,
|
|
76
|
-
snapshot_json = excluded.snapshot_json,
|
|
77
|
-
updated_at = excluded.updated_at
|
|
78
|
-
`).run(
|
|
79
|
-
snapshot.workspaceId,
|
|
80
|
-
snapshot.version,
|
|
81
|
-
authorityEpoch,
|
|
82
|
-
JSON.stringify(snapshotRecord(snapshot)),
|
|
83
|
-
new Date().toISOString(),
|
|
84
|
-
);
|
|
85
|
-
return read(snapshot.workspaceId, { database });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function ensure(snapshot, authorityEpoch, options = {}) {
|
|
89
|
-
const database = options.database || openLocalDb();
|
|
90
|
-
const existing = read(snapshot.workspaceId, { database });
|
|
91
|
-
if (existing) return existing;
|
|
92
|
-
return write(snapshot, authorityEpoch, { database });
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
module.exports = {
|
|
96
|
-
ensure,
|
|
97
|
-
read,
|
|
98
|
-
snapshotRecord,
|
|
99
|
-
write,
|
|
100
|
-
};
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const crypto = require('crypto');
|
|
4
|
-
|
|
5
|
-
function stableHold(input) {
|
|
6
|
-
const operation = input?.operation || null;
|
|
7
|
-
const operationKind = typeof operation?.kind === 'string' ? operation.kind : null;
|
|
8
|
-
const explicitNodeId = typeof input?.nodeId === 'string' ? input.nodeId : null;
|
|
9
|
-
const operationNodeId = (
|
|
10
|
-
operationKind !== 'tree.create'
|
|
11
|
-
&& typeof operation?.nodeId === 'string'
|
|
12
|
-
) ? operation.nodeId : null;
|
|
13
|
-
return {
|
|
14
|
-
reason: typeof input?.reason === 'string' && input.reason
|
|
15
|
-
? input.reason
|
|
16
|
-
: 'observation-pending',
|
|
17
|
-
nodeId: explicitNodeId || operationNodeId,
|
|
18
|
-
operationKind,
|
|
19
|
-
paths: Array.from(new Set(
|
|
20
|
-
Array.isArray(input?.paths)
|
|
21
|
-
? input.paths.filter((value) => typeof value === 'string' && value)
|
|
22
|
-
: [],
|
|
23
|
-
)).sort(),
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function ownerScope(options = {}) {
|
|
28
|
-
return {
|
|
29
|
-
ownerKind: typeof options.ownerKind === 'string' && options.ownerKind
|
|
30
|
-
? options.ownerKind
|
|
31
|
-
: 'observer',
|
|
32
|
-
ownerId: typeof options.ownerId === 'string' && options.ownerId
|
|
33
|
-
? options.ownerId
|
|
34
|
-
: 'workspace-tree-observer',
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function holdKey(owner, hold) {
|
|
39
|
-
return crypto.createHash('sha256')
|
|
40
|
-
.update(JSON.stringify({ ...owner, hold }))
|
|
41
|
-
.digest('hex');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function normalize(rows) {
|
|
45
|
-
return rows.map((row) => ({
|
|
46
|
-
id: row.hold_key,
|
|
47
|
-
ownerKind: row.owner_kind,
|
|
48
|
-
ownerId: row.owner_id,
|
|
49
|
-
reason: row.reason,
|
|
50
|
-
nodeId: row.node_id || null,
|
|
51
|
-
operationKind: row.operation_kind || null,
|
|
52
|
-
paths: JSON.parse(row.paths_json),
|
|
53
|
-
firstObservedAt: row.first_observed_at,
|
|
54
|
-
}));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function list(workspaceId, database) {
|
|
58
|
-
return normalize(database.prepare(`
|
|
59
|
-
SELECT *
|
|
60
|
-
FROM workspace_tree_observation_holds
|
|
61
|
-
WHERE workspace_id = ?
|
|
62
|
-
ORDER BY owner_kind, owner_id, reason, hold_key
|
|
63
|
-
`).all(workspaceId));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function replace(workspaceId, inputs, database, options = {}) {
|
|
67
|
-
const owner = ownerScope(options);
|
|
68
|
-
const next = (Array.isArray(inputs) ? inputs : [])
|
|
69
|
-
.map(stableHold)
|
|
70
|
-
.map((hold) => ({ ...hold, key: holdKey(owner, hold) }))
|
|
71
|
-
.sort((left, right) => left.key.localeCompare(right.key));
|
|
72
|
-
const current = database.prepare(`
|
|
73
|
-
SELECT *
|
|
74
|
-
FROM workspace_tree_observation_holds
|
|
75
|
-
WHERE workspace_id = ? AND owner_kind = ? AND owner_id = ?
|
|
76
|
-
ORDER BY hold_key
|
|
77
|
-
`).all(workspaceId, owner.ownerKind, owner.ownerId);
|
|
78
|
-
const currentShape = current.map((row) => ({
|
|
79
|
-
key: row.hold_key,
|
|
80
|
-
reason: row.reason,
|
|
81
|
-
nodeId: row.node_id || null,
|
|
82
|
-
operationKind: row.operation_kind || null,
|
|
83
|
-
paths: JSON.parse(row.paths_json),
|
|
84
|
-
}));
|
|
85
|
-
const nextShape = next.map((hold) => ({
|
|
86
|
-
key: hold.key,
|
|
87
|
-
reason: hold.reason,
|
|
88
|
-
nodeId: hold.nodeId,
|
|
89
|
-
operationKind: hold.operationKind,
|
|
90
|
-
paths: hold.paths,
|
|
91
|
-
}));
|
|
92
|
-
if (JSON.stringify(currentShape) === JSON.stringify(nextShape)) return false;
|
|
93
|
-
|
|
94
|
-
const firstObservedByKey = new Map(
|
|
95
|
-
current.map((row) => [row.hold_key, row.first_observed_at]),
|
|
96
|
-
);
|
|
97
|
-
const now = new Date().toISOString();
|
|
98
|
-
database.prepare(`
|
|
99
|
-
DELETE FROM workspace_tree_observation_holds
|
|
100
|
-
WHERE workspace_id = ? AND owner_kind = ? AND owner_id = ?
|
|
101
|
-
`).run(workspaceId, owner.ownerKind, owner.ownerId);
|
|
102
|
-
const insert = database.prepare(`
|
|
103
|
-
INSERT INTO workspace_tree_observation_holds (
|
|
104
|
-
workspace_id, hold_key, owner_kind, owner_id, reason, node_id,
|
|
105
|
-
operation_kind, paths_json, first_observed_at
|
|
106
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
107
|
-
`);
|
|
108
|
-
for (const hold of next) {
|
|
109
|
-
insert.run(
|
|
110
|
-
workspaceId,
|
|
111
|
-
hold.key,
|
|
112
|
-
owner.ownerKind,
|
|
113
|
-
owner.ownerId,
|
|
114
|
-
hold.reason,
|
|
115
|
-
hold.nodeId,
|
|
116
|
-
hold.operationKind,
|
|
117
|
-
JSON.stringify(hold.paths),
|
|
118
|
-
firstObservedByKey.get(hold.key) || now,
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
module.exports = {
|
|
125
|
-
list,
|
|
126
|
-
replace,
|
|
127
|
-
};
|