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
|
- 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."
|
||||||
|
|||||||
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
|
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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
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)
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user