Skip to content

refactor!: security hardening, standards alignment, and unified API (v0.9.0)#32

Merged
LGDiMaggio merged 17 commits into
mainfrom
refactor/v0.9.0-credibility-unified-api
Jul 13, 2026
Merged

refactor!: security hardening, standards alignment, and unified API (v0.9.0)#32
LGDiMaggio merged 17 commits into
mainfrom
refactor/v0.9.0-credibility-unified-api

Conversation

@LGDiMaggio

@LGDiMaggio LGDiMaggio commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

This release advances the project along its industrial-grade roadmap: it extends the path-safety hardening to every published code path, tightens the alignment between outputs and their declared inputs and cited standards, and consolidates the tool surface into a single, coherent golden path. It implements Phases 0 and 1 of the internal roadmap.

Final surface: 33 tools + 3 prompts + 0 resources (consolidated from 54 endpoints). Test suite: 884 passed, 17 skipped, coverage 92.17% (gate 85%). CI green across the full matrix (Python 3.11/3.12 × Windows/Linux/macOS, mypy, flake8, black).

⚠️ Breaking changes — pre-1.0 release. Some tool names and parameters change, with a clean cut (no deprecation aliases). A full migration table is in the CHANGELOG (0.9.0 section). The package entry point (predictive-maintenance-mcp / python -m predictive_maintenance_mcp) is unchanged, so existing Claude Desktop configurations keep working.

What changed, by unit

Unit Change
U1 (security) Path containment extended to every user-controlled filesystem path (the train_anomaly_model write side, the model/report read sides, the signal loader) through a single path_safety module (safe_resolve / resolve_model_paths). Adds a 43-case regression suite including the sibling-directory bypass class.
U2 A single ISO severity-threshold engine (iso20816.py) with a precise citation (ISO 10816-3:2009 values, 4-zone scheme, noting that ISO 20816-3:2022 merges zones A/B). The engine returns an explicit refusal when an assessment is not technically valid (Nyquist below the evaluation band, machine power out of scope).
U3 The bearing catalog is rebuilt around entries with verified geometry and a tracked source field (CWRU 6205/6203, XJTU UER204), with a geometry-validation test guarding every entry. Reference values are single-sourced from the catalog JSON.
U4 Prognostics tightened for physical soundness: estimate_rul requires a multi-measurement series (single-recording extrapolation is refused with a pointer to within-recording screening); output fields are named for what they measure (fit_r_squared, evidence_strength derived from corroborating evidence); the Kalman delta-method variance gains its covariance cross-term.
U5 ISO verdicts require a declared signal unit; when it is absent, diagnose_vibration degrades gracefully — the ISO block returns a structured refused result (reason + remedy) while the spectral/bearing/anomaly blocks still run. Sampling rate follows the same discipline: explicit > companion metadata > structured error.
U6 All tools are module-level, directly importable functions with a single two-rail error contract (raise for misuse/failures, typed results for legitimate negative outcomes).
U7 The legacy monolith (machinery_diagnostics_server.py) and the root shims are removed so the package ships one modular server; a CI guard prevents their reintroduction.
U8 signal_id is the single analysis/report/prognostics handle; the repository gains relative-path ids, explicit collision handling, atomic batch loads, read-only array views, and a fix for absolute-path loading.
U9 Endpoint consolidation (54 → 36) with golden characterization on the behavior-preserving merges plus a surface-parity test mapping every former name to its destination; loop closures (generate_test_signal auto-registers, predict_anomalies returns bounded summaries, report filenames are timestamped).
U10 The plugin (8 skills, 2 agents, 3 commands, README) and the 3 MCP prompts are regenerated from the real schemas, with CI guards against future drift (test_documented_calls.py, test_version_alignment.py).
U11 CHANGELOG 0.9.0 with the migration table and the rationale for retiring the monolith this release; version strings aligned across all manifests.

Review and follow-up fixes

After the units landed, an adversarial multi-agent review across 10 dimensions (correctness, security, cross-module consistency, domain math, test quality, dead code…) produced 28 verified findings, all addressed in 4a2e8be. Highlights:

  • [blocking] the default envelope band is now fs-aware (previously it rejected any signal with fs ≤ 10 kHz); output is byte-identical at fs = 10 kHz (golden tests green).
  • [blocking] the diagnose_bearing prompt no longer defaults the signal unit on the ISO-refusal path — it collects the real unit first, keeping the U5 discipline intact.
  • non-positive sampling rates rejected up front, a clear message for signals shorter than one segment, deep-copied cached metadata, a version-alignment guard that now sees every occurrence, and several domain-math refinements (ISO band coverage, Kalman initialization, onset detection).

Tests & verification

  • pytest: 884 passed, 17 skipped, coverage 92.17%; green on all six platform/version combinations.
  • Golden characterization on the U9 merges (frozen snapshots), surface parity (33/0/3 asserted in CI), end-to-end workflow walkthroughs, and the 43-case security regression suite.
  • validate_server.py green (33 tools / 3 prompts / 0 resources).

Release notes

  • Version: shipping directly as v0.9.0 (the security hardening is included). The v* tag triggers the automated, non-reversible publish pipeline (PyPI → MCP Registry); tag on main after merge.
  • Coverage scope: report_generator.py / html_templates.py remain excluded from the coverage figure (a planned follow-up adds their backend tests), so the headline number excludes those two report backends.
  • The audit and planning documents that describe the security details at file:line precision are intentionally kept out of this branch until a patched version is published.

🤖 Generated with Claude Code

LGDiMaggio and others added 16 commits July 10, 2026 23:41
…aths

Close path traversal across every user-supplied filesystem path in both the
modular server and the legacy monolith:
- train_anomaly_model built its pickle output path from an unvalidated
  model_name (arbitrary-file-write reachable by any MCP client); the model-load
  sites and read_report_metadata read path were likewise unvalidated.
- load_signal_data (the shared read sink behind every analysis tool) allowed
  arbitrary file-content reads via a traversal signal_file.

Introduce a canonical path_safety module (safe_resolve via Path.is_relative_to,
sanitize_filename, validate_name_component, resolve_model_paths -> ModelPaths),
re-exported from mcp_tools._utils, and route every write/read site through it.
Invalid names are rejected before any I/O; the diagnosis pipeline degrades to
None rather than aborting. Add a 52-case security regression suite covering the
sibling-directory bypass (regression of 61627b0 -> d689886).

Bump to 0.8.1 (security-only; features stay under [Unreleased]).
Realign CITATION.cff from stale 0.7.1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Document the v0.8.1 fix as a searchable docs/solutions entry: the
str.startswith sibling-directory bypass, the single-source-of-truth
path_safety choke point, validate-don't-rewrite for security names, and the
cross-platform backslash test gotcha. Surface docs/solutions/ in CLAUDE.md so
future agents discover it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…st citation

One threshold table (ISO 10816-3:2009 values, 4-zone scheme) lives in
diagnostics/iso10816.py; alerts.py's drifted duplicate is gone and every
path now classifies identically. Consumers switch from the invented
machine_class I-IV mapping to native machine_group/support_type. The
engine refuses assessment when Nyquist < 1 kHz or declared power < 15 kW,
reports the real integration band, and stamps provenance (20816-3:2022
merges zones A/B) on every output.
…move fictitious references

Keep only source-traceable entries: 6205 and 6203 from CWRU Bearing Data
Center (with published frequency factors cross-checked in tests) and
UER204 from the XJTU-SY dataset. Delete 19 entries with fabricated
internal geometry misattributed to ISO 15:2017 — the old 6205 pitch
(34.55 mm vs real 39.04) was contaminated from the XJTU bearing.
document_reader's in-memory fallback now reads the same JSON instead of
duplicating values; kinematic formulas cite Randall & Antoni 2011 instead
of ISO 15243; the hardcoded 81.13 Hz 'example @ 1500 RPM' block no longer
pollutes envelope output. New geometry-validation suite (36 tests) guards
every entry: bore<pitch<OD, ball fit, BPFO/fr+BPFI/fr≈Z, non-empty source.
…nvented confidence

estimate_rul now takes a timestamped measurement series (explicit values
or multiple stored signals) and refuses single-recording extrapolation,
pointing to the within-recording trend screening instead. Weibull
estimator removed. Kalman delta-method variance gains the missing
covariance cross-term; its precision field is explicitly heuristic.
Trend direction is gated on the computed p-value, not R-squared>0.3, and
onset detection can no longer fire inside its own baseline window.
Confidence fields renamed to honest ones (fit_r_squared,
evidence_strength derived from corroborating evidence rather than
severity) and no tool accepts confidence as input anymore.
…uessing

ISO severity verdicts are only produced when the signal unit is declared
— via load_signal(signal_unit=...) or the companion _metadata.json —
never guessed from amplitude. The RMS>0.5→'g' heuristic, the
HYPOTHESIS/PROCEEDING flow and the PLEASE-CONFIRM ctx.info wall are gone.
diagnose_vibration degrades instead of failing: the iso_severity block
becomes a schema-level ISOSeverityRefusal (status/reason/remedy) when the
unit is undeclared or Nyquist is below the ISO band, while spectral,
bearing and anomaly blocks still run. sampling_rate follows the same
discipline: explicit > metadata > structured error, no silent 1 or 10 kHz
defaults anywhere.
…rror contract

All 54 endpoints (46 tools, 4 resources, 4 prompts) are now module-level
functions importable and testable without FastMCP; register() is a plain
list of registrations. Protocol identity verified against a frozen
inventory snapshot (byte-identical descriptions and prompt renders).
Error contract unified on two rails: misuse/failures raise ValueError
with problem-plus-remedy messages (FastMCP maps them to isError), and
legitimate negative outcomes are typed results (BearingCatalogMiss).
Error-shaped dicts returned as success and f-string JSON in resources are
gone, guarded by AST source checks. New resolve_signal helper replaces
the repeated repository-lookup idiom at eight sites; report_tools no
longer imports from the monolith.
…r server only

Delete machinery_diagnostics_server.py (5,289 lines) and the five root
import shims; move the real signal_loader/signal_repository
implementations to signal_acquisition/ (loaders.py, repository.py) so
every import path is canonical. Rename diagnostics/iso10816.py to
iso20816.py now that its last legacy importers are gone, and make the
unreachable unknown-unit fallback raise. report_generator raises natively
instead of returning error dicts. Migrate/delete the six monolith-bound
test files and add a CI guard (test_monolith_removed.py) that fails on
any reappearing monolith reference or bare ISO 10816 citation. Dockerfile
health check and build check, .vscode/mcp.json, validate_server.py and
operational docs repointed to the modular server.

Removal ahead of the v1.0.0 promise (commit 114bed3) is deliberate: the
unpatched second copy was a standing security liability. Full rationale
lands in the v0.9.0 CHANGELOG migration notes.

BREAKING CHANGE: machinery_diagnostics_server and root-level shim modules
no longer exist; import from the package's canonical module paths.
…ository correctness

Every analysis, diagnostics, prognostics and report tool now takes
signal_id resolved through the shared resolve_signal helper; filename
parameters and their silent 10 kHz sampling-rate defaults are gone (clean
cut, no aliases). load_signal accepts str | list[str] with fail-fast
atomic batch semantics: every path, derived id and collision is validated
up front and nothing loads on the first problem. Repository fixes:
default ids derive from the relative path (real_train/baseline_1 and
real_test/baseline_1 no longer silently collide), reloading an existing
id errors unless overwrite=True, absolute paths outside DATA_DIR load
that exact file instead of a same-named file inside DATA_DIR, arrays are
returned as read-only views over repository-owned memory, and the
not-found message lists loaded ids and explains cache eviction. Analysis
segments are deterministic by default with random sampling behind an
explicit random_seed parameter.

BREAKING CHANGE: filename/signal_file parameters removed from all
analysis-era tools; use load_signal then signal_id.
… tools

Four major merges with golden characterization armor (old-tool outputs
snapshotted on fixed fixtures before removal, asserted equivalent through
the new tools):
- assess_severity absorbs evaluate_iso_20816, assess_vibration_severity,
  check_vibration_alert and check_custom_vibration_alert (signal_id XOR
  rms_velocity_mm_s, native machine_group/support_type, optional custom
  thresholds, machine_power_kw scope refusal)
- analyze_envelope absorbs compute_envelope_spectrum_tool (500-5000 Hz
  default band, invalid band raises instead of clamping, detrend + Hann
  window before the envelope FFT kills the DC skirt on the FTF zone)
- check_bearing_faults absorbs check_bearing_fault_peak_tool,
  check_bearing_faults_direct and lookup_bearing_and_compute_tool
  (bearing_id XOR frequencies XOR explicit geometry; canonical fault
  vocabulary and catalog source echoed)
- analyze_signal_trend absorbs detect_signal_degradation_onset (onset
  fields folded into within-recording screening output)

BREAKING CHANGE: the nine absorbed tool names no longer exist.
Minor merges: list_signals(scope), clear_signals(signal_id=None),
list_html_reports absorbs get_report_info (read path stays on
safe_resolve), plot_spectrum/plot_envelope/plot_iso_20816_chart fold into
their generate_* reports. The four duplicate resources and the ASCII-art
ISO prompt are dropped; every piece of information they exposed is
reachable through tools. Loop closures: generate_test_signal writes
companion metadata, auto-registers and returns StoredSignalInfo;
predict_anomalies returns bounded summaries instead of per-segment
arrays and its not-found error lists disk models; report filenames are
timestamped; fault_types is a canonical Literal list that raises on
unknown values; get_signal_info exposes complete source_metadata.
Naming sweep: diagnose_vibration drops the _tool suffix, one name per
concept (rpm, file_name, bearing_id, signal_id). New surface-parity test
maps all 54 v0.8.x endpoints to their destinations and pins the
inventory at exactly 33/0/3; W1-W8 workflow walkability tests prove the
golden paths end-to-end.

BREAKING CHANGE: resources removed, plot_* and get_report_info merged
into report tools, several parameters renamed to the canonical
vocabulary.
… drift guards

Test-first: test_documented_calls.py parses every backtick tool call in
plugin/**/*.md and in the rendered MCP prompt templates and validates
name + kwargs against the introspected inventory; test_version_alignment
pins version strings across pyproject/__init__/server.json/CITATION.cff
and endpoint counts in both READMEs to the introspected surface. The
guards were born red on the old docs (dead tool names, filename-era
params), then all 8 skills, 2 agents, 3 commands, plugin README (which
finally documents the prognostics skill) and the 3 MCP prompts were
regenerated against the real 33-tool schema until green. Golden-path
workflows follow load_signal(signal_unit=...) -> analyze ->
assess_severity/diagnose -> report, and every regenerated document keeps
the augments-expert-judgment framing. Manifests untouched beyond content
(marketplace schema strictness). README endpoint counts corrected.
CHANGELOG 0.9.0 with full migration table (54 -> 36 endpoints, parameter
renames, honest-field renames) and the explicit rationale for removing
the monolith ahead of the v1.0.0 promise: after 0.8.1 the unmaintained
twin copy was a standing security and drift liability. Version bumped to
0.9.0 across pyproject, __init__, server.json, CITATION.cff and the
README BibTeX block (the last one caught by the new version-alignment
guard); plugin bumped to 1.1.0 in plugin.json and marketplace.json.
Untracked README drafts and stale rag_result.txt removed;
.claude/worktrees/ gitignored.

Pre-tag checks: mcp-name string present in README, server.json
description 91 chars (limit 100), suite 861 passed / coverage 92.23%,
server import smoke test green.
…ensions)

Adversarial multi-agent review of the U2-U11 diff surfaced 28 findings;
all are addressed. Full suite 884 passed / 17 skipped / coverage 92.17%.

BLOCKING:
- Envelope default band is now fs-aware: compute_envelope_spectrum takes a
  None sentinel and resolves the default upper edge to min(5000, Nyquist-1)
  so a bearing check on any fs<=10kHz signal analyzes 500-Nyquist instead
  of hard-raising; an explicitly over-Nyquist band still raises. fs=10000
  output is byte-identical (golden tests green).
- The diagnose_bearing prompt no longer fabricates signal_unit='g' on the
  ISO-refusal path — it collects the unit up front and instructs confirming
  the real unit with the user before re-loading, restoring the U2 discipline.

SHOULD-FIX:
- resolve_signal and the load path reject non-positive sampling rates
  (was ZeroDivisionError downstream); StoredSignalInfo gains gt=0.
- predict_anomalies and generate_pca_visualization_report guard signals
  shorter than one segment with a clear message instead of an opaque sklearn
  error.
- get_signal_info/list_signals deep-copy the cached info so callers can't
  mutate source_metadata/shape in the cache.
- CITATION.cff preferred-citation bumped to 0.9.0; the version-alignment
  guard now checks both version occurrences.
- Gear crest-factor screening gate reconciled to >5 across prompt and skill.
- evidence_strength moderate/strong branches now have tests.

NICE-TO-HAVE:
- Domain-math edge cases: ISO band-coverage guard refuses fs<2106Hz (was a
  truncated-band verdict); Kalman no longer double-uses the first two
  samples; constant-baseline onset requires an epsilon margin.
- Robustness: batch load_signals is atomic under one lock; get_metadata_path
  routes through safe_resolve; read-only-view docstring softened to match.
- De-duplication: RUL/pipeline docstrings and severity labels reference the
  single-source ISO table; document_reader demo sources 6205 from the
  catalog; fault-vocabulary sync guard added.
- Dead code removed (SignalInfo model, unused imports); signal-management
  skill gains the augments-expert-judgment framing.

Deliberately not changed: the absolute-path signal read (pre-existing
accepted local-first design) and the report-backend coverage omit
(plan-sanctioned deferral).
… honest outputs)

Knowledge-track learning distilling the five reusable patterns from the
v0.9.0 refactor: single-source-of-truth + CI drift guards for the repo's
recurring value-drift bug class; refuse-don't-guess for physical units and
sampling rate; golden characterization for behavior-preserving merges;
security rationale for removing a duplicated live copy ahead of a
deprecation promise; and default-vs-explicit resolution so tightened
validation doesn't reject legitimate data. Each is anchored to a concrete
near-miss the adversarial review caught.
The list_html_reports traversal regression asserted 'Invalid report
filename' for a backslash payload ('..\secret.txt'), but a backslash is
only a path separator on Windows — on POSIX it is a literal filename
character, so safe_resolve correctly treats it as an in-bounds
(nonexistent) name and the read falls through to 'Report not found'. The
containment itself is correct on every platform (the forward-slash and
absolute-path payloads prove it); only the backslash case is now gated to
Windows. Fixes the Linux/macOS CI failure.
@LGDiMaggio LGDiMaggio changed the title refactor!: risanamento sicurezza, credibilità tecnica e API unificata (v0.9.0) refactor!: security hardening, standards alignment, and unified API (v0.9.0) Jul 13, 2026
Reword the credibility-refactor learnings to describe failure modes and
the patterns that prevent them without characterizing earlier work
negatively — 'input-aligned outputs' rather than 'honest outputs',
'inferred' rather than 'guessed', 'confidently incorrect' as a failure
mode to engineer against. Technical substance unchanged.
@LGDiMaggio
LGDiMaggio merged commit a279050 into main Jul 13, 2026
9 checks passed
@LGDiMaggio
LGDiMaggio deleted the refactor/v0.9.0-credibility-unified-api branch July 13, 2026 16:22
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.

1 participant