Repository navigation
feat: broker OpenCode Go API keys to Boat OMP homes - #260
Merged
Merged
Conversation
…ing configuration
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.
Intent
Implement brokered in-memory delivery of API keys for opencode-go-dnth, opencode-go-dnth-1, opencode-go-neuralengine, and opencode-go-neuralengine-1 to Boat-hosted OMP homes (the Boat secondmate and its workers). The existing per-lease credential broker is the only key path; OMP provider blocks and model configuration on Boat must reference the broker, never a key file. The workstation must stay online while a lease is live. vend() accepts api_key credentials only for allowlisted Boat providers and returns the minimum OMP shape; non-allowlisted API keys are refused and OAuth behavior stays unchanged. Follow the existing owner of the provider allowlist. Boat lane selection must not claim quota-informed choices for these accounts because quota-axi reads only the workstation auth.json; document how the main home supplies account choice at routing time. Docs must state the brokered credential path, keys in Boat memory only per lease and never at rest, workstation-online dependency, and key rotation as revocation. AC1 uses fake-key automated tests. AC2 requires real OMP calls through the broker on all four providers on a throwaway large Boat box plus a check for no auth.json and no OpenCode key in any file, models config, or environment; the box must be deleted under the $0.30 cap. Existing live evidence records four calls passed, 60,279 files scanned without key matches, box deleted, cost $0.01115. AC3 is the quota and documentation behavior. AC4 is proven only by green GitHub CI lanes on the PR; do not mark it accepted-blocked. Local suite context is attached in ~/Desktop/firstmate/data/fm-boat-opencode-go-vend/artifacts/baseline-classification.md: 25 failures reproduced at base with the same failure, 4 flaky failures passed on serial branch repeat, 0 branch-only. Include the classification summary/table and live-smoke cost/deletion in PR context. If a PR CI lane fails, report it before changing anything outside this task surface. Do not broaden scope: no Boat keys in argv, logs, commits, or disk; tests use fake keys; do not touch boatmate bx_cbbk3thq; out of scope are GitHub/Codex/Claude logins on the box, Boat API key, box size, Discord, and scope policy.
What Changed
Risk Assessment
✅ Low: Credential delivery is explicitly scoped, Boat launch settings remain bounded, and configuration changes are validated before atomic replacement.
Testing
Real local broker/consumer, configuration, and marker checks passed. Focused simulated launch coverage passed but is not live, so Boat launch emission is untested live. Temporary-home setup errors were resolved; the broader launch file was stopped after an unrelated recovery stall. Real Boat inference, remote scanning, and cost/deletion remain untested because isolated authentication is unavailable. This CLI-only change needs no screenshot; CI remains with the outer executor.
Evidence: Real OMP broker, facade, and consumer transcript
Evidence: Configuration preservation and refusal transcript
Evidence: Real missing-marker relaunch refusal
Evidence: Generated keyless OMP models configuration
Evidence: Isolated Boat access unavailable
{"code":"unauthorized","error":"not signed in, or your session expired. runboat onboard","event":"error","status":401}Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed (3) ✅
bin/fm-boat-remote-omp.py:151- Replacing an existing Go provider deletes unrelated providers that follow it. For example, with opencode-go-dnth followed by private-provider, _provider_key() recognizes only the Go entry, so its deletion range extends through private-provider to the end of the mapping. Preserve every sibling boundary when calculating replacement ranges. Related sites: bin/fm-boat-remote-omp.py:86 recognizes only target providers; :151 filters sibling boundaries; :155 calculates the oversized range; :165 deletes it.bin/fm-spawn.sh:5288- The unconditional snapshot-cache override also changes RunPod and other broker-backed OMP launches: fm-remote-secondmate-control.sh exports the same broker variables for RunPod, reaching this branch. No Boat requirement needs that additional behavior. Remove the override for non-Boat launches and retain a Boat-specific form. Related changed assertion: tests/fm-omp-secondmate.test.sh:560 requires the override for a generic broker launch.🔧 Fix applied.
2 errors still open:
bin/fm-boat-remote-omp.py:151- Replacing an existing Go provider deletes unrelated providers that follow it. For example, with opencode-go-dnth followed by private-provider, _provider_key() recognizes only the Go entry, so its deletion range extends through private-provider to the end of the mapping. Preserve every sibling boundary when calculating replacement ranges. Related sites: bin/fm-boat-remote-omp.py:86 recognizes only target providers; :151 filters sibling boundaries; :155 calculates the oversized range; :165 deletes it.bin/fm-boat-remote-omp.py:94- A valid existing config such asproviders: {private-provider: {baseUrl: https://private.example/v1}}is treated as having no providers mapping. Installation then appends a secondproviders:key, hiding existing providers or causing YAML rejection. Round 1 fixed sibling preservation for two-space block mappings but left this preservation failure reachable. Related sites: bin/fm-boat-remote-omp.py:94 recognizes only block headers; :103 accepts the inline header; :128 appends the duplicate mapping; :218 checks only the generated block and accepts the corrupted result. Recognize existing mappings semantically, or reject unsupported representations before writing; preserve the original file on refusal.🔧 Fix applied.
4 issues (3 errors, 1 warning) still open:
bin/fm-boat-remote-omp.py:151- Replacing an existing Go provider deletes unrelated providers that follow it. For example, with opencode-go-dnth followed by private-provider, _provider_key() recognizes only the Go entry, so its deletion range extends through private-provider to the end of the mapping. Preserve every sibling boundary when calculating replacement ranges. Related sites: bin/fm-boat-remote-omp.py:86 recognizes only target providers; :151 filters sibling boundaries; :155 calculates the oversized range; :165 deletes it.bin/fm-boat-remote-omp.py:94- A valid existing config such asproviders: {private-provider: {baseUrl: https://private.example/v1}}is treated as having no providers mapping. Installation then appends a secondproviders:key, hiding existing providers or causing YAML rejection. Round 1 fixed sibling preservation for two-space block mappings but left this preservation failure reachable. Related sites: bin/fm-boat-remote-omp.py:94 recognizes only block headers; :103 accepts the inline header; :128 appends the duplicate mapping; :218 checks only the generated block and accepts the corrupted result. Recognize existing mappings semantically, or reject unsupported representations before writing; preserve the original file on refusal.bin/fm-boat-remote-omp.py:170- Rounds 1/2 left another provider-preservation failure reachable. A valid models.yml containingproviders:\n private-provider:\n baseUrl: https://private.example/v1\npasses admission, but installation appends two-space provider entries beneath the existing four-space mapping, producing invalid YAML while install-models and check-models succeed. Recognize the existing indentation or refuse unsupported representations before writing, preserving the original bytes. Related sites: bin/fm-boat-remote-omp.py:87 assumes two-space sibling keys; :107 accepts the mapping without validating its children; :170 appends the incompatible block; :219 and :221 validate the header and generated substring rather than YAML meaning.tests/boat-auth-cases.py:166- New generated-configuration assertions check provider-name substrings and counts without validating YAML meaning, contrary to the test-quality rule for machine-consumed declarative artifacts. These checks can accept malformed configuration or names appearing outside the providers mapping. Parse the generated YAML with duplicate-key rejection and assert provider values and broker commands semantically; retain byte comparisons for preservation and idempotence. Sites: tests/boat-auth-cases.py:161-166, :483, :486-491. The replacement assertions were added or extended by the previous fix rounds.🔧 Fix applied.
✅ Re-checked - no issues remain.
TMPDIR="$PWD/.local-test/tmp" PYTHONDONTWRITEBYTECODE=1 python3 tests/boat-auth-cases.py "$PWD" ModelConfigTests -vPYTHONDONTWRITEBYTECODE=1 python3 .local-test/live-drive.pyusing real OMP 18.10.1 broker, Node facade, and remote helper with fake credentialsPYTHONDONTWRITEBYTECODE=1 python3 .local-test/config-drive.pyomp models --jsonwith isolated HOME and PI_CODING_AGENT_DIRnode bin/fm-boat-policy.mjs --model <provider/model>with default and explicit allowlists for all four accountsFM_HOME="$PWD/.local-test/primary" TMPDIR="$PWD/.local-test/tmp" bash .local-test/product/tests/fm-omp-secondmate.test.shagainst a disposable archive of HEAD; passed after resolving nested-home setup refusalsCaptured generated Boat and generic launch scripts with a targeted disposable test driverReviewed credential, quota-routing, workstation-online, and revocation documentation; removed disposable files and confirmed a clean working tree🔧 Fix applied.
1 warning still open:
TMPDIR="$PWD/.test-boat/tmp" PYTHONDONTWRITEBYTECODE=1 python3 tests/boat-auth-cases.py "$PWD" ModelConfigTests LaunchAuthTests -vPYTHONDONTWRITEBYTECODE=1 python3 .test-boat/live.py— real isolated OMP broker, facade, credential synchronization, four OMP token consumers, rotation, revocation, and remote-file scanPYTHONDONTWRITEBYTECODE=1 python3 .test-boat/config-probe.py— real install-models/check-models CLI preservation and refusal checksPYTHONDONTWRITEBYTECODE=1 python3 .test-boat/marker-probe.py— real auth installation and missing-marker relaunch refusalbash tests/fm-omp-secondmate.test.sh— two temporary-home setup refusals; default-temp retry passed launch checks but was stopped after a later recovery case stalledenv -u TMPDIR FM_HOME="$PWD/.test-boat/main" PYTHONDONTWRITEBYTECODE=1 bash tests/.boat-launch-phase.test.sh— existing launch function selected alone; passedenv -i PATH="$PATH" HOME="$PWD/.test-boat/boat-home" XDG_CONFIG_HOME="$PWD/.test-boat/boat-home/config" boat list— HTTP 401Reviewed the changed quota-routing and credential documentation; removed disposable files and verified a clean working tree and stopped test processes✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.
Post-test-gate live evidence
This section supersedes the earlier live-validation status for current-head relaunch behavior; the preceding pipeline snapshot remains historical.
fm-remote-secondmate-control.sh relaunchpath calledconfigure_compute_omp_auth_launch. A downstream stub on the disposable box prevented any Herdr or pane operation and launched OMP 18.10.1 directly with the secondmate environment composed byfm-spawn.sh.opencode-go-neuralengine/deepseek-v4.1-flashreturned exactlyOK(assistant_reply_seen=true,reply_ok=true,timed_out=false). The test driver terminated OMP after recording the reply (process_exit=143); no HTTP status or request host/path was captured./home/user/.fm/boat-auth.marker, the same relaunch path exited 1 withBoat OMP auth marker is missing or unsafe; restore the Boat credential lease before launching. The stub call count stayed at one, so OMP did not start on the second attempt.boat usagebefore deletion reported 660 seconds and$0.0066. The box was deleted andboat inforeturned 404. Total task spend upper bound is$0.15507against the$0.30cap. Decision 009 contains the four-provider live smoke and full key scan; this box added one current-head provider call and marker-refusal check, not another storage scan.live validation verdict: inconclusiveandTest exception approved: no operator reason supplied; this is not a clean test pass. CI passed.