Skip to content

test(OMN-15744): golden and error chain tests for the MSK SNI bastion path - #3418

Merged
jonahgabriel merged 2 commits into
devfrom
jonah/omn-15744-bastion-chain-canary
Sep 11, 2026
Merged

jonahgabriel merged 2 commits into
devfrom
jonah/omn-15744-bastion-chain-canary

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator

Adds the golden and error chain tests for the lab to MSK SNI bastion to MSK path. Companion to OmniNode-ai/omninode_infra#1313, which fixed the map itself and put it under source control.

Ticket: OMN-15744. Requested by the operator on 2026-09-10, whose point was that this path had no mechanical proof at all.

Why nothing caught the original defect

MSK omninode-dev-msk scaled 2 to 6 brokers on 2026-08-06. The bastion's map still described the 2-broker cluster and carried default b1, so four of six brokers were mis-routed onto broker 1 for a month.

The reason it survived is the design constraint for any test here: bootstrap and metadata are served by any broker, so every liveness surface stayed green. Only leader-routed requests failed, and they failed with NotLeaderForPartition, which reads as transient Kafka noise. Eight of 21 tenant topic-partitions were silently unwritable, including delegation-request, delegation-completed and delegation-failed. It was found by hand during an unrelated diagnosis.

The three tests

All in tests/integration/bastion/test_msk_sni_bastion_chain.py.

  • test_bastion_golden_every_broker_is_reachable_and_distinct — every live broker must answer as itself through the map. It discriminates on the broker's own error code: a leader-routed offsets request returns 0 from the partition leader and 6 from any other broker, so the answer reports the responder's identity. Emits a per-broker table as the evidence artifact and asserts its own positive control, so a run where every broker errored cannot pass on the not-the-leader rows alone.
  • test_bastion_error_chain_unmapped_sni_fails_closed — an SNI the map does not carry must fail at connect. The original defect was exactly that an unknown name still got a working session from the wrong broker, which made a misconfiguration indistinguishable from health. A mapped SNI must succeed in the same run, so a bastion that is simply down cannot read as a green error chain.
  • test_bastion_map_covers_every_live_broker — every broker the MSK control plane reports must be routable, with an unmapped name required to be unroutable so the claim is not vacuous. This is the one that turns red on the next resize, within one scheduled run.

Two designs rejected, on measurement rather than taste

A TLS-only test. The cheap version is "handshake against each broker SNI". It is worthless here and I verified that rather than reasoning about it: all six broker SNIs present one wildcard certificate, sha256 prefix e4224daa249fdb90, byte-identical across b-1 through b-6. A handshake test cannot tell which broker answered, so it would have been green for the entire month-long outage. Any test that cannot identify the responder does not test this defect.

"Produce one record to a topic led by each of the six brokers and read it back." This was the requested shape and it is not achievable, for two reasons established against the live system. No visible topic was led by broker 4 at census time. More fundamentally, the sanctioned topic-creation path governs replication factor only; nothing in this codebase can request or influence partition leader placement, so a topic created in the hope of landing on broker 4 would sit wherever the cluster chose and the test would be flaky by construction. The error-code discriminator covers all six brokers deterministically instead, with no topic creation. It is the stronger test, not a weaker substitute.

Broker 4 is genuinely covered. In the live run below the reference partition happened to be led by broker 4, so broker 4 was exercised as the leader returning 0 while the other five returned 6.

Live run

Executed against the live bastion and MSK cluster from a host with tailnet reach, 2026-09-10.

tests/integration/bastion/test_msk_sni_bastion_chain.py::test_bastion_golden_every_broker_is_reachable_and_distinct
reference partition: tenant-ac-exec-16027-probe.onex.cmd.omnibase-infra.delegation-inference-request.v1[0], leader=4
broker                        answer  verdict
     1                             6  PASS
     2                             6  PASS
     3                             6  PASS
     4                             0  PASS
     5                             6  PASS
     6                             6  PASS
PASSED
tests/integration/bastion/test_msk_sni_bastion_chain.py::test_bastion_error_chain_unmapped_sni_fails_closed
  POSITIVE CONTROL mapped   b-1...: handshake completed
  UNDER TEST       unmapped b-99...: ConnectionResetError: [Errno 54] Connection reset by peer
PASSED
tests/integration/bastion/test_msk_sni_bastion_chain.py::test_bastion_map_covers_every_live_broker
  control plane reports 6 broker(s)
    broker 1..6: routable
PASSED

3 passed in 13.32s

An earlier run of the golden test failed on a connect timeout, and that was my harness rather than the routing: the broker DNS names resolve to VPC-private addresses no external host can reach, while the deployed gateway pins them to the bastion with a dnsmasq sidecar. The test now pins resolution to the bastion for the duration of the block, which is what makes it measure the bastion's routing instead of the caller's DNS. The reasoning is recorded at the helper.

Scheduling

.github/workflows/msk-bastion-canary.yml, hourly at :17, on the self-hosted omnibase-deploy runner. That is the only runner with reach to the bastion, the same fate boundary chain-canary.yml and dev-lane-liveness.yml already document. Offset from the chain canary's :41 so the two do not contend for the single runner. The job is read-only by design: no produce and no topic creation against tenant data on a schedule, unlike the chain canary which publishes a real delegation command.

Expect the first scheduled runs to be red, and read the message before fixing it. The omnibase-deploy runner carries no AWS identity today, and these tests need one. The workflow header names exactly what must be provisioned: kafka:ListClustersV2 and kafka:ListNodes for all three tests, plus an MSK data-plane identity for the golden one. The lab host already has a working data-plane identity in the gateway's IAM Roles Anywhere profile, so referencing it is configuration rather than a new credential, and minting a second identity would be the wrong move.

I deliberately did not provision it. That is a credential action and belongs to whoever owns credentials, not to the lane that found a routing bug. Landing a knowingly-red canary that names its own blocker follows the precedent this repo set with chain-canary.yml, and is the honest alternative to a silent skip that reports green over zero tests.

Guard

tests/ci/test_msk_bastion_canary_workflow.py runs on every PR and makes defanging the canary a red test rather than a review catch: a dropped schedule, a runner that cannot see the bastion, continue-on-error, a leg no longer invoked, a missing marker that would make the selection silently match nothing, a produce creeping into a scheduled cloud job, or the nightly exclusion being removed.

The new bastion marker is excluded from nightly-integration.yml, which runs tests/integration/ on a runner with neither AWS credentials nor bastion reach. Collecting them there would produce a permanent red that people learn to skim past, which is worse than not running them.

Local verification: ruff and mypy --strict clean on all new files, all 56 applicable pre-commit hooks pass, the 7 guard tests pass, and the 3 chain tests pass live as shown above.

Evidence-Ticket: OMN-15744
Evidence-Source: OCC#9000

… path

Nothing watched the lab -> SNI bastion -> MSK routing path, which is why a
2 -> 6 broker scale-out on 2026-08-06 left four of six brokers mis-routed for
a month. Bootstrap and metadata are served by any broker, so every liveness
surface stayed green; only leader-routed requests failed, with
NotLeaderForPartition, which reads as transient noise. It was found by hand
during an unrelated diagnosis. These tests replace that luck.

Three tests in tests/integration/bastion:

  golden — every live broker answers AS ITSELF through the map. Discriminates
    on the broker's own error code: a leader-routed offsets request returns 0
    from the partition leader and 6 from any other broker, so the answer
    reports the responder's identity. Carries its own positive control.

  error — an SNI the map does not carry must fail at connect, not silently
    reach a real broker. The original defect was precisely that an unknown
    name got a working session from the wrong broker. A mapped SNI must
    succeed in the same run, so a down bastion cannot read as a pass.

  resize detector — every broker the MSK control plane reports must be
    routable, with an unmapped name required to be unroutable so the claim is
    not vacuous. This is the test that turns red on the next resize.

A TLS-only test was considered and rejected on measurement, not taste: all six
broker SNIs present ONE wildcard certificate (sha256[:16] e4224daa249fdb90),
so a handshake test cannot tell which broker answered and would have been
green throughout the outage.

"Produce to a topic led by each of the six brokers" was the first design and is
not achievable: the sanctioned topic path governs replication factor only and
nothing here can influence leader placement, so such a topic would land
wherever the cluster chose. The error-code discriminator covers all six
brokers deterministically instead, broker 4 included.

Scheduled hourly by msk-bastion-canary.yml on the self-hosted omnibase-deploy
runner, the only runner with reach to the bastion. Read-only by design: no
produce and no topic creation against tenant data on a schedule.

tests/ci/test_msk_bastion_canary_workflow.py guards the canary against being
defanged — dropped schedule, wrong runner, continue-on-error, legs no longer
invoked, or a produce creeping in. The bastion marker is excluded from the
nightly sweep, which runs where the bastion is unreachable and would otherwise
go permanently red.
@github-actions

github-actions Bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Hostile Reviewer — DEGRADED (informational)

Critical findings: 0
Major findings: 0
Total findings: 0
Models succeeded: glm-review

Note: Fewer than 2 reviewer models succeeded. Degraded results are informational (OMN-8468/OMN-8524) and do not block merge. Error: cli_review exit 2 (fewer than 2 models succeeded — partial/total outage)


Semantics (OMN-17492 — the model finds, thread resolution gates)

Surface Meaning Blocks merge?
Review threads Per-finding, posted by the reviewer No (informational)
Hostile Review Thread Gate Deterministic: unresolved hostile-reviewer threads exist Fails until resolved (not yet a required context)
degraded verdict Fewer than 2 models succeeded (infra) No

Powered by omniintelligence.review_pairing.cli_review — multi-model adversarial review: qwen3-review, qwen3-review-b, glm-review (OMN-8468/OMN-8524/OMN-17492)

@jonahgabriel
jonahgabriel marked this pull request as draft September 11, 2026 00:05
jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 11, 2026
…ibase_infra#3418 (#9000)

* evidence(OMN-15744): author OCC companion for OmniNode-ai/omnibase_infra#3418

OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head 485784a0ec507e19ca9e902eae252cc45a111e6a.

* evidence(OMN-15744): self-bind OCC#9000 + rebind contract_sha256

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hostile Reviewer — adversarial findings (OMN-17492)

Models succeeded: glm-review
Models failed: codex
New finding threads: 2
Deduped (already posted on this PR): 0
Nit-level findings suppressed: 2

The model is the FINDER, never the gate: merge is gated only by the
deterministic Hostile Review Thread Gate, which blocks while
hostile-reviewer threads are unresolved. Resolve each thread after
addressing (or rejecting, with a reply) its finding.

Findings not anchored to a changed file

  • [CRITICAL] hostile-reviewer (glm-review)

    Golden test cannot discriminate non-leader broker identity | The discriminator rests entirely on error code 6 (NotLeaderForPartition), which is returned by EVERY broker that is not the partition leader. Under the exact stale-map defect this canary exists to detect, SNI b-3..b-6 routed to the wrong non-leader broker still yields error 6. The golden test only detects misrouting when the leader's own SNI lands on a non-leader (or vice versa). For a reference partition led by broker L, the other five SNIs are i

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MAJOR] hostile-reviewer (glm-review)

    TLS certificate verification disabled in _tls_probe | ssl.CERT_NONE with check_hostname=False disables all server identity verification. The test then asserts a security property (fail-closed routing) over a channel where the endpoint is unauthenticated: any host that can intercept traffic to the bastion IP can satisfy the mapped-SNI positive control and complete the handshake. The Resize Detector and Error Chain tests derive their entire evidentiary value from the peer's identity, yet never verify it. Addi

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MAJOR] hostile-reviewer (glm-review)

    Global getaddrinfo monkeypatch is not concurrency-safe | _dns_pinned_to_bastion replaces socket.getaddrinfo process-wide. aiokafka resolves via loop.getaddrinfo, which executes the patched callable in the default executor's worker threads. The patch-and-restore is not atomic against other coroutines or threads resolving concurrently within the same process, and if the golden test is ever run alongside other async tests in one process, the window between patch and restore leaks pinned resolution to unrelated

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Reference partition selection is a single point of discrimination | _pick_reference_partition returns the FIRST tenant partition with any live leader. The golden test's discriminating power for the leader leg is then a function of which broker happens to lead that one partition. Combined with the finding above, a run can exercise the error-0 path on only one of up to five leader-bearing brokers, and which one varies per run, making the canary's coverage nondeterministic and partially redundant with itself a

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Reliance on private aiokafka API fetch_all_metadata | consumer._client.fetch_all_metadata() reaches into aiokafka internals. Any aiokafka version bump in uv.lock can rename or remove _client, turning the canary red with an AttributeError that reads as a bastion failure, exactly the alert fatigue the workflow commentary warns against. | Evidence: metadata = await consumer._client.fetch_all_metadata() | Fix: Use the public describe_topics / partitions_for_topic consumer APIs, or pin and document the aiokafka

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Marker-carrying check matches docstrings and comments | test_the_chain_tests_exist_and_carry_their_markers verifies "pytest.mark.integration" via raw substring on the module source. A docstring mentioning the marker, or a move to pytestmark where integration is commented out, satisfies the check. The same applies to the def-name checks, which break on any formatting change (e.g., line wrapping in the signature) and produce a confusing 'was removed or renamed' failure. | Evidence: assert "pytest.mark.integra

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Transport failure of non-leader SNIs is indistinguishable from correct behavior in table output only | _leader_discriminating_error returns a string on transport failure. In the golden test the string fails the verdict correctly, but the misrouted assertion message assumes error-code semantics ('A broker answering 0 when it is not the leader'), which will not describe a timeout-shaped failure. Operators following the remediation text will regenerate the map for what may be a network problem on the runner. |

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

Findings demoted from threads (anchor rejected)

  • [MINOR] hostile-reviewer (glm-review)

    Workflow guard asserts on raw substring, trivially satisfiable | test_canary_actually_invokes_both_chains joins all run blocks into one string and checks for selector substrings. A future edit that moves the selectors into a matrix variable, an env block, or a shell script invoked by name passes the guard while running nothing. Similarly, test_nightly_integration_sweep_excludes_the_bastion_tests greps for 'not bastion' anywhere in the file, including a comment, so the actual pytest selector could be removed

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Hardcoded bastion IP contradicts the file's own anti-staleness rationale | The module argues at length that hardcoded broker lists are 'the exact artifact that went stale and caused this outage,' then ships a hardcoded tailscale IP as the default for MSK_SNI_BASTION_HOST. A bastion readdress is invisible to every test here; all three legs would report identical connection failures that read as 'bastion down' rather than 'stale config,' and the resize detector's failure message would actively mislead. | Evid

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

Adding the `bastion` pytest marker to pyproject.toml changed a component the
runner image identity binds, so tests/ci/test_runner_image_identity.py went
red. Regenerated with scripts/ci/runner_image_identity.py --mode generate.
Identity v7 d2e218e9a49625aad7918e6592d1f11c.
@jonahgabriel
jonahgabriel marked this pull request as ready for review September 11, 2026 00:54
@jonahgabriel
jonahgabriel enabled auto-merge (squash) September 11, 2026 00:54
@jonahgabriel
jonahgabriel merged commit 24b6827 into dev Sep 11, 2026
243 of 274 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-15744-bastion-chain-canary branch September 11, 2026 01:54
Patel230 pushed a commit that referenced this pull request Sep 16, 2026
… path (#3418)

* test(OMN-15744): golden and error chain tests for the MSK SNI bastion path

Nothing watched the lab -> SNI bastion -> MSK routing path, which is why a
2 -> 6 broker scale-out on 2026-08-06 left four of six brokers mis-routed for
a month. Bootstrap and metadata are served by any broker, so every liveness
surface stayed green; only leader-routed requests failed, with
NotLeaderForPartition, which reads as transient noise. It was found by hand
during an unrelated diagnosis. These tests replace that luck.

Three tests in tests/integration/bastion:

  golden — every live broker answers AS ITSELF through the map. Discriminates
    on the broker's own error code: a leader-routed offsets request returns 0
    from the partition leader and 6 from any other broker, so the answer
    reports the responder's identity. Carries its own positive control.

  error — an SNI the map does not carry must fail at connect, not silently
    reach a real broker. The original defect was precisely that an unknown
    name got a working session from the wrong broker. A mapped SNI must
    succeed in the same run, so a down bastion cannot read as a pass.

  resize detector — every broker the MSK control plane reports must be
    routable, with an unmapped name required to be unroutable so the claim is
    not vacuous. This is the test that turns red on the next resize.

A TLS-only test was considered and rejected on measurement, not taste: all six
broker SNIs present ONE wildcard certificate (sha256[:16] e4224daa249fdb90),
so a handshake test cannot tell which broker answered and would have been
green throughout the outage.

"Produce to a topic led by each of the six brokers" was the first design and is
not achievable: the sanctioned topic path governs replication factor only and
nothing here can influence leader placement, so such a topic would land
wherever the cluster chose. The error-code discriminator covers all six
brokers deterministically instead, broker 4 included.

Scheduled hourly by msk-bastion-canary.yml on the self-hosted omnibase-deploy
runner, the only runner with reach to the bastion. Read-only by design: no
produce and no topic creation against tenant data on a schedule.

tests/ci/test_msk_bastion_canary_workflow.py guards the canary against being
defanged — dropped schedule, wrong runner, continue-on-error, legs no longer
invoked, or a produce creeping in. The bastion marker is excluded from the
nightly sweep, which runs where the bastion is unreachable and would otherwise
go permanently red.

* chore(OMN-15744): regenerate runner image identity lock

Adding the `bastion` pytest marker to pyproject.toml changed a component the
runner image identity binds, so tests/ci/test_runner_image_identity.py went
red. Regenerated with scripts/ci/runner_image_identity.py --mode generate.
Identity v7 d2e218e9a49625aad7918e6592d1f11c.
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