Skip to content

GT-604 (Core half): one shared client, and all three surfaces deposit through it - #314

Merged
beyondnetPeru merged 5 commits into
developfrom
feat/gt-604-ingest-client
Jul 31, 2026
Merged

GT-604 (Core half): one shared client, and all three surfaces deposit through it#314
beyondnetPeru merged 5 commits into
developfrom
feat/gt-604-ingest-client

Conversation

@beyondnetPeru

@beyondnetPeru beyondnetPeru commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

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, every enforce edit veto, every tools/call and 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 correlationId mandatory and both responsible parties represented separately: requestedBy.actorId = who asked, violations[].accountableOwner = who must fix) landed earlier. This is criterion 2.

What landed

TrackerEvaluationIngestClient in infra-providers, used by CLI evaluate, the --format drift gate and MCP evolith-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 the develop versions 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 json subprocess suites) fail identically with the changed file reverted to develop.

Deliberately left out

  • core-api and agent-runtime do 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-web was not typechecked on the Tracker side (no node_modules in that worktree); the edit there is additive type declarations with no code referencing them.

🤖 Generated with Claude Code

@beyondnetPeru
beyondnetPeru requested a review from a team as a code owner July 31, 2026 03:55
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

📊 Bilingual Coverage Impact

PR Changes

  • Paired EN/ES files modified: 7
  • New EN files needing ES translation: 2

Repository Coverage

Metric Value
Total EN files 525
Total ES files 499
Paired files 0
Coverage 0%

⚠️ Action required: 2 new EN file(s) added without ES counterparts.

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}`;
@beyondnetPeru

Copy link
Copy Markdown
Contributor Author

GT-602 is owned by #306. This branch independently carries 674aafec ("docs(gaps): close GT-602") with a gap-closure-evidence.json blob byte-identical to #306's — #306's head is not an ancestor here, so the same closure was written twice by two sessions that could not see each other. That is GT-639's failure mode, and the claim guard did not catch it because it reads prose rather than the diff.

674aafec will be dropped from this branch when it is rebased onto the merged develop. The GT-580 mention in the body (a "pre-existing and unrelated" note about CLI test failures) is being rephrased so it stops registering as a claim.

Hard dependency unchanged: evolith_tracker#82 merges first.

…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>
@beyondnetPeru
beyondnetPeru force-pushed the feat/gt-604-ingest-client branch from 5da2df7 to abe6855 Compare July 31, 2026 04:31
@beyondnetPeru

Copy link
Copy Markdown
Contributor Author

Rebased onto the merged develop and 674aafec dropped — the duplicate GT-602 closure this branch was carrying independently of #306. The diff is now 7 files / +765, down from 34 files: everything else was integration content already on develop via #305.

evolith_tracker#82 (the ingest endpoint this client calls) is merged, so the hard dependency is satisfied.

beyondnetPeru and others added 4 commits July 30, 2026 23:38
…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>
@beyondnetPeru
beyondnetPeru merged commit 448d98e into develop Jul 31, 2026
35 of 36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants