[grade=B urn:ump:35dpnggtxsqrxvlqm2d2buqkbnggw3acflbccnkqpqyiiijcxx3q] fix(DC-170): MCP truth batch — 6 dead tools fixed, full-catalog drift gate
Audit P0-5 + deploy_app truth (batch 5 of the 2026-10-27 sweep, AUDIT-2026-10-27.md committed here): ROUTE ADDITIONS: - routes/containers.js GET / — container list (dockerode listContainers); the router had NO list route, so dashcaddy_list_containers 404'd always - routes/dns.js GET /records — record list via active provider with a list-records capability guard (501 + guidance for push-only providers like RFC2136); dashcaddy_list_dns called GET /dns, never existed HANDLER FIXES (src/mcp/mcp-core.js): - get_service resolves from GET /services by id/name (single-service GET never existed) with a known-ids 404 - check_health + diagnose -> /health/service/:id and /health/services (both previously hit nonexistent routes) - container_action remove -> DELETE /containers/:id (was /:id/remove) - system_health/system_metrics via apiCall error envelope (metrics text capped at 64KB) - search_catalog applies category filter client-side (/catalog/search has no category param — constraint was silently dropped) - generate_caddyfile defaults authService=status when auth:true (auth flag alone silently produced UNGATED Caddyfiles) - deploy_app honest re-contract: registers service + generates Caddyfile, does NOT claim image pull/container creation/deployed:true; base domain read from /config (hardcoded owner .sami TLD removed); caddy-code failure now surfaces as caddyfileError + FAILED message - list_fleet description gains Premium hint VERIFIER (audit #40): EXPECTED table 12 -> 34 rows covering EVERY tool's (method,path); NEW Layer-3 handler-derived test drives the real createToolHandler for all 28 tools and strictly resolves every captured call mount-aware (a vacuous first draft of this gate was caught and fixed pre-grade); extractRoutes regex extended to apiRouter.get inline decls; Bearer credential-passthrough test added (DC-162 coverage gap) NEW TESTS: dns-records.routes.test.js (5 route-level cases incl. 501 capability behavior); deploy_app partial-failure + no-.sami tests HOST RELIABILITY (same sweep, evidenced here): jest maxWorkers 50% — default 5-worker runs repeatedly OOM-killed this host's gateway (dmesg 2026-10-29 00:58:40; swap was 4G/4G full). Gateway protected separately via systemd OOMScoreAdjust=-500 drop-in + /swapfile2 (host ops, graded B urn:ump:35dpnggtxsqrxvlqm2d2buqkbnggw3acflbccnkqpqyiiijcxx3q). DC-171 DNS provider-parity ticket (Sami, plug-in-directly) appended to backlog. Full suite: 173/173 suites, 3740/3740 tests. Judge B r1 (urn:ump:jsobkg2t2omzvpinel2fufk7sspoy3vubsrth3e6lat3ygolvffq) blockers all fixed: dns.js 501 arg order (the file aliases error() message-first — the sweep's 'reversed-arg' finding was a false positive for its 7 sites; lesson #14), deploy_app partial-failure honesty, handler-derived verifier.
This commit is contained in:
9 files changed
+529
-40
No files matched your search
@@ -190,10 +190,64 @@ describe('DC-166 handler routing (captured fetchImpl)', () => {
|
||||
expect(calls[0].headers['x-api-key']).toBe('dk_' + 'test_key');
|
||||
});
|
||||
|
||||
test('credential passthrough preserved for JWT callers (Bearer), not just x-api-key', async () => {
|
||||
// Merge-judge r2 polish (urn:ump:5clu4udrbwuwxn74tqwxtukfjnrwkrbiykp6uh66ipipzxr32xsq):
|
||||
// the DC-162 contract preserves the CALLER'S credential TYPE — exercise the
|
||||
// Bearer path, which previously had zero coverage.
|
||||
const { calls, handleTool } = harness({ headers: { Authorization: 'Bearer test_jwt' } });
|
||||
await handleTool('dashcaddy_dns_add_record', { domain: 'w.example', ip: '1.1.1.1' });
|
||||
expect(calls[0].headers['Authorization']).toBe('Bearer ' + 'test_jwt');
|
||||
expect(calls[0].headers['x-api-key']).toBeUndefined();
|
||||
});
|
||||
|
||||
test('unknown tool still rejected after catalog extension', async () => {
|
||||
const { handleTool } = harness();
|
||||
const r = await handleTool('dashcaddy_delete_everything', {});
|
||||
expect(r.error).toBe(true);
|
||||
expect(r.message).toMatch(/Unknown tool/);
|
||||
});
|
||||
|
||||
test('deploy_app reports partial failure when Caddyfile generation fails (DC-170)', async () => {
|
||||
// r2 judge blocker (urn:ump:jsobkg2t2omzvpinel2fufk7sspoy3vubsrth3e6lat3ygolvffq):
|
||||
// a caddy-code failure was swallowed — registered:true was returned while
|
||||
// the message claimed the route was generated.
|
||||
const calls = [];
|
||||
const fetchImpl = async (url, opts = {}) => {
|
||||
calls.push(String(url));
|
||||
if (String(url).includes('/caddycode/generate')) {
|
||||
return { ok: false, status: 500, text: async () => JSON.stringify({ error: 'caddy templating exploded' }) };
|
||||
}
|
||||
return { ok: true, status: 200, text: async () => JSON.stringify({
|
||||
success: true, dashboardHost: 'status.example.net',
|
||||
apps: [{ id: 'plex', name: 'Plex', category: 'media', image: 'plex/plex', ports: [32400] }],
|
||||
}) };
|
||||
};
|
||||
const handleTool = createToolHandler({ baseUrl: 'http://127.0.0.1:1', apiKey: 'dk_x', fetchImpl });
|
||||
const r = await handleTool('dashcaddy_deploy_app', { templateId: 'plex' });
|
||||
expect(calls.filter((c) => c.includes('/config'))).toHaveLength(1); // base domain from /config, not hardcoded .sami
|
||||
expect(r.registered).toBe(true);
|
||||
expect(r.caddyfile).toBeNull();
|
||||
expect(r.caddyfileError).toMatch(/caddy templating exploded/);
|
||||
expect(r.message).toMatch(/FAILED/);
|
||||
expect(r.url).toBe('https://plex.example.net');
|
||||
});
|
||||
|
||||
test('deploy_app derives the base domain from /config (no hardcoded .sami)', async () => {
|
||||
const calls = [];
|
||||
const fetchImpl = async (url, opts = {}) => {
|
||||
calls.push({ url: String(url), body: opts.body ? JSON.parse(opts.body) : undefined });
|
||||
return { ok: true, status: 200, text: async () => JSON.stringify({
|
||||
success: true, dashboardHost: 'status.example.net',
|
||||
apps: [{ id: 'plex', name: 'Plex', category: 'media', image: 'plex/plex', ports: [32400] }],
|
||||
caddyfile: 'generated-route',
|
||||
}) };
|
||||
};
|
||||
const handleTool = createToolHandler({ baseUrl: 'http://127.0.0.1:1', apiKey: 'dk_x', fetchImpl });
|
||||
const r = await handleTool('dashcaddy_deploy_app', { templateId: 'plex', subdomain: 'plex' });
|
||||
expect(r.registered).toBe(true);
|
||||
expect(r.url).toBe('https://plex.example.net');
|
||||
const svcCall = calls.find((c) => c.url.includes('/services'));
|
||||
expect(svcCall.body.domain).toBe('plex.example.net');
|
||||
expect(JSON.stringify(svcCall.body)).not.toMatch(/\.sami/);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user