Skip to content

#182: Harden the 0.6.0 release: rerank deadline, pinned embedders, MCP rerank cap, release checks - #187

Merged
devmaxxx merged 18 commits into
mainfrom
fix/release-0-6-0-hardening
Oct 16, 2026
Merged

devmaxxx merged 18 commits into
mainfrom
fix/release-0-6-0-hardening

Conversation

@devmaxxx

@devmaxxx devmaxxx commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Summary

Hardening from the 0.6.0 audit, in one PR. Closes #182, closes #183, closes #184, closes #185, closes #186.

  • Rerank deadline (Rerank command has no timeout: a hung claude -p freezes ask and serve #182). A model command that gives no answer within 180 s is stopped with its whole process group on unix, so the claude that sh started stops spending; the output reads share the deadline, and Ctrl-C/SIGTERM/SIGHUP are forwarded to the group and then to the previous handler. 180 s is two minutes under serve's 300 s reply window. On Windows only sh is stopped, as the docs say.
  • Pinned embedder revisions (Catalogued embedders are fetched at the hub's moving main, not a pinned commit #183). Each catalogued model is fetched at the hub commit it was measured on, not at main; an existing snapshot of that commit is adopted without a download.
  • MCP rerank allowance (MCP rerank has no spending bound #184). mcp_reranks (default 25, machine config only) bounds the rerank commands one MCP server process starts. It is charged by commands actually started (a failed or timed-out one counts, an exact id or a refused command does not); serve's reply carries the count.
  • npm package. One unscoped zerograph package on the public npm registry carries the macOS, Linux and Windows binaries and runs the one for its platform (npm refused zerograph-win32-x64 to spam detection, and one name means one placeholder and one trusted-publisher setup). Published through trusted publishing (OIDC, provenance); no .npmrc, no install script. One-time placeholder publish and trusted-publisher setup is runbook step 5 and must happen before the tag.
  • Rerank accounting and waits. A failed resident or local answer still charges the reranks it started to mcp_reranks; the command runner wakes on the model's exit instead of a 50 ms poll, never waits on a prompt writer a lingering child keeps blocked, and forwards signals to each running command's group.
  • Rename finished. Docs, plans, ADRs and bench scripts say zerograph. The old name stays only where it recognises an install made before the rename (.repograph/, repograph.toml, REPOGRAPH_*, old hooks and MCP entry), in the bench kit that drives the pinned fixture, and in recorded data (runs.jsonl, bench/results, read under the new tool key). The repograph bin alias and the @devmaxxx/repograph stub are dropped.
  • Release (release.yml publishes under the tag's version without checking Cargo.toml #185, 0.6.0 release notes, stale enrich references, and the old @devmaxxx/repograph package #186). The workflow refuses a tag whose version is not Cargo.toml's; publish skips versions already on the registry so a rerun completes. Plus docs/releases/0.6.0.md and the stale enrich references.

Test plan

  • cargo test and cargo clippy --all-targets -- -D warnings on macOS
  • the_stop_reaches_what_the_shell_started and tests/rerank_interrupt.rs fail with their fix removed
  • Code review at high with --fix, twice; second pass: allowance charged by started commands, 8 MiB pipe cap, 2 s read grace, SIG_ERR guard
  • Packed tarballs installed in a scratch project: zerograph reports zerograph 0.6.0 and is the only bin installed
  • CI green on macOS, Linux, Windows
  • Before tagging: the zerograph placeholder published and its trusted publisher set (runbook step 5)
  • Fixture bench at d9593d8 on beauty-crm 502e8a6d2, every floor holds:
    • gemma dense: keyword 40/40, paraphrase 20/30 (floor 18), code 12/12, p90 248 (floor 250)
    • gemma lexical: 39/40, 7/30, 12/12, p90 238
    • e5-small dense / lexical: 40/40, 9/30, 12/12, p90 237 / 39/40, 7/30, 12/12, p90 238, identical to the 2026-10-13 rows at 360a4c2
    • gemma rows recorded in bench/history/runs.jsonl; dense p90 is two tokens under its floor, the tightest margin

run_command waited on the configured command with no deadline, so an offline or unauthenticated claude -p blocked ask forever and froze serve's single answer loop. The command now runs in its own process group and is killed with everything it started at the deadline, which sits under serve's 300 s reply window. Closes #182.
A hub id is a moving branch, so a cold cache could load weights nobody chose. Each catalogue row now carries its commit and the fetch asks for it; a cache filled at that commit under refs/main is adopted without a download. Models trusted by name keep following main. Closes #183.
The agent decides every rerank: true call and each one spends tokens. mcp_reranks (25 by default, machine file only) caps them per MCP server process; past it the answer comes from the fused order with a line saying so, and 0 turns rerank over MCP off. Closes #184.
…package name as a stub

A tag naming another version would have published packages under a number the binary does not report, and a spent number is never given back. The release now fails first on a mismatch. @devmaxxx/repograph is released after the launcher as a stub that depends on @devmaxxx/zerograph and runs it, and CI runs it from the packed tarball. Closes #185.
…output reads

The command runs in its own process group so the deadline can stop what sh started, which took it out of the terminal's group: a Ctrl-C ended zerograph and left the paid call running. SIGINT, SIGTERM and SIGHUP are now passed to the group and then to the previous handler, so serve's flag still sees them. The reads after sh exits share the deadline, so a grandchild holding the pipes cannot hold the answer. DEADLINE drops to 180 s, two minutes under serve's reply window.

MCP ask returns the allowance when the rerank did not run (refused, failed, timed out, or the ask errored). The npm publish step skips versions the registry already has, so a rerun after a part-way failure completes.
The launcher and its platform packages become zerograph and zerograph-<platform> on the public npm registry, published from release.yml through trusted publishing, so an install needs no .npmrc or token and every version carries provenance. The old name, @devmaxxx/repograph, stays on GitHub Packages, the registry an old install's .npmrc names for the scope, and now depends on the unscoped zerograph. The runbook gains the one-time placeholder publish and trusted-publisher setup npm requires before a name's first release.
… starts

The allowance was taken before the answer and returned when a rerank notice appeared. An exact-id question, which never reaches the model, spent a slot, while a command that billed and then timed out or failed gave its slot back, so the cap did not bound spend. Context now counts the rerank commands it starts, serve's reply carries the count (absent from an older server, read as 0), and MCP charges by it.

Also from the review: a pipe keeps at most 8 MiB, the output reads get a 2 s grace past the deadline so an answer that arrived at the edge is not discarded, and a SIG_ERR from installing the interrupt forwarder is never chained to.
…me alias and stub

Docs, plans, ADRs and bench scripts now say zerograph. What stays under the old name is what recognises an install made before the rename: the .repograph store, repograph.toml, REPOGRAPH_* variables, the old hooks and MCP entry, and the bench kit that drives the pinned fixture. Recorded data (runs.jsonl rows, bench/results) is untouched; report.py and track.py read its old tool key as zerograph.

The launcher no longer installs a repograph command, and the @devmaxxx/repograph stub, its release job and its CI checks are gone.
The quiet check and the agent score match repograph processes and commands again, since the fixture's hooks and older binaries still run under that name. Paths to bench-kit files on disk are back to the names those files have. serve takes the rerank count before an answer's error propagates, so a failed answer no longer leaks its count into the next client's reply.
… exit

A resident answer that fails now sends its error and its rerank count instead of closing the connection, and the MCP server charges both that count and a failed local answer's, so a failure that already ran a paid model no longer slips past mcp_reranks. The command runner waits on the stdout reader before polling the exit, so a rerank no longer sits up to 50 ms after the model is done, and signal forwarding keeps one slot per running command so a command that finishes first cannot stop another's forward. The npm README quotes the current default embedder and its numbers.
npm refused the name zerograph-win32-x64 to spam detection, and four names each needed a placeholder and a trusted-publisher setup. The launcher now carries the macOS, Linux and Windows binaries under bin/<platform>/ and runs the one for its platform, so a release publishes one package, an install runs no script and fetches nothing else, and a lockfile still installs across platforms. CI installs the single tarball and checks the error a pack without this platform's binary gives.
@devmaxxx
devmaxxx merged commit d484b0e into main Oct 16, 2026
4 checks passed
devmaxxx added a commit that referenced this pull request Oct 16, 2026
Bounds the rerank command with a deadline and forwards an interrupt to it, pins every measured embedder to a hub revision, caps an MCP server's paid reranks and charges them by the model commands an answer starts, checks the tag against Cargo.toml before a release publishes, ships every binary inside one unscoped zerograph package on the npm registry through trusted publishing, and renames the remaining repograph mentions while keeping what recognises an install from before the rename. Closes #182, #183, #184, #185 and #186.
devmaxxx added a commit that referenced this pull request Oct 17, 2026
The npm page showed npm/zerograph/README.md, a short copy last touched in
#187, so the badges, navigation and comparison image from #188 never reached
it. scripts/npm-pack.sh now cuts the package README from the root one and
pins every relative path to the version's tag, since npm resolves none.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant