Skip to content

chore: v0.6.0 tech debt cleanup (#101, #104, #83)#107

Merged
dean0x merged 10 commits intomainfrom
chore/v060-tech-debt
Mar 20, 2026
Merged

chore: v0.6.0 tech debt cleanup (#101, #104, #83)#107
dean0x merged 10 commits intomainfrom
chore/v060-tech-debt

Conversation

@dean0x
Copy link
Owner

@dean0x dean0x commented Mar 19, 2026

Summary

Tech debt consolidation for v0.6.0, addressing three architectural and design improvements for maintainability and code organization.

Changes

#101: OutputRepository Interface in Core

  • Move OutputRepository interface to core/interfaces.ts for DIP (Dependency Inversion Principle) compliance
  • Consolidate all repository interfaces in core layer
  • Remove unused Config adapter and getConfig() function from bootstrap

#104: BootstrapMode Enum

  • Replace boolean flags in BootstrapOptions with BootstrapMode enum
  • Improves clarity and prevents invalid flag combinations
  • Cleaner API surface

#83: ScheduleExecutor Transaction Safety

  • Wrap ScheduleExecutor missed-run FAIL policy in database transaction
  • Ensures atomicity when handling cascading task failures
  • Prevents partial state updates

Also Closes

Testing

  • All existing tests pass (597 total)
  • Coverage verified for affected components
  • No breaking changes to public APIs

Related Issues

Dean Sharon added 5 commits March 20, 2026 00:51
All other repository interfaces (TaskRepository, ScheduleRepository,
DependencyRepository, WorkerRepository, CheckpointRepository) live in
core/interfaces.ts. OutputRepository was the sole exception, defined in
the implementations layer — violating DIP.

Moves the interface to core, updates 8 import sites (5 production,
3 test/fixture). Zero behavioral change.
Three boolean flags (skipResourceMonitoring, skipScheduleExecutor,
skipRecovery) allowed 8 possible configurations, only 3 valid. Replace
with BootstrapMode enum: 'server' | 'cli' | 'run'.

Adds mode mapping verification test. Integration tests now inject
TestResourceMonitor instead of using skipResourceMonitoring flag.

BREAKING: BootstrapOptions drops 3 boolean fields, adds mode.
Acceptable for pre-1.0 semver.
The missed-run FAIL branch performed two sequential async DB writes
(update schedule status + record execution) without atomicity. If
update() succeeded but recordExecution() failed, the schedule would be
cancelled with no audit trail.

Replaces async calls with a synchronous transaction using
runInTransaction(). Event emission now fires after the transaction
commits, so handlers see consistent DB state.

Adds atomicity rollback test verifying partial failure reverts both
writes.
- Remove unused getConfig() function from bootstrap.ts (Config interface no longer needed)
- Remove redundant ARCHITECTURE comment in OutputRepository interface
- Cleanup from v0.6.0 tech debt consolidation

Related: #101, #104, #83
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 19, 2026

Confidence Score: 5/5

  • This PR is safe to merge — all changes are mechanical refactors or targeted correctness improvements with full test coverage.
  • All three goals are well-executed: the interface relocation is purely mechanical, BootstrapMode eliminates an invalid-flag-combination class of bugs with a pure testable function, and the transaction wrapping directly closes a data-integrity gap. No breaking changes to public APIs, all 18 changed files reviewed. The two inline comments are suggestions for future improvement, not blockers.
  • src/services/schedule-executor.ts — the SKIP missed-run path has a pre-existing emit-before-persist ordering that is now visually inconsistent with the newly hardened FAIL path.

Important Files Changed

Filename Overview
src/bootstrap.ts Introduces BootstrapMode string-union type and deriveModeFlags() to replace ad-hoc boolean flags; removes obsolete Config adapter; wires TransactionRunner into ScheduleExecutor factory. Clean and consistent refactor.
src/services/schedule-executor.ts Wraps the MissedRunPolicy.FAIL path in a runInTransaction() call for atomicity. The SKIP path still emits ScheduleMissed before updateNextRun, creating a minor ordering inconsistency (pre-existing, not introduced here).
src/core/interfaces.ts Removes Config interface and adds OutputRepository interface to core, completing DIP compliance for the repository layer; no interface changes, only relocation.
tests/unit/services/schedule-executor.test.ts Adds database parameter to ScheduleExecutor.create() calls and a new rollback test that sabotages recordExecutionSync mid-transaction and verifies schedule stays ACTIVE with no audit record and no event emitted.
tests/integration/service-initialization.test.ts Replaces skipResourceMonitoring:true with resourceMonitor:new TestResourceMonitor() and adds deriveModeFlags() unit tests covering all three modes.
tests/integration/task-scheduling.test.ts Removes redundant skipResourceMonitoring flag and adds database as required third argument to all ScheduleExecutor.create() calls within tests.

Sequence Diagram

sequenceDiagram
    participant SE as ScheduleExecutor
    participant DB as Database (TransactionRunner)
    participant SR as SQLiteScheduleRepository
    participant EB as EventBus

    SE->>SE: tick() — finds due schedule
    SE->>SE: handleMissedRun(schedule, now)
    note over SE: MissedRunPolicy.FAIL

    SE->>DB: runInTransaction(() => { ... })
    activate DB
    DB->>SR: updateSync(id, { status: CANCELLED })
    DB->>SR: recordExecutionSync({ status: 'missed', ... })
    alt recordExecutionSync throws
        SR-->>DB: throws Error
        DB-->>SE: err(BackbeatError) — rolled back
        SE->>SE: log error, break (no event emitted)
    else both ops succeed
        DB-->>SE: ok(undefined)
        deactivate DB
        SE->>EB: emit('ScheduleMissed', { policy: FAIL })
        SE->>SE: log "Schedule failed due to missed run"
    end
Loading

Comments Outside Diff (2)

  1. src/services/schedule-executor.ts, line 363-374 (link)

    SKIP policy emits event before persisting next-run update

    For MissedRunPolicy.FAIL (the focus of this PR) the ScheduleMissed event is correctly emitted only after a successful transaction. However, the SKIP path emits the event before calling updateNextRun(schedule):

    await this.eventBus.emit('ScheduleMissed', { ... });  // event first
    await this.updateNextRun(schedule);                    // persist second

    If updateNextRun fails (e.g. DB write error), the ScheduleMissed event has already been dispatched to all subscribers but the schedule's nextRunAt was never advanced — so the same run will be treated as missed again on the next tick. Consider persisting the state change before emitting the event, consistent with the FAIL path's approach.

  2. src/bootstrap.ts, line 58-65 (link)

    mode flag silently has no effect when resourceMonitor is injected

    When a caller provides both mode: 'run' (or any mode with skipResourceMonitoring: true) and a custom resourceMonitor, the skip flag derived from the mode is effectively ignored. The factory returns the injected monitor immediately before reaching the startMonitoring() branch:

    container.registerSingleton('resourceMonitor', () => {
      if (options.resourceMonitor) {
        return options.resourceMonitor;  // exits here — mode flag never checked
      }
      // ... skipResourceMonitoring only applies below this point
    });

    This is fine in practice (a custom monitor has no startMonitoring() side effects anyway), but the interaction isn't obvious. A short note in the BootstrapOptions JSDoc or deriveModeFlags documentation clarifying that skipResourceMonitoring only affects the built-in SystemResourceMonitor would help future contributors avoid confusion.

Last reviewed commit: "chore: fix biome for..."

@dean0x
Copy link
Owner Author

dean0x commented Mar 19, 2026

Code Review Summary

Nine reviewers analyzed this PR across architecture, complexity, consistency, documentation, performance, regression, security, tests, and TypeScript. Here's the consolidated feedback:

Blocking Issues (Must Fix Before Merge)

1. Dead Config interface left in core/interfaces.ts (95-90% confidence)

File: src/core/interfaces.ts:351-359
Reviewers: Architecture, Consistency, TypeScript, Regression

The getConfig() adapter in bootstrap.ts was correctly removed, along with its Config import (commit c1c2861). However, the Config interface itself remains exported but has zero consumers. It's dead code in the core interfaces module.

Fix: Delete lines 351-359:

/**
 * Configuration
 */
export interface Config {
  readonly maxOutputBuffer: number;
  readonly taskTimeout: number;
  readonly cpuCoresReserved: number;
  readonly memoryReserve: number;
  readonly logLevel: 'debug' | 'info' | 'warn' | 'error';
  readonly maxListenersPerEvent?: number;
  readonly maxTotalSubscriptions?: number;
}

2. BootstrapMode test re-implements logic instead of testing behavior (85% confidence)

File: tests/integration/service-initialization.test.ts:387-398
Reviewers: Tests, TypeScript

The test duplicates the mode-to-flag derivation logic from src/bootstrap.ts:119-122 verbatim, then asserts against hardcoded values. This tests the test's own logic, not the actual bootstrap() function. If someone changes the mapping in bootstrap(), this test will silently pass with stale expectations.

Fix Options:

  • (A) Extract derivation function — Export deriveModeFlags(mode: BootstrapMode) from bootstrap.ts and test that pure function directly (simplest, fastest)
  • (B) Integration test — Call bootstrap({ mode: 'cli' }) and verify actual behavior (e.g., no scheduleExecutor in container)

Recommendation: Use Option A for speed and clarity.

// In src/bootstrap.ts
export function deriveModeFlags(mode: BootstrapMode) {
  return {
    skipResourceMonitoring: mode === 'run',
    skipScheduleExecutor: mode === 'cli' || mode === 'run',
    skipRecovery: mode === 'cli',
  };
}

// In test: import and test the real function
import { deriveModeFlags } from '../../src/bootstrap.js';

it.each([...])('mode "%s" produces correct flags', (mode, expected) => {
  expect(deriveModeFlags(mode)).toEqual(expected);
});

Should-Fix Issues (Improve Code Quality)

3. Rollback test missing ScheduleMissed event verification (82% confidence)

File: tests/unit/services/schedule-executor.test.ts:367-394
Reviewer: Tests

The transaction rollback test correctly verifies schedule and execution history state, but does NOT assert that ScheduleMissed event was suppressed. The source code (schedule-executor.ts:405-409) correctly breaks before emitting the event on failure, but the test doesn't validate this invariant. A future refactor could easily break this by reordering the code.

Fix: Subscribe to ScheduleMissed and assert zero events:

const missedEvents: unknown[] = [];
eventBus.subscribe('ScheduleMissed', async (event: unknown) => {
  missedEvents.push(event);
});

await executor.triggerTick();
await flushEventLoop();

// ...existing assertions...

// No ScheduleMissed event — transaction failed, so no side effects
expect(missedEvents).toHaveLength(0);

4. BootstrapOptions JSDoc removed without equivalent documentation (85% confidence)

File: src/bootstrap.ts:39-43
Reviewer: Documentation

The original BootstrapOptions interface had per-property JSDoc explaining each field's purpose (e.g., "Custom ProcessSpawner implementation (e.g., NoOpProcessSpawner for tests)"). The refactoring removed these hints. While the BootstrapMode type alias has good top-level documentation, the remaining processSpawner and resourceMonitor fields lost their JSDoc hints.

Fix: Add brief JSDoc to DI fields:

export interface BootstrapOptions {
  mode?: BootstrapMode;
  /** Custom ProcessSpawner (e.g., NoOpProcessSpawner for tests) */
  processSpawner?: ProcessSpawner;
  /** Custom ResourceMonitor (e.g., TestResourceMonitor for tests) */
  resourceMonitor?: ResourceMonitor;
}

5. OutputRepository methods lack per-method JSDoc (82% confidence)

File: src/core/interfaces.ts:471-476
Reviewer: Documentation

The OutputRepository interface was moved from the implementation file to core/interfaces.ts as part of issue #101. The move is an opportunity to add documentation since it's now a first-class core contract alongside well-documented repository interfaces. All four methods (save, append, get, delete) are currently undocumented.

Fix: Add method-level JSDoc:

export interface OutputRepository {
  /** Persist full output snapshot (stdout + stderr) */
  save(taskId: TaskId, output: TaskOutput): Promise<Result<void>>;
  /** Append incremental data to a stream */
  append(taskId: TaskId, stream: 'stdout' | 'stderr', data: string): Promise<Result<void>>;
  /** Retrieve stored output for a task */
  get(taskId: TaskId): Promise<Result<TaskOutput | null>>;
  /** Remove stored output for a task */
  delete(taskId: TaskId): Promise<Result<void>>;
}

Pre-Existing Issues (Not Blocking, Context Only)

bootstrap() function length (82% confidence, pre-existing)

  • File: src/bootstrap.ts:118-469 (351 lines)
  • The function exceeds recommended length, but is an accepted exception as a DI composition root with linear, sequential structure and low cyclomatic complexity. No action needed for this PR.

interfaces.ts file size (82% confidence, pre-existing)

  • File: src/core/interfaces.ts (504 lines)
  • Slightly above 500-line threshold, but acceptable for a "barrel" interfaces file. No action needed for this PR.

ROADMAP.md needs update (85% confidence, pre-existing)


Lower-Confidence Suggestions (60-79%)

Several reviewers flagged lower-confidence observations that don't block merge but document patterns worth considering:

  • Sequential schedule processing (65%) — intentional to avoid thundering herd
  • clearRunningScheduleByTask linear scan (60%) — acceptable due to small active schedule counts
  • Integration tests for ScheduleExecutor signature change (65%) — TypeScript already enforces types
  • CLAUDE.md ScheduleExecutor description refinement (65%) — note about transactional wrapping is still accurate

Scores Summary

Category Score Status
Architecture 9/10 APPROVED_WITH_CONDITIONS
Complexity 9/10 APPROVED
Consistency 9/10 APPROVED_WITH_CONDITIONS
Documentation 7/10 APPROVED_WITH_CONDITIONS
Performance 9/10 APPROVED
Regression 9/10 APPROVED
Security 9/10 APPROVED
Tests 8/10 CHANGES_REQUESTED
TypeScript 9/10 APPROVED_WITH_CONDITIONS

Reviewer Consensus

Architecture: All three tech debt cleanups execute correctly:

  • OutputRepository interface move follows established pattern ✓
  • BootstrapMode enum eliminates invalid flag combinations (8 → 3 valid states) ✓
  • ScheduleExecutor transaction wrapping fixes atomicity bug ✓

Quality verdict: These changes reduce complexity, improve type safety, and fix a real correctness issue. Address the 5 issues above (1 blocking + 4 should-fix) before merge. All suggestions are clear and actionable.


Reviewed by: Architecture, Complexity, Consistency, Documentation, Performance, Regression, Security, Tests, TypeScript reviewers
PR Recommendation: APPROVED_WITH_CONDITIONS (fix blocking issue + 4 should-fix items)

Dean Sharon and others added 5 commits March 20, 2026 09:32
…n rollback

The rollback test verified schedule status and execution history were
restored, but did not check that ScheduleMissed was suppressed. Add
event subscriber assertion so reordering the emit before the txResult.ok
guard is caught by regression.

Co-Authored-By: Claude <noreply@anthropic.com>
… JSDoc

Remove unused Config interface (no imports across entire codebase) and
add per-method JSDoc to OutputRepository to match peer repositories
(ScheduleRepository, DependencyRepository, CheckpointRepository).

Co-Authored-By: Claude <noreply@anthropic.com>
…trapOptions

Extract the inline BootstrapMode-to-flags derivation into a pure,
exported deriveModeFlags() function so the integration test exercises
the real production code instead of duplicating the logic locally.
Add missing JSDoc to BootstrapOptions fields.

Co-Authored-By: Claude <noreply@anthropic.com>
@dean0x dean0x merged commit 5c25ec7 into main Mar 20, 2026
2 checks passed
@dean0x dean0x deleted the chore/v060-tech-debt branch March 20, 2026 07:59
dean0x pushed a commit that referenced this pull request Mar 20, 2026
- Bump version 0.5.0 → 0.6.0
- Update release notes with all 8 PRs (was missing #85, #86, #91, #94, #100, #106, #107)
- Mark v0.6.0 as released in ROADMAP.md
- Update FEATURES.md architecture section for hybrid event model
- Expand "What's New in v0.6.0" with architectural simplification, bug fixes, tech debt
- Fix README roadmap: v0.6.1 → v0.7.0 for loops
- Update bug report template example version to 0.6.0
@dean0x dean0x mentioned this pull request Mar 20, 2026
7 tasks
dean0x added a commit that referenced this pull request Mar 20, 2026
## Summary

- Bump version `0.5.0` → `0.6.0` (package.json + package-lock.json)
- Expand release notes with all 8 PRs (#78, #85, #86, #91, #94, #100,
#106, #107) — was only covering #78
- Mark v0.6.0 as released in ROADMAP.md, update status and version
timeline
- Update FEATURES.md architecture section for hybrid event model (was
describing old fully event-driven architecture with removed services)
- Expand "What's New in v0.6.0" in FEATURES.md with architectural
simplification, additional bug fixes, tech debt, breaking changes,
migration 9
- Fix README roadmap version: `v0.6.1` → `v0.7.0` for task/pipeline
loops
- Update bug report template example version `0.5.0` → `0.6.0`

### GitHub Issues
- Closed #82 (cancelTasks scope — PR #106)
- Closed #95 (totalSize tail-slicing — PR #106)
- Updated #105 release tracker checklist (all items checked)

## Test plan
- [x] `npm run build` — clean compilation
- [x] `npm run test:all` — full suite passes (822 tests, 0 failures)
- [x] `npx biome check src/ tests/` — no lint errors
- [x] `package.json` version is `0.6.0`
- [x] Release notes file exists and covers all PRs
- [ ] After merge: trigger Release workflow from GitHub Actions
- [ ] After release published: close #105

---------

Co-authored-by: Dean Sharon <deanshrn@gmain.com>
@dean0x dean0x mentioned this pull request Mar 20, 2026
16 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant