@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,65 @@
|
|
|
1
|
+
import { type HolderLiveness } from './holder-registry.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Environment variables that name anthropic-auth files. A process with any of
|
|
4
|
+
* them set never moves a dataset or writes `layout.json`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const STATE_PATH_OVERRIDE_VARIABLES: readonly ['OPENCODE_ANTHROPIC_AUTH_FILE', 'OPENCODE_ANTHROPIC_AUTH_STATE_FILE', 'OPENCODE_ANTHROPIC_AUTH_ROUTING_STATE_FILE', 'OPENCODE_ANTHROPIC_AUTH_CLAUSTRUM_ENROLLMENT_FILE', 'OPENCODE_TUI_PREFERENCES_FILE'];
|
|
7
|
+
/** The path overrides set in `env`. */
|
|
8
|
+
export declare function statePathOverrides(env: NodeJS.ProcessEnv): string[];
|
|
9
|
+
/** Something that keeps a dataset where it is. */
|
|
10
|
+
export type FenceBlocker = {
|
|
11
|
+
readonly kind: 'holder';
|
|
12
|
+
readonly holderFile: string;
|
|
13
|
+
readonly pid: number | null;
|
|
14
|
+
readonly role: string | null;
|
|
15
|
+
readonly file: string | null;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: 'holder-file';
|
|
18
|
+
readonly holderFile: string;
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: 'pre-n-artifact';
|
|
22
|
+
readonly artifact: string;
|
|
23
|
+
readonly pid: number;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: 'artifact-scan';
|
|
26
|
+
readonly path: string;
|
|
27
|
+
readonly reason: string;
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: 'override';
|
|
30
|
+
readonly variable: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The fence's holder check: every live entry, in the holder file of each
|
|
34
|
+
* directory, that lists one of `files` blocks; so does a holder file that
|
|
35
|
+
* can't be read or parsed (an absent one is fine). An entry this version
|
|
36
|
+
* can't read blocks too, because it may belong to a live process. Nothing is
|
|
37
|
+
* excused by pid, not even this process's own: the mover registers nothing
|
|
38
|
+
* before it has decided, so any entry of its pid belongs to another copy of
|
|
39
|
+
* the code.
|
|
40
|
+
*/
|
|
41
|
+
export declare function holderFenceBlockers(input: {
|
|
42
|
+
directories: readonly string[];
|
|
43
|
+
files: readonly string[];
|
|
44
|
+
liveness?: HolderLiveness;
|
|
45
|
+
}): Promise<FenceBlocker[]>;
|
|
46
|
+
/** Where pid artifacts of older versions may be found. */
|
|
47
|
+
export interface PreNArtifactLocations {
|
|
48
|
+
/** Folders whose every subfolder holds RPC port files (`<root>/rpc`). */
|
|
49
|
+
readonly rpcRoots: readonly string[];
|
|
50
|
+
/** Folders that hold RPC port files directly (an overridden RPC folder). */
|
|
51
|
+
readonly rpcDirs: readonly string[];
|
|
52
|
+
/** CacheKeep registry folders (`<pid>-<uuid>.json` records). */
|
|
53
|
+
readonly cacheKeepDirs: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
/** The artifact folders this process can find for `env`. */
|
|
56
|
+
export declare function defaultPreNArtifactLocations(env?: NodeJS.ProcessEnv, home?: string): PreNArtifactLocations;
|
|
57
|
+
/**
|
|
58
|
+
* The fence's check for older versions: an RPC port file or CacheKeep record without the
|
|
59
|
+
* `writer` stamp, whose pid is alive, belongs to a version that registers
|
|
60
|
+
* nothing and blocks. Every entry is read; a folder or file that can't be
|
|
61
|
+
* read or parsed blocks too. No timestamp ever clears a live pid.
|
|
62
|
+
*/
|
|
63
|
+
export declare function preNArtifactBlockers(locations: PreNArtifactLocations, pidAlive?: (pid: number) => boolean): Promise<FenceBlocker[]>;
|
|
64
|
+
/** One line naming a blocker, for the log. */
|
|
65
|
+
export declare function describeFenceBlocker(blocker: FenceBlocker): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { NativeRefreshHooks, NativeRefreshRestriction, NativeRefreshSubject } from './native-refresh-coordinator.ts';
|
|
2
|
-
import type
|
|
2
|
+
import { type NativePoolPaths } from './pool-paths.ts';
|
|
3
3
|
/** Host adapters supply mode, account and quota policy for local OAuth authorization and refresh. */
|
|
4
4
|
export type NativeLocalExternalPolicy = Exclude<NativeRefreshRestriction, {
|
|
5
5
|
credentialFingerprint: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type NativePoolPaths } from './pool-paths.ts';
|
|
2
2
|
import { type StateLayout } from './state-layout-paths.ts';
|
|
3
3
|
/** Vault files have no migration journal: enrollment alone authorizes their use. */
|
|
4
4
|
export interface NativeVaultPaths {
|
|
@@ -13,10 +13,9 @@ export interface NativeVaultPaths {
|
|
|
13
13
|
/**
|
|
14
14
|
* Keep vault settings, metadata and affinity outside every local recovery file.
|
|
15
15
|
*
|
|
16
|
-
* `layout` must be the one `local` was resolved in
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* legacy-layout path.
|
|
16
|
+
* `layout` must be the one `local` was resolved in. It defaults to the layout
|
|
17
|
+
* `local` itself shows (see `stateLayoutOfPaths`). Vault settings stay beside
|
|
18
|
+
* the account file in every layout, and the storage id is always computed
|
|
19
|
+
* from the vault store state's legacy-layout path.
|
|
21
20
|
*/
|
|
22
21
|
export declare function nativeVaultPaths(local: NativePoolPaths, layout?: StateLayout): NativeVaultPaths;
|
package/dist/pool-paths.d.ts
CHANGED
|
@@ -9,6 +9,12 @@ export interface NativePoolPaths {
|
|
|
9
9
|
roster: string;
|
|
10
10
|
storageId: string;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* The layout `paths` were resolved in. In the legacy and explicit layouts the
|
|
14
|
+
* runtime file sits beside the account state file; only the state-home layout
|
|
15
|
+
* puts it in another folder, which is then the state-home folder.
|
|
16
|
+
*/
|
|
17
|
+
export declare function stateLayoutOfPaths(paths: Pick<NativePoolPaths, 'legacyState' | 'runtime'>): StateLayout;
|
|
12
18
|
/** Local hosts share the native pool's routing file unless explicitly overridden. */
|
|
13
19
|
export declare function resolveLocalRoutingDestination(host: 'opencode' | 'pi', paths: NativePoolPaths, env: NodeJS.ProcessEnv): string;
|
|
14
20
|
/**
|
package/dist/pool-store.d.ts
CHANGED
|
@@ -26,6 +26,13 @@ export declare function nativePoolStoreLocks(paths: NativePoolStorePaths): reado
|
|
|
26
26
|
readonly path: string;
|
|
27
27
|
readonly name: 'pool-state';
|
|
28
28
|
}];
|
|
29
|
+
/**
|
|
30
|
+
* Close every store this process opened on one of `statePaths`. Closing
|
|
31
|
+
* drains the store's coalesced quota writes (bounded by common-auth's drain
|
|
32
|
+
* time) and makes later quota readings on it refuse. Returns how many were
|
|
33
|
+
* closed; a failed close rejects.
|
|
34
|
+
*/
|
|
35
|
+
export declare function drainNativePoolStores(statePaths: readonly string[]): Promise<number>;
|
|
29
36
|
/**
|
|
30
37
|
* Storage machinery only. Callers must separately establish local migration
|
|
31
38
|
* authority or vault enrollment authority before using these files.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type NativePoolPaths } from './pool-paths.ts';
|
|
2
2
|
import { type RelocationPointer } from './state-layout-paths.ts';
|
|
3
3
|
/**
|
|
4
4
|
* Fixed text of the refusal a process gives once a newer version has moved
|
|
@@ -33,6 +33,11 @@ export interface RelocationGuard {
|
|
|
33
33
|
isRelocated(): Promise<boolean>;
|
|
34
34
|
}
|
|
35
35
|
/** The first tombstone among `runtimePaths`, with the path that holds it. */
|
|
36
|
+
/**
|
|
37
|
+
* Forget every runtime file observation, so the next check reads each file
|
|
38
|
+
* again. A move calls it after replacing files in this process.
|
|
39
|
+
*/
|
|
40
|
+
export declare function forgetRelocationObservations(): void;
|
|
36
41
|
export declare function findRelocationTombstone(runtimePaths: readonly string[]): Promise<{
|
|
37
42
|
path: string;
|
|
38
43
|
pointer: RelocationPointer;
|
|
@@ -44,5 +49,11 @@ export declare function createRelocationGuard(runtimePaths: readonly string[]):
|
|
|
44
49
|
* runtime. A move replaces both with tombstones before it commits.
|
|
45
50
|
*/
|
|
46
51
|
export declare function nativeRelocationRuntimePaths(paths: NativePoolPaths): [string, string];
|
|
47
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* The guard for a dataset's locked write callbacks. In the legacy layout it
|
|
54
|
+
* refuses once a newer version has moved the dataset (a tombstone over either
|
|
55
|
+
* legacy runtime file). In the state-home layout the tombstones are expected,
|
|
56
|
+
* and the same callbacks instead refuse once the dataset is in conflict: an
|
|
57
|
+
* old copy of the credentials or journal is back beside the moved one.
|
|
58
|
+
*/
|
|
48
59
|
export declare function nativeRelocationGuard(paths: NativePoolPaths): RelocationGuard;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { NativePoolPaths } from './pool-paths.ts';
|
|
2
|
+
import type { RelocationGuard } from './relocation-guard.ts';
|
|
3
|
+
import { StateLayoutRefusalError } from './state-layout-errors.ts';
|
|
4
|
+
import { type StateLayoutRecord } from './state-layout-record.ts';
|
|
5
|
+
/** The files a state-home process watches for a conflict with its own copies. */
|
|
6
|
+
export interface StateHomeServingFiles {
|
|
7
|
+
/** The state-home folder, holding `layout.json`. */
|
|
8
|
+
readonly sub: string;
|
|
9
|
+
readonly oldPoolState: string;
|
|
10
|
+
readonly newPoolState: string;
|
|
11
|
+
readonly oldJournal: string;
|
|
12
|
+
readonly newJournal: string;
|
|
13
|
+
/**
|
|
14
|
+
* The old custody roster and vault store state, roster and routing files.
|
|
15
|
+
* They hold no credential, so one back in the old folder is only warned
|
|
16
|
+
* about.
|
|
17
|
+
*/
|
|
18
|
+
readonly oldMetadata: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The serving files of a state-home dataset. Only OpenCode's default dataset
|
|
22
|
+
* can be in the state-home layout, so the old copies have the default names
|
|
23
|
+
* beside the account state file.
|
|
24
|
+
*/
|
|
25
|
+
export declare function stateHomeServingFiles(paths: Pick<NativePoolPaths, 'legacyState' | 'state' | 'journal' | 'runtime'>): StateHomeServingFiles;
|
|
26
|
+
/** Old files that hold no credential and are back in the old folder after a move. */
|
|
27
|
+
export declare function reappearedMetadataFiles(record: StateLayoutRecord): Promise<string[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Warn, once per file and process, about old metadata files back in the old
|
|
30
|
+
* folder. Serving goes on: they hold no credential and nothing reads them.
|
|
31
|
+
*/
|
|
32
|
+
export declare function warnReappearedMetadata(record: StateLayoutRecord): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* What an old file holds: absent, the hash of a regular file, or `other` for
|
|
35
|
+
* anything that isn't a plain file (a symlink, a folder), which can never be
|
|
36
|
+
* proven to be the verified copy.
|
|
37
|
+
*/
|
|
38
|
+
type Observed = string | 'absent' | 'other';
|
|
39
|
+
/** What the serving check saw of the old files. */
|
|
40
|
+
export interface StateHomeObservation {
|
|
41
|
+
readonly oldJournal: boolean;
|
|
42
|
+
readonly oldPoolState: Observed;
|
|
43
|
+
/**
|
|
44
|
+
* The journal under the name the move set it aside as
|
|
45
|
+
* (`<journal>.moving-<attempt>`). Cleanup removes it; until then it must
|
|
46
|
+
* still hold the verified bytes.
|
|
47
|
+
*/
|
|
48
|
+
readonly movingJournal: Observed;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Why a state-home dataset must not be served now, or undefined when it may.
|
|
52
|
+
* An old pool state or journal back at the legacy path after the move, or an
|
|
53
|
+
* old pool state or set-aside journal whose bytes differ from the verified
|
|
54
|
+
* copy while cleanup is still pending, is a fatal conflict: two
|
|
55
|
+
* usable-looking copies of a single-use refresh token.
|
|
56
|
+
*/
|
|
57
|
+
export declare function stateHomeConflictFrom(record: StateLayoutRecord, files: StateHomeServingFiles, observed: StateHomeObservation): StateLayoutRefusalError | undefined;
|
|
58
|
+
/** Read the marker and old files and report a conflict, without caching. */
|
|
59
|
+
export declare function checkStateHomeServing(files: StateHomeServingFiles): Promise<StateLayoutRefusalError | undefined>;
|
|
60
|
+
/** What a test observes of the guard's file access. */
|
|
61
|
+
export type StateHomeServingProbe = (event: {
|
|
62
|
+
type: 'lstat';
|
|
63
|
+
paths: number;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'read';
|
|
66
|
+
}) => void;
|
|
67
|
+
/** Observe the guard's `lstat` calls and its full reads; tests only. */
|
|
68
|
+
export declare function probeStateHomeServingForTests(next: StateHomeServingProbe | undefined): void;
|
|
69
|
+
/**
|
|
70
|
+
* A guard that stops an already serving state-home process once its dataset
|
|
71
|
+
* is in conflict. It has the relocation guard's shape so every locked write
|
|
72
|
+
* callback that checks for a move (vault store, roster, routing, authorize)
|
|
73
|
+
* checks for a conflict instead. It also warns once about an old metadata
|
|
74
|
+
* file back in the old folder. It takes no lock, and reads files again only
|
|
75
|
+
* when the `lstat` identity of the marker, an old file or the set-aside
|
|
76
|
+
* journal changed, so a check costs a few parallel `lstat`s while nothing
|
|
77
|
+
* changes.
|
|
78
|
+
*/
|
|
79
|
+
export declare function createStateHomeServingGuard(files: StateHomeServingFiles): RelocationGuard & {
|
|
80
|
+
check(): Promise<StateLayoutRefusalError | undefined>;
|
|
81
|
+
};
|
|
82
|
+
/** Forget logged conflicts and shared observations; tests only. */
|
|
83
|
+
export declare function resetStateHomeServingGuardForTests(): void;
|
|
84
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { type HolderLiveness, type HolderRole, type NativeHolderRegistration } from './holder-registry.ts';
|
|
2
|
+
import { type PreNArtifactLocations } from './layout-fence.ts';
|
|
3
|
+
import type { NativeLockEvent } from './native-lock-observer.ts';
|
|
4
|
+
import { type NativePoolPaths } from './pool-paths.ts';
|
|
5
|
+
import { type StateLayoutFs } from './state-layout-io.ts';
|
|
6
|
+
import { type RelocationDataset, type StateLayoutStep } from './state-layout-move.ts';
|
|
7
|
+
import { type StateLayout } from './state-layout-paths.ts';
|
|
8
|
+
import { type StateLayoutRecord } from './state-layout-record.ts';
|
|
9
|
+
/**
|
|
10
|
+
* Whether a start may move OpenCode's dataset out of its config folder into
|
|
11
|
+
* the state folder. While it is false, a start the fence would let move keeps
|
|
12
|
+
* the legacy layout instead, and registration of a dataset that has not
|
|
13
|
+
* moved is best-effort, as in 2.4.x (see `unmovedRegistrationPolicy`).
|
|
14
|
+
* Everything else the decision does still runs: serving a dataset already
|
|
15
|
+
* moved, recovering an unfinished move or restore, the conflict and refusal
|
|
16
|
+
* checks, and the mandatory state-home registration. The release that ships
|
|
17
|
+
* the move turns it on.
|
|
18
|
+
*/
|
|
19
|
+
export declare const RELOCATION_MOVE_ENABLED = false;
|
|
20
|
+
/** Turn the move on or off for this process, or back to the default; tests only. */
|
|
21
|
+
export declare function setRelocationMoveEnabledForTests(enabled: boolean | undefined): void;
|
|
22
|
+
/**
|
|
23
|
+
* How a dataset that has not moved (OpenCode's legacy or explicit dataset,
|
|
24
|
+
* or Pi's) is registered, from the same switch as the move itself.
|
|
25
|
+
*
|
|
26
|
+
* While moving is off this code ships in ordinary releases before the move
|
|
27
|
+
* release, and must serve every install that 2.4.x serves. So, as in 2.4.x,
|
|
28
|
+
* a failed registration is logged once at WARN and retried in the
|
|
29
|
+
* background (`undefined`, the ordinary policy of `registerNativePoolHolder`).
|
|
30
|
+
* Once moving is on, an unregistered process could have its files moved from
|
|
31
|
+
* under it, so registration is `mandatory`. Either way a state-home
|
|
32
|
+
* admission must register, and a relocation tombstone or an override into a
|
|
33
|
+
* managed folder refuses.
|
|
34
|
+
*/
|
|
35
|
+
export declare function unmovedRegistrationPolicy(options?: {
|
|
36
|
+
moveEnabled?: boolean;
|
|
37
|
+
}): 'mandatory' | undefined;
|
|
38
|
+
/** Why a start the fence would let move stays legacy while moving is off. */
|
|
39
|
+
export declare const RELOCATION_MOVE_DISABLED_REASON = "moving state out of the config folder is not enabled in this release";
|
|
40
|
+
export interface DecideStateLayoutOptions {
|
|
41
|
+
role: HolderRole;
|
|
42
|
+
env?: NodeJS.ProcessEnv;
|
|
43
|
+
home?: string;
|
|
44
|
+
/** Filesystem seam; `node:fs` for this platform otherwise. */
|
|
45
|
+
fs?: StateLayoutFs;
|
|
46
|
+
liveness?: HolderLiveness;
|
|
47
|
+
/** Where pid artifacts of older versions are looked for. */
|
|
48
|
+
artifacts?: PreNArtifactLocations;
|
|
49
|
+
onLockEvent?: (event: NativeLockEvent) => void;
|
|
50
|
+
/** Runs at each named step of a move, rollback or cleanup; tests inject faults here. */
|
|
51
|
+
onStep?: (step: StateLayoutStep) => void | Promise<void>;
|
|
52
|
+
now?: () => number;
|
|
53
|
+
/** Tests shorten the waits; production uses the defaults. */
|
|
54
|
+
timeouts?: {
|
|
55
|
+
layoutMs?: number;
|
|
56
|
+
holderMs?: number;
|
|
57
|
+
dataMs?: number;
|
|
58
|
+
/** Between background retries of a failed registration. */
|
|
59
|
+
retryMs?: number;
|
|
60
|
+
};
|
|
61
|
+
/** Overrides `RELOCATION_MOVE_ENABLED`; tests only. */
|
|
62
|
+
moveEnabled?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/** The layout a process uses for OpenCode's dataset, decided once at start. */
|
|
65
|
+
export type StateLayoutDecision = {
|
|
66
|
+
readonly kind: 'explicit';
|
|
67
|
+
readonly layout: StateLayout;
|
|
68
|
+
readonly registration: NativeHolderRegistration;
|
|
69
|
+
} | {
|
|
70
|
+
readonly kind: 'legacy';
|
|
71
|
+
readonly layout: StateLayout;
|
|
72
|
+
/** Why the dataset was not moved, when a move was considered. */
|
|
73
|
+
readonly reasons: readonly string[];
|
|
74
|
+
readonly registration?: NativeHolderRegistration;
|
|
75
|
+
} | {
|
|
76
|
+
readonly kind: 'state-home';
|
|
77
|
+
readonly layout: StateLayout;
|
|
78
|
+
readonly dataset: RelocationDataset;
|
|
79
|
+
readonly record: StateLayoutRecord;
|
|
80
|
+
/** True when this process made the move. */
|
|
81
|
+
readonly moved: boolean;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Decide, once at start and before any pool path is resolved, where this
|
|
85
|
+
* process keeps OpenCode's dataset, moving it into the state
|
|
86
|
+
* folder when nothing can still be using the old files. The
|
|
87
|
+
* result is recorded for the process, so every later resolution of the
|
|
88
|
+
* dataset uses it; it never changes.
|
|
89
|
+
*
|
|
90
|
+
* Refuses with `StateLayoutRefusalError` or `HolderRegistrationError` when
|
|
91
|
+
* neither copy may be served safely; the process must then do no pool I/O
|
|
92
|
+
* for the dataset. `RelocatedError` is never thrown here: a dataset moved by
|
|
93
|
+
* a newer version of the same layout rules is served from its new folder.
|
|
94
|
+
*/
|
|
95
|
+
export declare function decideOpenCodeStateLayout(options: DecideStateLayoutOptions): Promise<StateLayoutDecision>;
|
|
96
|
+
/**
|
|
97
|
+
* `decideOpenCodeStateLayout`, once per process for each dataset: every
|
|
98
|
+
* later call with the same config folder, state root and overrides gets the
|
|
99
|
+
* same decision. A refusal is not kept, so a later start in the same
|
|
100
|
+
* process (for example after "start again in a minute") decides afresh.
|
|
101
|
+
*/
|
|
102
|
+
export declare function decideOpenCodeStateLayoutOnce(options: DecideStateLayoutOptions): Promise<StateLayoutDecision>;
|
|
103
|
+
/** Forget the decisions this process made; tests only. */
|
|
104
|
+
export declare function resetStateLayoutDecisionsForTests(): void;
|
|
105
|
+
/**
|
|
106
|
+
* Pi's part of the decision. Pi never moves: OpenCode doesn't watch its
|
|
107
|
+
* folder, so its dataset stays in the legacy layout. It still registers
|
|
108
|
+
* beside its files before any pool I/O, by `unmovedRegistrationPolicy`, so a
|
|
109
|
+
* move of a dataset it aliases waits for it and an alias into a moved or
|
|
110
|
+
* managed folder refuses.
|
|
111
|
+
*/
|
|
112
|
+
export declare function decidePiStateLayout(options: {
|
|
113
|
+
paths: NativePoolPaths;
|
|
114
|
+
role: HolderRole;
|
|
115
|
+
onLockEvent?: (event: NativeLockEvent) => void;
|
|
116
|
+
liveness?: HolderLiveness;
|
|
117
|
+
lockTimeoutMs?: number;
|
|
118
|
+
/** Between background retries of a failed registration; tests only. */
|
|
119
|
+
retryIntervalMs?: number;
|
|
120
|
+
/** Overrides `RELOCATION_MOVE_ENABLED`; tests only. */
|
|
121
|
+
moveEnabled?: boolean;
|
|
122
|
+
}): Promise<{
|
|
123
|
+
readonly kind: 'pi';
|
|
124
|
+
readonly layout: StateLayout;
|
|
125
|
+
readonly registration: NativeHolderRegistration;
|
|
126
|
+
}>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a process refuses to use a dataset while deciding or serving the
|
|
3
|
+
* state-home layout. Each code has one fixed message; where a person must act
|
|
4
|
+
* on a file, the message names it.
|
|
5
|
+
*/
|
|
6
|
+
export type StateLayoutRefusalCode =
|
|
7
|
+
/** An old copy of the credentials or journal is back beside a moved copy. */
|
|
8
|
+
'conflict'
|
|
9
|
+
/** The dataset was moved under another state root (`XDG_STATE_HOME`). */
|
|
10
|
+
| 'moved-elsewhere'
|
|
11
|
+
/** A marker or tombstone belongs to a different config folder or dataset. */
|
|
12
|
+
| 'other-dataset'
|
|
13
|
+
/** `layout.json` exists but can't be read or parsed, or is missing after a move. */
|
|
14
|
+
| 'marker-unreadable'
|
|
15
|
+
/** A restore to the legacy layout stopped part way. */
|
|
16
|
+
| 'restore-unfinished'
|
|
17
|
+
/** Another process holds the layout for longer than this process waits. */
|
|
18
|
+
| 'busy'
|
|
19
|
+
/** The move could not be made durable or undone; serving either copy is unsafe. */
|
|
20
|
+
| 'unsafe'
|
|
21
|
+
/** The state folder holds data this process can't prove it wrote. */
|
|
22
|
+
| 'unexpected-data'
|
|
23
|
+
/** A restore to the legacy layout can't start; the message names what to fix. */
|
|
24
|
+
| 'restore-refused'
|
|
25
|
+
/**
|
|
26
|
+
* A folder or file that decides where the state is (the config folder, the
|
|
27
|
+
* state folder, or a lock beside them) could not be read or written.
|
|
28
|
+
*/
|
|
29
|
+
| 'io-failed';
|
|
30
|
+
/** The command that restores the legacy layout, as users run it. */
|
|
31
|
+
export declare const RESTORE_LEGACY_COMMAND = "npx @cortexkit/opencode-anthropic-auth setup --restore-legacy";
|
|
32
|
+
/** Every refusal code, for readers that check a code they were handed. */
|
|
33
|
+
export declare const STATE_LAYOUT_REFUSAL_CODES: readonly StateLayoutRefusalCode[];
|
|
34
|
+
/** Whether `value` is one of `STATE_LAYOUT_REFUSAL_CODES`. */
|
|
35
|
+
export declare function isStateLayoutRefusalCode(value: unknown): value is StateLayoutRefusalCode;
|
|
36
|
+
/** Every fixed message `stateLayoutRefusalPublicMessage` can return. */
|
|
37
|
+
export declare const STATE_LAYOUT_REFUSAL_PUBLIC_MESSAGES: readonly string[];
|
|
38
|
+
/** The fixed message, naming no file, for a refusal with `code`. */
|
|
39
|
+
export declare function stateLayoutRefusalPublicMessage(code: StateLayoutRefusalCode): string;
|
|
40
|
+
/**
|
|
41
|
+
* A refusal to serve a dataset from either layout. The process must not do
|
|
42
|
+
* any pool I/O for the dataset after it.
|
|
43
|
+
*/
|
|
44
|
+
export declare class StateLayoutRefusalError extends Error {
|
|
45
|
+
readonly code: StateLayoutRefusalCode;
|
|
46
|
+
/** The files the message names, for logs and diagnostics. */
|
|
47
|
+
readonly paths: readonly string[];
|
|
48
|
+
constructor(code: StateLayoutRefusalCode, message: string, paths?: readonly string[], cause?: unknown);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Two copies of the local credentials (or of the migration journal) exist:
|
|
52
|
+
* the moved one in the state folder and an old one in the config folder that
|
|
53
|
+
* changed or reappeared after the move was verified. Using either could spend
|
|
54
|
+
* a single-use refresh token twice, so every process stops serving the
|
|
55
|
+
* dataset until a person keeps one copy.
|
|
56
|
+
*/
|
|
57
|
+
export declare function stateLayoutConflict(oldPath: string, newPath: string, cause?: unknown): StateLayoutRefusalError;
|
|
58
|
+
/**
|
|
59
|
+
* A file system failure while deciding or changing where the state is, such
|
|
60
|
+
* as a lock file that can't be created or a folder that can't be resolved.
|
|
61
|
+
* `what` says what was being done, for the log; the public text is fixed.
|
|
62
|
+
*/
|
|
63
|
+
export declare function stateLayoutIoFailure(what: string, paths: readonly string[], cause: unknown): StateLayoutRefusalError;
|
|
64
|
+
/** The dataset was moved to a state root other than the one this process uses. */
|
|
65
|
+
export declare function stateLayoutMovedElsewhere(configDir: string, relocatedTo: string, stateRoot: string): StateLayoutRefusalError;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type NativeLockEvent } from './native-lock-observer.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The filesystem calls a layout transition makes, so tests can fail one, drop
|
|
4
|
+
* one, or record their order. Production uses `node:fs`.
|
|
5
|
+
*/
|
|
6
|
+
export interface StateLayoutFs {
|
|
7
|
+
readonly platform: NodeJS.Platform;
|
|
8
|
+
rename(from: string, to: string): Promise<void>;
|
|
9
|
+
unlink(path: string): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Make a directory's entries durable. On Windows Node can't flush a
|
|
12
|
+
* directory, so this does nothing there: renames rely on NTFS applying
|
|
13
|
+
* metadata changes in the order they were made.
|
|
14
|
+
*/
|
|
15
|
+
syncDirectory(dir: string): Promise<void>;
|
|
16
|
+
/** Flush a file's bytes to disk. Defaults to `syncFile`. */
|
|
17
|
+
syncFile?: (path: string) => Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Create a file owner-only and exclusively with `bytes`, flushed. Defaults
|
|
20
|
+
* to `writeOwnerOnlyExclusive` for `platform`.
|
|
21
|
+
*/
|
|
22
|
+
createOwnerOnly?: (path: string, bytes: Uint8Array) => Promise<void>;
|
|
23
|
+
/** Retry pacing for Windows sharing violations. */
|
|
24
|
+
now?: () => number;
|
|
25
|
+
sleep?: (ms: number) => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
/** Create a file owner-only through `fs`. */
|
|
28
|
+
export declare function createOwnerOnlyFile(fs: StateLayoutFs, path: string, bytes: Uint8Array): Promise<void>;
|
|
29
|
+
/** Flush a file's bytes to disk. */
|
|
30
|
+
export declare function syncFile(path: string): Promise<void>;
|
|
31
|
+
export declare function defaultStateLayoutFs(platform?: NodeJS.Platform): StateLayoutFs;
|
|
32
|
+
/** What a path holds, by content hash. */
|
|
33
|
+
export type FileDigest = {
|
|
34
|
+
readonly status: 'absent';
|
|
35
|
+
} | {
|
|
36
|
+
readonly status: 'file';
|
|
37
|
+
readonly sha256: string;
|
|
38
|
+
};
|
|
39
|
+
export declare function sha256Hex(bytes: Uint8Array | string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Hash a regular file without following a symlink at the path. Anything other
|
|
42
|
+
* than an absent path or a regular file is refused: the mover only ever
|
|
43
|
+
* handles files it can prove are plain data files.
|
|
44
|
+
*/
|
|
45
|
+
export declare function digestFile(path: string): Promise<FileDigest>;
|
|
46
|
+
/** The sha256 of a regular file, or null when the path is absent. */
|
|
47
|
+
export declare function fileSha256(path: string): Promise<string | null>;
|
|
48
|
+
/**
|
|
49
|
+
* Create `path` owner-only and exclusively, write `bytes`, and flush them.
|
|
50
|
+
* On POSIX the file is created `0600`; on Windows the native helper sets a
|
|
51
|
+
* protected owner-only DACL before any byte is written. The file never exists
|
|
52
|
+
* with wider permissions, so no later `chmod` is needed.
|
|
53
|
+
*/
|
|
54
|
+
export declare function writeOwnerOnlyExclusive(path: string, bytes: Uint8Array, platform: NodeJS.Platform): Promise<void>;
|
|
55
|
+
/** A target changed between the check before a destructive step and the step. */
|
|
56
|
+
export declare class StateLayoutOwnershipError extends Error {
|
|
57
|
+
readonly path: string;
|
|
58
|
+
constructor(path: string, detail: string);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* One destructive step of a layout transition: a rename onto a final name or
|
|
62
|
+
* an unlink. Before the first attempt every lease is asserted and `expect`
|
|
63
|
+
* checks that the slot still holds what the phase recorded; a mismatch throws
|
|
64
|
+
* `StateLayoutOwnershipError` and nothing is changed. On Windows a sharing
|
|
65
|
+
* violation is retried for up to 500 ms, and each retry asserts the leases and
|
|
66
|
+
* repeats `expect` first; a mismatch there returns `skipped` without retrying.
|
|
67
|
+
*/
|
|
68
|
+
export declare function publishOwned(step: {
|
|
69
|
+
readonly kind: 'rename';
|
|
70
|
+
readonly from: string;
|
|
71
|
+
readonly to: string;
|
|
72
|
+
} | {
|
|
73
|
+
readonly kind: 'unlink';
|
|
74
|
+
readonly path: string;
|
|
75
|
+
}, guard: {
|
|
76
|
+
assertLeases(): Promise<void>;
|
|
77
|
+
/** Resolves to a reason when the slot no longer holds what is expected. */
|
|
78
|
+
expect(): Promise<string | undefined>;
|
|
79
|
+
}, fs: StateLayoutFs): Promise<'published' | 'skipped'>;
|
|
80
|
+
/** A lock a layout transition takes: `<path>.<name>.lock`. */
|
|
81
|
+
export interface StateLayoutLockSpec {
|
|
82
|
+
readonly name: string;
|
|
83
|
+
readonly path: string;
|
|
84
|
+
readonly ttlMs?: number;
|
|
85
|
+
readonly timeoutMs?: number;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Locks taken in a fixed order and released in reverse. Each lock is a
|
|
89
|
+
* renewed common-auth file lease, the same kind every pool, runtime, roster
|
|
90
|
+
* and routing writer takes, so holding one excludes those writers in every
|
|
91
|
+
* process. Every acquisition re-asserts the leases already held, because a
|
|
92
|
+
* long wait is exactly when an earlier lease can be lost.
|
|
93
|
+
*/
|
|
94
|
+
export declare class StateLayoutLockStack {
|
|
95
|
+
private readonly held;
|
|
96
|
+
private readonly observe;
|
|
97
|
+
constructor(onLockEvent?: (event: NativeLockEvent) => void);
|
|
98
|
+
get size(): number;
|
|
99
|
+
/** The names of the held locks, oldest first; for tests and logs. */
|
|
100
|
+
heldLocks(): readonly {
|
|
101
|
+
name: string;
|
|
102
|
+
path: string;
|
|
103
|
+
}[];
|
|
104
|
+
acquire(spec: StateLayoutLockSpec): Promise<void>;
|
|
105
|
+
assertAll(): Promise<void>;
|
|
106
|
+
/** Release every lock taken after `mark` (an earlier `size`), newest first. */
|
|
107
|
+
releaseTo(mark: number): Promise<void>;
|
|
108
|
+
releaseAll(): Promise<void>;
|
|
109
|
+
}
|