Compare commits
6 Commits
3dfecb72c6
...
fix/gitea-
| Author | SHA1 | Date | |
|---|---|---|---|
| 328fa39c55 | |||
| bf20a84a94 | |||
| c2914af401 | |||
| 81c42c41b9 | |||
| 245c62cff3 | |||
| d91a9256c5 |
93
.github/workflows/candidate.yml
vendored
93
.github/workflows/candidate.yml
vendored
@@ -45,6 +45,9 @@ jobs:
|
|||||||
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
|
||||||
@@ -63,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:
|
||||||
@@ -91,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 }}
|
||||||
@@ -99,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
|
||||||
@@ -127,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 }}
|
||||||
@@ -165,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 }}
|
||||||
@@ -173,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:
|
||||||
@@ -227,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 }}
|
||||||
@@ -235,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."
|
||||||
|
|||||||
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
|
||||||
|
|||||||
28
scripts/private_default_gateway.rb
Executable file
28
scripts/private_default_gateway.rb
Executable 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}"
|
||||||
@@ -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
|
||||||
|
|||||||
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"
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -275,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
|
||||||
@@ -295,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)
|
||||||
|
|||||||
Reference in New Issue
Block a user