Skip to content

relay: the settings validator must own the ceiling, not just the floor #998

Open

Description

@chappie-daemon

The zero rule put a floor in validate_document; the ceiling stayed in nostream-relay (MAX_SETTING_BOUND, in_range, refused_bounds). The asymmetry has a measured cost: --check prints settings valid: 0 issues for 1099511627776 and for 1099511627777, two documents whose runtime meaning differs 4000×, because the validator cannot see the ceiling. At start-up the warn! does fire — so the absurd value is invisible at the door and visible only from a log line an operator has to be watching for. That is the silent-failure shape ADR 0008 exists to end, reproduced at the settings boundary.

What it needs

  1. The ceiling moves to nostream-config, beside validate_document. One rule, <key> must be at most <N>, in the same Issue::new shape as the zero rule.
  2. from_settings loses its silent branch: None means the compiled default; anything else is a number the validator already accepted. in_range's ceiling arm and BoundRefusal/refused_bounds either go or stay explicitly as defence in depth, commented as defence rather than as the rule.
  3. guided.rs's two entries carry the same ceiling, so the editor cannot offer 9e15 for a bound the relay will refuse.
  4. CONFIGURATION.md rows state both ends: default, zero refused, ceiling.
  5. The ADR writes the zero rule and the ceiling rule as one sentence about the same two keys — a value the relay does not use is a value the operator must be told about — each quoting its validator line verbatim.

If a number is chosen, it should be one the relay can enforce

1 TiB for the budget is defensible (it is what a 64-bit usize allows with room to spare for accounted + bytes), but it was chosen to make the arithmetic fit, not to describe a deployment. For the cap, 1 TiB sockets is not a number any host can hold; the ceiling there should be whatever the relay can actually carry or the operator can plausibly need, with the reason in the ADR. Do not leave a number that exists only because the type is 64 bits wide.

Discovered by the operator in job 018's review, after 017 and 018 had both shipped the asymmetry.

Activity

  1. added
    rewritenostream 4.0 Rust/LMDB rewrite
    rustRust implementation work
    factoryBuilt by the software factory
    on Oct 25, 2026
  2. chappie-daemon commented on Oct 25, 2026

    @chappie-daemon
    CollaboratorAuthor

    Two refinements from the operator, recorded before anyone opens this as a job.

    1. It waits for the stack to merge. 019 opens only after Cameri has merged into 990 → 995 → 997. Three stacked branches is as deep as one reviewer should be asked to walk. When it does open, the ceiling goes in as a union with whatever those merges left behind — rebase first, then measure, then write the rule.

    2. The cap's ceiling is derived, not chosen. No round number. The rule for the job:

    Derive the ceiling from the same arithmetic ADR 0008 already uses — sockets × frame bytes — and state the derivation in the ADR. A ceiling nobody can derive is the same defect as a floor that only exists in prose.

    For the budget, that arithmetic already exists (256 MiB / 8 MiB = 32 sockets, below the 48 the attempt limiter allows and the 64 the cap allows). For the cap, it is ulimit territory: what a host can actually hold open, times the bytes each socket may queue. If the derived number depends on the deployment, say so and make the bound a documented deployment constraint rather than a settings constant — that is a decision for the job to state, not for this issue to assume.

    The design rule the job implements, in the operator's words: a limits pair belongs in one layer or the other, not split by which crate the arithmetic happens to sit in — and if the split is deliberate, the ADR says which half is the rule and which is defence.

  3. chappie-daemon commented on Oct 25, 2026

    @chappie-daemon
    CollaboratorAuthor

    Two more constraints, from the operator, that decide whether 019 is small or not.

    Deletion, not demotion. in_range, BoundRefusal and refused_bounds are deleted, not kept as defence with a comment. A guard that exists is a guard the next reader will treat as the rule — that half-measure is what produced this asymmetry. If FrameBudget::admit needs a defensive check of its own, it gets an assert! or nothing, and the ADR says why the validator is the only gate.

    The cap's ceiling is derived, not chosen — sockets × frame bytes, from ADR 0008's own arithmetic, stated as a derivation in the record. For the budget that arithmetic already exists (256 MiB / 8 MiB = 32, below both the 48 the attempt limiter allows and the 64 the cap allows). For the cap it is what a host can actually hold open, times what each socket may queue; if the answer depends on the deployment, the job makes the bound a documented deployment constraint and says so.

    It opens after the stack merges. 990 → 995 → 997 are merged first: the stack is already three deep on one reviewer, and 019 changes the same functions every branch above it touches, so a fourth branch could not be read independently of the third. When it opens, the ceiling goes in as a union with whatever those merges left.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    factoryBuilt by the software factoryrewritenostream 4.0 Rust/LMDB rewriterustRust implementation work

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions