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
with:
ruby-version: "3.4"
bundler-cache: true
bundler-cache: ${{ github.server_url == 'https://github.com' }}
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
name: Verify immutable Ruby candidate checkout
env:
@@ -94,7 +104,7 @@ jobs:
mkdir -p ../.compat-evidence
bundle exec ruby ../ruby/opencode_ruby_fixture_contract.rb
- name: Upload fixture evidence
if: always()
if: always() && github.server_url == 'https://github.com'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ruby-fixtures-${{ github.run_id }}-${{ github.run_attempt }}
@@ -102,6 +112,9 @@ jobs:
include-hidden-files: true
if-no-files-found: warn
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:
needs: prepare
@@ -130,8 +143,18 @@ jobs:
- uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
bundler-cache: ${{ github.server_url == 'https://github.com' }}
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
env:
EXPECTED_RUBY_SHA: ${{ needs.prepare.outputs.ruby_ref }}
@@ -168,7 +191,7 @@ jobs:
mkdir -p ../.compat-evidence
bundle exec ruby ../ruby/lockstep_client_contract.rb
- name: Upload lockstep evidence
if: always()
if: always() && github.server_url == 'https://github.com'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: lockstep-ruby-${{ matrix.ruby }}-${{ github.run_id }}-${{ github.run_attempt }}
@@ -176,8 +199,12 @@ jobs:
include-hidden-files: true
if-no-files-found: warn
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:
if: github.server_url == 'https://github.com'
needs: prepare
runs-on: ubuntu-latest
strategy:
@@ -230,7 +257,7 @@ jobs:
mkdir -p .compat-evidence
bundle exec scripts/run_image_contract.sh
- name: Upload exact-image evidence
if: always()
if: always() && github.server_url == 'https://github.com'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: image-${{ matrix.id }}-${{ github.run_id }}-${{ github.run_attempt }}
@@ -238,3 +265,51 @@ jobs:
include-hidden-files: true
if-no-files-found: warn
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
external model provider.
Candidate CI uploads machine-readable fixture, lockstep-client, and exact-image
evidence for 30 days. These artifacts are review inputs, not certification by
themselves. A person must review a passing artifact and commit the durable
certification document under `evidence/`; no workflow commits or promotes its
own result.
On GitHub, candidate CI uploads machine-readable fixture, lockstep-client, and
exact-image evidence for 30 days. These artifacts are review inputs, not
certification by themselves. Gitea runs the same repository, fixture, and
lockstep contracts and executes every exact-image manifest entry sequentially,
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;

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
evidence.
CI writes canonical JSON artifacts for the shared fixture, lockstep client, and
each exact-image target. Artifact retention is 30 days and supplies reviewable
workflow provenance; it is not the long-term ledger. After review, 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.
GitHub CI writes canonical JSON artifacts for the shared fixture, lockstep
client, and each exact-image target. Artifact retention is 30 days and supplies
reviewable workflow provenance; it is not the long-term ledger. Gitea executes
the same contracts, with its exact-image targets run sequentially from the full
generated manifest, but its installed artifact service cannot accept the
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
`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)
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
paths = %w[
lib/opencode_compat/runtime_tuple_promoter.rb