# Retcorpus K001 Prompt 012: Correct Sage 4/3 versus GNU 3/2 Benchmarker commissioning

Status: `CORRECTIVE_COMMISSIONING_TASK`
Parent lane: `retcorpus-k001`
Coordinator: `SAGE3_GNU4_BENCHMARK_SUB_COORDINATOR`
Updated: 2026-08-19 EDT

This is corrective work inside the already-authorized `commission-durable-sage4-gnu3-benchmark-campaign-v1` task. Preserve the useful implementation at the current campaign head and add normal corrective commit(s). Do not rewrite history, reset, rebase, amend the accepted checkpoint, or force-push.

## Exact implementation authority

```text
repository: backgammonsimplified/backgammon-engine-benchmarker
branch: campaign/sage4-gnu3-v1
current reviewed head: 8ef773e4e81ca3421696b09b9adaeb88cd046f52
required PR #2 merge base: 1c12f43fd236aa806d2113c72b1c99c087e58779
PR #3 head that must remain independent: 1569284972239a2627de8e5176eb47caef454fda
PR #3 common/master base: f99402277ab7c434eae5baebeb6d7cc166edafc7
```

Validated Engine Kit authority remains:

```text
repository: backgammonsimplified/backgammon-engine-kit
branch: feature/configurable-engine-ply-profiles-v1
source head: 833929ea72ccec058527f3cd1fa0b54a07ac666b
base: ab42f8186c5b04ca965a268bdc203179a7a669d8
```

Frozen match-play semantics remain:

```text
match length: 7
Sage checker: 4ply
Sage cube: 3ply
GNU checker configured target: 3ply
GNU cube: 2ply
GNU checker move filter: normal-v1
mirrored physical-seat dice protocol: physical-seat-dice-stream-v1
```

Do not change those semantics as part of this correction.

## Preserve the good work already present

Keep and improve the current Benchmarker-owned campaign configuration, deterministic pair IDs/base seeds, A/B physical-seat streams, ledger states, immutable publication model, manifests/checksums, stop/resume behavior, no-duplicate committed-pair behavior, Engine Kit delegation, CLI experiment-setting lockout, and Canonical/PR #3 boundaries.

Do not replace the architecture wholesale.

## Correction 1: pending double response must use responder take/pass semantics

The current runner is incorrect when a cube offer is already pending. It currently maps `cube_decision.recommended_action_id` to `take` or `pass`. That field represents the overall cube recommendation from the doubler/on-roll perspective and is not the correct invariant for a responder after a double has already been offered.

Historical authority in `backgammon_bot@336d7eca593cdf08461c917cbc213b8c4cee4668` handled a pending cube offer separately with BGSage `should_take` semantics. Preserve that behavior without parsing raw engine protocol and without duplicating Engine Kit invocation logic.

Implement a small pure policy helper over the normalized Engine Kit `CubeDecision` data. For a normal match-play pending double:

- legal responder actions are only Take or Pass;
- derive the responder choice from the normalized `double-take` and `double-pass` action equities emitted by Engine Kit;
- equities are from the doubler/on-roll perspective, so the responder chooses the action giving the doubler the lower equity;
- do not use overall `recommended_action_id` to infer the response;
- fail closed if the two required actions or their numeric equities are absent/ambiguous;
- do not enable beaver/raccoon behavior for this seven-point match campaign.

Add focused tests including at least:

1. overall recommendation `no-double`, but `double-take` has lower doubler equity than `double-pass` -> responder must `take`;
2. `double-pass` has lower doubler equity -> responder must `pass`;
3. missing/non-numeric required action equity -> fail closed.

The game-driving path must call this helper for a pending normal double.

## Correction 2: campaign-owned runner environment, not Engine Kit checkout `.venv`

Current preflight/README require real match execution under `<engine-kit-root>/.venv`. That is a development checkout environment and violates the accepted ownership split.

Correct the runtime architecture so Benchmarker creates/manages a campaign-owned runner environment beneath the operator-selected runtime root. A suitable shape is approximately:

```text
<runtime-root>/<campaign-id>/runner-workspace/
  .venv/
  environment manifest/lock evidence
  pair-*/attempt-*/...
```

Exact path naming may follow repository conventions, but requirements are:

- the campaign runner environment is under `runtime_root`, outside both source checkouts and outside `artifact_root`;
- it has its own `.venv`;
- Benchmarker owns creation/reconciliation of that environment;
- the Engine Kit source checkout remains an immutable commissioning input, not the Python environment used to execute matches;
- for this commissioning source-pin phase, install exact Engine Kit source commit `833929ea...` **non-editably** into the runner environment, or build/install a local immutable wheel derived from that exact clean source;
- verify the imported `backgammon_engine_kit` resolves inside the campaign runner environment, not into the Engine Kit source checkout;
- verify and record exact Engine Kit source commit plus installed package/artifact identity;
- record Python executable identity and an environment/dependency/freeze hash in campaign/run/pair provenance;
- pair attempt workspaces reference this campaign-owned environment rather than creating a mutable experiment authority;
- exact BGSage/GNU binaries/resources may remain referenced externally, but Engine Kit must verify their content identities;
- do not commit machine-local absolute paths as portable defaults.

Provide an idempotent bootstrap/reconcile operator surface. The normal workflow should remain approximately one committed runner command after environment preparation. If the environment exists but conflicts with the pinned authority, fail closed instead of silently mutating it.

The Benchmarker source checkout `.venv` may remain a development/test environment. It is not the real campaign runner environment.

Do not require `<engine-kit-root>/.venv` as the real-match Python runtime after this correction.

Add focused tests proving:

- runner environment path is beneath runtime root and outside source/artifact roots;
- imported Engine Kit must resolve under the runner environment during real execution;
- editable/source-checkout import leakage is rejected;
- Engine Kit source/package identity mismatch fails closed;
- conflicting existing runner environment fails closed or is reconciled only under an explicit safe rule;
- environment identity fields enter manifests.

## Correction 3: strengthen PR #3 isolation beyond exact head ancestry

The current runtime check only forbids the exact PR #3 head from being an ancestor. That would not catch a subset/cherry-pick of PR #3 commits.

Keep the exact-head rejection, and additionally prove at real-execution preflight that:

```text
merge-base(HEAD, pinned PR #3 head) == f99402277ab7c434eae5baebeb6d7cc166edafc7
```

for this commissioning authority.

Pin the expected PR #3 common/base identity in campaign provenance/config as needed. Fail closed if the merge base moves beyond the accepted common master base.

Add a focused Git/repository-boundary test or isolated temporary-repo test that catches PR #3 ancestry/subset drift rather than checking only a string in source.

Do not import PR #3 code.

## Correction 4: harden immutable publication fsync/atomic durability

The ledger already uses atomic replace plus fsync. Bring durable pair publication/manifests to the same standard before real immutable pairs exist.

For durable campaign/run/pair JSON, checksum files, commit markers, and pair publication:

- write through same-directory temporary files where appropriate;
- flush and `fsync` file contents before atomic replacement;
- fsync containing directories after atomic replacement;
- before renaming a completed pair staging directory into its immutable destination, ensure copied/generated files and relevant staging directories are durably flushed;
- write/fsync the checksum manifest and commit marker before publication;
- fsync the destination parent after directory publication;
- preserve crash-recovery behavior and do not overwrite a conflicting staging/destination directory.

Add focused tests around atomic/durable helper behavior where practical, plus the existing publication checksum/authority tests.

## Clarification: thread count is not the historical equivalence claim

The current campaign config pins Sage threads `1` and GNU threads `1`. The historical BGSage runner defaulted to automatic logical-core count unless overridden, while the bounded Engine Kit capability smoke intentionally used one thread.

Do **not** silently change thread counts in this corrective task because they are already included in the campaign configuration/hash/pair semantics. Instead:

- document that `threads=1` is a deliberate conservative/reproducible commissioning campaign setting;
- explicitly state that the campaign is reproducing the validated **decision-depth/filter profile and mirrored dice protocol**, not claiming historical parallelism/throughput equivalence;
- keep the thread fields pinned and manifest-visible;
- if exact historical parallelism is later required, treat it as a new campaign-authority/config decision that changes the configuration hash and pair identities.

## Game-driver validation

The first implementation tests did not execute `PairExecutor._run_match`. Add bounded non-engine unit/simulation coverage of the game-driving policy wherever practical, especially:

- pending double responder mapping;
- ordinary pre-roll cube mapping (`no-double` -> `roll`, doubling recommendation -> `double`);
- checker recommendation extraction;
- resignation handling remains board-rule only;
- no path invokes GNU evaluator through the neutral board process.

Do not run real Sage/GNU match compute in this corrective Codex task.

## Required validation

Run at minimum:

```text
.venv/bin/python -m pytest tests/sage_gnu_campaign -q
.venv/bin/python -m pytest
```

Expected external-input skips from the PR #2 Stage 1 suite are acceptable if unchanged and explained.

Also run:

```text
git diff --check <PR2-base>...HEAD
git status --short --branch
```

Verify remote branch head after push.

If the correction needs extra focused tests, run them too.

## Hard boundaries

Do NOT:

- launch a real mirrored-pair smoke;
- launch the long campaign;
- run post-match GNU 4-ply analysis;
- mutate/merge PR #3;
- use/acquire the Canonical writer lease;
- use PR #3 runtime roots;
- perform Canonical ingestion;
- alter MAIN Retcorpus state;
- alter Analyzer/Explainer state;
- change the frozen Sage 4/3 vs GNU 3/2 + normal-v1 decision profile;
- change pair/dice semantics;
- rewrite or force-push campaign history;
- duplicate raw Engine Kit protocol/invocation logic in Benchmarker.

## Git requirements

Work only on:

```text
campaign/sage4-gnu3-v1
```

Start from exact reviewed head:

```text
8ef773e4e81ca3421696b09b9adaeb88cd046f52
```

Create normal corrective commit(s) on top, test, and push to SSH origin.

Do not open or merge a PR.

## Return format

```text
TASK RESULT
Outcome: <complete/blocked>
Previous reviewed head: 8ef773e4e81ca3421696b09b9adaeb88cd046f52
Corrected Benchmarker branch/head: <branch@sha>
PR2 merge base: <sha>
PR3 merge-base isolation: <evidence>
Pending-double correction: <implementation + tests>
Runner environment correction: <implementation + tests>
Publication durability correction: <implementation + tests>
Thread policy: <documented value/meaning>
Campaign config: <path + resulting hash/schema>
Tests: <commands/results>
Diff check: <result>
Remote head verified: <sha>
Real match smoke launched: false
Campaign launched: false
Canonical writer touched: false
Explainer state touched: false
Remaining gates: <exact list>
```
