diff --git a/README.md b/README.md index 6b603dc..f7806f6 100644 --- a/README.md +++ b/README.md @@ -93,10 +93,12 @@ 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; -they do not certify alpha8. Ajent and Mushu custom-image rows remain pending -until their host canaries run against this exact pair. +`manifests/image-matrix.json` binds the certified shared-client matrix to both +exact, unpublished alpha8 commits. Each public row is explicitly scoped to the +shared Ruby/SSE contract; its separate `required_consumer_profiles` are not +implied by that result. The host rows bind the Ajent, Travelwolf, and Mushu +application canaries to their exact current alpha8 and previous alpha7 consumer +commits and durable evidence documents. The checked-in alpha8 local exact-image result is explicitly marked `not-certified` and `shared-client-contract-only`. It proves the Ruby/SSE probe diff --git a/docs/certification.md b/docs/certification.md index 02703b7..a0930f7 100644 --- a/docs/certification.md +++ b/docs/certification.md @@ -85,6 +85,13 @@ must explicitly contain values matching the promotion: } ``` +The promotion command stores each accepted document as a hash-bound reference, +`{"path":"evidence/...json","sha256":"sha256:..."}`. Later transitions +re-read the bytes, verify that digest, and cross-check any detailed consumer +tree, client, and runtime coordinates against the tuple. Editing an evidence +document therefore invalidates the certification until it is reviewed and +promoted again; a copied `tuple_sha256` alone is not sufficient. + Preview the exact manifest transition with `--dry-run`, then repeat without it to write the manifest: @@ -153,7 +160,8 @@ create and canary a distinct consumer rollback commit that preserves the same known-good client and exact runtime. Only after both immutable consumer commits have real passing evidence can the manifest honestly contain certified `current` and `previous` tuples. Until then, `promotion_readiness` remains -blocked and the candidate PR must not be treated as a deploy authorization. +non-certified (`bootstrap-current-only` or `candidate`) and the candidate PR +must not be treated as deploy authorization. Use the explicit degraded-bootstrap transition for the first passing rollback tuple. It requires passing evidence bound to the complete candidate fingerprint diff --git a/docs/maintenance.md b/docs/maintenance.md index 05186d0..1ebad26 100644 --- a/docs/maintenance.md +++ b/docs/maintenance.md @@ -29,6 +29,16 @@ Generated release snapshots, detached operational copies, editor locks, caches, and linked task worktrees are evidence or tooling. They are not additional consumers and must not be bulk-upgraded. +Direct consumers still need immutable inputs even though they do not use the +Ruby gems. A system service must execute a clean certified checkout or image, +not whichever files happen to be in a mutable development worktree. Node and +Bun plugins must be pinned through their package lock and tested against the +same OpenCode digest; a globally cached, unversioned plugin is not release +provenance. Generated OpenCode or MCP configuration needs both schema validation +and a candidate-runtime smoke test. Keep OpenCode auto-update disabled in every +certified lane so a process restart cannot silently change the server half of a +previously passing tuple. + ## Supported window Support the exact current and previous certified runtime tuples. Do not claim @@ -65,6 +75,19 @@ state must record the annotated tag object and peeled commit. Tags or `latest` may be kept as human-readable provenance only; execution coordinates use full Git commits and `image@sha256:...` references. +## Rollback + +Rollback restores the whole certified `previous` tuple. Do not roll back only +the gem, only the consumer, or only the runtime image: the wire contract is the +unit of compatibility. + +The deployment platform's immediately preceding application image is not +automatically the certified OpenCode rollback. Keep it as emergency service +provenance, but if it contains a client/runtime tuple known to fail the contract +it must not occupy `previous`. In that case rollback means deploying the exact +consumer commit and runtime coordinates recorded in the certified `previous` +tuple, even if that is different from the platform's one-click rollback target. + ## Custom images Record each provenance layer separately: @@ -85,6 +108,10 @@ the labels. Do not relabel an already certified image: that changes its digest. Runner upgrades are a separate compatibility surface from OpenCode upgrades. Workflows must install their required Ruby/toolchain explicitly, declare Bash for scripts that use Bash syntax, and avoid relying on ambient runner packages. +Browser suites must provision the reviewed Chrome/Selenium path explicitly; +an absent browser is a runner failure, not product evidence. Keep a system-test +suite serial when its harness shares an ephemeral server port instead of +mistaking parallel `EADDRINUSE` failures for OpenCode regressions. GitHub and Gitea do not implement every Actions feature identically. Keep the same tests on both forges, but use forge-specific execution where necessary: @@ -96,22 +123,36 @@ A runner-only workflow repair needs exact-head CI, not an application canary. A client, runtime image, event, persistence, or toolchain change needs the profile and consumer canaries described above. +For a frozen rollback snapshot that is no longer a merge candidate, provide a +default-branch audit workflow that accepts and checks out an explicit full SHA. +That lets current runner plumbing test the immutable old application tree +without adding a CI-only commit to the rollback coordinate or pretending a +known runner-workflow failure is an application incompatibility. + ## Expected breakages - Prompt submission before event subscription can miss a terminal event and hang a turn. +- An SSE parser that recognizes only `\n\n` can stall on CRLF, bare-CR, + byte-order-mark, comment, or multiline-data framing that is valid on the + wire. - Changes to terminal or message-part events can duplicate or lose final text. - Usage events can undercount multi-step requests if totals are overwritten. - Reconnect logic can replay a prompt and create duplicate model requests. +- A Bun/Node server close can wait forever on idle keep-alive sockets unless + idle connections are explicitly reaped after admissions stop; the resulting + orphan can exhaust a service cgroup's PID budget and block a safe upgrade. - Asset builds can fail when runtime configuration is evaluated without the image variables available only at deploy time. - A workflow can pass locally but fail under `sh` when it uses Bash arrays or `mapfile`. - Plugin hook names, provider schema, config/MCP schema, or CLI flags can break direct consumers even when the Ruby profile stays green. +- `Opencode::Turn` is still an alpha-stage Rails composition seam; constructor, + observer, persistence, or finalization changes can break hosts even when the + lower-level Ruby wire client remains compatible. - A custom fork can silently lose its required ordering or permission patch if only an opaque output digest is retained. When one of these changes, update the owning profile and consumer evidence. Do not weaken a strict invariant to make a new upstream release pass. - diff --git a/evidence/2026-07-20-ajent-rails-alpha7-rollback.json b/evidence/2026-07-20-ajent-rails-alpha7-rollback.json new file mode 100644 index 0000000..2d24fed --- /dev/null +++ b/evidence/2026-07-20-ajent-rails-alpha7-rollback.json @@ -0,0 +1,76 @@ +{ + "schema_version": 1, + "consumer": "ajent-rails", + "profile": "rails-persisted-turn", + "consumer_commit": "6caa0132e4b172777126ed537de99cb7e9758e0b", + "consumer_tree": "29e6cd35313a06cef6f9c1f40a36e3ee582fd035", + "status": "pass", + "certified_at": "2026-07-20T08:14:53Z", + "tuple_sha256": "sha256:ae48333ba453f442d14263a916418a384f576db34d09a503111fc28c340eb408", + "clients": { + "opencode_ruby": { + "version": "0.0.1.alpha7", + "commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798" + }, + "opencode_rails": { + "version": "0.0.1.alpha7", + "commit": "2a391ccad1d098e4bb51eb19b6ca52adaa79e5cb" + } + }, + "runtime": { + "application_image": "docker-registry:5000/ajent_rails@sha256:fe6fdc2325d304ddb276df7d35807c183e7e574876a0d76a6c6de6014619c76e", + "aigl_image": "docker-registry:5000/opencode-aigl@sha256:f4064cb8b7a0f680f076dba9c72ba8e63b1457227c9fad5aa1407f0e0aac12e7", + "blackline_image": "docker-registry:5000/opencode-blackline@sha256:7b4ea4d70ff5fd35e3b17c571865dd6bb489217c7c00942e8569132fe2f749c8", + "raven_image": "docker-registry:5000/opencode-raven@sha256:9fe37ee88d8b10165e17f5f2a5c47b2396256464e732b4ef49138fb92a1234e3", + "reported_version": "1.18.3", + "upstream_base": "ghcr.io/anomalyco/opencode@sha256:c2d5d6398df72aac85cb1bdc8f900c71a9b75a33fb7c0a76dc1484e4b126e41e" + }, + "consumer_verification": { + "test_runs": 4247, + "assertions": 15740, + "failures": 0, + "errors": 0, + "skips": 4, + "rubocop_files": 1430, + "status": "pass" + }, + "application_canary": { + "products": [ + { + "id": "aigl", + "agent": "travel-agent", + "model": "openai-gpt-5.5", + "final_text": "CANARY_OK", + "status": "pass" + }, + { + "id": "blackline", + "agent": "employment-lawyer", + "model": "moonshot-kimi-k2.5", + "final_text": "CANARY_OK", + "status": "pass" + }, + { + "id": "raven", + "agent": "raven-legal", + "model": "moonshot-kimi-k2.5", + "final_text": "CANARY_OK", + "status": "pass" + } + ], + "session_cleanup_status": "pass", + "status": "pass" + }, + "source_availability": { + "origin_ref": "refs/heads/opencode/opencode-alpha7-rollback-certified-20260720", + "backup_ref": "refs/heads/opencode/opencode-alpha7-rollback-certified-20260720", + "github_ref": "refs/heads/opencode/opencode-alpha7-rollback-certified-20260720", + "status": "pass" + }, + "rollback_operability": { + "source_archived_on_all_remotes": true, + "prebuilt_application_image_retained": true, + "application_rebuild_required": false, + "deployed": false + } +} diff --git a/evidence/2026-07-20-ajent-rails-alpha8-production.json b/evidence/2026-07-20-ajent-rails-alpha8-production.json new file mode 100644 index 0000000..16a8318 --- /dev/null +++ b/evidence/2026-07-20-ajent-rails-alpha8-production.json @@ -0,0 +1,80 @@ +{ + "schema_version": 1, + "consumer": "ajent-rails", + "profile": "rails-persisted-turn", + "consumer_commit": "2ec8b879c5a34bb683fa5aaf26b998041d0e1264", + "consumer_tree": "a1e044f3a4f5c5ac0510a1d998c4afb55286da5a", + "status": "pass", + "certified_at": "2026-07-20T08:15:17Z", + "tuple_sha256": "sha256:326a87cdb67061bdd36dfcee989ae149511a0c627e857488d59e0f2b3c796720", + "clients": { + "publication_state": "unpublished", + "opencode_ruby": { + "version": "0.0.1.alpha8", + "commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766" + }, + "opencode_rails": { + "version": "0.0.1.alpha8", + "commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f" + } + }, + "runtime": { + "application_image": "docker-registry:5000/ajent_rails@sha256:c7b2c6f3c3ffb6af62069c3142a2d76e606b0794077b81c5464add2266b7c350", + "aigl_image": "docker-registry:5000/opencode-aigl@sha256:f4064cb8b7a0f680f076dba9c72ba8e63b1457227c9fad5aa1407f0e0aac12e7", + "blackline_image": "docker-registry:5000/opencode-blackline@sha256:7b4ea4d70ff5fd35e3b17c571865dd6bb489217c7c00942e8569132fe2f749c8", + "raven_image": "docker-registry:5000/opencode-raven@sha256:9fe37ee88d8b10165e17f5f2a5c47b2396256464e732b4ef49138fb92a1234e3", + "reported_version": "1.18.3", + "upstream_base": "ghcr.io/anomalyco/opencode@sha256:c2d5d6398df72aac85cb1bdc8f900c71a9b75a33fb7c0a76dc1484e4b126e41e" + }, + "forge_ci": { + "pr_run_id": "2718", + "main_build_run_id": "2719", + "protected_main_run_id": "2720", + "deploy_job_id": "12065", + "completed_at": "2026-07-20T08:06:22Z", + "status": "pass" + }, + "application_canary": { + "checked_at": "2026-07-20T08:15:17Z", + "products": [ + { + "id": "aigl", + "agent": "travel-agent", + "model": "openai-gpt-5.5", + "final_text": "CANARY_OK", + "status": "pass" + }, + { + "id": "blackline", + "agent": "employment-lawyer", + "model": "moonshot-kimi-k2.5", + "final_text": "CANARY_OK", + "status": "pass" + }, + { + "id": "raven", + "agent": "raven-legal", + "model": "moonshot-kimi-k2.5", + "final_text": "CANARY_OK", + "status": "pass" + } + ], + "session_cleanup_status": "pass", + "status": "pass" + }, + "production": { + "checked_at": "2026-07-20T08:15:17Z", + "application_health_status": 200, + "blackline_health_status": 200, + "ajent_public_status": 308, + "voice_contract_status": "pass", + "scotus_contract_status": "pass", + "status": "pass" + }, + "source_availability": { + "origin_main": "2ec8b879c5a34bb683fa5aaf26b998041d0e1264", + "backup_main": "2ec8b879c5a34bb683fa5aaf26b998041d0e1264", + "github_main": "2ec8b879c5a34bb683fa5aaf26b998041d0e1264", + "status": "pass" + } +} diff --git a/evidence/2026-07-20-mushu-alpha7-rollback.json b/evidence/2026-07-20-mushu-alpha7-rollback.json new file mode 100644 index 0000000..dfc876e --- /dev/null +++ b/evidence/2026-07-20-mushu-alpha7-rollback.json @@ -0,0 +1,68 @@ +{ + "schema_version": 1, + "consumer": "mushu", + "profile": "ruby-rest-sse", + "consumer_commit": "af2e9b0ce7ccacf49500450595a67c765a9d617a", + "consumer_tree": "f43f48972ab444310508e9067831dfc6977f1a24", + "status": "pass", + "certified_at": "2026-07-20T06:39:46.921805Z", + "tuple_sha256": "sha256:8d7d3cb2d3e2d920c900a9c1ada365026872904588cff0b166903d1d6d32183f", + "clients": { + "opencode_ruby": { + "version": "0.0.1.alpha7", + "commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798" + }, + "opencode_rails": null + }, + "runtime": { + "registry_ref": "docker-registry:5000/mushu-opencode@sha256:ed6293ca87e6db1dc07870d3ed6da21960743e6e2e904533b3c566d334b58d6a", + "base_image": "docker-registry:5000/mushu-opencode-base@sha256:de8a6adeefb30ce1c474cde23fe173dfe059434a13df0a54d14c7473edb33e16", + "custom_opencode_source_commit": "99d6328b18ff8a340a928449a2aa8bc184a063f9", + "build_source_commit": null, + "reported_version": "0.0.0-permission-shortcuts-202606300721", + "provenance_strength": "one-time-tag-source-attestation" + }, + "source_availability": { + "origin_ref": "refs/heads/review/mushu-opencode-alpha7-consumer-final-20260719", + "backup_ref": "refs/heads/review/mushu-opencode-alpha7-consumer-final-20260719", + "status": "archived" + }, + "shared_contract": { + "review_input": "opencode-compat-mushu-final-head.json", + "review_input_path_at_capture": "/tmp/opencode-compat-mushu-final-head.json", + "review_input_sha256": "sha256:57bf4b4f3c65159de4476b530aa0bc9b6077f1eacc69f7ea404f02314cb9760c", + "status": "pass" + }, + "application_canary": { + "started_at": "2026-07-20T06:37:55Z", + "completed_at": "2026-07-20T06:39:46.921805Z", + "successful_turn_count": 13, + "turn_count": 13, + "successful_conversation_count": 6, + "conversation_count": 6, + "successful_mcp_context_count": 7, + "mcp_context_count": 7, + "database_sha256": "sha256:a567d2ff9a906c9b78831bb6fa43153af539f1e76ee63bf35961472f55651d74", + "log_sha256": "sha256:a35a83b003a1f082b8a721b3faad8b35cb71dc9d1ceaedfc3cec3b26916e85b9", + "clean_worktree": true, + "status": "pass" + }, + "raw_artifact_retention": "raw clean-worktree database and log artifacts are ephemeral; durable counts, hashes, and sanitized results are retained in this committed evidence document", + "forge_ci": { + "status": "not_run", + "run_ids": [], + "reason": "The frozen snapshot predates a runner-only empty-system-test repair. Earlier runs 2588 and 2590 tested different commits and are deliberately not attributed to this tuple." + }, + "rollback_operability": { + "source_archived_on_all_remotes": true, + "prebuilt_application_image_retained": false, + "application_rebuild_required": true, + "immediate_platform_rollback_is_certified_tuple": false, + "immediate_platform_rollback_commit": "82ca30b18e1f38b55e2bed91cbd46f1eaf4a5922", + "reason": "The retained one-click application rollback uses alpha2 and the old runtime; the certified compatibility rollback is this alpha7 source tuple." + }, + "limitations": [ + "The old runtime image metadata does not recover an exact Mushu overlay-build commit.", + "No exact-head forge run is claimed; the clean-worktree full application canary is the application evidence." + ] +} diff --git a/evidence/2026-07-20-mushu-alpha8-production.json b/evidence/2026-07-20-mushu-alpha8-production.json new file mode 100644 index 0000000..5eb5bec --- /dev/null +++ b/evidence/2026-07-20-mushu-alpha8-production.json @@ -0,0 +1,87 @@ +{ + "schema_version": 1, + "consumer": "mushu", + "profile": "ruby-rest-sse", + "consumer_commit": "3f6754caab3f66537a5834f44ee63c1f05ecb047", + "consumer_tree": "726c3878e71f7a4cf8c0b8a3fa8fc15382bbab45", + "status": "pass", + "certified_at": "2026-07-20T07:13:51Z", + "tuple_sha256": "sha256:804410a63cf2b6e4056d6dd9ce289ac72464f441c9785b6fc8dd50016263a1a8", + "clients": { + "publication_state": "unpublished", + "opencode_ruby": { + "version": "0.0.1.alpha8", + "commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766" + }, + "opencode_rails": null + }, + "runtime": { + "registry_ref": "docker-registry:5000/mushu-opencode@sha256:a16bf0c4cd17698eba02244f6ba0e429b2238b6455e1415ff69fb08965c863a7", + "base_image": "docker-registry:5000/mushu-opencode-base@sha256:de8a6adeefb30ce1c474cde23fe173dfe059434a13df0a54d14c7473edb33e16", + "custom_opencode_source_commit": "99d6328b18ff8a340a928449a2aa8bc184a063f9", + "build_source_commit": "521185613a864a16a76a21c7a60597ab3973bae9", + "reported_version": "0.0.0-permission-shortcuts-202606300721" + }, + "forge_ci": { + "pr": { + "run_id": "2700", + "job_ids": ["11903", "11904", "11905", "11906", "11907"], + "status": "pass", + "started_at": "2026-07-20T06:29:41Z", + "completed_at": "2026-07-20T06:41:14Z" + }, + "main": { + "run_id": "2705", + "job_ids": ["11934", "11935", "11936", "11937", "11938"], + "status": "pass", + "started_at": "2026-07-20T06:43:03Z", + "completed_at": "2026-07-20T06:54:58Z" + } + }, + "shared_contract": { + "review_input_path_at_capture": "/tmp/opencode-alpha8-mushu-permission-shortcuts.json", + "review_input_sha256": "sha256:6be88f0929815fa80839687e357473b232830aca4ccd1686f2a38ac9381088de", + "durable_ci_evidence": "evidence/2026-07-20-opencode-alpha8-shared-client-ci.json", + "status": "pass" + }, + "preproduction_canary": { + "successful_turn_count": 13, + "turn_count": 13, + "successful_conversation_count": 6, + "conversation_count": 6, + "successful_mcp_context_count": 2, + "mcp_context_count": 2, + "database_sha256": "sha256:7fbe32ff9621196a2504aeba5eca78cb0c9c670edd7a1168b1f81f667872e2b6", + "log_path_at_capture": "tmp/alpha8-canary-rails.log", + "log_sha256": "sha256:51b698fcc8b7bce68e79cbae10997e19eb47a88985cbe24871d10740dda26225", + "status": "pass" + }, + "raw_artifact_retention": "raw clean-worktree database and log artifacts are ephemeral; durable counts, hashes, and sanitized results are retained in this committed evidence document", + "application_canary": { + "started_at": "2026-07-20T07:03:17Z", + "completed_at": "2026-07-20T07:04:51.376351Z", + "successful_turn_count": 13, + "turn_count": 13, + "successful_conversation_count": 6, + "conversation_count": 6, + "successful_mcp_context_count": 1, + "mcp_context_count": 1, + "status": "pass" + }, + "production": { + "checked_at": "2026-07-20T07:13:20Z", + "application_tag_provenance": "docker-registry:5000/mushu:3f6754caab3f66537a5834f44ee63c1f05ecb047", + "application_image": "docker-registry:5000/mushu@sha256:30f204184520d504c7c384c7f6e503fa3547217aaaef2f1d0d65884533bdeacd", + "web_health_status": 200, + "status_health_status": 200, + "runtime_readiness": "ready", + "containers": ["web", "calendar", "telegram", "opencode"], + "deployment_mode": "manual", + "status": "pass" + }, + "rollback_operability": { + "certified_previous_consumer_commit": "af2e9b0ce7ccacf49500450595a67c765a9d617a", + "immediate_platform_rollback_is_certified_tuple": false, + "immediate_platform_rollback_commit": "82ca30b18e1f38b55e2bed91cbd46f1eaf4a5922" + } +} diff --git a/evidence/2026-07-20-opencode-alpha8-shared-client-ci.json b/evidence/2026-07-20-opencode-alpha8-shared-client-ci.json new file mode 100644 index 0000000..baeb9f6 --- /dev/null +++ b/evidence/2026-07-20-opencode-alpha8-shared-client-ci.json @@ -0,0 +1,107 @@ +{ + "schema_version": 1, + "kind": "shared-client-and-lockstep-certification", + "status": "pass", + "certification_scope": "shared-ruby-rest-sse-and-rails-lockstep", + "publication_state": "unpublished", + "clients": { + "opencode_ruby": { + "version": "0.0.1.alpha8", + "commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766" + }, + "opencode_rails": { + "version": "0.0.1.alpha8", + "commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f", + "runtime_opencode_ruby_commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766" + } + }, + "github_workflow": { + "run_id": "29725021192", + "run_attempt": "1", + "head_sha": "328fa39c557d598ad09b270ad1ceab19901031cd", + "repository": "ajaynomics/opencode-compat", + "run_url": "https://github.com/ajaynomics/opencode-compat/actions/runs/29725021192", + "completed_at": "2026-07-20T07:34:59Z", + "status": "pass", + "artifact_retention_days": 30 + }, + "gitea_workflow": { + "run_id": "2717", + "head_sha": "328fa39c557d598ad09b270ad1ceab19901031cd", + "repository": "ajaynomics/opencode-compat", + "run_url": "https://gitea.ajay.to/ajaynomics/opencode-compat/actions/runs/2717", + "completed_at": "2026-07-20T07:36:06Z", + "status": "pass", + "artifact_evidence_claimed": false + }, + "fixture_contract": { + "checked_at": "2026-07-20T07:34:34Z", + "fixture_count": 7, + "status": "pass" + }, + "rails_lockstep": [ + { + "ruby_runtime_version": "3.2.11", + "status": "pass" + }, + { + "ruby_runtime_version": "3.3.12", + "status": "pass" + }, + { + "ruby_runtime_version": "3.4.10", + "status": "pass" + }, + { + "ruby_runtime_version": "4.0.6", + "status": "pass" + } + ], + "exact_image_contracts": [ + { + "id": "upstream-1.16.1", + "checked_at": "2026-07-20T07:34:50Z", + "image": "ghcr.io/anomalyco/opencode@sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a", + "reported_version": "1.16.1", + "status": "pass", + "expected_text": "compat-ok", + "full_text": "compat-ok", + "authoritative_assistant_message_count": 1, + "llm_request_count": 1, + "executed_profiles": ["ruby-rest-sse"], + "required_consumer_profiles": ["rails-persisted-turn"] + }, + { + "id": "upstream-1.17.18", + "checked_at": "2026-07-20T07:34:55Z", + "image": "ghcr.io/anomalyco/opencode@sha256:bf9d0e84b7cedef436a8f57db4d48767cd5d8fc6604f666335c1cc916b199a97", + "reported_version": "1.17.18", + "status": "pass", + "expected_text": "compat-ok", + "full_text": "compat-ok", + "authoritative_assistant_message_count": 1, + "llm_request_count": 1, + "executed_profiles": ["ruby-rest-sse"], + "required_consumer_profiles": ["plugin-ledger"] + }, + { + "id": "upstream-1.18.3", + "checked_at": "2026-07-20T07:34:48Z", + "image": "ghcr.io/anomalyco/opencode@sha256:c2d5d6398df72aac85cb1bdc8f900c71a9b75a33fb7c0a76dc1484e4b126e41e", + "reported_version": "1.18.3", + "status": "pass", + "expected_text": "compat-ok", + "full_text": "compat-ok", + "authoritative_assistant_message_count": 1, + "llm_request_count": 1, + "executed_profiles": ["ruby-rest-sse"], + "required_consumer_profiles": ["rails-persisted-turn", "plugin-ledger"] + } + ], + "certified_at": "2026-07-20T07:36:06Z", + "limitations": [ + "The exact-image contracts execute opencode-ruby, not opencode-rails.", + "Rails persistence and each consumer application profile require separate passing evidence.", + "The exact client commits are certified candidates but remain unpublished until RubyGems trusted publishers are configured and registry publication is verified." + ] +} diff --git a/evidence/2026-07-20-travelwolf-alpha7-rollback.json b/evidence/2026-07-20-travelwolf-alpha7-rollback.json new file mode 100644 index 0000000..dc75e7d --- /dev/null +++ b/evidence/2026-07-20-travelwolf-alpha7-rollback.json @@ -0,0 +1,73 @@ +{ + "schema_version": 1, + "consumer": "travelwolf", + "profile": "rails-persisted-turn", + "consumer_commit": "531011defe4acc45a15ffe44a8e9da9cb610aaac", + "consumer_tree": "d0e47b2d90ace87c6cc9c4b94b2f22987441c1f6", + "status": "pass", + "certified_at": "2026-07-20T06:31:42Z", + "tuple_sha256": "sha256:20ecec3d2c3743efcd8575e71ec46dd7b86c2865e472511d16a96d57ddb8b40c", + "clients": { + "opencode_ruby": { + "version": "0.0.1.alpha7", + "commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798" + }, + "opencode_rails": { + "version": "0.0.1.alpha7", + "commit": "2a391ccad1d098e4bb51eb19b6ca52adaa79e5cb" + } + }, + "runtime": { + "image": "ghcr.io/anomalyco/opencode@sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a", + "reported_version": "1.16.1" + }, + "forge_ci": { + "pr": { + "run_id": "2696", + "job_id": "11898", + "status": "pass", + "started_at": "2026-07-20T06:20:57Z", + "completed_at": "2026-07-20T06:23:32Z" + }, + "main": { + "run_id": "2698", + "job_id": "11900", + "status": "pass", + "started_at": "2026-07-20T06:23:58Z", + "completed_at": "2026-07-20T06:25:49Z" + } + }, + "shared_contract": { + "artifact": "opencode-alpha7-shared-contract-531011de.json", + "source_path_at_capture": "/data/projects/travelwolf/.worktrees/opencode-alpha8-candidate-20260720/tmp/opencode-alpha7-shared-contract-531011de.json", + "artifact_sha256": "sha256:12cef4d8b713f32289dc98ce3a0a3ef2094a54205a160d3a5b4bcfc81f2805c9", + "expected_text": "compat-ok", + "full_text": "compat-ok", + "authoritative_assistant_message_count": 1, + "llm_request_count": 1, + "status": "pass" + }, + "application_canary": { + "artifact": "opencode-alpha7-application-531011de.json", + "source_path_at_capture": "/data/projects/travelwolf/.worktrees/opencode-alpha8-candidate-20260720/tmp/opencode-alpha7-application-531011de.json", + "artifact_sha256": "sha256:5f5693bba27a16bc703c3211827a09171c77d5f9ef1acf6def1917df43a6cebc", + "checked_at": "2026-07-20T06:22:36.958Z", + "final_text": "TRAVELWOLF_ALPHA7_CANARY_OK", + "application_prompt_count": 1, + "user_turn_count": 1, + "authoritative_assistant_turn_count": 1, + "child_turn_count": 0, + "model_request_count_observed": false, + "cleanup_status": "pass", + "status": "pass" + }, + "raw_artifact_retention": "ephemeral clean-worktree files; durable sanitized results and hashes are retained in this committed evidence document", + "rollback_operability": { + "source_archived_on_all_remotes": true, + "prebuilt_application_image_retained": false, + "application_rebuild_required": true, + "immediate_platform_rollback_is_certified_tuple": false, + "immediate_platform_rollback_commit": "8494c52d11bf2995c378398e977d82d06a09e046", + "reason": "The retained one-click application rollback uses alpha6 clients; the certified compatibility rollback is this alpha7 source tuple." + } +} diff --git a/evidence/2026-07-20-travelwolf-alpha8-production.json b/evidence/2026-07-20-travelwolf-alpha8-production.json new file mode 100644 index 0000000..246e076 --- /dev/null +++ b/evidence/2026-07-20-travelwolf-alpha8-production.json @@ -0,0 +1,84 @@ +{ + "schema_version": 1, + "consumer": "travelwolf", + "profile": "rails-persisted-turn", + "consumer_commit": "02aed70eb4de201c6510e3be583699f8eb984ce7", + "consumer_tree": "f436a7248d90e5ae9e84c45f264acd913a4162aa", + "status": "pass", + "certified_at": "2026-07-20T06:51:48Z", + "tuple_sha256": "sha256:0321c4246bed6ab62587924a83204a9e2ca7517b1dec5e4aef35dcac400fcc8e", + "clients": { + "publication_state": "unpublished", + "opencode_ruby": { + "version": "0.0.1.alpha8", + "commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766" + }, + "opencode_rails": { + "version": "0.0.1.alpha8", + "commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f" + } + }, + "runtime": { + "image": "ghcr.io/anomalyco/opencode@sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a", + "reported_version": "1.16.1" + }, + "forge_ci": { + "pr": { + "run_id": "2702", + "job_id": "11920", + "status": "pass", + "started_at": "2026-07-20T06:33:58Z", + "completed_at": "2026-07-20T06:35:29Z" + }, + "main": { + "run_id": "2704", + "job_id": "11933", + "status": "pass", + "started_at": "2026-07-20T06:36:57Z", + "completed_at": "2026-07-20T06:38:11Z" + } + }, + "shared_contract": { + "review_input_path_at_capture": "/data/projects/travelwolf/.worktrees/opencode-alpha8-candidate-20260720/tmp/opencode-alpha8-shared-contract-02aed70e.json", + "review_input_sha256": "sha256:f63edbafba8fb7646d1c88e33472636af56770110d920006211b4ff3f115b977", + "durable_ci_evidence": "evidence/2026-07-20-opencode-alpha8-shared-client-ci.json", + "expected_text": "compat-ok", + "full_text": "compat-ok", + "authoritative_assistant_message_count": 1, + "llm_request_count": 1, + "status": "pass" + }, + "application_canary": { + "review_input_path_at_capture": "/data/projects/travelwolf/.worktrees/opencode-alpha8-candidate-20260720/tmp/opencode-alpha8-application-02aed70e.json", + "review_input_sha256": "sha256:6f4dfe14a74bb84c13cf566302222f737603d2fbafa7231c7bb332a2567bca24", + "offline_input_path_at_capture": "/data/projects/travelwolf/.worktrees/opencode-alpha8-candidate-20260720/tmp/opencode-alpha8-offline-contract-02aed70e.json", + "offline_input_sha256": "sha256:4e8157fbb44d6fd0346bea79aee1271e400cd7fde57bc8a45afa77250134029c", + "checked_at": "2026-07-20T06:31:13.506Z", + "final_text": "TRAVELWOLF_ALPHA8_CANARY_OK", + "application_prompt_count": 1, + "user_turn_count": 1, + "authoritative_assistant_turn_count": 1, + "child_turn_count": 0, + "model_request_count_observed": false, + "cleanup_status": "pass", + "status": "pass" + }, + "raw_artifact_retention": "ephemeral clean-worktree files; durable sanitized results and hashes are retained in this committed evidence document", + "production": { + "checked_at": "2026-07-20T06:51:48Z", + "application_tag_provenance": "127.0.0.1:5000/travelwolf:02aed70eb4de201c6510e3be583699f8eb984ce7", + "application_image_id": "sha256:031371e20f34e1014712ddc668b4e182483664bb70f116b683c0ec83d6668338", + "application_image_created_at": "2026-07-20T06:42:55.929825054Z", + "internal_health_status": 200, + "authenticated_public_health_status": 200, + "container_status": "running", + "deployment_mode": "manual-isolated-kamal", + "status": "pass" + }, + "rollback_operability": { + "certified_previous_consumer_commit": "531011defe4acc45a15ffe44a8e9da9cb610aaac", + "immediate_platform_rollback_is_certified_tuple": false, + "immediate_platform_rollback_commit": "8494c52d11bf2995c378398e977d82d06a09e046", + "immediate_platform_rollback_image_id": "sha256:4291b256b37eeb694435093655db17154c837e53335b8d0eb8f1b07a744e856e" + } +} diff --git a/lib/opencode_compat/runtime_tuple_promoter.rb b/lib/opencode_compat/runtime_tuple_promoter.rb index e05cb56..d5ae164 100644 --- a/lib/opencode_compat/runtime_tuple_promoter.rb +++ b/lib/opencode_compat/runtime_tuple_promoter.rb @@ -32,6 +32,26 @@ module OpenCodeCompat source_image upstream_base ].freeze + EVIDENCE_REFERENCE_KEYS = %w[path sha256].freeze + NULLABLE_COMMIT_KEYS = %w[build_source_commit].freeze + EVIDENCE_RUNTIME_KEY_MAP = { + "custom_opencode_source_commit" => "source_commit" + }.freeze + EVIDENCE_RUNTIME_COORDINATE_KEYS = %w[ + application_image + application_image_id + base_image + build_source_commit + docker_image_id + image + provenance_strength + registry_ref + reported_version + source_commit + source_image + tag_provenance + upstream_base + ].freeze def initialize(root:, manifest_path: File.join(root, "manifests/runtime-tuples.json")) @root = File.realpath(root) @@ -317,21 +337,23 @@ module OpenCodeCompat 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, + tuple: tuple, status: status, certified_at: certified_at, tuple_fingerprint: expected_fingerprint, label: label ) end + evidence_paths = normalized_evidence.map { |reference| reference.fetch("path") } + unless evidence_paths.uniq == evidence_paths + raise PromotionError, "#{label} certification evidence files must be unique" + end { "status" => status, @@ -347,7 +369,13 @@ module OpenCodeCompat raise PromotionError, "#{label} tuple has invalid or stale certification metadata" end - validate_supplied_certification!( + references = Array(certification["evidence"]) + unless references.all? { |reference| structured_evidence_reference?(reference) } + raise PromotionError, + "#{label} recorded certification evidence must use hash-bound {path, sha256} references" + end + + validated = validate_supplied_certification!( certification, consumer: consumer, profile: profile, @@ -355,23 +383,24 @@ module OpenCodeCompat expected_fingerprint: expected_fingerprint, label: label ) + unless certification["evidence"] == validated["evidence"] + raise PromotionError, "#{label} recorded certification evidence references are not canonical" + end end def validate_evidence!( reference, consumer:, profile:, - consumer_commit:, + tuple:, status:, certified_at:, tuple_fingerprint:, label: ) - unless reference.is_a?(String) && !reference.empty? - raise PromotionError, "#{label} evidence references must be non-empty strings" - end + reference_path, expected_sha256 = evidence_reference_parts!(reference, label) - relative = Pathname.new(reference).cleanpath + relative = Pathname.new(reference_path).cleanpath if relative.absolute? || relative.each_filename.first != "evidence" raise PromotionError, "#{label} evidence must be a repository-relative path under evidence/" end @@ -383,14 +412,23 @@ module OpenCodeCompat raise PromotionError, "#{label} evidence resolves outside evidence/" end - document = parse_json(File.read(real_path), relative.to_s) + contents = File.binread(real_path) + actual_sha256 = "sha256:#{Digest::SHA256.hexdigest(contents)}" + if expected_sha256 && expected_sha256 != actual_sha256 + raise PromotionError, + "#{label} evidence #{relative} has sha256=#{actual_sha256.inspect}; " \ + "expected #{expected_sha256.inspect}" + end + + document = parse_json(contents, relative.to_s) unless document.is_a?(Hash) && document["schema_version"] == 1 raise PromotionError, "#{label} evidence #{relative} must use schema_version 1" end + validate_immutable_fields!(document, "#{label} evidence #{relative}") expected = { "consumer" => consumer, "profile" => profile, - "consumer_commit" => consumer_commit, + "consumer_commit" => tuple.fetch("consumer_commit"), "status" => status, "certified_at" => certified_at, "tuple_sha256" => tuple_fingerprint @@ -402,14 +440,20 @@ module OpenCodeCompat "#{label} evidence #{relative} has #{key}=#{document[key].inspect}; expected #{value.inspect}" end - relative.to_s + validate_evidence_tuple_details!(document, tuple, "#{label} evidence #{relative}") + + { + "path" => relative.to_s, + "sha256" => actual_sha256 + } rescue Errno::ENOENT - raise PromotionError, "#{label} evidence does not exist: #{reference}" + raise PromotionError, "#{label} evidence does not exist: #{reference_path || reference}" end def validate_tuple!(tuple, label) raise PromotionError, "#{label} tuple must be an object" unless tuple.is_a?(Hash) + validate_immutable_fields!(tuple, label) 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) @@ -447,8 +491,10 @@ module OpenCodeCompat 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") + elsif key.end_with?("_commit") + next if value.nil? && NULLABLE_COMMIT_KEYS.include?(key) + + validate_full_commit!(value, "#{label} #{key.tr('_', ' ')}") end end @@ -463,6 +509,154 @@ module OpenCodeCompat (key.end_with?("_ref") && key != "tag_provenance") end + def structured_evidence_reference?(reference) + reference.is_a?(Hash) && reference.keys.sort == EVIDENCE_REFERENCE_KEYS + end + + def evidence_reference_parts!(reference, label) + if reference.is_a?(String) + unless !reference.empty? + raise PromotionError, "#{label} evidence references must contain a non-empty path" + end + + return [reference, nil] + end + + unless structured_evidence_reference?(reference) + raise PromotionError, + "#{label} evidence references must be a path string or an object containing only path and sha256" + end + + path = reference["path"] + sha256 = reference["sha256"] + unless path.is_a?(String) && !path.empty? + raise PromotionError, "#{label} evidence references must contain a non-empty path" + end + unless sha256.is_a?(String) && sha256.match?(DIGEST) + raise PromotionError, "#{label} evidence reference sha256 must be an exact sha256 digest" + end + + [path, sha256] + end + + def validate_evidence_tuple_details!(document, tuple, label) + if document.key?("consumer_tree") + expected_tree = tuple["consumer_tree"] + unless expected_tree && document["consumer_tree"] == expected_tree + raise PromotionError, + "#{label} has consumer_tree=#{document['consumer_tree'].inspect}; " \ + "expected #{expected_tree.inspect}" + end + end + + validate_evidence_clients!(document["clients"], tuple, label) if document.key?("clients") + validate_evidence_runtime!(document["runtime"], tuple.fetch("runtime"), label) if document.key?("runtime") + end + + def validate_evidence_clients!(clients, tuple, label) + unless clients.is_a?(Hash) + raise PromotionError, "#{label} clients must be an object" + end + + %w[opencode_ruby opencode_rails].each do |client_name| + next unless clients.key?(client_name) + + evidence_client = clients[client_name] + tuple_client = tuple[client_name] + if evidence_client.nil? + unless tuple_client.nil? + raise PromotionError, "#{label} #{client_name} is null but the tuple declares a client" + end + next + end + + unless evidence_client.is_a?(Hash) && tuple_client.is_a?(Hash) + raise PromotionError, "#{label} #{client_name} must match the tuple client" + end + unless evidence_client["version"].is_a?(String) && !evidence_client["version"].empty? + raise PromotionError, "#{label} #{client_name} must include a non-empty version" + end + + commits = %w[commit git_commit].filter_map do |key| + evidence_client[key] if evidence_client.key?(key) + end + if commits.empty? + raise PromotionError, "#{label} #{client_name} must include commit or git_commit" + end + + compare_evidence_coordinate!( + evidence_client["version"], + tuple_client["version"], + "#{label} #{client_name}.version" + ) + commits.each do |commit| + compare_evidence_coordinate!(commit, tuple_client["git_commit"], "#{label} #{client_name}.commit") + end + end + end + + def validate_evidence_runtime!(runtime, tuple_runtime, label) + unless runtime.is_a?(Hash) + raise PromotionError, "#{label} runtime must be an object" + end + + runtime.each do |key, value| + tuple_key = EVIDENCE_RUNTIME_KEY_MAP.fetch(key, key) + next unless EVIDENCE_RUNTIME_COORDINATE_KEYS.include?(tuple_key) + + unless tuple_runtime.key?(tuple_key) + raise PromotionError, "#{label} runtime.#{key} is not present in the certified tuple" + end + + compare_evidence_coordinate!(value, tuple_runtime[tuple_key], "#{label} runtime.#{key}") + end + end + + def compare_evidence_coordinate!(actual, expected, label) + return if actual == expected + + raise PromotionError, "#{label}=#{actual.inspect}; expected #{expected.inspect}" + end + + def validate_immutable_fields!(value, label, path = []) + case value + when Hash + value.each do |key, child| + field_path = path + [key] + field_label = "#{label} #{field_path.join('.')}" + if key == "commit" || key.end_with?("_commit") || key == "head_sha" || key.end_with?("_head_sha") + unless child.nil? && NULLABLE_COMMIT_KEYS.include?(key) + validate_full_commit!(child, field_label) + end + elsif key == "tree" || key.end_with?("_tree") + validate_full_oid!(child, field_label) + elsif key == "annotated_tag_object" || key.end_with?("_tag_object") + validate_full_oid!(child, field_label) + elsif digest_field?(key) + unless child.is_a?(String) && child.match?(DIGEST) + raise PromotionError, "#{field_label} must be an exact sha256 digest" + end + end + + validate_immutable_fields!(child, label, field_path) + end + when Array + value.each_with_index { |child, index| validate_immutable_fields!(child, label, path + [index]) } + end + end + + def digest_field?(key) + key == "sha256" || key.end_with?("_sha256") || + key == "digest" || key.end_with?("_digest") || + key == "docker_image_id" || key.end_with?("_image_id") + end + + def validate_full_oid!(value, label) + return if value.is_a?(String) && value.match?(FULL_COMMIT) + + raise PromotionError, "#{label} must be a full 40-character lowercase Git object ID" + end + def validate_full_commit!(value, label) return if value.is_a?(String) && value.match?(FULL_COMMIT) diff --git a/manifests/image-matrix.json b/manifests/image-matrix.json index df35f80..087c357 100644 --- a/manifests/image-matrix.json +++ b/manifests/image-matrix.json @@ -5,7 +5,9 @@ "publication_state": "unpublished", "opencode_ruby_commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766", "opencode_rails_commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f", - "certification_status": "pending" + "certification_status": "certified", + "certified_at": "2026-07-20T07:36:06Z", + "evidence": "evidence/2026-07-20-opencode-alpha8-shared-client-ci.json" }, "public_ci": [ { @@ -16,7 +18,20 @@ "consumers": ["travelwolf"], "profiles": ["ruby-rest-sse"], "required_consumer_profiles": ["rails-persisted-turn"], - "certification_status": "pending", + "certification_scope": "shared-client-contract-only", + "certification_status": "certified", + "current_certification": { + "status": "certified", + "client_commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766", + "rails_commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f", + "checked_at": "2026-07-20T07:34:50Z", + "expected_text": "compat-ok", + "full_text": "compat-ok", + "llm_request_count": 1, + "workflow_run_id": "29725021192", + "workflow_head_sha": "328fa39c557d598ad09b270ad1ceab19901031cd", + "evidence": "evidence/2026-07-20-opencode-alpha8-shared-client-ci.json" + }, "previous_certification": { "status": "certified", "client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", @@ -34,7 +49,20 @@ "consumers": ["opencode-ajent"], "profiles": ["ruby-rest-sse"], "required_consumer_profiles": ["plugin-ledger"], - "certification_status": "pending", + "certification_scope": "shared-client-contract-only", + "certification_status": "certified", + "current_certification": { + "status": "certified", + "client_commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766", + "rails_commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f", + "checked_at": "2026-07-20T07:34:55Z", + "expected_text": "compat-ok", + "full_text": "compat-ok", + "llm_request_count": 1, + "workflow_run_id": "29725021192", + "workflow_head_sha": "328fa39c557d598ad09b270ad1ceab19901031cd", + "evidence": "evidence/2026-07-20-opencode-alpha8-shared-client-ci.json" + }, "previous_certification": { "status": "certified", "client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", @@ -52,7 +80,20 @@ "consumers": ["ajent-rails"], "profiles": ["ruby-rest-sse"], "required_consumer_profiles": ["rails-persisted-turn", "plugin-ledger"], - "certification_status": "pending", + "certification_scope": "shared-client-contract-only", + "certification_status": "certified", + "current_certification": { + "status": "certified", + "client_commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766", + "rails_commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f", + "checked_at": "2026-07-20T07:34:48Z", + "expected_text": "compat-ok", + "full_text": "compat-ok", + "llm_request_count": 1, + "workflow_run_id": "29725021192", + "workflow_head_sha": "328fa39c557d598ad09b270ad1ceab19901031cd", + "evidence": "evidence/2026-07-20-opencode-alpha8-shared-client-ci.json" + }, "previous_certification": { "status": "certified", "client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", @@ -65,42 +106,87 @@ ], "host_canary": [ { - "id": "ajent-blackline-alpha7-canary", + "id": "ajent-rails-alpha8-production", "consumer": "ajent-rails", - "consumer_commit": "96552893baccd5ccba1592b2585edfef1299b4f9", - "tag_provenance": "ajent-opencode-blackline-canary:96552893", - "docker_image_id": "sha256:239e3dc6cd2958251cda636fb20bd7cd7c46f596bd4c61cb81863ebc49d824e8", - "source_commit": "96552893baccd5ccba1592b2585edfef1299b4f9", + "consumer_commit": "2ec8b879c5a34bb683fa5aaf26b998041d0e1264", + "application_image": "docker-registry:5000/ajent_rails@sha256:c7b2c6f3c3ffb6af62069c3142a2d76e606b0794077b81c5464add2266b7c350", + "product_images": { + "aigl": "docker-registry:5000/opencode-aigl@sha256:f4064cb8b7a0f680f076dba9c72ba8e63b1457227c9fad5aa1407f0e0aac12e7", + "blackline": "docker-registry:5000/opencode-blackline@sha256:7b4ea4d70ff5fd35e3b17c571865dd6bb489217c7c00942e8569132fe2f749c8", + "raven": "docker-registry:5000/opencode-raven@sha256:9fe37ee88d8b10165e17f5f2a5c47b2396256464e732b4ef49138fb92a1234e3" + }, "reported_version": "1.18.3", "upstream_base": "ghcr.io/anomalyco/opencode@sha256:c2d5d6398df72aac85cb1bdc8f900c71a9b75a33fb7c0a76dc1484e4b126e41e", - "profiles": ["rails-persisted-turn"], - "certification_status": "pending", + "profiles": ["rails-persisted-turn", "plugin-ledger"], + "certification_status": "certified", + "current_certification": { + "status": "certified", + "client_commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766", + "rails_commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f", + "consumer_commit": "2ec8b879c5a34bb683fa5aaf26b998041d0e1264", + "certified_at": "2026-07-20T08:15:17Z", + "evidence": "evidence/2026-07-20-ajent-rails-alpha8-production.json" + }, "previous_certification": { - "status": "candidate-certified", + "status": "certified", "client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", "rails_commit": "2a391ccad1d098e4bb51eb19b6ca52adaa79e5cb", - "certified_at": "2026-07-18T21:58:00Z", - "persisted_final_text": "compat-ok", - "model_request_count": 1 + "consumer_commit": "6caa0132e4b172777126ed537de99cb7e9758e0b", + "certified_at": "2026-07-20T08:14:53Z", + "evidence": "evidence/2026-07-20-ajent-rails-alpha7-rollback.json" } }, { - "id": "mushu-permission-shortcuts-alpha7-canary", + "id": "travelwolf-alpha8-production", + "consumer": "travelwolf", + "consumer_commit": "02aed70eb4de201c6510e3be583699f8eb984ce7", + "image": "ghcr.io/anomalyco/opencode@sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a", + "application_image_id": "sha256:031371e20f34e1014712ddc668b4e182483664bb70f116b683c0ec83d6668338", + "reported_version": "1.16.1", + "profiles": ["rails-persisted-turn"], + "certification_status": "certified", + "current_certification": { + "status": "certified", + "client_commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766", + "rails_commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f", + "consumer_commit": "02aed70eb4de201c6510e3be583699f8eb984ce7", + "certified_at": "2026-07-20T06:51:48Z", + "evidence": "evidence/2026-07-20-travelwolf-alpha8-production.json" + }, + "previous_certification": { + "status": "certified", + "client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", + "rails_commit": "2a391ccad1d098e4bb51eb19b6ca52adaa79e5cb", + "consumer_commit": "531011defe4acc45a15ffe44a8e9da9cb610aaac", + "certified_at": "2026-07-20T06:31:42Z", + "evidence": "evidence/2026-07-20-travelwolf-alpha7-rollback.json" + } + }, + { + "id": "mushu-permission-shortcuts-alpha8-production", "consumer": "mushu", - "consumer_commit": "f26d958b64f214a422aeb629860c8c9a46a4c084", - "run_id": "mushu-alpha7-20260718T215300Z-f26d958", - "tag_provenance": "docker-registry:5000/mushu-opencode:sha-99d6328b1", - "docker_image_id": "sha256:ed6293ca87e6db1dc07870d3ed6da21960743e6e2e904533b3c566d334b58d6a", + "consumer_commit": "3f6754caab3f66537a5834f44ee63c1f05ecb047", + "registry_ref": "docker-registry:5000/mushu-opencode@sha256:a16bf0c4cd17698eba02244f6ba0e429b2238b6455e1415ff69fb08965c863a7", + "application_image": "docker-registry:5000/mushu@sha256:30f204184520d504c7c384c7f6e503fa3547217aaaef2f1d0d65884533bdeacd", + "base_image": "docker-registry:5000/mushu-opencode-base@sha256:de8a6adeefb30ce1c474cde23fe173dfe059434a13df0a54d14c7473edb33e16", "source_commit": "99d6328b18ff8a340a928449a2aa8bc184a063f9", + "build_source_commit": "521185613a864a16a76a21c7a60597ab3973bae9", "reported_version": "0.0.0-permission-shortcuts-202606300721", "profiles": ["ruby-rest-sse"], - "certification_status": "pending", + "certification_status": "certified", + "current_certification": { + "status": "certified", + "client_commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766", + "consumer_commit": "3f6754caab3f66537a5834f44ee63c1f05ecb047", + "certified_at": "2026-07-20T07:13:51Z", + "evidence": "evidence/2026-07-20-mushu-alpha8-production.json" + }, "previous_certification": { - "status": "candidate-certified", + "status": "certified", "client_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", - "certified_at": "2026-07-18T21:53:00Z", - "persisted_final_text": "compat-ok", - "model_request_count": 1 + "consumer_commit": "af2e9b0ce7ccacf49500450595a67c765a9d617a", + "certified_at": "2026-07-20T06:39:46.921805Z", + "evidence": "evidence/2026-07-20-mushu-alpha7-rollback.json" } } ] diff --git a/manifests/runtime-tuples.json b/manifests/runtime-tuples.json index 98cf2b8..a30fdae 100644 --- a/manifests/runtime-tuples.json +++ b/manifests/runtime-tuples.json @@ -1,16 +1,200 @@ { "schema_version": 1, - "migration_state": "candidate", + "migration_state": "certified", "promotion_readiness": { - "status": "blocked", - "reason": "Every observed alpha2 baseline fails the hardened exact-final-text contract, so none can be certified as the previous rollback tuple.", - "evidence": "evidence/2026-07-18-opencode-ruby-alpha2-regression.json", - "required_action": "Create and canary a distinct rollback consumer commit that pins alpha7 and the exact runtime before promoting any candidate." + "status": "certified", + "reason": "Every consumer has exact current and previous passing tuples.", + "required_action": "Promote only a newly certified, materially changed tuple." }, "consumers": { "ajent-rails": { "profile": "rails-persisted-turn", "current": { + "status": "certified", + "consumer_commit": "2ec8b879c5a34bb683fa5aaf26b998041d0e1264", + "consumer_tree": "a1e044f3a4f5c5ac0510a1d998c4afb55286da5a", + "opencode_ruby": { + "version": "0.0.1.alpha8", + "git_commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766", + "source_provenance": "unpublished-exact-commit" + }, + "opencode_rails": { + "version": "0.0.1.alpha8", + "git_commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f", + "source_provenance": "unpublished-exact-commit" + }, + "runtime": { + "image": "docker-registry:5000/ajent_rails@sha256:c7b2c6f3c3ffb6af62069c3142a2d76e606b0794077b81c5464add2266b7c350", + "application_image": "docker-registry:5000/ajent_rails@sha256:c7b2c6f3c3ffb6af62069c3142a2d76e606b0794077b81c5464add2266b7c350", + "aigl_image": "docker-registry:5000/opencode-aigl@sha256:f4064cb8b7a0f680f076dba9c72ba8e63b1457227c9fad5aa1407f0e0aac12e7", + "blackline_image": "docker-registry:5000/opencode-blackline@sha256:7b4ea4d70ff5fd35e3b17c571865dd6bb489217c7c00942e8569132fe2f749c8", + "raven_image": "docker-registry:5000/opencode-raven@sha256:9fe37ee88d8b10165e17f5f2a5c47b2396256464e732b4ef49138fb92a1234e3", + "build_source_commit": "2ec8b879c5a34bb683fa5aaf26b998041d0e1264", + "reported_version": "1.18.3", + "upstream_base": "ghcr.io/anomalyco/opencode@sha256:c2d5d6398df72aac85cb1bdc8f900c71a9b75a33fb7c0a76dc1484e4b126e41e" + }, + "application_canary": { + "checked_at": "2026-07-20T08:15:17Z", + "products": [ + { + "id": "aigl", + "agent": "travel-agent", + "model": "openai-gpt-5.5", + "final_text": "CANARY_OK", + "reported_version": "1.18.3", + "status": "pass" + }, + { + "id": "blackline", + "agent": "employment-lawyer", + "model": "moonshot-kimi-k2.5", + "final_text": "CANARY_OK", + "reported_version": "1.18.3", + "status": "pass" + }, + { + "id": "raven", + "agent": "raven-legal", + "model": "moonshot-kimi-k2.5", + "final_text": "CANARY_OK", + "reported_version": "1.18.3", + "status": "pass" + } + ], + "session_cleanup_status": "pass", + "status": "pass" + }, + "forge_ci": { + "pr_run_id": "2718", + "main_build_run_id": "2719", + "protected_main_run_id": "2720", + "deploy_job_id": "12065", + "status": "pass" + }, + "production": { + "checked_at": "2026-07-20T08:15:17Z", + "application_health_status": 200, + "blackline_health_status": 200, + "ajent_public_status": 308, + "voice_contract_status": "pass", + "scotus_contract_status": "pass", + "status": "pass" + }, + "source_availability": { + "origin_main": "2ec8b879c5a34bb683fa5aaf26b998041d0e1264", + "backup_main": "2ec8b879c5a34bb683fa5aaf26b998041d0e1264", + "github_main": "2ec8b879c5a34bb683fa5aaf26b998041d0e1264", + "status": "pass" + }, + "compatibility_certified_at": "2026-07-20T08:15:17Z", + "certification": { + "status": "pass", + "certified_at": "2026-07-20T08:15:17Z", + "tuple_sha256": "sha256:326a87cdb67061bdd36dfcee989ae149511a0c627e857488d59e0f2b3c796720", + "evidence": [ + { + "path": "evidence/2026-07-20-ajent-rails-alpha8-production.json", + "sha256": "sha256:1db57a6df214d03a3466ce8668c773723e733f00d08d072dfa55206c3e8b09a1" + } + ] + } + }, + "candidate": null, + "previous": { + "status": "certified", + "consumer_commit": "6caa0132e4b172777126ed537de99cb7e9758e0b", + "consumer_tree": "29e6cd35313a06cef6f9c1f40a36e3ee582fd035", + "opencode_ruby": { + "version": "0.0.1.alpha7", + "git_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", + "tag_provenance": { + "tag": "v0.0.1.alpha7", + "annotated_tag_object": "abfce3ec8cb1ee468107d0845521769502251645" + } + }, + "opencode_rails": { + "version": "0.0.1.alpha7", + "git_commit": "2a391ccad1d098e4bb51eb19b6ca52adaa79e5cb", + "tag_provenance": { + "tag": "v0.0.1.alpha7", + "annotated_tag_object": "61e128084b2b665c0ba20b332987e41794948d7c" + } + }, + "runtime": { + "image": "docker-registry:5000/ajent_rails@sha256:fe6fdc2325d304ddb276df7d35807c183e7e574876a0d76a6c6de6014619c76e", + "application_image": "docker-registry:5000/ajent_rails@sha256:fe6fdc2325d304ddb276df7d35807c183e7e574876a0d76a6c6de6014619c76e", + "aigl_image": "docker-registry:5000/opencode-aigl@sha256:f4064cb8b7a0f680f076dba9c72ba8e63b1457227c9fad5aa1407f0e0aac12e7", + "blackline_image": "docker-registry:5000/opencode-blackline@sha256:7b4ea4d70ff5fd35e3b17c571865dd6bb489217c7c00942e8569132fe2f749c8", + "raven_image": "docker-registry:5000/opencode-raven@sha256:9fe37ee88d8b10165e17f5f2a5c47b2396256464e732b4ef49138fb92a1234e3", + "build_source_commit": "6caa0132e4b172777126ed537de99cb7e9758e0b", + "reported_version": "1.18.3", + "upstream_base": "ghcr.io/anomalyco/opencode@sha256:c2d5d6398df72aac85cb1bdc8f900c71a9b75a33fb7c0a76dc1484e4b126e41e" + }, + "application_canary": { + "checked_at": "2026-07-20T08:14:53Z", + "products": [ + { + "id": "aigl", + "agent": "travel-agent", + "model": "openai-gpt-5.5", + "final_text": "CANARY_OK", + "reported_version": "1.18.3", + "status": "pass" + }, + { + "id": "blackline", + "agent": "employment-lawyer", + "model": "moonshot-kimi-k2.5", + "final_text": "CANARY_OK", + "reported_version": "1.18.3", + "status": "pass" + }, + { + "id": "raven", + "agent": "raven-legal", + "model": "moonshot-kimi-k2.5", + "final_text": "CANARY_OK", + "reported_version": "1.18.3", + "status": "pass" + } + ], + "session_cleanup_status": "pass", + "status": "pass" + }, + "consumer_verification": { + "test_runs": 4247, + "assertions": 15740, + "failures": 0, + "errors": 0, + "skips": 4, + "rubocop_files": 1430, + "status": "pass" + }, + "source_availability": { + "origin_ref": "refs/heads/opencode/opencode-alpha7-rollback-certified-20260720", + "backup_ref": "refs/heads/opencode/opencode-alpha7-rollback-certified-20260720", + "github_ref": "refs/heads/opencode/opencode-alpha7-rollback-certified-20260720" + }, + "rollback_operability": { + "source_archived_on_all_remotes": true, + "prebuilt_application_image_retained": true, + "application_rebuild_required": false, + "deployed": false + }, + "compatibility_certified_at": "2026-07-20T08:14:53Z", + "certification": { + "status": "pass", + "certified_at": "2026-07-20T08:14:53Z", + "tuple_sha256": "sha256:ae48333ba453f442d14263a916418a384f576db34d09a503111fc28c340eb408", + "evidence": [ + { + "path": "evidence/2026-07-20-ajent-rails-alpha7-rollback.json", + "sha256": "sha256:c3554a346a6585c7f98fde2aee2ad4f06ef4db7b621c98160183274e9e6ddc5c" + } + ] + } + }, + "emergency_provenance": { "status": "observed-production-contract-failed", "consumer_commit": "83eed3c247a352842b08fc5cf339c4d0acdaacba", "opencode_ruby": { @@ -27,10 +211,95 @@ "reported_version": "1.18.3", "upstream_base": "ghcr.io/anomalyco/opencode@sha256:c2d5d6398df72aac85cb1bdc8f900c71a9b75a33fb7c0a76dc1484e4b126e41e" } + } + }, + "travelwolf": { + "profile": "rails-persisted-turn", + "current": { + "status": "certified", + "consumer_commit": "02aed70eb4de201c6510e3be583699f8eb984ce7", + "consumer_tree": "f436a7248d90e5ae9e84c45f264acd913a4162aa", + "opencode_ruby": { + "version": "0.0.1.alpha8", + "git_commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766", + "source_provenance": "unpublished-exact-commit" + }, + "opencode_rails": { + "version": "0.0.1.alpha8", + "git_commit": "a9add2a7c1dd3eb978aa8b4ebf9ef7e111d1057f", + "source_provenance": "unpublished-exact-commit" + }, + "runtime": { + "image": "ghcr.io/anomalyco/opencode@sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a", + "application_image_id": "sha256:031371e20f34e1014712ddc668b4e182483664bb70f116b683c0ec83d6668338", + "reported_version": "1.16.1" + }, + "application_canary": { + "checked_at": "2026-07-20T06:31:13.506Z", + "final_text": "TRAVELWOLF_ALPHA8_CANARY_OK", + "application_prompt_count": 1, + "user_turn_count": 1, + "authoritative_assistant_turn_count": 1, + "child_turn_count": 0, + "model_request_count_observed": false, + "cleanup_status": "pass", + "status": "pass" + }, + "forge_ci": { + "status": "pass", + "pr_run_id": "2702", + "main_run_id": "2704" + }, + "production": { + "checked_at": "2026-07-20T06:51:48Z", + "application_image_created_at": "2026-07-20T06:42:55.929825054Z", + "internal_health_status": 200, + "authenticated_public_health_status": 200, + "container_status": "running", + "deployment_mode": "manual-isolated-kamal", + "status": "pass" + }, + "rollback_operability": { + "certified_previous_consumer_commit": "531011defe4acc45a15ffe44a8e9da9cb610aaac", + "immediate_platform_rollback_is_certified_tuple": false, + "immediate_platform_rollback_commit": "8494c52d11bf2995c378398e977d82d06a09e046" + }, + "compatibility_certified_at": "2026-07-20T06:51:48Z", + "certification": { + "status": "pass", + "certified_at": "2026-07-20T06:51:48Z", + "tuple_sha256": "sha256:0321c4246bed6ab62587924a83204a9e2ca7517b1dec5e4aef35dcac400fcc8e", + "evidence": [ + { + "path": "evidence/2026-07-20-travelwolf-alpha8-production.json", + "sha256": "sha256:ab8d8de768d415b3a68090507fb6ec40bdfad439fcaf0471beea4f52450e9d2f" + } + ] + } }, - "candidate": { - "status": "compatibility-certified", - "consumer_commit": "96552893baccd5ccba1592b2585edfef1299b4f9", + "immediate_platform_rollback": { + "status": "uncertified-known-contract-mismatch", + "consumer_commit": "8494c52d11bf2995c378398e977d82d06a09e046", + "opencode_ruby": { + "version": "0.0.1.alpha6", + "git_commit": "9356268fb339269cd53763435af7ce88df1eeec7" + }, + "opencode_rails": { + "version": "0.0.1.alpha6", + "git_commit": "8d1eb7701fe9e24baa64c16d6af03c6ed1f423eb" + }, + "runtime": { + "image": "ghcr.io/anomalyco/opencode@sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a", + "application_image_id": "sha256:4291b256b37eeb694435093655db17154c837e53335b8d0eb8f1b07a744e856e", + "reported_version": "1.16.1" + }, + "reason": "This retained application image is the platform's one-click target, but its alpha6 clients are not the certified alpha7 previous tuple." + }, + "candidate": null, + "previous": { + "status": "certified", + "consumer_commit": "531011defe4acc45a15ffe44a8e9da9cb610aaac", + "consumer_tree": "d0e47b2d90ace87c6cc9c4b94b2f22987441c1f6", "opencode_ruby": { "version": "0.0.1.alpha7", "git_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", @@ -48,26 +317,44 @@ } }, "runtime": { - "tag_provenance": "ajent-opencode-blackline-canary:96552893", - "docker_image_id": "sha256:239e3dc6cd2958251cda636fb20bd7cd7c46f596bd4c61cb81863ebc49d824e8", - "source_commit": "96552893baccd5ccba1592b2585edfef1299b4f9", - "reported_version": "1.18.3", - "upstream_base": "ghcr.io/anomalyco/opencode@sha256:c2d5d6398df72aac85cb1bdc8f900c71a9b75a33fb7c0a76dc1484e4b126e41e" + "image": "ghcr.io/anomalyco/opencode@sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a", + "reported_version": "1.16.1" }, "application_canary": { - "checked_at": "2026-07-18T21:58:00Z", - "path": "Blackline::GenerateResponseJob", - "persisted_final_text": "compat-ok", - "model_request_count": 1, + "checked_at": "2026-07-20T06:22:36.958Z", + "final_text": "TRAVELWOLF_ALPHA7_CANARY_OK", + "application_prompt_count": 1, + "user_turn_count": 1, + "authoritative_assistant_turn_count": 1, + "child_turn_count": 0, + "model_request_count_observed": false, + "cleanup_status": "pass", "status": "pass" }, - "certified_at": "2026-07-18T22:03:14Z" + "forge_ci": { + "status": "pass", + "pr_run_id": "2696", + "main_run_id": "2698" + }, + "rollback_operability": { + "source_archived_on_all_remotes": true, + "prebuilt_application_image_retained": false, + "application_rebuild_required": true + }, + "compatibility_certified_at": "2026-07-20T06:31:42Z", + "certification": { + "status": "pass", + "certified_at": "2026-07-20T06:31:42Z", + "tuple_sha256": "sha256:20ecec3d2c3743efcd8575e71ec46dd7b86c2865e472511d16a96d57ddb8b40c", + "evidence": [ + { + "path": "evidence/2026-07-20-travelwolf-alpha7-rollback.json", + "sha256": "sha256:d5e36ffc40f44ff28cbd7eee7bf209fa595ce57619081a76f8d049499b5d30a4" + } + ] + } }, - "previous": null - }, - "travelwolf": { - "profile": "rails-persisted-turn", - "current": { + "emergency_provenance": { "status": "observed-production-contract-failed", "consumer_commit": "1ca756d64f058c9e0379f69fc4a3d53330cfcd15", "opencode_ruby": { @@ -82,10 +369,110 @@ "image": "ghcr.io/anomalyco/opencode@sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a", "reported_version": "1.16.1" } + } + }, + "mushu": { + "profile": "ruby-rest-sse", + "current": { + "status": "certified", + "consumer_commit": "3f6754caab3f66537a5834f44ee63c1f05ecb047", + "consumer_tree": "726c3878e71f7a4cf8c0b8a3fa8fc15382bbab45", + "opencode_ruby": { + "version": "0.0.1.alpha8", + "git_commit": "9277646a4bb2cf25a8384ffc140b154f49ea5766", + "source_provenance": "unpublished-exact-commit" + }, + "runtime": { + "tag_provenance": "docker-registry:5000/mushu-opencode:sha-521185613a864a16a76a21c7a60597ab3973bae9", + "registry_ref": "docker-registry:5000/mushu-opencode@sha256:a16bf0c4cd17698eba02244f6ba0e429b2238b6455e1415ff69fb08965c863a7", + "application_image": "docker-registry:5000/mushu@sha256:30f204184520d504c7c384c7f6e503fa3547217aaaef2f1d0d65884533bdeacd", + "base_image": "docker-registry:5000/mushu-opencode-base@sha256:de8a6adeefb30ce1c474cde23fe173dfe059434a13df0a54d14c7473edb33e16", + "source_commit": "99d6328b18ff8a340a928449a2aa8bc184a063f9", + "build_source_commit": "521185613a864a16a76a21c7a60597ab3973bae9", + "reported_version": "0.0.0-permission-shortcuts-202606300721" + }, + "preproduction_canary": { + "successful_turn_count": 13, + "turn_count": 13, + "successful_conversation_count": 6, + "conversation_count": 6, + "successful_mcp_context_count": 2, + "mcp_context_count": 2, + "database_sha256": "sha256:7fbe32ff9621196a2504aeba5eca78cb0c9c670edd7a1168b1f81f667872e2b6", + "log_sha256": "sha256:51b698fcc8b7bce68e79cbae10997e19eb47a88985cbe24871d10740dda26225", + "status": "pass" + }, + "application_canary": { + "started_at": "2026-07-20T07:03:17Z", + "checked_at": "2026-07-20T07:04:51.376351Z", + "successful_turn_count": 13, + "turn_count": 13, + "successful_conversation_count": 6, + "conversation_count": 6, + "successful_mcp_context_count": 1, + "mcp_context_count": 1, + "status": "pass" + }, + "forge_ci": { + "status": "pass", + "pr_run_id": "2700", + "main_run_id": "2705" + }, + "production": { + "checked_at": "2026-07-20T07:13:20Z", + "web_health_status": 200, + "status_health_status": 200, + "runtime_readiness": "ready", + "containers": [ + "web", + "calendar", + "telegram", + "opencode" + ], + "deployment_mode": "manual", + "status": "pass" + }, + "rollback_operability": { + "certified_previous_consumer_commit": "af2e9b0ce7ccacf49500450595a67c765a9d617a", + "immediate_platform_rollback_is_certified_tuple": false, + "immediate_platform_rollback_commit": "82ca30b18e1f38b55e2bed91cbd46f1eaf4a5922" + }, + "compatibility_certified_at": "2026-07-20T07:13:51Z", + "certification": { + "status": "pass", + "certified_at": "2026-07-20T07:13:51Z", + "tuple_sha256": "sha256:804410a63cf2b6e4056d6dd9ce289ac72464f441c9785b6fc8dd50016263a1a8", + "evidence": [ + { + "path": "evidence/2026-07-20-mushu-alpha8-production.json", + "sha256": "sha256:786bf92a02588b951b5b7f7a21ed915b7ac1120538c5f894af027c190379170f" + } + ] + } }, - "candidate": { - "status": "compatibility-certified", - "consumer_commit": "19747b59fa21e12560afd9edd73ed674ab3a644c", + "immediate_platform_rollback": { + "status": "uncertified-known-contract-failure", + "consumer_commit": "82ca30b18e1f38b55e2bed91cbd46f1eaf4a5922", + "opencode_ruby": { + "version": "0.0.1.alpha2", + "git_commit": "889d38332f98f5f7b76d16952b7204d8a5b9a662" + }, + "opencode_rails": null, + "runtime": { + "registry_ref": "docker-registry:5000/mushu-opencode@sha256:ed6293ca87e6db1dc07870d3ed6da21960743e6e2e904533b3c566d334b58d6a", + "application_image": null, + "application_image_provenance": "not-retained", + "base_image": "docker-registry:5000/mushu-opencode-base@sha256:de8a6adeefb30ce1c474cde23fe173dfe059434a13df0a54d14c7473edb33e16", + "source_commit": "99d6328b18ff8a340a928449a2aa8bc184a063f9", + "reported_version": "0.0.0-permission-shortcuts-202606300721" + }, + "reason": "The one-click application image is not retained and the alpha2 client failed the contract; use the certified alpha7 previous source tuple instead." + }, + "candidate": null, + "previous": { + "status": "certified", + "consumer_commit": "af2e9b0ce7ccacf49500450595a67c765a9d617a", + "consumer_tree": "f43f48972ab444310508e9067831dfc6977f1a24", "opencode_ruby": { "version": "0.0.1.alpha7", "git_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", @@ -94,34 +481,56 @@ "annotated_tag_object": "abfce3ec8cb1ee468107d0845521769502251645" } }, - "opencode_rails": { - "version": "0.0.1.alpha7", - "git_commit": "2a391ccad1d098e4bb51eb19b6ca52adaa79e5cb", - "tag_provenance": { - "tag": "v0.0.1.alpha7", - "annotated_tag_object": "61e128084b2b665c0ba20b332987e41794948d7c" - } - }, "runtime": { - "image": "ghcr.io/anomalyco/opencode@sha256:e975a0647576016dfdf77d54b979ca30d32b4750472c10263e9894aad6628c2a", - "reported_version": "1.16.1" + "tag_provenance": "docker-registry:5000/mushu-opencode:sha-99d6328b1", + "registry_ref": "docker-registry:5000/mushu-opencode@sha256:ed6293ca87e6db1dc07870d3ed6da21960743e6e2e904533b3c566d334b58d6a", + "base_image": "docker-registry:5000/mushu-opencode-base@sha256:de8a6adeefb30ce1c474cde23fe173dfe059434a13df0a54d14c7473edb33e16", + "source_commit": "99d6328b18ff8a340a928449a2aa8bc184a063f9", + "build_source_commit": null, + "provenance_strength": "one-time-tag-source-attestation", + "reported_version": "0.0.0-permission-shortcuts-202606300721" }, "application_canary": { - "run_id": "20260718215135-8d8102c0", - "checked_at": "2026-07-18T21:51:56.552Z", - "final_text": "TRAVELWOLF_ALPHA7_SUBSCRIBE_READY", - "application_prompt_count": 1, - "model_request_count_observed": false, - "cleanup_status": "pass", + "started_at": "2026-07-20T06:37:55Z", + "checked_at": "2026-07-20T06:39:46.921805Z", + "successful_turn_count": 13, + "turn_count": 13, + "successful_conversation_count": 6, + "conversation_count": 6, + "successful_mcp_context_count": 7, + "mcp_context_count": 7, + "database_sha256": "sha256:a567d2ff9a906c9b78831bb6fa43153af539f1e76ee63bf35961472f55651d74", + "log_sha256": "sha256:a35a83b003a1f082b8a721b3faad8b35cb71dc9d1ceaedfc3cec3b26916e85b9", "status": "pass" }, - "certified_at": "2026-07-18T22:03:14Z" + "forge_ci": { + "status": "not_run", + "run_ids": [], + "reason": "Frozen exact head predates the runner-only system-test workflow repair; clean-worktree application canary is authoritative." + }, + "source_availability": { + "origin_ref": "refs/heads/review/mushu-opencode-alpha7-consumer-final-20260719", + "backup_ref": "refs/heads/review/mushu-opencode-alpha7-consumer-final-20260719" + }, + "rollback_operability": { + "source_archived_on_all_remotes": true, + "prebuilt_application_image_retained": false, + "application_rebuild_required": true + }, + "compatibility_certified_at": "2026-07-20T06:39:46.921805Z", + "certification": { + "status": "pass", + "certified_at": "2026-07-20T06:39:46.921805Z", + "tuple_sha256": "sha256:8d7d3cb2d3e2d920c900a9c1ada365026872904588cff0b166903d1d6d32183f", + "evidence": [ + { + "path": "evidence/2026-07-20-mushu-alpha7-rollback.json", + "sha256": "sha256:143deb6b400a05305991c956eaf80f07fdee76a9cce98af96f8eee395ed9847d" + } + ] + } }, - "previous": null - }, - "mushu": { - "profile": "ruby-rest-sse", - "current": { + "emergency_provenance": { "status": "observed-production-contract-failed", "consumer_commit": "fe16e5bab028e3c92cd5508ef2cebd9bfa23386f", "opencode_ruby": { @@ -134,34 +543,7 @@ "source_commit": "99d6328b18ff8a340a928449a2aa8bc184a063f9", "reported_version": "0.0.0-permission-shortcuts-202606300721" } - }, - "candidate": { - "status": "compatibility-certified", - "consumer_commit": "f26d958b64f214a422aeb629860c8c9a46a4c084", - "opencode_ruby": { - "version": "0.0.1.alpha7", - "git_commit": "78b6f9c9e9c7d58b699af1c3c17764acd33de798", - "tag_provenance": { - "tag": "v0.0.1.alpha7", - "annotated_tag_object": "abfce3ec8cb1ee468107d0845521769502251645" - } - }, - "runtime": { - "tag_provenance": "docker-registry:5000/mushu-opencode:sha-99d6328b1", - "docker_image_id": "sha256:ed6293ca87e6db1dc07870d3ed6da21960743e6e2e904533b3c566d334b58d6a", - "source_commit": "99d6328b18ff8a340a928449a2aa8bc184a063f9", - "reported_version": "0.0.0-permission-shortcuts-202606300721" - }, - "application_canary": { - "run_id": "mushu-alpha7-20260718T215300Z-f26d958", - "checked_at": "2026-07-18T21:53:00Z", - "persisted_final_text": "compat-ok", - "model_request_count": 1, - "status": "pass" - }, - "certified_at": "2026-07-18T22:03:14Z" - }, - "previous": null + } } } } diff --git a/test/repository_test.rb b/test/repository_test.rb index 7064d37..7abb438 100644 --- a/test/repository_test.rb +++ b/test/repository_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "json" +require "digest" require "minitest/autorun" require_relative "../lib/opencode_compat/client_candidate" require_relative "../lib/opencode_compat/runtime_tuple_promoter" @@ -48,7 +49,14 @@ class RepositoryTest < Minitest::Test assert_equal ["ruby-rest-sse"], target.fetch("profiles") assert_equal "shared-client-contract-only", target.fetch("certification_scope", "shared-client-contract-only") - assert_equal "pending", target.fetch("certification_status") + assert_equal "certified", target.fetch("certification_status") + current = target.fetch("current_certification") + assert_equal "certified", current.fetch("status") + assert_match(/\A[0-9a-f]{40}\z/, current.fetch("client_commit")) + assert_match(/\A[0-9a-f]{40}\z/, current.fetch("rails_commit")) + assert_equal current.fetch("expected_text"), current.fetch("full_text") + assert_equal 1, current.fetch("llm_request_count") + assert_path_exists File.join(ROOT, current.fetch("evidence")) previous = target["previous_certification"] next unless previous @@ -88,22 +96,29 @@ class RepositoryTest < Minitest::Test assert_equal clients.fetch("opencode-ruby").fetch("ref"), dependency.fetch("ref") end - def test_image_matrix_is_pending_and_bound_to_the_exact_client_candidate + def test_image_matrix_is_certified_and_bound_to_the_exact_unpublished_client_candidate candidate = json("manifests/client-candidate.json") clients = candidate.fetch("clients") matrix_candidate = json("manifests/image-matrix.json").fetch("client_candidate") - assert_equal "pending", matrix_candidate.fetch("certification_status") + assert_equal "certified", matrix_candidate.fetch("certification_status") + assert_equal "unpublished", matrix_candidate.fetch("publication_state") + assert_path_exists File.join(ROOT, matrix_candidate.fetch("evidence")) assert_equal candidate.fetch("release_train"), matrix_candidate.fetch("release_train") assert_equal candidate.fetch("publication_state"), matrix_candidate.fetch("publication_state") assert_equal clients.dig("opencode-ruby", "ref"), matrix_candidate.fetch("opencode_ruby_commit") assert_equal clients.dig("opencode-rails", "ref"), matrix_candidate.fetch("opencode_rails_commit") json("manifests/image-matrix.json").fetch("host_canary").each do |target| - assert_equal "pending", target.fetch("certification_status") + assert_equal "certified", target.fetch("certification_status") + current = target.fetch("current_certification") + assert_equal "certified", current.fetch("status") + assert_match(/\A[0-9a-f]{40}\z/, current.fetch("client_commit")) + assert_path_exists File.join(ROOT, current.fetch("evidence")) previous = target.fetch("previous_certification") - assert_includes %w[candidate-certified certified], previous.fetch("status") + assert_equal "certified", previous.fetch("status") assert_match(/\A[0-9a-f]{40}\z/, previous.fetch("client_commit")) + assert_path_exists File.join(ROOT, previous.fetch("evidence")) end end @@ -129,6 +144,32 @@ class RepositoryTest < Minitest::Test assert evidence.fetch("limitations").any? { |entry| entry.include?("remain unverified") } end + def test_alpha8_ci_evidence_certifies_shared_and_lockstep_scopes_without_claiming_apps + evidence = json("evidence/2026-07-20-opencode-alpha8-shared-client-ci.json") + candidate = json("manifests/client-candidate.json").fetch("clients") + + assert_equal "pass", evidence.fetch("status") + assert_equal "unpublished", evidence.fetch("publication_state") + assert_equal candidate.dig("opencode-ruby", "ref"), evidence.dig("clients", "opencode_ruby", "commit") + assert_equal candidate.dig("opencode-rails", "ref"), evidence.dig("clients", "opencode_rails", "commit") + assert_equal "29725021192", evidence.dig("github_workflow", "run_id") + assert_equal "2717", evidence.dig("gitea_workflow", "run_id") + assert_equal false, evidence.dig("gitea_workflow", "artifact_evidence_claimed") + assert_operator evidence.fetch("certified_at"), :>=, evidence.dig("github_workflow", "completed_at") + assert_operator evidence.fetch("certified_at"), :>=, evidence.dig("gitea_workflow", "completed_at") + assert_equal %w[3.2.11 3.3.12 3.4.10 4.0.6], + evidence.fetch("rails_lockstep").map { |entry| entry.fetch("ruby_runtime_version") } + assert evidence.fetch("rails_lockstep").all? { |entry| entry.fetch("status") == "pass" } + assert_equal 3, evidence.fetch("exact_image_contracts").length + evidence.fetch("exact_image_contracts").each do |contract| + assert_equal "pass", contract.fetch("status") + assert_equal contract.fetch("expected_text"), contract.fetch("full_text") + assert_equal 1, contract.fetch("authoritative_assistant_message_count") + assert_equal 1, contract.fetch("llm_request_count") + end + assert evidence.fetch("limitations").any? { |entry| entry.include?("application profile") } + end + def test_certified_migration_keeps_previous_tuple tuples = json("manifests/runtime-tuples.json") return unless tuples.fetch("migration_state") == "certified" @@ -155,7 +196,7 @@ class RepositoryTest < Minitest::Test def test_runtime_tuples_use_full_commits_and_no_mutable_registry_coordinate tuples = json("manifests/runtime-tuples.json") tuples.fetch("consumers").each_value do |consumer| - %w[current candidate previous].each do |slot| + %w[current candidate previous emergency_provenance].each do |slot| tuple = consumer[slot] next unless tuple @@ -176,37 +217,83 @@ class RepositoryTest < Minitest::Test end end - def test_failed_alpha2_baselines_block_promotion_instead_of_becoming_previous + def test_failed_alpha2_baselines_remain_uncertified_emergency_provenance tuples = json("manifests/runtime-tuples.json") readiness = tuples.fetch("promotion_readiness") - assert_equal "blocked", readiness.fetch("status") - assert_path_exists File.join(ROOT, readiness.fetch("evidence")) + assert_equal "certified", tuples.fetch("migration_state") + assert_equal "certified", readiness.fetch("status") tuples.fetch("consumers").each_value do |consumer| - assert_equal "observed-production-contract-failed", consumer.dig("current", "status") - assert_nil consumer.fetch("previous") + emergency = consumer.fetch("emergency_provenance") + assert_equal "observed-production-contract-failed", emergency.fetch("status") + refute emergency.key?("certification") + assert_equal "certified", consumer.dig("current", "status") + assert_equal "0.0.1.alpha8", consumer.dig("current", "opencode_ruby", "version") + assert_equal "certified", consumer.dig("previous", "status") + assert_equal "0.0.1.alpha7", consumer.dig("previous", "opencode_ruby", "version") + refute consumer.key?("rollback_state") end end - def test_candidate_evidence_is_bound_to_complete_tuple_fingerprints + def test_platform_one_click_targets_are_explicitly_uncertified_and_separate + consumers = json("manifests/runtime-tuples.json").fetch("consumers") + + %w[travelwolf mushu].each do |name| + rollback = consumers.fetch(name).fetch("immediate_platform_rollback") + assert_match(/\Auncertified-/, rollback.fetch("status")) + assert_match(/\A[0-9a-f]{40}\z/, rollback.fetch("consumer_commit")) + assert_match(/\A[0-9a-f]{40}\z/, rollback.dig("opencode_ruby", "git_commit")) + refute rollback.key?("certification") + refute_equal consumers.dig(name, "previous", "consumer_commit"), rollback.fetch("consumer_commit") + end + + travelwolf_runtime = consumers.dig("travelwolf", "immediate_platform_rollback", "runtime") + assert_match(/@sha256:[0-9a-f]{64}\z/, travelwolf_runtime.fetch("image")) + assert_match(/\Asha256:[0-9a-f]{64}\z/, travelwolf_runtime.fetch("application_image_id")) + + mushu_runtime = consumers.dig("mushu", "immediate_platform_rollback", "runtime") + assert_match(/@sha256:[0-9a-f]{64}\z/, mushu_runtime.fetch("registry_ref")) + assert_nil mushu_runtime.fetch("application_image") + assert_equal "not-retained", mushu_runtime.fetch("application_image_provenance") + end + + def test_certified_tuple_evidence_is_bound_to_complete_tuple_fingerprints promoter = OpenCodeCompat::RuntimeTuplePromoter.new(root: ROOT) - evidence_paths = { - "ajent-rails" => "evidence/2026-07-18-ajent-rails-alpha7-candidate.json", - "travelwolf" => "evidence/2026-07-18-travelwolf-alpha7-candidate.json", - "mushu" => "evidence/2026-07-18-mushu-alpha7-candidate.json" - } - evidence_paths.each do |consumer, path| - evidence = json(path) - fingerprints = promoter.fingerprints( - consumer: consumer, - consumer_commit: evidence.fetch("consumer_commit") - ) + json("manifests/runtime-tuples.json").fetch("consumers").each do |consumer, entry| + profile = entry.fetch("profile") + %w[current previous].each do |slot| + tuple = entry.fetch(slot) + fingerprint = promoter.send(:tuple_fingerprint, tuple, consumer: consumer, profile: profile) + certification = tuple.fetch("certification") + promoter.send( + :validate_recorded_certification!, + tuple, + consumer: consumer, + profile: profile, + expected_fingerprint: fingerprint, + label: "#{consumer} #{slot}" + ) - assert_equal consumer, evidence.fetch("consumer") - assert_equal fingerprints.fetch("profile"), evidence.fetch("profile") - assert_equal fingerprints.fetch("candidate_tuple_sha256"), evidence.fetch("tuple_sha256") - assert_equal "pass", evidence.fetch("status") + assert_equal "certified", tuple.fetch("status") + assert_equal "pass", certification.fetch("status") + assert_equal fingerprint, certification.fetch("tuple_sha256") + refute_empty certification.fetch("evidence") + + certification.fetch("evidence").each do |reference| + assert_equal %w[path sha256], reference.keys.sort + path = reference.fetch("path") + bytes = File.binread(File.join(ROOT, path)) + assert_equal "sha256:#{Digest::SHA256.hexdigest(bytes)}", reference.fetch("sha256") + evidence = json(path) + assert_equal consumer, evidence.fetch("consumer") + assert_equal profile, evidence.fetch("profile") + assert_equal tuple.fetch("consumer_commit"), evidence.fetch("consumer_commit") + assert_equal certification.fetch("certified_at"), evidence.fetch("certified_at") + assert_equal fingerprint, evidence.fetch("tuple_sha256") + assert_equal "pass", evidence.fetch("status") + end + end end end diff --git a/test/runtime_tuple_promoter_test.rb b/test/runtime_tuple_promoter_test.rb index 5231409..f5fdd15 100644 --- a/test/runtime_tuple_promoter_test.rb +++ b/test/runtime_tuple_promoter_test.rb @@ -17,6 +17,9 @@ class RuntimeTuplePromoterTest < Minitest::Test RAILS_CURRENT = "5" * 40 RAILS_CANDIDATE = "6" * 40 RAILS_NEXT = "9" * 40 + CURRENT_TREE = "a" * 40 + CANDIDATE_TREE = "b" * 40 + NEXT_TREE = "c" * 40 CURRENT_IMAGE = "ghcr.io/anomalyco/opencode@sha256:#{'a' * 64}" CANDIDATE_IMAGE = "ghcr.io/anomalyco/opencode@sha256:#{'b' * 64}" NEXT_IMAGE = "ghcr.io/anomalyco/opencode@sha256:#{'c' * 64}" @@ -54,10 +57,12 @@ class RuntimeTuplePromoterTest < Minitest::Test 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 evidence_reference(candidate), consumer.dig("current", "certification", "evidence", 0) 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 evidence_reference(previous), consumer.dig("previous", "certification", "evidence", 0) assert_equal "certified", promoted.fetch("migration_state") assert_equal promoted, JSON.parse(File.read(manifest_path)) end @@ -126,6 +131,41 @@ class RuntimeTuplePromoterTest < Minitest::Test assert_match(/full 40-character/, error.message) end + def test_rejects_malformed_immutable_tuple_coordinates_but_allows_unknown_build_source + mutations = { + "consumer tree" => lambda { |candidate| candidate["consumer_tree"] = "abc123" }, + "build source commit" => lambda { |candidate| candidate.fetch("runtime")["build_source_commit"] = "abc123" }, + "rollback commit" => lambda do |candidate| + candidate["rollback_operability"] = {"immediate_platform_rollback_commit" => "abc123"} + end, + "artifact digest" => lambda do |candidate| + candidate["application_canary"] = {"database_sha256" => "sha256:abc123"} + end + } + + mutations.each do |label, mutate| + manifest = valid_manifest + mutate.call(manifest.dig("consumers", CONSUMER, "candidate")) + write_manifest(manifest) + + error = assert_raises(OpenCodeCompat::PromotionError, label) do + @promoter.fingerprints(consumer: CONSUMER, consumer_commit: CANDIDATE_COMMIT) + end + assert_match(/full 40-character|exact sha256 digest/, error.message, label) + end + + manifest = valid_manifest + manifest.dig("consumers", CONSUMER, "candidate", "runtime")["build_source_commit"] = nil + write_manifest(manifest) + assert_match( + /\Asha256:[0-9a-f]{64}\z/, + @promoter.fingerprints( + consumer: CONSUMER, + consumer_commit: CANDIDATE_COMMIT + ).fetch("candidate_tuple_sha256") + ) + end + def test_rejects_missing_server_version_or_unknown_profile manifest = valid_manifest manifest.dig("consumers", CONSUMER, "candidate", "runtime").delete("reported_version") @@ -358,6 +398,114 @@ class RuntimeTuplePromoterTest < Minitest::Test assert_equal before, File.binread(manifest_path) end + def test_hash_binds_evidence_and_accepts_a_structured_reference + candidate, previous = write_matching_evidence + structured_candidate = evidence_reference(candidate) + structured_previous = evidence_reference(previous) + + promoted = @promoter.promote( + consumer: CONSUMER, + consumer_commit: CANDIDATE_COMMIT, + certification: certification(CANDIDATE_TIME, candidate).merge("evidence" => [structured_candidate]), + previous_certification: certification(CURRENT_TIME, previous).merge("evidence" => [structured_previous]), + dry_run: true + ) + + assert_equal structured_candidate, promoted.dig("consumers", CONSUMER, "current", "certification", "evidence", 0) + + error = assert_raises(OpenCodeCompat::PromotionError) do + @promoter.promote( + consumer: CONSUMER, + consumer_commit: CANDIDATE_COMMIT, + certification: certification(CANDIDATE_TIME, candidate).merge( + "evidence" => [structured_candidate.merge("sha256" => "sha256:#{'0' * 64}")] + ), + previous_certification: certification(CURRENT_TIME, previous).merge("evidence" => [structured_previous]), + dry_run: true + ) + end + assert_match(/has sha256=.*expected/, error.message) + end + + def test_rejects_evidence_coordinates_that_disagree_with_the_tuple + mutations = { + "consumer_tree" => lambda { |document| document["consumer_tree"] = "f" * 40 }, + "opencode_ruby.commit" => lambda do |document| + document["clients"] = { + "opencode_ruby" => {"version" => "0.0.1.alpha7", "commit" => "f" * 40} + } + end, + "runtime.image" => lambda do |document| + document["runtime"] = { + "image" => "ghcr.io/anomalyco/opencode@sha256:#{'f' * 64}", + "reported_version" => "1.18.3" + } + end + } + + mutations.each do |field, mutate| + write_manifest(valid_manifest) + candidate, previous = write_matching_evidence + path = File.join(@root, candidate.fetch("path")) + document = JSON.parse(File.read(path)) + mutate.call(document) + File.write(path, JSON.pretty_generate(document) + "\n") + + error = assert_raises(OpenCodeCompat::PromotionError, field) do + @promoter.promote( + consumer: CONSUMER, + consumer_commit: CANDIDATE_COMMIT, + certification: certification(CANDIDATE_TIME, candidate), + previous_certification: certification(CURRENT_TIME, previous), + dry_run: true + ) + end + assert_match(/#{Regexp.escape(field)}/, error.message, field) + end + end + + def test_recorded_certifications_require_hash_bound_evidence_references + 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.dig("consumers", CONSUMER) + consumer.dig("current", "certification")["evidence"] = [candidate.fetch("path")] + consumer["candidate"] = { + "status" => "compatibility-certified", + "certified_at" => NEXT_TIME, + "consumer_commit" => NEXT_COMMIT, + "consumer_tree" => NEXT_TREE, + "opencode_ruby" => {"version" => "0.0.1.alpha8", "git_commit" => RUBY_NEXT}, + "opencode_rails" => {"version" => "0.0.1.alpha8", "git_commit" => RAILS_NEXT}, + "runtime" => {"image" => NEXT_IMAGE, "reported_version" => "1.19.0"} + } + write_manifest(promoted) + fingerprint = @promoter.fingerprints( + consumer: CONSUMER, + consumer_commit: NEXT_COMMIT + ).fetch("candidate_tuple_sha256") + next_evidence = write_evidence( + "next-after-legacy-reference.json", + commit: NEXT_COMMIT, + timestamp: NEXT_TIME, + fingerprint: fingerprint + ) + + error = assert_raises(OpenCodeCompat::PromotionError) do + @promoter.promote( + consumer: CONSUMER, + consumer_commit: NEXT_COMMIT, + certification: certification(NEXT_TIME, next_evidence), + dry_run: true + ) + end + assert_match(/hash-bound \{path, sha256\}/, error.message) + end + def test_rejects_non_passing_or_implicit_certification_metadata candidate, previous = write_matching_evidence @@ -424,6 +572,7 @@ class RuntimeTuplePromoterTest < Minitest::Test "current" => { "status" => "observed-production", "consumer_commit" => CURRENT_COMMIT, + "consumer_tree" => CURRENT_TREE, "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"} @@ -431,6 +580,7 @@ class RuntimeTuplePromoterTest < Minitest::Test "candidate" => { "status" => "compatibility-certified", "certified_at" => "2026-07-18T10:00:00Z", + "consumer_tree" => CANDIDATE_TREE, "opencode_ruby" => {"version" => "0.0.1.alpha7", "git_commit" => RUBY_CANDIDATE}, "opencode_rails" => {"version" => "0.0.1.alpha7", "git_commit" => RAILS_CANDIDATE}, "runtime" => {"image" => CANDIDATE_IMAGE, "reported_version" => "1.18.3"} @@ -481,6 +631,14 @@ class RuntimeTuplePromoterTest < Minitest::Test } end + def evidence_reference(evidence) + path = evidence.fetch("path") + { + "path" => path, + "sha256" => "sha256:#{Digest::SHA256.file(File.join(@root, path)).hexdigest}" + } + end + def manifest_path File.join(@root, "manifests", "runtime-tuples.json") end