Make candidate CI dual-forge compatible
This commit is contained in:
85
.github/workflows/candidate.yml
vendored
85
.github/workflows/candidate.yml
vendored
@@ -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."
|
||||
|
||||
15
README.md
15
README.md
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
44
scripts/run_image_matrix_contract.sh
Executable file
44
scripts/run_image_matrix_contract.sh
Executable 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"
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user