@cat-factory/orchestration 0.328.0 → 0.329.0

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.
Files changed (24) hide show
  1. package/dist/modules/execution/CredentialWaitController.d.ts +51 -0
  2. package/dist/modules/execution/CredentialWaitController.d.ts.map +1 -0
  3. package/dist/modules/execution/CredentialWaitController.js +140 -0
  4. package/dist/modules/execution/CredentialWaitController.js.map +1 -0
  5. package/dist/modules/execution/ExecutionService.d.ts +1230 -0
  6. package/dist/modules/execution/ExecutionService.d.ts.map +1 -1
  7. package/dist/modules/execution/ExecutionService.js +17 -5
  8. package/dist/modules/execution/ExecutionService.js.map +1 -1
  9. package/dist/modules/execution/RunStateMachine.d.ts +6 -0
  10. package/dist/modules/execution/RunStateMachine.d.ts.map +1 -1
  11. package/dist/modules/execution/RunStateMachine.js +13 -3
  12. package/dist/modules/execution/RunStateMachine.js.map +1 -1
  13. package/dist/modules/execution/StepDecisionController.d.ts.map +1 -1
  14. package/dist/modules/execution/StepDecisionController.js +1 -0
  15. package/dist/modules/execution/StepDecisionController.js.map +1 -1
  16. package/dist/modules/execution/gate-window-controllers.d.ts +10 -0
  17. package/dist/modules/execution/gate-window-controllers.d.ts.map +1 -1
  18. package/dist/modules/execution/gate-window-controllers.js +16 -0
  19. package/dist/modules/execution/gate-window-controllers.js.map +1 -1
  20. package/dist/modules/execution/step-park.logic.d.ts +1 -1
  21. package/dist/modules/execution/step-park.logic.d.ts.map +1 -1
  22. package/dist/modules/execution/step-park.logic.js +5 -0
  23. package/dist/modules/execution/step-park.logic.js.map +1 -1
  24. package/package.json +11 -11
@@ -0,0 +1,51 @@
1
+ import type { Clock, ExecutionInstance, ExecutionRepository, Logger, WorkRunner } from '@cat-factory/kernel';
2
+ import type { AdvanceResult } from './advance.js';
3
+ import type { RunStateMachine } from './RunStateMachine.js';
4
+ import type { StepGraph } from './StepGraph.js';
5
+ export interface CredentialWaitControllerDeps {
6
+ stateMachine: RunStateMachine;
7
+ stepGraph: StepGraph;
8
+ executionRepository: ExecutionRepository;
9
+ clock: Clock;
10
+ workRunner: WorkRunner;
11
+ logger?: Logger;
12
+ }
13
+ /**
14
+ * Parks a run whose personal-subscription credential lapsed, and resumes it.
15
+ *
16
+ * A run on an individual-usage model leases its initiator's credential at every dispatch, from an
17
+ * activation with a fixed lifetime. Whatever advanced the run to that dispatch (a human answering
18
+ * a review, a background re-review converging, a timer) is irrelevant here: the lease is refused,
19
+ * and the step parks on the generic approval with `credentialWait` set, instead of failing the run.
20
+ * The person answers it with {@link resume}, which re-mints the activation from their personal
21
+ * password before re-dispatching.
22
+ *
23
+ * {@link guard} is the ONE place the refusal is recognised. The engine routes every call that can
24
+ * dispatch (advance and the three poll entry points) through it, so no dispatch site and no
25
+ * run-advancing route has to remember to check the credential itself.
26
+ */
27
+ export declare class CredentialWaitController {
28
+ private readonly deps;
29
+ private readonly log;
30
+ constructor(deps: CredentialWaitControllerDeps);
31
+ /**
32
+ * Run one engine call that may dispatch a step, parking the run when the dispatch's lease was
33
+ * refused. Anything else (a result or a throw) passes through untouched.
34
+ */
35
+ guard(workspaceId: string, executionId: string, work: () => Promise<AdvanceResult>): Promise<AdvanceResult>;
36
+ /**
37
+ * Park the run's current step on a credential wait. Answers `null` when the run is no longer
38
+ * live (stopped or settled while the dispatch was failing), so the caller keeps the original
39
+ * outcome rather than parking a run nobody is driving.
40
+ */
41
+ private park;
42
+ /**
43
+ * Resume a step parked on a lapsed credential: re-mint the activation through `renew`, clear the
44
+ * park and wake the driver, which re-dispatches the SAME step. The park and the caller are
45
+ * checked before `renew` runs, so a run that is not parked (already resumed, stopped) never gets
46
+ * a fresh credential minted for it, and nobody but the initiator mints one the lease would not
47
+ * read.
48
+ */
49
+ resume(workspaceId: string, executionId: string, actorUserId: string | null, renew: () => Promise<void>): Promise<ExecutionInstance>;
50
+ }
51
+ //# sourceMappingURL=CredentialWaitController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CredentialWaitController.d.ts","sourceRoot":"","sources":["../../../src/modules/execution/CredentialWaitController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,iBAAiB,EACjB,mBAAmB,EACnB,MAAM,EACN,UAAU,EACX,MAAM,qBAAqB,CAAA;AAU5B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AA6C/C,MAAM,WAAW,4BAA4B;IAC3C,YAAY,EAAE,eAAe,CAAA;IAC7B,SAAS,EAAE,SAAS,CAAA;IACpB,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,KAAK,EAAE,KAAK,CAAA;IACZ,UAAU,EAAE,UAAU,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,wBAAwB;IAGvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAE5B,YAA6B,IAAI,EAAE,4BAA4B,EAE9D;IAED;;;OAGG;IACG,KAAK,CACT,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,GACjC,OAAO,CAAC,aAAa,CAAC,CAYxB;IAED;;;;OAIG;YACW,IAAI;IA6BlB;;;;;;OAMG;IACG,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAiC5B;CACF"}
@@ -0,0 +1,140 @@
1
+ import { isCredentialWaitParked, mayResumeCredentialWait } from '@cat-factory/contracts';
2
+ import { ConflictError, CredentialRequiredError, ForbiddenError, assertFound, getErrorReason, noopLogger, } from '@cat-factory/kernel';
3
+ /**
4
+ * The `details.reason` a lease refusal carries. Every dispatch that runs on a personal
5
+ * subscription leases the run's activation, and the lease is the one authority on whether that
6
+ * activation is still live, so this reason is what the park keys on rather than a prediction of
7
+ * which steps will need a credential.
8
+ */
9
+ const LAPSED_REASON = 'password_required';
10
+ /** Whether a thrown error is a step's lease being refused for a lapsed credential. */
11
+ function isLapseError(error) {
12
+ return error instanceof CredentialRequiredError && getErrorReason(error) === LAPSED_REASON;
13
+ }
14
+ /**
15
+ * Whether a settled step result is the same refusal, after an async dispatch site classified the
16
+ * throw into a `preflight` failure (see `classifyDispatchFailure`).
17
+ */
18
+ function isLapseResult(result) {
19
+ return (result.kind === 'job_failed' &&
20
+ result.failureKind === 'preflight' &&
21
+ result.reason === LAPSED_REASON);
22
+ }
23
+ /**
24
+ * The approval id of a LIVE credential park, or null. Only a `blocked` run counts: a run stopped
25
+ * while parked keeps the step exactly as it stood, and resuming it would re-mint a credential for
26
+ * a run nobody is driving.
27
+ */
28
+ function parkedApprovalId(instance) {
29
+ const step = instance.steps[instance.currentStep];
30
+ if (instance.status !== 'blocked' || !step || !isCredentialWaitParked(step))
31
+ return null;
32
+ return step.approval.id;
33
+ }
34
+ function notParked() {
35
+ return new ConflictError('This run is not waiting on a personal subscription credential.', 'credential_wait_not_parked');
36
+ }
37
+ /**
38
+ * Parks a run whose personal-subscription credential lapsed, and resumes it.
39
+ *
40
+ * A run on an individual-usage model leases its initiator's credential at every dispatch, from an
41
+ * activation with a fixed lifetime. Whatever advanced the run to that dispatch (a human answering
42
+ * a review, a background re-review converging, a timer) is irrelevant here: the lease is refused,
43
+ * and the step parks on the generic approval with `credentialWait` set, instead of failing the run.
44
+ * The person answers it with {@link resume}, which re-mints the activation from their personal
45
+ * password before re-dispatching.
46
+ *
47
+ * {@link guard} is the ONE place the refusal is recognised. The engine routes every call that can
48
+ * dispatch (advance and the three poll entry points) through it, so no dispatch site and no
49
+ * run-advancing route has to remember to check the credential itself.
50
+ */
51
+ export class CredentialWaitController {
52
+ deps;
53
+ log;
54
+ constructor(deps) {
55
+ this.deps = deps;
56
+ this.log = deps.logger ?? noopLogger;
57
+ }
58
+ /**
59
+ * Run one engine call that may dispatch a step, parking the run when the dispatch's lease was
60
+ * refused. Anything else (a result or a throw) passes through untouched.
61
+ */
62
+ async guard(workspaceId, executionId, work) {
63
+ let result;
64
+ try {
65
+ result = await work();
66
+ }
67
+ catch (error) {
68
+ if (!isLapseError(error))
69
+ throw error;
70
+ const parked = await this.park(workspaceId, executionId);
71
+ if (!parked)
72
+ throw error;
73
+ return parked;
74
+ }
75
+ if (!isLapseResult(result))
76
+ return result;
77
+ return (await this.park(workspaceId, executionId)) ?? result;
78
+ }
79
+ /**
80
+ * Park the run's current step on a credential wait. Answers `null` when the run is no longer
81
+ * live (stopped or settled while the dispatch was failing), so the caller keeps the original
82
+ * outcome rather than parking a run nobody is driving.
83
+ */
84
+ async park(workspaceId, executionId) {
85
+ let decisionId = null;
86
+ const instance = await this.deps.stateMachine.mutateInstance(workspaceId, executionId, (inst) => {
87
+ decisionId = null;
88
+ const step = inst.steps[inst.currentStep];
89
+ if (!step || (inst.status !== 'running' && inst.status !== 'paused'))
90
+ return;
91
+ step.credentialWait = { raisedAt: this.deps.clock.now() };
92
+ decisionId = this.deps.stateMachine.openDecisionPark(step, "The run's personal subscription credential lapsed.");
93
+ inst.status = 'blocked';
94
+ });
95
+ if (!decisionId)
96
+ return null;
97
+ await this.deps.stateMachine.updateBlockProgress(workspaceId, instance, 'blocked');
98
+ this.log.info('run parked: personal subscription credential lapsed', {
99
+ workspaceId,
100
+ runId: executionId,
101
+ stepIndex: instance.currentStep,
102
+ });
103
+ await this.deps.stateMachine.emitInstance(workspaceId, instance);
104
+ await this.deps.stateMachine.ensureWaitingNotification(workspaceId, instance);
105
+ return { kind: 'awaiting_decision', decisionId };
106
+ }
107
+ /**
108
+ * Resume a step parked on a lapsed credential: re-mint the activation through `renew`, clear the
109
+ * park and wake the driver, which re-dispatches the SAME step. The park and the caller are
110
+ * checked before `renew` runs, so a run that is not parked (already resumed, stopped) never gets
111
+ * a fresh credential minted for it, and nobody but the initiator mints one the lease would not
112
+ * read.
113
+ */
114
+ async resume(workspaceId, executionId, actorUserId, renew) {
115
+ const current = assertFound(await this.deps.executionRepository.get(workspaceId, executionId), 'Execution', executionId);
116
+ if (!parkedApprovalId(current))
117
+ throw notParked();
118
+ if (!mayResumeCredentialWait(current.initiatedBy, actorUserId)) {
119
+ throw new ForbiddenError('Only the person who started this run can renew its personal subscription credential.', { reason: 'credential_wait_not_initiator' });
120
+ }
121
+ await renew();
122
+ let approvalId = '';
123
+ const instance = await this.deps.stateMachine.mutateInstance(workspaceId, executionId, (inst) => {
124
+ const parked = parkedApprovalId(inst);
125
+ if (!parked)
126
+ throw notParked();
127
+ approvalId = parked;
128
+ const step = inst.steps[inst.currentStep];
129
+ step.credentialWait = null;
130
+ step.approval = null;
131
+ this.deps.stepGraph.startStep(step);
132
+ inst.status = 'running';
133
+ });
134
+ await this.deps.stateMachine.updateBlockProgress(workspaceId, instance, 'in_progress');
135
+ await this.deps.stateMachine.emitInstance(workspaceId, instance);
136
+ await this.deps.workRunner.signalDecision(workspaceId, instance.id, approvalId, 'approved');
137
+ return instance;
138
+ }
139
+ }
140
+ //# sourceMappingURL=CredentialWaitController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CredentialWaitController.js","sourceRoot":"","sources":["../../../src/modules/execution/CredentialWaitController.ts"],"names":[],"mappings":"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"}