@amalgm/shell 0.1.105 → 0.1.106
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 +14 -0
- package/dist/entity-apply-store.js +60 -4
- package/dist/entity-apply-store.js.map +1 -1
- package/dist/entity-record-store.js +15 -1
- package/dist/entity-record-store.js.map +1 -1
- package/dist/snapshot-delivery-baseline.d.ts +12 -0
- package/dist/snapshot-delivery-baseline.js +58 -0
- package/dist/snapshot-delivery-baseline.js.map +1 -0
- package/dist/user-ground-host.d.ts +5 -0
- package/dist/user-ground-host.js +147 -20
- package/dist/user-ground-host.js.map +1 -1
- package/dist/wire-client.js +1 -1
- package/package.json +2 -2
package/dist/user-ground-host.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync, lstatSync, realpathSync, readFileSync, readdirSync, readlin
|
|
|
3
3
|
import { open } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { buildUserHomeManifest, buildUserManifest, liveMachineStateDir, scopedAmalgmDir, shippedUserHomeDeclaration, } from "@amalgm/core/identity";
|
|
6
|
-
import { CHUNK_BYTES, CONTENT_CONTRACT, INLINE_CARGO_MAX_BYTES, ENTITY_CLOUD_CONTRACT, ENTITY_CLOUD_SCHEMA_VERSION, artifactForRecord, canonicalVersion, checkContentManifest, classifyDirectory, classifyFile, classifyRegisteredRoot, createLocalEntityRecord, createEntityRecordAuthorityPort, convergeUserGround, createUserGroundEnrollmentPolicy, isRepositoryMetadataEntry, indexRepositoryTerritory, downloadAll as downloadAllArtifacts, encodeEntityRecord, EntityApplyRail, EntityRecordRail, membershipHash, parseSnapshot, pathIsSuspect, PRESIGN_BATCH_OBJECTS, privateEntityResourceId, repositoryIdentityHash, rootReplacementFromRecords, sameRecords, snapshotFromRecords, stableJson, travelingRecords, upload as uploadArtifact, userGroundRecords, } from "@amalgm/live";
|
|
6
|
+
import { CHUNK_BYTES, CONTENT_CONTRACT, INLINE_CARGO_MAX_BYTES, ENTITY_CLOUD_CONTRACT, ENTITY_CLOUD_SCHEMA_VERSION, artifactForRecord, canonicalVersion, checkContentManifest, classifyDirectory, classifyFile, classifyRegisteredRoot, createLocalEntityRecord, createEntityRecordAuthorityPort, convergeUserGround, decodeAcceptedEntityRecord, createUserGroundEnrollmentPolicy, isRepositoryMetadataEntry, indexRepositoryTerritory, downloadAll as downloadAllArtifacts, encodeEntityRecord, EntityApplyRail, EntityRecordRail, membershipHash, parseSnapshot, pathIsSuspect, PRESIGN_BATCH_OBJECTS, privateEntityResourceId, repositoryIdentityHash, rootReplacementFromRecords, sameRecords, snapshotFromRecords, stableJson, travelingRecords, upload as uploadArtifact, userGroundRecords, } from "@amalgm/live";
|
|
7
7
|
import Database from "better-sqlite3";
|
|
8
8
|
import { atomicCopy, atomicWrite, ensurePrivateDir, ensureUserDir } from "./filesystem.js";
|
|
9
9
|
import { emitFilesStage, } from "./files-observability.js";
|
|
@@ -25,6 +25,7 @@ import { projectMaterializedGraph } from "./materialized-graph.js";
|
|
|
25
25
|
import { NodeWatchHost, } from "./watching/index.js";
|
|
26
26
|
import { GroundCoordinator } from "./ground-coordination.js";
|
|
27
27
|
import { submitMutationOperation } from "./paged-mutation-submit.js";
|
|
28
|
+
import { adoptSnapshotDeliveryBaseline } from "./snapshot-delivery-baseline.js";
|
|
28
29
|
import { WireClient, WireRequestError } from "./wire-client.js";
|
|
29
30
|
const SMALL_CONTENT_UPLOAD_CONCURRENCY = 16;
|
|
30
31
|
/** Storage-object lanes for one download set. Tiny objects use the wide
|
|
@@ -259,13 +260,31 @@ export class UserGroundHost {
|
|
|
259
260
|
const cloud = await this.cloudPort(identity).lookup(resourceId);
|
|
260
261
|
if (!cloud)
|
|
261
262
|
throw new Error("this user has no cloud entity registry");
|
|
263
|
+
// The authority's active snapshot is the compact base; Add's active
|
|
264
|
+
// materialization head is that snapshot plus the complete committed
|
|
265
|
+
// tail this machine has now received. Fold the tail before Live
|
|
266
|
+
// selects the workspace so a destination created after mutations is
|
|
267
|
+
// exact immediately, rather than first installing stale ground and
|
|
268
|
+
// racing Apply afterward.
|
|
269
|
+
await this.recordRail?.flush();
|
|
270
|
+
const deliveryWatermark = readAuthorityDeliveryCursor(this.databasePath(identity), resourceId);
|
|
271
|
+
if (deliveryWatermark < cloud.deliveryWatermark) {
|
|
272
|
+
throw new Error("local delivery cursor is behind the active snapshot watermark");
|
|
273
|
+
}
|
|
274
|
+
const snapshot = snapshotFromRecords(recordsAtDeliveryPrefix(cloud.snapshot.records, readAcceptedResults(this.databasePath(identity), resourceId, cloud.deliveryWatermark, deliveryWatermark)));
|
|
275
|
+
const current = {
|
|
276
|
+
...cloud,
|
|
277
|
+
deliveryWatermark,
|
|
278
|
+
checksum: sha256Hex(stableJson(snapshot)),
|
|
279
|
+
snapshot,
|
|
280
|
+
};
|
|
262
281
|
this.stage({
|
|
263
282
|
primitive: "download",
|
|
264
283
|
stage: "registry-lookup",
|
|
265
284
|
status: "completed",
|
|
266
285
|
durationMs: performance.now() - started,
|
|
267
286
|
});
|
|
268
|
-
return
|
|
287
|
+
return current;
|
|
269
288
|
},
|
|
270
289
|
materializeWorkspace: async (input) => {
|
|
271
290
|
const groundWaitStarted = performance.now();
|
|
@@ -298,9 +317,10 @@ export class UserGroundHost {
|
|
|
298
317
|
records: input.records,
|
|
299
318
|
destinationParent: input.destinationParent,
|
|
300
319
|
cloudHead: input.cloudHead,
|
|
320
|
+
deliveryWatermark: input.deliveryWatermark,
|
|
301
321
|
readContent: (artifacts) => this.downloadContentSet(privateEntityResourceId(identity.userId, sha256Hex), this.cacheDir(identity), artifacts.map((artifact) => ({ artifact })), 10, { journey: "add", workspaceId: input.workspace.uuid }),
|
|
302
322
|
adoptMaterialization: (rootUUID) => {
|
|
303
|
-
const settled = this.
|
|
323
|
+
const settled = this.adoptCloudDeliveryBaseline(identity, input.deliveryWatermark, rootUUID);
|
|
304
324
|
this.applyRail?.recordsAvailable();
|
|
305
325
|
if (settled > 0) {
|
|
306
326
|
this.stage({
|
|
@@ -424,9 +444,10 @@ export class UserGroundHost {
|
|
|
424
444
|
records: intent.records,
|
|
425
445
|
destinationParent: dirname(intent.destinationPath),
|
|
426
446
|
cloudHead: intent.cloudHead,
|
|
447
|
+
deliveryWatermark: intent.deliveryWatermark,
|
|
427
448
|
readContent: (artifacts) => this.downloadContentSet(intent.resourceId, this.cacheDir(identity), artifacts.map((artifact) => ({ artifact })), 10, { journey: "add", workspaceId: intent.workspaceId }),
|
|
428
449
|
adoptMaterialization: (rootUUID) => {
|
|
429
|
-
this.
|
|
450
|
+
this.adoptCloudDeliveryBaseline(identity, intent.deliveryWatermark, rootUUID);
|
|
430
451
|
this.applyRail?.recordsAvailable();
|
|
431
452
|
},
|
|
432
453
|
onStage: (stage) => this.options.onWorkspaceAddStage?.({
|
|
@@ -504,7 +525,8 @@ export class UserGroundHost {
|
|
|
504
525
|
// the publish path re-proves seal evidence and repairs any missing
|
|
505
526
|
// uploads before this answer is lawful.
|
|
506
527
|
try {
|
|
507
|
-
await this.
|
|
528
|
+
const deliveryWatermark = await this.prepareSnapshotPublication(identity, { journey: "register", workspaceId });
|
|
529
|
+
await this.publishMaterializedSnapshot(identity, new Set([workspaceId]), deliveryWatermark, { journey: "register", workspaceId });
|
|
508
530
|
}
|
|
509
531
|
finally {
|
|
510
532
|
this.finishColdOperation();
|
|
@@ -519,6 +541,10 @@ export class UserGroundHost {
|
|
|
519
541
|
// Register is a cold declaration, not a filesystem edit. It performs one
|
|
520
542
|
// complete reconciliation and publishes one graph head; it must not mint
|
|
521
543
|
// a parallel stream of Detect records for the same initial contents.
|
|
544
|
+
// Freeze the official prefix before taking ground ownership. Apply may
|
|
545
|
+
// need that same ground, so draining it inside the registration scope
|
|
546
|
+
// would make the two rails wait on one another.
|
|
547
|
+
const deliveryWatermark = await this.prepareSnapshotPublication(identity, { journey: "register", workspaceId });
|
|
522
548
|
let releaseGround = null;
|
|
523
549
|
try {
|
|
524
550
|
const groundWaitStarted = performance.now();
|
|
@@ -612,7 +638,7 @@ export class UserGroundHost {
|
|
|
612
638
|
if (!scanned.publicationRootIds) {
|
|
613
639
|
throw new Error("cold registration reconciliation did not name its publication roots");
|
|
614
640
|
}
|
|
615
|
-
await this.publishMaterializedSnapshot(identity, new Set(scanned.publicationRootIds), { journey: "register", workspaceId });
|
|
641
|
+
await this.publishMaterializedSnapshot(identity, new Set(scanned.publicationRootIds), deliveryWatermark, { journey: "register", workspaceId });
|
|
616
642
|
this.watchHost.settle(observations);
|
|
617
643
|
}
|
|
618
644
|
catch (error) {
|
|
@@ -710,6 +736,20 @@ export class UserGroundHost {
|
|
|
710
736
|
if (this.syncing)
|
|
711
737
|
await this.syncing;
|
|
712
738
|
}
|
|
739
|
+
/** Install the exact snapshot/tail boundary before Receive or Apply can
|
|
740
|
+
* treat older official history as materialization work. The SQLite cursor,
|
|
741
|
+
* retained Inbox resolution, and Apply baseline all derive from the same N. */
|
|
742
|
+
adoptCloudDeliveryBaseline(identity, deliveryWatermark, rootUUID = null) {
|
|
743
|
+
const database = initializeDatabase(this.databasePath(identity));
|
|
744
|
+
let resolved = 0;
|
|
745
|
+
try {
|
|
746
|
+
resolved = adoptSnapshotDeliveryBaseline(database, privateEntityResourceId(identity.userId, sha256Hex), deliveryWatermark, this.options.now?.().getTime() ?? Date.now());
|
|
747
|
+
}
|
|
748
|
+
finally {
|
|
749
|
+
database.close();
|
|
750
|
+
}
|
|
751
|
+
return resolved + (this.applyHost?.adoptVerifiedMaterialization(rootUUID) ?? 0);
|
|
752
|
+
}
|
|
713
753
|
async startRecordRail(identity) {
|
|
714
754
|
if (!this.cloudState)
|
|
715
755
|
throw new Error("entity Record rail requires cloud authority");
|
|
@@ -717,12 +757,13 @@ export class UserGroundHost {
|
|
|
717
757
|
throw new Error("entity Record rail already started");
|
|
718
758
|
await this.wire.open();
|
|
719
759
|
// One floor, no per-feature gates: this shell and its gateway release
|
|
720
|
-
// together, and the gateway ships first.
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
if (this.wire.protocolVersion <
|
|
760
|
+
// together, and the gateway ships first. 11 = snapshot delivery
|
|
761
|
+
// watermarks; an older gateway cannot atomically bind Add's graph to the
|
|
762
|
+
// official entity-record prefix it already includes.
|
|
763
|
+
if (this.wire.protocolVersion < 11) {
|
|
724
764
|
throw new Error("gateway speaks an older wire protocol than this shell");
|
|
725
765
|
}
|
|
766
|
+
this.adoptCloudDeliveryBaseline(identity, this.cloudState.deliveryWatermark);
|
|
726
767
|
const store = new EntityRecordSqliteStore(initializeDatabase(this.databasePath(identity)));
|
|
727
768
|
const wireAuthority = createEntityRecordAuthorityPort({
|
|
728
769
|
request: (frame, acceptedTypes, binaryBody) => binaryBody === undefined
|
|
@@ -1760,6 +1801,7 @@ export class UserGroundHost {
|
|
|
1760
1801
|
authorityEpoch: first.authorityEpoch,
|
|
1761
1802
|
headVersion: first.baseVersion,
|
|
1762
1803
|
checksum: "",
|
|
1804
|
+
deliveryWatermark: first.deliveryWatermark,
|
|
1763
1805
|
records: snapshotFromRecords(JSON.parse(first.snapshotJson).records || []).records,
|
|
1764
1806
|
};
|
|
1765
1807
|
try {
|
|
@@ -1778,13 +1820,10 @@ export class UserGroundHost {
|
|
|
1778
1820
|
/** Register is the deliberate cold graph-publication boundary. Ordinary
|
|
1779
1821
|
* Watch detection stops at a durable exact record and never rebuilds this
|
|
1780
1822
|
* resource snapshot. */
|
|
1781
|
-
async
|
|
1782
|
-
//
|
|
1783
|
-
//
|
|
1784
|
-
//
|
|
1785
|
-
// and a conclusive refusal marks the row rejected so the snapshot
|
|
1786
|
-
// derived below may supersede it. A transport failure keeps the row
|
|
1787
|
-
// pending — its outcome is unknown, so nothing may replace it.
|
|
1823
|
+
async prepareSnapshotPublication(identity, trace) {
|
|
1824
|
+
// An older durable publication resolves before the new boundary derives.
|
|
1825
|
+
// Unknown outcomes remain pending; a conclusive refusal may be replaced
|
|
1826
|
+
// by the newer complete snapshot below.
|
|
1788
1827
|
try {
|
|
1789
1828
|
await this.publishPendingSnapshots(identity, trace);
|
|
1790
1829
|
}
|
|
@@ -1792,13 +1831,30 @@ export class UserGroundHost {
|
|
|
1792
1831
|
if (!conclusivelyRefused(error))
|
|
1793
1832
|
throw error;
|
|
1794
1833
|
}
|
|
1834
|
+
await this.recordRail?.flush();
|
|
1835
|
+
const state = this.cloudState;
|
|
1836
|
+
if (!state)
|
|
1837
|
+
throw new Error("cloud state is unavailable for graph publication");
|
|
1838
|
+
// Freeze N immediately after Receive reaches its explicit boundary. Apply
|
|
1839
|
+
// then settles every Inbox row already known at that boundary. A later
|
|
1840
|
+
// delivery may run concurrently, but the graph still declares the older
|
|
1841
|
+
// N and the authority refuses it if that newer record commits first.
|
|
1842
|
+
const deliveryWatermark = readAuthorityDeliveryCursor(this.databasePath(identity), state.resourceId);
|
|
1843
|
+
if (deliveryWatermark < state.deliveryWatermark) {
|
|
1844
|
+
throw new Error("local delivery cursor is behind the active snapshot watermark");
|
|
1845
|
+
}
|
|
1846
|
+
await this.applyRail?.flush();
|
|
1847
|
+
return deliveryWatermark;
|
|
1848
|
+
}
|
|
1849
|
+
async publishMaterializedSnapshot(identity, publicationRootIds, deliveryWatermark, trace) {
|
|
1795
1850
|
const state = this.cloudState;
|
|
1796
1851
|
if (!state)
|
|
1797
1852
|
throw new Error("cloud state is unavailable for graph publication");
|
|
1853
|
+
const officialRecords = recordsAtDeliveryPrefix(state.records, readAcceptedResults(this.databasePath(identity), state.resourceId, state.deliveryWatermark, deliveryWatermark));
|
|
1798
1854
|
const localRecords = recordsRetainingMissingEvidence(readGroundRowsForRoots(this.databasePath(identity), "entities", publicationRootIds)
|
|
1799
1855
|
.map(portableRecord), readGroundRowsForRoots(this.databasePath(identity), "detection_notebook", publicationRootIds)
|
|
1800
1856
|
.map(portableRecord));
|
|
1801
|
-
const merged = mergeMaterializedRoots(
|
|
1857
|
+
const merged = mergeMaterializedRoots(officialRecords, localRecords);
|
|
1802
1858
|
const traveling = travelingRecords(merged, sha256Hex);
|
|
1803
1859
|
const impossibleGitLeaf = traveling.find((record) => (record.type === "file.text" || record.type === "file.binary" || record.type === "link")
|
|
1804
1860
|
&& record.payloadVersion?.startsWith("git:"));
|
|
@@ -1826,7 +1882,7 @@ export class UserGroundHost {
|
|
|
1826
1882
|
const uploadRecords = cargo
|
|
1827
1883
|
.filter((entry) => !sealedArtifacts.has(entry.contentHash))
|
|
1828
1884
|
.map((entry) => entry.record);
|
|
1829
|
-
if (checksum === state.checksum) {
|
|
1885
|
+
if (checksum === state.checksum && deliveryWatermark === state.deliveryWatermark) {
|
|
1830
1886
|
// The graph is already committed; only cargo can be missing. Repair
|
|
1831
1887
|
// uploads need no journal row — a crash simply re-runs the same
|
|
1832
1888
|
// repair on the next Register.
|
|
@@ -1857,6 +1913,7 @@ export class UserGroundHost {
|
|
|
1857
1913
|
baseVersion: state.headVersion,
|
|
1858
1914
|
snapshotChecksum: checksum,
|
|
1859
1915
|
snapshotJson,
|
|
1916
|
+
deliveryWatermark,
|
|
1860
1917
|
uploadRecords,
|
|
1861
1918
|
replacementRootIds: replacement.rootIds,
|
|
1862
1919
|
replacementRecords: replacement.records,
|
|
@@ -1923,6 +1980,7 @@ export class UserGroundHost {
|
|
|
1923
1980
|
contract: ENTITY_CLOUD_CONTRACT,
|
|
1924
1981
|
schemaVersion: ENTITY_CLOUD_SCHEMA_VERSION,
|
|
1925
1982
|
operationKind: replacement.kind,
|
|
1983
|
+
deliveryWatermark: pending.deliveryWatermark,
|
|
1926
1984
|
}, replacement);
|
|
1927
1985
|
}
|
|
1928
1986
|
catch (error) {
|
|
@@ -1959,6 +2017,7 @@ export class UserGroundHost {
|
|
|
1959
2017
|
authorityEpoch: Number(frame.authority_epoch),
|
|
1960
2018
|
headVersion: Number(frame.version),
|
|
1961
2019
|
checksum: pending.snapshotChecksum,
|
|
2020
|
+
deliveryWatermark: Number(frame.delivery_watermark),
|
|
1962
2021
|
records: snapshot.records,
|
|
1963
2022
|
};
|
|
1964
2023
|
deleteJournalEntry(this.databasePath(identity), pending.mutationId);
|
|
@@ -2147,6 +2206,10 @@ function initializeDatabase(file) {
|
|
|
2147
2206
|
authority_id TEXT PRIMARY KEY,
|
|
2148
2207
|
received_through INTEGER NOT NULL CHECK(received_through >= 0)
|
|
2149
2208
|
);
|
|
2209
|
+
CREATE TABLE IF NOT EXISTS authority_snapshot_baselines (
|
|
2210
|
+
authority_id TEXT PRIMARY KEY,
|
|
2211
|
+
delivery_sequence INTEGER NOT NULL CHECK(delivery_sequence >= 0)
|
|
2212
|
+
);
|
|
2150
2213
|
CREATE TABLE IF NOT EXISTS snapshot_publications (
|
|
2151
2214
|
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2152
2215
|
mutation_id TEXT NOT NULL UNIQUE,
|
|
@@ -2456,9 +2519,13 @@ function parseWorkspaceAddIntent(json) {
|
|
|
2456
2519
|
const cloudHead = String(value.cloudHead || "");
|
|
2457
2520
|
const destinationPath = String(value.destinationPath || "");
|
|
2458
2521
|
const stagingPath = String(value.stagingPath || "");
|
|
2522
|
+
const deliveryWatermark = Number(value.deliveryWatermark);
|
|
2459
2523
|
if (!UUID.test(workspaceId) || !resourceId || !/^[0-9a-f]{64}$/i.test(cloudHead)) {
|
|
2460
2524
|
throw new Error("workspace Add intent identity is invalid");
|
|
2461
2525
|
}
|
|
2526
|
+
if (!Number.isSafeInteger(deliveryWatermark) || deliveryWatermark < 0) {
|
|
2527
|
+
throw new Error(`workspace Add intent ${workspaceId} has no delivery watermark`);
|
|
2528
|
+
}
|
|
2462
2529
|
if (!isAbsolute(destinationPath)
|
|
2463
2530
|
|| stagingPath !== workspaceAddStagingPath(destinationPath, workspaceId)) {
|
|
2464
2531
|
throw new Error(`workspace Add intent ${workspaceId} has invalid placement`);
|
|
@@ -2467,6 +2534,7 @@ function parseWorkspaceAddIntent(json) {
|
|
|
2467
2534
|
workspaceId,
|
|
2468
2535
|
resourceId,
|
|
2469
2536
|
cloudHead: cloudHead.toLowerCase(),
|
|
2537
|
+
deliveryWatermark,
|
|
2470
2538
|
destinationPath,
|
|
2471
2539
|
stagingPath,
|
|
2472
2540
|
records: snapshotFromRecords(Array.isArray(value.records) ? value.records : []).records,
|
|
@@ -2578,11 +2646,16 @@ function readSnapshotPublications(file) {
|
|
|
2578
2646
|
|| !Array.isArray(publication.replacementRecords)) {
|
|
2579
2647
|
throw new Error(`snapshot publication ${row.mutationId} has no exact root replacement`);
|
|
2580
2648
|
}
|
|
2649
|
+
const deliveryWatermark = Number(publication.deliveryWatermark);
|
|
2650
|
+
if (!Number.isSafeInteger(deliveryWatermark) || deliveryWatermark < 0) {
|
|
2651
|
+
throw new Error(`snapshot publication ${row.mutationId} has no delivery watermark`);
|
|
2652
|
+
}
|
|
2581
2653
|
return {
|
|
2582
2654
|
mutationId: row.mutationId,
|
|
2583
2655
|
resourceId: row.resourceId,
|
|
2584
2656
|
authorityEpoch: Number(publication.authorityEpoch),
|
|
2585
2657
|
baseVersion: Number(publication.baseVersion),
|
|
2658
|
+
deliveryWatermark,
|
|
2586
2659
|
snapshotChecksum: String(publication.snapshotChecksum),
|
|
2587
2660
|
snapshotJson: String(publication.snapshotJson),
|
|
2588
2661
|
uploadRecords: snapshotFromRecords(publication.uploadRecords).records,
|
|
@@ -2609,6 +2682,55 @@ function hasRecordsBeyondSnapshot(file) {
|
|
|
2609
2682
|
database.close();
|
|
2610
2683
|
}
|
|
2611
2684
|
}
|
|
2685
|
+
function readAuthorityDeliveryCursor(file, authorityId) {
|
|
2686
|
+
const database = initializeDatabase(file);
|
|
2687
|
+
try {
|
|
2688
|
+
const row = database.prepare(`
|
|
2689
|
+
SELECT received_through AS receivedThrough
|
|
2690
|
+
FROM authority_delivery_cursors WHERE authority_id = ?
|
|
2691
|
+
`).get(authorityId);
|
|
2692
|
+
return row?.receivedThrough ?? 0;
|
|
2693
|
+
}
|
|
2694
|
+
finally {
|
|
2695
|
+
database.close();
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
/** Latest official result for every UUID in the newly received complete
|
|
2699
|
+
* authority prefix. The record carries the whole logical result, so roots
|
|
2700
|
+
* this machine has not materialized still advance honestly in a Register
|
|
2701
|
+
* snapshot without inventing filesystem evidence. */
|
|
2702
|
+
function readAcceptedResults(file, authorityId, afterDelivery, throughDelivery) {
|
|
2703
|
+
if (throughDelivery <= afterDelivery)
|
|
2704
|
+
return [];
|
|
2705
|
+
const database = initializeDatabase(file);
|
|
2706
|
+
try {
|
|
2707
|
+
const latest = new Map();
|
|
2708
|
+
for (const { recordJson } of database.prepare(`
|
|
2709
|
+
SELECT record_json AS recordJson FROM record_inbox
|
|
2710
|
+
WHERE authority_id = ?
|
|
2711
|
+
AND delivery_sequence > ? AND delivery_sequence <= ?
|
|
2712
|
+
ORDER BY delivery_sequence
|
|
2713
|
+
`).iterate(authorityId, afterDelivery, throughDelivery)) {
|
|
2714
|
+
const accepted = decodeAcceptedEntityRecord(recordJson);
|
|
2715
|
+
latest.set(accepted.entityId, accepted.change.result);
|
|
2716
|
+
}
|
|
2717
|
+
return [...latest.values()];
|
|
2718
|
+
}
|
|
2719
|
+
finally {
|
|
2720
|
+
database.close();
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
function recordsAtDeliveryPrefix(base, results) {
|
|
2724
|
+
if (results.length === 0)
|
|
2725
|
+
return base;
|
|
2726
|
+
const records = new Map(base.map((record) => [record.uuid, record]));
|
|
2727
|
+
for (const result of results)
|
|
2728
|
+
records.set(result.uuid, result);
|
|
2729
|
+
// Entity channels are independently ordered. Container membership is the
|
|
2730
|
+
// one derived relationship, so derive it once from the final prefix rather
|
|
2731
|
+
// than letting record arrival order choose a container version.
|
|
2732
|
+
return recordsRetainingMissingEvidence([...records.values()], []);
|
|
2733
|
+
}
|
|
2612
2734
|
/** Record is the SQLite transaction that accepts Detect's exact proposals and
|
|
2613
2735
|
* advances the last-verified notebook. Watch may settle only after this
|
|
2614
2736
|
* function returns. The record table is the local-only outbox. */
|
|
@@ -2711,6 +2833,7 @@ function supersedeSnapshotJournal(file, entry) {
|
|
|
2711
2833
|
`).run(entry.mutationId, entry.resourceId, stableJson({
|
|
2712
2834
|
authorityEpoch: entry.authorityEpoch,
|
|
2713
2835
|
baseVersion: entry.baseVersion,
|
|
2836
|
+
deliveryWatermark: entry.deliveryWatermark,
|
|
2714
2837
|
snapshotChecksum: entry.snapshotChecksum,
|
|
2715
2838
|
snapshotJson: entry.snapshotJson,
|
|
2716
2839
|
uploadRecords: entry.uploadRecords,
|
|
@@ -4600,7 +4723,7 @@ function installCloudWorkspaceReferences(input) {
|
|
|
4600
4723
|
return references;
|
|
4601
4724
|
}
|
|
4602
4725
|
async function installCloudWorkspace(input) {
|
|
4603
|
-
const { identity, userRoot, database, cacheDir, bindingDir, resourceId, workspace, reference, references, records, destinationParent, cloudHead, readContent, onStage, } = input;
|
|
4726
|
+
const { identity, userRoot, database, cacheDir, bindingDir, resourceId, workspace, reference, references, records, destinationParent, cloudHead, deliveryWatermark, readContent, onStage, } = input;
|
|
4604
4727
|
const allLocalRows = readRows(database);
|
|
4605
4728
|
const existingBoundary = allLocalRows.find((row) => row.uuid === workspace.uuid);
|
|
4606
4729
|
const existing = existingBoundary
|
|
@@ -4654,6 +4777,7 @@ async function installCloudWorkspace(input) {
|
|
|
4654
4777
|
workspace: activeSelection.workspace,
|
|
4655
4778
|
references: activeReferences,
|
|
4656
4779
|
});
|
|
4780
|
+
input.adoptMaterialization(workspace.uuid);
|
|
4657
4781
|
return {
|
|
4658
4782
|
path: root.absolutePath,
|
|
4659
4783
|
rows: existing.length,
|
|
@@ -4707,6 +4831,7 @@ async function installCloudWorkspace(input) {
|
|
|
4707
4831
|
workspaceId: workspace.uuid,
|
|
4708
4832
|
resourceId,
|
|
4709
4833
|
cloudHead,
|
|
4834
|
+
deliveryWatermark,
|
|
4710
4835
|
destinationPath: destination,
|
|
4711
4836
|
stagingPath,
|
|
4712
4837
|
records: snapshotFromRecords(records).records,
|
|
@@ -4785,6 +4910,7 @@ function readyFromFrame(frame) {
|
|
|
4785
4910
|
const cloud = {
|
|
4786
4911
|
resourceId: String(frame.resource_id || ""),
|
|
4787
4912
|
version: Number(frame.head_version),
|
|
4913
|
+
deliveryWatermark: Number(frame.delivery_watermark),
|
|
4788
4914
|
checksum: String(frame.snapshot_checksum || ""),
|
|
4789
4915
|
snapshot: parseSnapshot(bytes),
|
|
4790
4916
|
};
|
|
@@ -4795,6 +4921,7 @@ function readyFromFrame(frame) {
|
|
|
4795
4921
|
authorityEpoch: Number(frame.authority_epoch),
|
|
4796
4922
|
headVersion: cloud.version,
|
|
4797
4923
|
checksum: cloud.checksum,
|
|
4924
|
+
deliveryWatermark: cloud.deliveryWatermark,
|
|
4798
4925
|
records: cloud.snapshot.records,
|
|
4799
4926
|
},
|
|
4800
4927
|
};
|