DriftGuard Dashboard

Generated: 2026-09-26T17:04:04.422Z  |  Project: taskflow-api  |  Run duration: 20ms

Summary
Total claims: 9
✓ Match: 0
✗ Drift: 9
? Unverifiable: 0
Per-Module Drift Score
users
0%
1 drift / 1 claims
tasks
0%
3 drift / 3 claims
services
0%
1 drift / 1 claims
db
0%
2 drift / 2 claims
auth
0%
2 drift / 2 claims
Drift Count by Module
drifts users 1 tasks 3 services 1 db 2 auth 2
Phase Timings
PhaseAgent wall-clock
doc_parse_ms4ms
code_map_ms4ms
drift_analysis_ms3ms
fix_gen_ms0ms
report_gen_ms0ms
Total run20ms

Manual review estimate: ~14 minutes
Agent total: 0s
Time saved: ~13 minutes
Findings (9)
CLM-001 DRIFT users confidence: 97%
No POST /users handler found in src/routes/users.js — only GET /:id exists
📄 Doc says
POST /users → 201 {id, username, email}  (openapi.yaml paths./users.post)
💻 Code says
router.get('/:id', ...) — no router.post() handler present  (src/routes/users.js)
Fix Candidates
📄 Doc Patch
Remove the undocumented POST /users endpoint from openapi.yaml
File: docs/openapi.yaml
--- a/docs/openapi.yaml
+++ b/docs/openapi.yaml
@@ -42,18 +42,6 @@
   /users/{id}:
     get:
       summary: Get user by ID
-
-  /users:
-    post:
-      summary: Create a user
-      requestBody:
-        required: true
-        content:
-          application/json:
-            schema:
-              $ref: "#/components/schemas/UserInput"
-      responses:
-        "201":
-          description: Created user
-          content:
-            application/json:
-              schema:
-                $ref: "#/components/schemas/User"
💻 Code TODO
Add TODO in routes/users.js noting the missing POST /users handler
File: src/routes/users.js
--- a/src/routes/users.js
+++ b/src/routes/users.js
@@ -3,6 +3,13 @@
 const router = express.Router();
 const { db } = require('../db/db');
 
+// TODO(DriftGuard DRIFT-001): openapi.yaml documents POST /users returning 201 {User},
+// but this handler has never been implemented. Either:
+//   (a) implement: router.post('/', (req, res) => { ... });
+//   (b) remove the endpoint from openapi.yaml if it is no longer planned.
+// Reference: docs/openapi.yaml → paths./users.post
+// Detected: DriftGuard Phase 4 — FixGenerator
+
 // GET /users/:id
CLM-002 DRIFT tasks confidence: 95%
Field "name" found instead of "title" in tasks table (src/models/task.js CREATE TABLE)
📄 Doc says
Task.properties.title: { type: string }  (openapi.yaml components.schemas.Task)
💻 Code says
name TEXT NOT NULL  (src/models/task.js, CREATE TABLE tasks)
Fix Candidates
📄 Doc Patch
Rename "title" to "name" in openapi.yaml Task schema to match code
File: docs/openapi.yaml
--- a/docs/openapi.yaml
+++ b/docs/openapi.yaml
@@ -68,8 +68,8 @@
     Task:
       type: object
       properties:
         id:
           type: integer
-        title:
+        name:
           type: string
-          description: "The task title (DRIFT-002: code uses 'name' instead)"
+          description: "The task name"
@@ -85,7 +85,7 @@
     TaskInput:
       type: object
-      required: [title]
+      required: [name]
       properties:
-        title:
+        name:
           type: string
💻 Code TODO
Add TODO in models/task.js noting the field name mismatch with OpenAPI spec
File: src/models/task.js
--- a/src/models/task.js
+++ b/src/models/task.js
@@ -4,6 +4,9 @@
 const { db } = require('../db/db');
 
 function createTable() {
+  // TODO(DriftGuard DRIFT-002): openapi.yaml specifies this field as "title" but
+  // the DB schema uses "name". Align one or the other.
+  // Reference: docs/openapi.yaml → components.schemas.Task.properties.title
   db.exec(`
     CREATE TABLE IF NOT EXISTS tasks (
       id INTEGER PRIMARY KEY AUTOINCREMENT,
CLM-003 DRIFT tasks confidence: 93%
DELETE handler returns 200 — not 204 (src/routes/tasks.js)
📄 Doc says
docs/openapi.yaml: DELETE /tasks/:id → 204 No Content
💻 Code says
res.status(200).json({ deleted: true })  (src/routes/tasks.js:31)
Fix Candidates
📄 Doc Patch
Update openapi.yaml DELETE /tasks/{id} response from 204 to 200
File: docs/openapi.yaml
--- a/docs/openapi.yaml
+++ b/docs/openapi.yaml
@@ -34,8 +34,10 @@
     delete:
       summary: Delete a task
       responses:
-        "204":
-          description: No Content — task deleted successfully
+        "200":
+          description: OK — returns confirmation body
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  deleted:
+                    type: boolean
💻 Code TODO
Add TODO in routes/tasks.js to align DELETE response with spec (204 vs 200)
File: src/routes/tasks.js
--- a/src/routes/tasks.js
+++ b/src/routes/tasks.js
@@ -28,6 +28,9 @@
 // DELETE /tasks/:id
-// DRIFT-006: README says this returns 204 No Content, but we return 200 + body
+// TODO(DriftGuard DRIFT-006): openapi.yaml and README both document this as 204 No Content.
+// Current code returns 200 + { deleted: true }. Choose one and align both doc and code.
+// Reference: docs/openapi.yaml → paths./tasks/{id}.delete.responses
 router.delete('/:id', async (req, res) => {
   await taskService.remove(req.params.id);
   res.status(200).json({ deleted: true });
CLM-007 DRIFT tasks confidence: 93%
DELETE handler returns 200 — not 204 (src/routes/tasks.js)
📄 Doc says
README.md: DELETE /tasks/:id → 204 No Content
💻 Code says
res.status(200).json({ deleted: true })  (src/routes/tasks.js:31)
CLM-006 DRIFT services confidence: 96%
notificationService.send() called inside setTimeout — fire-and-forget, not synchronous (src/services/taskService.js)
📄 Doc says
ADR-003: "notificationService.send() called with await; HTTP response not returned until send() resolves"
💻 Code says
setTimeout(async () => { await notificationService.send(...); }, 0)  (src/services/taskService.js:14-16)
Fix Candidates
📄 Doc Patch
Update ADR-003 to reflect that notifications are now fire-and-forget
File: docs/adr/ADR-003-async-queue.md
--- a/docs/adr/ADR-003-async-queue.md
+++ b/docs/adr/ADR-003-async-queue.md
@@ -10,9 +10,10 @@
 ## Decision
 
-Notifications will be sent **synchronously** within the request lifecycle.
-`notificationService.send()` will be called with `await` and the HTTP response
-will not be returned until `send()` resolves.
+Notifications are sent **asynchronously** as a fire-and-forget operation using
+`setTimeout`. The HTTP response is returned immediately; notification delivery
+is not awaited. This departs from the original synchronous intent in ADR-003
+and removes the delivery guarantee described therein.
💻 Code TODO
Add TODO in taskService.js noting the ADR expects synchronous delivery
File: src/services/taskService.js
--- a/src/services/taskService.js
+++ b/src/services/taskService.js
@@ -10,7 +10,10 @@
   const task = { id: info.lastInsertRowid, ...data };
 
-  // Fire-and-forget — not synchronous as ADR-003 claims
-  setTimeout(async () => {
+  // TODO(DriftGuard DRIFT-003): ADR-003 requires synchronous notification delivery
+  // (await notificationService.send() before returning). Current code is fire-and-forget.
+  // If async delivery is acceptable, update ADR-003. If sync is required, change to:
+  //   await notificationService.send({ event: 'task.created', taskId: task.id });
+  // Reference: docs/adr/ADR-003-async-queue.md → Decision section
+  setTimeout(async () => {  // DRIFTGUARD: fire-and-forget
     await notificationService.send({ event: 'task.created', taskId: task.id });
   }, 0);
CLM-004 DRIFT db confidence: 98%
"better-sqlite3" found — SQLite in use, not PostgreSQL (src/db/db.js)
📄 Doc says
docs/adr/ADR-001-database.md: "Database is PostgreSQL 15, accessed via the pg npm package"
💻 Code says
const Database = require('better-sqlite3')  (src/db/db.js:4)
Fix Candidates
📄 Doc Patch
Update ADR-001 to reflect SQLite instead of PostgreSQL
File: docs/adr/ADR-001-database.md
--- a/docs/adr/ADR-001-database.md
+++ b/docs/adr/ADR-001-database.md
@@ -1,6 +1,7 @@
 # ADR-001: Database Selection
 
 **Date:** 2024-01-10  
-**Status:** Accepted
+**Status:** Superseded (updated by DriftGuard DRIFT-004 detection)
 
 ## Decision
 
-We will use **PostgreSQL 15** as the primary database, accessed via the `pg`
-npm package. Connection pooling will be managed by `pg-pool`.
+We will use **SQLite** (via `better-sqlite3`) as the primary database.
+This decision supersedes the original PostgreSQL choice for simplicity
+in the current single-instance deployment.
💻 Code TODO
Add TODO in db/db.js noting the ADR still documents PostgreSQL
File: src/db/db.js
--- a/src/db/db.js
+++ b/src/db/db.js
@@ -1,5 +1,8 @@
 'use strict';
-// DRIFT-004: ADR-001 claims the database is PostgreSQL.
-// Actual implementation uses better-sqlite3 (SQLite).
+// TODO(DriftGuard DRIFT-004): ADR-001 documents the database as PostgreSQL 15 (pg package),
+// but this file uses better-sqlite3 (SQLite). If SQLite is the intentional choice,
+// update ADR-001. If PostgreSQL was required but never implemented, migrate this file.
+// Reference: docs/adr/ADR-001-database.md → Decision section
 const Database = require('better-sqlite3');
CLM-008 DRIFT db confidence: 98%
"better-sqlite3" found — SQLite in use, not PostgreSQL (src/db/db.js)
📄 Doc says
README.md: "Database is PostgreSQL 15, accessed via the pg npm package"
💻 Code says
const Database = require('better-sqlite3')  (src/db/db.js:4)
CLM-005 DRIFT auth confidence: 95%
No JWT middleware found in src/index.js — routes are unprotected
📄 Doc says
docs/adr/ADR-002-auth.md: "All API routes protected by JWT bearer token middleware in src/index.js"
💻 Code says
app.use(express.json()); app.use('/tasks', tasksRouter); — no auth middleware  (src/index.js)
Fix Candidates
📄 Doc Patch
Update ADR-002 to reflect that JWT auth was never implemented
File: docs/adr/ADR-002-auth.md
--- a/docs/adr/ADR-002-auth.md
+++ b/docs/adr/ADR-002-auth.md
@@ -3,7 +3,8 @@
 **Date:** 2024-01-12  
-**Status:** Accepted
+**Status:** NOT IMPLEMENTED — detected by DriftGuard DRIFT-005
 
 ## Decision
 
-All API routes will be protected by **JWT bearer token** middleware (`jsonwebtoken`
-package). The middleware will be applied globally in `src/index.js`.
+⚠️  This decision was accepted but never implemented. As of the current codebase,
+all routes are publicly accessible with no authentication.
💻 Code TODO
Add TODO in index.js to implement JWT middleware per ADR-002
File: src/index.js
--- a/src/index.js
+++ b/src/index.js
@@ -5,6 +5,10 @@
 const app = express();
 app.use(express.json());
 
+// TODO(DriftGuard DRIFT-005): ADR-002 mandates JWT bearer token middleware applied here
+// globally before any route handler. It was never implemented.
+// Add: const authMiddleware = require('./middleware/auth'); app.use(authMiddleware);
+// Reference: docs/adr/ADR-002-auth.md
+
 // NOTE: No auth middleware — ADR-002 specifies JWT protection on all routes
CLM-009 DRIFT auth confidence: 95%
No JWT middleware found in src/index.js — routes are unprotected
📄 Doc says
README.md: "All API routes protected by JWT bearer token middleware in src/index.js"
💻 Code says
app.use(express.json()); app.use('/tasks', tasksRouter); — no auth middleware  (src/index.js)