Make candidate CI dual-forge compatible

This commit is contained in:
2026-07-20 00:15:49 -07:00
parent d91a9256c5
commit 245c62cff3
5 changed files with 173 additions and 16 deletions

View File

@@ -66,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:
@@ -94,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 }}
@@ -102,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
@@ -130,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 }}
@@ -168,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 }}
@@ -176,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:
@@ -230,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 }}
@@ -238,3 +265,51 @@ 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: Exercise the complete manifest matrix without artifact claims
env:
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 executed every manifest image in one job, but this forge has no supported immutable artifact upload; generated JSON is transient and is not review evidence."

View File

@@ -82,11 +82,16 @@ 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 `manifests/image-matrix.json` binds the active pending matrix to both candidate
commits. Its `previous_certification` entries preserve the last alpha7 result; commits. Its `previous_certification` entries preserve the last alpha7 result;

View File

@@ -126,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,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

@@ -275,6 +275,35 @@ 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_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