2026-05-11-envelope-precedence-docs-drift

Fix: routing-gates precedence docstring + concept docs (drift)

Date: 2026-05-11 Status: shipped Slug: envelope-precedence-docs-drift Branch: fix/envelope-precedence-docs-drift Closes: the HIGH-severity finding from #293

Summary

The routing-gates applyTrustGates source-of-truth docstring claimed signal precedence was tier → xdr_risk → anomaly_score. The actual implementation evaluates xdr_risk → anomaly → tier. PR #293's stress tests caught the drift; this PR aligns the docstring and the two external concept docs that inherited the inverted order.

No behavior change — code was always correct; the docs were wrong.

What changed

src/security/trust-envelope/routing-gates.ts

The header docstring is reordered to match the actual if/else if chain at lines 114-130. The new docstring also explicitly names the source labels ("xdr_risk" / "anomaly" / "tier" / null) and notes that the label is "anomaly", not "anomaly_score" (a separate consistency finding from #293).

docs/concepts/trust-envelope.mdx

The "Signal precedence" block now lists XDR first, anomaly second, tier third. Adds an explicit note that scope filtering happens before the signal gates. Documents that source may be null and that the anomaly label is "anomaly", not "anomaly_score".

docs/concepts/agent-reputation.mdx

Same precedence reordering. Adds context on why XDR runs first (highest external reliability) and clarifies that the tier acts as the floor when neither XDR nor anomaly fired.

src/security/trust-envelope/__tests__/envelope-stress.test.ts

Two assertions had FINDING: comments documenting the drift. Now that the docs match the code, the comments are reframed to "this is the canonical order as of 2026-05-11" — the assertions still pass, the test now reads as positive coverage rather than a known-broken anchor.

Verification

  • pnpm tsgo — exit 0
  • pnpm exec oxfmt --check / oxlint --type-aware — clean
  • pnpm test:fast — 7813/0 (no regression, stress suite still green)

What this PR does NOT do

Doesn't touch the other two findings from #293:

  • MEDIUM: spent_usd not clamped in synth → next PR
  • MEDIUM: empty br_scope.models=[] semantics → needs design call

The remaining stress-test assertions still carry FINDING: comments for those.

Lockstep

  • TS / Python SDK / MCP — no public API surface change
  • OpenAPI — no new routes
  • Ship log — this file