Repository navigation
Phase 1: notification truth, damaged-record handling, OS lock, full mutation sweep - #1
Merged
Merged
Conversation
… quickstart, add 22 missing tests - pyproject.toml: the six pillars pinned to the same commits listing-assistants pins (they installed from branch tips). - tests/test_day3.py, demo/run_p11_demo.py: IDENTITY_DIR made absolute before the P11 test changes directory. The README's command (IDENTITY_DIR=../listing-assistants) failed one test; it now passes (164 passed). - tests/test_phase1_coverage.py (22 tests): the envelope's required fields and provenance.source, a misspelled audience label holding as counterparty, the escalation.confidence_underflow queue, Hub.arm_signer_registry(), and the WhatsApp notifier. Not yet done: the re-run of the three mutants these tests are meant to kill (stopped by the owner before it ran), Phase 1.1 (notification truth), 1.2 (torn last line), 1.6 (CI), the full mutation sweep, and the listing-assistants sync. This branch is not merged to main; listing- assistants is unchanged. Push-log row goes in listing-assistants with the sync commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRunDQmfmnZdtJiP78e8WR
… audit line is named and repairable 1.1 Notification truth (owner decision D3) - hub._notify records human.notified only when the notifier reports sent/delivered/shown. A failed status or a raised exception records human.notify_failed (status, http_status, channel, error - never the body). A notifier that reports nothing is recorded as transport "not reported". - escalate() and queue_and_notify() both go through _notify. - kpi escalation_transport counts confirmed / not_reported / notify_failed. - after_action prints "NOTIFICATION FAILED ... no person was reached". - tests/test_notification_truth.py: 16 tests. 7 targeted mutants, 7 killed. 1.2 Damaged or cut-off audit log (owner decision D4) - AuditLog refuses to open a log with an unreadable line: AuditLogDamaged names the line, says nothing was written, and says whether it is a cut-off last write (repairable) or damage to investigate. - verify_chain / check_file name the first bad line instead of raising. - AuditLog.repair_torn_tail + tools/audit_repair.py (run by a person): moves the partial bytes to <log>.torn-<UTC>-<sha12>, cuts back to the last complete entry, records audit.tail_quarantined on the chain. Refuses, changing nothing, if anything before the last line is damaged or the chain breaks. - A complete last entry missing only its newline opens; the next append writes the newline first. CRLF logs check and repair. - tests/test_audit_log_damage.py: 18 tests. 8 targeted mutants, 8 killed (the truncation-length mutant is killed by the interrupted-repair test). Suite: 198 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRunDQmfmnZdtJiP78e8WR
….12/3.13 and Windows Until now every core CI run skipped the 5 identity-pinned tests in tests/test_day3.py (no listing identity next to the clone), so green said nothing about them. New job with-identity clones listing-assistants at main, sets IDENTITY_DIR, and fails on any skip. Matrix: ubuntu 3.10 (the declared floor), 3.12, 3.13; windows 3.12 with PYTHONUTF8=0. The original job stays as the clone-alone path and now prints its skips (-rs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRunDQmfmnZdtJiP78e8WR
…dows CI) Core CI run 38437120185, windows-latest 3.12: gap_incidents 2, expected 1. The test slept 0.01 s then 0.06 s against a 0.01 s cadence (incident above 0.02 s); the Windows 15.6 ms timer tick plus a slow fsync pushed the first gap over too. A test fault, not a Watchdog fault. heartbeat() is still checked on a real hub; the gap logic now gets fixed timestamps, including a gap of exactly 2x cadence (not an incident) and fewer than 2 beats. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRunDQmfmnZdtJiP78e8WR
- README quickstart: 198 tests with the listing identity (was 137). - PILLAR_TESTING_MANUAL: 193 passed + 5 skipped alone, 198 with the identity (was 132 / 137); says CI runs both. splitvantage's own suite at the pinned commit is 14 tests (was stated 12) - each pinned pillar's suite was run 2026-10-13: 9, 6, 11, 27, 20, 14. - SKILL.md: 193 + 5 / 198 (was "89 doctrine tests"). README-BUILD.md's counts are a dated build log and stay as written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRunDQmfmnZdtJiP78e8WR
…ed, 4 equivalent with evidence Method (tools/mutation_sweep.py, now in the repo): one AST-span mutant at a time (comparison, and/or, not, constants, negated tests, return None, raise -> pass, call -> pass), the full suite with the listing identity against each, in a copy of the repo, baseline checked first. First sweep (fb7a04f): 807 mutants, 593 killed, 214 survived. They clustered where the core had no direct tests: the after-action report, hub integrity-violation and dead-letter records, the unconfirmed-audience marker, wildcard and sender/receiver route limits, anchors, the hash format, disk flushes, module audit records. 90 new tests (tests/test_sweep_*_2026_10_13.py). Code changes: - loader.py: the routes check was an assert (stripped under python -O, an empty track would load); now an explicit check. - core.py: a dead variable in repair_torn_tail removed. Final sweep with the packaged tool on this commit: 808 mutants, 804 killed, 4 survived, 0 timeouts. The 4 are equivalent, each with run evidence in findings/MUTATION_SWEEP_2026-10-13.md (chunk size of a streamed hash; a route clause subsumed by the line's first test; a flush on a handle that only truncates; Exception args set before __init__). One unexplained single failure in run 2 is recorded there; it did not recur in 25 reruns. EVIDENCE.md: rows for decline, notification truth, a cut-off record, pinned pillars, the identity CI job, the sweep. Counts: 283 passed + 5 skipped alone, 288 with the listing identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRunDQmfmnZdtJiP78e8WR
…repair, each escalation paired with its own notification A separate agent reviewed Phase 1 cold. Its findings, reproduced, fixed: - A writer that opened an intact log kept appending after the file was damaged under it (the running desk did). AuditLog now remembers the file's size after its own write; if it differs at the next append it re-verifies: damaged, cut back, replaced or deleted -> AuditLogDamaged, nothing written; another writer's valid entries -> continue from them (no fork). - The repair cut the log and then appended its record: a crash between left a cut log with no record. It now writes the repaired log to a new file and swaps it in with os.replace (folder synced on POSIX). - KPI and after-action matched notifications by queue and time, so a later delivery on the same queue covered an earlier failure. kpi.pair_escalations pairs each escalation with its own outcome; the report no longer gives a transport time to a notifier that reported nothing. - An odd notifier answer (list status, object values) raised out of the hub after escalation.raised was written. Now recorded as notify_failed. - Hub.notify(queue, record): notify without queueing, outcome recorded (listing's decision alerts called the notifier around the hub). - tools/kpi_gate.py checks the chain first (it crashed on a damaged log and passed an edited one). - CI with-identity uses the listing branch of the same name when it exists. - A test of mine passed for the wrong reason (its "cut back" case emptied the file); fixed, and the same read-after-truncate bug in a new test. Not fixed (owner decision pending): two processes appending in the very same instant - only an OS file lock stops that. Sweep on this commit (tools/mutation_sweep.py): 859 mutants, 855 killed, 1 timeout, 3 equivalent (findings/MUTATION_SWEEP_2026-10-13.md). 309 passed + 5 skipped alone; 314 with the listing identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRunDQmfmnZdtJiP78e8WR
…sk saves nothing unrecorded, outcomes name their escalation Owner decision (2026-10-13): an operating-system lock on <log>.lock (flock on POSIX, msvcrt.locking on Windows). Every AuditLog read and write takes it, re-entrant within a process; a writer waits up to LOCK_WAIT_S (30 s) and then raises AuditLogBusy, writing nothing. The repair takes it too. Shown by three processes appending 450 entries at once (fails without the lock). Second review (same agent re-checking its findings), fixed: - the change check compared size only; now size and modification time - AuditLog.ensure_intact(): callers check before writing anything else (listing's swarm saved a snapshot the log could not record) - each notification outcome carries raised_entry = the escalation's entry_hash; pairing is exact across processes (older logs by order) - Windows CI red: a test assumed LF; now built byte for byte - the repair keeps the log's permissions and owner where allowed, and a failed swap removes its side file; tools/audit_repair.py reports a busy lock or failed swap instead of a traceback - AuditLog.reader(path): read-only view (replaces __new__ hacks) - the repair's docstring said a holder would refuse; it follows the chain Sweep on this commit: 921 mutants, 908 killed, 1 timeout, 12 survived: 3 equivalent, 1 that only changes CPU use (the lock's retry sleep), 8 on Windows-only lines a Linux sweep cannot run (findings document). 325 passed + 5 skipped alone; 330 with the listing identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRunDQmfmnZdtJiP78e8WR
…le per process, holding() - Reads take the lock shared on POSIX (readers no longer block each other) and need no write access: if <log>.lock cannot be opened for writing a read opens it read-only, and if it cannot be opened at all (absent in a read-only folder) the read goes ahead unlocked. Before this, checking a log on read-only media crashed (third review, L1). - One lock state per file per process (_PROCESS_HELD), shared by every AuditLog instance on that file: two instances in one process never wait on each other (L3); threads take turns. A write inside a held read upgrades the lock to exclusive. - AuditLog.holding(): hold the lock across several steps (listing's state save: check, write the store, record it). The repair goes through the same per-process state. - Tests: read-only folder (lock file present / absent), a read through a read-only lock file still waits for a writer, two instances and two threads in one process, readers sharing across processes, the upgrade seen from another process, no leaked lock file handles, a repair from inside a held lock. Sweep: run 11 on this code, 942 mutants: 924 killed, 1 timeout, 17 survived; the 5 new ones killed by tests added after (run 12). Result: 929 killed, 1 timeout, 12 survived - 3 equivalent, 1 CPU-only, 8 on Windows-only lines. 334 passed + 5 skipped alone; 339 with the identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRunDQmfmnZdtJiP78e8WR
… escalations, count entries, drop dead route clauses - Opening a log edited at line 2 and cut off at its end said "cut off, run the repair"; the repair then refused because of line 2. AuditLog now reports the FIRST break in the file (AuditLog._first_problem), so the repair is suggested only when it will accept the file. - after_action: the run filter added every escalation and notification on the log regardless of context, so a report listed other runs' escalations as its own (the P11 demo's warm-lead report claimed the hot lead's escalation). Now only the run's own; pairing still uses the whole log. - verify_chain()["entries"] counts entries, not line numbers (blank lines). - Routes.tuple_legal: two clauses that could never change the answer removed (proved over every receiver set; one equivalent mutant gone). - Tests for each, shown to fail on the previous code. Sweep: run 13 on this code, 931 mutants: 917 killed, 1 timeout, 13 survived; the 2 new ones killed by tests added after (run 14). Result: 919 killed, 1 timeout, 11 survived - 2 equivalent, 1 CPU-only, 8 on Windows-only lines. 339 passed + 5 skipped alone; 344 with the identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRunDQmfmnZdtJiP78e8WR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1 of the repair plan (2026-10-13), approved by the owner for merge.
human.notify_failedotherwise). Each outcome names the entry hash of its escalation; the KPI and the after-action report pair them exactly.tools/audit_repair.py).<log>.lock(shared for reads on POSIX; reads need no write access).findings/MUTATION_SWEEP_2026-10-13.md.CI run 38488817589: green on all 5 jobs (344 passed with the identity, including Windows).
🤖 Generated with Claude Code
https://claude.ai/code/session_01CRunDQmfmnZdtJiP78e8WR
Generated by Claude Code