Skip to content

IO_CONTRACTS.md — living contract for I/O and control-plane semantics #1303

Closed

Description

@strawgate

Problem

The repo still lacks one authoritative place that defines what the I/O boundary and control-plane guarantees actually are. Without that, a "correct by construction" architecture has no shared contract to be correct against.

This issue is the contract layer for the architecture umbrella. It is not only about receiver -> pipeline -> sink semantics; it also now owns the diagnostics/control-plane contract that operators and internal tooling will rely on.

Desired end-state

dev-docs/IO_CONTRACTS.md becomes the living contract for:

  • receiver -> pipeline guarantees
  • pipeline -> sink guarantees
  • checkpoint advancement rules
  • error and retry classification vocabulary
  • duplicate/loss windows for file-tailing and restart behavior
  • the probe/control-plane contract:
    • GET /live
    • GET /ready
    • GET /admin/v1/status
  • shared health vocabulary and meanings:
    • starting
    • healthy
    • degraded
    • stopping
    • stopped
    • failed
  • readiness impact vocabulary and roll-up rules
  • quiet-source rule: lack of fresh traffic must not by itself fail readiness
  • drain/shutdown semantics for readiness and serving state
  • what is currently enforced by proofs/tests/runtime checks vs what is still intent

Scope

  • Write and maintain dev-docs/IO_CONTRACTS.md as the current-truth contract document
  • Define probe semantics for /live, /ready, and /admin/v1/status
  • Define status payload semantics including state, reason, timestamps, and readiness impact
  • Define roll-up rules from component -> pipeline -> process
  • Define degraded policy defaults including the default fail-open readiness stance for degraded states
  • Define enforcement status for each guarantee: proof, proptest, integration test, runtime check, or TODO
  • Cross-link existing docs such as SCANNER_CONTRACT.md instead of duplicating inner-layer details
  • Document duplicate/loss windows honestly — no stronger claims than implementation and tests justify
  • Keep it living — future I/O and control-plane changes must update this doc as part of landing semantic changes

Design stance

This document should describe the final contract we want, not a backward-compatibility story. We do not currently have external consumers that force us to preserve a weaker shape.

The initial revision should still be honest about current implementation truth where the repo has not caught up yet, but the contract should clearly define the target semantics we are building toward.

Relationships

Relates to:

Activity

  1. added
    documentationImprovements or additions to documentation
    P2: mediumMinor bug, missing validation, metric inaccuracy
    architectureSystemic design improvements that eliminate classes of bugs
    on Apr 6, 2026
  2. github-actions commented on Apr 6, 2026

    @github-actions
    Contributor

    tl;dr: This is needs-decision because the issue’s checkpoint rule (never on reject) conflicts with current implemented + verified behavior (reject advances checkpoint), so we need one explicit policy decision before drafting dev-docs/IO_CONTRACTS.md as a normative contract.

    Recommendation

    Pick Option 1 below (keep current reject semantics, document it explicitly), then implement IO_CONTRACTS.md mechanically.

    Findings

    Code evidence (paths + lines)
    1. Receiver→Pipeline backpressure is already explicit and enforced

      • crates/logfwd-io/src/otlp_receiver.rs:25 (CHANNEL_BOUND: 4096)
      • crates/logfwd-io/src/otlp_receiver.rs:59 (mpsc::sync_channel(capacity))
      • crates/logfwd-io/src/otlp_receiver.rs:230-236 full channel => HTTP 429
      • crates/logfwd-io/src/otlp_receiver.rs:1043 test returns_429_when_channel_full_not_200
    2. Receiver field names are currently hardcoded (not semantic-fields-driven yet)

      • crates/logfwd-io/src/otlp_receiver.rs:363,371,379,403,409,496,504,511,532,539
        (timestamp_int, level, message, trace_id, span_id)
    3. Pipeline→Sink contract today is RecordBatch + BatchMetadata; checkpoint state is separate ticket state

      • crates/logfwd-output/src/sink.rs:74-80 (Sink::send_batch(&RecordBatch, &BatchMetadata))
      • crates/logfwd-output/src/lib.rs:75-80 (BatchMetadata { resource_attrs, observed_time_ns })
      • crates/logfwd/src/pipeline.rs:917-930 metadata creation + submit to worker pool
    4. Checkpoint behavior conflict (core decision point)

      • crates/logfwd/src/pipeline.rs:977-982 failed output path uses ticket.reject() then machine.apply_ack(receipt)
      • crates/logfwd-types/src/pipeline/lifecycle.rs:524-532 unit test reject_advances_checkpoint
      • crates/logfwd-types/src/pipeline/lifecycle.rs:1069-1089 proof comment + verification for reject-advances
      • tla/PipelineMachine.tla:153,173 RejectBatch(s,b) == AckBatch(s,b)
    5. Delivery semantics are documented, but not in one boundary contract doc yet

      • book/src/architecture/pipeline.md:116 at-least-once with queue
      • dev-docs/SCANNER_CONTRACT.md:1-5 scanner-only scope; does not cover full I/O boundaries
    6. Related roadmap context is active

    Verification

    Commands run
    $ cargo test -q -p logfwd-io returns_429_when_channel_full_not_200 -- --nocapture
    running 1 test
    .
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 41 filtered out; finished in 0.00s
    
    $ cargo test -q -p logfwd-types reject_advances_checkpoint -- --nocapture
    running 1 test
    .
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 182 filtered out; finished in 0.01s

    Needs human decision

    What’s unclear: Should checkpoint advancement occur on permanent sink rejection?

    1. Option 1 (recommended): keep current behavior

      • Rule: reject advances checkpoint (drop undeliverable data, avoid infinite replay/drain stalls)
      • Pros: matches current code, tests, and TLA model; no behavior change required
      • Cons: explicitly accepts at-most-once for rejected batches
    2. Option 2: change behavior to “never advance on reject”

      • Rule: keep checkpoint behind rejected batch
      • Pros: stricter loss-avoidance interpretation
      • Cons: can stall drain/replay indefinitely on permanently bad records; requires coordinated code/spec/test/model rewrite
    3. Option 3: configurable policy (advance_on_reject = true|false)

      • Pros: explicit operator control
      • Cons: bigger design surface + combinatorial test/spec burden

    Recommendation: Option 1.

    Label: please add needs-decision.

    Detailed action plan (after decision)

    Mechanical implementation steps
    1. Create dev-docs/IO_CONTRACTS.md

      • Add sections:
        • Receiver → Pipeline
        • Pipeline → Sink
        • Checkpoint rules
        • Error classification
        • File lifecycle
      • Use a single matrix schema per row:
        • Boundary | Guarantee | Preconditions | Failure mode | Delivery semantics | Duplicate/loss window | Checkpoint rule | Enforcement mechanism | Evidence (path:line) | TODO
    2. Cross-link scanner scope in dev-docs/SCANNER_CONTRACT.md (top section)

      • Add explicit note: scanner-only contract
      • Link to dev-docs/IO_CONTRACTS.md for boundary-level guarantees
    3. Populate initial enforcement column

      • Kani / proptest / TLA+ / integration test where evidence exists
      • TODO: not yet enforced where it does not
    4. Resolve semantic field naming row with Shared semantic field definitions (logfwd-types) #1302 state

    5. Conflict check with open PR [codex] add OTLP and file IO contract oracles #1290 before merge

      • Reconcile overlapping contract/oracle wording and references
    6. Tests/docs checks for triage handoff

      • cargo test -p logfwd-io --test it
      • cargo test -p logfwd-types reject_advances_checkpoint

    Related items

    Type Link Relevance
    Issue #1303 This triage target
    Issue #1283 I/O refactor research context
    Issue #1302 Semantic field constants dependency
    PR #1290 Open work on OTLP/file I/O contract oracles; likely overlap
    File crates/logfwd-io/src/otlp_receiver.rs Receiver boundary guarantees (429/backpressure + field names)
    File crates/logfwd/src/pipeline.rs Ticket ack/reject + checkpoint advancement path
    File crates/logfwd-types/src/pipeline/lifecycle.rs Ordered ACK + reject semantics tests/proofs
    File tla/PipelineMachine.tla Formal reject==ack transition model
    File dev-docs/SCANNER_CONTRACT.md Existing scanner-only contract scope
    File book/src/architecture/pipeline.md Delivery-semantics narrative

    Note

    🔒 Integrity filtering filtered 6 items

    Integrity filtering activated and filtered the following items during workflow execution.
    This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.


    What is this? | From workflow: Issue Triage

    Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.

  3. changed the title [-]IO_CONTRACTS.md — living contract document for I/O boundaries[/-] [+]IO_CONTRACTS.md — living contract for I/O and control-plane semantics[/+] on Apr 6, 2026
  4. strawgate commented on Apr 6, 2026

    @strawgate
    OwnerAuthor

    Scheduling layer for this issue:

    1. work-unit: control-plane contracts — probe, status, and roll-up semantics #1412 — contract completion for probe, status, and roll-up semantics
    2. work-unit: control plane — process phase, drain, and probe truth #1413 — process phase, drain, and probe truth
    3. work-unit: control plane — shared status model and admin status surface #1414 — shared status snapshots and /admin/v1/status
    4. work-unit: control plane — input lifecycle status wiring #1415 — input lifecycle status wiring for receivers and tailers
    5. work-unit: control plane — output lifecycle status wiring #1416 — sink lifecycle, degradation, and failure wiring

    This keeps #1303 as the contract source of truth while letting execution happen in bounded, merge-safe slices.

  5. added
    component:diagnosticsDiagnostics endpoints, dashboard, health, probes, and trace explorer
    on Apr 6, 2026
  6. strawgate commented on Apr 11, 2026

    @strawgate
    OwnerAuthor

    Closing as completed via the canonical contract document and follow-on work-units.

    Verification evidence:

    Note: issue text names , but implementation settled on as the canonical file.

  7. strawgate commented on Apr 11, 2026

    @strawgate
    OwnerAuthor

    Follow-up verification summary (clean copy):

    The original issue filename text used IO_CONTRACTS.md; implementation standardized on ADAPTER_CONTRACT.md.

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

    P2: mediumMinor bug, missing validation, metric inaccuracyarchitectureSystemic design improvements that eliminate classes of bugscomponent:diagnosticsDiagnostics endpoints, dashboard, health, probes, and trace explorerdocumentationImprovements or additions to documentationenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions