Merge origin/main into main
Reconcile the staged-projection and Diagnostics feature work with the CI quality-gate changes from origin/main. The origin side of every conflict was lint-only, so the feature imports were kept and the ruff fixes re-applied: - keep the branch's added imports (timefmt, activity_readout, projection ladder helpers) and drop the unused ones ruff reported - restore `noqa: E402` on the post-bootstrap imports in fenris-monitor and keep the `noqa: F401` re-exports in the TUI that tests assert on - make the TUI monitoring-glyph test clock-relative instead of pinned to a fixed date
This commit is contained in:
@@ -27,7 +27,7 @@ if VENV_DIR.exists():
|
||||
if site_packages:
|
||||
sys.path.insert(0, str(site_packages))
|
||||
|
||||
from fenris.collector import run_collection
|
||||
from fenris.collector import run_collection # noqa: E402 -- must follow the sys.path bootstrap above
|
||||
|
||||
|
||||
CONFIG_PATH = Path("/etc/fenris/fenris.conf")
|
||||
|
||||
@@ -9,7 +9,7 @@ usage-habit classification is known rather than unknown (§5.3).
|
||||
"""
|
||||
import sqlite3
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -12,7 +12,7 @@ No proportional allocation, endpoint assignment, or double counting.
|
||||
"""
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .hour_classify import classify_hour, HourSplit
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ Runit guarantees:
|
||||
Spec: §8.4, §8.5, §8.6, §8.7, §8.8, ADR 0008
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from enum import Enum
|
||||
|
||||
@@ -18,11 +18,10 @@ import logging
|
||||
import sqlite3
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .hour_classify import classify_hour
|
||||
from .segment import open_segment, normalize_identity
|
||||
from .monitoring_periods import close_period
|
||||
from .segment import open_segment
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -394,7 +394,7 @@ def repair_legacy_local_day_evidence(conn: sqlite3.Connection) -> int:
|
||||
"bytes_written": row[2], "bytes_read": row[3],
|
||||
"segment_id": row[4], "local_tz": row[5],
|
||||
}
|
||||
for row in conn.execute(sample_select)
|
||||
for row in conn.execute(sample_select) # nosemgrep: sqlalchemy-execute-raw-query -- query text is built from constant fragments only; no external input
|
||||
]
|
||||
for previous, current in pairwise(samples):
|
||||
start = previous["ts"]
|
||||
@@ -1018,7 +1018,7 @@ def query_local_day_summary(
|
||||
"""
|
||||
tz_filter = " AND tz_name = ?" if tz_name else ""
|
||||
params = (local_date, tz_name) if tz_name else (local_date,)
|
||||
row = conn.execute(
|
||||
row = conn.execute( # nosemgrep: sqlalchemy-execute-raw-query -- only constant fragments are concatenated; values are bound via ? placeholders
|
||||
"SELECT local_date, tz_name, tz_offset, utc_start, utc_end, "
|
||||
" bytes_written, bytes_read, coverage, sample_count, complete, "
|
||||
" activity_seconds, activity_intervals, activity_incomplete, "
|
||||
|
||||
@@ -31,14 +31,14 @@ if VENV_DIR.exists():
|
||||
if site_packages:
|
||||
sys.path.insert(0, str(site_packages))
|
||||
|
||||
from fenris.store import init_store, get_store_path
|
||||
from fenris.timefmt import local_timestamp, local_zone_name
|
||||
from fenris.monitoring_periods import (
|
||||
from fenris.store import init_store # noqa: E402 -- must follow the sys.path bootstrap above
|
||||
from fenris.timefmt import local_timestamp, local_zone_name # noqa: E402 -- sys.path bootstrap above
|
||||
from fenris.monitoring_periods import ( # noqa: E402 -- sys.path bootstrap above
|
||||
ensure_period_open,
|
||||
close_period,
|
||||
get_open_period,
|
||||
)
|
||||
from fenris.init_system import (
|
||||
from fenris.init_system import ( # noqa: E402 -- sys.path bootstrap above
|
||||
enable_timer,
|
||||
disable_timer,
|
||||
collect_now,
|
||||
|
||||
@@ -29,9 +29,9 @@ Criteria: PR-1–PR-17, CI-4.
|
||||
"""
|
||||
import sqlite3
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .monitoring_periods import interval_within_one_monitoring_period
|
||||
|
||||
|
||||
@@ -336,7 +336,7 @@ def prune_old_samples(
|
||||
if owns_transaction:
|
||||
conn.execute("BEGIN IMMEDIATE")
|
||||
else:
|
||||
conn.execute(f"SAVEPOINT {savepoint}")
|
||||
conn.execute(f"SAVEPOINT {savepoint}") # nosemgrep: sqlalchemy-execute-raw-query, formatted-sql-query -- savepoint name is a function-local constant; SQLite cannot bind identifiers
|
||||
|
||||
try:
|
||||
rows = _sample_rows(conn)
|
||||
@@ -344,7 +344,7 @@ def prune_old_samples(
|
||||
if owns_transaction:
|
||||
conn.commit()
|
||||
else:
|
||||
conn.execute(f"RELEASE SAVEPOINT {savepoint}")
|
||||
conn.execute(f"RELEASE SAVEPOINT {savepoint}") # nosemgrep: sqlalchemy-execute-raw-query, formatted-sql-query -- savepoint name is a function-local constant; SQLite cannot bind identifiers
|
||||
return 0
|
||||
|
||||
newest_id = rows[-1][0]
|
||||
@@ -361,12 +361,12 @@ def prune_old_samples(
|
||||
if owns_transaction:
|
||||
conn.commit()
|
||||
else:
|
||||
conn.execute(f"RELEASE SAVEPOINT {savepoint}")
|
||||
conn.execute(f"RELEASE SAVEPOINT {savepoint}") # nosemgrep: sqlalchemy-execute-raw-query, formatted-sql-query -- savepoint name is a function-local constant; SQLite cannot bind identifiers
|
||||
return len(expired)
|
||||
except Exception:
|
||||
if owns_transaction:
|
||||
conn.rollback()
|
||||
else:
|
||||
conn.execute(f"ROLLBACK TO SAVEPOINT {savepoint}")
|
||||
conn.execute(f"RELEASE SAVEPOINT {savepoint}")
|
||||
conn.execute(f"ROLLBACK TO SAVEPOINT {savepoint}") # nosemgrep: sqlalchemy-execute-raw-query, formatted-sql-query -- savepoint name is a function-local constant; SQLite cannot bind identifiers
|
||||
conn.execute(f"RELEASE SAVEPOINT {savepoint}") # nosemgrep: sqlalchemy-execute-raw-query, formatted-sql-query -- savepoint name is a function-local constant; SQLite cannot bind identifiers
|
||||
raise
|
||||
|
||||
@@ -13,11 +13,11 @@ Contracts:
|
||||
"""
|
||||
import logging
|
||||
import sqlite3
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
from .derive import find_previous_sample, derive_hours_from_interval, _parse_ts
|
||||
from .derive import derive_hours_from_interval, _parse_ts
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import sqlite3
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
from .collector import normalize_identity
|
||||
|
||||
|
||||
def find_current_segment(conn: sqlite3.Connection) -> Optional[Dict[str, Any]]:
|
||||
|
||||
@@ -15,8 +15,7 @@ Criteria: LC-9, CI-2, CI-4, FL-4, FL-5, FL-7.
|
||||
"""
|
||||
import sqlite3
|
||||
from contextlib import contextmanager
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Iterator, List, Optional, Tuple, TYPE_CHECKING
|
||||
|
||||
@@ -24,7 +23,7 @@ if TYPE_CHECKING:
|
||||
from .status_composition import StatusComposition
|
||||
|
||||
from .projection import (
|
||||
compute_projection, ConfidenceState, DISCLOSURES, ProjectionStage, ladder_count_text,
|
||||
compute_projection, DISCLOSURES, ProjectionStage, ladder_count_text,
|
||||
ladder_lines, write_rate_gb_day,
|
||||
)
|
||||
from .store import SCHEMA_VERSION
|
||||
|
||||
@@ -11,24 +11,19 @@ Freshness grading uses shared constants from status.py.
|
||||
import enum
|
||||
import sqlite3
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
# Status poll interval (AC78-6): lightweight 5s systemctl show poll
|
||||
STATUS_POLL_INTERVAL_S = 5
|
||||
|
||||
from .timefmt import freshness_age_human
|
||||
from .status import (
|
||||
FRESH_THRESHOLD_S,
|
||||
STALENESS_THRESHOLD_S,
|
||||
grade_freshness,
|
||||
is_deliberately_paused,
|
||||
monitoring_continuity,
|
||||
deliberate_pause_lines,
|
||||
open_store_readonly,
|
||||
StoreFault,
|
||||
NewerSchema,
|
||||
)
|
||||
from .timefmt import freshness_age_human
|
||||
|
||||
# Status poll interval (AC78-6): lightweight 5s systemctl show poll
|
||||
STATUS_POLL_INTERVAL_S = 5
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
+5
-6
@@ -7,7 +7,6 @@ This module handles:
|
||||
"""
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# Schema version - increment on each migration
|
||||
@@ -77,7 +76,7 @@ def init_store(store_path: Path) -> sqlite3.Connection:
|
||||
if current_version == 0:
|
||||
# New database - create schema
|
||||
_create_schema(conn)
|
||||
conn.execute(f"PRAGMA user_version={SCHEMA_VERSION}")
|
||||
conn.execute(f"PRAGMA user_version={SCHEMA_VERSION}") # nosemgrep: sqlalchemy-execute-raw-query, formatted-sql-query -- SCHEMA_VERSION is an int constant; PRAGMA cannot bind parameters
|
||||
conn.commit()
|
||||
elif current_version > SCHEMA_VERSION:
|
||||
# Unknown newer version - refuse
|
||||
@@ -339,7 +338,7 @@ def _apply_migrations(conn: sqlite3.Connection, current_version: int):
|
||||
conn.execute("BEGIN IMMEDIATE")
|
||||
try:
|
||||
migration(conn)
|
||||
conn.execute(f"PRAGMA user_version={target_version}")
|
||||
conn.execute(f"PRAGMA user_version={target_version}") # nosemgrep: sqlalchemy-execute-raw-query, formatted-sql-query -- target_version is an int key of the static migrations map; PRAGMA cannot bind parameters
|
||||
conn.commit()
|
||||
except Exception:
|
||||
conn.rollback()
|
||||
@@ -364,7 +363,7 @@ def _migrate_1_to_2(conn: sqlite3.Connection) -> None:
|
||||
).fetchall()}
|
||||
for column in ("unattributed_bytes_written", "unattributed_bytes_read"):
|
||||
if column not in cols:
|
||||
conn.execute(
|
||||
conn.execute( # nosemgrep: sqlalchemy-execute-raw-query, formatted-sql-query -- column comes from a hardcoded tuple; DDL cannot bind identifiers
|
||||
f"ALTER TABLE day_aggregates ADD COLUMN {column} INTEGER DEFAULT 0"
|
||||
)
|
||||
|
||||
@@ -421,7 +420,7 @@ def _migrate_4_to_5(conn: sqlite3.Connection) -> None:
|
||||
("last_sample_id", "INTEGER"),
|
||||
):
|
||||
if column not in local_cols:
|
||||
conn.execute(
|
||||
conn.execute( # nosemgrep: sqlalchemy-execute-raw-query, formatted-sql-query -- column and declaration come from a hardcoded tuple; DDL cannot bind identifiers
|
||||
f"ALTER TABLE local_days ADD COLUMN {column} {declaration}"
|
||||
)
|
||||
_create_local_day_shared_evidence(conn)
|
||||
@@ -479,7 +478,7 @@ def migrate_to_latest(store_path: Path) -> int:
|
||||
# Version 0 means no schema — create fresh (issue #73)
|
||||
if current_version == 0:
|
||||
_create_schema(conn)
|
||||
conn.execute(f"PRAGMA user_version={SCHEMA_VERSION}")
|
||||
conn.execute(f"PRAGMA user_version={SCHEMA_VERSION}") # nosemgrep: sqlalchemy-execute-raw-query, formatted-sql-query -- SCHEMA_VERSION is an int constant; PRAGMA cannot bind parameters
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return SCHEMA_VERSION
|
||||
|
||||
+4
-7
@@ -15,7 +15,6 @@ from __future__ import annotations
|
||||
import json
|
||||
import math
|
||||
import sqlite3
|
||||
import sys
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
@@ -26,7 +25,7 @@ from rich.markup import escape
|
||||
from rich.text import Text
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.binding import Binding
|
||||
from textual.containers import Container, Horizontal, VerticalScroll
|
||||
from textual.containers import Container, VerticalScroll
|
||||
from textual.screen import ModalScreen
|
||||
from textual.message import Message
|
||||
from textual.widget import Widget
|
||||
@@ -56,7 +55,6 @@ from .projection import (
|
||||
ConfidenceState,
|
||||
ProjectionResult,
|
||||
ProjectionStage,
|
||||
ScenarioRange,
|
||||
compute_projection,
|
||||
ladder_count_text,
|
||||
ladder_lines,
|
||||
@@ -64,10 +62,10 @@ from .projection import (
|
||||
)
|
||||
from .status import (
|
||||
CADENCE_DEFAULT_S,
|
||||
FRESH_THRESHOLD_S,
|
||||
STALENESS_THRESHOLD_S,
|
||||
FRESH_THRESHOLD_S, # noqa: F401 -- re-exported; tests/test_acceptance_sweep.py asserts parity with status
|
||||
STALENESS_THRESHOLD_S, # noqa: F401 -- re-exported (see above)
|
||||
format_disclosures,
|
||||
grade_freshness,
|
||||
grade_freshness, # noqa: F401 -- re-exported (see above)
|
||||
deliberate_pause_lines,
|
||||
read_status,
|
||||
_journalctl_hint,
|
||||
@@ -75,7 +73,6 @@ from .status import (
|
||||
from .status_composition import (
|
||||
StatusComposition,
|
||||
render_status_tui,
|
||||
STATUS_POLL_INTERVAL_S,
|
||||
)
|
||||
from .control import MONITOR_HELPER, MonitorError, run_monitor
|
||||
from .derive import _parse_ts
|
||||
|
||||
@@ -5,7 +5,7 @@ computation used by local-day derivation. All functions are
|
||||
stateless and safe to call from the collector and TUI reader.
|
||||
"""
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user