@mnemonik/cli 7.111.10 → 7.112.3
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/auth/credentials.d.ts +7 -2
- package/dist/auth/credentials.d.ts.map +1 -1
- package/dist/auth/credentials.js +311 -98
- package/dist/auth/index.d.ts +15 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +72 -15
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/installSession.js +3 -2
- package/dist/auth/status.js +3 -2
- package/dist/diagnostics.js +315 -102
- package/dist/digests.json +53 -53
- package/dist/identity/migrate.js +40 -2
- package/dist/install/hosts.js +3 -2
- package/dist/install/journal.js +40 -2
- package/dist/install/journey.js +40 -2
- package/dist/install/ownership.js +40 -2
- package/dist/install/transaction.d.ts +1 -3
- package/dist/install/transaction.d.ts.map +1 -1
- package/dist/install/transaction.js +67 -16
- package/dist/install/transaction.js.map +1 -1
- package/dist/install/ui.d.ts.map +1 -1
- package/dist/install/ui.js +42 -2
- package/dist/install/ui.js.map +1 -1
- package/dist/installSession.js +3 -2
- package/dist/installation.js +40 -2
- package/dist/launcher.js +40 -2
- package/dist/preflight.js +4 -3
- package/dist/project/eligibility.js +40 -2
- package/dist/project/records.js +40 -2
- package/dist/project.d.ts +11 -2
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +46 -4
- package/dist/project.js.map +1 -1
- package/dist/router.js +41 -3
- package/dist/runtime/releaseSource.js +40 -2
- package/dist/runtime/selfUpdate.js +40 -2
- package/dist/runtime/signers.js +242 -50
- package/dist/runtime/store.js +34 -0
- package/dist/runtime/updateCheck.js +40 -2
- package/dist/sbom.json +21 -21
- package/dist/scanner/control.js +40 -2
- package/dist/scanner/data.js +3 -2
- package/dist/scanner/discover.js +4 -3
- package/dist/scanner/enable.js +40 -2
- package/dist/scanner/picker.js +3 -2
- package/dist/scanner/service.js +40 -2
- package/dist/scanner/update.js +40 -2
- package/dist/scanner-release.json +29 -29
- package/dist/status.js +44 -6
- package/dist/transport/server.d.ts +21 -2
- package/dist/transport/server.d.ts.map +1 -1
- package/dist/transport/server.js +15 -1
- package/dist/transport/server.js.map +1 -1
- package/dist/vendor/local-setup.js +40 -2
- package/package.json +31 -31
package/dist/diagnostics.js
CHANGED
|
@@ -501,6 +501,13 @@ var require_ignore = __commonJS({
|
|
|
501
501
|
}
|
|
502
502
|
});
|
|
503
503
|
|
|
504
|
+
// packages/shared/dist/hookTimeouts.js
|
|
505
|
+
var init_hookTimeouts = __esm({
|
|
506
|
+
"packages/shared/dist/hookTimeouts.js"() {
|
|
507
|
+
"use strict";
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
504
511
|
// packages/shared/dist/runtimeSigners.js
|
|
505
512
|
import { lstatSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "node:fs";
|
|
506
513
|
import { basename, dirname as dirname2, join as join2, resolve, sep, win32 } from "node:path";
|
|
@@ -747,11 +754,37 @@ var init_runtimeReader = __esm({
|
|
|
747
754
|
});
|
|
748
755
|
|
|
749
756
|
// packages/shared/dist/projectSetupHandoff.js
|
|
757
|
+
var PROJECT_ENSURE_TIMEOUT_MS, SUPERVISOR, RUN_STALE_MS;
|
|
750
758
|
var init_projectSetupHandoff = __esm({
|
|
751
759
|
"packages/shared/dist/projectSetupHandoff.js"() {
|
|
752
760
|
"use strict";
|
|
753
761
|
init_runtimeSigners();
|
|
762
|
+
init_hookTimeouts();
|
|
754
763
|
init_runtimeReader();
|
|
764
|
+
PROJECT_ENSURE_TIMEOUT_MS = 3e4;
|
|
765
|
+
SUPERVISOR = `
|
|
766
|
+
const { execFile } = require('node:child_process');
|
|
767
|
+
const { writeFileSync, renameSync } = require('node:fs');
|
|
768
|
+
const [out, bin, ...args] = process.argv.slice(1);
|
|
769
|
+
let input = '';
|
|
770
|
+
process.stdin.setEncoding('utf8');
|
|
771
|
+
process.stdin.on('data', (chunk) => (input += chunk));
|
|
772
|
+
process.stdin.on('end', () => {
|
|
773
|
+
const child = execFile(
|
|
774
|
+
process.execPath,
|
|
775
|
+
[bin, ...args],
|
|
776
|
+
{ shell: false, timeout: ${PROJECT_ENSURE_TIMEOUT_MS}, killSignal: 'SIGKILL', maxBuffer: 64 * 1024, env: process.env },
|
|
777
|
+
(error, stdout) => {
|
|
778
|
+
const result = { code: error ? (typeof error.code === 'number' ? error.code : null) : 0, killed: !!(error && error.killed), stdout: String(stdout) };
|
|
779
|
+
writeFileSync(out + '.tmp', JSON.stringify(result), { mode: 0o600 });
|
|
780
|
+
renameSync(out + '.tmp', out);
|
|
781
|
+
}
|
|
782
|
+
);
|
|
783
|
+
child.stdin.on('error', () => {});
|
|
784
|
+
child.stdin.end(input);
|
|
785
|
+
});
|
|
786
|
+
`;
|
|
787
|
+
RUN_STALE_MS = PROJECT_ENSURE_TIMEOUT_MS + 1e4;
|
|
755
788
|
}
|
|
756
789
|
});
|
|
757
790
|
|
|
@@ -760,6 +793,7 @@ var init_hookRuntime = __esm({
|
|
|
760
793
|
"packages/shared/dist/hookRuntime.js"() {
|
|
761
794
|
"use strict";
|
|
762
795
|
init_runtimeSigners();
|
|
796
|
+
init_hookTimeouts();
|
|
763
797
|
init_repositoryRoot();
|
|
764
798
|
init_runtimeReader();
|
|
765
799
|
init_runtimeSigners();
|
|
@@ -2345,14 +2379,15 @@ var require_proper_lockfile = __commonJS({
|
|
|
2345
2379
|
});
|
|
2346
2380
|
|
|
2347
2381
|
// packages/cli/src/diagnostics.ts
|
|
2348
|
-
import { createHash as
|
|
2382
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
2349
2383
|
import { execFile as nodeExecFile2 } from "node:child_process";
|
|
2350
2384
|
import { lstat as lstat2, mkdir as mkdir3, readFile as readFile2 } from "node:fs/promises";
|
|
2351
|
-
import { dirname as
|
|
2385
|
+
import { dirname as dirname6, join as join7 } from "node:path";
|
|
2352
2386
|
|
|
2353
2387
|
// packages/credentials/dist/index.js
|
|
2354
|
-
import { createHmac, randomBytes } from "node:crypto";
|
|
2355
|
-
import {
|
|
2388
|
+
import { createHash as createHash4, createHmac, randomBytes } from "node:crypto";
|
|
2389
|
+
import { realpath } from "node:fs/promises";
|
|
2390
|
+
import { basename as basename3, dirname as dirname5, join as join6 } from "node:path";
|
|
2356
2391
|
|
|
2357
2392
|
// packages/shared/dist/instructions.js
|
|
2358
2393
|
var INSTRUCTIONS_CONTENT = `Mnemonik provides persistent project memory: decisions, rationale, tasks, and policies across sessions. If no block begins "PROJECT_CONTEXT schemaVersion=", session_bootstrap({ cwd }) loads project context when cwd is the real absolute path of the project root; a placeholder or relative path fails. With that block, context is loaded and no session_bootstrap call is needed. Code and docs show current artifacts, git shows changes, and memory carries the why; memory_search recalls rationale or intent, then Grep or Read verifies current source.
|
|
@@ -2365,7 +2400,7 @@ Project work items live in tasks. A pending list requires action:"list" and stat
|
|
|
2365
2400
|
|
|
2366
2401
|
Which tool for what:
|
|
2367
2402
|
- memory_get: hydrates exact originals by id.
|
|
2368
|
-
- memory_add: saves a
|
|
2403
|
+
- memory_add: saves a decision, a user's preference, or a root cause.
|
|
2369
2404
|
- memory_state: corrects wrong, outdated, or conflicting memory.
|
|
2370
2405
|
- memory_info: explains confidence or origin when memory looks suspect.
|
|
2371
2406
|
- memory_links: connects related decisions so they surface together.
|
|
@@ -2374,7 +2409,8 @@ Which tool for what:
|
|
|
2374
2409
|
- policy: stores durable enforced rules or preferences, not memories.
|
|
2375
2410
|
- tasks: creates follow-up work and closes completed work.
|
|
2376
2411
|
- code_search: finds conceptual code when source wording differs.
|
|
2377
|
-
- memory_search: finds rationale, intent, decisions, and prior work
|
|
2412
|
+
- memory_search: finds rationale, intent, decisions, and prior work.
|
|
2413
|
+
- memory_ingest: brings the project's docs and code comments into memory when asked.`;
|
|
2378
2414
|
function getMcpInstructions() {
|
|
2379
2415
|
const process2 = globalThis.process;
|
|
2380
2416
|
if (!process2?.env) {
|
|
@@ -6536,6 +6572,9 @@ var DEFAULT_OPTIONS = {
|
|
|
6536
6572
|
};
|
|
6537
6573
|
var BUILT_IN_IGNORE_DIR_SET = new Set(BUILT_IN_IGNORE_DIRS);
|
|
6538
6574
|
|
|
6575
|
+
// packages/shared/dist/index.js
|
|
6576
|
+
init_hookTimeouts();
|
|
6577
|
+
|
|
6539
6578
|
// packages/shared/dist/jitDeliveryAck.js
|
|
6540
6579
|
init_runtimeSigners();
|
|
6541
6580
|
|
|
@@ -6820,16 +6859,17 @@ var CredentialSessionUnavailableError = class extends Error {
|
|
|
6820
6859
|
// packages/credentials/dist/storage.js
|
|
6821
6860
|
import { createHash as createHash3 } from "node:crypto";
|
|
6822
6861
|
import { lstat as nodeLstat, mkdir as mkdir2, open as open2, readdir, rmdir as rmdir2, unlink as unlink2 } from "node:fs/promises";
|
|
6823
|
-
import { constants } from "node:fs";
|
|
6862
|
+
import { constants, realpathSync as realpathSync2 } from "node:fs";
|
|
6824
6863
|
import { basename as basename2, dirname as dirname4, isAbsolute, join as join5, parse, relative, resolve as resolve2, sep as sep2 } from "node:path";
|
|
6825
6864
|
var CredentialError = class extends Error {
|
|
6826
6865
|
reason;
|
|
6827
|
-
constructor(reason) {
|
|
6828
|
-
super(
|
|
6866
|
+
constructor(reason, message = reason) {
|
|
6867
|
+
super(message);
|
|
6829
6868
|
this.reason = reason;
|
|
6830
6869
|
this.name = "CredentialError";
|
|
6831
6870
|
}
|
|
6832
6871
|
};
|
|
6872
|
+
var STATE_ROOT_LINK_MESSAGE = "The Mnemonik state directory is a symbolic link. Replace it with a real directory, or set MNEMONIK_STATE_DIR to one.";
|
|
6833
6873
|
var digest = (value) => createHash3("sha256").update(value).digest("hex");
|
|
6834
6874
|
function credentialPaths(stateDir = stateDirectory(), familyId) {
|
|
6835
6875
|
const root = join5(stateDir, "credentials");
|
|
@@ -6849,8 +6889,25 @@ function credentialPaths(stateDir = stateDirectory(), familyId) {
|
|
|
6849
6889
|
};
|
|
6850
6890
|
}
|
|
6851
6891
|
var codeIs2 = (error, code) => error.code === code;
|
|
6892
|
+
function canonicalDirectory(path) {
|
|
6893
|
+
const missing = [];
|
|
6894
|
+
let current = resolve2(path);
|
|
6895
|
+
for (; ; ) {
|
|
6896
|
+
try {
|
|
6897
|
+
return join5(realpathSync2.native(current), ...missing.reverse());
|
|
6898
|
+
} catch (error) {
|
|
6899
|
+
const parent = dirname4(current);
|
|
6900
|
+
if (!codeIs2(error, "ENOENT") || parent === current)
|
|
6901
|
+
return resolve2(path);
|
|
6902
|
+
missing.push(basename2(current));
|
|
6903
|
+
current = parent;
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6906
|
+
}
|
|
6852
6907
|
var SecureFiles = class {
|
|
6853
6908
|
stateDir;
|
|
6909
|
+
/** The root as configured; paths spelled through it are rebased onto `stateDir`. */
|
|
6910
|
+
configuredStateDir;
|
|
6854
6911
|
platform;
|
|
6855
6912
|
lstat;
|
|
6856
6913
|
uid;
|
|
@@ -6859,7 +6916,8 @@ var SecureFiles = class {
|
|
|
6859
6916
|
username;
|
|
6860
6917
|
aclRun;
|
|
6861
6918
|
constructor(options = {}) {
|
|
6862
|
-
this.
|
|
6919
|
+
this.configuredStateDir = resolve2(options.stateDir ?? stateDirectory());
|
|
6920
|
+
this.stateDir = join5(canonicalDirectory(dirname4(this.configuredStateDir)), basename2(this.configuredStateDir));
|
|
6863
6921
|
this.platform = options.platform ?? process.platform;
|
|
6864
6922
|
this.lstat = options.lstat ?? nodeLstat;
|
|
6865
6923
|
this.uid = options.uid ?? process.getuid?.();
|
|
@@ -6869,7 +6927,10 @@ var SecureFiles = class {
|
|
|
6869
6927
|
this.aclRun = options.aclRun;
|
|
6870
6928
|
}
|
|
6871
6929
|
assertInsideState(path) {
|
|
6872
|
-
|
|
6930
|
+
let absolute = resolve2(path);
|
|
6931
|
+
const fromConfigured = relative(this.configuredStateDir, absolute);
|
|
6932
|
+
if (this.configuredStateDir !== this.stateDir && fromConfigured !== ".." && !fromConfigured.startsWith(`..${sep2}`) && !isAbsolute(fromConfigured))
|
|
6933
|
+
absolute = join5(this.stateDir, fromConfigured);
|
|
6873
6934
|
const fromState = relative(this.stateDir, absolute);
|
|
6874
6935
|
if (fromState === ".." || fromState.startsWith(`..${sep2}`) || isAbsolute(fromState))
|
|
6875
6936
|
throw new CredentialError("path_outside_state");
|
|
@@ -6890,6 +6951,8 @@ var SecureFiles = class {
|
|
|
6890
6951
|
async inspect(path, expectFile, allowMissing) {
|
|
6891
6952
|
const absolute = this.assertInsideState(path);
|
|
6892
6953
|
for (const component of this.components(absolute)) {
|
|
6954
|
+
if (component !== this.stateDir && !component.startsWith(`${this.stateDir}${sep2}`))
|
|
6955
|
+
continue;
|
|
6893
6956
|
let value;
|
|
6894
6957
|
try {
|
|
6895
6958
|
value = await this.lstat(component);
|
|
@@ -6899,10 +6962,7 @@ var SecureFiles = class {
|
|
|
6899
6962
|
throw error;
|
|
6900
6963
|
}
|
|
6901
6964
|
if (value.isSymbolicLink())
|
|
6902
|
-
throw new CredentialError("symlink_rejected");
|
|
6903
|
-
const protectedComponent = component === this.stateDir || component.startsWith(`${this.stateDir}${sep2}`);
|
|
6904
|
-
if (!protectedComponent)
|
|
6905
|
-
continue;
|
|
6965
|
+
throw new CredentialError("symlink_rejected", component === this.stateDir ? STATE_ROOT_LINK_MESSAGE : void 0);
|
|
6906
6966
|
const final = component === absolute;
|
|
6907
6967
|
if (this.platform === "win32") {
|
|
6908
6968
|
if (final)
|
|
@@ -7105,6 +7165,7 @@ var action = (familyId, reason) => ({
|
|
|
7105
7165
|
reason,
|
|
7106
7166
|
familyId
|
|
7107
7167
|
});
|
|
7168
|
+
var isSecretMissing = (error) => error instanceof Error && error.message === "credential_secret_missing";
|
|
7108
7169
|
var retryLater = (familyId, reason) => ({
|
|
7109
7170
|
status: "RETRY_LATER",
|
|
7110
7171
|
reason,
|
|
@@ -7112,12 +7173,15 @@ var retryLater = (familyId, reason) => ({
|
|
|
7112
7173
|
});
|
|
7113
7174
|
var isFailure = (result) => "status" in result && (result.status === "ACTION_REQUIRED" || result.status === "RETRY_LATER");
|
|
7114
7175
|
var diagnosedStores = /* @__PURE__ */ new WeakSet();
|
|
7176
|
+
var familyFileName = /^([0-9a-f]{64})\.json(?:\.[0-9a-f-]{36}\.tmp)?$/;
|
|
7177
|
+
var temporaryOf = (base, name2) => name2.startsWith(`${base}.`) && /^\.[0-9a-f-]{36}\.tmp$/.test(name2.slice(base.length));
|
|
7115
7178
|
function createCredentialAdapter(options = {}) {
|
|
7116
7179
|
const files = new SecureFiles({ ...options, stateDir: options.stateDir ?? stateDirectory() });
|
|
7117
7180
|
const paths = credentialPaths(files.stateDir);
|
|
7118
7181
|
const now = options.now ?? Date.now;
|
|
7119
7182
|
const sleep = options.sleep ?? wait;
|
|
7120
7183
|
const retryLimit = options.retryLimit ?? 2;
|
|
7184
|
+
const lockWaitMs = options.lockWaitMs ?? 6e4;
|
|
7121
7185
|
const store = options.secretStore;
|
|
7122
7186
|
const rotations = /* @__PURE__ */ new Map();
|
|
7123
7187
|
let osAvailable;
|
|
@@ -7133,7 +7197,10 @@ function createCredentialAdapter(options = {}) {
|
|
|
7133
7197
|
disable();
|
|
7134
7198
|
return ok;
|
|
7135
7199
|
}) : Promise.resolve(false);
|
|
7136
|
-
const reference = (name2) =>
|
|
7200
|
+
const reference = async (name2) => {
|
|
7201
|
+
const directory = createHash4("sha256").update(await realpath(files.stateDir));
|
|
7202
|
+
return `mnemonik.credentials.v1:${name2}:${directory.digest("hex").slice(0, 32)}`;
|
|
7203
|
+
};
|
|
7137
7204
|
const relativeSecret = (path) => path.slice(files.stateDir.length + 1);
|
|
7138
7205
|
const fileBackend = {
|
|
7139
7206
|
store: "file",
|
|
@@ -7165,7 +7232,7 @@ function createCredentialAdapter(options = {}) {
|
|
|
7165
7232
|
async function locator(name2, file, existing) {
|
|
7166
7233
|
if (existing?.store === "os")
|
|
7167
7234
|
return existing;
|
|
7168
|
-
return name2 === "cli-oauth" && await osBackend.isAvailable() ? { store: "os", secretRef: reference(name2) } : { store: "file", secretFile: relativeSecret(file) };
|
|
7235
|
+
return name2 === "cli-oauth" && await osBackend.isAvailable() ? { store: "os", secretRef: await reference(name2) } : { store: "file", secretFile: relativeSecret(file) };
|
|
7169
7236
|
}
|
|
7170
7237
|
const useFile = (location, file) => {
|
|
7171
7238
|
Object.assign(location, { store: "file", secretFile: relativeSecret(file) });
|
|
@@ -7219,6 +7286,14 @@ function createCredentialAdapter(options = {}) {
|
|
|
7219
7286
|
return bytes ? parse2(bytes, kind) : null;
|
|
7220
7287
|
}
|
|
7221
7288
|
async function putCliOAuth(metadata2, tokens) {
|
|
7289
|
+
await files.ensureParent(paths.cliRecord);
|
|
7290
|
+
return leased(paths.cliRecord, () => writeCliOAuth(metadata2, tokens), () => putCliOAuth(metadata2, tokens));
|
|
7291
|
+
}
|
|
7292
|
+
async function waitForCliLease() {
|
|
7293
|
+
await files.ensureParent(paths.cliRecord);
|
|
7294
|
+
await leased(paths.cliRecord, async () => void 0, () => void 0);
|
|
7295
|
+
}
|
|
7296
|
+
async function writeCliOAuth(metadata2, tokens) {
|
|
7222
7297
|
const current = await readRecord(paths.cliRecord, "cli-oauth");
|
|
7223
7298
|
const location = await locator("cli-oauth", paths.cliSecret, current ?? void 0);
|
|
7224
7299
|
const secret = typeof tokens === "string" ? { accessToken: "", refreshToken: tokens, accessExpiresAt: (/* @__PURE__ */ new Date(0)).toISOString() } : tokens;
|
|
@@ -7285,8 +7360,11 @@ function createCredentialAdapter(options = {}) {
|
|
|
7285
7360
|
}
|
|
7286
7361
|
async function putFamily(componentKind, response) {
|
|
7287
7362
|
const familyPaths = credentialPaths(files.stateDir, response.id);
|
|
7288
|
-
|
|
7289
|
-
return
|
|
7363
|
+
await files.ensureParent(familyPaths.record);
|
|
7364
|
+
return leased(familyPaths.record, async () => {
|
|
7365
|
+
const current = await readRecord(familyPaths.record, "component");
|
|
7366
|
+
return persistFamily(componentKind, response, current ?? void 0);
|
|
7367
|
+
}, () => putFamily(componentKind, response));
|
|
7290
7368
|
}
|
|
7291
7369
|
async function readFamily(familyId) {
|
|
7292
7370
|
const familyPaths = credentialPaths(files.stateDir, familyId);
|
|
@@ -7306,49 +7384,73 @@ function createCredentialAdapter(options = {}) {
|
|
|
7306
7384
|
void _file;
|
|
7307
7385
|
return { ...publicMetadata, ...secret };
|
|
7308
7386
|
}
|
|
7309
|
-
async function requestRotation(familyId, request) {
|
|
7310
|
-
const started = now();
|
|
7311
|
-
let lostResponseRetried = false;
|
|
7312
|
-
let retryableAttempt = 0;
|
|
7387
|
+
async function requestRotation(familyId, request, state) {
|
|
7313
7388
|
while (true) {
|
|
7314
7389
|
let response;
|
|
7315
7390
|
try {
|
|
7316
7391
|
response = await request();
|
|
7317
7392
|
} catch {
|
|
7318
|
-
if (lostResponseRetried)
|
|
7393
|
+
if (state.lostResponseRetried)
|
|
7319
7394
|
return action(familyId, "rotation_response_lost");
|
|
7320
|
-
lostResponseRetried = true;
|
|
7395
|
+
state.lostResponseRetried = true;
|
|
7321
7396
|
await sleep(250);
|
|
7322
|
-
if (now() > started + 6e4)
|
|
7397
|
+
if (now() > state.started + 6e4)
|
|
7323
7398
|
return action(familyId, "rotation_response_lost");
|
|
7324
7399
|
continue;
|
|
7325
7400
|
}
|
|
7326
7401
|
if (response.status === 400 && response.body.error === "invalid_grant")
|
|
7327
7402
|
return action(familyId, "invalid_grant");
|
|
7328
7403
|
if (response.status === 429 || response.status >= 500) {
|
|
7329
|
-
if (lostResponseRetried)
|
|
7404
|
+
if (state.lostResponseRetried)
|
|
7330
7405
|
return action(familyId, "rotation_response_lost");
|
|
7331
|
-
if (retryableAttempt >= retryLimit)
|
|
7406
|
+
if (state.retryableAttempt >= retryLimit)
|
|
7332
7407
|
return retryLater(familyId, response.status === 429 ? "rate_limited" : "server_error");
|
|
7333
|
-
const delay2 = response.retryAfterMs ?? 250 * 2 ** retryableAttempt;
|
|
7334
|
-
retryableAttempt += 1;
|
|
7335
|
-
|
|
7336
|
-
continue;
|
|
7408
|
+
const delay2 = response.retryAfterMs ?? 250 * 2 ** state.retryableAttempt;
|
|
7409
|
+
state.retryableAttempt += 1;
|
|
7410
|
+
return { backoffMs: delay2 };
|
|
7337
7411
|
}
|
|
7338
7412
|
return response;
|
|
7339
7413
|
}
|
|
7340
7414
|
}
|
|
7415
|
+
async function leased(recordPath2, work, vanished) {
|
|
7416
|
+
let entered = false;
|
|
7417
|
+
try {
|
|
7418
|
+
return await withLock(recordPath2, lockWaitMs, () => {
|
|
7419
|
+
entered = true;
|
|
7420
|
+
return work();
|
|
7421
|
+
});
|
|
7422
|
+
} catch (error) {
|
|
7423
|
+
if (entered || error.code !== "ENOENT")
|
|
7424
|
+
throw error;
|
|
7425
|
+
return vanished();
|
|
7426
|
+
}
|
|
7427
|
+
}
|
|
7341
7428
|
async function rotateStored(lockPath, missingId, read, request, decode, persist) {
|
|
7342
7429
|
const predecessor = await read();
|
|
7343
7430
|
if (!predecessor)
|
|
7344
7431
|
return action(missingId, "family_missing");
|
|
7345
|
-
|
|
7432
|
+
const attempts = {
|
|
7433
|
+
started: now(),
|
|
7434
|
+
lostResponseRetried: false,
|
|
7435
|
+
retryableAttempt: 0
|
|
7436
|
+
};
|
|
7437
|
+
for (; ; ) {
|
|
7438
|
+
const outcome = await rotateStoredOnce(lockPath, predecessor, read, request, decode, persist, attempts);
|
|
7439
|
+
if (!("backoffMs" in outcome))
|
|
7440
|
+
return outcome;
|
|
7441
|
+
await sleep(outcome.backoffMs);
|
|
7442
|
+
}
|
|
7443
|
+
}
|
|
7444
|
+
async function rotateStoredOnce(lockPath, predecessor, read, request, decode, persist, attempts) {
|
|
7445
|
+
return leased(lockPath, async () => {
|
|
7346
7446
|
const current = await read();
|
|
7347
7447
|
if (!current)
|
|
7348
7448
|
return action(predecessor.familyId, "family_missing");
|
|
7349
7449
|
if (current.refreshToken !== predecessor.refreshToken)
|
|
7350
7450
|
return current;
|
|
7351
|
-
const response = await requestRotation(current.familyId, () => request(current));
|
|
7451
|
+
const response = await requestRotation(current.familyId, () => request(current), attempts);
|
|
7452
|
+
if ("backoffMs" in response)
|
|
7453
|
+
return response;
|
|
7352
7454
|
if ("status" in response && (response.status === "ACTION_REQUIRED" || response.status === "RETRY_LATER"))
|
|
7353
7455
|
return response;
|
|
7354
7456
|
if (response.status !== 200)
|
|
@@ -7361,21 +7463,19 @@ function createCredentialAdapter(options = {}) {
|
|
|
7361
7463
|
} catch {
|
|
7362
7464
|
return action(current.familyId, "persistence_failed");
|
|
7363
7465
|
}
|
|
7364
|
-
});
|
|
7466
|
+
}, () => action(predecessor.familyId, "family_missing"));
|
|
7365
7467
|
}
|
|
7366
7468
|
function rotateLocked(familyId, transport) {
|
|
7367
7469
|
const familyPaths = credentialPaths(files.stateDir, familyId);
|
|
7368
|
-
return rotateStored(
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
...current.store === "os" ? { store: "os", secretRef: reference(`component:${familyId}`) } : { store: "file", secretFile: relativeSecret(familyPaths.secret) }
|
|
7378
|
-
}));
|
|
7470
|
+
return rotateStored(
|
|
7471
|
+
familyPaths.record,
|
|
7472
|
+
familyId,
|
|
7473
|
+
() => readFamily(familyId),
|
|
7474
|
+
(current) => transport.rotateFamily(familyId, current.refreshToken),
|
|
7475
|
+
(body2) => "access_token" in body2 ? body2 : null,
|
|
7476
|
+
// The stored record's own locator: a reference rebuilt from the name could differ.
|
|
7477
|
+
async (current, body2) => persistFamily(current.componentKind, body2, await readRecord(familyPaths.record, "component") ?? void 0)
|
|
7478
|
+
);
|
|
7379
7479
|
}
|
|
7380
7480
|
function rotateFamily(familyId, transport) {
|
|
7381
7481
|
const active = rotations.get(familyId);
|
|
@@ -7393,7 +7493,7 @@ function createCredentialAdapter(options = {}) {
|
|
|
7393
7493
|
function rotateCliLocked(transport) {
|
|
7394
7494
|
return rotateStored(paths.cliRecord, "cli", readCliOAuth, (current) => transport.rotateCli(current), (body2) => "access_token" in body2 && typeof body2.access_token === "string" && "refresh_token" in body2 && typeof body2.refresh_token === "string" && "expires_in" in body2 && typeof body2.expires_in === "number" && "scope" in body2 && typeof body2.scope === "string" ? body2 : null, async (current, body2) => {
|
|
7395
7495
|
const rotatedAt = now();
|
|
7396
|
-
await
|
|
7496
|
+
await writeCliOAuth({
|
|
7397
7497
|
issuer: current.issuer,
|
|
7398
7498
|
clientId: current.clientId,
|
|
7399
7499
|
familyId: current.familyId,
|
|
@@ -7444,11 +7544,33 @@ function createCredentialAdapter(options = {}) {
|
|
|
7444
7544
|
return second.status === 401 ? action(rotated.familyId, "protected_unauthorized") : second;
|
|
7445
7545
|
}
|
|
7446
7546
|
async function removeCliOAuth() {
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
await
|
|
7451
|
-
|
|
7547
|
+
await removeLeased(paths.cliRecord, "cli-oauth", paths.cliSecret);
|
|
7548
|
+
}
|
|
7549
|
+
async function revokeCli(transport) {
|
|
7550
|
+
if (!await readRecord(paths.cliRecord, "cli-oauth"))
|
|
7551
|
+
return action("cli", "family_missing");
|
|
7552
|
+
return leased(paths.cliRecord, async () => {
|
|
7553
|
+
let current;
|
|
7554
|
+
try {
|
|
7555
|
+
current = await readCliOAuth();
|
|
7556
|
+
} catch (error) {
|
|
7557
|
+
if (!isSecretMissing(error))
|
|
7558
|
+
throw error;
|
|
7559
|
+
await removeStored(paths.cliRecord, "cli-oauth", paths.cliSecret);
|
|
7560
|
+
return action("cli", "credential_secret_missing");
|
|
7561
|
+
}
|
|
7562
|
+
if (!current)
|
|
7563
|
+
return action("cli", "family_missing");
|
|
7564
|
+
const response = await transport.revokeCli(current);
|
|
7565
|
+
if (response.status === 429)
|
|
7566
|
+
return retryLater(current.familyId, "rate_limited");
|
|
7567
|
+
if (response.status >= 500)
|
|
7568
|
+
return retryLater(current.familyId, "server_error");
|
|
7569
|
+
if (response.status !== 200)
|
|
7570
|
+
return action(current.familyId, "error" in response.body ? response.body.error : "revoke_refused");
|
|
7571
|
+
await removeStored(paths.cliRecord, "cli-oauth", paths.cliSecret);
|
|
7572
|
+
return { status: "revoked", familyId: current.familyId };
|
|
7573
|
+
}, () => action("cli", "family_missing"));
|
|
7452
7574
|
}
|
|
7453
7575
|
async function withCredential(familyId, transport, work) {
|
|
7454
7576
|
let current = await readFamily(familyId);
|
|
@@ -7469,43 +7591,98 @@ function createCredentialAdapter(options = {}) {
|
|
|
7469
7591
|
const second = await work(rotated.accessToken);
|
|
7470
7592
|
return second.status === 401 ? action(familyId, "protected_unauthorized") : second;
|
|
7471
7593
|
}
|
|
7594
|
+
async function removeStored(recordPath2, kind, secretPath) {
|
|
7595
|
+
const record = await readRecord(recordPath2, kind);
|
|
7596
|
+
if (!record)
|
|
7597
|
+
return null;
|
|
7598
|
+
await files.remove(recordPath2);
|
|
7599
|
+
await deleteSecret(record, secretPath);
|
|
7600
|
+
return record;
|
|
7601
|
+
}
|
|
7602
|
+
async function removeLeased(recordPath2, kind, secretPath) {
|
|
7603
|
+
if (!await readRecord(recordPath2, kind))
|
|
7604
|
+
return null;
|
|
7605
|
+
return leased(recordPath2, () => removeStored(recordPath2, kind, secretPath), () => null);
|
|
7606
|
+
}
|
|
7607
|
+
async function orphanRefreshToken(secretPath) {
|
|
7608
|
+
const bytes = await files.read(secretPath);
|
|
7609
|
+
if (!bytes)
|
|
7610
|
+
return null;
|
|
7611
|
+
const secret = JSON.parse(bytes.toString());
|
|
7612
|
+
if (typeof secret.refreshToken !== "string")
|
|
7613
|
+
throw new Error("credential_record_invalid");
|
|
7614
|
+
return secret.refreshToken;
|
|
7615
|
+
}
|
|
7472
7616
|
async function removeFamily(familyId) {
|
|
7473
7617
|
const familyPaths = credentialPaths(files.stateDir, familyId);
|
|
7474
|
-
|
|
7475
|
-
if (!record)
|
|
7476
|
-
return;
|
|
7477
|
-
await deleteSecret(record, familyPaths.secret);
|
|
7478
|
-
await files.remove(familyPaths.record);
|
|
7618
|
+
await removeStored(familyPaths.record, "component", familyPaths.secret);
|
|
7479
7619
|
}
|
|
7480
7620
|
async function revokeFamily(familyId, transport) {
|
|
7481
|
-
const
|
|
7482
|
-
if (!
|
|
7621
|
+
const familyPaths = credentialPaths(files.stateDir, familyId);
|
|
7622
|
+
if (!await readRecord(familyPaths.record, "component") && !await files.read(familyPaths.secret))
|
|
7483
7623
|
return action(familyId, "family_missing");
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7624
|
+
await files.ensureParent(familyPaths.record);
|
|
7625
|
+
return leased(familyPaths.record, async () => {
|
|
7626
|
+
let current;
|
|
7627
|
+
try {
|
|
7628
|
+
current = await readFamily(familyId);
|
|
7629
|
+
} catch (error) {
|
|
7630
|
+
if (!isSecretMissing(error))
|
|
7631
|
+
throw error;
|
|
7632
|
+
await removeFamily(familyId);
|
|
7633
|
+
return action(familyId, "credential_secret_missing");
|
|
7634
|
+
}
|
|
7635
|
+
const refreshToken = current?.refreshToken ?? await orphanRefreshToken(familyPaths.secret);
|
|
7636
|
+
if (!refreshToken)
|
|
7637
|
+
return action(familyId, "family_missing");
|
|
7638
|
+
const response = await transport.revokeFamily(familyId, refreshToken);
|
|
7639
|
+
if (response.status === 429)
|
|
7640
|
+
return retryLater(familyId, "rate_limited");
|
|
7641
|
+
if (response.status >= 500)
|
|
7642
|
+
return retryLater(familyId, "server_error");
|
|
7643
|
+
const refusal = response.status === 200 ? null : "error" in response.body ? response.body.error : "revoke_refused";
|
|
7644
|
+
if (refusal)
|
|
7645
|
+
return action(familyId, refusal);
|
|
7646
|
+
if (current)
|
|
7647
|
+
await removeFamily(familyId);
|
|
7648
|
+
else
|
|
7649
|
+
await files.remove(familyPaths.secret);
|
|
7650
|
+
return { status: "revoked", familyId };
|
|
7651
|
+
}, () => action(familyId, "family_missing"));
|
|
7493
7652
|
}
|
|
7494
|
-
async function
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
}
|
|
7503
|
-
const location = await locator("root-binding", paths.rootSecret);
|
|
7504
|
-
const key = randomBytes(32);
|
|
7505
|
-
await writeSecret(location, paths.rootSecret, key);
|
|
7506
|
-
await files.write(paths.rootRecord, json({ schemaVersion: 1, kind: "root-binding", ...location }), "record");
|
|
7507
|
-
return key;
|
|
7653
|
+
async function storedRootKey() {
|
|
7654
|
+
const record = await readRecord(paths.rootRecord, "root-binding");
|
|
7655
|
+
if (!record)
|
|
7656
|
+
return null;
|
|
7657
|
+
const key = await readSecret(record, paths.rootSecret).catch((error) => {
|
|
7658
|
+
if (isSecretMissing(error))
|
|
7659
|
+
return null;
|
|
7660
|
+
throw error;
|
|
7508
7661
|
});
|
|
7662
|
+
if (key && key.length !== 32)
|
|
7663
|
+
throw new Error("root_binding_key_invalid");
|
|
7664
|
+
return key;
|
|
7665
|
+
}
|
|
7666
|
+
async function rootKey() {
|
|
7667
|
+
const existing = await storedRootKey();
|
|
7668
|
+
if (existing)
|
|
7669
|
+
return existing;
|
|
7670
|
+
await files.ensureParent(paths.rootRecord);
|
|
7671
|
+
return leased(
|
|
7672
|
+
paths.rootRecord,
|
|
7673
|
+
async () => {
|
|
7674
|
+
const stored = await storedRootKey();
|
|
7675
|
+
if (stored)
|
|
7676
|
+
return stored;
|
|
7677
|
+
const location = await locator("root-binding", paths.rootSecret);
|
|
7678
|
+
const key = randomBytes(32);
|
|
7679
|
+
await writeSecret(location, paths.rootSecret, key);
|
|
7680
|
+
await files.write(paths.rootRecord, json({ schemaVersion: 1, kind: "root-binding", ...location }), "record");
|
|
7681
|
+
return key;
|
|
7682
|
+
},
|
|
7683
|
+
// forget erased the binding and its directory while this call waited: create afresh.
|
|
7684
|
+
rootKey
|
|
7685
|
+
);
|
|
7509
7686
|
}
|
|
7510
7687
|
async function hmacRootBinding(version, input) {
|
|
7511
7688
|
if (version !== 1)
|
|
@@ -7513,26 +7690,60 @@ function createCredentialAdapter(options = {}) {
|
|
|
7513
7690
|
const key = await rootKey();
|
|
7514
7691
|
return { version, hmac: createHmac("sha256", key).update(input).digest("base64url") };
|
|
7515
7692
|
}
|
|
7693
|
+
async function eraseEntry(recordPath2, kind, secretPath) {
|
|
7694
|
+
const record = await removeStored(recordPath2, kind, secretPath);
|
|
7695
|
+
if (!record)
|
|
7696
|
+
await files.remove(secretPath);
|
|
7697
|
+
for (const path of [recordPath2, secretPath])
|
|
7698
|
+
for (const entry of await files.list(dirname5(path)))
|
|
7699
|
+
if (temporaryOf(basename3(path), entry))
|
|
7700
|
+
await files.remove(join6(dirname5(path), entry));
|
|
7701
|
+
return record;
|
|
7702
|
+
}
|
|
7703
|
+
async function forgetCliOsOrphan() {
|
|
7704
|
+
if (!store)
|
|
7705
|
+
return;
|
|
7706
|
+
const recordPath2 = paths.cliRecord;
|
|
7707
|
+
const ref = await reference("cli-oauth").catch(() => null);
|
|
7708
|
+
if (!ref || !await available())
|
|
7709
|
+
return;
|
|
7710
|
+
if (!await osBackend.read(ref, "").catch(() => null))
|
|
7711
|
+
return;
|
|
7712
|
+
await files.ensureParent(recordPath2);
|
|
7713
|
+
await leased(recordPath2, async () => {
|
|
7714
|
+
const record = await readRecord(recordPath2, "cli-oauth");
|
|
7715
|
+
if (record?.store === "os" && record.secretRef === ref)
|
|
7716
|
+
return;
|
|
7717
|
+
await osBackend.delete(ref, "");
|
|
7718
|
+
}, () => void 0);
|
|
7719
|
+
}
|
|
7720
|
+
async function forgetEntry(recordPath2, kind, secretPath) {
|
|
7721
|
+
let present = false;
|
|
7722
|
+
for (const path of [recordPath2, secretPath])
|
|
7723
|
+
for (const entry of await files.list(dirname5(path)))
|
|
7724
|
+
present ||= entry === basename3(path) || temporaryOf(basename3(path), entry);
|
|
7725
|
+
if (!present)
|
|
7726
|
+
return null;
|
|
7727
|
+
await files.ensureParent(recordPath2);
|
|
7728
|
+
return leased(recordPath2, () => eraseEntry(recordPath2, kind, secretPath), () => null);
|
|
7729
|
+
}
|
|
7516
7730
|
async function forget() {
|
|
7517
|
-
const cli = await readRecord(paths.cliRecord, "cli-oauth");
|
|
7518
7731
|
const componentFamilyIds = [];
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
const
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
}
|
|
7531
|
-
const
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
await files.remove(paths.rootRecord);
|
|
7535
|
-
}
|
|
7732
|
+
const digests = /* @__PURE__ */ new Set();
|
|
7733
|
+
for (const directory of [paths.familyRecords, paths.familySecrets])
|
|
7734
|
+
for (const name2 of await files.list(directory)) {
|
|
7735
|
+
const digest3 = familyFileName.exec(name2)?.[1];
|
|
7736
|
+
if (digest3)
|
|
7737
|
+
digests.add(digest3);
|
|
7738
|
+
}
|
|
7739
|
+
for (const digest3 of [...digests].sort()) {
|
|
7740
|
+
const record = await forgetEntry(join6(paths.familyRecords, `${digest3}.json`), "component", join6(paths.familySecrets, `${digest3}.json`));
|
|
7741
|
+
if (record)
|
|
7742
|
+
componentFamilyIds.push(record.familyId);
|
|
7743
|
+
}
|
|
7744
|
+
const cli = await forgetEntry(paths.cliRecord, "cli-oauth", paths.cliSecret);
|
|
7745
|
+
await forgetCliOsOrphan();
|
|
7746
|
+
await forgetEntry(paths.rootRecord, "root-binding", paths.rootSecret);
|
|
7536
7747
|
await files.removeEmptyDirectories([
|
|
7537
7748
|
paths.familySecrets,
|
|
7538
7749
|
paths.familyRecords,
|
|
@@ -7554,6 +7765,8 @@ function createCredentialAdapter(options = {}) {
|
|
|
7554
7765
|
rotateCli,
|
|
7555
7766
|
withCliCredential,
|
|
7556
7767
|
removeCliOAuth,
|
|
7768
|
+
revokeCli,
|
|
7769
|
+
waitForCliLease,
|
|
7557
7770
|
putFamily,
|
|
7558
7771
|
readFamily,
|
|
7559
7772
|
rotateFamily,
|
|
@@ -7574,7 +7787,7 @@ var DiagnosticsError = class extends Error {
|
|
|
7574
7787
|
}
|
|
7575
7788
|
code;
|
|
7576
7789
|
};
|
|
7577
|
-
var digest2 = (bytes) =>
|
|
7790
|
+
var digest2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
7578
7791
|
var validId = (id) => /^diag-[A-Za-z0-9._-]{1,120}$/u.test(id);
|
|
7579
7792
|
async function scannerConfig(stateDir) {
|
|
7580
7793
|
const state = JSON.parse(await readFile2(join7(stateDir, "scanner", "state.json"), "utf8"));
|
|
@@ -7627,7 +7840,7 @@ async function previewDiagnostics(out2, dependencies = {}) {
|
|
|
7627
7840
|
throw new DiagnosticsError("diagnostics_preview_invalid");
|
|
7628
7841
|
const receipt = { schemaVersion: 1, ...result };
|
|
7629
7842
|
const receiptPath = join7(stateDir, "diagnostics", `${result.manifest.bundleId}.json`);
|
|
7630
|
-
await mkdir3(
|
|
7843
|
+
await mkdir3(dirname6(receiptPath), { recursive: true, mode: 448 });
|
|
7631
7844
|
await atomicWrite(receiptPath, Buffer.from(`${JSON.stringify(receipt)}
|
|
7632
7845
|
`));
|
|
7633
7846
|
return result;
|