From 336f97ed601c65005cacd4c0ee79c80b4652a79f Mon Sep 17 00:00:00 2001 From: Teakowa <27560638+Teakowa@users.noreply.github.com> Date: Mon, 19 Oct 2026 19:06:15 +0800 Subject: [PATCH 1/3] docs(adr): propose structured statement edits for raw Workshop ADR-0029 decides the write direction that ADR-0024 left open: clients submit rule-content-v1 statements, workshop-rs converts and emits them as checked source edits, and Wright exposes editStatements over serve and MCP, validated through the existing edit-transaction path. --- docs/adr/0029-structured-statement-edits.md | 132 ++++++++++++++++++++ docs/adr/README.md | 1 + 2 files changed, 133 insertions(+) create mode 100644 docs/adr/0029-structured-statement-edits.md diff --git a/docs/adr/0029-structured-statement-edits.md b/docs/adr/0029-structured-statement-edits.md new file mode 100644 index 00000000..0799f22c --- /dev/null +++ b/docs/adr/0029-structured-statement-edits.md @@ -0,0 +1,132 @@ +# ADR-0029: Structured statement edits for raw Workshop + +- Status: Proposed +- Date: 2026-10-19 +- Related: [ADR-0021](0021-name-and-signature-lookup.md), [ADR-0024](0024-structured-rule-content.md), [ADR-0028](0028-profile-applies-to-workshop-input-only.md), [Agent contract](../agent-contract.md), `workshop-rs` `docs/source-preservation.md` and its ADR-0017 + +## Context + +ADR-0024 made rule content readable as `workshop-rs/rule-content-v1` call trees and left "a typed authoring API" undecided. Two consumers now need the write direction: + +- **Agents.** To add or change a condition or action, an agent writes Workshop text, using `lookup` (ADR-0021) for spellings. It then checks the result with `validateEditTransaction`. The agent therefore has to know locale spellings, number and string formatting, variable syntax, operator layout, and indentation. Goal principle 4 promises structured access instead. +- **A structured editing view.** A read-only prototype builds the in-game "create condition / create action" forms from `lookup` signatures. Each form produces a correct `rule-content-v1` node: its shape matches the content Wright returns for the same calls in a real project. No operation can turn that node into source text. The only way to write it today would be for the client to compose Workshop text itself. + +Wright already has most of the pieces it needs: + +- `workshop-rs` keeps the exact authored source, exposes rule, condition, and action spans, and produces checked, fail-closed `SourceEdit` replacements through `Program::edit_source`. Bytes outside an edit are unchanged. +- `validateEditTransaction` applies a text transaction, reparses and revalidates it through the session's `workshop-rs` path, and returns diagnostics or a preview. A stale source refuses with `edit-stale-source`. +- `rule-content-v1` is a deterministic, locale-independent export of canonical statements. + +Two pieces are missing: + +- A way back from a content node to the public model. +- Statement-level emission. `workshop-rs` emits only complete programs, and canonical whole-program regeneration does not preserve comments or formatting. + +## Decision + +### 1. Input form: `rule-content-v1` list items + +A statement is written in the form `rule-content-v1` already uses for list items: + +- A condition is `{"value": }`. +- An action is an action node, `{"call": id, "args": [...]}`. + +Either may carry `"disabled": true`. Spans are output-only and are rejected on input. No second authoring format is introduced. A node that a client read from `rules` with `content: true` is valid input unchanged. + +### 2. Owner split + +| Owner | Responsibility | +| --- | --- | +| `workshop-rs` | Converts a `rule-content-v1` statement into the public model. Unknown ids, wrong argument counts, internal sentinels, and nodes that cannot form a statement fail with a structured error. Produces the checked `SourceEdit` that inserts, replaces, or deletes one condition or action of a parsed program at a statement address. That includes the statement's canonical text in the source file's Workshop locale and its indentation relative to the surrounding source. Owns the round-trip contract in decision 5. | +| Wright | Owns the operation, addressing and freshness, the transaction it assembles from owner edits, its validation through the existing `validateEditTransaction` path, refusals, and presentation in `serve` and MCP. | + +Wright composes no Workshop text and reads no catalog spellings to build it. Locale, spelling, literal formatting, variable syntax, operator layout, and indentation are decided only by `workshop-rs`. + +### 3. Surface: `editStatements` + +`wright-agent/v1` gains one operation: + +```json +{ + "op": "editStatements", + "inputIdentity": "", + "edits": [ + { "rule": 3, "list": "actions", "index": 2, "change": "insert", + "statement": { "call": "startThrottleInDirection", "args": [ ... ] } }, + { "rule": "Cover enums 108", "list": "conditions", "index": 0, "change": "replace", + "statement": { "value": { "call": "==", "args": [ ... ] } } } + ] +} +``` + +**Addressing.** +- `rule` is the same `Address` that `rules` and `cfg` accept. +- `list` is `conditions` or `actions`. +- `index` is the position in that list. For actions this is the flat index space that `rules`, `cfg`, `references`, and findings already share. +- `change` is `insert`, `replace`, or `delete`. `insert` at the list length appends. `delete` takes no `statement`. + +**Atomicity.** +- Every address in a request refers to the program before any edit in that request. +- Two edits at the same address refuse, as zero-width conflicts do in `EditTransaction`. Edits do not see each other's results, so an agent that adds an `If` and its `End` sends both in one request. + +**Freshness.** `inputIdentity` must equal the current `project.inputIdentity`. Otherwise the request refuses with `edit-stale-source` and edits nothing, so a client cannot address statements in a program it has not read. + +**Result.** The result has the `validateEditTransaction` payload (`ok`, `exit`, `diagnostics`, and `preview` when valid) plus the assembled `transaction`. A client holding editor buffers can apply the transaction itself; other clients write the preview. Nothing is written by the operation: as with every Wright edit, applying it is the caller's write path. + +**Validity.** Whether the edited program is valid is decided by revalidation, not by the operation. An `If` without an `End`, or a value of the wrong type, comes back as `ok: false` with owner diagnostics. The operation adds no pairing, typing, or normalization rules of its own. + +**Locality.** Only the statements named by the request are converted and emitted. Other statements are left as source bytes. A rule whose existing content `rule-content-v1` cannot express, such as an unresolved ambiguous enum, can still take an insert or an edit to a different statement. + +**Surfaces.** `serve` exposes the operation, and the MCP adapter exposes it as `wright_edit_statements` with an input schema derived from the request schema (ADR-0020). + +### 4. Raw Workshop only + +`editStatements` applies to raw Workshop input. For an OverPy or OSTW input it refuses with a structured error that names the owning implementation and edits nothing. Like ADR-0028's profile rule, it neither silently ignores the request nor accepts it and does nothing. Canonical content of a source language is lowered output, and its statements do not map back to authored source. Source-language authoring needs its own owner capability, negotiated through the language provider protocol, and is not decided here. + +### 5. Round-trip contract + +For every statement a raw parse can produce whose content `rule-content-v1` can express, `workshop-rs` guarantees: + +``` +content(parse(emit(statement_from_content(c)))) == c +``` + +This is checked over the owner's corpora in every supported Workshop locale. The guarantee lets a client read a statement, change one argument, and write it back without any other change to the meaning of that statement. It does not promise byte-identical text: an edited statement is emitted canonically, even if the replaced text was spelled differently. + +## Alternatives considered + +- **The client composes text from `lookup` spellings and validates it with `validateEditTransaction`:** rejected. Every client, including agents, an editing view, and the benchmark, would carry its own copy of Workshop emission. Validation would catch invalid text, but not a valid statement emitted differently from canonical output. +- **Regenerate the whole program with the canonical emitter and diff it:** rejected. Canonical emission does not preserve comments or formatting, so one inserted action would rewrite the file. +- **A separate typed authoring format or Rust builder as the wire input:** rejected. It would add a second format next to `rule-content-v1` and force readers to translate between them. +- **Addressing nested arguments by path:** rejected. The statement is the unit of span, validation, and index space. Replacing the whole statement covers argument changes without a second address space. +- **An operation that also writes to disk:** rejected. It is inconsistent with the existing edit contract, where `validateEditTransaction`, `semanticRename`, and lint fixes return validated edits and the caller writes them. +- **Extending `validateEditTransaction` to accept structured edits:** rejected. That operation's contract is caller-proposed text, and mixing the two input kinds would blur its refusals and its MCP schema. + +## Consequences + +- Agents can add, change, and remove conditions and actions by catalog id and argument position, and they get owner diagnostics before any byte is written. +- A structured editing view can write what its forms build without owning any Workshop syntax. Where that view lives and what it looks like is a separate decision. +- `rule-content-v1` becomes an input format as well as an output format. Its stability now affects writers, and an incompatible change needs a new format id under the rules ADR-0024 set. +- Follow-up work: + - `workshop-rs`: content-to-model conversion, statement-level edits with locale and indentation, structured errors, and round-trip tests over its corpora. + - Wright: `editStatements` in `wright-driver`, the schema `$defs`, the agent contract, the MCP tool, driver tests on a real raw Workshop project in at least `en-US` and `zh-CN`, and refusal tests for stale input, conflicts, and source-language input. +- Authoring is limited by what `lookup` describes. Gaps found by the editing prototype are tracked as separate `lookup` and `rule-content-v1` work, outside this decision. They include missing value return types, missing signatures for structural actions such as `Set Global Variable`, partial parameter defaults, and unlocalized parameter names. + +## Compatibility impact + +- **`wright-agent/v1`:** additive. The new operation is advertised through `capabilities.operations`, and existing operations are unchanged. +- **`workshop-rs`:** additive public API. `rule-content-v1` keeps its format id and schema, and the input direction is a new guarantee on the same schema. +- **Emitted Workshop:** an edited statement is canonical `workshop-rs` emission in the file's locale. Statements and bytes outside the edit are unchanged, as `SourceEdit` already guarantees. +- **Unaffected:** source-language compilation and structural convergence (goal principle 7), lint, `validateEditTransaction`, and every read operation. + +## Scope boundaries + +- Not decided: + - rule-level edits: creating, deleting, or reordering rules, and changing a rule's name, event, or disabled state; + - variable and subroutine declarations; + - workshop settings, which `SettingSourceEdit` already covers; + - a CLI command. + Each needs its own decision when a consumer needs it. +- Not decided: source-language authoring for OverPy or OSTW (decision 4). +- Not decided: the form factor of a local editing view. +- The exact Rust API, error ids, and indentation rules belong to `workshop-rs`. The request field spellings above are illustrative until the Wright schema is written, but the addressing, atomicity, freshness, and write-path rules are decided here. diff --git a/docs/adr/README.md b/docs/adr/README.md index 6a98a2f3..d440b582 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -48,4 +48,5 @@ its audit date. * [ADR-0026: The agent benchmark lives with the benchmark suite](0026-agent-benchmark-ownership.md) * [ADR-0027: Project run cache and comparison against the previous run](0027-project-run-cache.md) * [ADR-0028: Transformation profiles apply to Workshop input only](0028-profile-applies-to-workshop-input-only.md) +* [ADR-0029: Structured statement edits for raw Workshop](0029-structured-statement-edits.md) * [Post-ADR-0010 decision inventory](post-0010-inventory.md) From 9dcfc6b238dd857b1ebf8c441528bee7c7bf2c52 Mon Sep 17 00:00:00 2001 From: Teakowa <27560638+Teakowa@users.noreply.github.com> Date: Mon, 19 Oct 2026 19:14:41 +0800 Subject: [PATCH 2/3] docs(adr): cover comments, empty lists, sources, and locale in ADR-0029 Statement comments survive replace and go with delete, inserting into an absent section creates it, editStatements takes the same optional sources as validateEditTransaction, and emission uses the session's resolved locale. The ADR also states that a replaced statement is visibly respelled in canonical form. --- docs/adr/0029-structured-statement-edits.md | 28 ++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/adr/0029-structured-statement-edits.md b/docs/adr/0029-structured-statement-edits.md index 0799f22c..cc9ea5a0 100644 --- a/docs/adr/0029-structured-statement-edits.md +++ b/docs/adr/0029-structured-statement-edits.md @@ -37,7 +37,7 @@ Either may carry `"disabled": true`. Spans are output-only and are rejected on i | Owner | Responsibility | | --- | --- | -| `workshop-rs` | Converts a `rule-content-v1` statement into the public model. Unknown ids, wrong argument counts, internal sentinels, and nodes that cannot form a statement fail with a structured error. Produces the checked `SourceEdit` that inserts, replaces, or deletes one condition or action of a parsed program at a statement address. That includes the statement's canonical text in the source file's Workshop locale and its indentation relative to the surrounding source. Owns the round-trip contract in decision 5. | +| `workshop-rs` | Converts a `rule-content-v1` statement into the public model. Unknown ids, wrong argument counts, internal sentinels, and nodes that cannot form a statement fail with a structured error. Produces the checked `SourceEdit` that inserts, replaces, or deletes one condition or action of a parsed program at a statement address. That includes the statement's canonical text in the session's resolved Workshop locale (decision 3), its indentation relative to the surrounding source, statement comments (decision 3), and creating a missing section. Owns the round-trip contract in decision 5. | | Wright | Owns the operation, addressing and freshness, the transaction it assembles from owner edits, its validation through the existing `validateEditTransaction` path, refusals, and presentation in `serve` and MCP. | Wright composes no Workshop text and reads no catalog spellings to build it. Locale, spelling, literal formatting, variable syntax, operator layout, and indentation are decided only by `workshop-rs`. @@ -71,6 +71,19 @@ Wright composes no Workshop text and reads no catalog spellings to build it. Loc **Freshness.** `inputIdentity` must equal the current `project.inputIdentity`. Otherwise the request refuses with `edit-stale-source` and edits nothing, so a client cannot address statements in a program it has not read. +**Unsaved buffers.** The request takes the same optional `sources` as `validateEditTransaction` (#472). When it is omitted or null, the program is loaded from the files on disk. When it is supplied, the program is loaded from those texts, which are also the precondition texts, and `inputIdentity` is checked against the identity of that loaded program. A client holding unsaved editor buffers can therefore address statements in the text it shows. + +**Locale.** An emitted statement uses the session's resolved Workshop locale for its source: the `--locale` override when the session has one, otherwise the locale detected for that source. This is the same resolution that reading uses, so a statement is written in the locale its neighbours were read in. + +**Statement comments.** A Workshop statement can carry a leading comment string, such as `"BOTTOM LINE"` before an action. `rule-content-v1` has no comment field, so: +- `replace` keeps the replaced statement's comment and changes only the statement; +- `delete` removes the statement together with its comment; +- `insert` writes no comment. + +Creating or editing a comment is out of scope (see Scope boundaries). + +**Empty lists.** A rule can have no conditions section at all. An `insert` into a list whose section is absent creates the section. After a `delete` of the last statement, the rule must reparse with an empty list; whether the empty section stays in the text is a `workshop-rs` layout decision. + **Result.** The result has the `validateEditTransaction` payload (`ok`, `exit`, `diagnostics`, and `preview` when valid) plus the assembled `transaction`. A client holding editor buffers can apply the transaction itself; other clients write the preview. Nothing is written by the operation: as with every Wright edit, applying it is the caller's write path. **Validity.** Whether the edited program is valid is decided by revalidation, not by the operation. An `If` without an `End`, or a value of the wrong type, comes back as `ok: false` with owner diagnostics. The operation adds no pairing, typing, or normalization rules of its own. @@ -93,6 +106,8 @@ content(parse(emit(statement_from_content(c)))) == c This is checked over the owner's corpora in every supported Workshop locale. The guarantee lets a client read a statement, change one argument, and write it back without any other change to the meaning of that statement. It does not promise byte-identical text: an edited statement is emitted canonically, even if the replaced text was spelled differently. +**Canonical form is visible.** A replaced statement is emitted in its canonical spelling, not in the spelling it was authored in. An alternative spelling the parser accepts, number formatting, and line wrapping all come back the way the canonical emitter writes them. Only the replaced statement changes, but a user who edits one argument can see the whole line respelled. This is accepted: it keeps emission in one place. Preserving the authored spelling would need a spelling-aware edit that the owner does not have. + ## Alternatives considered - **The client composes text from `lookup` spellings and validates it with `validateEditTransaction`:** rejected. Every client, including agents, an editing view, and the benchmark, would carry its own copy of Workshop emission. Validation would catch invalid text, but not a valid statement emitted differently from canonical output. @@ -108,8 +123,14 @@ This is checked over the owner's corpora in every supported Workshop locale. The - A structured editing view can write what its forms build without owning any Workshop syntax. Where that view lives and what it looks like is a separate decision. - `rule-content-v1` becomes an input format as well as an output format. Its stability now affects writers, and an incompatible change needs a new format id under the rules ADR-0024 set. - Follow-up work: - - `workshop-rs`: content-to-model conversion, statement-level edits with locale and indentation, structured errors, and round-trip tests over its corpora. - - Wright: `editStatements` in `wright-driver`, the schema `$defs`, the agent contract, the MCP tool, driver tests on a real raw Workshop project in at least `en-US` and `zh-CN`, and refusal tests for stale input, conflicts, and source-language input. + - `workshop-rs`: + - content-to-model conversion and structured errors; + - statement-level edits with locale, indentation, comment preservation, and section creation; + - round-trip tests over its corpora, including statements with comments and rules without a conditions section. + - Wright: + - `editStatements` in `wright-driver`, the schema `$defs`, the agent contract, and the MCP tool; + - driver tests on a real raw Workshop project in at least `en-US` and `zh-CN`, including a `--locale` override and supplied `sources`; + - refusal tests for stale input, conflicts, and source-language input. - Authoring is limited by what `lookup` describes. Gaps found by the editing prototype are tracked as separate `lookup` and `rule-content-v1` work, outside this decision. They include missing value return types, missing signatures for structural actions such as `Set Global Variable`, partial parameter defaults, and unlocalized parameter names. ## Compatibility impact @@ -127,6 +148,7 @@ This is checked over the owner's corpora in every supported Workshop locale. The - workshop settings, which `SettingSourceEdit` already covers; - a CLI command. Each needs its own decision when a consumer needs it. +- Not decided: creating or editing statement comments. That needs a comment field in a future `rule-content` format version, which `workshop-rs` owns. - Not decided: source-language authoring for OverPy or OSTW (decision 4). - Not decided: the form factor of a local editing view. - The exact Rust API, error ids, and indentation rules belong to `workshop-rs`. The request field spellings above are illustrative until the Wright schema is written, but the addressing, atomicity, freshness, and write-path rules are decided here. From 6936ac6239e3e871d6ebdfc3bd78a40c5348580e Mon Sep 17 00:00:00 2001 From: Teakowa <27560638+Teakowa@users.noreply.github.com> Date: Mon, 19 Oct 2026 19:33:07 +0800 Subject: [PATCH 3/3] docs(adr): accept ADR-0029 with batched inserts and defined terms An insert now takes a statements array, so an If, its body, and its End land at one address without tripping the same-address conflict rule. The ADR defines statement, statement address, and structured view, and leaves the view's form factor and its operation boundary to the decision that introduces it. Accepted after the owner confirmed raw-Workshop-only scope, comment preservation on replace, visible canonical respelling, and the caller-writes edit path. --- docs/adr/0029-structured-statement-edits.md | 32 +++++++++++++-------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/docs/adr/0029-structured-statement-edits.md b/docs/adr/0029-structured-statement-edits.md index cc9ea5a0..1d4f177d 100644 --- a/docs/adr/0029-structured-statement-edits.md +++ b/docs/adr/0029-structured-statement-edits.md @@ -1,6 +1,6 @@ # ADR-0029: Structured statement edits for raw Workshop -- Status: Proposed +- Status: Accepted - Date: 2026-10-19 - Related: [ADR-0021](0021-name-and-signature-lookup.md), [ADR-0024](0024-structured-rule-content.md), [ADR-0028](0028-profile-applies-to-workshop-input-only.md), [Agent contract](../agent-contract.md), `workshop-rs` `docs/source-preservation.md` and its ADR-0017 @@ -9,7 +9,7 @@ ADR-0024 made rule content readable as `workshop-rs/rule-content-v1` call trees and left "a typed authoring API" undecided. Two consumers now need the write direction: - **Agents.** To add or change a condition or action, an agent writes Workshop text, using `lookup` (ADR-0021) for spellings. It then checks the result with `validateEditTransaction`. The agent therefore has to know locale spellings, number and string formatting, variable syntax, operator layout, and indentation. Goal principle 4 promises structured access instead. -- **A structured editing view.** A read-only prototype builds the in-game "create condition / create action" forms from `lookup` signatures. Each form produces a correct `rule-content-v1` node: its shape matches the content Wright returns for the same calls in a real project. No operation can turn that node into source text. The only way to write it today would be for the client to compose Workshop text itself. +- **A structured view** — a presentation of a program as rules, conditions, and actions instead of text. A read-only prototype builds the in-game "create condition / create action" forms from `lookup` signatures. Each form produces a correct `rule-content-v1` node: its shape matches the content Wright returns for the same calls in a real project. No operation can turn that node into source text. The only way to write it today would be for the client to compose Workshop text itself. Wright already has most of the pieces it needs: @@ -26,7 +26,7 @@ Two pieces are missing: ### 1. Input form: `rule-content-v1` list items -A statement is written in the form `rule-content-v1` already uses for list items: +A **statement** is one condition or one action of a rule. It is the unit this decision edits, the unit `workshop-rs` records a span for, and an entry in a rule's `conditions` or `actions` list. A statement is written in the form `rule-content-v1` already uses for list items: - A condition is `{"value": }`. - An action is an action node, `{"call": id, "args": [...]}`. @@ -37,7 +37,7 @@ Either may carry `"disabled": true`. Spans are output-only and are rejected on i | Owner | Responsibility | | --- | --- | -| `workshop-rs` | Converts a `rule-content-v1` statement into the public model. Unknown ids, wrong argument counts, internal sentinels, and nodes that cannot form a statement fail with a structured error. Produces the checked `SourceEdit` that inserts, replaces, or deletes one condition or action of a parsed program at a statement address. That includes the statement's canonical text in the session's resolved Workshop locale (decision 3), its indentation relative to the surrounding source, statement comments (decision 3), and creating a missing section. Owns the round-trip contract in decision 5. | +| `workshop-rs` | Converts a `rule-content-v1` statement into the public model. Unknown ids, wrong argument counts, internal sentinels, and nodes that cannot form a statement fail with a structured error. Produces the checked `SourceEdit` that inserts statements before, replaces, or deletes the statement at a statement address of a parsed program. That includes each emitted statement's canonical text in the session's resolved Workshop locale (decision 3), its indentation relative to the surrounding source, statement comments (decision 3), and creating a missing section. Owns the round-trip contract in decision 5. | | Wright | Owns the operation, addressing and freshness, the transaction it assembles from owner edits, its validation through the existing `validateEditTransaction` path, refusals, and presentation in `serve` and MCP. | Wright composes no Workshop text and reads no catalog spellings to build it. Locale, spelling, literal formatting, variable syntax, operator layout, and indentation are decided only by `workshop-rs`. @@ -52,22 +52,28 @@ Wright composes no Workshop text and reads no catalog spellings to build it. Loc "inputIdentity": "", "edits": [ { "rule": 3, "list": "actions", "index": 2, "change": "insert", - "statement": { "call": "startThrottleInDirection", "args": [ ... ] } }, + "statements": [ { "call": "if", "args": [ ... ] }, + { "call": "startThrottleInDirection", "args": [ ... ] }, + { "call": "end", "args": [] } ] }, { "rule": "Cover enums 108", "list": "conditions", "index": 0, "change": "replace", "statement": { "value": { "call": "==", "args": [ ... ] } } } ] } ``` -**Addressing.** +**Addressing.** A **statement address** is `rule` + `list` + `index`: - `rule` is the same `Address` that `rules` and `cfg` accept. - `list` is `conditions` or `actions`. - `index` is the position in that list. For actions this is the flat index space that `rules`, `cfg`, `references`, and findings already share. -- `change` is `insert`, `replace`, or `delete`. `insert` at the list length appends. `delete` takes no `statement`. + +**Changes.** +- `insert` takes a non-empty `statements` array and places it, in order, before the statement at `index`. `index` equal to the list length appends. +- `replace` takes one `statement` and replaces the statement at `index`. +- `delete` takes neither and removes the statement at `index`. **Atomicity.** -- Every address in a request refers to the program before any edit in that request. -- Two edits at the same address refuse, as zero-width conflicts do in `EditTransaction`. Edits do not see each other's results, so an agent that adds an `If` and its `End` sends both in one request. +- Every address in a request refers to the program before any edit in that request. Edits do not see each other's results. +- Two edits at the same address refuse, as zero-width conflicts do in `EditTransaction`. Statements that belong at one position, such as an `If`, its body, and its `End`, go in one `insert`. **Freshness.** `inputIdentity` must equal the current `project.inputIdentity`. Otherwise the request refuses with `edit-stale-source` and edits nothing, so a client cannot address statements in a program it has not read. @@ -110,9 +116,11 @@ This is checked over the owner's corpora in every supported Workshop locale. The ## Alternatives considered -- **The client composes text from `lookup` spellings and validates it with `validateEditTransaction`:** rejected. Every client, including agents, an editing view, and the benchmark, would carry its own copy of Workshop emission. Validation would catch invalid text, but not a valid statement emitted differently from canonical output. +- **The client composes text from `lookup` spellings and validates it with `validateEditTransaction`:** rejected. Every client, including agents, a structured view, and the benchmark, would carry its own copy of Workshop emission. Validation would catch invalid text, but not a valid statement emitted differently from canonical output. - **Regenerate the whole program with the canonical emitter and diff it:** rejected. Canonical emission does not preserve comments or formatting, so one inserted action would rewrite the file. - **A separate typed authoring format or Rust builder as the wire input:** rejected. It would add a second format next to `rule-content-v1` and force readers to translate between them. +- **Several single-statement inserts at one address, ordered by their position in the request:** rejected. Only inserts would be exempt from the same-address conflict rule. One `insert` with a `statements` array says the same thing with no exception. +- **Sequential addressing, where each edit sees the previous edit's result:** rejected. Callers would compute shifted positions themselves, and the request would no longer describe edits against the program the caller read. - **Addressing nested arguments by path:** rejected. The statement is the unit of span, validation, and index space. Replacing the whole statement covers argument changes without a second address space. - **An operation that also writes to disk:** rejected. It is inconsistent with the existing edit contract, where `validateEditTransaction`, `semanticRename`, and lint fixes return validated edits and the caller writes them. - **Extending `validateEditTransaction` to accept structured edits:** rejected. That operation's contract is caller-proposed text, and mixing the two input kinds would blur its refusals and its MCP schema. @@ -120,7 +128,7 @@ This is checked over the owner's corpora in every supported Workshop locale. The ## Consequences - Agents can add, change, and remove conditions and actions by catalog id and argument position, and they get owner diagnostics before any byte is written. -- A structured editing view can write what its forms build without owning any Workshop syntax. Where that view lives and what it looks like is a separate decision. +- A structured view can write what its forms build without owning any Workshop syntax. Where that view lives and what it looks like is a separate decision. - `rule-content-v1` becomes an input format as well as an output format. Its stability now affects writers, and an incompatible change needs a new format id under the rules ADR-0024 set. - Follow-up work: - `workshop-rs`: @@ -150,5 +158,5 @@ This is checked over the owner's corpora in every supported Workshop locale. The Each needs its own decision when a consumer needs it. - Not decided: creating or editing statement comments. That needs a comment field in a future `rule-content` format version, which `workshop-rs` owns. - Not decided: source-language authoring for OverPy or OSTW (decision 4). -- Not decided: the form factor of a local editing view. +- Not decided: the form factor of a local structured view, and whether such a view may use operations outside `wright-agent/v1`. Both belong to the decision that introduces the view. - The exact Rust API, error ids, and indentation rules belong to `workshop-rs`. The request field spellings above are illustrative until the Wright schema is written, but the addressing, atomicity, freshness, and write-path rules are decided here.