Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,16 @@ jobs:
# BOTH platforms on the cheap Linux leg (below). Running them on the 2x-billed Windows legs was
# duplicate cost, so gate them to Linux. The Windows legs keep pytest — the one check whose
# result genuinely differs by OS (real sockets, ProactorEventLoop, Windows service paths).
# `.` — the WHOLE repo, matching the format check below and the pre-commit ruff hook, which has no
# `exclude` and so lints every changed Python file. This was an explicit allow-list of six paths,
# and the mismatch was not academic: harness/ (73 findings), tee/ (12), samples/ and docker/ were
# linted by the hook and by nothing in CI, so touching any file there blocked the commit on errors
# CI could not see and no CI run could ever have reported. Scope now lives in ONE place —
# pyproject's [tool.ruff] extend-exclude — instead of being re-stated, differently, per tool.
# tests/test_lint_scope_parity.py fails if the two drift apart again.
- name: Lint (ruff)
if: (needs.changes.outputs.code == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch') && runner.os == 'Linux'
run: ruff check messagefoundry messagefoundry_webconsole tests packaging/messagefoundry-webconsole scripts/webconsole_seam_snapshot.py scripts/docs/backlog_status_check.py scripts/tray/make_icons.py
run: ruff check .

- name: Format check (ruff)
if: (needs.changes.outputs.code == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch') && runner.os == 'Linux'
Expand Down
15 changes: 14 additions & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,20 @@ jobs:
# `tee/` is in-tree vendored SOUP (a standalone relay) — scanned to the SAME bar as the
# engine. Its one urllib GET (tee/mefor_api.py) is per-line `# nosec B310`: the URL scheme is
# fixed by operator config (default localhost), never message-derived.
bandit -r messagefoundry tee --skip B101,B110,B311,B404,B608
#
# SCOPE = the whole repo minus the excludes below, which mirrors the pre-commit bandit hook
# (.pre-commit-config.yaml) EXACTLY. It was `-r messagefoundry tee` while the hook scanned
# everything except tests/harness/samples — so scripts/ (security tooling, subprocess-heavy)
# was gated locally and by nothing in CI. Touching a file there failed the commit on findings
# no CI run could report. Widening cost nothing: the repo is already clean at this bar.
# tests/harness/samples — intentional non-production idioms (asserts, synthetic-data RNG)
# packaging/messagefoundry-webconsole/tests — the same, one directory deeper: 20 B105/B106
# "hardcoded password" hits that are all literal test credentials
# ide/ — TypeScript; no Python to scan
# docs/benchmarks/results — archived measurement artifacts, not maintained source
# tests/test_lint_scope_parity.py fails if this and the hook drift apart again.
bandit -r . --skip B101,B110,B311,B404,B608 \
--exclude ./tests,./harness,./samples,./ide,./docs/benchmarks/results,./packaging/messagefoundry-webconsole/tests,./.venv,./node_modules

gitleaks:
name: gitleaks (secret scan)
Expand Down
13 changes: 10 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,18 @@ repos:
hooks:
- id: gitleaks

# Python SAST — same test set + skips as the CI bandit job (security.yml). Engine code only;
# tests/harness/samples carry intentional non-production idioms (asserts, synthetic-data RNG).
# Python SAST — the SAME skips AND the same excluded paths as the CI bandit job (security.yml).
# It said "same ... as CI" before and was not: CI scanned `-r messagefoundry tee` while this hook
# scanned everything but tests/harness/samples, so scripts/ was gated here and by nothing in CI —
# a commit could fail on findings no CI run would ever report. Both sides now name one list, and
# tests/test_lint_scope_parity.py fails if they drift.
# tests/harness/samples — intentional non-production idioms (asserts, synthetic-data RNG)
# packaging/messagefoundry-webconsole/tests — the same, one directory deeper (literal test creds)
# ide/ — TypeScript; no Python to scan
# docs/benchmarks/results — archived measurement artifacts, not maintained source
- repo: https://github.com/PyCQA/bandit
rev: 1.9.4
hooks:
- id: bandit
args: ["--skip", "B101,B110,B311,B404,B608"]
exclude: ^(tests/|harness/|samples/)
exclude: ^(tests/|harness/|samples/|ide/|docs/benchmarks/results/|packaging/messagefoundry-webconsole/tests/)
2 changes: 1 addition & 1 deletion docker/smoke/config/IB_Test_ADT.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
reaches a terminal PROCESSED disposition; a non-ADT message would be logged UNROUTED.
"""

from messagefoundry import File, MLLP, Send, handler, inbound, outbound, router
from messagefoundry import MLLP, File, Send, handler, inbound, outbound, router

inbound("IB_Test_ADT", MLLP(port=2575), router="adt_router")
outbound("FILE-OUT_Test_ADT", File(directory="/var/lib/mefor/out/adt", filename="{MSH-10}.hl7"))
Expand Down
5 changes: 2 additions & 3 deletions docker/smoke/send_adt.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from __future__ import annotations

import asyncio
import contextlib
import sys

from messagefoundry.transports.mllp import MLLPDecoder, frame
Expand Down Expand Up @@ -37,10 +38,8 @@ async def _send(host: str, port: int, payload: str, timeout: float) -> bytes:
return message
finally:
writer.close()
try:
with contextlib.suppress(OSError):
await writer.wait_closed()
except OSError:
pass


def main() -> int:
Expand Down
21 changes: 9 additions & 12 deletions harness/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@

import argparse
import sys
from datetime import UTC


def main(argv: list[str] | None = None) -> int:
Expand All @@ -94,7 +95,7 @@ def main(argv: list[str] | None = None) -> int:
# CI use. Force UTF-8 on the CLI streams — best-effort, since a pytest/redirect wrapper may
# not support reconfigure.
for _stream in (sys.stdout, sys.stderr):
try:
try: # noqa: SIM105 - suppress() would add an import to satisfy a pure style preference
_stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[attr-defined]
except (AttributeError, ValueError, OSError):
pass
Expand Down Expand Up @@ -268,8 +269,8 @@ def _list_scenarios() -> int:


def _run_scenario(name: str, engine_url: str, token: str | None, timeout: float) -> int:
from messagefoundry.apiclient import ApiError, EngineClient
from harness.scenarios import SCENARIOS, run_scenario
from messagefoundry.apiclient import ApiError, EngineClient

scenario = SCENARIOS.get(name)
if scenario is None:
Expand Down Expand Up @@ -302,11 +303,10 @@ def _run_load(args: argparse.Namespace) -> int:
import time
from pathlib import Path

from messagefoundry.apiclient import ApiError

from harness.load.profile import LoadProfileError, get_profile
from harness.load.report import compare_to_baseline
from harness.load.runner import PreflightError, run_load
from messagefoundry.apiclient import ApiError

try:
profile = get_profile(args.load)
Expand Down Expand Up @@ -1249,10 +1249,9 @@ def _run_shardcert_driver(argv: list[str]) -> int:
import json
from pathlib import Path

from messagefoundry.apiclient import ApiError

from harness.load.coord import CoordTimeout, FileDropCoord
from harness.load.shardcert import run_shardcert_driver
from messagefoundry.apiclient import ApiError

coord = _coord_from_args(args)
assert isinstance(coord, FileDropCoord)
Expand Down Expand Up @@ -1523,10 +1522,9 @@ def _run_shardcert_drive(argv: list[str]) -> int:
parser.add_argument("--report-json", help="write the JSON report to this path")
args = parser.parse_args(argv)

from messagefoundry.apiclient import ApiError

from harness.load.coord import CoordTimeout, FileDropCoord
from harness.load.shardcert import run_shardcert_drive
from messagefoundry.apiclient import ApiError

coord = _coord_from_args(args)
assert isinstance(coord, FileDropCoord)
Expand Down Expand Up @@ -1776,7 +1774,7 @@ def _run_shardcert_engine_ladder(argv: list[str]) -> int:

def _run_shardcert_drive_ladder(argv: list[str]) -> int:
import asyncio
from datetime import datetime, timezone
from datetime import datetime

parser = argparse.ArgumentParser(
prog="harness shardcert-drive-ladder",
Expand Down Expand Up @@ -1868,11 +1866,10 @@ def _run_shardcert_drive_ladder(argv: list[str]) -> int:
parser.add_argument("--report-json", help="write the consolidated JSON report to this path")
args = parser.parse_args(argv)

from messagefoundry.apiclient import ApiError

from harness.load.coord import CoordTimeout, FileDropCoord
from harness.load.shardcert import parse_rate_ladder
from harness.load.shardcert_ladder import run_drive_ladder
from messagefoundry.apiclient import ApiError

try:
rates = parse_rate_ladder(args.rate_ladder)
Expand Down Expand Up @@ -1936,7 +1933,7 @@ def _run_shardcert_drive_ladder(argv: list[str]) -> int:
# P2: the raw --rate-ladder string (the invocation's primary independent variable, and the value
# that must match the engine box verbatim) so the consolidated JSON is self-describing.
"rate_ladder": args.rate_ladder,
"generated_at": datetime.now(timezone.utc).isoformat(),
"generated_at": datetime.now(UTC).isoformat(),
"commit_sha": _git_commit_sha(),
}
# D5 P1: parent-mkdir + never raise on a bad path (a completed run's exit_code must still return).
Expand Down
3 changes: 1 addition & 2 deletions harness/_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
QWidget,
)

from messagefoundry.apiclient import ApiError, EngineClient

from harness._console_widgets import ERROR_COLOR
from messagefoundry.apiclient import ApiError, EngineClient


class LoginDialog(QDialog):
Expand Down
2 changes: 1 addition & 1 deletion harness/acceptance/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

def main(argv: list[str] | None = None) -> int:
for _stream in (sys.stdout, sys.stderr):
try:
try: # noqa: SIM105 - suppress() would add an import to satisfy a pure style preference
_stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[attr-defined]
except (AttributeError, ValueError, OSError):
pass
Expand Down
6 changes: 3 additions & 3 deletions harness/acceptance/matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from enum import StrEnum


class Status(str, Enum):
class Status(StrEnum):
"""Outcome of a single matrix row. ``str`` mix-in so it serialises as its value."""

PASS = "PASS"
Expand All @@ -36,7 +36,7 @@ class Status(str, Enum):
ERROR = "ERROR" # the check itself broke (bad node id, probe raised)


class Coverage(str, Enum):
class Coverage(StrEnum):
"""How a row is exercised."""

PROBE = "probe"
Expand Down
2 changes: 1 addition & 1 deletion harness/acceptance/probes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
import socket
import sys
import tempfile
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
from typing import Callable

from harness.acceptance.matrix import Status

Expand Down
2 changes: 1 addition & 1 deletion harness/acceptance/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
import csv
import io
from collections import Counter
from collections.abc import Sequence
from pathlib import Path
from typing import Sequence

from harness._spreadsheet import (
SPREADSHEET_FORMULA_TRIGGERS,
Expand Down
2 changes: 1 addition & 1 deletion harness/acceptance/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
import subprocess
import sys
import tempfile
from collections.abc import Callable, Iterable, Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Iterable, Sequence
from xml.etree import ElementTree

from harness.acceptance.matrix import MATRIX, Coverage, MatrixRow, Status
Expand Down
8 changes: 5 additions & 3 deletions harness/compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@
)

from harness._console_widgets import ConfigurableTable
from messagefoundry.generators import _core
from messagefoundry.generators import all_types # noqa: F401 (registers the built-in message types)
from messagefoundry.parsing import HL7PeekError, Peek, normalize
from harness.file_transport import DropResult, FileDropWorker
from harness.mllp import SendItem, SendResult, SendWorker
from messagefoundry.generators import (
_core,
all_types, # noqa: F401 (registers the built-in message types)
)
from messagefoundry.parsing import HL7PeekError, Peek, normalize

_COLUMNS = ["Time", "Transport", "Result", "Expected", "OK", "Error"]
_ACCEPT, _REJECT, _NONE = "Accept (AA/CA)", "Reject (AE/AR)", "No ACK"
Expand Down
3 changes: 1 addition & 2 deletions harness/config/connscale/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@

from __future__ import annotations

from harness.config.connscale._shape import EDIT, ConnScaleShape, load_connscale_shape
from messagefoundry import MLLP, Send, handler, inbound, outbound, router
from messagefoundry.config.models import RetryPolicy
from messagefoundry.config.wiring import HandlerFn, RouterFn
from messagefoundry.parsing.message import Message, RawMessage

from harness.config.connscale._shape import EDIT, ConnScaleShape, load_connscale_shape

_SHAPE = load_connscale_shape()

# A few attempts with brief backoff: the sink always AA's, so retries shouldn't fire — but a transient
Expand Down
3 changes: 1 addition & 2 deletions harness/config/load/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@

from __future__ import annotations

from harness.config.load._shape import Shape, apply_transform, load_shape
from messagefoundry import MLLP, Send, handler, inbound, outbound, router
from messagefoundry.config.models import RetryPolicy
from messagefoundry.config.wiring import HandlerFn
from messagefoundry.parsing.message import Message, RawMessage

from harness.config.load._shape import Shape, apply_transform, load_shape

_SHAPE = load_shape()

# A few attempts with brief backoff: the sink always AA's, so retries shouldn't fire — but a transient
Expand Down
3 changes: 1 addition & 2 deletions harness/config/passthrough/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@

from __future__ import annotations

from harness.config.load._shape import load_shape
from messagefoundry import MLLP, PassThrough, Send, handler, inbound, outbound, router
from messagefoundry.config.models import RetryPolicy
from messagefoundry.parsing.message import Message, RawMessage

from harness.config.load._shape import load_shape

_SHAPE = load_shape()

_RETRY = RetryPolicy(
Expand Down
3 changes: 1 addition & 2 deletions harness/config/shardcert/_shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,8 @@ class B10, in the permissive direction). Split the three, never overload ``dests
import os
from dataclasses import dataclass

from messagefoundry.parsing.message import Message, RawMessage

from harness.load.ids import SHARDCERT_IDS
from messagefoundry.parsing.message import Message, RawMessage

CHEAP = "cheap"
EDIT = "edit"
Expand Down
9 changes: 4 additions & 5 deletions harness/config/shardcert/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,16 @@

from __future__ import annotations

from messagefoundry import MLLP, Send, handler, inbound, outbound, router
from messagefoundry.config.models import RetryPolicy
from messagefoundry.config.wiring import HandlerFn, RouterFn
from messagefoundry.parsing.message import Message, RawMessage

from harness.config.shardcert._shape import (
ShardCertShape,
apply_transform,
load_shape,
shared_dest_name,
)
from messagefoundry import MLLP, Send, handler, inbound, outbound, router
from messagefoundry.config.models import RetryPolicy
from messagefoundry.config.wiring import HandlerFn, RouterFn
from messagefoundry.parsing.message import Message, RawMessage

_SHAPE = load_shape()

Expand Down
3 changes: 1 addition & 2 deletions harness/config/store_once/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@

from __future__ import annotations

from harness.config.load._shape import load_shape
from messagefoundry import MLLP, Send, handler, inbound, outbound, router
from messagefoundry.config.models import RetryPolicy
from messagefoundry.parsing.message import Message, RawMessage

from harness.config.load._shape import load_shape

_SHAPE = load_shape()

# A few attempts with brief backoff (the sink always AA's, so retries shouldn't fire) — a transient
Expand Down
6 changes: 4 additions & 2 deletions harness/file_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@
)

from harness._console_widgets import ConfigurableTable
from messagefoundry.generators import _core
from messagefoundry.generators import all_types # noqa: F401 (registers the built-in message types)
from harness.file_transport import DropResult, FileDropWorker, FolderWatcher
from harness.mllp import Received, SendItem
from messagefoundry.generators import (
_core,
all_types, # noqa: F401 (registers the built-in message types)
)

_RANDOM = "(random across all)"
_DROP_COLUMNS = ["#", "Type", "Trigger", "Control ID", "File", "Error"]
Expand Down
2 changes: 1 addition & 1 deletion harness/file_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

from PySide6.QtCore import QFileSystemWatcher, QObject, QTimer, Signal

from messagefoundry.parsing import HL7PeekError, Peek, normalize
from harness.mllp import Received, SendItem
from messagefoundry.parsing import HL7PeekError, Peek, normalize

_RESCAN_MS = 1000 # safety-net poll; QFileSystemWatcher can miss bursts on some platforms

Expand Down
Loading
Loading