@agentxm/workspace-operations 0.28.12 → 0.28.13

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 (35) hide show
  1. package/dist/src/index.d.ts +11 -10
  2. package/dist/src/index.js +16 -12
  3. package/dist/src/operations/scan-plan-readiness.d.ts +1 -1
  4. package/dist/src/operations/scan-plan-readiness.js +1 -1
  5. package/dist/src/plan/apply-plan.js +8 -8
  6. package/dist/src/plan/execution-candidate.d.ts +7 -1
  7. package/dist/src/plan/execution-candidate.js +9 -2
  8. package/dist/src/plan/interruption-resolution.d.ts +50 -0
  9. package/dist/src/plan/interruption-resolution.js +158 -0
  10. package/dist/src/plan/operation-journal.d.ts +9 -9
  11. package/dist/src/plan/operation-journal.js +5 -20
  12. package/dist/src/plan/operation-resolution.d.ts +1 -1
  13. package/dist/src/plan/plan-execution-fixtures.d.ts +2 -2
  14. package/dist/src/plan/plan-execution-fixtures.js +2 -2
  15. package/dist/src/plan/plan-execution.d.ts +48 -0
  16. package/dist/src/plan/plan-execution.js +40 -0
  17. package/dist/src/plan/plan.d.ts +9 -19
  18. package/dist/src/plan/resolve-plan-interaction.d.ts +1 -1
  19. package/dist/src/plan/resolve-plan-interaction.js +1 -1
  20. package/dist/src/plan/resolve-plan.d.ts +38 -23
  21. package/dist/src/plan/resolve-plan.js +114 -96
  22. package/dist/src/plan/step-failure-conversions.d.ts +2 -1
  23. package/dist/src/testing.d.ts +22 -1
  24. package/dist/src/testing.js +23 -1
  25. package/package.json +12 -16
  26. package/dist/src/live.d.ts +0 -13
  27. package/dist/src/live.js +0 -12
  28. package/dist/src/operations/load-workspace.d.ts +0 -44
  29. package/dist/src/operations/load-workspace.js +0 -80
  30. package/dist/src/operations/memory-transition-lock.d.ts +0 -20
  31. package/dist/src/operations/memory-transition-lock.js +0 -57
  32. package/dist/src/operations/transaction.d.ts +0 -60
  33. package/dist/src/operations/transaction.js +0 -358
  34. package/dist/src/operations/transition-lock.d.ts +0 -72
  35. package/dist/src/operations/transition-lock.js +0 -298
@@ -1,31 +1,32 @@
1
1
  /**
2
2
  * @agentxm/workspace-operations public API.
3
3
  *
4
- * The workspace-operations kernel: plan vocabulary and execution, the
5
- * interactive preview/apply orchestration (`previewOrApplyPlan`), operation
6
- * resolutions and journals, plan readiness and reconciliation gating, and the
7
- * workspace transaction and transition-lock machinery. The composed workspace
8
- * layer lives behind `./live`.
4
+ * The workspace-operations capability: plan vocabulary and execution, the
5
+ * two-phase candidate orchestration (`prepareExecutionCandidate` then
6
+ * `resolveExecutionCandidate`), operation resolutions and journals, and plan
7
+ * readiness and reconciliation gating. Transactions and the transition lock
8
+ * live in `@agentxm/workspace-transactions`; the composed workspace layer in
9
+ * `@agentxm/workspace-state/live`.
9
10
  *
10
11
  * @experimental This API is unstable and may change without notice.
11
12
  * @packageDocumentation
12
13
  */
13
14
  export { BlockingClassSchema, defaultOperationPresentation, operationPresentation, presentationOf, ArtifactMechanismSchema, ConfirmableConsentSchema, OperationPreconditionSchema, PlanPolicyIdSchema, PlanPolicyIds, PlanRiskConditionSchema, } from "./plan/plan.js";
14
- export type { ArtifactMechanism, BlockingClass, CompletedJobStep, ConfirmableConsent, ErrorJobStep, ExecutedJob, ExecutedPlan, Job, JobStepArtifact, JobStepArtifactSource, JobStepArtifactTarget, JobStepResult, Operation, OperationPrecondition, OperationPresentation, Plan, PlanPolicyId, PlanRiskCondition, PlannedJobStep, ReadyJobStep, RegistryBindingProposal, RegistryLifecycleEvidence, UnitBlocking, WarnJobStep, } from "./plan/plan.js";
15
+ export type { ArtifactMechanism, BlockingClass, CompletedJobStep, ConfirmableConsent, ErrorJobStep, ExecutedJob, ExecutedPlan, Job, JobStepArtifact, JobStepArtifactSource, JobStepArtifactTarget, JobStepResult, Operation, OperationPrecondition, OperationPresentation, Plan, PlanExecutionCapabilities, PlanPolicyId, PlanRiskCondition, PlannedJobStep, ReadyJobStep, RegistryLifecycleEvidence, UnitBlocking, WarnJobStep, } from "./plan/plan.js";
15
16
  export { AtomicityClassSchema, OperationOutcomeSchema, OperationPhaseSchema, UnitDispositionSchema, UnitStateSchema, countUnitStates, declaredAtomicity, deriveOperationOutcome, executedUnits, makeOperationResolution, plannedUnits, unitIdOf, unitsByStableIdentity, } from "./plan/operation-resolution.js";
16
17
  export type { AtomicityClass, MakeOperationResolutionArgs, OperationAtomicity, OperationBlock, OperationFootprintEntry, OperationInterruption, OperationOutcome, OperationPhase, OperationRecovery, OperationResolution, ResolvedUnit, UnitDisposition, UnitState, UnitStateCounts, } from "./plan/operation-resolution.js";
17
18
  export { OperationJournal, appendResolvedUnit, appendStartedUnit, recordJournalPhase, getOperationJournal, makeOperationJournal, recordOperationJournal, updateOperationJournal, type OperationJournalService, type OperationJournalState, } from "./plan/operation-journal.js";
18
19
  export { applyPlan, type ApplyPlanOptions, type OperationHandler } from "./plan/apply-plan.js";
19
20
  export { CurrentOperationUnit, OperationEventSchema, OperationLifecycle, OperationModeSchema, ProgressUnitSchema, SettledOutcomeSchema, awaitDrained, lifecycleEvents, makeOperationLifecycle, makeThrottledUnitProgress, observeChildUnit, observeUnit, publishOperationEvent, publishPhaseStarted, publishUnitProgress, publishWaitEnded, publishWaiting, settleOperation, subscribeLossless, type ObservedUnit, type OperationEvent, type OperationEventEncoded, type OperationEventInput, type OperationLifecycleService, type OperationMode, type ProgressUnit, type SettledOutcome, } from "./plan/operation-events.js";
20
21
  export { ApprovalRecoveryMissing, CandidateFingerprintFailed, OPERATION_ERROR_CATEGORIES, OperationErrorCategorySchema, PlanInteractionFailed, STALE_CANDIDATE_DETAIL, StaleExecutionCandidate, StepFailure, type OperationErrorCategory, } from "./plan/errors.js";
21
- export { previewOrApplyPlan } from "./plan/resolve-plan.js";
22
+ export { prepareExecutionCandidate, resolveExecutionCandidate, type PrepareExecutionCandidateOptions, type ResolveExecutionCandidateOptions, } from "./plan/resolve-plan.js";
23
+ export { candidateFingerprintFailedToStepFailure, configuredAgentOutcomesUnavailableToStepFailure, restorationIncompleteToStepFailure, workspaceStateReadFailureToStepFailure, workspaceTransactionFailureToStepFailure, } from "./plan/step-failure-conversions.js";
22
24
  export { ResolvePlanInteraction, type ApplyConfirmation, type ResolvePlanInteractionService, } from "./plan/resolve-plan-interaction.js";
23
25
  export { InterruptionSignalSource, type InterruptionSignalSourceService, } from "./plan/interruption-signal.js";
26
+ export { resolveInterruption, type InterruptedInvocation, type ObservedFootprintEntry, } from "./plan/interruption-resolution.js";
24
27
  export { isExecutionCandidateFresh, makeExecutionCandidate, type ExecutionCandidate, } from "./plan/execution-candidate.js";
25
- export { applyPlanExecution, confirmationRecoverySuggestions, namedPolicyRecoverySuggestions, credentialFreeLocatorRecoveryValue, previewPlanExecution, protectedRecoveryValue, publicRecoveryValue, recoveryOption, recoveryPositional, recoverySwitch, renderConfirmationRecoveryCommand, unclassifiedRecoveryValue, type ConfirmableRiskApproval, type ConfirmationRecovery, type ConfirmationRecoveryArgument, type ConfirmationRecoveryValue, type ConfiguredAgentOperation, type PlanExecution, type PlanExecutionRequest, type RecoveryApproval, } from "./plan/plan-execution.js";
28
+ export { applyPlanExecution, confirmableRiskApproval, confirmationRecoverySuggestions, namedPolicyRecoverySuggestions, credentialFreeLocatorRecoveryValue, previewPlanExecution, protectedRecoveryValue, publicRecoveryValue, recoveryOption, recoveryPositional, recoverySwitch, renderConfirmationRecoveryCommand, requestedPlanExecution, unclassifiedRecoveryValue, type ConfirmableRiskApproval, type ConfirmationRecovery, type ConfirmationRecoveryArgument, type ConfirmationRecoveryValue, type ConfiguredAgentOperation, type PlanExecution, type PlanExecutionRequest, type RecoveryApproval, type RequestedApproval, type RequestedPlanIntent, } from "./plan/plan-execution.js";
26
29
  export * from "./plan/job-step-message.js";
27
30
  export { scanPlanReadiness, type PlanReadinessReport } from "./operations/scan-plan-readiness.js";
28
31
  export { augmentPlanWithReconciliation, type AugmentedPlanResult, type DegradedLockfileState, } from "./operations/augment-plan.js";
29
- export { rollbackWorkspaceClosure, runWorkspaceTransaction, settleWorkspaceClosure, withWorkspaceClosure, type WorkspaceTransactionArgs, } from "./operations/transaction.js";
30
- export { liveWorkspaceTransitionLock, type WorkspaceTransitionLock, TRANSITION_WAIT_BOUND_MILLIS, acquireWorkspaceTransitionLock, heldWorkspaceTransition, isWorkspaceTransitionHeldByThisInvocation, transitionLockPath, type HeldWorkspaceTransition, } from "./operations/transition-lock.js";
31
32
  //# sourceMappingURL=index.d.ts.map
package/dist/src/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * @agentxm/workspace-operations public API.
3
3
  *
4
- * The workspace-operations kernel: plan vocabulary and execution, the
5
- * interactive preview/apply orchestration (`previewOrApplyPlan`), operation
6
- * resolutions and journals, plan readiness and reconciliation gating, and the
7
- * workspace transaction and transition-lock machinery. The composed workspace
8
- * layer lives behind `./live`.
4
+ * The workspace-operations capability: plan vocabulary and execution, the
5
+ * two-phase candidate orchestration (`prepareExecutionCandidate` then
6
+ * `resolveExecutionCandidate`), operation resolutions and journals, and plan
7
+ * readiness and reconciliation gating. Transactions and the transition lock
8
+ * live in `@agentxm/workspace-transactions`; the composed workspace layer in
9
+ * `@agentxm/workspace-state/live`.
9
10
  *
10
11
  * @experimental This API is unstable and may change without notice.
11
12
  * @packageDocumentation
@@ -23,21 +24,24 @@ export { applyPlan } from "./plan/apply-plan.js";
23
24
  export { CurrentOperationUnit, OperationEventSchema, OperationLifecycle, OperationModeSchema, ProgressUnitSchema, SettledOutcomeSchema, awaitDrained, lifecycleEvents, makeOperationLifecycle, makeThrottledUnitProgress, observeChildUnit, observeUnit, publishOperationEvent, publishPhaseStarted, publishUnitProgress, publishWaitEnded, publishWaiting, settleOperation, subscribeLossless, } from "./plan/operation-events.js";
24
25
  // Serialized error vocabulary and the plan-family tagged errors.
25
26
  export { ApprovalRecoveryMissing, CandidateFingerprintFailed, OPERATION_ERROR_CATEGORIES, OperationErrorCategorySchema, PlanInteractionFailed, STALE_CANDIDATE_DETAIL, StaleExecutionCandidate, StepFailure, } from "./plan/errors.js";
26
- // Interactive preview/apply orchestration over the workspace read model.
27
- export { previewOrApplyPlan } from "./plan/resolve-plan.js";
27
+ // Candidate preparation and interactive preview/apply resolution over the
28
+ // workspace read model: preview, confirmation and apply share one candidate,
29
+ // so what a person reads and what is applied are the same decision.
30
+ export { prepareExecutionCandidate, resolveExecutionCandidate, } from "./plan/resolve-plan.js";
31
+ // Conversions from the kernel families a step can surface into the
32
+ // serialized step vocabulary, so a producer serializes them identically
33
+ // wherever the step was assembled.
34
+ export { candidateFingerprintFailedToStepFailure, configuredAgentOutcomesUnavailableToStepFailure, restorationIncompleteToStepFailure, workspaceStateReadFailureToStepFailure, workspaceTransactionFailureToStepFailure, } from "./plan/step-failure-conversions.js";
28
35
  // Interaction port for preview/apply presentation, progress, and confirmation.
29
36
  // The CLI runtime provides the Live implementation.
30
37
  export { ResolvePlanInteraction, } from "./plan/resolve-plan-interaction.js";
31
38
  export { InterruptionSignalSource, } from "./plan/interruption-signal.js";
39
+ export { resolveInterruption, } from "./plan/interruption-resolution.js";
32
40
  export { isExecutionCandidateFresh, makeExecutionCandidate, } from "./plan/execution-candidate.js";
33
- export { applyPlanExecution, confirmationRecoverySuggestions, namedPolicyRecoverySuggestions, credentialFreeLocatorRecoveryValue, previewPlanExecution, protectedRecoveryValue, publicRecoveryValue, recoveryOption, recoveryPositional, recoverySwitch, renderConfirmationRecoveryCommand, unclassifiedRecoveryValue, } from "./plan/plan-execution.js";
41
+ export { applyPlanExecution, confirmableRiskApproval, confirmationRecoverySuggestions, namedPolicyRecoverySuggestions, credentialFreeLocatorRecoveryValue, previewPlanExecution, protectedRecoveryValue, publicRecoveryValue, recoveryOption, recoveryPositional, recoverySwitch, renderConfirmationRecoveryCommand, requestedPlanExecution, unclassifiedRecoveryValue, } from "./plan/plan-execution.js";
34
42
  // Job step messaging
35
43
  export * from "./plan/job-step-message.js";
36
44
  // Plan readiness and reconciliation gating
37
45
  export { scanPlanReadiness } from "./operations/scan-plan-readiness.js";
38
46
  export { augmentPlanWithReconciliation, } from "./operations/augment-plan.js";
39
- // Workspace transaction runner and closure lifecycle
40
- export { rollbackWorkspaceClosure, runWorkspaceTransaction, settleWorkspaceClosure, withWorkspaceClosure, } from "./operations/transaction.js";
41
- // Transition lock runtime
42
- export { liveWorkspaceTransitionLock, TRANSITION_WAIT_BOUND_MILLIS, acquireWorkspaceTransitionLock, heldWorkspaceTransition, isWorkspaceTransitionHeldByThisInvocation, transitionLockPath, } from "./operations/transition-lock.js";
43
47
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Pure function to scan a plan's steps and collect readiness statistics.
3
3
  *
4
- * Extracts from the inline logic in `previewOrApplyPlan` so it can be tested
4
+ * Extracts from the inline logic in `resolveExecutionCandidate` so it can be tested
5
5
  * independently and reused from core without CLI dependencies.
6
6
  *
7
7
  * @experimental This API is unstable and may change without notice.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Pure function to scan a plan's steps and collect readiness statistics.
3
3
  *
4
- * Extracts from the inline logic in `previewOrApplyPlan` so it can be tested
4
+ * Extracts from the inline logic in `resolveExecutionCandidate` so it can be tested
5
5
  * independently and reused from core without CLI dependencies.
6
6
  *
7
7
  * @experimental This API is unstable and may change without notice.
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import * as Array from "effect/Array";
16
16
  import * as Effect from "effect/Effect";
17
+ import * as Ref from "effect/Ref";
17
18
  import { StepFailure } from "./errors.js";
18
19
  // -----------------------------------------------------------------------------
19
20
  // Implementation
@@ -202,10 +203,12 @@ export const applyPlan = (plan, options) => Effect.gen(function* () {
202
203
  const observeStep = options?.onStepCompleted ?? (() => Effect.void);
203
204
  const observeStart = options?.onStepStarted ?? (() => Effect.void);
204
205
  const hasReadinessError = plan.jobs.some((job) => job.steps.some((step) => step.readiness === "error"));
205
- let blocked = false;
206
+ // Any job with an error blocks every later job; the fact travels in a
207
+ // `Ref` because it crosses the job traversal's iteration boundary.
208
+ const blocked = yield* Ref.make(false);
206
209
  const jobResults = hasReadinessError
207
210
  ? applyReadinessGate(plan)
208
- : yield* Effect.forEach(plan.jobs, (job) => blocked
211
+ : yield* Effect.forEach(plan.jobs, (job) => Effect.flatMap(Ref.get(blocked), (isBlocked) => isBlocked
209
212
  ? Effect.succeed(job.steps.map((step) => blockStep(step, "blocked by earlier job failure", {
210
213
  class: "operation-aborted",
211
214
  })))
@@ -213,12 +216,9 @@ export const applyPlan = (plan, options) => Effect.gen(function* () {
213
216
  ? executeDependencyAwareJob(job, observeStart, observeStep)
214
217
  : job.executionPolicy === "best-effort"
215
218
  ? executeBestEffortJob(job, observeStart, observeStep)
216
- : executeFailFastJob(job, observeStart, observeStep)).pipe(Effect.tap((steps) => {
217
- if (steps.some((step) => step.result.result === "error")) {
218
- blocked = true;
219
- }
220
- return Effect.void;
221
- })), { concurrency: 1 });
219
+ : executeFailFastJob(job, observeStart, observeStep)).pipe(Effect.tap((steps) => steps.some((step) => step.result.result === "error")
220
+ ? Ref.set(blocked, true)
221
+ : Effect.void))), { concurrency: 1 });
222
222
  return {
223
223
  _tag: "ExecutedPlan",
224
224
  name: plan.name,
@@ -3,6 +3,7 @@ import * as Path from "effect/Path";
3
3
  import * as Effect from "effect/Effect";
4
4
  import { CandidateFingerprintFailed } from "./errors.js";
5
5
  import type { Plan } from "./plan.js";
6
+ import type { ConfiguredAgentOperation } from "./plan-execution.js";
6
7
  export interface ExecutionCandidate<Requirements = never, Output = never> {
7
8
  readonly id: string;
8
9
  readonly plan: Plan<Requirements, Output>;
@@ -10,11 +11,16 @@ export interface ExecutionCandidate<Requirements = never, Output = never> {
10
11
  readonly materialFingerprint: string;
11
12
  /** Base the material fingerprint is relative to; freshness recomputes against it. */
12
13
  readonly baseDir: string;
14
+ /**
15
+ * Configured-agent operations whose outcomes the candidate projected into
16
+ * its steps and verifies after apply.
17
+ */
18
+ readonly configuredAgentOperations: ReadonlyArray<ConfiguredAgentOperation>;
13
19
  }
14
20
  export declare const makeExecutionCandidate: <Requirements, Output>(plan: Plan<Requirements, Output>, paths: {
15
21
  readonly settingsPath: string;
16
22
  readonly lockPath: string;
17
23
  readonly baseDir: string;
18
- }) => Effect.Effect<ExecutionCandidate<Requirements, Output>, CandidateFingerprintFailed, FileSystem.FileSystem | Path.Path>;
24
+ }, configuredAgentOperations?: ReadonlyArray<ConfiguredAgentOperation>) => Effect.Effect<ExecutionCandidate<Requirements, Output>, CandidateFingerprintFailed, FileSystem.FileSystem | Path.Path>;
19
25
  export declare const isExecutionCandidateFresh: (candidate: ExecutionCandidate<unknown, unknown>) => Effect.Effect<boolean, CandidateFingerprintFailed, FileSystem.FileSystem | Path.Path>;
20
26
  //# sourceMappingURL=execution-candidate.d.ts.map
@@ -73,7 +73,7 @@ const planIdentity = (plan, baseDir, path) => JSON.stringify({
73
73
  ? path.relative(baseDir, value)
74
74
  : value;
75
75
  });
76
- export const makeExecutionCandidate = (plan, paths) => Effect.gen(function* () {
76
+ export const makeExecutionCandidate = (plan, paths, configuredAgentOperations = []) => Effect.gen(function* () {
77
77
  const fs = yield* FileSystem.FileSystem;
78
78
  const path = yield* Path.Path;
79
79
  const materialPaths = resolveMaterialPaths(plan, paths.settingsPath, paths.lockPath, paths.baseDir, path);
@@ -84,7 +84,14 @@ export const makeExecutionCandidate = (plan, paths) => Effect.gen(function* () {
84
84
  .update("\0")
85
85
  .update(materialFingerprint)
86
86
  .digest("hex");
87
- return { id, plan, materialPaths, materialFingerprint, baseDir: paths.baseDir };
87
+ return {
88
+ id,
89
+ plan,
90
+ materialPaths,
91
+ materialFingerprint,
92
+ baseDir: paths.baseDir,
93
+ configuredAgentOperations,
94
+ };
88
95
  });
89
96
  export const isExecutionCandidateFresh = (candidate) => Effect.gen(function* () {
90
97
  const fs = yield* FileSystem.FileSystem;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The terminal resolution of an interrupted operation.
3
+ *
4
+ * Interruption is the one lifecycle fact the operation body cannot settle for
5
+ * itself: the signal arrives from outside it. Turning the operation journal
6
+ * into a contract-valid resolution — which closures settled, which were in
7
+ * flight, what durable state is retained, and which atomicity class actually
8
+ * held — is execution semantics, so it lives beside closure execution rather
9
+ * than in a transport adapter. The caller supplies only the resume command it
10
+ * would print, because how an operator re-runs a request is a property of the
11
+ * surface that accepted it.
12
+ *
13
+ * @experimental This API is unstable and may change without notice.
14
+ */
15
+ import type * as Option from "effect/Option";
16
+ import { type AtomicityClass, type OperationResolution } from "./operation-resolution.js";
17
+ import type { OperationPresentation } from "./plan.js";
18
+ import type { OperationJournalState } from "./operation-journal.js";
19
+ /** What the invoking surface knows about a request the signal cut short. */
20
+ export interface InterruptedInvocation {
21
+ /** Operation name for a resolution produced before planning completed. */
22
+ readonly planName: string;
23
+ readonly mode: "preview" | "apply";
24
+ /**
25
+ * The command family's statically declared atomicity, for a resolution
26
+ * produced before the journal exists. Defaults to `closure-atomic`.
27
+ */
28
+ readonly declaredAtomicity?: AtomicityClass;
29
+ readonly presentation?: OperationPresentation;
30
+ /** The command an operator re-runs to continue; the surface renders it. */
31
+ readonly replayCommand: string;
32
+ }
33
+ /** One durable path the footprint recorder observed before the signal landed. */
34
+ export interface ObservedFootprintEntry {
35
+ readonly path: string;
36
+ readonly change: "created" | "modified" | "removed" | "restored";
37
+ }
38
+ /**
39
+ * Derive the terminal resolution for an externally interrupted invocation.
40
+ *
41
+ * Before the journal exists nothing was planned or attempted: the resolution
42
+ * carries the requested mode and the command family's declared atomicity,
43
+ * never a hardcoded apply/closure-atomic claim. Once apply has begun, settled
44
+ * closures keep their settlement — a commit stands as retained durable state,
45
+ * and a failure of a restoring apply had already rolled back only its own
46
+ * closure — while a unit that started without a settlement fact is reported
47
+ * `interrupted`, never as not attempted.
48
+ */
49
+ export declare const resolveInterruption: (invocation: InterruptedInvocation, journal: Option.Option<OperationJournalState>, signal: "SIGINT" | "SIGTERM", footprint: ReadonlyArray<ObservedFootprintEntry>) => OperationResolution<unknown>;
50
+ //# sourceMappingURL=interruption-resolution.d.ts.map
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The terminal resolution of an interrupted operation.
3
+ *
4
+ * Interruption is the one lifecycle fact the operation body cannot settle for
5
+ * itself: the signal arrives from outside it. Turning the operation journal
6
+ * into a contract-valid resolution — which closures settled, which were in
7
+ * flight, what durable state is retained, and which atomicity class actually
8
+ * held — is execution semantics, so it lives beside closure execution rather
9
+ * than in a transport adapter. The caller supplies only the resume command it
10
+ * would print, because how an operator re-runs a request is a property of the
11
+ * surface that accepted it.
12
+ *
13
+ * @experimental This API is unstable and may change without notice.
14
+ */
15
+ import * as OptionModule from "effect/Option";
16
+ import { executedUnits, makeOperationResolution, unitIdOf, } from "./operation-resolution.js";
17
+ /**
18
+ * Derive the terminal resolution for an externally interrupted invocation.
19
+ *
20
+ * Before the journal exists nothing was planned or attempted: the resolution
21
+ * carries the requested mode and the command family's declared atomicity,
22
+ * never a hardcoded apply/closure-atomic claim. Once apply has begun, settled
23
+ * closures keep their settlement — a commit stands as retained durable state,
24
+ * and a failure of a restoring apply had already rolled back only its own
25
+ * closure — while a unit that started without a settlement fact is reported
26
+ * `interrupted`, never as not attempted.
27
+ */
28
+ export const resolveInterruption = (invocation, journal, signal, footprint) => {
29
+ const observedFootprint = footprint.length === 0 ? {} : { footprint };
30
+ if (OptionModule.isNone(journal)) {
31
+ return makeOperationResolution({
32
+ name: invocation.planName,
33
+ description: OptionModule.none(),
34
+ mode: invocation.mode,
35
+ atomicity: {
36
+ declared: invocation.declaredAtomicity ?? "closure-atomic",
37
+ // Nothing was attempted, so no durable effect was made or retained.
38
+ applied: "closure-atomic",
39
+ },
40
+ units: [],
41
+ presentation: invocation.presentation,
42
+ interruption: { signal, disposition: "none" },
43
+ ...observedFootprint,
44
+ });
45
+ }
46
+ const state = journal.value;
47
+ const applying = state.phase === "apply" || state.phase === "restoration";
48
+ if (!applying) {
49
+ // Planning, preview, confirmation, or validation: nothing was attempted
50
+ // and the planned units stand as planned.
51
+ return makeOperationResolution({
52
+ name: state.name,
53
+ description: state.description,
54
+ mode: state.mode,
55
+ candidateId: state.candidateId,
56
+ atomicity: { declared: state.atomicity.declared, applied: "closure-atomic" },
57
+ units: state.plannedUnits,
58
+ presentation: state.presentation ?? invocation.presentation,
59
+ releaseAge: state.releaseAge,
60
+ preconditions: state.preconditions,
61
+ riskConditions: state.riskConditions,
62
+ interruption: { signal, disposition: "none" },
63
+ ...observedFootprint,
64
+ });
65
+ }
66
+ const resolvedUnits = executedUnits({
67
+ _tag: "ExecutedPlan",
68
+ name: state.name,
69
+ description: state.description,
70
+ jobs: [{ concurrency: 1, steps: state.resolved }],
71
+ });
72
+ // Closures settle independently: a settled commit stands as retained
73
+ // durable state regardless of the interruption, and a settled failure of a
74
+ // restoring apply had already rolled back only its own closure.
75
+ const settledUnits = resolvedUnits.map((unit) => unit.state === "committed"
76
+ ? { ...unit, disposition: "retained" }
77
+ : unit.state === "failed" && state.restoresOnFailure
78
+ ? { ...unit, disposition: "restored" }
79
+ : unit);
80
+ const resolvedIds = new Set(state.resolved.map((step) => unitIdOf(step)));
81
+ const startedIds = new Set(state.startedUnitIds);
82
+ // A started unit missing a settlement fact was in flight at the stopping
83
+ // point: its effects were restored by the closure's rollback, or their
84
+ // settlement was simply not observed — never "not attempted".
85
+ const inFlight = state.plannedUnits
86
+ .filter((unit) => startedIds.has(unit.id) && !resolvedIds.has(unit.id))
87
+ .map((unit) => ({
88
+ ...unit,
89
+ state: "interrupted",
90
+ disposition: state.restoresOnFailure ? "restored" : "unknown",
91
+ message: state.restoresOnFailure
92
+ ? "interrupted while in flight; effects were restored"
93
+ : "interrupted while in flight; settlement was not observed",
94
+ }));
95
+ const notStarted = state.plannedUnits
96
+ .filter((unit) => !startedIds.has(unit.id) && !resolvedIds.has(unit.id))
97
+ .map((unit) => ({
98
+ ...unit,
99
+ state: "blocked",
100
+ message: "not attempted: the operation was interrupted",
101
+ blocking: {
102
+ class: "operation-aborted",
103
+ subject: unit.id,
104
+ phase: "apply",
105
+ detail: "not attempted: the operation was interrupted",
106
+ reference: "interruption",
107
+ },
108
+ }));
109
+ const units = [...settledUnits, ...inFlight, ...notStarted];
110
+ const committed = settledUnits.filter((unit) => unit.state === "committed");
111
+ // Unknown dominates — durable state may exist beyond what settled. With
112
+ // everything settled or restored, retained commits are the headline;
113
+ // restored in-flight work without commits reports restored.
114
+ const disposition = inFlight.length > 0 && !state.restoresOnFailure
115
+ ? "unknown"
116
+ : committed.length > 0
117
+ ? "retained"
118
+ : inFlight.length > 0 || (state.resolved.length > 0 && state.restoresOnFailure)
119
+ ? "restored"
120
+ : "none";
121
+ const recovery = disposition === "retained" || disposition === "unknown"
122
+ ? {
123
+ retained: committed.flatMap((unit) => unit.artifact === undefined ? [unit.id] : [unit.artifact.path]),
124
+ actions: [
125
+ {
126
+ description: "Re-run the command to continue the remaining units.",
127
+ cmd: invocation.replayCommand,
128
+ },
129
+ ],
130
+ }
131
+ : undefined;
132
+ return makeOperationResolution({
133
+ name: state.name,
134
+ description: state.description,
135
+ mode: state.mode,
136
+ candidateId: state.candidateId,
137
+ atomicity: {
138
+ declared: state.atomicity.declared,
139
+ // A restoring apply keeps its closure-atomic promise: settled commits
140
+ // stand and everything else was restored. Only unobserved settlement
141
+ // or non-rollbackable retention downgrade the applied class.
142
+ applied: state.restoresOnFailure
143
+ ? "closure-atomic"
144
+ : disposition === "retained" || disposition === "unknown"
145
+ ? "non-rollbackable"
146
+ : "closure-atomic",
147
+ },
148
+ units,
149
+ presentation: state.presentation ?? invocation.presentation,
150
+ releaseAge: state.releaseAge,
151
+ preconditions: state.preconditions,
152
+ riskConditions: state.riskConditions,
153
+ interruption: { signal, disposition },
154
+ recovery,
155
+ ...observedFootprint,
156
+ });
157
+ };
158
+ //# sourceMappingURL=interruption-resolution.js.map
@@ -18,7 +18,7 @@ import * as Option from "effect/Option";
18
18
  import * as Ref from "effect/Ref";
19
19
  import * as ServiceMap from "effect/Context";
20
20
  import type { CompletedJobStep, OperationPresentation, PlanRiskCondition } from "./plan.js";
21
- import type { ReleaseAgeOperationEvidence } from "@agentxm/registry-protocol/unstable/registry/release-age-policy";
21
+ import type { ReleaseAgeOperationEvidence } from "@agentxm/extension-resolution";
22
22
  import type { OperationPrecondition } from "./plan.js";
23
23
  import type { OperationAtomicity, OperationPhase, ResolvedUnit } from "./operation-resolution.js";
24
24
  export interface OperationJournalState {
@@ -53,17 +53,17 @@ export interface OperationJournalService {
53
53
  declare const OperationJournal_base: ServiceMap.ServiceClass<OperationJournal, "@agentxm/workspace-operations/plan/operation-journal/OperationJournal", OperationJournalService>;
54
54
  export declare class OperationJournal extends OperationJournal_base {
55
55
  }
56
- /** Record the operation's frozen facts. No-op when no journal is provided. */
57
- export declare const recordOperationJournal: (state: OperationJournalState) => Effect.Effect<void>;
58
- /** Merge updates onto the recorded state. No-op when nothing was recorded. */
59
- export declare const updateOperationJournal: (update: (state: OperationJournalState) => OperationJournalState) => Effect.Effect<void>;
56
+ /** Record the operation's frozen facts. */
57
+ export declare const recordOperationJournal: (state: OperationJournalState) => Effect.Effect<void, never, OperationJournal>;
58
+ /** Merge updates onto the recorded state. No-op when nothing was recorded yet. */
59
+ export declare const updateOperationJournal: (update: (state: OperationJournalState) => OperationJournalState) => Effect.Effect<void, never, OperationJournal>;
60
60
  /** Record the lifecycle phase the invocation has entered. */
61
- export declare const recordJournalPhase: (phase: OperationPhase) => Effect.Effect<void>;
61
+ export declare const recordJournalPhase: (phase: OperationPhase) => Effect.Effect<void, never, OperationJournal>;
62
62
  /** Record that a unit's run began. */
63
- export declare const appendStartedUnit: (unitId: string) => Effect.Effect<void>;
63
+ export declare const appendStartedUnit: (unitId: string) => Effect.Effect<void, never, OperationJournal>;
64
64
  /** Record one unit's settlement fact. */
65
- export declare const appendResolvedUnit: (step: CompletedJobStep<unknown>) => Effect.Effect<void>;
66
- export declare const getOperationJournal: Effect.Effect<Option.Option<OperationJournalState>>;
65
+ export declare const appendResolvedUnit: (step: CompletedJobStep<unknown>) => Effect.Effect<void, never, OperationJournal>;
66
+ export declare const getOperationJournal: Effect.Effect<Option.Option<OperationJournalState>, never, OperationJournal>;
67
67
  export declare const makeOperationJournal: Effect.Effect<OperationJournalService>;
68
68
  export {};
69
69
  //# sourceMappingURL=operation-journal.d.ts.map
@@ -19,20 +19,10 @@ import * as Ref from "effect/Ref";
19
19
  import * as ServiceMap from "effect/Context";
20
20
  export class OperationJournal extends ServiceMap.Service()("@agentxm/workspace-operations/plan/operation-journal/OperationJournal") {
21
21
  }
22
- /** Record the operation's frozen facts. No-op when no journal is provided. */
23
- export const recordOperationJournal = (state) => Effect.gen(function* () {
24
- const service = yield* Effect.serviceOption(OperationJournal);
25
- if (Option.isNone(service))
26
- return;
27
- yield* Ref.set(service.value.ref, Option.some(state));
28
- });
29
- /** Merge updates onto the recorded state. No-op when nothing was recorded. */
30
- export const updateOperationJournal = (update) => Effect.gen(function* () {
31
- const service = yield* Effect.serviceOption(OperationJournal);
32
- if (Option.isNone(service))
33
- return;
34
- yield* Ref.update(service.value.ref, Option.map(update));
35
- });
22
+ /** Record the operation's frozen facts. */
23
+ export const recordOperationJournal = (state) => Effect.flatMap(OperationJournal, (service) => Ref.set(service.ref, Option.some(state)));
24
+ /** Merge updates onto the recorded state. No-op when nothing was recorded yet. */
25
+ export const updateOperationJournal = (update) => Effect.flatMap(OperationJournal, (service) => Ref.update(service.ref, Option.map(update)));
36
26
  /** Record the lifecycle phase the invocation has entered. */
37
27
  export const recordJournalPhase = (phase) => updateOperationJournal((state) => ({ ...state, phase }));
38
28
  /** Record that a unit's run began. */
@@ -42,11 +32,6 @@ export const appendStartedUnit = (unitId) => updateOperationJournal((state) => (
42
32
  }));
43
33
  /** Record one unit's settlement fact. */
44
34
  export const appendResolvedUnit = (step) => updateOperationJournal((state) => ({ ...state, resolved: [...state.resolved, step] }));
45
- export const getOperationJournal = Effect.gen(function* () {
46
- const service = yield* Effect.serviceOption(OperationJournal);
47
- if (Option.isNone(service))
48
- return Option.none();
49
- return yield* Ref.get(service.value.ref);
50
- });
35
+ export const getOperationJournal = Effect.flatMap(OperationJournal, (service) => Ref.get(service.ref));
51
36
  export const makeOperationJournal = Ref.make(Option.none()).pipe(Effect.map((ref) => ({ ref })));
52
37
  //# sourceMappingURL=operation-journal.js.map
@@ -15,7 +15,7 @@
15
15
  import type * as Option from "effect/Option";
16
16
  import * as Schema from "effect/Schema";
17
17
  import type { OperationErrorCategory, StepFailure } from "./errors.js";
18
- import type { ReleaseAgeOperationEvidence } from "@agentxm/registry-protocol/unstable/registry/release-age-policy";
18
+ import type { ReleaseAgeOperationEvidence } from "@agentxm/extension-resolution";
19
19
  import type { SuggestedAction } from "@agentxm/registry-protocol/unstable/suggested-action";
20
20
  import type { BlockingClass, ExecutedPlan, Job, JobStepArtifact, OperationPrecondition, OperationPresentation, PlanRiskCondition, RegistryLifecycleEvidence } from "./plan.js";
21
21
  import type { ConfiguredAgentOutcome } from "@agentxm/workspace-state";
@@ -2,8 +2,8 @@
2
2
  * Plan-execution fixtures for tests and executable specifications.
3
3
  *
4
4
  * Production commands convert their parsed intent into a `PlanExecution` at
5
- * the CLI boundary; these constants let kernel and handler tests drive
6
- * `previewOrApplyPlan` with an already-decoded approval decision. Production
5
+ * the CLI boundary; these constants let kernel and handler tests resolve
6
+ * an execution candidate with an already-decoded approval decision. Production
7
7
  * source never imports this module.
8
8
  *
9
9
  * @experimental This API is unstable and may change without notice.
@@ -2,8 +2,8 @@
2
2
  * Plan-execution fixtures for tests and executable specifications.
3
3
  *
4
4
  * Production commands convert their parsed intent into a `PlanExecution` at
5
- * the CLI boundary; these constants let kernel and handler tests drive
6
- * `previewOrApplyPlan` with an already-decoded approval decision. Production
5
+ * the CLI boundary; these constants let kernel and handler tests resolve
6
+ * an execution candidate with an already-decoded approval decision. Production
7
7
  * source never imports this module.
8
8
  *
9
9
  * @experimental This API is unstable and may change without notice.
@@ -44,6 +44,27 @@ export interface ConfirmationRecovery {
44
44
  * approval, never a preapproval flag the command does not accept.
45
45
  */
46
46
  export type ConfirmableRiskApproval = "preapproved" | "prompt-if-interactive" | "interactive-only";
47
+ /**
48
+ * What an invoking surface parsed about the approval a request carries.
49
+ *
50
+ * `preapproved` is present only on the routes whose capabilities declare a
51
+ * preapprovable confirmation; every other route omits it. The absence of the
52
+ * field is therefore a fact about the route, not a missing answer, and it is
53
+ * what separates "the person declined to preapprove" from "this request could
54
+ * never have been preapproved".
55
+ */
56
+ export interface RequestedApproval {
57
+ readonly preapproved?: boolean;
58
+ }
59
+ /**
60
+ * The one derivation of an approval mode from what a request carries.
61
+ *
62
+ * It lives with plan execution rather than in a transport adapter because the
63
+ * distinction it draws — between a route that offers preapproval and one that
64
+ * does not — is what `resolveExecutionCandidate` uses to decide whether an
65
+ * unattended apply blocks naming interactive approval or names a flag.
66
+ */
67
+ export declare const confirmableRiskApproval: (requested: RequestedApproval) => ConfirmableRiskApproval;
47
68
  export type PlanExecutionRequest = {
48
69
  readonly mode: "preview";
49
70
  } | {
@@ -65,6 +86,33 @@ export type PlanExecution = {
65
86
  readonly configuredAgentOperations?: ReadonlyArray<ConfiguredAgentOperation>;
66
87
  };
67
88
  export declare const previewPlanExecution: PlanExecution;
89
+ /**
90
+ * What a command parsed about its own invocation before any plan exists.
91
+ *
92
+ * `yes` is present only on the routes whose capabilities declare a
93
+ * preapprovable confirmation; every other route omits it, exactly as
94
+ * `RequestedApproval` describes.
95
+ */
96
+ export interface RequestedPlanIntent {
97
+ readonly preview: boolean;
98
+ readonly yes?: boolean;
99
+ }
100
+ /**
101
+ * The one derivation of a plan execution from what a request carries.
102
+ *
103
+ * It lives here for the same reason `confirmableRiskApproval` does: the
104
+ * preview arm of `PlanExecution` has no field an approval could occupy, so
105
+ * advance approval accompanying a preview is dropped by this derivation
106
+ * rather than by a transport adapter's own convention. A preview therefore
107
+ * cannot spend an approval it never receives, and two requests that differ
108
+ * only in approval reach `resolveExecutionCandidate` as the same execution.
109
+ */
110
+ export declare const requestedPlanExecution: (options: {
111
+ readonly intent: RequestedPlanIntent;
112
+ readonly recovery: ConfirmationRecovery;
113
+ readonly acceptedPolicies?: ReadonlySet<PlanPolicyId>;
114
+ readonly configuredAgentOperations?: ReadonlyArray<ConfiguredAgentOperation>;
115
+ }) => PlanExecution;
68
116
  export declare const applyPlanExecution: (options: {
69
117
  readonly approval: ConfirmableRiskApproval;
70
118
  readonly acceptedPolicies?: ReadonlySet<PlanPolicyId>;