Skip to content

Introduce Spectral Sentinel — an online subspace anomaly detector - #827

Open
da2ce7 wants to merge 59 commits into
torrust:developfrom
da2ce7:20260219_sentinel
Open

da2ce7 wants to merge 59 commits into
torrust:developfrom
da2ce7:20260219_sentinel

Conversation

@da2ce7

@da2ce7 da2ce7 commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

A sentinel is not a judge. It stands watch, keeps its bearings, and reports what has changed.

That is the idea behind Spectral Sentinel. It does not decide whether a pattern is dangerous, important, or actionable. It measures structure in a stream, learns what has been ordinary so far, and returns statistical readouts when new observations depart from the learned geometry.

The "spectral" part is literal: each selected region is modelled with low-rank subspace trackers, and a second tier models the spectrum of those scores across related cells. The "sentinel" part is restraint: the crate observes, scores, and reports, but policy stays with the host.


Summary

This PR adds torrust-sentinel to the workspace: a library crate for hierarchical online subspace anomaly detection over positionally structured observation streams. It is rebuilt as a single commit on the current develop (after #884), so it sits on the MSRV-1.89 floor of ADR-T-011 and follows the per-crate versioning of ADR-T-012.

It is built on top of Mudlark. Mudlark provides the adaptive spatial substrate — regions that receive more observation volume earn finer resolution, quiet regions remain coarse. Spectral Sentinel uses that structure to decide where statistical trackers are worth maintaining. It selects significant V-Tree entries, closes them under G-tree ancestry so every selected cell has a complete ancestor chain back to the root, and scores incoming batches against learned subspace models at every selected scale.

The simplest way to think about it: Mudlark decides where the stream has shape; Sentinel measures whether the recent shape still looks like what that region has learned to expect.

The crate is deliberately policy-free. Reports carry raw measurements — four scoring axes (novelty, displacement, surprise, coherence), maturity, baselines, CUSUM drift accumulators, geometry, contour summaries, and health snapshots. They do not encode threat levels, recommended actions, or decisions. The host reads the measurements and decides what they mean.

The core invariant is feed-forward: every input value updates Mudlark with exactly one unit of observation volume. Anomaly scores never flow back into the spatial index. That keeps spatial adaptation driven by traffic structure, not by the detector's own conclusions. Temporal policy is host-controlled: Sentinel never applies decay automatically.

A second analysis tier — coordination trackers — runs at internal G-tree nodes whose subtrees both contribute competitive cells. It scores cross-cell patterns of the four axes, so a coordinated shift that no single cell would flag still surfaces in the report.

Contents

The addition is substantial, but almost entirely self-contained within packages/sentinel.

  • A new torrust-sentinel crate (1.0.0) with a narrow public surface exposed through flat crate-root re-exports
  • SpectralSentinel<C, V, N> as the generic engine, with Sentinel128 and Sentinel64 aliases for the common domain widths
  • SentinelConfig, NoiseSchedule, and SvdStrategy for host-controlled measurement parameters, with structured ConfigError / ConfigErrors / ConfigWarning validation rather than panics
  • Public readout types for batch, cell, coordination, contour, health, maturity, geometry, baseline, score, and analysis-set summaries — ordered deterministically by GNodeId
  • Analysis-set selection over Mudlark's G-V Graph: top-K competitive V-entries plus ancestor closure into the investment set
  • Per-cell subspace trackers scoring novelty, displacement, surprise, and coherence; EWMA baselines with upper-tail clipping; CUSUM drift accumulators with a separate slow EWMA
  • Hierarchical coordination trackers scoring cross-cell score patterns at G-tree internal nodes
  • Automatic synthetic noise warm-up for every newly created tracker, plus a deferred staging area and optional background warming thread so cell creation does not block the ingest hot path
  • Brand incremental SVD for subspace evolution, with a naive thin-SVD path used both as a fallback for small/numerically sensitive cases and as a debug-mode oracle
  • Optional serde support (off by default; pulls in torrust-mudlark/serde)
  • A Criterion benchmark suite (15 bench functions across 8 groups: encoding, ingest, auxiliary, convergence, scaling, temporal, analysis)
  • 21 architecture decision records covering measurement-not-opinions, the feed-forward invariant, Mudlark integration, deterministic ordering, analysis-set recomputation, automatic noise injection, scoring geometry, decay semantics, routing, degenerate-dimension guards, test budgets, warm-up convergence, visibility, cell-creation performance, Brand SVD, deferred warm-up, generic domain parameters, investment-set terminology, and the clip-pressure / mean-centred-variance EWMA refinements
  • README, public API reference, algorithm document, and implementation notes (~4.7k lines of crate-level documentation total)
  • 642 tests with all features enabled (631 in the default configuration) across crate-level tests (src/tests/), integration tests (tests/) and doc-tests, with the README compiled as a doc-test via #[cfg(doctest)] include_str!
  • Two pedagogy integration tests (pedagogy.rs, pedagogy_advanced.rs) written to be read end-to-end as a walkthrough of the public surface

Changes outside sentinel

  • Cargo.tomlpackages/sentinel added as a workspace member
  • packages/mudlark1.0.11.1.0: monotonic structural mutation counters (splits, evictions, restorations) recorded at the sites that already maintain the node and terminal counts, and a semi_internal_count() accessor; both additive, with a changelog entry
  • Cargo.lock — 69 entries added for the dependency closure (faer, rand_distr, plus dev-only criterion and tracing-subscriber); the only moved entry is torrust-mudlark following its bump
  • AGENTS.md — adds Sentinel's S- cross-reference prefix to the package table and ADR examples

Manifest under ADR-T-012

The dependency on the sibling torrust-mudlark pins version = "1.1.0" beside its path, because cargo publish writes that requirement into the published manifest and the report reads the structural mutation counters that arrive with that minor. faer, tracing and criterion name the 0.x line the sources are written against (0.24, 0.1, 0.8) instead of a bare 0, for the reason #884 gave for the root's requirements. cargo publish --dry-run -p torrust-sentinel stops at resolution because torrust-mudlark is not on crates.io yet; that is the publication order ADR-T-012 documents, and torrust-mudlark itself dry-runs cleanly.

Reviewing this

The best starting point is the public surface:

packages/sentinel/src/lib.rspackages/sentinel/docs/api.mdpackages/sentinel/README.md

From there, the main implementation path is src/sentinel/mod.rs for the orchestrator, src/analysis_set.rs for competitive selection and ancestor closure, src/sentinel/tracker.rs for per-cell scoring, src/sentinel/{cusum,staging,warming_thread}.rs for drift and warm-up, and src/maths/ for the SVD plumbing.

For a focused review, I would look at:

  • the public API shape and the flat crate-root re-exports
  • configuration validation, defaults, and the structured error/warning types
  • the feed-forward Mudlark integration (Δ = 1 per observation, scores never feed back)
  • report semantics, deterministic ordering, and what is and isn't part of the public surface
  • tracker warm-up, the deferred staging area, and the optional background warming thread
  • the Brand SVD fallback boundary (small d, narrow rank gaps) and the debug-mode oracle path
  • integration tests that assert invariants through the public API only

The pedagogy tests are intended to be readable end-to-end; running cargo test -p torrust-sentinel --test pedagogy -- --nocapture produces a narrated walk through the public surface.

Verification

On the rebuilt commit: cargo fmt --check clean; cargo clippy --workspace --all-targets --all-features -- -D warnings clean under the workspace lint table; the crate's 565 tests and 15 doc-tests pass; the whole workspace passes (2,370 tests, none failed); cargo audit keeps the vulnerability count of develop (the one rsa advisory with no fixed release) and adds a single allowed unmaintained-crate warning, RUSTSEC-2024-0436 (paste, a proc-macro pulled by faer through gemm).

Notes

  • Ships at 1.0.0: the public surface documented in docs/api.md is covered by semver guarantees from this release onwards. A sibling crate consumes it through a version-beside-path pin, the same discipline this manifest applies to torrust-mudlark, so the version a consumer pins is the one the manifest declares.
  • MSRV 1.89, inherited from the workspace (ADR-T-011).
  • No unsafe code; #![forbid(unsafe_code)] at the crate root.
  • AGPL-3.0-only, inherited from the workspace. Unlike Mudlark, no linking exception is shipped with this crate.
  • Default features: none. serde is opt-in.
  • The crate measures only. Interpretation and response remain external host policy.
  • Temporal policy is host-controlled: Sentinel never applies decay automatically.
  • Configuration prefers structured errors over panics.
  • Sentinel docs and ADRs use the S- cross-reference prefix added in this PR.

Review fixes

Since the previous head, nine commits on top of the three original ones fix every finding an automated review of the package produced and a code-level verification confirmed: the configuration validation refuses non-numbers, an unrepresentable depth-buffer headroom and a coordinate width below the tracker minimum (two additive error variants); the full-width cell owns the domain maximum and the root leaves the analysis candidates before the capacity cut; staged cells warm by their real volume and a pass with no competitive scores retires every coordination context; health and batch reports count the online sets, populate the semi-internal count from the graph (one additive mudlark accessor, and the headroom arithmetic in mudlark saturates instead of wrapping), count the whole contour and order coordination reports by depth then identifier; the geometric schedule reports its true maximum, a failed corrective factorisation reports failure so the dispatcher falls back, and the unread round scores are gone. Prose follows the code (the z-score denominator, the live-tracker figure, the open bit-source trait, the implemented dimension guard), and the one exact float equality in the invariants suite compares bit patterns. Nothing on the public surface is removed or reshaped.

Second round of review fixes

Five further commits fix every finding of a second automated review at the previous head. The warming thread's shutdown transition is made under the staging lock its wait is paired with, so a shutdown can no longer be lost between the worker reading its predicate and sleeping on it, which left the join — reached from Drop — waiting forever; the u128 centred-bit conversion caps the requested width at the type's own instead of indexing past its backing array; the centred bit vector gains a validated constructor and a length accessor so an implementation of the open bridge trait outside the crate can return the value its impl must produce; the online summary reports the investment count over the whole selection, warming cells included, as its contract states. The test support's four-bit generator refuses a nibble at sixteen or above (which shifted every set bit out of the coordinate and aliased the range sixteen below), a six-bit generator carries the sprays that claim sixty-four distinct ranges, and the ordering, budget and concentration witnesses assert the documented order, the structure's own budget and a report below the root. The upper bounds of analysis entries and coordination contexts document the top-of-domain exception, the analysis set's full field is named as the investment set it is, the thread-safety plan states the Send + Sync the crate asserts statically, and section-mark references with no referent leave the record and the test banners. Public surface: three additive constant functions on the centred bit vector; nothing is removed or reshaped. Verified on stable 1.98 (the toolchain this repository lints and tests with), 1.89.0 and nightly.

Third round of review fixes

Four further commits fix every finding of a third automated review at the previous head. The headroom a depth pair demands was computed with one checked step and three unchecked ones around it, so a creation depth of zero beside an eviction depth at the top of the range overflowed inside the very method that promises to hand back its faults as values; the computation now lives in a helper whose every step is checked, and any overflow reports the existing structured error for a buffer too large to honour, with the widest pair a budget can clear pinned as accepted. The centred-bit vector holds at most 128 values, but the coordinate trait it is fed from is open to wider types and the only width guard compared the tracker's dimension with the coordinate's declared bits, so a 200-wide tracker over a 256-bit coordinate was admitted and fed from a 128-slot vector; the sentinel now refuses a width above the vector's ceiling with a configuration error naming the width and the maximum, and the ceiling is documented on the bit source, on the bit vector and in the crate docs. The prefix generators in the test support guarded their ranges with assertions that release builds compile out; all three sites assert unconditionally. The dimension guard's doc said widths at or below the minimum are refused where the predicate refuses only widths below it, and now names the side of the boundary that is kept; a bare section ordinal in the exponential-average module is replaced by the sense it carried. Public surface: one additive configuration-error variant; nothing is removed or reshaped. Verified on stable 1.98 (the toolchain this repository lints and tests with), 1.89.0 and nightly.

Fourth round of review fixes

Three further commits correct every finding of a fourth automated review at the previous head; all eight are documentation, and no Rust moves. The two warming modes draw from two different generators: synchronous warming drains from the sentinel's own generator, while background warming draws from a second one seeded on the worker and promotes whatever the worker has finished at each ingest, against a map the main thread is concurrently writing. Four sites promised bit-for-bit reproducibility across runs without saying which mode delivers it; each now scopes the claim to synchronous warming on a fixed build, in one identical clause, and names the background-mode interleaving as the second source of randomness that reaches the scores. Three lifecycle records described mechanisms that no longer run where they said: the cell-width rejection lives in the suffix-width filter applied at runtime rather than in configuration validation, and its effective range is stated; creation schedules noise injection rather than performing it, now that the injection itself is deferred; and the bounded per-ingest work of the deferred warm-up record is stated for the background mode it holds in, with the default mode's in-line drain named beside it. The dimension guard's record said cells at or below the minimum are excluded where the filter keeps a cell at it, and now carries the same words as the constant's own documentation. Verified on stable 1.98 and nightly, with the crate's rustdoc and doc tests, since the README is the crate's front-page documentation.

Fifth round of review fixes

Three further commits correct every finding of a fifth automated review at the previous head. Construction asked the operating system for the background warming thread and aborted the host when the request was refused, over a resource limit that has nothing to do with the configuration's correctness; the request now returns the environment's own account as a configuration error naming the setting, and it arrives alone because the thread is asked for only once validation has passed. Reset, which has no error channel, keeps the sentinel running and warms cells synchronously instead, recording the refusal as a warning: the warm-up dispatch keys on whether a thread is present rather than on the flag, so the fallback is complete and every report is produced as before. Seeding one baseline from another copied the numbers but only ever raised warmth, so a receiver seeded from a cold source stayed warm over placeholder statistics and the cold path that replaces them never ran again; warmth is now part of what is handed over, in both directions, with a witness that fails at the previous head. Three tests and their prose claimed more, or other, than the engine guarantees: the determinism test compared three lengths and a few means where it now compares whole reports figure by figure with equal bit patterns; the coordination-report ordering test asserted ascending handle where the producer sorts by depth and then handle, and both prose statements of the handle-only order are corrected with it; and the reproducibility claim at the top of the determinism suite is scoped to synchronous warming on a fixed build, which is the configuration those tests share. Public surface: one additive configuration-error variant, and the configuration-error enumeration is marked non-exhaustive ahead of first publication so a later refusal is additive too; the warming-thread handle whose signature changed is crate-internal. Verified on stable 1.98 (the whole workspace lints clean; the crate's tests pass), 1.89.0 and nightly, with the crate's rustdoc and doc tests.

Later rounds of review fixes

The remaining rounds of automated review, each verified at code level before a change was made, are answered by the commits after the fifth round. The CUSUM allowance now follows the algorithm text, κσ·√v_slow, with the denominator-protection constant kept out of it; the geometric noise schedule saturates an unrepresentable exponent instead of wrapping it, so a public caller with an arbitrarily deep argument still lands on the floor; coordination contexts are retained by online competitive membership rather than by which cells happened to score in the batch, so a quiet batch no longer destroys a context that the next joint batch would have to re-warm. Every section reference in source and tests uses the qualified §ALGO S-N form and points at the section that carries the cited content; the clip-pressure implementation plan moved to docs/plans/ so the ADR identifier it borrowed resolves to one record; the implementation guide describes the lazy coordination warm-up the code performs; a failed warming worker is recorded rather than allowed to take reset() down; the report's geometry record describes the model that produced the scores beside it; the compile-time width bound is named where a reader would look for it in the error list; and a test comment that cited a document the package never contained now derives its tolerance in place. Finally, the structural mutation counts in the contour snapshot are read from the spatial layer's own counters instead of being inferred from node and terminal deltas, an inference that a last-child eviction falsified; that is what the Mudlark minor bump carries.

@codecov

codecov Bot commented Feb 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.76115% with 218 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.37%. Comparing base (843aaff) to head (bab66fa).

Files with missing lines Patch % Lines
packages/sentinel/src/sentinel/mod.rs 91.34% 53 Missing and 21 partials ⚠️
packages/sentinel/src/config.rs 78.02% 49 Missing ⚠️
packages/sentinel/src/maths/bench_tracing.rs 0.00% 45 Missing ⚠️
packages/sentinel/src/sentinel/tracker.rs 96.13% 13 Missing and 4 partials ⚠️
packages/sentinel/src/maths/mod.rs 91.25% 13 Missing and 1 partial ⚠️
packages/sentinel/src/maths/brand_svd.rs 91.46% 6 Missing and 1 partial ⚠️
packages/sentinel/src/sentinel/staging.rs 98.56% 3 Missing and 3 partials ⚠️
packages/sentinel/src/sentinel/warming_thread.rs 96.05% 3 Missing ⚠️
packages/sentinel/src/analysis_set.rs 98.33% 2 Missing ⚠️
packages/sentinel/src/maths/naive_svd.rs 97.61% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #827      +/-   ##
===========================================
+ Coverage    68.52%   72.37%   +3.85%     
===========================================
  Files          161      175      +14     
  Lines        13111    15757    +2646     
  Branches     13111    15757    +2646     
===========================================
+ Hits          8984    11404    +2420     
- Misses        3853     4048     +195     
- Partials       274      305      +31     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@da2ce7
da2ce7 force-pushed the 20260219_sentinel branch from 98b82af to 0d4bf03 Compare March 25, 2026 05:05
@da2ce7 da2ce7 added the Needs Rebase Base Branch has Incompatibilities label Apr 23, 2026
@da2ce7
da2ce7 force-pushed the 20260219_sentinel branch from 0d4bf03 to 84defab Compare April 23, 2026 10:36
@da2ce7 da2ce7 added Needs Rebase Base Branch has Incompatibilities and removed Needs Rebase Base Branch has Incompatibilities labels Apr 23, 2026
@da2ce7
da2ce7 force-pushed the 20260219_sentinel branch from 84defab to 7d2fd0c Compare May 1, 2026 12:50
@da2ce7 da2ce7 removed the Needs Rebase Base Branch has Incompatibilities label May 1, 2026
@da2ce7
da2ce7 force-pushed the 20260219_sentinel branch from 7d2fd0c to 57ac0f5 Compare May 12, 2026 17:37
@da2ce7
da2ce7 force-pushed the 20260219_sentinel branch from 57ac0f5 to 3972d50 Compare May 12, 2026 22:39
@da2ce7 da2ce7 changed the title 20260219 sentinel Introduce Sentinel — an online subspace anomaly detector May 12, 2026
@da2ce7
da2ce7 force-pushed the 20260219_sentinel branch from 3972d50 to afde2c7 Compare May 12, 2026 23:01
@da2ce7
da2ce7 force-pushed the 20260219_sentinel branch from afde2c7 to 022a672 Compare May 12, 2026 23:17
@da2ce7
da2ce7 marked this pull request as ready for review May 12, 2026 23:26
Copilot AI review requested due to automatic review settings May 12, 2026 23:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

A seed fixes noise assignment and reports only for identical inputs with
synchronous warming on a fixed build. Background warming chooses staged
cells as the worker observes their priorities, so its scheduling also
affects learned baselines and the first scoring cycle. State those limits
on the seed and warming fields.

The bit encoding places its two levels symmetrically around zero. A zero
expected mean requires balanced bits; arbitrary traffic retains its bias.
Align the public vector documentation and test premise with that condition
without changing the encoding or any public item.
A background worker can take the only waiting cell before the test locks
staging, so a long warming schedule does not ensure a nonempty waiting
map. Accepting only an in-flight record would also leave no cached volume
to inspect.

Stop the worker through its existing shutdown handshake before enqueueing
traffic and retain its handle so reconciliation still selects deferred
staging. The waiting-map assertion and every volume assertion then inspect
actual queued cells regardless of scheduling. Production behavior and all
public item shapes remain unchanged.
…e open the ceiling

The tracker holds both baselines when the clip filter retains no samples. A clip ratio of one still raises pressure, widening the ceiling on subsequent batches while CUSUM receives the raw batch mean. The algorithm previously described learning the unclipped batch, which would let the baselines chase a sustained shift and defeat the accumulation required by gradual_single_cell_cusum.

Describe the implemented lockout behavior, condition both EWMA updates on retained samples, and align the recovery explanation and tracker comments. Only documentation changes; executable statements and the existing detection witness stay intact.
Allocated warming trackers consume investment capacity before they can produce scores. Preserve the deferred-warm-up decision as history while marking its online-only accounting as superseded and recording the investment-versus-production distinction.

The crate keeps implementation modules crate-private and re-exports downstream types at the root. Record that flat surface in the API plan so its intended imports match the API the crate exposes.
A cell completed after the promotion pass remains in the warm-up pipeline until the next ingest promotes it. The total warming count includes that ready state, so excluding it from the competitive subset can make one health snapshot contradict itself.

Count competitive ready entries alongside waiting and in-flight work. A deterministic staging witness completes a target after promotion and checks the public health fields before another ingest can promote it.
Scores and their normalisation geometry are computed before rank adaptation. Reading rank-dependent fields afterward can pair a rank-two geometry with coherence that was deliberately disabled while the batch was scored at rank one.

Snapshot rank, energy ratio, and scoring geometry before adaptation, leaving the adapted rank for the next batch. A deterministic rank-change witness checks the report against the scoring state and the tracker against its next state.
Batch generation only creates random centred vectors; it does not feed trackers or reset drift state. Describing the whole injection lifecycle on that helper gives it responsibilities its body never performs.

Keep the generator description local to the generator. The injection helper already documents the schedule, baseline seeding, and evidence reset that it performs.
Explicit shutdown deliberately surfaces a failed background worker to its caller, but destruction has no caller that can recover from a second panic. Joining through the same method made an earlier worker failure turn ordinary teardown into unwinding and made teardown during another unwind abort the process.

Separate the shared shutdown handshake from the join policy. Explicit shutdown retains its existing failure surface, while destruction consumes a failed join and records it through tracing. A test-only failure seam drives the worker through its poisoned-staging path and proves that dropping its sentinel completes without panic.
Noise warm-up now defers cell work and creates coordination contexts lazily, selection filters dimensions before ancestor closure, and synchronous fallback drains staging before ingest returns. Leaving earlier ADR consequences phrased as current behaviour makes operational expectations contradict the code.

Record the current lifecycle while preserving the original decisions, name the collected convergence test location, and remove a rendering artifact from the SVD witness source. Regenerate the affected test inventories so their labels and claims match the current sources.
The rank-change witness requires the coherence score to be the exact structural zero produced at rank one. A direct floating-point equality obscures that intent from the lint configuration even though approximate comparison would weaken the claim.

Compare the score and expected zero through their bit patterns. This preserves the exact-value requirement, including the sign of zero, without suppressing the floating-point comparison lint.
The collected test module exercises crate-visible internals, so its overview now uses the repository's test classification.

The ingest contract accepts the generic coordinate type, so its documentation describes coordinate observations without narrowing the method to one convenience alias.
The drift allowance is defined in slow-baseline sigma units, while epsilon protects unrelated denominators. Computing the allowance from slow variance alone keeps configured numerical stability from changing the accumulator trajectory and matches the documented detection rule.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Coordination lifecycle semantics conflict across implementation and specification, and several public documentation references are stale or ambiguous.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (6)

packages/sentinel/src/sentinel/mod.rs:1289

  • §5.5 documents coherence scoring, not coordination-context pruning. This should point to the lifecycle pruning section and use the package-qualified source-reference form required by AGENTS.md:72-83.
    packages/sentinel/src/sentinel/mod.rs:1532
  • This repeats the stale coherence-section reference for lifecycle pruning. Source references must be package-qualified (AGENTS.md:72-83), and the relevant contract is §ALGO S-7.7.2.
    packages/sentinel/src/sentinel/tracker.rs:105
  • This source cross-reference is missing the required document and Sentinel package qualifiers (AGENTS.md:72-83).
    packages/sentinel/src/sentinel/tracker.rs:653
  • This source cross-reference is missing the required document and Sentinel package qualifiers (AGENTS.md:72-83).
    packages/sentinel/src/sentinel/tracker.rs:785
  • This source cross-reference is missing the required document and Sentinel package qualifiers (AGENTS.md:72-83).
    packages/sentinel/src/sentinel/tracker.rs:834
  • This source cross-reference is missing the required document and Sentinel package qualifiers (AGENTS.md:72-83).
  • Files reviewed: 54/101 changed files
  • Comments generated: 10
  • Review effort level: Balanced

Comment thread packages/sentinel/adr/020-clip-pressure-ewma-implementation-plan.md Outdated
Comment thread packages/sentinel/docs/algorithm.md
Comment thread packages/sentinel/docs/implementation.md Outdated
Comment thread packages/sentinel/src/sentinel/mod.rs
Comment thread packages/sentinel/src/sentinel/mod.rs Outdated
Comment thread packages/sentinel/src/sentinel/mod.rs Outdated
Comment thread packages/sentinel/src/sentinel/tracker.rs Outdated
Comment thread packages/sentinel/src/sentinel/tracker.rs Outdated
Comment thread packages/sentinel/src/tests/convergence_common.rs Outdated
Comment thread packages/sentinel/tests/warm_up.rs Outdated
The public schedule accepts every usize depth, so narrowing the exponent can turn an extreme depth negative and grow the schedule above its root. Saturating the exponent at the signed limit preserves the documented taper, floor, and maximum across the full input domain.
Coordination firing depends on which cells score in a batch, but learned context state belongs to the online competitive membership. Building retention from that membership preserves state through quiet and one-sided batches while still destroying contexts when either subtree loses its last member.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Structural mutation counts rely on an invalid Mudlark eviction identity, causing debug panics and incorrect release reports.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (4)

packages/sentinel/src/sentinel/tracker.rs:493

  • This bare source-code section reference violates the package-qualified format required by AGENTS.md:94-100.
    packages/sentinel/src/sentinel/tracker.rs:651
  • This bare source-code section reference violates the package-qualified format required by AGENTS.md:94-100.
    packages/sentinel/src/sentinel/tracker.rs:785
  • This bare source-code section reference violates the package-qualified format required by AGENTS.md:94-100.
    packages/sentinel/src/sentinel/tracker.rs:833
  • This bare source-code section reference violates the package-qualified format required by AGENTS.md:94-100.
  • Files reviewed: 54/101 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment thread packages/sentinel/src/sentinel/mod.rs Outdated
Comment thread packages/sentinel/src/sentinel/tracker.rs Outdated
Comment thread packages/sentinel/src/sentinel/tracker.rs Outdated
Source and test references need package-qualified targets that continue to resolve when the algorithm document is read outside its own context. The coordination guide likewise needs to describe lazy context creation rather than the retired chained warm-up flow.

Moving the implementation plan out of the decision-record namespace leaves ADR-S-020 with one authoritative record, while the constructor documentation explains why the graph width relation is absent from runtime configuration errors.
Reset has no error channel and promises that a background resource failure will not abort the host. Raising a failed worker join through expect turns an earlier thread failure into an undocumented reset panic.

Recording the join failure through the existing tracing channel lets reset rebuild the sentinel, while the documented staging-mutex poison condition remains unchanged.
A batch is scored before rank adaptation, so deriving its geometry from the evolved rank pairs its novelty value with the wrong residual degrees of freedom. Consumers can no longer reconstruct the residual energy or judge saturation from the fields beside the score.

Snapshotting geometry at scoring time keeps rank, residual degrees of freedom, and saturation aligned with the batch they describe. The current rank remains available as the model prepared for the next batch.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The shared seeded fixture never crosses its split threshold, leaving several coordination tests vacuous.

Review details
  • Files reviewed: 54/101 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

The convergence helper cited a document that does not exist, leaving the statistical basis of its block comparison unverifiable. Its nearby numeric explanation also treated a two-block difference as though only one block contributed variance.

Deriving the EWMA transient, autocorrelation inflation, and two-block difference scale beside the helper makes the tolerance checkable from the quantities the test uses. The existing per-axis budgets and test behaviour remain unchanged.
Node and terminal population deltas cannot identify every structural event: removing the last child leaves the terminal population unchanged, and recreating a missing child overlaps other growth signatures.

Monotonic totals now record each child created by bisection as a split, each terminal child removal as an eviction, and each missing child recreated by legacy promotion as a restoration. The accessor and value record are additive public API, released in the semver-minor 1.1.0 line.
…ounters

Node and terminal population deltas do not identify an eviction that turns a semi-internal parent into a terminal. Reading the spatial layer's monotonic event counters preserves every structural event, while snapshotting each new graph prevents reset from creating a synthetic interval.

The split field counts created children, and unsigned net removals floor restoration-heavy intervals at zero and saturate values outside the report field's range.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The large public numerical API and concurrent warming lifecycle warrant final human review despite extensive documentation and tests.

Review details
  • Files reviewed: 63/110 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread packages/sentinel/Cargo.toml

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Maturity decays inconsistently with the learned model, infinite epsilon can disable scoring, and several public references are inaccurate.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 63/110 changed files
  • Comments generated: 5
  • Review effort level: Balanced

Comment thread packages/sentinel/src/config.rs Outdated
Comment thread packages/sentinel/src/sentinel/tracker.rs Outdated
Comment thread packages/sentinel/src/config.rs Outdated
Comment thread packages/sentinel/src/observation.rs Outdated
Comment thread packages/sentinel/src/sentinel/mod.rs Outdated
An infinite denominator guard passes a positivity check while making every protected score or energy denominator infinite, collapsing the resulting ratios to zero. Validation now separates non-finite values from non-positive finite values and leaves the largest finite guard admissible.

EpsNotFinite is an additive variant of the existing non-exhaustive ConfigError enum, giving callers a truthful diagnostic without reshaping any existing item.
The learned subspace, latent statistics, and score baselines consume the forgetting factor once per tracker batch, while maturity consumed it once per row. Larger batches therefore claimed that warm-up had been forgotten before the model state had forgotten it.

Advance noise influence once per tracker batch while retaining sample-based observation counters. The algorithm previously specified the recurrence per observation even though its convergence table used batches; it now states the model-aligned cadence and its batch-size independence.
Rank adaptation advances once per tracker batch, so describing its interval in observations makes the schedule appear to depend on batch size. State the unit consistently in the public configuration and supporting documents.

Replace internal record and section keys in source documentation with the navigable ADR and package-qualified algorithm references required by the contributor contract.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The new statistical engine, numerical routines, concurrency lifecycle, and broad public API require final human validation despite no confirmed blocking defect in the reviewed changes.

Review details
  • Files reviewed: 63/110 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@da2ce7

da2ce7 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

ACK 4c052f8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants