pinokiod 8.0.73 → 8.0.75
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/kernel/vault/automatic_scans.js +256 -152
- package/kernel/vault/registry_core.js +65 -23
- package/package.json +1 -1
- package/server/public/app-vault-mode.js +2 -1
- package/test/vault-automatic-scan-ui.test.js +63 -8
- package/test/vault-automatic-scans.test.js +1393 -93
- package/test/vault-engine.test.js +7 -0
|
@@ -52,7 +52,6 @@ class AutomaticScans {
|
|
|
52
52
|
this.manualDepth = 0
|
|
53
53
|
this.observedApps = new Set()
|
|
54
54
|
this.changedPaths = new Map()
|
|
55
|
-
this.changedPathIndex = new Map()
|
|
56
55
|
this.pendingStops = new Map()
|
|
57
56
|
this.lifecycleWork = new Set()
|
|
58
57
|
this.stopSettleMs = STOP_SETTLE_MS
|
|
@@ -138,7 +137,6 @@ class AutomaticScans {
|
|
|
138
137
|
}
|
|
139
138
|
this.observedApps.clear()
|
|
140
139
|
this.changedPaths.clear()
|
|
141
|
-
this.changedPathIndex.clear()
|
|
142
140
|
if (watcherError) throw watcherError
|
|
143
141
|
}
|
|
144
142
|
|
|
@@ -151,117 +149,123 @@ class AutomaticScans {
|
|
|
151
149
|
})
|
|
152
150
|
return
|
|
153
151
|
}
|
|
154
|
-
|
|
152
|
+
const watcherEvents = Array.isArray(events) ? events : []
|
|
153
|
+
if (!watcherEvents.some((event) => event && event.type === "delete")) {
|
|
154
|
+
for (const event of watcherEvents) {
|
|
155
|
+
if (event && ["create", "update"].includes(event.type)) {
|
|
156
|
+
this.recordChangedPath(event.path)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
const batches = new Map()
|
|
162
|
+
for (let index = 0; index < watcherEvents.length; index++) {
|
|
163
|
+
const event = watcherEvents[index]
|
|
155
164
|
if (!event || !["create", "update", "delete"].includes(event.type)) {
|
|
156
165
|
continue
|
|
157
166
|
}
|
|
158
|
-
|
|
159
|
-
|
|
167
|
+
const target = this.changedPathTarget(
|
|
168
|
+
event.path, event.type === "delete")
|
|
169
|
+
if (!target) continue
|
|
170
|
+
let batch = batches.get(target.app)
|
|
171
|
+
if (!batch) {
|
|
172
|
+
batch = {
|
|
173
|
+
root: target.root,
|
|
174
|
+
updates: new Map(),
|
|
175
|
+
deletes: new Map()
|
|
176
|
+
}
|
|
177
|
+
batches.set(target.app, batch)
|
|
178
|
+
}
|
|
179
|
+
const key = pathKey(target.path)
|
|
180
|
+
if (event.type === "delete") batch.deletes.set(key, index)
|
|
181
|
+
else batch.updates.set(key, { index, path: target.path })
|
|
182
|
+
}
|
|
183
|
+
for (const [app, batch] of batches) {
|
|
184
|
+
this.applyChangedPathBatch(app, batch)
|
|
160
185
|
}
|
|
161
186
|
}
|
|
162
187
|
|
|
163
|
-
|
|
188
|
+
changedPathTarget(filePath, allowRoot = false) {
|
|
164
189
|
if (this.disposed || !this.supported ||
|
|
165
190
|
typeof filePath !== "string" || !filePath) {
|
|
166
|
-
return
|
|
191
|
+
return null
|
|
167
192
|
}
|
|
168
193
|
const app = this.appForLaunchPath(filePath)
|
|
169
|
-
if (!app || !this.observedApps.has(app)) return
|
|
170
|
-
const
|
|
194
|
+
if (!app || !this.observedApps.has(app)) return null
|
|
195
|
+
const root = path.resolve(this.vault.kernel.homedir, "api", app)
|
|
171
196
|
const resolved = path.resolve(filePath)
|
|
172
|
-
if (resolved ===
|
|
197
|
+
if ((!allowRoot && resolved === root) || !inside(root, resolved)) return null
|
|
198
|
+
return { app, root, path: resolved }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
recordChangedPath(filePath) {
|
|
202
|
+
const target = this.changedPathTarget(filePath)
|
|
203
|
+
if (!target) return false
|
|
204
|
+
const { app, path: resolved } = target
|
|
173
205
|
let paths = this.changedPaths.get(app)
|
|
174
206
|
if (!paths) {
|
|
175
207
|
paths = new Set()
|
|
176
208
|
this.changedPaths.set(app, paths)
|
|
177
209
|
}
|
|
178
|
-
let index = this.changedPathIndex.get(app)
|
|
179
|
-
if (!index) {
|
|
180
|
-
index = { exact: new Map(), descendants: new Map() }
|
|
181
|
-
this.changedPathIndex.set(app, index)
|
|
182
|
-
}
|
|
183
|
-
const key = pathKey(resolved)
|
|
184
|
-
if (index.exact.has(key)) return true
|
|
185
210
|
paths.add(resolved)
|
|
186
|
-
index.exact.set(key, resolved)
|
|
187
|
-
this.addChangedPathToIndex(index.descendants, appRoot, resolved)
|
|
188
211
|
return true
|
|
189
212
|
}
|
|
190
213
|
|
|
191
|
-
|
|
192
|
-
const rootKey = pathKey(
|
|
193
|
-
|
|
194
|
-
let
|
|
214
|
+
lastDeleteIndex(root, filePath, deletes) {
|
|
215
|
+
const rootKey = pathKey(root)
|
|
216
|
+
let current = path.resolve(filePath)
|
|
217
|
+
let latest = -1
|
|
195
218
|
while (true) {
|
|
196
219
|
const key = pathKey(current)
|
|
197
|
-
|
|
198
|
-
if (
|
|
220
|
+
const index = deletes.get(key)
|
|
221
|
+
if (index !== undefined && index > latest) latest = index
|
|
222
|
+
if (key === rootKey) return latest
|
|
199
223
|
current = path.dirname(current)
|
|
200
224
|
}
|
|
201
225
|
}
|
|
202
226
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
descendants.add(filePath)
|
|
227
|
+
applyChangedPathBatch(app, { root, updates, deletes }) {
|
|
228
|
+
const paths = this.changedPaths.get(app)
|
|
229
|
+
if (!deletes.size) {
|
|
230
|
+
const collected = paths || new Set()
|
|
231
|
+
for (const update of updates.values()) collected.add(update.path)
|
|
232
|
+
if (collected.size) this.changedPaths.set(app, collected)
|
|
233
|
+
return
|
|
211
234
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
235
|
+
const retained = new Set()
|
|
236
|
+
if (paths) {
|
|
237
|
+
for (const candidate of paths) {
|
|
238
|
+
const key = pathKey(candidate)
|
|
239
|
+
const update = updates.get(key)
|
|
240
|
+
if (this.lastDeleteIndex(root, candidate, deletes) >
|
|
241
|
+
(update ? update.index : -1) || retained.has(key)) {
|
|
242
|
+
paths.delete(candidate)
|
|
243
|
+
} else {
|
|
244
|
+
retained.add(key)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
221
247
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
248
|
+
let collected = paths
|
|
249
|
+
for (const [key, update] of updates) {
|
|
250
|
+
if (update.index <= this.lastDeleteIndex(root, update.path, deletes) ||
|
|
251
|
+
retained.has(key)) continue
|
|
252
|
+
if (!collected) collected = new Set()
|
|
253
|
+
collected.add(update.path)
|
|
254
|
+
retained.add(key)
|
|
228
255
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const paths = this.changedPaths.get(app)
|
|
232
|
-
if (!paths) return false
|
|
233
|
-
const resolved = path.resolve(filePath)
|
|
234
|
-
const appRoot = path.resolve(this.vault.kernel.homedir, "api", app)
|
|
235
|
-
const index = this.changedPathIndex.get(app)
|
|
236
|
-
if (!index) return false
|
|
237
|
-
const key = pathKey(resolved)
|
|
238
|
-
const exact = index.exact.get(key)
|
|
239
|
-
const descendants = index.descendants.get(key)
|
|
240
|
-
if (!exact && !descendants) return false
|
|
241
|
-
const matches = new Set(descendants || [])
|
|
242
|
-
if (exact) matches.add(exact)
|
|
243
|
-
for (const candidate of matches) {
|
|
244
|
-
paths.delete(candidate)
|
|
245
|
-
this.removeChangedPathFromIndex(index, appRoot, candidate)
|
|
246
|
-
}
|
|
247
|
-
if (!paths.size) this.discardChangedPaths(app)
|
|
248
|
-
return true
|
|
256
|
+
if (collected && collected.size) this.changedPaths.set(app, collected)
|
|
257
|
+
else this.changedPaths.delete(app)
|
|
249
258
|
}
|
|
250
259
|
|
|
251
260
|
discardChangedPaths(app) {
|
|
252
261
|
this.changedPaths.delete(app)
|
|
253
|
-
this.changedPathIndex.delete(app)
|
|
254
262
|
}
|
|
255
263
|
|
|
256
264
|
consumeChangedPaths(app, paths) {
|
|
257
265
|
const collected = this.changedPaths.get(app)
|
|
258
266
|
if (!collected) return
|
|
259
|
-
const appRoot = path.resolve(this.vault.kernel.homedir, "api", app)
|
|
260
|
-
const index = this.changedPathIndex.get(app)
|
|
261
267
|
for (const filePath of paths || []) {
|
|
262
|
-
|
|
263
|
-
if (!collected.delete(resolved) || !index) continue
|
|
264
|
-
this.removeChangedPathFromIndex(index, appRoot, resolved)
|
|
268
|
+
collected.delete(path.resolve(filePath))
|
|
265
269
|
}
|
|
266
270
|
if (!collected.size) this.discardChangedPaths(app)
|
|
267
271
|
}
|
|
@@ -890,7 +894,7 @@ class AutomaticScans {
|
|
|
890
894
|
})
|
|
891
895
|
}
|
|
892
896
|
|
|
893
|
-
async
|
|
897
|
+
async currentAutomaticEntry(active, entry, counts) {
|
|
894
898
|
this.checkpoint(active)
|
|
895
899
|
let current
|
|
896
900
|
try {
|
|
@@ -911,7 +915,20 @@ class AutomaticScans {
|
|
|
911
915
|
active.app, entry, "snapshot-changed", counts)
|
|
912
916
|
return null
|
|
913
917
|
}
|
|
914
|
-
|
|
918
|
+
return {
|
|
919
|
+
stat: current,
|
|
920
|
+
entry: Object.assign({}, entry, fileSnapshot(current))
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
async verifiedAutomaticHash(
|
|
925
|
+
active, entry, memory, verifiedHashes, counts, prepared = null
|
|
926
|
+
) {
|
|
927
|
+
const checked = prepared || await this.currentAutomaticEntry(
|
|
928
|
+
active, entry, counts)
|
|
929
|
+
if (!checked) return null
|
|
930
|
+
const current = checked.stat
|
|
931
|
+
const currentEntry = checked.entry
|
|
915
932
|
const key = this.automaticHashKey(currentEntry)
|
|
916
933
|
const cachedSnapshot = {
|
|
917
934
|
size: entry.cached_size,
|
|
@@ -931,10 +948,11 @@ class AutomaticScans {
|
|
|
931
948
|
if (reusable) {
|
|
932
949
|
counts.hash_reuses += 1
|
|
933
950
|
memory.set(key, reusable)
|
|
934
|
-
|
|
951
|
+
const verifiedEntry = Object.assign({}, currentEntry, {
|
|
935
952
|
hash: reusable
|
|
936
|
-
})
|
|
937
|
-
|
|
953
|
+
})
|
|
954
|
+
verifiedHashes.set(pathKey(entry.path), verifiedEntry)
|
|
955
|
+
return verifiedEntry
|
|
938
956
|
}
|
|
939
957
|
let verified
|
|
940
958
|
try {
|
|
@@ -961,25 +979,36 @@ class AutomaticScans {
|
|
|
961
979
|
counts.hashed += 1
|
|
962
980
|
counts.hash_bytes += Math.max(0, Number(verified.result.size) || 0)
|
|
963
981
|
memory.set(key, verified.result.hash)
|
|
964
|
-
|
|
982
|
+
const verifiedEntry = Object.assign({}, currentEntry, {
|
|
965
983
|
hash: verified.result.hash
|
|
966
|
-
})
|
|
967
|
-
|
|
984
|
+
})
|
|
985
|
+
verifiedHashes.set(pathKey(entry.path), verifiedEntry)
|
|
986
|
+
return verifiedEntry
|
|
968
987
|
}
|
|
969
988
|
|
|
970
989
|
async runPrecheck(active) {
|
|
971
990
|
const app = active.app
|
|
972
991
|
const root = path.resolve(this.vault.kernel.homedir, "api", app)
|
|
992
|
+
const startedAt = Date.now()
|
|
993
|
+
active.startedAt = startedAt
|
|
994
|
+
const markStage = (stage, entry = null) => {
|
|
995
|
+
active.stage = stage
|
|
996
|
+
active.stagePath = entry && entry.path ? entry.path : null
|
|
997
|
+
active.stageKind = entry && entry.kind ? entry.kind : null
|
|
998
|
+
}
|
|
999
|
+
markStage("app-root-check")
|
|
973
1000
|
if (!await this.appRootIsAvailable(app)) {
|
|
974
1001
|
const error = new Error("That app is no longer available.")
|
|
975
1002
|
error.code = "ENOENT"
|
|
976
1003
|
throw error
|
|
977
1004
|
}
|
|
978
|
-
const startedAt = Date.now()
|
|
979
1005
|
const counts = {
|
|
980
1006
|
files: 0,
|
|
981
1007
|
bytes: 0,
|
|
982
1008
|
candidates: 0,
|
|
1009
|
+
registry_pages: 0,
|
|
1010
|
+
candidate_checks: 0,
|
|
1011
|
+
peer_checks: 0,
|
|
983
1012
|
path_errors: 0,
|
|
984
1013
|
hashed: 0,
|
|
985
1014
|
hash_bytes: 0,
|
|
@@ -987,6 +1016,7 @@ class AutomaticScans {
|
|
|
987
1016
|
hash_failures: 0,
|
|
988
1017
|
unstable_hashes: 0
|
|
989
1018
|
}
|
|
1019
|
+
markStage("threshold-read")
|
|
990
1020
|
const threshold = await this.candidateThreshold()
|
|
991
1021
|
const paths = [...new Set(active.paths || [])].filter((filePath) =>
|
|
992
1022
|
typeof filePath === "string" && inside(root, filePath))
|
|
@@ -998,6 +1028,7 @@ class AutomaticScans {
|
|
|
998
1028
|
changed_paths: paths.length,
|
|
999
1029
|
policy: "metadata-prefilter-sha256"
|
|
1000
1030
|
})
|
|
1031
|
+
markStage("staging-reset")
|
|
1001
1032
|
await this.vault.registry.beginAutomaticPrecheck(app)
|
|
1002
1033
|
try {
|
|
1003
1034
|
this.checkpoint(active)
|
|
@@ -1017,9 +1048,11 @@ class AutomaticScans {
|
|
|
1017
1048
|
return true
|
|
1018
1049
|
}
|
|
1019
1050
|
}
|
|
1051
|
+
markStage("changed-parent-metadata")
|
|
1020
1052
|
const safePaths = await this.changedPathsWithSafeParents(
|
|
1021
1053
|
paths, root, metadataOptions, () => this.checkpoint(active))
|
|
1022
1054
|
this.checkpoint(active)
|
|
1055
|
+
markStage("changed-path-metadata")
|
|
1023
1056
|
const safeStats = await this.statChangedPaths(
|
|
1024
1057
|
safePaths, metadataOptions, () => this.checkpoint(active))
|
|
1025
1058
|
const statsByPath = new Map(safePaths.map((filePath, index) =>
|
|
@@ -1046,107 +1079,164 @@ class AutomaticScans {
|
|
|
1046
1079
|
gid: stat.gid
|
|
1047
1080
|
})
|
|
1048
1081
|
}
|
|
1049
|
-
counts.candidates = candidates.length
|
|
1050
1082
|
if (candidates.length) {
|
|
1083
|
+
markStage("staging-write")
|
|
1051
1084
|
await this.vault.registry.stageAutomaticPrecheckFiles(app, candidates)
|
|
1052
1085
|
}
|
|
1053
1086
|
this.checkpoint(active)
|
|
1087
|
+
markStage("app-root-recheck")
|
|
1054
1088
|
if (!await this.appRootIsAvailable(app)) {
|
|
1055
1089
|
const error = new Error("That app is no longer available.")
|
|
1056
1090
|
error.code = "ENOENT"
|
|
1057
1091
|
throw error
|
|
1058
1092
|
}
|
|
1059
|
-
|
|
1093
|
+
this.log("verification-started", {
|
|
1094
|
+
app,
|
|
1095
|
+
files: counts.files,
|
|
1096
|
+
bytes: counts.bytes,
|
|
1097
|
+
threshold_candidates: candidates.length,
|
|
1098
|
+
duration_ms: Date.now() - startedAt
|
|
1099
|
+
})
|
|
1100
|
+
let proof = null
|
|
1060
1101
|
let group = null
|
|
1102
|
+
const acceptProof = (candidate, peer, hash) => {
|
|
1103
|
+
proof = { path: candidate.path, hash }
|
|
1104
|
+
this.log("first-proof-exit", {
|
|
1105
|
+
app,
|
|
1106
|
+
candidate_path: candidate.path,
|
|
1107
|
+
peer_path: peer.path,
|
|
1108
|
+
peer_kind: peer.kind,
|
|
1109
|
+
size: candidate.size,
|
|
1110
|
+
duration_ms: Date.now() - startedAt,
|
|
1111
|
+
registry_pages: counts.registry_pages,
|
|
1112
|
+
candidate_checks: counts.candidate_checks,
|
|
1113
|
+
peer_checks: counts.peer_checks,
|
|
1114
|
+
hashed: counts.hashed,
|
|
1115
|
+
hash_bytes: counts.hash_bytes,
|
|
1116
|
+
hash_reuses: counts.hash_reuses
|
|
1117
|
+
})
|
|
1118
|
+
}
|
|
1061
1119
|
const preparePeers = () => {
|
|
1062
1120
|
if (!group || group.unmatched) return
|
|
1063
1121
|
group.unmatched = new Map()
|
|
1064
|
-
group.unmatchedIdentities = new Map()
|
|
1065
1122
|
for (const [hash, value] of group.candidatesByHash) {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
} else {
|
|
1071
|
-
const identity = value.identities.values().next().value
|
|
1072
|
-
group.unmatched.set(hash, {
|
|
1073
|
-
identity,
|
|
1074
|
-
candidates: value.candidates
|
|
1075
|
-
})
|
|
1076
|
-
group.unmatchedIdentities.set(
|
|
1077
|
-
identity,
|
|
1078
|
-
(group.unmatchedIdentities.get(identity) || 0) + 1)
|
|
1079
|
-
}
|
|
1123
|
+
group.unmatched.set(hash, {
|
|
1124
|
+
identity: value.identities.values().next().value,
|
|
1125
|
+
candidate: value.candidate
|
|
1126
|
+
})
|
|
1080
1127
|
}
|
|
1081
1128
|
}
|
|
1082
|
-
const
|
|
1083
|
-
if (!group) return
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1129
|
+
const hashCandidates = async (peerIdentity = null) => {
|
|
1130
|
+
if (!group || group.candidatesHashed) return !!group
|
|
1131
|
+
const identities = new Set(group.candidates.map((candidate) =>
|
|
1132
|
+
this.automaticIdentityKey(candidate.entry)))
|
|
1133
|
+
if (identities.size < 2 &&
|
|
1134
|
+
(!peerIdentity || identities.has(peerIdentity))) {
|
|
1135
|
+
return false
|
|
1136
|
+
}
|
|
1137
|
+
group.candidatesHashed = true
|
|
1138
|
+
counts.candidates += group.candidates.length
|
|
1139
|
+
for (const candidate of group.candidates) {
|
|
1140
|
+
this.checkpoint(active)
|
|
1141
|
+
markStage("candidate-hash", candidate.entry)
|
|
1142
|
+
const verified = await this.verifiedAutomaticHash(
|
|
1143
|
+
active,
|
|
1144
|
+
candidate.entry,
|
|
1145
|
+
group.memory,
|
|
1146
|
+
verifiedHashes,
|
|
1147
|
+
counts,
|
|
1148
|
+
candidate
|
|
1149
|
+
)
|
|
1150
|
+
if (!verified) continue
|
|
1151
|
+
const hash = verified.hash
|
|
1152
|
+
let value = group.candidatesByHash.get(hash)
|
|
1153
|
+
if (!value) {
|
|
1154
|
+
value = { candidate: verified, identities: new Set() }
|
|
1155
|
+
group.candidatesByHash.set(hash, value)
|
|
1156
|
+
}
|
|
1157
|
+
value.identities.add(this.automaticIdentityKey(verified))
|
|
1158
|
+
if (value.identities.size > 1) {
|
|
1159
|
+
acceptProof(value.candidate, verified, hash)
|
|
1160
|
+
return true
|
|
1089
1161
|
}
|
|
1090
1162
|
}
|
|
1163
|
+
preparePeers()
|
|
1164
|
+
return true
|
|
1165
|
+
}
|
|
1166
|
+
const finishGroup = async () => {
|
|
1167
|
+
if (!group) return false
|
|
1168
|
+
await hashCandidates()
|
|
1169
|
+
if (proof) return true
|
|
1091
1170
|
group = null
|
|
1171
|
+
return false
|
|
1172
|
+
}
|
|
1173
|
+
const complete = async () => {
|
|
1174
|
+
this.checkpoint(active)
|
|
1175
|
+
markStage("result-signature")
|
|
1176
|
+
const result = await this.vault.registry.automaticPrecheckResult(
|
|
1177
|
+
app, proof ? [proof] : [])
|
|
1178
|
+
this.checkpoint(active)
|
|
1179
|
+
return Object.assign({}, counts, {
|
|
1180
|
+
signature: result.signature,
|
|
1181
|
+
verified_files: Math.max(0, Number(result.files) || 0),
|
|
1182
|
+
duration_ms: Date.now() - startedAt
|
|
1183
|
+
})
|
|
1092
1184
|
}
|
|
1093
1185
|
let cursor = null
|
|
1094
1186
|
do {
|
|
1187
|
+
markStage("candidate-query")
|
|
1188
|
+
counts.registry_pages += 1
|
|
1095
1189
|
const page = await this.vault.registry.automaticPrecheckEntries(
|
|
1096
1190
|
app, cursor, ENTRY_BATCH_SIZE)
|
|
1097
1191
|
this.checkpoint(active)
|
|
1098
1192
|
for (const entry of page.entries) {
|
|
1099
1193
|
const key = `${entry.dev}\0${entry.size}`
|
|
1100
1194
|
if (!group || group.key !== key) {
|
|
1101
|
-
finishGroup()
|
|
1195
|
+
if (await finishGroup()) return await complete()
|
|
1102
1196
|
group = {
|
|
1103
1197
|
key,
|
|
1198
|
+
candidates: [],
|
|
1199
|
+
candidatesHashed: false,
|
|
1104
1200
|
candidatesByHash: new Map(),
|
|
1105
|
-
matched: new Set(),
|
|
1106
1201
|
unmatched: null,
|
|
1107
|
-
unmatchedIdentities: null,
|
|
1108
1202
|
memory: new Map()
|
|
1109
1203
|
}
|
|
1110
1204
|
}
|
|
1111
1205
|
if (entry.kind === "changed") {
|
|
1112
|
-
const candidate = entry
|
|
1113
1206
|
this.checkpoint(active)
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
if (
|
|
1119
|
-
value = { candidates: [], identities: new Set() }
|
|
1120
|
-
group.candidatesByHash.set(hash, value)
|
|
1121
|
-
}
|
|
1122
|
-
value.candidates.push(candidate)
|
|
1123
|
-
value.identities.add(this.automaticIdentityKey(candidate))
|
|
1207
|
+
markStage("candidate-metadata", entry)
|
|
1208
|
+
counts.candidate_checks += 1
|
|
1209
|
+
const candidate = await this.currentAutomaticEntry(
|
|
1210
|
+
active, entry, counts)
|
|
1211
|
+
if (candidate) group.candidates.push(candidate)
|
|
1124
1212
|
continue
|
|
1125
1213
|
}
|
|
1126
|
-
|
|
1214
|
+
if (!group.candidatesHashed) await hashCandidates()
|
|
1215
|
+
if (proof) return await complete()
|
|
1216
|
+
if (group.candidatesHashed && !group.unmatched.size) continue
|
|
1217
|
+
markStage("peer-metadata", entry)
|
|
1218
|
+
counts.peer_checks += 1
|
|
1219
|
+
const peer = await this.currentAutomaticEntry(active, entry, counts)
|
|
1220
|
+
if (!peer) continue
|
|
1221
|
+
const identity = this.automaticIdentityKey(peer.entry)
|
|
1222
|
+
if (!await hashCandidates(identity)) continue
|
|
1223
|
+
if (proof) return await complete()
|
|
1127
1224
|
if (!group.unmatched.size) continue
|
|
1225
|
+
if (![...group.unmatched.values()].some((wanted) =>
|
|
1226
|
+
wanted.identity !== identity)) continue
|
|
1128
1227
|
this.checkpoint(active)
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
const
|
|
1133
|
-
active, entry, group.memory, verifiedHashes, counts)
|
|
1134
|
-
const wanted = hash && group.unmatched.get(hash)
|
|
1228
|
+
markStage("peer-hash", entry)
|
|
1229
|
+
const verified = await this.verifiedAutomaticHash(
|
|
1230
|
+
active, entry, group.memory, verifiedHashes, counts, peer)
|
|
1231
|
+
const wanted = verified && group.unmatched.get(verified.hash)
|
|
1135
1232
|
if (!wanted || wanted.identity === identity) {
|
|
1136
1233
|
continue
|
|
1137
1234
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
}
|
|
1141
|
-
group.unmatched.delete(hash)
|
|
1142
|
-
const remaining = group.unmatchedIdentities.get(wanted.identity) - 1
|
|
1143
|
-
if (remaining) {
|
|
1144
|
-
group.unmatchedIdentities.set(wanted.identity, remaining)
|
|
1145
|
-
} else {
|
|
1146
|
-
group.unmatchedIdentities.delete(wanted.identity)
|
|
1147
|
-
}
|
|
1235
|
+
acceptProof(wanted.candidate, verified, verified.hash)
|
|
1236
|
+
return await complete()
|
|
1148
1237
|
}
|
|
1149
1238
|
if (verifiedHashes.size >= ENTRY_BATCH_SIZE) {
|
|
1239
|
+
markStage("hash-cache-write")
|
|
1150
1240
|
await this.vault.registry.rememberHashCache(
|
|
1151
1241
|
[...verifiedHashes.values()])
|
|
1152
1242
|
verifiedHashes.clear()
|
|
@@ -1154,24 +1244,26 @@ class AutomaticScans {
|
|
|
1154
1244
|
}
|
|
1155
1245
|
cursor = page.next_cursor
|
|
1156
1246
|
} while (cursor)
|
|
1157
|
-
finishGroup()
|
|
1158
|
-
|
|
1159
|
-
const result = await this.vault.registry.automaticPrecheckResult(
|
|
1160
|
-
app, verifiedMatches)
|
|
1161
|
-
this.checkpoint(active)
|
|
1162
|
-
return Object.assign({}, counts, {
|
|
1163
|
-
signature: result.signature,
|
|
1164
|
-
verified_files: Math.max(0, Number(result.files) || 0),
|
|
1165
|
-
duration_ms: Date.now() - startedAt
|
|
1166
|
-
})
|
|
1247
|
+
await finishGroup()
|
|
1248
|
+
return await complete()
|
|
1167
1249
|
} finally {
|
|
1168
1250
|
try {
|
|
1169
1251
|
if (verifiedHashes.size) {
|
|
1170
|
-
|
|
1171
|
-
|
|
1252
|
+
try {
|
|
1253
|
+
await this.vault.registry.rememberHashCache(
|
|
1254
|
+
[...verifiedHashes.values()])
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
markStage("hash-cache-write")
|
|
1257
|
+
throw error
|
|
1258
|
+
}
|
|
1172
1259
|
}
|
|
1173
1260
|
} finally {
|
|
1174
|
-
|
|
1261
|
+
try {
|
|
1262
|
+
await this.vault.registry.abortAutomaticPrecheck(app)
|
|
1263
|
+
} catch (error) {
|
|
1264
|
+
markStage("staging-cleanup")
|
|
1265
|
+
throw error
|
|
1266
|
+
}
|
|
1175
1267
|
}
|
|
1176
1268
|
}
|
|
1177
1269
|
}
|
|
@@ -1275,10 +1367,22 @@ class AutomaticScans {
|
|
|
1275
1367
|
? (error.code === "EVAULTCANCELLED" ? "cancelled" : "failed")
|
|
1276
1368
|
: "complete",
|
|
1277
1369
|
cancel_reason: reason,
|
|
1278
|
-
duration_ms: result
|
|
1370
|
+
duration_ms: result
|
|
1371
|
+
? result.duration_ms
|
|
1372
|
+
: error && active.startedAt
|
|
1373
|
+
? Date.now() - active.startedAt
|
|
1374
|
+
: undefined,
|
|
1375
|
+
failure_stage: error && active.stage,
|
|
1376
|
+
failure_path: error && (error.path || active.stagePath),
|
|
1377
|
+
failure_kind: error && active.stageKind,
|
|
1378
|
+
error_code: error && error.code,
|
|
1379
|
+
error_syscall: error && error.syscall,
|
|
1279
1380
|
files: result && result.files,
|
|
1280
1381
|
bytes: result && result.bytes,
|
|
1281
1382
|
candidates: result && result.candidates,
|
|
1383
|
+
registry_pages: result && result.registry_pages,
|
|
1384
|
+
candidate_checks: result && result.candidate_checks,
|
|
1385
|
+
peer_checks: result && result.peer_checks,
|
|
1282
1386
|
verified_files: result && result.verified_files,
|
|
1283
1387
|
hashed: result && result.hashed,
|
|
1284
1388
|
hash_bytes: result && result.hash_bytes,
|