-
Notifications
You must be signed in to change notification settings - Fork 0
fix(opencode): allow governed free models for private repositories #830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
seonghobae
wants to merge
37
commits into
main
Choose a base branch
from
fix/opencode-private-free-opt-in-20260808
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
840d5d2
fix(opencode): permit governed private free-model reviews
seonghobae 8e22caf
test(opencode): preserve central runner contracts
seonghobae 3362860
fix(opencode): retain delegated runner contracts
seonghobae 23eb9f7
fix(opencode): keep stable quick-gate contracts
seonghobae 58de30c
fix(test): restore exact quick-gate assertion
seonghobae daa1b36
fix(test): restore verified runner contract fixture
seonghobae dda2e68
test(opencode): reject type-confused free-model policy
seonghobae df7fcef
fix(opencode): require type-exact free-model policy
seonghobae e7adeb5
test(opencode): cover equals and duplicate model selectors
seonghobae 8d22eeb
fix(opencode): parse model selectors fail closed
seonghobae f861d08
fix(opencode): align duplicate selector diagnostic
seonghobae 891058b
test(opencode): pin wrapper cleanup and poll hardening
seonghobae e427cfb
fix(opencode): harden model-pool wrapper setup
seonghobae 1ef9e3a
docs(opencode): fix governed free model identifiers
seonghobae 9f27a42
test(opencode): reject zero fatal poll intervals
seonghobae 9a9b3e0
fix(opencode): validate fatal poll and clean probes
seonghobae 3405ffa
test(opencode): fail closed on private free-pool bypass
seonghobae 654c6f0
test(opencode): reject malformed policy tree records
seonghobae 0723847
test(opencode): cover provider guard fail-closed aliases
seonghobae c0fc46c
fix(opencode): reject truncated trusted policy tree records
seonghobae a952da8
fix(opencode): gate free candidates on verified visibility
seonghobae 919f41e
fix(opencode): harden provider selector parsing
seonghobae 45339d3
fix(opencode): normalize delegated runtime controls
seonghobae 50c4b60
fix(opencode): validate model-pool integer controls
seonghobae 1362055
fix(opencode): align delegated runner integrity markers
seonghobae 6cf29ab
docs(opencode): align private free policy with live catalog
seonghobae 1ff4f65
test(opencode): bind generic model-pool fixture to public visibility
seonghobae 6dea351
test(opencode): reject selector-like model values
seonghobae 66f36dc
test(opencode): require positive model-pool cycle delay
seonghobae 72f1c02
fix(opencode): reject ambiguous model selectors
seonghobae 164aa82
fix(opencode): require positive model-pool cycle delay
seonghobae 269ca3c
fix(opencode): prevent zero-delay model-pool cycles
seonghobae a18cd1a
fix(opencode): preserve model attempt diagnostics
seonghobae 82ef875
test(opencode): assert normalized retry budget contract
seonghobae 1a08181
fix(test): restore retry-budget assertion formatting
seonghobae 1e3f8f8
test(strix): assert normalized retry budget contract
seonghobae c001fcb
test(strix): assert normalized OpenCode run timeout
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| # OpenCode private-repository free-model policy | ||
|
|
||
| **Status:** Implemented design decision | ||
| **Decision date:** 2026-08-08 | ||
| **Scope:** `ContextualWisdomLab/.github` OpenCode review control plane | ||
|
|
||
| ## Decision | ||
|
|
||
| A private repository may use the anonymous `opencode-free/*` review pool only when | ||
| its trusted pull-request base commit contains the exact policy file below and the | ||
| reviewed head does not add, remove, rename, chmod, or modify that file. | ||
|
|
||
| ```text | ||
| .github/opencode-private-free-models.json | ||
| ``` | ||
|
|
||
| ```json | ||
| { | ||
| "schema_version": 1, | ||
| "allow_private_free_models": true, | ||
| "repository_data_classification": "public_equivalent", | ||
| "external_model_data_use_accepted": true | ||
| } | ||
| ``` | ||
|
|
||
| The declaration means all tracked repository content that OpenCode can read is | ||
| approved for processing under the external free-model terms as though it were | ||
| public. It does **not** mean the repository becomes public, and it is not a | ||
| claim that a scanner proved the absence of every secret or confidential fact. | ||
|
|
||
| The pull request that introduces or changes the policy remains ineligible. The | ||
| policy takes effect only after that change has passed normal review and reached | ||
| the base branch, on a subsequent pull request. Protect this file with normal | ||
| branch protection and, where available, `CODEOWNERS` review. | ||
|
|
||
| ## Why an explicit policy is required | ||
|
|
||
| Repository visibility and data sensitivity are different attributes. A private | ||
| repository may contain only non-sensitive open-source work, while another may | ||
| contain customer data, personal data, unreleased intellectual property, access | ||
| credentials, or confidential architecture. The absence of configured GitHub | ||
| Actions secrets does not classify the source tree. | ||
|
|
||
| GitHub secret scanning is an important independent control, but it detects | ||
| supported patterns rather than proving that no confidential information exists. | ||
| For organization-owned private and internal repositories, secret scanning | ||
| requires GitHub Secret Protection on an eligible plan. Therefore, model egress | ||
| is enabled by an auditable data-owner declaration, not by a heuristic scan or by | ||
| repository visibility alone. | ||
|
|
||
| ## Provider-data and catalog boundary | ||
|
|
||
| OpenCode documents free models as limited offerings used to collect feedback or | ||
| improve models. Its privacy documentation warns that some free endpoints may | ||
| retain or use collected data and that personal or confidential data must not be | ||
| submitted. Accordingly, the policy is restricted to `public_equivalent` | ||
| repositories and requires explicit acceptance of external-model data use. | ||
|
|
||
| The governed anonymous pool is synchronized to the zero-cost OpenCode Zen catalog | ||
| published in the primary Zen documentation. At the current decision revision it | ||
| contains exactly these seven aliases: | ||
|
|
||
| 1. `opencode-free/nemotron-3-ultra-free` | ||
| 2. `opencode-free/deepseek-v4-flash-free` | ||
| 3. `opencode-free/north-mini-code-free` | ||
| 4. `opencode-free/laguna-s-2.1-free` | ||
| 5. `opencode-free/ling-3.0-flash-free` | ||
| 6. `opencode-free/big-pickle` | ||
| 7. `opencode-free/mimo-v2.5-free` | ||
|
|
||
| Aliases previously carried as `hy3-free`, `minimax-m3-free`, `glm-5-free`, | ||
| `kimi-k2.5-free`, and `qwen3.6-plus-free` are not in the current documented | ||
| zero-cost catalog and are therefore removed before model selection. The wrapper | ||
| never infers that an arbitrary `opencode-free/*` prefix is actually free. A | ||
| catalog change requires an independently reviewable source update. | ||
|
|
||
| Candidate availability is still runtime-dependent. A provider rejection or | ||
| retirement remains ordinary bounded fallback evidence; it does not weaken review | ||
| or merge gates. | ||
|
|
||
| ## Repository-visibility boundary | ||
|
|
||
| Preconfigured anonymous candidates are not themselves authorization. The wrapper | ||
| first needs positive visibility evidence: | ||
|
|
||
| - a trusted caller may provide `OPENCODE_REPOSITORY_IS_PRIVATE=false`; or | ||
| - when that signal is absent, the wrapper may prove only the **public** case by | ||
| performing a credential-free `git ls-remote` against a strictly validated | ||
| `https://github.com/ContextualWisdomLab/<repository>[.git]` origin. | ||
|
|
||
| `true`, malformed visibility input, private/auth-required Git access, timeout, | ||
| transport failure, missing remote metadata, or any other indeterminate outcome is | ||
| fail-closed. The wrapper removes every preconfigured anonymous candidate and the | ||
| unchanged trusted-base policy becomes the sole re-enable path. The public probe | ||
| runs with GitHub, Actions, model-provider, and OIDC credentials removed and with | ||
| Git credential helpers disabled. | ||
|
|
||
| This preserves public-repository behavior without treating an untrusted candidate | ||
| list as visibility evidence and prevents a private caller from bypassing policy | ||
| by pre-populating `OPENCODE_MODEL_CANDIDATES`. | ||
|
|
||
| ## Credential boundary | ||
|
|
||
| Each OpenCode subprocess receives only the credential for its selected provider. | ||
| In particular, an anonymous `opencode-free/*` process receives none of these | ||
| values: | ||
|
|
||
| - GitHub tokens | ||
| - GitHub Actions OIDC request credentials | ||
| - GitHub Actions runtime, cache, or results credentials | ||
| - OpenCode application tokens | ||
| - NVIDIA NIM keys | ||
| - OpenCode Zen keys | ||
| - OpenAI keys | ||
| - OpenRouter keys | ||
| - GitHub Models tokens | ||
|
|
||
| Session export runs without any provider credential. Unknown future provider | ||
| prefixes also default to zero provider credentials until they are explicitly | ||
| classified. The guard recognizes OpenCode's long and short model selectors | ||
| (`--model`, `--model=`, `-m`, and `-m=`), rejects duplicate or missing model | ||
| selectors, and stops option parsing at `--` so argument text cannot accidentally | ||
| change credential selection. | ||
|
|
||
| The model remains read-only under the existing OpenCode review agent contract. | ||
| Credential isolation does not make confidential source safe to send to an | ||
| external model; the repository-level data classification remains the primary | ||
| eligibility control. | ||
|
|
||
| ## Fail-closed validation | ||
|
|
||
| The policy checker: | ||
|
|
||
| - accepts only full 40-character base and head commit SHAs; | ||
| - reads the policy directly from the immutable base Git tree; | ||
| - rejects a policy changed by the current head; | ||
| - requires the `git ls-tree -z` response to be exactly one NUL-terminated record, | ||
| rejecting truncated or extra records rather than reconstructing delimiters; | ||
| - accepts only one regular, non-executable `100644` blob at the fixed path; | ||
| - limits the blob to 4,096 bytes; | ||
| - requires strict UTF-8 and JSON without duplicate keys; | ||
| - rejects missing or unknown fields and requires the exact canonical values; | ||
| - ignores system and user Git configuration and disables hooks and filesystem | ||
| monitors during evaluation; | ||
| - removes preconfigured anonymous candidates on private or unverified calls before | ||
| policy evaluation; and | ||
| - leaves the existing keyed/private fallback pool unchanged on every denial or | ||
| local evaluation error. | ||
|
|
||
| The model-pool boundary also validates integer runtime, retry, cycle, and export | ||
| controls before shell arithmetic or `timeout` consumption. Malformed values fall | ||
| back to reviewed defaults rather than reaching Bash arithmetic or busy-looping a | ||
| runner. | ||
|
|
||
| ## Operating procedure | ||
|
|
||
| 1. Confirm the repository contains no credentials, personal data, customer data, | ||
| confidential documents, restricted source, or other data prohibited by the | ||
| free-model terms. | ||
| 2. Resolve active secret-scanning alerts and enable Secret Protection, push | ||
| protection, generic patterns, and organization-specific custom patterns where | ||
| available. | ||
| 3. Add the exact policy file in a separately reviewed pull request. | ||
| 4. Merge that policy through normal branch protection. Its own pull request will | ||
| not use the private free pool. | ||
| 5. On a later pull request, verify the OpenCode log records that the unchanged | ||
| trusted base policy enabled the anonymous candidates and verify the selected | ||
| child environment contains no GitHub, Actions, OIDC, or provider credentials. | ||
| 6. Run a private negative control without the policy and verify anonymous | ||
| candidates remain disabled while configured keyed fallbacks remain available. | ||
| 7. To disable the feature, remove or change the policy through a normal pull | ||
| request. The change takes effect after merge; the policy-changing pull request | ||
| itself remains fail-closed. | ||
|
|
||
| ## Rejected alternatives | ||
|
|
||
| ### Infer eligibility from missing Actions secrets | ||
|
|
||
| Rejected because repository source, history, fixtures, issues, and generated | ||
| review evidence may be confidential even when no Actions secret is configured. | ||
|
|
||
| ### Let the current pull-request head add an opt-in marker | ||
|
|
||
| Rejected because untrusted code could authorize its own external disclosure. | ||
| The marker must already exist on the base and remain unchanged in the head. | ||
|
|
||
| ### Trust a preconfigured `opencode-free/*` candidate as proof of eligibility | ||
|
|
||
| Rejected because candidate text is not a data-classification or visibility | ||
| signal. Private or unverified callers must pass the immutable-base policy gate. | ||
|
|
||
| ### Send all provider keys and rely on agent instructions | ||
|
|
||
| Rejected because a model process does not need unrelated credentials. Provider | ||
| selection is enforced in the process environment rather than by prompt text. | ||
|
|
||
| ### Treat secret scanning as a proof of public-equivalent data | ||
|
|
||
| Rejected because secret scanning is a defense-in-depth detector, not a complete | ||
| information-classification system. | ||
|
|
||
| ## Verification evidence | ||
|
|
||
| The implementation includes tests for valid base policy activation, missing and | ||
| self-added policies, head mutations, unknown and weaker declarations, duplicate | ||
| JSON keys, symlinks, oversized blobs, malformed UTF-8 and JSON, Git failures, | ||
| truncated and extra `ls-tree -z` records, provider-specific credential retention, | ||
| anonymous free credential removal, short and long model selectors, option | ||
| termination, export isolation, unknown-provider fail-safe behavior, private | ||
| preconfigured-free bypass rejection, catalog filtering, visibility fail-closed | ||
| behavior, runtime integer controls, and wrapper ordering. | ||
|
|
||
| Operational acceptance remains separate from code-level tests. Issue #833 tracks | ||
| the required protected-base private canary, negative control, credential-absence | ||
| evidence, schema/evidence validation, independent review/protection gates, and | ||
| rollback rehearsal. | ||
|
|
||
| ## References | ||
|
|
||
| GitHub. (n.d.-a). *Enabling secret scanning for your repository*. GitHub Docs. | ||
| Retrieved August 8, 2026, from | ||
| https://docs.github.com/en/code-security/how-tos/secure-your-secrets/detect-secret-leaks/enable-secret-scanning | ||
|
|
||
| GitHub. (n.d.-b). *Secrets*. GitHub Docs. Retrieved August 8, 2026, from | ||
| https://docs.github.com/en/actions/concepts/security/secrets | ||
|
|
||
| OpenCode. (n.d.). *Zen*. Retrieved August 9, 2026, from | ||
| https://opencode.ai/docs/zen | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "schema_version": 1, | ||
| "allow_private_free_models": true, | ||
| "repository_data_classification": "public_equivalent", | ||
| "external_model_data_use_accepted": true | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.