Four QA gates are wrong by construction, and the corpus's own completeness gate fails today
Источник:
dev/fera-research/docs/oc1/014-qa-gates-wrong-by-construction.md(read-only; сгенерировано scripts/sync-docs.mjs)
Four QA gates are wrong by construction, and the corpus’s own completeness gate fails today
Section titled “Four QA gates are wrong by construction, and the corpus’s own completeness gate fails today”-
kind: problem
-
area: ui / storage / modules / perf
-
evidence — QA.md is
README.md:19’s “quality gates” doc; it defines “what ‘done’ and ‘safe to merge’ mean”. Four of its gates cannot be satisfied or test what they claim:-
“Zero dropped calls” contradicts the straggler kill.
QA.md:129-132— “A module can be hot-reloaded while a concurrent stream of calls is in flight, with zero dropped calls”.OQ.md:739-742— “Host-function TTL: 1 minute, then kill — and the TTL timer starts only after the epoch has ended… kill stragglers at 60 s.” A deliberate bounded-drop policy exists; the gate demands an absolute. (And per my finding 009 the gate measures call accounting, not code liveness, which is the thing that actually breaks.) -
The signature gate has a hole in the load-bearing field.
QA.md:149-151— “signature covers the entire entry: a tampered-but-unsigned modification of any field (includinghost.max_version,fallback,depends_on) is rejected.”ADR.md:781-783— “The manifest declareslayer: shared | types | implanddepends_on; load order is derived topologically”;ADR.md:795-796— “manifest layering and the dependency graph become load-bearing metadata (load-order correctness), not decoration.”layeris not in QA’s list, so an unsigned tamper oflayerpasses this gate.QA.md:157-159separately tests that load order followslayer+depends_on, but asserts correctness, not rejection. -
The regex gate demands a threshold the decision replaced with a matrix.
QA.md:69-72— “measurably faster on a corpus above a defined size threshold (performance) — threshold TBD, seeOQ.md”.OQ.md:288-304— “The threshold is a decision matrix, not one number: (corpus size) × (narrowing quality)… No literals extractable from the pattern → always naive. Candidate count > K (hard cap, % of corpus) → naive… modeled as a typestate plan (RegexSearchPlan::Naive | TwoPhase{…})”. A pure size threshold cannot express either branch. Calibration was gated on OQ-006 being frozen (OQ.md:303); OQ-006 froze 2026-10-02 and no calibration is recorded — so the gate is now stale-blocked, not TBD.PREDICT.md:60adds a third mechanism (a cost model with calibrated constants) layered on the typestate plan. -
The Lottie gate requires a benchmark that is no longer the deciding mechanism.
QA.md:87-90— “runs undercriterionfor bothtlottieandvelatobackends before a default is chosen; the decision and its measured basis are recorded inADR.mdas an addendum once made.”OQ.md:96-101— “Resolved (2026-09-30) → sequencing decision.tlottieis the default, implemented first.velatotesting is deferred to near v2.” The gate demands a pre-default benchmark; the decision was made by sequencing. And the obligation to record it in ADR.md is un-runnable:grep -n -i "sticker\|tgs" ADR.md→ 0 hits — no ADR mentions stickers at all.OQ.md:92-94also states a precondition both docs omit: coverage must be confirmed before benchmarking (“a faster-but-visually- wrong renderer is not a real candidate”).
-
The encryption-tier gate cites the wrong ADR and is looser than it.
QA.md:22-24— “Any new persisted data type has an explicit answer to ‘which encryption tier’ (metadata vs content, ADR-016) recorded in code comments or the relevant ADR addendum”.ADR.md:633-635(ADR-016) is the ADR that lacks the answer (“flagged as an open question”);ADR.md:684-686(ADR-031) has it and requires more: “New persisted fields require an audit-table entry before merge”. So the gate (a) points at the empty ADR and (b) accepts a code comment where ADR-031 demands a table row. Two data classes currently have no tier at all: the sticker frame cache (absent from both lists,ADR.md:648-664;RESEARCH-OQ-003.md:132-133argues it needs none but no doc says so) and Turso’s.ttsofiles (my finding 020). -
Two of the corpus’s own completeness gates fail as written:
QA.md:186-187— “Every ADR has a corresponding section inRFC.mdand/orTECHSPEC.md”. Verified mention counts (RFC / TECHSPEC / QA): ADR-006: 0/0/0 · ADR-024: 0/0/0 · ADR-025: 0/0/0 · ADR-026: 0/0/0 · ADR-027: 0/0/0 · ADR-030: 0/0/0 · ADR-031: 0/0/0 — 7 of 31.QA.md:190-192— “Phase 1 … achievable using only crates/decisions already recorded here”: phase 1 (TECHSPEC.md:454-456) needs the protocol layer, whose only recorded decision is the ADR-026 fork with no crate inTECHSPEC.md:20’s layout, and step 3 specifies “minimal GPUI rendering” on the substrate ADR-028 rejected.QA.md:188-189— “Every ‘open question’ flagged inline … is also present inOQ.md” is self-defeating: QA never cites an OQ number (QA.md:72,:87,:104all say bare “OQ.md”), so a linter cannot run it, and 6 of the referenced entries are now Resolved while the referring text still says TBD. -
One gate with no ADR behind it at all:
QA.md:163-164— the launcher replaces the core module “on all three target OSes”. The phrase “three target OSes” appears once in the entire corpus; no document states the target OS set. -
Gates that cannot fail:
QA.md:116-119(frame budgets) — see my finding 008;QA.md:77-79(Argon2id “with the rationale for the chosen cost”) — pure prose;QA.md:108-110and:147-148are self-declared review gates.
-
-
severity: mid
Суть / Substance
Section titled “Суть / Substance”QA.md is the doc that decides whether the project is allowed to ship, and a meaningful fraction of its gates have drifted out of sync with the decisions they police. The common cause is the same one behind findings 001–003, 008 and 011: a decision moved, the gate did not. When the answer changes in an ADR and the gate keeps the old premise, the gate either becomes unsatisfiable (case 1, 3, 4) or silently stops testing the thing that matters (case 2, 5).
Case 2 is the one worth escalating. ADR-020 made layer and depends_on
load-bearing for load-order correctness — a tampered layer reorders
the module graph. The signature gate enumerates the fields it protects and
omits one. The omission is harmless only while the ordering test catches it,
and that test asserts the result is correct, not that the tampering is
rejected — so an attacker who controls the manifest but not the signature
gets a different failure mode (wrong load order, possibly a module loaded
before its dependency) instead of a clean rejection. For a trust manifest
whose entire purpose is authenticity, that is the wrong trade.
Case 5 is structural: QA’s cross-cutting “every persisted type has a tier” gate is the mechanism that keeps ADR-031’s audit table true, but it points at the ADR that has no answer, accepts a weaker form of evidence, and two persisted data classes have no entry at all.
Варианты решения (tradeoffs)
Section titled “Варианты решения (tradeoffs)”- (A) Rewrite the four gates against current decisions, add
layerand the two missing tiers, and make the completeness gates mechanical. ~1 day of editing, no architecture work. - (B) Add a “gate ↔ decision” traceability column to QA.md (each gate names the ADR/OQ it enforces, plus the date of that decision), and a review rule: changing a decision obliges re-reading the gates citing it. Highest leverage against recurrence; slightly more process.
- (C) Generate the mechanical gates as a script (
crates/-adjacent tooling): ADR-coverage check, OQ-resolution-marker check, manifest field-coverage check. Catches 3 of the 6 defects automatically. - (D) Leave QA as prose intent. Reject.
Предлагаемое решение
Section titled “Предлагаемое решение”(A) + (C), with (B) as the durable rule.
- Rewrite
QA.md:129-132to matchOQ.md:739-742: zero dropped calls within the drain window, stragglers killed at 60 s, plus a new gate for the real hazard (no thread executing in a retired mapping, after many swap cycles) — see finding 009. - Rewrite
QA.md:69-72to test the decision matrix, not a threshold: “for a corpus of N and a pattern with/without extractable literals, the chosenRegexSearchPlanbranch matches the OQ-011 matrix, and the two-phase path is faster than naive on the branch where it is chosen” — that is falsifiable without calibration constants. - Rewrite
QA.md:87-90to matchOQ.md:96-101:tlottieis default;velatoA/B is a near-v2 obligation; and add the coverage-first precondition (:92-94) as its own gate. Record the sticker format decision in a new ADR once OQ-003 closes (it currently has zero ADR coverage — grep confirms 0 sticker mentions in ADR.md). - Add
layertoQA.md:149-151’s field list, and add a gate that unsigned tampering of every manifest field is rejected (field enumeration generated from the schema, not hand-maintained). - Fix
QA.md:22-24to cite ADR-031, require an audit-table row (not a code comment), and add the two missing entries (sticker cache,.ttsofiles) — the first needs a tier decision (finding 013), the second needs an ADR (finding 020). - Make the completeness gates runnable (
QA.md:186-192): a script that fails when an ADR has no downstream mention, when a doc says “TBD/open question” about an OQ markedResolved, or when a referencedOQ-NNNnumber does not exist. All three checks are ~15 lines and would have caught findings 001, 002, 003, 008 and 011 on their own. - State the target OS set in one place and point
QA.md:163-164at it (see my finding on the platform matrix).