Commit graph

40 commits

Author SHA1 Message Date
Luna
715db9ec36 Add tray status model
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 06:01:53 -07:00
Luna
826daa3ab7 Add --json output to the check command
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 05:58:57 -07:00
Luna
5e560f4307 Add tray applet implementation plan
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 05:49:06 -07:00
Luna
2e7bf8b248 Add tray applet design spec
Optional desktop system-tray applet as a thin launcher over the existing
CLI and systemd. Core agent stays stdlib-only; tray ships as an optional
[tray] extra (pystray + Pillow). Covers menu actions, icon state, error
handling, packaging, and testing seams.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 05:12:16 -07:00
Luna
6a9e94c9fa Make release-artifacts dirty assertion track real git state
test_release_script_generates_checksummed_artifacts hard-coded
`dirty == 1`, which only held while the worktree happened to be dirty. The
script derives `dirty` from actual git state and RELEASE_ALLOW_DIRTY is a
permission flag, not a forced value — so the test failed the moment the tree
was clean (all work committed). Compute the expected dirty flag the same way
the script does so the test is deterministic on both clean and dirty trees.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 10:47:31 -07:00
Luna
9cbdb989ac Surface reconciliation in the dashboard and keep the view read-only
Two gaps in the baseline-reconciliation feature:

- The dashboard claimed to summarise acknowledged drift, but renderIntegrity()
  never rendered it. Add a `recon` chip to the integrity summary and a
  Reconciliation state-card (accepted/ok/stale counts plus stale items).
- A read-only GET /api/integrity could write the store: list() flushed a
  TTL-lapsed ok->stale transition to disk, violating the read-only dashboard
  invariant. Add list(persist=False) and use it from the web summary path so
  staleness is evaluated in memory without touching the file.

Covered by a new test asserting integrity_report() never rewrites the store.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 10:39:15 -07:00
Luna
dbbe35b3fc Add baseline reconciliation: accept audited drift with a reason
Implements the v0.9 roadmap item from the approved design spec. Operators
accept a specific FIM/package/listener/SUID drift item with a mandatory
reason; the ack suppresses that one alert only while the live state still
matches the recorded fingerprint. Content kinds (fim/pkgfile) re-alert on
further change; identity kinds (listener/suid) retire on TTL or revoke.

- reconcile.py: ReconcileStore (mtime-cached, fails closed on missing/corrupt
  store), fingerprint builders, and the filter_alerts chokepoint.
- CLI: baseline accept/revoke/list with --reason/--expires/--force/--stale/--json.
- Wired at the daemon sweep + eBPF chokepoint, fim-check, and /api/integrity.
- RECONCILE_V1 schema, config knob, COMMAND_REFERENCE/SCHEMAS/OPERATIONS/ROADMAP
  docs, and reconcile unit/CLI/integration tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 10:33:55 -07:00
Luna
3d2047fde2 Add baseline reconciliation design spec
Covers the v0.9 roadmap item: accept legitimate FIM/package/listener/SUID
drift with a mandatory reason and audited fingerprint-bound ack store.
Approach A selected: ReconcileStore with re-alert-on-fingerprint-change,
optional TTL, and stale tracking. Filter applied at daemon chokepoint,
CLI fim check, and dashboard API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 03:49:59 -07:00
Luna
69798b26e4 Add systemd unit posture checks
Close the remaining v0.8 posture bullet: flag enabled systemd units with
group/world-writable unit files (systemd_unit_writable, sid 100050) and
units whose ExecStart binary runs from a writable/unsafe path such as
/tmp, /dev/shm, /var/tmp, /home, or /run/user (systemd_exec_unsafe_path,
sid 100051).

- posture.systemd_findings/parse_systemctl_show/read_systemd_units, wired
  into posture.run() behind the new posture_systemd config knob.
- read_systemd_units shells out to systemctl with timeouts and fails closed
  on non-systemd hosts; the dashboard /api/posture surfaces findings for free.
- Unit tests for the parser and evaluator; TestRunIntegration patches the
  new reader so run() never touches live systemctl.
- Docs: COMMAND_REFERENCE posture list, ROADMAP v0.8 bullet, RUNBOOKS
  Runbook 2 triggers, config TOML knob.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 02:27:19 -07:00
Luna
11d91a40b4 Add dashboard integrity/watchdog console
Surface integrity and watchdog state in the read-only dashboard via a new
/api/integrity endpoint and integrity_report(): watchdog/heartbeat
verdict, FIM baseline and package-DB anchor freshness, pacman keyring and
SigLevel posture, and Sentinel's own self-integrity footprint. The
endpoint summarizes existing anchors and heartbeats only — it runs no live
FIM or package verification from the request path, keeping the dashboard
read-only.

Add the enodia.integrity.v1 schema (schemas.py + docs/SCHEMAS.md) with a
contract test, a new "Integrity" dashboard tab and summary metric, and
web tests for the report shape, schema contract, endpoint, and console
wiring. Docs updated; completes the v1.0 "dashboard to local console"
roadmap item.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 17:39:53 -07:00
Luna
e0e5cd62d9 Document packaging and release artifacts 2026-06-17 00:22:43 -07:00
Luna
aee984d862 Version documentation index and contracts 2026-06-16 04:14:26 -07:00
Luna
7a176ea238 Add stable v1 schema IDs and docs 2026-06-16 04:05:12 -07:00
Luna
bfef23fc4a Add dashboard theme settings and contrast tests 2026-06-16 02:29:45 -07:00
Luna
7f4d5b42fd Generate event rule documentation 2026-06-15 18:52:28 -07:00
Luna
e43d7689a0 Add dashboard rule atlas 2026-06-15 16:37:51 -07:00
Luna
5db88d285e Add event rule inspection commands 2026-06-15 05:36:58 -07:00
Luna
1f05923e0f Document current Sentinel project state 2026-06-13 06:46:33 -07:00
Luna
6a09fbb7f9 Frame Sentinel as IDS IPS and EDR 2026-06-13 05:59:03 -07:00
Luna
1a5b6f1d8d Persist dry-run response plans 2026-06-13 05:53:48 -07:00
Luna
812cf0c836 Describe Sentinel as endpoint detection and response 2026-06-13 05:49:34 -07:00
Luna
a51478fa22 Add event-driven memory syscall telemetry 2026-06-13 05:45:30 -07:00
Luna
893409b549 Add memory obfuscation and ps-hidden process detection 2026-06-13 03:55:09 -07:00
Luna
cb334c0c94 Expand monitoring for credential theft and covert protocols 2026-06-13 03:30:36 -07:00
Luna
3e5f8fc3f7 Expand rootkit monitoring and Claude docs 2026-06-12 04:57:11 -07:00
Luna
a7129e5666 Add HTTPS management console and response planning 2026-06-12 02:39:53 -07:00
Luna
a56d72edd6 Add incident grouping (roadmap v0.8: incident list/show/export)
Collapse related alerts into one incident, process-lineage first with a
time-window fallback. Each alert batch's flagged PIDs are walked up the
/proc PPid chain into a lineage set (excluding pid 0/1 so everything
doesn't correlate through init); batches whose lineage sets intersect —
sharing a process or a common ancestor like the web server or SSH
session — join the same incident. PID-less batches (FIM drift, package
tamper, hidden modules) fall back to the most recently active open
incident within incident_window.

snapshot.capture now computes lineage and records each snapshot into a
JSON incident index (incidents.json), writing incident_id into both the
report JSON (report level, so the per-alert schema is untouched) and the
text header. New commands:

  incident list              incidents newest-first, with signatures
  incident show <id>         summary + time-ordered snapshot timeline
  incident export <id>       JSON bundle: record + inlined snapshots

The lineage/assign cores are pure functions; record() serializes the
index under a lock (capture runs from sweep + eBPF threads) and is
best-effort so an index problem never loses a snapshot. 17 new tests
(lineage, assign, record, and an end-to-end capture→group→CLI path).
Config: incident_tracking / incident_window / incident_lineage_depth.
Docs + sample config updated; closes the last v0.8 roadmap item.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 17:52:42 -07:00
Luna
4015ec872b Add egress drill + sid-verified harness and IR runbooks (roadmap v0.8)
Red-team harness: every detector now has a named drill with an expected
signature/sid, shown by `sentinel-redteam --list`. Add the missing
`egress` drill (the one detector without one) — explicit-only, since it
is the sole drill that emits real outbound traffic. After firing, the
harness runs `enodia-sentinel check` and reports PASS/MISS per poll
detection, turning it into a shared regression check.

Add docs/RUNBOOKS.md: confirm/preserve/contain/recover playbooks for
reverse shells & egress, persistence, trojaned binaries / package
tampering, hidden listeners & rootkits, and sensor tampering — each tied
to the real signatures, sids, and commands. Linked from README and the
OPERATIONS alert workflow.

Both close the remaining smaller v0.8 roadmap items.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 05:28:53 -07:00
Luna
f2d896a2d7 Add status command (roadmap v0.8: status --json)
Add `enodia-sentinel status [--json]` — a local health and alert
summary: daemon running state, heartbeat freshness, per-severity alert
counts, last alert time, and eBPF state. Reuses web.daemon_status (the
same data behind /api/status), so the CLI and dashboard never diverge.

Exit code reflects health (0 = running and fresh, 1 = down or stale), so
it doubles as a cron/monitoring probe. Three CLI tests drive it against a
temp log_dir; no daemon or root needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 05:17:29 -07:00
Luna
9ebc355936 Add host posture checks (roadmap v0.8: posture check)
Add `enodia-sentinel posture check` — a config-hygiene audit that finds
the conditions making a host easy to attack, complementing the live
detectors. Checks: SSH root/password/empty-password login (Include-aware,
first-wins sshd resolution), passwordless and group/world-writable
sudoers, world-writable or non-root PATH directories, loose permissions
on sensitive files (/etc/shadow, /etc/passwd, ...), and downgraded
package-signature policy (reusing pkgdb.siglevel_alert).

Findings reuse the Alert type, so they serialize through the existing
JSON/triage pipeline (`posture check --json`). Each check is a pure
evaluator over injected content/stat facts plus a thin system reader, so
the 18 new tests need no root or live /etc. Posture is command-driven and
never runs in the daemon loop, per the v0.8 exit criterion.

SIDs 100040-100047 (classtype host-posture). Sample config and docs
(COMMAND_REFERENCE, OPERATIONS, ROADMAP, SPECIFICATION) updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 05:15:06 -07:00
Luna
6ff2087329 Add platform docs; reposition as host security platform
Add docs/ (SPECIFICATION, ROADMAP, OPERATIONS, COMMAND_REFERENCE,
THREAT_MODEL) describing the current v0.7 agent and the phased path from
local HIDS to a host security platform. Reframe README, package metadata,
and CLI/module descriptions from "intrusion-detection daemon" to "Linux
host security platform", and surface the v0.7 signed-package verify
(pkgdb_pkgverify*) and anti-rootkit (rootcheck_*) knobs in the sample
config. Also wrap the pacman.conf read in pkgdb.siglevel_alert in a
context manager to avoid a leaked file handle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 04:03:06 -07:00
Luna
1de5e86fed Add signed-package verification and anti-rootkit cross-view (v0.7)
Closes the tamper-evidence loop with two trust anchors the attacker can't
forge from userland:

- pkgdb Layer 2: verify on-disk files against the .MTREE in the *signed*
  cache package, surviving a rewritten local checksum DB. Rotating-sample
  cadence keeps it affordable; flags pkg_signature_mismatch (sid 100027)
  and SigLevel downgrades (sid 100026). Fixes parse_mtree, which required
  type=file on every line and so matched nothing on real pacman MTREEs
  (which use a /set type=file default with bare file entries).
- rootcheck: anti-rootkit cross-view — hidden processes, modules, ports,
  and promiscuous interfaces, each caught by diffing two views of the
  same state (sids 100022-100025).

Wired both through config, the daemon (off-loop slow cadence), and CLI
(pkgdb-verify, rootcheck). 14 new tests (95 total). Docs + version bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 05:42:57 -07:00
Luna
34bc09041a Add tamper-evidence: package-DB integrity, self-integrity, dead-man's switch
Answers two hard questions: "what guards the hashes FIM trusts?" and "how do we
make the sensor itself hard to silently disable?" — within the honest limit that
a root attacker who shares your privileges can't be fully stopped on-box, only
made loud.

- pkgdb.py: guards the package DB that `pacman -Qkk` trusts. Anchors a
  fingerprint of /var/lib/pacman/local (refreshed ONLY by the pacman hook) and
  cross-checks pacman.log; a DB change with no logged transaction is flagged
  pkgdb_tamper (CRITICAL, sid 100021) — catches an attacker rewriting a stored
  checksum to mask a modified binary. Verified end-to-end against a simulated
  hash-overwrite.
- selfprotect.py: Sentinel's own binaries/config/units/hook are always in the
  FIM watch set (self-integrity); a heartbeat is written each loop; an external
  `watchdog` command polls a remote dashboard and pushes if the sensor goes
  silent or unreachable — silence becomes the alarm.
- daemon: heartbeat + slow-cadence pkgdb check; DB anchor re-anchored in
  build_fim_baseline so the pacman hook refreshes it after each transaction
- web: /api/status now reports heartbeat_age/stale; dashboard shows it
- cli: pkgdb-check, watchdog (--url/--token/--max-age, bypasses min-severity)
- config: pkgdb_verify/_interval, heartbeat_max_age
- README: threat model (trust-anchor problem) + hardening layers (immutability,
  signed-package + external anchor); reframes "anti-rootkit" as tamper-evidence
- tests: +8 (DB fingerprint, anchor/transaction logic, pacman.log parse,
  heartbeat + watchdog verdicts). 80/80 pass

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 22:27:50 -07:00
Luna
5d577b624f Add file integrity monitoring (Tripwire-style), auto-refreshed via pacman hook
Detects binary/config tampering by content hash — catching a malicious swap
even when mtime is preserved (the gap in mtime-based persistence checks). Two
engines split by file ownership:

- fim.py hash baseline: SHA-256 (+ mode/uid/gid/size) of security-critical
  files the package manager doesn't track (/usr/local, /etc configs, systemd
  units, SSH keys). The baseline refreshes ONLY via fim-update / the pacman
  hook, so a flagged change stays flagged until acknowledged (Tripwire
  semantics). Alerts: fim_modified 100017 / fim_added 100018 / fim_removed 100019.
- package verification: `pacman -Qkk` checks package-owned binaries against the
  distro's own signed checksums — no baseline to maintain, implicitly current
  because the package DB updates on every upgrade. fim_pkg_modified 100020.

Auto-update on system updates: a pacman PostTransaction hook runs
`enodia-sentinel fim-update` after every install/upgrade/remove, so legitimate
package changes never alert — no manual `tripwire --update`.

- daemon: backgrounded FIM scan + optional pkg-verify on slow cadences, feeding
  the normal alert/snapshot/push pipeline; baseline loaded/built at startup
- cli: fim-baseline / fim-update / fim-check [--packages]
- config: fim_enabled, fim_paths, fim_scan_interval, fim_pkg_verify[_interval]
- packaging: ship + install the pacman hook (Makefile + PKGBUILD)
- tests: +7 (hashing, diff incl. mtime-preserving tamper, pacman -Qkk parse).
  72/72 pass. Verified end-to-end: a content swap with preserved mtime is caught.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 22:18:42 -07:00
Luna
07f5261d59 Add false-positive triage via package-ownership provenance
A noisy IDS gets ignored. Adds explicit FP-handling built on provenance — a
binary owned by the package manager is almost certainly legitimate (the OSSEC
rootcheck / AIDE principle).

- provenance.py: package_owner()/is_package_owned() via pacman/dpkg/rpm, cached;
  framed as confidence-raising, never proof-of-safety
- triage.py: triage_alert() labels each detection likely-FP vs review with a
  reason (package-owned binary, loopback-only listener, allowlisted comm, …);
  reverse_shell/egress/exec rules are ALWAYS review (provenance can't clear a
  network shell); unattributable listeners are reviewed, not cleared
- cli: `enodia-sentinel triage` summarizes captured alerts and suggests
  allowlist entries
- new_listener: listener_allow_comms + optional suppress_package_owned_listeners
  gate (the best single knob for a desktop/seedbox running P2P apps)
- tests: +10 (provenance injected); README + config documented. 65/65 pass

Verified on a live seedbox: 12 detections, 11 auto-cleared as FP (qbittorrent /
nicotine / kdeconnectd / the dashboard itself), 1 correctly held for review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 22:05:00 -07:00
Luna
c00fff224c Add read-only web dashboard and phone push notifications
Both zero-dependency (stdlib http.server + urllib), consistent with the
project's no-dependency-tree stance for a security daemon.

Web dashboard (enodia_sentinel/web.py + static/dashboard.html):
- read-only JSON API over the log dir: /api/status, /api/alerts,
  /api/alerts/<id>, /api/events
- bearer-token auth (constant-time; header or ?token=), required on non-
  loopback binds, auto-generated + persisted (0600) when unset
- binds the host's Tailscale IP by default (auto-detected), reachable from the
  tailnet but not the LAN/internet
- self-contained dark SPA: severity cards, live alert list, full snapshot
  viewer; 10s auto-refresh
- path-traversal-safe alert lookup; `enodia-sentinel web` subcommand; daemon
  now writes a pidfile so the dashboard can show live status
- hardened enodia-sentinel-web.service (read-only, no caps)

Phone push (enodia_sentinel/notify/):
- pluggable backends — ntfy, Pushover, generic webhook — each separating a pure
  build() (unit-tested, no network) from send()
- a backend turns on when its config keys are set; pushes gated by
  notify_min_severity; severity → per-service priority/tags
- fired from snapshot.capture on worker threads, errors swallowed
- desktop notify-send retained

Tests: +16 (9 web incl. a real-server 401/200 auth test, 7 notify request-build
cases). 55/55 pass. Live end-to-end verified: daemon → alert → API → page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 16:18:00 -07:00
Luna
0eb5077551 Add event-driven eBPF execve layer with a Snort-style rule engine
Closes polling's blind spot (processes that exit between sweeps) with a real
eBPF probe and a declarative, data-driven detection engine — inspired by Snort
(rule language, signature IDs) and OSSEC (host-IDS framing).

New events/ subpackage:
- bcc_source.py : eBPF C tracing execve (filename, argv[1..2], ppid, uid,
                  parent comm) over a perf buffer, loaded via bcc; lazy import
                  + available()/try-except so it fails closed to poll-only when
                  bcc/root/BTF are absent — a broken probe never downs the daemon
- exec_event.py : the ExecEvent type
- rules.py      : ExecRule (sid/msg/severity/classtype + path/exec/parent/argv
                  conditions) and ExecRuleEngine; 4 shipped rules (fileless exec
                  100001, reverse-shell argv 100002, web/DB→shell RCE 100003,
                  curl|sh 100004); operators add more via exec_rules_file TOML
- monitor.py    : runs the source on a thread, routes events through the engine

Integration:
- daemon starts the monitor, shares a lock-guarded cooldown with the sweep
  loop, and feeds event alerts into the same snapshot pipeline
- Alert gains Snort-style sid + classtype; retrofitted onto all 7 poll
  detectors; snapshots and JSON now carry them
- config: ebpf_exec_monitor (default on, degrades), exec_rules_file
- systemd: opt-in ebpf.conf drop-in (relaxes MemoryDenyWriteExecute + widens
  caps for bcc's JIT) so the base unit stays hardened for poll-only
- sentinel-redteam: ebpf_exec drill (short-lived /tmp exec + /dev/tcp argv the
  poller can't see); footer now uses the Python CLI

Tests: +14 cases for the rule engine (each default rule match/non-match, rule
validation, parent-exclude). 39/39 pass. Graceful non-root degradation verified.

NOTE: the eBPF C follows bcc's execsnoop pattern but could not be run here
(BPF needs root); it wants a root smoke-test on a real host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 07:16:53 -07:00
Luna
586f74b929 Relicense under GPL-3.0-or-later
Replace MIT with the full GNU GPLv3 text, update license metadata in
pyproject.toml (+ trove classifiers) and PKGBUILD, and add
SPDX-License-Identifier headers to all Python modules and shell scripts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 06:52:34 -07:00
Luna
28d67a1360 Re-architect Sentinel as a zero-dependency Python package
Port the bash prototype to a structured, testable Python codebase while
preserving the same control loop and the seven detection signatures. The bash
implementation stays in src/ as the regression oracle.

Highlights:
- enodia_sentinel/ package (stdlib only — no runtime deps):
  - detectors/ : one pure function per signature, detect(state, cfg)->Alerts
  - system.py  : SystemState — one cached /proc + ss snapshot per sweep,
                 fully injectable so detectors are unit-testable
  - daemon.py  : sweep loop, in-process cooldown dedup, threaded snapshot
                 capture, and a SUID filesystem scan moved OFF the loop
                 thread onto a slow background cadence
  - snapshot.py: forensic text + JSON sidecar with per-signature IR guidance
  - config.py  : dataclass config via TOML + env overrides
  - netutil.py : public-IP / CIDR logic via stdlib ipaddress
- tests/ : 25 stdlib-unittest cases (no root, no /proc, no ss needed)
- TOML config, launcher wrapper, Makefile (pip-free install), hardened
  systemd unit (env-resolved ExecStart), updated PKGBUILD, rewritten README

Performance: per-sweep cost ~200 ms (shared cached state); the multi-second
SUID walk no longer blocks detection. scandir-based walk replaces os.walk.

Verified on Arch: all 7 detectors fire on red-team drills (reverse_shell,
ld_preload, deleted_exe, new_listener, new_suid confirmed live end-to-end),
no false positives on a clean sweep, 25/25 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 01:50:50 -07:00
Luna
45f8acb24a Enodia Sentinel v0.1 — bash host-IDS prototype
Poll-based host intrusion-detection daemon modeled on freeze-watcher's
sample→trigger→snapshot→classify loop, with security signatures instead
of performance ones:

- reverse_shell  (interpreter with a network socket on stdio)
- ld_preload     (/etc/ld.so.preload or LD_PRELOAD in writable dirs)
- deleted_exe    (process running from a deleted/memfd binary)
- new_listener   (listening port absent from startup baseline)
- new_suid       (new SUID/SGID binary; critical in writable dirs)
- persistence    (cron/systemd/authorized_keys/rc-file changes)
- suspicious_egress (interpreter holding an outbound public connection)

Each capture writes a forensic snapshot with per-signature incident-
response guidance. Includes a safe, self-cleaning red-team harness
(sentinel-redteam), hardened systemd unit, config, Makefile, and Arch
packaging.

Tested on Arch: detectors fire on drills, no false positives on a clean
sweep, sweep cost optimized from ~15s to ~1s via batched syscalls and a
gated filesystem-wide SUID scan.

This bash implementation is retained as the regression oracle for the
forthcoming Python rewrite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 00:53:35 -07:00