Compare commits
3 Commits
fix/gitea-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82378391c6 | ||
| b25ac238f5 | |||
|
|
3bb0877034 |
1
.github/workflows/watch-upstream.yml
vendored
1
.github/workflows/watch-upstream.yml
vendored
@@ -15,6 +15,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
open-compatibility-pr:
|
||||
if: github.server_url == 'https://github.com'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
14
README.md
14
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
|
||||
@@ -122,7 +124,9 @@ durable certification documents and Git history are the archive. This keeps a
|
||||
fast-moving upstream from making every future run retest every old release.
|
||||
|
||||
See [docs/certification.md](docs/certification.md) for promotion, canary, and
|
||||
rollback evidence requirements.
|
||||
rollback evidence requirements, and [docs/maintenance.md](docs/maintenance.md)
|
||||
for consumer ownership, release order, runner boundaries, and expected failure
|
||||
modes.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -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,16 +160,34 @@ 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.
|
||||
|
||||
The schema-v1 promotion command deliberately cannot perform the first degraded
|
||||
bootstrap transition when `current` is a known-failing baseline. Do not bypass
|
||||
that guard by hand-editing the manifest or relabeling alpha2 evidence. A
|
||||
separate reviewed state-machine change must first add an explicit
|
||||
`bootstrap-current-only` state that preserves the failed baseline as
|
||||
uncertified emergency provenance and leaves `previous` null. Itemized rollback
|
||||
certification is complete only after a later, materially distinct passing tuple
|
||||
can move the first certified current into `previous`.
|
||||
Use the explicit degraded-bootstrap transition for the first passing rollback
|
||||
tuple. It requires passing evidence bound to the complete candidate fingerprint
|
||||
and a literal acknowledgement; it preserves the failed baseline as uncertified
|
||||
`emergency_provenance`, leaves `previous` null, and records the temporary
|
||||
`bootstrap-current-only` state:
|
||||
|
||||
```sh
|
||||
ruby scripts/promote_runtime_tuple.rb bootstrap-current \
|
||||
--consumer travelwolf \
|
||||
--consumer-commit FULL_40_CHARACTER_ROLLBACK_COMMIT \
|
||||
--status pass \
|
||||
--certified-at 2026-07-20T12:00:00Z \
|
||||
--evidence evidence/travelwolf-rollback.json \
|
||||
--acknowledge-degraded-rollback \
|
||||
accept-degraded-rollback-with-failed-emergency-provenance \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
Review the dry run, repeat without `--dry-run`, and then treat that passing
|
||||
tuple as the base for a normal promotion. Normal promotion moves it into
|
||||
`previous`, removes the degraded rollback marker, and keeps the original failed
|
||||
baseline only as historical emergency provenance. The repository becomes
|
||||
`certified` only when every consumer has exact passing `current` and `previous`
|
||||
tuples. Never use bootstrap to replace an existing previous tuple or to certify
|
||||
a baseline already known to fail.
|
||||
|
||||
## Rollback
|
||||
|
||||
|
||||
158
docs/maintenance.md
Normal file
158
docs/maintenance.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Maintaining OpenCode consumers
|
||||
|
||||
OpenCode compatibility is an executable tuple, not a gem version. The tuple is
|
||||
the exact client commit, Rails adapter commit where used, OpenCode image digest,
|
||||
consumer commit, compatibility profile, and passing evidence. A change to any
|
||||
coordinate creates a new candidate.
|
||||
|
||||
## Dependency ownership
|
||||
|
||||
The adjacent projects intentionally do not all use the same gems:
|
||||
|
||||
| Consumer | OpenCode boundary | Owned dependency |
|
||||
| --- | --- | --- |
|
||||
| Ajent Rails | Ruby REST/SSE plus persisted Rails turns | `opencode-ruby` and `opencode-rails` |
|
||||
| Travelwolf | Ruby REST/SSE plus persisted Rails turns and Sprite lifecycle | `opencode-ruby` and `opencode-rails` |
|
||||
| Mushu | Ruby REST/SSE with application-owned conversation, claim, recovery, Telegram scope, and idempotency | `opencode-ruby` only |
|
||||
| Greenroom | Direct voice-stream worker | No Ruby adapter gem |
|
||||
| Leela | Custom strict-v2 server, security, and toolchain lane | No Ruby adapter gem |
|
||||
| opencode-ajent | Native CLI and plugin hook/event lane | No Ruby adapter gem |
|
||||
| inference | Provider configuration, hooks, routing, and migration lane | No Ruby adapter gem |
|
||||
| Context Kit | MCP and OpenCode configuration producer | No Ruby adapter gem |
|
||||
|
||||
Do not add `opencode-rails` to an application that owns different persistence
|
||||
semantics, and do not route plugin, provider, voice, or strict-v2 behavior
|
||||
through the Ruby REST/SSE adapter merely to make versions look uniform. Share
|
||||
fixtures, provenance, and promotion policy across those lanes instead.
|
||||
|
||||
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
|
||||
compatibility with arbitrary future OpenCode versions. Additive fields and
|
||||
unknown events should remain tolerant, while terminal text, request count,
|
||||
ownership, persistence, and cleanup invariants remain strict.
|
||||
|
||||
The Ruby/SSE profile covers the endpoints and events the client actually uses,
|
||||
including session creation and deletion, asynchronous prompts, event
|
||||
subscription, status, terminal idle/status events, part deltas and updates,
|
||||
authoritative assistant messages, questions, and permissions. A passing shared
|
||||
profile does not certify Rails persistence, voice streaming, plugin hooks,
|
||||
provider migrations, or generated MCP configuration.
|
||||
|
||||
## Release and promotion order
|
||||
|
||||
1. The watcher records a new upstream release tag and resolved OCI digest in a
|
||||
PR. It never merges or deploys.
|
||||
2. Update fixtures for any observed protocol change before changing the client.
|
||||
3. Build `opencode-ruby` and `opencode-rails` as one release train. Rails must
|
||||
resolve the exact Ruby version and commit being tested.
|
||||
4. Run the shared fixture corpus, Ruby 3.2 through 4.0 lockstep matrix, and every
|
||||
exact public image still used by a current, previous, or candidate tuple.
|
||||
5. Run only the application-owned profiles for each consumer, in its own PR and
|
||||
isolated environment. Health checks alone do not certify a tuple.
|
||||
6. Promote and deploy one consumer at a time. Record the exact production
|
||||
commit, image/base digest, loaded client commits, and live result.
|
||||
7. Commit reviewed evidence and move the old passing current tuple to previous.
|
||||
8. Publish annotated gem tags only after the exact commit candidate is green and
|
||||
the trusted publisher is configured. Publication never implies deployment.
|
||||
|
||||
Commit pins are valid for an unpublished candidate, but the durable published
|
||||
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:
|
||||
|
||||
- consumer commit;
|
||||
- exact output registry digest or Docker image ID;
|
||||
- exact base image digest;
|
||||
- custom OpenCode source commit, when the base is a fork;
|
||||
- build-source commit when it differs from the deployment commit.
|
||||
|
||||
The tuple fingerprint binds these values. On the next rebuild of an older
|
||||
unlabelled private image, add OCI labels for the custom OpenCode source, reported
|
||||
version, consumer build revision, and base digest, then make preflight compare
|
||||
the labels. Do not relabel an already certified image: that changes its digest.
|
||||
|
||||
## Runner and forge contract
|
||||
|
||||
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:
|
||||
GitHub retains review artifacts and parallel dynamic image jobs; Gitea runs the
|
||||
same manifest image set sequentially and makes no artifact-retention claim.
|
||||
Neither path may mutate a runner or deploy a consumer.
|
||||
|
||||
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.
|
||||
76
evidence/2026-07-20-ajent-rails-alpha7-rollback.json
Normal file
76
evidence/2026-07-20-ajent-rails-alpha7-rollback.json
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
80
evidence/2026-07-20-ajent-rails-alpha8-production.json
Normal file
80
evidence/2026-07-20-ajent-rails-alpha8-production.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
68
evidence/2026-07-20-mushu-alpha7-rollback.json
Normal file
68
evidence/2026-07-20-mushu-alpha7-rollback.json
Normal file
@@ -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."
|
||||
]
|
||||
}
|
||||
87
evidence/2026-07-20-mushu-alpha8-production.json
Normal file
87
evidence/2026-07-20-mushu-alpha8-production.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
107
evidence/2026-07-20-opencode-alpha8-shared-client-ci.json
Normal file
107
evidence/2026-07-20-opencode-alpha8-shared-client-ci.json
Normal file
@@ -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."
|
||||
]
|
||||
}
|
||||
73
evidence/2026-07-20-travelwolf-alpha7-rollback.json
Normal file
73
evidence/2026-07-20-travelwolf-alpha7-rollback.json
Normal file
@@ -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."
|
||||
}
|
||||
}
|
||||
84
evidence/2026-07-20-travelwolf-alpha8-production.json
Normal file
84
evidence/2026-07-20-travelwolf-alpha8-production.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ module OpenCodeCompat
|
||||
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"
|
||||
DEGRADED_BOOTSTRAP_ACKNOWLEDGEMENT =
|
||||
"accept-degraded-rollback-with-failed-emergency-provenance"
|
||||
NON_CERTIFIABLE_TUPLE_STATUSES = %w[observed-production-contract-failed].freeze
|
||||
TUPLE_METADATA_KEYS = %w[
|
||||
certification
|
||||
@@ -30,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)
|
||||
@@ -82,6 +104,37 @@ module OpenCodeCompat
|
||||
end
|
||||
end
|
||||
|
||||
def bootstrap_current(consumer:, consumer_commit:, certification:, acknowledgement:, dry_run: false)
|
||||
validate_full_commit!(consumer_commit, "consumer commit")
|
||||
unless acknowledgement == DEGRADED_BOOTSTRAP_ACKNOWLEDGEMENT
|
||||
raise PromotionError,
|
||||
"bootstrap requires explicit acknowledgement #{DEGRADED_BOOTSTRAP_ACKNOWLEDGEMENT.inspect}"
|
||||
end
|
||||
|
||||
if dry_run
|
||||
manifest = read_manifest
|
||||
return bootstrap_current_manifest(
|
||||
manifest,
|
||||
consumer: consumer,
|
||||
consumer_commit: consumer_commit,
|
||||
certification: certification,
|
||||
acknowledgement: acknowledgement
|
||||
)
|
||||
end
|
||||
|
||||
with_current_manifest_lock do |manifest|
|
||||
bootstrapped = bootstrap_current_manifest(
|
||||
manifest,
|
||||
consumer: consumer,
|
||||
consumer_commit: consumer_commit,
|
||||
certification: certification,
|
||||
acknowledgement: acknowledgement
|
||||
)
|
||||
atomic_write(bootstrapped)
|
||||
bootstrapped
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def read_manifest
|
||||
@@ -124,7 +177,55 @@ module OpenCodeCompat
|
||||
consumer_entry["previous"] = certified_previous
|
||||
consumer_entry["current"] = certified_candidate
|
||||
consumer_entry["candidate"] = nil
|
||||
manifest["migration_state"] = all_consumers_certified?(manifest) ? "certified" : "candidate"
|
||||
consumer_entry.delete("rollback_state")
|
||||
refresh_promotion_state!(manifest)
|
||||
manifest
|
||||
end
|
||||
|
||||
def bootstrap_current_manifest(manifest, consumer:, consumer_commit:, certification:, acknowledgement:)
|
||||
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 retain as emergency provenance"
|
||||
end
|
||||
validate_tuple!(current, "#{consumer} current")
|
||||
unless NON_CERTIFIABLE_TUPLE_STATUSES.include?(current["status"])
|
||||
raise PromotionError, "degraded bootstrap is only for a current tuple known to fail the contract"
|
||||
end
|
||||
unless consumer_entry["previous"].nil?
|
||||
raise PromotionError, "degraded bootstrap cannot replace an existing previous tuple"
|
||||
end
|
||||
if consumer_entry.key?("emergency_provenance") || consumer_entry.key?("rollback_state")
|
||||
raise PromotionError, "degraded bootstrap has already been recorded for this consumer"
|
||||
end
|
||||
|
||||
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 failed current tuple"
|
||||
end
|
||||
|
||||
certified_candidate = certify_tuple!(
|
||||
candidate,
|
||||
consumer: consumer,
|
||||
profile: profile,
|
||||
supplied: certification,
|
||||
expected_fingerprint: candidate_fingerprint,
|
||||
label: "bootstrap candidate"
|
||||
)
|
||||
|
||||
consumer_entry["emergency_provenance"] = deep_copy(current)
|
||||
consumer_entry["current"] = certified_candidate
|
||||
consumer_entry["candidate"] = nil
|
||||
consumer_entry["previous"] = nil
|
||||
consumer_entry["rollback_state"] = {
|
||||
"status" => "degraded-no-certified-previous",
|
||||
"acknowledgement" => acknowledgement,
|
||||
"recorded_at" => certification.fetch("certified_at"),
|
||||
"emergency_provenance_status" => current.fetch("status")
|
||||
}
|
||||
refresh_promotion_state!(manifest)
|
||||
manifest
|
||||
end
|
||||
|
||||
@@ -236,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,
|
||||
@@ -266,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,
|
||||
@@ -274,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
|
||||
@@ -302,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
|
||||
@@ -321,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)
|
||||
@@ -366,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
|
||||
|
||||
@@ -382,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)
|
||||
|
||||
@@ -455,6 +730,61 @@ module OpenCodeCompat
|
||||
end
|
||||
end
|
||||
|
||||
def all_consumers_bootstrapped?(manifest)
|
||||
manifest.fetch("consumers").all? do |consumer, entry|
|
||||
next false unless entry["candidate"].nil? && entry["previous"].nil?
|
||||
next false unless entry.dig("rollback_state", "status") == "degraded-no-certified-previous"
|
||||
next false unless NON_CERTIFIABLE_TUPLE_STATUSES.include?(entry.dig("emergency_provenance", "status"))
|
||||
|
||||
profile = fetch_profile!(entry, consumer)
|
||||
tuple = entry["current"]
|
||||
next false unless tuple.is_a?(Hash) && tuple["status"] == "certified"
|
||||
|
||||
validate_tuple!(tuple, "#{consumer} current")
|
||||
validate_recorded_certification!(
|
||||
tuple,
|
||||
consumer: consumer,
|
||||
profile: profile,
|
||||
expected_fingerprint: tuple_fingerprint(tuple, consumer: consumer, profile: profile),
|
||||
label: "#{consumer} current"
|
||||
)
|
||||
true
|
||||
rescue PromotionError
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def migration_state_for(manifest)
|
||||
return "certified" if all_consumers_certified?(manifest)
|
||||
return "bootstrap-current-only" if all_consumers_bootstrapped?(manifest)
|
||||
|
||||
"candidate"
|
||||
end
|
||||
|
||||
def refresh_promotion_state!(manifest)
|
||||
manifest["migration_state"] = migration_state_for(manifest)
|
||||
manifest["promotion_readiness"] = case manifest.fetch("migration_state")
|
||||
when "certified"
|
||||
{
|
||||
"status" => "certified",
|
||||
"reason" => "Every consumer has exact current and previous passing tuples.",
|
||||
"required_action" => "Promote only a newly certified, materially changed tuple."
|
||||
}
|
||||
when "bootstrap-current-only"
|
||||
{
|
||||
"status" => "bootstrap-current-only",
|
||||
"reason" => "Every current tuple is certified, but no independently passing previous tuple exists yet.",
|
||||
"required_action" => "Certify the next meaningful release so normal promotion retains the current tuple as previous."
|
||||
}
|
||||
else
|
||||
{
|
||||
"status" => "candidate",
|
||||
"reason" => "At least one consumer transition remains incomplete.",
|
||||
"required_action" => "Finish exact tuple certification without treating failed emergency provenance as rollback evidence."
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def with_current_manifest_lock
|
||||
loop do
|
||||
retry_with_new_inode = false
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"candidate": {
|
||||
"status": "compatibility-certified",
|
||||
"consumer_commit": "96552893baccd5ccba1592b2585edfef1299b4f9",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"previous": null
|
||||
"rollback_operability": {
|
||||
"source_archived_on_all_remotes": true,
|
||||
"prebuilt_application_image_retained": false,
|
||||
"application_rebuild_required": true
|
||||
},
|
||||
"travelwolf": {
|
||||
"profile": "rails-persisted-turn",
|
||||
"current": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"candidate": {
|
||||
"status": "compatibility-certified",
|
||||
"consumer_commit": "19747b59fa21e12560afd9edd73ed674ab3a644c",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"previous": null
|
||||
"source_availability": {
|
||||
"origin_ref": "refs/heads/review/mushu-opencode-alpha7-consumer-final-20260719",
|
||||
"backup_ref": "refs/heads/review/mushu-opencode-alpha7-consumer-final-20260719"
|
||||
},
|
||||
"mushu": {
|
||||
"profile": "ruby-rest-sse",
|
||||
"current": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ parser = OptionParser.new do |opts|
|
||||
--status pass --certified-at TIMESTAMP --evidence evidence/FILE.json \\
|
||||
[--previous-status pass --previous-certified-at TIMESTAMP \\
|
||||
--previous-evidence evidence/FILE.json] [--dry-run]
|
||||
ruby scripts/promote_runtime_tuple.rb bootstrap-current --consumer NAME --consumer-commit SHA \\
|
||||
--status pass --certified-at TIMESTAMP --evidence evidence/FILE.json \\
|
||||
--acknowledge-degraded-rollback \\
|
||||
accept-degraded-rollback-with-failed-emergency-provenance [--dry-run]
|
||||
USAGE
|
||||
|
||||
opts.on("--consumer NAME") { |value| options["consumer"] = value }
|
||||
@@ -30,6 +34,9 @@ parser = OptionParser.new do |opts|
|
||||
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("--acknowledge-degraded-rollback VALUE") do |value|
|
||||
options["degraded_rollback_acknowledgement"] = value
|
||||
end
|
||||
opts.on("--dry-run") { options["dry_run"] = true }
|
||||
end
|
||||
|
||||
@@ -94,8 +101,42 @@ begin
|
||||
"migration_state" => promoted.fetch("migration_state")
|
||||
)
|
||||
end
|
||||
when "bootstrap-current"
|
||||
required!(
|
||||
options,
|
||||
"consumer",
|
||||
"consumer_commit",
|
||||
"status",
|
||||
"certified_at",
|
||||
"evidence",
|
||||
"degraded_rollback_acknowledgement"
|
||||
)
|
||||
bootstrapped = promoter.bootstrap_current(
|
||||
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")
|
||||
},
|
||||
acknowledgement: options.fetch("degraded_rollback_acknowledgement"),
|
||||
dry_run: options.fetch("dry_run", false)
|
||||
)
|
||||
if options.fetch("dry_run", false)
|
||||
puts JSON.pretty_generate(bootstrapped)
|
||||
else
|
||||
raise OptionParser::InvalidArgument, "command must be fingerprint or promote"
|
||||
consumer = bootstrapped.fetch("consumers").fetch(options.fetch("consumer"))
|
||||
puts JSON.pretty_generate(
|
||||
"consumer" => options.fetch("consumer"),
|
||||
"current_consumer_commit" => consumer.dig("current", "consumer_commit"),
|
||||
"current_tuple_sha256" => consumer.dig("current", "certification", "tuple_sha256"),
|
||||
"previous" => consumer.fetch("previous"),
|
||||
"rollback_state" => consumer.fetch("rollback_state"),
|
||||
"migration_state" => bootstrapped.fetch("migration_state")
|
||||
)
|
||||
end
|
||||
else
|
||||
raise OptionParser::InvalidArgument, "command must be fingerprint, promote, or bootstrap-current"
|
||||
end
|
||||
rescue OpenCodeCompat::PromotionError, OptionParser::ParseError => e
|
||||
warn "error: #{e.message}"
|
||||
|
||||
@@ -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,39 +217,85 @@ 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(
|
||||
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,
|
||||
consumer_commit: evidence.fetch("consumer_commit")
|
||||
profile: profile,
|
||||
expected_fingerprint: fingerprint,
|
||||
label: "#{consumer} #{slot}"
|
||||
)
|
||||
|
||||
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 fingerprints.fetch("profile"), evidence.fetch("profile")
|
||||
assert_equal fingerprints.fetch("candidate_tuple_sha256"), evidence.fetch("tuple_sha256")
|
||||
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
|
||||
|
||||
def test_watcher_has_no_deployment_commands
|
||||
workflow = File.read(File.join(ROOT, ".github/workflows/watch-upstream.yml"))
|
||||
|
||||
@@ -10,14 +10,22 @@ class RuntimeTuplePromoterTest < Minitest::Test
|
||||
CONSUMER = "example"
|
||||
CURRENT_COMMIT = "1" * 40
|
||||
CANDIDATE_COMMIT = "2" * 40
|
||||
NEXT_COMMIT = "7" * 40
|
||||
RUBY_CURRENT = "3" * 40
|
||||
RUBY_CANDIDATE = "4" * 40
|
||||
RUBY_NEXT = "8" * 40
|
||||
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}"
|
||||
CURRENT_TIME = "2026-07-17T12:00:00Z"
|
||||
CANDIDATE_TIME = "2026-07-18T12:00:00Z"
|
||||
NEXT_TIME = "2026-07-19T12:00:00Z"
|
||||
|
||||
def setup
|
||||
@root = Dir.mktmpdir("opencode-compat-promotion")
|
||||
@@ -49,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
|
||||
@@ -121,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")
|
||||
@@ -175,6 +220,163 @@ class RuntimeTuplePromoterTest < Minitest::Test
|
||||
assert_equal before, File.binread(manifest_path)
|
||||
end
|
||||
|
||||
def test_explicit_degraded_bootstrap_preserves_failed_emergency_provenance
|
||||
manifest = valid_manifest
|
||||
manifest.dig("consumers", CONSUMER, "current")["status"] = "observed-production-contract-failed"
|
||||
write_manifest(manifest)
|
||||
candidate_fingerprint = @promoter.fingerprints(
|
||||
consumer: CONSUMER,
|
||||
consumer_commit: CANDIDATE_COMMIT
|
||||
).fetch("candidate_tuple_sha256")
|
||||
evidence = write_evidence(
|
||||
"bootstrap-candidate.json",
|
||||
commit: CANDIDATE_COMMIT,
|
||||
timestamp: CANDIDATE_TIME,
|
||||
fingerprint: candidate_fingerprint
|
||||
)
|
||||
before = File.binread(manifest_path)
|
||||
|
||||
preview = @promoter.bootstrap_current(
|
||||
consumer: CONSUMER,
|
||||
consumer_commit: CANDIDATE_COMMIT,
|
||||
certification: certification(CANDIDATE_TIME, evidence),
|
||||
acknowledgement: OpenCodeCompat::RuntimeTuplePromoter::DEGRADED_BOOTSTRAP_ACKNOWLEDGEMENT,
|
||||
dry_run: true
|
||||
)
|
||||
|
||||
assert_equal before, File.binread(manifest_path)
|
||||
assert_equal "bootstrap-current-only", preview.fetch("migration_state")
|
||||
|
||||
bootstrapped = @promoter.bootstrap_current(
|
||||
consumer: CONSUMER,
|
||||
consumer_commit: CANDIDATE_COMMIT,
|
||||
certification: certification(CANDIDATE_TIME, evidence),
|
||||
acknowledgement: OpenCodeCompat::RuntimeTuplePromoter::DEGRADED_BOOTSTRAP_ACKNOWLEDGEMENT
|
||||
)
|
||||
consumer = bootstrapped.dig("consumers", CONSUMER)
|
||||
|
||||
assert_equal "certified", consumer.dig("current", "status")
|
||||
assert_equal CANDIDATE_COMMIT, consumer.dig("current", "consumer_commit")
|
||||
assert_nil consumer["candidate"]
|
||||
assert_nil consumer["previous"]
|
||||
assert_equal "observed-production-contract-failed", consumer.dig("emergency_provenance", "status")
|
||||
assert_nil consumer.dig("emergency_provenance", "certification")
|
||||
assert_equal "degraded-no-certified-previous", consumer.dig("rollback_state", "status")
|
||||
assert_equal "bootstrap-current-only", bootstrapped.dig("promotion_readiness", "status")
|
||||
assert_equal bootstrapped, JSON.parse(File.read(manifest_path))
|
||||
end
|
||||
|
||||
def test_bootstrap_then_normal_promotion_retains_the_first_passing_tuple_as_previous
|
||||
manifest = valid_manifest
|
||||
manifest.dig("consumers", CONSUMER, "current")["status"] = "observed-production-contract-failed"
|
||||
write_manifest(manifest)
|
||||
bootstrap_fingerprint = @promoter.fingerprints(
|
||||
consumer: CONSUMER,
|
||||
consumer_commit: CANDIDATE_COMMIT
|
||||
).fetch("candidate_tuple_sha256")
|
||||
bootstrap_evidence = write_evidence(
|
||||
"bootstrap-rollback.json",
|
||||
commit: CANDIDATE_COMMIT,
|
||||
timestamp: CANDIDATE_TIME,
|
||||
fingerprint: bootstrap_fingerprint
|
||||
)
|
||||
@promoter.bootstrap_current(
|
||||
consumer: CONSUMER,
|
||||
consumer_commit: CANDIDATE_COMMIT,
|
||||
certification: certification(CANDIDATE_TIME, bootstrap_evidence),
|
||||
acknowledgement: OpenCodeCompat::RuntimeTuplePromoter::DEGRADED_BOOTSTRAP_ACKNOWLEDGEMENT
|
||||
)
|
||||
|
||||
manifest = JSON.parse(File.read(manifest_path))
|
||||
manifest.dig("consumers", CONSUMER)["candidate"] = {
|
||||
"status" => "compatibility-certified",
|
||||
"certified_at" => NEXT_TIME,
|
||||
"consumer_commit" => NEXT_COMMIT,
|
||||
"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(manifest)
|
||||
next_fingerprint = @promoter.fingerprints(
|
||||
consumer: CONSUMER,
|
||||
consumer_commit: NEXT_COMMIT
|
||||
).fetch("candidate_tuple_sha256")
|
||||
next_evidence = write_evidence(
|
||||
"next-current.json",
|
||||
commit: NEXT_COMMIT,
|
||||
timestamp: NEXT_TIME,
|
||||
fingerprint: next_fingerprint
|
||||
)
|
||||
|
||||
promoted = @promoter.promote(
|
||||
consumer: CONSUMER,
|
||||
consumer_commit: NEXT_COMMIT,
|
||||
certification: certification(NEXT_TIME, next_evidence)
|
||||
)
|
||||
consumer = promoted.dig("consumers", CONSUMER)
|
||||
|
||||
assert_equal NEXT_COMMIT, consumer.dig("current", "consumer_commit")
|
||||
assert_equal CANDIDATE_COMMIT, consumer.dig("previous", "consumer_commit")
|
||||
assert_equal "certified", consumer.dig("previous", "status")
|
||||
assert_equal "observed-production-contract-failed", consumer.dig("emergency_provenance", "status")
|
||||
refute consumer.key?("rollback_state")
|
||||
assert_equal "certified", promoted.fetch("migration_state")
|
||||
assert_equal "certified", promoted.dig("promotion_readiness", "status")
|
||||
end
|
||||
|
||||
def test_degraded_bootstrap_requires_exact_acknowledgement
|
||||
error = assert_raises(OpenCodeCompat::PromotionError) do
|
||||
@promoter.bootstrap_current(
|
||||
consumer: CONSUMER,
|
||||
consumer_commit: CANDIDATE_COMMIT,
|
||||
certification: {},
|
||||
acknowledgement: "yes",
|
||||
dry_run: true
|
||||
)
|
||||
end
|
||||
|
||||
assert_match(/explicit acknowledgement/, error.message)
|
||||
end
|
||||
|
||||
def test_degraded_bootstrap_rejects_a_passing_baseline_or_existing_previous_tuple
|
||||
candidate, = write_matching_evidence
|
||||
error = assert_raises(OpenCodeCompat::PromotionError) do
|
||||
@promoter.bootstrap_current(
|
||||
consumer: CONSUMER,
|
||||
consumer_commit: CANDIDATE_COMMIT,
|
||||
certification: certification(CANDIDATE_TIME, candidate),
|
||||
acknowledgement: OpenCodeCompat::RuntimeTuplePromoter::DEGRADED_BOOTSTRAP_ACKNOWLEDGEMENT,
|
||||
dry_run: true
|
||||
)
|
||||
end
|
||||
assert_match(/only for a current tuple known to fail/, error.message)
|
||||
|
||||
manifest = valid_manifest
|
||||
manifest.dig("consumers", CONSUMER, "current")["status"] = "observed-production-contract-failed"
|
||||
manifest.dig("consumers", CONSUMER)["previous"] = manifest.dig("consumers", CONSUMER, "current").dup
|
||||
write_manifest(manifest)
|
||||
candidate_fingerprint = @promoter.fingerprints(
|
||||
consumer: CONSUMER,
|
||||
consumer_commit: CANDIDATE_COMMIT
|
||||
).fetch("candidate_tuple_sha256")
|
||||
evidence = write_evidence(
|
||||
"bootstrap-existing-previous.json",
|
||||
commit: CANDIDATE_COMMIT,
|
||||
timestamp: CANDIDATE_TIME,
|
||||
fingerprint: candidate_fingerprint
|
||||
)
|
||||
error = assert_raises(OpenCodeCompat::PromotionError) do
|
||||
@promoter.bootstrap_current(
|
||||
consumer: CONSUMER,
|
||||
consumer_commit: CANDIDATE_COMMIT,
|
||||
certification: certification(CANDIDATE_TIME, evidence),
|
||||
acknowledgement: OpenCodeCompat::RuntimeTuplePromoter::DEGRADED_BOOTSTRAP_ACKNOWLEDGEMENT,
|
||||
dry_run: true
|
||||
)
|
||||
end
|
||||
assert_match(/cannot replace an existing previous/, error.message)
|
||||
end
|
||||
|
||||
def test_rejects_evidence_that_does_not_match_the_complete_tuple
|
||||
candidate, previous = write_matching_evidence
|
||||
manifest = JSON.parse(File.read(manifest_path))
|
||||
@@ -196,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
|
||||
|
||||
@@ -262,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"}
|
||||
@@ -269,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"}
|
||||
@@ -319,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
|
||||
|
||||
@@ -170,6 +170,10 @@ class WatcherTest < Minitest::Test
|
||||
def test_workflow_can_only_update_manifests_and_open_a_pr
|
||||
workflow = File.read(File.join(ROOT, ".github/workflows/watch-upstream.yml"))
|
||||
|
||||
assert_match(
|
||||
/\n open-compatibility-pr:\n if: github\.server_url == 'https:\/\/github\.com'\n runs-on:/,
|
||||
workflow
|
||||
)
|
||||
assert_includes workflow, "contents: write"
|
||||
assert_includes workflow, "pull-requests: write"
|
||||
assert_includes workflow, "git add manifests/image-matrix.json manifests/upstream.json"
|
||||
|
||||
Reference in New Issue
Block a user