instar 1.3.35 → 1.3.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +53 -3
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  6. package/dist/config/ConfigDefaults.js +15 -0
  7. package/dist/config/ConfigDefaults.js.map +1 -1
  8. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  9. package/dist/core/PostUpdateMigrator.js +15 -0
  10. package/dist/core/PostUpdateMigrator.js.map +1 -1
  11. package/dist/core/featureRolloutScan.d.ts +23 -0
  12. package/dist/core/featureRolloutScan.d.ts.map +1 -1
  13. package/dist/core/featureRolloutScan.js +102 -0
  14. package/dist/core/featureRolloutScan.js.map +1 -1
  15. package/dist/core/types.d.ts +45 -0
  16. package/dist/core/types.d.ts.map +1 -1
  17. package/dist/core/types.js.map +1 -1
  18. package/dist/monitoring/ReleaseReadinessSentinel.d.ts +172 -0
  19. package/dist/monitoring/ReleaseReadinessSentinel.d.ts.map +1 -0
  20. package/dist/monitoring/ReleaseReadinessSentinel.js +315 -0
  21. package/dist/monitoring/ReleaseReadinessSentinel.js.map +1 -0
  22. package/dist/monitoring/releaseReadinessWiring.d.ts +54 -0
  23. package/dist/monitoring/releaseReadinessWiring.d.ts.map +1 -0
  24. package/dist/monitoring/releaseReadinessWiring.js +241 -0
  25. package/dist/monitoring/releaseReadinessWiring.js.map +1 -0
  26. package/dist/scaffold/templates.d.ts.map +1 -1
  27. package/dist/scaffold/templates.js +5 -0
  28. package/dist/scaffold/templates.js.map +1 -1
  29. package/dist/server/AgentServer.d.ts +1 -0
  30. package/dist/server/AgentServer.d.ts.map +1 -1
  31. package/dist/server/AgentServer.js +1 -0
  32. package/dist/server/AgentServer.js.map +1 -1
  33. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  34. package/dist/server/CapabilityIndex.js +14 -0
  35. package/dist/server/CapabilityIndex.js.map +1 -1
  36. package/dist/server/routes.d.ts +3 -0
  37. package/dist/server/routes.d.ts.map +1 -1
  38. package/dist/server/routes.js +55 -0
  39. package/dist/server/routes.js.map +1 -1
  40. package/package.json +1 -1
  41. package/scripts/analyze-release.js +221 -0
  42. package/scripts/upgrade-guide-validator.mjs +120 -0
  43. package/src/data/builtin-manifest.json +63 -63
  44. package/src/scaffold/templates/jobs/instar/release-readiness-check.md +24 -0
  45. package/src/scaffold/templates.ts +5 -0
  46. package/upgrades/1.3.36.md +39 -0
  47. package/upgrades/side-effects/release-readiness-visibility-pr2a.md +27 -0
  48. package/upgrades/side-effects/release-readiness-visibility-pr2b.md +26 -0
  49. package/upgrades/side-effects/release-readiness-visibility-pr2c.md +37 -0
  50. package/upgrades/side-effects/release-readiness-visibility-pr3.md +33 -0
@@ -0,0 +1,27 @@
1
+ # Side-Effects Review — Release-Readiness Visibility, PR-2a (Layer A auto-draft + publish-gate amendment)
2
+
3
+ **Spec:** docs/specs/RELEASE-READINESS-VISIBILITY-SPEC.md §4.1 (converged + approved).
4
+ **Scope:** the auto-draft half of PR-2 — `analyze-release.js --draft-guide`, the `upgrade-guide-validator.mjs` review gate, and the `publish.yml` skip-predicate amendment. (Layer B — the ReleaseReadinessSentinel + routes + job — lands in a following commit on this same branch.)
5
+
6
+ ## What changed
7
+
8
+ - **`scripts/analyze-release.js`** — new `--draft-guide` mode. Builds `upgrades/NEXT.md` from the already-computed `generateChangeDescriptions`, structured into the required sections + a seeded `<!-- bump: -->`. Every section carries an `auto-draft-unreviewed` marker. Commit-message text is sanitized (HTML comments stripped, length-capped, leading `#`/`---` escaped) so a crafted message can't forge markers or break section bounds. Two write modes: full draft when the guide is absent/pristine-template; additive uncovered-delta block (never clobbers human content) otherwise. Idempotent — coverage is measured against human content only, so the delta block doesn't oscillate. Race-guarded against publish-finalize via the `upgrades/{version}.md` existence check.
9
+ - **`scripts/upgrade-guide-validator.mjs`** — `autoDraftReviewIssues()` (wired into `validateGuideContent`): blocks while any `auto-draft-unreviewed` marker remains; validates `reviewed-by` receipts (required `:hash=<sha256>` matching the canonicalized section, ≤30-day window). `canonicalizeSectionForHash` / `sectionReviewHash` exported for reuse + tests.
10
+ - **`.github/workflows/publish.yml`** — skip predicate also sets `skip=true` when `auto-draft-unreviewed` is present (keeps silent-skip semantics for the unreviewed case; the sentinel surfaces it as a signal in Layer B).
11
+
12
+ ## Side-effects analysis
13
+
14
+ **Over/under-reach.** The validator change adds block conditions that fire ONLY on content the auto-drafter produces (`auto-draft-unreviewed` markers) or on malformed `reviewed-by` receipts. A hand-written guide that never uses these markers is completely unaffected — existing guides and the existing finalization flow are untouched. `--draft-guide` is opt-in (no existing caller passes it); the default `analyze-release.js` behavior is byte-identical.
15
+
16
+ **Defeating-the-gate (the iter-1 adversarial finding).** The whole point: a fully auto-drafted guide PASSES every prior check (sections present, length, no template placeholders, evidence-section present) but is BLOCKED by the new unreviewed-marker rule. Verified: drafting then validating returns the unreviewed-marker issue; replacing markers with correct hash-locked receipts clears it; wrong-hash / missing-hash / stale receipts re-block. So auto-fill removes the blank-guide root cause without shipping un-reviewed notes.
17
+
18
+ **Signal vs authority.** The gate amendment teaches the EXISTING publish gate (which already has blocking authority) a new recognize-and-block condition; it introduces no new low-context detector with veto power. Consistent with §5.
19
+
20
+ **Spec deviation (documented).** §4.1.3 also describes an intra-host O_EXCL advisory lock. It is intentionally omitted: releasing it needs a destructive `fs.unlink`, forbidden by `lint-no-direct-destructive` in this dependency-free, test-copyable script, and the draft job is single-runner via the multi-machine lease so concurrent local drafters don't occur. The cross-host guarantee (the one §4.1.3 calls the real guarantee) is fully preserved via the `{version}.md` existence check. The full "marker stripped without a receipt" detection remains the tracked Phase-2 git-diff CI check per §4.1.1.
21
+
22
+ **Rollback.** Revert restores prior `analyze-release.js` / validator / workflow. No state, config, or migration introduced by PR-2a.
23
+
24
+ ## Testing
25
+
26
+ - Unit (19 green): `analyze-release-draft-guide.test.ts` (8 — full draft, template overwrite, never-clobber merge, idempotency, fully-covered no-op, finalize-race skip, no stray lock, HTML-comment sanitization); `upgrade-guide-autodraft-review.test.ts` (7 — marker blocks, block-marker blocks, correct receipt passes, hash-mismatch/missing-hash/stale block, validateGuideContent surfaces it); `analyze-release-ref-flag.test.ts` (4, from PR-1).
27
+ - Integration/E2E for the readiness pipeline land with Layer B (the routes + the "feature is alive" E2E that reproduces the original stall).
@@ -0,0 +1,26 @@
1
+ # Side-Effects Review — Release-Readiness Visibility, PR-2b (Layer B core: sentinel + I/O wiring)
2
+
3
+ **Spec:** docs/specs/RELEASE-READINESS-VISIBILITY-SPEC.md §4.2 (converged + approved).
4
+ **Scope of THIS commit:** the dependency-injected `ReleaseReadinessSentinel` (pure decision logic) and `releaseReadinessWiring` (its real git/analyzer/state/Attention I/O bridge), plus their tests. **This commit does NOT yet wire the sentinel into server startup or add the HTTP routes** — so on this commit the sentinel is constructed nowhere (it is not yet live). The server construction + `GET /release-readiness` + `POST /release-readiness/rollback` + config defaults + job template + migrations + integration/E2E land in the following commit on this same (unmerged) branch. Calling this out explicitly to avoid the PR #334 failure mode (sentinels merged as dead code with a false "wired" claim) — nothing here is claimed to be live.
5
+
6
+ ## What changed
7
+
8
+ - **`src/monitoring/ReleaseReadinessSentinel.ts`** — EventEmitter with injected deps. `tick()` (detect → surface → auto-resolve → reap), `resolveEpisodesInRange()` (publish-finalize hook; ancestry-based so it's correct under oldest-SHA churn), `priorityForAge()`, `reap()`. Blocked-predicate is decoupled from NEXT.md state (commits-with-blocked-guide OR coverage-gaps) so Layer A's auto-draft can't silence the alarm. One deduped signal keyed on the oldest-unreleased-commit SHA; age-scaled priority; 12h hysteresis; fail-loud (every evaluation error → low-priority Attention, never a silent catch). Tier 0.
9
+ - **`src/monitoring/releaseReadinessWiring.ts`** — `buildReleaseReadinessDeps()` (real I/O), `isAnalyzableRepo()` (repo-gate), `resolveCanonicalRemote()` (allow-list), `loadReadinessState`/`saveReadinessState` (atomic via temp+rename — no destructive-lint verb), `makeAttentionPoster`.
10
+
11
+ ## Side-effects analysis
12
+
13
+ **Reach.** Both modules are new, imported by nothing yet. Zero runtime effect on any existing path at this commit. No config, route, job, or migration is introduced here.
14
+
15
+ **Repo-gated dev-environment scope (spec refinement, documented).** The sentinel analyzes the instar git repo via `analyze-release.js`. That repo only exists in the dev/maintainer environment (Echo's agent home IS the instar checkout). A plain npm-installed agent has no instar repo to analyze. `isAnalyzableRepo()` is the gate the server will consult: on a non-analyzable install the sentinel is simply not constructed (inert) — it never posts a spurious Attention item about a missing repo. This is consistent with the spec's "ships off by default; Echo dogfoods first," and makes explicit a reality the spec under-specified.
16
+
17
+ **Security / input safety.** All git goes through `SafeGitExecutor` (execFileSync, no shell). The canonical-remote allow-list is anchored on `github.com` host (`^(https://github\.com/|git@github\.com:)JKHeadley/instar(\.git)?$`) — a look-alike host like `git@evil.com:JKHeadley/instar.git` does NOT match (iter-3 adversarial V3); a configured override to a non-canonical URL is permitted but flips `canonicalRemoteOverridden`, which the server will surface as a HIGH-priority signal. The analyzer is spawned via `execFile(process.execPath, [...])` (argv array, no shell). State load tolerates corruption (returns a fresh state rather than throwing into the tick).
18
+
19
+ **Fail-open vs fail-loud.** The sentinel's contract is fail-loud: `tick()`'s top-level catch and the per-stage `failLoud()` convert fetch/analyzer errors into low-priority Attention items (deduped per failure episode). This is verified by unit tests, and is the core anti-pattern guard (a silent catch would re-create the bug being fixed).
20
+
21
+ **Rollback.** Deleting the two modules + their tests fully reverts; nothing imports them yet.
22
+
23
+ ## Testing
24
+
25
+ - Unit (21 green): `ReleaseReadinessSentinel.test.ts` (13 — thresholds, silent-below-threshold, single deduped signal, escalation, coverage-gap blocking, decoupled-from-guide, auto-resolve, fail-loud×2, hysteresis, resolveEpisodesInRange, stale-reap, disabled-no-op); `releaseReadinessWiring.test.ts` (8 wiring-integrity — allow-list, repo-gate, override detection, auto-detect, state round-trip+corruption, all deps are real callables, guideBlocksPublish truth table).
26
+ - Integration (HTTP routes) + E2E (feature-alive: reproduce the original stall → one Attention item) land with the server-wiring commit, which is where they become meaningful (a route/E2E can't exist before the route does).
@@ -0,0 +1,37 @@
1
+ # Side-Effects Review — Release-Readiness Visibility, PR-2c (Layer B wired live)
2
+
3
+ **Spec:** docs/specs/RELEASE-READINESS-VISIBILITY-SPEC.md §§4.2 / 4.3.1 / 4.2.7 / 7 / 10.
4
+ **Scope of this commit:** make Layer B actually live — server construction (gated on an analyzable instar repo + config), the four HTTP routes, the off-by-default cron job, config defaults + migrations (config + CLAUDE.md), and the integration + real-I/O E2E that prove it. Plus the four new rollback/enable/snapshot/disabled methods on the sentinel that PR-2b's commit couldn't fully ship without the routes.
5
+
6
+ ## What changed
7
+
8
+ - **`src/commands/server.ts`** — constructs `ReleaseReadinessSentinel` IFF `config.monitoring.releaseReadiness.enabled` AND `isAnalyzableRepo(repoPath)` (default repoPath = process.cwd()). NOT `start()`ed — the off-by-default cron job is the cadence (one tick per cron). On an inert install (no instar repo or feature off) the sentinel is null and the routes 503 — never a spurious signal.
9
+ - **`src/server/AgentServer.ts` + `src/server/routes.ts`** — `releaseReadinessSentinel` plumbed through `AgentServerOptions` → `RouteContext`. Four routes added: `GET /release-readiness` (read-only snapshot + `X-Readiness-Source` header), `POST /release-readiness/tick` (the cron entry point), `POST /release-readiness/rollback` (loud — raises a HIGH Attention item + audits, can't silently mute the alarm; iter-3 V5), `POST /release-readiness/enable` (re-arm).
10
+ - **`src/monitoring/ReleaseReadinessSentinel.ts`** — added `disabled` to `ReadinessState`, `tick()` no-ops when disabled (writes a `tick-skipped-disabled` audit), and added `snapshot()` / `rollback({sessionId, sourceIp})` / `enable()` methods. Rollback resolves all open episodes, persists the `rollbackHistory` audit, and posts the HIGH "alarm disabled" Attention item before saving state.
11
+ - **`src/monitoring/releaseReadinessWiring.ts`** — **production-bug fix discovered by the E2E**: the spec's `--depth=1` fetch SHALLOWS the local repo, which breaks `git log <tag>..ref` in `analyze-release.js` (commit count silently becomes 0 → the sentinel goes silent on a real backlog — the exact failure mode the spec exists to fix). Dropped `--depth=1`; `--no-tags --no-recurse-submodules` keeps incremental fetches bounded without shallowing. **This is the kind of bug only the E2E could catch — unit tests with stubbed deps would have passed.**
12
+ - **`src/core/types.ts`** — `MonitoringConfig.releaseReadiness` block (kill switch + thresholds + hysteresis + TTL + fetch timeout + canonical-remote + repoPath overrides).
13
+ - **`src/config/ConfigDefaults.ts`** — default block under `monitoring.releaseReadiness` (ships OFF). Existing agents pick it up automatically: `applyDefaults` adds missing keys, never overwrites — migration parity is automatic.
14
+ - **`src/core/PostUpdateMigrator.ts`** — `migrateClaudeMd` appends a Release Readiness section to existing agents' CLAUDE.md, content-sniffed on the `/release-readiness` marker (Agent Awareness Standard).
15
+ - **`src/scaffold/templates.ts`** — new agents get the same paragraph in the rendered CLAUDE.md.
16
+ - **`src/scaffold/templates/jobs/instar/release-readiness-check.md`** — declarative cron job, `enabled: false`, schedule `0 */6 * * *`, model `haiku`. Body curls `POST /release-readiness/tick`; reports nothing (the sentinel owns all signalling).
17
+
18
+ ## Side-effects analysis
19
+
20
+ **Reach.** All new code is gated. The sentinel is constructed only when (a) config enables it AND (b) `isAnalyzableRepo(repoPath)` returns true. On an npm-installed agent with no instar repo, the sentinel stays null and the routes 503 — zero behavior change. The job ships `enabled: false`. The CLAUDE.md migration is content-sniffed (idempotent).
21
+
22
+ **Rollback semantics (iter-3 V5).** `POST /release-readiness/rollback` cannot be a silent kill: it ALWAYS raises a HIGH-priority Attention item ("Release-readiness alarm disabled by session X at T"), writes a `rollbackHistory[]` entry to the readiness state, and emits a `rollback` audit line. A compromised/confused session calling rollback is loudly visible. `enable` re-arms cleanly.
23
+
24
+ **Production bug caught.** The `--depth=1` shallow-fetch bug (now fixed) would have silently broken the watchdog on every install after the first fetch — the alarm would have been *quiet for the exact reason it was built*. Caught by the real-I/O E2E (fixture git repo + real `analyze-release.js` subprocess + real fetch). Without that test, we would have shipped the failure mode the spec exists to fix.
25
+
26
+ **Sentinel-vs-cadence design.** Spec §4.2.1 said "host: a new declarative job." The class also exposes `start()`/`stop()` (self-ticking via setInterval) for tests + alt wiring. The SERVER does NOT call `start()` — the cron job is the single cadence driver, calling `POST /release-readiness/tick`. This avoids double-ticking and gives operators full control via the job's `enabled` flag.
27
+
28
+ **Migration parity.** Config defaults: handled automatically by `applyDefaults` (existing agents get the missing `monitoring.releaseReadiness` block on next migration). CLAUDE.md: explicit `migrateClaudeMd` block (Agent Awareness). Job template: shipped via the existing `InstallBuiltinJobs` always-overwrite path (operator `enabled` override is preserved per-slug). All three migration-parity standard surfaces covered.
29
+
30
+ **Rollback if reverted.** Reverting this PR removes the sentinel construction, routes, job template, config block, and CLAUDE.md mention. On existing agents post-revert: the config key + CLAUDE.md section become orphaned but harmless (no code references them). The state file `.instar/state/release-readiness.json` is left in place (cheap, ignored).
31
+
32
+ ## Testing
33
+
34
+ Full layered coverage, all green (47 tests across 7 files this PR):
35
+ - **Unit (40):** `analyze-release-ref-flag` (4), `analyze-release-draft-guide` (8), `upgrade-guide-autodraft-review` (7 — marker blocks, hash-locked receipts pass/block on tamper/age/missing-hash), `ReleaseReadinessSentinel` (13 — thresholds, dedupe, escalation, decoupled-from-guide, auto-resolve, fail-loud, hysteresis, resolveEpisodesInRange, stale-reap), `releaseReadinessWiring` (8 — allow-list, repo-gate, override detection, state round-trip, deps-are-real, guide-blocks truth table).
36
+ - **Integration (5):** real routes + real sentinel + controllable deps. Proves: route is 200 not 503 (alive), 503 when sentinel null (correctly inert), blocked+aged backlog → exactly one Attention signal (the original silent-stall bug reproduced + surfaced), clean backlog → no signal, rollback is loud and reversible.
37
+ - **E2E (2):** real `fixture` instar repo + real canonical remote + REAL git fetch + REAL `analyze-release.js` subprocess + REAL `merge-base`. Drives `sentinel.tick()` end-to-end and proves the wiring works against real I/O. Caught the `--depth=1` shallowing bug.
@@ -0,0 +1,33 @@
1
+ # Side-Effects Review — Release-Readiness Visibility, PR-3 (Layer C canonical-ref scan)
2
+
3
+ **Spec:** docs/specs/RELEASE-READINESS-VISIBILITY-SPEC.md §4.3 (converged + approved).
4
+ **Scope:** the third and final PR of the spec — fix the second silent self-driving loop. The `FeatureRolloutReconciler`'s spec scanner today reads the LOCAL working tree, so a freshly-merged spec that isn't on the developer's current branch (or whose trace receipts have been cleaned up) is invisible — the reconciler silently skips exactly the newest work. This PR adds a canonical-ref scan that reads `docs/specs/` and `.instar/instar-dev-traces/` from canonical `main` directly. Feature-flagged off; falls back to the local scan on any failure with a single degradation event; never throws into boot.
5
+
6
+ ## What changed
7
+
8
+ - **`src/core/featureRolloutScan.ts`** — new exports:
9
+ - `scanSpecArtifactsCanonical(opts)` — sync canonical scan. Bounded fetch (`--no-tags --no-recurse-submodules`, no `--depth` — matches Layer B's --depth=1 lesson that shallowing the local repo breaks downstream git operations). Enumerates spec + trace blobs via `git ls-tree` and reads contents via `git show <sha>:<path>`. Returns `SpecArtifact[]` with `merged: true` by construction (a spec on main IS merged — the local scan's inferred `approved && traceExists` shortcut is replaced with real ancestry).
10
+ - `scanSpecArtifactsWithCanonical(repoRoot, opts)` — the repo-gated, feature-flagged wrapper. When `canonicalRefScanEnabled` is false → calls the original local scanner. When the flag is on but `canonicalRemote` is missing → degradation + local fallback. When the canonical scan throws → degradation + local fallback. Boot-safe by construction.
11
+ - **`src/core/types.ts`** — new top-level `featureRollout` config block (`canonicalRefScan?: boolean` + `canonicalRemote?: string` + `fetchTimeoutMs?: number`). Default off.
12
+ - **`src/commands/server.ts`** — `FeatureRolloutReconciler` now constructs its `listSpecArtifacts` via the gated wrapper, passing config + an `onDegradation` log sink. With the flag off (the default) and the wrapper trivially routing to the local scan, behavior on every existing install is byte-identical.
13
+
14
+ ## Side-effects analysis
15
+
16
+ **Reach.** Default off → every existing install runs the exact local scan from before, unchanged. New code paths only activate when an operator flips `featureRollout.canonicalRefScan: true`. The reconciler's call shape is unchanged (still sync, still returns `SpecArtifact[]`).
17
+
18
+ **The bug being fixed (proved by test).** Layer C's signature unit test: write an approved spec, commit + push to a canonical bare remote, then DELETE the file from the local working tree. The original local scanner doesn't see it (the file is gone). `scanSpecArtifactsWithCanonical` with the flag on + canonical remote configured DOES see it — `merged: true` by construction. That is the exact failure mode this layer exists to fix.
19
+
20
+ **Security / input safety.** All git through `SafeGitExecutor.run` (execFileSync, no shell). The canonical-remote allow-list lives on the Layer B side (release-readiness wiring) — Layer C accepts the remote NAME (already validated upstream) and uses it as a single argv token to `git fetch`. Trace JSON is parsed with a `try/catch` per blob; a malformed trace is skipped, never crashes the scan.
21
+
22
+ **Fail-loud / fail-safe.** Every failure path (no flag, no remote, scan throws) routes through `onDegradation(reason)` once per scan with a structured reason string, then returns the local-scan fallback. The reconciler's sync contract is preserved; the function NEVER throws into the boot path or the every-6h reconcile timer.
23
+
24
+ **Cross-PR alignment.** The Layer B production-bug fix from PR-2 (drop `--depth=1`) was carried through here verbatim. The `featureRollout.canonicalRemote` setting is INDEPENDENT of `releaseReadiness.canonicalRemote` — Layer C doesn't depend on PR-2 having landed; if both ship, an operator can either configure one for both or set them independently.
25
+
26
+ **Spec deferrals (per spec §4.3.3).** The skip-signal escalation (K=1 first tick then K=3, degradation → low-priority Attention after K consecutive deserved-skips) and the per-tick `lastProcessedCommit` cursor are mentioned in the spec but are NOT in this commit's scope. The canonical scan as-shipped already removes the silent-skip ROOT cause (the scan now sees what main has, not what the local tree has). The skip-signal + cursor are an additional observability enhancement on top — tracked here for a follow-up: <!-- tracked: release-readiness-visibility-pr3-followups -->. The follow-up does not block this PR's correctness — Layer C as-shipped is a self-contained improvement.
27
+
28
+ **Rollback.** Revert removes the canonical-scan path; the reconciler returns to using the local-only scanner. The `featureRollout` config key becomes orphaned but harmless.
29
+
30
+ ## Testing
31
+
32
+ - **Unit (5):** flag off → local fallback; flag on + no remote → degradation + local fallback; **the headline test — a spec deleted from the local tree but committed to main is still detected**; trace + spec joined by `specPath` (canonical-only); bad remote → ONE degradation + local fallback.
33
+ - All run against real fixture git repos with `SafeGitExecutor`; no stubs of git. Layer C's real-I/O coverage is built into the unit tests themselves (the wrapper has trivial branching; the canonical scan IS the I/O).