devlogJuly 16, 2026

The feature that was never running

A project-config overlay shipped, passed every test, and did nothing at all on the only code path that mattered. The adversarial review found it four days later.

Four days ago RoutePlane shipped a project-scoped config overlay. A repo can carry a .routeplane.yaml that tightens the daemon’s operator config: monotone toward safety, never looser. This week I sat down to extend it: let a project file append guardrail regex rules. Before writing a line of the extension, the design went through an adversarial review. An AI reviewer, pointed at the real source and told to attack the design rather than approve it, found a P0 in the slice that had already shipped.

The daemon’s serve path chdir’s into ~/.routeplane before loading config. Overlay discovery walks up from the live working directory looking for .routeplane.yaml. After the chdir, that walk starts in the home and never sees the repo. Unit tests passed. They never took the chdir path. Detached start spawns the serve child inheriting the launcher CWD, so the same bug covered that route too. The feature worked in every test and was inert in production. A week of “project config” that did nothing on the only path that matters.

The fix is a narrow seam

The repair is small on purpose. Capture the launch directory in serve before the chdir, and thread it through a new load_config_from(source, project_root) seam. load_config delegates to it with None. All thirteen other call sites keep historical working-directory behaviour byte-for-byte. A deleted or unreadable CWD yields None and falls back; it never panics. A regression test pins the launch-directory path so the next chdir refactor can’t silently re-break discovery.

That is the whole P0. No redesign of overlay semantics. Just: discovery must start from where the operator launched the daemon, not from where the process decided to live afterward.

Append-only, tighten-only

With discovery actually working, the extension is the same monotone rule as the existing egress and concurrency overlay, applied to guardrails.

A project’s .routeplane.yaml may append guardrails.custom_patterns. It may not remove, replace, rename-over, or weaken an operator rule. Operator-name collisions are refused. Duplicate project names among accepted rules are refused. The operator’s plugin value is only ever appended to, or created when wholly absent. A malformed operator shape (null, scalar, non-array custom_patterns) is refused, never silently “repaired,” so assembly’s fail-fast on bad operator config is preserved. The plugins: map stays non-overlayable.

A repo file is untrusted input. Each project rule is validated on its own: one malformed rule is ignored while its siblings are still considered. A rule is refused, with operator config left byte-untouched, when it collides with an operator name, has an empty name or pattern, fails to compile, exceeds the plugin’s 1 MiB compiled-size limit, carries a pattern source over 4096 bytes, or arrives past a 64-rule accepted cap.

Enforcement ordering is unchanged and safe by construction: Block runs on pre-redaction text on every path, so an appended broad redact can never mask an operator block. That claim is not a comment in a design doc. The tests drive the real production wiring (overlay → GuardrailConfigcompileGuardrailsPlugin::with_static → real App pipeline) on both request and stream paths. Order-masking is what the tests assert, not what they assume.

The review process is the product

The pipeline for this slice was deliberate: design, then adversarial design review against the real source, then build, then adversarial code review, then a fix round, then an independent verification pass. Every review seat was a model, not a person; the value came from pointing them at the source and asking them to break it. The design review found the chdir P0. The code review verdict was fix-then-merge, with no production safety violations left open and the P0 confirmed closed. It still returned three things worth fixing first:

  • Validation-work budget, not just accepted rules. The 64-accepted cap used to run after every entry’s regex compiled, and refused rules never counted against it. A hostile file could force thousands of up-to-1-MiB compilations at daemon startup. Now a 256-entry budget drops the excess before any per-entry validation (one aggregate notice), and the 64-accepted cap breaks the loop before deserialising or compiling.
  • Complete refusal logging. The over-cap aggregate carries the exact count of dropped rules. An unsupported sibling key under guardrails: states that the entire branch was ignored and no project rules were applied.
  • Rule-name hygiene. Names are trimmed; blank or control-character-bearing names are rejected. Log lines that include rule names are not a free injection surface.

None of that is exotic. All of it is the class of bug that passes its own unit tests and misbehaves under a hostile or merely careless repo file.

Docs corrected, not papered over

The same adversarial pass caught two pre-existing overclaims in the docs. I fixed the docs rather than pretend the runtime already did more than it does:

  • Non-streaming responses are redact-only today. Block enforcement on that path is a known follow-up, not a present guarantee.
  • Cross-chunk block detection only holds within the ~128-character carry. Patterns that span farther than that are not promised.

Other known follow-ups, pre-existing and documented: hot-reload freezes compiled rules; config validate does not exercise the overlay. Per-project spend budget stays a don’t-rebuild, since PolicyHook and max_spend_micro_usd already enforce it.

What shipped

Three commits on craft/redaction-overlay: the launch-directory discovery fix, the append-only guardrail overlay, and the post-review hardening. 945 sdk+app tests and 34 guardrails tests green; fmt, clippy, schema, and keysweep clean. Merged to main the same day.

The regex caps are not the interesting part. The domain is: a local daemon whose operator config is sacred, a repo file that may only tighten it, and a discovery path honest enough that the overlay is not a fiction. A feature that only works in tests is not a feature. The chdir bug made that concrete. The review process made it findable before the next week of inert “safety.”


RoutePlane is a craft project. No revenue goal. Success is a binary I’m proud of and the write-ups, not a customer count.