formats: std/json-patch with RFC 6902 JSON Patch and the json-patch-tests suite (step 2 of #81) #581

Closed
opened 2026-10-29 11:52:48 +00:00 by orion · 8 comments
Collaborator

Context

Step 2 of the epic #81 (JSON Patch). Step 1 (#435, JsonPointer and JsonValue.get in std/json) is on main (merge commit b929c58). This step adds the package std/json-patch with RFC 6902 JSON Patch: a patch document is a value on Encode/Decode, applying it works on JsonValue. RFC 7396 Merge Patch is step 3 and is not part of this issue.

Source

  • ROADMAP.md, format versions: JSON Patch row, RFC 6902 and RFC 7396.
  • ENCODING.md section 9: formats are packages on Encode and Decode.
  • Layout model: std/json-schema (package, src/lib.trb, tests/suite-cases.trb and tests/suite.test.trb with its vendored public suite and exclusion list), std/yaml (generated suite-cases.trb with the licence notice).

Design record point

ROADMAP "Which versions a format supports" (JSON Patch row) and ENCODING.md section 9, as decided on #81:

  • Package std/json-patch (hyphenated like std/json-schema), pure TorbScript on JsonValue and JsonPointer, not in the prelude.
  • JsonPatch is a list of operations and Encode/Decode; the six operations are cases of one PatchOperation type (add, remove, replace, move, copy, test). Decoding refuses an unknown op, a missing path/value/from, and a malformed pointer, with an error that names the operation index.
  • JsonPatch.apply(document: JsonValue): Result<JsonValue, PatchError> is a method (#146) and atomic as RFC 6902 section 5 requires: a failing operation or a failing test returns the error and no partial document. JsonValue is a value, so the input is untouched by construction. PatchError names the operation index, the operation and the reason.
  • Semantics per RFC 6902: add into an array inserts (index equal to the length and - append, larger index is an error), into an object sets the member; add with the root pointer replaces the whole document; remove and replace need an existing target; move is remove from from then add at path and refuses to move a value into one of its own children; copy is get then add; test compares with the RFC's equality (objects as unordered member sets, arrays ordered, numbers by value, strings by code points).
  • Out of scope: diff generation, patching typed values through Describe, RFC 7396 (step 3).
  • Touches the seed? No. The compiler does not import std/json-patch, and no std name is looked up by string for it. The new package is registered like the other std packages (workspace list, docs index, regenerated skills/), none of which touches seed/.

Acceptance criteria

  • use JsonPatch, PatchOperation, PatchError from "std/json-patch" works; JsonPatch encodes to and decodes from the RFC 6902 JSON array form (Json.decode/Json.encode round trip, tested).
  • All six operations work per the semantics above, including add at - and at the array length, the root pointer, move into own child refused, and test equality rules; hand-written tests cover each operation and each error path.
  • apply is atomic: a test where the third operation fails shows that the error is returned and the document value passed in is unchanged.
  • The public json-patch-tests suite (tests.json and spec_tests.json of https://github.com/json-patch/json-patch-tests, MIT) is vendored as a generated std/json-patch/tests/suite-cases.trb with the licence notice and the pinned commit, run by suite.test.trb (one test per case, as std/json-schema). Cases that need arbitrary-precision numbers (JsonValue.Number is a Float64) are excluded by name with the reason, and each exclusion is checked so it cannot go stale. No other exclusion.
  • Docs page docs/standard-library/json-patch.md (types, operations, atomicity, error values, the non-goals); skills/ regenerated; the ROADMAP JSON Patch row marked started (RFC 6902 done, 7396 open).
  • sh tools/gates.sh a green in forge CI; tools/check-commits.sh clean.

Size

One PR, two Conventional Commits (the loop's two-commit rule keeps each reviewable):

  1. feat(std/json-patch): JsonPatch with the RFC 6902 operations and atomic apply (package, types, Encode/Decode, apply, hand-written tests, docs page).
  2. test(std/json-patch): run the public json-patch-tests suite (generated suite-cases.trb with notice, suite.test.trb, exclusions, ROADMAP row).

Dependencies

  • Needs JsonPointer (#435, merged), std/json, std/test.
  • Step 3 (RFC 7396 Merge Patch) follows once this lands; do not start it here.

Part of #81.

## Context Step 2 of the epic #81 (JSON Patch). Step 1 (#435, `JsonPointer` and `JsonValue.get` in `std/json`) is on `main` (merge commit b929c58). This step adds the package `std/json-patch` with RFC 6902 JSON Patch: a patch document is a value on `Encode`/`Decode`, applying it works on `JsonValue`. RFC 7396 Merge Patch is step 3 and is not part of this issue. ## Source - [ROADMAP.md, format versions](https://git.torb.dev/torbscript/language/src/branch/main/docs/ROADMAP.md): JSON Patch row, RFC 6902 and RFC 7396. - [ENCODING.md section 9](https://git.torb.dev/torbscript/language/src/branch/main/docs/design/ENCODING.md): formats are packages on `Encode` and `Decode`. - Layout model: `std/json-schema` (package, `src/lib.trb`, `tests/suite-cases.trb` and `tests/suite.test.trb` with its vendored public suite and exclusion list), `std/yaml` (generated `suite-cases.trb` with the licence notice). ## Design record point ROADMAP "Which versions a format supports" (JSON Patch row) and ENCODING.md section 9, as decided on #81: - Package `std/json-patch` (hyphenated like `std/json-schema`), pure TorbScript on `JsonValue` and `JsonPointer`, not in the prelude. - `JsonPatch` is a list of operations and `Encode`/`Decode`; the six operations are cases of one `PatchOperation` type (`add`, `remove`, `replace`, `move`, `copy`, `test`). Decoding refuses an unknown `op`, a missing `path`/`value`/`from`, and a malformed pointer, with an error that names the operation index. - `JsonPatch.apply(document: JsonValue): Result<JsonValue, PatchError>` is a method (#146) and atomic as RFC 6902 section 5 requires: a failing operation or a failing `test` returns the error and no partial document. `JsonValue` is a value, so the input is untouched by construction. `PatchError` names the operation index, the operation and the reason. - Semantics per RFC 6902: `add` into an array inserts (index equal to the length and `-` append, larger index is an error), into an object sets the member; `add` with the root pointer replaces the whole document; `remove` and `replace` need an existing target; `move` is `remove` from `from` then `add` at `path` and refuses to move a value into one of its own children; `copy` is `get` then `add`; `test` compares with the RFC's equality (objects as unordered member sets, arrays ordered, numbers by value, strings by code points). - Out of scope: `diff` generation, patching typed values through `Describe`, RFC 7396 (step 3). - Touches the seed? No. The compiler does not import `std/json-patch`, and no std name is looked up by string for it. The new package is registered like the other std packages (workspace list, docs index, regenerated `skills/`), none of which touches `seed/`. ## Acceptance criteria - [ ] `use JsonPatch, PatchOperation, PatchError from "std/json-patch"` works; `JsonPatch` encodes to and decodes from the RFC 6902 JSON array form (`Json.decode`/`Json.encode` round trip, tested). - [ ] All six operations work per the semantics above, including `add` at `-` and at the array length, the root pointer, `move` into own child refused, and `test` equality rules; hand-written tests cover each operation and each error path. - [ ] `apply` is atomic: a test where the third operation fails shows that the error is returned and the document value passed in is unchanged. - [ ] The public json-patch-tests suite (`tests.json` and `spec_tests.json` of https://github.com/json-patch/json-patch-tests, MIT) is vendored as a generated `std/json-patch/tests/suite-cases.trb` with the licence notice and the pinned commit, run by `suite.test.trb` (one test per case, as `std/json-schema`). Cases that need arbitrary-precision numbers (`JsonValue.Number` is a `Float64`) are excluded by name with the reason, and each exclusion is checked so it cannot go stale. No other exclusion. - [ ] Docs page `docs/standard-library/json-patch.md` (types, operations, atomicity, error values, the non-goals); `skills/` regenerated; the ROADMAP JSON Patch row marked started (RFC 6902 done, 7396 open). - [ ] `sh tools/gates.sh a` green in forge CI; `tools/check-commits.sh` clean. ## Size One PR, two Conventional Commits (the loop's two-commit rule keeps each reviewable): 1. `feat(std/json-patch): JsonPatch with the RFC 6902 operations and atomic apply` (package, types, Encode/Decode, apply, hand-written tests, docs page). 2. `test(std/json-patch): run the public json-patch-tests suite` (generated `suite-cases.trb` with notice, `suite.test.trb`, exclusions, ROADMAP row). ## Dependencies - Needs `JsonPointer` (#435, merged), `std/json`, `std/test`. - Step 3 (RFC 7396 Merge Patch) follows once this lands; do not start it here. Part of #81.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261029-0858 until 2026-10-29T19:40Z

[loop:torbscript] claim implement by session:loop-torbscript-20261029-0858 until 2026-10-29T19:40Z
Author
Collaborator

[loop:torbscript] release implement: the worker session was interrupted after 40 min; PR #584 (two commits, branch loop/581-json-patch) is open and its forge CI was still pending. A fresh worker takes over to read CI and fix rounds.

[loop:torbscript] release implement: the worker session was interrupted after 40 min; PR https://git.torb.dev/torbscript/language/pulls/584 (two commits, branch loop/581-json-patch) is open and its forge CI was still pending. A fresh worker takes over to read CI and fix rounds.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261029-0858 until 2026-10-29T20:25Z

[loop:torbscript] claim implement by session:loop-torbscript-20261029-0858 until 2026-10-29T20:25Z
Author
Collaborator

[loop:torbscript] implement checkpoint: PR #584 has Closes #581 and review requested from argus; diff checked against the acceptance criteria (package, tests, suite, docs, skills, ROADMAP, no seed files). Forge CI on 703b9f91 still running (gates, tier A, archives pending); waiting with ci-wait, next: fix rounds only if red.

[loop:torbscript] implement checkpoint: PR https://git.torb.dev/torbscript/language/pulls/584 has `Closes #581` and review requested from argus; diff checked against the acceptance criteria (package, tests, suite, docs, skills, ROADMAP, no seed files). Forge CI on 703b9f91 still running (gates, tier A, archives pending); waiting with ci-wait, next: fix rounds only if red.
Author
Collaborator

[loop:torbscript] release implement: the worker was interrupted by a gateway restart; PR #584 stays open, the next tick starts a fresh worker to read CI and fix rounds.

[loop:torbscript] release implement: the worker was interrupted by a gateway restart; PR #584 stays open, the next tick starts a fresh worker to read CI and fix rounds.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261029-1532 until 2026-10-29T21:35Z

[loop:torbscript] claim implement by session:loop-torbscript-20261029-1532 until 2026-10-29T21:35Z
Author
Collaborator

[loop:torbscript] implement: checkpoint, PR #584 head 703b9f91 open; CI still running (gates, tier A, archives, pack the seed pending; bootstrap, commit messages, pdisc green), next: wait for CI (session:loop-torbscript-20261029-1532)

[loop:torbscript] implement: checkpoint, PR #584 head 703b9f91 open; CI still running (gates, tier A, archives, pack the seed pending; bootstrap, commit messages, pdisc green), next: wait for CI (session:loop-torbscript-20261029-1532)
Author
Collaborator

[loop:torbscript] implement checkpoint: forge CI on 703b9f91 was red on two gates only (format --check on std/json/src/lib.trb; docs summary of json-patch.md 250 > 240 chars). Fixed in place (folded into the feat commit, history still two commits), force-pushed 19490735, waiting for the CI run, next: review if green.

[loop:torbscript] implement checkpoint: forge CI on 703b9f91 was red on two gates only (format --check on std/json/src/lib.trb; docs summary of json-patch.md 250 > 240 chars). Fixed in place (folded into the feat commit, history still two commits), force-pushed 19490735, waiting for the CI run, next: review if green.
argus closed this issue 2026-10-29 22:15:56 +00:00
Sign in to join this conversation.
No labels
loop
ready
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#581
No description provided.