Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,18 @@ jobs:
node .harness/scripts/ci/61-validate-chart-image-uid.mjs --verbose
node --test .harness/scripts/ci/61-validate-chart-image-uid.test.mjs

# GT-654 — three services of one product answered /health in three shapes,
# and the verdict literal differed in case too. A probe written against
# either shape reported the others as broken: on 2026-08-03 a cross-cluster
# check matched `"status":"ok"` literally and called two healthy services
# unreachable. Nothing reads the body — probes use httpGet, the Dockerfiles
# use `curl -f`, k6 checks r.status — which is why unifying was safe and
# why leaving it divergent bought nothing.
- name: Every surface answers /health in the ADR-0073 envelope
run: |
node .harness/scripts/ci/62-validate-health-envelope.mjs --verbose
node --test .harness/scripts/ci/62-validate-health-envelope.test.mjs

# GT-650 / ADR-0125 — the artifact registry is the accepted single declaration, and the gate
# corpus is still hand-maintained while the migration lands. For as long as both exist, the
# only thing making that intermediate state safe is that they are checked to agree. This
Expand Down
183 changes: 183 additions & 0 deletions .harness/scripts/ci/62-validate-health-envelope.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/usr/bin/env node

/**
* Every surface answers `/health` in the ADR-0073 envelope. GT-654.
*
* ## The defect this closes
*
* Three services of one product answered three shapes:
*
* core-api {success, data: {status: "OK", …}, meta: {…}}
* mcp {"status":"ok","transport":"http","protocol":"mcp",…}
* agent-runtime {"status":"ok","service":"agent-runtime-api",…}
*
* The nesting differed and so did the CASE of the verdict. Anything probing all
* three had to special-case each one, and a probe written against either shape
* reported the others as broken — which is not hypothetical: on 2026-08-03 a
* cross-cluster check matched `"status":"ok"` literally and called two healthy
* services unreachable while they were serving.
*
* ## Why unifying was safe, measured before it was done
*
* NOTHING reads the body. The Helm probes use `httpGet` (status code only), the
* Dockerfiles use `curl -f` (non-2xx only), the k6 profiles check
* `r.status === 200`, and RoboSoft checks `hr.ok`. The earlier worry — "the
* probes are configured against a shape" — was wrong, and checking it turned a
* decision into a one-line answer.
*
* ## What it checks
*
* This is a SOURCE check, not a live one: it asserts each surface's health
* handler constructs the envelope, so a fourth surface cannot land answering a
* fourth shape. It deliberately does not boot the three services — a guard that
* needs a running cluster runs nowhere, and the live contrast already exists in
* `local-test.sh url`.
*
* The two claims per surface are separate on purpose: emitting `success`/`data`
* without `schemaVersion` is still a shape nobody can version, and emitting a
* lowercase `'ok'` inside a correct envelope reproduces exactly the false
* failure that started this.
*
* ## Anti-vacuous pass
*
* Zero surfaces checked is a hard failure through `assertScanned`: a renamed
* handler must not read as "all three agree".
*
* USAGE
* node .harness/scripts/ci/62-validate-health-envelope.mjs
* node .harness/scripts/ci/62-validate-health-envelope.mjs --verbose
*
* EXIT CODES
* 0 every surface's health handler emits the envelope
* 1 a bare shape, a missing schemaVersion, a lowercase verdict, or a vacuous scan
*/

import fs from 'node:fs';
import path from 'node:path';

import { findRepoRoot } from '../lib/paths.mjs';
import { assertScanned } from '../lib/coverage.mjs';

const GUARD = '62-validate-health-envelope';

/**
* Hand-written pairs. Deriving "which file serves health" from a convention
* would silently skip a surface the day someone moves a handler, and a skipped
* surface is the shape of the defect itself.
*/
export const SURFACES = [
{
name: 'core-api',
file: 'src/apps/core-api/src/infrastructure/interceptors/envelope.interceptor.ts',
// core-api envelopes EVERY response through a global interceptor, so its
// health route carries no shape of its own — the interceptor IS the claim,
// and that file never mentions `/health`.
why: 'the global EnvelopeInterceptor wraps every response, health included',
},
{
name: 'mcp',
file: 'src/packages/mcp-server/src/mcp/mcp-server.service.ts',
// `schemaVersion` lives in the imported envelope module, not here; what this
// file must show is that the health routes go THROUGH `success(`.
version: 'src/packages/mcp-server/src/common/envelopes.ts',
why: 'health routes call success() from common/envelopes',
},
{
name: 'agent-runtime',
file: 'src/apps/agent-runtime-api/src/health/health.controller.ts',
why: 'the controller builds the envelope in-file',
},
];

/**
* Whole file, not a slice.
*
* The first version cut a 4000-character window forward from the first
* `/health` and checked that. It failed on all three for its own reasons: the
* core-api interceptor never mentions `/health`, the agent-runtime helper is
* defined ABOVE the routes, and the MCP's `schemaVersion` lives in an imported
* module. A heuristic about text layout is not a claim about behaviour, and
* this guard had no business inventing one.
*/
export function checkSurface(name, text, versionText) {
const problems = [];

const enveloped = /success\s*\(/.test(text) || /success:\s*true/.test(text);
if (!enveloped) {
problems.push(
`${name}: builds no ADR-0073 envelope (no \`success(\` call and no \`success: true\`). A bare object is the shape that made a probe report healthy services as unreachable`,
);
}

// Reachable in this file OR in the module it takes the envelope from.
const versioned = /schemaVersion|ENVELOPE_SCHEMA_VERSION/.test(text + (versionText ?? ''));
if (!versioned) {
problems.push(
`${name}: no \`schemaVersion\` reachable — an envelope nobody can version is not a contract`,
);
}

// The case matters on its own: a lowercase verdict inside a correct envelope
// reproduces exactly the literal mismatch that started this.
//
// Comments are stripped first. The handler's own header EXPLAINS the shape it
// replaced — `{status: 'ok', …}` — and a raw scan fired on that explanation.
// Second time this session a guard flagged its own documentation; a guard that
// does that gets the documentation deleted, not the defect fixed.
const code = text
.replace(/\/\*[\s\S]*?\*\//g, '')
.split('\n')
.filter((l) => !/^\s*(\/\/|\*)/.test(l))
.join('\n');
if (/status:\s*'ok'/.test(code) || /"status"\s*:\s*"ok"/.test(code)) {
problems.push(
`${name}: emits a lowercase \`ok\` verdict; core-api emits \`OK\`, and a literal comparison across surfaces is what failed on 2026-08-03`,
);
}

return problems;
}

function main(argv = process.argv.slice(2)) {
const verbose = argv.includes('--verbose');
const root = findRepoRoot();
const rows = [];
const violations = [];

for (const s of SURFACES) {
const abs = path.join(root, s.file);
if (!fs.existsSync(abs)) {
violations.push(`${s.name}: ${s.file} not found. A moved file must not read as agreement`);
continue;
}
const versionText = s.version && fs.existsSync(path.join(root, s.version))
? fs.readFileSync(path.join(root, s.version), 'utf8')
: undefined;
const problems = checkSurface(s.name, fs.readFileSync(abs, 'utf8'), versionText);
rows.push({ name: s.name, ok: problems.length === 0, why: s.why });
violations.push(...problems);
}

assertScanned(rows.length, { what: 'health surfaces', where: SURFACES.map((s) => s.file) });

console.log(`${GUARD} — every surface answers /health in the ADR-0073 envelope`);
console.log(` surfaces checked ... ${rows.length}`);
if (verbose) {
for (const r of rows) {
console.log(` • ${r.ok ? 'OK ' : 'FAIL'} ${r.name.padEnd(16)}${r.why ? ' — ' + r.why : ''}`);
}
}

if (violations.length > 0) {
console.error(`\n✗ ${GUARD}: ${violations.length} problem(s):\n`);
for (const v of violations) console.error(` • ${v}`);
console.error('\n Context: reference/core/control-center/gaps/gap-reference-catalog.md#gt-654');
process.exit(1);
}

console.log(`\n✓ ${GUARD}: all ${rows.length} surface(s) emit the envelope.`);
}

if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
90 changes: 90 additions & 0 deletions .harness/scripts/ci/62-validate-health-envelope.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env node --test

/**
* Negative fixtures for `62-validate-health-envelope.mjs` (GT-654).
*
* Every rejection below was run against the predicate and seen to turn it red;
* the green case is here so a reject-everything predicate cannot masquerade as
* thorough. Its first version did the opposite and failed all three real
* surfaces for its own reasons — see the note on comment stripping.
*
* Run: node --test .harness/scripts/ci/62-validate-health-envelope.test.mjs
*/

import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';

import { findRepoRoot } from '../lib/paths.mjs';
import { checkSurface, SURFACES } from './62-validate-health-envelope.mjs';

const GOOD = `
const ENVELOPE_SCHEMA_VERSION = '1.0.0';
function envelope(command, data) {
return { success: true, data, meta: { command, schemaVersion: ENVELOPE_SCHEMA_VERSION } };
}
health() { return envelope('http GET /health', { status: 'OK', service: 'x' }); }
`;

test('the enveloped shape is accepted, or every rejection below proves nothing', () => {
assert.deepEqual(checkSurface('x', GOOD), []);
});

test('a bare object is rejected — the exact shape two surfaces shipped', () => {
const bare = `health() { return { status: 'OK', service: 'x' }; }`;
const problems = checkSurface('x', bare);
assert.equal(problems.length, 2, 'no envelope AND no schemaVersion');
assert.ok(problems.some((p) => /builds no ADR-0073 envelope/.test(p)));
});

test('an envelope with no schemaVersion is rejected — a shape nobody can version', () => {
const unversioned = `health() { return { success: true, data: { status: 'OK' }, meta: {} }; }`;
const problems = checkSurface('x', unversioned);
assert.equal(problems.length, 1);
assert.match(problems[0], /schemaVersion/);
});

test('a lowercase verdict INSIDE a correct envelope is still rejected', () => {
// This is the literal that made a cross-cluster probe call two healthy
// services unreachable. A correct envelope does not excuse it.
const problems = checkSurface('x', GOOD.replace("status: 'OK'", "status: 'ok'"));
assert.equal(problems.length, 1);
assert.match(problems[0], /lowercase/);
});

test('schemaVersion may live in the imported envelope module, not the handler', () => {
// The MCP calls `success()` from `common/envelopes`; requiring the constant in
// the handler file would fail a surface that is entirely correct.
const handler = `res.end(JSON.stringify(success({ status: 'OK' }, meta)));`;
assert.deepEqual(checkSurface('mcp', handler, 'export const MCP_ENVELOPE_SCHEMA_VERSION = "1.0.0";'), []);
});

test('a comment describing the OLD bare shape does not fail the file', () => {
// The first version scanned raw text and fired on the handler's own header,
// which explains `{status: 'ok', …}` as the shape it replaced. A guard that
// flags its own documentation gets the documentation deleted, not the defect
// fixed — the same trap already hit once this session on GITLEAKS_LICENSE.
const documented = `/**\n * These used to return \`{status: 'ok', service}\`.\n */\n` + GOOD;
assert.deepEqual(checkSurface('x', documented), []);
});

test('every real surface satisfies the predicate', () => {
const root = findRepoRoot();
for (const s of SURFACES) {
const abs = join(root, s.file);
assert.ok(existsSync(abs), `${s.file} is registered but missing`);
const versionText = s.version && existsSync(join(root, s.version))
? readFileSync(join(root, s.version), 'utf8')
: undefined;
assert.deepEqual(
checkSurface(s.name, readFileSync(abs, 'utf8'), versionText),
[],
`${s.name} does not emit the envelope`,
);
}
});

test('all three surfaces are registered — a dropped one must not read as agreement', () => {
assert.deepEqual(SURFACES.map((s) => s.name).sort(), ['agent-runtime', 'core-api', 'mcp']);
});
Original file line number Diff line number Diff line change
Expand Up @@ -9713,6 +9713,39 @@
],
"dependencyDisposition": "satisfied",
"dependencyRationale": "The licence dependency was removed rather than satisfied: only the gitleaks-action wrapper required an org licence, so installing the MIT binary closed the Dependabot blind spot without any secret. The remaining dependency was ordering \u2014 the required-context promotion had to follow a green run on both protected branches, or every open pull request would have deadlocked as in PR #218. Both runs are recorded (develop 30867424760, main 30868306980) and the promotion was read back from the API: 8 required contexts on each branch, up from 7, with enforce_admins true."
},
{
"id": "GT-654",
"closedAt": "2026-08-04",
"closureCommit": "a29f593a",
"evidence": [
"src/packages/mcp-server/src/mcp/mcp-server.service.ts",
"src/apps/agent-runtime-api/src/health/health.controller.ts",
".harness/scripts/ci/62-validate-health-envelope.mjs",
".harness/scripts/ci/62-validate-health-envelope.test.mjs"
],
"validationCommands": [
"node .harness/scripts/ci/62-validate-health-envelope.mjs --verbose",
"node --test .harness/scripts/ci/62-validate-health-envelope.test.mjs",
"npx jest --config src/packages/mcp-server/jest.config.js --testPathPatterns 'mcp-server.service'"
],
"dependencyDisposition": "none",
"dependencyRationale": "The row was registered as a decision, and the decision turned out to be forced by measurement rather than open: nothing reads the /health body. Helm probes use httpGet, the Dockerfiles curl -f, k6 checks r.status === 200 and RoboSoft hr.ok, so unifying on the ADR-0073 envelope broke no consumer. The caution the row carried was wrong and is recorded as such."
},
{
"id": "GT-655",
"closedAt": "2026-08-04",
"closureCommit": "a29f593a",
"evidence": [
"src/tests/exploration/bindings.ts",
"reference/core/control-center/audits/surface-parity-matrix.json"
],
"validationCommands": [
"npm run test:exploration",
"node -e \"const c=require('./src/tests/exploration/.out/coverage.json'); if(c.executedOperations!==51||c.uncoveredTriangleOps.length!==1) process.exit(1)\""
],
"dependencyDisposition": "accepted-scope",
"dependencyRationale": "Three of the four are bound and invoked on all three surfaces (executed 48 -> 51, invocations 66 -> 75). satellite-create stays unbound with its reason written in bindings.ts: it provisions a live GitHub repository and writes the local registry, so binding it as-is would have CI create real repositories on every run. It needs an undoable effect or a trustworthy dry-run and has neither, so uncoveredTriangleOps keeps listing it rather than the exemption being silent."
}
]
}
Loading
Loading