mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 16:09:43 +02:00
fix(api): close contract gaps found by post-merge adversarial audit
A 15-agent audit of the merged tree confirmed 9 envelope/contract bugs;
all fixed here, with live-server contract tests added:
Blockers (fresh-install quick start broken):
- session-ui.js runClaude/runShell unwrapped .data from the /api/cases/:name
404 error envelope (which has no data key), so a not-yet-created case threw
TypeError instead of triggering the auto-create fallback. Now '?.data ?? {}'.
Contract violations on the new stable surface:
- Unknown /api routes returned HTTP 404 with {success:true,...} (Fastify's
default not-found payload was wrapped by the envelope hook). Added a
setNotFoundHandler returning the standard error envelope for /api paths.
- POST /api/events/subscribe 400 body became {success:true,data:{error}};
now createErrorResponse(INVALID_INPUT).
- POST /api/clipboard validation error lacked errorCode and shipped HTTP 200;
now createErrorResponse(INVALID_INPUT) -> 400.
- POST /api/run catch path returned bare {success:false,sessionId,error}
(HTTP 200, no errorCode); now OPERATION_FAILED envelope -> 422 with the
dead session id in the message.
- DELETE tail-file/:streamId returned {success: closed}, colliding with the
envelope discriminator; now returns {closed}.
Dead/regressed UI paths:
- Plan history modal could never open: route returned the bare history array
under data while the frontend read data.data.history/currentVersion. Route
now returns {history, currentVersion}; modal task count fixed to stats.total.
- Self-update error toast read j.error.message from the string-typed envelope
error, always falling back to the generic message; now reads the string.
Cleanup:
- Removed the stale QuickStartResponse type (unreferenced; documented the
pre-envelope shape and invited success-key collisions).
Tests: new test/http-contract.test.ts boots a real WebServer (port 3168) and
pins the envelope, /api/v1 alias, error statuses, and the /api 404 shape —
the route-test harness does not install the server-level hook, so these need
the live server. Updated file-routes/plan-routes/scheduled-runs tests to the
fixed shapes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,13 +6,14 @@
|
||||
import { FastifyInstance } from 'fastify';
|
||||
import { SseEvent } from '../sse-events.js';
|
||||
import type { EventPort } from '../ports/index.js';
|
||||
import { createErrorResponse, ApiErrorCode } from '../../types.js';
|
||||
|
||||
export function registerClipboardRoutes(app: FastifyInstance, ctx: EventPort): void {
|
||||
app.post('/api/clipboard', async (req) => {
|
||||
const body = req.body as { text?: string; sessionId?: string };
|
||||
const text = body?.text;
|
||||
if (typeof text !== 'string' || text.length === 0) {
|
||||
return { success: false, error: 'Missing or empty "text" field' };
|
||||
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Missing or empty "text" field');
|
||||
}
|
||||
ctx.broadcast(SseEvent.ClipboardWrite, {
|
||||
text,
|
||||
|
||||
@@ -375,12 +375,15 @@ export function registerFileRoutes(app: FastifyInstance, ctx: SessionPort): void
|
||||
});
|
||||
});
|
||||
|
||||
// Close a file stream
|
||||
// Close a file stream. Returns { closed } rather than { success: closed } —
|
||||
// a top-level `success` key would collide with the envelope discriminator
|
||||
// (the preSerialization hook would pass `{success:false}` through as a
|
||||
// malformed error envelope instead of wrapping it).
|
||||
app.delete('/api/sessions/:id/tail-file/:streamId', async (req) => {
|
||||
const { id, streamId } = req.params as { id: string; streamId: string };
|
||||
findSessionOrFail(ctx, id); // Validates session exists
|
||||
const closed = fileStreamManager.closeStream(streamId);
|
||||
return { success: closed };
|
||||
return { closed };
|
||||
});
|
||||
// Session-scoped file download.
|
||||
// Uses the same realpath-based workspace boundary as file preview/raw routes;
|
||||
|
||||
@@ -408,7 +408,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, 'Ralph tracker not available');
|
||||
}
|
||||
|
||||
return { success: true, data: tracker.getPlanHistory() };
|
||||
return { success: true, data: { history: tracker.getPlanHistory(), currentVersion: tracker.planVersion } };
|
||||
});
|
||||
|
||||
// ========== Rollback to Version ==========
|
||||
|
||||
@@ -1086,9 +1086,10 @@ export function registerSessionRoutes(
|
||||
await ctx.cleanupSession(session.id, true, 'run_prompt_complete');
|
||||
return { sessionId: session.id, ...result };
|
||||
} catch (err) {
|
||||
// Clean up session on error too
|
||||
// Clean up session on error too. The session is destroyed here, so its id
|
||||
// is only useful for log correlation — carry it in the error message.
|
||||
await ctx.cleanupSession(session.id, true, 'run_prompt_error');
|
||||
return { success: false, sessionId: session.id, error: getErrorMessage(err) };
|
||||
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, `${getErrorMessage(err)} (session ${session.id})`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user