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
Phase Timings
| Phase | Agent wall-clock |
| doc_parse_ms | 4ms |
| code_map_ms | 4ms |
| drift_analysis_ms | 3ms |
| fix_gen_ms | 0ms |
| report_gen_ms | 0ms |
| Total run | 20ms |
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)