Add atomic runtime tuple promotion gate

This commit is contained in:
2026-07-18 14:14:41 -07:00
parent b84432f989
commit a640f44972
7 changed files with 995 additions and 5 deletions

View File

@@ -20,7 +20,7 @@ jobs:
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.4"
- run: ruby test/repository_test.rb
- run: ruby test/repository_test.rb && ruby test/runtime_tuple_promoter_test.rb
prepare:
runs-on: ubuntu-latest

View File

@@ -41,6 +41,7 @@ Prerequisites are Ruby 3.2+, Python 3, `jq`, and Docker.
```sh
ruby test/repository_test.rb
ruby test/runtime_tuple_promoter_test.rb
OPENCODE_RUBY_PATH=/data/projects/opencode-ruby-alpha6 \
ruby ruby/opencode_ruby_fixture_contract.rb
OPENCODE_RUBY_PATH=/data/projects/opencode-ruby-alpha6 \

View File

@@ -25,6 +25,63 @@ Promotion is a reviewed manifest change that moves the old `current` tuple to
merged and deployed separately. Workflows in this repository have no deploy
credentials or deploy steps.
Use the repository promotion command; do not hand-edit the three tuple slots.
It binds evidence to a canonical SHA-256 of the complete tuple, requires full
consumer and gem commits, rejects mutable runtime image coordinates, and writes
the manifest with a same-filesystem atomic rename. The command only changes
`manifests/runtime-tuples.json`; it cannot deploy a consumer.
First inspect the fingerprints for both the candidate and rollback tuple:
```sh
ruby scripts/promote_runtime_tuple.rb fingerprint \
--consumer travelwolf \
--consumer-commit FULL_40_CHARACTER_CONSUMER_COMMIT
```
Commit one or more JSON evidence documents under `evidence/`. Each document
must explicitly contain values matching the promotion:
```json
{
"schema_version": 1,
"consumer": "travelwolf",
"profile": "rails-persisted-turn",
"consumer_commit": "FULL_40_CHARACTER_CONSUMER_COMMIT",
"status": "pass",
"certified_at": "2026-07-18T12:00:00Z",
"tuple_sha256": "sha256:FULL_64_CHARACTER_FINGERPRINT"
}
```
Preview the exact manifest transition with `--dry-run`, then repeat without it
to write the manifest:
```sh
ruby scripts/promote_runtime_tuple.rb promote \
--consumer travelwolf \
--consumer-commit FULL_40_CHARACTER_CONSUMER_COMMIT \
--status pass \
--certified-at 2026-07-18T12:00:00Z \
--evidence evidence/travelwolf-candidate.json \
--previous-status pass \
--previous-certified-at 2026-07-17T12:00:00Z \
--previous-evidence evidence/travelwolf-rollback.json \
--dry-run
```
The `--previous-*` arguments are required while the old `current` tuple lacks a
valid certification record. Later promotions reuse and revalidate that record.
Both candidate and previous evidence must match the printed tuple fingerprint,
consumer, full consumer commit, timestamp, and `pass` status. A tag may be kept
only in a `tag_provenance` field; `image`, `registry_ref`, and base image fields
must use `image@sha256:...`, while a private local artifact may use an exact
`docker_image_id`.
The command clears `candidate` after promotion. It sets the repository-wide
`migration_state` to `certified` only after every consumer has certified
`current` and `previous` tuples and no pending candidate.
## Rollback
Rollback restores the whole `previous` tuple. Do not roll back only the gem or

View File

@@ -0,0 +1,495 @@
# frozen_string_literal: true
require "digest"
require "json"
require "pathname"
require "tempfile"
require "time"
module OpenCodeCompat
class PromotionError < StandardError; end
class RuntimeTuplePromoter
FULL_COMMIT = /\A[0-9a-f]{40}\z/
DIGEST = /\Asha256:[0-9a-f]{64}\z/
IMMUTABLE_IMAGE = /\A[^@\s]+@sha256:[0-9a-f]{64}\z/
UTC_TIMESTAMP = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z\z/
CERTIFICATION_STATUS = "pass"
TUPLE_METADATA_KEYS = %w[
certification
certified_at
compatibility_certified_at
promoted_at
status
].freeze
EXACT_IMAGE_REFERENCE_KEYS = %w[
base_image
image
registry_ref
source_image
upstream_base
].freeze
def initialize(root:, manifest_path: File.join(root, "manifests/runtime-tuples.json"))
@root = File.realpath(root)
@manifest_path = File.expand_path(manifest_path)
end
def fingerprints(consumer:, consumer_commit:)
validate_full_commit!(consumer_commit, "consumer commit")
manifest = read_manifest
consumer_entry = fetch_consumer!(manifest, consumer)
profile = fetch_profile!(consumer_entry, consumer)
candidate = prepare_candidate!(consumer_entry, consumer_commit)
current = consumer_entry.fetch("current") do
raise PromotionError, "#{consumer} has no current tuple to preserve as previous"
end
validate_tuple!(current, "#{consumer} current")
{
"consumer" => consumer,
"profile" => profile,
"candidate_tuple_sha256" => tuple_fingerprint(candidate, consumer: consumer, profile: profile),
"current_tuple_sha256" => tuple_fingerprint(current, consumer: consumer, profile: profile)
}
end
def promote(consumer:, consumer_commit:, certification:, previous_certification: nil, dry_run: false)
validate_full_commit!(consumer_commit, "consumer commit")
if dry_run
manifest = read_manifest
return promote_manifest(
manifest,
consumer: consumer,
consumer_commit: consumer_commit,
certification: certification,
previous_certification: previous_certification
)
end
with_current_manifest_lock do |manifest|
promoted = promote_manifest(
manifest,
consumer: consumer,
consumer_commit: consumer_commit,
certification: certification,
previous_certification: previous_certification
)
atomic_write(promoted)
promoted
end
end
private
def read_manifest
parse_json(File.read(@manifest_path), @manifest_path)
rescue Errno::ENOENT
raise PromotionError, "runtime tuple manifest does not exist: #{@manifest_path}"
end
def promote_manifest(manifest, consumer:, consumer_commit:, certification:, previous_certification:)
consumer_entry = fetch_consumer!(manifest, consumer)
profile = fetch_profile!(consumer_entry, consumer)
candidate = prepare_candidate!(consumer_entry, consumer_commit)
current = consumer_entry.fetch("current") do
raise PromotionError, "#{consumer} has no current tuple to preserve as previous"
end
validate_tuple!(current, "#{consumer} current")
candidate_fingerprint = tuple_fingerprint(candidate, consumer: consumer, profile: profile)
current_fingerprint = tuple_fingerprint(current, consumer: consumer, profile: profile)
if candidate_fingerprint == current_fingerprint
raise PromotionError, "#{consumer} candidate is identical to its current tuple"
end
certified_candidate = certify_tuple!(
candidate,
consumer: consumer,
profile: profile,
supplied: certification,
expected_fingerprint: candidate_fingerprint,
label: "candidate"
)
certified_previous = certify_previous!(
current,
consumer: consumer,
profile: profile,
supplied: previous_certification,
expected_fingerprint: current_fingerprint
)
consumer_entry["previous"] = certified_previous
consumer_entry["current"] = certified_candidate
consumer_entry["candidate"] = nil
manifest["migration_state"] = all_consumers_certified?(manifest) ? "certified" : "candidate"
manifest
end
def fetch_consumer!(manifest, consumer)
unless manifest.is_a?(Hash) && manifest["schema_version"] == 1 && manifest["consumers"].is_a?(Hash)
raise PromotionError, "runtime tuple manifest must use schema_version 1 and contain consumers"
end
manifest.fetch("consumers").fetch(consumer) do
raise PromotionError, "unknown consumer #{consumer.inspect}"
end
end
def prepare_candidate!(consumer_entry, consumer_commit)
candidate = consumer_entry["candidate"]
unless candidate.is_a?(Hash)
raise PromotionError, "consumer has no candidate tuple to promote"
end
unless candidate["status"] == "compatibility-certified"
raise PromotionError, "candidate status must be compatibility-certified"
end
validate_timestamp!(candidate["certified_at"], "candidate compatibility certification timestamp")
if candidate.key?("consumer_commit") && candidate["consumer_commit"] != consumer_commit
raise PromotionError, "explicit consumer commit does not match the candidate"
end
prepared = deep_copy(candidate)
prepared["consumer_commit"] = consumer_commit
validate_tuple!(prepared, "candidate")
prepared
end
def fetch_profile!(consumer_entry, consumer)
profile = consumer_entry["profile"]
unless profile.is_a?(String) && profile.match?(/\A[a-z0-9][a-z0-9-]*\z/)
raise PromotionError, "#{consumer} must declare a valid compatibility profile"
end
profile_path = File.join(@root, "profiles", "#{profile}.json")
unless File.file?(profile_path)
raise PromotionError, "#{consumer} compatibility profile does not exist: profiles/#{profile}.json"
end
profile
end
def certify_previous!(tuple, consumer:, profile:, supplied:, expected_fingerprint:)
if tuple["status"] == "certified"
validate_recorded_certification!(
tuple,
consumer: consumer,
profile: profile,
expected_fingerprint: expected_fingerprint,
label: "current rollback"
)
return deep_copy(tuple)
end
unless supplied
raise PromotionError,
"current tuple is not certified; explicit previous certification evidence, timestamp, and status are required"
end
certify_tuple!(
tuple,
consumer: consumer,
profile: profile,
supplied: supplied,
expected_fingerprint: expected_fingerprint,
label: "previous"
)
end
def certify_tuple!(tuple, consumer:, profile:, supplied:, expected_fingerprint:, label:)
certification = validate_supplied_certification!(
supplied,
consumer: consumer,
profile: profile,
tuple: tuple,
expected_fingerprint: expected_fingerprint,
label: label
)
certified = deep_copy(tuple)
if certified.key?("certified_at")
certified["compatibility_certified_at"] = certified.delete("certified_at")
end
certified["status"] = "certified"
certified["certification"] = certification
certified
end
def validate_supplied_certification!(supplied, consumer:, profile:, tuple:, expected_fingerprint:, label:)
unless supplied.is_a?(Hash)
raise PromotionError, "#{label} certification evidence, timestamp, and status are required"
end
status = supplied["status"]
certified_at = supplied["certified_at"]
evidence = Array(supplied["evidence"])
unless status == CERTIFICATION_STATUS
raise PromotionError, "#{label} certification status must be #{CERTIFICATION_STATUS.inspect}"
end
validate_timestamp!(certified_at, "#{label} certification timestamp")
raise PromotionError, "#{label} certification requires at least one evidence file" if evidence.empty?
raise PromotionError, "#{label} certification evidence files must be unique" unless evidence.uniq == evidence
consumer_commit = tuple.fetch("consumer_commit")
normalized_evidence = evidence.map do |reference|
validate_evidence!(
reference,
consumer: consumer,
profile: profile,
consumer_commit: consumer_commit,
status: status,
certified_at: certified_at,
tuple_fingerprint: expected_fingerprint,
label: label
)
end
{
"status" => status,
"certified_at" => certified_at,
"tuple_sha256" => expected_fingerprint,
"evidence" => normalized_evidence
}
end
def validate_recorded_certification!(tuple, consumer:, profile:, expected_fingerprint:, label:)
certification = tuple["certification"]
unless certification.is_a?(Hash) && certification["tuple_sha256"] == expected_fingerprint
raise PromotionError, "#{label} tuple has invalid or stale certification metadata"
end
validate_supplied_certification!(
certification,
consumer: consumer,
profile: profile,
tuple: tuple,
expected_fingerprint: expected_fingerprint,
label: label
)
end
def validate_evidence!(
reference,
consumer:,
profile:,
consumer_commit:,
status:,
certified_at:,
tuple_fingerprint:,
label:
)
unless reference.is_a?(String) && !reference.empty?
raise PromotionError, "#{label} evidence references must be non-empty strings"
end
relative = Pathname.new(reference).cleanpath
if relative.absolute? || relative.each_filename.first != "evidence"
raise PromotionError, "#{label} evidence must be a repository-relative path under evidence/"
end
full_path = File.join(@root, relative.to_s)
evidence_root = File.realpath(File.join(@root, "evidence"))
real_path = File.realpath(full_path)
unless real_path.start_with?("#{evidence_root}#{File::SEPARATOR}")
raise PromotionError, "#{label} evidence resolves outside evidence/"
end
document = parse_json(File.read(real_path), relative.to_s)
unless document.is_a?(Hash) && document["schema_version"] == 1
raise PromotionError, "#{label} evidence #{relative} must use schema_version 1"
end
expected = {
"consumer" => consumer,
"profile" => profile,
"consumer_commit" => consumer_commit,
"status" => status,
"certified_at" => certified_at,
"tuple_sha256" => tuple_fingerprint
}
expected.each do |key, value|
next if document[key] == value
raise PromotionError,
"#{label} evidence #{relative} has #{key}=#{document[key].inspect}; expected #{value.inspect}"
end
relative.to_s
rescue Errno::ENOENT
raise PromotionError, "#{label} evidence does not exist: #{reference}"
end
def validate_tuple!(tuple, label)
raise PromotionError, "#{label} tuple must be an object" unless tuple.is_a?(Hash)
validate_full_commit!(tuple["consumer_commit"], "#{label} consumer commit")
validate_client!(tuple["opencode_ruby"], "#{label} opencode_ruby", required: true)
validate_client!(tuple["opencode_rails"], "#{label} opencode_rails", required: false)
validate_runtime!(tuple["runtime"], "#{label} runtime")
end
def validate_client!(client, label, required:)
if client.nil?
raise PromotionError, "#{label} is required" if required
return
end
unless client.is_a?(Hash) && client["version"].is_a?(String) && !client["version"].empty?
raise PromotionError, "#{label} must include a non-empty version"
end
validate_full_commit!(client["git_commit"], "#{label} git commit")
end
def validate_runtime!(runtime, label)
raise PromotionError, "#{label} must be an object" unless runtime.is_a?(Hash)
unless runtime["reported_version"].is_a?(String) && !runtime["reported_version"].empty?
raise PromotionError, "#{label} must include the reported OpenCode server version"
end
exact_execution_coordinate = false
runtime.each do |key, value|
if key == "docker_image_id" || key.end_with?("_image_id")
unless value.is_a?(String) && value.match?(DIGEST)
raise PromotionError, "#{label} #{key} must be an exact sha256 image ID"
end
exact_execution_coordinate = true if key == "docker_image_id"
elsif image_reference_key?(key)
unless value.is_a?(String) && value.match?(IMMUTABLE_IMAGE)
raise PromotionError, "#{label} #{key} must be an immutable image@sha256 digest, not a tag"
end
exact_execution_coordinate = true if %w[image registry_ref].include?(key)
elsif key == "source_commit"
validate_full_commit!(value, "#{label} source commit")
end
end
unless exact_execution_coordinate
raise PromotionError, "#{label} must include an immutable image, registry_ref, or docker_image_id"
end
end
def image_reference_key?(key)
EXACT_IMAGE_REFERENCE_KEYS.include?(key) ||
(key.end_with?("_image") && key != "tag_provenance") ||
(key.end_with?("_ref") && key != "tag_provenance")
end
def validate_full_commit!(value, label)
return if value.is_a?(String) && value.match?(FULL_COMMIT)
raise PromotionError, "#{label} must be a full 40-character lowercase Git commit"
end
def validate_timestamp!(value, label)
unless value.is_a?(String) && value.match?(UTC_TIMESTAMP)
raise PromotionError, "#{label} must be an explicit RFC 3339 UTC timestamp"
end
Time.iso8601(value)
rescue ArgumentError
raise PromotionError, "#{label} is not a valid timestamp"
end
def tuple_fingerprint(tuple, consumer:, profile:)
payload = deep_copy(tuple)
TUPLE_METADATA_KEYS.each { |key| payload.delete(key) }
envelope = {
"consumer" => consumer,
"profile" => profile,
"tuple" => payload
}
"sha256:#{Digest::SHA256.hexdigest(JSON.generate(canonicalize(envelope)))}"
end
def canonicalize(value)
case value
when Hash
value.keys.sort.to_h { |key| [key, canonicalize(value.fetch(key))] }
when Array
value.map { |item| canonicalize(item) }
else
value
end
end
def deep_copy(value)
JSON.parse(JSON.generate(value))
end
def parse_json(contents, label)
JSON.parse(contents)
rescue JSON::ParserError => e
raise PromotionError, "invalid JSON in #{label}: #{e.message}"
end
def all_consumers_certified?(manifest)
manifest.fetch("consumers").all? do |consumer, entry|
next false unless entry["candidate"].nil?
profile = fetch_profile!(entry, consumer)
%w[current previous].all? do |slot|
tuple = entry[slot]
next false unless tuple.is_a?(Hash) && tuple["status"] == "certified"
validate_tuple!(tuple, "#{consumer} #{slot}")
validate_recorded_certification!(
tuple,
consumer: consumer,
profile: profile,
expected_fingerprint: tuple_fingerprint(tuple, consumer: consumer, profile: profile),
label: "#{consumer} #{slot}"
)
true
rescue PromotionError
false
end
end
end
def with_current_manifest_lock
loop do
retry_with_new_inode = false
result = nil
File.open(@manifest_path, File::RDONLY) do |locked_file|
locked_file.flock(File::LOCK_EX)
unless File.identical?(locked_file, @manifest_path)
retry_with_new_inode = true
next
end
result = yield parse_json(locked_file.read, @manifest_path)
end
return result unless retry_with_new_inode
end
rescue Errno::ENOENT
raise PromotionError, "runtime tuple manifest does not exist: #{@manifest_path}"
end
def atomic_write(manifest)
directory = File.dirname(@manifest_path)
mode = File.stat(@manifest_path).mode & 0o777
payload = JSON.pretty_generate(manifest) + "\n"
Tempfile.create([".runtime-tuples-", ".tmp"], directory) do |temporary|
temporary.chmod(mode)
temporary.write(payload)
temporary.flush
temporary.fsync
temporary.close
File.rename(temporary.path, @manifest_path)
fsync_directory(directory)
end
end
def fsync_directory(directory)
File.open(directory, File::RDONLY, &:fsync)
rescue Errno::EINVAL, Errno::ENOTSUP
# Some filesystems do not support directory fsync; rename is still atomic.
end
end
end

104
scripts/promote_runtime_tuple.rb Executable file
View File

@@ -0,0 +1,104 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require "json"
require "optparse"
require_relative "../lib/opencode_compat/runtime_tuple_promoter"
root = File.expand_path("..", __dir__)
command = ARGV.shift
options = {
"evidence" => [],
"previous_evidence" => []
}
parser = OptionParser.new do |opts|
opts.banner = <<~USAGE
Usage:
ruby scripts/promote_runtime_tuple.rb fingerprint --consumer NAME --consumer-commit SHA
ruby scripts/promote_runtime_tuple.rb promote --consumer NAME --consumer-commit SHA \\
--status pass --certified-at TIMESTAMP --evidence evidence/FILE.json \\
[--previous-status pass --previous-certified-at TIMESTAMP \\
--previous-evidence evidence/FILE.json] [--dry-run]
USAGE
opts.on("--consumer NAME") { |value| options["consumer"] = value }
opts.on("--consumer-commit SHA") { |value| options["consumer_commit"] = value }
opts.on("--status STATUS") { |value| options["status"] = value }
opts.on("--certified-at TIMESTAMP") { |value| options["certified_at"] = value }
opts.on("--evidence PATH") { |value| options["evidence"] << value }
opts.on("--previous-status STATUS") { |value| options["previous_status"] = value }
opts.on("--previous-certified-at TIMESTAMP") { |value| options["previous_certified_at"] = value }
opts.on("--previous-evidence PATH") { |value| options["previous_evidence"] << value }
opts.on("--dry-run") { options["dry_run"] = true }
end
def required!(options, *keys)
missing = keys.reject { |key| options[key] && (!options[key].respond_to?(:empty?) || !options[key].empty?) }
raise OptionParser::MissingArgument, missing.join(", ") unless missing.empty?
end
begin
parser.parse!(ARGV)
raise OptionParser::InvalidArgument, "unexpected arguments: #{ARGV.join(' ')}" unless ARGV.empty?
promoter = OpenCodeCompat::RuntimeTuplePromoter.new(root: root)
case command
when "fingerprint"
required!(options, "consumer", "consumer_commit")
puts JSON.pretty_generate(
promoter.fingerprints(
consumer: options.fetch("consumer"),
consumer_commit: options.fetch("consumer_commit")
)
)
when "promote"
required!(options, "consumer", "consumer_commit", "status", "certified_at", "evidence")
previous_fields = %w[previous_status previous_certified_at previous_evidence]
supplied_previous_fields = previous_fields.select do |key|
value = options[key]
value && (!value.respond_to?(:empty?) || !value.empty?)
end
if supplied_previous_fields.any? && supplied_previous_fields.length != previous_fields.length
raise OptionParser::MissingArgument, (previous_fields - supplied_previous_fields).join(", ")
end
previous_certification = if supplied_previous_fields.empty?
nil
else
{
"status" => options.fetch("previous_status"),
"certified_at" => options.fetch("previous_certified_at"),
"evidence" => options.fetch("previous_evidence")
}
end
promoted = promoter.promote(
consumer: options.fetch("consumer"),
consumer_commit: options.fetch("consumer_commit"),
certification: {
"status" => options.fetch("status"),
"certified_at" => options.fetch("certified_at"),
"evidence" => options.fetch("evidence")
},
previous_certification: previous_certification,
dry_run: options.fetch("dry_run", false)
)
if options.fetch("dry_run", false)
puts JSON.pretty_generate(promoted)
else
current = promoted.fetch("consumers").fetch(options.fetch("consumer")).fetch("current")
puts JSON.pretty_generate(
"consumer" => options.fetch("consumer"),
"current_consumer_commit" => current.fetch("consumer_commit"),
"current_tuple_sha256" => current.dig("certification", "tuple_sha256"),
"migration_state" => promoted.fetch("migration_state")
)
end
else
raise OptionParser::InvalidArgument, "command must be fingerprint or promote"
end
rescue OpenCodeCompat::PromotionError, OptionParser::ParseError => e
warn "error: #{e.message}"
warn parser
exit 1
end

View File

@@ -11,7 +11,7 @@ class RepositoryTest < Minitest::Test
end
def test_every_json_document_parses
paths = Dir.glob(File.join(ROOT, "{fixtures,manifests,profiles}/**/*.json"))
paths = Dir.glob(File.join(ROOT, "{evidence,fixtures,manifests,profiles}/**/*.json"))
refute_empty paths
paths.each { |path| JSON.parse(File.read(path)) }
end
@@ -50,9 +50,21 @@ class RepositoryTest < Minitest::Test
return unless tuples.fetch("migration_state") == "certified"
tuples.fetch("consumers").each_value do |consumer|
refute_nil consumer["current"]
refute_nil consumer["previous"]
assert_equal "certified", consumer.fetch("current").fetch("status")
%w[current previous].each do |slot|
tuple = consumer.fetch(slot)
assert_equal "certified", tuple.fetch("status")
assert_equal "pass", tuple.fetch("certification").fetch("status")
assert_match(/\Asha256:[0-9a-f]{64}\z/, tuple.fetch("certification").fetch("tuple_sha256"))
refute_empty tuple.fetch("certification").fetch("evidence")
end
end
end
def test_runtime_tuple_profiles_exist
tuples = json("manifests/runtime-tuples.json")
tuples.fetch("consumers").each_value do |consumer|
profile = consumer.fetch("profile")
assert_path_exists File.join(ROOT, "profiles", "#{profile}.json")
end
end
@@ -60,4 +72,16 @@ class RepositoryTest < Minitest::Test
workflow = File.read(File.join(ROOT, ".github/workflows/watch-upstream.yml"))
refute_match(/\b(kamal|kubectl|helm|nomad|docker\s+service)\b/i, workflow)
end
def test_tuple_promotion_has_no_command_execution_or_deployment_client
paths = %w[
lib/opencode_compat/runtime_tuple_promoter.rb
scripts/promote_runtime_tuple.rb
]
implementation = paths.map { |path| File.read(File.join(ROOT, path)) }.join("\n")
refute_match(/\b(system|exec|spawn|popen|Open3)\b/, implementation)
refute_match(/`[^`]+`/, implementation)
refute_match(/\b(kamal|kubectl|helm|nomad|docker\s+service)\b/i, implementation)
end
end

View File

@@ -0,0 +1,309 @@
# frozen_string_literal: true
require "fileutils"
require "json"
require "minitest/autorun"
require "tmpdir"
require_relative "../lib/opencode_compat/runtime_tuple_promoter"
class RuntimeTuplePromoterTest < Minitest::Test
CONSUMER = "example"
CURRENT_COMMIT = "1" * 40
CANDIDATE_COMMIT = "2" * 40
RUBY_CURRENT = "3" * 40
RUBY_CANDIDATE = "4" * 40
RAILS_CURRENT = "5" * 40
RAILS_CANDIDATE = "6" * 40
CURRENT_IMAGE = "ghcr.io/anomalyco/opencode@sha256:#{'a' * 64}"
CANDIDATE_IMAGE = "ghcr.io/anomalyco/opencode@sha256:#{'b' * 64}"
CURRENT_TIME = "2026-07-17T12:00:00Z"
CANDIDATE_TIME = "2026-07-18T12:00:00Z"
def setup
@root = Dir.mktmpdir("opencode-compat-promotion")
FileUtils.mkdir_p(File.join(@root, "manifests"))
FileUtils.mkdir_p(File.join(@root, "evidence"))
FileUtils.mkdir_p(File.join(@root, "profiles"))
File.write(File.join(@root, "profiles", "rails-persisted-turn.json"), "{}\n")
write_manifest(valid_manifest)
@promoter = OpenCodeCompat::RuntimeTuplePromoter.new(root: @root)
end
def teardown
FileUtils.remove_entry(@root)
end
def test_promotion_atomically_moves_certified_tuples_and_clears_candidate
candidate, previous = write_matching_evidence
promoted = @promoter.promote(
consumer: CONSUMER,
consumer_commit: CANDIDATE_COMMIT,
certification: certification(CANDIDATE_TIME, candidate),
previous_certification: certification(CURRENT_TIME, previous)
)
consumer = promoted.fetch("consumers").fetch(CONSUMER)
assert_nil consumer["candidate"]
assert_equal CANDIDATE_COMMIT, consumer.dig("current", "consumer_commit")
assert_equal RUBY_CANDIDATE, consumer.dig("current", "opencode_ruby", "git_commit")
assert_equal "certified", consumer.dig("current", "status")
assert_equal candidate.fetch("tuple_sha256"), consumer.dig("current", "certification", "tuple_sha256")
assert_equal CURRENT_COMMIT, consumer.dig("previous", "consumer_commit")
assert_equal RUBY_CURRENT, consumer.dig("previous", "opencode_ruby", "git_commit")
assert_equal "certified", consumer.dig("previous", "status")
assert_equal previous.fetch("tuple_sha256"), consumer.dig("previous", "certification", "tuple_sha256")
assert_equal "certified", promoted.fetch("migration_state")
assert_equal promoted, JSON.parse(File.read(manifest_path))
end
def test_dry_run_returns_promotion_without_changing_manifest
candidate, previous = write_matching_evidence
before = File.binread(manifest_path)
promoted = @promoter.promote(
consumer: CONSUMER,
consumer_commit: CANDIDATE_COMMIT,
certification: certification(CANDIDATE_TIME, candidate),
previous_certification: certification(CURRENT_TIME, previous),
dry_run: true
)
assert_equal "certified", promoted.dig("consumers", CONSUMER, "current", "status")
assert_equal before, File.binread(manifest_path)
end
def test_rejects_missing_candidate_without_touching_manifest
manifest = valid_manifest
manifest.fetch("consumers").fetch(CONSUMER)["candidate"] = nil
write_manifest(manifest)
before = File.binread(manifest_path)
error = assert_raises(OpenCodeCompat::PromotionError) do
@promoter.promote(
consumer: CONSUMER,
consumer_commit: CANDIDATE_COMMIT,
certification: certification(CANDIDATE_TIME, {"path" => "evidence/missing.json"})
)
end
assert_match(/no candidate/, error.message)
assert_equal before, File.binread(manifest_path)
end
def test_rejects_mutable_candidate_image_even_with_an_exact_image_id
manifest = valid_manifest
runtime = manifest.dig("consumers", CONSUMER, "candidate", "runtime")
runtime["registry_ref"] = "ghcr.io/anomalyco/opencode:latest"
runtime["docker_image_id"] = "sha256:#{'c' * 64}"
write_manifest(manifest)
before = File.binread(manifest_path)
error = assert_raises(OpenCodeCompat::PromotionError) do
@promoter.fingerprints(consumer: CONSUMER, consumer_commit: CANDIDATE_COMMIT)
end
assert_match(/immutable image@sha256 digest/, error.message)
assert_equal before, File.binread(manifest_path)
end
def test_rejects_short_consumer_and_client_commits
assert_raises(OpenCodeCompat::PromotionError) do
@promoter.fingerprints(consumer: CONSUMER, consumer_commit: "abc123")
end
manifest = valid_manifest
manifest.dig("consumers", CONSUMER, "candidate", "opencode_ruby")["git_commit"] = "abc123"
write_manifest(manifest)
error = assert_raises(OpenCodeCompat::PromotionError) do
@promoter.fingerprints(consumer: CONSUMER, consumer_commit: CANDIDATE_COMMIT)
end
assert_match(/full 40-character/, error.message)
end
def test_rejects_missing_server_version_or_unknown_profile
manifest = valid_manifest
manifest.dig("consumers", CONSUMER, "candidate", "runtime").delete("reported_version")
write_manifest(manifest)
error = assert_raises(OpenCodeCompat::PromotionError) do
@promoter.fingerprints(consumer: CONSUMER, consumer_commit: CANDIDATE_COMMIT)
end
assert_match(/reported OpenCode server version/, error.message)
manifest = valid_manifest
manifest.dig("consumers", CONSUMER)["profile"] = "untracked-profile"
write_manifest(manifest)
error = assert_raises(OpenCodeCompat::PromotionError) do
@promoter.fingerprints(consumer: CONSUMER, consumer_commit: CANDIDATE_COMMIT)
end
assert_match(/profile does not exist/, error.message)
end
def test_rejects_missing_previous_certification_for_an_uncertified_baseline
candidate, = write_matching_evidence
before = File.binread(manifest_path)
error = assert_raises(OpenCodeCompat::PromotionError) do
@promoter.promote(
consumer: CONSUMER,
consumer_commit: CANDIDATE_COMMIT,
certification: certification(CANDIDATE_TIME, candidate)
)
end
assert_match(/current tuple is not certified/, error.message)
assert_equal before, File.binread(manifest_path)
end
def test_rejects_evidence_that_does_not_match_the_complete_tuple
candidate, previous = write_matching_evidence
manifest = JSON.parse(File.read(manifest_path))
manifest.dig("consumers", CONSUMER, "candidate", "runtime")["image"] =
"ghcr.io/anomalyco/opencode@sha256:#{'f' * 64}"
write_manifest(manifest)
before = File.binread(manifest_path)
error = assert_raises(OpenCodeCompat::PromotionError) do
@promoter.promote(
consumer: CONSUMER,
consumer_commit: CANDIDATE_COMMIT,
certification: certification(CANDIDATE_TIME, candidate),
previous_certification: certification(CURRENT_TIME, previous)
)
end
assert_match(/tuple_sha256/, error.message)
assert_equal before, File.binread(manifest_path)
end
def test_rejects_non_passing_or_implicit_certification_metadata
candidate, previous = write_matching_evidence
assert_raises(OpenCodeCompat::PromotionError) do
@promoter.promote(
consumer: CONSUMER,
consumer_commit: CANDIDATE_COMMIT,
certification: certification(CANDIDATE_TIME, candidate).merge("status" => "pending"),
previous_certification: certification(CURRENT_TIME, previous),
dry_run: true
)
end
assert_raises(OpenCodeCompat::PromotionError) do
@promoter.promote(
consumer: CONSUMER,
consumer_commit: CANDIDATE_COMMIT,
certification: certification("today", candidate),
previous_certification: certification(CURRENT_TIME, previous),
dry_run: true
)
end
end
def test_rejects_duplicate_or_unversioned_evidence
candidate, previous = write_matching_evidence
assert_raises(OpenCodeCompat::PromotionError) do
@promoter.promote(
consumer: CONSUMER,
consumer_commit: CANDIDATE_COMMIT,
certification: certification(CANDIDATE_TIME, candidate).merge(
"evidence" => [candidate.fetch("path"), candidate.fetch("path")]
),
previous_certification: certification(CURRENT_TIME, previous),
dry_run: true
)
end
path = File.join(@root, candidate.fetch("path"))
document = JSON.parse(File.read(path))
document.delete("schema_version")
File.write(path, JSON.pretty_generate(document) + "\n")
assert_raises(OpenCodeCompat::PromotionError) do
@promoter.promote(
consumer: CONSUMER,
consumer_commit: CANDIDATE_COMMIT,
certification: certification(CANDIDATE_TIME, candidate),
previous_certification: certification(CURRENT_TIME, previous),
dry_run: true
)
end
end
private
def valid_manifest
{
"schema_version" => 1,
"migration_state" => "candidate",
"consumers" => {
CONSUMER => {
"profile" => "rails-persisted-turn",
"current" => {
"status" => "observed-production",
"consumer_commit" => CURRENT_COMMIT,
"opencode_ruby" => {"version" => "0.0.1.alpha2", "git_commit" => RUBY_CURRENT},
"opencode_rails" => {"version" => "0.0.1.alpha2", "git_commit" => RAILS_CURRENT},
"runtime" => {"image" => CURRENT_IMAGE, "reported_version" => "1.16.1"}
},
"candidate" => {
"status" => "compatibility-certified",
"certified_at" => "2026-07-18T10:00:00Z",
"opencode_ruby" => {"version" => "0.0.1.alpha6", "git_commit" => RUBY_CANDIDATE},
"opencode_rails" => {"version" => "0.0.1.alpha6", "git_commit" => RAILS_CANDIDATE},
"runtime" => {"image" => CANDIDATE_IMAGE, "reported_version" => "1.18.3"}
},
"previous" => nil
}
}
}
end
def write_matching_evidence
fingerprints = @promoter.fingerprints(consumer: CONSUMER, consumer_commit: CANDIDATE_COMMIT)
candidate = write_evidence(
"candidate.json",
commit: CANDIDATE_COMMIT,
timestamp: CANDIDATE_TIME,
fingerprint: fingerprints.fetch("candidate_tuple_sha256")
)
previous = write_evidence(
"previous.json",
commit: CURRENT_COMMIT,
timestamp: CURRENT_TIME,
fingerprint: fingerprints.fetch("current_tuple_sha256")
)
[candidate, previous]
end
def write_evidence(name, commit:, timestamp:, fingerprint:)
path = File.join("evidence", name)
document = {
"schema_version" => 1,
"consumer" => CONSUMER,
"profile" => "rails-persisted-turn",
"consumer_commit" => commit,
"status" => "pass",
"certified_at" => timestamp,
"tuple_sha256" => fingerprint
}
File.write(File.join(@root, path), JSON.pretty_generate(document) + "\n")
{"path" => path, "tuple_sha256" => fingerprint}
end
def certification(timestamp, evidence)
{
"status" => "pass",
"certified_at" => timestamp,
"evidence" => [evidence.fetch("path")]
}
end
def manifest_path
File.join(@root, "manifests", "runtime-tuples.json")
end
def write_manifest(manifest)
File.write(manifest_path, JSON.pretty_generate(manifest) + "\n")
end
end