[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
108
application/state/pluginImporterTransaction.ts
Normal file
108
application/state/pluginImporterTransaction.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION } from '../../infrastructure/config/storageKeys';
|
||||
|
||||
type TransactionStorage = {
|
||||
read<T>(key: string): T | null;
|
||||
readString(key: string): string | null;
|
||||
write<T>(key: string, value: T): boolean;
|
||||
writeString(key: string, value: string): boolean;
|
||||
remove(key: string): void;
|
||||
};
|
||||
|
||||
type PreviousEntry = { key: string; value: string | null };
|
||||
type PreparedJournal = {
|
||||
version: 1;
|
||||
phase: 'prepared';
|
||||
previous: PreviousEntry[];
|
||||
};
|
||||
type CommittedJournal = { version: 1; phase: 'committed' };
|
||||
type ImportJournal = PreparedJournal | CommittedJournal;
|
||||
|
||||
const restorePrevious = (storage: TransactionStorage, previous: PreviousEntry[]) => {
|
||||
for (const entry of previous) {
|
||||
if (entry.value === null) {
|
||||
storage.remove(entry.key);
|
||||
if (storage.readString(entry.key) !== null) {
|
||||
throw new Error(`Vault importer rollback failed for ${entry.key}`);
|
||||
}
|
||||
} else if (
|
||||
!storage.writeString(entry.key, entry.value)
|
||||
|| storage.readString(entry.key) !== entry.value
|
||||
) {
|
||||
throw new Error(`Vault importer rollback failed for ${entry.key}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const parseJournal = (value: unknown, allowedKeys: ReadonlySet<string>): ImportJournal | null => {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
||||
const journal = value as Partial<ImportJournal>;
|
||||
if (journal.version !== 1 || (journal.phase !== 'prepared' && journal.phase !== 'committed')) return null;
|
||||
if (journal.phase === 'committed') return { version: 1, phase: 'committed' };
|
||||
const previous = (journal as Partial<PreparedJournal>).previous;
|
||||
if (!Array.isArray(previous) || previous.length === 0 || previous.length > allowedKeys.size) return null;
|
||||
const seen = new Set<string>();
|
||||
for (const entry of previous) {
|
||||
if (!entry || typeof entry !== 'object' || Array.isArray(entry)
|
||||
|| typeof entry.key !== 'string' || !allowedKeys.has(entry.key) || seen.has(entry.key)
|
||||
|| (entry.value !== null && typeof entry.value !== 'string')) return null;
|
||||
seen.add(entry.key);
|
||||
}
|
||||
return { version: 1, phase: 'prepared', previous: previous as PreviousEntry[] };
|
||||
};
|
||||
|
||||
export function recoverPluginImporterTransaction(
|
||||
storage: TransactionStorage,
|
||||
allowedKeys: ReadonlySet<string>,
|
||||
): 'none' | 'rolled-back' | 'committed' | 'discarded' {
|
||||
if (storage.readString(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION) === null) return 'none';
|
||||
const raw = storage.read<unknown>(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION);
|
||||
if (raw === null) {
|
||||
storage.remove(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION);
|
||||
return 'discarded';
|
||||
}
|
||||
const journal = parseJournal(raw, allowedKeys);
|
||||
if (!journal) {
|
||||
storage.remove(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION);
|
||||
return 'discarded';
|
||||
}
|
||||
if (journal.phase === 'prepared') restorePrevious(storage, journal.previous);
|
||||
storage.remove(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION);
|
||||
return journal.phase === 'prepared' ? 'rolled-back' : 'committed';
|
||||
}
|
||||
|
||||
export function commitPluginImporterTransaction(
|
||||
storage: TransactionStorage,
|
||||
writes: ReadonlyArray<readonly [key: string, value: unknown]>,
|
||||
): void {
|
||||
if (storage.readString(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION) !== null) {
|
||||
throw new Error('An unfinished Vault import recovery record already exists');
|
||||
}
|
||||
const keys = new Set(writes.map(([key]) => key));
|
||||
if (keys.size !== writes.length || keys.has(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION)) {
|
||||
throw new Error('Vault importer transaction keys are invalid');
|
||||
}
|
||||
const previous = writes.map(([key]) => ({ key, value: storage.readString(key) }));
|
||||
const prepared: PreparedJournal = { version: 1, phase: 'prepared', previous };
|
||||
if (!storage.write(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION, prepared)) {
|
||||
throw new Error('Vault storage rejected the importer transaction journal');
|
||||
}
|
||||
try {
|
||||
for (const [key, value] of writes) {
|
||||
const expected = JSON.stringify(value);
|
||||
if (expected === undefined
|
||||
|| !storage.write(key, value)
|
||||
|| storage.readString(key) !== expected) {
|
||||
throw new Error(`Vault storage rejected importer transaction key ${key}`);
|
||||
}
|
||||
}
|
||||
const committed: CommittedJournal = { version: 1, phase: 'committed' };
|
||||
if (!storage.write(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION, committed)) {
|
||||
throw new Error('Vault storage rejected the importer transaction commit marker');
|
||||
}
|
||||
} catch (error) {
|
||||
restorePrevious(storage, previous);
|
||||
storage.remove(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION);
|
||||
throw error;
|
||||
}
|
||||
storage.remove(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION);
|
||||
}
|
||||
Reference in New Issue
Block a user