Skip to content

Remove six measured hot spots from the SyberFactory kernel without changing any persisted byte (efficiency audit 2026-10-26, items 2, 3, 4, 5, 6 and 8): the journal skips re-opening a file whose size - #105

Merged
sykosyber merged 1 commit into
mainfrom
syberlabs/b130b3e6
Oct 26, 2026

Conversation

@sykosyber

@sykosyber sykosyber commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Objective: Remove six measured hot spots from the SyberFactory kernel without changing any persisted byte (efficiency audit 2026-10-26, items 2, 3, 4, 5, 6 and 8): the journal skips re-opening a file whose size equals its read offset, candidate_views indexes the newest evaluation in one pass, effect_unresolved computes its finished proposals once, Ed25519 signing reuses a public key already derived from the same seed, the query-guard hook loads its two leaf modules without importing the syberlabs package, and Kit.threads reads only a thread's first records. Every digest, event, signature, decision and golden trace stays identical, each change has an equality test against the old behaviour, and each has a before/after measurement on the audit's machine.

Class: R
Agent-platform: patch@1
Admitted-by: canadianbacon345@gmail.com
Reviewed-by: rules@1 (critic check critic.rules: high 0, medium 0, low 0)
Evidence: tested

Accepted in SyberLabs thread b130b3e6-eb9d-440c-aea1-5287ed9eef5a as candidate c1 (commit 8e781db0e099a55967e6735ec650879d854215b0).
Host checks on that tree: conformance passed, critic.rules passed, scenarios passed, tests passed.

Reviewed-by: claude (independent verifier session, SyberFactory workflow)

…views@1k 674->17 ms, hook 235->80 ms, Kit.threads 155->83 ms, sign_dsse 292->220 ms; outputs identical

SyberLabs-Thread: b130b3e6-eb9d-440c-aea1-5287ed9eef5a
SyberLabs-Candidate: c1
SyberLabs-Provider: patch@1
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T10:54:50.806882Z 8e781db PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@sykosyber

Copy link
Copy Markdown
Member Author

Handoff: perf-packet-a (Class R). Built by Build Thread b130b3e6 on a fresh clone through contract v4 with the unattended runner. Base main at e2e640870f. The packet named 7d6292dc02 as the expected base; main had moved six commits (Attest #86, #87, #89, #92, #93 and #83). None of them touches the six changed paths. The PR body is the factory's own text and was left untouched. No model was called.

Scope: owner approval on 2026-10-26 of items 2, 3, 4, 5, 6 and 8 of the efficiency audit's ranked plan. The audit report is D:/syberlabs/officer_workspace/reports/2026-10-26-syberfactory-efficiency-audit.md (sections 1, 4, 6 and 7), with its data appendix ...-efficiency-audit-data.md beside it. Item 1 (test sharding) is a separate packet; this one leaves the test runner, CI workflows and scripts/ alone.

Invariants. Every persisted byte, digest, event, signature and golden trace is unchanged:

  • python -m conformance.run matched all 23 golden traces.
  • No schema, golden trace or event shape changed.
  • On the 50 real receipts, base and packet give identical digests for: candidate views at two clocks; effect.unresolved on all 3,606 prefix and action pairs (150 denials); DSSE chain-head envelopes signed with a fixed test seed; transparency-log checkpoints for 12 heads; and, on a journal home rebuilt from the receipts (25 threads, 554 events), the Kit.threads() rows, every loaded event, side record and offset, and the journal files' bytes. The query-guard hook gave identical exit codes, stderr, decisions, MACs and policy digest on an 11-query corpus. Script: factory/ppa/equality.py; results in results/equality-{base,packet}.json.

What changed, item by item

  • Item 2, journal stat-before-open (syberlabs/journal.py). read_new stats the file first and returns [] without opening it when the size equals the offset already read. This was the only case in which the old reader opened the file, read nothing and returned []. The torn-line repair runs unchanged on the path that still opens. _write already trusts this same size to detect another writer.
    • Tests in tests/test_journal.py, class StatBeforeOpen:
      • test_every_byte_and_every_read_equal_the_reader_that_always_opened runs a fixed scenario under a copy of the old reader and under the new one, then compares every journal byte, every read_new result and each session's view. The scenario is two sessions interleaving on one home, a writer dying mid-record, a third session repairing it and the first writing again.
      • test_an_unchanged_file_is_not_opened_and_a_grown_one_is_read_once
  • Item 3, candidate_views (syberlabs/evolution.py). The newest evaluation of each (candidate, commit, tree) comes from an index built in the pass that already collects decisions and outcomes. The last event in history order wins, which is the one latest_evaluation finds scanning backwards. latest_evaluation stays, and it is still the admission rule's lookup.
    • Test: tests/test_evolution.py, IndexedEvaluations.test_views_equal_the_backward_scan_on_random_histories. It covers 30 seeded random histories: re-evaluations, failed, timed-out and stale evidence, and promotions.
  • Item 4, effect.unresolved (syberlabs/admission.py). The second _finished_proposal_ids(ctx.history) becomes finished = settled_ids. The line count is unchanged, so rule_provenance lines do not move.
    • Tests in tests/test_admission.py, class FinishedProposalsOnce:
      • a copy of the old rule against the new one on 600 random effect histories × 2 actions;
      • _finished_proposal_ids is called once.
  • Item 5, Ed25519 public key (syberlabs/ed25519.py, dsse.py, tlog.py). sign(seed, message, public=None) skips the scalar multiplication when it is given the key, and refuses a key that is not 32 bytes. sign_dsse(..., *, public=None) derives the key once and passes it, so it does 2 scalar multiplications instead of 3. TransparencyLog keeps the key it derived at construction in _seed_public and passes it for each checkpoint. Pure Python is still the only implementation.
    • Tests in tests/test_witness.py, class CachedPublicKey:
      • RFC 8032 TEST 1, 2 and 3 give the same signature with and without the key;
      • byte-identical signatures and DSSE envelopes over 6 seeds × 3 messages;
      • the _mul call count;
      • a log checkpoint equals sign_dsse without the key.
  • Item 6, query-guard hook (syberlabs/_patterns.py new, query_guard.py, metrics.py). SECRET moves to _patterns.py. metrics._SECRET and query_guard.CREDENTIAL are that same object.
    • Finding: the audit put the hook's cost on syberlabs.metrics. In fact any syberlabs.* import, syberlabs.canonical included, runs the package __init__, which imports the whole SDK (-X importtime: 236 ms cumulative). Moving the regex alone would therefore have saved about 20 ms.
    • So the hook, run by absolute path under -I, now loads canonical.py and _patterns.py from beside itself by file path and does not import the package. A worktree's syberlabs is still never on the path. Imported as a module (the host), it imports both normally.
    • Tests in tests/test_query_guard.py, class Process:
      • the hook run with -X importtime imports no syberlabs module, and its MAC verifies under the host's canonical JSON;
      • the regex object is one object, its pattern is pinned, and the default policy digest is pinned at e6b17cc24a;
      • the hook decides 11 credential, email, length, line-count and tree-line cases exactly as the in-process decide does.
  • Item 8, Kit.threads / _incomplete (syberlabs/build.py _incomplete only, plus Journal.records in journal.py). _incomplete reads a thread's records from the first line until a verified base observation, without the lock and without loading the thread, as Journal.first reads the case row. It uses the same predicate, build.latest_observation, on each event. A torn final line ends the reading.
    • Test: tests/test_build_thread.py, ThreadListing.test_states_equal_a_full_load_and_listing_loads_no_thread. Four threads: started, unattended, died after case_created, and died mid-record writing the base. The states equal a full load, and session.events stays empty after listing.

Docs:

  • docs/CLAUDE_CODE.md: two sentences, on what the hook loads and where the pattern lives.
  • spec/GAPS.md: a dated note under entry 33. The listing cost is removed. Deferred: listing no longer verifies each chain, so an altered thread is refused journal_corrupt when it is opened (use, status, a runner pass), not when threads lists it.

Before/after measurement

Method:

  • The audit's own helpers from C:/Users/MATEO/AppData/Local/Temp/sfv/perf_bench/ (_common.bench, bench_journal.append_latency, bench_admission.build, bench_receipts.build_home), driven by factory/ppa/measure.py <checkout> <label>. Each figure is the median of ≥5 runs after warm-up; journal appends are the median of 300.
  • The same machine as the audit: Ryzen 7 3700X, Windows 11 26200, Python 3.12.6.
  • Base checkout e2e640870f and this candidate's tree, run one after the other.
  • Another agent's full test suite (the sharding packet) was running on the same machine during these runs. Absolute numbers carry that load. The admission figures were the noisiest, so for those I also ran an interleaved A/B in one process.
Item What Before After Change
2 Session.observe append, typical event, fsync off 7.69 ms 0.44 ms −94% (17×)
2 same, fsync on 9.94 ms 1.49 ms −85%
3 Session.candidates, 1,000 candidates / 20,002 events 674 ms 17.2 ms −97%
4 effect.unresolved rule alone, 20,002 events (median of 15) 16.3 ms 10.1 ms −38%
4 admission preview at 20,002 events, interleaved A/B in one process (median of 40; two runs) 35.4 / 27.5 ms 29.9 / 21.9 ms −16% / −20%
5 ed25519.sign 1 KB, key passed vs derived 196 ms 92.8 ms −53%
5 dsse.sign_dsse 1 KB (certify's signature) 292 ms 220 ms −25%
5 TransparencyLog.append (verify head + root + sign checkpoint) 513 ms 364 ms −29%
6 query-guard hook process per lookup, pass / tree_line block 235 / 238 ms 80 / 78 ms −66% / −67%
8 Kit.local + Kit.threads() cold, 25 real threads / 554 events 155 ms 83 ms −46%
8 Kit.threads() with every thread loaded 89 ms 8.5 ms −90%
2+8 Runner.once() read-only pass, 25 accepted threads 543 ms 123 ms −77%

Against the audit's targets:

  • Met or beaten: item 2 (target 0.42 ms without fsync), item 3 (−97%).
  • Item 4: −16% to −20% on the whole preview, short of the audit's −41%. The rule itself is −38%. The audit's figure was in-process under suite load. Here the second computation is about 5 ms of a 20–35 ms admission.
  • Item 5: sign_dsse is −25%, not the audit's −64%. sign_dsse must still derive the key once, since no key is cached across calls, so it does 2 scalar multiplications instead of 3. The −53% applies to a caller that already holds the key: the log, and any future caller.
  • Item 6: 80 ms, not ~60 ms. What remains is the interpreter (21 ms), the stdlib imports and reading the 1 MB tree-digest file.

Checks (run before propose, in the clone, on this machine):

  • Changed and dependent modules (test_journal, test_evolution, test_admission, test_witness, test_query_guard, test_build_thread, test_metrics, test_runner, test_settle, test_certificate, test_external_witness) with -v: 172 tests OK.
  • Full suite (unittest discover -s tests): 648 tests OK, 22 skipped, 968 s.
  • python -m conformance.run: matched 23 golden traces.
  • benchmarks/run_scenarios.py: 11 replayed, exit 0.
  • python -m conformance.clean_install: exit 0.
  • Host checks on the accepted tree (result 8e781db0e0, tree a3ce32b16c):
    • conformance passed (9.5 s)
    • critic.rules passed
    • scenarios passed
    • tests passed (1,155 s)

Bite: the new tests were run against the base package in a scratch clone.

  • These fail on base: StatBeforeOpen "not opened", FinishedProposalsOnce "computed once", 4 of the CachedPublicKey tests (sign takes no key; 3 multiplications), ThreadListing (listing loads every thread), and all of test_query_guard (no _patterns module).
  • The pure-equality tests pass on base, as an equality pin should.

Limits

  • sign(seed, message, public) trusts its caller. Two signatures of one message under two different public values reveal the private scalar. The docstring says so, and only sign_dsse (with the key it just derived) and TransparencyLog (with the key derived at construction, kept in a private attribute) pass one.
  • Listing threads no longer verifies each chain (dated GAPS 33 note). Opening a thread still does.
  • The stat-before-open relies on writers closing their handle before they release the lock, which the journal already relies on in _write. It was measured on Windows only. The audit's open question 4 (CI runner, Linux) still stands.
  • The measurements shared the machine with another agent's suite run, as stated above.
  • GitHits did not connect in this session, so no dependency grounding ran. None was needed: no dependency changed.

Certificate: certify c1 signed result 8e781db0e0 (ed25519 key 4b6ae2deea, journal chain head 1f030756d5 at 14 events). verify returned signature ok, repository ok (18 changed paths, diff digest match) and journal ok, with verdict pass.

@sykosyber

Copy link
Copy Markdown
Member Author

Independent verification of this head (8e781db0e0, fresh clone, base e2e640870f; no code edited, no model or paid call). main has not moved since the base (origin/main is still e2e640870f), so no newer commit touches these files.

  • Scope: own diff against e2e640870f is exactly the 18 claimed files (+484/-30). Nothing under conformance/ or spec/*.schema.json changed.
  • Changed and dependent modules with -v (test_journal, test_evolution, test_admission, test_witness, test_query_guard, test_build_thread, test_metrics): 129 tests OK, 66 s; every new class (StatBeforeOpen, IndexedEvaluations, FinishedProposalsOnce, CachedPublicKey, ThreadListing, test_query_guard.Process) ran and passed. Full suite (discover -s tests): 648 tests, 22 skipped, 1026 s, OK. python -m conformance.run: matched 23 golden traces.
  • Equality: I ran the author's equality.py (copied, results written to my own folder) against a base worktree and this head. All 17 outputs are identical: candidate views, effect.unresolved (3,681 prefix/action checks, 153 denials; the receipt set has grown since the handoff's 3,606/150, so the absolute digests differ from the author's files, base and head agree), DSSE chain-head envelopes, transparency-log checkpoints, Kit.threads() rows before and after loading, loaded threads, offsets, journal bytes (unchanged by reading), hook exits, decisions and MACs, and the policy digest (e6b17cc24a, as pinned).
  • CI on this head: all green on the first pass, with no rerun: admit, policy, and both runs of compose, test 3.11/3.12/3.13 and windows (19-20 min).

1. ed25519.sign(seed, message, public=None).

  • The hazard is real. sign derives the nonce from the seed prefix and the message only (ed25519.py:113) and hashes the caller's public into the challenge (:115); it checks only the length (:111). I signed one message under one seed twice, once with the right key and once with an unrelated 32-byte key: same R, (S1 - S2)/(h1 - h2) mod L equals the private scalar, and a signature forged with the recovered scalar on a new message verifies under the real key. The wrong-key signature alone does not verify, so a mismatch is visible where checked, but the leak happens on emission.
  • Reachability in this repository: none found. The only caller of sign is sign_dsse (dsse.py:46). Of the four callers of sign_dsse (certificate.py:428-429, dsse.py:95 sign_chain_head, tlog.py:170), only TransparencyLog.append passes public=. That value is _seed_public, derived from the same seed in __init__ (tlog.py:96-97) and never reassigned. TransparencyLog takes no key argument, and open_readonly (:117) has no seed, so append refuses before signing (:158). The public log.public attribute is not what is signed with, so assigning it changes nothing. A mismatch needs code to overwrite the private _seed_public (or _seed), and the same checkpoint payload signed twice under one seed. Nothing is deserialized into these fields, and no CLI, witness or adapter path supplies a key (witness.py:52 constructs the log from the seed only; cli.py:728 derives the public key separately and passes it nowhere).
  • Public surface: neither sign nor sign_dsse is in syberlabs.__all__ (the package exports sign_chain_head, TransparencyLog and generate_signing_key), and neither appears in docs/ or examples/. Both are importable module functions, though, and the parameter is a key-recovery footgun on a signing primitive.
  • The safer form is better. Memoize the derived public key inside ed25519.py (a private _public(seed) behind functools.lru_cache(maxsize=8), used by both public_from_seed and sign), and drop public from sign and sign_dsse. The speed is the same: sign_dsse still costs two multiplications (one to derive the key, one for R), and a log or witness re-signing under one seed costs one, as in this PR. No caller-supplied value ever reaches the challenge hash. Two costs: the cache keeps up to 8 seeds as keys for the life of the process (the log and witness already hold theirs in _seed, and certify is a short CLI process), and a bytearray seed must be passed through bytes() to be hashable.
  • Severity: low (latent, unreachable today; catastrophic if reached). Recommendation: follow-up packet, not a merge block. Swap the parameter for the memo and keep CachedPublicKey's equality tests. Land it before any new caller of sign/sign_dsse appears.

2. Journal stat-before-open: the claim holds.

  • read_new is reached only under Journal.locked() (session.py:73, :95; _case at :651 runs inside guarded methods).
  • _write checks the size, then appends, flushes, fsyncs and closes its handle (journal.py:189-195) inside the same locked operation. So a reader holding the lock never overlaps an open writer.
  • On size == offset, the old reader also returned [] (it opened the file and read zero bytes). The only change is where the size comes from, so a record can be hidden only if stat reports less than the bytes present.
  • Windows, measured here (Win 11 26200, Python 3.12.6): os.stat in a second process saw the appended size while the writer's handle was still open, both after a flush with no fsync and after fsync. Only the directory-entry size (os.scandir) lagged, and read_new does not use it.
  • POSIX local file systems: st_size reflects a completed write immediately.
  • Residual: on a network file system with attribute caching (NFS acregmin), a stat without an open skips close-to-open revalidation, which the old reader's open forced. The journal's flock model is single-host already, so this is a note, not a finding.

3. Kit.threads no longer verifies chains. I altered one thread of the receipt-built home four ways and ran each surface fresh on base and head:

  • Dashboard is unaffected. It does not use Kit.threads: collect opens every thread through thread_model (dashboard.py:88-95, :152). Base and head both show unreadable (journal_corrupt).
  • Kit.threads now lists the altered thread as healthy. This covers syberlabs threads (cli.py:502), the inspector's /api/threads (inspector.py:121), Runner.once (build.py:1536) and settle (settle.py:300). With an edited body it lists started; with the base observation deleted or unverified it lists incomplete, with the "start again" remedy. Base refused the whole listing with journal_corrupt. The first action on the thread (open, status, a runner pass, start resuming it) still refuses journal_corrupt. This is the deferral in the new GAPS 33 note.
  • New, and the only defect I found in the PR: a hash-invalid record whose observed body is not an object makes _incomplete raise an uncoded TypeError (build.py:747 into latest_observation, build.py:860). That takes down the listing for every thread; base gave a coded journal_corrupt.

4. Query-guard hook loading: confirmed.

  • hook_command (query_guard.py:294-296) always passes -I and the host's own Path(__file__).resolve().
  • _beside (:68-75) loads canonical.py and _patterns.py from that file's own directory. Both are stdlib-only, so nothing comes from the import path.
  • Planted-package test: I ran the hook from a directory holding a planted syberlabs/ package (__init__, canonical, _patterns, metrics, query_guard) plus top-level canonical.py and _patterns.py, each of which writes a marker when imported, with PYTHONPATH pointing at that directory. Under -X importtime the hook imported no syberlabs module, no marker was written, and the decisions were correct (pass 0, secret 2).
  • The policy digest is unchanged (e6b17cc24a, the same on base and head).

Read. All six changes do what the handoff says, and every persisted output I could compare is byte-identical to base, including the golden traces. The Ed25519 parameter is a correct speed-up behind a sharp edge: no path in this repository reaches it today, but the same speed is available without trusting callers, so it should be replaced. The listing change is the documented trade. Its one new defect is a crash where a coded refusal used to be, and it needs write access to the journal to trigger.

Findings (none blocks the merge).

  1. low, ed25519.py:99-115, dsse.py:39-46, tlog.py:97,170: caller-trusted public enables private-key recovery if two keys ever meet one seed and one message. Unreachable today. Follow-up packet: a per-seed memo inside ed25519.py and drop the parameter.
  2. low, build.py:745-748: _incomplete runs latest_observation on unverified records, so a malformed record whose body is not an object crashes every listing with TypeError. Catch (KeyError, TypeError, AttributeError) there and raise Rejected("journal_corrupt", ...), or classify only records whose event.body is a dict.
  3. low, build.py:762-771 (GAPS 33 note): an altered thread now lists as started or incomplete (syberlabs threads, inspector, runner, settle). The dashboard still shows unreadable (journal_corrupt), and every action refuses. Documented; accept.
  4. Pre-existing, not this PR (same on base): read_new advances the offset (journal.py:173) before Session._load checks the chain (session.py:119-132). After a refused load, the verified prefix stays in memory, and the next open in the same process succeeds on that prefix. I saw a second open succeed and the dashboard report complete on base and head alike. In a long-lived inspector, the second /api/threads request shows an altered thread as healthy. Worth its own packet: on rejection, drop the thread's in-memory state and offset.
  5. note, journal.py:153-157: stat-before-open trusts stat freshness, which holds on local NTFS and POSIX file systems but not on attribute-caching network file systems. The journal is single-host already.

Verdict: pass

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant