7 Commits

Author SHA1 Message Date
328fa39c55 Resolve Gitea Docker gateway without iproute2
All checks were successful
Candidate compatibility / prepare (push) Successful in 30s
Candidate compatibility / image ${{ matrix.id }} (push) Has been skipped
Candidate compatibility / fixture-contract (push) Successful in 1m9s
Candidate compatibility / lockstep clients Ruby 3.3 (push) Successful in 1m15s
Candidate compatibility / repository (push) Successful in 1m50s
Candidate compatibility / lockstep clients Ruby 3.4 (push) Successful in 1m49s
Candidate compatibility / Gitea full exact image matrix (push) Successful in 1m53s
Candidate compatibility / lockstep clients Ruby 4.0 (push) Successful in 2m16s
Candidate compatibility / lockstep clients Ruby 3.2 (push) Successful in 2m20s
2026-07-20 00:30:05 -07:00
bf20a84a94 Use private host transport in containerized CI 2026-07-20 00:27:41 -07:00
c2914af401 Make deterministic model stub runner portable 2026-07-20 00:21:44 -07:00
81c42c41b9 Bind Gitea matrix runner to candidate Gemfile 2026-07-20 00:17:55 -07:00
245c62cff3 Make candidate CI dual-forge compatible 2026-07-20 00:15:49 -07:00
d91a9256c5 Provision Ruby for candidate matrix preparation 2026-07-19 23:45:08 -07:00
3dfecb72c6 Test alpha8 commit candidates before publication
Some checks failed
Candidate compatibility / prepare (push) Failing after 32s
Candidate compatibility / fixture-contract (push) Has been skipped
Candidate compatibility / lockstep clients Ruby 3.2 (push) Has been skipped
Candidate compatibility / lockstep clients Ruby 3.3 (push) Has been skipped
Candidate compatibility / lockstep clients Ruby 3.4 (push) Has been skipped
Candidate compatibility / lockstep clients Ruby 4.0 (push) Has been skipped
Candidate compatibility / image ${{ matrix.id }} (push) Has been skipped
Candidate compatibility / repository (push) Successful in 37s
Watch upstream OpenCode / open-compatibility-pr (push) Failing after 11s
2026-07-19 22:43:44 -07:00
18 changed files with 1122 additions and 156 deletions

View File

@@ -23,6 +23,7 @@ jobs:
ruby test/runtime_tuple_promoter_test.rb ruby test/runtime_tuple_promoter_test.rb
ruby test/exact_live_contract_test.rb ruby test/exact_live_contract_test.rb
ruby test/watcher_test.rb ruby test/watcher_test.rb
ruby test/client_candidate_test.rb
ruby test/lockstep_client_contract_test.rb ruby test/lockstep_client_contract_test.rb
ruby test/matrix_json_test.rb ruby test/matrix_json_test.rb
ruby test/image_contract_evidence_test.rb ruby test/image_contract_evidence_test.rb
@@ -31,51 +32,26 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
matrix: ${{ steps.matrix.outputs.value }} matrix: ${{ steps.matrix.outputs.value }}
publication_state: ${{ steps.clients.outputs.publication_state }}
ruby_provenance_kind: ${{ steps.clients.outputs.ruby_provenance_kind }}
ruby_ref: ${{ steps.clients.outputs.ruby_ref }} ruby_ref: ${{ steps.clients.outputs.ruby_ref }}
ruby_version: ${{ steps.clients.outputs.ruby_version }} ruby_version: ${{ steps.clients.outputs.ruby_version }}
ruby_tag: ${{ steps.clients.outputs.ruby_tag }} ruby_tag: ${{ steps.clients.outputs.ruby_tag }}
ruby_tag_object: ${{ steps.clients.outputs.ruby_tag_object }} ruby_tag_object: ${{ steps.clients.outputs.ruby_tag_object }}
rails_provenance_kind: ${{ steps.clients.outputs.rails_provenance_kind }}
rails_ref: ${{ steps.clients.outputs.rails_ref }} rails_ref: ${{ steps.clients.outputs.rails_ref }}
rails_version: ${{ steps.clients.outputs.rails_version }} rails_version: ${{ steps.clients.outputs.rails_version }}
rails_tag: ${{ steps.clients.outputs.rails_tag }} rails_tag: ${{ steps.clients.outputs.rails_tag }}
rails_tag_object: ${{ steps.clients.outputs.rails_tag_object }} rails_tag_object: ${{ steps.clients.outputs.rails_tag_object }}
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1
with:
ruby-version: "3.4"
- id: matrix - id: matrix
run: echo "value=$(ruby scripts/matrix_json.rb)" >> "$GITHUB_OUTPUT" run: echo "value=$(ruby scripts/matrix_json.rb)" >> "$GITHUB_OUTPUT"
- id: clients - id: clients
run: | run: ruby scripts/client_candidate_outputs.rb >> "$GITHUB_OUTPUT"
ruby_ref="$(jq -r '.clients["opencode-ruby"].ref' manifests/client-candidate.json)"
ruby_version="$(jq -r '.clients["opencode-ruby"].version' manifests/client-candidate.json)"
ruby_tag="$(jq -r '.clients["opencode-ruby"].tag_provenance.tag' manifests/client-candidate.json)"
ruby_tag_object="$(jq -r '.clients["opencode-ruby"].tag_provenance.annotated_tag_object' manifests/client-candidate.json)"
rails_ref="$(jq -r '.clients["opencode-rails"].ref' manifests/client-candidate.json)"
rails_version="$(jq -r '.clients["opencode-rails"].version' manifests/client-candidate.json)"
rails_tag="$(jq -r '.clients["opencode-rails"].tag_provenance.tag' manifests/client-candidate.json)"
rails_tag_object="$(jq -r '.clients["opencode-rails"].tag_provenance.annotated_tag_object' manifests/client-candidate.json)"
if ! [[ "$ruby_ref" =~ ^[0-9a-f]{40}$ ]]; then
echo "opencode-ruby candidate ref must be a full 40-hex commit" >&2
exit 2
fi
if ! [[ "$rails_ref" =~ ^[0-9a-f]{40}$ ]]; then
echo "opencode-rails candidate ref must be a full 40-hex commit" >&2
exit 2
fi
if ! [[ "$ruby_tag_object" =~ ^[0-9a-f]{40}$ && "$rails_tag_object" =~ ^[0-9a-f]{40}$ ]]; then
echo "candidate annotated tag objects must be full 40-hex object IDs" >&2
exit 2
fi
test "$ruby_tag" = "v$ruby_version"
test "$rails_tag" = "v$rails_version"
test "$ruby_version" = "$rails_version"
echo "ruby_ref=$ruby_ref" >> "$GITHUB_OUTPUT"
echo "ruby_version=$ruby_version" >> "$GITHUB_OUTPUT"
echo "ruby_tag=$ruby_tag" >> "$GITHUB_OUTPUT"
echo "ruby_tag_object=$ruby_tag_object" >> "$GITHUB_OUTPUT"
echo "rails_ref=$rails_ref" >> "$GITHUB_OUTPUT"
echo "rails_version=$rails_version" >> "$GITHUB_OUTPUT"
echo "rails_tag=$rails_tag" >> "$GITHUB_OUTPUT"
echo "rails_tag_object=$rails_tag_object" >> "$GITHUB_OUTPUT"
fixture-contract: fixture-contract:
needs: prepare needs: prepare
@@ -90,8 +66,18 @@ jobs:
- uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1 - uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1
with: with:
ruby-version: "3.4" ruby-version: "3.4"
bundler-cache: true bundler-cache: ${{ github.server_url == 'https://github.com' }}
working-directory: ruby-client working-directory: ruby-client
- name: Install candidate dependencies without the unavailable Gitea cache service
if: github.server_url != 'https://github.com'
working-directory: ruby-client
env:
BUNDLE_APP_CONFIG: ${{ runner.temp }}/opencode-ruby-fixture-bundle-config
BUNDLE_PATH: ${{ runner.temp }}/opencode-ruby-fixture-bundle
run: |
echo "BUNDLE_APP_CONFIG=$BUNDLE_APP_CONFIG" >> "$GITHUB_ENV"
echo "BUNDLE_PATH=$BUNDLE_PATH" >> "$GITHUB_ENV"
bundle install --jobs 4 --retry 3
- id: client-sha - id: client-sha
name: Verify immutable Ruby candidate checkout name: Verify immutable Ruby candidate checkout
env: env:
@@ -118,7 +104,7 @@ jobs:
mkdir -p ../.compat-evidence mkdir -p ../.compat-evidence
bundle exec ruby ../ruby/opencode_ruby_fixture_contract.rb bundle exec ruby ../ruby/opencode_ruby_fixture_contract.rb
- name: Upload fixture evidence - name: Upload fixture evidence
if: always() if: always() && github.server_url == 'https://github.com'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: ruby-fixtures-${{ github.run_id }}-${{ github.run_attempt }} name: ruby-fixtures-${{ github.run_id }}-${{ github.run_attempt }}
@@ -126,6 +112,9 @@ jobs:
include-hidden-files: true include-hidden-files: true
if-no-files-found: warn if-no-files-found: warn
retention-days: 30 retention-days: 30
- name: State the Gitea evidence boundary
if: always() && github.server_url != 'https://github.com'
run: echo "Gitea executed the fixture contract, but this forge has no supported immutable artifact upload; generated JSON is transient and is not review evidence."
lockstep-client-contract: lockstep-client-contract:
needs: prepare needs: prepare
@@ -133,7 +122,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
ruby: ["3.2", "3.3", "3.4"] ruby: ["3.2", "3.3", "3.4", "4.0"]
name: lockstep clients Ruby ${{ matrix.ruby }} name: lockstep clients Ruby ${{ matrix.ruby }}
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -154,8 +143,18 @@ jobs:
- uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1 - uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1
with: with:
ruby-version: ${{ matrix.ruby }} ruby-version: ${{ matrix.ruby }}
bundler-cache: true bundler-cache: ${{ github.server_url == 'https://github.com' }}
working-directory: opencode-rails working-directory: opencode-rails
- name: Install candidate dependencies without the unavailable Gitea cache service
if: github.server_url != 'https://github.com'
working-directory: opencode-rails
env:
BUNDLE_APP_CONFIG: ${{ runner.temp }}/opencode-rails-${{ matrix.ruby }}-bundle-config
BUNDLE_PATH: ${{ runner.temp }}/opencode-rails-${{ matrix.ruby }}-bundle
run: |
echo "BUNDLE_APP_CONFIG=$BUNDLE_APP_CONFIG" >> "$GITHUB_ENV"
echo "BUNDLE_PATH=$BUNDLE_PATH" >> "$GITHUB_ENV"
bundle install --jobs 4 --retry 3
- name: Verify immutable candidate checkouts - name: Verify immutable candidate checkouts
env: env:
EXPECTED_RUBY_SHA: ${{ needs.prepare.outputs.ruby_ref }} EXPECTED_RUBY_SHA: ${{ needs.prepare.outputs.ruby_ref }}
@@ -171,10 +170,13 @@ jobs:
env: env:
OPENCODE_RUBY_PATH: ${{ github.workspace }}/ruby-client OPENCODE_RUBY_PATH: ${{ github.workspace }}/ruby-client
OPENCODE_RAILS_PATH: ${{ github.workspace }}/opencode-rails OPENCODE_RAILS_PATH: ${{ github.workspace }}/opencode-rails
OPENCODE_CLIENT_PUBLICATION_STATE: ${{ needs.prepare.outputs.publication_state }}
OPENCODE_RUBY_COMMIT: ${{ needs.prepare.outputs.ruby_ref }} OPENCODE_RUBY_COMMIT: ${{ needs.prepare.outputs.ruby_ref }}
OPENCODE_RUBY_PROVENANCE_KIND: ${{ needs.prepare.outputs.ruby_provenance_kind }}
OPENCODE_RUBY_TAG: ${{ needs.prepare.outputs.ruby_tag }} OPENCODE_RUBY_TAG: ${{ needs.prepare.outputs.ruby_tag }}
OPENCODE_RUBY_TAG_OBJECT: ${{ needs.prepare.outputs.ruby_tag_object }} OPENCODE_RUBY_TAG_OBJECT: ${{ needs.prepare.outputs.ruby_tag_object }}
OPENCODE_RAILS_COMMIT: ${{ needs.prepare.outputs.rails_ref }} OPENCODE_RAILS_COMMIT: ${{ needs.prepare.outputs.rails_ref }}
OPENCODE_RAILS_PROVENANCE_KIND: ${{ needs.prepare.outputs.rails_provenance_kind }}
OPENCODE_RAILS_TAG: ${{ needs.prepare.outputs.rails_tag }} OPENCODE_RAILS_TAG: ${{ needs.prepare.outputs.rails_tag }}
OPENCODE_RAILS_TAG_OBJECT: ${{ needs.prepare.outputs.rails_tag_object }} OPENCODE_RAILS_TAG_OBJECT: ${{ needs.prepare.outputs.rails_tag_object }}
OPENCODE_RUBY_VERSION: ${{ needs.prepare.outputs.ruby_version }} OPENCODE_RUBY_VERSION: ${{ needs.prepare.outputs.ruby_version }}
@@ -189,7 +191,7 @@ jobs:
mkdir -p ../.compat-evidence mkdir -p ../.compat-evidence
bundle exec ruby ../ruby/lockstep_client_contract.rb bundle exec ruby ../ruby/lockstep_client_contract.rb
- name: Upload lockstep evidence - name: Upload lockstep evidence
if: always() if: always() && github.server_url == 'https://github.com'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: lockstep-ruby-${{ matrix.ruby }}-${{ github.run_id }}-${{ github.run_attempt }} name: lockstep-ruby-${{ matrix.ruby }}-${{ github.run_id }}-${{ github.run_attempt }}
@@ -197,8 +199,12 @@ jobs:
include-hidden-files: true include-hidden-files: true
if-no-files-found: warn if-no-files-found: warn
retention-days: 30 retention-days: 30
- name: State the Gitea evidence boundary
if: always() && github.server_url != 'https://github.com'
run: echo "Gitea executed the lockstep client contract, but this forge has no supported immutable artifact upload; generated JSON is transient and is not review evidence."
exact-image-contract: exact-image-contract:
if: github.server_url == 'https://github.com'
needs: prepare needs: prepare
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@@ -251,7 +257,7 @@ jobs:
mkdir -p .compat-evidence mkdir -p .compat-evidence
bundle exec scripts/run_image_contract.sh bundle exec scripts/run_image_contract.sh
- name: Upload exact-image evidence - name: Upload exact-image evidence
if: always() if: always() && github.server_url == 'https://github.com'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: image-${{ matrix.id }}-${{ github.run_id }}-${{ github.run_attempt }} name: image-${{ matrix.id }}-${{ github.run_id }}-${{ github.run_attempt }}
@@ -259,3 +265,56 @@ jobs:
include-hidden-files: true include-hidden-files: true
if-no-files-found: warn if-no-files-found: warn
retention-days: 30 retention-days: 30
exact-image-contract-gitea:
if: github.server_url != 'https://github.com'
needs: prepare
runs-on: ubuntu-latest
name: Gitea full exact image matrix
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ajaynomics/opencode-ruby
ref: ${{ needs.prepare.outputs.ruby_ref }}
path: ruby-client
- uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1
with:
ruby-version: "3.4"
- name: Install candidate dependencies outside the checkout
working-directory: ruby-client
env:
BUNDLE_APP_CONFIG: ${{ runner.temp }}/opencode-ruby-bundle-config
BUNDLE_PATH: ${{ runner.temp }}/opencode-ruby-bundle
run: |
echo "BUNDLE_APP_CONFIG=$BUNDLE_APP_CONFIG" >> "$GITHUB_ENV"
echo "BUNDLE_PATH=$BUNDLE_PATH" >> "$GITHUB_ENV"
bundle install --jobs 4 --retry 3
- id: client-sha
name: Verify immutable Ruby candidate checkout
env:
EXPECTED_CLIENT_SHA: ${{ needs.prepare.outputs.ruby_ref }}
run: |
actual_client_sha="$(git -C ruby-client rev-parse HEAD)"
test "$actual_client_sha" = "$EXPECTED_CLIENT_SHA"
echo "value=$actual_client_sha" >> "$GITHUB_OUTPUT"
- name: Resolve the private Docker host transport
run: |
probe_host="$(ruby scripts/private_default_gateway.rb)"
echo "OPENCODE_PROBE_HOST=$probe_host" >> "$GITHUB_ENV"
- name: Exercise the complete manifest matrix without artifact claims
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/ruby-client/Gemfile
OPENCODE_RUBY_PATH: ${{ github.workspace }}/ruby-client
OPENCODE_RUBY_COMMIT: ${{ steps.client-sha.outputs.value }}
OPENCODE_RAILS_COMMIT: ${{ needs.prepare.outputs.rails_ref }}
OPENCODE_COMPAT_EVIDENCE_DIR: ${{ github.workspace }}/.compat-evidence
OPENCODE_COMPAT_RUN_ID: ${{ github.run_id }}
OPENCODE_COMPAT_RUN_ATTEMPT: ${{ github.run_attempt }}
OPENCODE_COMPAT_HEAD_SHA: ${{ github.sha }}
OPENCODE_COMPAT_REPOSITORY: ${{ github.repository }}
OPENCODE_COMPAT_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: scripts/run_image_matrix_contract.sh
- name: State the Gitea evidence boundary
if: always()
run: echo "Gitea's exact-image path uploads nothing; generated JSON is transient and is not review evidence. This job passes only after every manifest image passes."

View File

@@ -8,11 +8,14 @@ prompts, credentials, sessions, or user data belong here.
## Policy ## Policy
- A gem release is a candidate until the fixture suite and every required image - An unpublished gem build is a candidate until the fixture suite and every
profile pass. required image profile pass. It is identified only by an exact commit.
- `opencode-ruby` and `opencode-rails` are one candidate release train. The - `opencode-ruby` and `opencode-rails` are one candidate release train. The
manifest records both peeled commits, and CI rejects a Rails candidate whose manifest records both exact commits, and CI rejects a Rails candidate whose
exact runtime dependency resolves to a different Ruby version or commit. exact runtime dependency resolves to a different Ruby version or commit.
- Once a train is published, commit-only provenance is no longer accepted. The
manifest and lockstep contract require each annotated tag object, local tag
ref, and peeled commit to bind to the tested checkout.
- Image references are immutable OCI index digests. A tag may be recorded as - Image references are immutable OCI index digests. A tag may be recorded as
human-readable provenance, but it is never an execution coordinate. human-readable provenance, but it is never an execution coordinate.
- Custom consumer images are certified with an isolated canary on the host that - Custom consumer images are certified with an isolated canary on the host that
@@ -50,23 +53,23 @@ ruby test/repository_test.rb
ruby test/runtime_tuple_promoter_test.rb ruby test/runtime_tuple_promoter_test.rb
ruby test/exact_live_contract_test.rb ruby test/exact_live_contract_test.rb
ruby test/watcher_test.rb ruby test/watcher_test.rb
OPENCODE_RUBY_PATH=/path/to/opencode-ruby-at-78b6f9c9e9c7d58b699af1c3c17764acd33de798 \ ruby test/client_candidate_test.rb
OPENCODE_RUBY_PATH=/path/to/opencode-ruby-at-9277646a4bb2cf25a8384ffc140b154f49ea5766 \
ruby ruby/opencode_ruby_fixture_contract.rb ruby ruby/opencode_ruby_fixture_contract.rb
BUNDLE_GEMFILE=/path/to/opencode-rails/Gemfile \ BUNDLE_GEMFILE=/path/to/opencode-rails/Gemfile \
OPENCODE_RUBY_PATH=/path/to/opencode-ruby \ OPENCODE_RUBY_PATH=/path/to/opencode-ruby \
OPENCODE_RAILS_PATH=/path/to/opencode-rails \ OPENCODE_RAILS_PATH=/path/to/opencode-rails \
OPENCODE_RUBY_COMMIT=FULL_40_HEX_COMMIT \ OPENCODE_CLIENT_PUBLICATION_STATE=unpublished \
OPENCODE_RUBY_TAG=v0.0.1.alpha7 \ OPENCODE_RUBY_COMMIT=9277646a4bb2cf25a8384ffc140b154f49ea5766 \
OPENCODE_RUBY_TAG_OBJECT=FULL_40_HEX_ANNOTATED_TAG_OBJECT \ OPENCODE_RUBY_PROVENANCE_KIND=commit \
OPENCODE_RAILS_COMMIT=FULL_40_HEX_COMMIT \ OPENCODE_RAILS_COMMIT=a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f \
OPENCODE_RAILS_TAG=v0.0.1.alpha7 \ OPENCODE_RAILS_PROVENANCE_KIND=commit \
OPENCODE_RAILS_TAG_OBJECT=FULL_40_HEX_ANNOTATED_TAG_OBJECT \ OPENCODE_RUBY_VERSION=0.0.1.alpha8 \
OPENCODE_RUBY_VERSION=0.0.1.alpha7 \ OPENCODE_RAILS_VERSION=0.0.1.alpha8 \
OPENCODE_RAILS_VERSION=0.0.1.alpha7 \
bundle exec ruby ruby/lockstep_client_contract.rb bundle exec ruby ruby/lockstep_client_contract.rb
BUNDLE_GEMFILE=/path/to/opencode-ruby-at-78b6f9c9e9c7d58b699af1c3c17764acd33de798/Gemfile \ BUNDLE_GEMFILE=/path/to/opencode-ruby-at-9277646a4bb2cf25a8384ffc140b154f49ea5766/Gemfile \
OPENCODE_RUBY_PATH=/path/to/opencode-ruby-at-78b6f9c9e9c7d58b699af1c3c17764acd33de798 \ OPENCODE_RUBY_PATH=/path/to/opencode-ruby-at-9277646a4bb2cf25a8384ffc140b154f49ea5766 \
OPENCODE_RUBY_COMMIT=78b6f9c9e9c7d58b699af1c3c17764acd33de798 \ OPENCODE_RUBY_COMMIT=9277646a4bb2cf25a8384ffc140b154f49ea5766 \
OPENCODE_IMAGE='ghcr.io/anomalyco/opencode@sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a' \ OPENCODE_IMAGE='ghcr.io/anomalyco/opencode@sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a' \
bundle exec scripts/run_image_contract.sh bundle exec scripts/run_image_contract.sh
``` ```
@@ -79,11 +82,27 @@ expected text byte-for-byte and the deterministic model stub to observe exactly
one request and exactly one authoritative assistant message. It never calls an one request and exactly one authoritative assistant message. It never calls an
external model provider. external model provider.
Candidate CI uploads machine-readable fixture, lockstep-client, and exact-image On GitHub, candidate CI uploads machine-readable fixture, lockstep-client, and
evidence for 30 days. These artifacts are review inputs, not certification by exact-image evidence for 30 days. These artifacts are review inputs, not
themselves. A person must review a passing artifact and commit the durable certification by themselves. Gitea runs the same repository, fixture, and
certification document under `evidence/`; no workflow commits or promotes its lockstep contracts and executes every exact-image manifest entry sequentially,
own result. because its runner cannot expand a matrix from a dependency job's JSON output.
The installed Gitea artifact service does not support the pinned GitHub upload
action, so Gitea deliberately uploads nothing: its generated JSON is transient
and must not be described as review evidence. A person must review a passing
GitHub artifact and commit the durable certification document under `evidence/`;
no workflow commits or promotes its own result.
`manifests/image-matrix.json` binds the active pending matrix to both candidate
commits. Its `previous_certification` entries preserve the last alpha7 result;
they do not certify alpha8. Ajent and Mushu custom-image rows remain pending
until their host canaries run against this exact pair.
The checked-in alpha8 local exact-image result is explicitly marked
`not-certified` and `shared-client-contract-only`. It proves the Ruby/SSE probe
on the recorded image IDs but does not claim Rails, plugin, application, or
deployment canary coverage; exact-head CI still supplies reviewable workflow
provenance.
## Adding an upstream release ## Adding an upstream release

View File

@@ -10,7 +10,7 @@ The gate requires:
1. repository validation and the shared fixture corpus; 1. repository validation and the shared fixture corpus;
2. exact `opencode-ruby` and `opencode-rails` candidate checkouts, all Rails 2. exact `opencode-ruby` and `opencode-rails` candidate checkouts, all Rails
candidate tests on Ruby 3.2 through 3.4, an exact Rails-to-Ruby runtime candidate tests on Ruby 3.2 through 4.0, an exact Rails-to-Ruby runtime
dependency, and loaded Bundler provenance for the Ruby commit; dependency, and loaded Bundler provenance for the Ruby commit;
3. the public exact-image matrix against the deterministic model stub; 3. the public exact-image matrix against the deterministic model stub;
4. an isolated custom-image canary for Ajent Rails and Mushu; 4. an isolated custom-image canary for Ajent Rails and Mushu;
@@ -21,6 +21,29 @@ The gate requires:
Health-only probes do not certify a tuple. Health-only probes do not certify a tuple.
### Client source provenance
Before publication, `manifests/client-candidate.json` must use
`publication_state: unpublished` and `kind: commit` for both clients. Each
provenance commit must equal its full 40-character checkout ref, and the Rails
Gemfile/runtime dependency must resolve the same exact Ruby commit and version.
Commit-only evidence is candidate evidence; it is not proof of a gem release.
The lockstep checkout fetches tags and fails unpublished mode if the matching
`vVERSION` tag appears, forcing the manifest through the published gate.
After publication, change the state to `published` and replace both commit
provenance objects with `kind: annotated-tag`, the exact tag, annotated tag
object ID, and peeled commit. The repository validator rejects a published
commit-only candidate. The lockstep job additionally verifies that each local
tag ref resolves to the declared annotated tag object and that both the object
and ref peel to the tested checkout. This transition is a reviewed manifest
change; CI does not create or publish tags.
The image matrix's active candidate coordinates must equal the client manifest.
Changing either commit resets the active matrix to `pending`. A
`previous_certification` preserves historical evidence but does not certify the
new candidate.
The shared live probe is intentionally strict: `full_text` must equal the The shared live probe is intentionally strict: `full_text` must equal the
expected text byte-for-byte and the deterministic model must receive exactly expected text byte-for-byte and the deterministic model must receive exactly
one request. A response that merely contains the expected text, including a one request. A response that merely contains the expected text, including a
@@ -103,12 +126,16 @@ still does not certify a consumer's ActiveRecord schema, persistence callbacks,
container adapter, or application canary; those remain consumer-owned profile container adapter, or application canary; those remain consumer-owned profile
evidence. evidence.
CI writes canonical JSON artifacts for the shared fixture, lockstep client, and GitHub CI writes canonical JSON artifacts for the shared fixture, lockstep
each exact-image target. Artifact retention is 30 days and supplies reviewable client, and each exact-image target. Artifact retention is 30 days and supplies
workflow provenance; it is not the long-term ledger. After review, copy the reviewable workflow provenance; it is not the long-term ledger. Gitea executes
relevant facts into a repository evidence document bound to the complete tuple the same contracts, with its exact-image targets run sequentially from the full
fingerprint. Automated workflows never update certified evidence or promote a generated manifest, but its installed artifact service cannot accept the
tuple. reviewed GitHub upload action. Gitea therefore keeps generated JSON transient
and makes no artifact-evidence claim. After reviewing a GitHub artifact, copy
the relevant facts into a repository evidence document bound to the complete
tuple fingerprint. Automated workflows never update certified evidence or
promote a tuple.
The command clears `candidate` after promotion. It sets the repository-wide The command clears `candidate` after promotion. It sets the repository-wide
`migration_state` to `certified` only after every consumer has certified `migration_state` to `certified` only after every consumer has certified

View File

@@ -0,0 +1,103 @@
{
"schema_version": 1,
"kind": "pre-release-client-image-review-input",
"status": "pass",
"certification_status": "not-certified",
"publication_state": "unpublished",
"coverage_scope": "shared-client-contract-only",
"clients": {
"opencode_ruby": {
"version": "0.0.1.alpha8",
"commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766",
"executed": true
},
"opencode_rails": {
"version": "0.0.1.alpha8",
"commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f",
"executed_by_image_contract": false
}
},
"checks": [
{
"id": "upstream-1.16.1",
"checked_at": "2026-07-20T05:38:34Z",
"image": "ghcr.io/anomalyco/opencode@sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a",
"docker_image_id": "sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a",
"platform": "linux/amd64",
"reported_version": "1.16.1",
"status": "pass",
"expected_text": "compat-ok",
"full_text": "compat-ok",
"authoritative_assistant_message_count": 1,
"llm_request_count": 1,
"executed_profiles": ["ruby-rest-sse"],
"required_consumer_profiles": ["rails-persisted-turn"]
},
{
"id": "upstream-1.17.18",
"checked_at": "2026-07-20T05:39:06Z",
"image": "ghcr.io/anomalyco/opencode@sha256:bf9d0e84b7cedef436a8f57db4d48767cd5d8fc6604f666335c1cc916b199a97",
"docker_image_id": "sha256:bf9d0e84b7cedef436a8f57db4d48767cd5d8fc6604f666335c1cc916b199a97",
"platform": "linux/amd64",
"reported_version": "1.17.18",
"status": "pass",
"expected_text": "compat-ok",
"full_text": "compat-ok",
"authoritative_assistant_message_count": 1,
"llm_request_count": 1,
"executed_profiles": ["ruby-rest-sse"],
"required_consumer_profiles": ["plugin-ledger"]
},
{
"id": "upstream-1.18.3",
"checked_at": "2026-07-20T05:39:18Z",
"image": "ghcr.io/anomalyco/opencode@sha256:c2d5d6398df72aac85cb1bdc8f900c71a9b75a33fb7c0a76dc1484e4b126e41e",
"docker_image_id": "sha256:c2d5d6398df72aac85cb1bdc8f900c71a9b75a33fb7c0a76dc1484e4b126e41e",
"platform": "linux/amd64",
"reported_version": "1.18.3",
"status": "pass",
"expected_text": "compat-ok",
"full_text": "compat-ok",
"authoritative_assistant_message_count": 1,
"llm_request_count": 1,
"executed_profiles": ["ruby-rest-sse"],
"required_consumer_profiles": ["rails-persisted-turn", "plugin-ledger"]
},
{
"id": "ajent-blackline-alpha8-shared-contract",
"checked_at": "2026-07-20T05:39:57Z",
"image": "sha256:239e3dc6cd2958251cda636fb20bd7cd7c46f596bd4c61cb81863ebc49d824e8",
"docker_image_id": "sha256:239e3dc6cd2958251cda636fb20bd7cd7c46f596bd4c61cb81863ebc49d824e8",
"platform": "linux/amd64",
"reported_version": "1.18.3",
"status": "pass",
"expected_text": "compat-ok",
"full_text": "compat-ok",
"authoritative_assistant_message_count": 1,
"llm_request_count": 1,
"executed_profiles": ["ruby-rest-sse"],
"required_consumer_profiles": ["rails-persisted-turn", "plugin-ledger"]
},
{
"id": "mushu-permission-shortcuts-alpha8-shared-contract",
"checked_at": "2026-07-20T05:40:08Z",
"image": "sha256:ed6293ca87e6db1dc07870d3ed6da21960743e6e2e904533b3c566d334b58d6a",
"docker_image_id": "sha256:ed6293ca87e6db1dc07870d3ed6da21960743e6e2e904533b3c566d334b58d6a",
"platform": "linux/amd64",
"reported_version": "0.0.0-permission-shortcuts-202606300721",
"status": "pass",
"expected_text": "compat-ok",
"full_text": "compat-ok",
"authoritative_assistant_message_count": 1,
"llm_request_count": 1,
"executed_profiles": ["ruby-rest-sse"],
"required_consumer_profiles": []
}
],
"limitations": [
"This local evidence has no GitHub workflow run provenance and is review input only.",
"The exact-image contract executed opencode-ruby but did not execute opencode-rails.",
"Rails persistence, plugin-ledger, consumer application, deployment, and user-visible canary profiles remain unverified.",
"No runtime tuple was promoted and no consumer was deployed."
]
}

View File

@@ -0,0 +1,154 @@
# frozen_string_literal: true
require "json"
require "rubygems"
module OpenCodeCompat
class ClientCandidate
class ValidationError < StandardError; end
SCHEMA_VERSION = 3
CLIENT_NAMES = %w[opencode-rails opencode-ruby].freeze
PUBLICATION_PROVENANCE = {
"published" => "annotated-tag",
"unpublished" => "commit"
}.freeze
SHA_PATTERN = /\A[0-9a-f]{40}\z/
def self.load(path)
new(JSON.parse(File.binread(path)))
rescue JSON::ParserError => error
raise ValidationError, "client candidate is not valid JSON: #{error.message}"
end
attr_reader :document
def initialize(document)
@document = document
end
def verify!
assert_exact_keys!(document,
%w[clients publication_state release_train schema_version status], "client candidate")
assert_equal!(SCHEMA_VERSION, document.fetch("schema_version"), "schema_version")
assert_equal!("candidate", document.fetch("status"), "status")
release_train = exact_version!(document.fetch("release_train"), "release_train")
publication_state = document.fetch("publication_state")
expected_kind = PUBLICATION_PROVENANCE.fetch(publication_state) do
raise ValidationError,
"publication_state must be one of #{PUBLICATION_PROVENANCE.keys.join(', ')}, got #{publication_state.inspect}"
end
clients = document.fetch("clients")
assert_equal!(CLIENT_NAMES, clients.keys.sort, "client names")
clients.each do |name, client|
validate_client!(name, client, release_train, expected_kind)
end
validate_runtime_dependency!(clients, release_train)
self
rescue KeyError => error
raise ValidationError, "client candidate is missing required field #{error.key.inspect}"
end
def github_outputs
verify!
clients = document.fetch("clients")
{
"publication_state" => document.fetch("publication_state"),
"ruby_provenance_kind" => provenance(clients, "opencode-ruby").fetch("kind"),
"ruby_ref" => clients.dig("opencode-ruby", "ref"),
"ruby_tag" => provenance(clients, "opencode-ruby")["tag"].to_s,
"ruby_tag_object" => provenance(clients, "opencode-ruby")["annotated_tag_object"].to_s,
"ruby_version" => clients.dig("opencode-ruby", "version"),
"rails_provenance_kind" => provenance(clients, "opencode-rails").fetch("kind"),
"rails_ref" => clients.dig("opencode-rails", "ref"),
"rails_tag" => provenance(clients, "opencode-rails")["tag"].to_s,
"rails_tag_object" => provenance(clients, "opencode-rails")["annotated_tag_object"].to_s,
"rails_version" => clients.dig("opencode-rails", "version")
}
end
private
def validate_client!(name, client, release_train, expected_kind)
expected_keys = %w[provenance ref repository version]
expected_keys << "runtime_dependencies" if name == "opencode-rails"
assert_exact_keys!(client, expected_keys.sort, "#{name} candidate")
assert_equal!("ajaynomics/#{name}", client.fetch("repository"), "#{name} repository")
assert_equal!(release_train, exact_version!(client.fetch("version"), "#{name} version"), "#{name} version")
ref = exact_commit!(client.fetch("ref"), "#{name} ref")
source = client.fetch("provenance")
assert_equal!(expected_kind, source.fetch("kind"), "#{name} provenance kind")
case expected_kind
when "commit"
assert_exact_keys!(source, %w[commit kind], "#{name} commit provenance")
assert_equal!(ref, exact_commit!(source.fetch("commit"), "#{name} provenance commit"),
"#{name} provenance commit")
when "annotated-tag"
assert_exact_keys!(source, %w[annotated_tag_object kind peeled_commit tag],
"#{name} annotated-tag provenance")
assert_equal!("v#{release_train}", exact_tag!(source.fetch("tag"), "#{name} tag"), "#{name} tag")
exact_commit!(source.fetch("annotated_tag_object"), "#{name} annotated tag object")
assert_equal!(ref, exact_commit!(source.fetch("peeled_commit"), "#{name} peeled commit"),
"#{name} peeled commit")
end
end
def validate_runtime_dependency!(clients, release_train)
dependency = clients.fetch("opencode-rails")
.fetch("runtime_dependencies")
.fetch("opencode-ruby")
assert_exact_keys!(dependency, %w[ref requirement], "opencode-rails runtime dependency")
assert_equal!("= #{release_train}", dependency.fetch("requirement"),
"opencode-rails runtime dependency requirement")
assert_equal!(clients.dig("opencode-ruby", "ref"),
exact_commit!(dependency.fetch("ref"), "opencode-rails runtime dependency ref"),
"opencode-rails runtime dependency ref")
end
def provenance(clients, name)
clients.fetch(name).fetch("provenance")
end
def exact_commit!(value, label)
return value if value.is_a?(String) && value.match?(SHA_PATTERN)
raise ValidationError, "#{label} must be a full lowercase 40-character Git commit, got #{value.inspect}"
end
def exact_tag!(value, label)
if value.is_a?(String) && value.match?(/\A[A-Za-z0-9][A-Za-z0-9._-]*\z/) &&
!value.include?("..") && !value.end_with?(".")
return value
end
raise ValidationError, "#{label} must be a safe exact Git tag, got #{value.inspect}"
end
def exact_version!(value, label)
if value.is_a?(String) && value.match?(/\A[0-9A-Za-z][0-9A-Za-z.-]*\z/) && Gem::Version.correct?(value)
return value
end
raise ValidationError, "#{label} must be an exact RubyGems version, got #{value.inspect}"
end
def assert_exact_keys!(value, expected, label)
actual = value.keys.sort
return if actual == expected
raise ValidationError, "#{label} keys must equal #{expected.inspect}, got #{actual.inspect}"
end
def assert_equal!(expected, actual, label)
return if expected == actual
raise ValidationError, "#{label} must equal #{expected.inspect}, got #{actual.inspect}"
end
end
end

View File

@@ -1,30 +1,29 @@
{ {
"schema_version": 2, "schema_version": 3,
"release_train": "0.0.1.alpha7", "release_train": "0.0.1.alpha8",
"publication_state": "unpublished",
"clients": { "clients": {
"opencode-ruby": { "opencode-ruby": {
"repository": "ajaynomics/opencode-ruby", "repository": "ajaynomics/opencode-ruby",
"ref": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", "ref": "9277646a4bb2cf25a8384ffc140b154f49ea5766",
"version": "0.0.1.alpha7", "version": "0.0.1.alpha8",
"tag_provenance": { "provenance": {
"tag": "v0.0.1.alpha7", "kind": "commit",
"annotated_tag_object": "abfce3ec8cb1ee468107d0845521769502251645", "commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766"
"peeled_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798"
} }
}, },
"opencode-rails": { "opencode-rails": {
"repository": "ajaynomics/opencode-rails", "repository": "ajaynomics/opencode-rails",
"ref": "2a391ccad1d098e4bb51eb19b6ca52adaa79e5cb", "ref": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f",
"version": "0.0.1.alpha7", "version": "0.0.1.alpha8",
"tag_provenance": { "provenance": {
"tag": "v0.0.1.alpha7", "kind": "commit",
"annotated_tag_object": "61e128084b2b665c0ba20b332987e41794948d7c", "commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f"
"peeled_commit": "2a391ccad1d098e4bb51eb19b6ca52adaa79e5cb"
}, },
"runtime_dependencies": { "runtime_dependencies": {
"opencode-ruby": { "opencode-ruby": {
"requirement": "= 0.0.1.alpha7", "requirement": "= 0.0.1.alpha8",
"ref": "78b6f9c9e9c7d58b699af1c3c17764acd33de798" "ref": "9277646a4bb2cf25a8384ffc140b154f49ea5766"
} }
} }
} }

View File

@@ -1,5 +1,12 @@
{ {
"schema_version": 1, "schema_version": 2,
"client_candidate": {
"release_train": "0.0.1.alpha8",
"publication_state": "unpublished",
"opencode_ruby_commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766",
"opencode_rails_commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f",
"certification_status": "pending"
},
"public_ci": [ "public_ci": [
{ {
"id": "upstream-1.16.1", "id": "upstream-1.16.1",
@@ -9,12 +16,15 @@
"consumers": ["travelwolf"], "consumers": ["travelwolf"],
"profiles": ["ruby-rest-sse"], "profiles": ["ruby-rest-sse"],
"required_consumer_profiles": ["rails-persisted-turn"], "required_consumer_profiles": ["rails-persisted-turn"],
"certification_status": "certified", "certification_status": "pending",
"certified_client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", "previous_certification": {
"status": "certified",
"client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798",
"certified_at": "2026-07-18T22:03:12Z", "certified_at": "2026-07-18T22:03:12Z",
"expected_text": "compat-ok", "expected_text": "compat-ok",
"full_text": "compat-ok", "full_text": "compat-ok",
"llm_request_count": 1 "llm_request_count": 1
}
}, },
{ {
"id": "upstream-1.17.18", "id": "upstream-1.17.18",
@@ -24,12 +34,15 @@
"consumers": ["opencode-ajent"], "consumers": ["opencode-ajent"],
"profiles": ["ruby-rest-sse"], "profiles": ["ruby-rest-sse"],
"required_consumer_profiles": ["plugin-ledger"], "required_consumer_profiles": ["plugin-ledger"],
"certification_status": "certified", "certification_status": "pending",
"certified_client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", "previous_certification": {
"status": "certified",
"client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798",
"certified_at": "2026-07-18T22:03:13Z", "certified_at": "2026-07-18T22:03:13Z",
"expected_text": "compat-ok", "expected_text": "compat-ok",
"full_text": "compat-ok", "full_text": "compat-ok",
"llm_request_count": 1 "llm_request_count": 1
}
}, },
{ {
"id": "upstream-1.18.3", "id": "upstream-1.18.3",
@@ -39,13 +52,16 @@
"consumers": ["ajent-rails"], "consumers": ["ajent-rails"],
"profiles": ["ruby-rest-sse"], "profiles": ["ruby-rest-sse"],
"required_consumer_profiles": ["rails-persisted-turn", "plugin-ledger"], "required_consumer_profiles": ["rails-persisted-turn", "plugin-ledger"],
"certification_status": "certified", "certification_status": "pending",
"certified_client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", "previous_certification": {
"status": "certified",
"client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798",
"certified_at": "2026-07-18T22:03:14Z", "certified_at": "2026-07-18T22:03:14Z",
"expected_text": "compat-ok", "expected_text": "compat-ok",
"full_text": "compat-ok", "full_text": "compat-ok",
"llm_request_count": 1 "llm_request_count": 1
} }
}
], ],
"host_canary": [ "host_canary": [
{ {
@@ -58,12 +74,15 @@
"reported_version": "1.18.3", "reported_version": "1.18.3",
"upstream_base": "ghcr.io/anomalyco/opencode@sha256:c2d5d6398df72aac85cb1bdc8f900c71a9b75a33fb7c0a76dc1484e4b126e41e", "upstream_base": "ghcr.io/anomalyco/opencode@sha256:c2d5d6398df72aac85cb1bdc8f900c71a9b75a33fb7c0a76dc1484e4b126e41e",
"profiles": ["rails-persisted-turn"], "profiles": ["rails-persisted-turn"],
"certification_status": "candidate-certified", "certification_status": "pending",
"certified_client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", "previous_certification": {
"certified_rails_commit": "2a391ccad1d098e4bb51eb19b6ca52adaa79e5cb", "status": "candidate-certified",
"client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798",
"rails_commit": "2a391ccad1d098e4bb51eb19b6ca52adaa79e5cb",
"certified_at": "2026-07-18T21:58:00Z", "certified_at": "2026-07-18T21:58:00Z",
"persisted_final_text": "compat-ok", "persisted_final_text": "compat-ok",
"model_request_count": 1 "model_request_count": 1
}
}, },
{ {
"id": "mushu-permission-shortcuts-alpha7-canary", "id": "mushu-permission-shortcuts-alpha7-canary",
@@ -75,11 +94,14 @@
"source_commit": "99d6328b18ff8a340a928449a2aa8bc184a063f9", "source_commit": "99d6328b18ff8a340a928449a2aa8bc184a063f9",
"reported_version": "0.0.0-permission-shortcuts-202606300721", "reported_version": "0.0.0-permission-shortcuts-202606300721",
"profiles": ["ruby-rest-sse"], "profiles": ["ruby-rest-sse"],
"certification_status": "candidate-certified", "certification_status": "pending",
"certified_client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", "previous_certification": {
"status": "candidate-certified",
"client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798",
"certified_at": "2026-07-18T21:53:00Z", "certified_at": "2026-07-18T21:53:00Z",
"persisted_final_text": "compat-ok", "persisted_final_text": "compat-ok",
"model_request_count": 1 "model_request_count": 1
} }
}
] ]
} }

View File

@@ -8,18 +8,30 @@ require "rubygems"
module OpenCodeCompat module OpenCodeCompat
class LockstepClientContract class LockstepClientContract
class ContractError < StandardError; end class ContractError < StandardError; end
class GitCommandError < ContractError
attr_reader :exitstatus
def initialize(message, exitstatus:)
@exitstatus = exitstatus
super(message)
end
end
CONTRACT_NAME = "opencode-client-lockstep" CONTRACT_NAME = "opencode-client-lockstep"
OPENCODE_RUBY_GIT_URI = "https://github.com/ajaynomics/opencode-ruby.git"
PUBLICATION_PROVENANCE = {
"published" => "annotated-tag",
"unpublished" => "commit"
}.freeze
REQUIRED_ENV = %w[ REQUIRED_ENV = %w[
OPENCODE_CLIENT_PUBLICATION_STATE
OPENCODE_RUBY_PATH OPENCODE_RUBY_PATH
OPENCODE_RUBY_COMMIT OPENCODE_RUBY_COMMIT
OPENCODE_RUBY_TAG OPENCODE_RUBY_PROVENANCE_KIND
OPENCODE_RUBY_TAG_OBJECT
OPENCODE_RUBY_VERSION OPENCODE_RUBY_VERSION
OPENCODE_RAILS_PATH OPENCODE_RAILS_PATH
OPENCODE_RAILS_COMMIT OPENCODE_RAILS_COMMIT
OPENCODE_RAILS_TAG OPENCODE_RAILS_PROVENANCE_KIND
OPENCODE_RAILS_TAG_OBJECT
OPENCODE_RAILS_VERSION OPENCODE_RAILS_VERSION
].freeze ].freeze
@@ -87,22 +99,25 @@ module OpenCodeCompat
rails_commit = exact_commit("OPENCODE_RAILS_COMMIT") rails_commit = exact_commit("OPENCODE_RAILS_COMMIT")
expected_ruby_version = exact_version("OPENCODE_RUBY_VERSION") expected_ruby_version = exact_version("OPENCODE_RUBY_VERSION")
expected_rails_version = exact_version("OPENCODE_RAILS_VERSION") expected_rails_version = exact_version("OPENCODE_RAILS_VERSION")
publication_state = exact_publication_state
assert_equal!(ruby_commit, checkout_head(ruby_path, "opencode-ruby"), "opencode-ruby checkout commit") assert_equal!(ruby_commit, checkout_head(ruby_path, "opencode-ruby"), "opencode-ruby checkout commit")
assert_equal!(rails_commit, checkout_head(rails_path, "opencode-rails"), "opencode-rails checkout commit") assert_equal!(rails_commit, checkout_head(rails_path, "opencode-rails"), "opencode-rails checkout commit")
ruby_tag_provenance = annotated_tag_provenance( ruby_source_provenance = source_provenance(
path: ruby_path, path: ruby_path,
label: "opencode-ruby", label: "opencode-ruby",
tag_name: "OPENCODE_RUBY_TAG", prefix: "OPENCODE_RUBY",
tag_object: "OPENCODE_RUBY_TAG_OBJECT", publication_state: publication_state,
expected_commit: ruby_commit expected_commit: ruby_commit,
expected_version: expected_ruby_version
) )
rails_tag_provenance = annotated_tag_provenance( rails_source_provenance = source_provenance(
path: rails_path, path: rails_path,
label: "opencode-rails", label: "opencode-rails",
tag_name: "OPENCODE_RAILS_TAG", prefix: "OPENCODE_RAILS",
tag_object: "OPENCODE_RAILS_TAG_OBJECT", publication_state: publication_state,
expected_commit: rails_commit expected_commit: rails_commit,
expected_version: expected_rails_version
) )
assert_equal!(expected_ruby_version, @ruby_version, "loaded Opencode::VERSION") assert_equal!(expected_ruby_version, @ruby_version, "loaded Opencode::VERSION")
assert_equal!(expected_rails_version, @rails_version, "loaded Opencode::RAILS_VERSION") assert_equal!(expected_rails_version, @rails_version, "loaded Opencode::RAILS_VERSION")
@@ -117,36 +132,45 @@ module OpenCodeCompat
dependency = exact_runtime_dependency!(rails_spec, expected_ruby_version) dependency = exact_runtime_dependency!(rails_spec, expected_ruby_version)
bundle_spec = bundled_ruby_spec! bundle_spec = bundled_ruby_spec!
source = bundle_spec.source source = bundle_spec.source
unless source && source.respond_to?(:revision) && source.class.name.match?(/(?:\A|::)Git\z/) unless source && source.respond_to?(:revision) && source.respond_to?(:ref) && source.respond_to?(:uri) &&
source.class.name.match?(/(?:\A|::)Git\z/)
raise ContractError, "Bundler opencode-ruby source must be a Git source" raise ContractError, "Bundler opencode-ruby source must be a Git source"
end end
bundler_revision = source.revision.to_s bundler_revision = source.revision.to_s
bundler_ref = source.ref.to_s
bundler_uri = source.uri.to_s
assert_exact_commit!(bundler_revision, "Bundler opencode-ruby revision") assert_exact_commit!(bundler_revision, "Bundler opencode-ruby revision")
assert_exact_commit!(bundler_ref, "Bundler opencode-ruby ref")
assert_equal!(ruby_commit, bundler_revision, "Bundler opencode-ruby revision") assert_equal!(ruby_commit, bundler_revision, "Bundler opencode-ruby revision")
assert_equal!(ruby_commit, bundler_ref, "Bundler opencode-ruby ref")
assert_equal!(OPENCODE_RUBY_GIT_URI, bundler_uri, "Bundler opencode-ruby URI")
{ {
"contract" => CONTRACT_NAME, "contract" => CONTRACT_NAME,
"publication_state" => publication_state,
"opencode_rails" => { "opencode_rails" => {
"checkout_commit" => rails_commit, "checkout_commit" => rails_commit,
"gem_version" => rails_spec.version.to_s, "gem_version" => rails_spec.version.to_s,
"loaded_version" => @rails_version, "loaded_version" => @rails_version,
"tag_provenance" => rails_tag_provenance, "source_provenance" => rails_source_provenance,
"runtime_dependency" => { "runtime_dependency" => {
"name" => dependency.name, "name" => dependency.name,
"requirement" => dependency.requirement.to_s "requirement" => dependency.requirement.to_s
} }
}, },
"opencode_ruby" => { "opencode_ruby" => {
"bundler_git_ref" => bundler_ref,
"bundler_git_revision" => bundler_revision, "bundler_git_revision" => bundler_revision,
"bundler_git_uri" => bundler_uri,
"bundler_source" => source.class.name, "bundler_source" => source.class.name,
"checkout_commit" => ruby_commit, "checkout_commit" => ruby_commit,
"gem_version" => ruby_spec.version.to_s, "gem_version" => ruby_spec.version.to_s,
"loaded_version" => @ruby_version, "loaded_version" => @ruby_version,
"tag_provenance" => ruby_tag_provenance "source_provenance" => ruby_source_provenance
}, },
"ruby_runtime_version" => @runtime_ruby_version, "ruby_runtime_version" => @runtime_ruby_version,
"schema_version" => 1, "schema_version" => 2,
"status" => "pass", "status" => "pass",
"workflow" => { "workflow" => {
"head_sha" => @env["OPENCODE_COMPAT_HEAD_SHA"], "head_sha" => @env["OPENCODE_COMPAT_HEAD_SHA"],
@@ -194,13 +218,24 @@ module OpenCodeCompat
value value
end end
def exact_publication_state
value = @env.fetch("OPENCODE_CLIENT_PUBLICATION_STATE")
return value if PUBLICATION_PROVENANCE.key?(value)
raise ContractError,
"OPENCODE_CLIENT_PUBLICATION_STATE must be one of #{PUBLICATION_PROVENANCE.keys.join(', ')}, got #{value.inspect}"
end
def exact_tag(name) def exact_tag(name)
value = @env.fetch(name) exact_tag_value(@env.fetch(name), name)
end
def exact_tag_value(value, label)
if value.match?(/\A[A-Za-z0-9][A-Za-z0-9._-]*\z/) && !value.include?("..") && !value.end_with?(".") if value.match?(/\A[A-Za-z0-9][A-Za-z0-9._-]*\z/) && !value.include?("..") && !value.end_with?(".")
return value return value
end end
raise ContractError, "#{name} must be a safe exact Git tag name, got #{value.inspect}" raise ContractError, "#{label} must be a safe exact Git tag name, got #{value.inspect}"
end end
def exact_tag_object(name) def exact_tag_object(name)
@@ -238,6 +273,55 @@ module OpenCodeCompat
raise ContractError, "could not resolve #{label} checkout HEAD: #{error.message}" raise ContractError, "could not resolve #{label} checkout HEAD: #{error.message}"
end end
def source_provenance(path:, label:, prefix:, publication_state:, expected_commit:, expected_version:)
kind_name = "#{prefix}_PROVENANCE_KIND"
kind = @env.fetch(kind_name)
expected_kind = PUBLICATION_PROVENANCE.fetch(publication_state)
assert_equal!(expected_kind, kind, "#{label} provenance kind for #{publication_state} candidate")
case kind
when "commit"
ensure_unpublished_tag_absent!(path, label, expected_version)
supplied_tag_fields = %W[#{prefix}_TAG #{prefix}_TAG_OBJECT].select do |name|
!@env[name].to_s.empty?
end
unless supplied_tag_fields.empty?
raise ContractError,
"#{label} unpublished commit provenance must not supply #{supplied_tag_fields.join(', ')}"
end
{"commit" => expected_commit, "kind" => "commit"}
when "annotated-tag"
annotated_tag_provenance(
path: path,
label: label,
tag_name: "#{prefix}_TAG",
tag_object: "#{prefix}_TAG_OBJECT",
expected_commit: expected_commit
).merge("kind" => "annotated-tag")
else
raise ContractError, "#{kind_name} has unsupported value #{kind.inspect}"
end
end
def ensure_unpublished_tag_absent!(path, label, expected_version)
tag = exact_tag_value("v#{expected_version}", "#{label} expected release tag")
tag_ref = "refs/tags/#{tag}"
resolved = begin
@git_resolver.call(path, "rev-parse", "--verify", "--quiet", tag_ref).to_s.strip
rescue GitCommandError => error
raise ContractError, "could not check #{label} release tag: #{error.message}" unless error.exitstatus == 1
nil
rescue KeyError
nil
end
return if resolved.nil? || resolved.empty?
raise ContractError,
"#{label} release tag #{tag} already exists; publication_state must be published with annotated-tag provenance"
end
def annotated_tag_provenance(path:, label:, tag_name:, tag_object:, expected_commit:) def annotated_tag_provenance(path:, label:, tag_name:, tag_object:, expected_commit:)
tag = exact_tag(tag_name) tag = exact_tag(tag_name)
object = exact_tag_object(tag_object) object = exact_tag_object(tag_object)
@@ -277,7 +361,7 @@ module OpenCodeCompat
detail = stderr.strip detail = stderr.strip
detail = "git exited #{status.exitstatus}" if detail.empty? detail = "git exited #{status.exitstatus}" if detail.empty?
raise ContractError, detail raise GitCommandError.new(detail, exitstatus: status.exitstatus)
end end
def loaded_spec!(name) def loaded_spec!(name)
@@ -324,7 +408,7 @@ if $PROGRAM_NAME == __FILE__
failure = OpenCodeCompat::LockstepClientContract.canonical_json( failure = OpenCodeCompat::LockstepClientContract.canonical_json(
"contract" => OpenCodeCompat::LockstepClientContract::CONTRACT_NAME, "contract" => OpenCodeCompat::LockstepClientContract::CONTRACT_NAME,
"error" => "#{error.class}: #{error.message}", "error" => "#{error.class}: #{error.message}",
"schema_version" => 1, "schema_version" => 2,
"status" => "fail" "status" => "fail"
) )
if (evidence_path = ENV["OPENCODE_COMPAT_EVIDENCE_PATH"]) && !evidence_path.empty? if (evidence_path = ENV["OPENCODE_COMPAT_EVIDENCE_PATH"]) && !evidence_path.empty?

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require_relative "../lib/opencode_compat/client_candidate"
root = File.expand_path("..", __dir__)
path = ARGV.fetch(0, File.join(root, "manifests/client-candidate.json"))
candidate = OpenCodeCompat::ClientCandidate.load(path)
candidate.github_outputs.each { |key, value| puts "#{key}=#{value}" }

View File

@@ -1,9 +1,27 @@
# frozen_string_literal: true # frozen_string_literal: true
require "json" require "json"
require_relative "../lib/opencode_compat/client_candidate"
root = File.expand_path("..", __dir__) root = File.expand_path("..", __dir__)
manifest = JSON.parse(File.read(File.join(root, "manifests/image-matrix.json"))) manifest = JSON.parse(File.read(File.join(root, "manifests/image-matrix.json")))
candidate = OpenCodeCompat::ClientCandidate.load(File.join(root, "manifests/client-candidate.json"))
candidate.verify!
clients = candidate.document.fetch("clients")
expected_candidate = {
"release_train" => candidate.document.fetch("release_train"),
"publication_state" => candidate.document.fetch("publication_state"),
"opencode_ruby_commit" => clients.dig("opencode-ruby", "ref"),
"opencode_rails_commit" => clients.dig("opencode-rails", "ref")
}
matrix_candidate = manifest.fetch("client_candidate")
unless expected_candidate.all? { |key, value| matrix_candidate[key] == value }
abort "image matrix client_candidate must equal the exact client candidate"
end
unless %w[pending certified].include?(matrix_candidate.fetch("certification_status"))
abort "image matrix client_candidate certification_status must be pending or certified"
end
matrix = manifest.fetch("public_ci").map do |target| matrix = manifest.fetch("public_ci").map do |target|
id = target.fetch("id") id = target.fetch("id")
profiles = target.fetch("profiles") profiles = target.fetch("profiles")

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require "ipaddr"
route_path = ARGV.fetch(0, "/proc/net/route")
routes = File.foreach(route_path).drop(1).filter_map do |line|
fields = line.split
next unless fields.length >= 8
next unless fields.fetch(1) == "00000000"
next unless (fields.fetch(3).to_i(16) & 0x2) == 0x2
fields
end
routes.sort_by { |fields| fields.fetch(6).to_i }.each do |fields|
gateway_hex = fields.fetch(2)
next unless gateway_hex.match?(/\A[0-9A-Fa-f]{8}\z/)
gateway = gateway_hex.scan(/../).reverse.map { |octet| octet.to_i(16) }.join(".")
address = IPAddr.new(gateway)
next unless address.ipv4? && address.private?
puts address
exit 0
end
abort "no private IPv4 default-route gateway found in #{route_path}"

View File

@@ -6,6 +6,7 @@ image="${OPENCODE_IMAGE:-}"
gem_path="${OPENCODE_RUBY_PATH:-}" gem_path="${OPENCODE_RUBY_PATH:-}"
expected_gem_commit="${OPENCODE_RUBY_COMMIT:-}" expected_gem_commit="${OPENCODE_RUBY_COMMIT:-}"
evidence_path="${OPENCODE_COMPAT_EVIDENCE_PATH:-}" evidence_path="${OPENCODE_COMPAT_EVIDENCE_PATH:-}"
probe_host="${OPENCODE_PROBE_HOST:-127.0.0.1}"
container_name="opencode-compat-${RANDOM}-$$" container_name="opencode-compat-${RANDOM}-$$"
llm_container_name="opencode-compat-llm-${RANDOM}-$$" llm_container_name="opencode-compat-llm-${RANDOM}-$$"
network_name="opencode-compat-net-${RANDOM}-$$" network_name="opencode-compat-net-${RANDOM}-$$"
@@ -97,6 +98,18 @@ if [[ ! "$image" =~ ^[^[:space:]@]+@sha256:[0-9a-f]{64}$ ]]; then
exit 2 exit 2
fi fi
fi fi
if ! ruby -ripaddr -e '
begin
address = IPAddr.new(ARGV.fetch(0))
valid = address.ipv4? && (address.loopback? || address.private?)
rescue IPAddr::InvalidAddressError
valid = false
end
exit(valid ? 0 : 1)
' "$probe_host"; then
echo "OPENCODE_PROBE_HOST must be a loopback or private IPv4 address" >&2
exit 2
fi
if [[ ! "$expected_gem_commit" =~ ^[0-9a-f]{40}$ ]]; then if [[ ! "$expected_gem_commit" =~ ^[0-9a-f]{40}$ ]]; then
echo "OPENCODE_RUBY_COMMIT must be a full lowercase 40-character Git commit" >&2 echo "OPENCODE_RUBY_COMMIT must be a full lowercase 40-character Git commit" >&2
@@ -121,11 +134,14 @@ docker run --detach \
--name "$llm_container_name" \ --name "$llm_container_name" \
--network "$network_name" \ --network "$network_name" \
--network-alias compat-llm \ --network-alias compat-llm \
--volume "$repo_root/scripts:/compat:ro" \
"$python_image" \ "$python_image" \
python /compat/fake_llm.py --port 8080 --port-file /tmp/compat-port \ python -c 'import time; time.sleep(3600)' \
>/dev/null >/dev/null
llm_container_started=1 llm_container_started=1
docker cp "$repo_root/scripts/fake_llm.py" "$llm_container_name:/tmp/fake_llm.py"
docker exec --detach \
"$llm_container_name" \
python /tmp/fake_llm.py --port 8080 --port-file /tmp/compat-port
for _ in $(seq 1 100); do for _ in $(seq 1 100); do
if docker exec "$llm_container_name" wget -qO- http://127.0.0.1:8080/health >/dev/null 2>&1; then if docker exec "$llm_container_name" wget -qO- http://127.0.0.1:8080/health >/dev/null 2>&1; then
@@ -168,7 +184,7 @@ config_json="$(jq -cn --arg url "http://compat-llm:8080/v1" '{
docker run --detach \ docker run --detach \
--name "$container_name" \ --name "$container_name" \
--network "$network_name" \ --network "$network_name" \
--publish 127.0.0.1::4096 \ --publish "${probe_host}::4096" \
--env "OPENCODE_CONFIG_CONTENT=$config_json" \ --env "OPENCODE_CONFIG_CONTENT=$config_json" \
--env OPENCODE_DISABLE_AUTOUPDATE=1 \ --env OPENCODE_DISABLE_AUTOUPDATE=1 \
--env OPENCODE_DISABLE_AUTOCOMPACT=1 \ --env OPENCODE_DISABLE_AUTOCOMPACT=1 \
@@ -180,7 +196,7 @@ docker run --detach \
opencode_container_started=1 opencode_container_started=1
host_port="$(docker port "$container_name" 4096/tcp | sed -E 's/.*:([0-9]+)$/\1/' | head -1)" host_port="$(docker port "$container_name" 4096/tcp | sed -E 's/.*:([0-9]+)$/\1/' | head -1)"
base_url="http://127.0.0.1:${host_port}" base_url="http://${probe_host}:${host_port}"
ready=0 ready=0
for _ in $(seq 1 120); do for _ in $(seq 1 120); do

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
evidence_dir="${OPENCODE_COMPAT_EVIDENCE_DIR:-$repo_root/.compat-evidence}"
matrix_json="$(ruby "$repo_root/scripts/matrix_json.rb")"
entry_count="$(jq -er '.include | length' <<<"$matrix_json")"
if [[ ! "$entry_count" =~ ^[1-9][0-9]*$ ]]; then
echo "exact image matrix must contain at least one entry" >&2
exit 2
fi
mkdir -p "$evidence_dir"
for ((index = 0; index < entry_count; index++)); do
entry="$(jq -ec --argjson index "$index" '.include[$index]' <<<"$matrix_json")"
matrix_id="$(jq -er '.id' <<<"$entry")"
image="$(jq -er '.image' <<<"$entry")"
expected_version="$(jq -er '.version' <<<"$entry")"
required_profiles="$(jq -ec '.required_consumer_profiles' <<<"$entry")"
evidence_path="$evidence_dir/$matrix_id.json"
printf 'Running exact image matrix entry %s (%s)\n' "$matrix_id" "$image"
OPENCODE_IMAGE="$image" \
OPENCODE_EXPECTED_VERSION="$expected_version" \
OPENCODE_MATRIX_ID="$matrix_id" \
OPENCODE_REQUIRED_CONSUMER_PROFILES="$required_profiles" \
OPENCODE_COMPAT_EVIDENCE_PATH="$evidence_path" \
bundle exec "$repo_root/scripts/run_image_contract.sh"
jq -e \
--arg matrix_id "$matrix_id" \
--arg image "$image" \
--arg version "$expected_version" \
'.status == "pass" and
.matrix_target == $matrix_id and
.image.requested == $image and
.image.reported_version == $version' \
"$evidence_path" >/dev/null
printf 'Exact image matrix entry %s passed; JSON remains transient on this forge.\n' "$matrix_id"
done
printf 'Full exact image matrix passed (%s entries).\n' "$entry_count"

View File

@@ -0,0 +1,114 @@
# frozen_string_literal: true
require "fileutils"
require "json"
require "minitest/autorun"
require "open3"
require "rbconfig"
require "tmpdir"
require_relative "../lib/opencode_compat/client_candidate"
class ClientCandidateTest < Minitest::Test
ROOT = File.expand_path("..", __dir__)
RUBY_COMMIT = "9277646a4bb2cf25a8384ffc140b154f49ea5766"
RAILS_COMMIT = "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f"
def setup
@document = JSON.parse(File.binread(File.join(ROOT, "manifests/client-candidate.json")))
end
def test_accepts_exact_unpublished_commit_candidate_and_emits_safe_outputs
outputs = OpenCodeCompat::ClientCandidate.new(@document).github_outputs
assert_equal "unpublished", outputs.fetch("publication_state")
assert_equal "commit", outputs.fetch("ruby_provenance_kind")
assert_equal RUBY_COMMIT, outputs.fetch("ruby_ref")
assert_equal "", outputs.fetch("ruby_tag")
assert_equal "commit", outputs.fetch("rails_provenance_kind")
assert_equal RAILS_COMMIT, outputs.fetch("rails_ref")
assert_equal "", outputs.fetch("rails_tag_object")
end
def test_unpublished_candidate_rejects_mixed_or_mismatched_provenance
@document.dig("clients", "opencode-ruby", "provenance")["tag"] = "v0.0.1.alpha8"
error = assert_raises(OpenCodeCompat::ClientCandidate::ValidationError) { candidate.verify! }
assert_match(/commit provenance keys/, error.message)
@document.dig("clients", "opencode-ruby", "provenance").delete("tag")
@document.dig("clients", "opencode-ruby", "provenance")["commit"] = "f" * 40
error = assert_raises(OpenCodeCompat::ClientCandidate::ValidationError) { candidate.verify! }
assert_match(/provenance commit/, error.message)
end
def test_published_candidate_requires_annotated_tag_provenance_for_both_clients
publish_document!
candidate.verify!
@document.dig("clients", "opencode-ruby")["provenance"] = {
"kind" => "commit",
"commit" => RUBY_COMMIT
}
error = assert_raises(OpenCodeCompat::ClientCandidate::ValidationError) { candidate.verify! }
assert_match(/provenance kind must equal "annotated-tag"/, error.message)
end
def test_published_candidate_rejects_wrong_tag_object_shape_or_peel
publish_document!
ruby_provenance = @document.dig("clients", "opencode-ruby", "provenance")
ruby_provenance["annotated_tag_object"] = "short"
error = assert_raises(OpenCodeCompat::ClientCandidate::ValidationError) { candidate.verify! }
assert_match(/annotated tag object must be a full lowercase 40-character/, error.message)
ruby_provenance["annotated_tag_object"] = "3" * 40
ruby_provenance["peeled_commit"] = "f" * 40
error = assert_raises(OpenCodeCompat::ClientCandidate::ValidationError) { candidate.verify! }
assert_match(/peeled commit/, error.message)
end
def test_rejects_non_lockstep_runtime_dependency
dependency = @document.dig("clients", "opencode-rails", "runtime_dependencies", "opencode-ruby")
dependency["requirement"] = "~> 0.0.1.alpha8"
error = assert_raises(OpenCodeCompat::ClientCandidate::ValidationError) { candidate.verify! }
assert_match(/runtime dependency requirement/, error.message)
dependency["requirement"] = "= 0.0.1.alpha8"
dependency["ref"] = "f" * 40
error = assert_raises(OpenCodeCompat::ClientCandidate::ValidationError) { candidate.verify! }
assert_match(/runtime dependency ref/, error.message)
end
def test_output_script_validates_the_repository_manifest
output, error, status = Open3.capture3(
RbConfig.ruby,
File.join(ROOT, "scripts/client_candidate_outputs.rb")
)
assert status.success?, error
assert_includes output.lines, "ruby_ref=#{RUBY_COMMIT}\n"
assert_includes output.lines, "rails_ref=#{RAILS_COMMIT}\n"
assert_includes output.lines, "publication_state=unpublished\n"
end
private
def candidate
OpenCodeCompat::ClientCandidate.new(@document)
end
def publish_document!
@document["publication_state"] = "published"
{
"opencode-ruby" => "3" * 40,
"opencode-rails" => "4" * 40
}.each do |name, tag_object|
client = @document.fetch("clients").fetch(name)
client["provenance"] = {
"kind" => "annotated-tag",
"tag" => "v#{client.fetch('version')}",
"annotated_tag_object" => tag_object,
"peeled_commit" => client.fetch("ref")
}
end
end
end

View File

@@ -4,11 +4,13 @@ require "fileutils"
require "json" require "json"
require "minitest/autorun" require "minitest/autorun"
require "open3" require "open3"
require "rbconfig"
require "tmpdir" require "tmpdir"
class ImageContractEvidenceTest < Minitest::Test class ImageContractEvidenceTest < Minitest::Test
ROOT = File.expand_path("..", __dir__) ROOT = File.expand_path("..", __dir__)
RUNNER = File.join(ROOT, "scripts/run_image_contract.sh") RUNNER = File.join(ROOT, "scripts/run_image_contract.sh")
GATEWAY_RESOLVER = File.join(ROOT, "scripts/private_default_gateway.rb")
VALID_IMAGE = "ghcr.io/anomalyco/opencode@sha256:#{'a' * 64}" VALID_IMAGE = "ghcr.io/anomalyco/opencode@sha256:#{'a' * 64}"
def setup def setup
@@ -108,6 +110,49 @@ class ImageContractEvidenceTest < Minitest::Test
assert_failed_without_docker(evidence_path) assert_failed_without_docker(evidence_path)
end end
def test_rejects_malformed_ipv6_and_public_probe_hosts_before_docker
checkout, commit = git_checkout
["not-an-address", "::1", "8.8.8.8"].each_with_index do |probe_host, index|
evidence_path = File.join(@tmp, "probe-host-#{index}.json")
environment = base_environment(checkout, commit, evidence_path).merge(
"OPENCODE_PROBE_HOST" => probe_host
)
_output, error, status = run_contract(environment)
assert_equal 2, status.exitstatus
assert_match(/loopback or private IPv4 address/, error)
assert_failed_without_docker(evidence_path)
end
end
def test_resolves_a_private_default_gateway_from_linux_route_hex
route_path = File.join(@tmp, "route")
File.write(route_path, <<~ROUTES)
Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth0 00000000 010011AC 0003 0 0 10 00000000 0 0 0
ROUTES
output, error, status = Open3.capture3(RbConfig.ruby, GATEWAY_RESOLVER, route_path)
assert status.success?, error
assert_equal "172.17.0.1\n", output
end
def test_gateway_resolver_rejects_a_public_default_gateway
route_path = File.join(@tmp, "public-route")
File.write(route_path, <<~ROUTES)
Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth0 00000000 08080808 0003 0 0 10 00000000 0 0 0
ROUTES
_output, error, status = Open3.capture3(RbConfig.ruby, GATEWAY_RESOLVER, route_path)
refute status.success?
assert_match(/no private IPv4 default-route gateway found/, error)
end
private private
def base_environment(checkout, commit, evidence_path) def base_environment(checkout, commit, evidence_path)

View File

@@ -8,10 +8,12 @@ require_relative "../ruby/lockstep_client_contract"
module LockstepClientContractTestSources module LockstepClientContractTestSources
class Git class Git
attr_reader :revision attr_reader :ref, :revision, :uri
def initialize(revision) def initialize(revision, ref: revision, uri: OpenCodeCompat::LockstepClientContract::OPENCODE_RUBY_GIT_URI)
@revision = revision @revision = revision
@ref = ref
@uri = uri
end end
end end
@@ -36,13 +38,16 @@ class LockstepClientContractTest < Minitest::Test
Dir.mkdir(@ruby_path) Dir.mkdir(@ruby_path)
Dir.mkdir(@rails_path) Dir.mkdir(@rails_path)
@env = { @env = {
"OPENCODE_CLIENT_PUBLICATION_STATE" => "published",
"OPENCODE_RUBY_PATH" => @ruby_path, "OPENCODE_RUBY_PATH" => @ruby_path,
"OPENCODE_RUBY_COMMIT" => RUBY_COMMIT, "OPENCODE_RUBY_COMMIT" => RUBY_COMMIT,
"OPENCODE_RUBY_PROVENANCE_KIND" => "annotated-tag",
"OPENCODE_RUBY_TAG" => RUBY_TAG, "OPENCODE_RUBY_TAG" => RUBY_TAG,
"OPENCODE_RUBY_TAG_OBJECT" => RUBY_TAG_OBJECT, "OPENCODE_RUBY_TAG_OBJECT" => RUBY_TAG_OBJECT,
"OPENCODE_RUBY_VERSION" => VERSION, "OPENCODE_RUBY_VERSION" => VERSION,
"OPENCODE_RAILS_PATH" => @rails_path, "OPENCODE_RAILS_PATH" => @rails_path,
"OPENCODE_RAILS_COMMIT" => RAILS_COMMIT, "OPENCODE_RAILS_COMMIT" => RAILS_COMMIT,
"OPENCODE_RAILS_PROVENANCE_KIND" => "annotated-tag",
"OPENCODE_RAILS_TAG" => RAILS_TAG, "OPENCODE_RAILS_TAG" => RAILS_TAG,
"OPENCODE_RAILS_TAG_OBJECT" => RAILS_TAG_OBJECT, "OPENCODE_RAILS_TAG_OBJECT" => RAILS_TAG_OBJECT,
"OPENCODE_RAILS_VERSION" => VERSION "OPENCODE_RAILS_VERSION" => VERSION
@@ -79,20 +84,90 @@ class LockstepClientContractTest < Minitest::Test
assert_equal "#{expected_json}\n", output.string assert_equal "#{expected_json}\n", output.string
assert_equal "#{expected_json}\n", File.binread(evidence_path) assert_equal "#{expected_json}\n", File.binread(evidence_path)
assert_equal "pass", document.fetch("status") assert_equal "pass", document.fetch("status")
assert_equal 2, document.fetch("schema_version")
assert_equal "published", document.fetch("publication_state")
assert_equal RUBY_COMMIT, document.dig("opencode_ruby", "checkout_commit") assert_equal RUBY_COMMIT, document.dig("opencode_ruby", "checkout_commit")
assert_equal RUBY_COMMIT, document.dig("opencode_ruby", "bundler_git_ref")
assert_equal RUBY_COMMIT, document.dig("opencode_ruby", "bundler_git_revision") assert_equal RUBY_COMMIT, document.dig("opencode_ruby", "bundler_git_revision")
assert_equal RUBY_TAG_OBJECT, document.dig("opencode_ruby", "tag_provenance", "annotated_tag_object") assert_equal OpenCodeCompat::LockstepClientContract::OPENCODE_RUBY_GIT_URI,
assert_equal RUBY_COMMIT, document.dig("opencode_ruby", "tag_provenance", "peeled_commit") document.dig("opencode_ruby", "bundler_git_uri")
assert_equal RUBY_TAG, document.dig("opencode_ruby", "tag_provenance", "tag") assert_equal "annotated-tag", document.dig("opencode_ruby", "source_provenance", "kind")
assert_equal RUBY_TAG_OBJECT, document.dig("opencode_ruby", "source_provenance", "annotated_tag_object")
assert_equal RUBY_COMMIT, document.dig("opencode_ruby", "source_provenance", "peeled_commit")
assert_equal RUBY_TAG, document.dig("opencode_ruby", "source_provenance", "tag")
assert_equal RAILS_COMMIT, document.dig("opencode_rails", "checkout_commit") assert_equal RAILS_COMMIT, document.dig("opencode_rails", "checkout_commit")
assert_equal RAILS_TAG_OBJECT, document.dig("opencode_rails", "tag_provenance", "annotated_tag_object") assert_equal RAILS_TAG_OBJECT, document.dig("opencode_rails", "source_provenance", "annotated_tag_object")
assert_equal RAILS_COMMIT, document.dig("opencode_rails", "tag_provenance", "peeled_commit") assert_equal RAILS_COMMIT, document.dig("opencode_rails", "source_provenance", "peeled_commit")
assert_equal "= #{VERSION}", document.dig("opencode_rails", "runtime_dependency", "requirement") assert_equal "= #{VERSION}", document.dig("opencode_rails", "runtime_dependency", "requirement")
assert_equal RUBY_VERSION, document.fetch("ruby_runtime_version") assert_equal RUBY_VERSION, document.fetch("ruby_runtime_version")
assert_nil document.dig("workflow", "run_id") assert_nil document.dig("workflow", "run_id")
assert_equal expected_json, JSON.generate(JSON.parse(expected_json).sort.to_h) assert_equal expected_json, JSON.generate(JSON.parse(expected_json).sort.to_h)
end end
def test_accepts_unpublished_commit_only_provenance_without_tag_lookup
use_commit_only_provenance!
document = contract.verify
assert_equal "unpublished", document.fetch("publication_state")
assert_equal({"commit" => RUBY_COMMIT, "kind" => "commit"},
document.dig("opencode_ruby", "source_provenance"))
assert_equal({"commit" => RAILS_COMMIT, "kind" => "commit"},
document.dig("opencode_rails", "source_provenance"))
end
def test_publication_state_cannot_weaken_published_tag_binding
@env["OPENCODE_RUBY_PROVENANCE_KIND"] = "commit"
error = assert_raises(OpenCodeCompat::LockstepClientContract::ContractError) { contract.verify }
assert_match(/published candidate/, error.message)
use_commit_only_provenance!
@env["OPENCODE_RAILS_PROVENANCE_KIND"] = "annotated-tag"
error = assert_raises(OpenCodeCompat::LockstepClientContract::ContractError) { contract.verify }
assert_match(/unpublished candidate/, error.message)
end
def test_commit_only_provenance_rejects_residual_tag_coordinates
use_commit_only_provenance!
@env["OPENCODE_RUBY_TAG"] = RUBY_TAG
error = assert_raises(OpenCodeCompat::LockstepClientContract::ContractError) { contract.verify }
assert_match(/must not supply OPENCODE_RUBY_TAG/, error.message)
end
def test_commit_only_provenance_stops_when_the_release_tag_appears
use_commit_only_provenance!
set_git_result(
@ruby_path,
["rev-parse", "--verify", "--quiet", "refs/tags/#{RUBY_TAG}"],
RUBY_TAG_OBJECT
)
error = assert_raises(OpenCodeCompat::LockstepClientContract::ContractError) { contract.verify }
assert_match(/release tag #{Regexp.escape(RUBY_TAG)} already exists/, error.message)
assert_match(/publication_state must be published/, error.message)
end
def test_commit_only_provenance_does_not_treat_git_errors_as_an_absent_tag
use_commit_only_provenance!
@git_results[[@ruby_path, ["rev-parse", "--verify", "--quiet", "refs/tags/#{RUBY_TAG}"]]] =
OpenCodeCompat::LockstepClientContract::GitCommandError.new("corrupt ref", exitstatus: 128)
resolver = lambda do |path, *arguments|
result = resolve_git(path, *arguments)
raise result if result.is_a?(Exception)
result
end
error = assert_raises(OpenCodeCompat::LockstepClientContract::ContractError) do
contract(git_resolver: resolver).verify
end
assert_match(/could not check opencode-ruby release tag: corrupt ref/, error.message)
end
def test_rejects_checkout_commit_mismatch_for_either_client def test_rejects_checkout_commit_mismatch_for_either_client
{ {
@ruby_path => "opencode-ruby checkout commit", @ruby_path => "opencode-ruby checkout commit",
@@ -223,6 +298,17 @@ class LockstepClientContractTest < Minitest::Test
@bundle_ruby_spec.source = LockstepClientContractTestSources::Git.new("f" * 40) @bundle_ruby_spec.source = LockstepClientContractTestSources::Git.new("f" * 40)
error = assert_raises(OpenCodeCompat::LockstepClientContract::ContractError) { contract.verify } error = assert_raises(OpenCodeCompat::LockstepClientContract::ContractError) { contract.verify }
assert_match(/Bundler opencode-ruby revision/, error.message) assert_match(/Bundler opencode-ruby revision/, error.message)
@bundle_ruby_spec.source = LockstepClientContractTestSources::Git.new(RUBY_COMMIT, ref: "main")
error = assert_raises(OpenCodeCompat::LockstepClientContract::ContractError) { contract.verify }
assert_match(/Bundler opencode-ruby ref/, error.message)
@bundle_ruby_spec.source = LockstepClientContractTestSources::Git.new(
RUBY_COMMIT,
uri: "https://example.test/opencode-ruby.git"
)
error = assert_raises(OpenCodeCompat::LockstepClientContract::ContractError) { contract.verify }
assert_match(/Bundler opencode-ruby URI/, error.message)
end end
def test_rejects_loaded_rails_gem_from_another_checkout def test_rejects_loaded_rails_gem_from_another_checkout
@@ -278,6 +364,7 @@ class LockstepClientContractTest < Minitest::Test
assert_equal 1, status.exitstatus assert_equal 1, status.exitstatus
failure = JSON.parse(File.binread(evidence_path)) failure = JSON.parse(File.binread(evidence_path))
assert_equal "opencode-client-lockstep", failure.fetch("contract") assert_equal "opencode-client-lockstep", failure.fetch("contract")
assert_equal 2, failure.fetch("schema_version")
assert_equal "fail", failure.fetch("status") assert_equal "fail", failure.fetch("status")
assert_match(/missing required environment/, failure.fetch("error")) assert_match(/missing required environment/, failure.fetch("error"))
assert_equal failure, JSON.parse(stderr) assert_equal failure, JSON.parse(stderr)
@@ -285,10 +372,10 @@ class LockstepClientContractTest < Minitest::Test
private private
def contract(ruby_version: VERSION, rails_version: VERSION) def contract(ruby_version: VERSION, rails_version: VERSION, git_resolver: method(:resolve_git))
OpenCodeCompat::LockstepClientContract.new( OpenCodeCompat::LockstepClientContract.new(
env: @env, env: @env,
git_resolver: method(:resolve_git), git_resolver: git_resolver,
loaded_specs: { loaded_specs: {
"opencode-ruby" => @ruby_spec, "opencode-ruby" => @ruby_spec,
"opencode-rails" => @rails_spec "opencode-rails" => @rails_spec
@@ -315,4 +402,14 @@ class LockstepClientContractTest < Minitest::Test
def set_git_result(path, arguments, result) def set_git_result(path, arguments, result)
@git_results[[path, arguments]] = result @git_results[[path, arguments]] = result
end end
def use_commit_only_provenance!
@env["OPENCODE_CLIENT_PUBLICATION_STATE"] = "unpublished"
@env["OPENCODE_RUBY_PROVENANCE_KIND"] = "commit"
@env["OPENCODE_RAILS_PROVENANCE_KIND"] = "commit"
@env.delete("OPENCODE_RUBY_TAG")
@env.delete("OPENCODE_RUBY_TAG_OBJECT")
@env.delete("OPENCODE_RAILS_TAG")
@env.delete("OPENCODE_RAILS_TAG_OBJECT")
end
end end

View File

@@ -12,9 +12,18 @@ class MatrixJsonTest < Minitest::Test
def setup def setup
@tmp = Dir.mktmpdir("opencode-matrix-json") @tmp = Dir.mktmpdir("opencode-matrix-json")
FileUtils.mkdir_p(File.join(@tmp, "lib/opencode_compat"))
FileUtils.mkdir_p(File.join(@tmp, "scripts")) FileUtils.mkdir_p(File.join(@tmp, "scripts"))
FileUtils.mkdir_p(File.join(@tmp, "manifests")) FileUtils.mkdir_p(File.join(@tmp, "manifests"))
FileUtils.cp(File.join(ROOT, "scripts/matrix_json.rb"), File.join(@tmp, "scripts")) FileUtils.cp(File.join(ROOT, "scripts/matrix_json.rb"), File.join(@tmp, "scripts"))
FileUtils.cp(
File.join(ROOT, "lib/opencode_compat/client_candidate.rb"),
File.join(@tmp, "lib/opencode_compat")
)
FileUtils.cp(
File.join(ROOT, "manifests/client-candidate.json"),
File.join(@tmp, "manifests")
)
@manifest = JSON.parse(File.read(File.join(ROOT, "manifests/image-matrix.json"))) @manifest = JSON.parse(File.read(File.join(ROOT, "manifests/image-matrix.json")))
end end
@@ -50,6 +59,32 @@ class MatrixJsonTest < Minitest::Test
assert_match(/certification_scope must be shared-client-contract-only/, error) assert_match(/certification_scope must be shared-client-contract-only/, error)
end end
def test_rejects_a_matrix_bound_to_another_client_candidate
@manifest.fetch("client_candidate")["opencode_ruby_commit"] = "f" * 40
_output, error, status = run_matrix(@manifest)
refute status.success?
assert_match(/must equal the exact client candidate/, error)
end
def test_allows_reviewed_candidate_certification_without_changing_coordinates
@manifest.fetch("client_candidate")["certification_status"] = "certified"
_output, error, status = run_matrix(@manifest)
assert status.success?, error
end
def test_rejects_unknown_candidate_certification_status
@manifest.fetch("client_candidate")["certification_status"] = "deployed"
_output, error, status = run_matrix(@manifest)
refute status.success?
assert_match(/certification_status must be pending or certified/, error)
end
private private
def run_matrix(manifest) def run_matrix(manifest)

View File

@@ -2,6 +2,7 @@
require "json" require "json"
require "minitest/autorun" require "minitest/autorun"
require_relative "../lib/opencode_compat/client_candidate"
require_relative "../lib/opencode_compat/runtime_tuple_promoter" require_relative "../lib/opencode_compat/runtime_tuple_promoter"
class RepositoryTest < Minitest::Test class RepositoryTest < Minitest::Test
@@ -37,7 +38,9 @@ class RepositoryTest < Minitest::Test
end end
def test_public_matrix_uses_only_immutable_oci_digests def test_public_matrix_uses_only_immutable_oci_digests
targets = json("manifests/image-matrix.json").fetch("public_ci") manifest = json("manifests/image-matrix.json")
assert_equal 2, manifest.fetch("schema_version")
targets = manifest.fetch("public_ci")
refute_empty targets refute_empty targets
targets.each do |target| targets.each do |target|
assert_match %r{\Aghcr\.io/anomalyco/opencode@sha256:[0-9a-f]{64}\z}, target.fetch("image") assert_match %r{\Aghcr\.io/anomalyco/opencode@sha256:[0-9a-f]{64}\z}, target.fetch("image")
@@ -45,38 +48,87 @@ class RepositoryTest < Minitest::Test
assert_equal ["ruby-rest-sse"], target.fetch("profiles") assert_equal ["ruby-rest-sse"], target.fetch("profiles")
assert_equal "shared-client-contract-only", assert_equal "shared-client-contract-only",
target.fetch("certification_scope", "shared-client-contract-only") target.fetch("certification_scope", "shared-client-contract-only")
next unless target.fetch("certification_status") == "certified" assert_equal "pending", target.fetch("certification_status")
previous = target["previous_certification"]
next unless previous
assert_match(/\A[0-9a-f]{40}\z/, target.fetch("certified_client_commit")) assert_equal "certified", previous.fetch("status")
assert_equal target.fetch("expected_text"), target.fetch("full_text") assert_match(/\A[0-9a-f]{40}\z/, previous.fetch("client_commit"))
assert_equal 1, target.fetch("llm_request_count") assert_equal previous.fetch("expected_text"), previous.fetch("full_text")
assert_equal 1, previous.fetch("llm_request_count")
end end
end end
def test_candidate_client_train_is_lockstep_and_bound_to_annotated_tags def test_candidate_client_train_is_lockstep_and_bound_to_unpublished_commits
candidate = json("manifests/client-candidate.json") candidate = json("manifests/client-candidate.json")
OpenCodeCompat::ClientCandidate.new(candidate).verify!
clients = candidate.fetch("clients") clients = candidate.fetch("clients")
release_train = candidate.fetch("release_train") release_train = candidate.fetch("release_train")
assert_equal 2, candidate.fetch("schema_version") assert_equal 3, candidate.fetch("schema_version")
assert_equal "candidate", candidate.fetch("status") assert_equal "candidate", candidate.fetch("status")
assert_equal "unpublished", candidate.fetch("publication_state")
assert_equal "0.0.1.alpha8", release_train
assert_equal %w[opencode-rails opencode-ruby], clients.keys.sort assert_equal %w[opencode-rails opencode-ruby], clients.keys.sort
clients.each do |name, client| clients.each do |name, client|
provenance = client.fetch("tag_provenance") provenance = client.fetch("provenance")
assert_equal "ajaynomics/#{name}", client.fetch("repository") assert_equal "ajaynomics/#{name}", client.fetch("repository")
assert_equal release_train, client.fetch("version") assert_equal release_train, client.fetch("version")
assert_match(/\A[0-9a-f]{40}\z/, client.fetch("ref")) assert_match(/\A[0-9a-f]{40}\z/, client.fetch("ref"))
assert_equal "v#{release_train}", provenance.fetch("tag") assert_equal "commit", provenance.fetch("kind")
assert_match(/\A[0-9a-f]{40}\z/, provenance.fetch("annotated_tag_object")) assert_equal client.fetch("ref"), provenance.fetch("commit")
assert_equal client.fetch("ref"), provenance.fetch("peeled_commit") refute provenance.key?("tag")
end end
assert_equal "9277646a4bb2cf25a8384ffc140b154f49ea5766", clients.dig("opencode-ruby", "ref")
assert_equal "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f", clients.dig("opencode-rails", "ref")
dependency = clients.fetch("opencode-rails").fetch("runtime_dependencies").fetch("opencode-ruby") dependency = clients.fetch("opencode-rails").fetch("runtime_dependencies").fetch("opencode-ruby")
assert_equal "= #{release_train}", dependency.fetch("requirement") assert_equal "= #{release_train}", dependency.fetch("requirement")
assert_equal clients.fetch("opencode-ruby").fetch("ref"), dependency.fetch("ref") assert_equal clients.fetch("opencode-ruby").fetch("ref"), dependency.fetch("ref")
end end
def test_image_matrix_is_pending_and_bound_to_the_exact_client_candidate
candidate = json("manifests/client-candidate.json")
clients = candidate.fetch("clients")
matrix_candidate = json("manifests/image-matrix.json").fetch("client_candidate")
assert_equal "pending", matrix_candidate.fetch("certification_status")
assert_equal candidate.fetch("release_train"), matrix_candidate.fetch("release_train")
assert_equal candidate.fetch("publication_state"), matrix_candidate.fetch("publication_state")
assert_equal clients.dig("opencode-ruby", "ref"), matrix_candidate.fetch("opencode_ruby_commit")
assert_equal clients.dig("opencode-rails", "ref"), matrix_candidate.fetch("opencode_rails_commit")
json("manifests/image-matrix.json").fetch("host_canary").each do |target|
assert_equal "pending", target.fetch("certification_status")
previous = target.fetch("previous_certification")
assert_includes %w[candidate-certified certified], previous.fetch("status")
assert_match(/\A[0-9a-f]{40}\z/, previous.fetch("client_commit"))
end
end
def test_alpha8_local_image_evidence_is_review_input_not_app_certification
evidence = json("evidence/2026-07-20-opencode-alpha8-pre-release-shared-client.json")
candidate = json("manifests/client-candidate.json").fetch("clients")
assert_equal "not-certified", evidence.fetch("certification_status")
assert_equal "shared-client-contract-only", evidence.fetch("coverage_scope")
assert_equal "unpublished", evidence.fetch("publication_state")
assert_equal candidate.dig("opencode-ruby", "ref"), evidence.dig("clients", "opencode_ruby", "commit")
assert_equal candidate.dig("opencode-rails", "ref"), evidence.dig("clients", "opencode_rails", "commit")
assert_equal true, evidence.dig("clients", "opencode_ruby", "executed")
assert_equal false, evidence.dig("clients", "opencode_rails", "executed_by_image_contract")
assert_equal 5, evidence.fetch("checks").length
evidence.fetch("checks").each do |check|
assert_equal "pass", check.fetch("status")
assert_equal ["ruby-rest-sse"], check.fetch("executed_profiles")
assert_equal check.fetch("expected_text"), check.fetch("full_text")
assert_equal 1, check.fetch("authoritative_assistant_message_count")
assert_equal 1, check.fetch("llm_request_count")
end
assert evidence.fetch("limitations").any? { |entry| entry.include?("remain unverified") }
end
def test_certified_migration_keeps_previous_tuple def test_certified_migration_keeps_previous_tuple
tuples = json("manifests/runtime-tuples.json") tuples = json("manifests/runtime-tuples.json")
return unless tuples.fetch("migration_state") == "certified" return unless tuples.fetch("migration_state") == "certified"
@@ -204,7 +256,11 @@ class RepositoryTest < Minitest::Test
assert_includes workflow, "repository: ajaynomics/opencode-ruby" assert_includes workflow, "repository: ajaynomics/opencode-ruby"
assert_includes workflow, "repository: ajaynomics/opencode-rails" assert_includes workflow, "repository: ajaynomics/opencode-rails"
assert_includes workflow, "path: opencode-rails" assert_includes workflow, "path: opencode-rails"
assert_includes workflow, "ruby: [\"3.2\", \"3.3\", \"3.4\"]" assert_includes workflow, "ruby: [\"3.2\", \"3.3\", \"3.4\", \"4.0\"]"
assert_includes workflow, "scripts/client_candidate_outputs.rb"
assert_includes workflow, "OPENCODE_CLIENT_PUBLICATION_STATE"
assert_includes workflow, "OPENCODE_RUBY_PROVENANCE_KIND"
assert_includes workflow, "OPENCODE_RAILS_PROVENANCE_KIND"
assert_includes workflow, "ruby/lockstep_client_contract.rb" assert_includes workflow, "ruby/lockstep_client_contract.rb"
assert_includes workflow, "OPENCODE_RUBY_TAG_OBJECT" assert_includes workflow, "OPENCODE_RUBY_TAG_OBJECT"
assert_includes workflow, "OPENCODE_RAILS_TAG_OBJECT" assert_includes workflow, "OPENCODE_RAILS_TAG_OBJECT"
@@ -219,6 +275,38 @@ class RepositoryTest < Minitest::Test
refute_match(/\b(kamal|kubectl|helm|nomad|docker\s+service|gh\s+pr\s+merge)\b/i, workflow) refute_match(/\b(kamal|kubectl|helm|nomad|docker\s+service|gh\s+pr\s+merge)\b/i, workflow)
end end
def test_candidate_workflow_has_an_explicit_dual_forge_evidence_boundary
workflow = File.read(File.join(ROOT, ".github/workflows/candidate.yml"))
upload_conditions = workflow.scan(
/- name: Upload (?:fixture|lockstep|exact-image) evidence\n\s+if: ([^\n]+)\n\s+uses: actions\/upload-artifact@/
).flatten
assert_equal 3, upload_conditions.length
assert_equal ["always() && github.server_url == 'https://github.com'"], upload_conditions.uniq
assert_includes workflow, "bundler-cache: ${{ github.server_url == 'https://github.com' }}"
assert_includes workflow, "exact-image-contract-gitea:"
assert_includes workflow, "if: github.server_url != 'https://github.com'"
assert_includes workflow, "run: scripts/run_image_matrix_contract.sh"
assert_includes workflow, 'probe_host="$(ruby scripts/private_default_gateway.rb)"'
assert_includes workflow, 'echo "OPENCODE_PROBE_HOST=$probe_host" >> "$GITHUB_ENV"'
assert_operator workflow.scan('BUNDLE_GEMFILE: ${{ github.workspace }}/ruby-client/Gemfile').length, :>=, 2
assert_operator workflow.scan("generated JSON is transient and is not review evidence").length, :>=, 3
gitea_job = workflow.split(/^ exact-image-contract-gitea:\n/, 2).fetch(1)
refute_includes gitea_job, "actions/upload-artifact@"
end
def test_gitea_matrix_runner_uses_every_generated_entry_without_hardcoded_coordinates
runner = File.read(File.join(ROOT, "scripts/run_image_matrix_contract.sh"))
assert_includes runner, 'matrix_json="$(ruby "$repo_root/scripts/matrix_json.rb")"'
assert_includes runner, 'for ((index = 0; index < entry_count; index++))'
assert_includes runner, 'bundle exec "$repo_root/scripts/run_image_contract.sh"'
assert_includes runner, '.image.reported_version == $version'
refute_match(/upstream-[0-9]/, runner)
refute_match(/ghcr\.io\/anomalyco/, runner)
end
def test_tuple_promotion_has_no_command_execution_or_deployment_client def test_tuple_promotion_has_no_command_execution_or_deployment_client
paths = %w[ paths = %w[
lib/opencode_compat/runtime_tuple_promoter.rb lib/opencode_compat/runtime_tuple_promoter.rb
@@ -239,6 +327,11 @@ class RepositoryTest < Minitest::Test
assert_includes probe, "ExactLiveContract.assert_authoritative_assistant_count!" assert_includes probe, "ExactLiveContract.assert_authoritative_assistant_count!"
refute_includes probe, "full_text.include?" refute_includes probe, "full_text.include?"
assert_includes runner, "exact_live_contract.rb" assert_includes runner, "exact_live_contract.rb"
assert_includes runner, 'docker cp "$repo_root/scripts/fake_llm.py"'
refute_includes runner, '--volume "$repo_root/scripts:/compat:ro"'
assert_includes runner, '--publish "${probe_host}::4096"'
assert_includes runner, 'base_url="http://${probe_host}:${host_port}"'
refute_includes runner, "--publish 0.0.0.0"
assert_includes runner, "OPENCODE_COMPAT_EVIDENCE_PATH" assert_includes runner, "OPENCODE_COMPAT_EVIDENCE_PATH"
assert_includes runner, "OPENCODE_EXPECTED_VERSION" assert_includes runner, "OPENCODE_EXPECTED_VERSION"
refute_match(/request_count.*-lt\s+1/, runner) refute_match(/request_count.*-lt\s+1/, runner)