ADR — Findings Runtime Disabled Mode

Formerly docs/architecture/ADR-FINDINGS-RUNTIME-DISABLED-MODE.md.

One of two ADRs numbered 013; the other is the unrelated .NET callgraph extractor dual-mode. This is the canonical “ADR-013” for the Findings Runtime cluster (ADR-013 → ADR-017).

FieldValue
StatusAccepted (signed off 2026-04-29 — Architecture lead via user approval)
Date2026-04-28
AuthorsFindings Ledger Runtime sub-team
Modulesrc/Findings/StellaOps.Findings.Ledger.WebService/Endpoints/RuntimeTracesEndpoints.cs, src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/Runtime/
RelatedSPRINT_20260429_011..014, SPRINT_20260429_015_FindingsLedger_runtime_documentation, SPRINT_20260429_023_Findings_runtime_contract_corrections

In one line: runtime instrumentation in the Stella Ops Findings Ledger is off by default; when off, a startup-bound null-repository pattern guarantees no runtime data is persisted, ingest endpoints accept-and-no-op (202), and read endpoints return 404 — wire-indistinguishable from “enabled but no data yet” so clients need only one code path.

Audience: Findings Ledger engineers and operators planning a safe, opt-in runtime rollout.

Context

Runtime instrumentation is opt-in. Existing deployments that upgrade to the runtime-aware Findings Ledger build must not start receiving trace ingest, growing storage, or surfacing runtime data on findings until the operator explicitly opts in. This is captured as a sprint decision:

*Decision: Runtime instrumentation is disabled by default in new

  • upgraded deployments. Operators opt in. Avoids surprise data ingestion + disk growth on upgrade.*
    SPRINT_20260429_015_FindingsLedger_runtime_documentation Decisions & Risks

The toggle is a single config key: findings:ledger:runtime:enabled, default false.

Sprint 023 corrected the DI binding to match this contract: an absent config key now resolves the NullRuntime*Repository implementations. Only an explicit true value wires Postgres runtime persistence.

When the toggle is off, three things must happen:

  1. No persistence is touched. Even if a misconfigured agent posts trace data, the WebService must not write to the runtime tables.
  2. Read endpoints behave like “feature has no data yet”, not “feature is broken”. Clients (UI / CLI / SDK) should differentiate “you turned off the feature” from “you turned on the feature but nothing has been ingested yet” — but the wire contract should be identical, so client logic is uniform.
  3. Ingest endpoints don’t fail noisily. Agents should be able to keep posting; the server simply no-ops the request. This is the familiar “204-style sink” pattern that makes opt-in rollout safe.

Decision

Disabled mode is implemented with two cooperating mechanisms:

1. Null-pattern repository implementations

When the toggle is off, the DI registration binds the runtime repositories to no-op (null-pattern) implementations:

The application code (RuntimeInstrumentationService, RuntimeTracesEndpoints, RuntimeTimelineEndpoints) is unchanged between enabled and disabled modes. It calls the repository interface either way. The repository selection happens once at startup.

2. The 202 / 404 wire contract

EndpointDisabled mode responseEnabled-but-empty responseEnabled-with-data response
POST .../runtime/traces202 Accepted, frameCount=0202 Accepted, frameCount=N202 Accepted, frameCount=N
GET .../runtime/traces404 NotFound404 NotFound200 OK + body
GET .../runtime/score404 NotFound404 NotFound200 OK + body
GET .../runtime-timeline404 NotFound404 NotFound200 OK + body

Disabled and “enabled but no data” are wire-indistinguishable. This is intentional — clients should not branch on the toggle state. The toggle is an operator concept, not a client concept.

3. Empty-result reads return 404 (not 200 + empty)

Returning 200 OK with [] would force every client to inspect the body to decide whether to render an empty state. Returning 404 makes the empty-state path a status-code branch, which is the standard HTTP idiom for “no resource here yet”. This is asserted by contract tests:

Architecture-lead sign-off

Rationale

Why null-pattern instead of a config check at every call site? A config check at every call site is impossible to audit — one missed branch and a disabled deployment writes data. The null-pattern collapses the safety check to one binding decision at startup, which is trivially auditable and trivially testable.

Why 202 on ingest in disabled mode (not 404 / 503)? Two reasons:

  1. Operational rollout safety. Operators can deploy the agent side first, then flip the server toggle, with zero downtime and zero error spam in agent logs. If we returned 404, every agent would log an error indefinitely.
  2. Symmetric contract. “Enabled with no data yet” and “disabled” should look the same on the wire so clients have one code path.

Why 404 on read in disabled mode (not 200 + empty body)? Three reasons:

  1. Standard HTTP idiom. 404 means “no resource”; 200 + [] means “resource exists, contains nothing”. They’re different.
  2. Client UX. UI components can short-circuit to an empty state on a 404 without parsing the body. Less code in every client.
  3. Backward compatibility. Older clients that don’t understand the runtime feature will simply skip a 404 and continue.

Why one toggle and not per-endpoint toggles? Splitting the toggle forces operators to understand the feature’s internal structure to configure it sensibly. One toggle keeps the decision space small. Sub-toggles (e.g. “disable timeline retention but keep traces”) can land later if there’s demand.

Alternatives considered

Consequences

Test coverage

References

Sign-off Window

Accepted on 2026-04-29 by Architecture lead. The 14-day default-accept window from sprint 024 (deadline 2026-05-13) closed early via explicit approval rather than the timeout path. Original window text retained below for history.

Following the project’s RFC default-accept process: if this ADR remains Proposed with no objection logged in this document by 2026-05-13 (14 days from sprint 024 commit), it is automatically promoted to Accepted. Architecture-lead may flip status earlier on review. Objections should be added under “Open Issues” below with name + date.

Open Issues

No objections logged during the sign-off window.