Skip to content

[AS-319] Tech note: Router customizations — Rhai vs Coprocessors - #44

Open
ilan-bel wants to merge 2 commits into
mainfrom
docs/as-319-rhai-coprocessors
Open

ilan-bel wants to merge 2 commits into
mainfrom
docs/as-319-rhai-coprocessors

Conversation

@ilan-bel

@ilan-bel ilan-bel commented Jun 5, 2026

Copy link
Copy Markdown

Summary

Adds docs/router-customizations.md. Decision-first comparison table covering latency, deployability, failure modes, and what each is good at. Includes worked examples in Rhai (header transformation) and a coprocessor (claim enrichment via directory I/O). Surfaces the fail-open vs fail-closed pitfall on coprocessor stages.

Tracks AS-319.

Note

Placement: apollographql/docs archived; apollographql/platform-docs requires SAML.

Test plan

  • Renders cleanly
  • Apollo docs and apollosolutions links resolve
  • Rhai snippet uses current Rhai surface (Router 2.x)

🤖 Generated with Claude Code

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

@apollo-solutions-reviewer apollo-solutions-reviewer 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.

The decision framing (table, rule-of-thumb thresholds, failure-mode section) is genuinely useful and reads well. Two code samples use APIs that do not exist, which is blocking for a customer-facing reference doc whose stated purpose is correct, copy-pasteable examples.

  1. Rhai header methods are fictitious. The router's Rhai header object uses an indexed accessor, not method calls. headers.remove(...), headers.contains(...), and headers.set(...) are not part of the documented Rhai surface (https://www.apollographql.com/docs/graphos/routing/customization/rhai/reference). Reads use the indexed accessor (request.subgraph.headers["x"]) or values() for multi-value; writes assign to the index. Removal is done by assigning empty/overwriting, not .remove(). As written, this script will not run. The subgraph_service(service, subgraph) signature and request.subgraph.headers path are correct; only the method calls are wrong.

  2. The JS coprocessor sample imports a package and class that do not exist. There is no @apollo/router-coprocessor package exposing an ApolloRouterCoprocessor builder with .onSupergraphRequest().listen(). Apollo coprocessors are plain HTTP servers that respond to the router's stage payloads; this repo's own coprocessor/ uses Express + jose. Either rewrite the snippet against a real HTTP-server shape (ideally mirroring coprocessor/src) or excerpt the existing coprocessor so the "canonical example layout" claim holds.

  3. Soften or verify the fail-open default claim. "The defaults are fail-open" is a security-relevant assertion; confirm it against the coprocessor configuration reference before stating it as the default, since getting this wrong in a doc about auth bypass is the exact failure the section warns about.

Fix the two API errors at minimum; align the coprocessor example with the in-repo implementation and confirm the failure-default wording.

Comment thread docs/router-customizations.md Outdated
Comment on lines +31 to +35
if request.subgraph.headers.contains("authorization") {
request.subgraph.headers.set("x-request-from-router", "true");
}
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These header methods don't exist in the Rhai surface. The router exposes headers via an indexed accessor, not .remove()/.contains()/.set(). Per the Rhai reference, writes assign to the index and multi-value reads use values(); e.g. request.subgraph.headers["x-request-from-router"] = "true"; and read with request.subgraph.headers["authorization"]. There is no documented .remove(); overwrite/clear instead. As written this script fails to run.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verified at 33f64b1: the Rhai snippet now uses the indexed accessor for writes (request.subgraph.headers["x-request-from-router"] = "true") and contains() for the read, with no .remove()/.set() calls. The prose states there is no .remove() or .set() method and routes header removal to the YAML headers plugin. This thread can be resolved.

Comment thread docs/router-customizations.md Outdated
Comment on lines +50 to +55

new ApolloRouterCoprocessor()
.onSupergraphRequest(async (req) => {
const user = await directory.lookupBySub(req.context.entries.jwt_claims.sub);
req.context.entries.user_groups = user.groups;
return req;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@apollo/router-coprocessor and the ApolloRouterCoprocessor fluent builder don't exist. Coprocessors are plain HTTP servers handling the router's stage payloads; this repo's coprocessor/src uses Express + jose. Rewrite against that real shape or excerpt the in-repo coprocessor so the "canonical example layout" reference is accurate.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verified at 33f64b1: the coprocessor sample is now a plain Express HTTP server (import express, app.post("/"), reads payload.stage, returns res.json(payload)) with no @apollo/router-coprocessor import or ApolloRouterCoprocessor builder. The prose states there is no Apollo SDK and points to the in-repo coprocessor/ directory; I confirmed coprocessor/src/index.ts uses the same Express shape. This thread can be resolved.

Comment thread docs/router-customizations.md Outdated

## See also

- [Router Rhai docs](https://www.apollographql.com/docs/router/customizations/rhai)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"The defaults are fail-open" is security-relevant; confirm against the coprocessor configuration reference before asserting it as the default, then keep or correct the wording.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verified at 33f64b1: the fail-open claim is reversed. The table and Failure modes section now state coprocessors are fail-closed (unreachable/non-2xx/timeout/shape-mismatch returns an error to the client) and note there is no per-stage fail_open/fail_closed toggle, matching the coprocessor configuration reference. This thread can be resolved.

… (AS-319)

Address docs-reviewer feedback on three blocking accuracy issues:

1. Rhai header methods. The Rhai surface has no .remove()/.set() —
   only indexed accessor (`headers["x"] = ...`), contains(), and
   values() for multi-value reads. Rewrite the snippet against the
   real API, and note that header removal lives in the YAML
   header-propagation plugin, not Rhai.
2. Coprocessor SDK. `@apollo/router-coprocessor` and the
   ApolloRouterCoprocessor fluent builder don't exist; coprocessors
   are plain HTTP servers. Rewrite against the actual shape used by
   this repo's coprocessor/src (Express + JSON payload echo).
3. Failure mode. Coprocessor failures are fail-closed by default
   (timeout / non-2xx / shape mismatch → error to client), with no
   per-stage fail-open toggle. The previous "defaults are fail-open"
   claim was wrong; reframe around the documented behaviour and what
   that implies for coprocessors on non-critical stages.

Also repoint Rhai and coprocessor See-also links to the current
/docs/graphos/routing/customization/ IA.

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

@apollo-solutions-reviewer apollo-solutions-reviewer 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.

Re-reviewed at 33f64b1. All three blocking items from the prior review are resolved and verified against the file and the in-repo coprocessor.

  1. Rhai header surface: the snippet uses the indexed accessor for writes and contains() for reads, with no fictitious .remove()/.set() method calls. The prose correctly directs header removal to the YAML headers plugin.
  2. Coprocessor example: rewritten as a plain Express HTTP server matching coprocessor/src/index.ts (app.post("/"), reads payload.stage, returns res.json(payload)); the bogus @apollo/router-coprocessor package and ApolloRouterCoprocessor builder are gone.
  3. Failure default: the fail-open claim is reversed to fail-closed throughout, consistent with Router coprocessor behavior, and correctly notes there is no per-stage toggle.

The decision table, rule-of-thumb thresholds, and failure-mode section remain clear and accurate. Examples are correct and copy-pasteable. Approving.

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.

2 participants