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
111 lines
4.0 KiB
TypeScript
111 lines
4.0 KiB
TypeScript
// ---------------------------------------------------------------------------
|
|
// GENERATED FILE — DO NOT EDIT.
|
|
//
|
|
// Source: contracts/schema/**
|
|
// Generator: contracts/codegen/gen_ts.py
|
|
// Contract: v1.1.0
|
|
//
|
|
// Hand-editing this file is a merge blocker. Fix the schema and regenerate:
|
|
// python3 contracts/codegen/gen_ts.py
|
|
// Only lane PROTO commits to contracts/.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
import type {
|
|
AuthRequiredEvent,
|
|
GrabberProgressEvent,
|
|
NotifyEvent,
|
|
SettingsChangedEvent,
|
|
SpeedGlobalEvent,
|
|
TaskAddedEvent,
|
|
TaskProgressEvent,
|
|
TaskRemovedEvent,
|
|
TaskStateEvent,
|
|
} from './types.js';
|
|
|
|
/** Payload for each server-to-client notification, keyed by its wire name. */
|
|
export interface EventMap {
|
|
/**
|
|
* A server asked for credentials. The task sits in retry_wait until the client supplies
|
|
* them. Credentials travel to the Secret Service, never back through this event and never
|
|
* into a log.
|
|
*/
|
|
"event.auth.required": AuthRequiredEvent;
|
|
/**
|
|
* Crawl progress for the Site Grabber wizard. done true means the file list in
|
|
* grabber.status is final.
|
|
*/
|
|
"event.grabber.progress": GrabberProgressEvent;
|
|
/**
|
|
* Something the user should see: a completion, a failure, a queue finishing. The client
|
|
* decides between a toast, a tray balloon and a sound; the daemon does not assume a GUI is
|
|
* running.
|
|
*/
|
|
"event.notify": NotifyEvent;
|
|
/**
|
|
* Settings were written by some client. Carries only the key names; a client re-reads what
|
|
* it cares about. The extension watches for capture.* here and re-fetches capture.getRules
|
|
* so its rules never lag the daemon's.
|
|
*/
|
|
"event.settings.changed": SettingsChangedEvent;
|
|
/**
|
|
* Aggregate throughput for the status bar, the tray tooltip and the extension popup.
|
|
* Emitted at 1 Hz even when nothing is active, so a client can tell 'idle' from
|
|
* 'disconnected'.
|
|
*/
|
|
"event.speed.global": SpeedGlobalEvent;
|
|
/**
|
|
* A task entered the list. summary is always present so a client can insert the row
|
|
* without a follow-up download.get.
|
|
*/
|
|
"event.task.added": TaskAddedEvent;
|
|
/**
|
|
* Batched byte counters for every active task. Emitted at no more than 4 Hz as one array,
|
|
* never one notification per task: at twenty active downloads that is four messages a
|
|
* second instead of eighty. Clients apply a row patch and repaint the touched columns;
|
|
* rebuilding a model on this event is a bug.
|
|
*/
|
|
"event.task.progress": TaskProgressEvent;
|
|
/** A task left the list. The client deletes the row; there is nothing further to fetch. */
|
|
"event.task.removed": TaskRemovedEvent;
|
|
/**
|
|
* A task changed lifecycle state. Carries the summary so the row can be repainted in full
|
|
* without a round trip, and error whenever the new state is failed or retry_wait.
|
|
*/
|
|
"event.task.state": TaskStateEvent;
|
|
}
|
|
|
|
export type EventName = keyof EventMap;
|
|
export type EventPayload<E extends EventName> = EventMap[E];
|
|
|
|
/**
|
|
* Discriminated on `method`: narrowing an incoming notification gives the
|
|
* correctly typed params with no cast at the call site.
|
|
*/
|
|
export type ServerNotification = {
|
|
[E in EventName]: { jsonrpc: '2.0'; method: E; params: EventMap[E] };
|
|
}[EventName];
|
|
|
|
export interface EventMeta {
|
|
/** Upper bound on emission rate, where the contract sets one. */
|
|
readonly maxRateHz: number | null;
|
|
}
|
|
|
|
export const EVENTS: { readonly [E in EventName]: EventMeta } = {
|
|
"event.auth.required": { maxRateHz: null },
|
|
"event.grabber.progress": { maxRateHz: 4 },
|
|
"event.notify": { maxRateHz: null },
|
|
"event.settings.changed": { maxRateHz: null },
|
|
"event.speed.global": { maxRateHz: 1 },
|
|
"event.task.added": { maxRateHz: null },
|
|
"event.task.progress": { maxRateHz: 4 },
|
|
"event.task.removed": { maxRateHz: null },
|
|
"event.task.state": { maxRateHz: null },
|
|
} as const;
|
|
|
|
export const EVENT_NAMES = Object.keys(EVENTS) as EventName[];
|
|
|
|
export function isEventName(v: unknown): v is EventName {
|
|
return typeof v === 'string' && Object.prototype.hasOwnProperty.call(EVENTS, v);
|
|
}
|