Files
samiandClaude Sonnet 5 60363a7142 proto: land B4 and B2a — buffer bounds, budget knobs, effective readback (1.1.0)
Minor bump on 1.0.0, per core/docs/buffer-sizing.md.

B4 — bufferBytes bounds corrected in all four locations (DownloadSpec,
TaskDetail, download.update's patch, Settings.connection.bufferBytes): was
4 KiB-8 MiB with no stated default, now 64 KiB-16 MiB with a 1 MiB default.
64 KiB because 4 KiB is smaller than one libcurl HTTP/2 write-callback delivery;
16 MiB because throughput from write size is flat past ~1-4 MiB and past 16 MiB
there is stall-cover left to buy but no memory left to spend it on; 1 MiB
default because it is the only candidate for which docs/04's 60 MB RSS target
actually holds once buffers are counted per segment, not per download.

Two new settings keys: connection.maxTotalBufferBytes (128 MiB default) and
connection.maxActiveSegments (32 default). Without them CORE's clamp — reduce
every live segment's buffer to fit the global cap — has no wire configuration
surface, and "20 active downloads" has no meaning distinct from 160 live TLS
connections.

B2a — TaskDetail.effectiveBufferBytes: what a segment is actually using right
now, after the clamp. Placed on TaskDetail next to bufferBytes, following the
requested/effective pattern ADR 0010 already established for segments. The
download.get fixture now demonstrates a real clamp (16 MiB requested, 4 MiB
effective) rather than a case where the cap happens not to bind.

docs/04-engine-design.md §4 and §8 updated in the same change per CORE's
request and CLAUDE.md rule 5: the RSS target is now stated as conditional on
maxActiveSegments = 32, and the old 4 MiB/64 MiB/256 MiB numbers are corrected
to match the schema. ADR 0012 records the reasoning and explicitly keeps the
60 MB target over CORE's offered 120 MB alternative, with the arithmetic that
makes 60 MB achievable with margin.

Numbered 0012 rather than 0011: DAEMON is independently drafting ADR 0011
(admission control / segment budget split) in a peer session at time of
writing, so 0011 was reserved to avoid a collision.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012fgjnqFCS5h5L7gZTZo3rV
2026-09-09 23:20:58 +04:00
..

contracts/fixtures — golden request/response pairs

Every method has at least one success fixture. A method with no fixture is not done.

These files are replayed by tests/conformance/ against both the generated C++ and a live server, which is what lets four lanes build in parallel and still be compatible: green fixtures mean the C++ daemon and the TypeScript extension agree, without either having ever run against the other. tools/mockd also answers from them, so the GUI and extension are developed against the same bytes conformance asserts.

Layout

fixtures/
├── *.json          one success fixture per method
├── errors/         error cases: auth, transport, not-found, bad path, timeout
└── events/         one fixture per server-to-client notification

Shape

{
  "name": "download.add — start an ISO now, into the Programs category",
  "description": "Why this case is worth pinning.",
  "transport": "uds",          // optional: replay only on this transport
  "requires": "...",           // optional: a condition a plain server cannot produce
  "kind": "timeout",           // optional: the correct behaviour is *no reply*
  "request":  { "jsonrpc": "2.0", "id": 11, "method": "download.add", "params": { } },
  "response": { "jsonrpc": "2.0", "id": 11, "result": { } },
  "assertions": [ "things a runner or a reviewer should check" ]
}

An event fixture carries notification instead of request/response.

assertions are prose, for the human writing the implementation. The runners check the machine-checkable parts: schema validity, error codes, shape, and the timeout.

Placeholders

Some values cannot be pinned in a golden file. These stand in for them, and the runners treat them as "any value of the right shape":

Placeholder Means
$uuid any UUID
$isoDate any RFC 3339 date-time
$opaque a credential-shaped string (a token)
$any any value
$taskId, $taskId2 a task the runner creates during setup, and binds before replaying

$taskId exists so a fixture never depends on a task id that only happens to exist in a seeded mock. The same fixture then runs against an empty veloxd and a populated mockd.

Values are matched by shape, not by equality

A live daemon returns its own task ids and its own clock. Demanding byte-identical results would only teach the suite to lie, so the runners assert:

  • the payload passes the generated validator — this is the real cross-language check;
  • the key structure matches the golden file, with no extra and no missing fields;
  • error codes match exactly.

A null where the golden shows a value is accepted: the validator has already ruled on whether null is legal there, and a golden file shows one plausible value, not the only one.

requires: fixtures a mock cannot produce

Most error fixtures are intrinsic — a path outside the allowed roots, an out-of-range parameter, an unknown task id — and any correct server produces them from the request alone. Those are replayed everywhere.

Four are environmental: a 403 from an origin server, a full disk, a pairing lockout, a wedged daemon. They carry requires, are skipped by default, and are exercised where the condition can actually be arranged — run.sh starts a deliberately slow mockd to prove capture.offer fails open, and lane PKG/QA's tools/testserver covers the hostile-server cases in tests/integration/.

errors/capture.offer.timeout.json is the most important file in this directory. Its correct response is no response: past 750 ms the extension must abandon the offer and let Firefox download normally. A download manager that eats downloads when its daemon is down is worse than no download manager.