hikoutei 0.11.101-dev → 0.11.103-dev
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/cli/adoptFlow.d.ts.map +1 -1
- package/dist/cli/adoptFlow.js +31 -0
- package/dist/cli/adoptFlow.js.map +1 -1
- package/dist/cli/checkpoint.d.ts +38 -52
- package/dist/cli/checkpoint.d.ts.map +1 -1
- package/dist/cli/checkpoint.js +87 -34
- package/dist/cli/checkpoint.js.map +1 -1
- package/dist/cli/envFileWriter.d.ts +42 -8
- package/dist/cli/envFileWriter.d.ts.map +1 -1
- package/dist/cli/envFileWriter.js +60 -30
- package/dist/cli/envFileWriter.js.map +1 -1
- package/dist/cli/errors.d.ts +6 -5
- package/dist/cli/errors.d.ts.map +1 -1
- package/dist/cli/errors.js +6 -5
- package/dist/cli/errors.js.map +1 -1
- package/dist/cli/gcloudRunner.d.ts +22 -5
- package/dist/cli/gcloudRunner.d.ts.map +1 -1
- package/dist/cli/gcloudRunner.js +168 -0
- package/dist/cli/gcloudRunner.js.map +1 -1
- package/dist/cli/keyContract.d.ts +62 -0
- package/dist/cli/keyContract.d.ts.map +1 -0
- package/dist/cli/keyContract.js +24 -0
- package/dist/cli/keyContract.js.map +1 -0
- package/dist/cli/keyMaterial.d.ts +1 -1
- package/dist/cli/keyMaterial.d.ts.map +1 -1
- package/dist/cli/keyMaterial.js +2 -2
- package/dist/cli/keyMaterial.js.map +1 -1
- package/dist/cli/keyProvision.d.ts +22 -44
- package/dist/cli/keyProvision.d.ts.map +1 -1
- package/dist/cli/keyProvision.js +212 -103
- package/dist/cli/keyProvision.js.map +1 -1
- package/dist/cli/setupFlow.d.ts +2 -7
- package/dist/cli/setupFlow.d.ts.map +1 -1
- package/dist/cli/setupFlow.js +301 -55
- package/dist/cli/setupFlow.js.map +1 -1
- package/dist/cli/setupPathCollision.d.ts +11 -3
- package/dist/cli/setupPathCollision.d.ts.map +1 -1
- package/dist/cli/setupPathCollision.js +10 -7
- package/dist/cli/setupPathCollision.js.map +1 -1
- package/dist/cli/setupPaths.d.ts +2 -0
- package/dist/cli/setupPaths.d.ts.map +1 -1
- package/dist/cli/setupPaths.js +9 -0
- package/dist/cli/setupPaths.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/batchBuilder.d.ts +10 -0
- package/dist/sheets/providers/google-sheets-api/model/batchBuilder.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/batchBuilder.js +25 -36
- package/dist/sheets/providers/google-sheets-api/model/batchBuilder.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/planner.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/planner.js +0 -3
- package/dist/sheets/providers/google-sheets-api/model/planner.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/fastAppend.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/fastAppend.js +3 -13
- package/dist/sheets/providers/google-sheets-api/operations/fastAppend.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/rowChecks.js +9 -10
- package/dist/sheets/providers/google-sheets-api/operations/rowChecks.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setupFlow.d.ts","sourceRoot":"","sources":["../../../../../../src/setupFlow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;
|
|
1
|
+
{"version":3,"file":"setupFlow.d.ts","sourceRoot":"","sources":["../../../../../../src/setupFlow.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/setupFlow.js
CHANGED
|
@@ -65,18 +65,19 @@
|
|
|
65
65
|
* lock, checkpoint, key, or env mutation, and manual setup remains
|
|
66
66
|
* available.
|
|
67
67
|
*/
|
|
68
|
-
import { basename, dirname, join } from "node:path";
|
|
69
68
|
import { acquireSetupLock, checkStateCompatibility, generateCreationMarker, isValidGcpProjectId, isValidServiceAccountName, loadSetupState, releaseSetupLock, saveSetupState, serviceAccountEmail, SERVICE_ACCOUNT_KEY_FILE_MODE, SETUP_STATE_VERSION, setupLockPath, setupStateTempPath, } from "./checkpoint.js";
|
|
70
69
|
import { MAX_SETUP_SA_COUNT, parseSaCount } from "./args.js";
|
|
71
70
|
import { SETUP_ERROR_CODES, carrierCode, setupFailure } from "./errors.js";
|
|
72
71
|
import { describeGcloudFailure, errorResult, outcomeOf } from "./flowResult.js";
|
|
73
72
|
import { revalidateSetupPaths, writeSetupEnvFile, } from "./envFileWriter.js";
|
|
74
73
|
import { findSetupPathCollision } from "./setupPathCollision.js";
|
|
74
|
+
import { poolKeyPath } from "./setupPaths.js";
|
|
75
75
|
import { readServiceAccountKeyCredentialSecurely, readServiceAccountKeySecurely, } from "./keyMaterial.js";
|
|
76
76
|
// Compat re-exports: the extracted modules are the canonical homes; the
|
|
77
77
|
// setupFlow import path is kept stable for existing importers.
|
|
78
78
|
export { writeSetupEnvFile, atomicWritePrivateFile, SETUP_ENV_KEYS } from "./envFileWriter.js";
|
|
79
79
|
export { findSetupPathCollision } from "./setupPathCollision.js";
|
|
80
|
+
export { poolKeyPath };
|
|
80
81
|
import { createSafeRunner } from "./gcloudRunner.js";
|
|
81
82
|
import { boundedCheckReporter, POOL_EXPANSION_PHASE, safeProgressSink, SETUP_PROGRESS_OPERATIONS, } from "./setupProgress.js";
|
|
82
83
|
import { checkHumanDriveAccess, DRIVE_ACCESS_COMMAND, } from "./humanAuth.js";
|
|
@@ -292,7 +293,50 @@ export function planSetupCommands(options, slug) {
|
|
|
292
293
|
kind: "file",
|
|
293
294
|
label: `write ${options.outputPath}`,
|
|
294
295
|
outcome: "GOOGLE_APPLICATION_CREDENTIALS and HIKOUTEI_SYNC_SPREADSHEET_URL set; unrelated lines preserved; written atomically via a private temp file + rename, never through a symlink alias",
|
|
295
|
-
}
|
|
296
|
+
});
|
|
297
|
+
// The requested credential pool is part of the dry-run plan: one
|
|
298
|
+
// ensure/key/share/verify group per additional account, both pool
|
|
299
|
+
// checkpoint writes, and the pool .env rewrite. Single-SA runs plan
|
|
300
|
+
// exactly as before.
|
|
301
|
+
const dryRunSaCount = options.saCount ?? 1;
|
|
302
|
+
for (let index = 2; index <= dryRunSaCount; index += 1) {
|
|
303
|
+
const poolSaName = `${options.saName}-${index}`;
|
|
304
|
+
const poolEmail = serviceAccountEmail(poolSaName, projectId);
|
|
305
|
+
const poolPath = poolKeyPath(options.keyPath, index);
|
|
306
|
+
commands.push({
|
|
307
|
+
kind: "gcloud",
|
|
308
|
+
command: ["iam", "service-accounts", "create", poolSaName, "--project", projectId, "--display-name", "hikoutei setup"],
|
|
309
|
+
outcome: `pool service account ${index} created (reused when it already exists)`,
|
|
310
|
+
}, {
|
|
311
|
+
kind: "file",
|
|
312
|
+
label: `checkpoint ${options.statePath}`,
|
|
313
|
+
outcome: `pool key ${index} write-ahead marker and baseline persisted before its single create; a resume reconciles and never creates a duplicate`,
|
|
314
|
+
}, {
|
|
315
|
+
kind: "gcloud",
|
|
316
|
+
command: ["iam", "service-accounts", "keys", "create", KEY_STAGE_PLACEHOLDER, "--iam-account", poolEmail, "--project", projectId],
|
|
317
|
+
outcome: `pool key ${index} created in a private staging directory, validated there, and installed atomically at ${poolPath}`,
|
|
318
|
+
}, {
|
|
319
|
+
kind: "api",
|
|
320
|
+
label: `drive.permissions: share ${poolEmail} as writer`,
|
|
321
|
+
outcome: `pool service account ${index} granted writer access without a notification email`,
|
|
322
|
+
}, {
|
|
323
|
+
kind: "api",
|
|
324
|
+
label: `spreadsheets.get with the ${poolEmail} key`,
|
|
325
|
+
outcome: `pool service-account access ${index} verified`,
|
|
326
|
+
}, {
|
|
327
|
+
kind: "file",
|
|
328
|
+
label: `checkpoint ${options.statePath}`,
|
|
329
|
+
outcome: `verified pool entry ${index} persisted; its in-flight write-ahead is cleared`,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
if (dryRunSaCount > 1) {
|
|
333
|
+
commands.push({
|
|
334
|
+
kind: "file",
|
|
335
|
+
label: `write ${options.outputPath}`,
|
|
336
|
+
outcome: `HIKOUTEI_SYNC_CREDENTIALS set to the ${dryRunSaCount}-entry credential pool; unrelated lines preserved`,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
commands.push({
|
|
296
340
|
kind: "file",
|
|
297
341
|
label: `release setup lock ${lockPath}`,
|
|
298
342
|
outcome: "released",
|
|
@@ -395,14 +439,25 @@ export async function runSetup(options) {
|
|
|
395
439
|
(!Number.isSafeInteger(options.saCount) || options.saCount < 1 || options.saCount > MAX_SETUP_SA_COUNT)) {
|
|
396
440
|
return errorResult(SETUP_ERROR_CODES.INVALID_ARGS, `invalid sa-count value: expected a positive integer between 1 and ${MAX_SETUP_SA_COUNT}`);
|
|
397
441
|
}
|
|
442
|
+
// Derived pool identities are validated before anything runs (dry runs
|
|
443
|
+
// included): an over-long `--sa-name` that cannot expand into N valid
|
|
444
|
+
// `<saName>-<i>` accounts is a usage error, never a mid-run failure
|
|
445
|
+
// after the primary resources were already mutated.
|
|
446
|
+
const poolPlanError = validatePoolPlan(options.saName, options.saCount ?? 1);
|
|
447
|
+
if (poolPlanError !== null) {
|
|
448
|
+
return errorResult(SETUP_ERROR_CODES.INVALID_ARGS, poolPlanError);
|
|
449
|
+
}
|
|
398
450
|
// Reject canonical path collisions before anything else runs: `--output`
|
|
399
451
|
// must never alias the key, checkpoint, checkpoint temp, or lock path, and
|
|
400
|
-
// the key must never alias the checkpoint, temp, or lock.
|
|
401
|
-
//
|
|
452
|
+
// the key must never alias the checkpoint, temp, or lock. Planned
|
|
453
|
+
// credential-pool key paths (entries 2..N) are reserved too: a pool key
|
|
454
|
+
// aliasing any reserved path rejects the run before any mutation. This is
|
|
455
|
+
// a usage error, not a runtime failure.
|
|
402
456
|
const collision = findSetupPathCollision({
|
|
403
457
|
keyPath: options.keyPath,
|
|
404
458
|
outputPath: options.outputPath,
|
|
405
459
|
statePath: options.statePath,
|
|
460
|
+
poolKeyPaths: plannedPoolKeyPaths(options.keyPath, options.saCount ?? 1),
|
|
406
461
|
});
|
|
407
462
|
if (collision.status === "collision") {
|
|
408
463
|
return errorResult(SETUP_ERROR_CODES.INVALID_ARGS, collision.message);
|
|
@@ -532,6 +587,12 @@ async function runSetupLocked(options, executed, human, runner, keySleeper, prog
|
|
|
532
587
|
return errorResult(SETUP_ERROR_CODES.SETUP_STATE_INVALID, checkpointResult.message);
|
|
533
588
|
}
|
|
534
589
|
const checkpoint = checkpointResult.status === "loaded" ? checkpointResult.state : undefined;
|
|
590
|
+
if (checkpoint?.status === "complete" &&
|
|
591
|
+
((checkpoint.pool?.length ?? 1) > MAX_SETUP_SA_COUNT ||
|
|
592
|
+
(checkpoint.poolKeyStarted?.index ?? 0) > MAX_SETUP_SA_COUNT)) {
|
|
593
|
+
return errorResult(SETUP_ERROR_CODES.SETUP_STATE_INVALID, `the credential pool in ${options.statePath} exceeds the supported maximum of ${MAX_SETUP_SA_COUNT}; ` +
|
|
594
|
+
`remove the setup state and rerun setup`);
|
|
595
|
+
}
|
|
535
596
|
// Report the resume context once: the phases a checkpoint guarantees as
|
|
536
597
|
// already complete. cloud_auth and drive_access are never
|
|
537
598
|
// checkpoint-complete (every run re-runs them fresh), and the output phase
|
|
@@ -542,6 +603,24 @@ async function runSetupLocked(options, executed, human, runner, keySleeper, prog
|
|
|
542
603
|
completedFromCheckpoint: checkpointCompletedPhases(checkpoint.status),
|
|
543
604
|
});
|
|
544
605
|
}
|
|
606
|
+
// Stored + planned pool key paths must never alias a reserved path: a
|
|
607
|
+
// checkpointed pool (or in-flight pool write-ahead) that collides with
|
|
608
|
+
// the current run's key/output/state/temp/lock rejects the run here,
|
|
609
|
+
// still before any mutation (the project phase is the first writer).
|
|
610
|
+
const storedPoolPaths = storedPoolKeyPaths(checkpoint);
|
|
611
|
+
if (storedPoolPaths.length > 0 || (options.saCount ?? 1) > 1) {
|
|
612
|
+
const poolCollision = findSetupPathCollision({
|
|
613
|
+
keyPath: options.keyPath,
|
|
614
|
+
outputPath: options.outputPath,
|
|
615
|
+
statePath: options.statePath,
|
|
616
|
+
poolKeyPaths: [
|
|
617
|
+
...new Set([...plannedPoolKeyPaths(options.keyPath, options.saCount ?? 1), ...storedPoolPaths]),
|
|
618
|
+
],
|
|
619
|
+
});
|
|
620
|
+
if (poolCollision.status === "collision") {
|
|
621
|
+
return errorResult(SETUP_ERROR_CODES.INVALID_ARGS, poolCollision.message);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
545
624
|
// Key file: on resume it must exist and match the checkpoint once the
|
|
546
625
|
// key phase was reached (key_ready and later); a `key_create_started`
|
|
547
626
|
// checkpoint defers key handling to the reconciliation step because the
|
|
@@ -1086,8 +1165,15 @@ async function runSetupLocked(options, executed, human, runner, keySleeper, prog
|
|
|
1086
1165
|
// spreadsheet URL is derived from the id — never trusted from storage.
|
|
1087
1166
|
// Revalidate the reserved paths immediately before this write so an alias
|
|
1088
1167
|
// planted mid-run can never redirect the .env write onto the key or the
|
|
1089
|
-
// checkpoint.
|
|
1090
|
-
|
|
1168
|
+
// checkpoint. A checkpointed pool survives smaller/default reruns: its
|
|
1169
|
+
// paths are preserved in the .env (the writer only emits the pool line
|
|
1170
|
+
// for >= 2 paths, so a fresh single-SA run still strips a stale line)
|
|
1171
|
+
// and reserved against aliasing, exactly like the primary key.
|
|
1172
|
+
const preservedPoolPaths = checkpointPoolEntries(checkpoint).map((entry) => entry.keyPath);
|
|
1173
|
+
const poolReservedPaths = [
|
|
1174
|
+
...new Set([...plannedPoolKeyPaths(options.keyPath, options.saCount ?? 1), ...storedPoolKeyPaths(checkpoint)]),
|
|
1175
|
+
];
|
|
1176
|
+
const envPreflight = revalidateSetupPaths(options, poolReservedPaths);
|
|
1091
1177
|
if (envPreflight !== null) {
|
|
1092
1178
|
return envPreflight;
|
|
1093
1179
|
}
|
|
@@ -1095,7 +1181,7 @@ async function runSetupLocked(options, executed, human, runner, keySleeper, prog
|
|
|
1095
1181
|
progress.report({ type: "operation_started", phase: "output", operation: SETUP_PROGRESS_OPERATIONS.ENV_WRITE });
|
|
1096
1182
|
let envResult;
|
|
1097
1183
|
try {
|
|
1098
|
-
envResult = writeSetupEnvFile(options.outputPath, options.keyPath, spreadsheetEditUrl(spreadsheet.spreadsheetId), [options.statePath, setupLockPath(options.statePath), setupStateTempPath(options.statePath)]);
|
|
1184
|
+
envResult = writeSetupEnvFile(options.outputPath, options.keyPath, spreadsheetEditUrl(spreadsheet.spreadsheetId), [options.statePath, setupLockPath(options.statePath), setupStateTempPath(options.statePath), ...poolReservedPaths], preservedPoolPaths);
|
|
1099
1185
|
}
|
|
1100
1186
|
catch (error) {
|
|
1101
1187
|
return errorResult(carrierCode(error) ?? SETUP_ERROR_CODES.OUTPUT_WRITE_FAILED, `could not write ${options.outputPath}: ${messageOf(error)}`);
|
|
@@ -1133,10 +1219,28 @@ async function runSetupLocked(options, executed, human, runner, keySleeper, prog
|
|
|
1133
1219
|
resumed: checkpoint !== undefined,
|
|
1134
1220
|
};
|
|
1135
1221
|
// Single-SA runs return exactly as before (no pool checkpoint key, no
|
|
1136
|
-
// pool env line, no pool progress)
|
|
1137
|
-
//
|
|
1222
|
+
// pool env line, no pool progress) — unless a checkpointed pool exists:
|
|
1223
|
+
// resuming a pooled setup with a smaller or default count never removes
|
|
1224
|
+
// entries, so the stored pool (with its kept count) is reported and the
|
|
1225
|
+
// .env above already preserved its pool line. The pool expansion below
|
|
1226
|
+
// runs only when more than one service account was requested.
|
|
1138
1227
|
const saCount = options.saCount ?? 1;
|
|
1139
|
-
|
|
1228
|
+
const checkpointPool = checkpointPoolEntries(checkpoint);
|
|
1229
|
+
const hasPoolKeyInFlight = checkpoint?.status === "complete" && checkpoint.poolKeyStarted !== undefined;
|
|
1230
|
+
if (saCount <= 1 && !hasPoolKeyInFlight) {
|
|
1231
|
+
if (checkpointPool.length > 0) {
|
|
1232
|
+
return {
|
|
1233
|
+
status: "ok",
|
|
1234
|
+
dryRun: false,
|
|
1235
|
+
commands: executed,
|
|
1236
|
+
summary: {
|
|
1237
|
+
...baseSummary,
|
|
1238
|
+
poolSize: checkpointPool.length,
|
|
1239
|
+
poolPaths: checkpointPool.map((entry) => entry.keyPath),
|
|
1240
|
+
poolKeptEntries: checkpointPool.length,
|
|
1241
|
+
},
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1140
1244
|
return {
|
|
1141
1245
|
status: "ok",
|
|
1142
1246
|
dryRun: false,
|
|
@@ -1259,17 +1363,81 @@ async function ensureServiceAccount(runner, executed, progress, phase, input) {
|
|
|
1259
1363
|
return { status: "ok", reused: false };
|
|
1260
1364
|
}
|
|
1261
1365
|
/**
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1265
|
-
|
|
1366
|
+
* Planned key paths for pool entries 2..N next to the primary key path.
|
|
1367
|
+
*
|
|
1368
|
+
* Empty for single-SA runs, so single-account callers are unchanged.
|
|
1369
|
+
*/
|
|
1370
|
+
function plannedPoolKeyPaths(primaryKeyPath, saCount) {
|
|
1371
|
+
const paths = [];
|
|
1372
|
+
for (let index = 2; index <= saCount; index += 1) {
|
|
1373
|
+
paths.push(poolKeyPath(primaryKeyPath, index));
|
|
1374
|
+
}
|
|
1375
|
+
return paths;
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Validates every derived `<saName>-<i>` pool account name for entries
|
|
1379
|
+
* 2..N against the canonical service-account format.
|
|
1380
|
+
*
|
|
1381
|
+
* Returns an `invalid_args` message, or null when the plan is valid. Runs
|
|
1382
|
+
* before any subprocess, API call, or file mutation (dry runs included),
|
|
1383
|
+
* so an over-long `--sa-name` that cannot expand into N valid accounts
|
|
1384
|
+
* fails before the primary resources are mutated. Single-SA runs always
|
|
1385
|
+
* validate (the loop is empty).
|
|
1386
|
+
*/
|
|
1387
|
+
function validatePoolPlan(saName, saCount) {
|
|
1388
|
+
for (let index = 2; index <= saCount; index += 1) {
|
|
1389
|
+
const derived = `${saName}-${index}`;
|
|
1390
|
+
if (!isValidServiceAccountName(derived)) {
|
|
1391
|
+
return (`service-account name "${saName}" is too long to expand into a ${saCount}-account ` +
|
|
1392
|
+
`pool ("${derived}" is not a valid service-account name); use a shorter --sa-name`);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
return null;
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* Additional key paths (entries 2..N) stored in a loaded checkpoint plus
|
|
1399
|
+
* any in-flight pool-key write-ahead. Entry 1 is the primary key and is
|
|
1400
|
+
* already reserved separately.
|
|
1401
|
+
*
|
|
1402
|
+
* Empty unless the checkpoint is `complete` with a pool, so single-SA
|
|
1403
|
+
* resumes are unchanged.
|
|
1266
1404
|
*/
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1405
|
+
function storedPoolKeyPaths(checkpoint) {
|
|
1406
|
+
if (checkpoint?.status !== "complete") {
|
|
1407
|
+
return [];
|
|
1408
|
+
}
|
|
1409
|
+
// Entry 1 is the primary key path, already reserved by `options.keyPath`.
|
|
1410
|
+
const paths = (checkpoint.pool ?? []).slice(1).map((entry) => entry.keyPath);
|
|
1411
|
+
if (checkpoint.poolKeyStarted !== undefined) {
|
|
1412
|
+
paths.push(checkpoint.poolKeyStarted.keyPath);
|
|
1413
|
+
}
|
|
1414
|
+
return paths;
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Additional key paths (entries 2..N) carried by a checkpoint STATE value
|
|
1418
|
+
* plus any in-flight pool-key write-ahead. Entry 1 is reserved separately
|
|
1419
|
+
* as the primary key.
|
|
1420
|
+
*
|
|
1421
|
+
* Empty unless the state is `complete` with a pool.
|
|
1422
|
+
*/
|
|
1423
|
+
function statePoolPaths(state) {
|
|
1424
|
+
if (state.status !== "complete") {
|
|
1425
|
+
return [];
|
|
1426
|
+
}
|
|
1427
|
+
// Entry 1 is the primary key path, already reserved by `options.keyPath`.
|
|
1428
|
+
const paths = (state.pool ?? []).slice(1).map((entry) => entry.keyPath);
|
|
1429
|
+
if (state.poolKeyStarted !== undefined) {
|
|
1430
|
+
paths.push(state.poolKeyStarted.keyPath);
|
|
1431
|
+
}
|
|
1432
|
+
return paths;
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Pool entries stored in a loaded checkpoint.
|
|
1436
|
+
*
|
|
1437
|
+
* Empty for fresh runs and single-SA checkpoints.
|
|
1438
|
+
*/
|
|
1439
|
+
function checkpointPoolEntries(checkpoint) {
|
|
1440
|
+
return checkpoint?.status === "complete" ? (checkpoint.pool ?? []) : [];
|
|
1273
1441
|
}
|
|
1274
1442
|
/** Prompt text for the interactive service-account count question. */
|
|
1275
1443
|
export const SA_COUNT_PROMPT = "Service accounts to create? [1]: ";
|
|
@@ -1328,22 +1496,23 @@ export async function resolveSaCountForSetup(options) {
|
|
|
1328
1496
|
*
|
|
1329
1497
|
* Runs after the primary flow reached `complete`: for each missing entry
|
|
1330
1498
|
* it ensures the `<saName>-<i>` service account exists (the same
|
|
1331
|
-
* ensure/create path as the primary), issues its key
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
* the
|
|
1335
|
-
*
|
|
1336
|
-
*
|
|
1499
|
+
* ensure/create path as the primary), issues its key under a per-entry
|
|
1500
|
+
* write-ahead (the fresh baseline plus a new marker are persisted as
|
|
1501
|
+
* `poolKeyStarted` on the `complete` checkpoint BEFORE the single create,
|
|
1502
|
+
* and only the invocation that just persisted it may create with `fresh`
|
|
1503
|
+
* permission), grants it writer access, verifies access with the same
|
|
1504
|
+
* verifier, and persists the entry to the `complete` checkpoint
|
|
1505
|
+
* IMMEDIATELY (clearing the write-ahead) — a mid-pool failure keeps
|
|
1506
|
+
* entries 1..i-1 so the resume skips them, and a crash at any pool-key
|
|
1507
|
+
* boundary resumes by reconciling the stored marker/baseline with
|
|
1508
|
+
* `reconcile` permission instead of creating a second key (an unmatched
|
|
1509
|
+
* cloud key fails with `key_create_uncertain`; nothing is ever deleted
|
|
1510
|
+
* automatically). Failures use the same error codes as the primary path.
|
|
1337
1511
|
* Finally the `.env` file is rewritten with the comma-separated
|
|
1338
1512
|
* `HIKOUTEI_SYNC_CREDENTIALS` pool line. The bounded key/verify progress
|
|
1339
1513
|
* reporters stay unwired here (the tracker hosts one bounded kind per
|
|
1340
1514
|
* phase): only generic operation events are emitted under
|
|
1341
1515
|
* `pool_expansion`, which never advances the overall ten-phase count.
|
|
1342
|
-
*
|
|
1343
|
-
* // ponytail: no per-pool-SA key write-ahead checkpoint; a crash between
|
|
1344
|
-
* // a pool key create and its pool-entry persist leaves an orphaned cloud
|
|
1345
|
-
* // key that the retry treats as baseline (fails uncertain, never
|
|
1346
|
-
* // duplicates silently). Persist per-SA key markers if this ever matters.
|
|
1347
1516
|
*/
|
|
1348
1517
|
async function expandCredentialPool(runner, executed, progress, api, options, keySleeper, context) {
|
|
1349
1518
|
progress.report({ type: "phase_started", phase: POOL_EXPANSION_PHASE });
|
|
@@ -1377,11 +1546,18 @@ async function expandCredentialPool(runner, executed, progress, api, options, ke
|
|
|
1377
1546
|
}
|
|
1378
1547
|
}
|
|
1379
1548
|
// Keep-all resume: resuming with a smaller --sa-count never removes
|
|
1380
|
-
// entries
|
|
1381
|
-
// pool only grows and the summary reports the actual (larger) size.
|
|
1549
|
+
// entries, and any existing write-ahead is reconciled before returning.
|
|
1382
1550
|
const keptPoolEntries = storedPool?.length ?? 0;
|
|
1383
1551
|
const pool = storedPool !== undefined ? [...storedPool] : [primaryEntry];
|
|
1384
|
-
|
|
1552
|
+
// A crashed pool-key run leaves its write-ahead on the complete
|
|
1553
|
+
// checkpoint: the next run reconciles that entry only (stored
|
|
1554
|
+
// marker/baseline, reconcile permission) and never creates a second key
|
|
1555
|
+
// for it. Only the first missing index can carry a write-ahead — every
|
|
1556
|
+
// persisted entry clears it.
|
|
1557
|
+
const resumedPoolKey = reloaded.state.poolKeyStarted;
|
|
1558
|
+
const resumedPoolKeyIndex = pool.length + 1;
|
|
1559
|
+
const targetSaCount = Math.max(context.saCount, resumedPoolKey === undefined ? 0 : resumedPoolKeyIndex);
|
|
1560
|
+
for (let index = pool.length + 1; index <= targetSaCount; index += 1) {
|
|
1385
1561
|
const saName = `${options.saName}-${index}`;
|
|
1386
1562
|
if (!isValidServiceAccountName(saName)) {
|
|
1387
1563
|
return {
|
|
@@ -1400,25 +1576,79 @@ async function expandCredentialPool(runner, executed, progress, api, options, ke
|
|
|
1400
1576
|
if (ensured.status === "error") {
|
|
1401
1577
|
return ensured;
|
|
1402
1578
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1579
|
+
// Pool-key write-ahead: a resumed write-ahead for THIS index settles
|
|
1580
|
+
// reconcile-only with the stored marker/baseline (no fresh baseline
|
|
1581
|
+
// list — the stored baseline scopes the reconciliation, exactly like
|
|
1582
|
+
// the primary key_create_started resume); otherwise the fresh baseline
|
|
1583
|
+
// is listed and the new marker plus baseline are persisted BEFORE the
|
|
1584
|
+
// single create, and only this invocation (fresh permission) may issue
|
|
1585
|
+
// it.
|
|
1586
|
+
let settled;
|
|
1587
|
+
if (resumedPoolKey !== undefined && index === resumedPoolKeyIndex) {
|
|
1588
|
+
if (resumedPoolKey.index !== index ||
|
|
1589
|
+
resumedPoolKey.saName !== saName ||
|
|
1590
|
+
resumedPoolKey.saEmail !== saEmail ||
|
|
1591
|
+
resumedPoolKey.keyPath !== keyPath) {
|
|
1592
|
+
return {
|
|
1593
|
+
status: "error",
|
|
1594
|
+
error: errorResult(SETUP_ERROR_CODES.SETUP_STATE_INVALID, `the in-flight pool key in ${options.statePath} does not match pool entry ${index}; ` +
|
|
1595
|
+
`remove the setup state and rerun setup`),
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
settled = await settleServiceAccountKey(runner, executed, {
|
|
1599
|
+
keyPath,
|
|
1600
|
+
projectId: context.projectId,
|
|
1601
|
+
saEmail,
|
|
1602
|
+
keyMarker: resumedPoolKey.keyMarker,
|
|
1603
|
+
baseline: resumedPoolKey.keyBaseline,
|
|
1604
|
+
createPermission: "reconcile",
|
|
1605
|
+
sleeper: keySleeper,
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
else {
|
|
1609
|
+
progress.report({ type: "operation_started", phase: POOL_EXPANSION_PHASE, operation: SETUP_PROGRESS_OPERATIONS.KEY_LIST });
|
|
1610
|
+
const keyList = await listUserManagedServiceAccountKeys(runner, executed, {
|
|
1611
|
+
projectId: context.projectId,
|
|
1612
|
+
saEmail,
|
|
1613
|
+
purpose: "baseline",
|
|
1614
|
+
});
|
|
1615
|
+
progress.report({ type: "operation_completed", phase: POOL_EXPANSION_PHASE, operation: SETUP_PROGRESS_OPERATIONS.KEY_LIST });
|
|
1616
|
+
if (keyList.status === "error") {
|
|
1617
|
+
return { status: "error", error: keyList.error };
|
|
1618
|
+
}
|
|
1619
|
+
const keyMarker = generateCreationMarker();
|
|
1620
|
+
progress.report({ type: "operation_started", phase: POOL_EXPANSION_PHASE, operation: SETUP_PROGRESS_OPERATIONS.CHECKPOINT_PERSIST });
|
|
1621
|
+
const writeAheadBase = spreadsheetState(options, context.projectId, context.ownerEmail, context.title, context.primaryEmail, { spreadsheetId: context.spreadsheetId }, "complete", context.projectMode, context.keyOrigin, context.shareOrigin);
|
|
1622
|
+
if (writeAheadBase.status !== "complete") {
|
|
1623
|
+
// Unreachable: the builder returns the requested status; fail
|
|
1624
|
+
// closed rather than create a key without its write-ahead.
|
|
1625
|
+
return {
|
|
1626
|
+
status: "error",
|
|
1627
|
+
error: errorResult(SETUP_ERROR_CODES.SETUP_STATE_INVALID, `could not persist the credential pool in ${options.statePath}; remove the setup state and rerun setup`),
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
const writeAheadError = persistState(options, executed, {
|
|
1631
|
+
...writeAheadBase,
|
|
1632
|
+
pool: [...pool],
|
|
1633
|
+
poolKeyStarted: { index, saName, saEmail, keyPath, keyMarker, keyBaseline: [...keyList.names] },
|
|
1634
|
+
});
|
|
1635
|
+
progress.report({ type: "operation_completed", phase: POOL_EXPANSION_PHASE, operation: SETUP_PROGRESS_OPERATIONS.CHECKPOINT_PERSIST });
|
|
1636
|
+
if (writeAheadError !== null) {
|
|
1637
|
+
return { status: "error", error: writeAheadError };
|
|
1638
|
+
}
|
|
1639
|
+
// This invocation JUST persisted the fresh pool-key write-ahead, so
|
|
1640
|
+
// it is the only one allowed to issue the single key create for
|
|
1641
|
+
// this entry (fresh permission).
|
|
1642
|
+
settled = await settleServiceAccountKey(runner, executed, {
|
|
1643
|
+
keyPath,
|
|
1644
|
+
projectId: context.projectId,
|
|
1645
|
+
saEmail,
|
|
1646
|
+
keyMarker,
|
|
1647
|
+
baseline: keyList.names,
|
|
1648
|
+
createPermission: "fresh",
|
|
1649
|
+
sleeper: keySleeper,
|
|
1650
|
+
});
|
|
1412
1651
|
}
|
|
1413
|
-
const settled = await settleServiceAccountKey(runner, executed, {
|
|
1414
|
-
keyPath,
|
|
1415
|
-
projectId: context.projectId,
|
|
1416
|
-
saEmail,
|
|
1417
|
-
keyMarker: generateCreationMarker(),
|
|
1418
|
-
baseline: keyList.names,
|
|
1419
|
-
createPermission: "fresh",
|
|
1420
|
-
sleeper: keySleeper,
|
|
1421
|
-
});
|
|
1422
1652
|
if (settled.status === "error") {
|
|
1423
1653
|
return settled;
|
|
1424
1654
|
}
|
|
@@ -1509,7 +1739,15 @@ async function expandCredentialPool(runner, executed, progress, api, options, ke
|
|
|
1509
1739
|
progress.report({ type: "operation_started", phase: POOL_EXPANSION_PHASE, operation: SETUP_PROGRESS_OPERATIONS.ENV_WRITE });
|
|
1510
1740
|
let envModified = false;
|
|
1511
1741
|
try {
|
|
1512
|
-
|
|
1742
|
+
// The pool keys are reserved against aliasing here too: the rewritten
|
|
1743
|
+
// .env must never land on (or be read through) a pool key path.
|
|
1744
|
+
const poolPaths = pool.map((entry) => entry.keyPath);
|
|
1745
|
+
const poolKeyPaths = poolPaths.slice(1);
|
|
1746
|
+
const poolCollision = revalidateSetupPaths(options, poolKeyPaths);
|
|
1747
|
+
if (poolCollision !== null) {
|
|
1748
|
+
return { status: "error", error: poolCollision };
|
|
1749
|
+
}
|
|
1750
|
+
const poolWrite = writeSetupEnvFile(options.outputPath, options.keyPath, spreadsheetEditUrl(context.spreadsheetId), [options.statePath, setupLockPath(options.statePath), setupStateTempPath(options.statePath), ...poolKeyPaths], poolPaths);
|
|
1513
1751
|
envModified = poolWrite.modified;
|
|
1514
1752
|
}
|
|
1515
1753
|
catch (error) {
|
|
@@ -1815,8 +2053,16 @@ function uncertainCreateResult(message) {
|
|
|
1815
2053
|
function persistState(options, executed, state) {
|
|
1816
2054
|
// Fail closed if a reserved path alias appeared after the initial
|
|
1817
2055
|
// preflight: the checkpoint write must never be redirected by a symlink
|
|
1818
|
-
// or hardlink that was not there when the run started.
|
|
1819
|
-
|
|
2056
|
+
// or hardlink that was not there when the run started. Planned pool key
|
|
2057
|
+
// paths plus the pool paths carried by the state being saved are
|
|
2058
|
+
// reserved too, so an alias planted mid-run can never redirect a pool
|
|
2059
|
+
// checkpoint write onto a key or the .env.
|
|
2060
|
+
const revalidated = revalidateSetupPaths(options, [
|
|
2061
|
+
...new Set([
|
|
2062
|
+
...plannedPoolKeyPaths(options.keyPath, options.saCount ?? 1),
|
|
2063
|
+
...statePoolPaths(state),
|
|
2064
|
+
]),
|
|
2065
|
+
]);
|
|
1820
2066
|
if (revalidated !== null) {
|
|
1821
2067
|
return revalidated;
|
|
1822
2068
|
}
|