Repository navigation
feat(gateway): plan edits become constraints for the caller's agent, never mutations (product item 10) - #432
Merged
Merged
Conversation
…s, never mutations Adds planEditsToIntent (product pack section 5, item 10): a pure, total function that reads untrusted `edits` against a PlanPresentation and produces a typed PlanEditIntent — exclusion/bound constraints plus layout-only preferences — for the CALLER's own agent to re-plan against. PCC never applies an edit to a plan (AGENTS SYNTHESIZE; PCC ENFORCES). Dragging a graph never becomes a constraint. Read-once per edit inside one try/catch (never inspecting the thrown value), modeled on plan-presentation.ts's own discipline without touching that file. Money context (currency/decimals) comes only from the presentation, never the edit. Sealed/invalid presentations refuse every semantic edit unconditionally; layout edits still land. Normalization dedups identical constraints, keeps the stricter bound (min of max-*, max of min-tier), and is independent of edit order except for refused[].index. implementer-charlie Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… price edits before compile This is the owning lane's review of 6bc1516 (implementer-charlie). Three corrections; the first and third fix gaps in the lane's own spec. - Bounded work. A batch over 256 edits is now refused as a whole, with ONE entry. Before, the code pushed one refusal per element, so a sparse array claiming 2^32 - 1 entries meant unbounded work and memory. - Proxy-safe. The code assumed a real array, but Array.isArray throws on a revoked Proxy, and a proxied `length` can throw or lie. Both are now read once, inside a guard, and the length must be a safe non-negative integer; otherwise the answer is one 'unreadable' refusal, never a throw. - Price edits work before compilation, which is when a user edits: - the CURRENCY comes from the presentation (the compiled preview's, or else the one live currency the server re-read; two live currencies give no context); - the DECIMALS come from the server's SETTLEMENT_TOKEN_DECIMALS table, never the edit; - a preview whose decimals disagree with the table is not trusted. Before, the decimals came only from compiled money, so every price edit was refused until compile. Tests: 46 (+3 over 6bc1516, which had 43): the proxy and sparse cases, the pre-compile money context and the no-context cases, and layer B freezing a plan even when its state disagrees. With external-plan-trace, 104 tests pass. tsc is clean for the service, its test and plan-presentation. 18 mutation checks: 17 killed. E8 (the per-edit currency taken from the edit) is EQUIVALENT: the output constraints are rebuilt from the stricter-wins maps with the presentation's currency. That rebuild is the guard, and E8b (the rebuild's currency changed) is killed. The first run had one survivor, E2 (layer B alone does not freeze), which was a test gap and is now killed. pcc-composition 8a0f4de0, goal pcc-reconciliation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t-intents #432 is based on #357. This brings in 3475654 (M1: the execution contract is bound to its planHash, binding and unit), 22fe64d (M2: verdicts are an exact cover of the nodes and a refusal's two lists agree) and 2a6a71a (their mutation-driven test additions). No conflicts. Agent: implementer-india Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
…tion, taken before the edits are read (#432, F1) F1 (astra, #432 r1): planEditsToIntent copied the basis, then read the untrusted edits array's `length`, and only afterwards read the presentation's layer, state, nodes and money context. A getter on `length` could therefore change what the gates decided: a sealed plan accepted a semantic removal, an invalid one with nodes was revived, and a node list set to null made the later `.map()` throw. The presentation is now read exactly once, first, into owned data (`readFacts`: basis, layer, state, the node ids, the live currencies and the preview's currency and decimals), and every later decision uses only that snapshot. A node list that is not an array, or holds anything but nodes with a string id, makes the presentation invalid (no semantic edit passes, no node is known); a presentation that cannot be read at all is the same, and nothing throws. Tests: the reviewer's Proxy reproduction (flipping layer and state), the same for an invalid presentation with nodes, for an added node and a moved money context, the p.nodes = null totality case, a table of unreadable node lists, and an unreadable presentation. All but the last failed at d59a22d. Agent: implementer-india Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
…fferent notes (#432, F2) F2 (astra, #432 r1): the note de-duplication key mapped both a plan-wide note (null) and a note on a node whose id is the empty string to the same string, so only one of the two survived and the survivor's scope depended on input order. The key is now the tuple JSON.stringify([nodeId, text]), where a plan-wide note is null. Exact duplicates are still one note. Tests: the reviewer's case in both input orders, and the exact-duplicate cases for each scope. Both failed at d59a22d. Agent: implementer-india Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
…ne break (#432, F3: not reproduced) F3 (astra, #432 r1) says the validators accept a final newline because of `$`. Not reproduced: in JavaScript `$` without the m flag matches only at the very end of the input, and none of the three patterns has the m flag. The reviewer's three cases (maxBaseUnits "5\n", an operator plus "\n", capabilityId "cap-mail\n") already refuse with malformed-value, as does every other line terminator, leading or trailing. No code change. The tests are kept as pins, and fail if a pattern ever gains the m flag or a trim. Agent: implementer-india Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
…st it only there (#432, F4) F4 (astra, #432 r1, LOW): the section header and test names claimed order independence without qualification, but distinct notes keep their input order and conflicting layout edits are last-write-wins (both intended, both already pinned by their own tests). planEditsToIntent's comment now states the two exceptions (and that `refused` carries input indices). The headers and names of the existing order tests are narrowed to what they cover, and two tests are added: the exceptions themselves with swapped inputs, and every permutation (reversed, all rotations, 200 seeded shuffles) of a list that has one note and layout edits for distinct node and kind pairs giving identical constraints and layout. Agent: implementer-india Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
…ents Merges 384e097 (a test pinning that M1 keeps presenting an economics-agreement deal). Tests only on the #357 side; no conflicts. Agent: implementer-india Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
…e, no edits; #357 landed, retarget to master) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSBxBEX3sn9DPSqihyjuZE
…ents (plain merge, no edits; fresh CI) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSBxBEX3sn9DPSqihyjuZE
LamaSu
had a problem deploying
to
trusted-checks
October 4, 2026 06:43 — with
GitHub Actions
Failure
LamaSu
marked this pull request as ready for review
October 4, 2026 13:09
LamaSu
had a problem deploying
to
trusted-checks
October 4, 2026 13:10 — with
GitHub Actions
Failure
This branch had an error being deployed
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.
Draft, stacked on #357 (PlanPresentation). Product pack section 5, item 10: "safe user edits before acceptance become new constraints/intents for the caller's agent; dragging a graph does not mutate execution semantics."
What it does
planEditsToIntent(presentation, edits)turns a batch of untyped user edits, made against a server-builtPlanPresentation, into a typedpcc.plan-edit-intent.v1. It goes to the caller's own agent to re-plan against.PCC never applies an edit to a plan. PCC is not the planner ("agents synthesize; PCC enforces"), and the UI is not authority.
authority: "none".unknown-op.move-nodeandcollapsebecomelayoutpreferences (layers D/E) and never a constraint.plan-sealed; layout still works. An invalid presentation refuses semantic edits the same way.no-money-context.unreadable.unreadablerefusal.Not in this PR
The HTTP route that returns the intent to the caller's agent. It belongs with the R9 routes (#391), after #391's first cross-family verdict.
Tests (
src/__tests__/plan-edit-intents.test.ts, 46)The presentations are real (
presentPlanover the accept seam) plus hand-typed edge cases. The tests cover purity (deep-frozen inputs), totality (garbage, throwing getters, proxied, revoked and sparse arrays), every malformed value, sealed and invalid gates, the money context, stricter-wins, order independence and layout.With
external-plan-trace: 104/104. tsc is clean.Mutation checks: 18, 17 killed. E8 (a per-edit currency) is EQUIVALENT: the output is rebuilt with the presentation's currency, and E8b, which changes the rebuild, is killed.
Written by a sonnet subagent (implementer-charlie, 6bc1516) to the lane's spec. The lane's Opus review fixed three issues (d59a22d): bounded work, proxy-safety, and price edits before compile.
pcc-composition 8a0f4de0, goal pcc-reconciliation.
🤖 Generated with Claude Code