Skip to content

Tool handlers bypass DB port layer via _getAdapter() raw SQL #14

@igouss

Description

@igouss

Summary

Six tool handlers import _getAdapter() (an internal function marked with underscore convention) from gsd-db.ts and either issue raw SQL directly or carry a dead import. This bypasses the DB port layer that is the foundation of the single-writer architecture.

Impact

Tool handlers are Boundary adapters (hexagonal architecture). They should call named port functions on gsd-db.ts, not reach through to the SQLite adapter. Any schema change, column rename, or DB migration now has to audit raw SQL scattered across tool handlers in addition to the DB module.

_getAdapter() is exported as a named export at gsd-db.ts:879 with an underscore prefix signaling internal use — but TypeScript has no real access control on named exports, so any file can grab it.


All callsites (exhaustive)

Category 1: Existing gsd-db.ts functions can replace directly

File Line Raw SQL Replacement
tools/complete-task.ts 251-259 UPDATE tasks SET status = 'pending' WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid updateTaskStatus(mid, sid, tid, 'pending') — exists at gsd-db.ts:1203
tools/complete-slice.ts 302-310 UPDATE slices SET status = 'pending' WHERE milestone_id = :mid AND id = :sid updateSliceStatus(mid, sid, 'pending') — exists at gsd-db.ts:1298
tools/validate-milestone.ts 105-115 INSERT OR REPLACE INTO assessments (path, milestone_id, slice_id, task_id, status, scope, full_content, created_at) VALUES (...) insertAssessment() — exists at gsd-db.ts:1672, exact match

Category 2: New functions needed in gsd-db.ts

File Line Raw SQL New function
tools/complete-milestone.ts 168-174 UPDATE milestones SET status = 'complete', completed_at = :completed_at WHERE id = :mid updateMilestoneStatus(id: string, status: string, completedAt?: string | null): void
tools/complete-milestone.ts 202-206 UPDATE milestones SET status = 'active', completed_at = NULL WHERE id = :mid Same function as above with ('active', null)
tools/complete-task.ts 266-276 UPDATE tasks SET full_summary_md = :md WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid setTaskSummaryMd(mid: string, sid: string, tid: string, md: string): void
tools/complete-slice.ts 316-325 UPDATE slices SET full_summary_md = :summary_md, full_uat_md = :uat_md WHERE milestone_id = :mid AND id = :sid setSliceSummaryMd(mid: string, sid: string, summaryMd: string, uatMd: string): void

Category 3: Dead imports (no callsite, just delete)

File Line
tools/plan-slice.ts 10
tools/plan-milestone.ts 9

Out-of-scope: _getAdapter() usage outside tools/

28+ callsites in non-tool files. Same pattern but different scope — tracked here for awareness, not for this issue:

File Callsites Nature
memory-store.ts 11 Entire module is a DB abstraction — should be promoted into gsd-db.ts
context-store.ts 3 Same pattern as memory-store
db-writer.ts 3 Lower-level module
workflow-manifest.ts 1 requireDb() helper wrapping _getAdapter()
workflow-projections.ts 1 Live-DB probe
workflow-migration.ts 3 Migration utility
md-importer.ts 2 Import + nullcheck
auto-post-unit.ts 1 Rogue detection query
triage-resolution.ts 1
commands-maintenance.ts 1 Bulk DELETE during recover
commands-inspect.ts 1 Admin inspection
doctor-engine-checks.ts 1 Integrity checks

Fix plan

Step 1: Add 3 new functions to gsd-db.ts

export function updateMilestoneStatus(id: string, status: string, completedAt?: string | null): void {
  const adapter = _getAdapter();
  if (!adapter) throw new GsdStaleStateError('updateMilestoneStatus');
  adapter.prepare('UPDATE milestones SET status = :status, completed_at = :completedAt WHERE id = :id')
    .run({ id, status, completedAt: completedAt ?? null });
}

export function setTaskSummaryMd(milestoneId: string, sliceId: string, taskId: string, md: string): void {
  const adapter = _getAdapter();
  if (!adapter) throw new GsdStaleStateError('setTaskSummaryMd');
  adapter.prepare('UPDATE tasks SET full_summary_md = :md WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid')
    .run({ mid: milestoneId, sid: sliceId, tid: taskId, md });
}

export function setSliceSummaryMd(milestoneId: string, sliceId: string, summaryMd: string, uatMd: string): void {
  const adapter = _getAdapter();
  if (!adapter) throw new GsdStaleStateError('setSliceSummaryMd');
  adapter.prepare('UPDATE slices SET full_summary_md = :summary_md, full_uat_md = :uat_md WHERE milestone_id = :mid AND id = :sid')
    .run({ mid: milestoneId, sid: sliceId, summary_md: summaryMd, uat_md: uatMd });
}

Step 2: Replace raw SQL in tool handlers

  • complete-task.ts:251updateTaskStatus(mid, sid, tid, 'pending')
  • complete-task.ts:266setTaskSummaryMd(mid, sid, tid, summaryMd)
  • complete-slice.ts:302updateSliceStatus(mid, sid, 'pending')
  • complete-slice.ts:316setSliceSummaryMd(mid, sid, summaryMd, uatMd)
  • complete-milestone.ts:168updateMilestoneStatus(mid, 'complete', completedAt)
  • complete-milestone.ts:202updateMilestoneStatus(mid, 'active', null)
  • validate-milestone.ts:105insertAssessment({ ... })

Step 3: Remove dead imports

  • Delete _getAdapter from import in plan-slice.ts:10
  • Delete _getAdapter from import in plan-milestone.ts:9

Step 4: Remove _getAdapter imports from all modified tool files

After replacing raw SQL, none of the 6 tool files need _getAdapter anymore.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions