GT-604 (Core half): one shared client, and all three surfaces deposit through it - #314
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
📊 Bilingual Coverage ImpactPR Changes
Repository Coverage
To create skeletons: node .harness/scripts/generate-es-skeleton.mjs <file.md>Generated by GitHub Actions |
| // The route is taken from the published contract rather than typed here, so a | ||
| // path change upstream moves this client with it instead of leaving a caller | ||
| // that 404s against a route nobody remembers renaming. | ||
| this.endpoint = `${options.baseUrl.replace(/\/+$/, '')}${EVALUATION_INGEST_ENDPOINT_CONTRACT.path}`; |
|
GT-602 is owned by #306. This branch independently carries
Hard dependency unchanged: |
…gh it (GT-604)
Criterion 1 shipped the contract; the Tracker side of this gap shipped the route
(`POST /api/v1/core-evaluation-transactions`, machine-key authenticated exactly
as `/runtime-approvals`). This is the caller — the reason criterion 2 could not
be written before: a client against a route that does not exist is a mock with a
network stack, and it passes its own tests forever.
`TrackerEvaluationIngestClient` lives in `infra-providers` and is used by the
CLI (`evolith evaluate`), by the drift gate (`--format drift`) and by the MCP
`evolith-evaluate` tool. ONE client rather than three, because three hand-written
`fetch`es are three chances to drop `rulesExecuted[].engine`, to send `owner`
where the wire says `accountableOwner`, or to put a `tenantId` in the body — and
the last is a security hole, not a typo: the Tracker derives the tenant from
WHICH machine key matched, so a body-supplied tenant would let one key deposit
into another tenant's ledger. The payload is built exactly once, by
`toEvaluationIngestPayload`, the same module the Tracker's conformance test pins,
and the route comes from `EVALUATION_INGEST_ENDPOINT_CONTRACT` rather than from a
hand-typed string.
Two decisions worth arguing with:
* a failed deposit NEVER changes a verdict. `depositEvaluation` reports on stderr
and returns `{ deposited: false }`. Making the gate depend on the ledger's
availability would mean a Tracker outage silently opens the gate — the exact
inversion a governance product cannot afford. It is reported and never
swallowed: a deposit path that fails quietly is indistinguishable from one that
was never wired, which is the state this gap started from;
* not configured is not a failure. `createEvaluationIngestClientFromEnv` returns
`undefined` without `EVOLITH_TRACKER_URL`, and THROWS on a URL without a key —
that combination would 401 on every evaluation and lose every verdict anyway,
so it fails once at construction instead of once per run.
The CLI deposits BEFORE it exits on a blocking verdict. A run that blocks is
precisely the run whose evidence matters; exiting first would lose exactly those
and keep the passing ones, leaving a ledger that reads as if nothing ever failed.
Tests (21, all seen red first):
* `evaluation-ingest.client.spec.ts` (14) — red as "Cannot find module" before the
client existed; asserts the wire body against the SAME oracle the Tracker runs,
plus no-tenant, verbatim engine, the two owners kept apart, and that an outage
does not throw;
* `ingest-surface-parity.spec.ts` (7) — the criterion is not "a client exists" but
"all three surfaces use it". Run against the develop versions of the two call
sites it fails 5/7; it also forbids any surface from building its own request to
the ingest route.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5da2df7 to
abe6855
Compare
|
Rebased onto the merged
|
…mports it
`Test mcp-server`, `Test core-api`, `Unit Tests` and `E2E Tests` all died in
"Build workspace dependencies" with
src/tracker/evaluation-ingest.client.ts(49,8): error TS2307:
Cannot find module '@beyondnet/evolith-contracts/ingest'
The `./ingest` subpath IS declared in the contracts `exports` map, and
infra-providers declares the dependency in its package.json. What was missing is
cruder: **no CI step has ever built `@beyondnet/evolith-contracts`**, so
`dist/ingest/*.d.ts` did not exist when tsc looked for it. Nothing needed it
until this branch imported it, so the omission was invisible.
Added the build ahead of infra-providers in all five steps that build it.
This is the same failure class as GT-625 — a subpath import that resolves in one
environment and not another. There it was the npm workspace symlink hiding an
unpublishable package from every suite; here it is a build order hiding a missing
artifact. Worth noting on that row: the shape recurs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…yondnet dist The first attempt anchored on the infra-providers build line, which fixed the four jobs that build infra-providers and missed the ones that do not. `Test infra-providers` builds only core-domain and then runs jest, so it failed the same way one layer down: Cannot find module '@beyondnet/evolith-contracts/ingest' from 'src/tracker/evaluation-ingest.client.ts' Test Suites: 1 failed, 17 passed Anchored instead on `@beyondnet/evolith-core-domain`, which is built in all ten build steps across the workflow — every job that later resolves a `@beyondnet` package through its `exports` map to `dist/`. Three of those steps were single-line `run:` and became block form. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nd by enumeration Third attempt at this, and the first one that started from the full picture rather than from the job that happened to be red. Attempt 1 anchored on the infra-providers build line and fixed only the jobs that build infra-providers. Attempt 2 re-anchored on core-domain and fixed the rest of `ci-cd.yml` — `Test infra-providers` went green. `Lint and Type Check`, `Unit Tests` and `E2E Tests` stayed red with the identical error, because **they are not in `ci-cd.yml` at all**: they live in `sdk-cli-ci.yml`, which writes `npm run build -w` with the SHORT flag, so every `--workspace` search had been blind to it. Enumerated instead of guessed: every workflow that builds a `@beyondnet` workspace is `ci-cd.yml`, `sdk-cli-ci.yml`, `evolith-validate-dogfood.yml`, `reliability.yml` and `npm-release.yml`. All five now build the contracts package first, and a per-step check over all workflow files reports no build step that touches a `@beyondnet` workspace without it (the single flagged step is preceded by one in the same job). `npm-release.yml` matters beyond CI: it publishes, and it built only `@beyondnet/evolith-mcp`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Core half of GT-604 (P0) — "No surface writes evidence to the Tracker". Pairs with
evolith_tracker#82, which opens the ingest endpoint this client calls. Merge that one first.What the row was about
Every
evolith validate, everyenforce editveto, everytools/calland every drift-gate run evaporated when the process ended. The strategy rests on accumulated evidence while the surfaces that produce it had nowhere to put it. It is a composition defect invisible to any single-component review — which is why it survived this long.Criterion 1 (the ingest contract, with
correlationIdmandatory and both responsible parties represented separately:requestedBy.actorId= who asked,violations[].accountableOwner= who must fix) landed earlier. This is criterion 2.What landed
TrackerEvaluationIngestClientininfra-providers, used by CLIevaluate, the--format driftgate and MCPevolith-evaluate. Route and payload come from the published contract, never re-typed — so a contract change cannot drift silently into three call sites.A failed deposit warns on stderr and never changes a verdict or an exit code. A ledger outage must not open a gate; that is the whole point of the direction of this dependency.
Evidence
21 tests. Red first as
Cannot find module './evaluation-ingest.client', and the surface-parity guard fails 5/7 against thedevelopversions of the two call sites — so the wiring is guarded, not just the client.readlink -f node_modules/@beyondnet/evolith-core-domain→ worktree-local, verified before anything was measured; in these agent worktrees that symlink otherwise resolves into the main checkout and every cross-package test silently measures the wrong tree.Pre-existing and unrelated: 4 CLI test failures (the
init --format jsonsubprocess suites) fail identically with the changed file reverted todevelop.Deliberately left out
core-apiandagent-runtimedo not deposit. The contract's surface vocabulary includes them, but this gap row names CLI / MCP / drift-gate. A long-lived server's deposit lifecycle — async, retries, back-pressure — deserves its own decision rather than a copy of the CLI's synchronous one.tracker-webwas not typechecked on the Tracker side (nonode_modulesin that worktree); the edit there is additive type declarations with no code referencing them.🤖 Generated with Claude Code