@amalgm/live 0.2.26 → 0.2.28
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/PURPOSE.md +52 -29
- package/README.md +1 -2
- package/dist/contracts/index.d.ts +0 -1
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +0 -1
- package/dist/contracts/index.js.map +1 -1
- package/dist/detection/record-proposal.d.ts +3 -2
- package/dist/detection/record-proposal.d.ts.map +1 -1
- package/dist/detection/record-proposal.js +9 -7
- package/dist/detection/record-proposal.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/machines/index.d.ts +0 -2
- package/dist/machines/index.d.ts.map +1 -1
- package/dist/machines/index.js +0 -2
- package/dist/machines/index.js.map +1 -1
- package/dist/records/authority.d.ts +23 -0
- package/dist/records/authority.d.ts.map +1 -0
- package/dist/records/authority.js +28 -0
- package/dist/records/authority.js.map +1 -0
- package/dist/records/codec.d.ts +6 -0
- package/dist/records/codec.d.ts.map +1 -0
- package/dist/records/codec.js +42 -0
- package/dist/records/codec.js.map +1 -0
- package/dist/records/contracts.d.ts +46 -0
- package/dist/records/contracts.d.ts.map +1 -0
- package/dist/records/contracts.js +114 -0
- package/dist/records/contracts.js.map +1 -0
- package/dist/records/inbox.d.ts +18 -0
- package/dist/records/inbox.d.ts.map +1 -0
- package/dist/records/inbox.js +34 -0
- package/dist/records/inbox.js.map +1 -0
- package/dist/records/index.d.ts +7 -0
- package/dist/records/index.d.ts.map +1 -0
- package/dist/records/index.js +7 -0
- package/dist/records/index.js.map +1 -0
- package/dist/records/outbox.d.ts +20 -0
- package/dist/records/outbox.d.ts.map +1 -0
- package/dist/records/outbox.js +44 -0
- package/dist/records/outbox.js.map +1 -0
- package/dist/registration/user-ground.d.ts +5 -2
- package/dist/registration/user-ground.d.ts.map +1 -1
- package/dist/registration/user-ground.js +14 -3
- package/dist/registration/user-ground.js.map +1 -1
- package/dist/watching/suspicion.d.ts.map +1 -1
- package/dist/watching/suspicion.js +29 -1
- package/dist/watching/suspicion.js.map +1 -1
- package/dist-cjs/contracts/binding.js +26 -0
- package/dist-cjs/contracts/binding.js.map +1 -0
- package/dist-cjs/contracts/content.js +99 -0
- package/dist-cjs/contracts/content.js.map +1 -0
- package/dist-cjs/contracts/docs.js +56 -0
- package/dist-cjs/contracts/docs.js.map +1 -0
- package/dist-cjs/contracts/events.js +44 -0
- package/dist-cjs/contracts/events.js.map +1 -0
- package/dist-cjs/contracts/frames/gateway.js +39 -0
- package/dist-cjs/contracts/frames/gateway.js.map +1 -0
- package/dist-cjs/contracts/frames/stream.js +20 -0
- package/dist-cjs/contracts/frames/stream.js.map +1 -0
- package/dist-cjs/contracts/frames/tunnel.js +60 -0
- package/dist-cjs/contracts/frames/tunnel.js.map +1 -0
- package/dist-cjs/contracts/index.js +34 -0
- package/dist-cjs/contracts/index.js.map +1 -0
- package/dist-cjs/contracts/presence.js +38 -0
- package/dist-cjs/contracts/presence.js.map +1 -0
- package/dist-cjs/contracts/resources.js +114 -0
- package/dist-cjs/contracts/resources.js.map +1 -0
- package/dist-cjs/contracts/snapshot.js +31 -0
- package/dist-cjs/contracts/snapshot.js.map +1 -0
- package/dist-cjs/contracts/validate.js +34 -0
- package/dist-cjs/contracts/validate.js.map +1 -0
- package/dist-cjs/detection/adapter-contract.js +168 -0
- package/dist-cjs/detection/adapter-contract.js.map +1 -0
- package/dist-cjs/detection/batch.js +71 -0
- package/dist-cjs/detection/batch.js.map +1 -0
- package/dist-cjs/detection/classify.js +48 -0
- package/dist-cjs/detection/classify.js.map +1 -0
- package/dist-cjs/detection/content.js +122 -0
- package/dist-cjs/detection/content.js.map +1 -0
- package/dist-cjs/detection/continuity.js +88 -0
- package/dist-cjs/detection/continuity.js.map +1 -0
- package/dist-cjs/detection/diff.js +193 -0
- package/dist-cjs/detection/diff.js.map +1 -0
- package/dist-cjs/detection/echo.js +25 -0
- package/dist-cjs/detection/echo.js.map +1 -0
- package/dist-cjs/detection/enrollment.js +189 -0
- package/dist-cjs/detection/enrollment.js.map +1 -0
- package/dist-cjs/detection/index.js +57 -0
- package/dist-cjs/detection/index.js.map +1 -0
- package/dist-cjs/detection/record-proposal.js +170 -0
- package/dist-cjs/detection/record-proposal.js.map +1 -0
- package/dist-cjs/detection/refusal.js +44 -0
- package/dist-cjs/detection/refusal.js.map +1 -0
- package/dist-cjs/detection/repository.js +67 -0
- package/dist-cjs/detection/repository.js.map +1 -0
- package/dist-cjs/detection/territory.js +82 -0
- package/dist-cjs/detection/territory.js.map +1 -0
- package/dist-cjs/detection/watch-boundary.js +51 -0
- package/dist-cjs/detection/watch-boundary.js.map +1 -0
- package/dist-cjs/entities/bytes.js +92 -0
- package/dist-cjs/entities/bytes.js.map +1 -0
- package/dist-cjs/entities/cloud.js +171 -0
- package/dist-cjs/entities/cloud.js.map +1 -0
- package/dist-cjs/entities/index.js +39 -0
- package/dist-cjs/entities/index.js.map +1 -0
- package/dist-cjs/entities/moves.js +85 -0
- package/dist-cjs/entities/moves.js.map +1 -0
- package/dist-cjs/entities/repo-states.js +256 -0
- package/dist-cjs/entities/repo-states.js.map +1 -0
- package/dist-cjs/entities/rules.js +216 -0
- package/dist-cjs/entities/rules.js.map +1 -0
- package/dist-cjs/entities/types.js +44 -0
- package/dist-cjs/entities/types.js.map +1 -0
- package/dist-cjs/files/commands.js +120 -0
- package/dist-cjs/files/commands.js.map +1 -0
- package/dist-cjs/files/index.js +19 -0
- package/dist-cjs/files/index.js.map +1 -0
- package/dist-cjs/index.js +39 -0
- package/dist-cjs/index.js.map +1 -0
- package/dist-cjs/machines/arbitration.js +41 -0
- package/dist-cjs/machines/arbitration.js.map +1 -0
- package/dist-cjs/machines/backoff.js +38 -0
- package/dist-cjs/machines/backoff.js.map +1 -0
- package/dist-cjs/machines/bootstrap.js +110 -0
- package/dist-cjs/machines/bootstrap.js.map +1 -0
- package/dist-cjs/machines/chunks.js +72 -0
- package/dist-cjs/machines/chunks.js.map +1 -0
- package/dist-cjs/machines/index.js +28 -0
- package/dist-cjs/machines/index.js.map +1 -0
- package/dist-cjs/machines/presence.js +53 -0
- package/dist-cjs/machines/presence.js.map +1 -0
- package/dist-cjs/merge/index.js +25 -0
- package/dist-cjs/merge/index.js.map +1 -0
- package/dist-cjs/merge/merge3.js +191 -0
- package/dist-cjs/merge/merge3.js.map +1 -0
- package/dist-cjs/package.json +1 -0
- package/dist-cjs/records/authority.js +31 -0
- package/dist-cjs/records/authority.js.map +1 -0
- package/dist-cjs/records/codec.js +48 -0
- package/dist-cjs/records/codec.js.map +1 -0
- package/dist-cjs/records/contracts.js +121 -0
- package/dist-cjs/records/contracts.js.map +1 -0
- package/dist-cjs/records/inbox.js +37 -0
- package/dist-cjs/records/inbox.js.map +1 -0
- package/dist-cjs/records/index.js +23 -0
- package/dist-cjs/records/index.js.map +1 -0
- package/dist-cjs/records/outbox.js +48 -0
- package/dist-cjs/records/outbox.js.map +1 -0
- package/dist-cjs/registration/index.js +19 -0
- package/dist-cjs/registration/index.js.map +1 -0
- package/dist-cjs/registration/user-ground.js +183 -0
- package/dist-cjs/registration/user-ground.js.map +1 -0
- package/dist-cjs/transfer/artifacts.js +75 -0
- package/dist-cjs/transfer/artifacts.js.map +1 -0
- package/dist-cjs/transfer/batches.js +44 -0
- package/dist-cjs/transfer/batches.js.map +1 -0
- package/dist-cjs/transfer/download.js +88 -0
- package/dist-cjs/transfer/download.js.map +1 -0
- package/dist-cjs/transfer/index.js +21 -0
- package/dist-cjs/transfer/index.js.map +1 -0
- package/dist-cjs/transfer/parallel.js +35 -0
- package/dist-cjs/transfer/parallel.js.map +1 -0
- package/dist-cjs/transfer/upload.js +161 -0
- package/dist-cjs/transfer/upload.js.map +1 -0
- package/dist-cjs/watching/coverage.js +54 -0
- package/dist-cjs/watching/coverage.js.map +1 -0
- package/dist-cjs/watching/index.js +24 -0
- package/dist-cjs/watching/index.js.map +1 -0
- package/dist-cjs/watching/suspicion.js +105 -0
- package/dist-cjs/watching/suspicion.js.map +1 -0
- package/dist-cjs/workspace/api.js +66 -0
- package/dist-cjs/workspace/api.js.map +1 -0
- package/dist-cjs/workspace/identity.js +32 -0
- package/dist-cjs/workspace/identity.js.map +1 -0
- package/dist-cjs/workspace/index.js +14 -0
- package/dist-cjs/workspace/index.js.map +1 -0
- package/dist-cjs/workspace/memory-store.js +21 -0
- package/dist-cjs/workspace/memory-store.js.map +1 -0
- package/dist-cjs/workspace/registry.js +98 -0
- package/dist-cjs/workspace/registry.js.map +1 -0
- package/dist-cjs/workspace/types.js +3 -0
- package/dist-cjs/workspace/types.js.map +1 -0
- package/package.json +30 -8
- package/dist/contracts/mutation.d.ts +0 -51
- package/dist/contracts/mutation.d.ts.map +0 -1
- package/dist/contracts/mutation.js +0 -55
- package/dist/contracts/mutation.js.map +0 -1
- package/dist/machines/journal.d.ts +0 -63
- package/dist/machines/journal.d.ts.map +0 -1
- package/dist/machines/journal.js +0 -71
- package/dist/machines/journal.js.map +0 -1
- package/dist/machines/ordering.d.ts +0 -53
- package/dist/machines/ordering.d.ts.map +0 -1
- package/dist/machines/ordering.js +0 -87
- package/dist/machines/ordering.js.map +0 -1
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The compare core of detection: rows (store memory) + entries (disk
|
|
4
|
+
* truth) in, actions out. Pure — the walk that produces entries and the
|
|
5
|
+
* hashing that settles `checks` are the host's; only the identity
|
|
6
|
+
* verdicts live here. Ratified by entity-model.md ("Address and
|
|
7
|
+
* physical continuity are the two identity witnesses, never content").
|
|
8
|
+
*
|
|
9
|
+
* Three kinds of channel, three orderings of the same two witnesses:
|
|
10
|
+
*
|
|
11
|
+
* - Files: path claims first; the inode rescues path-orphans. An
|
|
12
|
+
* atomic save replaces a file's inode in place, so the address must
|
|
13
|
+
* absorb that churn.
|
|
14
|
+
* - Directories: physical continuity claims first; the address claims
|
|
15
|
+
* what no inode claims. Nothing replaces a directory's inode in
|
|
16
|
+
* place, so a known inode at a new address IS the directory, moved.
|
|
17
|
+
* - Links: the FILE rules, for the file reason — retarget is unlink +
|
|
18
|
+
* recreate, so the inode churns exactly like an atomic save.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.matchByInode = matchByInode;
|
|
22
|
+
exports.diffDirState = diffDirState;
|
|
23
|
+
exports.diffLinkState = diffLinkState;
|
|
24
|
+
exports.diffRootState = diffRootState;
|
|
25
|
+
/**
|
|
26
|
+
* Physical continuity, shared by every compare function: a row and an
|
|
27
|
+
* entry sharing one device:inode, exactly one match on both sides → the
|
|
28
|
+
* same physical thing. Anything else is never guessed. For files this
|
|
29
|
+
* rescues path-orphans (rule 2); for directories it IS rule 1.
|
|
30
|
+
*/
|
|
31
|
+
function matchByInode(rows, entries) {
|
|
32
|
+
const one = (map, key, value) => map.set(key, map.has(key) ? 'many' : value);
|
|
33
|
+
const rowsByInode = new Map();
|
|
34
|
+
for (const row of rows)
|
|
35
|
+
one(rowsByInode, `${row.device}:${row.inode}`, row);
|
|
36
|
+
const entriesByInode = new Map();
|
|
37
|
+
for (const entry of entries)
|
|
38
|
+
one(entriesByInode, `${entry.device}:${entry.inode}`, entry);
|
|
39
|
+
const matched = [];
|
|
40
|
+
for (const [key, entry] of entriesByInode) {
|
|
41
|
+
const row = rowsByInode.get(key);
|
|
42
|
+
if (entry === 'many' || !row || row === 'many')
|
|
43
|
+
continue;
|
|
44
|
+
matched.push({ row, entry });
|
|
45
|
+
}
|
|
46
|
+
return matched;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The compare function for directory channels — the file identity rules
|
|
50
|
+
* in the OPPOSITE order, for the mirror reason. A file's inode is
|
|
51
|
+
* replaced in place on every atomic save, so for files the path must
|
|
52
|
+
* absorb that churn (path claims first). Nothing replaces a directory's
|
|
53
|
+
* inode in place: a known inode at a new address IS the directory,
|
|
54
|
+
* moved — even when its old address was reoccupied in the same interval
|
|
55
|
+
* (two directories exchanging names cross by inode, exactly like the
|
|
56
|
+
* files inside them). So:
|
|
57
|
+
* 1. Physical continuity claims the channel: unique device:inode,
|
|
58
|
+
* whole sets, same path = continued, new path = rename.
|
|
59
|
+
* 2. The address claims what no inode claims — recreation under an old
|
|
60
|
+
* name is the same channel wearing a never-seen inode (`retags`,
|
|
61
|
+
* silent).
|
|
62
|
+
* 3. Leftover entries are creates; leftover rows are deletes.
|
|
63
|
+
* No content: a directory's membership is derived state upstream, so
|
|
64
|
+
* there is nothing to hash or check.
|
|
65
|
+
*/
|
|
66
|
+
function diffDirState(rows, entries) {
|
|
67
|
+
const matched = matchByInode(rows, entries);
|
|
68
|
+
const matchedRowIds = new Set(matched.map((pair) => pair.row.dirId));
|
|
69
|
+
const matchedEntries = new Set(matched.map((pair) => pair.entry));
|
|
70
|
+
const rowsByPath = new Map(rows.filter((row) => !matchedRowIds.has(row.dirId))
|
|
71
|
+
.map((row) => [row.relPath, row]));
|
|
72
|
+
const retags = [];
|
|
73
|
+
const creates = [];
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
if (matchedEntries.has(entry))
|
|
76
|
+
continue;
|
|
77
|
+
const row = rowsByPath.get(entry.relPath);
|
|
78
|
+
if (row) {
|
|
79
|
+
rowsByPath.delete(entry.relPath);
|
|
80
|
+
retags.push({ row, entry });
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
creates.push(entry);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
renames: matched.filter((pair) => pair.row.relPath !== pair.entry.relPath),
|
|
88
|
+
retags,
|
|
89
|
+
creates,
|
|
90
|
+
deletes: [...rowsByPath.values()],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The compare function for link channels — the FILE identity rules, for
|
|
95
|
+
* the file reason: a symlink cannot be retargeted in place (retarget is
|
|
96
|
+
* unlink + recreate, so the inode churns exactly like an atomic save),
|
|
97
|
+
* so the path absorbs that churn and claims first; the inode rescues
|
|
98
|
+
* path-orphans (`mv` preserves a symlink's inode). The payload is the
|
|
99
|
+
* target string, already read at walk time. A consequence worth naming:
|
|
100
|
+
* two links exchanging names read as two retargets (path-first),
|
|
101
|
+
* because a link's meaning IS its written address.
|
|
102
|
+
* 1. Path claims the channel: same target = continued (a new inode
|
|
103
|
+
* under an old name with an old target is silent recreation),
|
|
104
|
+
* different target = changed.
|
|
105
|
+
* 2. Inode rescues path-orphans: renames.
|
|
106
|
+
* 3. Leftover entries are creates; leftover rows are deletes.
|
|
107
|
+
*/
|
|
108
|
+
function diffLinkState(rows, entries) {
|
|
109
|
+
const changes = [];
|
|
110
|
+
const retags = [];
|
|
111
|
+
const rowsByPath = new Map(rows.map((row) => [row.relPath, row]));
|
|
112
|
+
const claimedRowIds = new Set();
|
|
113
|
+
const orphanEntries = [];
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
const row = rowsByPath.get(entry.relPath);
|
|
116
|
+
if (!row) {
|
|
117
|
+
orphanEntries.push(entry);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
claimedRowIds.add(row.linkId);
|
|
121
|
+
if (row.target !== entry.target)
|
|
122
|
+
changes.push({ row, entry });
|
|
123
|
+
else if (row.device !== entry.device || row.inode !== entry.inode)
|
|
124
|
+
retags.push({ row, entry });
|
|
125
|
+
}
|
|
126
|
+
const orphanRows = rows.filter((row) => !claimedRowIds.has(row.linkId));
|
|
127
|
+
const renames = matchByInode(orphanRows, orphanEntries);
|
|
128
|
+
const renamedRowIds = new Set(renames.map((pair) => pair.row.linkId));
|
|
129
|
+
const renamedEntries = new Set(renames.map((pair) => pair.entry));
|
|
130
|
+
return {
|
|
131
|
+
changes,
|
|
132
|
+
retags,
|
|
133
|
+
renames,
|
|
134
|
+
creates: orphanEntries.filter((entry) => !renamedEntries.has(entry)),
|
|
135
|
+
deletes: orphanRows.filter((row) => !renamedRowIds.has(row.linkId)),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The compare function for file channels — doorbells and rescans both
|
|
140
|
+
* end here. Hashing is deferred — `checks` name the files whose content
|
|
141
|
+
* must be read before an event may be claimed.
|
|
142
|
+
*
|
|
143
|
+
* Identity rules, in order:
|
|
144
|
+
* 1. Path claims the channel. Same rel_path = same file_id, even when
|
|
145
|
+
* the inode changed (atomic saves replace the inode on every
|
|
146
|
+
* write).
|
|
147
|
+
* 2. Inode rescues path-orphans (matchByInode above).
|
|
148
|
+
* 3. Leftover entries are creates; leftover rows are deletes.
|
|
149
|
+
*
|
|
150
|
+
* The single stat-shortcut this layer allows itself: a (size, mtime,
|
|
151
|
+
* inode) triple identical to the stored one is skipped as unchanged —
|
|
152
|
+
* but only when the mtime is strictly older than the moment we last
|
|
153
|
+
* read the bytes (git's "racily clean" rule), and only when nothing
|
|
154
|
+
* pointed at the file. A path named in `mustCheck` (the doorbell said
|
|
155
|
+
* so) is always hashed: timestamps can lie, a delivered watcher event
|
|
156
|
+
* cannot be un-delivered.
|
|
157
|
+
*/
|
|
158
|
+
function diffRootState(rows, entries, mustCheck = null) {
|
|
159
|
+
const checks = [];
|
|
160
|
+
let unchanged = 0;
|
|
161
|
+
const rowsByPath = new Map(rows.map((row) => [row.relPath, row]));
|
|
162
|
+
const claimedRowIds = new Set();
|
|
163
|
+
const orphanEntries = [];
|
|
164
|
+
for (const entry of entries) {
|
|
165
|
+
const row = rowsByPath.get(entry.relPath);
|
|
166
|
+
if (!row) {
|
|
167
|
+
orphanEntries.push(entry);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
claimedRowIds.add(row.fileId);
|
|
171
|
+
const statsMatch = row.size === entry.size && row.mtimeMs === entry.mtimeMs && row.inode === entry.inode;
|
|
172
|
+
const provablyBeforeLastRead = typeof row.hashedAtMs === 'number' && entry.mtimeMs < row.hashedAtMs;
|
|
173
|
+
const named = mustCheck !== null && mustCheck.has(entry.relPath);
|
|
174
|
+
if (statsMatch && provablyBeforeLastRead && !named) {
|
|
175
|
+
unchanged += 1;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
checks.push({ row, entry });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const orphanRows = rows.filter((row) => !claimedRowIds.has(row.fileId));
|
|
182
|
+
const renames = matchByInode(orphanRows, orphanEntries);
|
|
183
|
+
const renamedRowIds = new Set(renames.map((pair) => pair.row.fileId));
|
|
184
|
+
const renamedEntries = new Set(renames.map((pair) => pair.entry));
|
|
185
|
+
return {
|
|
186
|
+
checks,
|
|
187
|
+
renames,
|
|
188
|
+
creates: orphanEntries.filter((entry) => !renamedEntries.has(entry)),
|
|
189
|
+
deletes: orphanRows.filter((row) => !renamedRowIds.has(row.fileId)),
|
|
190
|
+
unchanged,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/detection/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Echo judgment after incoming Apply.
|
|
4
|
+
*
|
|
5
|
+
* Apply never suppresses watcher rings. It verifies the incoming result and
|
|
6
|
+
* advances the notebook. The retained suspicion then uses this ordinary
|
|
7
|
+
* equality judgment: exact expected truth is silence; different truth is a
|
|
8
|
+
* real local change; unreadable truth is retried.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.echoDecision = echoDecision;
|
|
12
|
+
function echoDecision(notebook, observed) {
|
|
13
|
+
if (observed.truth === 'unsettled') {
|
|
14
|
+
return observed.reason === undefined
|
|
15
|
+
? { kind: 'retry' }
|
|
16
|
+
: { kind: 'retry', reason: observed.reason };
|
|
17
|
+
}
|
|
18
|
+
if (notebook.status === 'active') {
|
|
19
|
+
return observed.truth === 'present' && observed.version === notebook.version
|
|
20
|
+
? { kind: 'silence' }
|
|
21
|
+
: { kind: 'detect' };
|
|
22
|
+
}
|
|
23
|
+
return observed.truth === 'absent' ? { kind: 'silence' } : { kind: 'detect' };
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=echo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"echo.js","sourceRoot":"","sources":["../../src/detection/echo.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAUH,oCAeC;AAfD,SAAgB,YAAY,CAC1B,QAAuD,EACvD,QAAsB;IAEtB,IAAI,QAAQ,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;QACnC,OAAO,QAAQ,CAAC,MAAM,KAAK,SAAS;YAClC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;YACnB,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;IACjD,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,QAAQ,CAAC,KAAK,KAAK,SAAS,IAAI,QAAQ,CAAC,OAAO,KAAK,QAAQ,CAAC,OAAO;YAC1E,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE;YACrB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACzB,CAAC;IACD,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Detection enrollment policies.
|
|
4
|
+
*
|
|
5
|
+
* Current user ground enrolls everything except structural local state and
|
|
6
|
+
* paths excluded by its tracked `.amalgmignore`; `.gitignore` never decides
|
|
7
|
+
* Amalgm enrollment. `defaultShouldEnroll` remains only as the older explicit
|
|
8
|
+
* registration policy for additive API compatibility. Current portable paths
|
|
9
|
+
* already use `/`; a host converts its native separator at the boundary. A
|
|
10
|
+
* literal `\\` is therefore preserved as part of a POSIX filename. Both
|
|
11
|
+
* policies are pure strings-in/verdict-out and never inspect disk themselves.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.defaultShouldEnroll = exports.DEFAULT_AMALGM_IGNORE = exports.AMALGM_IGNORE_FILE = exports.AMALGM_CONTROL_DIRECTORY = void 0;
|
|
15
|
+
exports.normalizedRelativePath = normalizedRelativePath;
|
|
16
|
+
exports.isAmalgmControlPath = isAmalgmControlPath;
|
|
17
|
+
exports.isSqliteCoordinationPath = isSqliteCoordinationPath;
|
|
18
|
+
exports.createUserGroundEnrollmentPolicy = createUserGroundEnrollmentPolicy;
|
|
19
|
+
/** The Git-like names at the user-ground boundary. Structural coordination
|
|
20
|
+
* state is local and cannot be negated by user policy; the ignore file is
|
|
21
|
+
* portable ground so every machine evaluates the same user rules. */
|
|
22
|
+
exports.AMALGM_CONTROL_DIRECTORY = '.amalgm';
|
|
23
|
+
exports.AMALGM_IGNORE_FILE = '.amalgmignore';
|
|
24
|
+
exports.DEFAULT_AMALGM_IGNORE = `/${exports.AMALGM_CONTROL_DIRECTORY}/\n`;
|
|
25
|
+
const EXCLUDED_DIRECTORIES = new Set([
|
|
26
|
+
'.amalgm',
|
|
27
|
+
'.aws',
|
|
28
|
+
'.cache',
|
|
29
|
+
'.docker',
|
|
30
|
+
'.git',
|
|
31
|
+
'.gnupg',
|
|
32
|
+
'.gradle',
|
|
33
|
+
'.hg',
|
|
34
|
+
'.kube',
|
|
35
|
+
'.mypy_cache',
|
|
36
|
+
'.next',
|
|
37
|
+
'.nyc_output',
|
|
38
|
+
'.parcel-cache',
|
|
39
|
+
'.pytest_cache',
|
|
40
|
+
'.ruff_cache',
|
|
41
|
+
'.ssh',
|
|
42
|
+
'.svn',
|
|
43
|
+
'.svelte-kit',
|
|
44
|
+
'.turbo',
|
|
45
|
+
'.venv',
|
|
46
|
+
'.vite',
|
|
47
|
+
'__pycache__',
|
|
48
|
+
'coverage',
|
|
49
|
+
'node_modules',
|
|
50
|
+
]);
|
|
51
|
+
const EXCLUDED_FILES = new Set([
|
|
52
|
+
'.DS_Store',
|
|
53
|
+
'Thumbs.db',
|
|
54
|
+
'desktop.ini',
|
|
55
|
+
'npm-debug.log',
|
|
56
|
+
'pnpm-debug.log',
|
|
57
|
+
'yarn-debug.log',
|
|
58
|
+
'yarn-error.log',
|
|
59
|
+
]);
|
|
60
|
+
function isPrivateFile(name) {
|
|
61
|
+
if (/^\.env(?:\..+)?$/i.test(name) && !/\.example$/i.test(name))
|
|
62
|
+
return true;
|
|
63
|
+
if (/^(?:credentials?|secrets?)\.(?:json|ya?ml|toml|ini|conf)$/i.test(name))
|
|
64
|
+
return true;
|
|
65
|
+
if (/^id_(?:dsa|ecdsa|ed25519|rsa)$/i.test(name))
|
|
66
|
+
return true;
|
|
67
|
+
if (/^(?:\.git-credentials|\.netrc|\.npmrc|\.pypirc|auth\.json|kubeconfig)$/i.test(name))
|
|
68
|
+
return true;
|
|
69
|
+
if (/^(?:service[-_]?account|firebase[-_]?adminsdk).*\.json$/i.test(name))
|
|
70
|
+
return true;
|
|
71
|
+
return /\.(?:key|keystore|kdbx|p12|pfx|pem|ppk|tfstate(?:\.backup)?)$/i.test(name);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Legacy explicit-registration policy retained for additive API compatibility.
|
|
75
|
+
* Current user-ground Detect must use {@link createUserGroundEnrollmentPolicy};
|
|
76
|
+
* there are no implicit dependency, dotfile, cache, or secret exclusions.
|
|
77
|
+
* Both separators are path separators; '' never enrolls.
|
|
78
|
+
*
|
|
79
|
+
* @deprecated Use `createUserGroundEnrollmentPolicy` for current user ground.
|
|
80
|
+
*/
|
|
81
|
+
const defaultShouldEnroll = (relPath) => {
|
|
82
|
+
const parts = normalizedRelativePath(String(relPath || '').replace(/\\/g, '/')).split('/').filter(Boolean);
|
|
83
|
+
const name = parts.at(-1);
|
|
84
|
+
if (name === undefined || parts.some((part) => EXCLUDED_DIRECTORIES.has(part)))
|
|
85
|
+
return false;
|
|
86
|
+
if (EXCLUDED_FILES.has(name) || isPrivateFile(name))
|
|
87
|
+
return false;
|
|
88
|
+
return !name.startsWith('.#') && !name.endsWith('~') && !/\.(?:swp|swo)$/i.test(name);
|
|
89
|
+
};
|
|
90
|
+
exports.defaultShouldEnroll = defaultShouldEnroll;
|
|
91
|
+
/**
|
|
92
|
+
* One normalized spelling for portable enrollment and ignore matching.
|
|
93
|
+
* Portable relative paths use `/`. Do not reinterpret `\\`: it is a valid,
|
|
94
|
+
* literal filename character on POSIX after the host has crossed this seam.
|
|
95
|
+
*/
|
|
96
|
+
function normalizedRelativePath(relPath) {
|
|
97
|
+
return String(relPath || '')
|
|
98
|
+
.replace(/^\.\//, '')
|
|
99
|
+
.replace(/^\/+|\/+$/g, '')
|
|
100
|
+
.replace(/\/{2,}/g, '/');
|
|
101
|
+
}
|
|
102
|
+
/** `.amalgm/` is local control state wherever a registered tree is rooted. */
|
|
103
|
+
function isAmalgmControlPath(relPath) {
|
|
104
|
+
return normalizedRelativePath(relPath).split('/').includes(exports.AMALGM_CONTROL_DIRECTORY);
|
|
105
|
+
}
|
|
106
|
+
/** SQLite's WAL, shared-memory, and rollback-journal files coordinate one
|
|
107
|
+
* local database connection. They are not a portable copy of the database. */
|
|
108
|
+
function isSqliteCoordinationPath(relPath) {
|
|
109
|
+
const name = normalizedRelativePath(relPath).split('/').at(-1) ?? '';
|
|
110
|
+
return /\.(?:db|sqlite|sqlite3)-(?:wal|shm|journal)$/i.test(name);
|
|
111
|
+
}
|
|
112
|
+
function escapeRegex(character) {
|
|
113
|
+
return /[\\^$.*+?()[\]{}|]/.test(character) ? `\\${character}` : character;
|
|
114
|
+
}
|
|
115
|
+
function globExpression(pattern) {
|
|
116
|
+
let expression = '';
|
|
117
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
118
|
+
const character = pattern[index];
|
|
119
|
+
if (character === '*') {
|
|
120
|
+
if (pattern[index + 1] === '*') {
|
|
121
|
+
while (pattern[index + 1] === '*')
|
|
122
|
+
index += 1;
|
|
123
|
+
if (pattern[index + 1] === '/') {
|
|
124
|
+
index += 1;
|
|
125
|
+
expression += '(?:.*/)?';
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
expression += '.*';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
expression += '[^/]*';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else if (character === '?') {
|
|
136
|
+
expression += '[^/]';
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
expression += escapeRegex(character);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return expression;
|
|
143
|
+
}
|
|
144
|
+
function ignoreRule(line) {
|
|
145
|
+
let value = line.trim();
|
|
146
|
+
if (!value || value.startsWith('#'))
|
|
147
|
+
return null;
|
|
148
|
+
let negated = false;
|
|
149
|
+
if (value.startsWith('!')) {
|
|
150
|
+
negated = true;
|
|
151
|
+
value = value.slice(1);
|
|
152
|
+
}
|
|
153
|
+
if (!value)
|
|
154
|
+
return null;
|
|
155
|
+
const anchored = value.startsWith('/');
|
|
156
|
+
if (anchored)
|
|
157
|
+
value = value.slice(1);
|
|
158
|
+
if (value.endsWith('/'))
|
|
159
|
+
value = value.slice(0, -1);
|
|
160
|
+
if (!value)
|
|
161
|
+
return null;
|
|
162
|
+
const body = globExpression(value);
|
|
163
|
+
const prefix = anchored || value.includes('/') ? '^' : '(?:^|/)';
|
|
164
|
+
return { negated, expression: new RegExp(`${prefix}${body}(?:$|/)`) };
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Compile the root `.amalgmignore` for a user ground. Rules are ordered and
|
|
168
|
+
* `!` negates a prior user rule. Reserved control state and transient SQLite
|
|
169
|
+
* coordination files are structural exclusions; every other path is ground
|
|
170
|
+
* unless this tracked file excludes it. The policy file itself always travels.
|
|
171
|
+
*/
|
|
172
|
+
function createUserGroundEnrollmentPolicy(ignoreText = '') {
|
|
173
|
+
const rules = String(ignoreText).split(/\r?\n/).map(ignoreRule)
|
|
174
|
+
.filter((rule) => rule !== null);
|
|
175
|
+
return (input) => {
|
|
176
|
+
const relPath = normalizedRelativePath(input);
|
|
177
|
+
if (!relPath || isAmalgmControlPath(relPath) || isSqliteCoordinationPath(relPath))
|
|
178
|
+
return false;
|
|
179
|
+
if (relPath === exports.AMALGM_IGNORE_FILE)
|
|
180
|
+
return true;
|
|
181
|
+
let excluded = false;
|
|
182
|
+
for (const rule of rules) {
|
|
183
|
+
if (rule.expression.test(relPath))
|
|
184
|
+
excluded = !rule.negated;
|
|
185
|
+
}
|
|
186
|
+
return !excluded;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=enrollment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrollment.js","sourceRoot":"","sources":["../../src/detection/enrollment.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Detection — the pure cores that turn observed disk evidence into facts.
|
|
4
|
+
* Normative sources:
|
|
5
|
+
* entity-model.md (identity axioms, the two continuity witnesses) and
|
|
6
|
+
* entity-implementation.md (the ratified seams).
|
|
7
|
+
*
|
|
8
|
+
* Everything here is rows and entries in, verdicts out. Walking disk,
|
|
9
|
+
* hashing bytes, opening watchers, and writing stores are the host's;
|
|
10
|
+
* the host injects its answers (a stat, a channel row, an enrollment
|
|
11
|
+
* policy) and this layer owns only the laws:
|
|
12
|
+
*
|
|
13
|
+
* - `adapter-contract` the seven rails, the ratified detect socket,
|
|
14
|
+
* descriptor conviction, the three-state truth
|
|
15
|
+
* judgment, the reference adapter
|
|
16
|
+
* - `classify` text/binary from detect's own read evidence
|
|
17
|
+
* - `refusal` declined-vs-broken, named where it is raised
|
|
18
|
+
* - `enrollment` tracked user-ground policy plus legacy compatibility
|
|
19
|
+
* - `diff` the compare functions: file/dir/link identity
|
|
20
|
+
* - `continuity` root rescue and the undecided address
|
|
21
|
+
* - `watch-boundary` generation-safe Watch input and coverage output
|
|
22
|
+
* - `territory` nearest-repository recorder selection
|
|
23
|
+
* - `record-proposal` the exact Detect-to-Record contract
|
|
24
|
+
* - `batch` net-difference planning and Watch settlement
|
|
25
|
+
* - `content` text/binary/link replay from one captured read
|
|
26
|
+
* - `repository` verified Card + Checkpoint transport planning
|
|
27
|
+
* - `echo` incoming Apply equality without suppression
|
|
28
|
+
*/
|
|
29
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
30
|
+
if (k2 === undefined) k2 = k;
|
|
31
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
32
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
33
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
34
|
+
}
|
|
35
|
+
Object.defineProperty(o, k2, desc);
|
|
36
|
+
}) : (function(o, m, k, k2) {
|
|
37
|
+
if (k2 === undefined) k2 = k;
|
|
38
|
+
o[k2] = m[k];
|
|
39
|
+
}));
|
|
40
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
41
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
42
|
+
};
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
__exportStar(require("./adapter-contract.js"), exports);
|
|
45
|
+
__exportStar(require("./batch.js"), exports);
|
|
46
|
+
__exportStar(require("./classify.js"), exports);
|
|
47
|
+
__exportStar(require("./continuity.js"), exports);
|
|
48
|
+
__exportStar(require("./content.js"), exports);
|
|
49
|
+
__exportStar(require("./diff.js"), exports);
|
|
50
|
+
__exportStar(require("./echo.js"), exports);
|
|
51
|
+
__exportStar(require("./enrollment.js"), exports);
|
|
52
|
+
__exportStar(require("./record-proposal.js"), exports);
|
|
53
|
+
__exportStar(require("./repository.js"), exports);
|
|
54
|
+
__exportStar(require("./refusal.js"), exports);
|
|
55
|
+
__exportStar(require("./territory.js"), exports);
|
|
56
|
+
__exportStar(require("./watch-boundary.js"), exports);
|
|
57
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/detection/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;;;;;;;;;;;;;;;;AAEH,wDAAsC;AACtC,6CAA2B;AAC3B,gDAA8B;AAC9B,kDAAgC;AAChC,+CAA6B;AAC7B,4CAA0B;AAC1B,4CAA0B;AAC1B,kDAAgC;AAChC,uDAAqC;AACrC,kDAAgC;AAChC,+CAA6B;AAC7B,iDAA+B;AAC/B,sDAAoC"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Detect's exact output to Record.
|
|
4
|
+
*
|
|
5
|
+
* A proposal is not yet a durable mutation: Record assigns one mutation id,
|
|
6
|
+
* appends it atomically, and then Send publishes that same row. Detect owns the
|
|
7
|
+
* complete result and replay instruction so no later rail has to rediscover
|
|
8
|
+
* filesystem truth.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.DETECTED_CHANGE_CONTRACT = void 0;
|
|
12
|
+
exports.detectedChangeProposal = detectedChangeProposal;
|
|
13
|
+
const cloud_js_1 = require("../entities/cloud.js");
|
|
14
|
+
const types_js_1 = require("../entities/types.js");
|
|
15
|
+
const content_js_1 = require("../contracts/content.js");
|
|
16
|
+
const artifacts_js_1 = require("../transfer/artifacts.js");
|
|
17
|
+
exports.DETECTED_CHANGE_CONTRACT = 'amalgm.detected-change-proposal@1';
|
|
18
|
+
const HASH = /^[0-9a-f]{64}$/;
|
|
19
|
+
const facetsOf = (base, result) => {
|
|
20
|
+
if (base === null)
|
|
21
|
+
return ['create'];
|
|
22
|
+
const facets = [];
|
|
23
|
+
if (base.parentUUID !== result.parentUUID)
|
|
24
|
+
facets.push('move');
|
|
25
|
+
if (base.name !== result.name)
|
|
26
|
+
facets.push('rename');
|
|
27
|
+
if (base.type !== result.type)
|
|
28
|
+
facets.push('retype');
|
|
29
|
+
const resultCarriesPayload = !types_js_1.DERIVED_PAYLOAD.has(result.type) && result.type !== 'reference';
|
|
30
|
+
if ((base.payloadVersion !== result.payloadVersion && resultCarriesPayload)
|
|
31
|
+
|| (result.type === 'repo.git' && base.transportVersion !== result.transportVersion)) {
|
|
32
|
+
facets.push('content');
|
|
33
|
+
}
|
|
34
|
+
if (base.status !== result.status) {
|
|
35
|
+
if (result.status === 'trashed')
|
|
36
|
+
facets.push('trash');
|
|
37
|
+
else if (base.status === 'trashed' && result.status === 'active')
|
|
38
|
+
facets.push('restore');
|
|
39
|
+
}
|
|
40
|
+
return facets;
|
|
41
|
+
};
|
|
42
|
+
const contentRequired = (base, result, facets) => result.status === 'active'
|
|
43
|
+
&& (base === null || facets.includes('content') || facets.includes('retype'))
|
|
44
|
+
&& ['file.text', 'file.binary', 'link', 'repo.git'].includes(result.type);
|
|
45
|
+
const validateReplay = (base, result, facets, replay) => {
|
|
46
|
+
const needsContent = contentRequired(base, result, facets);
|
|
47
|
+
if (!needsContent) {
|
|
48
|
+
if (replay.kind !== 'structure') {
|
|
49
|
+
throw new Error('a structural-only detected change must use structure replay');
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (replay.kind !== result.type) {
|
|
54
|
+
throw new Error(`detected ${result.type} content cannot use ${replay.kind} replay`);
|
|
55
|
+
}
|
|
56
|
+
if (result.payloadVersion === null) {
|
|
57
|
+
throw new Error(`detected ${result.type} content has no result payload version`);
|
|
58
|
+
}
|
|
59
|
+
switch (replay.kind) {
|
|
60
|
+
case 'file.text':
|
|
61
|
+
const expectedTextBase = base?.payloadVersion && HASH.test(base.payloadVersion)
|
|
62
|
+
? base.payloadVersion
|
|
63
|
+
: null;
|
|
64
|
+
if (replay.basePayloadVersion !== expectedTextBase
|
|
65
|
+
|| replay.resultPayloadVersion !== result.payloadVersion
|
|
66
|
+
|| (replay.mode === 'delta') !== (replay.delta !== null)) {
|
|
67
|
+
throw new Error('text replay does not name the detected base and result');
|
|
68
|
+
}
|
|
69
|
+
if (!HASH.test(replay.resultPayloadVersion)
|
|
70
|
+
|| (replay.basePayloadVersion !== null && !HASH.test(replay.basePayloadVersion))) {
|
|
71
|
+
throw new Error('text replay has an invalid content head');
|
|
72
|
+
}
|
|
73
|
+
if (replay.delta !== null
|
|
74
|
+
&& (!Number.isSafeInteger(replay.delta.offset) || replay.delta.offset < 0
|
|
75
|
+
|| !Number.isSafeInteger(replay.delta.deleteBytes) || replay.delta.deleteBytes < 0
|
|
76
|
+
|| !(replay.delta.insert instanceof Uint8Array))) {
|
|
77
|
+
throw new Error('text replay has an invalid byte splice');
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
case 'file.binary': {
|
|
81
|
+
const manifest = (0, content_js_1.checkContentManifest)(replay.manifest, result.payloadVersion);
|
|
82
|
+
if (!manifest.ok || replay.resultPayloadVersion !== result.payloadVersion) {
|
|
83
|
+
throw new Error('binary replay does not name the detected result');
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case 'link':
|
|
88
|
+
if (typeof replay.target !== 'string'
|
|
89
|
+
|| replay.resultPayloadVersion !== result.payloadVersion
|
|
90
|
+
|| !HASH.test(replay.resultPayloadVersion)) {
|
|
91
|
+
throw new Error('link replay does not name the detected result');
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
case 'repo.git':
|
|
95
|
+
if (replay.stateId !== result.payloadVersion
|
|
96
|
+
|| result.transportVersion === null
|
|
97
|
+
|| replay.transportVersion !== result.transportVersion
|
|
98
|
+
|| ![replay.stateId, replay.cardId, replay.checkpointId, replay.transportVersion]
|
|
99
|
+
.every((head) => HASH.test(head))
|
|
100
|
+
|| (replay.parentTransportVersion !== null && !HASH.test(replay.parentTransportVersion))
|
|
101
|
+
|| typeof replay.cardChanged !== 'boolean'
|
|
102
|
+
|| typeof replay.checkpointChanged !== 'boolean') {
|
|
103
|
+
throw new Error('repository replay does not name the detected state and transport');
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Build one immutable proposal, or null when settled truth has the same logical
|
|
110
|
+
* version. A repository transport-only change is still meaningful because its
|
|
111
|
+
* worktree identity map is part of exact reproduction even when Card and
|
|
112
|
+
* Checkpoint IDs did not move.
|
|
113
|
+
*/
|
|
114
|
+
function detectedChangeProposal(input) {
|
|
115
|
+
const result = (0, cloud_js_1.assertRecord)(input.result);
|
|
116
|
+
const base = input.base === null ? null : (0, cloud_js_1.assertRecord)(input.base);
|
|
117
|
+
if (base !== null && base.uuid !== result.uuid) {
|
|
118
|
+
throw new Error('a detected transition cannot change entity UUID');
|
|
119
|
+
}
|
|
120
|
+
if (base === null && result.status !== 'active') {
|
|
121
|
+
throw new Error('newly detected ground must begin active');
|
|
122
|
+
}
|
|
123
|
+
if (base?.status === 'deleted') {
|
|
124
|
+
throw new Error('Detection cannot resurrect a deleted tombstone');
|
|
125
|
+
}
|
|
126
|
+
if (result.status === 'deleted') {
|
|
127
|
+
throw new Error('Detection cannot propose permanent deletion; deletion is an explicit lifecycle operation');
|
|
128
|
+
}
|
|
129
|
+
if (base?.version === result.version && base.transportVersion === result.transportVersion) {
|
|
130
|
+
const sameState = base.type === result.type
|
|
131
|
+
&& base.parentUUID === result.parentUUID
|
|
132
|
+
&& base.name === result.name
|
|
133
|
+
&& base.status === result.status
|
|
134
|
+
&& base.payloadVersion === result.payloadVersion;
|
|
135
|
+
if (!sameState)
|
|
136
|
+
throw new Error('one logical version cannot name two different entity states');
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const facets = facetsOf(base, result);
|
|
140
|
+
if (facets.length === 0) {
|
|
141
|
+
if (base !== null && types_js_1.DERIVED_PAYLOAD.has(base.type) && base.type === result.type) {
|
|
142
|
+
// Container membership is a projection of child parentUUID values. Its
|
|
143
|
+
// derived version can move locally, but it is never an independent write
|
|
144
|
+
// that could erase a sibling created by another machine.
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
throw new Error('detected records disagree without a named logical facet');
|
|
148
|
+
}
|
|
149
|
+
if (facets.includes('trash') && input.lifecycleAuthorization !== 'trash') {
|
|
150
|
+
throw new Error('filesystem absence is not authority to Trash an entity');
|
|
151
|
+
}
|
|
152
|
+
if (facets.includes('restore') && input.lifecycleAuthorization !== 'restore') {
|
|
153
|
+
throw new Error('restoring an entity requires explicit lifecycle authority');
|
|
154
|
+
}
|
|
155
|
+
validateReplay(base, result, facets, input.replay);
|
|
156
|
+
const artifact = input.replay.kind === 'structure' ? null : (0, artifacts_js_1.artifactForRecord)(result);
|
|
157
|
+
if (input.replay.kind !== 'structure' && artifact === null) {
|
|
158
|
+
throw new Error(`detected ${result.type} replay has no transferable artifact`);
|
|
159
|
+
}
|
|
160
|
+
return Object.freeze({
|
|
161
|
+
contract: exports.DETECTED_CHANGE_CONTRACT,
|
|
162
|
+
entityId: result.uuid,
|
|
163
|
+
basedOnState: base?.version ?? null,
|
|
164
|
+
result,
|
|
165
|
+
facets: Object.freeze(facets),
|
|
166
|
+
replay: input.replay,
|
|
167
|
+
cargo: Object.freeze(artifact === null ? [] : [artifact]),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=record-proposal.js.map
|