[pending-judge] fix(r4): audit contract, subdomain forwarding, timeout kill (judge r3 blockers)

BLOCK0: catalog deploy audit now uses the REAL AuditLogger contract
(auditLogger.log({action,resource,details,outcome,ip})) fire-and-forget
via Promise.resolve().catch — async audit failures can never fail the
deploy response (tested with a throwing audit backend).
BLOCK1: verify.sh targeted-jest block captures jest exit code explicitly
(set +e / rc / loud FAIL) — no silent termination under set -e.
BLOCK2: subdomain advertised by the App Selector modal is now FORWARDED
end-to-end (selector -> /deploys/catalog/deploy validated charset ->
bridge --subdomain -> CLI CatalogInstallOptions, implemented in
dc-catalog-2 r3); invalid subdomains 400 before the bridge (4 cases).
POLISH: boot-timeout SIGKILLs the child (no leaked process).
New r4 suite: 7 tests (audit shape success/failure, subdomain forward +
4 rejections). Full suite 157 suites / 3519 tests green.
This commit is contained in:
Hermes Agent committed 2026-10-07 03:59:30 -07:00
1 parent 2549101af1
commit 5cf5dac281
5 files changed
+123 -10

No files matched your search

+16 -7
View File
@@ -118,9 +118,14 @@ module.exports = function ({ asyncHandler, log, auditLogger, fetchT }) {
}
env[k] = v;
}
const subdomain = String((req.body && req.body.subdomain) || '');
if (subdomain !== '' && !/^[a-z0-9][a-z0-9-]{0,62}$/.test(subdomain)) {
return errorResponse(res, 400, 'invalid subdomain');
}
const payload = {
app,
port,
subdomain,
media_path: String((req.body && req.body.media_path) || ''),
host_ip: String((req.body && req.body.host_ip) || ''),
env,
@@ -130,14 +135,18 @@ module.exports = function ({ asyncHandler, log, auditLogger, fetchT }) {
const { status, body } = await bridge('POST', '/api/catalog/deploy', payload, SHIPDECK_DEPLOY_TIMEOUT);
const seconds = ((Date.now() - t0) / 1000).toFixed(1);
if (auditLogger) {
auditLogger('deploy.catalog', {
// Audit via the real AuditLogger contract (this.log({action, resource,
// details, outcome, ip}) — see src/security/audit-logger.js); non-fatal.
if (auditLogger && typeof auditLogger.log === 'function') {
// Fire-and-forget: async rejections escape sync try/catch, so bind a
// .catch() instead (audit must never fail the deploy response).
Promise.resolve(auditLogger.log({
action: 'deploy.catalog',
app,
port,
ok: status === 200 && !!body.ok,
seconds,
});
resource: `catalog-app:${app}`,
details: { app, port, bridgeStatus: status, ok: !!(body && body.ok), seconds },
outcome: status === 200 && !!(body && body.ok) ? 'success' : 'failure',
ip: req.ip,
})).catch(() => { /* non-fatal by contract */ });
}
if (status !== 200 || !body.ok) {