feat(route): route new through the warm daemon, byte-identically (NRN-230 PR C) - #118
Merged
Conversation
…-230 PR C)
`norn new`'s agent-facing paths now route through a live `norn serve` daemon
via the NRN-229 routing seam, rendering byte-identically to direct execution —
the fifth routed mutation after set/edit/move/delete/rewrite-wikilink.
- src/new/route.rs: pure `to_mcp_arguments` / `reconstruct` / `emit` (the
`set::route` template). `to_mcp_arguments` builds the `vault.new` NewParams
wire object (path/rule/title/vars/field/field_json/parents/force/confirm);
`reconstruct` serde-round-trips PR B's NewReport out of `{report}`; `emit`
renders through the SAME `new::report::{render_records,render_json}` the
direct path uses. Refusals render format-INDEPENDENTLY as `error: {message}`
+ exit 2 (audit F5 — new has no JSON error envelope, unlike set).
- src/lib.rs: `try_route_new` (unix + non-unix stub) + Command::New wiring.
Routes BEFORE the local mutation lock (the daemon takes the same per-vault
lock in-process). `--var` is parsed CLI-side with the direct path's
`parse_var_args` before routing OR the lock, so a malformed `--var` refuses
pre-send byte-identically. Confirm ladder mirrors new's direct dry-run/apply
tree (TTY keyed off STDOUT, not stdin like set). Gated to Direct:
`--body-from-stdin`, interactive TTY, and `routing_forced_direct`
(`--config` / `--no-cache-refresh`) — the #113 lesson.
- Unlike set, both `--field` AND `--field-json` route: vault.new's params carry
them as ordered Vec<String> token lists (audit F6), so the wire preserves
last-wins.
- tests/serve_new_routing.rs: a 30-case byte-identity matrix (three modes ×
dry-run/apply × records/json + title-ignored warning + valid --field-json +
NRN-114 empty-frontmatter + every coded refusal), (stdout,stderr,exit)
triples + full post-state snapshots + `served vault.new` markers; {{seq}}
dry-run-predicts / interleaved-routed+direct-applies-stay-unique;
--body-from-stdin and forced-direct-flags served-count guards; malformed
--var pre-send refusal; and the held-lock contention safety case (safety
pinned, not byte-identity — the same legitimate divergence set documents).
- CHANGELOG: operator-facing PR C entry in the NRN-229 routing register.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Final PR of NRN-230:
norn new's agent-facing paths now route through the warm daemon viaroute_call, byte-identically — completing the create verb and leavingrepair/apply(NRN-231) as the last unrouted mutations.Routes (proven by a 30-case routed-vs-direct matrix: (stdout, stderr, exit) triples + full-vault byte snapshots +
served vault.newmarkers):--asrule / inbox fallback — × dry-run/apply × records/json--fieldand--field-json(unlikeset: vault.new already carries ordered token lists, so nothing gates),--var(parsed CLI-side, malformed--varerrors pre-send byte-identically),--title/--force/--parents{{seq}}: routed dry-run predicts non-bindingly, routed apply resolves under the shared flock; a routed/direct/routed interleave allocates unique idserror: {message}+ exit 2 (thenewpath deliberately has no JSON error envelope), including body-scaffold render failures and the NRN-114 empty-frontmatter quirkGates to Direct (served-count 0 asserted):
--body-from-stdin, interactive TTY without--yes, androuting_forced_direct(--config/--no-cache-refresh) — with regression shapes for the #113 bypass class.Honest divergence pinned, not fudged: lock-timeout prose differs routed-vs-direct (same as the set/move precedent); the suite pins safety (non-zero exit, nothing written, contended call genuinely served) rather than byte-identity there.
Review
Adversarial review: APPROVE, no changes — targeted the two blocker classes from the sibling PRs: same-file guarantee probed with path-normalization shapes (
a/../b.md,./dot.md, trailing slash,--parentsdeep create) and a{{date}}-templated rule (daemon resolves once; no CLI-side second computation to disagree with); consent ladder verified line-for-line against the direct arm (both key TTY off stdout; routed sends confirm:true only for--yes); dead-daemon and adversarial-wire probes confirmed clean pre-send fallback / post-send-uncertain per ADR 0011.Surfaced pre-existing (filed separately, NRN-s16):
norn newignores--configon both paths — the guard still forces Direct correctly and both paths behave identically.Verification
cargo check --workspace --locked/cargo test --workspace(2484 passed, 0 failed; serve_new_routing 7/7) /clippy --all-targets -D warnings/fmt --check— clean,Cargo.lockuntouched.Closes NRN-230 (3/3, after #116 and #117).