Skip to content

feat(signing): attested gate resolve on the capability-dispatch model - #6822

Open
zmanian wants to merge 7 commits into
attested-g8-ledger-productfrom
attested-g9-gate-ingress
Open

zmanian wants to merge 7 commits into
attested-g8-ledger-productfrom
attested-g9-gate-ingress

Conversation

@zmanian

@zmanian zmanian commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

What

The attested gate/resolve ingress, deferred from #6769 and now re-expressed for current main. Stacks on #6818.

The old stack routed this through a WebUI-inbound facade that main deleted outright — webui_inbound has zero references there — and replaced with capability/command dispatch. Since RESOLVE_GATE_COMMAND already exists, an attested resolve is a gate resolution carrying a claim, not a new command. It therefore inherits the existing authorization, audit, and safety pipeline instead of standing up a second dispatch surface.

The cross-user IDOR contract — the load-bearing part

An attested resolution routes to the attested resolver even when the run cannot be read — which is exactly what a cross-user request looks like. That resolver runs its own owner-scoped verify-and-claim and fails closed indistinguishably from a missing gate.

Falling through to the generic route would instead reject the resolution shape with a 400 — confirming to the caller that the request was attested-shaped at all. Gate-ref shape cannot divert it either.

Mutation-verified: removing the early route makes two tests fail.

Ordering is the other contract

Verify + claim the one-shot grant before the turn transitions; resume only on success; then broadcast. A rejected proof leaves the turn BlockedAttested with no state-machine mutation, so the gate stays cleanly retryable and an unverified proof never advances a signing turn.

The approval resolver, the auth resolver, and the generic fallback all refuse an attested resolution rather than resuming it — the generic path has only one-shot resume_turn and no verification, so resuming there would be the exact failure this design prevents.

Sanitized rejections

Every rejection collapses to one of two shapes on the way out. A caller must not be able to distinguish a missing binding from a forged proof from an already-claimed grant — that distinction is an oracle for which gates exist and which have been driven. Backend unavailability is the one genuinely-our-fault case and is reported as such.

Wire shape

ProductResolveGateRequest gains three optional attested fields, parsed into a ProductGateResolution::Attested variant. All three are required together — a partial proof is a validation error, never a silently-defaulted claim carried on to the device path. The payload is opaque and passes through uninterpreted; a test pins that an arbitrary JSON shape is not reshaped here.

ProductGateResolution and ProductInboundCommand drop their Eq derives: the proof payload is serde_json::Value, which is PartialEq but not Eq.

The port is wired in composition only when the runtime composed both an attested signing graph and an intent store; absent either, an attested resolution is refused.

Verification

8 new tests (4 wire-layer, 4 routing/IDOR), full ironclaw_product suite, ironclaw_architecture 100%, whole workspace compiles including tests, fmt, clippy --tests zero warnings, check_no_panics.py, check-hermetic-env.sh — all clean against current main.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • staging
  • reborn-integration

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9237bf49-eee4-4410-9854-091e2371a4b2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions github-actions Bot added size: L 200-499 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jul 28, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80d1bede59

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +5927 to +5931
// Verify + claim BEFORE any turn transition.
let verified = port
.verify_and_claim(&scope, &actor, run_id, &gate_ref, &claim)
.await
.map_err(map_attested_continuation_rejection)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate the attested hash before consuming the grant

When proof_json contains a valid proof for the authoritative hash but the separate attested_approved_tx_hash is malformed or different, the production verify_and_claim path verifies the hash embedded in proof_json and irreversibly claims the grant/advances the signing ledger before this top-level value is validated by AttestationClaimRef and the resume port. The later resume then fails and leaves the run BlockedAttested, while a corrected retry is rejected because the one-shot grant and ledger row were already consumed. Validate and bind every resume input before claiming, or make the claim and turn transition recoverable/atomic.

AGENTS.md reference: AGENTS.md:L189-L190

Useful? React with 👍 / 👎.

Comment on lines +6688 to +6689
R::Unavailable | R::BackendUnavailable => {
ProductSurfaceError::internal_from("attested-signing continuation is unavailable")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the retryable 503 for continuation outages

When signing or broadcast infrastructure fails, the composition adapter maps the failure to Unavailable or BackendUnavailable, but this branch calls internal_from, which always produces Internal/HTTP 500 with retryable: false. A transient RPC or backend outage therefore loses the service-unavailable and retry semantics documented for the continuation path, preventing callers from distinguishing a recoverable outage from an internal invariant failure; map these cases to the intended sanitized 503 response with the appropriate retry flag.

Useful? React with 👍 / 👎.

@ironloopai

ironloopai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Review · PR #6822

🟢 Completed · Review submitted

2 actionable findings →

The attested ingress is fail-closed for invalid proofs and ownership mismatches, but the new orchestration cannot recover from broadcast-tail failures after transitioning the turn, and it misclassifies continuation outages as non-retryable internal errors.

Automatic · PR opened · attempt 1 of 3 · completed in 1m 55s

Run details
  • Repository: nearai/ironclaw
  • Base: attested-g8-ledger-product at 12384b4
  • Head: attested-g9-gate-ingress at 80d1bed
  • Created: Jul 28, 2026, 7:44 PM UTC
  • Updated: Jul 28, 2026, 7:46 PM UTC
  • Run: 68c8f1d4-a174-4c7c-ac29-6ccb06acd2dd
  • Latest attempt: 1 · Completed · 188a4cde-1e56-44b2-972c-05f16fdc5b52

@ironloopai ironloopai 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.

🔍 Review complete · PR #6822

⚠️ 2 findings · 2 blocking

The attested ingress is fail-closed for invalid proofs and ownership mismatches, but the new orchestration cannot recover from broadcast-tail failures after transitioning the turn, and it misclassifies continuation outages as non-retryable internal errors.

Findings

  1. 🔴 High · Broadcast failure leaves the resolved turn with no retry path — crates/ironclaw_product/src/reborn_services.rs:5968-5971
    Details are attached to the relevant diff.
  2. 🟠 Medium · Recoverable continuation outages are returned as non-retryable 500s — crates/ironclaw_product/src/reborn_services.rs:6687-6690
    Details are attached to the relevant diff.
Validation and technical details
  • Reviewed the complete trusted comparison refs/ironloop/base (12384b4) to refs/ironloop/head (80d1bed): five changed files, 444 additions and 3 deletions.
  • Inspected crate-local AGENTS.md/CLAUDE.md guidance, request parsing and proof-size validation, run-state routing, ownership checks, continuation verification/claim/broadcast behavior, composition wiring, turn-store attested resume transitions, and all changed tests.
  • git diff --check refs/ironloop/base..refs/ironloop/head completed without whitespace errors.
  • Focused Rust tests could not be executed because cargo is not installed in the review environment (cargo: command not found).
  • Base: attested-g8-ledger-product
  • Head: attested-g9-gate-ingress at 80d1bed
  • Run: 68c8f1d4-a174-4c7c-ac29-6ccb06acd2dd

Comment on lines +5968 to +5971
// Signed and claimed; now broadcast. No re-verification, no re-claim.
port.broadcast_resolved(&scope, run_id, &gate_ref, verified)
.await
.map_err(map_attested_continuation_rejection)?;

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.

🔴 High · Broadcast failure leaves the resolved turn with no retry path

verify_and_claim consumes the one-shot grant, then resume_turn durably advances the run to AttestedResolved, and only afterward does this call broadcast. If broadcasting returns an RPC/backend error, the opaque verified handle is dropped and the request returns an error even though the gate is already resolved. Retrying the endpoint cannot recover: routing rejects the now non-blocked/terminal state before reaching the continuation, and re-running verification would encounter the already-claimed grant anyway. This can strand an approved signing operation without broadcast or intent projection. Persist a recoverable continuation/outbox before transitioning, or make transition plus continuation handoff durable so a worker can retry the broadcast tail.

Comment on lines +6687 to +6690
match rejection {
R::Unavailable | R::BackendUnavailable => {
ProductSurfaceError::internal_from("attested-signing continuation is unavailable")
}

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.

🟠 Medium · Recoverable continuation outages are returned as non-retryable 500s

Both Unavailable (documented as a post-verification broadcast-tail failure) and BackendUnavailable are mapped through ProductSurfaceError::internal_from, which produces Internal, HTTP 500, and retryable: false. This contradicts the rejection contract and prevents clients/operators from distinguishing a recoverable service outage from an invariant failure. Map recoverable backend/broadcast unavailability to a sanitized Unavailable 503 with the appropriate retryability; keep any genuinely terminal category separate.

@zmanian

zmanian commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

Review-response summary (g9 — gate resolve on capability dispatch)

Force-pushed several times, so inline anchors have moved. Consolidated response to the 4 findings here, plus a summary of what this branch grew — it changed the most of any PR in the stack.

Five commits added since review

  1. compose the attested-signing graph in the shipping runtime — the feature was entirely inert. build_backend_production (the sole runtime construction site, serving production and local-storage-production-shaped deployments alike) hardcoded attested_signing: None and intent_store: None, and no raise hook was registered. Four seams unwired: the resolve-gate ingress this PR adds had nothing to dispatch to, the turn store never admitted an attested resume, and request_signature could never raise. Every piece was shipped and unit-tested; nothing ever built them.
  2. erase the attested graph over its persistence backends — RebornRuntime.attested_signing was typed to the concrete InMemoryAttestedComposition, so the durable compositions from feat(signing): durable PostgreSQL + libSQL attested stores (5/8) #6809 could not be stored at all. A type error, not a wiring gap. Two narrow dyn-safe traits fix it; the composition trait excludes register_attested_gate so holders of the erased graph cannot seal grants.
  3. select the durable attested backend when configured — durable when a database handle, the attested-broadcast feature, and at least one chain RPC endpoint hold together. All three are required: a durable graph with no broadcaster would persist grants for signatures it can never submit.
  4. close the request_signature authorization bypass — see below.
  5. Doc commit linking the tracking issue.

The authorization bypass (HIGH)

invoke_capability intercepted request_signature and called the raise hook, returning before the kernel authorize() fold. A caller with an empty grant set reached the hook and could mint a human-facing approval prompt for a transaction of its choosing. It was a rebase artifact, not a design decision. An external security review rated it HIGH and rejected both cheap fixes (a partial reimplementation would create a divergent authority path; authorize() is not a predicate — it starts run state and mints a single-use witness).

Now fail-closed: the intercept is gone, so request_signature flows through the fold and reaches only the deliberately-refusing handler. Raising is unavailable rather than unauthorized. Correct end state specced in #6860.

Two pre-existing defects surfaced while testing this, both real and both fixed:

  • builtin.request_signature referenced an input schema (schemas/builtin/request_signature.input.v1.json) that was never written, so granting the capability failed surface construction outright — it could never have been used by anyone.
  • The builtin package expectations omitted the capability and defaulted its permission to Allow; it declares Ask.

The 4 findings on this PR — genuinely open

None are fixed in this push:

  • Broadcast failure leaves the resolved turn with no retry path (High, reborn_services.rs:5971) — open, and the one I would take first. It is the resolve-side counterpart of the g2 "transient failure strands the ledger" finding, and the two should probably be fixed together since both concern what happens after the one-shot grant is consumed.
  • Validate the attested hash before consuming the grant (:5931) — open. Same ordering family as the seal/binding findings on feat(signing): attested signer-continuation runtime + composition seam (4/8) #6769 and feat(signing): durable PostgreSQL + libSQL attested stores (5/8) #6809.
  • Recoverable continuation outages returned as non-retryable 500s (Medium, :6690) / Preserve the retryable 503 (:6689) — open. Worth noting these interact with the first item: a non-retryable status on a recoverable outage is what turns "no retry path" from a latent gap into an operational one.

Process note

I force-pushed g4–g8 once without compiling them, and a test build was red as a result (an upstream signature change broke a mock). Caught at the stack tip. All nine branches have since been compiled individually with cargo check --workspace --all-targets and re-pushed.

Also: the ironclaw_reborn_composition suite is intermittently red under full parallelism — 0/3/5/13 failures across runs, all RunTimeout{3s} or Elapsed. I measured this rather than assumed: 5 failures with my new tests skipped, 3 with them included, so it is pre-existing CPU contention against the 3s budgets, not something this stack introduced. It will produce confusing CI and deserves its own issue.


Verification: clippy clean at --workspace --all-targets --all-features; feature matrix (libsql, libsql,attested-broadcast, postgres,attested-broadcast, all three) clippy-clean; full ironclaw_host_runtime suite green; attested suites green.

zmanian and others added 3 commits July 30, 2026 00:23
Re-expresses the attested gate/resolve ingress for current main. The old
stack routed it through a WebUI-inbound facade that main deleted
outright -- webui_inbound has zero references there -- and replaced with
capability/command dispatch. RESOLVE_GATE_COMMAND already exists, so an
attested resolve is a GATE RESOLUTION CARRYING A CLAIM, not a new
command. It therefore inherits the existing authorization, audit, and
safety pipeline rather than standing up a second dispatch surface.

Wire shape: ProductResolveGateRequest gains three optional attested
fields, parsed into a ProductGateResolution::Attested variant. All three
are required together -- a partial proof is a validation error, never a
silently-defaulted claim carried on to the device path. The payload is
opaque and passes through uninterpreted; a test pins that an arbitrary
JSON shape is not reshaped here.

The cross-user IDOR contract is the load-bearing part. An attested
resolution routes to the attested resolver EVEN WHEN THE RUN CANNOT BE
READ -- which is exactly what a cross-user request looks like. That
resolver runs its own owner-scoped verify-and-claim and fails closed
indistinguishably from a missing gate. Falling through to the generic
route would instead reject the resolution SHAPE with a 400, confirming
to the caller that the request was attested-shaped at all. Gate-ref
shape cannot divert it either. Mutation-verified: removing the early
route lets two tests fail.

Ordering is the other contract: verify + claim the one-shot grant BEFORE
the turn transitions, resume only on success, then broadcast. A rejected
proof leaves the turn BlockedAttested with no state-machine mutation, so
the gate stays cleanly retryable and an unverified proof never advances
a signing turn. The approval and auth resolvers, and the generic
fallback, all refuse an attested resolution rather than resuming it --
the generic path has only one-shot resume_turn and no verification.

Every rejection collapses to one of two shapes on the way out. A caller
must not be able to distinguish a missing binding from a forged proof
from an already-claimed grant; that distinction is an oracle for which
gates exist and which have been driven. Backend unavailability is the
one genuinely-our-fault case and is reported as such.

ProductGateResolution and ProductInboundCommand drop their Eq derives:
the proof payload is serde_json::Value, which is PartialEq but not Eq.

The port is wired in composition only when the runtime composed both an
attested signing graph and an intent store; absent either, an attested
resolution is refused.
…ntime

Every piece of the attested-signing path shipped and was unit-tested, but
nothing ever constructed it. build_backend_production -- the sole runtime
construction site, serving both production and local-storage
production-shaped deployments -- hardcoded attested_signing: None and
intent_store: None, and no AttestedRaiseHook was handed to the host runtime.

The gaps compounded across four seams, so the feature was inert end to end:

  1. product_surface registers the attested continuation only when BOTH
     attested_signing() and intent_store() are Some, so the resolve-gate
     ingress had nothing to dispatch to and refused every resolution.
  2. production_turn_state_store received attested_resume_port: None, so the
     turn store never admitted an attested resume.
  3. With no raise hook, request_signature could never raise a gate.

No gate could be raised, and no gate could be resolved.

Wire all four: build the composition and intent minting, share one binding
store between the composition and the resume port (two stores would let a
gate be resumed that the driver cannot verify), and register the raise hook.

The stores are in-memory, which is fail-closed rather than merely
non-durable: grants, claims, and bindings share a lifetime, so a restart
drops a pending gate entirely instead of leaving a resolvable grant whose
claim record was lost. Swapping in the durable backends from
attested_durable additionally requires erasing RebornRuntime.attested_signing
behind a trait -- it is typed to the in-memory monomorphization today --
which is its own change.

The behaviour of each piece was already covered; what no existing suite could
see was whether anything built them. The new test asserts exactly that, and
drives the raise hook through the real capability path rather than an
accessor. Its discriminating assertion is mutation-verified: dropping
with_attested_raise_hook fails it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…kends

RebornRuntime.attested_signing was typed to the CONCRETE
InMemoryAttestedComposition, so the durable PostgreSQL and libSQL
compositions -- fully assembled and tested since the durable-stores group --
could not be stored in it at all. Not a wiring oversight: a type error. The
runtime could only ever hold one backend shape, and it was the in-memory one.

Introduce two narrow dyn-safe seams:

  * SignerContinuationDriver (attested_runtime) -- exactly the three calls the
    resume path makes: assert_binding_owner, verify_and_sign,
    broadcast_signed_continuation. Kept minimal deliberately; widening it
    would let callers reach past the ownership assertion into verify/
    broadcast.
  * AttestedComposition (composition) -- bindings() + driver(). Raise-path
    methods stay OFF it: the raise hook holds the concrete composition, so
    exposing register_attested_gate here would hand every holder of the
    erased graph the ability to seal grants.

RebornAttestedContinuation now holds the erased driver, so the resume path
cannot observe which persistence backend it is running on.

This is the blocker removal only -- no behaviour change, and the production
path still composes the in-memory graph. Selecting the durable backends
additionally needs DurableBackend threaded into build_backend_production
(it is consumed earlier, on RootFilesystemBundle) plus RPC-endpoint config,
which is a separate change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
zmanian and others added 4 commits July 30, 2026 00:23
The erasure made durable compositions storable; this makes one get chosen.

build_backend_production now receives the deployment's database handle and
selects the durable graph when THREE things hold together: a database handle,
the attested-broadcast feature, and at least one configured chain RPC
endpoint. All three are required because a durable graph without a
broadcaster would persist grants for signatures it can never submit --
durable bookkeeping around a dead end. Falling back never weakens a check; it
narrows durability, and a restart then drops pending gates outright rather
than leaving them half-resolvable.

Three supporting changes fell out of doing this honestly:

  * assemble() now takes the binding store TYPED rather than as an erased
    handle, and derives both the async store view and the sync resume-port
    view from that one value. Two parameters could be handed different
    stores, and a resume port that admits a gate the driver cannot verify is
    exactly the divergence this path must not have. The durable module
    already closed this gap for its own assembly; assemble() was the way
    around it, and a test was in fact taking that route.

  * The resume port is now built FROM the composition, so it reads the same
    bindings the driver verifies against on every backend.

  * present_env() reads through the shared runtime-env seam instead of
    std::env::var, so attested config honours the same override mechanism as
    the rest of composition -- and so selection is testable without set_var
    (this crate forbids unsafe).

AttestedComposition gains broadcasts(), which is the documented guard made
observable: the in-memory fallback wires the non-submitting broadcaster, and
"signed but silently never broadcast" is the one failure this path cannot
otherwise detect from outside. The three selection tests pin the
discriminator in both directions.

Custodial keys stay process-scoped even on the durable path, which is
deliberate: custodial mainnet signing is already refused without a KMS
backend, and the durable path exists for the external-wallet ceremonies,
which hold no key here. Durable custodial custody means wiring KMS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
invoke_capability intercepted request_signature and called the composition
raise hook directly, returning BEFORE invoke_json ran the kernel authorize()
fold. That skipped trust classification, capability grants, runtime policy,
credential pre-flight, persistent approval, and the sealed Authorized
witness. Demonstrated: this exact call with an EMPTY grant set reached the
hook, so an ungranted caller -- prompt injection included -- could mint a
human-facing approval prompt for a transaction of its choosing. The human
approval boundary still stood, but a signing capability must not depend on it
as its only gate.

It was a rebase artifact, not a design decision: authorization used to run
before invoke_capability and upstream moved it inside the fold. An external
security review rated it HIGH and rejected the two cheap fixes -- partially
reimplementing authorization in production.rs would create a second,
divergent authority path, and authorize() cannot serve as a pre-check because
it starts run state, prepares obligations, and mints a single-use witness.

Fail-closed for now: the intercept is gone, so request_signature flows
through the fold like every other capability and reaches only the
deliberately-refusing first-party handler. Raising is unavailable rather than
unauthorized. The correct fix is to raise as an AUTHORIZED DISPATCH RESULT --
invoke the hook from the dispatcher, which already receives the sealed
Authorized, and carry the gate back through a neutral typed deferred channel
that DefaultHostRuntime maps to AttestedSigningRequired, the same shape
ApprovalRequired and AuthRequired already use. The kernel stays free of
attested/chain/crypto types.

Two pre-existing defects surfaced while testing this, both real:

  * builtin.request_signature referenced an input schema
    (schemas/builtin/request_signature.input.v1.json) that was never added,
    so granting the capability failed surface construction outright -- it
    could never have been used. Added, mirroring RequestSignatureParams and
    deliberately leaving `decoded` free-form so a chain-free layer does not
    grow a second source of truth about transaction shape.
  * The builtin package expectations omitted the capability entirely and
    defaulted its permission to Allow; it declares Ask.

Tests now pin the boundary: an ungranted caller must be refused with
Authorization, and a wired hook must NOT be reached from invoke_capability
even for a granted caller. Both carry the note that they flip when the
authorized-dispatch rework lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The test set IRONCLAW_DISABLE_OS_KEYCHAIN via raw std::env::set_var, which
trips the hermetic-env guard (process env is global, and the mutation is UB
on 1.82+). It also was not needed: the justification comment claimed the
custodial keystore build reaches for the OS keychain, but SecretsCrypto's
generate() only draws random bytes. Verified by running the test with the
variable unset in the environment -- it passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@zmanian
zmanian force-pushed the attested-g8-ledger-product branch from a814f8f to 6487113 Compare July 29, 2026 23:48
@zmanian
zmanian force-pushed the attested-g9-gate-ingress branch from 8c00165 to 7f1e445 Compare July 29, 2026 23:48

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant