Skip to content

Commit ff3a5c9

Browse files
refactor(lifecycle): normalize operation failures
1 parent 4d39c5a commit ff3a5c9

7 files changed

Lines changed: 47 additions & 10 deletions

File tree

docs/intentional-architecture-rewrite-2026-06-27/decision-log.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ Claude and Codex transcript-store scanning, raw transcript snapshot reads, line
5757

5858
Build, Absorb, and Garden operation specs are lifecycle product mechanics, so their provider-neutral construction lives under `src/services/lifecycle/operations/`. CLI commands and peer services call lifecycle workflow contracts instead of importing operation internals.
5959

60+
Lifecycle workflow results expose lifecycle-owned failure contracts. Agent runtime failures are normalized in `src/services/lifecycle/operation-results.ts` before command rendering reads them, so CLI output code does not depend on provider/runtime event types.
61+
6062
### Absorb source parsing is lifecycle, GitHub mechanics are platform
6163

6264
Absorb owns product input normalization under `src/services/lifecycle/absorb/`: source refs, source input contracts, context rendering, and Absorb run-start shaping. GitHub remote parsing and URL construction are external mechanics, so they live under `src/platform/github/` and return plain typed facts.

docs/intentional-architecture-rewrite-2026-06-27/status.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ Branch: `codex/intentional-architecture-rewrite`
55

66
## Current State
77

8-
The branch has 227 committed rewrite commits past `dev`. The worklog records 179 production slices so far.
8+
The branch has 228 committed rewrite commits past `dev`. The worklog records 180 production slices so far.
99

10-
The diff is broad: 473 files changed, with 23,659 insertions and 12,875 deletions.
10+
The diff is broad: 473 files changed, with 23,696 insertions and 12,875 deletions.
1111

1212
This is no longer a small cleanup branch. It is a real ownership rewrite.
1313

@@ -32,6 +32,7 @@ This is no longer a small cleanup branch. It is a real ownership rewrite.
3232
- Moved sync ledger and lock persistence into explicit stores.
3333
- Moved local Claude/Codex transcript discovery, transcript snapshot reads, and timestamp boundary parsing into `src/platform/transcripts/` and removed the old top-level `src/sync/` source bucket.
3434
- Moved lifecycle operation construction and Absorb input/source handling into `src/services/lifecycle/` and removed the old top-level `src/operations/` and `src/absorb/` source buckets.
35+
- Normalized lifecycle operation failures into lifecycle-owned result contracts before command rendering sees them.
3536
- Moved GitHub source resolution mechanics into `src/platform/github/`.
3637
- Moved provider execution runtime into `src/agent/runtime/`, especially Claude and Codex app-server mechanics, and made provider runtime environment flow through explicit job/registry contracts.
3738
- Moved setup, diagnostics, update, automation, jobs, sync, lifecycle, config, and agents workflows behind service-owned contracts.
@@ -55,9 +56,9 @@ This is no longer a small cleanup branch. It is a real ownership rewrite.
5556

5657
## Latest Checkpoint
5758

58-
The latest slice moved `almanac uninstall` terminal UI from `src/cli/commands/` to `src/edges/cli/`. Uninstall confirmations, output rendering, and edge orchestration now live with CLI edge code, while setup cleanup stays under `src/services/setup/uninstall.ts`.
59+
The latest slice normalized lifecycle operation failures into `src/services/lifecycle/operation-results.ts`. Operation command rendering now depends on lifecycle-owned failure contracts instead of importing agent runtime failure types directly.
5960

60-
Verification passed: focused uninstall/CLI/boundary tests, `npm run lint`, full `npm test`, `npm run build`, `node dist/codealmanac.js --version`, `node dist/codealmanac.js uninstall --help`, `node dist/codealmanac.js setup --help`, and `node dist/codealmanac.js doctor --help`.
61+
Verification passed: focused operation-command/boundary tests, `npm run lint`, full `npm test`, `npm run build`, `node dist/codealmanac.js --version`, `node dist/codealmanac.js init --help`, `node dist/codealmanac.js absorb --help`, and `node dist/codealmanac.js garden --help`.
6162

6263
## Immediate Next Work
6364

docs/intentional-architecture-rewrite-2026-06-27/worklog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,3 +1221,10 @@ One-hundred-seventy-ninth production slice:
12211221
- Updated setup command registration and uninstall tests to import the edge-owned uninstall runner.
12221222
- Kept `src/services/setup/uninstall.ts` responsible for deterministic cleanup workflow over automation and instruction artifacts.
12231223
- Strengthened boundary coverage so old uninstall command files stay deleted while CLI edge owns confirmations and stdout rendering.
1224+
1225+
One-hundred-eightieth production slice:
1226+
1227+
- Added a lifecycle-owned `LifecycleOperationFailure` contract in `src/services/lifecycle/operation-results.ts`.
1228+
- Normalized agent runtime failure objects into lifecycle failures before operation command rendering reads them.
1229+
- Removed the direct `AgentRuntimeFailure` import from `src/cli/commands/operations-render.ts`.
1230+
- Strengthened boundary coverage so lifecycle command rendering cannot re-import agent runtime failure types directly.

src/cli/commands/operations-render.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { AgentRuntimeFailure } from "../../agent/runtime/events.js";
21
import {
2+
type LifecycleOperationFailure,
33
type LifecycleOperationRunResult,
44
type LifecycleOperationWorkflowResult,
55
} from "../../services/lifecycle/index.js";
@@ -90,7 +90,7 @@ function renderOperationFailureMessage(args: {
9090
operation: string;
9191
jobId: string;
9292
error?: string;
93-
failure?: AgentRuntimeFailure;
93+
failure?: LifecycleOperationFailure;
9494
}): string {
9595
const lines = [`${args.operation} failed: ${args.jobId}`];
9696
if (args.failure !== undefined) {

src/services/lifecycle/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export {
2222
} from "./workflows.js";
2323

2424
export {
25+
type LifecycleOperationFailure,
2526
type LifecycleOperationBackgroundResult,
2627
type LifecycleOperationForegroundResult,
2728
type LifecycleOperationJobResult,

src/services/lifecycle/operation-results.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,24 @@ export type LifecycleOperationJobStatus =
99
| "failed"
1010
| "cancelled";
1111

12+
export interface LifecycleOperationFailure {
13+
provider: string;
14+
message: string;
15+
code?: string;
16+
fix?: string;
17+
}
18+
1219
export interface LifecycleOperationJobResult {
1320
status: LifecycleOperationJobStatus;
1421
pid: number;
1522
logPath: string;
16-
failure?: AgentRuntimeFailure;
23+
failure?: LifecycleOperationFailure;
1724
}
1825

1926
export interface LifecycleOperationForegroundResult {
2027
success: boolean;
2128
error?: string;
22-
failure?: AgentRuntimeFailure;
29+
failure?: LifecycleOperationFailure;
2330
}
2431

2532
export interface LifecycleOperationBackgroundResult {
@@ -48,7 +55,9 @@ export function lifecycleOperationRunResultFromOperation(
4855
status: record.status,
4956
pid: record.pid,
5057
logPath: record.logPath,
51-
...(record.failure !== undefined ? { failure: record.failure } : {}),
58+
...(record.failure !== undefined
59+
? { failure: lifecycleFailureFromAgent(record.failure) }
60+
: {}),
5261
},
5362
...(result.foreground !== undefined
5463
? { foreground: lifecycleForegroundResultFromOperation(result.foreground.result) }
@@ -65,6 +74,19 @@ function lifecycleForegroundResultFromOperation(
6574
return {
6675
success: result.success,
6776
...(result.error !== undefined ? { error: result.error } : {}),
68-
...(result.failure !== undefined ? { failure: result.failure } : {}),
77+
...(result.failure !== undefined
78+
? { failure: lifecycleFailureFromAgent(result.failure) }
79+
: {}),
80+
};
81+
}
82+
83+
function lifecycleFailureFromAgent(
84+
failure: AgentRuntimeFailure,
85+
): LifecycleOperationFailure {
86+
return {
87+
provider: failure.provider,
88+
message: failure.message,
89+
...(failure.code !== undefined ? { code: failure.code } : {}),
90+
...(failure.fix !== undefined ? { fix: failure.fix } : {}),
6991
};
7092
}

test/architecture-boundaries.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,6 +1481,7 @@ describe("architecture boundaries", () => {
14811481
const platformGithubSource = await readSource("src/platform/github/source.ts");
14821482
const syncService = await readSource("src/services/sync/sync.ts");
14831483
const operationsCommand = await readSource("src/cli/commands/operations.ts");
1484+
const operationsRender = await readSource("src/cli/commands/operations-render.ts");
14841485

14851486
expect(existsSync(join(ROOT, "src/services/lifecycle/operation-results.ts"))).toBe(true);
14861487
expect(existsSync(join(ROOT, "src/services/lifecycle/workflows.ts"))).toBe(true);
@@ -1519,10 +1520,13 @@ describe("architecture boundaries", () => {
15191520
expect(lifecycleWorkflows).not.toContain("Command context:");
15201521
expect(lifecycleWorkflows).toContain("runPreparedAbsorbOperationWorkflow");
15211522
expect(lifecycleResults).toContain("lifecycleOperationRunResultFromOperation");
1523+
expect(lifecycleResults).toContain("interface LifecycleOperationFailure");
15221524
expect(platformGithubSource).not.toContain("services/lifecycle");
15231525
expect(syncService).toContain("runPreparedAbsorbOperationWorkflow");
15241526
expect(syncService).not.toContain("services/lifecycle/operations");
15251527
expect(operationsCommand).toContain("services/lifecycle/index.js");
1528+
expect(operationsRender).not.toContain("../../agent");
1529+
expect(operationsRender).not.toContain("AgentRuntimeFailure");
15261530
expect(operationsCommand).not.toContain("import type { CommandResult }");
15271531
expect(operationsCommand).not.toContain("extends InitOperationWorkflowOptions");
15281532
expect(operationsCommand).not.toContain("extends AbsorbOperationWorkflowOptions");

0 commit comments

Comments
 (0)