@lossless.org/nosqldb 10.10.0 → 10.11.0

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.
Files changed (39) hide show
  1. package/dist_rust/rustdb_linux_amd64 +0 -0
  2. package/dist_rust/rustdb_linux_amd64.tsrust-build.json +5 -5
  3. package/dist_rust/rustdb_linux_amd64_musl +0 -0
  4. package/dist_rust/rustdb_linux_amd64_musl.tsrust-build.json +5 -5
  5. package/dist_rust/rustdb_linux_arm64 +0 -0
  6. package/dist_rust/rustdb_linux_arm64.tsrust-build.json +5 -5
  7. package/dist_rust/rustdb_linux_arm64_musl +0 -0
  8. package/dist_rust/rustdb_linux_arm64_musl.tsrust-build.json +5 -5
  9. package/dist_rust/rustdb_macos_amd64 +0 -0
  10. package/dist_rust/rustdb_macos_amd64.tsrust-build.json +5 -5
  11. package/dist_rust/rustdb_macos_arm64 +0 -0
  12. package/dist_rust/rustdb_macos_arm64.tsrust-build.json +5 -5
  13. package/dist_ts/00_commitinfo_data.js +1 -1
  14. package/dist_ts_debugserver/bundled.js +2 -6
  15. package/package.json +5 -14
  16. package/readme.md +64 -1
  17. package/third_party_licenses/inventory.json +1 -1
  18. package/.smartconfig.json +0 -115
  19. package/ts/00_commitinfo_data.ts +0 -8
  20. package/ts/index.ts +0 -85
  21. package/ts/ts_local/classes.localnosqldb.ts +0 -608
  22. package/ts/ts_local/index.ts +0 -19
  23. package/ts/ts_local/plugins.ts +0 -1
  24. package/ts/ts_nosqldb/classes.storagerehearsal.ts +0 -109
  25. package/ts/ts_nosqldb/committed-writes.ts +0 -38
  26. package/ts/ts_nosqldb/file-storage-options.ts +0 -33
  27. package/ts/ts_nosqldb/file-storage-startup.ts +0 -10
  28. package/ts/ts_nosqldb/index.ts +0 -127
  29. package/ts/ts_nosqldb/offline-inspection.ts +0 -375
  30. package/ts/ts_nosqldb/plugins.ts +0 -12
  31. package/ts/ts_nosqldb/resource-fencing.ts +0 -368
  32. package/ts/ts_nosqldb/rust-db-bridge.ts +0 -1066
  33. package/ts/ts_nosqldb/server/classes.nosqldbserver.ts +0 -738
  34. package/ts/ts_nosqldb/server/index.ts +0 -4
  35. package/ts/ts_nosqldb/service-types.ts +0 -499
  36. package/ts/ts_nosqldb/stopped-storage.ts +0 -108
  37. package/ts/ts_nosqldb/storage-rehearsal.ts +0 -215
  38. package/ts/ts_nosqldb/storage-root-alignment.ts +0 -176
  39. package/ts/ts_nosqldb/storage-root-relocation.ts +0 -279
@@ -1,215 +0,0 @@
1
- import * as plugins from './plugins.js';
2
- import type { INoSqlDbServerOptions } from './server/classes.nosqldbserver.js';
3
-
4
- export const noSqlDbStorageRehearsalErrorCodes = [
5
- 'INVALID_REQUEST', 'UNSUPPORTED', 'BUSY', 'NOT_FOUND', 'DESTINATION_CONFLICT',
6
- 'IDENTITY_MISMATCH', 'STATE_CORRUPT', 'SOURCE_CHANGED', 'CAPACITY', 'CANCELLED',
7
- 'IO', 'RECOVERY_REQUIRED', 'BINARY_UNAVAILABLE',
8
- ] as const;
9
- export type TNoSqlDbStorageRehearsalErrorCode = typeof noSqlDbStorageRehearsalErrorCodes[number];
10
-
11
- /** Errors deliberately contain neither persisted values nor nested engine errors. */
12
- export class NoSqlDbStorageRehearsalError extends Error {
13
- public constructor(public readonly code: TNoSqlDbStorageRehearsalErrorCode) {
14
- super(`NoSQLDB storage rehearsal failed with code ${code}`);
15
- this.name = 'NoSqlDbStorageRehearsalError';
16
- }
17
- }
18
-
19
- export interface INoSqlDbStorageRehearsalOptions {
20
- /** Exact executable, with SmartRust's exclusive non-mutating selection contract. */
21
- binaryPath?: string;
22
- }
23
-
24
- export interface INoSqlDbStorageRehearsalInput {
25
- /** Absolute canonical path of an already initialized, stopped source. */
26
- sourceFolderPath: string;
27
- /** Absent destination; its existing parent must be private and on the source filesystem. */
28
- destinationFolderPath: string;
29
- /** Optional previously attested provider identity. The engine always verifies source ownership and bindings. */
30
- expectedSourceProviderRootId?: string;
31
- /** Stable caller operation ID. Every retry must retain the same request. */
32
- operationId: string;
33
- limits?: {
34
- /** Source files plus directories, including the root. Default/maximum: 100,000. */
35
- maximumEntries: number;
36
- /** Total source file bytes. Default/maximum: 1 TiB. */
37
- maximumBytes: number;
38
- };
39
- }
40
-
41
- /** Counts include retained historical records separately from live contents. */
42
- export interface INoSqlDbStorageRehearsalPreservation {
43
- files: number;
44
- bytes: number;
45
- databases: number;
46
- collections: number;
47
- documents: number;
48
- indexes: number;
49
- indexAliases: number;
50
- users: number;
51
- principalIdentities: number;
52
- retiredDatabases: number;
53
- retiredCollections: number;
54
- retiredDocuments: number;
55
- retiredIndexes: number;
56
- retiredPrincipals: number;
57
- allocationReplays: number;
58
- resources: number;
59
- receipts: number;
60
- maximumFencingToken: string;
61
- publicationHolds: number;
62
- activeAllocations: number;
63
- retiredAllocations: number;
64
- sessions: number;
65
- retiringSessions: number;
66
- retryOutcomes: number;
67
- transactionOutcomes: number;
68
- obsoleteOutcomes: number;
69
- records: number;
70
- encodedRecordBytes: number;
71
- }
72
-
73
- /** Immutable creation evidence. Completed retries replay this receipt even after isolated writes. */
74
- export interface INoSqlDbStorageRehearsalReceipt {
75
- format: 'smartdb.storage-rehearsal.receipt.v2';
76
- operationId: string;
77
- requestSha256: string;
78
- storageProfile: 'native-mvcc-authenticated-v1';
79
- sourceFolderPath: string;
80
- destinationFolderPath: string;
81
- sourceProviderRootId: string;
82
- destinationProviderRootId: string;
83
- sourceStorageRootSha256: string;
84
- sourceSnapshotSha256: string;
85
- destinationDevice: string;
86
- destinationInode: string;
87
- groupId: string;
88
- timelineId: string;
89
- sourceLsn: string;
90
- destinationLsn: string;
91
- sourceRecordsSha256: string;
92
- preservedRecordsSha256: string;
93
- scramIterations: number;
94
- preservation: INoSqlDbStorageRehearsalPreservation;
95
- sourceUnchanged: true;
96
- contentsAndIndexesPreserved: true;
97
- namespacesPreserved: true;
98
- authenticationPreserved: true;
99
- historicalFencingPreserved: true;
100
- outcomesPreserved: true;
101
- filesystemBindingsVerified: true;
102
- productionAuthority: false;
103
- promotionSupported: false;
104
- receiptSha256: string;
105
- }
106
-
107
- export type TNormalizedNoSqlDbStorageRehearsalInput = INoSqlDbStorageRehearsalInput & {
108
- limits: NonNullable<INoSqlDbStorageRehearsalInput['limits']>;
109
- };
110
-
111
- const fail = (code: TNoSqlDbStorageRehearsalErrorCode = 'INVALID_REQUEST'): never => {
112
- throw new NoSqlDbStorageRehearsalError(code);
113
- };
114
- const object = (value: unknown): value is Record<string, unknown> => {
115
- try {
116
- if (value === null || typeof value !== 'object' || Array.isArray(value)
117
- || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return false;
118
- return Reflect.ownKeys(value).every((key) => {
119
- const property = Object.getOwnPropertyDescriptor(value, key);
120
- return typeof key === 'string' && property?.enumerable === true && Object.hasOwn(property, 'value');
121
- });
122
- } catch { return false; }
123
- };
124
- const digest = (value: unknown): value is string => typeof value === 'string' && /^[0-9a-f]{64}$/.test(value);
125
- const identifier = (value: unknown): value is string => typeof value === 'string'
126
- && /^[A-Za-z0-9][A-Za-z0-9._:@-]{0,127}$/.test(value);
127
- const canonicalPath = (value: unknown): value is string => typeof value === 'string'
128
- && value.startsWith('/') && value !== '/' && plugins.buffer.Buffer.byteLength(value) <= 4095
129
- && !/[\p{Cc}\p{Cs}]/u.test(value) && !value.endsWith('/') && !value.includes('//')
130
- && value.split('/').every((part) => part !== '.' && part !== '..' && !part.startsWith('.smartdb-rehearsal-'));
131
- const integer = (value: unknown, minimum: number, maximum: number): value is number =>
132
- typeof value === 'number' && Number.isSafeInteger(value) && value >= minimum && value <= maximum;
133
- const exactKeys = (value: Record<string, unknown>, required: readonly string[], optional: readonly string[] = []) =>
134
- required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
135
- const sha256 = (domain: string, value: unknown) => plugins.crypto.createHash('sha256').update(domain).update(JSON.stringify(value)).digest('hex');
136
-
137
- export const normalizeNoSqlDbStorageRehearsalOptions = (value: unknown): INoSqlDbStorageRehearsalOptions => {
138
- try {
139
- if (!object(value) || !exactKeys(value, [], ['binaryPath'])
140
- || (value.binaryPath !== undefined && (typeof value.binaryPath !== 'string' || value.binaryPath.length === 0))) return fail();
141
- return { binaryPath: value.binaryPath as string | undefined };
142
- } catch { return fail(); }
143
- };
144
-
145
- export const normalizeNoSqlDbStorageRehearsalOperationOptions = (value: unknown): { timeoutMs: number; signal?: AbortSignal } => {
146
- try {
147
- if (value === undefined) return { timeoutMs: 3_600_000 };
148
- if (!object(value) || !exactKeys(value, [], ['timeoutMs', 'signal'])
149
- || (value.timeoutMs !== undefined && !integer(value.timeoutMs, 1, 86_400_000))
150
- || (value.signal !== undefined && !(value.signal instanceof AbortSignal))) return fail();
151
- return { timeoutMs: value.timeoutMs as number | undefined ?? 3_600_000, signal: value.signal as AbortSignal | undefined };
152
- } catch { return fail(); }
153
- };
154
-
155
- export const normalizeNoSqlDbStorageRehearsalInput = (value: unknown): TNormalizedNoSqlDbStorageRehearsalInput => {
156
- if (!object(value) || !exactKeys(value, ['sourceFolderPath', 'destinationFolderPath', 'operationId'], ['expectedSourceProviderRootId', 'limits'])
157
- || !canonicalPath(value.sourceFolderPath) || !canonicalPath(value.destinationFolderPath)
158
- || value.sourceFolderPath === value.destinationFolderPath
159
- || value.sourceFolderPath.startsWith(`${value.destinationFolderPath}/`)
160
- || value.destinationFolderPath.startsWith(`${value.sourceFolderPath}/`)
161
- || (value.expectedSourceProviderRootId !== undefined && !digest(value.expectedSourceProviderRootId))
162
- || !identifier(value.operationId)) return fail();
163
- const limits = value.limits === undefined ? { maximumEntries: 100_000, maximumBytes: 1024 ** 4 } : value.limits;
164
- if (!object(limits) || !exactKeys(limits, ['maximumEntries', 'maximumBytes'])
165
- || !integer(limits.maximumEntries, 1, 100_000) || !integer(limits.maximumBytes, 1, 1024 ** 4)) return fail();
166
- return {
167
- sourceFolderPath: value.sourceFolderPath,
168
- destinationFolderPath: value.destinationFolderPath,
169
- ...(value.expectedSourceProviderRootId === undefined ? {} : { expectedSourceProviderRootId: value.expectedSourceProviderRootId as string }),
170
- operationId: value.operationId,
171
- limits: { maximumEntries: limits.maximumEntries, maximumBytes: limits.maximumBytes },
172
- };
173
- };
174
-
175
- const preservationKeys = ['files', 'bytes', 'databases', 'collections', 'documents', 'indexes', 'indexAliases', 'users', 'principalIdentities', 'retiredDatabases', 'retiredCollections', 'retiredDocuments', 'retiredIndexes', 'retiredPrincipals', 'allocationReplays', 'resources', 'receipts', 'maximumFencingToken', 'publicationHolds', 'activeAllocations', 'retiredAllocations', 'sessions', 'retiringSessions', 'retryOutcomes', 'transactionOutcomes', 'obsoleteOutcomes', 'records', 'encodedRecordBytes'] as const;
176
- const receiptKeys = ['format', 'operationId', 'requestSha256', 'storageProfile', 'sourceFolderPath', 'destinationFolderPath', 'sourceProviderRootId', 'destinationProviderRootId', 'sourceStorageRootSha256', 'sourceSnapshotSha256', 'destinationDevice', 'destinationInode', 'groupId', 'timelineId', 'sourceLsn', 'destinationLsn', 'sourceRecordsSha256', 'preservedRecordsSha256', 'scramIterations', 'preservation', 'sourceUnchanged', 'contentsAndIndexesPreserved', 'namespacesPreserved', 'authenticationPreserved', 'historicalFencingPreserved', 'outcomesPreserved', 'filesystemBindingsVerified', 'productionAuthority', 'promotionSupported', 'receiptSha256'] as const;
177
- const unsigned = (value: unknown): value is string => typeof value === 'string'
178
- && /^(0|[1-9][0-9]{0,19})$/.test(value) && BigInt(value) <= 0xffff_ffff_ffff_ffffn;
179
-
180
- export const normalizeNoSqlDbStorageRehearsalReceipt = (value: unknown, input: TNormalizedNoSqlDbStorageRehearsalInput): INoSqlDbStorageRehearsalReceipt => {
181
- if (!object(value) || !exactKeys(value, receiptKeys) || !object(value.preservation)
182
- || !exactKeys(value.preservation, preservationKeys)
183
- || !preservationKeys.every((key) => key === 'maximumFencingToken'
184
- ? unsigned((value.preservation as Record<string, unknown>)[key])
185
- : integer((value.preservation as Record<string, unknown>)[key], 0, Number.MAX_SAFE_INTEGER))
186
- || value.format !== 'smartdb.storage-rehearsal.receipt.v2' || value.storageProfile !== 'native-mvcc-authenticated-v1'
187
- || value.operationId !== input.operationId || value.sourceFolderPath !== input.sourceFolderPath
188
- || value.destinationFolderPath !== input.destinationFolderPath
189
- || (input.expectedSourceProviderRootId !== undefined && value.sourceProviderRootId !== input.expectedSourceProviderRootId)
190
- || !['sourceProviderRootId', 'destinationProviderRootId', 'sourceSnapshotSha256', 'sourceRecordsSha256', 'preservedRecordsSha256', 'receiptSha256'].every((key) => digest(value[key]))
191
- || value.sourceProviderRootId === value.destinationProviderRootId
192
- || value.sourceStorageRootSha256 !== plugins.crypto.createHash('sha256').update(`${input.sourceFolderPath}/.__rustdb_mvcc/data`).digest('hex')
193
- || value.requestSha256 !== sha256('smartdb.storage-rehearsal.request.v2\0', input)
194
- || !integer(value.scramIterations, 4096, 0xffff_ffff)
195
- || ![value.destinationDevice, value.destinationInode, value.sourceLsn, value.destinationLsn].every(unsigned)
196
- || BigInt(value.sourceLsn as string) < 1n || BigInt(value.sourceLsn as string) + 1n !== BigInt(value.destinationLsn as string)
197
- || !identifier(value.groupId) || !identifier(value.timelineId)
198
- || !['sourceUnchanged', 'contentsAndIndexesPreserved', 'namespacesPreserved', 'authenticationPreserved', 'historicalFencingPreserved', 'outcomesPreserved', 'filesystemBindingsVerified'].every((key) => value[key] === true)
199
- || value.productionAuthority !== false || value.promotionSupported !== false) return fail('STATE_CORRUPT');
200
- const preservation = Object.fromEntries(preservationKeys.map((key) => [key, (value.preservation as Record<string, unknown>)[key]]));
201
- const normalized = Object.fromEntries(receiptKeys.map((key) => [key,
202
- key === 'preservation' ? preservation : key === 'receiptSha256' ? '' : value[key]]));
203
- if (sha256('smartdb.storage-rehearsal.receipt.v2\0', normalized) !== value.receiptSha256) return fail('STATE_CORRUPT');
204
- normalized.receiptSha256 = value.receiptSha256;
205
- return normalized as unknown as INoSqlDbStorageRehearsalReceipt;
206
- };
207
-
208
- export const validateNoSqlDbStorageRehearsalStartup = (options: INoSqlDbServerOptions): void => {
209
- if (options.storageRehearsalId === undefined) return;
210
- if (!identifier(options.storageRehearsalId) || options.storage !== 'file'
211
- || (options.fileStorageStartup !== undefined && options.fileStorageStartup !== 'current-format-only') || !canonicalPath(options.storagePath)
212
- || !canonicalPath(options.socketPath) || options.socketPath.startsWith(`${options.storagePath}/`)
213
- || options.auth?.enabled !== true || (options.auth.users?.length ?? 0) !== 0
214
- || !integer(options.auth.scramIterations, 4096, 0xffff_ffff)) return fail();
215
- };
@@ -1,176 +0,0 @@
1
- import * as plugins from './plugins.js';
2
- import { normalizeLocalNoSqlDbStoppedStorageRootRelocationInput } from './storage-root-relocation.js';
3
- import {
4
- NoSqlDbStoppedOperationError, normalizeStoppedStorageRootInspectionInput,
5
- type ILocalNoSqlDbAuditedStorageRoot, type TNoSqlDbStorageRootBindingChange,
6
- } from './stopped-storage.js';
7
-
8
- export interface INoSqlDbStorageRootPins {
9
- groupId: string;
10
- timelineId: string;
11
- lsn: string;
12
- recordsSha256: string;
13
- providerRootId?: string;
14
- }
15
- export interface ILocalNoSqlDbStoppedStorageRootAlignmentInput {
16
- folderPath: string;
17
- /** Retained immutable receipt in a private existing parent outside the root, on the same filesystem. */
18
- receiptPath: string;
19
- alignmentId: string;
20
- identityPolicy: 'fork';
21
- attestLineageNeverFenced: true;
22
- expect?: INoSqlDbStorageRootPins;
23
- acknowledgeUnverifiedCopy?: true;
24
- dryRun?: boolean;
25
- }
26
- export interface ILocalNoSqlDbStoppedStorageRootAlignmentReceipt {
27
- format: 'smartdb.storage-root-alignment.receipt.v1';
28
- status: 'aligned';
29
- alignmentId: string;
30
- folderPath: string;
31
- receiptPath: string;
32
- requestSha256: string;
33
- previousProviderRootId: string;
34
- providerRootId: string;
35
- bindingChange: Exclude<TNoSqlDbStorageRootBindingChange, 'none'>;
36
- groupId: string;
37
- timelineId: string;
38
- sourceLsn: string;
39
- alignedLsn: string;
40
- records: string;
41
- sourceRecordsSha256: string;
42
- alignedRecordsSha256: string;
43
- preservedRecordsSha256: string;
44
- pinned: boolean;
45
- receiptSha256: string;
46
- }
47
- export type TLocalNoSqlDbStoppedStorageRootAlignmentResult =
48
- | ILocalNoSqlDbStoppedStorageRootAlignmentReceipt
49
- | { status: 'eligible'; inspection: ILocalNoSqlDbAuditedStorageRoot }
50
- | { status: 'already-bound'; bindingChange: 'none'; inspection: ILocalNoSqlDbAuditedStorageRoot };
51
- export interface ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult {
52
- status: 'aborted' | 'not-pending';
53
- alignmentId: string;
54
- folderPath: string;
55
- receiptPath: string;
56
- }
57
-
58
- function fail(code: 'INVALID_REQUEST' | 'RECOVERY_REQUIRED'): never { throw new NoSqlDbStoppedOperationError(code); }
59
- const data = (value: unknown, required: string[], optional: string[] = []): value is Record<string, unknown> => {
60
- if (!value || Object.getPrototypeOf(value) !== Object.prototype) return false;
61
- const keys = Reflect.ownKeys(value);
62
- if (keys.some(key => typeof key !== 'string' || ![...required, ...optional].includes(key))) return false;
63
- return required.every(key => keys.includes(key)) && keys.every(key => {
64
- const descriptor = Object.getOwnPropertyDescriptor(value, key)!;
65
- return descriptor.enumerable && 'value' in descriptor;
66
- });
67
- };
68
- const sha = (value: unknown): value is string => typeof value === 'string' && /^[0-9a-f]{64}$/.test(value);
69
- const decimal = (value: unknown): value is string => typeof value === 'string'
70
- && /^(0|[1-9][0-9]{0,19})$/.test(value) && BigInt(value) <= 18446744073709551615n;
71
- const lineage = (value: unknown): value is string => typeof value === 'string' && value.length > 0
72
- && plugins.buffer.Buffer.byteLength(value) <= 256 && !/\p{Cc}/u.test(value);
73
- const digest = (domain: string, payload: unknown[]): string => plugins.crypto.createHash('sha256')
74
- .update(domain).update(JSON.stringify(payload)).digest('hex');
75
-
76
- export const normalizeStoppedStorageRootAlignmentInput = (
77
- value: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
78
- ): ILocalNoSqlDbStoppedStorageRootAlignmentInput => {
79
- if (!data(value, ['folderPath', 'receiptPath', 'alignmentId', 'identityPolicy', 'attestLineageNeverFenced'],
80
- ['expect', 'acknowledgeUnverifiedCopy', 'dryRun'])) fail('INVALID_REQUEST');
81
- try {
82
- normalizeStoppedStorageRootInspectionInput({ folderPath: value.folderPath });
83
- normalizeStoppedStorageRootInspectionInput({ folderPath: value.receiptPath });
84
- normalizeLocalNoSqlDbStoppedStorageRootRelocationInput({ sourceFolderPath: value.folderPath,
85
- destinationFolderPath: value.receiptPath, relocationId: value.alignmentId });
86
- } catch { fail('INVALID_REQUEST'); }
87
- if (value.identityPolicy !== 'fork' || value.attestLineageNeverFenced !== true
88
- || (value.dryRun !== undefined && typeof value.dryRun !== 'boolean')) fail('INVALID_REQUEST');
89
- let expect: INoSqlDbStorageRootPins | undefined;
90
- if (value.expect !== undefined) {
91
- const pins = value.expect;
92
- if (value.acknowledgeUnverifiedCopy !== undefined || !data(pins, ['groupId', 'timelineId', 'lsn', 'recordsSha256'], ['providerRootId'])
93
- || !lineage(pins.groupId) || !lineage(pins.timelineId) || !decimal(pins.lsn) || pins.lsn === '0'
94
- || !sha(pins.recordsSha256) || (pins.providerRootId !== undefined && !sha(pins.providerRootId))) fail('INVALID_REQUEST');
95
- expect = { groupId: pins.groupId, timelineId: pins.timelineId, lsn: pins.lsn, recordsSha256: pins.recordsSha256,
96
- ...(pins.providerRootId === undefined ? {} : { providerRootId: pins.providerRootId }) };
97
- } else if (value.acknowledgeUnverifiedCopy !== true) fail('INVALID_REQUEST');
98
- return { folderPath: value.folderPath, receiptPath: value.receiptPath, alignmentId: value.alignmentId,
99
- identityPolicy: 'fork', attestLineageNeverFenced: true,
100
- ...(expect ? { expect } : { acknowledgeUnverifiedCopy: true }), ...(value.dryRun ? { dryRun: true } : {}) };
101
- };
102
- export const storageRootAlignmentRequestDigest = (request: ILocalNoSqlDbStoppedStorageRootAlignmentInput): string =>
103
- digest('smartdb.storage-root-alignment.request.v1\0', [request.folderPath, request.receiptPath, request.alignmentId,
104
- request.identityPolicy, request.attestLineageNeverFenced, request.expect ? [request.expect.groupId,
105
- request.expect.timelineId, request.expect.lsn, request.expect.recordsSha256, request.expect.providerRootId ?? null] : null,
106
- request.acknowledgeUnverifiedCopy ?? null]);
107
-
108
- const receiptFields = ['format', 'status', 'alignmentId', 'folderPath', 'receiptPath', 'requestSha256',
109
- 'previousProviderRootId', 'providerRootId', 'bindingChange', 'groupId', 'timelineId', 'sourceLsn', 'alignedLsn', 'records',
110
- 'sourceRecordsSha256', 'alignedRecordsSha256', 'preservedRecordsSha256', 'pinned'] as const;
111
-
112
- const eligibleGenerations = (value: unknown): boolean => {
113
- if (!data(value, ['manifest', 'onDisk']) || !decimal(value.manifest) || !Array.isArray(value.onDisk)
114
- || !value.onDisk.length || !value.onDisk.includes(value.manifest)) return false;
115
- const manifest = BigInt(value.manifest);
116
- return value.onDisk.every(generation => decimal(generation) && BigInt(generation) <= manifest);
117
- };
118
-
119
- const eligibleInspection = (value: unknown, request: ILocalNoSqlDbStoppedStorageRootAlignmentInput): value is ILocalNoSqlDbAuditedStorageRoot => {
120
- if (!data(value, ['status', 'bindingState', 'bindingChange', 'storagePurpose', 'profileSchema', 'providerRootId',
121
- 'groupId', 'timelineId', 'lsn', 'records', 'recordsSha256', 'recordsWithoutFencingProfileSha256', 'authUsers',
122
- 'authority', 'generations', 'alignmentEligible', 'reasons', 'physicalAdmissionViolations'])
123
- || value.status !== 'audited' || value.alignmentEligible !== true || value.storagePurpose !== 'production'
124
- || ![1, 4].includes(value.profileSchema as number) || !sha(value.providerRootId) || !sha(value.recordsSha256)
125
- || !sha(value.recordsWithoutFencingProfileSha256) || !lineage(value.groupId) || !lineage(value.timelineId)
126
- || !decimal(value.lsn) || value.lsn === '0' || !decimal(value.records) || value.records === '0' || value.authUsers !== '0'
127
- || !['none', 'path', 'inode', 'path+inode'].includes(value.bindingChange as string)
128
- || value.bindingState !== (value.bindingChange === 'none' ? 'matching' : 'foreign')
129
- || !data(value.authority, ['resourceFences', 'receipts', 'publicationHolds', 'activeAllocations', 'retiredAllocations',
130
- 'allocationReplays', 'activeGrants', 'importStages']) || Object.values(value.authority).some(count => count !== '0')
131
- || !eligibleGenerations(value.generations)
132
- || !Array.isArray(value.reasons) || value.reasons.length || !Array.isArray(value.physicalAdmissionViolations)
133
- || value.physicalAdmissionViolations.length) return false;
134
- const pins = request.expect;
135
- return !pins || (value.groupId === pins.groupId && value.timelineId === pins.timelineId && value.lsn === pins.lsn
136
- && value.recordsSha256 === pins.recordsSha256 && (pins.providerRootId === undefined || value.providerRootId === pins.providerRootId));
137
- };
138
-
139
- export const normalizeStoppedStorageRootAlignmentResult = (value: unknown,
140
- request: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
141
- ): TLocalNoSqlDbStoppedStorageRootAlignmentResult => {
142
- if (!value || typeof value !== 'object' || !('status' in value)) fail('RECOVERY_REQUIRED');
143
- if (value.status === 'eligible' || value.status === 'already-bound') {
144
- if (!data(value, value.status === 'eligible' ? ['status', 'inspection'] : ['status', 'inspection', 'bindingChange'])) fail('RECOVERY_REQUIRED');
145
- const record = value as Record<string, unknown>;
146
- const inspection = record.inspection as ILocalNoSqlDbAuditedStorageRoot;
147
- if (!eligibleInspection(inspection, request)
148
- || (value.status === 'eligible' && !request.dryRun)
149
- || (value.status === 'already-bound' && (record.bindingChange !== 'none' || inspection.bindingState !== 'matching'))) fail('RECOVERY_REQUIRED');
150
- return value as unknown as TLocalNoSqlDbStoppedStorageRootAlignmentResult;
151
- }
152
- if (!data(value, [...receiptFields, 'receiptSha256'])) fail('RECOVERY_REQUIRED');
153
- const receipt = value as unknown as ILocalNoSqlDbStoppedStorageRootAlignmentReceipt;
154
- if (receipt.format !== 'smartdb.storage-root-alignment.receipt.v1' || receipt.status !== 'aligned'
155
- || receipt.alignmentId !== request.alignmentId || receipt.folderPath !== request.folderPath || receipt.receiptPath !== request.receiptPath
156
- || receipt.requestSha256 !== storageRootAlignmentRequestDigest(request) || receipt.pinned !== (request.expect !== undefined)
157
- || ![receipt.previousProviderRootId, receipt.providerRootId, receipt.sourceRecordsSha256, receipt.alignedRecordsSha256,
158
- receipt.preservedRecordsSha256, receipt.receiptSha256].every(sha)
159
- || receipt.previousProviderRootId === receipt.providerRootId || !lineage(receipt.groupId) || !lineage(receipt.timelineId)
160
- || !['path', 'inode', 'path+inode'].includes(receipt.bindingChange)
161
- || !decimal(receipt.sourceLsn) || receipt.sourceLsn === '0' || !decimal(receipt.alignedLsn)
162
- || BigInt(receipt.sourceLsn) + 1n !== BigInt(receipt.alignedLsn) || !decimal(receipt.records) || receipt.records === '0'
163
- || receipt.receiptSha256 !== digest('smartdb.storage-root-alignment.receipt.v1\0', receiptFields.map(key => receipt[key]))) fail('RECOVERY_REQUIRED');
164
- if (request.expect && (receipt.groupId !== request.expect.groupId || receipt.timelineId !== request.expect.timelineId
165
- || receipt.sourceLsn !== request.expect.lsn || receipt.sourceRecordsSha256 !== request.expect.recordsSha256
166
- || (request.expect.providerRootId !== undefined && receipt.previousProviderRootId !== request.expect.providerRootId))) fail('RECOVERY_REQUIRED');
167
- return { ...receipt };
168
- };
169
- export const normalizeStoppedStorageRootAlignmentAbortResult = (value: unknown,
170
- request: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
171
- ): ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult => {
172
- if (!data(value, ['status', 'alignmentId', 'folderPath', 'receiptPath'])
173
- || (value.status !== 'aborted' && value.status !== 'not-pending') || value.alignmentId !== request.alignmentId
174
- || value.folderPath !== request.folderPath || value.receiptPath !== request.receiptPath) fail('RECOVERY_REQUIRED');
175
- return value as unknown as ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult;
176
- };
@@ -1,279 +0,0 @@
1
- import * as plugins from './plugins.js';
2
-
3
- export const localNoSqlDbStorageRootRelocationErrorCodes = [
4
- 'INVALID_REQUEST',
5
- 'UNSUPPORTED',
6
- 'BUSY',
7
- 'NOT_FOUND',
8
- 'DESTINATION_CONFLICT',
9
- 'IDENTITY_MISMATCH',
10
- 'STATE_CORRUPT',
11
- 'SOURCE_CHANGED',
12
- 'CAPACITY',
13
- 'CANCELLED',
14
- 'IO',
15
- 'RECOVERY_REQUIRED',
16
- ] as const;
17
-
18
- export type TLocalNoSqlDbStorageRootRelocationErrorCode =
19
- (typeof localNoSqlDbStorageRootRelocationErrorCodes)[number];
20
-
21
- export interface ILocalNoSqlDbStoppedStorageRootRelocationInput {
22
- sourceFolderPath: string;
23
- destinationFolderPath: string;
24
- relocationId: string;
25
- }
26
-
27
- export interface ILocalNoSqlDbStoppedStorageRootRelocationReceipt {
28
- format: 'smartdb.storage-root-relocation.receipt.v1';
29
- version: 1;
30
- relocationId: string;
31
- sourceFolderPath: string;
32
- destinationFolderPath: string;
33
- providerRootId: string;
34
- storageRootDevice: string;
35
- storageRootInode: string;
36
- receiptSha256: string;
37
- sourceReceiptRetained: true;
38
- }
39
-
40
- export class LocalNoSqlDbStorageRootRelocationError extends Error {
41
- public readonly code: TLocalNoSqlDbStorageRootRelocationErrorCode;
42
-
43
- public constructor(codeArg: TLocalNoSqlDbStorageRootRelocationErrorCode) {
44
- super(`NoSQLDB stopped storage-root relocation failed with code ${codeArg}`);
45
- this.name = 'LocalNoSqlDbStorageRootRelocationError';
46
- this.code = codeArg;
47
- }
48
- }
49
-
50
- const relocationInputKeys = [
51
- 'sourceFolderPath',
52
- 'destinationFolderPath',
53
- 'relocationId',
54
- ] as const;
55
- const relocationReceiptKeys = [
56
- 'format',
57
- 'version',
58
- 'relocationId',
59
- 'sourceFolderPath',
60
- 'destinationFolderPath',
61
- 'providerRootId',
62
- 'storageRootDevice',
63
- 'storageRootInode',
64
- 'receiptSha256',
65
- 'sourceReceiptRetained',
66
- ] as const;
67
- const maximumFolderPathBytes = 4095;
68
- const maximumRelocationIdBytes = 256;
69
- const maximumU64Decimal = '18446744073709551615';
70
- const receiptDigestDomain = 'smartdb.storage-root-relocation.receipt.v1\0';
71
-
72
- const relocationError = (
73
- codeArg: TLocalNoSqlDbStorageRootRelocationErrorCode,
74
- ): LocalNoSqlDbStorageRootRelocationError =>
75
- new LocalNoSqlDbStorageRootRelocationError(codeArg);
76
-
77
- const isPlainExactDataObject = (
78
- valueArg: unknown,
79
- keysArg: readonly string[],
80
- ): valueArg is Record<string, unknown> => {
81
- if (
82
- typeof valueArg !== 'object'
83
- || valueArg === null
84
- || Array.isArray(valueArg)
85
- || Object.getPrototypeOf(valueArg) !== Object.prototype
86
- ) {
87
- return false;
88
- }
89
- const ownKeys = Reflect.ownKeys(valueArg);
90
- if (
91
- ownKeys.length !== keysArg.length
92
- || ownKeys.some((keyArg) => typeof keyArg !== 'string' || !keysArg.includes(keyArg))
93
- ) {
94
- return false;
95
- }
96
- const descriptors = Object.getOwnPropertyDescriptors(valueArg);
97
- return keysArg.every((keyArg) => {
98
- const descriptor = descriptors[keyArg];
99
- return descriptor !== undefined
100
- && descriptor.enumerable
101
- && Object.prototype.hasOwnProperty.call(descriptor, 'value');
102
- });
103
- };
104
-
105
- const isValidUnicodeScalarString = (valueArg: string): boolean => {
106
- for (let index = 0; index < valueArg.length; index++) {
107
- const code = valueArg.charCodeAt(index);
108
- if (code >= 0xd800 && code <= 0xdbff) {
109
- const next = valueArg.charCodeAt(index + 1);
110
- if (next < 0xdc00 || next > 0xdfff) return false;
111
- index++;
112
- } else if (code >= 0xdc00 && code <= 0xdfff) {
113
- return false;
114
- }
115
- }
116
- return true;
117
- };
118
-
119
- const isNestedPath = (parentArg: string, childArg: string): boolean => {
120
- const relative = plugins.path.relative(parentArg, childArg);
121
- return relative !== ''
122
- && relative !== '..'
123
- && !relative.startsWith(`..${plugins.path.sep}`)
124
- && !plugins.path.isAbsolute(relative);
125
- };
126
-
127
- const normalizeFolderPath = (valueArg: unknown): string => {
128
- if (
129
- typeof valueArg !== 'string'
130
- || !plugins.path.isAbsolute(valueArg)
131
- || !isValidUnicodeScalarString(valueArg)
132
- || [...valueArg].some(characterArg => /\p{Cc}/u.test(characterArg))
133
- ) {
134
- throw relocationError('INVALID_REQUEST');
135
- }
136
- const initialBytes = plugins.buffer.Buffer.byteLength(valueArg, 'utf8');
137
- if (initialBytes < 1 || initialBytes > maximumFolderPathBytes) {
138
- throw relocationError('INVALID_REQUEST');
139
- }
140
- const normalized = plugins.path.resolve(valueArg);
141
- const normalizedBytes = plugins.buffer.Buffer.byteLength(normalized, 'utf8');
142
- if (
143
- normalizedBytes < 1
144
- || normalizedBytes > maximumFolderPathBytes
145
- || plugins.path.parse(normalized).root === normalized
146
- || !isValidUnicodeScalarString(normalized)
147
- || [...normalized].some(characterArg => /\p{Cc}/u.test(characterArg))
148
- ) {
149
- throw relocationError('INVALID_REQUEST');
150
- }
151
- return normalized;
152
- };
153
-
154
- export const normalizeLocalNoSqlDbStoppedStorageRootRelocationInput = (
155
- inputArg: ILocalNoSqlDbStoppedStorageRootRelocationInput,
156
- ): ILocalNoSqlDbStoppedStorageRootRelocationInput => {
157
- if (!isPlainExactDataObject(inputArg, relocationInputKeys)) {
158
- throw relocationError('INVALID_REQUEST');
159
- }
160
- const sourceFolderPath = normalizeFolderPath(inputArg.sourceFolderPath);
161
- const destinationFolderPath = normalizeFolderPath(inputArg.destinationFolderPath);
162
- if (
163
- sourceFolderPath === destinationFolderPath
164
- || isNestedPath(sourceFolderPath, destinationFolderPath)
165
- || isNestedPath(destinationFolderPath, sourceFolderPath)
166
- ) {
167
- throw relocationError('INVALID_REQUEST');
168
- }
169
- if (
170
- typeof inputArg.relocationId !== 'string'
171
- || plugins.buffer.Buffer.byteLength(inputArg.relocationId, 'utf8') > maximumRelocationIdBytes
172
- || !/^[A-Za-z0-9][A-Za-z0-9._:@-]*$/.test(inputArg.relocationId)
173
- ) {
174
- throw relocationError('INVALID_REQUEST');
175
- }
176
- return {
177
- sourceFolderPath,
178
- destinationFolderPath,
179
- relocationId: inputArg.relocationId,
180
- };
181
- };
182
-
183
- const receiptDigest = (
184
- receiptArg: Omit<ILocalNoSqlDbStoppedStorageRootRelocationReceipt, 'receiptSha256'>,
185
- ): string => plugins.crypto
186
- .createHash('sha256')
187
- .update(receiptDigestDomain, 'utf8')
188
- .update(JSON.stringify(receiptArg), 'utf8')
189
- .digest('hex');
190
-
191
- const isCanonicalU64Decimal = (valueArg: unknown): valueArg is string =>
192
- typeof valueArg === 'string'
193
- && /^(?:0|[1-9][0-9]{0,19})$/.test(valueArg)
194
- && (valueArg.length < 20 || valueArg <= maximumU64Decimal);
195
-
196
- const isLowercaseSha256 = (valueArg: unknown): valueArg is string =>
197
- typeof valueArg === 'string' && /^[0-9a-f]{64}$/.test(valueArg);
198
-
199
- export const normalizeLocalNoSqlDbStoppedStorageRootRelocationReceipt = (
200
- valueArg: unknown,
201
- inputArg: ILocalNoSqlDbStoppedStorageRootRelocationInput,
202
- ): ILocalNoSqlDbStoppedStorageRootRelocationReceipt => {
203
- if (!isPlainExactDataObject(valueArg, relocationReceiptKeys)) {
204
- throw relocationError('RECOVERY_REQUIRED');
205
- }
206
- const receipt = valueArg as unknown as ILocalNoSqlDbStoppedStorageRootRelocationReceipt;
207
- if (
208
- receipt.format !== 'smartdb.storage-root-relocation.receipt.v1'
209
- || receipt.version !== 1
210
- || receipt.relocationId !== inputArg.relocationId
211
- || receipt.sourceFolderPath !== inputArg.sourceFolderPath
212
- || receipt.destinationFolderPath !== inputArg.destinationFolderPath
213
- || !isLowercaseSha256(receipt.providerRootId)
214
- || !isCanonicalU64Decimal(receipt.storageRootDevice)
215
- || !isCanonicalU64Decimal(receipt.storageRootInode)
216
- || !isLowercaseSha256(receipt.receiptSha256)
217
- || receipt.sourceReceiptRetained !== true
218
- ) {
219
- throw relocationError('RECOVERY_REQUIRED');
220
- }
221
- const withoutDigest = {
222
- format: receipt.format,
223
- version: receipt.version,
224
- relocationId: receipt.relocationId,
225
- sourceFolderPath: receipt.sourceFolderPath,
226
- destinationFolderPath: receipt.destinationFolderPath,
227
- providerRootId: receipt.providerRootId,
228
- storageRootDevice: receipt.storageRootDevice,
229
- storageRootInode: receipt.storageRootInode,
230
- sourceReceiptRetained: receipt.sourceReceiptRetained,
231
- } satisfies Omit<ILocalNoSqlDbStoppedStorageRootRelocationReceipt, 'receiptSha256'>;
232
- if (receiptDigest(withoutDigest) !== receipt.receiptSha256) {
233
- throw relocationError('RECOVERY_REQUIRED');
234
- }
235
- return { ...withoutDigest, receiptSha256: receipt.receiptSha256 };
236
- };
237
-
238
- export const assertNoLocalNoSqlDbStorageRootRelocationWorkspace = async (
239
- storagePathArg: string,
240
- ): Promise<void> => {
241
- const storagePath = plugins.path.resolve(storagePathArg);
242
- const internalPath = plugins.path.join(storagePath, '.__rustdb_internal');
243
- try {
244
- const storage = await plugins.fs.stat(storagePath);
245
- if (!storage.isDirectory()) {
246
- throw new Error('not a directory');
247
- }
248
- } catch (error) {
249
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;
250
- throw new Error('NoSQLDB storage-root relocation workspace inspection is ambiguous');
251
- }
252
- try {
253
- const internal = await plugins.fs.lstat(internalPath);
254
- if (internal.isSymbolicLink() || !internal.isDirectory()) {
255
- throw new Error('not a safe directory');
256
- }
257
- } catch (error) {
258
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;
259
- throw new Error('NoSQLDB storage-root relocation workspace inspection is ambiguous');
260
- }
261
- for (const operation of ['relocation', 'alignment'] as const) {
262
- const workspacePath = plugins.path.join(internalPath, `storage-root-${operation}`);
263
- try {
264
- await plugins.fs.lstat(workspacePath);
265
- } catch (error) {
266
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
267
- throw new Error(`NoSQLDB storage-root ${operation} workspace inspection is ambiguous`);
268
- }
269
- throw new Error(`NoSQLDB storage-root ${operation} recovery must complete before startup`);
270
- }
271
- };
272
-
273
- export const isLocalNoSqlDbStorageRootRelocationErrorCode = (
274
- valueArg: unknown,
275
- ): valueArg is TLocalNoSqlDbStorageRootRelocationErrorCode =>
276
- typeof valueArg === 'string'
277
- && localNoSqlDbStorageRootRelocationErrorCodes.includes(
278
- valueArg as TLocalNoSqlDbStorageRootRelocationErrorCode,
279
- );