@skrr-ai/cli 0.1.123 → 0.1.125
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/commands/agents/chat.d.ts +26 -0
- package/dist/commands/agents/chat.js +48 -10
- package/dist/commands/agents/delete.d.ts +8 -0
- package/dist/commands/agents/delete.js +16 -0
- package/dist/commands/context.js +7 -0
- package/dist/commands/search/workspace.js +1 -1
- package/dist/commands/store/gallery-admissions/approve.js +1 -1
- package/dist/commands/store/gallery-admissions/preview.js +1 -1
- package/dist/commands/store/gallery-admissions/refuse.js +1 -1
- package/dist/commands/store/gallery-admissions/show.js +1 -1
- package/dist/generated/agent-guides.json +19 -19
- package/dist/lib/agentic-stream.js +15 -1
- package/dist/lib/auth-core-init.d.ts +15 -2
- package/dist/lib/auth-core-init.js +47 -6
- package/dist/lib/command-hygiene.js +30 -1
- package/dist/lib/daemonBrokerRefusal.js +5 -1
- package/dist/lib/keychain.js +41 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +13 -28
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +133 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +387 -90
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +106 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +77 -19
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.d.ts +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.js +77 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.d.ts +68 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.js +264 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.js +90 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +7 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +102 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.js +6 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.d.ts +18 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.js +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +11 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.d.ts +43 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.js +33 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.d.ts +154 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.js +281 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +6 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +8 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/profileStateDir.js +7 -27
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +12 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.js +89 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +15 -37
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +133 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +536 -101
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +106 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +131 -27
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.d.ts +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.js +81 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.d.ts +68 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.js +317 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.js +86 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +7 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +13 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.d.ts +18 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.js +15 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +20 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.d.ts +43 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.js +104 -8
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.d.ts +154 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.js +386 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +6 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +11 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/profileStateDir.js +6 -35
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +14 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.js +73 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +1094 -1067
- package/oclif.manifest.json +16 -3
- package/package.json +4 -1
|
@@ -20,18 +20,48 @@ var masterKekService_exports = {};
|
|
|
20
20
|
__export(masterKekService_exports, {
|
|
21
21
|
MASTER_KEK_KEYCHAIN_ACCOUNT: () => MASTER_KEK_KEYCHAIN_ACCOUNT,
|
|
22
22
|
MASTER_KEK_KEYCHAIN_SERVICE: () => MASTER_KEK_KEYCHAIN_SERVICE,
|
|
23
|
-
|
|
23
|
+
classifyDaemonKeychainService: () => classifyDaemonKeychainService,
|
|
24
|
+
isMasterKekKeychainService: () => isMasterKekKeychainService,
|
|
25
|
+
isSessionKeychainService: () => isSessionKeychainService,
|
|
26
|
+
isUserOwnedSecretKeychainService: () => isUserOwnedSecretKeychainService
|
|
24
27
|
});
|
|
25
28
|
module.exports = __toCommonJS(masterKekService_exports);
|
|
26
29
|
const MASTER_KEK_KEYCHAIN_SERVICE = "ai.skrr.daemon.kek";
|
|
27
30
|
const MASTER_KEK_KEYCHAIN_ACCOUNT = "master-kek-v1";
|
|
28
|
-
const MASTER_KEK_SERVICE_PATTERN = /^ai\.(?:skrr|oversky)\.daemon\.kek(?:\..*)?$/;
|
|
31
|
+
const MASTER_KEK_SERVICE_PATTERN = /^(?:ai\.(?:skrr|oversky)|com\.oversky)\.daemon\.kek(?:\..*)?$/;
|
|
29
32
|
function isMasterKekKeychainService(service) {
|
|
30
33
|
return typeof service === "string" && MASTER_KEK_SERVICE_PATTERN.test(service);
|
|
31
34
|
}
|
|
35
|
+
const USER_OWNED_SECRET_SERVICE_PATTERN = /^ai\.(?:skrr|oversky)\.daemon\.byok(?:\..*)?$/;
|
|
36
|
+
function isUserOwnedSecretKeychainService(service) {
|
|
37
|
+
return typeof service === "string" && USER_OWNED_SECRET_SERVICE_PATTERN.test(service);
|
|
38
|
+
}
|
|
39
|
+
const PROFILE_NAME = "[A-Za-z0-9][A-Za-z0-9._-]{0,63}";
|
|
40
|
+
const CONFIG_DIR_HASH = "-[a-f0-9]{8}";
|
|
41
|
+
const SESSION_SERVICE_PATTERNS = [
|
|
42
|
+
new RegExp(`^ai\\.skrr\\.daemon\\.${PROFILE_NAME}${CONFIG_DIR_HASH}$`),
|
|
43
|
+
new RegExp(`^ai\\.oversky\\.daemon(?:\\.${PROFILE_NAME}(?:${CONFIG_DIR_HASH})?)?$`)
|
|
44
|
+
];
|
|
45
|
+
function classifyDaemonKeychainService(service) {
|
|
46
|
+
if (typeof service !== "string")
|
|
47
|
+
return "unclassified";
|
|
48
|
+
if (isMasterKekKeychainService(service))
|
|
49
|
+
return "master_kek";
|
|
50
|
+
if (isUserOwnedSecretKeychainService(service))
|
|
51
|
+
return "user_secret";
|
|
52
|
+
if (SESSION_SERVICE_PATTERNS.some((pattern) => pattern.test(service)))
|
|
53
|
+
return "session";
|
|
54
|
+
return "unclassified";
|
|
55
|
+
}
|
|
56
|
+
function isSessionKeychainService(service) {
|
|
57
|
+
return classifyDaemonKeychainService(service) === "session";
|
|
58
|
+
}
|
|
32
59
|
// Annotate the CommonJS export names for ESM import in node:
|
|
33
60
|
0 && (module.exports = {
|
|
34
61
|
MASTER_KEK_KEYCHAIN_ACCOUNT,
|
|
35
62
|
MASTER_KEK_KEYCHAIN_SERVICE,
|
|
36
|
-
|
|
63
|
+
classifyDaemonKeychainService,
|
|
64
|
+
isMasterKekKeychainService,
|
|
65
|
+
isSessionKeychainService,
|
|
66
|
+
isUserOwnedSecretKeychainService
|
|
37
67
|
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { CONFIG_DIR_NAME } from './localIdentity.js';
|
|
2
|
+
/** Directory name of the state root inside a home directory. */
|
|
3
|
+
export { CONFIG_DIR_NAME };
|
|
4
|
+
/** Opt-in for a deliberate check against the machine's real state. */
|
|
5
|
+
export declare const ALLOW_LIVE_STATE_ENV = "SKRR_TEST_ALLOW_LIVE_STATE";
|
|
6
|
+
/** Set by every hermetic runner to the throwaway home it gave the process. */
|
|
7
|
+
export declare const HERMETIC_HOME_ENV = "SKRR_TEST_HERMETIC_HOME";
|
|
8
|
+
/** `LiveStateRefusedError.code`. Kept at its pre-consolidation value: scripts match on it. */
|
|
9
|
+
export declare const LIVE_STATE_REFUSED_CODE = "SKRR_TEST_LIVE_STATE";
|
|
10
|
+
/**
|
|
11
|
+
* Which signal says this is a test process, or `null`. The text names the signal so a false
|
|
12
|
+
* positive (a shell that exports `NODE_ENV=test`) diagnoses itself.
|
|
13
|
+
*
|
|
14
|
+
* `NODE_TEST_CONTEXT` is set by `node --test` in every file's child process and inherited by
|
|
15
|
+
* anything a test spawns. `--test` in `execArgv` covers `--test-isolation=none`, where tests run
|
|
16
|
+
* in the runner itself. A test file run DIRECTLY (`node file.test.ts`) has neither: node:test
|
|
17
|
+
* sets no marker for it, so the entry script's name is the only evidence. `JEST_WORKER_ID` is
|
|
18
|
+
* set in the real environment of every jest worker (and by `--runInBand`), so a child a spec
|
|
19
|
+
* spawns without `env` still carries it. `NODE_ENV=test` is what jest, vitest and `bun test`
|
|
20
|
+
* default to, and what custom scripts set by hand.
|
|
21
|
+
*
|
|
22
|
+
* `argv` is the process's own only when `env` is: a caller describing a hypothetical process
|
|
23
|
+
* (`{}`) describes all of it.
|
|
24
|
+
*/
|
|
25
|
+
export declare function testProcessSignal(env?: NodeJS.ProcessEnv, execArgv?: readonly string[], argv?: readonly string[]): string | null;
|
|
26
|
+
/** Is this a test process? See `testProcessSignal` for what counts. */
|
|
27
|
+
export declare function isTestProcess(env?: NodeJS.ProcessEnv, execArgv?: readonly string[], argv?: readonly string[]): boolean;
|
|
28
|
+
/** Should a test process be refused access to the machine's real state? */
|
|
29
|
+
export declare function refusesLiveStateInTest(env?: NodeJS.ProcessEnv, execArgv?: readonly string[], argv?: readonly string[]): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* The account's home directory from the user database. A test that points `HOME` somewhere else
|
|
32
|
+
* cannot move it, which is the point: it names where the INSTALLED product keeps its state,
|
|
33
|
+
* whatever this process believes.
|
|
34
|
+
*
|
|
35
|
+
* Node's `os.userInfo().homedir` reads the user database. Bun's returns `$HOME`, which is
|
|
36
|
+
* exactly what a test moves, so the compiled daemon asks the database itself — without that, a
|
|
37
|
+
* compiled binary spawned by a test with its own `HOME` saw that home as the live one and
|
|
38
|
+
* refused to start. `null` when it cannot be determined.
|
|
39
|
+
*/
|
|
40
|
+
export declare function accountHomeDir(): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* @internal Test seam — make `accountHomeDir()` answer `home` (`null`: "cannot be determined"),
|
|
43
|
+
* or restore the user-database lookup with `undefined`. It lets a spec stand a THROWAWAY
|
|
44
|
+
* directory in for the account's real home, so the guard can be exercised end to end — and its
|
|
45
|
+
* refusal removed, to prove the spec notices — without any path ever touching the real one.
|
|
46
|
+
* Never called from production code; `stateFileBoundary.spec.ts` fails if it is.
|
|
47
|
+
*/
|
|
48
|
+
export declare function __setAccountHomeForTest(home: string | null | undefined): void;
|
|
49
|
+
/** The state root of the product installed for this account, when it can be known. */
|
|
50
|
+
export declare function liveStateRoot(home?: string | null): string | null;
|
|
51
|
+
/**
|
|
52
|
+
* What a guard decision is made against. Everything defaults to the running process; a spec
|
|
53
|
+
* injects a throwaway directory as `realHome` to prove the guard fires WITHOUT going near the
|
|
54
|
+
* machine's actual home.
|
|
55
|
+
*/
|
|
56
|
+
export interface LiveStateProbe {
|
|
57
|
+
env?: NodeJS.ProcessEnv;
|
|
58
|
+
execArgv?: readonly string[];
|
|
59
|
+
argv?: readonly string[];
|
|
60
|
+
/** The account's real home. `null` means "cannot be determined". Default: the user database. */
|
|
61
|
+
realHome?: string | null;
|
|
62
|
+
}
|
|
63
|
+
export declare class LiveStateRefusedError extends Error {
|
|
64
|
+
readonly code = "SKRR_TEST_LIVE_STATE";
|
|
65
|
+
/** What the mutation was aimed at: a path, or a Keychain service. */
|
|
66
|
+
readonly target: string;
|
|
67
|
+
/** Why this process was taken for a test. */
|
|
68
|
+
readonly signal: string;
|
|
69
|
+
constructor(what: string, target: string, signal: string, why: string, hint?: string);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Is `err` a live-state refusal? By name and code as well as by class: auth-core ships as both
|
|
73
|
+
* ESM and CJS, and two copies of it in one process (a farmed worktree) have two classes.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isLiveStateRefusal(err: unknown): err is LiveStateRefusedError;
|
|
76
|
+
/**
|
|
77
|
+
* First line of any `catch` that wraps a guarded mutation. A refusal swallowed into a telemetry
|
|
78
|
+
* event is a write that silently did not happen under a test that reports green, which is the
|
|
79
|
+
* "everything reads healthy" shape this repo has paid for before. A no-op outside a test
|
|
80
|
+
* process, where no refusal is ever constructed.
|
|
81
|
+
*/
|
|
82
|
+
export declare function rethrowIfLiveStateRefusal(err: unknown): void;
|
|
83
|
+
/**
|
|
84
|
+
* Throw `LiveStateRefusedError` when a test process is about to mutate `target`, a path under
|
|
85
|
+
* the account's real home. Returns without reading anything else outside a test process.
|
|
86
|
+
*
|
|
87
|
+
* `what` completes "Refusing to …" (`replace the wrapped DEK`). `hint` replaces the default
|
|
88
|
+
* advice about how to run the test safely.
|
|
89
|
+
*/
|
|
90
|
+
export declare function assertMayMutateLiveState(target: string, what: string, probe?: LiveStateProbe, hint?: string): void;
|
|
91
|
+
/** Service name prefix of the daemon's credential slots; always followed by `.<profile>`. */
|
|
92
|
+
export declare const DAEMON_KEYCHAIN_SERVICE_BASE = "ai.skrr.daemon";
|
|
93
|
+
/**
|
|
94
|
+
* Separate Keychain service for local BYOK secrets.
|
|
95
|
+
*
|
|
96
|
+
* Keeping provider keys out of the daemon-auth service prevents a broad "export daemon
|
|
97
|
+
* credentials" operation from accidentally including them and lets the user remove a provider
|
|
98
|
+
* key without disturbing their login.
|
|
99
|
+
*/
|
|
100
|
+
export declare const DAEMON_BYOK_KEYCHAIN_SERVICE_BASE = "ai.skrr.daemon.byok";
|
|
101
|
+
/**
|
|
102
|
+
* The PRE-RENAME, pre-Phase-K service name. Read ONLY by the daemon's migration helper.
|
|
103
|
+
*
|
|
104
|
+
* It must NOT equal `DAEMON_KEYCHAIN_SERVICE_BASE`, and it held that value until a fix: the
|
|
105
|
+
* skrr rename swept the string like any other, and the two became identical. Nothing failed,
|
|
106
|
+
* because the failure is an absence — `migrateLegacyServiceToProfile` read a service nothing had
|
|
107
|
+
* ever written, found no slots, and reported success. Pre-rename credentials stayed where they
|
|
108
|
+
* were and the user silently re-authenticated. Written as a literal on purpose.
|
|
109
|
+
*/
|
|
110
|
+
export declare const LEGACY_DAEMON_KEYCHAIN_SERVICE = "ai.oversky.daemon";
|
|
111
|
+
/** The profile the installed daemon runs when nothing selects another. */
|
|
112
|
+
export declare const LIVE_DEFAULT_PROFILE = "default";
|
|
113
|
+
/**
|
|
114
|
+
* `-<sha256(dir)[0:8]>`, the suffix of every per-profile Keychain service name. The input is the
|
|
115
|
+
* absolute, trailing-slash-stripped config directory, so two OS users running the daemon under
|
|
116
|
+
* their own `~/.skrr/` get distinct names and cannot read each other's tokens.
|
|
117
|
+
*/
|
|
118
|
+
export declare function keychainSuffixForDir(dir: string): string;
|
|
119
|
+
/**
|
|
120
|
+
* Keychain services that belong to the product installed on this machine: the master KEK
|
|
121
|
+
* (current brand and the ones before it), the default profile's credential and BYOK slots under
|
|
122
|
+
* the account's real home, and the legacy names the daemon migrates from on start — a write to
|
|
123
|
+
* one of those is adopted by the next restart instead of the next read.
|
|
124
|
+
*
|
|
125
|
+
* The per-profile names are derived from the ACCOUNT's home, which no `HOME` override can reach.
|
|
126
|
+
* When the home is unknown only the names that need no home are returned; see
|
|
127
|
+
* `isLiveKeychainService` for what is done about the rest.
|
|
128
|
+
*/
|
|
129
|
+
export declare function liveKeychainServices(probe?: Pick<LiveStateProbe, 'realHome'>): ReadonlySet<string>;
|
|
130
|
+
/**
|
|
131
|
+
* Does `service` name an item of the installed product? When the real home is unknown, a
|
|
132
|
+
* default-profile slot with a hash suffix is assumed live: its hash cannot be checked, and the
|
|
133
|
+
* guard fails closed.
|
|
134
|
+
*/
|
|
135
|
+
export declare function isLiveKeychainService(service: string, probe?: Pick<LiveStateProbe, 'realHome'>): boolean;
|
|
136
|
+
/** Would a mutation of Keychain `service` be refused in a process with this shape? */
|
|
137
|
+
export declare function refusesLiveKeychainMutation(service: string, probe?: LiveStateProbe): boolean;
|
|
138
|
+
/** What a Keychain guard decision is made against. */
|
|
139
|
+
export interface LiveKeychainProbe extends LiveStateProbe {
|
|
140
|
+
/**
|
|
141
|
+
* The keychain FILE the write is aimed at, when the caller knows it (macOS passes the login
|
|
142
|
+
* keychain it resolved). A keychain outside the real home is not the account's, so nothing
|
|
143
|
+
* the installed product reads can be in it, whatever the service is called: a throwaway
|
|
144
|
+
* keychain file, or a fake `security` that answers with a path of its own. Without it, the
|
|
145
|
+
* service name alone decides.
|
|
146
|
+
*/
|
|
147
|
+
keychainFile?: string;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Throw `LiveStateRefusedError` when a test process is about to write or delete an item in a
|
|
151
|
+
* Keychain service the installed product reads. Callers that can be handed a fake `security`
|
|
152
|
+
* binary call this only when the real one would run: a mocked write touches nothing.
|
|
153
|
+
*/
|
|
154
|
+
export declare function assertMayMutateLiveKeychain(service: string, what: string, probe?: LiveKeychainProbe, hint?: string): void;
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var liveState_exports = {};
|
|
30
|
+
__export(liveState_exports, {
|
|
31
|
+
ALLOW_LIVE_STATE_ENV: () => ALLOW_LIVE_STATE_ENV,
|
|
32
|
+
CONFIG_DIR_NAME: () => import_localIdentity.CONFIG_DIR_NAME,
|
|
33
|
+
DAEMON_BYOK_KEYCHAIN_SERVICE_BASE: () => DAEMON_BYOK_KEYCHAIN_SERVICE_BASE,
|
|
34
|
+
DAEMON_KEYCHAIN_SERVICE_BASE: () => DAEMON_KEYCHAIN_SERVICE_BASE,
|
|
35
|
+
HERMETIC_HOME_ENV: () => HERMETIC_HOME_ENV,
|
|
36
|
+
LEGACY_DAEMON_KEYCHAIN_SERVICE: () => LEGACY_DAEMON_KEYCHAIN_SERVICE,
|
|
37
|
+
LIVE_DEFAULT_PROFILE: () => LIVE_DEFAULT_PROFILE,
|
|
38
|
+
LIVE_STATE_REFUSED_CODE: () => LIVE_STATE_REFUSED_CODE,
|
|
39
|
+
LiveStateRefusedError: () => LiveStateRefusedError,
|
|
40
|
+
__setAccountHomeForTest: () => __setAccountHomeForTest,
|
|
41
|
+
accountHomeDir: () => accountHomeDir,
|
|
42
|
+
assertMayMutateLiveKeychain: () => assertMayMutateLiveKeychain,
|
|
43
|
+
assertMayMutateLiveState: () => assertMayMutateLiveState,
|
|
44
|
+
isLiveKeychainService: () => isLiveKeychainService,
|
|
45
|
+
isLiveStateRefusal: () => isLiveStateRefusal,
|
|
46
|
+
isTestProcess: () => isTestProcess,
|
|
47
|
+
keychainSuffixForDir: () => keychainSuffixForDir,
|
|
48
|
+
liveKeychainServices: () => liveKeychainServices,
|
|
49
|
+
liveStateRoot: () => liveStateRoot,
|
|
50
|
+
refusesLiveKeychainMutation: () => refusesLiveKeychainMutation,
|
|
51
|
+
refusesLiveStateInTest: () => refusesLiveStateInTest,
|
|
52
|
+
rethrowIfLiveStateRefusal: () => rethrowIfLiveStateRefusal,
|
|
53
|
+
testProcessSignal: () => testProcessSignal
|
|
54
|
+
});
|
|
55
|
+
module.exports = __toCommonJS(liveState_exports);
|
|
56
|
+
var import_node_child_process = require("node:child_process");
|
|
57
|
+
var import_node_crypto = require("node:crypto");
|
|
58
|
+
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
59
|
+
var import_node_os = __toESM(require("node:os"), 1);
|
|
60
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
61
|
+
var import_masterKekService = require("./kek/masterKekService.js");
|
|
62
|
+
var import_localIdentity = require("./localIdentity.js");
|
|
63
|
+
const ALLOW_LIVE_STATE_ENV = "SKRR_TEST_ALLOW_LIVE_STATE";
|
|
64
|
+
const HERMETIC_HOME_ENV = "SKRR_TEST_HERMETIC_HOME";
|
|
65
|
+
const LIVE_STATE_REFUSED_CODE = "SKRR_TEST_LIVE_STATE";
|
|
66
|
+
const TEST_FILE_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
67
|
+
function testProcessSignal(env = process.env, execArgv = process.execArgv, argv = env === process.env ? process.argv : []) {
|
|
68
|
+
if (env.NODE_TEST_CONTEXT)
|
|
69
|
+
return "NODE_TEST_CONTEXT is set (node --test)";
|
|
70
|
+
if (env.JEST_WORKER_ID)
|
|
71
|
+
return "JEST_WORKER_ID is set (jest)";
|
|
72
|
+
if (env.VITEST || env.VITEST_WORKER_ID)
|
|
73
|
+
return "VITEST is set (vitest)";
|
|
74
|
+
if (env[HERMETIC_HOME_ENV])
|
|
75
|
+
return `${HERMETIC_HOME_ENV} is set (a hermetic test runner)`;
|
|
76
|
+
if (env.NODE_ENV === "test")
|
|
77
|
+
return "NODE_ENV=test";
|
|
78
|
+
if (execArgv.includes("--test"))
|
|
79
|
+
return "node was started with --test";
|
|
80
|
+
const entry = argv[1];
|
|
81
|
+
if (typeof entry === "string" && TEST_FILE_RE.test(entry)) {
|
|
82
|
+
return `the entry script ${import_node_path.default.basename(entry)} is a test file`;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function isTestProcess(env = process.env, execArgv = process.execArgv, argv) {
|
|
87
|
+
return testProcessSignal(env, execArgv, argv) !== null;
|
|
88
|
+
}
|
|
89
|
+
function refusesLiveStateInTest(env = process.env, execArgv = process.execArgv, argv) {
|
|
90
|
+
return isTestProcess(env, execArgv, argv) && env[ALLOW_LIVE_STATE_ENV] !== "1";
|
|
91
|
+
}
|
|
92
|
+
let cachedAccountHome;
|
|
93
|
+
function userDatabaseHome() {
|
|
94
|
+
const probe = (command, args) => (0, import_node_child_process.execFileSync)(command, args, {
|
|
95
|
+
encoding: "utf-8",
|
|
96
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
97
|
+
timeout: 5e3
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
if (process.platform === "darwin") {
|
|
101
|
+
const out = probe("dscl", [
|
|
102
|
+
".",
|
|
103
|
+
"-read",
|
|
104
|
+
`/Users/${import_node_os.default.userInfo().username}`,
|
|
105
|
+
"NFSHomeDirectory"
|
|
106
|
+
]);
|
|
107
|
+
const home = out.replace(/^NFSHomeDirectory:\s*/, "").trim();
|
|
108
|
+
return home || null;
|
|
109
|
+
}
|
|
110
|
+
if (process.platform === "linux" && typeof process.getuid === "function") {
|
|
111
|
+
const home = probe("getent", ["passwd", String(process.getuid())]).trim().split(":")[5];
|
|
112
|
+
return home || null;
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function accountHomeDir() {
|
|
120
|
+
if (cachedAccountHome !== void 0)
|
|
121
|
+
return cachedAccountHome;
|
|
122
|
+
if (typeof process.versions.bun === "string") {
|
|
123
|
+
cachedAccountHome = userDatabaseHome();
|
|
124
|
+
} else {
|
|
125
|
+
try {
|
|
126
|
+
cachedAccountHome = import_node_os.default.userInfo().homedir || null;
|
|
127
|
+
} catch {
|
|
128
|
+
cachedAccountHome = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return cachedAccountHome;
|
|
132
|
+
}
|
|
133
|
+
function __setAccountHomeForTest(home) {
|
|
134
|
+
cachedAccountHome = home;
|
|
135
|
+
}
|
|
136
|
+
function liveStateRoot(home = accountHomeDir()) {
|
|
137
|
+
return home ? import_node_path.default.resolve(home, import_localIdentity.CONFIG_DIR_NAME) : null;
|
|
138
|
+
}
|
|
139
|
+
function probeHome(probe) {
|
|
140
|
+
const home = probe.realHome === void 0 ? accountHomeDir() : probe.realHome;
|
|
141
|
+
if (!home || import_node_path.default.parse(import_node_path.default.resolve(home)).root === import_node_path.default.resolve(home))
|
|
142
|
+
return null;
|
|
143
|
+
return home;
|
|
144
|
+
}
|
|
145
|
+
function canonicalise(target) {
|
|
146
|
+
const absolute = import_node_path.default.resolve(target);
|
|
147
|
+
let head = absolute;
|
|
148
|
+
const tail = [];
|
|
149
|
+
for (; ; ) {
|
|
150
|
+
try {
|
|
151
|
+
const real = import_node_fs.default.realpathSync.native(head);
|
|
152
|
+
return tail.length === 0 ? real : import_node_path.default.join(real, ...tail.reverse());
|
|
153
|
+
} catch {
|
|
154
|
+
const parent = import_node_path.default.dirname(head);
|
|
155
|
+
if (parent === head)
|
|
156
|
+
return absolute;
|
|
157
|
+
tail.push(import_node_path.default.basename(head));
|
|
158
|
+
head = parent;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function isWithin(child, parent) {
|
|
163
|
+
const rel = import_node_path.default.relative(parent, child);
|
|
164
|
+
return rel !== ".." && !rel.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(rel);
|
|
165
|
+
}
|
|
166
|
+
class LiveStateRefusedError extends Error {
|
|
167
|
+
code = LIVE_STATE_REFUSED_CODE;
|
|
168
|
+
/** What the mutation was aimed at: a path, or a Keychain service. */
|
|
169
|
+
target;
|
|
170
|
+
/** Why this process was taken for a test. */
|
|
171
|
+
signal;
|
|
172
|
+
constructor(what, target, signal, why, hint) {
|
|
173
|
+
super(`Refusing to ${what}: ${why}. This process looks like a test (${signal}), and the target belongs to the Skrr daemon, CLI and desktop app installed on this machine. ` + (hint ?? "Run the suite through its own test script (`npm test`, or `npm run test:file -- <path>` in daemon/), which gives every test process a home of its own, or point HOME at a throwaway directory for the run (HOME=$(mktemp -d) \u2026). ") + `Set ${ALLOW_LIVE_STATE_ENV}=1 only for a deliberate check against the real state.`);
|
|
174
|
+
this.name = "LiveStateRefusedError";
|
|
175
|
+
this.target = target;
|
|
176
|
+
this.signal = signal;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function isLiveStateRefusal(err) {
|
|
180
|
+
return err instanceof LiveStateRefusedError || err?.name === "LiveStateRefusedError" && err.code === LIVE_STATE_REFUSED_CODE;
|
|
181
|
+
}
|
|
182
|
+
function rethrowIfLiveStateRefusal(err) {
|
|
183
|
+
if (isLiveStateRefusal(err))
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
function pathRefusal(target, probe, env) {
|
|
187
|
+
const resolved = canonicalise(target);
|
|
188
|
+
const home = probeHome(probe);
|
|
189
|
+
if (home === null) {
|
|
190
|
+
const sandbox = env[HERMETIC_HOME_ENV];
|
|
191
|
+
if (sandbox && isWithin(resolved, canonicalise(sandbox)))
|
|
192
|
+
return null;
|
|
193
|
+
return `${target} cannot be shown to be outside the real home, because the account's home directory could not be determined`;
|
|
194
|
+
}
|
|
195
|
+
return isWithin(resolved, canonicalise(home)) ? `${target} is inside the real home ${home}` : null;
|
|
196
|
+
}
|
|
197
|
+
function assertMayMutateLiveState(target, what, probe = {}, hint) {
|
|
198
|
+
const env = probe.env ?? process.env;
|
|
199
|
+
const signal = testProcessSignal(env, probe.execArgv ?? process.execArgv, probe.argv);
|
|
200
|
+
if (signal === null || env[ALLOW_LIVE_STATE_ENV] === "1")
|
|
201
|
+
return;
|
|
202
|
+
const why = pathRefusal(target, probe, env);
|
|
203
|
+
if (why !== null)
|
|
204
|
+
throw new LiveStateRefusedError(what, target, signal, why, hint);
|
|
205
|
+
}
|
|
206
|
+
const DAEMON_KEYCHAIN_SERVICE_BASE = `${import_localIdentity.NATIVE_ID_PREFIX}.daemon`;
|
|
207
|
+
const DAEMON_BYOK_KEYCHAIN_SERVICE_BASE = `${import_localIdentity.NATIVE_ID_PREFIX}.daemon.byok`;
|
|
208
|
+
const LEGACY_DAEMON_KEYCHAIN_SERVICE = "ai.oversky.daemon";
|
|
209
|
+
const LIVE_DEFAULT_PROFILE = "default";
|
|
210
|
+
function keychainSuffixForDir(dir) {
|
|
211
|
+
const resolved = import_node_path.default.resolve(dir.replace(/\/+$/, ""));
|
|
212
|
+
return `-${(0, import_node_crypto.createHash)("sha256").update(resolved).digest("hex").toLowerCase().slice(0, 8)}`;
|
|
213
|
+
}
|
|
214
|
+
const UNVERIFIABLE_DEFAULT_SLOT_RE = new RegExp(`^(?:${DAEMON_KEYCHAIN_SERVICE_BASE.replace(/\./g, "\\.")}|${DAEMON_BYOK_KEYCHAIN_SERVICE_BASE.replace(/\./g, "\\.")})\\.${LIVE_DEFAULT_PROFILE}-[0-9a-f]{8}$`);
|
|
215
|
+
function liveKeychainServices(probe = {}) {
|
|
216
|
+
const services = /* @__PURE__ */ new Set([
|
|
217
|
+
import_masterKekService.MASTER_KEK_KEYCHAIN_SERVICE,
|
|
218
|
+
...import_localIdentity.LEGACY_NATIVE_ID_PREFIXES.map((prefix) => `${prefix}.daemon.kek`),
|
|
219
|
+
`${DAEMON_KEYCHAIN_SERVICE_BASE}.${LIVE_DEFAULT_PROFILE}`,
|
|
220
|
+
DAEMON_KEYCHAIN_SERVICE_BASE,
|
|
221
|
+
LEGACY_DAEMON_KEYCHAIN_SERVICE
|
|
222
|
+
]);
|
|
223
|
+
const root = liveStateRoot(probeHome(probe));
|
|
224
|
+
if (root) {
|
|
225
|
+
const suffix = keychainSuffixForDir(import_node_path.default.resolve(root, "profiles", LIVE_DEFAULT_PROFILE));
|
|
226
|
+
services.add(`${DAEMON_KEYCHAIN_SERVICE_BASE}.${LIVE_DEFAULT_PROFILE}${suffix}`);
|
|
227
|
+
services.add(`${DAEMON_BYOK_KEYCHAIN_SERVICE_BASE}.${LIVE_DEFAULT_PROFILE}${suffix}`);
|
|
228
|
+
}
|
|
229
|
+
return services;
|
|
230
|
+
}
|
|
231
|
+
function isLiveKeychainService(service, probe = {}) {
|
|
232
|
+
if (liveKeychainServices(probe).has(service))
|
|
233
|
+
return true;
|
|
234
|
+
return probeHome(probe) === null && UNVERIFIABLE_DEFAULT_SLOT_RE.test(service);
|
|
235
|
+
}
|
|
236
|
+
function refusesLiveKeychainMutation(service, probe = {}) {
|
|
237
|
+
const env = probe.env ?? process.env;
|
|
238
|
+
return refusesLiveStateInTest(env, probe.execArgv ?? process.execArgv, probe.argv) && isLiveKeychainService(service, probe);
|
|
239
|
+
}
|
|
240
|
+
function assertMayMutateLiveKeychain(service, what, probe = {}, hint) {
|
|
241
|
+
const env = probe.env ?? process.env;
|
|
242
|
+
const signal = testProcessSignal(env, probe.execArgv ?? process.execArgv, probe.argv);
|
|
243
|
+
if (signal === null || env[ALLOW_LIVE_STATE_ENV] === "1")
|
|
244
|
+
return;
|
|
245
|
+
if (!isLiveKeychainService(service, probe))
|
|
246
|
+
return;
|
|
247
|
+
let where = "";
|
|
248
|
+
if (probe.keychainFile !== void 0) {
|
|
249
|
+
const why = pathRefusal(probe.keychainFile, probe, env);
|
|
250
|
+
if (why === null)
|
|
251
|
+
return;
|
|
252
|
+
where = `, in a keychain that is not shown to be anyone else's (${why})`;
|
|
253
|
+
}
|
|
254
|
+
throw new LiveStateRefusedError(what, service, signal, `the Keychain service ${service} is one the installed product reads${where}`, hint);
|
|
255
|
+
}
|
|
256
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
257
|
+
0 && (module.exports = {
|
|
258
|
+
ALLOW_LIVE_STATE_ENV,
|
|
259
|
+
CONFIG_DIR_NAME,
|
|
260
|
+
DAEMON_BYOK_KEYCHAIN_SERVICE_BASE,
|
|
261
|
+
DAEMON_KEYCHAIN_SERVICE_BASE,
|
|
262
|
+
HERMETIC_HOME_ENV,
|
|
263
|
+
LEGACY_DAEMON_KEYCHAIN_SERVICE,
|
|
264
|
+
LIVE_DEFAULT_PROFILE,
|
|
265
|
+
LIVE_STATE_REFUSED_CODE,
|
|
266
|
+
LiveStateRefusedError,
|
|
267
|
+
__setAccountHomeForTest,
|
|
268
|
+
accountHomeDir,
|
|
269
|
+
assertMayMutateLiveKeychain,
|
|
270
|
+
assertMayMutateLiveState,
|
|
271
|
+
isLiveKeychainService,
|
|
272
|
+
isLiveStateRefusal,
|
|
273
|
+
isTestProcess,
|
|
274
|
+
keychainSuffixForDir,
|
|
275
|
+
liveKeychainServices,
|
|
276
|
+
liveStateRoot,
|
|
277
|
+
refusesLiveKeychainMutation,
|
|
278
|
+
refusesLiveStateInTest,
|
|
279
|
+
rethrowIfLiveStateRefusal,
|
|
280
|
+
testProcessSignal
|
|
281
|
+
});
|
|
@@ -37,7 +37,9 @@ var import_node_crypto = __toESM(require("node:crypto"), 1);
|
|
|
37
37
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
38
38
|
var import_node_os = __toESM(require("node:os"), 1);
|
|
39
39
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
40
|
+
var import_liveState = require("./liveState.js");
|
|
40
41
|
var import_runtime = require("./runtime.js");
|
|
42
|
+
var import_stateFiles = require("./stateFiles.js");
|
|
41
43
|
const DAEMON_ID_FILENAME = "daemon.id";
|
|
42
44
|
const PROFILES_DIRNAME = "profiles";
|
|
43
45
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -67,14 +69,14 @@ function isUuidLike(raw) {
|
|
|
67
69
|
}
|
|
68
70
|
function writeDaemonIdFile(target, id) {
|
|
69
71
|
const dir = import_node_path.default.dirname(target);
|
|
70
|
-
|
|
72
|
+
(0, import_stateFiles.ensureStateDir)(dir, "create the machine id directory");
|
|
71
73
|
const tmp = `${target}.tmp.${process.pid}.${Math.random().toString(36).slice(2, 10)}`;
|
|
72
|
-
|
|
74
|
+
(0, import_stateFiles.writeStateFile)(tmp, id + "\n", { encoding: "utf-8", mode: 384 }, "write the machine id");
|
|
73
75
|
try {
|
|
74
76
|
import_node_fs.default.chmodSync(tmp, 384);
|
|
75
77
|
} catch {
|
|
76
78
|
}
|
|
77
|
-
|
|
79
|
+
(0, import_stateFiles.renameStateFile)(tmp, target, "write the machine id");
|
|
78
80
|
}
|
|
79
81
|
function defaultMachineUuidFallback() {
|
|
80
82
|
const host = import_node_os.default.hostname() || "unknown-host";
|
|
@@ -118,6 +120,7 @@ function ensureMachineDaemonId(machineUuidFallback) {
|
|
|
118
120
|
cachedStableDaemonId = fresh;
|
|
119
121
|
return fresh;
|
|
120
122
|
} catch (err) {
|
|
123
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
121
124
|
log.warn(`[machineIdentity] persist ${target} failed: ${err.message} \u2014 using fallback`);
|
|
122
125
|
const fallback = `fallback-${fallbackFn()}`;
|
|
123
126
|
cachedStableDaemonId = fallback;
|
|
@@ -47,6 +47,8 @@ var import_node_os = __toESM(require("node:os"), 1);
|
|
|
47
47
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
48
48
|
var import_node_child_process = require("node:child_process");
|
|
49
49
|
var import_configRoot = require("./configRoot.js");
|
|
50
|
+
var import_liveState = require("./liveState.js");
|
|
51
|
+
var import_stateFiles = require("./stateFiles.js");
|
|
50
52
|
const HOSTED_RUNTIME_MACHINE_SOURCES = /* @__PURE__ */ new Set([
|
|
51
53
|
"hosted-machine",
|
|
52
54
|
"cloud-coding",
|
|
@@ -152,7 +154,7 @@ function readPersistedChoice(file, hardware, profile, requireProfile = false) {
|
|
|
152
154
|
}
|
|
153
155
|
function persistChoice(dir, file, hardware, machineUuid, reason, profile, evidence) {
|
|
154
156
|
try {
|
|
155
|
-
|
|
157
|
+
(0, import_stateFiles.ensureStateDir)(dir, "create the machine identity directory");
|
|
156
158
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
157
159
|
const body = {
|
|
158
160
|
version: 1,
|
|
@@ -168,10 +170,11 @@ function persistChoice(dir, file, hardware, machineUuid, reason, profile, eviden
|
|
|
168
170
|
...evidence && evidence.length > 0 ? { evidence: [...evidence] } : {},
|
|
169
171
|
decidedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
170
172
|
};
|
|
171
|
-
|
|
172
|
-
`, { mode: 384 });
|
|
173
|
-
|
|
174
|
-
} catch {
|
|
173
|
+
(0, import_stateFiles.writeStateFile)(tmp, `${JSON.stringify(body)}
|
|
174
|
+
`, { mode: 384 }, "write the machine identity");
|
|
175
|
+
(0, import_stateFiles.renameStateFile)(tmp, file, "write the machine identity");
|
|
176
|
+
} catch (err) {
|
|
177
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
175
178
|
}
|
|
176
179
|
}
|
|
177
180
|
function recoveryChoicePath(profile) {
|
|
@@ -45,6 +45,7 @@ var import_node_crypto = require("node:crypto");
|
|
|
45
45
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
46
46
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
47
47
|
var import_runtime = require("./runtime.js");
|
|
48
|
+
var import_stateFiles = require("./stateFiles.js");
|
|
48
49
|
const PROFILES_SEGMENT = "profiles";
|
|
49
50
|
const SUPERSEDED_MARKER_SUFFIX = ".superseded.json";
|
|
50
51
|
const SUPERSEDED_COPY_SUFFIX = ".superseded";
|
|
@@ -190,28 +191,6 @@ function sha256OfFile(target) {
|
|
|
190
191
|
return null;
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
|
-
function atomicWrite0600(target, value) {
|
|
194
|
-
const dir = import_node_path.default.dirname(target);
|
|
195
|
-
import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
196
|
-
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
197
|
-
const fd = import_node_fs.default.openSync(tmp, "w", 384);
|
|
198
|
-
try {
|
|
199
|
-
import_node_fs.default.writeSync(fd, value);
|
|
200
|
-
import_node_fs.default.fsyncSync(fd);
|
|
201
|
-
} finally {
|
|
202
|
-
import_node_fs.default.closeSync(fd);
|
|
203
|
-
}
|
|
204
|
-
import_node_fs.default.renameSync(tmp, target);
|
|
205
|
-
try {
|
|
206
|
-
const dirFd = import_node_fs.default.openSync(dir, "r");
|
|
207
|
-
try {
|
|
208
|
-
import_node_fs.default.fsyncSync(dirFd);
|
|
209
|
-
} finally {
|
|
210
|
-
import_node_fs.default.closeSync(dirFd);
|
|
211
|
-
}
|
|
212
|
-
} catch {
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
194
|
function adopt(fromDir, toDir, files, dryRun) {
|
|
216
195
|
const written = [];
|
|
217
196
|
for (const name of files) {
|
|
@@ -219,8 +198,9 @@ function adopt(fromDir, toDir, files, dryRun) {
|
|
|
219
198
|
if (!existsFile(source))
|
|
220
199
|
continue;
|
|
221
200
|
const target = import_node_path.default.join(toDir, name);
|
|
222
|
-
if (!dryRun)
|
|
223
|
-
atomicWrite0600(target, import_node_fs.default.readFileSync(source));
|
|
201
|
+
if (!dryRun) {
|
|
202
|
+
(0, import_stateFiles.atomicWrite0600)(target, import_node_fs.default.readFileSync(source), `adopt ${name} into ${toDir}`);
|
|
203
|
+
}
|
|
224
204
|
written.push(target);
|
|
225
205
|
}
|
|
226
206
|
return { written, supersededAside: [] };
|
|
@@ -232,7 +212,7 @@ function moveAside(dir, files, suffix = SUPERSEDED_COPY_SUFFIX) {
|
|
|
232
212
|
const target = import_node_path.default.join(dir, name);
|
|
233
213
|
if (!existsFile(target))
|
|
234
214
|
continue;
|
|
235
|
-
|
|
215
|
+
(0, import_stateFiles.renameStateFile)(target, `${target}${suffix}.${stamp}`, `set aside ${target}`);
|
|
236
216
|
aside.push(target);
|
|
237
217
|
}
|
|
238
218
|
return aside;
|
|
@@ -248,8 +228,8 @@ function writeMarker(markerPath, supersededBy, reason) {
|
|
|
248
228
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
249
229
|
note: "Kept deliberately. This file is the pre-consolidation copy (OSK-10314); older binaries read it and it is the only rollback that needs no operator step. Remove it only through logout."
|
|
250
230
|
};
|
|
251
|
-
atomicWrite0600(markerPath, Buffer.from(`${JSON.stringify(payload, null, 2)}
|
|
252
|
-
`, "utf-8"));
|
|
231
|
+
(0, import_stateFiles.atomicWrite0600)(markerPath, Buffer.from(`${JSON.stringify(payload, null, 2)}
|
|
232
|
+
`, "utf-8"), `write the consolidation marker ${markerPath}`);
|
|
253
233
|
}
|
|
254
234
|
function emitOutcome(spec, status, meta) {
|
|
255
235
|
spec.onEvent?.(`path_consolidation.${status}`, status !== "conflict", {
|
|
@@ -231,7 +231,7 @@ export type DeviceProofSigner = (params: {
|
|
|
231
231
|
method: string;
|
|
232
232
|
url: string;
|
|
233
233
|
refreshToken: string;
|
|
234
|
-
}) => string | null | undefined
|
|
234
|
+
}) => string | null | undefined | Promise<string | null | undefined>;
|
|
235
235
|
export declare function refreshDaemonToken(serverUrl: string, currentRefreshToken: string, daemonId: string, options?: {
|
|
236
236
|
machineUuid?: string;
|
|
237
237
|
origin?: string;
|