@empire-builder-kit/nx 1.0.0-rc.44 → 1.0.0-rc.45
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/package.json +3 -3
- package/src/blueprints/external-sandbox.d.ts +1 -1
- package/src/cli/main.d.ts +11 -5
- package/src/cli/main.js +88 -29
- package/src/container-images.d.ts +1 -1
- package/src/container-images.js +1 -1
- package/src/executors/deployment-plan/deployment-plan.js +5 -1
- package/src/executors/deployment-plan/schema.json +1 -1
- package/src/executors/fleet/fleet.js +4 -0
- package/src/executors/fleet/schema.json +1 -1
- package/src/executors/local-deploy/federated-credential-transport.d.ts +4 -3
- package/src/executors/local-deploy/federated-credential-transport.js +15 -5
- package/src/foundation/cost-estimate.d.ts +8 -3
- package/src/foundation/cost-estimate.js +42 -19
- package/src/foundation/index.d.ts +1 -0
- package/src/foundation/index.js +1 -0
- package/src/foundation/local-private-smoke-access.js +6 -0
- package/src/foundation/logging.d.ts +3 -1
- package/src/foundation/logging.js +8 -1
- package/src/foundation/observability.d.ts +42 -0
- package/src/foundation/observability.js +181 -0
- package/src/foundation/preflight.js +1 -1
- package/src/generators/function/function.js +5 -2
- package/src/generators/observability-infrastructure.d.ts +2 -0
- package/src/generators/observability-infrastructure.js +31 -0
- package/src/generators/observability-policy.d.ts +4 -0
- package/src/generators/observability-policy.js +23 -0
- package/src/generators/preset/dependency-overrides.d.ts +17 -6
- package/src/generators/preset/dependency-overrides.js +17 -6
- package/src/generators/preset/tool-versions.d.ts +2 -2
- package/src/generators/preset/tool-versions.js +2 -2
- package/src/generators/preset/workspace-files-adopter-skills.js +28 -17
- package/src/generators/preset/workspace-files-agent-development.d.ts +9 -0
- package/src/generators/preset/workspace-files-agent-development.js +42 -0
- package/src/generators/preset/workspace-files-thin-deploy.js +12 -3
- package/src/generators/preset/workspace-files-thin-docs.js +10 -22
- package/src/generators/preset/workspace-files.d.ts +1 -0
- package/src/generators/preset/workspace-files.js +89 -7
- package/src/generators/slice/built-in-plan-alarm-identity.d.ts +173 -0
- package/src/generators/slice/built-in-plan-alarm-identity.js +202 -0
- package/src/generators/slice/built-in-plan-api-contract-files.js +136 -0
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js +1 -1
- package/src/generators/slice/built-in-plan-event-contract-literal.d.ts +6 -0
- package/src/generators/slice/built-in-plan-event-contract-literal.js +52 -0
- package/src/generators/slice/built-in-plan-infrastructure-files.js +304 -144
- package/src/generators/slice/built-in-plan-key-value-retention-infrastructure.js +2 -2
- package/src/generators/slice/built-in-plan-metadata-files.js +33 -1
- package/src/generators/slice/built-in-plan-operations-files.js +106 -6
- package/src/generators/slice/built-in-plan-project-files.js +1 -0
- package/src/generators/slice/built-in-plan-release-files.js +2 -43
- package/src/generators/slice/built-in-plan-reliability-files.js +222 -236
- package/src/generators/slice/built-in-plan-shared.d.ts +29 -3
- package/src/generators/slice/built-in-plan-shared.js +29 -2
- package/src/generators/slice/built-in-plan-web-files.js +18 -1
- package/src/generators/slice/built-in-plan.js +1 -0
- package/src/generators/slice/slice.js +2 -0
- package/src/generators/upgrade/upgrade.d.ts +4 -3
- package/src/generators/upgrade/upgrade.js +50 -26
- package/src/generators/workload/frozen-container.d.ts +1 -1
- package/src/generators/workload/workload.js +5 -1
- package/src/local-dev/dev-command-core.d.ts +24 -3
- package/src/local-dev/dev-command-core.js +124 -12
- package/src/local-dev/errors.d.ts +19 -1
- package/src/local-dev/errors.js +18 -1
- package/src/local-dev/fleet-client.d.ts +64 -6
- package/src/local-dev/fleet-client.js +189 -15
- package/src/local-dev/fleet-process.d.ts +47 -2
- package/src/local-dev/fleet-process.js +248 -31
- package/src/local-dev/fleet-runtime.d.ts +37 -0
- package/src/local-dev/fleet-runtime.js +127 -69
- package/src/local-dev/fleet-session.d.ts +1 -0
- package/src/local-dev/fleet-session.js +42 -3
- package/src/local-dev/fleet-state.d.ts +5 -0
- package/src/local-dev/fleet-state.js +11 -2
- package/src/local-dev/fleet-supervisor.d.ts +6 -0
- package/src/local-dev/fleet-supervisor.js +49 -1
- package/src/local-dev/slice-runner.d.ts +19 -1
- package/src/local-dev/slice-runner.js +290 -91
- package/src/repository-policy/deployment-plan.d.ts +1 -1
- package/src/repository-policy/deployment-plan.js +16 -1
- package/src/secrets/generated-aws-control-plane.d.ts +11 -1
- package/src/secrets/generated-aws-control-plane.js +92 -29
- package/src/secrets/operator.d.ts +3 -2
- package/src/secrets/operator.js +27 -5
- package/src/supply-chain/generated-files.js +9 -0
|
@@ -122,7 +122,7 @@ function validateFleetSliceRunnerState(value) {
|
|
|
122
122
|
'sessionGeneration',
|
|
123
123
|
'slice',
|
|
124
124
|
'updatedAt',
|
|
125
|
-
], ['child', 'failure'], 'Fleet Slice runner state');
|
|
125
|
+
], ['child', 'childExit', 'failure'], 'Fleet Slice runner state');
|
|
126
126
|
if (value.schemaVersion !== exports.FLEET_SLICE_RUNNER_SCHEMA_VERSION ||
|
|
127
127
|
!['failed', 'pending', 'running', 'succeeded'].includes(String(value.cleanup))) {
|
|
128
128
|
return runnerError('Fleet Slice runner state is invalid.');
|
|
@@ -140,11 +140,35 @@ function validateFleetSliceRunnerState(value) {
|
|
|
140
140
|
if (value.child !== undefined) {
|
|
141
141
|
state.child = processIdentity(value.child, 'Fleet Slice runner state child');
|
|
142
142
|
}
|
|
143
|
+
if (value.childExit !== undefined) {
|
|
144
|
+
state.childExit = validateRunnerChildExit(value.childExit);
|
|
145
|
+
}
|
|
143
146
|
if (value.failure !== undefined) {
|
|
144
147
|
state.failure = (0, contract_validation_js_1.requireBoundedString)(value.failure, 'Fleet Slice runner state failure', { maximumLength: 1_024 });
|
|
145
148
|
}
|
|
146
149
|
return state;
|
|
147
150
|
}
|
|
151
|
+
function validateRunnerChildExit(value) {
|
|
152
|
+
(0, contract_validation_js_1.assertRecord)(value, 'Fleet Slice runner child exit');
|
|
153
|
+
(0, contract_validation_js_1.assertExactProperties)(value, ['at', 'code', 'signal'], [], 'Fleet Slice runner child exit');
|
|
154
|
+
const code = value.code === null
|
|
155
|
+
? null
|
|
156
|
+
: (0, contract_validation_js_1.requireInteger)(value.code, 'Fleet Slice runner child exit code', -2_147_483_648, 4_294_967_295);
|
|
157
|
+
const signal = value.signal === null
|
|
158
|
+
? null
|
|
159
|
+
: (0, contract_validation_js_1.requireBoundedString)(value.signal, 'Fleet Slice runner child signal', {
|
|
160
|
+
maximumLength: 32,
|
|
161
|
+
pattern: /^SIG[A-Z0-9]+$/,
|
|
162
|
+
});
|
|
163
|
+
if ((code === null) === (signal === null)) {
|
|
164
|
+
return runnerError('Fleet Slice runner child exit must carry exactly one of status or signal.');
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
at: (0, contract_validation_js_1.requireIsoTimestamp)(value.at, 'Fleet Slice runner child exit at'),
|
|
168
|
+
code,
|
|
169
|
+
signal,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
148
172
|
function runnerStem(slice, runnerGeneration) {
|
|
149
173
|
return `${(0, fleet_lock_js_1.validateFleetIdentitySlug)(slice, 'Fleet Slice runner Slice')}-${(0, contract_validation_js_1.sha256Digest)({ runnerGeneration: generation(runnerGeneration, 'runner generation') }).slice(0, 20)}`;
|
|
150
174
|
}
|
|
@@ -184,8 +208,17 @@ function validateFleetSliceRunnerRestartRequest(value) {
|
|
|
184
208
|
slice: (0, fleet_lock_js_1.validateFleetIdentitySlug)(value.slice, 'Fleet Slice runner restart Slice'),
|
|
185
209
|
};
|
|
186
210
|
}
|
|
211
|
+
/** A runner publishes its state once its Nx plan is loaded, or earlier when
|
|
212
|
+
* quiescence reaches it during the load. Name that condition instead of
|
|
213
|
+
* surfacing the private-file check's missing-file error. */
|
|
214
|
+
function readPublishedFleetSliceRunnerState(statePath, security, consequence) {
|
|
215
|
+
if (!(0, node_fs_1.existsSync)(statePath)) {
|
|
216
|
+
throw new errors_js_1.LocalDevContractError('cleanup-incomplete', `Fleet Slice runner has not published its state; it is still loading its Nx plan or exited before starting. ${consequence}`);
|
|
217
|
+
}
|
|
218
|
+
return readFleetSliceRunnerState(statePath, security);
|
|
219
|
+
}
|
|
187
220
|
function requestFleetSliceRunnerRestart(launched, effects = {}) {
|
|
188
|
-
const state =
|
|
221
|
+
const state = readPublishedFleetSliceRunnerState(launched.statePath, effects.security ?? {}, 'Restart is refused until the Slice reports starting.');
|
|
189
222
|
if (state.fleetId !== launched.request.fleetId ||
|
|
190
223
|
state.runnerGeneration !== launched.request.runnerGeneration ||
|
|
191
224
|
state.sessionGeneration !== launched.request.sessionGeneration ||
|
|
@@ -372,7 +405,8 @@ function runnerParentStatus(request, options = {}) {
|
|
|
372
405
|
? 'lost'
|
|
373
406
|
: 'ambiguous';
|
|
374
407
|
}
|
|
375
|
-
async function runFleetSliceRunnerCore(requestPath, options = {},
|
|
408
|
+
async function runFleetSliceRunnerCore(requestPath, options = {}, control) {
|
|
409
|
+
const { stopSignal } = control;
|
|
376
410
|
const security = options.security ?? {};
|
|
377
411
|
const now = options.now ?? (() => new Date());
|
|
378
412
|
const request = readFleetSliceRunnerRequest(requestPath, security);
|
|
@@ -382,6 +416,47 @@ async function runFleetSliceRunnerCore(requestPath, options = {}, stopSignal) {
|
|
|
382
416
|
startup.workspaceRealpath !== request.workspaceRealpath) {
|
|
383
417
|
return runnerError('Fleet Slice runner request does not match its startup authority.');
|
|
384
418
|
}
|
|
419
|
+
const paths = fleetSliceRunnerPaths((0, node_path_1.join)((0, node_path_1.dirname)(request.startupPath), 'runners'), request.slice, request.runnerGeneration);
|
|
420
|
+
// A runner launched over retained state is a cleanup retry and never
|
|
421
|
+
// starts a writer. Decide that before quiescence can publish fresh state.
|
|
422
|
+
const retry = (0, node_fs_1.existsSync)(paths.state);
|
|
423
|
+
const runner = (options.processIdentity ?? process_identity_js_1.captureProcessIdentity)();
|
|
424
|
+
const pendingState = () => ({
|
|
425
|
+
cleanup: 'pending',
|
|
426
|
+
fleetId: request.fleetId,
|
|
427
|
+
runner,
|
|
428
|
+
runnerGeneration: request.runnerGeneration,
|
|
429
|
+
schemaVersion: exports.FLEET_SLICE_RUNNER_SCHEMA_VERSION,
|
|
430
|
+
sessionGeneration: request.sessionGeneration,
|
|
431
|
+
slice: request.slice,
|
|
432
|
+
updatedAt: now().toISOString(),
|
|
433
|
+
});
|
|
434
|
+
let phase = 'loading';
|
|
435
|
+
let published;
|
|
436
|
+
let quiesceFailure;
|
|
437
|
+
let quiesceRunning = () => undefined;
|
|
438
|
+
if (!retry) {
|
|
439
|
+
control.onQuiesce(() => {
|
|
440
|
+
if (phase === 'loading') {
|
|
441
|
+
// Quiescence reached this runner while it loads its Nx plan. No
|
|
442
|
+
// writer has started and none will: publish pending cleanup
|
|
443
|
+
// authority first, so a later stop runs the declared cleanup here
|
|
444
|
+
// and a restart intent can bind to this exact generation.
|
|
445
|
+
try {
|
|
446
|
+
published = writeFleetSliceRunnerState(paths.state, pendingState(), security);
|
|
447
|
+
phase = 'quiesced';
|
|
448
|
+
control.acknowledgeQuiesce();
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
quiesceFailure = error;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
else if (phase === 'running')
|
|
455
|
+
quiesceRunning();
|
|
456
|
+
else if (phase === 'quiesced')
|
|
457
|
+
control.acknowledgeQuiesce();
|
|
458
|
+
});
|
|
459
|
+
}
|
|
385
460
|
const plan = await (options.loadPlan ?? nx_workspace_js_1.loadWorkspaceFleetPlan)();
|
|
386
461
|
const adapter = plan.adapters.get(request.slice);
|
|
387
462
|
if (!adapter) {
|
|
@@ -390,8 +465,6 @@ async function runFleetSliceRunnerCore(requestPath, options = {}, stopSignal) {
|
|
|
390
465
|
const startTarget = (0, nx_workspace_js_1.declaredStartTarget)(plan, request.slice);
|
|
391
466
|
const cleanupTarget = (0, nx_workspace_js_1.declaredCleanupTarget)(plan, request.slice);
|
|
392
467
|
const runtime = (options.resolveRuntime ?? nx_workspace_js_1.resolveWorkspaceNxRuntime)(request.workspaceRealpath);
|
|
393
|
-
const paths = fleetSliceRunnerPaths((0, node_path_1.join)((0, node_path_1.dirname)(request.startupPath), 'runners'), request.slice, request.runnerGeneration);
|
|
394
|
-
const runner = (options.processIdentity ?? process_identity_js_1.captureProcessIdentity)();
|
|
395
468
|
const origin = new URL(request.origin);
|
|
396
469
|
const childEnvironment = (0, personal_stage_js_1.createPersonalDevelopmentEnvironment)(startup.identity.stage, {
|
|
397
470
|
...process.env,
|
|
@@ -417,9 +490,9 @@ async function runFleetSliceRunnerCore(requestPath, options = {}, stopSignal) {
|
|
|
417
490
|
};
|
|
418
491
|
const terminate = options.terminate ??
|
|
419
492
|
((child) => (0, fleet_process_js_1.terminateOwnedChild)(child, ownedChildren.get(child)));
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
|
|
493
|
+
if (retry) {
|
|
494
|
+
if (stopSignal.aborted)
|
|
495
|
+
throw new Error('Fleet Slice runner stopped before child execution.');
|
|
423
496
|
let previous = readFleetSliceRunnerState(paths.state, security);
|
|
424
497
|
if (previous.fleetId !== request.fleetId ||
|
|
425
498
|
previous.runnerGeneration !== request.runnerGeneration ||
|
|
@@ -488,57 +561,92 @@ async function runFleetSliceRunnerCore(requestPath, options = {}, stopSignal) {
|
|
|
488
561
|
return 1;
|
|
489
562
|
}
|
|
490
563
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
sessionGeneration: request.sessionGeneration,
|
|
498
|
-
slice: request.slice,
|
|
499
|
-
updatedAt: now().toISOString(),
|
|
500
|
-
}, security);
|
|
501
|
-
const child = spawnTarget(runtime, startTarget, {
|
|
502
|
-
additionalEnvironment: childEnvironment,
|
|
503
|
-
port: request.port,
|
|
504
|
-
stage: startup.identity.stage,
|
|
505
|
-
stdio: 'inherit',
|
|
506
|
-
workspaceRoot: request.workspaceRealpath,
|
|
507
|
-
});
|
|
508
|
-
try {
|
|
509
|
-
if (!child.pid)
|
|
510
|
-
throw new Error('Slice child did not expose a PID.');
|
|
511
|
-
state = writeFleetSliceRunnerState(paths.state, {
|
|
512
|
-
...state,
|
|
513
|
-
child: identifyChild(child),
|
|
514
|
-
updatedAt: now().toISOString(),
|
|
515
|
-
}, security);
|
|
564
|
+
if (quiesceFailure !== undefined) {
|
|
565
|
+
// Quiescence was requested but its pending authority could not be
|
|
566
|
+
// published: refuse to start a writer and report the failed runner.
|
|
567
|
+
throw quiesceFailure instanceof Error
|
|
568
|
+
? quiesceFailure
|
|
569
|
+
: new Error(String(quiesceFailure));
|
|
516
570
|
}
|
|
517
|
-
|
|
518
|
-
|
|
571
|
+
let child;
|
|
572
|
+
let state;
|
|
573
|
+
if (phase === 'quiesced' || stopSignal.aborted) {
|
|
574
|
+
// Quiescence or a stop reached this runner while it loaded its plan, so
|
|
575
|
+
// its writer never starts. The stop handled below then runs declared
|
|
576
|
+
// cleanup here, or a restart intent leaves cleanup pending.
|
|
577
|
+
state =
|
|
578
|
+
published ??
|
|
579
|
+
writeFleetSliceRunnerState(paths.state, pendingState(), security);
|
|
580
|
+
phase = 'quiesced';
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
state = writeFleetSliceRunnerState(paths.state, pendingState(), security);
|
|
584
|
+
const started = spawnTarget(runtime, startTarget, {
|
|
585
|
+
additionalEnvironment: childEnvironment,
|
|
586
|
+
port: request.port,
|
|
587
|
+
stage: startup.identity.stage,
|
|
588
|
+
stdio: 'inherit',
|
|
589
|
+
workspaceRoot: request.workspaceRealpath,
|
|
590
|
+
});
|
|
591
|
+
try {
|
|
592
|
+
if (!started.pid)
|
|
593
|
+
throw new Error('Slice child did not expose a PID.');
|
|
594
|
+
state = writeFleetSliceRunnerState(paths.state, {
|
|
595
|
+
...state,
|
|
596
|
+
child: identifyChild(started),
|
|
597
|
+
updatedAt: now().toISOString(),
|
|
598
|
+
}, security);
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
await terminate(started).catch(() => undefined);
|
|
602
|
+
state = writeFleetSliceRunnerState(paths.state, {
|
|
603
|
+
...state,
|
|
604
|
+
cleanup: 'failed',
|
|
605
|
+
failure: error instanceof Error ? error.message : String(error),
|
|
606
|
+
updatedAt: now().toISOString(),
|
|
607
|
+
}, security);
|
|
608
|
+
return 1;
|
|
609
|
+
}
|
|
610
|
+
child = started;
|
|
611
|
+
phase = 'running';
|
|
612
|
+
}
|
|
613
|
+
let finalizing;
|
|
614
|
+
let quiescing;
|
|
615
|
+
let interval;
|
|
616
|
+
let settle = () => undefined;
|
|
617
|
+
let abandon = () => undefined;
|
|
618
|
+
const done = new Promise((resolvePromise, reject) => {
|
|
619
|
+
settle = resolvePromise;
|
|
620
|
+
abandon = reject;
|
|
621
|
+
});
|
|
622
|
+
const recordFailure = (error) => {
|
|
519
623
|
state = writeFleetSliceRunnerState(paths.state, {
|
|
520
624
|
...state,
|
|
521
625
|
cleanup: 'failed',
|
|
522
626
|
failure: error instanceof Error ? error.message : String(error),
|
|
523
627
|
updatedAt: now().toISOString(),
|
|
524
628
|
}, security);
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
629
|
+
};
|
|
630
|
+
// The writer is gone only when its whole owned group is absent.
|
|
631
|
+
const settleChild = async (terminateChild) => {
|
|
632
|
+
if (!child)
|
|
633
|
+
return;
|
|
634
|
+
if (terminateChild)
|
|
635
|
+
await terminate(child);
|
|
636
|
+
if (!child.pid)
|
|
637
|
+
throw new Error('Slice child PID is unavailable.');
|
|
638
|
+
await (0, fleet_process_js_1.assertOwnedGroupAbsent)(child.pid);
|
|
639
|
+
};
|
|
529
640
|
const finalize = (terminateChild, cleanup) => {
|
|
530
641
|
if (finalizing)
|
|
531
642
|
return finalizing;
|
|
643
|
+
phase = 'finalizing';
|
|
644
|
+
if (interval)
|
|
645
|
+
clearInterval(interval);
|
|
532
646
|
finalizing = (async () => {
|
|
533
|
-
if (interval)
|
|
534
|
-
clearInterval(interval);
|
|
535
647
|
if (!cleanup) {
|
|
536
648
|
try {
|
|
537
|
-
|
|
538
|
-
await terminate(child);
|
|
539
|
-
if (!child.pid)
|
|
540
|
-
throw new Error('Slice child PID is unavailable.');
|
|
541
|
-
await (0, fleet_process_js_1.assertOwnedGroupAbsent)(child.pid);
|
|
649
|
+
await settleChild(terminateChild);
|
|
542
650
|
state = writeFleetSliceRunnerState(paths.state, {
|
|
543
651
|
...state,
|
|
544
652
|
cleanup: 'pending',
|
|
@@ -547,22 +655,15 @@ async function runFleetSliceRunnerCore(requestPath, options = {}, stopSignal) {
|
|
|
547
655
|
return 0;
|
|
548
656
|
}
|
|
549
657
|
catch (error) {
|
|
550
|
-
|
|
551
|
-
...state,
|
|
552
|
-
cleanup: 'failed',
|
|
553
|
-
failure: error instanceof Error ? error.message : String(error),
|
|
554
|
-
updatedAt: now().toISOString(),
|
|
555
|
-
}, security);
|
|
658
|
+
recordFailure(error);
|
|
556
659
|
return 1;
|
|
557
660
|
}
|
|
558
661
|
}
|
|
559
662
|
state = writeFleetSliceRunnerState(paths.state, { ...state, cleanup: 'running', updatedAt: now().toISOString() }, security);
|
|
560
663
|
try {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
throw new Error('Slice child PID is unavailable.');
|
|
565
|
-
await (0, fleet_process_js_1.assertOwnedGroupAbsent)(child.pid);
|
|
664
|
+
await settleChild(terminateChild);
|
|
665
|
+
// The cleanup child inherits this runner's stdio, which the Fleet
|
|
666
|
+
// supervisor captures into the Slice log store.
|
|
566
667
|
await runDeclaredCleanupTarget(cleanupTarget, runtime, childEnvironment, startup.identity.stage, request.workspaceRealpath, spawnTarget, (cleanup) => {
|
|
567
668
|
if (!cleanup.pid)
|
|
568
669
|
throw new Error('Cleanup child did not expose a PID.');
|
|
@@ -585,31 +686,94 @@ async function runFleetSliceRunnerCore(requestPath, options = {}, stopSignal) {
|
|
|
585
686
|
return 0;
|
|
586
687
|
}
|
|
587
688
|
catch (error) {
|
|
588
|
-
|
|
589
|
-
...state,
|
|
590
|
-
cleanup: 'failed',
|
|
591
|
-
failure: error instanceof Error ? error.message : String(error),
|
|
592
|
-
updatedAt: now().toISOString(),
|
|
593
|
-
}, security);
|
|
689
|
+
recordFailure(error);
|
|
594
690
|
return 1;
|
|
595
691
|
}
|
|
596
692
|
})();
|
|
693
|
+
void finalizing.then(settle);
|
|
597
694
|
return finalizing;
|
|
598
695
|
};
|
|
599
|
-
|
|
696
|
+
// Every exit request funnels here. A quiescence in progress finishes
|
|
697
|
+
// first, so finalization never races the writer it is still stopping.
|
|
698
|
+
const finish = (cleanup) => {
|
|
699
|
+
if (phase === 'finalizing')
|
|
700
|
+
return;
|
|
701
|
+
if (phase === 'quiescing') {
|
|
702
|
+
void quiescing?.then(() => finish(cleanup), () => undefined);
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
void finalize(phase === 'running', cleanup);
|
|
706
|
+
};
|
|
707
|
+
quiesceRunning = () => {
|
|
708
|
+
if (phase !== 'running')
|
|
709
|
+
return;
|
|
710
|
+
phase = 'quiescing';
|
|
711
|
+
// Stop the writer but keep this runner and its pending cleanup
|
|
712
|
+
// authority: peer writers may still be live, so cleanup waits for an
|
|
713
|
+
// explicit stop after every selected Slice has quiesced.
|
|
714
|
+
quiescing = (async () => {
|
|
715
|
+
await settleChild(true);
|
|
716
|
+
state = writeFleetSliceRunnerState(paths.state, { ...state, cleanup: 'pending', updatedAt: now().toISOString() }, security);
|
|
717
|
+
})();
|
|
718
|
+
quiescing.then(() => {
|
|
719
|
+
if (phase !== 'quiescing')
|
|
720
|
+
return;
|
|
721
|
+
phase = 'quiesced';
|
|
722
|
+
control.acknowledgeQuiesce();
|
|
723
|
+
}, (error) => {
|
|
724
|
+
if (phase !== 'quiescing')
|
|
725
|
+
return;
|
|
726
|
+
phase = 'finalizing';
|
|
727
|
+
if (interval)
|
|
728
|
+
clearInterval(interval);
|
|
729
|
+
try {
|
|
730
|
+
recordFailure(error);
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
// The failed exit status below still reports the refusal.
|
|
734
|
+
}
|
|
735
|
+
finalizing = Promise.resolve(1);
|
|
736
|
+
settle(1);
|
|
737
|
+
});
|
|
738
|
+
};
|
|
739
|
+
const onStop = () => {
|
|
600
740
|
const reason = consumeFleetSliceRunnerRestart(paths.restart, request, security)
|
|
601
741
|
? 'explicit-restart'
|
|
602
742
|
: 'termination';
|
|
603
|
-
|
|
743
|
+
finish(fleetSliceRunnerRequiresCleanup(reason));
|
|
604
744
|
};
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
745
|
+
if (child) {
|
|
746
|
+
const started = child;
|
|
747
|
+
started.once('error', abandon);
|
|
748
|
+
// A child exit is restartable process failure, not consent to destroy
|
|
749
|
+
// local prerequisites. The exact runner request and pending cleanup
|
|
750
|
+
// evidence stay behind so a later explicit down or proved-stale recovery
|
|
751
|
+
// can clean them.
|
|
752
|
+
started.once('exit', (code, signal) => {
|
|
753
|
+
// Owned termination (quiescence, restart, stop, lost parent) changes
|
|
754
|
+
// the phase first; only an exit the runner did not cause is the
|
|
755
|
+
// Slice's own failure cause.
|
|
756
|
+
if (phase !== 'running')
|
|
757
|
+
return;
|
|
758
|
+
if (code !== null || signal !== null) {
|
|
759
|
+
try {
|
|
760
|
+
state = writeFleetSliceRunnerState(paths.state, {
|
|
761
|
+
...state,
|
|
762
|
+
childExit: { at: now().toISOString(), code, signal },
|
|
763
|
+
updatedAt: now().toISOString(),
|
|
764
|
+
}, security);
|
|
765
|
+
}
|
|
766
|
+
catch {
|
|
767
|
+
// The diagnostic is best effort; finalization below owns cleanup.
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
void finalize(false, fleetSliceRunnerRequiresCleanup('child-exit'));
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
stopSignal.addEventListener('abort', onStop, { once: true });
|
|
774
|
+
try {
|
|
775
|
+
// Referenced: a quiesced runner without a child must stay alive to
|
|
776
|
+
// observe its parent until an explicit stop arrives.
|
|
613
777
|
interval = setInterval(() => {
|
|
614
778
|
if (runnerParentStatus(request, {
|
|
615
779
|
graceMs: options.parentGraceMs,
|
|
@@ -617,35 +781,63 @@ async function runFleetSliceRunnerCore(requestPath, options = {}, stopSignal) {
|
|
|
617
781
|
processProbe: options.processProbe?.(request.parent),
|
|
618
782
|
security,
|
|
619
783
|
}) === 'lost') {
|
|
620
|
-
|
|
784
|
+
finish(fleetSliceRunnerRequiresCleanup('parent-lost'));
|
|
621
785
|
}
|
|
622
786
|
}, options.monitorIntervalMs ?? 1_000);
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
(
|
|
787
|
+
if (stopSignal.aborted)
|
|
788
|
+
onStop();
|
|
789
|
+
return await done;
|
|
790
|
+
}
|
|
791
|
+
finally {
|
|
792
|
+
stopSignal.removeEventListener('abort', onStop);
|
|
793
|
+
if (interval)
|
|
794
|
+
clearInterval(interval);
|
|
795
|
+
}
|
|
629
796
|
}
|
|
630
797
|
async function runFleetSliceRunner(requestPath, options = {}) {
|
|
631
798
|
const request = readFleetSliceRunnerRequest(requestPath, options.security);
|
|
632
799
|
const stop = new AbortController();
|
|
800
|
+
let quiesce;
|
|
801
|
+
let quiesceRequested = false;
|
|
633
802
|
const interrupt = () => stop.abort();
|
|
803
|
+
const control = (value, type) => Boolean(value &&
|
|
804
|
+
typeof value === 'object' &&
|
|
805
|
+
Object.keys(value).sort().join(',') === 'runnerGeneration,type' &&
|
|
806
|
+
'type' in value &&
|
|
807
|
+
value.type === type &&
|
|
808
|
+
'runnerGeneration' in value &&
|
|
809
|
+
value.runnerGeneration === request.runnerGeneration);
|
|
634
810
|
const message = (value) => {
|
|
635
|
-
if (value
|
|
636
|
-
typeof value === 'object' &&
|
|
637
|
-
Object.keys(value).sort().join(',') === 'runnerGeneration,type' &&
|
|
638
|
-
'type' in value &&
|
|
639
|
-
value.type === fleet_process_js_1.FLEET_RUNNER_STOP_MESSAGE &&
|
|
640
|
-
'runnerGeneration' in value &&
|
|
641
|
-
value.runnerGeneration === request.runnerGeneration)
|
|
811
|
+
if (control(value, fleet_process_js_1.FLEET_RUNNER_STOP_MESSAGE))
|
|
642
812
|
interrupt();
|
|
813
|
+
else if (control(value, fleet_process_js_1.FLEET_RUNNER_QUIESCE_MESSAGE)) {
|
|
814
|
+
if (quiesce)
|
|
815
|
+
quiesce();
|
|
816
|
+
else
|
|
817
|
+
quiesceRequested = true;
|
|
818
|
+
}
|
|
643
819
|
};
|
|
644
820
|
process.once('SIGINT', interrupt);
|
|
645
821
|
process.once('SIGTERM', interrupt);
|
|
646
822
|
process.on('message', message);
|
|
647
823
|
try {
|
|
648
|
-
return await runFleetSliceRunnerCore(requestPath, options,
|
|
824
|
+
return await runFleetSliceRunnerCore(requestPath, options, {
|
|
825
|
+
acknowledgeQuiesce: () => {
|
|
826
|
+
if (process.connected)
|
|
827
|
+
process.send?.({
|
|
828
|
+
runnerGeneration: request.runnerGeneration,
|
|
829
|
+
type: fleet_process_js_1.FLEET_RUNNER_QUIESCED_MESSAGE,
|
|
830
|
+
}, () => undefined);
|
|
831
|
+
},
|
|
832
|
+
onQuiesce: (handler) => {
|
|
833
|
+
quiesce = handler;
|
|
834
|
+
if (!quiesceRequested)
|
|
835
|
+
return;
|
|
836
|
+
quiesceRequested = false;
|
|
837
|
+
handler();
|
|
838
|
+
},
|
|
839
|
+
stopSignal: stop.signal,
|
|
840
|
+
});
|
|
649
841
|
}
|
|
650
842
|
finally {
|
|
651
843
|
process.removeListener('SIGINT', interrupt);
|
|
@@ -654,7 +846,7 @@ async function runFleetSliceRunner(requestPath, options = {}) {
|
|
|
654
846
|
}
|
|
655
847
|
}
|
|
656
848
|
function assertFleetSliceRunnerCleanup(statePath, expected, security = {}) {
|
|
657
|
-
const state =
|
|
849
|
+
const state = readPublishedFleetSliceRunnerState(statePath, security, 'Its claim remains retained.');
|
|
658
850
|
if (state.cleanup !== 'succeeded' ||
|
|
659
851
|
state.fleetId !== expected.fleetId ||
|
|
660
852
|
state.runnerGeneration !== expected.runnerGeneration ||
|
|
@@ -667,7 +859,7 @@ async function retryFleetSliceRunnerCleanup(launched, effects = {}) {
|
|
|
667
859
|
const security = effects.security ?? {};
|
|
668
860
|
let state;
|
|
669
861
|
try {
|
|
670
|
-
state =
|
|
862
|
+
state = readPublishedFleetSliceRunnerState(launched.statePath, security, 'Its claim remains retained.');
|
|
671
863
|
}
|
|
672
864
|
catch (error) {
|
|
673
865
|
throw new errors_js_1.LocalDevContractError('cleanup-incomplete', `Fleet Slice cleanup retry lacks validated runner state: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -713,9 +905,16 @@ async function retryFleetSliceRunnerCleanup(launched, effects = {}) {
|
|
|
713
905
|
EBK_FLEET_SLICE_RUNNER_REQUEST: launched.requestPath,
|
|
714
906
|
}),
|
|
715
907
|
shell: false,
|
|
716
|
-
stdio:
|
|
908
|
+
stdio: effects.output
|
|
909
|
+
? ['ignore', 'pipe', 'pipe', 'ipc']
|
|
910
|
+
: ['ignore', 'inherit', 'inherit', 'ipc'],
|
|
717
911
|
windowsHide: true,
|
|
718
912
|
});
|
|
913
|
+
const output = effects.output;
|
|
914
|
+
if (output) {
|
|
915
|
+
child.stdout?.on('data', (chunk) => output('stdout', chunk));
|
|
916
|
+
child.stderr?.on('data', (chunk) => output('stderr', chunk));
|
|
917
|
+
}
|
|
719
918
|
// Client observation deadlines do not cancel a native cleanup operation.
|
|
720
919
|
// Explicit signals and generation-bound IPC still stop the coordinator;
|
|
721
920
|
// only its terminal exit and validated receipt can complete this retry.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ConfiguredDeploymentAccounts, DeploymentProfile, ProductionApprovalMode, ProductionFoundationProfile, RepositoryOperatingPolicy } from './policy.js';
|
|
2
2
|
export declare const PROTECTED_DEPLOYMENT_ACTIONS: readonly ["deploy", "refresh", "remove", "unlock"];
|
|
3
|
-
export declare const PROTECTED_DEPLOYMENT_STAGES: readonly ["staging", "production"];
|
|
3
|
+
export declare const PROTECTED_DEPLOYMENT_STAGES: readonly ["development", "staging", "production"];
|
|
4
4
|
export type ProtectedDeploymentAction = (typeof PROTECTED_DEPLOYMENT_ACTIONS)[number];
|
|
5
5
|
export type ProtectedDeploymentStage = (typeof PROTECTED_DEPLOYMENT_STAGES)[number];
|
|
6
6
|
export interface DeploymentPlanProject {
|
|
@@ -9,7 +9,11 @@ exports.PROTECTED_DEPLOYMENT_ACTIONS = [
|
|
|
9
9
|
'remove',
|
|
10
10
|
'unlock',
|
|
11
11
|
];
|
|
12
|
-
exports.PROTECTED_DEPLOYMENT_STAGES = [
|
|
12
|
+
exports.PROTECTED_DEPLOYMENT_STAGES = [
|
|
13
|
+
'development',
|
|
14
|
+
'staging',
|
|
15
|
+
'production',
|
|
16
|
+
];
|
|
13
17
|
function supportsAction(project, action) {
|
|
14
18
|
return Object.prototype.hasOwnProperty.call(project.targets ?? {}, action);
|
|
15
19
|
}
|
|
@@ -20,6 +24,17 @@ function compareCodePoints(left, right) {
|
|
|
20
24
|
return left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
|
|
21
25
|
}
|
|
22
26
|
function createProtectedDeploymentPlan(policy, options) {
|
|
27
|
+
// Only the starter protected pair deploys development through the protected
|
|
28
|
+
// workflow, from protected develop. Isolated profiles rehearse staging there,
|
|
29
|
+
// and the trunk lane protects main alone.
|
|
30
|
+
if (options.stage === 'development') {
|
|
31
|
+
if (policy.deploymentProfile !== 'starter') {
|
|
32
|
+
throw new Error(`Protected development deployment is unavailable for the ${policy.deploymentProfile} deployment profile; its protected develop lifecycle deploys staging.`);
|
|
33
|
+
}
|
|
34
|
+
if ((0, policy_js_1.deliveryLaneOf)(policy) === 'trunk') {
|
|
35
|
+
throw new Error('Protected development deployment is unavailable in the trunk delivery lane; development deploys locally and the protected lifecycle runs from main.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
23
38
|
(0, policy_js_1.configuredDeploymentAccountForStage)(policy.deploymentProfile, policy.deploymentAccounts, options.stage);
|
|
24
39
|
const deploymentAccounts = { ...policy.deploymentAccounts };
|
|
25
40
|
const deploymentRegion = (0, policy_js_1.configuredDeploymentRegion)(policy.deploymentRegion);
|
|
@@ -4,9 +4,18 @@ export type AwsGeneratedSecretCommand = CreateSecretCommand | DescribeSecretComm
|
|
|
4
4
|
export interface AwsGeneratedSecretClient {
|
|
5
5
|
send(command: AwsGeneratedSecretCommand): Promise<unknown>;
|
|
6
6
|
}
|
|
7
|
+
declare const observationPhases: readonly ["inspect", "pre-create", "post-create"];
|
|
8
|
+
declare const observationPredicates: readonly ["response-record", "arn-type", "arn-name", "name", "description", "deleted-date", "rotation-enabled", "tags-shape", "tags-count", "tag-entry", "tag-duplicate", "tag-value", "version-state", "not-found"];
|
|
9
|
+
export interface GeneratedSecretObservationDiagnostic {
|
|
10
|
+
readonly phase: (typeof observationPhases)[number];
|
|
11
|
+
readonly predicate: (typeof observationPredicates)[number];
|
|
12
|
+
}
|
|
13
|
+
/** Copy only static labels; never invoke getters or retain provider detail. */
|
|
14
|
+
export declare function safeGeneratedSecretObservationDiagnostic(value: unknown): GeneratedSecretObservationDiagnostic | undefined;
|
|
7
15
|
export declare class AwsGeneratedSecretInitializationError extends Error {
|
|
8
16
|
readonly code: 'generated-secret-input-invalid' | 'generated-secret-observation-invalid' | 'generated-secret-read-failed' | 'generated-secret-state-conflicting' | 'generated-secret-write-failed';
|
|
9
|
-
|
|
17
|
+
readonly observationDiagnostic?: GeneratedSecretObservationDiagnostic;
|
|
18
|
+
constructor(code: AwsGeneratedSecretInitializationError['code'], observationDiagnostic?: GeneratedSecretObservationDiagnostic);
|
|
10
19
|
}
|
|
11
20
|
/**
|
|
12
21
|
* Generates one declared machine value inside the AWS adapter and atomically
|
|
@@ -21,3 +30,4 @@ export declare function createAwsGeneratedSecretInitializationControlPlane(optio
|
|
|
21
30
|
randomBytes?: (size: number) => Buffer;
|
|
22
31
|
wait?: (milliseconds: number) => Promise<void>;
|
|
23
32
|
}): GeneratedSecretInitializationControlPlane;
|
|
33
|
+
export {};
|