@tearleads/client-sdk 0.1.112 → 0.1.113
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/data/persistence/keyingCheckpoints/keyingCheckpointAdvancePersistence.js +16 -11
- package/data/persistence/keyingCheckpoints/keyingCheckpointAdvancePersistence.js.map +1 -1
- package/data/persistence/keyingCheckpoints/keyingCheckpointPersistence.js +150 -2
- package/data/persistence/keyingCheckpoints/keyingCheckpointPersistence.js.map +1 -1
- package/data/persistence/keyingCheckpoints/principalAuthorizationCheckpoints.js +3 -2
- package/data/persistence/keyingCheckpoints/principalAuthorizationCheckpoints.js.map +1 -1
- package/data/persistence/principalHistory/principalHistoryEvidencePersistence.d.ts +14 -1
- package/data/persistence/principalHistory/principalHistoryEvidencePersistence.js +128 -34
- package/data/persistence/principalHistory/principalHistoryEvidencePersistence.js.map +1 -1
- package/data/persistence/principalHistory/principalHistoryNodeRetention.js +64 -24
- package/data/persistence/principalHistory/principalHistoryNodeRetention.js.map +1 -1
- package/data/persistence/principalHistory/principalHistoryPrefixPersistence.js +6 -1
- package/data/persistence/principalHistory/principalHistoryPrefixPersistence.js.map +1 -1
- package/data/persistence/principalHistory/principalHistoryRootOwnership.d.ts +45 -0
- package/data/persistence/principalHistory/principalHistoryRootOwnership.js +41 -4
- package/data/persistence/principalHistory/principalHistoryRootOwnership.js.map +1 -1
- package/data/persistence/principalHistory/principalHistoryStagePersistence.js +12 -8
- package/data/persistence/principalHistory/principalHistoryStagePersistence.js.map +1 -1
- package/data/persistence/principalKeys/principalGrantRetirementPersistence.js +35 -1
- package/data/persistence/principalKeys/principalGrantRetirementPersistence.js.map +1 -1
- package/data/persistence/principalPolicy/principalPolicyOrganizationPersistence.js +62 -1
- package/data/persistence/principalPolicy/principalPolicyOrganizationPersistence.js.map +1 -1
- package/data/persistence/trustedUserIdentityPinPersistence.js +46 -23
- package/data/persistence/trustedUserIdentityPinPersistence.js.map +1 -1
- package/data/persistence/verifiedManifestBundlePersistence.js +27 -10
- package/data/persistence/verifiedManifestBundlePersistence.js.map +1 -1
- package/data/sqlite/schema/principalHistoryNodeRetentionSchema.d.ts +222 -0
- package/package.json +1 -1
- package/workflows/principals/historyRecovery/loadRecoveredPrincipalHistoryPage.js +13 -12
- package/workflows/principals/historyRecovery/loadRecoveredPrincipalHistoryPage.js.map +1 -1
- package/workflows/principals/historyRecovery/principalHistoryRecoveryReferences.js +17 -19
- package/workflows/principals/historyRecovery/principalHistoryRecoveryReferences.js.map +1 -1
- package/workflows/principals/publicHistory/selectPublicPrincipalHistory.js +8 -10
- package/workflows/principals/publicHistory/selectPublicPrincipalHistory.js.map +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { compareCanonicalStrings, KeyingVerificationError, PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT, principalHistoryIndexLeaf, resolvePrincipalHistoryIndexProof, } from "../../../internal/crypto/index.js";
|
|
2
2
|
import { isPrincipalPolicyStateChainEntryResponse, } from "../../../internal/validators/response/index.js";
|
|
3
|
-
import { and, eq } from "drizzle-orm";
|
|
3
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
4
4
|
import { principalHistoryEntries, principalHistoryEvidenceTables, principalHistoryNodes, } from "../../sqlite/schema/principalHistoryEvidenceSchema.js";
|
|
5
|
-
import { sqliteInsertBatchSize } from "../../sqlite/sqliteBindBudget.js";
|
|
5
|
+
import { sqliteInsertBatchSize, sqliteRowsPerStatement, } from "../../sqlite/sqliteBindBudget.js";
|
|
6
6
|
import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
|
|
7
7
|
import { excludedValue } from "../../sqlite/sqliteUpsert.js";
|
|
8
8
|
import { ensureSqlTables } from "../../sqlite/sqlSchema.js";
|
|
@@ -23,7 +23,7 @@ export async function preparePrincipalHistoryEvidencePage(input) {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
/** Publish in the same guarded transaction as the accepted page's progress. */
|
|
26
|
-
export async function writePrincipalHistoryEvidencePage(tx, page) {
|
|
26
|
+
export async function writePrincipalHistoryEvidencePage(tx, page, rootEdges) {
|
|
27
27
|
const scope = { scopeId: page.scopeId, organizationId: page.organizationId };
|
|
28
28
|
// A repeated leaf keeps its last entry, as sequential upserts would.
|
|
29
29
|
const entries = new Map(page.entries.map((entry) => [
|
|
@@ -46,47 +46,141 @@ export async function writePrincipalHistoryEvidencePage(tx, page) {
|
|
|
46
46
|
},
|
|
47
47
|
})
|
|
48
48
|
.run();
|
|
49
|
-
await writePrincipalHistoryNodes(tx, scope, [...page.nodes].sort((a, b) => compareCanonicalStrings(a.hash, b.hash)));
|
|
49
|
+
await writePrincipalHistoryNodes(tx, scope, [...page.nodes].sort((a, b) => compareCanonicalStrings(a.hash, b.hash)), rootEdges);
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
// The scope id binds once per lookup.
|
|
52
|
+
const HASH_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);
|
|
53
|
+
/** Thrown by the preloaded node reader to ask the next level's set read for a hash. */
|
|
54
|
+
class PrincipalHistoryNodeNotLoaded {
|
|
55
|
+
hash;
|
|
56
|
+
constructor(hash) {
|
|
57
|
+
this.hash = hash;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function hashBatches(hashes) {
|
|
61
|
+
const result = [];
|
|
62
|
+
for (let index = 0; index < hashes.length; index += HASH_LOOKUP_BATCH_SIZE)
|
|
63
|
+
result.push(hashes.slice(index, index + HASH_LOOKUP_BATCH_SIZE));
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolves every version's index path level by level: each pass walks every
|
|
68
|
+
* unfinished version over the nodes read so far, and one set read per level
|
|
69
|
+
* supplies the nodes the walks stopped at. A node absent after its read stays
|
|
70
|
+
* missing, so the unchanged verifier fails that version as it would serially.
|
|
71
|
+
*/
|
|
72
|
+
async function resolvePrincipalHistoryProofs(db, input) {
|
|
73
|
+
const nodes = new Map();
|
|
74
|
+
const results = [];
|
|
75
|
+
let pending = input.versions.map((_, index) => index);
|
|
76
|
+
while (pending.length > 0) {
|
|
77
|
+
const needed = new Set();
|
|
78
|
+
const unfinished = [];
|
|
79
|
+
for (const index of pending) {
|
|
80
|
+
try {
|
|
81
|
+
results[index] = {
|
|
82
|
+
value: await resolvePrincipalHistoryIndexProof({
|
|
83
|
+
rootHash: input.history.indexRootHash,
|
|
84
|
+
treeSize: input.history.currentEntry.state.version,
|
|
85
|
+
version: input.versions[index] ?? 0,
|
|
86
|
+
readNode: async (hash) => {
|
|
87
|
+
const node = nodes.get(hash);
|
|
88
|
+
if (node === undefined)
|
|
89
|
+
throw new PrincipalHistoryNodeNotLoaded(hash);
|
|
90
|
+
return node;
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (!(error instanceof PrincipalHistoryNodeNotLoaded)) {
|
|
97
|
+
results[index] = { error };
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
needed.add(error.hash);
|
|
101
|
+
unfinished.push(index);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const batch of hashBatches([...needed])) {
|
|
105
|
+
for (const hash of batch)
|
|
106
|
+
nodes.set(hash, null);
|
|
107
|
+
const rows = await db
|
|
61
108
|
.select()
|
|
62
109
|
.from(principalHistoryNodes)
|
|
63
|
-
.where(and(eq(principalHistoryNodes.scopeId, input.scopeId),
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
110
|
+
.where(and(eq(principalHistoryNodes.scopeId, input.scopeId), inArray(principalHistoryNodes.hash, batch)));
|
|
111
|
+
for (const row of rows)
|
|
112
|
+
nodes.set(row.hash, row);
|
|
113
|
+
}
|
|
114
|
+
pending = unfinished;
|
|
115
|
+
}
|
|
116
|
+
return results;
|
|
117
|
+
}
|
|
118
|
+
async function loadPrincipalHistoryEntryJson(db, scopeId, leafHashes) {
|
|
119
|
+
const entries = new Map();
|
|
120
|
+
for (const batch of hashBatches([...new Set(leafHashes)])) {
|
|
121
|
+
const rows = await db
|
|
122
|
+
.select({
|
|
123
|
+
leafHash: principalHistoryEntries.leafHash,
|
|
124
|
+
entryJson: principalHistoryEntries.entryJson,
|
|
125
|
+
})
|
|
126
|
+
.from(principalHistoryEntries)
|
|
127
|
+
.where(and(eq(principalHistoryEntries.scopeId, scopeId), inArray(principalHistoryEntries.leafHash, batch)));
|
|
128
|
+
for (const row of rows)
|
|
129
|
+
entries.set(row.leafHash, row.entryJson);
|
|
130
|
+
}
|
|
131
|
+
return entries;
|
|
132
|
+
}
|
|
133
|
+
function parsePrincipalHistoryEntry(entryJson) {
|
|
73
134
|
let entry;
|
|
74
135
|
try {
|
|
75
|
-
entry =
|
|
136
|
+
entry = entryJson === undefined ? null : JSON.parse(entryJson);
|
|
76
137
|
}
|
|
77
138
|
catch {
|
|
78
139
|
entry = null;
|
|
79
140
|
}
|
|
80
141
|
if (!isPrincipalPolicyStateChainEntryResponse(entry))
|
|
81
142
|
throw new KeyingVerificationError("missing_dependency", "Principal history entry is missing or corrupt");
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
143
|
+
return entry;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Returns untrusted material, in `versions` order. The caller must verify it
|
|
147
|
+
* against the issued root. Index nodes are read one level at a time for every
|
|
148
|
+
* version and entries in one set read; the first version that fails, in
|
|
149
|
+
* order, throws the error loading it alone would.
|
|
150
|
+
*/
|
|
151
|
+
export async function loadPrincipalHistoryReferences(input) {
|
|
152
|
+
await ensureSqlTables(input.execSql, principalHistoryEvidenceTables);
|
|
153
|
+
const { db } = getClientSQLitePersistenceRuntime(input.execSql);
|
|
154
|
+
const resolved = await resolvePrincipalHistoryProofs(db, input);
|
|
155
|
+
const entries = await loadPrincipalHistoryEntryJson(db, input.scopeId, resolved.flatMap((result) => "value" in result ? [result.value.leafHash] : []));
|
|
156
|
+
return resolved.map((result) => {
|
|
157
|
+
if (!("value" in result))
|
|
158
|
+
throw result.error;
|
|
159
|
+
const entry = parsePrincipalHistoryEntry(entries.get(result.value.leafHash));
|
|
160
|
+
return {
|
|
161
|
+
reference: {
|
|
162
|
+
principalType: entry.state.principalType,
|
|
163
|
+
principalId: entry.state.principalId,
|
|
164
|
+
version: entry.state.version,
|
|
165
|
+
keyEpoch: entry.state.keyEpoch,
|
|
166
|
+
stateHash: entry.state.stateHash,
|
|
167
|
+
keyFingerprint: entry.state.keyFingerprint,
|
|
168
|
+
},
|
|
169
|
+
entry,
|
|
170
|
+
proof: result.value.proof,
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** Returns untrusted material. The caller must verify it against the issued root. */
|
|
175
|
+
export async function loadPrincipalHistoryReference(input) {
|
|
176
|
+
const [loaded] = await loadPrincipalHistoryReferences({
|
|
177
|
+
...input,
|
|
178
|
+
versions: [input.version],
|
|
179
|
+
});
|
|
180
|
+
if (!loaded)
|
|
181
|
+
throw new Error("Principal history reference was not loaded");
|
|
182
|
+
return input.expectedReference
|
|
183
|
+
? { ...loaded, reference: input.expectedReference }
|
|
184
|
+
: loaded;
|
|
91
185
|
}
|
|
92
186
|
//# sourceMappingURL=principalHistoryEvidencePersistence.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"principalHistoryEvidencePersistence.js","sourceRoot":"","sources":["principalHistoryEvidencePersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,kCAAkC,EAGlC,yBAAyB,EAEzB,iCAAiC,GAElC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,wCAAwC,GAEzC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EACL,uBAAuB,EACvB,8BAA8B,EAC9B,qBAAqB,GACtB,MAAM,oDAAoD,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAEL,iCAAiC,GAClC,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAgB,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAE7E,MAAM,sBAAsB,GAAG,qBAAqB,CAAC,uBAAuB,CAAC,CAAC;AAa9E,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,mCAAmC,CAAC,KAMzD;IACC,IACE,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,kCAAkC;QACzD,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,kCAAkC;QAE3D,MAAM,IAAI,uBAAuB,CAC/B,eAAe,EACf,kDAAkD,CACnD,CAAC;IACJ,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACrC,OAAO;QACL,GAAG,KAAK;QACR,OAAO,EAAE,MAAM,OAAO,CAAC,GAAG,CACxB,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YAClC,QAAQ,EAAE,MAAM,yBAAyB,CAAC,KAAK,CAAC,KAAK,CAAC;YACtD,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;SACjC,CAAC,CAAC,CACJ;KACF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,iCAAiC,CACrD,EAAgC,EAChC,IAAkC;IAElC,MAAM,KAAK,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;IAC7E,qEAAqE;IACrE,MAAM,OAAO,GAAG,IAAI,GAAG,CACrB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;QAC1B,KAAK,CAAC,QAAQ;QACd,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE;KACnE,CAAC,CACH,CAAC;IACF,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC/C,uBAAuB,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAChD,CAAC;IACF,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,sBAAsB;QACtE,MAAM,EAAE;aACL,MAAM,CAAC,uBAAuB,CAAC;aAC/B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,sBAAsB,CAAC,CAAC;aACzD,kBAAkB,CAAC;YAClB,MAAM,EAAE;gBACN,uBAAuB,CAAC,OAAO;gBAC/B,uBAAuB,CAAC,QAAQ;aACjC;YACD,GAAG,EAAE;gBACH,cAAc,EAAE,aAAa,CAAC,uBAAuB,CAAC,cAAc,CAAC;gBACrE,SAAS,EAAE,aAAa,CAAC,uBAAuB,CAAC,SAAS,CAAC;aAC5D;SACF,CAAC;aACD,GAAG,EAAE,CAAC;IACX,MAAM,0BAA0B,CAC9B,EAAE,EACF,KAAK,EACL,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CACxE,CAAC;AACJ,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,6BAA6B,CAAC,KAMnD;IACC,MAAM,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,8BAA8B,CAAC,CAAC;IACrE,MAAM,EAAE,EAAE,EAAE,GAAG,iCAAiC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,MAAM,iCAAiC,CAAC;QACvD,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,aAAa;QACrC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO;QAClD,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YACvB,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE;iBACnB,MAAM,EAAE;iBACR,IAAI,CAAC,qBAAqB,CAAC;iBAC3B,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,qBAAqB,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,EAChD,EAAE,CAAC,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,CACrC,CACF;iBACA,KAAK,CAAC,CAAC,CAAC,CAAC;YACZ,OAAO,GAAG,IAAI,IAAI,CAAC;QACrB,CAAC;KACF,CAAC,CAAC;IACH,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE;SACnB,MAAM,EAAE;SACR,IAAI,CAAC,uBAAuB,CAAC;SAC7B,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,uBAAuB,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,EAClD,EAAE,CAAC,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,CACxD,CACF;SACA,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACH,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,KAAK,GAAG,IAAI,CAAC;IACf,CAAC;IACD,IAAI,CAAC,wCAAwC,CAAC,KAAK,CAAC;QAClD,MAAM,IAAI,uBAAuB,CAC/B,oBAAoB,EACpB,+CAA+C,CAChD,CAAC;IACJ,MAAM,SAAS,GAAG,KAAK,CAAC,iBAAiB,IAAI;QAC3C,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC,aAAa;QACxC,WAAW,EAAE,KAAK,CAAC,KAAK,CAAC,WAAW;QACpC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO;QAC5B,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ;QAC9B,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS;QAChC,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,cAAc;KAC3C,CAAC;IACF,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;AACrD,CAAC","sourcesContent":["import {\n compareCanonicalStrings,\n KeyingVerificationError,\n PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT,\n type PrincipalHistoryIndexNode,\n type PrincipalPolicyHistoryReferenceProof,\n principalHistoryIndexLeaf,\n type ReferencedPrincipalHead,\n resolvePrincipalHistoryIndexProof,\n type VerifiedPrincipalPolicyHistory,\n} from \"@tearleads/crypto\";\nimport {\n isPrincipalPolicyStateChainEntryResponse,\n type PrincipalPolicyStateChainEntryResponse,\n} from \"@tearleads/validators/response\";\nimport { and, eq } from \"drizzle-orm\";\nimport {\n principalHistoryEntries,\n principalHistoryEvidenceTables,\n principalHistoryNodes,\n} from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport { sqliteInsertBatchSize } from \"../../sqlite/sqliteBindBudget\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { excludedValue } from \"../../sqlite/sqliteUpsert\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { writePrincipalHistoryNodes } from \"./principalHistoryNodeRetention\";\n\nconst ENTRY_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryEntries);\n\nexport interface PrincipalHistoryEvidencePage {\n readonly indexRootHash: string;\n readonly scopeId: string;\n readonly organizationId: string;\n readonly entries: readonly {\n readonly leafHash: string;\n readonly entryJson: string;\n }[];\n readonly nodes: readonly PrincipalHistoryIndexNode[];\n}\n\n/** Prepare only a page accepted by the signature/authorization verifier. */\nexport async function preparePrincipalHistoryEvidencePage(input: {\n readonly indexRootHash: string;\n readonly scopeId: string;\n readonly organizationId: string;\n readonly entries: readonly PrincipalPolicyStateChainEntryResponse[];\n readonly nodes: readonly PrincipalHistoryIndexNode[];\n}): Promise<PrincipalHistoryEvidencePage> {\n if (\n input.entries.length > PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT ||\n input.nodes.length > 2 * PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT\n )\n throw new KeyingVerificationError(\n \"invalid_shape\",\n \"Principal evidence page exceeds its batch budget\",\n );\n const owned = structuredClone(input);\n return {\n ...owned,\n entries: await Promise.all(\n owned.entries.map(async (entry) => ({\n leafHash: await principalHistoryIndexLeaf(entry.state),\n entryJson: JSON.stringify(entry),\n })),\n ),\n };\n}\n\n/** Publish in the same guarded transaction as the accepted page's progress. */\nexport async function writePrincipalHistoryEvidencePage(\n tx: ClientSQLiteTransactionScope,\n page: PrincipalHistoryEvidencePage,\n): Promise<void> {\n const scope = { scopeId: page.scopeId, organizationId: page.organizationId };\n // A repeated leaf keeps its last entry, as sequential upserts would.\n const entries = new Map(\n page.entries.map((entry) => [\n entry.leafHash,\n { ...scope, leafHash: entry.leafHash, entryJson: entry.entryJson },\n ]),\n );\n const rows = [...entries.values()].sort((a, b) =>\n compareCanonicalStrings(a.leafHash, b.leafHash),\n );\n for (let index = 0; index < rows.length; index += ENTRY_WRITE_BATCH_SIZE)\n await tx\n .insert(principalHistoryEntries)\n .values(rows.slice(index, index + ENTRY_WRITE_BATCH_SIZE))\n .onConflictDoUpdate({\n target: [\n principalHistoryEntries.scopeId,\n principalHistoryEntries.leafHash,\n ],\n set: {\n organizationId: excludedValue(principalHistoryEntries.organizationId),\n entryJson: excludedValue(principalHistoryEntries.entryJson),\n },\n })\n .run();\n await writePrincipalHistoryNodes(\n tx,\n scope,\n [...page.nodes].sort((a, b) => compareCanonicalStrings(a.hash, b.hash)),\n );\n}\n\n/** Returns untrusted material. The caller must verify it against the issued root. */\nexport async function loadPrincipalHistoryReference(input: {\n readonly execSql: ExecSql;\n readonly scopeId: string;\n readonly history: VerifiedPrincipalPolicyHistory;\n readonly version: number;\n readonly expectedReference?: ReferencedPrincipalHead | undefined;\n}): Promise<PrincipalPolicyHistoryReferenceProof> {\n await ensureSqlTables(input.execSql, principalHistoryEvidenceTables);\n const { db } = getClientSQLitePersistenceRuntime(input.execSql);\n const resolved = await resolvePrincipalHistoryIndexProof({\n rootHash: input.history.indexRootHash,\n treeSize: input.history.currentEntry.state.version,\n version: input.version,\n readNode: async (hash) => {\n const [row] = await db\n .select()\n .from(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, input.scopeId),\n eq(principalHistoryNodes.hash, hash),\n ),\n )\n .limit(1);\n return row ?? null;\n },\n });\n const [row] = await db\n .select()\n .from(principalHistoryEntries)\n .where(\n and(\n eq(principalHistoryEntries.scopeId, input.scopeId),\n eq(principalHistoryEntries.leafHash, resolved.leafHash),\n ),\n )\n .limit(1);\n let entry: unknown;\n try {\n entry = row ? JSON.parse(row.entryJson) : null;\n } catch {\n entry = null;\n }\n if (!isPrincipalPolicyStateChainEntryResponse(entry))\n throw new KeyingVerificationError(\n \"missing_dependency\",\n \"Principal history entry is missing or corrupt\",\n );\n const reference = input.expectedReference ?? {\n principalType: entry.state.principalType,\n principalId: entry.state.principalId,\n version: entry.state.version,\n keyEpoch: entry.state.keyEpoch,\n stateHash: entry.state.stateHash,\n keyFingerprint: entry.state.keyFingerprint,\n };\n return { reference, entry, proof: resolved.proof };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"principalHistoryEvidencePersistence.js","sourceRoot":"","sources":["principalHistoryEvidencePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n compareCanonicalStrings,\n KeyingVerificationError,\n PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT,\n type PrincipalHistoryIndexNode,\n type PrincipalPolicyHistoryReferenceProof,\n principalHistoryIndexLeaf,\n type ReferencedPrincipalHead,\n resolvePrincipalHistoryIndexProof,\n type VerifiedPrincipalPolicyHistory,\n} from \"@tearleads/crypto\";\nimport {\n isPrincipalPolicyStateChainEntryResponse,\n type PrincipalPolicyStateChainEntryResponse,\n} from \"@tearleads/validators/response\";\nimport { and, eq, inArray } from \"drizzle-orm\";\nimport {\n principalHistoryEntries,\n principalHistoryEvidenceTables,\n principalHistoryNodes,\n} from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport {\n sqliteInsertBatchSize,\n sqliteRowsPerStatement,\n} from \"../../sqlite/sqliteBindBudget\";\nimport {\n type ClientSQLiteDatabase,\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { excludedValue } from \"../../sqlite/sqliteUpsert\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { writePrincipalHistoryNodes } from \"./principalHistoryNodeRetention\";\nimport type { PrincipalHistoryRootEdges } from \"./principalHistoryRootOwnership\";\n\nconst ENTRY_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryEntries);\n\nexport interface PrincipalHistoryEvidencePage {\n readonly indexRootHash: string;\n readonly scopeId: string;\n readonly organizationId: string;\n readonly entries: readonly {\n readonly leafHash: string;\n readonly entryJson: string;\n }[];\n readonly nodes: readonly PrincipalHistoryIndexNode[];\n}\n\n/** Prepare only a page accepted by the signature/authorization verifier. */\nexport async function preparePrincipalHistoryEvidencePage(input: {\n readonly indexRootHash: string;\n readonly scopeId: string;\n readonly organizationId: string;\n readonly entries: readonly PrincipalPolicyStateChainEntryResponse[];\n readonly nodes: readonly PrincipalHistoryIndexNode[];\n}): Promise<PrincipalHistoryEvidencePage> {\n if (\n input.entries.length > PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT ||\n input.nodes.length > 2 * PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT\n )\n throw new KeyingVerificationError(\n \"invalid_shape\",\n \"Principal evidence page exceeds its batch budget\",\n );\n const owned = structuredClone(input);\n return {\n ...owned,\n entries: await Promise.all(\n owned.entries.map(async (entry) => ({\n leafHash: await principalHistoryIndexLeaf(entry.state),\n entryJson: JSON.stringify(entry),\n })),\n ),\n };\n}\n\n/** Publish in the same guarded transaction as the accepted page's progress. */\nexport async function writePrincipalHistoryEvidencePage(\n tx: ClientSQLiteTransactionScope,\n page: PrincipalHistoryEvidencePage,\n rootEdges?: PrincipalHistoryRootEdges | undefined,\n): Promise<void> {\n const scope = { scopeId: page.scopeId, organizationId: page.organizationId };\n // A repeated leaf keeps its last entry, as sequential upserts would.\n const entries = new Map(\n page.entries.map((entry) => [\n entry.leafHash,\n { ...scope, leafHash: entry.leafHash, entryJson: entry.entryJson },\n ]),\n );\n const rows = [...entries.values()].sort((a, b) =>\n compareCanonicalStrings(a.leafHash, b.leafHash),\n );\n for (let index = 0; index < rows.length; index += ENTRY_WRITE_BATCH_SIZE)\n await tx\n .insert(principalHistoryEntries)\n .values(rows.slice(index, index + ENTRY_WRITE_BATCH_SIZE))\n .onConflictDoUpdate({\n target: [\n principalHistoryEntries.scopeId,\n principalHistoryEntries.leafHash,\n ],\n set: {\n organizationId: excludedValue(principalHistoryEntries.organizationId),\n entryJson: excludedValue(principalHistoryEntries.entryJson),\n },\n })\n .run();\n await writePrincipalHistoryNodes(\n tx,\n scope,\n [...page.nodes].sort((a, b) => compareCanonicalStrings(a.hash, b.hash)),\n rootEdges,\n );\n}\n\ntype PrincipalHistoryNodeRow = typeof principalHistoryNodes.$inferSelect;\ntype ResolvedPrincipalHistoryProof = Awaited<\n ReturnType<typeof resolvePrincipalHistoryIndexProof>\n>;\n\n// The scope id binds once per lookup.\nconst HASH_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);\n\n/** Thrown by the preloaded node reader to ask the next level's set read for a hash. */\nclass PrincipalHistoryNodeNotLoaded {\n constructor(readonly hash: string) {}\n}\n\nfunction hashBatches(hashes: readonly string[]): string[][] {\n const result: string[][] = [];\n for (let index = 0; index < hashes.length; index += HASH_LOOKUP_BATCH_SIZE)\n result.push(hashes.slice(index, index + HASH_LOOKUP_BATCH_SIZE));\n return result;\n}\n\n/**\n * Resolves every version's index path level by level: each pass walks every\n * unfinished version over the nodes read so far, and one set read per level\n * supplies the nodes the walks stopped at. A node absent after its read stays\n * missing, so the unchanged verifier fails that version as it would serially.\n */\nasync function resolvePrincipalHistoryProofs(\n db: ClientSQLiteDatabase,\n input: {\n readonly scopeId: string;\n readonly history: VerifiedPrincipalPolicyHistory;\n readonly versions: readonly number[];\n },\n): Promise<({ value: ResolvedPrincipalHistoryProof } | { error: unknown })[]> {\n const nodes = new Map<string, PrincipalHistoryNodeRow | null>();\n const results: (\n | { value: ResolvedPrincipalHistoryProof }\n | { error: unknown }\n )[] = [];\n let pending = input.versions.map((_, index) => index);\n while (pending.length > 0) {\n const needed = new Set<string>();\n const unfinished: number[] = [];\n for (const index of pending) {\n try {\n results[index] = {\n value: await resolvePrincipalHistoryIndexProof({\n rootHash: input.history.indexRootHash,\n treeSize: input.history.currentEntry.state.version,\n version: input.versions[index] ?? 0,\n readNode: async (hash) => {\n const node = nodes.get(hash);\n if (node === undefined)\n throw new PrincipalHistoryNodeNotLoaded(hash);\n return node;\n },\n }),\n };\n } catch (error) {\n if (!(error instanceof PrincipalHistoryNodeNotLoaded)) {\n results[index] = { error };\n continue;\n }\n needed.add(error.hash);\n unfinished.push(index);\n }\n }\n for (const batch of hashBatches([...needed])) {\n for (const hash of batch) nodes.set(hash, null);\n const rows = await db\n .select()\n .from(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, input.scopeId),\n inArray(principalHistoryNodes.hash, batch),\n ),\n );\n for (const row of rows) nodes.set(row.hash, row);\n }\n pending = unfinished;\n }\n return results;\n}\n\nasync function loadPrincipalHistoryEntryJson(\n db: ClientSQLiteDatabase,\n scopeId: string,\n leafHashes: readonly string[],\n): Promise<Map<string, string>> {\n const entries = new Map<string, string>();\n for (const batch of hashBatches([...new Set(leafHashes)])) {\n const rows = await db\n .select({\n leafHash: principalHistoryEntries.leafHash,\n entryJson: principalHistoryEntries.entryJson,\n })\n .from(principalHistoryEntries)\n .where(\n and(\n eq(principalHistoryEntries.scopeId, scopeId),\n inArray(principalHistoryEntries.leafHash, batch),\n ),\n );\n for (const row of rows) entries.set(row.leafHash, row.entryJson);\n }\n return entries;\n}\n\nfunction parsePrincipalHistoryEntry(\n entryJson: string | undefined,\n): PrincipalPolicyStateChainEntryResponse {\n let entry: unknown;\n try {\n entry = entryJson === undefined ? null : JSON.parse(entryJson);\n } catch {\n entry = null;\n }\n if (!isPrincipalPolicyStateChainEntryResponse(entry))\n throw new KeyingVerificationError(\n \"missing_dependency\",\n \"Principal history entry is missing or corrupt\",\n );\n return entry;\n}\n\n/**\n * Returns untrusted material, in `versions` order. The caller must verify it\n * against the issued root. Index nodes are read one level at a time for every\n * version and entries in one set read; the first version that fails, in\n * order, throws the error loading it alone would.\n */\nexport async function loadPrincipalHistoryReferences(input: {\n readonly execSql: ExecSql;\n readonly scopeId: string;\n readonly history: VerifiedPrincipalPolicyHistory;\n readonly versions: readonly number[];\n}): Promise<PrincipalPolicyHistoryReferenceProof[]> {\n await ensureSqlTables(input.execSql, principalHistoryEvidenceTables);\n const { db } = getClientSQLitePersistenceRuntime(input.execSql);\n const resolved = await resolvePrincipalHistoryProofs(db, input);\n const entries = await loadPrincipalHistoryEntryJson(\n db,\n input.scopeId,\n resolved.flatMap((result) =>\n \"value\" in result ? [result.value.leafHash] : [],\n ),\n );\n return resolved.map((result) => {\n if (!(\"value\" in result)) throw result.error;\n const entry = parsePrincipalHistoryEntry(\n entries.get(result.value.leafHash),\n );\n return {\n reference: {\n principalType: entry.state.principalType,\n principalId: entry.state.principalId,\n version: entry.state.version,\n keyEpoch: entry.state.keyEpoch,\n stateHash: entry.state.stateHash,\n keyFingerprint: entry.state.keyFingerprint,\n },\n entry,\n proof: result.value.proof,\n };\n });\n}\n\n/** Returns untrusted material. The caller must verify it against the issued root. */\nexport async function loadPrincipalHistoryReference(input: {\n readonly execSql: ExecSql;\n readonly scopeId: string;\n readonly history: VerifiedPrincipalPolicyHistory;\n readonly version: number;\n readonly expectedReference?: ReferencedPrincipalHead | undefined;\n}): Promise<PrincipalPolicyHistoryReferenceProof> {\n const [loaded] = await loadPrincipalHistoryReferences({\n ...input,\n versions: [input.version],\n });\n if (!loaded) throw new Error(\"Principal history reference was not loaded\");\n return input.expectedReference\n ? { ...loaded, reference: input.expectedReference }\n : loaded;\n}\n"]}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { and, asc, eq, inArray } from "drizzle-orm";
|
|
1
|
+
import { and, asc, eq, inArray, sql } from "drizzle-orm";
|
|
2
2
|
import { principalHistoryNodes } from "../../sqlite/schema/principalHistoryEvidenceSchema.js";
|
|
3
3
|
import { principalHistoryNodeReferences } from "../../sqlite/schema/principalHistoryNodeRetentionSchema.js";
|
|
4
4
|
import { sqliteInsertBatchSize, sqliteRowsPerStatement, } from "../../sqlite/sqliteBindBudget.js";
|
|
5
5
|
import { excludedValue } from "../../sqlite/sqliteUpsert.js";
|
|
6
|
-
import { decrementPrincipalHistoryNodeReference, } from "./principalHistoryRootOwnership.js";
|
|
7
6
|
const REFERENCE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryNodeReferences);
|
|
8
7
|
const NODE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryNodes);
|
|
9
8
|
// The scope id binds once per lookup.
|
|
10
9
|
const HASH_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);
|
|
10
|
+
// The scope and organization ids bind once per statement.
|
|
11
|
+
const SCOPED_HASH_BATCH_SIZE = sqliteRowsPerStatement(1, 3);
|
|
11
12
|
function batches(items, size) {
|
|
12
13
|
const result = [];
|
|
13
14
|
for (let index = 0; index < items.length; index += size)
|
|
@@ -43,8 +44,9 @@ async function loadStoredNodeHashes(tx, scope, hashes) {
|
|
|
43
44
|
* Replays the per-node edge bookkeeping in memory: increment inserts or adds
|
|
44
45
|
* one, decrement floors at zero within the scope's organization, and an edge
|
|
45
46
|
* change keeps the count while marking nodes first stored here as managed.
|
|
47
|
+
* Folded root edges replay last, in the order root ownership applies them.
|
|
46
48
|
*/
|
|
47
|
-
function replayNodeEdges(scope, nodes, loaded, storedNodeHashes) {
|
|
49
|
+
function replayNodeEdges(scope, nodes, loaded, storedNodeHashes, rootEdges) {
|
|
48
50
|
const references = new Map(loaded);
|
|
49
51
|
const read = (hash) => {
|
|
50
52
|
const reference = references.get(hash);
|
|
@@ -97,6 +99,12 @@ function replayNodeEdges(scope, nodes, loaded, storedNodeHashes) {
|
|
|
97
99
|
}
|
|
98
100
|
storedNodeHashes.add(node.hash);
|
|
99
101
|
}
|
|
102
|
+
// Root ownership's edges follow the page's nodes, as its statements did.
|
|
103
|
+
if (rootEdges) {
|
|
104
|
+
increment(rootEdges.increment);
|
|
105
|
+
if (rootEdges.decrement)
|
|
106
|
+
decrement(rootEdges.decrement);
|
|
107
|
+
}
|
|
100
108
|
return references;
|
|
101
109
|
}
|
|
102
110
|
function isSameNodeReference(left, right) {
|
|
@@ -108,21 +116,26 @@ function isSameNodeReference(left, right) {
|
|
|
108
116
|
}
|
|
109
117
|
/**
|
|
110
118
|
* Register edges and store proof nodes in the given order, with the same
|
|
111
|
-
* result as recording each node's edges and then upserting it
|
|
112
|
-
*
|
|
113
|
-
* rows and the nodes as
|
|
119
|
+
* result as recording each node's edges and then upserting it, followed by
|
|
120
|
+
* any folded root ownership edges. Reads the touched liveness rows once,
|
|
121
|
+
* replays the bookkeeping, and writes the changed rows and the nodes as
|
|
122
|
+
* multi-row upserts.
|
|
114
123
|
*/
|
|
115
|
-
export async function writePrincipalHistoryNodes(tx, scope, nodes) {
|
|
116
|
-
if (nodes.length === 0)
|
|
124
|
+
export async function writePrincipalHistoryNodes(tx, scope, nodes, rootEdges) {
|
|
125
|
+
if (nodes.length === 0 && !rootEdges)
|
|
117
126
|
return;
|
|
118
127
|
const loaded = new Map();
|
|
119
|
-
await loadNodeReferences(tx, scope,
|
|
128
|
+
await loadNodeReferences(tx, scope, [
|
|
129
|
+
...nodes.flatMap((node) => [node.hash, node.leftHash, node.rightHash]),
|
|
130
|
+
...(rootEdges ? [rootEdges.increment] : []),
|
|
131
|
+
...(rootEdges?.decrement ? [rootEdges.decrement] : []),
|
|
132
|
+
], loaded);
|
|
120
133
|
// An edge change releases the stored row's previous children.
|
|
121
134
|
await loadNodeReferences(tx, scope, nodes.flatMap((node) => {
|
|
122
135
|
const stored = loaded.get(node.hash);
|
|
123
136
|
return [stored?.leftHash, stored?.rightHash].filter((hash) => Boolean(hash));
|
|
124
137
|
}), loaded);
|
|
125
|
-
const replayed = replayNodeEdges(scope, nodes, loaded, await loadStoredNodeHashes(tx, scope, nodes.map((node) => node.hash)));
|
|
138
|
+
const replayed = replayNodeEdges(scope, nodes, loaded, await loadStoredNodeHashes(tx, scope, nodes.map((node) => node.hash)), rootEdges);
|
|
126
139
|
const changed = [...replayed].flatMap(([hash, reference]) => {
|
|
127
140
|
const before = loaded.get(hash);
|
|
128
141
|
return reference && !(before && isSameNodeReference(before, reference))
|
|
@@ -169,6 +182,45 @@ export async function writePrincipalHistoryNodes(tx, scope, nodes) {
|
|
|
169
182
|
})
|
|
170
183
|
.run();
|
|
171
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Deletes one cascade iteration's obsolete nodes and their liveness rows, then
|
|
187
|
+
* releases their children: one decrement per distinct multiplicity, since
|
|
188
|
+
* `max(count - k, 0)` equals k successive floored decrements. A child that is
|
|
189
|
+
* itself obsolete in this iteration is deleted either way, as the per-node
|
|
190
|
+
* loop deleted it before or after decrementing it.
|
|
191
|
+
*/
|
|
192
|
+
async function releaseObsoleteNodes(tx, scope, obsolete) {
|
|
193
|
+
const hashes = obsolete.map((node) => node.hash);
|
|
194
|
+
for (const batch of batches(hashes, SCOPED_HASH_BATCH_SIZE)) {
|
|
195
|
+
await tx
|
|
196
|
+
.delete(principalHistoryNodes)
|
|
197
|
+
.where(and(eq(principalHistoryNodes.scopeId, scope.scopeId), eq(principalHistoryNodes.organizationId, scope.organizationId), inArray(principalHistoryNodes.hash, batch)))
|
|
198
|
+
.run();
|
|
199
|
+
await tx
|
|
200
|
+
.delete(principalHistoryNodeReferences)
|
|
201
|
+
.where(and(eq(principalHistoryNodeReferences.scopeId, scope.scopeId), inArray(principalHistoryNodeReferences.hash, batch)))
|
|
202
|
+
.run();
|
|
203
|
+
}
|
|
204
|
+
const released = new Set(hashes);
|
|
205
|
+
const multiplicity = new Map();
|
|
206
|
+
for (const node of obsolete)
|
|
207
|
+
for (const hash of [node.leftHash, node.rightHash])
|
|
208
|
+
if (hash && !released.has(hash))
|
|
209
|
+
multiplicity.set(hash, (multiplicity.get(hash) ?? 0) + 1);
|
|
210
|
+
const byCount = new Map();
|
|
211
|
+
for (const [hash, count] of multiplicity)
|
|
212
|
+
byCount.set(count, [...(byCount.get(count) ?? []), hash]);
|
|
213
|
+
for (const [count, children] of [...byCount].sort(([a], [b]) => a - b))
|
|
214
|
+
for (const batch of batches(children.sort(), SCOPED_HASH_BATCH_SIZE))
|
|
215
|
+
await tx
|
|
216
|
+
.update(principalHistoryNodeReferences)
|
|
217
|
+
.set({
|
|
218
|
+
// `count` is a positive integer computed here, inlined like the literal 1.
|
|
219
|
+
referenceCount: sql `max(${principalHistoryNodeReferences.referenceCount} - ${sql.raw(String(count))}, 0)`,
|
|
220
|
+
})
|
|
221
|
+
.where(and(eq(principalHistoryNodeReferences.scopeId, scope.scopeId), eq(principalHistoryNodeReferences.organizationId, scope.organizationId), inArray(principalHistoryNodeReferences.hash, batch)))
|
|
222
|
+
.run();
|
|
223
|
+
}
|
|
172
224
|
/** A bounded cascade releases only managed proof nodes with no live incoming edge. */
|
|
173
225
|
export async function reclaimPrincipalHistoryNodes(tx, scope) {
|
|
174
226
|
let reclaimed = 0;
|
|
@@ -181,20 +233,8 @@ export async function reclaimPrincipalHistoryNodes(tx, scope) {
|
|
|
181
233
|
.limit(64 - reclaimed);
|
|
182
234
|
if (obsolete.length === 0)
|
|
183
235
|
break;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.delete(principalHistoryNodes)
|
|
187
|
-
.where(and(eq(principalHistoryNodes.scopeId, scope.scopeId), eq(principalHistoryNodes.organizationId, scope.organizationId), eq(principalHistoryNodes.hash, node.hash)))
|
|
188
|
-
.run();
|
|
189
|
-
await tx
|
|
190
|
-
.delete(principalHistoryNodeReferences)
|
|
191
|
-
.where(and(eq(principalHistoryNodeReferences.scopeId, scope.scopeId), eq(principalHistoryNodeReferences.hash, node.hash)))
|
|
192
|
-
.run();
|
|
193
|
-
for (const hash of [node.leftHash, node.rightHash])
|
|
194
|
-
if (hash)
|
|
195
|
-
await decrementPrincipalHistoryNodeReference(tx, scope, hash);
|
|
196
|
-
reclaimed += 1;
|
|
197
|
-
}
|
|
236
|
+
await releaseObsoleteNodes(tx, scope, obsolete);
|
|
237
|
+
reclaimed += obsolete.length;
|
|
198
238
|
}
|
|
199
239
|
return reclaimed;
|
|
200
240
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"principalHistoryNodeRetention.js","sourceRoot":"","sources":["principalHistoryNodeRetention.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PrincipalHistoryIndexNode } from \"@tearleads/crypto\";\nimport { and, asc, eq, inArray } from \"drizzle-orm\";\nimport { principalHistoryNodes } from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport { principalHistoryNodeReferences } from \"../../sqlite/schema/principalHistoryNodeRetentionSchema\";\nimport {\n sqliteInsertBatchSize,\n sqliteRowsPerStatement,\n} from \"../../sqlite/sqliteBindBudget\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { excludedValue } from \"../../sqlite/sqliteUpsert\";\nimport {\n decrementPrincipalHistoryNodeReference,\n type PrincipalHistoryNodeScope,\n} from \"./principalHistoryRootOwnership\";\n\ntype PrincipalHistoryNodeReference =\n typeof principalHistoryNodeReferences.$inferSelect;\n\nconst REFERENCE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(\n principalHistoryNodeReferences,\n);\nconst NODE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryNodes);\n// The scope id binds once per lookup.\nconst HASH_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);\n\nfunction batches<T>(items: readonly T[], size: number): T[][] {\n const result: T[][] = [];\n for (let index = 0; index < items.length; index += size)\n result.push(items.slice(index, index + size));\n return result;\n}\n\nasync function loadNodeReferences(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n hashes: readonly string[],\n into: Map<string, PrincipalHistoryNodeReference | null>,\n): Promise<void> {\n const missing = [...new Set(hashes)].filter((hash) => !into.has(hash));\n for (const batch of batches(missing, HASH_LOOKUP_BATCH_SIZE)) {\n for (const hash of batch) into.set(hash, null);\n const rows = await tx\n .select()\n .from(principalHistoryNodeReferences)\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n inArray(principalHistoryNodeReferences.hash, batch),\n ),\n );\n for (const row of rows) into.set(row.hash, row);\n }\n}\n\nasync function loadStoredNodeHashes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n hashes: readonly string[],\n): Promise<Set<string>> {\n const stored = new Set<string>();\n for (const batch of batches([...new Set(hashes)], HASH_LOOKUP_BATCH_SIZE)) {\n const rows = await tx\n .select({ hash: principalHistoryNodes.hash })\n .from(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, scope.scopeId),\n inArray(principalHistoryNodes.hash, batch),\n ),\n );\n for (const row of rows) stored.add(row.hash);\n }\n return stored;\n}\n\n/**\n * Replays the per-node edge bookkeeping in memory: increment inserts or adds\n * one, decrement floors at zero within the scope's organization, and an edge\n * change keeps the count while marking nodes first stored here as managed.\n */\nfunction replayNodeEdges(\n scope: PrincipalHistoryNodeScope,\n nodes: readonly PrincipalHistoryIndexNode[],\n loaded: ReadonlyMap<string, PrincipalHistoryNodeReference | null>,\n storedNodeHashes: Set<string>,\n): Map<string, PrincipalHistoryNodeReference | null> {\n const references = new Map(loaded);\n const read = (hash: string) => {\n const reference = references.get(hash);\n if (reference === undefined)\n throw new Error(\"Principal history node reference was not loaded\");\n return reference;\n };\n const increment = (hash: string) => {\n const reference = read(hash);\n references.set(\n hash,\n reference\n ? { ...reference, referenceCount: reference.referenceCount + 1 }\n : {\n ...scope,\n hash,\n referenceCount: 1,\n managed: false,\n leftHash: null,\n rightHash: null,\n },\n );\n };\n const decrement = (hash: string) => {\n const reference = read(hash);\n if (reference?.organizationId === scope.organizationId)\n references.set(hash, {\n ...reference,\n referenceCount: Math.max(reference.referenceCount - 1, 0),\n });\n };\n for (const node of nodes) {\n const previous = read(node.hash);\n if (\n previous?.leftHash !== node.leftHash ||\n previous.rightHash !== node.rightHash\n ) {\n const managed = previous?.managed || !storedNodeHashes.has(node.hash);\n increment(node.leftHash);\n increment(node.rightHash);\n const current = read(node.hash);\n references.set(node.hash, {\n ...(current ?? {\n ...scope,\n hash: node.hash,\n referenceCount: previous?.referenceCount ?? 0,\n }),\n leftHash: node.leftHash,\n rightHash: node.rightHash,\n managed,\n });\n for (const hash of [previous?.leftHash, previous?.rightHash])\n if (hash) decrement(hash);\n }\n storedNodeHashes.add(node.hash);\n }\n return references;\n}\n\nfunction isSameNodeReference(\n left: PrincipalHistoryNodeReference,\n right: PrincipalHistoryNodeReference,\n): boolean {\n return (\n left.organizationId === right.organizationId &&\n left.referenceCount === right.referenceCount &&\n left.managed === right.managed &&\n left.leftHash === right.leftHash &&\n left.rightHash === right.rightHash\n );\n}\n\n/**\n * Register edges and store proof nodes in the given order, with the same\n * result as recording each node's edges and then upserting it. Reads the\n * touched liveness rows once, replays the bookkeeping, and writes the changed\n * rows and the nodes as multi-row upserts.\n */\nexport async function writePrincipalHistoryNodes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n nodes: readonly PrincipalHistoryIndexNode[],\n): Promise<void> {\n if (nodes.length === 0) return;\n const loaded = new Map<string, PrincipalHistoryNodeReference | null>();\n await loadNodeReferences(\n tx,\n scope,\n nodes.flatMap((node) => [node.hash, node.leftHash, node.rightHash]),\n loaded,\n );\n // An edge change releases the stored row's previous children.\n await loadNodeReferences(\n tx,\n scope,\n nodes.flatMap((node) => {\n const stored = loaded.get(node.hash);\n return [stored?.leftHash, stored?.rightHash].filter(\n (hash): hash is string => Boolean(hash),\n );\n }),\n loaded,\n );\n const replayed = replayNodeEdges(\n scope,\n nodes,\n loaded,\n await loadStoredNodeHashes(\n tx,\n scope,\n nodes.map((node) => node.hash),\n ),\n );\n const changed = [...replayed].flatMap(([hash, reference]) => {\n const before = loaded.get(hash);\n return reference && !(before && isSameNodeReference(before, reference))\n ? [reference]\n : [];\n });\n for (const batch of batches(changed, REFERENCE_WRITE_BATCH_SIZE))\n await tx\n .insert(principalHistoryNodeReferences)\n .values(batch)\n .onConflictDoUpdate({\n target: [\n principalHistoryNodeReferences.scopeId,\n principalHistoryNodeReferences.hash,\n ],\n set: {\n referenceCount: excludedValue(\n principalHistoryNodeReferences.referenceCount,\n ),\n managed: excludedValue(principalHistoryNodeReferences.managed),\n leftHash: excludedValue(principalHistoryNodeReferences.leftHash),\n rightHash: excludedValue(principalHistoryNodeReferences.rightHash),\n },\n })\n .run();\n const nodeRows = new Map(\n nodes.map((node) => [\n node.hash,\n {\n ...scope,\n hash: node.hash,\n leftHash: node.leftHash,\n rightHash: node.rightHash,\n },\n ]),\n );\n for (const batch of batches([...nodeRows.values()], NODE_WRITE_BATCH_SIZE))\n await tx\n .insert(principalHistoryNodes)\n .values(batch)\n .onConflictDoUpdate({\n target: [principalHistoryNodes.scopeId, principalHistoryNodes.hash],\n set: {\n organizationId: excludedValue(principalHistoryNodes.organizationId),\n leftHash: excludedValue(principalHistoryNodes.leftHash),\n rightHash: excludedValue(principalHistoryNodes.rightHash),\n },\n })\n .run();\n}\n\n/** A bounded cascade releases only managed proof nodes with no live incoming edge. */\nexport async function reclaimPrincipalHistoryNodes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n): Promise<number> {\n let reclaimed = 0;\n while (reclaimed < 64) {\n const obsolete = await tx\n .select()\n .from(principalHistoryNodeReferences)\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n eq(\n principalHistoryNodeReferences.organizationId,\n scope.organizationId,\n ),\n eq(principalHistoryNodeReferences.managed, true),\n eq(principalHistoryNodeReferences.referenceCount, 0),\n ),\n )\n .orderBy(asc(principalHistoryNodeReferences.hash))\n .limit(64 - reclaimed);\n if (obsolete.length === 0) break;\n for (const node of obsolete) {\n await tx\n .delete(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, scope.scopeId),\n eq(principalHistoryNodes.organizationId, scope.organizationId),\n eq(principalHistoryNodes.hash, node.hash),\n ),\n )\n .run();\n await tx\n .delete(principalHistoryNodeReferences)\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n eq(principalHistoryNodeReferences.hash, node.hash),\n ),\n )\n .run();\n for (const hash of [node.leftHash, node.rightHash])\n if (hash) await decrementPrincipalHistoryNodeReference(tx, scope, hash);\n reclaimed += 1;\n }\n }\n return reclaimed;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"principalHistoryNodeRetention.js","sourceRoot":"","sources":["principalHistoryNodeRetention.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PrincipalHistoryIndexNode } from \"@tearleads/crypto\";\nimport { and, asc, eq, inArray, sql } from \"drizzle-orm\";\nimport { principalHistoryNodes } from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport { principalHistoryNodeReferences } from \"../../sqlite/schema/principalHistoryNodeRetentionSchema\";\nimport {\n sqliteInsertBatchSize,\n sqliteRowsPerStatement,\n} from \"../../sqlite/sqliteBindBudget\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { excludedValue } from \"../../sqlite/sqliteUpsert\";\nimport type {\n PrincipalHistoryNodeScope,\n PrincipalHistoryRootEdges,\n} from \"./principalHistoryRootOwnership\";\n\ntype PrincipalHistoryNodeReference =\n typeof principalHistoryNodeReferences.$inferSelect;\n\nconst REFERENCE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(\n principalHistoryNodeReferences,\n);\nconst NODE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryNodes);\n// The scope id binds once per lookup.\nconst HASH_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);\n// The scope and organization ids bind once per statement.\nconst SCOPED_HASH_BATCH_SIZE = sqliteRowsPerStatement(1, 3);\n\nfunction batches<T>(items: readonly T[], size: number): T[][] {\n const result: T[][] = [];\n for (let index = 0; index < items.length; index += size)\n result.push(items.slice(index, index + size));\n return result;\n}\n\nasync function loadNodeReferences(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n hashes: readonly string[],\n into: Map<string, PrincipalHistoryNodeReference | null>,\n): Promise<void> {\n const missing = [...new Set(hashes)].filter((hash) => !into.has(hash));\n for (const batch of batches(missing, HASH_LOOKUP_BATCH_SIZE)) {\n for (const hash of batch) into.set(hash, null);\n const rows = await tx\n .select()\n .from(principalHistoryNodeReferences)\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n inArray(principalHistoryNodeReferences.hash, batch),\n ),\n );\n for (const row of rows) into.set(row.hash, row);\n }\n}\n\nasync function loadStoredNodeHashes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n hashes: readonly string[],\n): Promise<Set<string>> {\n const stored = new Set<string>();\n for (const batch of batches([...new Set(hashes)], HASH_LOOKUP_BATCH_SIZE)) {\n const rows = await tx\n .select({ hash: principalHistoryNodes.hash })\n .from(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, scope.scopeId),\n inArray(principalHistoryNodes.hash, batch),\n ),\n );\n for (const row of rows) stored.add(row.hash);\n }\n return stored;\n}\n\n/**\n * Replays the per-node edge bookkeeping in memory: increment inserts or adds\n * one, decrement floors at zero within the scope's organization, and an edge\n * change keeps the count while marking nodes first stored here as managed.\n * Folded root edges replay last, in the order root ownership applies them.\n */\nfunction replayNodeEdges(\n scope: PrincipalHistoryNodeScope,\n nodes: readonly PrincipalHistoryIndexNode[],\n loaded: ReadonlyMap<string, PrincipalHistoryNodeReference | null>,\n storedNodeHashes: Set<string>,\n rootEdges: PrincipalHistoryRootEdges | undefined,\n): Map<string, PrincipalHistoryNodeReference | null> {\n const references = new Map(loaded);\n const read = (hash: string) => {\n const reference = references.get(hash);\n if (reference === undefined)\n throw new Error(\"Principal history node reference was not loaded\");\n return reference;\n };\n const increment = (hash: string) => {\n const reference = read(hash);\n references.set(\n hash,\n reference\n ? { ...reference, referenceCount: reference.referenceCount + 1 }\n : {\n ...scope,\n hash,\n referenceCount: 1,\n managed: false,\n leftHash: null,\n rightHash: null,\n },\n );\n };\n const decrement = (hash: string) => {\n const reference = read(hash);\n if (reference?.organizationId === scope.organizationId)\n references.set(hash, {\n ...reference,\n referenceCount: Math.max(reference.referenceCount - 1, 0),\n });\n };\n for (const node of nodes) {\n const previous = read(node.hash);\n if (\n previous?.leftHash !== node.leftHash ||\n previous.rightHash !== node.rightHash\n ) {\n const managed = previous?.managed || !storedNodeHashes.has(node.hash);\n increment(node.leftHash);\n increment(node.rightHash);\n const current = read(node.hash);\n references.set(node.hash, {\n ...(current ?? {\n ...scope,\n hash: node.hash,\n referenceCount: previous?.referenceCount ?? 0,\n }),\n leftHash: node.leftHash,\n rightHash: node.rightHash,\n managed,\n });\n for (const hash of [previous?.leftHash, previous?.rightHash])\n if (hash) decrement(hash);\n }\n storedNodeHashes.add(node.hash);\n }\n // Root ownership's edges follow the page's nodes, as its statements did.\n if (rootEdges) {\n increment(rootEdges.increment);\n if (rootEdges.decrement) decrement(rootEdges.decrement);\n }\n return references;\n}\n\nfunction isSameNodeReference(\n left: PrincipalHistoryNodeReference,\n right: PrincipalHistoryNodeReference,\n): boolean {\n return (\n left.organizationId === right.organizationId &&\n left.referenceCount === right.referenceCount &&\n left.managed === right.managed &&\n left.leftHash === right.leftHash &&\n left.rightHash === right.rightHash\n );\n}\n\n/**\n * Register edges and store proof nodes in the given order, with the same\n * result as recording each node's edges and then upserting it, followed by\n * any folded root ownership edges. Reads the touched liveness rows once,\n * replays the bookkeeping, and writes the changed rows and the nodes as\n * multi-row upserts.\n */\nexport async function writePrincipalHistoryNodes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n nodes: readonly PrincipalHistoryIndexNode[],\n rootEdges?: PrincipalHistoryRootEdges | undefined,\n): Promise<void> {\n if (nodes.length === 0 && !rootEdges) return;\n const loaded = new Map<string, PrincipalHistoryNodeReference | null>();\n await loadNodeReferences(\n tx,\n scope,\n [\n ...nodes.flatMap((node) => [node.hash, node.leftHash, node.rightHash]),\n ...(rootEdges ? [rootEdges.increment] : []),\n ...(rootEdges?.decrement ? [rootEdges.decrement] : []),\n ],\n loaded,\n );\n // An edge change releases the stored row's previous children.\n await loadNodeReferences(\n tx,\n scope,\n nodes.flatMap((node) => {\n const stored = loaded.get(node.hash);\n return [stored?.leftHash, stored?.rightHash].filter(\n (hash): hash is string => Boolean(hash),\n );\n }),\n loaded,\n );\n const replayed = replayNodeEdges(\n scope,\n nodes,\n loaded,\n await loadStoredNodeHashes(\n tx,\n scope,\n nodes.map((node) => node.hash),\n ),\n rootEdges,\n );\n const changed = [...replayed].flatMap(([hash, reference]) => {\n const before = loaded.get(hash);\n return reference && !(before && isSameNodeReference(before, reference))\n ? [reference]\n : [];\n });\n for (const batch of batches(changed, REFERENCE_WRITE_BATCH_SIZE))\n await tx\n .insert(principalHistoryNodeReferences)\n .values(batch)\n .onConflictDoUpdate({\n target: [\n principalHistoryNodeReferences.scopeId,\n principalHistoryNodeReferences.hash,\n ],\n set: {\n referenceCount: excludedValue(\n principalHistoryNodeReferences.referenceCount,\n ),\n managed: excludedValue(principalHistoryNodeReferences.managed),\n leftHash: excludedValue(principalHistoryNodeReferences.leftHash),\n rightHash: excludedValue(principalHistoryNodeReferences.rightHash),\n },\n })\n .run();\n const nodeRows = new Map(\n nodes.map((node) => [\n node.hash,\n {\n ...scope,\n hash: node.hash,\n leftHash: node.leftHash,\n rightHash: node.rightHash,\n },\n ]),\n );\n for (const batch of batches([...nodeRows.values()], NODE_WRITE_BATCH_SIZE))\n await tx\n .insert(principalHistoryNodes)\n .values(batch)\n .onConflictDoUpdate({\n target: [principalHistoryNodes.scopeId, principalHistoryNodes.hash],\n set: {\n organizationId: excludedValue(principalHistoryNodes.organizationId),\n leftHash: excludedValue(principalHistoryNodes.leftHash),\n rightHash: excludedValue(principalHistoryNodes.rightHash),\n },\n })\n .run();\n}\n\n/**\n * Deletes one cascade iteration's obsolete nodes and their liveness rows, then\n * releases their children: one decrement per distinct multiplicity, since\n * `max(count - k, 0)` equals k successive floored decrements. A child that is\n * itself obsolete in this iteration is deleted either way, as the per-node\n * loop deleted it before or after decrementing it.\n */\nasync function releaseObsoleteNodes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n obsolete: readonly PrincipalHistoryNodeReference[],\n): Promise<void> {\n const hashes = obsolete.map((node) => node.hash);\n for (const batch of batches(hashes, SCOPED_HASH_BATCH_SIZE)) {\n await tx\n .delete(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, scope.scopeId),\n eq(principalHistoryNodes.organizationId, scope.organizationId),\n inArray(principalHistoryNodes.hash, batch),\n ),\n )\n .run();\n await tx\n .delete(principalHistoryNodeReferences)\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n inArray(principalHistoryNodeReferences.hash, batch),\n ),\n )\n .run();\n }\n const released = new Set(hashes);\n const multiplicity = new Map<string, number>();\n for (const node of obsolete)\n for (const hash of [node.leftHash, node.rightHash])\n if (hash && !released.has(hash))\n multiplicity.set(hash, (multiplicity.get(hash) ?? 0) + 1);\n const byCount = new Map<number, string[]>();\n for (const [hash, count] of multiplicity)\n byCount.set(count, [...(byCount.get(count) ?? []), hash]);\n for (const [count, children] of [...byCount].sort(([a], [b]) => a - b))\n for (const batch of batches(children.sort(), SCOPED_HASH_BATCH_SIZE))\n await tx\n .update(principalHistoryNodeReferences)\n .set({\n // `count` is a positive integer computed here, inlined like the literal 1.\n referenceCount: sql`max(${principalHistoryNodeReferences.referenceCount} - ${sql.raw(String(count))}, 0)`,\n })\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n eq(\n principalHistoryNodeReferences.organizationId,\n scope.organizationId,\n ),\n inArray(principalHistoryNodeReferences.hash, batch),\n ),\n )\n .run();\n}\n\n/** A bounded cascade releases only managed proof nodes with no live incoming edge. */\nexport async function reclaimPrincipalHistoryNodes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n): Promise<number> {\n let reclaimed = 0;\n while (reclaimed < 64) {\n const obsolete = await tx\n .select()\n .from(principalHistoryNodeReferences)\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n eq(\n principalHistoryNodeReferences.organizationId,\n scope.organizationId,\n ),\n eq(principalHistoryNodeReferences.managed, true),\n eq(principalHistoryNodeReferences.referenceCount, 0),\n ),\n )\n .orderBy(asc(principalHistoryNodeReferences.hash))\n .limit(64 - reclaimed);\n if (obsolete.length === 0) break;\n await releaseObsoleteNodes(tx, scope, obsolete);\n reclaimed += obsolete.length;\n }\n return reclaimed;\n}\n"]}
|
|
@@ -56,7 +56,12 @@ export async function savePrincipalHistoryPrefix(input) {
|
|
|
56
56
|
// or explicitly replaced before repair can rely on the new prefix.
|
|
57
57
|
prefix.progress = previous.progress;
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
// Archiving reads only these three fields, so an identical previous
|
|
60
|
+
// prefix would repeat the same idempotent write.
|
|
61
|
+
if (previous &&
|
|
62
|
+
(previous.organizationId !== prefix.organizationId ||
|
|
63
|
+
previous.currentJson !== prefix.currentJson ||
|
|
64
|
+
previous.version !== prefix.version))
|
|
60
65
|
await archivePrincipalHistoryKeyEnvelopes(tx, previous);
|
|
61
66
|
await archivePrincipalHistoryKeyEnvelopes(tx, prefix);
|
|
62
67
|
await tx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"principalHistoryPrefixPersistence.js","sourceRoot":"","sources":["principalHistoryPrefixPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport { assertProjectionVerificationCurrent } from \"../../keyingProjectionVerification/types\";\nimport {\n principalHistoryEvidenceTables,\n principalHistoryPrefixes,\n} from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport { principalHistoryStageTables } from \"../../sqlite/schema/principalHistoryStageSchema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { archivePrincipalHistoryKeyEnvelopes } from \"../principalKeys/principalKeyEnvelopeArchivePersistence\";\nimport { reclaimPrincipalHistoryNodes } from \"./principalHistoryNodeRetention\";\nimport {\n releasePrincipalHistoryRoot,\n retainPrincipalHistoryRoot,\n} from \"./principalHistoryRootOwnership\";\nimport { reclaimCompletedPrincipalHistoryStages } from \"./principalHistoryStageRetention\";\n\nexport type PrincipalHistoryPrefix =\n typeof principalHistoryPrefixes.$inferSelect;\n\nexport async function loadPrincipalHistoryPrefix(\n execSql: ExecSql,\n scopeId: string,\n) {\n await ensureSqlTables(execSql, principalHistoryEvidenceTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [prefix] = await db\n .select()\n .from(principalHistoryPrefixes)\n .where(eq(principalHistoryPrefixes.scopeId, scopeId))\n .limit(1);\n return prefix ?? null;\n}\n\n/**\n * Preserve identical completed progress; this sink does not authenticate hints.\n * Callers must discard an unreadable prefix or identify it with rejectedPrefix\n * before publishing a repair of the same head and current artifacts.\n */\nexport async function savePrincipalHistoryPrefix(input: {\n readonly execSql: ExecSql;\n readonly prefix: PrincipalHistoryPrefix;\n readonly indexRootHash: string;\n readonly stillCurrent: () => boolean;\n readonly rejectedPrefix?: PrincipalHistoryPrefix | null;\n}): Promise<void> {\n const prefix = { ...input.prefix };\n const indexRootHash = input.indexRootHash;\n const rejected = input.rejectedPrefix ? { ...input.rejectedPrefix } : null;\n await ensureSqlTables(input.execSql, [\n ...principalHistoryEvidenceTables,\n ...principalHistoryStageTables,\n ]);\n const runtime = getClientSQLitePersistenceRuntime(input.execSql);\n const result = await runtime.guardedTransaction(\n async (tx) => {\n const [previous] = await tx\n .select()\n .from(principalHistoryPrefixes)\n .where(eq(principalHistoryPrefixes.scopeId, prefix.scopeId))\n .limit(1);\n const replacesRejected =\n previous &&\n rejected?.scopeId === previous.scopeId &&\n rejected.progress === previous.progress;\n if (previous && previous.version > prefix.version && !replacesRejected)\n return;\n if (\n previous &&\n previous.version === prefix.version &&\n previous.organizationId === prefix.organizationId &&\n previous.headJson === prefix.headJson &&\n previous.currentJson === prefix.currentJson &&\n !replacesRejected\n ) {\n // Resealing an unchanged prefix would invalidate an acknowledgement's\n // progress CAS even though its authenticated predecessor is unchanged.\n // Still refresh authenticated root ownership and retention below.\n // Reuse authenticates this opaque progress; rejected hints are removed\n // or explicitly replaced before repair can rely on the new prefix.\n prefix.progress = previous.progress;\n }\n if (previous) await archivePrincipalHistoryKeyEnvelopes(tx, previous);\n await archivePrincipalHistoryKeyEnvelopes(tx, prefix);\n await tx\n .insert(principalHistoryPrefixes)\n .values(prefix)\n .onConflictDoUpdate({\n target: principalHistoryPrefixes.scopeId,\n set: prefix,\n })\n .run();\n await reclaimCompletedPrincipalHistoryStages(tx, prefix);\n await retainPrincipalHistoryRoot(tx, {\n id: `prefix:${prefix.scopeId}`,\n scopeId: prefix.scopeId,\n organizationId: prefix.organizationId,\n rootHash: indexRootHash,\n });\n await reclaimPrincipalHistoryNodes(tx, prefix);\n },\n input.stillCurrent,\n { behavior: \"immediate\" },\n );\n assertProjectionVerificationCurrent(() => result.committed);\n}\n\n/** Invalid hints are disposable, but a losing reader must preserve a newer hint. */\nexport async function discardPrincipalHistoryPrefix(input: {\n readonly execSql: ExecSql;\n readonly prefix: PrincipalHistoryPrefix;\n readonly stillCurrent: () => boolean;\n}): Promise<void> {\n const prefix = { ...input.prefix };\n await ensureSqlTables(input.execSql, principalHistoryEvidenceTables);\n const runtime = getClientSQLitePersistenceRuntime(input.execSql);\n const result = await runtime.guardedTransaction(\n async (tx) => {\n const [current] = await tx\n .select({ progress: principalHistoryPrefixes.progress })\n .from(principalHistoryPrefixes)\n .where(eq(principalHistoryPrefixes.scopeId, prefix.scopeId))\n .limit(1);\n if (current?.progress !== prefix.progress) return;\n await releasePrincipalHistoryRoot(tx, `prefix:${prefix.scopeId}`);\n await tx\n .delete(principalHistoryPrefixes)\n .where(\n and(\n eq(principalHistoryPrefixes.scopeId, prefix.scopeId),\n eq(principalHistoryPrefixes.progress, prefix.progress),\n ),\n )\n .run();\n },\n input.stillCurrent,\n { behavior: \"immediate\" },\n );\n assertProjectionVerificationCurrent(() => result.committed);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"principalHistoryPrefixPersistence.js","sourceRoot":"","sources":["principalHistoryPrefixPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport { assertProjectionVerificationCurrent } from \"../../keyingProjectionVerification/types\";\nimport {\n principalHistoryEvidenceTables,\n principalHistoryPrefixes,\n} from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport { principalHistoryStageTables } from \"../../sqlite/schema/principalHistoryStageSchema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { archivePrincipalHistoryKeyEnvelopes } from \"../principalKeys/principalKeyEnvelopeArchivePersistence\";\nimport { reclaimPrincipalHistoryNodes } from \"./principalHistoryNodeRetention\";\nimport {\n releasePrincipalHistoryRoot,\n retainPrincipalHistoryRoot,\n} from \"./principalHistoryRootOwnership\";\nimport { reclaimCompletedPrincipalHistoryStages } from \"./principalHistoryStageRetention\";\n\nexport type PrincipalHistoryPrefix =\n typeof principalHistoryPrefixes.$inferSelect;\n\nexport async function loadPrincipalHistoryPrefix(\n execSql: ExecSql,\n scopeId: string,\n) {\n await ensureSqlTables(execSql, principalHistoryEvidenceTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [prefix] = await db\n .select()\n .from(principalHistoryPrefixes)\n .where(eq(principalHistoryPrefixes.scopeId, scopeId))\n .limit(1);\n return prefix ?? null;\n}\n\n/**\n * Preserve identical completed progress; this sink does not authenticate hints.\n * Callers must discard an unreadable prefix or identify it with rejectedPrefix\n * before publishing a repair of the same head and current artifacts.\n */\nexport async function savePrincipalHistoryPrefix(input: {\n readonly execSql: ExecSql;\n readonly prefix: PrincipalHistoryPrefix;\n readonly indexRootHash: string;\n readonly stillCurrent: () => boolean;\n readonly rejectedPrefix?: PrincipalHistoryPrefix | null;\n}): Promise<void> {\n const prefix = { ...input.prefix };\n const indexRootHash = input.indexRootHash;\n const rejected = input.rejectedPrefix ? { ...input.rejectedPrefix } : null;\n await ensureSqlTables(input.execSql, [\n ...principalHistoryEvidenceTables,\n ...principalHistoryStageTables,\n ]);\n const runtime = getClientSQLitePersistenceRuntime(input.execSql);\n const result = await runtime.guardedTransaction(\n async (tx) => {\n const [previous] = await tx\n .select()\n .from(principalHistoryPrefixes)\n .where(eq(principalHistoryPrefixes.scopeId, prefix.scopeId))\n .limit(1);\n const replacesRejected =\n previous &&\n rejected?.scopeId === previous.scopeId &&\n rejected.progress === previous.progress;\n if (previous && previous.version > prefix.version && !replacesRejected)\n return;\n if (\n previous &&\n previous.version === prefix.version &&\n previous.organizationId === prefix.organizationId &&\n previous.headJson === prefix.headJson &&\n previous.currentJson === prefix.currentJson &&\n !replacesRejected\n ) {\n // Resealing an unchanged prefix would invalidate an acknowledgement's\n // progress CAS even though its authenticated predecessor is unchanged.\n // Still refresh authenticated root ownership and retention below.\n // Reuse authenticates this opaque progress; rejected hints are removed\n // or explicitly replaced before repair can rely on the new prefix.\n prefix.progress = previous.progress;\n }\n // Archiving reads only these three fields, so an identical previous\n // prefix would repeat the same idempotent write.\n if (\n previous &&\n (previous.organizationId !== prefix.organizationId ||\n previous.currentJson !== prefix.currentJson ||\n previous.version !== prefix.version)\n )\n await archivePrincipalHistoryKeyEnvelopes(tx, previous);\n await archivePrincipalHistoryKeyEnvelopes(tx, prefix);\n await tx\n .insert(principalHistoryPrefixes)\n .values(prefix)\n .onConflictDoUpdate({\n target: principalHistoryPrefixes.scopeId,\n set: prefix,\n })\n .run();\n await reclaimCompletedPrincipalHistoryStages(tx, prefix);\n await retainPrincipalHistoryRoot(tx, {\n id: `prefix:${prefix.scopeId}`,\n scopeId: prefix.scopeId,\n organizationId: prefix.organizationId,\n rootHash: indexRootHash,\n });\n await reclaimPrincipalHistoryNodes(tx, prefix);\n },\n input.stillCurrent,\n { behavior: \"immediate\" },\n );\n assertProjectionVerificationCurrent(() => result.committed);\n}\n\n/** Invalid hints are disposable, but a losing reader must preserve a newer hint. */\nexport async function discardPrincipalHistoryPrefix(input: {\n readonly execSql: ExecSql;\n readonly prefix: PrincipalHistoryPrefix;\n readonly stillCurrent: () => boolean;\n}): Promise<void> {\n const prefix = { ...input.prefix };\n await ensureSqlTables(input.execSql, principalHistoryEvidenceTables);\n const runtime = getClientSQLitePersistenceRuntime(input.execSql);\n const result = await runtime.guardedTransaction(\n async (tx) => {\n const [current] = await tx\n .select({ progress: principalHistoryPrefixes.progress })\n .from(principalHistoryPrefixes)\n .where(eq(principalHistoryPrefixes.scopeId, prefix.scopeId))\n .limit(1);\n if (current?.progress !== prefix.progress) return;\n await releasePrincipalHistoryRoot(tx, `prefix:${prefix.scopeId}`);\n await tx\n .delete(principalHistoryPrefixes)\n .where(\n and(\n eq(principalHistoryPrefixes.scopeId, prefix.scopeId),\n eq(principalHistoryPrefixes.progress, prefix.progress),\n ),\n )\n .run();\n },\n input.stillCurrent,\n { behavior: \"immediate\" },\n );\n assertProjectionVerificationCurrent(() => result.committed);\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { principalHistoryRootOwners } from "../../sqlite/schema/principalHistoryNodeRetentionSchema.js";
|
|
2
|
+
import type { ClientSQLiteTransactionScope } from "../../sqlite/sqlitePersistenceRuntime.js";
|
|
3
|
+
export interface PrincipalHistoryNodeScope {
|
|
4
|
+
/** Evidence scope hashes include organizationId; one scope belongs to one organization. */
|
|
5
|
+
readonly scopeId: string;
|
|
6
|
+
readonly organizationId: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function incrementPrincipalHistoryNodeReference(tx: ClientSQLiteTransactionScope, scope: PrincipalHistoryNodeScope, hash: string): Promise<void>;
|
|
9
|
+
export declare function decrementPrincipalHistoryNodeReference(tx: ClientSQLiteTransactionScope, scope: PrincipalHistoryNodeScope, hash: string): Promise<void>;
|
|
10
|
+
type PrincipalHistoryRootOwner = typeof principalHistoryRootOwners.$inferSelect;
|
|
11
|
+
type PrincipalHistoryRootInput = PrincipalHistoryNodeScope & {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly rootHash: string;
|
|
14
|
+
};
|
|
15
|
+
/** An owner whose root changes: the new root gains an edge, the old one loses it. */
|
|
16
|
+
export interface PrincipalHistoryRootRetention {
|
|
17
|
+
readonly input: PrincipalHistoryRootInput;
|
|
18
|
+
readonly previous: PrincipalHistoryRootOwner | undefined;
|
|
19
|
+
}
|
|
20
|
+
/** The root edges a node write replays itself, after its nodes and in this order. */
|
|
21
|
+
export interface PrincipalHistoryRootEdges {
|
|
22
|
+
readonly increment: string;
|
|
23
|
+
readonly decrement: string | null;
|
|
24
|
+
}
|
|
25
|
+
/** Null when the owner already holds this exact root, which changes nothing. */
|
|
26
|
+
export declare function loadPrincipalHistoryRootRetention(tx: ClientSQLiteTransactionScope, input: PrincipalHistoryRootInput): Promise<PrincipalHistoryRootRetention | null>;
|
|
27
|
+
/**
|
|
28
|
+
* The edges a write of `page`'s nodes can apply for this retention without
|
|
29
|
+
* reordering them: the increment only when the owner is in the page's scope,
|
|
30
|
+
* and the previous root's decrement only after it and in the same scope. A
|
|
31
|
+
* page without nodes writes no liveness rows, so nothing folds into it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function foldablePrincipalHistoryRootEdges(retention: PrincipalHistoryRootRetention | null, page: PrincipalHistoryNodeScope & {
|
|
34
|
+
readonly nodes: readonly unknown[];
|
|
35
|
+
}): PrincipalHistoryRootEdges | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Records the owner and applies the root edges `folded` did not already
|
|
38
|
+
* write. Caller commits ownership together with the exact saved progress it
|
|
39
|
+
* describes.
|
|
40
|
+
*/
|
|
41
|
+
export declare function writePrincipalHistoryRootRetention(tx: ClientSQLiteTransactionScope, retention: PrincipalHistoryRootRetention | null, folded?: PrincipalHistoryRootEdges | undefined): Promise<void>;
|
|
42
|
+
/** Caller commits ownership together with the exact saved progress it describes. */
|
|
43
|
+
export declare function retainPrincipalHistoryRoot(tx: ClientSQLiteTransactionScope, input: PrincipalHistoryRootInput): Promise<void>;
|
|
44
|
+
export declare function releasePrincipalHistoryRoot(tx: ClientSQLiteTransactionScope, id: string): Promise<void>;
|
|
45
|
+
export {};
|