Commit Graph
6 Commits
Author SHA1 Message Date
Hermes 9f9aeff0ab [pending-judge] feat(DC-165 rev2): product/site uniformity — fleet gate proven at composition root, single-source capabilities manifest
Site-vs-product audit (2026-10-09, owner decisions: "advertise as Premium"
and "It should be LLM readable") reconciles the entitlement triangle:
marketing copy <-> PREMIUM_FEATURES <-> license-server grant list.

r1 judge blockers/polish addressed:
- B2 (synthetic-router tests): NEW e2e fleet-gate-composition.e2e.test.js
  boots the REAL createApp() on an ephemeral port with a stdin control
  channel to mutate the live LicenseManager instance returned by createApp,
  proving: no activation -> 403 premiumRequired feature=fleet
  currentTier=free; activation WITHOUT fleet -> still 403 (per-feature,
  not tier-only); activation WITH fleet -> gate cleared; prefix inheritance
  across /fleet/hosts, /fleet/status, /fleet/deploy (4 real-boot tests).
- B0 (hand-typed constants in the manifest): MCP transport identity moved
  to MCP_RUNTIME in routes/mcp.js (the transport implementation's own
  module), exported; /info responses use MCP_RUNTIME.transport, and
  capabilities.js reads transport/protocolVersion/protocolVersions from
  the SAME object — nothing re-typed. New assertion: manifest == MCP_RUNTIME.
- B1 (OpenAPI 401 on a public route): capabilities block documents only
  200 with an explicit public-route note.
- P0 (migration replaced the whole array): now APPEND-ONLY union — current
  PREMIUM_FEATURES first, unknown legacy entries preserved verbatim.
  Dedicated test pins shape + persistence.
- P1 (eslint exit suppressed): verifier [5] hard-asserts rc==0.
- P2 (brief counts): brief quotes exact `git diff --stat` (below).

Product behavior:
- 'fleet' in PREMIUM_FEATURES; requirePremium('fleet') mounted at the
  /fleet ROUTER prefix in app.js BEFORE fleetRoutes -> every sub-route,
  present and future, inherits the gate.
- 'workflows' + 'auto-backup' (gated in routes forever, missing from the
  constant — offline grants could never include them) listed with
  descriptions quoted from src/recipes/bundled-workflows.js (5 ids) and
  routes/backups.js schedule schema (interval/retention/maxStorageBytes/
  destination/runImmediately). Manual one-click backup + Disaster Recovery
  stay free; only the schedule trio is gated (as coded).
- Offline activation forward-migration on load(); online untouched (server
  authoritative across the network boundary).
- GET /api/v1/capabilities: public ground truth derived from runtime
  modules only — APP_TEMPLATES, mcp-core TOOLS, mcp.js MCP_RUNTIME,
  PREMIUM_FEATURES, i18n, version.js. In PUBLIC_ROUTES (exact GET),
  covered by public-routes-drift directMounts; no-secrets negative test.
- openapi.yaml documents the endpoint.

diff --stat 4143bff..HEAD: 9 files, +558/-6:
  __tests__/dc165-uniformity.test.js        +201 (9 tests)
  __tests__/e2e-node/fleet-gate-composition +179 (4 real-boot tests)
  __tests__/public-routes-drift.test.js       +1
  openapi.yaml                               +20
  routes/capabilities.js                     +77 (NEW)
  routes/mcp.js                              +24/-4 (MCP_RUNTIME single source)
  src/app.js                                 +11 (fleet gate + capabilities mount)
  src/managers/license-manager.js            +50 (3 feature entries + migration)
  src/utilities/middleware.js                 +1 (PUBLIC_ROUTES)

Tests: 9 dc165 + 4 fleet-e2e + 76 license-manager + 21 tier-enforcement
+ 14 drift = 124 targeted; 73 MCP route/core tests unchanged green.
2026-10-09 02:28:52 -07:00
Hermes 026437035d [pending-judge] fix(r5): exercise the boot-timeout kill, total bridge-body handling, strict subdomain labels (judge r4 blockers)
- __tests__/e2e-node/composition-root.e2e.test.js: `boot()` takes {timeoutMs, code}
  and the timeout rejection carries the child it killed; a NEW test drives the real
  timeout against a child that never reports PORT/READY and asserts the OS-observed
  SIGKILL on that child (marker e2e-timeout-kill) — the r4 claim is now exercised.
- routes/deploys.js: bridge() coerces any non-object JSON body (null/[]/string) into
  the same failure shape as a non-JSON body, so no `body.ok` deref can 500 or report a
  false success; the subdomain label regex rejects leading AND trailing hyphens.
- __tests__/routes/deploys.catalog.r4.test.js: trailing-dash rejection case + three
  malformed-successful-body cases (clean 502, never 500).
2026-10-07 08:31:14 -07:00
Hermes Agent 88c9a3e4b6 [pending-judge] fix(r4/polish1): server-assigned port — no freePort release-rebind race
The e2e child now calls app.listen(0) and reports the OS-assigned port on
stdout (PORT:<n>); the parent reads it. The probe-then-release helper is
deleted, so no other process can take the port between probe and bind.
2026-10-07 04:23:00 -07:00
Hermes Agent 5cf5dac281 [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.
2026-10-07 03:59:30 -07:00
Hermes Agent 5bd5345469 [pending-judge] test(e2e): authenticated composition-root probes (r2 C blockers)
Judge r2 blockers addressed:
- MOUNTED vs UNKNOWN: probes now carry a REAL JWT minted by the same
  authManager the app wires (sub claim; generated in the boot child,
  emitted on READY). Every mounted-route assertion is compared against a
  live notFoundHandler-404 baseline for a known-nonexistent route on the
  same instance — a fall-through can no longer pass as mounted.
- Route-level behavior asserted with real payloads: /catalog/plex 200
  must contain plexinc/pms-docker AND {{PORT}}:32400; /security/shield 200
  must be success:true.
- OS-assigned ephemeral port (net server listen(0)) replaces the random
  30000-49999 guess; boot-timeout cleared on readiness; SIGTERM+SIGKILL
  shutdown with bounded wait.
- 7 tests (was 5). Suite: 156 suites / 3512 tests green.
2026-10-07 03:09:14 -07:00
Hermes Agent 8744609887 [pending-judge] fix(integration): catalog detail reads app.docker.* (DC-160); Shield committed; composition-root e2e
- DC-160: /catalog/:appId read app.image/ports/env/volumes which live under
  app.docker.* — every detail response was empty. Now reads docker.* and
  surfaces defaultPort/subdomain/healthCheck/mediaMount/claimToken/difficulty/
  setupInstructions for the deploy form. List/search use the templates own
  category field (old hand-rolled CATEGORY_MAP mis-categorized apps).
- Shield (routes/shield.js + 5-line app.js mount) committed to source after
  weeks as prod-only drift; regression test asserts the mount exists.
- New composition-root e2e suite: boots REAL createApp() on an ephemeral
  port, probes the historically-broken routes through the REAL mount table
  (wizard wiring, shield, catalog detail, deploys bridge) — the class of bug
  all 3 P7 dogfood findings belonged to. 5 tests.
- Full suite 3499/3499 (155 suites); table-driven DC-160 test covers all 73
  image templates.
2026-10-07 00:58:03 -07:00