M0: compilable skeleton — Kigi 0.1.0 fork surgery
Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.
Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
ptyctl, ptyctl-cli, third_party/ unchanged; proto package
xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
(templates re-encrypted)
Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
module & dc_log, heap-profile uploader, auth-diagnostics uploader,
session-analytics halves of feedback; local zero-egress observability
preserved in new kigi-log crate (unified log, --debug firehose,
subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
shell util
Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted
Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean
Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
(new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
fast-worktree); RSS measurement tests serialized via serial_test
Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
notices sustained; kigi-tools ported-code notices extended; README,
CONTRIBUTING, SECURITY, AGENTS.md rewritten
Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
This commit is contained in:
@@ -0,0 +1,800 @@
|
||||
//! The invariant suite: predicates over grouped `KIGI_SCROLL_LOG` streams.
|
||||
//!
|
||||
//! Stall-safety: every timing predicate reads the RECORDER's clock
|
||||
//! (`ts_ms`, `ms_since_prev_flush`, `avg_interval_ms`), never the test
|
||||
//! process's — CI load can stretch host-side gesture delays but can only
|
||||
//! ever *widen* the producer-measured spacings, so no invariant here can
|
||||
//! false-fail on a loaded machine.
|
||||
//!
|
||||
//! Two invariants are declared here but checked by the A13 matrix runner,
|
||||
//! not this module: [`InvariantId::Screen`] (the viewport visibly
|
||||
//! moved/clamped — needs `PtyHarness` marker positions) and
|
||||
//! [`InvariantId::Quiet`] (no repaint churn after finalize — needs the
|
||||
//! harness frame watermark). They exist in the id enum so cells can declare
|
||||
//! them and the runner can route by [`InvariantId::is_log_side`];
|
||||
//! [`check_log_invariant`] panics if asked to evaluate them.
|
||||
|
||||
use super::cells::ExpectedProfile;
|
||||
use super::gestures::{
|
||||
ACCEL_MIN_INTERVAL_MS, MIN_LINES_PER_WHEEL_STREAM, REDRAW_CADENCE_MS, TRACKPAD_ACCEL_MAX,
|
||||
};
|
||||
use super::log::{ScrollLogLine, StreamGroup};
|
||||
|
||||
/// Float slack for f32-serialized fields (accel/speed/carry comparisons).
|
||||
const F32_TOLERANCE: f64 = 0.01;
|
||||
|
||||
/// Invariant identifier; `as_str` is the design's I-* vocabulary.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum InvariantId {
|
||||
/// I-ORD: `ts_ms` is non-decreasing across the capture (flip
|
||||
/// boundaries legitimately share a timestamp).
|
||||
Ord,
|
||||
/// I-CAP: every record's `|flushed| ≤ cap` — the A4 teleport guard.
|
||||
Cap,
|
||||
/// I-DROP-EQ: finalize carries `dropped == backlog_after` (the producer
|
||||
/// constructs it that way; a mismatch is producer drift).
|
||||
DropEq,
|
||||
/// I-CADENCE: intra-stream flush spacings ≥ `REDRAW_CADENCE_MS − 1` —
|
||||
/// the A2 busy-spin guard. Skips `promotion`-triggered records (they
|
||||
/// flush immediately by design) and `finalize` records (the finalize
|
||||
/// flush deliberately ignores the cadence gate — `finalize_stream_at`
|
||||
/// in `mouse.rs` — and a flip finalize can land mid-slot; a gap
|
||||
/// finalize is ≥80ms anyway, so nothing real is lost). The design
|
||||
/// sketch listed only the promotion skip; the finalize skip is the
|
||||
/// code-verified correction.
|
||||
Cadence,
|
||||
/// I-CONS-W: forced-wheel totals are exact —
|
||||
/// `|applied+dropped| == trunc(events × wheel_lpt/ept × speed)` ±1,
|
||||
/// with the `MIN_LINES_PER_WHEEL_STREAM` substitution when the raw
|
||||
/// pricing truncates to zero. Wheel pricing never includes carry.
|
||||
ConsW,
|
||||
/// I-CONS-A: auto/trackpad totals are bounded —
|
||||
/// per-event pricing lies in `[min(wheel_lpt/ept, tp_lpt/3),
|
||||
/// max(wheel_lpt/ept, ACCEL_MAX × tp_lpt/3)] × speed` (trackpad divisor
|
||||
/// is the normalized 3, accel ceiling 3.0 = `trackpad_accel_max`;
|
||||
/// effective accel tops at 2.5 so the bound is loose-but-sound). Checks
|
||||
/// the finalize's `desired` within `[lo, hi]` and the delivered
|
||||
/// `|applied+dropped| ≤ hi`; ±1 slack absorbs carry/trunc.
|
||||
ConsA,
|
||||
/// I-ACCEL: `1.0 ≤ accel ≤ 3.0` on every record, and `avg_interval_ms`,
|
||||
/// when present, is ≥ `ACCEL_MIN_INTERVAL_MS` (6). The G5 clause: even
|
||||
/// ghostty-style 4ms duplicate reports must never drag the average
|
||||
/// under 6 — the producer excludes sub-6ms intervals from the window,
|
||||
/// so a lower value means that artifact guard regressed.
|
||||
Accel,
|
||||
/// I-CARRY: `|carry| < 1.0` everywhere (only sub-line remainders ride
|
||||
/// across streams), and a wheel-kind finalize zeroes it — the NEXT
|
||||
/// `stream_start` must echo `carry == 0`. The log carries no direction,
|
||||
/// but the machine zeroes carry at wheel finalize and resets it on
|
||||
/// direction change, so the next-start check holds for both same- and
|
||||
/// opposite-direction successors (direction-agnostic strengthening of
|
||||
/// the design's same-direction phrasing).
|
||||
Carry,
|
||||
/// I-CFG: the `stream_start` config echo matches the cell's expected
|
||||
/// profile (mode/ept/wheel_lpt/trackpad_lpt/invert/speed) — the env →
|
||||
/// profile plumbing witness.
|
||||
Cfg,
|
||||
/// I-MUX-NO-OVER: delivered total per stream ≤ `events × speed + 1` —
|
||||
/// the conservative remuxed profile (ept=1/wheel_lpt=1) prices at most
|
||||
/// one line per event. Attach only to accel-free gestures (>20ms
|
||||
/// spacing — exactly 20ms still interpolates to 1.6× in the accel band;
|
||||
/// G9's 55ms clears it): a fast trackpad-classified mux stream may
|
||||
/// legitimately exceed it via accel.
|
||||
MuxNoOver,
|
||||
/// I-SMOOTH-COAST: per stream, `Σ|flushed|` over flush-bearing records
|
||||
/// with `events_since_flush == 0` ≤ cap — motion delivered after input
|
||||
/// stopped is at most one capped catch-up. The jerk's coast-drain +
|
||||
/// finalize re-price burst exceeds it (xfail until A13's decel fix).
|
||||
SmoothCoast,
|
||||
/// I-NO-DROP: every finalize has `dropped == 0`. Attach to gestures the
|
||||
/// cap can keep up with; floods legitimately drop.
|
||||
NoDrop,
|
||||
/// I-SCREEN (harness-side, A13): viewport marker delta matches the
|
||||
/// gesture — moved on scroll, clamped at the bottom pin (G7).
|
||||
Screen,
|
||||
/// I-QUIET (harness-side, A13): frame watermark stays put after the
|
||||
/// last finalize — no post-gesture repaint churn (A2's symptom).
|
||||
Quiet,
|
||||
}
|
||||
|
||||
impl InvariantId {
|
||||
/// Design vocabulary label.
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
InvariantId::Ord => "I-ORD",
|
||||
InvariantId::Cap => "I-CAP",
|
||||
InvariantId::DropEq => "I-DROP-EQ",
|
||||
InvariantId::Cadence => "I-CADENCE",
|
||||
InvariantId::ConsW => "I-CONS-W",
|
||||
InvariantId::ConsA => "I-CONS-A",
|
||||
InvariantId::Accel => "I-ACCEL",
|
||||
InvariantId::Carry => "I-CARRY",
|
||||
InvariantId::Cfg => "I-CFG",
|
||||
InvariantId::MuxNoOver => "I-MUX-NO-OVER",
|
||||
InvariantId::SmoothCoast => "I-SMOOTH-COAST",
|
||||
InvariantId::NoDrop => "I-NO-DROP",
|
||||
InvariantId::Screen => "I-SCREEN",
|
||||
InvariantId::Quiet => "I-QUIET",
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether [`check_log_invariant`] can evaluate this id from the log
|
||||
/// alone. `false` = harness-side (screen/frame state), owned by the
|
||||
/// A13 runner.
|
||||
pub fn is_log_side(self) -> bool {
|
||||
!matches!(self, InvariantId::Screen | InvariantId::Quiet)
|
||||
}
|
||||
}
|
||||
|
||||
/// Verdict of one invariant over one cell's captured streams.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum InvariantResult {
|
||||
Pass,
|
||||
Violated { detail: String },
|
||||
}
|
||||
|
||||
impl InvariantResult {
|
||||
pub fn is_pass(&self) -> bool {
|
||||
matches!(self, InvariantResult::Pass)
|
||||
}
|
||||
}
|
||||
|
||||
fn violated(detail: String) -> InvariantResult {
|
||||
InvariantResult::Violated { detail }
|
||||
}
|
||||
|
||||
/// All records of all groups, in capture order.
|
||||
fn records<'a>(groups: &'a [StreamGroup<'a>]) -> impl Iterator<Item = &'a ScrollLogLine> {
|
||||
groups.iter().flat_map(|g| {
|
||||
std::iter::once(g.start)
|
||||
.chain(g.flushes.iter().copied())
|
||||
.chain(g.finalize)
|
||||
})
|
||||
}
|
||||
|
||||
/// Signed delivered total of a finalized stream: applied + discarded.
|
||||
fn delivered(finalize: &ScrollLogLine) -> i64 {
|
||||
finalize.applied_total + finalize.dropped.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Evaluate one log-side invariant. Panics on harness-side ids
|
||||
/// ([`InvariantId::is_log_side`] == false) — routing those here is a
|
||||
/// runner bug, not a cell verdict.
|
||||
pub fn check_log_invariant(
|
||||
id: InvariantId,
|
||||
expected: &ExpectedProfile,
|
||||
groups: &[StreamGroup<'_>],
|
||||
) -> InvariantResult {
|
||||
match id {
|
||||
InvariantId::Ord => check_ord(groups),
|
||||
InvariantId::Cap => check_cap(groups),
|
||||
InvariantId::DropEq => check_drop_eq(groups),
|
||||
InvariantId::Cadence => check_cadence(groups),
|
||||
InvariantId::ConsW => check_cons_w(expected, groups),
|
||||
InvariantId::ConsA => check_cons_a(expected, groups),
|
||||
InvariantId::Accel => check_accel(groups),
|
||||
InvariantId::Carry => check_carry(groups),
|
||||
InvariantId::Cfg => check_cfg(expected, groups),
|
||||
InvariantId::MuxNoOver => check_mux_no_over(expected, groups),
|
||||
InvariantId::SmoothCoast => check_smooth_coast(groups),
|
||||
InvariantId::NoDrop => check_no_drop(groups),
|
||||
InvariantId::Screen | InvariantId::Quiet => panic!(
|
||||
"{} is harness-side (needs PtyHarness screen/frame state); the A13 matrix \
|
||||
runner checks it — route by InvariantId::is_log_side",
|
||||
id.as_str()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_ord(groups: &[StreamGroup<'_>]) -> InvariantResult {
|
||||
let mut prev = f64::NEG_INFINITY;
|
||||
for rec in records(groups) {
|
||||
if rec.ts_ms < prev {
|
||||
return violated(format!(
|
||||
"ts_ms went backwards: {} after {prev} ({} record)",
|
||||
rec.ts_ms, rec.evt
|
||||
));
|
||||
}
|
||||
prev = rec.ts_ms;
|
||||
}
|
||||
InvariantResult::Pass
|
||||
}
|
||||
|
||||
fn check_cap(groups: &[StreamGroup<'_>]) -> InvariantResult {
|
||||
for rec in records(groups) {
|
||||
if rec.flushed.abs() > rec.cap {
|
||||
return violated(format!(
|
||||
"flushed {} exceeds cap {} at ts_ms={} (trigger {})",
|
||||
rec.flushed, rec.cap, rec.ts_ms, rec.trigger
|
||||
));
|
||||
}
|
||||
}
|
||||
InvariantResult::Pass
|
||||
}
|
||||
|
||||
fn check_drop_eq(groups: &[StreamGroup<'_>]) -> InvariantResult {
|
||||
for group in groups {
|
||||
let Some(fin) = group.finalize else { continue };
|
||||
match fin.dropped {
|
||||
Some(dropped) if dropped == fin.backlog_after => {}
|
||||
Some(dropped) => {
|
||||
return violated(format!(
|
||||
"finalize at ts_ms={} has dropped={dropped} but backlog_after={}",
|
||||
fin.ts_ms, fin.backlog_after
|
||||
));
|
||||
}
|
||||
None => {
|
||||
return violated(format!("finalize at ts_ms={} lacks dropped", fin.ts_ms));
|
||||
}
|
||||
}
|
||||
}
|
||||
InvariantResult::Pass
|
||||
}
|
||||
|
||||
fn check_cadence(groups: &[StreamGroup<'_>]) -> InvariantResult {
|
||||
// A stream's first flush-bearing record is skipped: its spacing is
|
||||
// global (measured from the previous stream — see
|
||||
// `intra_stream_flush_spacings_ms`).
|
||||
let floor = (REDRAW_CADENCE_MS - 1) as f64;
|
||||
for group in groups {
|
||||
for rec in group.flush_bearing().skip(1) {
|
||||
if rec.trigger == "promotion" || rec.trigger == "finalize" {
|
||||
continue;
|
||||
}
|
||||
if let Some(spacing) = rec.ms_since_prev_flush
|
||||
&& spacing < floor
|
||||
{
|
||||
return violated(format!(
|
||||
"{}ms flush spacing < {floor}ms at ts_ms={} (trigger {})",
|
||||
spacing, rec.ts_ms, rec.trigger
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
InvariantResult::Pass
|
||||
}
|
||||
|
||||
fn check_cons_w(expected: &ExpectedProfile, groups: &[StreamGroup<'_>]) -> InvariantResult {
|
||||
if expected.mode != "wheel" {
|
||||
return violated(format!(
|
||||
"I-CONS-W attached to a mode={} cell (needs forced wheel)",
|
||||
expected.mode
|
||||
));
|
||||
}
|
||||
let rate = f64::from(expected.wheel_lpt) / f64::from(expected.ept.max(1));
|
||||
for group in groups {
|
||||
let Some(fin) = group.finalize else { continue };
|
||||
let raw = (fin.events_total as f64 * rate * f64::from(expected.speed)).trunc() as i64;
|
||||
let want = if fin.events_total > 0 {
|
||||
raw.max(MIN_LINES_PER_WHEEL_STREAM)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let got = delivered(fin).abs();
|
||||
if (got - want).abs() > 1 {
|
||||
return violated(format!(
|
||||
"wheel stream at ts_ms={}: delivered {got} lines for {} events, expected {want}±1",
|
||||
fin.ts_ms, fin.events_total
|
||||
));
|
||||
}
|
||||
}
|
||||
InvariantResult::Pass
|
||||
}
|
||||
|
||||
fn check_cons_a(expected: &ExpectedProfile, groups: &[StreamGroup<'_>]) -> InvariantResult {
|
||||
if expected.mode == "wheel" {
|
||||
return violated("I-CONS-A attached to a forced-wheel cell (use I-CONS-W)".into());
|
||||
}
|
||||
let wheel_rate = f64::from(expected.wheel_lpt) / f64::from(expected.ept.max(1));
|
||||
let tp_rate = f64::from(expected.trackpad_lpt) / 3.0;
|
||||
// Forced trackpad never prices via the wheel table.
|
||||
let (min_rate, max_rate) = if expected.mode == "trackpad" {
|
||||
(tp_rate, TRACKPAD_ACCEL_MAX * tp_rate)
|
||||
} else {
|
||||
(
|
||||
wheel_rate.min(tp_rate),
|
||||
wheel_rate.max(TRACKPAD_ACCEL_MAX * tp_rate),
|
||||
)
|
||||
};
|
||||
for group in groups {
|
||||
let Some(fin) = group.finalize else { continue };
|
||||
let events = fin.events_total as f64;
|
||||
let speed = f64::from(expected.speed);
|
||||
let lo = events * min_rate * speed - 1.0;
|
||||
let hi = events * max_rate * speed + 1.0;
|
||||
let desired = f64::from(fin.desired).abs();
|
||||
if desired < lo || desired > hi {
|
||||
return violated(format!(
|
||||
"stream at ts_ms={}: |desired|={desired:.2} outside [{lo:.2}, {hi:.2}] \
|
||||
for {} events",
|
||||
fin.ts_ms, fin.events_total
|
||||
));
|
||||
}
|
||||
let got = delivered(fin).abs() as f64;
|
||||
if got > hi {
|
||||
return violated(format!(
|
||||
"stream at ts_ms={}: delivered {got} lines > bound {hi:.2} for {} events",
|
||||
fin.ts_ms, fin.events_total
|
||||
));
|
||||
}
|
||||
}
|
||||
InvariantResult::Pass
|
||||
}
|
||||
|
||||
fn check_accel(groups: &[StreamGroup<'_>]) -> InvariantResult {
|
||||
for rec in records(groups) {
|
||||
let accel = f64::from(rec.accel);
|
||||
if !(1.0 - F32_TOLERANCE..=TRACKPAD_ACCEL_MAX + F32_TOLERANCE).contains(&accel) {
|
||||
return violated(format!(
|
||||
"accel {accel} outside [1.0, {TRACKPAD_ACCEL_MAX}] at ts_ms={}",
|
||||
rec.ts_ms
|
||||
));
|
||||
}
|
||||
if let Some(avg) = rec.avg_interval_ms
|
||||
&& avg < ACCEL_MIN_INTERVAL_MS - F32_TOLERANCE
|
||||
{
|
||||
return violated(format!(
|
||||
"avg_interval_ms {avg} < 6 at ts_ms={} — sub-6ms batching artifacts \
|
||||
(ghostty dups) leaked into the interval window",
|
||||
rec.ts_ms
|
||||
));
|
||||
}
|
||||
}
|
||||
InvariantResult::Pass
|
||||
}
|
||||
|
||||
fn check_carry(groups: &[StreamGroup<'_>]) -> InvariantResult {
|
||||
for rec in records(groups) {
|
||||
if f64::from(rec.carry).abs() >= 1.0 {
|
||||
return violated(format!(
|
||||
"|carry| {} ≥ 1.0 at ts_ms={} — whole lines leaked across streams",
|
||||
rec.carry, rec.ts_ms
|
||||
));
|
||||
}
|
||||
}
|
||||
for pair in groups.windows(2) {
|
||||
let Some(fin) = pair[0].finalize else {
|
||||
continue;
|
||||
};
|
||||
if fin.kind == "wheel" && f64::from(pair[1].start.carry).abs() > F32_TOLERANCE {
|
||||
return violated(format!(
|
||||
"stream_start at ts_ms={} carries {} after a wheel finalize (must be 0)",
|
||||
pair[1].start.ts_ms, pair[1].start.carry
|
||||
));
|
||||
}
|
||||
}
|
||||
InvariantResult::Pass
|
||||
}
|
||||
|
||||
fn check_cfg(expected: &ExpectedProfile, groups: &[StreamGroup<'_>]) -> InvariantResult {
|
||||
for group in groups {
|
||||
let start = group.start;
|
||||
let echo = (
|
||||
start.mode.as_deref(),
|
||||
start.ept,
|
||||
start.wheel_lpt,
|
||||
start.trackpad_lpt,
|
||||
start.invert,
|
||||
);
|
||||
let want = (
|
||||
Some(expected.mode),
|
||||
Some(expected.ept),
|
||||
Some(expected.wheel_lpt),
|
||||
Some(expected.trackpad_lpt),
|
||||
Some(expected.invert),
|
||||
);
|
||||
let speed_ok = start
|
||||
.speed
|
||||
.is_some_and(|s| (f64::from(s) - f64::from(expected.speed)).abs() <= F32_TOLERANCE);
|
||||
if echo != want || !speed_ok {
|
||||
return violated(format!(
|
||||
"config echo at ts_ms={} is {:?} speed={:?}, cell expects \
|
||||
mode={} ept={} wheel_lpt={} trackpad_lpt={} invert={} speed={}",
|
||||
start.ts_ms,
|
||||
echo,
|
||||
start.speed,
|
||||
expected.mode,
|
||||
expected.ept,
|
||||
expected.wheel_lpt,
|
||||
expected.trackpad_lpt,
|
||||
expected.invert,
|
||||
expected.speed,
|
||||
));
|
||||
}
|
||||
}
|
||||
InvariantResult::Pass
|
||||
}
|
||||
|
||||
fn check_mux_no_over(expected: &ExpectedProfile, groups: &[StreamGroup<'_>]) -> InvariantResult {
|
||||
for group in groups {
|
||||
let Some(fin) = group.finalize else { continue };
|
||||
let bound = fin.events_total as f64 * f64::from(expected.speed) + 1.0;
|
||||
let got = delivered(fin).abs() as f64;
|
||||
if got > bound {
|
||||
return violated(format!(
|
||||
"remuxed stream at ts_ms={}: delivered {got} lines for {} events \
|
||||
(> {bound:.2} — over-scroll on the conservative mux profile)",
|
||||
fin.ts_ms, fin.events_total
|
||||
));
|
||||
}
|
||||
}
|
||||
InvariantResult::Pass
|
||||
}
|
||||
|
||||
fn check_smooth_coast(groups: &[StreamGroup<'_>]) -> InvariantResult {
|
||||
for group in groups {
|
||||
let coast: i64 = group
|
||||
.flush_bearing()
|
||||
.filter(|rec| rec.events_since_flush == 0)
|
||||
.map(|rec| rec.flushed.abs())
|
||||
.sum();
|
||||
let cap = group
|
||||
.flush_bearing()
|
||||
.map(|rec| rec.cap)
|
||||
.max()
|
||||
.unwrap_or(i64::MAX);
|
||||
if coast > cap {
|
||||
return violated(format!(
|
||||
"stream at ts_ms={}: {coast} coast lines (flushes with no new events) \
|
||||
> cap {cap} — post-input motion beyond one capped catch-up (the jerk)",
|
||||
group.start.ts_ms
|
||||
));
|
||||
}
|
||||
}
|
||||
InvariantResult::Pass
|
||||
}
|
||||
|
||||
fn check_no_drop(groups: &[StreamGroup<'_>]) -> InvariantResult {
|
||||
for group in groups {
|
||||
let Some(fin) = group.finalize else { continue };
|
||||
let dropped = fin.dropped.unwrap_or(0);
|
||||
if dropped != 0 {
|
||||
return violated(format!(
|
||||
"finalize at ts_ms={} dropped {dropped} lines",
|
||||
fin.ts_ms
|
||||
));
|
||||
}
|
||||
}
|
||||
InvariantResult::Pass
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::log::{group_streams, parse_jsonl_str};
|
||||
use super::*;
|
||||
|
||||
// ── JSONL fixture builders ─────────────────────────────────────────
|
||||
// Raw strings through parse_jsonl_str so every fixture also exercises
|
||||
// the wire schema (same stance as log.rs's producer-shaped constants).
|
||||
|
||||
const C1: ExpectedProfile = ExpectedProfile {
|
||||
mode: "auto",
|
||||
ept: 3,
|
||||
wheel_lpt: 3,
|
||||
trackpad_lpt: 3,
|
||||
invert: false,
|
||||
speed: 1.0,
|
||||
};
|
||||
const C1_WHEEL: ExpectedProfile = ExpectedProfile {
|
||||
mode: "wheel",
|
||||
..C1
|
||||
};
|
||||
|
||||
// Floats are formatted with `{:?}` so whole values keep their decimal
|
||||
// point (`32.0`, not `32`) — the mutation `.replace()`s below match on
|
||||
// that spelling, and it mirrors serde_json's f64 output for round
|
||||
// numbers.
|
||||
fn start(ts: f64, carry: f32, mode: &str, speed: f32) -> String {
|
||||
format!(
|
||||
r#"{{"ts_ms":{ts:?},"evt":"stream_start","trigger":"event","kind":"unknown","events_total":0,"events_since_flush":0,"accel":1.0,"desired":0.0,"applied_total":0,"flushed":0,"backlog_after":0,"carry":{carry:?},"cap":25,"mode":"{mode}","ept":3,"wheel_lpt":3,"trackpad_lpt":3,"invert":false,"speed":{speed:?},"viewport_height":50}}"#
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn row(
|
||||
evt: &str,
|
||||
ts: f64,
|
||||
trigger: &str,
|
||||
kind: &str,
|
||||
events: (u64, u64),
|
||||
accel_avg: (f32, Option<f64>),
|
||||
lines: (f32, i64, i64, i64), // desired, applied_total, flushed, backlog_after
|
||||
msf: Option<f64>,
|
||||
dropped: Option<i64>,
|
||||
) -> String {
|
||||
let avg = accel_avg
|
||||
.1
|
||||
.map_or(String::new(), |v| format!(r#""avg_interval_ms":{v:?},"#));
|
||||
let msf = msf.map_or(String::new(), |v| {
|
||||
format!(r#","ms_since_prev_flush":{v:?}"#)
|
||||
});
|
||||
let dropped = dropped.map_or(String::new(), |v| format!(r#","dropped":{v}"#));
|
||||
format!(
|
||||
r#"{{"ts_ms":{ts:?},"evt":"{evt}","trigger":"{trigger}","kind":"{kind}","events_total":{},"events_since_flush":{},{avg}"accel":{:?},"desired":{:?},"applied_total":{},"flushed":{},"backlog_after":{},"carry":0.0,"cap":25{msf}{dropped}}}"#,
|
||||
events.0, events.1, accel_avg.0, lines.0, lines.1, lines.2, lines.3,
|
||||
)
|
||||
}
|
||||
|
||||
fn check(id: InvariantId, expected: &ExpectedProfile, jsonl: &[String]) -> InvariantResult {
|
||||
let raw = jsonl.join("\n");
|
||||
let records = parse_jsonl_str(&raw).expect("fixture must parse");
|
||||
let groups = group_streams(&records).expect("fixture must group");
|
||||
check_log_invariant(id, expected, &groups)
|
||||
}
|
||||
|
||||
fn assert_violated(result: InvariantResult, needle: &str) {
|
||||
match result {
|
||||
InvariantResult::Violated { detail } => {
|
||||
assert!(
|
||||
detail.contains(needle),
|
||||
"detail {detail:?} lacks {needle:?}"
|
||||
);
|
||||
}
|
||||
InvariantResult::Pass => panic!("expected violation mentioning {needle:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Canonical clean capture: a trackpad stream (sub-line carry out), a
|
||||
/// wheel stream (carry zeroed), a third stream echoing that zero.
|
||||
/// One record per line (rustfmt::skip) — reads like the JSONL it builds.
|
||||
#[rustfmt::skip]
|
||||
fn canonical() -> Vec<String> {
|
||||
vec![
|
||||
start(0.0, 0.0, "auto", 1.0),
|
||||
row("flush", 16.0, "event", "trackpad", (6, 6), (1.0, Some(10.0)), (6.4, 6, 6, 0), None, None),
|
||||
row("flush", 32.0, "tick", "trackpad", (9, 3), (1.0, Some(10.0)), (9.4, 9, 3, 0), Some(16.0), None),
|
||||
row("finalize", 120.0, "finalize", "trackpad", (9, 0), (1.0, Some(10.0)), (9.4, 9, 0, 0), Some(88.0), Some(0)),
|
||||
start(500.0, 0.4, "auto", 1.0),
|
||||
row("flush", 505.0, "promotion", "wheel", (3, 3), (1.0, None), (3.0, 3, 3, 0), Some(385.0), None),
|
||||
row("finalize", 600.0, "finalize", "wheel", (3, 0), (1.0, None), (3.0, 3, 0, 0), Some(95.0), Some(0)),
|
||||
start(700.0, 0.0, "auto", 1.0),
|
||||
row("flush", 716.0, "event", "unknown", (6, 6), (1.0, Some(8.0)), (6.2, 6, 6, 0), Some(116.0), None),
|
||||
row("finalize", 800.0, "finalize", "trackpad", (6, 0), (1.0, Some(8.0)), (6.2, 6, 0, 0), Some(84.0), Some(0)),
|
||||
]
|
||||
}
|
||||
|
||||
/// The real G4 signature: coast drain + finalize re-price burst with a
|
||||
/// drop — mid-stream priced as unknown (~1 line/event), re-priced ×2.5
|
||||
/// at the trackpad finalize, backlog beyond one capped flush discarded.
|
||||
#[rustfmt::skip]
|
||||
fn jerk() -> Vec<String> {
|
||||
vec![
|
||||
start(0.0, 0.0, "auto", 1.0),
|
||||
row("flush", 16.0, "event", "unknown", (40, 40), (2.5, Some(6.0)), (40.0, 25, 25, 15), None, None),
|
||||
row("flush", 32.0, "tick", "unknown", (66, 26), (2.5, Some(6.0)), (66.0, 50, 25, 16), Some(16.0), None),
|
||||
row("flush", 48.0, "tick", "unknown", (66, 0), (2.5, Some(6.0)), (66.0, 66, 16, 0), Some(16.0), None),
|
||||
row("finalize", 146.0, "finalize", "trackpad", (66, 0), (2.5, Some(6.0)), (165.0, 91, 25, 74), Some(98.0), Some(74)),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_log_side_invariant_passes_on_the_canonical_capture() {
|
||||
// All log-side ids except I-CONS-W, which requires a forced-wheel
|
||||
// profile and has its own pass fixture.
|
||||
let fixture = canonical();
|
||||
for id in [
|
||||
InvariantId::Ord,
|
||||
InvariantId::Cap,
|
||||
InvariantId::DropEq,
|
||||
InvariantId::Cadence,
|
||||
InvariantId::ConsA,
|
||||
InvariantId::Accel,
|
||||
InvariantId::Carry,
|
||||
InvariantId::Cfg,
|
||||
InvariantId::MuxNoOver,
|
||||
InvariantId::SmoothCoast,
|
||||
InvariantId::NoDrop,
|
||||
] {
|
||||
let result = check(id, &C1, &fixture);
|
||||
assert!(result.is_pass(), "{} on canonical: {result:?}", id.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ord_rejects_backwards_timestamps() {
|
||||
let mut fixture = canonical();
|
||||
fixture[2] = fixture[2].replace(r#""ts_ms":32.0"#, r#""ts_ms":12.0"#);
|
||||
assert_violated(check(InvariantId::Ord, &C1, &fixture), "backwards");
|
||||
}
|
||||
|
||||
/// A4 teleport: one flush delivering more than the per-flush cap.
|
||||
#[test]
|
||||
fn cap_rejects_over_cap_flush() {
|
||||
let mut fixture = canonical();
|
||||
fixture[1] = fixture[1].replace(r#""flushed":6"#, r#""flushed":40"#);
|
||||
assert_violated(check(InvariantId::Cap, &C1, &fixture), "exceeds cap");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_eq_rejects_mismatched_finalize_accounting() {
|
||||
let mut fixture = canonical();
|
||||
fixture[3] = fixture[3].replace(r#""backlog_after":0,"#, r#""backlog_after":9,"#);
|
||||
fixture[3] = fixture[3].replace(r#""dropped":0"#, r#""dropped":5"#);
|
||||
assert_violated(check(InvariantId::DropEq, &C1, &fixture), "dropped=5");
|
||||
}
|
||||
|
||||
/// A2 busy-spin: an 8ms tick-flush spacing violates; the same spacing on
|
||||
/// a promotion-triggered record is skipped (promotion flushes bypass the
|
||||
/// cadence gate by design), as are finalize records (flip finalizes).
|
||||
#[test]
|
||||
fn cadence_rejects_sub_16ms_spacing_but_skips_promotion_and_finalize() {
|
||||
let mut fixture = canonical();
|
||||
fixture[2] = fixture[2].replace(
|
||||
r#""ms_since_prev_flush":16.0"#,
|
||||
r#""ms_since_prev_flush":8.0"#,
|
||||
);
|
||||
assert_violated(check(InvariantId::Cadence, &C1, &fixture), "8ms");
|
||||
|
||||
let mut skipped = canonical();
|
||||
skipped[2] = skipped[2]
|
||||
.replace(r#""trigger":"tick""#, r#""trigger":"promotion""#)
|
||||
.replace(
|
||||
r#""ms_since_prev_flush":16.0"#,
|
||||
r#""ms_since_prev_flush":8.0"#,
|
||||
);
|
||||
// Flip-style finalize 8ms after the previous flush: also skipped.
|
||||
skipped[3] = skipped[3].replace(
|
||||
r#""ms_since_prev_flush":88.0"#,
|
||||
r#""ms_since_prev_flush":8.0"#,
|
||||
);
|
||||
assert!(check(InvariantId::Cadence, &C1, &skipped).is_pass());
|
||||
}
|
||||
|
||||
/// A3 under-travel: a forced-wheel stream delivering two lines short.
|
||||
#[test]
|
||||
fn cons_w_exact_totals_with_min_lines_substitution() {
|
||||
// 6 events × (3/3) × 1.0 = 6 lines, delivered exactly; a second
|
||||
// 1-event stream prices to 1.0 → still ≥ the MIN_LINES floor.
|
||||
#[rustfmt::skip]
|
||||
let pass = vec![
|
||||
start(0.0, 0.0, "wheel", 1.0),
|
||||
row("flush", 16.0, "event", "wheel", (6, 6), (1.0, None), (6.0, 6, 6, 0), None, None),
|
||||
row("finalize", 100.0, "finalize", "wheel", (6, 0), (1.0, None), (6.0, 6, 0, 0), Some(84.0), Some(0)),
|
||||
start(300.0, 0.0, "wheel", 1.0),
|
||||
row("finalize", 400.0, "finalize", "wheel", (1, 1), (1.0, None), (1.0, 1, 1, 0), Some(300.0), Some(0)),
|
||||
];
|
||||
assert!(check(InvariantId::ConsW, &C1_WHEEL, &pass).is_pass());
|
||||
|
||||
let mut short = pass.clone();
|
||||
short[1] = short[1].replace(
|
||||
r#""applied_total":6,"flushed":6"#,
|
||||
r#""applied_total":4,"flushed":4"#,
|
||||
);
|
||||
short[2] = short[2].replace(r#""applied_total":6"#, r#""applied_total":4"#);
|
||||
assert_violated(check(InvariantId::ConsW, &C1_WHEEL, &short), "expected 6±1");
|
||||
|
||||
// Attached to a non-wheel cell = cell-table bug, not a pass.
|
||||
assert_violated(check(InvariantId::ConsW, &C1, &pass), "needs forced wheel");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cons_a_bounds_auto_totals() {
|
||||
// 3 events on the C1 profile can never desire 100 lines
|
||||
// (hi = 3 × max(1, 3×1) × 1 + 1 = 10).
|
||||
let mut fixture = canonical();
|
||||
fixture[6] = fixture[6].replace(r#""desired":3.0"#, r#""desired":100.0"#);
|
||||
assert_violated(check(InvariantId::ConsA, &C1, &fixture), "outside");
|
||||
assert_violated(
|
||||
check(InvariantId::ConsA, &C1_WHEEL, &canonical()),
|
||||
"use I-CONS-W",
|
||||
);
|
||||
}
|
||||
|
||||
/// G5 clause: a sub-6ms average means ghostty-style duplicate reports
|
||||
/// leaked into the interval window; out-of-range accel is the same
|
||||
/// regression on the multiplier side.
|
||||
#[test]
|
||||
fn accel_rejects_out_of_band_multiplier_and_sub_6ms_average() {
|
||||
let mut fixture = canonical();
|
||||
fixture[1] = fixture[1].replace(r#""accel":1.0"#, r#""accel":4.5"#);
|
||||
assert_violated(check(InvariantId::Accel, &C1, &fixture), "accel 4.5");
|
||||
|
||||
let mut dup = canonical();
|
||||
dup[1] = dup[1].replace(r#""avg_interval_ms":10.0"#, r#""avg_interval_ms":3.0"#);
|
||||
assert_violated(check(InvariantId::Accel, &C1, &dup), "interval window");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn carry_rejects_whole_line_carry_and_nonzero_start_after_wheel_finalize() {
|
||||
let mut fixture = canonical();
|
||||
fixture[4] = start(500.0, 1.4, "auto", 1.0);
|
||||
assert_violated(check(InvariantId::Carry, &C1, &fixture), "≥ 1.0");
|
||||
|
||||
// Wheel finalize (stream 2) must zero the carry into stream 3.
|
||||
let mut leak = canonical();
|
||||
leak[7] = start(700.0, 0.4, "auto", 1.0);
|
||||
assert_violated(check(InvariantId::Carry, &C1, &leak), "wheel finalize");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cfg_rejects_echo_profile_mismatch() {
|
||||
assert!(check(InvariantId::Cfg, &C1, &canonical()).is_pass());
|
||||
let mut fixture = canonical();
|
||||
fixture[0] = start(0.0, 0.0, "auto", 6.0); // speed echo ≠ expected 1.0
|
||||
assert_violated(check(InvariantId::Cfg, &C1, &fixture), "speed");
|
||||
let expected_wheel = ExpectedProfile {
|
||||
mode: "wheel",
|
||||
..C1
|
||||
};
|
||||
assert_violated(
|
||||
check(InvariantId::Cfg, &expected_wheel, &canonical()),
|
||||
"mode",
|
||||
);
|
||||
}
|
||||
|
||||
/// A4-class over-scroll on the conservative remuxed profile: more
|
||||
/// delivered lines than events at speed 1.0.
|
||||
#[test]
|
||||
fn mux_no_over_rejects_over_delivery() {
|
||||
let mut fixture = canonical();
|
||||
fixture[3] = fixture[3].replace(r#""applied_total":9"#, r#""applied_total":20"#);
|
||||
// Keep DropEq-independent: only this invariant is under test.
|
||||
assert_violated(check(InvariantId::MuxNoOver, &C1, &fixture), "over-scroll");
|
||||
}
|
||||
|
||||
/// The jerk fixture violates exactly the two xfail invariants of
|
||||
/// `c1_auto_g4_jerk_xfail` — and nothing else in the core suite, which
|
||||
/// is what confines the expected failure to those rows.
|
||||
#[test]
|
||||
fn jerk_shape_violates_smooth_coast_and_no_drop_only() {
|
||||
let fixture = jerk();
|
||||
assert_violated(check(InvariantId::SmoothCoast, &C1, &fixture), "coast");
|
||||
assert_violated(check(InvariantId::NoDrop, &C1, &fixture), "dropped 74");
|
||||
for id in [
|
||||
InvariantId::Ord,
|
||||
InvariantId::Cap,
|
||||
InvariantId::DropEq,
|
||||
InvariantId::Cadence,
|
||||
InvariantId::ConsA,
|
||||
InvariantId::Accel,
|
||||
InvariantId::Carry,
|
||||
InvariantId::Cfg,
|
||||
] {
|
||||
let result = check(id, &C1, &fixture);
|
||||
assert!(result.is_pass(), "{} on jerk: {result:?}", id.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
/// One capped catch-up flush after input stops is legitimate (that is
|
||||
/// the finalize contract) — coast ≤ cap passes.
|
||||
#[test]
|
||||
fn smooth_coast_allows_a_single_capped_catchup() {
|
||||
#[rustfmt::skip]
|
||||
let fixture = vec![
|
||||
start(0.0, 0.0, "auto", 1.0),
|
||||
row("flush", 16.0, "event", "unknown", (30, 30), (1.0, Some(6.0)), (30.0, 25, 25, 5), None, None),
|
||||
row("finalize", 100.0, "finalize", "trackpad", (30, 0), (1.0, Some(6.0)), (30.0, 30, 5, 0), Some(84.0), Some(0)),
|
||||
];
|
||||
assert!(check(InvariantId::SmoothCoast, &C1, &fixture).is_pass());
|
||||
assert!(check(InvariantId::NoDrop, &C1, &fixture).is_pass());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "harness-side")]
|
||||
fn harness_side_ids_panic_in_the_log_checker() {
|
||||
let _ = check_log_invariant(InvariantId::Screen, &C1, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn log_side_partition_matches_the_a13_split() {
|
||||
for id in [InvariantId::Screen, InvariantId::Quiet] {
|
||||
assert!(!id.is_log_side(), "{}", id.as_str());
|
||||
}
|
||||
for id in [
|
||||
InvariantId::Ord,
|
||||
InvariantId::Cap,
|
||||
InvariantId::DropEq,
|
||||
InvariantId::Cadence,
|
||||
InvariantId::ConsW,
|
||||
InvariantId::ConsA,
|
||||
InvariantId::Accel,
|
||||
InvariantId::Carry,
|
||||
InvariantId::Cfg,
|
||||
InvariantId::MuxNoOver,
|
||||
InvariantId::SmoothCoast,
|
||||
InvariantId::NoDrop,
|
||||
] {
|
||||
assert!(id.is_log_side(), "{}", id.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user