encoding: a derived decode that collects every field's failure (WEB F6) #95

Closed
opened 2026-10-05 21:07:19 +00:00 by orion · 8 comments
Collaborator

Goal

A derived record decode reports every field that failed, not only the first. A form with three bad fields yields three problems, and so does a config file (decision D12 (a): every format gains it).

Source

  • WEB.md F6 and decision D12 (a), which settles where the change goes: ENCODING's derived decode collects (not std/web decoding field by field).
  • Design record to update with the change: docs/design/ENCODING.md (section 2 DecodeError, section 3 "Fields with a default…", the derived decode).

Acceptance criteria

  • DecodeError gains problems: List<DecodeError> = [] (default empty, so every existing construction and DecodeError message still compiles and Show of a single error is unchanged).
  • The derived record decode keeps reading after a failing field and fails once at the end. The returned error carries one entry in problems per failed field, each with its own path (price.currency, items[2].name), in declaration order. A record with exactly one failing field still reports that one problem.
  • A record whose fields all decode is unchanged (same value, same defaults rule: a default is evaluated only when the field is missing).
  • Nested records and sequence items contribute their own problems to the same flat list, with the full path.
  • Hand-written decode (for example the Email/Slug wrappers) and the TryFrom failure path still produce a single DecodeError.
  • Conformance test(s) in tests/conformance/encoding-*.trb pin: three bad fields give three problems with paths; one bad field gives one; a bad nested field gives its full path; JSON and one non-JSON format both collect.
  • ENCODING.md describes problems and the collecting derivation and notes the resume contract below; WEB.md F6 is marked done.

Seed

Not expected to touch the seed: no syntax change, no new native, no driver flag, no std name the compiler looks up by string (DecodeError is not in wellknown.trb). The change is in the derivation (compiler/src/ir/lower/derive.trb, semantics/checker/derive.trb) and std/encoding. To verify during implementation: if the compiler's own sources use a derived Decode, the first commit must still bootstrap with the old seed; if that fails it becomes the two-commit seed path from CLAUDE.md.

Size

One PR, one or two Conventional Commits: feat(encoding): derived decode collects every field's failure, plus optionally docs(encoding): ... if kept separate. Do not mix in the forms work (WEB section 3.5, Submitted/Form), which is a separate item.

Open question

How a Decoder resumes after a failed field (position after the failed value, nested records and sequences, streaming formats) is not specified in ENCODING.md. See the owner decision comment; the issue is not ready until it is answered.

Dependencies

None known.

Moved from the planning lists of the docs; the design record stays the specification.

## Goal A derived record `decode` reports every field that failed, not only the first. A form with three bad fields yields three problems, and so does a config file (decision D12 (a): every format gains it). ## Source - [WEB.md F6](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/WEB.md#L928) and decision D12 (a), which settles *where* the change goes: ENCODING's derived `decode` collects (not `std/web` decoding field by field). - Design record to update with the change: `docs/design/ENCODING.md` (section 2 `DecodeError`, section 3 "Fields with a default…", the derived `decode`). ## Acceptance criteria - [ ] `DecodeError` gains `problems: List<DecodeError> = []` (default empty, so every existing construction and `DecodeError message` still compiles and `Show` of a single error is unchanged). - [ ] The derived record `decode` keeps reading after a failing field and fails once at the end. The returned error carries one entry in `problems` per failed field, each with its own `path` (`price.currency`, `items[2].name`), in declaration order. A record with exactly one failing field still reports that one problem. - [ ] A record whose fields all decode is unchanged (same value, same defaults rule: a default is evaluated only when the field is missing). - [ ] Nested records and sequence items contribute their own problems to the same flat list, with the full path. - [ ] Hand-written `decode` (for example the `Email`/`Slug` wrappers) and the `TryFrom` failure path still produce a single `DecodeError`. - [ ] Conformance test(s) in `tests/conformance/encoding-*.trb` pin: three bad fields give three problems with paths; one bad field gives one; a bad nested field gives its full path; JSON and one non-JSON format both collect. - [ ] ENCODING.md describes `problems` and the collecting derivation and notes the resume contract below; WEB.md F6 is marked done. ## Seed Not expected to touch the seed: no syntax change, no new native, no driver flag, no std name the compiler looks up by string (`DecodeError` is not in `wellknown.trb`). The change is in the derivation (`compiler/src/ir/lower/derive.trb`, `semantics/checker/derive.trb`) and `std/encoding`. To verify during implementation: if the compiler's own sources use a derived `Decode`, the first commit must still bootstrap with the old seed; if that fails it becomes the two-commit seed path from CLAUDE.md. ## Size One PR, one or two Conventional Commits: `feat(encoding): derived decode collects every field's failure`, plus optionally `docs(encoding): ...` if kept separate. Do not mix in the forms work (WEB section 3.5, `Submitted`/`Form`), which is a separate item. ## Open question How a `Decoder` resumes after a failed field (position after the failed value, nested records and sequences, streaming formats) is not specified in ENCODING.md. See the owner decision comment; the issue is not `ready` until it is answered. ## Dependencies None known. _Moved from the planning lists of the docs; the design record stays the specification._
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop). Matches WEB.md F6 and decision D12 (a), which settles the design: change ENCODING's derived decode to collect. No collision with a design record or CONCEPT "Open Questions". Possibly touches the seed (derived decode); to settle in refine. next: refine (waiting for the ready buffer slot, 0 of 2 ready).

[loop:torbscript] intake: in scope (author orion, label loop). Matches WEB.md F6 and decision D12 (a), which settles the design: change ENCODING's derived `decode` to collect. No collision with a design record or CONCEPT "Open Questions". Possibly touches the seed (derived decode); to settle in refine. next: refine (waiting for the ready buffer slot, 0 of 2 ready).
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript until 2026-10-06T06:58:30Z

[loop:torbscript] claim refine by session:loop-torbscript until 2026-10-06T06:58:30Z
Author
Collaborator

[loop:torbscript] owner decision: how does a Decoder resume after a failed field?
What: the derived decode must keep going after a field fails (D12 (a) settles that it collects), but ENCODING.md does not say what state a Decoder is in after an Err from bool/int/string/..., record, sequence or a nested field. Collecting needs a defined position to read the next field from.
Options:
(1) Contract: after an Err from a scalar the value is consumed and the decoder stays positioned for the next field; an Err from a structural call (record, sequence, variant) is fatal for that subtree and its enclosing record stops collecting there. No new Decoder member; each format (JSON, CSV, XML, ...) must honour it.
(2) Add a required Decoder.skip() that the derivation calls after a failed field to discard the rest of that value. Most robust for streaming formats, but it grows the trait every format implements.
(3) Collect only for fields read by the derivation itself over an in-memory value (ValueDecoder), and stay first-error for streaming decoders. Smallest change, but a config file would not say "three mistakes".
Recommendation: (1) with conformance tests for JSON and one non-JSON format; no trait growth, and D12's "every format gains it" holds. Not breaking (problems has a default); no seed change expected.
Not labelled ready until you answer.

[loop:torbscript] owner decision: how does a `Decoder` resume after a failed field? What: the derived `decode` must keep going after a field fails (D12 (a) settles that it collects), but ENCODING.md does not say what state a `Decoder` is in after an `Err` from `bool/int/string/...`, `record`, `sequence` or a nested `field`. Collecting needs a defined position to read the next field from. Options: (1) Contract: after an `Err` from a scalar the value is consumed and the decoder stays positioned for the next `field`; an `Err` from a structural call (`record`, `sequence`, `variant`) is fatal for that subtree and its enclosing record stops collecting there. No new `Decoder` member; each format (JSON, CSV, XML, ...) must honour it. (2) Add a required `Decoder.skip()` that the derivation calls after a failed field to discard the rest of that value. Most robust for streaming formats, but it grows the trait every format implements. (3) Collect only for fields read by the derivation itself over an in-memory value (`ValueDecoder`), and stay first-error for streaming decoders. Smallest change, but a config file would not say "three mistakes". Recommendation: (1) with conformance tests for JSON and one non-JSON format; no trait growth, and D12's "every format gains it" holds. Not breaking (`problems` has a default); no seed change expected. Not labelled `ready` until you answer.
Author
Collaborator

[loop:torbscript] refine: body rewritten to definition of ready (goal, criteria, design record, seed: not expected, size: one PR); open owner question on decoder resume after a failed field, not labelled ready, next: wait for owner decision

[loop:torbscript] refine: body rewritten to definition of ready (goal, criteria, design record, seed: not expected, size: one PR); open owner question on decoder resume after a failed field, not labelled ready, next: wait for owner decision
Author
Collaborator

[loop:torbscript] owner decision recorded (torben, 2026-10-06): option 1. A failed scalar field is consumed and the Decoder is positioned at the next field, so the derived decode collects every field error in one pass. ENCODING.md documents this state. Labelled ready.

[loop:torbscript] owner decision recorded (torben, 2026-10-06): option 1. A failed scalar field is consumed and the `Decoder` is positioned at the next field, so the derived `decode` collects every field error in one pass. ENCODING.md documents this state. Labelled `ready`.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261007 until 2026-10-07T05:32:48Z

[loop:torbscript] claim implement by session:loop-torbscript-20261007 until 2026-10-07T05:32:48Z
Author
Collaborator

[loop:torbscript] implement: PR #150 open (one commit feat(encoding): derived decode collects every field's failure), all acceptance criteria covered, forge CI green (tier A, tier B, bootstrap, cross-compile, same-C, playground, archives); tier A also green in a local gcc container, check-commits clean, no seed change. Worker usage: ~160k tokens. next: review (waiting on merge gate argus)

[loop:torbscript] implement: PR https://git.torb.dev/torbscript/language/pulls/150 open (one commit `feat(encoding): derived decode collects every field's failure`), all acceptance criteria covered, forge CI green (tier A, tier B, bootstrap, cross-compile, same-C, playground, archives); tier A also green in a local gcc container, check-commits clean, no seed change. Worker usage: ~160k tokens. next: review (waiting on merge gate argus)
Author
Collaborator

[loop:torbscript] claim review-fix by session:loop-torbscript-20261007 until 2026-10-07T04:07:23Z

[loop:torbscript] claim review-fix by session:loop-torbscript-20261007 until 2026-10-07T04:07:23Z
argus closed this issue 2026-10-07 06:00:46 +00:00
Sign in to join this conversation.
No labels
loop
ready
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#95
No description provided.