@cortexkit/anthropic-auth-core 2.4.0 → 2.4.2
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/dist/holder-registry.d.ts +88 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.js +23001 -20075
- package/dist/layout-fence.d.ts +65 -0
- package/dist/native-local-runtime-readers.d.ts +1 -1
- package/dist/native-vault-paths.d.ts +5 -6
- package/dist/pool-paths.d.ts +6 -0
- package/dist/pool-store.d.ts +7 -0
- package/dist/relocation-guard.d.ts +13 -2
- package/dist/state-home-serving-guard.d.ts +84 -0
- package/dist/state-layout-decision.d.ts +126 -0
- package/dist/state-layout-errors.d.ts +65 -0
- package/dist/state-layout-io.d.ts +109 -0
- package/dist/state-layout-move.d.ts +253 -0
- package/dist/state-layout-paths.d.ts +28 -4
- package/dist/state-layout-record.d.ts +120 -0
- package/dist/state-layout-restore.d.ts +83 -0
- package/package.json +3 -3
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { type NativeVaultPaths } from './native-vault-paths.ts';
|
|
2
|
+
import { type NativePoolPaths } from './pool-paths.ts';
|
|
3
|
+
import { publishOwned, type StateLayoutFs, type StateLayoutLockSpec, type StateLayoutLockStack } from './state-layout-io.ts';
|
|
4
|
+
import { type RelocationRole, type StateLayoutRecord } from './state-layout-record.ts';
|
|
5
|
+
export { movingJournalPath } from './state-layout-record.ts';
|
|
6
|
+
/** Lock name of the layout lock on `<sub>/layout.json`. */
|
|
7
|
+
export declare const LAYOUT_LOCK_NAME = "runtime-layout";
|
|
8
|
+
/** How long a process waits for the layout lock: a whole move at 15 s per contended lock. */
|
|
9
|
+
export declare const LAYOUT_LOCK_TIMEOUT_MS = 120000;
|
|
10
|
+
/** One file slot of a dataset in both layouts. */
|
|
11
|
+
export interface RelocationSlot {
|
|
12
|
+
readonly role: RelocationRole;
|
|
13
|
+
/** The canonical legacy-layout path, in the config folder. */
|
|
14
|
+
readonly from: string;
|
|
15
|
+
/**
|
|
16
|
+
* The legacy-layout path as configured: the config folder as given joined
|
|
17
|
+
* with the role's fixed file name. `from` follows a symlink at the leaf, so
|
|
18
|
+
* only this path shows whether the configured file is a link.
|
|
19
|
+
*/
|
|
20
|
+
readonly configured: string;
|
|
21
|
+
/** The state-home path, in `<sub>`. */
|
|
22
|
+
readonly to: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* OpenCode's default dataset of one config folder, resolved in both layouts.
|
|
26
|
+
* Only that dataset ever moves.
|
|
27
|
+
*/
|
|
28
|
+
export interface RelocationDataset {
|
|
29
|
+
/** The config folder as configured (not canonicalized). */
|
|
30
|
+
readonly configDir: string;
|
|
31
|
+
readonly identity: string;
|
|
32
|
+
readonly stateRoot: string;
|
|
33
|
+
/** The canonical state-home folder. */
|
|
34
|
+
readonly sub: string;
|
|
35
|
+
/** The canonical config folder holding the legacy files. */
|
|
36
|
+
readonly oldDir: string;
|
|
37
|
+
readonly oldPaths: NativePoolPaths;
|
|
38
|
+
readonly oldVault: NativeVaultPaths;
|
|
39
|
+
readonly newPaths: NativePoolPaths;
|
|
40
|
+
readonly newVault: NativeVaultPaths;
|
|
41
|
+
readonly slots: readonly RelocationSlot[];
|
|
42
|
+
readonly storageIds: {
|
|
43
|
+
readonly local: string;
|
|
44
|
+
readonly vault: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** The legacy-layout files of OpenCode's default dataset of one config folder. */
|
|
48
|
+
export interface LegacyDatasetFiles {
|
|
49
|
+
/** The canonical config folder holding the legacy files. */
|
|
50
|
+
readonly oldDir: string;
|
|
51
|
+
readonly oldPaths: NativePoolPaths;
|
|
52
|
+
readonly oldVault: NativeVaultPaths;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolve only the config folder's side of the dataset. Nothing under the
|
|
56
|
+
* state folder is resolved or read, so a start that stays legacy does not
|
|
57
|
+
* depend on the state folder being readable.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveLegacyDatasetFiles(configDir: string): Promise<LegacyDatasetFiles>;
|
|
60
|
+
export declare function resolveRelocationDataset(input: {
|
|
61
|
+
configDir: string;
|
|
62
|
+
identity: string;
|
|
63
|
+
stateRoot: string;
|
|
64
|
+
/** The canonical state-home folder. */
|
|
65
|
+
sub: string;
|
|
66
|
+
}): Promise<RelocationDataset>;
|
|
67
|
+
/** The named points of a transition; the step hook runs at each. */
|
|
68
|
+
export type StateLayoutStep = 'old-holders-locking' | 'locks-held' | 'fence-checked' | 'config-dir-written' | 'preparing-written' | `copied:${RelocationRole}` | 'copies-flushed' | 'artifacts-checked' | 'journal-set-aside' | 'local-runtime-tombstoned' | 'vault-runtime-tombstoned' | 'old-dir-flushed' | 'verified' | 'committed' | `cleanup:${RelocationRole}` | 'cleanup-recorded' | 'rollback:journal' | 'rollback:localRuntime' | 'rollback:vaultRuntime' | 'rollback:old-dir-flushed' | 'rollback:destination-removed' | 'rollback:sub-flushed' | 'rolled-back' | 'restore:locks-held' | 'restore:discarded' | 'restore:restoring-written' | `restore:staged:${RelocationRole}` | 'restore:staging-flushed' | 'restore:staged-written' | `restore:placed:${RelocationRole}` | 'restore:data-flushed' | 'restore:barriers-flushed' | 'restore:restored-written' | `restore:retired:${RelocationRole}` | 'restore:sub-flushed' | 'restore:done';
|
|
69
|
+
/**
|
|
70
|
+
* Thrown by a step hook to stop a transition exactly there, with no rollback
|
|
71
|
+
* and nothing else written, as a crash of the process would. Tests use it to
|
|
72
|
+
* leave every intermediate state on disk; the transition releases its locks
|
|
73
|
+
* so the next start needn't wait for their leases to expire.
|
|
74
|
+
*/
|
|
75
|
+
export declare class StateLayoutCrash extends Error {
|
|
76
|
+
readonly step: StateLayoutStep;
|
|
77
|
+
constructor(step: StateLayoutStep);
|
|
78
|
+
}
|
|
79
|
+
/** Everything a transition needs besides the dataset. */
|
|
80
|
+
export interface StateLayoutTransitionContext {
|
|
81
|
+
readonly fs: StateLayoutFs;
|
|
82
|
+
readonly stack: StateLayoutLockStack;
|
|
83
|
+
readonly release: string;
|
|
84
|
+
readonly now: () => number;
|
|
85
|
+
readonly onStep?: (step: StateLayoutStep) => void | Promise<void>;
|
|
86
|
+
/** The longest wait for one data lock; the pool store's default otherwise. */
|
|
87
|
+
readonly dataLockTimeoutMs?: number;
|
|
88
|
+
/** The longest wait for a holder lock. */
|
|
89
|
+
readonly holderLockTimeoutMs?: number;
|
|
90
|
+
}
|
|
91
|
+
export declare function stateLayoutStep(ctx: StateLayoutTransitionContext, step: StateLayoutStep): Promise<void>;
|
|
92
|
+
/** The two holder files a transition locks, in the order it locks them. */
|
|
93
|
+
export declare function transitionHolderFiles(dataset: RelocationDataset): string[];
|
|
94
|
+
/**
|
|
95
|
+
* Take the `holders` lock on `holderFile`. Waiting too long throws
|
|
96
|
+
* `LockContentionError`, which callers report as busy. Any other failure to
|
|
97
|
+
* take it (the lock file can't be created: EACCES, EROFS, ENOSPC) throws
|
|
98
|
+
* `HolderRegistrationError('unavailable')`, whose fixed message names no
|
|
99
|
+
* file, so a host never shows the lock file's path. `timeoutMs` undefined
|
|
100
|
+
* waits the lock stack's default.
|
|
101
|
+
*/
|
|
102
|
+
export declare function acquireHolderLock(ctx: Pick<StateLayoutTransitionContext, 'stack'>, holderFile: string, timeoutMs: number | undefined): Promise<void>;
|
|
103
|
+
/** Take the `holders` lock on both holder files, in sorted path order. */
|
|
104
|
+
export declare function acquireHolderLocks(dataset: RelocationDataset, ctx: StateLayoutTransitionContext): Promise<void>;
|
|
105
|
+
/** A pool's rows changed between the unlocked snapshot and its locks. */
|
|
106
|
+
export declare class PoolSnapshotChanged extends Error {
|
|
107
|
+
readonly statePath: string;
|
|
108
|
+
constructor(statePath: string);
|
|
109
|
+
}
|
|
110
|
+
/** A pool can't be read, so its row locks can't be named. */
|
|
111
|
+
export declare class PoolUnreadable extends Error {
|
|
112
|
+
readonly statePath: string;
|
|
113
|
+
readonly reason: string;
|
|
114
|
+
constructor(statePath: string, reason: string);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The refresh locks of one pool: every row lock (by id and by identity, since
|
|
118
|
+
* a refresh that started before the row learned its identity holds the id
|
|
119
|
+
* lock) and every per-identity provider lock, each list in the order it is
|
|
120
|
+
* taken. With the default provider lock between them, these are the locks
|
|
121
|
+
* common-auth's refresh holds from before the token exchange until the
|
|
122
|
+
* successor is written, so once they are held no refresh of this pool is
|
|
123
|
+
* between its exchange and its write.
|
|
124
|
+
*/
|
|
125
|
+
export interface PoolRefreshLocks {
|
|
126
|
+
readonly rows: readonly string[];
|
|
127
|
+
readonly providers: readonly string[];
|
|
128
|
+
readonly identities: readonly string[];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The one order in which every layout transition takes the data locks of a
|
|
132
|
+
* dataset's old files, each lock once: `native-migration`, the local pool's
|
|
133
|
+
* locks, the custody roster and local runtime, the vault pool's locks, the
|
|
134
|
+
* vault roster, the vault runtime and the vault routing file.
|
|
135
|
+
*
|
|
136
|
+
* A move passes `refresh`, which adds each pool's row and provider locks
|
|
137
|
+
* before its store locks, so that no refresh is in flight while it copies.
|
|
138
|
+
* A restore passes none: the old pool states are absent after a move's
|
|
139
|
+
* cleanup and the old-layout barriers stop every refresh before its provider
|
|
140
|
+
* call, while no process may use the moved copy without a live registration,
|
|
141
|
+
* which restore refuses. The restore order is therefore a subsequence of the
|
|
142
|
+
* move order, and the two can't wait on each other in opposite orders.
|
|
143
|
+
*/
|
|
144
|
+
export declare function stateLayoutDataLockOrder(dataset: RelocationDataset, options?: {
|
|
145
|
+
timeoutMs?: number;
|
|
146
|
+
refresh?: {
|
|
147
|
+
readonly local: PoolRefreshLocks;
|
|
148
|
+
readonly vault: PoolRefreshLocks;
|
|
149
|
+
};
|
|
150
|
+
}): StateLayoutLockSpec[];
|
|
151
|
+
/**
|
|
152
|
+
* Take every data lock of the dataset's old files in
|
|
153
|
+
* `stateLayoutDataLockOrder`. A move (`drainRefreshes`, the default) first
|
|
154
|
+
* reads each pool's rows without a lock to name their row and provider locks,
|
|
155
|
+
* then reads them again under the store locks: if a row changed while the
|
|
156
|
+
* locks were taken, everything is released and taken once more, and a second
|
|
157
|
+
* change throws `PoolSnapshotChanged`. A restore takes no row or provider
|
|
158
|
+
* lock.
|
|
159
|
+
*/
|
|
160
|
+
export declare function acquireDataLocks(dataset: RelocationDataset, ctx: StateLayoutTransitionContext, options?: {
|
|
161
|
+
drainRefreshes?: boolean;
|
|
162
|
+
}): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Commit the coalesced writes this process has queued for the dataset before
|
|
165
|
+
* any lock is taken: close every pool store open on the old pool states (a
|
|
166
|
+
* bounded drain of their quota readings) and drain the vault roster's quota
|
|
167
|
+
* writer. Both commit under locks the mover is about to hold, so they must
|
|
168
|
+
* finish first. Afterwards forget every cached file observation, so nothing
|
|
169
|
+
* this process cached about the old files answers for them after the move.
|
|
170
|
+
*/
|
|
171
|
+
export declare function drainCoalescedWrites(dataset: RelocationDataset): Promise<void>;
|
|
172
|
+
/** A relocation tombstone as written over an old runtime file. */
|
|
173
|
+
export declare function relocationTombstone(record: Pick<StateLayoutRecord, 'sub' | 'stateRoot' | 'identity' | 'attempt' | 'by'>): string;
|
|
174
|
+
export declare function tombstoneAttempt(path: string): Promise<string | undefined>;
|
|
175
|
+
export declare function exists(path: string): Promise<boolean>;
|
|
176
|
+
export declare function removeOwnStaging(path: string): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* Why `record` doesn't list exactly the files of `dataset`, each role with the
|
|
179
|
+
* paths this start resolved, or undefined when it does. Recovery deletes and
|
|
180
|
+
* renames only files a record names, so a record naming any other file is
|
|
181
|
+
* never acted on.
|
|
182
|
+
*/
|
|
183
|
+
export declare function recordFilesProblem(record: Pick<StateLayoutRecord, 'files' | 'restore'>, dataset: Pick<RelocationDataset, 'slots'>): string | undefined;
|
|
184
|
+
/** Why a move may not start, or undefined when it may. */
|
|
185
|
+
export declare function movePreconditionFailure(dataset: RelocationDataset, platform: NodeJS.Platform): Promise<string | undefined>;
|
|
186
|
+
/** A rollback met a state no supported writer produces; it changed nothing more. */
|
|
187
|
+
export declare class StateLayoutRollbackRefused extends Error {
|
|
188
|
+
readonly path: string;
|
|
189
|
+
constructor(path: string, detail: string);
|
|
190
|
+
}
|
|
191
|
+
/** Expect `path` to hold bytes hashing to `sha256`, or to be absent for null. */
|
|
192
|
+
export declare function expectDigest(path: string, sha256: string | null): () => Promise<string | undefined>;
|
|
193
|
+
export declare function expectTombstone(path: string, attempt: string): () => Promise<string | undefined>;
|
|
194
|
+
export declare function expectAbsent(path: string): () => Promise<string | undefined>;
|
|
195
|
+
/**
|
|
196
|
+
* Rename `from` to `to` (or unlink `from` when `to` is undefined) through
|
|
197
|
+
* `publishOwned`, treating a skipped retry as a failure.
|
|
198
|
+
*/
|
|
199
|
+
export declare function owned(ctx: StateLayoutTransitionContext, step: Parameters<typeof publishOwned>[0], expect: () => Promise<string | undefined>): Promise<void>;
|
|
200
|
+
/**
|
|
201
|
+
* Undo a `preparing` record, under the mover's full lock set. Only this
|
|
202
|
+
* attempt's own artifacts are undone; any other state is left alone because
|
|
203
|
+
* the old layout is still authoritative. Every step is a no-op once done, so
|
|
204
|
+
* recovery may run again from any point. Throws `StateLayoutRollbackRefused`
|
|
205
|
+
* when a slot is in a state no supported writer produces.
|
|
206
|
+
*/
|
|
207
|
+
export declare function rollbackPreparing(record: StateLayoutRecord, dataset: RelocationDataset, ctx: StateLayoutTransitionContext): Promise<StateLayoutRecord>;
|
|
208
|
+
/**
|
|
209
|
+
* Remove the verified old copies, local pool state first, while
|
|
210
|
+
* every data lock is still held. A file is unlinked only if it still hashes
|
|
211
|
+
* to the recorded value. A changed or reappeared old pool state or journal is
|
|
212
|
+
* a conflict: both copies are kept and the record says `conflict`, which
|
|
213
|
+
* stops every state-home process. Any other changed file is kept and listed.
|
|
214
|
+
* Running it again after a conflict was resolved by hand finishes the
|
|
215
|
+
* cleanup.
|
|
216
|
+
*
|
|
217
|
+
* Once a credential conflict is found it outlives any later failure: if a
|
|
218
|
+
* later step or the final record write fails, the conflict is still recorded
|
|
219
|
+
* when possible, and the call throws a `conflict` refusal rather than the
|
|
220
|
+
* failure, so no caller can mistake it for an ordinary pending cleanup.
|
|
221
|
+
*/
|
|
222
|
+
export declare function cleanupOldCopies(record: StateLayoutRecord, dataset: RelocationDataset, ctx: StateLayoutTransitionContext): Promise<StateLayoutRecord>;
|
|
223
|
+
/** How a move ended. */
|
|
224
|
+
export type StateLayoutMoveOutcome = {
|
|
225
|
+
readonly status: 'committed';
|
|
226
|
+
readonly record: StateLayoutRecord;
|
|
227
|
+
} | {
|
|
228
|
+
readonly status: 'rolled-back';
|
|
229
|
+
readonly reason: string;
|
|
230
|
+
readonly record?: StateLayoutRecord;
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* The move itself, under the layout lock, both holder locks and every data
|
|
234
|
+
* lock, all held by `ctx.stack`. `checkArtifacts` repeats the fence's check
|
|
235
|
+
* for pid artifacts of older versions and returns a blocker description or
|
|
236
|
+
* undefined.
|
|
237
|
+
*
|
|
238
|
+
* Before the commit any failure rolls back and resolves `rolled-back`; a
|
|
239
|
+
* rollback that refuses throws `StateLayoutRollbackRefused`. A failure of the
|
|
240
|
+
* commit write itself is resolved from the record on disk. After the commit
|
|
241
|
+
* nothing goes back to legacy: a cleanup failure leaves cleanup `pending` and
|
|
242
|
+
* still resolves `committed`.
|
|
243
|
+
*/
|
|
244
|
+
export declare function moveDataset(dataset: RelocationDataset, ctx: StateLayoutTransitionContext, checkArtifacts: () => Promise<string | undefined>): Promise<StateLayoutMoveOutcome>;
|
|
245
|
+
/**
|
|
246
|
+
* Flush `<sub>/layout.json` and `<sub>`. A process that died after renaming
|
|
247
|
+
* a new record into place but before flushing `<sub>` leaves a record that
|
|
248
|
+
* power loss can still revert, so a process that acts on a record it found
|
|
249
|
+
* makes it durable first.
|
|
250
|
+
*/
|
|
251
|
+
export declare function syncLayoutRecord(dataset: Pick<RelocationDataset, 'sub'>, ctx: Pick<StateLayoutTransitionContext, 'fs'>): Promise<void>;
|
|
252
|
+
/** Flush a committed record and `<sub>`. */
|
|
253
|
+
export declare function syncCommitted(dataset: RelocationDataset, ctx: Pick<StateLayoutTransitionContext, 'fs'>): Promise<void>;
|
|
@@ -23,10 +23,19 @@ export declare const LEGACY_STATE_LAYOUT: StateLayout;
|
|
|
23
23
|
* The layout a resolver uses when its caller passes none. A process decides it
|
|
24
24
|
* once, before it resolves any pool path, and never switches: every
|
|
25
25
|
* re-resolution must use the same layout as the first, or comparisons between
|
|
26
|
-
* them fail.
|
|
27
|
-
*
|
|
26
|
+
* them fail. `legacyConfig` is the canonical account file the resolver works
|
|
27
|
+
* from. A dataset with no recorded decision, and every call without
|
|
28
|
+
* `legacyConfig`, uses the legacy layout.
|
|
28
29
|
*/
|
|
29
|
-
export declare function processStateLayout(): StateLayout;
|
|
30
|
+
export declare function processStateLayout(legacyConfig?: string): StateLayout;
|
|
31
|
+
/**
|
|
32
|
+
* Record the layout decided for the dataset whose canonical account file is
|
|
33
|
+
* `legacyConfig`. A decision is final for the process: recording a different
|
|
34
|
+
* layout for the same dataset throws.
|
|
35
|
+
*/
|
|
36
|
+
export declare function recordProcessStateLayout(legacyConfig: string, layout: StateLayout): void;
|
|
37
|
+
/** Forget every recorded layout decision; tests only. */
|
|
38
|
+
export declare function resetProcessStateLayoutsForTests(): void;
|
|
30
39
|
/** The `node:path` functions the pure helpers use; tests pass `path.win32`. */
|
|
31
40
|
export type StatePathApi = Pick<typeof nodePath, 'join' | 'isAbsolute'>;
|
|
32
41
|
export interface StateRootInputs {
|
|
@@ -86,6 +95,16 @@ export declare function resolveStateHomeTarget(options?: {
|
|
|
86
95
|
env?: NodeJS.ProcessEnv;
|
|
87
96
|
home?: string;
|
|
88
97
|
}): StateHomeTarget | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* `resolveStateHomeTarget` for a config folder whose identity is already
|
|
100
|
+
* known. Naming the subfolder resolves the home directory's default config
|
|
101
|
+
* folder, which may be a different folder from the one in use, so a start
|
|
102
|
+
* that never looks at the state folder never calls this.
|
|
103
|
+
*/
|
|
104
|
+
export declare function stateHomeTargetFor(identity: string, options?: {
|
|
105
|
+
env?: NodeJS.ProcessEnv;
|
|
106
|
+
home?: string;
|
|
107
|
+
}): StateHomeTarget;
|
|
89
108
|
/**
|
|
90
109
|
* Names of the files kept in a state-home subfolder. Data files keep their
|
|
91
110
|
* default legacy names so the folder stays readable for manual recovery.
|
|
@@ -143,7 +162,12 @@ export interface ConfigDirMarker {
|
|
|
143
162
|
export declare function readConfigDirMarker(dir: string): Promise<ConfigDirMarker | undefined>;
|
|
144
163
|
export declare const STATE_LAYOUT_PHASES: readonly ['preparing', 'committed', 'rolled-back', 'restoring', 'restore-staged', 'restored'];
|
|
145
164
|
export type StateLayoutPhase = (typeof STATE_LAYOUT_PHASES)[number];
|
|
146
|
-
|
|
165
|
+
/**
|
|
166
|
+
* `conflict`: cleanup found that an old copy of the local pool state or the
|
|
167
|
+
* journal changed after the move was verified, so both copies are kept and no
|
|
168
|
+
* process serves the dataset until a person removes one of them.
|
|
169
|
+
*/
|
|
170
|
+
declare const CLEANUP_STATES: readonly ['pending', 'done', 'kept', 'conflict'];
|
|
147
171
|
declare const RESTORE_CLEANUP_STATES: readonly ['pending', 'done'];
|
|
148
172
|
/** The fields of `layout.json` that decide whether a process may use a layout. */
|
|
149
173
|
export interface StateLayoutMarker {
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { publishOwned, type StateLayoutFs } from './state-layout-io.ts';
|
|
2
|
+
import { type StateLayoutPhase } from './state-layout-paths.ts';
|
|
3
|
+
/**
|
|
4
|
+
* The data files a move copies, in the order cleanup removes the old copies:
|
|
5
|
+
* the local pool state (refresh tokens) first.
|
|
6
|
+
*/
|
|
7
|
+
export declare const RELOCATION_ROLES: readonly ['localPoolState', 'journal', 'localRoster', 'vaultStoreState', 'vaultRoster', 'vaultRouting', 'localRuntime', 'vaultRuntime'];
|
|
8
|
+
export type RelocationRole = (typeof RELOCATION_ROLES)[number];
|
|
9
|
+
/** Old files whose change or reappearance after a move is a fatal conflict. */
|
|
10
|
+
export declare const CREDENTIAL_AUTHORITY_ROLES: readonly RelocationRole[];
|
|
11
|
+
/** One file of a move: where it was, where it goes, and its bytes' hash. */
|
|
12
|
+
export interface RelocationFileRecord {
|
|
13
|
+
readonly role: RelocationRole;
|
|
14
|
+
readonly from: string;
|
|
15
|
+
readonly to: string;
|
|
16
|
+
/** Null when the file was absent at the move. */
|
|
17
|
+
readonly sha256: string | null;
|
|
18
|
+
}
|
|
19
|
+
export interface RelocationBarriers {
|
|
20
|
+
/** Whether the old journal existed and was set aside. */
|
|
21
|
+
readonly journal: 'present' | 'absent';
|
|
22
|
+
/** The hash of each old runtime before its tombstone, or null when absent. */
|
|
23
|
+
readonly localRuntime: {
|
|
24
|
+
readonly original: string | null;
|
|
25
|
+
};
|
|
26
|
+
readonly vaultRuntime: {
|
|
27
|
+
readonly original: string | null;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** One file a restore copies back: from `to` (state folder) to `from` (config folder). */
|
|
31
|
+
export interface RestoreFileRecord {
|
|
32
|
+
readonly role: RelocationRole;
|
|
33
|
+
/** The legacy-layout path the file goes back to. */
|
|
34
|
+
readonly from: string;
|
|
35
|
+
/** The state-home copy it is read from. */
|
|
36
|
+
readonly to: string;
|
|
37
|
+
readonly sha256: string;
|
|
38
|
+
/** `<from>.restoring-<restore attempt>`: the name it is staged under. */
|
|
39
|
+
readonly staging: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* What a restore to the legacy layout records in `layout.json`. A restore
|
|
43
|
+
* done by hand (the documented manual procedure) writes only
|
|
44
|
+
* `{ cleanup: 'done' }` with phase `restored`; every restore made by the
|
|
45
|
+
* command records all fields.
|
|
46
|
+
*/
|
|
47
|
+
export interface StateLayoutRestoreRecord {
|
|
48
|
+
readonly attempt?: string;
|
|
49
|
+
readonly startedAt?: number;
|
|
50
|
+
readonly files?: readonly RestoreFileRecord[];
|
|
51
|
+
/**
|
|
52
|
+
* For each runtime: `copy` when the state folder holds one, which goes
|
|
53
|
+
* back over the tombstone; `unlink` when it holds none, so the tombstone
|
|
54
|
+
* is only removed.
|
|
55
|
+
*/
|
|
56
|
+
readonly runtimes?: {
|
|
57
|
+
readonly local: 'copy' | 'unlink';
|
|
58
|
+
readonly vault: 'copy' | 'unlink';
|
|
59
|
+
};
|
|
60
|
+
/** Set from phase `restored` on: whether the state-folder copies are removed. */
|
|
61
|
+
readonly cleanup?: 'pending' | 'done';
|
|
62
|
+
/** State-folder copies that changed after they were staged; left in place. */
|
|
63
|
+
readonly kept?: readonly string[];
|
|
64
|
+
}
|
|
65
|
+
/** The staging name a restore uses for `from`. */
|
|
66
|
+
export declare function restoreStagingPath(from: string, attempt: string): string;
|
|
67
|
+
/** The full `layout.json` a move writes. */
|
|
68
|
+
export interface StateLayoutRecord {
|
|
69
|
+
readonly version: 1;
|
|
70
|
+
readonly phase: StateLayoutPhase;
|
|
71
|
+
readonly attempt: string;
|
|
72
|
+
readonly identity: string;
|
|
73
|
+
readonly stateRoot: string;
|
|
74
|
+
readonly sub: string;
|
|
75
|
+
readonly oldDir: string;
|
|
76
|
+
readonly storageIds: {
|
|
77
|
+
readonly local: string;
|
|
78
|
+
readonly vault: string;
|
|
79
|
+
};
|
|
80
|
+
readonly by: string;
|
|
81
|
+
readonly startedAt: number;
|
|
82
|
+
readonly files: readonly RelocationFileRecord[];
|
|
83
|
+
readonly barriers: RelocationBarriers;
|
|
84
|
+
readonly committedAt?: number;
|
|
85
|
+
readonly cleanup?: 'pending' | 'done' | 'kept' | 'conflict';
|
|
86
|
+
/** Old non-credential files whose bytes changed after the move; left in place. */
|
|
87
|
+
readonly kept?: readonly string[];
|
|
88
|
+
/** Old credential or journal files found changed or back after the move. */
|
|
89
|
+
readonly conflict?: readonly {
|
|
90
|
+
readonly old: string;
|
|
91
|
+
readonly new: string;
|
|
92
|
+
}[];
|
|
93
|
+
readonly rolledBackAt?: number;
|
|
94
|
+
/** Present while a restore is under way or finished; written by restore. */
|
|
95
|
+
readonly restore?: StateLayoutRestoreRecord;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Whether `name` is exactly the name a move sets the journal named
|
|
99
|
+
* `journalName` aside under: `<journalName>.moving-<attempt>`, with a valid
|
|
100
|
+
* attempt id and nothing after it.
|
|
101
|
+
*/
|
|
102
|
+
export declare function isMovingJournalName(journalName: string, name: string): boolean;
|
|
103
|
+
export declare function layoutRecordPath(sub: string): string;
|
|
104
|
+
/** The name the journal is set aside under during a move. */
|
|
105
|
+
export declare function movingJournalPath(journal: string, attempt: string): string;
|
|
106
|
+
/**
|
|
107
|
+
* Read `<sub>/layout.json` in full. Undefined only when it is absent; any other
|
|
108
|
+
* read error, and any content that isn't a complete record, throws
|
|
109
|
+
* `StateLayoutFileError`. An unreadable marker never counts as "no move".
|
|
110
|
+
*/
|
|
111
|
+
export declare function readStateLayoutRecord(sub: string): Promise<StateLayoutRecord | undefined>;
|
|
112
|
+
/**
|
|
113
|
+
* Publish `record` as `<sub>/layout.json` durably: stage it owner-only as
|
|
114
|
+
* `layout.json.<attempt>.<nonce>.tmp`, flush it, rename it into place through
|
|
115
|
+
* `publishOwned`, then flush `<sub>`. `expect` is the ownership check for the
|
|
116
|
+
* slot (for example "still this attempt's preparing record").
|
|
117
|
+
*/
|
|
118
|
+
export declare function publishStateLayoutRecord(record: StateLayoutRecord, guard: Parameters<typeof publishOwned>[1], fs: StateLayoutFs): Promise<void>;
|
|
119
|
+
/** Whether `name` in `<sub>` is a staging file of a layout record. */
|
|
120
|
+
export declare function isLayoutRecordStage(name: string, attempt?: string): boolean;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type HolderLiveness } from './holder-registry.ts';
|
|
2
|
+
import { type PreNArtifactLocations } from './layout-fence.ts';
|
|
3
|
+
import type { NativeLockEvent } from './native-lock-observer.ts';
|
|
4
|
+
import { type StateLayoutFs } from './state-layout-io.ts';
|
|
5
|
+
import { type RelocationDataset, type StateLayoutStep, type StateLayoutTransitionContext } from './state-layout-move.ts';
|
|
6
|
+
import { type StateLayoutRecord } from './state-layout-record.ts';
|
|
7
|
+
export interface RestoreLegacyOptions {
|
|
8
|
+
env?: NodeJS.ProcessEnv;
|
|
9
|
+
home?: string;
|
|
10
|
+
/** Filesystem seam; `node:fs` for this platform otherwise. */
|
|
11
|
+
fs?: StateLayoutFs;
|
|
12
|
+
liveness?: HolderLiveness;
|
|
13
|
+
/** Where pid artifacts of older versions are looked for. */
|
|
14
|
+
artifacts?: PreNArtifactLocations;
|
|
15
|
+
onLockEvent?: (event: NativeLockEvent) => void;
|
|
16
|
+
/** Runs at each named step; tests inject faults here. */
|
|
17
|
+
onStep?: (step: StateLayoutStep) => void | Promise<void>;
|
|
18
|
+
now?: () => number;
|
|
19
|
+
/** Tests shorten the waits; production uses the defaults. */
|
|
20
|
+
timeouts?: {
|
|
21
|
+
layoutMs?: number;
|
|
22
|
+
holderMs?: number;
|
|
23
|
+
dataMs?: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** How a restore ended. */
|
|
27
|
+
export type RestoreLegacyOutcome = {
|
|
28
|
+
readonly status: 'restored';
|
|
29
|
+
/** The config folder that holds the dataset again. */
|
|
30
|
+
readonly oldDir: string;
|
|
31
|
+
/** The state folder it came from. */
|
|
32
|
+
readonly sub: string;
|
|
33
|
+
/** State-folder copies that changed after staging and were left in place. */
|
|
34
|
+
readonly kept: readonly string[];
|
|
35
|
+
} | {
|
|
36
|
+
/** The dataset is already in the legacy layout; nothing was written. */
|
|
37
|
+
readonly status: 'not-moved';
|
|
38
|
+
readonly reason: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Make the record this run found durable, under the locks, before acting on
|
|
42
|
+
* it. The run that published it may have died after renaming it into place
|
|
43
|
+
* but before flushing `<sub>`: R3 or R5 acting on a record that power loss
|
|
44
|
+
* can still revert would leave legacy names or state-folder copies changed
|
|
45
|
+
* under an older phase, whose recovery goes the other way.
|
|
46
|
+
*/
|
|
47
|
+
export declare function syncObservedRestoreRecord(dataset: RelocationDataset, ctx: StateLayoutTransitionContext): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* R5, under the restore's locks (or a start's layout and holder locks): remove
|
|
50
|
+
* each state-folder copy that still holds the bytes that were staged, then
|
|
51
|
+
* record the cleanup as done. A copy that changed, can't be read, or can't be
|
|
52
|
+
* removed is kept and listed: no process was admitted to the state folder
|
|
53
|
+
* since R1, so only a person can have changed it, and the legacy copy is
|
|
54
|
+
* already durable. Only the record write itself can fail this step.
|
|
55
|
+
*/
|
|
56
|
+
export declare function finishRestoreRetirement(record: StateLayoutRecord, dataset: RelocationDataset, ctx: StateLayoutTransitionContext): Promise<StateLayoutRecord>;
|
|
57
|
+
/**
|
|
58
|
+
* Undo a restore that stopped before its boundary (phase `restoring`), under
|
|
59
|
+
* the layout lock and both holder locks: unlink this attempt's staging files,
|
|
60
|
+
* flush the config folder, then publish the move's record again as
|
|
61
|
+
* `committed`, as it was before the restore began. Nothing at a legacy name
|
|
62
|
+
* was touched before the boundary and the barriers stayed up, so no version
|
|
63
|
+
* served from the config folder, and the state-folder files were only read.
|
|
64
|
+
*/
|
|
65
|
+
export declare function discardAbandonedRestore(record: StateLayoutRecord, dataset: RelocationDataset, ctx: StateLayoutTransitionContext): Promise<StateLayoutRecord>;
|
|
66
|
+
/**
|
|
67
|
+
* R3 to R5 of a restore whose staging is durable (phase `restore-staged`):
|
|
68
|
+
* classify every slot afresh, refuse before writing anything if one is in a
|
|
69
|
+
* state no supported writer produces, then put the data files in place, flush,
|
|
70
|
+
* remove the barriers, flush, publish `restored` and retire the state-folder
|
|
71
|
+
* copies. Running it again after a crash at any point converges.
|
|
72
|
+
*/
|
|
73
|
+
export declare function finishStagedRestore(record: StateLayoutRecord, dataset: RelocationDataset, ctx: StateLayoutTransitionContext): Promise<StateLayoutRecord>;
|
|
74
|
+
/**
|
|
75
|
+
* Restore OpenCode's dataset from the state folder to the legacy layout in
|
|
76
|
+
* its config folder, or finish a restore that stopped. Run it only with every
|
|
77
|
+
* host stopped, behind setup's process fence; it also refuses while any
|
|
78
|
+
* process is registered beside either copy. Refuses with
|
|
79
|
+
* `StateLayoutRefusalError` (code `restore-refused` with a message naming
|
|
80
|
+
* what to fix, or a marker problem); resolves `not-moved` when there is
|
|
81
|
+
* nothing to restore.
|
|
82
|
+
*/
|
|
83
|
+
export declare function restoreLegacyLayout(options?: RestoreLegacyOptions): Promise<RestoreLegacyOutcome>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/anthropic-auth-core",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"@cortexkit/common-auth": "0.28.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@cortexkit/claustrum-client": "0.
|
|
36
|
+
"@cortexkit/claustrum-client": "0.13.0",
|
|
37
37
|
"@cortexkit/private-files": "0.2.1",
|
|
38
|
-
"@cortexkit/subc-client": "^0.22.
|
|
38
|
+
"@cortexkit/subc-client": "^0.22.10",
|
|
39
39
|
"jsonc-parser": "^3.3.1",
|
|
40
40
|
"xxhash-wasm": "^1.1.0"
|
|
41
41
|
}
|