Skip to content

fix(OMN-15521): remediation round — real rollback retention, seam build-args, digest-verified reload, AC5 restart proof - #2613

Merged
jonahgabriel merged 1 commit into
devfrom
jonah/omn-15521-build-lane
Aug 1, 2026
Merged

jonahgabriel merged 1 commit into
devfrom
jonah/omn-15521-build-lane

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Aug 1, 2026 •

Copy link
Copy Markdown
Collaborator

OMN-15521 — remediation round (5 confirmed defects from the first pass)

Follow-up PR on the same branch (jonah/omn-15521-build-lane) after the
first PR (#2611, merged) shipped the sanctioned .201 gateway-forwarder
deploy path but an independent adversarial verifier found 5 defects in it.
This PR fixes exactly those 5, on the same branch/ticket. It does not touch
AC1-4's core mechanism, which the verifier confirmed held on its own
evidence.

Operator ruling (2026-08-01T17:20Z, cited in #2611): the .201
gateway-forwarder lane is the bridge connecting cloud architecture to .201
for local inference — load-bearing for the beta; instruction was "deploy the
gateway cleanly."

The 5 defects and their fixes

1. AC6 rollback target nominally satisfied but functionally dead (CONFIRMED live).
current_gateway_image_digest() awk'd GATEWAY_IMAGE= out of
gateway.env — a value that can drift from what the container is actually
running — with no existence check, and the previous image was never
retagged before the build moved docker-gateway-forwarder:build onto the
new image, so it became dangling and prune-eligible immediately. Live on
.201: registry.json previous_digest
(sha256:b51b380dfb351ad6740743d8e1e093951d15b67c06e98687ba4308d5e91d6546)
resolves to docker image inspect: No such image.

Fix: resolve_running_container_image() (docker inspect omninode-gateway-forwarder --format '{{.Image}}') replaces the env-file
read as the rollback-target source of truth, and retain_previous_image()
retags that digest as docker-gateway-forwarder:previous before
build_image() runs, so it survives the build tag move. If the previous
image no longer resolves locally (docker tag fails), no rollback target
is recorded rather than a digest docker image inspect can't find.

2. Seam mismatch: 3 sibling-ref build-args silently dropped (CONFIRMED live).
deploy-runtime.sh's build_images() always passes OMNIBASE_COMPAT_REF
/ OMNIMARKET_REF / ONEX_CHANGE_CONTROL_REF; deploy-gateway.sh omitted
all three, so the image fell back to docker/Dockerfile.runtime's
hardcoded ARG defaults (v0.5.5 / dev / v0.5.3). Live on .201:
omninode-gateway-forwarder carries onex-change-control 0.5.3 while
omninode-runtime on the same box carries 0.5.1 — the exact
runtime-parity gap OMN-15625 tracks.

Fix: read_repo_ref_or_main() (mirrors deploy-runtime.sh's helper of the
same name) + all three args added to resolve_build_args().

3. AC5 had zero implementing work (CONFIRMED).
The ticket's AC5 text is "the OMN-12912 restart/redelivery proof
(source-offset-ack / dedupe receipt) is re-run against the newly deployed
forwarder — that receipt lands on OMN-12912, not this ticket" — a filing
instruction, not a scope exclusion. The first pass's PR body and runbook
converted it into "Out of scope per the ticket text," skipping the proof
even though the container was force-recreated in the same session (the one
moment it was cheap to capture).

Fix: scripts/gateway_restart_safety_proof.sh — a real restart-durability
smoke proof (snapshot the running container's idempotency-store row count,
sudo systemctl reload onex-gateway-forwarder, wait for Docker-healthy,
re-snapshot, fail if any durable marker was lost). Ran it live this
session
against the already-deployed omninode-gateway-forwarder
container on .201:

before restart: rows=333 max_processed_at=1785612197.017322
container healthy after restart (not false-green)
after restart:  rows=334 max_processed_at=1785612230.986156
AC5-PROOF OK: idempotency store survived restart (rows 333 -> 334, no durable-marker loss)

Receipt filed on
OMN-12912 per the ticket's
own filing instruction — this PR does not claim AC5 as its own closing
criterion. This proof is a restart-durability smoke test, not the full
cross-broker at-least-once/exactly-once redelivery proof (a deliberately
killed in-flight message never duplicating/dropping on the far side) —
PR #2556 itself scoped that out of live proof, and it still hasn't run
against a real deployed container; that gap is unchanged by this PR.

4. Live evidence was unreproducible (CONFIRMED).
The .201 canonical omnibase_infra clone
(/data/omninode/omni_home/omnibase_infra) was still pre-merge
(beebd1d636b9) and did not have scripts/deploy-gateway.sh at all — the
first pass's "ran against the canonical clone" claim could not be
substantiated.

Fix (this session): pulled the .201 clone to merged dev
(664480335f2d33ae578ce41acb5087d5f6ac9a4, git merge-base --is-ancestor
clean), confirmed scripts/deploy-gateway.sh is byte-identical to git show HEAD:scripts/deploy-gateway.sh (no ephemeral copy), then ran the AC5
proof above against that clone's checked-out state. AC2/AC3/AC4 were not
re-executed live in this round (they already held on the previous
session's independently re-verified evidence per #2611); the code fixes
for defects 1/2/5 are proven by the test suite below, not a live --execute
run — deploying my unmerged branch's code to the shared .201 lab lane
would itself repeat the "ran an unmerged/ephemeral artifact" pattern this
defect flags. They'll get their live proof once this PR merges and .201's
clone advances.

5. verify_deployment() never compared the running digest (CONFIRMED).
It only checked image labels and file presence — both of which a STALE
(already-deployed) container also satisfies. A systemctl reload that
silently fails to recreate the container would report success on every
subsequent redeploy.

Fix: verify_deployment() now takes the new digest as an argument and
fails closed (AC-VERIFY FAILED) if docker inspect ... --format '{{.Image}}' doesn't match it, before checking labels/files.

Bonus fix surfaced by shellcheck during remediation: usage()'s
heredoc used backtick-quoted `docker image prune` inside an unquoted
<<EOF — which performs command substitution on backticks, so running
--help would have executed docker image prune as a side effect
(SC2006). Replaced with single quotes; confirmed the original heredoc had
zero backtick usage anywhere else.

Seam definition (delta from #2611)

Seam Direction Shape
docker inspect <container> --format '{{.Image}}' script reads (NEW) sha256:<64 hex> or empty — replaces the gateway.env GATEWAY_IMAGE= awk read as the rollback-target + post-reload-verification source of truth
docker tag <previous_digest> docker-gateway-forwarder:previous script writes (NEW) retention tag, applied before build_image() runs
--build-arg OMNIBASE_COMPAT_REF=<sha|main> / OMNIMARKET_REF=<sha|dev> / ONEX_CHANGE_CONTROL_REF=<sha|main> script → docker compose build (NEW) resolved from $OMNI_HOME/{omnibase_compat,omnimarket,onex_change_control} HEAD SHAs via read_repo_ref_or_main(), falling back per-repo when the sibling clone or OMNI_HOME is absent
docker exec <container> python3 -c "..." against /app/data/gateway/delivery.sqlite3 new script reads (NEW) SELECT COUNT(*), MAX(processed_at) FROM idempotency_records — read-only, stdlib sqlite3 (no sqlite3 CLI in the runtime image)

Acceptance-criterion → test map (delta only; unchanged ACs keep #2611's map)

AC New/changed test
AC2 (seam) test_build_command_stamps_sibling_ref_build_args
AC5 tests/scripts/test_gateway_restart_safety_proof.py (6 cases) + live receipt on OMN-12912
AC6 test_rollback_target_derived_from_running_container_not_env_file, test_previous_image_retagged_for_retention_before_build, test_rollback_target_not_recorded_if_previous_image_missing, test_first_deploy_records_no_rollback_target
verify-digest (new gate, not a numbered AC but load-bearing) test_verify_deployment_red_before_reload_silently_fails

28/28 tests pass (22 in test_deploy_gateway.py, 6 in the new
test_gateway_restart_safety_proof.py) — subprocess-driven against the real
scripts with fake docker/sudo, same convention as #2611. Each new test is
RED-before/GREEN-after against the specific defect it closes (verified by
running with the pre-fix code before committing the fix).

Notes

  • Gates + push ran on stickybeatz-studio (.200) via patch-transfer with
    sha256 verification of every changed file (gates_host=.200). Governed
    pre-push selector (scripts/tests/ tests/scripts/ tests/unit/scripts/):
    1547 passed / 5 skipped (pre-existing platform-gated skips, unrelated).
  • .201's canonical omnibase_infra clone advanced from beebd1d636b9 to
    664480335f2d (merged dev, includes feat(OMN-15521): sanctioned .201 gateway-forwarder deploy path #2611) as part of closing defect 4 —
    a git pull --ff-only, no commits made there.
  • Never merges — Codex merge sweep owns landing this.

Evidence-Ticket: OMN-15521

Evidence-Source: b40fd17ba390110f6e0e8470e67b90161702241b

…-ref build-args, digest-verified reload, and a real AC5 restart-safety proof

Fixes 5 adversarial-verifier-confirmed defects from the first OMN-15521
pass, on the same branch:

1. AC6 rollback target was nominally satisfied but functionally dead:
   current_gateway_image_digest() awk-ed GATEWAY_IMAGE out of gateway.env
   (which can drift from what is actually running) with no existence
   check, and the previous image was never retagged before the build
   moved BUILD_IMAGE_TAG onto the new image, so it became dangling and
   prune-eligible immediately. Fixed via resolve_running_container_image()
   (docker inspect --format {{.Image}}) + retain_previous_image() (docker
   tag under docker-gateway-forwarder:previous BEFORE build_image()).
   Live on .201: previous registry.json previous_digest
   (sha256:b51b380d...) resolved to "No such image" -- confirmed dead.

2. Seam mismatch: OMNIBASE_COMPAT_REF / OMNIMARKET_REF /
   ONEX_CHANGE_CONTROL_REF -- the three sibling-ref build-args
   deploy-runtime.sh always passes -- were silently dropped, so the
   gateway image fell back to the Dockerfile's hardcoded ARG defaults.
   Live on .201 this showed as onex-change-control 0.5.3 on the gateway
   container vs 0.5.1 on the omnibase-infra runtime container on the same
   box. Fixed via read_repo_ref_or_main() (mirrors deploy-runtime.sh) and
   added to resolve_build_args().

3. AC5 had zero implementing work: the PR/runbook converted the ticket's
   own "that receipt lands on OMN-12912, not this ticket" filing
   instruction into a blanket scope exclusion, skipping the restart proof
   even though the container was force-recreated in the same session.
   Added scripts/gateway_restart_safety_proof.sh -- a real restart-
   durability smoke proof (snapshot idempotency store row count, reload,
   wait-healthy, re-snapshot, assert no marker loss) -- and ran it live
   against the deployed omninode-gateway-forwarder container on .201:
   rows survived 333->334 across a real restart, container returned
   genuinely healthy. Receipt filed on OMN-12912 per the ticket's own
   AC5 wording (this ticket does not claim AC5 as its own closing
   criterion).

4. Live evidence was unreproducible: the .201 canonical omnibase_infra
   clone was still pre-merge (beebd1d) and did not have
   scripts/deploy-gateway.sh at all, so the prior "ran against the
   canonical clone" PR claim could not be substantiated. Pulled the
   .201 clone to merged dev (6644803, confirmed ancestor-clean,
   deploy-gateway.sh byte-identical to git show HEAD:...) before running
   the AC5 proof above.

5. verify_deployment() never compared the running container's actual
   image against the digest it just built -- only checked labels and
   file presence, both of which a STALE container also satisfies after
   the first successful deploy. A systemctl reload that silently fails
   to recreate the container would report success on every subsequent
   redeploy. Fixed: verify_deployment() now takes the new digest and
   fails closed if docker inspect --format {{.Image}} does not match it.

Seam definition (fields/paths read or written across a boundary, delta
from the original PR):
  - docker inspect <container> --format '{{.Image}}' (NEW read) -- the
    rollback-target and post-reload-verification source of truth,
    replacing the gateway.env GATEWAY_IMAGE= awk read.
  - docker tag <previous_digest> docker-gateway-forwarder:previous (NEW
    write) -- retention tag applied before build_image() runs.
  - --build-arg OMNIBASE_COMPAT_REF/OMNIMARKET_REF/ONEX_CHANGE_CONTROL_REF
    (NEW writes to the build command) -- resolved from OMNI_HOME sibling
    clone HEAD SHAs via read_repo_ref_or_main(), falling back to
    main/dev/main.

Tests: 12 new/changed cases across tests/scripts/test_deploy_gateway.py
(rollback-from-running-container RED/GREEN, retention-tag-before-build
ordering, missing-image fail-closed, first-deploy no-target, sibling-ref
build-args, digest-verify RED-before-reload-silently-fails) and a new
tests/scripts/test_gateway_restart_safety_proof.py (6 cases: green
survival, red data-loss, red never-healthy, empty-store warning) -- all
subprocess-driven against the real scripts with fake docker/sudo, same
convention as the original PR. 28/28 pass locally and on .200.

Also fixes a latent bug the remediation surfaced: usage()'s heredoc used
backtick-quoted `docker image prune` inside an unquoted <<EOF heredoc,
which would have executed docker image prune as a side effect of running
--help (shellcheck SC2006 caught it; confirmed zero backtick usage in the
original heredoc).

Evidence-Ticket: OMN-15521
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5ccadc5c-bce8-46c6-bfd9-0dcf9dc46fe1

📥 Commits

Reviewing files that changed from the base of the PR and between e236010 and da95994.

📒 Files selected for processing (5)
  • docs/runbooks/gateway-lane-deploy.md
  • scripts/deploy-gateway.sh
  • scripts/gateway_restart_safety_proof.sh
  • tests/scripts/test_deploy_gateway.py
  • tests/scripts/test_gateway_restart_safety_proof.py

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

⚠️ Hostile Reviewer — DEGRADED (informational)

Blocking findings (critical): 0
Total findings: 0
Models succeeded: none

Note: All reviewer models failed or were unavailable. Degraded results are informational during the pilot phase (OMN-8468/OMN-8524) and do not block merge. Error: all review endpoints [192.168.86.201:8000 192.168.86.201:8001 ] unreachable — preflight short-circuit (no models available)


Gate semantics (pilot phase)

Verdict Meaning Blocks merge?
passed No critical findings No
blocked CRITICAL findings found Yes
degraded All models unavailable (infra) No (pilot)

Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8468/OMN-8524)

jonahgabriel added a commit to OmniNode-ai/onex_change_control that referenced this pull request Aug 1, 2026
#5872)

* evidence: OCC companion pass 1 for OmniNode-ai/omnibase_infra#2613

* evidence: OCC companion self-bind for #5872

* fix(OMN-15521): repair OCC companion append-only evidence

---------

Co-authored-by: node-occ-companion-effect <occ-companion-effect@omninode.ai>
Co-authored-by: Jonah Gray <jonah@omninode.ai>
@jonahgabriel
jonahgabriel merged commit 72d27ef into dev Aug 1, 2026
244 of 261 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-15521-build-lane branch August 1, 2026 20:22
jonahgabriel added a commit that referenced this pull request Aug 1, 2026
…e-mode staging, and a genuine AC5 restart-recreate proof (#2614)

Fixes four defects an adversarial verifier found in the round-2 build (PR #2613, already merged to dev):

1. registry.json rollback_command jq bug (deploy-gateway.sh write_registry): the
   previous_digest interpolation used jq default operator (//) against an empty
   STRING, which never falls through, so a first deploy / pruned-previous-image
   deploy emitted a rollback_command with an EMPTY GATEWAY_IMAGE= value instead
   of null. Running that command would have sed-corrupted gateway.env, wedging
   the systemd unit ExecStartPre digest-format assertion on the next restart.
   Fixed with an explicit if/else in the jq filter, matching previous_digests
   own null semantics. Runbook rollback recipe now reads rollback_command
   directly instead of reconstructing it from previous_digest via jq -r (which
   prints the literal string null on a JSON null).

2. BUILD_SOURCE=workspace never invoked stage_workspace.sh (unlike
   deploy-runtime.sh, which always calls stage_workspace_if_needed() before
   build_images()). docker/Dockerfile.runtime unconditionally COPYs
   workspace/sibling-repos/, so a workspace-mode build silently used the
   committed placeholder / stale staging while still stamping
   workspace-provenance labels. Added stage_workspace_if_needed() +
   check_sibling_lock_pins(), invoking the SAME scripts/runtime_build/
   stage_workspace.sh + check_sibling_lock_pins.py deploy-runtime.sh uses, in
   main() before build_image().

3. gateway_restart_safety_proof.sh false-green: a fake sudo that exits 0 and
   does nothing (never actually reloads) satisfied both the health check and
   the row-count check, since neither compares container identity across the
   restart. Added container_identity() (Id + StartedAt) captured before/after
   and asserted to CHANGE, and require_reachable() so an unreachable container
   fails loudly instead of snapshot()s own exception handler silently
   collapsing to 0\t0 (which made the after<before durability check vacuous).

4. AC2/AC3/AC4/AC6 live evidence on .201 came from the round-2 (pre-this-fix)
   deploy — this PR re-runs the fixed script against .201 and captures fresh
   readback (see PR body).

Seam: registry.json rollback_command (string|null) and previous_digest
(string|null) must agree on presence; gateway_restart_safety_proof.sh
container_identity() format {{.Id}}|{{.State.StartedAt}} vs deploy-gateway.shs
own verify_deployment() digest-only check (different signal, same container).

Tests: RED-before/GREEN-after for all four, driven via subprocess against the
real scripts with docker/sudo faked (docker/tests/scripts/test_deploy_gateway.py,
tests/scripts/test_gateway_restart_safety_proof.py). 34/34 passed on .200.

OMN-15521
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant