[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.
This commit is contained in:
9 files changed
+566
-6
No files matched your search
@@ -63,8 +63,18 @@ const { ValidationError, ForbiddenError } = require('../src/utilities/errors');
|
||||
const { ok } = require('../src/utils/responses');
|
||||
const { createToolHandler, createMcpHandler, TOOLS } = require('../src/mcp/mcp-core');
|
||||
|
||||
const PROTOCOL_VERSION = '2025-03-10';
|
||||
const SUPPORTED_PROTOCOL_VERSIONS = ['2025-03-10', '2024-11-05'];
|
||||
// MCP_RUNTIME is the single source of truth for the transport identity.
|
||||
// DC-165 rev2 (judge B0): the /info route AND the /api/v1/capabilities
|
||||
// manifest both read these values instead of re-typing them, so a protocol
|
||||
// bump in one place cannot leave the other stale. The capability test asserts
|
||||
// the manifest equals these constants.
|
||||
const MCP_RUNTIME = {
|
||||
transport: 'streamable-http',
|
||||
protocolVersion: '2025-03-10',
|
||||
supportedProtocolVersions: ['2025-03-10', '2024-11-05'],
|
||||
};
|
||||
const PROTOCOL_VERSION = MCP_RUNTIME.protocolVersion;
|
||||
const SUPPORTED_PROTOCOL_VERSIONS = MCP_RUNTIME.supportedProtocolVersions;
|
||||
const SERVER_VERSION = (() => {
|
||||
try {
|
||||
return require(path.join(__dirname, '..', 'package.json')).version;
|
||||
@@ -275,7 +285,7 @@ module.exports = function createMcpRoutes({ asyncHandler, errorResponse, log, au
|
||||
router.get('/info', asyncHandler(async (req, res) => {
|
||||
ok(res, {
|
||||
endpoint: publicEndpoint(req),
|
||||
transport: 'streamable-http',
|
||||
transport: MCP_RUNTIME.transport,
|
||||
protocolVersion: PROTOCOL_VERSION,
|
||||
supportedProtocolVersions: SUPPORTED_PROTOCOL_VERSIONS,
|
||||
serverVersion: SERVER_VERSION,
|
||||
@@ -320,7 +330,7 @@ module.exports = function createMcpRoutes({ asyncHandler, errorResponse, log, au
|
||||
// shapes they don't (e.g. ChatGPT hosted connectors need public https).
|
||||
const httpConfig = {
|
||||
url: endpoint,
|
||||
transport: 'streamable-http',
|
||||
transport: MCP_RUNTIME.transport,
|
||||
headers: { 'x-api-key': keyData.key },
|
||||
mcpProtocolVersion: PROTOCOL_VERSION,
|
||||
};
|
||||
@@ -336,7 +346,7 @@ module.exports = function createMcpRoutes({ asyncHandler, errorResponse, log, au
|
||||
name: keyData.name,
|
||||
scopes: keyData.scopes,
|
||||
endpoint,
|
||||
transport: 'streamable-http',
|
||||
transport: MCP_RUNTIME.transport,
|
||||
httpConfig,
|
||||
note: clientNotes[client],
|
||||
warning: 'Save this key securely — it will not be shown again. Revoke any time from Settings → Connect your AI.',
|
||||
@@ -347,3 +357,7 @@ module.exports = function createMcpRoutes({ asyncHandler, errorResponse, log, au
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
// DC-165 rev2: expose transport identity so the capabilities manifest reads
|
||||
// the SAME constants the live MCP route serves (single source of truth).
|
||||
module.exports.MCP_RUNTIME = MCP_RUNTIME;
|
||||
Reference in new issue
Block a user