Parent epic: #82
Sprint: v0.3 Sprint 1 - App, Combat, Deterministic Gates
Summary
Add a deterministic provider-contract smoke adapter that proves the native macOS app can launch configured Codex/OpenClaw provider commands, pass the expected ClawDnD environment, capture logs, and fail closed without running a live narrative session.
Architecture invariant
The native app is an orchestrator/read surface. Provider commands must not write campaign snapshots, play-state, qa/state, or engine data directly. The only write this smoke adapter may perform is to a caller-provided temporary CLAWDND_PLAYER_MOVES JSONL file, using the same constrained move-intent shape the viewer already appends.
Code map
scripts/provider_contract_smoke.sh: new deterministic command launched by the app or manually.
macos/ClawDnDApp/Sources/ClawDnDApp/Services/ProviderAdapters.swift: no behavior change required, but this script should be usable as the configured Codex/OpenClaw command.
macos/ClawDnDApp/Sources/ClawDnDApp/Views/ProvidersView.swift: optional help text can suggest the smoke command.
viewer/server.py: source of the /move contract and sanitizer; do not bypass it for real play.
Required environment contract
The smoke command must read and validate:
CLAWDND_PROVIDER
CLAWDND_WORLD
CLAWDND_RUN_ID
CLAWDND_PLAY_PORT
CLAWDND_PLAY_COMPANIONS
CLAWDND_PLAYER_MOVES for smoke mode only
It should reject unknown providers and missing/invalid paths with non-zero exit and clear stderr.
Minimal behavior
When CLAWDND_PLAYER_MOVES is set to a safe path under a temp directory, append one harmless move intent such as:
{"kind":"continue","text":"provider contract smoke"}
Then print a redacted JSON summary of provider, world, run id, port, companion count, and move path. Never print secrets or full process environment.
Acceptance criteria
- Running the script without required env fails with actionable messages.
- Running with temp env appends exactly one valid JSONL move.
- The script can be used as the configured Codex and OpenClaw command in the native app.
- No live Claude/Codex/OpenClaw narrative session is started.
- No campaign snapshot is mutated.
Focused validation
cd /Volumes/LEXAR/repos/ClawDnD
bash -n scripts/provider_contract_smoke.sh
TMPDIR=$(mktemp -d)
CLAWDND_PROVIDER=codex \
CLAWDND_WORLD=baldurs-gate \
CLAWDND_RUN_ID=smoke \
CLAWDND_PLAY_PORT=8765 \
CLAWDND_PLAY_COMPANIONS='' \
CLAWDND_PLAYER_MOVES="$TMPDIR/moves.jsonl" \
scripts/provider_contract_smoke.sh
jq -e 'select(.kind == "continue")' "$TMPDIR/moves.jsonl"
Original-agent handoff
Inspect these first:
macos/ClawDnDApp/Sources/ClawDnDApp/Services/ProviderAdapters.swift
macos/ClawDnDApp/Sources/ClawDnDApp/Services/AppProcessService.swift
viewer/server.py sanitize_move and /move
Parent epic: #82
Sprint: v0.3 Sprint 1 - App, Combat, Deterministic Gates
Summary
Add a deterministic provider-contract smoke adapter that proves the native macOS app can launch configured Codex/OpenClaw provider commands, pass the expected ClawDnD environment, capture logs, and fail closed without running a live narrative session.
Architecture invariant
The native app is an orchestrator/read surface. Provider commands must not write campaign snapshots,
play-state,qa/state, or engine data directly. The only write this smoke adapter may perform is to a caller-provided temporaryCLAWDND_PLAYER_MOVESJSONL file, using the same constrained move-intent shape the viewer already appends.Code map
scripts/provider_contract_smoke.sh: new deterministic command launched by the app or manually.macos/ClawDnDApp/Sources/ClawDnDApp/Services/ProviderAdapters.swift: no behavior change required, but this script should be usable as the configured Codex/OpenClaw command.macos/ClawDnDApp/Sources/ClawDnDApp/Views/ProvidersView.swift: optional help text can suggest the smoke command.viewer/server.py: source of the/movecontract and sanitizer; do not bypass it for real play.Required environment contract
The smoke command must read and validate:
CLAWDND_PROVIDERCLAWDND_WORLDCLAWDND_RUN_IDCLAWDND_PLAY_PORTCLAWDND_PLAY_COMPANIONSCLAWDND_PLAYER_MOVESfor smoke mode onlyIt should reject unknown providers and missing/invalid paths with non-zero exit and clear stderr.
Minimal behavior
When
CLAWDND_PLAYER_MOVESis set to a safe path under a temp directory, append one harmless move intent such as:{"kind":"continue","text":"provider contract smoke"}Then print a redacted JSON summary of provider, world, run id, port, companion count, and move path. Never print secrets or full process environment.
Acceptance criteria
Focused validation
Original-agent handoff
Inspect these first:
macos/ClawDnDApp/Sources/ClawDnDApp/Services/ProviderAdapters.swiftmacos/ClawDnDApp/Sources/ClawDnDApp/Services/AppProcessService.swiftviewer/server.pysanitize_moveand/move