merge: integrate GT-580, GT-602, GT-606 and GT-614 into develop - #305
Merged
Conversation
…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>
|
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
✅ Good: All EN changes have ES counterparts. Generated by GitHub Actions |
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request Summary
Integra en
developlos cuatro gaps que quedaron verificados en ramas de agente y que hasta ahora vivían solo enintegration/gt-580-602-606-614:Evolith CLI) — canal máquina probado en todo el registry y taxonomía de exit codes gobernada.Evolith MCP) — elpolicy.wasmcompilado se verifica sobre el registry completo, nunca se salta.Evolith MCP) — concurrencia optimista de ADR-0093 aplicada a las 20 tools mutativas.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.gites un archivo (worktrees).developestá 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/developestá contenido en esta rama.Verificación local (antes de abrir el PR)
npm run build(tsc -b)mcp-serverjestcore-apijestsdk/clijest50-validate-gap-claim.test.mjs01-validate-docs.mjs04-check-bilingual-parity.mjsEvolith Core Quality Gates
gap-tracking,gap-reference-catalog, ADR-0093) llevan sus dos versiones.node .harness/scripts/ci/01-validate-docs.mjscorrido localmente en verde.node .harness/scripts/ci/04-check-bilingual-parity.mjscorrido localmente en verde.Linked ADRs / Issues
reference/core/adrs/core/0093-mcp-concurrency-locking.md(GT-606)Conventional Commits
🤖 Generated with Claude Code