PERF GUARD: keep file parsing and provider IO off interactive read paths (#663) #793

Open
opened 2026-10-02 13:10:53 +00:00 by kayg · 1 comment
Owner

PERF GUARD: keep file parsing and provider IO off interactive read paths (#663)

Finding and context

origin/dev crates/plugins/notes/src/lib.rs:3568–3578 resolves a Note ID then
calls store::read in a GET handler. The write at :3598 legitimately reads the
source for a checked edit. A blanket grep for Markdown or file reads cannot
distinguish them. #702 already owns Note projection adoption; #677/#684/#687
and related rule-1 issues own other Tabs. No latency number is claimed here.

Exact detection

Start from registered interactive GET/read handlers. Build a source call graph
through local helpers, re-exports and trait implementations. Maintain a small
explicit symbol catalog for capabilities: calternal-fs content reads, Note/Task
source decoders, calternal-notes-core Markdown parsers, Money codecs, filesystem
walks, provider HTTP/IMAP/CalDAV clients and synchronous IO. Flag any reachable
capability from an interactive metadata/list/body JSON read. Offloading to
spawn_blocking is still on the request path if the request awaits the result.
Unknown dynamic dispatch/macro expansion requires a scoped resolution or
exception; report limited analysis rather than claim complete Rust inference.

Classify raw file downloads/media streams, authentication, mutations and
background indexers separately. Preserve authentication and authorization on
all reads. Raw download streaming is allowed; metadata parsing during that
stream is not automatically allowed. Mutations may parse checked source files
under the existing single-writer rules. Do not force them into the read guard.

Add test-only capability counters/fakes at the existing boundaries. For each
registered projected read, seed its Index, disable provider/source-read
capabilities, call the handler, and assert success with zero content reads,
parser calls and provider calls. Repeat after projection invalidation: return
a defined unavailable/not-ready state or committed projection, never silently
repair from files in the request. Call a background indexer separately and
prove it can rebuild the projection. These are test fakes, never shipped UI data.

Tests and gates

Negative source fixtures hide IO in a helper, alias, trait and awaited worker.
Positive fixtures use reader_pool projections and mutation-only codec calls.
The counter test catches an indirect read the source scan misses. Run lint in
CI and capability contracts with each affected crate's tests. Reuse existing
storage/provider abstractions; do not create a second filesystem path API.

Scope and exception contract

Specification only, from #663 perf-guards. Implement in a later job. Reuse the
shared guard registry and ledger described in the coverage issue. Until it
exists, use this exact contract: an exception has rule ID, repo-relative file,
symbol or route/surface ID, normalized syntax hash (or exact build metric),
owner issue, reason, replacement test and UTC expiry date. No directory globs,
line-number-only entries or ignore all comments. Fail on expired, changed,
duplicate and unused exceptions. Existing debt needs an explicit entry and a
non-growing limit. Print exceptions in CI. An exception does not waive access,
session clearing or accessibility tests. Generated and test fixtures are
excluded by explicit source class, not substring matching. Tests must prove
both violation detection and valid exceptions. No product fix belongs here.

The static/contract command exits 1 for violations, 2 for invalid configuration
or parse/build errors, and 0 only for complete passing coverage. Output includes
rule ID, file:line, symbol/route, actual value and required value. No User content.
Measured host timing stays periodic under CLAUDE.md. Deterministic guards fail
required CI; the dedicated perf-budget runner reports measured budget failures
with nonzero status but is not a required merge check.

# PERF GUARD: keep file parsing and provider IO off interactive read paths (#663) ## Finding and context origin/dev `crates/plugins/notes/src/lib.rs:3568–3578` resolves a Note ID then calls store::read in a GET handler. The write at `:3598` legitimately reads the source for a checked edit. A blanket grep for Markdown or file reads cannot distinguish them. #702 already owns Note projection adoption; #677/#684/#687 and related rule-1 issues own other Tabs. No latency number is claimed here. ## Exact detection Start from registered interactive GET/read handlers. Build a source call graph through local helpers, re-exports and trait implementations. Maintain a small explicit symbol catalog for capabilities: calternal-fs content reads, Note/Task source decoders, calternal-notes-core Markdown parsers, Money codecs, filesystem walks, provider HTTP/IMAP/CalDAV clients and synchronous IO. Flag any reachable capability from an interactive metadata/list/body JSON read. Offloading to spawn_blocking is still on the request path if the request awaits the result. Unknown dynamic dispatch/macro expansion requires a scoped resolution or exception; report limited analysis rather than claim complete Rust inference. Classify raw file downloads/media streams, authentication, mutations and background indexers separately. Preserve authentication and authorization on all reads. Raw download streaming is allowed; metadata parsing during that stream is not automatically allowed. Mutations may parse checked source files under the existing single-writer rules. Do not force them into the read guard. Add test-only capability counters/fakes at the existing boundaries. For each registered projected read, seed its Index, disable provider/source-read capabilities, call the handler, and assert success with zero content reads, parser calls and provider calls. Repeat after projection invalidation: return a defined unavailable/not-ready state or committed projection, never silently repair from files in the request. Call a background indexer separately and prove it can rebuild the projection. These are test fakes, never shipped UI data. ## Tests and gates Negative source fixtures hide IO in a helper, alias, trait and awaited worker. Positive fixtures use reader_pool projections and mutation-only codec calls. The counter test catches an indirect read the source scan misses. Run lint in CI and capability contracts with each affected crate's tests. Reuse existing storage/provider abstractions; do not create a second filesystem path API. ## Scope and exception contract Specification only, from #663 perf-guards. Implement in a later job. Reuse the shared guard registry and ledger described in the coverage issue. Until it exists, use this exact contract: an exception has rule ID, repo-relative file, symbol or route/surface ID, normalized syntax hash (or exact build metric), owner issue, reason, replacement test and UTC expiry date. No directory globs, line-number-only entries or `ignore all` comments. Fail on expired, changed, duplicate and unused exceptions. Existing debt needs an explicit entry and a non-growing limit. Print exceptions in CI. An exception does not waive access, session clearing or accessibility tests. Generated and test fixtures are excluded by explicit source class, not substring matching. Tests must prove both violation detection and valid exceptions. No product fix belongs here. The static/contract command exits 1 for violations, 2 for invalid configuration or parse/build errors, and 0 only for complete passing coverage. Output includes rule ID, file:line, symbol/route, actual value and required value. No User content. Measured host timing stays periodic under CLAUDE.md. Deterministic guards fail required CI; the dedicated perf-budget runner reports measured budget failures with nonzero status but is not a required merge check.
Author
Owner

F5 — P2: compliant projected reads have no way to clear unresolved-call debt

Evidence: scripts/perf_guards/rules.py:84 resolves local source functions.
At lines 86–88, every other call except Ok, Err, Some and None is unresolved.
At lines 114–115, each unresolved call always produces a finding. The function
does not take test exports and does not consult the declared projection tests
to resolve the capability. Even sqlx::query(...).fetch_one(...) remains debt
after a real zero-source-read projection test is added. A positive fixture at
scripts/perf_guards/test_rules.py:153 checks only the absence of
io.content-read; it does not require a complete passing result.

Unknown dispatch must fail closed under #793. But a known safe boundary or an
exact tested capability must also have a supported resolution. Add explicit
bindings for reviewed external calls and bounded capability adapters. Require
their real tests. Add a complete positive projection fixture that passes with
no ledger, and negative provider, trait and macro fixtures. Do not mark a
method safe by its name alone. Owner: #793.

## F5 — P2: compliant projected reads have no way to clear unresolved-call debt Evidence: `scripts/perf_guards/rules.py:84` resolves local source functions. At lines 86–88, every other call except Ok, Err, Some and None is unresolved. At lines 114–115, each unresolved call always produces a finding. The function does not take test exports and does not consult the declared projection tests to resolve the capability. Even `sqlx::query(...).fetch_one(...)` remains debt after a real zero-source-read projection test is added. A positive fixture at `scripts/perf_guards/test_rules.py:153` checks only the absence of `io.content-read`; it does not require a complete passing result. Unknown dispatch must fail closed under #793. But a known safe boundary or an exact tested capability must also have a supported resolution. Add explicit bindings for reviewed external calls and bounded capability adapters. Require their real tests. Add a complete positive projection fixture that passes with no ledger, and negative provider, trait and macro fixtures. Do not mark a method safe by its name alone. Owner: #793.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#793
No description provided.