Certify alpha8 production and rollback tuples (#7)
Some checks failed
Candidate compatibility / prepare (push) Successful in 12s
Candidate compatibility / repository (push) Successful in 17s
Candidate compatibility / image ${{ matrix.id }} (push) Has been skipped
Candidate compatibility / fixture-contract (push) Successful in 48s
Candidate compatibility / lockstep clients Ruby 4.0 (push) Successful in 1m0s
Candidate compatibility / lockstep clients Ruby 3.4 (push) Successful in 1m1s
Candidate compatibility / lockstep clients Ruby 3.2 (push) Successful in 1m4s
Candidate compatibility / lockstep clients Ruby 3.3 (push) Successful in 1m43s
Candidate compatibility / Gitea full exact image matrix (push) Successful in 1m48s
Watch upstream OpenCode / open-compatibility-pr (push) Failing after 12s
Some checks failed
Candidate compatibility / prepare (push) Successful in 12s
Candidate compatibility / repository (push) Successful in 17s
Candidate compatibility / image ${{ matrix.id }} (push) Has been skipped
Candidate compatibility / fixture-contract (push) Successful in 48s
Candidate compatibility / lockstep clients Ruby 4.0 (push) Successful in 1m0s
Candidate compatibility / lockstep clients Ruby 3.4 (push) Successful in 1m1s
Candidate compatibility / lockstep clients Ruby 3.2 (push) Successful in 1m4s
Candidate compatibility / lockstep clients Ruby 3.3 (push) Successful in 1m43s
Candidate compatibility / Gitea full exact image matrix (push) Successful in 1m48s
Watch upstream OpenCode / open-compatibility-pr (push) Failing after 12s
* Add explicit rollback bootstrap and maintenance model * Certify alpha8 production and rollback tuples
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user