ftmon 2.0.0a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. ftmon-2.0.0a1/.github/workflows/ci.yml +26 -0
  2. ftmon-2.0.0a1/.github/workflows/release.yml +82 -0
  3. ftmon-2.0.0a1/.gitignore +14 -0
  4. ftmon-2.0.0a1/AGENTS.md +44 -0
  5. ftmon-2.0.0a1/CLAUDE.md +118 -0
  6. ftmon-2.0.0a1/CONTRIBUTING.md +58 -0
  7. ftmon-2.0.0a1/DESIGN.md +990 -0
  8. ftmon-2.0.0a1/LICENSE +21 -0
  9. ftmon-2.0.0a1/PKG-INFO +190 -0
  10. ftmon-2.0.0a1/README.md +157 -0
  11. ftmon-2.0.0a1/SPEC.md +1071 -0
  12. ftmon-2.0.0a1/design/builtins/disk.toml +126 -0
  13. ftmon-2.0.0a1/design/builtins/events.toml +38 -0
  14. ftmon-2.0.0a1/design/builtins/hog.toml +42 -0
  15. ftmon-2.0.0a1/design/builtins/leak.toml +67 -0
  16. ftmon-2.0.0a1/design/builtins/load.toml +47 -0
  17. ftmon-2.0.0a1/design/builtins/net.toml +40 -0
  18. ftmon-2.0.0a1/design/builtins/self.toml +62 -0
  19. ftmon-2.0.0a1/design/builtins/service.toml +38 -0
  20. ftmon-2.0.0a1/docs/definitions.md +371 -0
  21. ftmon-2.0.0a1/docs/external-checks.md +179 -0
  22. ftmon-2.0.0a1/docs/install.md +597 -0
  23. ftmon-2.0.0a1/docs/manual.md +336 -0
  24. ftmon-2.0.0a1/docs/why-ftmon.md +75 -0
  25. ftmon-2.0.0a1/extra-monitors/README.md +63 -0
  26. ftmon-2.0.0a1/extra-monitors/_template/README.md +31 -0
  27. ftmon-2.0.0a1/extra-monitors/_template/checks.toml.example +4 -0
  28. ftmon-2.0.0a1/extra-monitors/_template/fixtures/ok.txt +1 -0
  29. ftmon-2.0.0a1/extra-monitors/_template/monitor.toml +21 -0
  30. ftmon-2.0.0a1/extra-monitors/_template/recipe.toml +20 -0
  31. ftmon-2.0.0a1/pyproject.toml +70 -0
  32. ftmon-2.0.0a1/src/ftmon/__init__.py +3 -0
  33. ftmon-2.0.0a1/src/ftmon/__main__.py +9 -0
  34. ftmon-2.0.0a1/src/ftmon/checks/__init__.py +7 -0
  35. ftmon-2.0.0a1/src/ftmon/checks/jsoncheck.py +55 -0
  36. ftmon-2.0.0a1/src/ftmon/checks/model.py +36 -0
  37. ftmon-2.0.0a1/src/ftmon/checks/nagios.py +98 -0
  38. ftmon-2.0.0a1/src/ftmon/checks/registry.py +150 -0
  39. ftmon-2.0.0a1/src/ftmon/checks/runner.py +121 -0
  40. ftmon-2.0.0a1/src/ftmon/checks/sampler.py +129 -0
  41. ftmon-2.0.0a1/src/ftmon/checks/text.py +10 -0
  42. ftmon-2.0.0a1/src/ftmon/cli.py +837 -0
  43. ftmon-2.0.0a1/src/ftmon/clock.py +130 -0
  44. ftmon-2.0.0a1/src/ftmon/config.py +324 -0
  45. ftmon-2.0.0a1/src/ftmon/daemon.py +654 -0
  46. ftmon-2.0.0a1/src/ftmon/definitions/__init__.py +31 -0
  47. ftmon-2.0.0a1/src/ftmon/definitions/builtins/disk.toml +126 -0
  48. ftmon-2.0.0a1/src/ftmon/definitions/builtins/events.toml +38 -0
  49. ftmon-2.0.0a1/src/ftmon/definitions/builtins/hog.toml +42 -0
  50. ftmon-2.0.0a1/src/ftmon/definitions/builtins/leak.toml +67 -0
  51. ftmon-2.0.0a1/src/ftmon/definitions/builtins/load.toml +47 -0
  52. ftmon-2.0.0a1/src/ftmon/definitions/builtins/net.toml +40 -0
  53. ftmon-2.0.0a1/src/ftmon/definitions/builtins/self.toml +62 -0
  54. ftmon-2.0.0a1/src/ftmon/definitions/builtins/service.toml +38 -0
  55. ftmon-2.0.0a1/src/ftmon/definitions/loader.py +1147 -0
  56. ftmon-2.0.0a1/src/ftmon/definitions/manage.py +162 -0
  57. ftmon-2.0.0a1/src/ftmon/definitions/schema.py +160 -0
  58. ftmon-2.0.0a1/src/ftmon/demo.py +191 -0
  59. ftmon-2.0.0a1/src/ftmon/deploy/Caddyfile.demo +53 -0
  60. ftmon-2.0.0a1/src/ftmon/engine/__init__.py +0 -0
  61. ftmon-2.0.0a1/src/ftmon/engine/actions.py +127 -0
  62. ftmon-2.0.0a1/src/ftmon/engine/context.py +52 -0
  63. ftmon-2.0.0a1/src/ftmon/engine/effects.py +107 -0
  64. ftmon-2.0.0a1/src/ftmon/engine/episodes.py +239 -0
  65. ftmon-2.0.0a1/src/ftmon/engine/events.py +364 -0
  66. ftmon-2.0.0a1/src/ftmon/engine/incidents.py +311 -0
  67. ftmon-2.0.0a1/src/ftmon/engine/pipeline.py +249 -0
  68. ftmon-2.0.0a1/src/ftmon/engine/render.py +39 -0
  69. ftmon-2.0.0a1/src/ftmon/engine/rings.py +109 -0
  70. ftmon-2.0.0a1/src/ftmon/engine/scheduler.py +77 -0
  71. ftmon-2.0.0a1/src/ftmon/expr/__init__.py +38 -0
  72. ftmon-2.0.0a1/src/ftmon/expr/eval.py +253 -0
  73. ftmon-2.0.0a1/src/ftmon/expr/functions.py +133 -0
  74. ftmon-2.0.0a1/src/ftmon/expr/ir.py +83 -0
  75. ftmon-2.0.0a1/src/ftmon/expr/parse.py +255 -0
  76. ftmon-2.0.0a1/src/ftmon/expr/tribool.py +41 -0
  77. ftmon-2.0.0a1/src/ftmon/mcp_server.py +588 -0
  78. ftmon-2.0.0a1/src/ftmon/model.py +160 -0
  79. ftmon-2.0.0a1/src/ftmon/notify/__init__.py +19 -0
  80. ftmon-2.0.0a1/src/ftmon/notify/base.py +55 -0
  81. ftmon-2.0.0a1/src/ftmon/notify/desktop.py +51 -0
  82. ftmon-2.0.0a1/src/ftmon/notify/file.py +44 -0
  83. ftmon-2.0.0a1/src/ftmon/notify/http.py +68 -0
  84. ftmon-2.0.0a1/src/ftmon/notify/ntfy.py +64 -0
  85. ftmon-2.0.0a1/src/ftmon/notify/smtp.py +87 -0
  86. ftmon-2.0.0a1/src/ftmon/notify/webhook.py +51 -0
  87. ftmon-2.0.0a1/src/ftmon/paths.py +98 -0
  88. ftmon-2.0.0a1/src/ftmon/scenarios/__init__.py +1 -0
  89. ftmon-2.0.0a1/src/ftmon/scenarios/demo-v1.jsonl +16 -0
  90. ftmon-2.0.0a1/src/ftmon/selfmon.py +83 -0
  91. ftmon-2.0.0a1/src/ftmon/sources/__init__.py +0 -0
  92. ftmon-2.0.0a1/src/ftmon/sources/base.py +198 -0
  93. ftmon-2.0.0a1/src/ftmon/sources/disk.py +94 -0
  94. ftmon-2.0.0a1/src/ftmon/sources/fixtures.py +363 -0
  95. ftmon-2.0.0a1/src/ftmon/sources/journald.py +155 -0
  96. ftmon-2.0.0a1/src/ftmon/sources/net.py +84 -0
  97. ftmon-2.0.0a1/src/ftmon/sources/process.py +124 -0
  98. ftmon-2.0.0a1/src/ftmon/sources/system.py +121 -0
  99. ftmon-2.0.0a1/src/ftmon/sources/unit.py +144 -0
  100. ftmon-2.0.0a1/src/ftmon/store/__init__.py +8 -0
  101. ftmon-2.0.0a1/src/ftmon/store/db.py +70 -0
  102. ftmon-2.0.0a1/src/ftmon/store/doctor.py +67 -0
  103. ftmon-2.0.0a1/src/ftmon/store/migrations/0001_init.sql +51 -0
  104. ftmon-2.0.0a1/src/ftmon/store/migrations/0002_action_runs.sql +6 -0
  105. ftmon-2.0.0a1/src/ftmon/store/migrations/0003_notification_deliveries.sql +44 -0
  106. ftmon-2.0.0a1/src/ftmon/store/outbox.py +350 -0
  107. ftmon-2.0.0a1/src/ftmon/store/query.py +500 -0
  108. ftmon-2.0.0a1/src/ftmon/store/retention.py +337 -0
  109. ftmon-2.0.0a1/src/ftmon/store/writer.py +435 -0
  110. ftmon-2.0.0a1/src/ftmon/systemd/ftmon-demo-build.service +27 -0
  111. ftmon-2.0.0a1/src/ftmon/systemd/ftmon-demo-refresh.service +17 -0
  112. ftmon-2.0.0a1/src/ftmon/systemd/ftmon-demo-refresh.timer +15 -0
  113. ftmon-2.0.0a1/src/ftmon/systemd/ftmon-demo-web.service +38 -0
  114. ftmon-2.0.0a1/src/ftmon/systemd/ftmon-server.service +54 -0
  115. ftmon-2.0.0a1/src/ftmon/systemd/ftmon.service +18 -0
  116. ftmon-2.0.0a1/src/ftmon/web/__init__.py +6 -0
  117. ftmon-2.0.0a1/src/ftmon/web/app.py +756 -0
  118. ftmon-2.0.0a1/src/ftmon/web/demo_app.py +161 -0
  119. ftmon-2.0.0a1/src/ftmon/web/static/brand/README.md +16 -0
  120. ftmon-2.0.0a1/src/ftmon/web/static/brand/apple-touch-icon.png +0 -0
  121. ftmon-2.0.0a1/src/ftmon/web/static/brand/favicon-64.png +0 -0
  122. ftmon-2.0.0a1/src/ftmon/web/static/brand/favicon.ico +0 -0
  123. ftmon-2.0.0a1/src/ftmon/web/static/brand/ftmon-mark.png +0 -0
  124. ftmon-2.0.0a1/src/ftmon/web/static/ftmon.css +1 -0
  125. ftmon-2.0.0a1/src/ftmon/web/static/ftmon.js +34 -0
  126. ftmon-2.0.0a1/src/ftmon/web/static/vendor/README.md +10 -0
  127. ftmon-2.0.0a1/src/ftmon/web/static/vendor/uPlot.LICENSE.txt +21 -0
  128. ftmon-2.0.0a1/src/ftmon/web/static/vendor/uPlot.iife.min.js +2 -0
  129. ftmon-2.0.0a1/src/ftmon/web/static/vendor/uPlot.min.css +1 -0
  130. ftmon-2.0.0a1/src/ftmon/web/templates/base.html +8 -0
  131. ftmon-2.0.0a1/src/ftmon/web/templates/dashboard.html +5 -0
  132. ftmon-2.0.0a1/src/ftmon/web/templates/events.html +1 -0
  133. ftmon-2.0.0a1/src/ftmon/web/templates/incident.html +4 -0
  134. ftmon-2.0.0a1/src/ftmon/web/templates/incident_rows.html +2 -0
  135. ftmon-2.0.0a1/src/ftmon/web/templates/incidents.html +1 -0
  136. ftmon-2.0.0a1/src/ftmon/web/templates/metrics.html +2 -0
  137. ftmon-2.0.0a1/src/ftmon/web/templates/monitors.html +2 -0
  138. ftmon-2.0.0a1/src/ftmon/web/templates/self.html +1 -0
  139. ftmon-2.0.0a1/src/ftmon/web/templates/trends.html +8 -0
  140. ftmon-2.0.0a1/tests/__init__.py +0 -0
  141. ftmon-2.0.0a1/tests/conftest.py +45 -0
  142. ftmon-2.0.0a1/tests/e2e/__init__.py +0 -0
  143. ftmon-2.0.0a1/tests/e2e/harness.py +149 -0
  144. ftmon-2.0.0a1/tests/e2e/test_daemon_e2e.py +297 -0
  145. ftmon-2.0.0a1/tests/e2e/test_demo_builder.py +28 -0
  146. ftmon-2.0.0a1/tests/e2e_real/__init__.py +1 -0
  147. ftmon-2.0.0a1/tests/e2e_real/test_real_system.py +144 -0
  148. ftmon-2.0.0a1/tests/e2e_real/test_server_deployment.py +116 -0
  149. ftmon-2.0.0a1/tests/extra_monitors/test_recipes.py +129 -0
  150. ftmon-2.0.0a1/tests/reqindex.json +189 -0
  151. ftmon-2.0.0a1/tests/traceability_pending.json +31 -0
  152. ftmon-2.0.0a1/tests/unit/__init__.py +0 -0
  153. ftmon-2.0.0a1/tests/unit/test_actions.py +118 -0
  154. ftmon-2.0.0a1/tests/unit/test_check_registry.py +122 -0
  155. ftmon-2.0.0a1/tests/unit/test_cli.py +384 -0
  156. ftmon-2.0.0a1/tests/unit/test_config_secrets.py +101 -0
  157. ftmon-2.0.0a1/tests/unit/test_core.py +101 -0
  158. ftmon-2.0.0a1/tests/unit/test_definitions.py +590 -0
  159. ftmon-2.0.0a1/tests/unit/test_demo.py +67 -0
  160. ftmon-2.0.0a1/tests/unit/test_doctor.py +70 -0
  161. ftmon-2.0.0a1/tests/unit/test_engine.py +339 -0
  162. ftmon-2.0.0a1/tests/unit/test_episodes.py +163 -0
  163. ftmon-2.0.0a1/tests/unit/test_events_engine.py +221 -0
  164. ftmon-2.0.0a1/tests/unit/test_expr_eval.py +140 -0
  165. ftmon-2.0.0a1/tests/unit/test_expr_parse.py +120 -0
  166. ftmon-2.0.0a1/tests/unit/test_external_checks.py +130 -0
  167. ftmon-2.0.0a1/tests/unit/test_external_integration.py +120 -0
  168. ftmon-2.0.0a1/tests/unit/test_external_sampler.py +155 -0
  169. ftmon-2.0.0a1/tests/unit/test_fixtures.py +114 -0
  170. ftmon-2.0.0a1/tests/unit/test_incidents.py +264 -0
  171. ftmon-2.0.0a1/tests/unit/test_m2_integration.py +191 -0
  172. ftmon-2.0.0a1/tests/unit/test_mcp.py +432 -0
  173. ftmon-2.0.0a1/tests/unit/test_notification_dispatch.py +247 -0
  174. ftmon-2.0.0a1/tests/unit/test_notify_remote.py +247 -0
  175. ftmon-2.0.0a1/tests/unit/test_packaging.py +130 -0
  176. ftmon-2.0.0a1/tests/unit/test_quiet.py +122 -0
  177. ftmon-2.0.0a1/tests/unit/test_retention.py +256 -0
  178. ftmon-2.0.0a1/tests/unit/test_samplers.py +596 -0
  179. ftmon-2.0.0a1/tests/unit/test_samplers_m3.py +111 -0
  180. ftmon-2.0.0a1/tests/unit/test_scenarios_m4.py +176 -0
  181. ftmon-2.0.0a1/tests/unit/test_store.py +418 -0
  182. ftmon-2.0.0a1/tests/unit/test_traceability.py +171 -0
  183. ftmon-2.0.0a1/tests/unit/test_trends.py +148 -0
  184. ftmon-2.0.0a1/tests/unit/test_tribool.py +88 -0
  185. ftmon-2.0.0a1/tests/unit/test_web.py +389 -0
  186. ftmon-2.0.0a1/tests/unit/test_web_demo.py +200 -0
  187. ftmon-2.0.0a1/tools/gen_reqindex.py +161 -0
  188. ftmon-2.0.0a1/uv.lock +1052 -0
@@ -0,0 +1,26 @@
1
+ # The same gate contributors run locally (README "Development" section).
2
+ # Deterministic by design: realsystem tests are deselected by pytest defaults.
3
+ name: CI
4
+
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ pull_request:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ matrix:
18
+ python-version: ["3.11", "3.13"]
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: astral-sh/setup-uv@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+ - run: uv sync --locked
25
+ - run: uv run ruff check src tests
26
+ - run: uv run pytest -q
@@ -0,0 +1,82 @@
1
+ # Tag-driven release: v* tags run the full gate, build sdist+wheel, publish
2
+ # to PyPI via Trusted Publishing (OIDC - no stored API tokens), then attach
3
+ # the artifacts to a GitHub Release. The PyPI side requires a (pending)
4
+ # trusted publisher registered for project "ftmon" with this repository,
5
+ # workflow file "release.yml", and environment "pypi".
6
+ name: Release
7
+
8
+ on:
9
+ push:
10
+ tags: ["v*"]
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ test:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: astral-sh/setup-uv@v5
21
+ - run: uv sync --locked
22
+ - run: uv run ruff check src tests
23
+ - run: uv run pytest -q
24
+
25
+ build:
26
+ needs: test
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ - uses: astral-sh/setup-uv@v5
31
+ - run: uv build
32
+ # The tag must match the built version so PyPI, the GitHub Release,
33
+ # and `ftmon --version` can never disagree.
34
+ - name: Check tag matches package version
35
+ run: |
36
+ version=$(uv run python -c "import ftmon; print(ftmon.__version__)")
37
+ test "v${version}" = "${GITHUB_REF_NAME}" || {
38
+ echo "tag ${GITHUB_REF_NAME} != package version v${version}" >&2
39
+ exit 1
40
+ }
41
+ - uses: actions/upload-artifact@v4
42
+ with:
43
+ name: dist
44
+ path: dist/
45
+
46
+ publish-pypi:
47
+ needs: build
48
+ runs-on: ubuntu-latest
49
+ environment:
50
+ name: pypi
51
+ url: https://pypi.org/p/ftmon
52
+ permissions:
53
+ id-token: write # OIDC token for PyPI Trusted Publishing
54
+ steps:
55
+ - uses: actions/download-artifact@v4
56
+ with:
57
+ name: dist
58
+ path: dist/
59
+ - uses: pypa/gh-action-pypi-publish@release/v1
60
+
61
+ github-release:
62
+ needs: publish-pypi
63
+ runs-on: ubuntu-latest
64
+ permissions:
65
+ contents: write
66
+ steps:
67
+ - uses: actions/download-artifact@v4
68
+ with:
69
+ name: dist
70
+ path: dist/
71
+ - name: Create GitHub Release
72
+ env:
73
+ GH_TOKEN: ${{ github.token }}
74
+ run: |
75
+ prerelease=""
76
+ case "${GITHUB_REF_NAME}" in
77
+ *a*|*b*|*rc*) prerelease="--prerelease" ;;
78
+ esac
79
+ gh release create "${GITHUB_REF_NAME}" dist/* \
80
+ --repo "${GITHUB_REPOSITORY}" \
81
+ --title "${GITHUB_REF_NAME}" \
82
+ --generate-notes ${prerelease}
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ dist/
7
+ *.egg-info/
8
+ .coverage
9
+ # Local AI-agent session state (tool permission grants), not project content.
10
+ .claude/
11
+ # The original GPLv2 Perl project is published on SourceForge. Keep any local
12
+ # reference checkout out of this MIT-licensed v2 repository.
13
+ ftmon-legacy/
14
+ ftmon_legacy/
@@ -0,0 +1,44 @@
1
+ # Repository Guidelines
2
+
3
+ > **Note to AI Agents:** The comprehensive, up-to-date instructions, architecture, and workflow for this repository are maintained in [CLAUDE.md](CLAUDE.md). Please read `CLAUDE.md` before proceeding with any code changes.
4
+
5
+ ## Project Structure & Module Organization
6
+
7
+ This repository contains FTMON v2. The original Perl implementation remains
8
+ separately published at <https://sourceforge.net/projects/ftmon/> and must not
9
+ be added to this repository.
10
+
11
+ - `SPEC.md` is the authoritative product and architecture specification for new work. Requirements have stable IDs and tests should reference them.
12
+ - `CLAUDE.md` summarizes the v2 architecture, commands, and the spec/traceability workflow for AI coding agents.
13
+ - `README.md` gives users and contributors the repository entry point.
14
+ - `docs/` contains the install guide, user manual, and definition reference.
15
+
16
+ New v2 code should follow `SPEC.md`: Python package code in `ftmon/`, tests in `tests/`, and user-editable monitor definitions as TOML.
17
+
18
+ ## Build, Test, and Development Commands
19
+
20
+ Use the checked-in `uv` environment for development and validation:
21
+
22
+ ```sh
23
+ uv sync
24
+ uv run ruff check src tests
25
+ uv run pytest -q
26
+ ```
27
+
28
+ ## Coding Style & Naming Conventions
29
+
30
+ For v2, follow `SPEC.md`: Python 3.11+, `uv`, `ruff`, `pytest`, SQLite, and TOML monitor definitions. Keep platform-specific logic behind the specified adapters.
31
+
32
+ ## Testing Guidelines
33
+
34
+ Add pytest coverage for all v2 behavior. Name tests by behavior, and include requirement IDs from `SPEC.md` where relevant, for example `test_daemon_rejects_second_instance_pm_02`. Prefer fixture-driven deterministic tests; keep real-system smoke tests opt-in.
35
+
36
+ ## Commit & Pull Request Guidelines
37
+
38
+ This repository has no existing commit history, so no local convention is established. Use concise, imperative commit subjects such as `Add daemon lock handling` or `Document legacy config validation`.
39
+
40
+ Pull requests should describe the change, reference affected `SPEC.md` requirement IDs, and list validation commands run. Include screenshots only for web UI changes.
41
+
42
+ ## Security & Configuration Tips
43
+
44
+ Monitor definitions must remain declarative TOML with restricted expression evaluation, as specified in `SPEC.md`.
@@ -0,0 +1,118 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Overview
6
+
7
+ FTMON v2 is a lightweight, local-first, single-host systems monitor for Linux
8
+ (Python ≥ 3.11, MIT). It detects memory leaks, CPU hogs, disks filling, service
9
+ failures, and notable journal events; keeps bounded metric history in SQLite;
10
+ and exposes a CLI, a loopback-only web dashboard, and a local stdio MCP server.
11
+
12
+ It succeeds a GPL-licensed Perl monitoring engine from 2001–2003, porting its
13
+ design ideas (delta/monotonic calcs, consecutive-cycle confirmation,
14
+ baselining, escalation) but none of its code. The original source is
15
+ intentionally kept out of this repository (a local reference checkout may sit
16
+ in the gitignored `ftmon-legacy/`) and remains available at
17
+ <https://sourceforge.net/projects/ftmon/>. Do not vendor it here — the MIT/GPL
18
+ boundary is deliberate.
19
+
20
+ ## Commands
21
+
22
+ Everything runs through `uv`:
23
+
24
+ ```sh
25
+ uv sync # install/refresh the environment
26
+ uv run ruff check src tests # lint (ruff is also the formatter; line length 100)
27
+ uv run pytest -q # full gate: unit + e2e + traceability (~20 s)
28
+ uv run pytest tests/unit/test_expr_eval.py -q # one file
29
+ uv run ftmon init --profile desktop|server # write config + builtin definitions
30
+ uv run ftmon check # one-shot sample/evaluate
31
+ uv run ftmon daemon # the monitor loop
32
+ uv run ftmon web # dashboard on http://127.0.0.1:8420/ (loopback only)
33
+ uv run ftmon doctor # DB/config diagnostics
34
+ python3 tools/gen_reqindex.py --check # regenerate/verify tests/reqindex.json from SPEC.md
35
+ ```
36
+
37
+ Real-system smoke tests are opt-in (deselected by default); the CI suite is
38
+ deterministic and fixture-driven.
39
+
40
+ ## Spec-driven workflow (the central process fact)
41
+
42
+ `SPEC.md` is the authoritative product spec; `DESIGN.md` is the companion
43
+ design (elements marked FROZEN must not be changed without amending the doc
44
+ first). Every requirement has a stable ID like `SA-06`, `IN-03`, `EC-02`.
45
+
46
+ Traceability is machine-enforced by `tests/unit/test_traceability.py` (TS-01):
47
+
48
+ - Tests cite the requirement IDs they verify in **bracketed docstrings**:
49
+ `"""[EX-06] ..."""`.
50
+ - `tests/reqindex.json` is generated from SPEC.md by `tools/gen_reqindex.py`
51
+ and must match on regeneration; `tests/traceability_pending.json` lists
52
+ testable IDs not yet covered (a ratchet — an ID can't be both covered and
53
+ pending).
54
+ - Adding/changing a requirement ⇒ regenerate the index. Covering a pending
55
+ ID ⇒ remove it from the pending list. `NG-*`/`DO-*` IDs are exempt.
56
+
57
+ When you land a user-visible change, updating the matching docs
58
+ (`docs/manual.md`, `docs/install.md`, `docs/definitions.md`) is part of the
59
+ work package, not a follow-up. See `CONTRIBUTING.md` — its prime rule:
60
+ comments/docstrings record **why** (constraint, trade-off, spec ID), never
61
+ narrate mechanics. Every module docstring cites the spec IDs that shaped it.
62
+
63
+ ## Architecture
64
+
65
+ Package layout (full annotated tree in DESIGN.md §1):
66
+
67
+ - `src/ftmon/expr/` — restricted expression language (parse → IR → eval).
68
+ **Stdlib-only, imports nothing from `ftmon.*`** (EX-04); `eval` never raises
69
+ (EX-06) — errors become `None` plus a counter tick. Three-valued logic in
70
+ `tribool.py`.
71
+ - `src/ftmon/definitions/` — TOML monitor definitions: `schema.py` validator,
72
+ `loader.py` (TOML → `MonitorDef`, topo-sorted derived metrics), and
73
+ `builtins/*.toml` package data. **Definitions are data, never code.** The
74
+ normative copies live in `design/builtins/*.toml` and are mirrored into
75
+ `src/ftmon/definitions/builtins/`; keep the two trees identical.
76
+ - `src/ftmon/sources/` — `Sampler`/`EventSource` implementations (process,
77
+ disk, system, net, unit, journald) plus deterministic `fixtures.py` fakes
78
+ that ship in the prod package and keep the platform seams honest (PL-04).
79
+ - `src/ftmon/checks/` — administrator-registered external checks: `registry.py`
80
+ (argv authority), `runner.py` (no shell, scrubbed env, process-group kill,
81
+ bounded output), Nagios/FTMON-JSON adapters. AI/definitions may reference a
82
+ check alias but can never create one (EC-01).
83
+ - `src/ftmon/engine/` — scheduler tick loop, per-monitor `pipeline.py`
84
+ (snapshot → rings → derived → rules), ring buffers, `incidents.py` (pure
85
+ state machine, FROZEN), episodes, effects/actions.
86
+ - `src/ftmon/store/` — SQLite (WAL, incremental autovacuum): migrations gated
87
+ by `PRAGMA user_version`, daemon-side batched `writer.py`, shared `query.py`,
88
+ retention/rollups, durable notification `outbox.py`.
89
+ - `src/ftmon/notify/` — desktop/file/ntfy/webhook/SMTP adapters with
90
+ independent durable retry.
91
+ - `daemon.py` (composition root; owns the only bulk-write connection),
92
+ `cli.py`, `mcp_server.py`, `web/` (operational app + isolated synthetic demo
93
+ app), `demo.py`, `selfmon.py`, `systemd/` units.
94
+
95
+ Key invariants:
96
+
97
+ - **No direct time access** — `time.time`/`datetime.now`/`time.sleep` only in
98
+ `clock.py` (TS-03); everything else takes an injected `Clock`.
99
+ - Platform-specific behavior lives behind exactly four seams: samplers, event
100
+ sources, notification adapter, paths/service wrapper (PL-01). No platform
101
+ conditionals elsewhere.
102
+ - The web UI binds to 127.0.0.1 only; no auth exists by design (NG-05) — never
103
+ add a non-loopback listener.
104
+ - Core model types are frozen dataclasses; the pipeline is pure-ish data flow
105
+ so incidents can be tested independently.
106
+ - Respect SPEC §1.1 non-goals (fleet monitoring, plugin loading, etc.) — they
107
+ are enforced scope, not TODOs.
108
+
109
+ ## Conventions
110
+
111
+ - Lint rules are enforced as tests; `uv run pytest -q` is the gate for
112
+ everything.
113
+ - Name tests by behavior and requirement, e.g.
114
+ `test_daemon_rejects_second_instance_pm_02`.
115
+ - Commit subjects are concise and imperative, often milestone-prefixed
116
+ (`M9: add bounded external checks`, `Docs: ...`).
117
+ - `dist/`, `.venv/`, caches, and `ftmon-legacy/` are gitignored; don't commit
118
+ build artifacts or the legacy tree.
@@ -0,0 +1,58 @@
1
+ # Contributing / Documentation Standard
2
+
3
+ This project is built spec-first and implemented partly by AI models working
4
+ from frozen contracts. Documentation is how rationale survives that process.
5
+ These rules are binding for all code, human- or model-written.
6
+
7
+ ## The prime rule: document the WHY
8
+
9
+ Code shows *what* it does; comments and docstrings exist to record what the
10
+ code cannot show — **why it is this way**: the constraint, the trade-off, the
11
+ rejected alternative, the spec requirement. A comment that restates the next
12
+ line is noise and will be removed in review.
13
+
14
+ Bad: `# loop over the entities`
15
+ Good: `# deadline is checked between entities, not inside psutil calls -
16
+ a stuck native call cannot be interrupted in-process (SA-02)`
17
+
18
+ ## Docstrings
19
+
20
+ - **Every module** starts with a docstring: one line of purpose, then the
21
+ rationale/constraints that shaped it, citing SPEC requirement IDs in
22
+ parentheses — e.g. `(EX-04)`, `(PM-06)`. The IDs are load-bearing: they let
23
+ a reader jump from code to the reasoning in SPEC.md/DESIGN.md.
24
+ - **Every public class/function** gets a docstring stating behavior at the
25
+ contract level (inputs, outputs, error behavior, None semantics). Private
26
+ helpers need one only when their reason for existing is non-obvious.
27
+ - **Every test** carries the requirement ID(s) it verifies in its docstring,
28
+ bracketed: `"""[EX-06] Three-valued semantics..."""` — the traceability
29
+ tooling (TS-01) depends on this.
30
+
31
+ ## Comments
32
+
33
+ - Explain invariants the type system cannot express ("oldest first",
34
+ "one transaction per tick, PM-03").
35
+ - Explain deliberate omissions ("per-process attribution deferred, NG-06").
36
+ - Never narrate mechanics, never leave commented-out code, never write
37
+ comments addressed to a reviewer about the change itself.
38
+
39
+ ## Documentation deliverables (SPEC section 17)
40
+
41
+ | Doc | Audience | Grows |
42
+ | --- | --- | --- |
43
+ | `docs/manual.md` (DO-04) | end users - install, concepts, daily use, tuning | one chapter per milestone; placeholders are marked |
44
+ | `docs/definitions.md` (DO-01) | monitor authors, human or AI (also the MCP resource) | frozen with the language |
45
+ | `docs/install.md` (DO-02) | operators | M6 |
46
+ | `--help` text (DO-03) | CLI users | with each subcommand |
47
+ | SPEC.md / DESIGN.md | maintainers | change-controlled, changelog required |
48
+
49
+ When you land a user-visible feature, updating the manual chapter is part of
50
+ the work package, not a follow-up.
51
+
52
+ ## Style
53
+
54
+ Python >= 3.11, ruff (line length 100) is the linter and formatter authority.
55
+ No direct `time.time`/`datetime.now`/`time.sleep` outside `clock.py` (TS-03).
56
+ The `expr/` package imports stdlib only (EX-04). Layering rules: DESIGN.md
57
+ section 1. All lint rules are enforced as tests - `uv run pytest tests -q`
58
+ is the gate.