Skip to content

merge: integrate GT-580, GT-602, GT-606 and GT-614 into develop - #305

Merged
beyondnetPeru merged 13 commits into
developfrom
integration/gt-580-602-606-614
Jul 31, 2026
Merged

merge: integrate GT-580, GT-602, GT-606 and GT-614 into develop#305
beyondnetPeru merged 13 commits into
developfrom
integration/gt-580-602-606-614

Conversation

@beyondnetPeru

Copy link
Copy Markdown
Contributor

Pull Request Summary

Integra en develop los cuatro gaps que quedaron verificados en ramas de agente y que hasta ahora vivían solo en integration/gt-580-602-606-614:

  • GT-580 (Evolith CLI) — canal máquina probado en todo el registry y taxonomía de exit codes gobernada.
  • GT-602 (Evolith MCP) — el policy.wasm compilado se verifica sobre el registry completo, nunca se salta.
  • GT-606 (Evolith MCP) — concurrencia optimista de ADR-0093 aplicada a las 20 tools mutativas.
  • GT-614 (Evolith Core) — el pipeline deja de pagar por gates que nadie pidió (evaluación selectiva).

Incluye además lo que ya estaba en la rama: el claim guard de GT-639 (50-validate-gap-claim.mjs) y el fix de los hooks pre-commit/pre-push cuando .git es un archivo (worktrees).

develop está protegido con 7 checks requeridos, así que la integración va por PR en vez de push directo. El merge es un fast-forward: origin/develop está contenido en esta rama.

Verificación local (antes de abrir el PR)

Suite Resultado
npm run build (tsc -b) limpio
mcp-server jest 55 suites / 452 tests ✅
core-api jest 31 suites / 179 tests ✅
sdk/cli jest 101 suites / 1446 tests ✅
50-validate-gap-claim.test.mjs 10/10 ✅
01-validate-docs.mjs 1491 archivos ✅
04-check-bilingual-parity.mjs 1643 archivos / 524 pares ✅

Evolith Core Quality Gates

  • Bilingual Parity: los documentos tocados (gap-tracking, gap-reference-catalog, ADR-0093) llevan sus dos versiones.
  • Validation Scripts: node .harness/scripts/ci/01-validate-docs.mjs corrido localmente en verde.
  • Bilingual Validation: node .harness/scripts/ci/04-check-bilingual-parity.mjs corrido localmente en verde.
  • Agnosticism: no introduce dependencias tecnológicas nuevas en el Core agnóstico.

Linked ADRs / Issues

  • Link to ADR: reference/core/adrs/core/0093-mcp-concurrency-locking.md (GT-606)
  • Link to Issue: GT-580, GT-602, GT-606, GT-614, GT-639

Conventional Commits

  • Título y commits siguen Conventional Commits.

🤖 Generated with Claude Code

beyondnetPeru and others added 13 commits July 29, 2026 14:25
…try, never skipped

GT-602 criteria 1 and 3.

The compiled-bundle block of `abac-rego-parity.spec.ts` was governed by
`EVOLITH_REQUIRE_OPA_WASM`: it ran in one CI job and `it.skip`ped everywhere
else, because `policy.wasm` is a gitignored build output. That is the shape that
produced GT-632 — a stale wasm at the pre-`src/` path on one machine, every MCP
tool denied in production, and a green suite the whole time.

The block now COMPILES the policy itself. Missing or older than any `.rego` it is
built from -> run `.harness/scripts/compile-opa-wasm.mjs` (the same script
`npm run build:policy` runs, writing the same paths the runtime loads); if that
compilation cannot happen, the suite fails with the compiler's own diagnostic.
There is no environment in which it passes without having evaluated a wasm, and
staleness is treated as absence because a stale bundle is the actual defect.

Denominators are asserted rather than assumed: the registry must hold at least
the 50 tools measured today, every one of those names must be evaluated
(`evaluated.length === registered.length`), and every evaluation must come back
allowed (`allowed.length === registered.length`). The ABAC-03 negative control
now runs first, since an `evaluate()` returning an unexpected shape would make
every ALLOW assertion pass vacuously.

`compile-opa-wasm.mjs` installs by rename instead of `copyFileSync`. The test
compiling on demand makes concurrent read-during-write reachable: sibling jest
workers read the same `policy.wasm`, and a partial read throws in `loadPolicy`,
which the evaluator reports as OPA_ERROR — a denial.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… tools (GT-606)

ADR-0093 has been Accepted since 2026-06-20 and mandated a `baseSha` parameter,
HEAD verification before applying, and a CONCURRENCY_CONFLICT error contract.
Grepping the MCP server for any of those returned nothing, against 20 tools
declaring `mutative: true`. Two agents on one workspace produced exactly the
lost update the ADR was written to prevent.

The MCP server is the thing doing the mutating — the tools write to disk
themselves via IFileSystem/fs-extra into a caller-supplied directory — so the
check belongs here and is not ceremony.

Enforced at the single dispatch, keyed on `tool.mutative`, which is the same key
the HITL approval gate already turns on. The protected set is therefore the
mutative set by construction: a tool registered tomorrow inherits the check with
no wiring, which a per-tool implementation would have made twenty chances to
forget. `baseSha` is likewise derived onto every mutative tool's advertised
schema by ToolRegistryService.describe rather than hand-copied into 20 input
schemas, so the declared surface and the enforced behaviour cannot drift.

Semantics are If-Match, not a lock: `baseSha` is optional, and a caller that
declares the state it planned against gets protection. Requiring it outright
would break every existing caller and make the tools that legitimately target a
not-yet-initialised directory (evolith-scaffold, evolith-init-batch) unusable;
EVOLITH_MCP_REQUIRE_BASE_SHA=1 gives a deployment the strict reading. When
`baseSha` IS supplied, verification fails closed — an unresolvable HEAD is not
evidence that the asserted base state holds.

Tests. The load-bearing case is a genuine race, not a fabricated SHA: a real git
repo, a real `git rev-parse` read by the client, and a second mutative call in
flight at the same time landing a real commit in the window between that read
and the dispatch's verification. It asserts both the conflict envelope and that
the victim tool's side-effect file does not exist — a conflict reported after
the write landed would be worse than no check. A deliberately stale SHA is kept
as the cheaper second case. The denominator guard enumerates the mutative set
off the live DI registry and drives every member through the real dispatch, so
the count is observed rather than remembered and an unprotected tool fails the
build with its own name in the diff. Verified non-vacuous: with the check
disabled, the race fails and all 20 tools go unprotected.

ADR-0093 stays Accepted and now records what backs it, including that §2
(pessimistic locking) remains deliberately unimplemented — no current tool holds
an exclusive non-git resource long enough for a two-minute lease to be the right
mechanism. Recording that is the point: the ADR should not keep claiming a
mechanism nothing implements.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ody asked for

GT-614's contract half already existed: a PipelineExecutionPlan, a reference
`createKindSelectivePipeline`, and a suite proving selectivity. None of it was
adopted, so the gap's own sentence was still true of the product — asking for
`compliance` alone still loaded the f1..f5 gate corpus and ran every Rego rule
in it, because `SatelliteEvaluationPipeline` took no plan at all.

That pipeline is what `POST /api/v1/evaluate` (both branches), `evolith
evaluate` and the MCP `evaluate` tool actually execute. It is now expressed as
two kind-tagged stages — the SDLC phase gates (`gate`/`artifact`) and the
canonical ruleset corpus (`rule`/`compliance`) — and a stage whose kinds were
not requested is never entered: not its loader, not its evaluator. The plan is
forwarded from the orchestrator through ValidateSatelliteUseCase on all four
surfaces, so they cannot disagree about what a single-kind request costs.

An unrun stage is OUT OF SCOPE, not `skipped`. `skipped` means the engine tried
and the outcome is UNKNOWN: it belongs in the GT-569 denominator, becomes a
coverage risk, and since GT-595 a blocking rule reported skipped FAILS the run.
None of that is true of a question nobody asked. The stage therefore contributes
nothing to rulesChecked/rulesSkipped/rulesErrored/rulesTotal and no gate result;
it is recorded only in `coverage.outOfScopeGateIds`, so a reader can still see
what the request did not buy. The pipeline now publishes that coverage quadruple
(it published none before), and it keeps `checked + skipped + errored === total`.

The load-bearing test is negative and about outcomes: the out-of-scope stage is
one that WOULD FAIL the run. A call-count assertion would survive a refactor
that re-ran the gate and discarded it; `expect(verdict.passed).toBe(true)`
cannot. 8 of the 9 new tests fail against the previous code — the ninth is the
baseline that proves the gate really does fail when it is in scope.

Verified: npm run build (tsc -b, whole monorepo) clean; core-domain 1442,
core-api 179, mcp-server 433, cli 1436 tests green; eslint boundaries clean
(the new cross-layer imports are type-only).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…the exit taxonomy (GT-580)

Closes the two open criteria of GT-580.

CRITERION 2 — `--format json` writes DATA ONLY to stdout.
Output formatting and process exits were ALREADY centralized: `installMachineChannelGuard`
(main.ts, armed before the command graph boots) reroutes every non-JSON stdout write to
stderr while a machine format is active, and `exit-codes.ts` + `BaseEvolithCommand.handleError`
map a thrown error onto the taxonomy in one place. What was missing was proof that the
centralization actually holds across the CLI: the existing subprocess spec pipes two
hand-picked commands. `machine-channel.registry-sweep.spec.ts` asks commander — the CLI's
own registry, built from the real AppModule — which commands declare `--format`, and sweeps
all 32 in real subprocesses with stdout and stderr on separate pipes. Each must hand back
exactly one parseable JSON document and exit from the taxonomy. The enumeration has a floor,
so a sweep over nothing fails instead of passing vacuously.

Observed red: commenting out `installMachineChannelGuard()` turns `agents` (a clack banner)
and `init-wizard` (a progress line) DIRTY while the other thirty stay clean.

CRITERION 3 — a ruleset with Rego parity fails a command that exits outside the taxonomy.
  - `src/rulesets/cli/exit-code-taxonomy.rules.json` — CLI-EXIT-01 (no exit outside
    {0,1,2,3}), CLI-EXIT-02 (the scan must be non-vacuous), CLI-EXIT-03 (the taxonomy may
    not be widened to absorb an offender — otherwise CLI-EXIT-01 has a one-line escape).
  - `src/rulesets/opa/cli-exit-code-taxonomy.rego` + tests — the same three ids decided over
    the fact document the CLI's own producer emits. Aggregated into `main.rego`, and scoped
    to `input.core.cli` so a satellite evaluation stays silent.
  - `CliExitTaxonomyRuleHandler` — the native half, so `evolith validate` evaluates the rules
    instead of reporting three blocking rules that never ran.

Observed red, end to end: planting `process.exit(7)` in a real CLI command file makes
`evolith validate --core .` emit a BLOCKING CLI-EXIT-01 naming the file, and the CLI exits 2
(BLOCKED). The same file drives the committed producer and the committed .rego to the same
verdict in `exit-code-taxonomy-ruleset.spec.ts`.

Derived artifacts moved with the corpus: native-handler 151 -> 154, ISO/IEC 5055 mapping and
the inventory summary regenerated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
release: align main — the CLI and MCP resolve the SDK that carries the security wave, 1.2.2 (GT-634)
A board row carries a STATUS but not who is working it or where, so two sessions
can both read `PENDING` and both start, correctly. On 2026-07-30 that happened
three times in a day.

`50-validate-gap-claim` derives the claim set from OPEN PULL REQUESTS — every
`GT-*` in a PR's title, body or branch name — and fails when one id is claimed by
two of them, naming both with their branches and titles.

DERIVED, NEVER HAND-WRITTEN, and the row asked for that on purpose: a hand-written
claim goes stale in the direction that matters, because someone forgets to remove
it and the next session works around a claim nobody holds.

THREE CRITERIA OF FOUR, and the fourth is left open with its reason rather than
stretched to fit. A claim list committed to the board would be derived from live
GitHub state, so it would be stale the moment anyone opened a pull request — and
GT-630's chain exists precisely to insist derived artifacts reach a fixed point. A
perpetually-stale artifact inside that chain would be worse than none. Making the
claim discoverable from the board needs something the board can render without
committing, and that is not built. The row stays IN-PROGRESS.

What it cannot see is in its own failure text, not implied: a branch with no open
pull request claims nothing. Opening the PR early — draft is enough — is what
makes the claim visible, which turns a convention into something with a check
behind it.

Anti-vacuous: a pull-request query that cannot be answered is a hard failure, not
a quiet pass. A guard built because two sessions could not see each other must not
stay silent when it cannot look. Zero open PRs is reported in words, because it
looks identical to a broken query otherwise.

Verified: 10/10 fixtures; 42-validate-guard-denominators 62 guards classified;
43-validate-guard-negative-fixtures 39/39 observed red, up from 38. Run against
the real repository it reports 0 claims across 5 open PRs — checked rather than
assumed: all five are Dependabot bumps naming no gap, so the zero is real and not
an empty query. 08-validate-tracking (640 gaps), 01, 04, 41 within budget, 46
chain at a fixed point. Counters: 600/640, in-progress 14 -> 15, pending 22 -> 21.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(ci): one gap, one claim — the claim guard, advancing GT-639
`.git/EVOLITH_*` is a DIRECTORY only in the primary checkout; inside a `git
worktree` it is a FILE holding a `gitdir:` pointer, so every write/read to
`.git/EVOLITH_*` was ENOTDIR. In pre-commit that made `set -e` abort the
commit outright; in pre-push the `rm -f` failed and TARGET stayed unread —
together the reason `--no-verify` had become habitual from a worktree.

`git rev-parse --git-dir` resolves both layouts; both hooks now go through
it via a shared `write_push_intent`/read path, per-worktree, so the push
intent belongs to the checkout that was committed in rather than the repo
as a whole.

Also in pre-push: `assert_checkoutable` refuses up front, before repo
optimization or board sync run, when the merge target branch (main/develop)
is already checked out in another worktree — `git checkout "$to"` would
otherwise fail mid-dispatch with the push half-done.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fix(ci): pre-commit/pre-push hooks assumed .git is always a directory
Four conflicts, resolved by intent rather than by picking a side:

- machine-channel.registry-sweep.spec.ts — another session had already landed
  this sweep on main AND improved it (e21e162 corrected the sweep's own
  invariant and found a fifth taxonomy mismatch). main's version is a strict
  superset, so it wins outright. Duplicated work, caught at the merge.
- rule-corpus-triage.spec.ts — main refactored the counts to a SINGLE
  PINNED_CLASS_COUNTS declaration, deleting a second inline copy; GT-580's side
  edited the copy that no longer exists. Kept main's structure. Both sides agree
  on native-handler=154, but that is verified below, not assumed.
- iso-5055-mapping.{csv,json} — generated artifacts. Regenerated rather than
  hand-merged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@beyondnetPeru
beyondnetPeru requested a review from a team as a code owner July 30, 2026 20:38
@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

Copy link
Copy Markdown

📊 Bilingual Coverage Impact

PR Changes

  • Paired EN/ES files modified: 1
  • New EN files needing ES translation: 0

Repository Coverage

Metric Value
Total EN files 524
Total ES files 498
Paired files 0
Coverage 0%

Good: All EN changes have ES counterparts.


Generated by GitHub Actions

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.

1 participant