Repository navigation
feat(OMN-15750): gateway attach/session control-plane effect node (G6) - #2694
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 16 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
📝 WalkthroughWalkthroughAdded a contract-driven gateway attach effect node. It validates tokens, stores tenant-bound sessions, handles attach, heartbeat, and detach operations, emits lifecycle events, and adds a dry-run proof harness with Docker Compose wiring. ChangesGateway attach control plane
Estimated code review effort: 4 (Complex) | ~60 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docker/docker-compose.gateway-attach-test-lane.yml`:
- Line 42: Remove the GATEWAY_ATTACH_TEST_LANE_BOOTSTRAP_SERVERS argument from
the edge-side connector configuration in the gateway attach test-lane setup.
Update the proof harness to use the gateway ingress API for edge requests, while
retaining Kafka bootstrap configuration only within infrastructure-side
components.
In `@scripts/proof/gateway_attach_e2e_proof.py`:
- Around line 146-152: Add an explicit stage_detach() call in run_live after the
active heartbeat and before optional revocation, register that stage in
STAGE_SURFACES, and make it consume detach_request_topic while asserting
DETACHED state and session removal.
In
`@src/omnibase_infra/nodes/node_gateway_attach_effect/handlers/handler_gateway_attach.py`:
- Around line 51-57: Update the handler constructors to accept
ModelONEXContainer instead of direct configuration and service dependencies,
resolving required values with container.get_service(...). In
handler_gateway_attach.py lines 51-57, resolve the configuration and
ProtocolGatewaySessionStore; in handler_gateway_detach.py lines 39-40, resolve
ProtocolGatewaySessionStore; and in handler_gateway_heartbeat.py lines 53-61,
resolve the configuration, ProtocolGatewaySessionStore, and secret resolver.
Update stored fields and constructor call sites as needed while preserving
handler behavior.
- Around line 72-91: Validate claims.expires_at_epoch before calculating TTL or
constructing ModelGatewaySession, and reject the request when the token is
already expired. Keep session registration through self._session_store.put
limited to tokens with a positive remaining lifetime.
In
`@src/omnibase_infra/nodes/node_gateway_attach_effect/handlers/handler_gateway_heartbeat.py`:
- Around line 74-114: The heartbeat handler must prevent stale reads from
resurrecting sessions after detach. In handler_gateway_heartbeat.py, make the
revoke and updated-session writes conditional on the session version/state read
before introspection, using a per-session lock or shared
compare-and-delete/compare-and-put lifecycle operation; in
handler_gateway_detach.py, replace the direct deletion with that same atomic
lifecycle operation so detach and heartbeat cannot interleave inconsistently.
In
`@src/omnibase_infra/nodes/node_gateway_attach_effect/models/model_gateway_session_event.py`:
- Line 8: Remove the hardcoded Kafka topic literal from the gateway session
event model and reference the topic exposed by the contract declaration instead.
Update the surrounding model definition in model_gateway_session_event.py to use
the existing generated or imported contract symbol, ensuring the contract YAML
remains the sole source of truth.
In
`@src/omnibase_infra/nodes/node_gateway_attach_effect/services/service_keycloak_token_validator.py`:
- Around line 57-111: Update decode_claims to verify the JWT signature using
trusted Keycloak keys and reject alg:none or altered signed claims before
constructing ClaimSet; validate issuer, audience, and expiration as part of this
flow. In
tests/unit/nodes/node_gateway_attach_effect/test_service_keycloak_token_validator.py
lines 59-87, replace the unsigned success fixture with a verified-token fixture
and add coverage rejecting alg:none tokens and post-signature claim changes.
- Around line 156-179: Update ServiceKeycloakTokenValidator to inherit from
MixinAsyncCircuitBreaker and route Keycloak introspection through its resilience
mechanism, raising InfraUnavailableError when the circuit opens or transport,
non-200, or malformed responses indicate infrastructure failure; return False
only for an explicit active: false response. Update
tests/unit/nodes/node_gateway_attach_effect/test_service_keycloak_token_validator.py
lines 191-222 to expect InfraUnavailableError for transport and upstream
failures, while preserving active: false as the sole revocation result.
In `@tests/unit/nodes/node_gateway_attach_effect/test_handlers.py`:
- Around line 216-227: Update test_heartbeat_unknown_session_raises to import
SessionNotFoundError from handler_gateway_heartbeat under an alias and pass that
exact exception type to pytest.raises instead of the broad Exception class.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 67c383aa-e4af-4319-8481-9170c6c251cd
⛔ Files ignored due to path filters (1)
src/omnibase_infra/enums/generated/enum_omnibase_infra_topic.pyis excluded by!**/generated/**
📒 Files selected for processing (33)
docker/docker-compose.gateway-attach-test-lane.ymlpyproject.tomlscripts/check_subscribe_wiring_health.pyscripts/ci/infra-node-allowlist.txtscripts/proof/gateway_attach_e2e_proof.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/__init__.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/contract.yamlsrc/omnibase_infra/nodes/node_gateway_attach_effect/handlers/__init__.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/handlers/handler_gateway_attach.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/handlers/handler_gateway_detach.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/handlers/handler_gateway_heartbeat.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/models/__init__.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/models/enum_gateway_session_event_type.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/models/enum_gateway_session_status.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/models/model_gateway_attach_config.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/models/model_gateway_attach_request.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/models/model_gateway_attach_response.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/models/model_gateway_detach_request.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/models/model_gateway_detach_response.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/models/model_gateway_heartbeat_request.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/models/model_gateway_heartbeat_response.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/models/model_gateway_session.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/models/model_gateway_session_event.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/node.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/services/__init__.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/services/protocol_gateway_session_store.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/services/service_keycloak_token_validator.pysrc/omnibase_infra/nodes/node_gateway_attach_effect/services/store_gateway_session_memory.pysrc/omnibase_infra/validation/validation_exemptions.yamltests/unit/contracts/test_protocol_ownership.pytests/unit/nodes/node_gateway_attach_effect/__init__.pytests/unit/nodes/node_gateway_attach_effect/test_handlers.pytests/unit/nodes/node_gateway_attach_effect/test_service_keycloak_token_validator.py
…ibase_infra#2694 (#6224) * evidence(OMN-15750): author OCC companion for OmniNode-ai/omnibase_infra#2694 OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head f5d78db63dc9dc9862aa15bf6893519a3a6f71fb. * evidence(OMN-15750): self-bind OCC#6224 + rebind contract_sha256 --------- Co-authored-by: omnimarket-bot <bot@omninode.ai>
✅ Hostile Reviewer — PASSEDBlocking findings (critical): 0 Gate semantics (pilot phase)
Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8468/OMN-8524) |
…d PR events (#2703) * fix(OMN-14241): occ-preflight eval-path callers must listen for `edited` PR events ci.yml (implicit default types, no `edited`) and hostile-reviewer.yml (`types: [opened, synchronize, reopened]`) each declare a top-level `occ-preflight` job calling the canonical reusable occ-preflight.yml, but neither retriggers when a PR body is edited without a new commit. The reusable workflow already live-fetches the current PR body via `gh pr view` on every run it does execute -- the defect was never a frozen-body read, it was that no new run fired at all when a body-only edit (typically stamping `Evidence-Source: OCC#<n>` after the companion OCC PR merges) landed. The last commit-triggered run -- correctly FAILURE before the stamp existed -- then sits as the permanent `occ-preflight / eligibility` status, and CI Summary fails closed on it forever without a manual empty-commit retrigger. Live-root-caused on infra#2696 and #2694 (2026-08-09): both PRs stamped Evidence-Source via a body edit; CI's and Hostile Reviewer's occ-preflight copies stayed FAILURE from before the edit while call-reject-skip.yml's copy (which already lists `edited`) correctly re-ran and passed. Adds tests/ci/test_occ_eval_path_trigger_coverage.py (RED before this change on both workflows, GREEN after), sibling to the existing test_occ_born_path_trigger_coverage.py (OMN-14987) guard for the autobind/companion-effect minting workflows. OMN-14241 (adopted existing Backlog ticket; relatedTo OMN-15725/OMN-15214 lineage). No debounce/diff guard added -- today's actual behavior already pays a full-CI-run cost per stamp fix via manual empty-commit retriggers, so automating the same retrigger via `edited` is net-neutral CI cost, not new cost; it removes the manual-toil failure mode currently blocking both PRs. * chore(OMN-14241): [mergesweep-0809-infraunblock] retrigger CI Deploy Agent Tests (OMN-15378) failed once with a docker-teardown flake unrelated to this PR's diff (subprocess.TimeoutExpired on `docker rm -f` in test_executor_digest_verification_image_id_omn15181.py, a file this PR never touches). First-strike retry per two-strike allowance. * chore(OMN-14241): [mergesweep-0809-infraunblock] retrigger CodeRabbit gate gate/CodeRabbit Thread Check's prior run (31333667026) was CANCELLED by a concurrent same-SHA run collision, not a real failure (log confirmed "Unresolved CodeRabbit threads: 0"). An issue_comment retrigger attempt resolved to the wrong head SHA (dev tip, not this PR's head) due to how cr-thread-gate-caller.yml's issue_comment path checks out ref -- a genuine workflow quirk, not applicable to this PR's actual commit. Using a synchronize-triggering empty commit instead, which correctly targets this PR's head per the workflow's pull_request trigger. * chore(OMN-14241): [mergesweep-0809-infraunblock] retrigger CI Summary poll CI Summary FAILED (run 31334333041) purely on poller-deadline timing: its own log shows CodeQL still listed as "missing/pending" at the 20:37:31Z deadline, but CodeQL's own run (31334296327) completed SUCCESS at 20:36:49Z -- 42 seconds earlier. A fresh poll will observe CodeQL as already-passed. Not a repeat of the same failure class (prior strikes were Deploy Agent Tests docker-teardown timeout and a CodeRabbit gate wrong-SHA issue) -- this is a first-instance poller/job-completion race. * chore(OMN-14241): [mergesweep-0809-infraunblock] retrigger past known flake OMN-15749 Deploy Agent Tests failed twice on TestRepoDigestsOnContainerIsRealDockerFailureMode (docker create / docker rm -f timing out at 30s). Root-caused, not transient hand-waving: this is the pre-existing, already-ticketed OMN-15749 ("deploy-agent digest-verification fixture tests time out on real docker create/docker rm -f -- 30s too short under runner contention", Backlog, unrelated to this PR's diff -- this PR touches zero deploy-agent files). Confirmed non-systemic: sibling PRs #2701/#2702/#2700 all passed the identical test on concurrent runs at the same time, so this is isolated runner-instance docker contention, not a fleet-wide or code-caused failure. Retrying past the known flake rather than blocking an unrelated CI-workflow-trigger fix on it. * chore(OMN-14241): [mergesweep-0809-infraunblock] retrigger past transient pin-reachability timeout Tests (Split 1/15) failed on tests/integration/ci/test_pin_reachability_live_omn15538.py with "transport error: The read operation timed out" resolving the pre-existing core-ref pin in ci.yml/hostile-reviewer.yml (unrelated to this PR's diff -- neither line is touched here; the stale pin itself is OMN-15725's scope, PR #2680 still open). Confirmed transient, not a durable unreachable-pin state: sibling PR #2688/#2697 passed this same check hours ago; sibling PR #2701 failed the identical check in the same ~30-40min window as this PR, consistent with a shared transient GitHub API rate-limit/timeout window (cf. OMN-14352) rather than a code-caused or permanently-broken pin.
Adds node_gateway_attach_effect: the attach ingress node recommended by
OMN-15739's ADOPT ruling (candidate A) and the gateway node-architecture
lift's G6 item. Three def-B EFFECT handlers, stateless, no direct bus
access (runtime publishes the typed response's embedded session_event --
node_owned_publish):
- HandlerGatewayAttach (gateway.attach): decodes a per-tenant Keycloak
client-credentials access token's claims, registers a tenant-bound
ModelGatewaySession, returns ATTACHED session_event.
- HandlerGatewayHeartbeat (gateway.heartbeat): re-validates the token via
RFC 7662 introspection against Keycloak on every tick -- this is the
revocation mechanism. Disabling the tenant's confidential client makes
the next heartbeat observe active:false, tear the session down, and
emit REVOKED, independent of the token's own unexpired exp.
- HandlerGatewayDetach (gateway.detach): explicit edge-initiated teardown.
Config is 100% contract + overlay (ModelGatewayAttachConfig, no env reads);
secrets resolve by ref via SecretResolver at the effect boundary
(service_keycloak_token_validator.py). Session storage is in-process
(StoreGatewaySessionMemory) behind ProtocolGatewaySessionStore for this
first slice -- documented as a known gap in contract.yaml.
Also (OMN-15752): docker/docker-compose.gateway-attach-test-lane.yml, a
.201-side test-lane connector parallel to (and independent of) the live
bastion-path docker-compose.gateway.yml -- untouched by this PR.
Also (OMN-15753): scripts/proof/gateway_attach_e2e_proof.py, an e2e slice
proof harness skeleton matching e2e_cloud_workflow_harness.py's convention
-- --live defaults off, live stage bodies raise StageNotImplementedError
until run against a deployed node (OMN-15754, not landed by this PR).
Local-gate fixes applied after pre-commit passes: split enum/model mixed
files per ONEX Architecture Validation; runtime_profiles [effects, canary]
so the subscribing contract has a draining consumer (OMN-12957); added the
handler files to scripts/ci/infra-node-allowlist.txt (infra-owned
trust-boundary node, sibling of node_bus_forwarder_effect); registered the
node entry point in pyproject.toml; regenerated topic enums; added
validation_exemptions.yaml entries for non-UUID id fields.
Local proof: 16/16 unit tests pass (including
test_heartbeat_after_keycloak_revocation_tears_down_session, the literal
revocation proof), mypy --strict clean (22 files), ruff clean.
Does not touch node_bus_forwarder_effect, the gateway Phase-0 lane's files
(OMN-15740-44), the bastion, or any SG rule.
OMN-15750, OMN-15752, OMN-15753
Two pre-push full-suite failures surfaced after the first commit, both
addressed with the repo's existing allowlist mechanisms rather than
weakening either gate:
- tests/unit/contracts/test_protocol_ownership.py: ProtocolGatewaySessionStore
is a genuine [NODE] DI seam (swap in-process -> Valkey later without
touching handlers), same category as the existing node-internal
protocol entries. Added to KNOWN_INFRA_PROTOCOLS.
- tests/unit/scripts/test_check_subscribe_wiring_health.py: the three
gateway-attach command topics are published by the edge-side dialer
(docker-compose.gateway-attach-test-lane.yml / the eventual onex
connect client), never by a contract-declared node -- the same shape
as the existing baselines-batch-compute CLI-publisher entry. Added to
_EXTERNAL_PUBLISHER_ALLOWLIST with owner/expiry, not the closed
baseline ratchet list.
OMN-15750
occ-preflight / eligibility is stuck FAILURE (both CI's and Hostile Reviewer's copies) from before the PR body was edited to add Evidence-Source: OCC#6224 (21:24:59Z). This is the OMN-14241 failure class: ci.yml/hostile-reviewer.yml lack `edited` in on.pull_request.types, so the body edit never retriggered them, and the stale pre-stamp FAILURE never self-heals. Retriggering via a synchronize event (empty commit) per the allowed mechanisms until OMN-14241 (infra#2703) lands.
…ach node - Move Keycloak RFC 7662 introspection HTTP call from the freestanding services/ module into HandlerGatewayHeartbeat._introspect so the raw transport call lives under handlers/, satisfying the imperative-contract-guard's handlers/-only I/O boundary (root cause of the Imperative Contract Guard failure: 1 LIVE violation). Declare metadata.transport_type: HTTP on the node contract per the sanctioned pattern (node_github_pr_poller_effect). - Regenerate tests/fixtures/dispatch_parity/baseline-selection-v2.json: the PR's three new gateway.* dispatchers were never in the committed Mode-A oracle (dispatch-parity-gate diff is scoped exactly to the new handlers). - Reject already-expired tokens at attach time before session registration (CodeRabbit Major/security finding). - Remove a hardcoded topic literal from a docstring; assert the exact SessionNotFoundError type instead of bare Exception in a heartbeat test (CodeRabbit Minor findings). - Resolve the remaining 4 CodeRabbit threads (DI container, atomic session-lifecycle transitions, JWT signature verification, Keycloak outage vs revocation) with reasoning + follow-up ticket OMN-15918 -- each is an independent heavy-lift architecture change, not a fix scoped to this PR. Topic Drift Check / Version Pin Compliance / Pin Reachability / runner-image-build-smoke were runner-infra faults (job timeout mid uv-sync, git fetch transport errors, stale shared_env_digest) on a 4-day-old head 41 commits behind dev -- addressed by rebasing onto dev rather than a code fix. Evidence-Ticket: OMN-15750
0bf0be8 to
a5fc3c8
Compare
…validation R4 (runner-image-build-smoke, merge-blocking): this PR's pyproject.toml entry-point addition for node_gateway_attach_effect moves the runner-image shared_env_digest (pyproject.toml + uv.lock are the only digest inputs). Regenerated via `scripts/ci/runner_image_identity.py --mode generate`; verify mode now confirms recorded==recomputed (638933d1c8de12afe5c8024c). Diff is scoped to docker/runners/runner-image.lock.json only. R3 (dead security config field): keycloak_issuer_ref was declared in ModelGatewayAttachConfig and contract.yaml but had zero readers -- decode_claims presence-checked the iss claim but never compared it to the configured issuer, so a token from any issuer that satisfied the other claims would attach. Wired it: HandlerGatewayAttach now takes a SecretResolver (same DI shape as HandlerGatewayHeartbeat), resolves keycloak_issuer_ref before calling decode_claims, and decode_claims raises TokenValidationError on a mismatched iss. decode_claims stays I/O-free (receives the already-resolved issuer string) so the imperative-contract- guard boundary (I/O only under handlers/) is preserved -- same pattern HandlerGatewayHeartbeat._introspect already uses for the introspection endpoint ref. Added test_attach_rejects_mismatched_issuer + test_attach_accepts_matching_issuer (handler-level, end-to-end through SecretResolver) and test_mismatched_issuer_raises + test_matching_issuer_happy_path (decode_claims-level). Updated all existing HandlerGatewayAttach call sites for the new required secret_resolver param. 19/19 focused unit tests pass, mypy --strict clean (22 files), ruff clean, pre-commit run --files clean on all 5 changed files. Ticket: OMN-15750
… verification, identity binding, atomic transitions, outage/revocation split (#2727) * feat(OMN-15918): node_gateway_attach_effect hardening — JWKS signature verification, identity binding, atomic transitions, outage/revocation split CodeRabbit-flagged hardening follow-ups on OMN-15750 (PR #2694), TDD RED-before/GREEN-after for each: - R1 (JWT signature verification): `decode_claims` (structural-only, never referenced the signature segment) replaced with `verify_and_decode_claims` — verifies against a resolved JWKS keyset via PyJWT before trusting any claim. `alg:none`, wrong-key-signed, and unknown-kid tokens are all rejected. JWKS fetch (network I/O) lives inline in each handler (`_fetch_jwks`), circuit-breaker guarded; verification itself stays I/O-free in the service module, matching the existing `_introspect` I/O-boundary pattern. - R2 (identity binding): heartbeat and detach now re-verify the presented token's signature and bind its tenant_id/principal_id/client_id to the STORED session's identity from attach time before acting. Detach previously took zero credential at all (session_id + free-text reason); ModelGatewayDetachRequest now requires access_token (contract minor bump 0.1.0 -> 0.2.0, wire-breaking on this new node with no external consumers yet). - R3 (atomic transitions): ProtocolGatewaySessionStore.put_if_present closes the heartbeat resurrection race — a concurrent detach landing in the read-introspect-write gap is no longer silently resurrected by the heartbeat's final write. - R4 (outage vs revocation): JWKS fetch and RFC 7662 introspection are now MixinAsyncCircuitBreaker-guarded; a transport error, non-200, or malformed body raises InfraUnavailableError and leaves the session untouched, instead of the previous fail-closed False that made every Keycloak outage read as mass revocation. Ticket item 1 (DI container for handler construction) is explicitly deferred — out of scope for this hardening slice, tracked as a known_gap in contract.yaml. Filed OMN-15952 (linked blocker to OMN-15877) for the unattended pairing/renewal contract gap the ground-truth verification surfaced separately. 35 tests (12 validator + 20 handler + 3 store), all new/changed assertions verified RED against the pre-hardening source via git stash before GREEN. ruff + mypy --strict + pre-commit (98 hooks, file-scoped) all clean. * chore(OMN-15918): trigger clean CI Summary re-run after Evidence-Source/Evidence-Ticket PR-body fix
Summary
First slice of the OMN-15739 ADOPT ruling (candidate A: edge-gateway agent -> OmniNode-owned attach ingress -> MSK server-side). Builds
node_gateway_attach_effect, the attach/session control-plane node (G6 indocs/design/2026-08-08-gateway-node-architecture-lift.md) recommended bydocs/design/2026-08-08-customer-cloud-connectivity-design.md's Open Question 2.Three def-B EFFECT handlers, contract-driven config, secrets by ref:
gateway.attach-- decodes a per-tenant Keycloak client-credentials access token, registers a tenant-boundModelGatewaySession.gateway.heartbeat-- re-validates via RFC 7662 introspection on every tick. This is the revocation mechanism: disabling the tenant's Keycloak confidential client makes the next heartbeat observeactive:false, tear the session down, and emitREVOKED-- independent of the token's own unexpiredexp.gateway.detach-- explicit edge-initiated teardown.Also in this PR:
docker/docker-compose.gateway-attach-test-lane.yml(OMN-15752) -- a.201-side test-lane connector, parallel to and independent of the live bastion-pathdocker-compose.gateway.yml. The bastion is untouched.scripts/proof/gateway_attach_e2e_proof.py(OMN-15753) -- an e2e slice-proof harness skeleton (attach -> heartbeat -> Keycloak client disable -> heartbeat observes revocation), matching the existinge2e_cloud_workflow_harness.pyconvention:--livedefaults off, live stage bodies raiseStageNotImplementedErroruntil run against a deployed node.What is proven, and how (proof_class: code-only + receipt-bound)
test_heartbeat_after_keycloak_revocation_tears_down_session-- the literal handler-level revocation proof (introspectionactive:false-> session torn down,REVOKEDevent, gone from the store).mypy --strictclean on the new node package (22 files).ruff format+ruff checkclean..200-shaped governed selector via the repo's own pre-push hook) green with this diff.pre-commit runfull suite green, including the non-canonical-lifecycle-class ratchet, ONEX Architecture Validation, runtime_profiles validation, protocol-ownership, and subscribe-wiring-health gates -- each genuine finding fixed via the repo's existing allowlist mechanisms (see commit 2), not weakened.What is NOT proven -- deploy and live e2e are explicitly out of scope for this PR:
onex-dev's runningomninode-runtime/omninode-runtime-effects/omninode-runtime-workerpods are built from released/merged code. Standing doctrine (reference_runtime_deployment) is image-rebuild deploy, not hot-install -- there is no live-attach path for this node until it merges and a new image ships.scripts/proof/gateway_attach_e2e_proof.py --live ...).POST /v1/tenants) is BLOCKED, not done. Live investigation ononex-dev(i-06169517a92b45f86, read-only + onekubectl execdiagnostic) foundPOST /v1/tenantssits behindm4_usage_metering_middleware, which requires an already-resolved tenant credential (Bearer JWT orx-api-key) -- it is not inPUBLIC_V1_PATHS. This is circular for a first/house tenant: there is no existing tenant credential to present. The two endpoints that are public (/v1/auth/provision,/v1/alpha/invite) create a Keycloak user (human login), not the P0B confidential client (machine client-credentials identity) that/v1/tenants->_provision_tenant_credentials_p0bprovisions and that this node's attach flow actually needs. Direct DB/Keycloak provisioning viakubectl execinto theonex-apipod was attempted but the SSM-wrappedkubectl execoutput capture is unreliable (diagnosticechomarkers did not reliably appear in captured stdout) -- root-causing that tooling gap was out of scope for this PR. No tenant was created, no Keycloak client was created, nothing was mutated ononex-dev.Scope discipline
Does not touch
node_bus_forwarder_effect, any file in the gateway Phase-0 lane (OMN-15740-44,wf_51aa94f0-854), the bastion (docker-compose.gateway.yml), or any AWS SG rule. No AWS IAM/DNS/SG mutation. No prod. Config is contract + overlay only; secrets resolve by ref viaSecretResolverat the effect boundary -- no new env-var reads.Tickets
OMN-15750, OMN-15752, OMN-15753 (code + proof harness). OMN-15751 and OMN-15754 remain open -- see blockers above.
Evidence-Source / OCC companion: not yet opened for this PR (informational-only for now; will pair before merge per repo
Evidence-Source: OCC#<PR>receipt-gate convention if/when this heads toward merge).Evidence-Ticket: OMN-15750
Evidence-Source: OCC#6224
Gate-fix pass (2026-08-12)
Root-caused and fixed the full red set (
gh pr checks 2694at pickup:CI Summary,Pin Reachability (OMN-15538),dispatch-parity-gate,gate / CodeRabbit Thread Check,imperative-contract-guard,runner-image-build-smoke,Topic Drift Check,Version Pin Compliance):service_keycloak_token_validator.introspect()performed a rawhttpx.AsyncClientcall from a freestandingservices/module (outsidenode_*/handlers/), whichscan_freestanding_imperative_ioflags unconditionally regardless of DI shape. Moved the introspection call intoHandlerGatewayHeartbeat._introspect(its sole caller) so the only I/O this node performs lives underhandlers/;decode_claims(no I/O) stays in the service module. Declaredmetadata.transport_type: "HTTP"on the contract (sanctioned pattern, matchesnode_github_pr_poller_effect). Verified locally:check_imperative_contracts.pynow reportslive: 0for this repo.gateway.*dispatchers were never in the committed Mode-A oracle. Regeneratedtests/fixtures/dispatch_parity/baseline-selection-v2.jsonper the test's own documented recipe; diff is scoped exactly to the three newnode_gateway_attach_effectdispatcher entries.The job has exceeded the maximum execution time of 10m0s+fetch-pack: invalid index-pack output/early EOF/RPC failed; curl 56 GnuTLS recv error (-54)during sibling-repo checkout.uv synchung mid-download (CACHE_ENABLED: false,UV_CONCURRENT_DOWNLOADS: 1) and was killed by the same 10-minute job timeout (The operation was canceled.).onex_change_control @ 2dd26ade7c...—transport error: The read operation timed outresolving the pin (network, not a code defect).runner image shared_env_digest is stale (recorded=... recomputed=...)— the PR's 4-day-old head (41 commits behinddev) had a stalerunner-image.lock.jsonrelative to currentdev.All four are consistent with a stale, contended head rather than a code defect. Fixed by rebasing onto
origin/dev(git rebase origin/dev, clean, no conflicts) rather than a code change.SessionNotFoundErrorinstead of bareException; attach now rejects an already-expired token withTokenValidationErrorbefore registering a session, with a new regression test). 6 acknowledged with reasoning and resolved: the test-lane compose file's Kafka-bootstrap exposure (it's the OMN-15752 proof-only test lane, not the customer-facing edge connector, which doesn't exist yet); the e2e harness's missingstage_detach()(every stage in that skeleton raisesStageNotImplementedErroruntil OMN-15754 deploys — wiring a real detach stage now would be untestable); and four independent heavy-lift architecture items (DI-container handler construction, atomic session-lifecycle transitions, JWT signature verification via JWKS, and circuit-breaker-based Keycloak-outage-vs-revocation distinction) tracked in follow-up ticket OMN-15918.Seams (OMN-14208) — claims produced by OMN-15877 vs consumed here
This node's
decode_claimsis the consumer side of whatever token OmniWeb mints (OMN-15877, PR omnibase_infra#2726). Field-by-field, checked against #2726's live diff (docker/keycloak/desired-clients.json,omniwebclient):decode_claimsomniwebclient)isssubaud(must includegateway-attach)required_audience)gateway-attach-audiencemapper, NEW in #2726tenant_idtenant_iduser-attribute mapper (pre-existing)tenant_slugtenant_sluguser-attribute mapper (pre-existing)principal_idprincipal_iduser-attribute mapper, NEW in #2726azp(->ClaimSet.client_id)"omniweb"for every user of every tenantexpWire shape: fully compatible —
decode_claimswill not raise a validation error against a realomniwebtoken once #2726 lands.Credential-class design question (per OMN-15877's explicit requirement — recorded, not silently picked): this node (#2694) was built assuming a client-credentials machine token from a per-tenant confidential Keycloak client (the P0B chain) — see the contract's
capabilities.introspection_revocationand every handler docstring: "disabling the tenant's Keycloak client" is the revocation mechanism, andsession.keycloak_client_id(fromazp) is assumed per-tenant.omniweb's actual client config isstandardFlowEnabled: true, serviceAccountsEnabled: false— a user-session (authorization-code) token from one shared client, which #2726 extends with thegateway-attachaudience rather than issuing a second, tenant-scoped credential (OMN-15877's option (a): "one token satisfies both validators"). Under that shapeazpis always"omniweb", which breaks per-tenant revocation and the heartbeat's defense-in-depthclient_idmatch as designed. This is an open PAIR_INCOMPATIBLE risk, not resolved by either PR — byte-cited comment posted on OMN-15877 (2026-08-12); resolution belongs to that ticket's confirmation loop with Daniyal's team, not a unilateral pick on either PR.Summary by CodeRabbit