[1.0.0] Design: failproofaid — Rust daemon, thin client, and unified collector - #601
Draft
NiveditJain wants to merge 2 commits into
Draft
[1.0.0] Design: failproofaid — Rust daemon, thin client, and unified collector#601NiveditJain wants to merge 2 commits into
NiveditJain wants to merge 2 commits into
Conversation
Design document for v1.0.0, for review. No implementation. Proposes turning failproofai from a stateless cold-start-per-event hook into a long-lived daemon that also absorbs agenteye-collector, so one process per machine both enforces policy and collects agent sessions. The shape: a Rust supervisor owning the socket, verdict, spool, shipper and tailers, driving unprivileged Node workers that host the existing TypeScript policy engine unchanged. The runtime decision (the collector is Rust) and the privilege-separation decision resolve to the same topology — the unprivileged worker simply happens to be a JS runtime. Three things it unlocks that are impossible today: policies that remember (require-tests-before-stop), one collector covering all 12 CLIs instead of 3, and a timeline joining hook verdicts to transcript content that neither product can compute alone. Includes eight findings in the current codebase that this architecture would amplify — chief among them that the builtins shell out to git/gh with a cwd taken verbatim from the client payload, which is harmless in a user process and root RCE in a root one — plus fifteen ranked silent-enforcement-loss risks, each with a mechanical guard rather than a review note, and a 16-stage delivery sequence that front-loads the value which needs no Rust. Three recommended deviations from earlier decisions are flagged for the review rather than folded in silently, and eight open questions are listed, including one load-bearing dependency on the AgentEye ingest endpoint being an upsert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ex49L3LF4YmcWicwwZWZXg
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Two review changes to the failproofaid design doc. 1. The dashboard's re-architecture moves from out of scope to in scope. It is not just repackaged: today two page files import the twelve parser modules directly and re-parse transcripts per request, two Server Actions call writeHooksConfig with no gate, there is no middleware.ts in the repo at all, and app/api/auth is an identity cache for the audit feature rather than an access gate. That shape is redundant in single-user mode and a confused deputy under a root, multi-user daemon. Reads and writes both move to the daemon, the dashboard acquires a uid-bound identity, and it goes live instead of polled. Lands as stages 4b and 13b. 2. Adds "Staying current" — the update channel and vendor drift detection. There is no update path in the product today: no version check, no update notifier, no update command. Project scope drifts forward through npx; user scope bakes an absolute binary path into the vendor settings file at install and freezes. So a vendor schema change surfaces as "it quietly stopped catching things." The daemon auto-updates on the stable channel with a health-gated swap and automatic rollback, gains a failproofai update command, and detects drift three ways — unrecognized transcript shapes, vendor config that no longer matches what we wrote, and silence (a session file growing while zero hook events arrive). Lands as stages 7b and 11b. Also: F9 (nothing knows it is out of date), R16-R18, tests 9-11, and open question 8 rewritten — it previously proposed no self-update by default, which this reverses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ex49L3LF4YmcWicwwZWZXg
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.
What this is
A design document for review — no implementation. It proposes re-architecting failproofai
from a stateless, cold-start-per-event hook into a long-lived daemon that also absorbs
agenteye-collector, so one process per machine both enforces policy and collectsagent sessions.
📄
docs/superpowers/specs/2026-07-27-failproofaid-daemon-design.mdDeliberately a draft. The expectation is several rounds of review before any code is written.
The shape
A Rust supervisor owns the socket, the verdict, the durable spool, the shipper, and the
transcript tailers. It drives unprivileged Node workers that host the existing TypeScript
policy engine unchanged.
That is not a compromise between two decisions — it is the same answer arrived at twice. The
runtime decision (the collector is already Rust, and it merges into one binary) and the
privilege-separation decision land on identical topology; the unprivileged worker simply
happens to be a JavaScript runtime.
Three rules hold it together:
cheap, because the dominant failure mode of "Rust daemon + TS engine" is someone later
porting "just the hot builtins" for speed — and then the Rust one says allow while the JS
one says deny.
exit code. Composition is a lattice meet, never a join, so no worker message can turn a
root DENY into an ALLOW. Absence is never allow.
transcript directory is a one-line exfiltration primitive.
Why it's worth doing
require-tests-before-stopis unwritable today because "didtests run this session?" has no answer. The five existing stop-gates fake state by
re-deriving it every fire —
require-ci-green-before-stopspawns ~4 subprocesses includinga 15-second
ghcall on every Stop.ships 3. The audit pillar is already a collector that ships nowhere.
output and tool results. Neither product can compute the join alone.
quietly stop working. Nothing in the product checks whether it is out of date — and only a
long-lived process can notice that a vendor has moved.
What reviewers should push on
Three recommended deviations from decisions already made — raised explicitly rather than
folded in silently, because they change agreed scope:
locally", not "the socket was missing". As specified, the first stale socket after a reboot
stops every agent on every machine.
--useras the default and the only npm-installable mode. A root daemoninstallable from npm means a compromised release runs as root on every developer machine
in the org.
npx -y failproofai; under per-platformoptionalDependenciesyoudon't have to.
Nine findings in the current codebase that this architecture would amplify, several worth
fixing regardless. The sharpest: the builtins shell out to
git/ghwith acwdtakenverbatim from the client payload — harmless in a user process, root RCE on demand in a
root one. Also: the activity store writes 0644 on shared boxes, a cloned repo's policy file is
executed on the first hook event with no prompt, and the collector's documented install pipes
a remote installer straight into a shell — a pattern failproofai's own
block-curl-pipe-shbuiltin denies. (That builtin blocked the first attempt to open this PR, because the body
described the finding literally. Working as intended.)
Eighteen ranked silent-enforcement-loss risks, each with a mechanical guard rather than a
review note — this repo's characteristic bug is the one where everything looks green and
nothing is enforced. Notably, an unresolvable
optionalDependencyis a successful npminstall, and custom policies would silently vanish after event #1 in any long-lived process
because no existing test fires two events in one process.
Nine open questions, including one load-bearing dependency: whether the AgentEye ingest
endpoint upserts on
event_id. Much of the design's simplicity rests on it.Sequencing
Sixteen numbered stages plus four lettered sub-stages, ordered so value lands before the Rust
risk. Stages 0–2 need no Rust at all — they make every existing user's hook materially
faster and fix five present-tense vulnerabilities, so the project has shipped something real
even if the Rust work slips.
Point of no return is stage 14, the first release where an install can transmit session
content. Before it, local-first is a property of the artifact; after it, a configuration.
Revision 2
The dashboard is in scope, and needs more than repackaging
Previously listed as out of scope with "its internals do not change here." That was wrong.
What the dashboard actually is today, verified:
lib/<cli>-projects/-sessionsmodules directly andre-parse transcripts from disk on every request — a second, independent reader of the exact
files the daemon will already be tailing incrementally.
update-hooks-config.tsandupdate-policy-params.tscallwriteHooksConfigdirectly —a second writer that bypasses every precedence rule the daemon would enforce.
middleware.tsin the repo at all.app/api/auth/is an identity cachefor the audit feature; nothing under
app/actions/orapp/project/consults it. It is notan access gate.
That shape is merely redundant in single-user mode. Under a root, multi-user daemon it is a
confused deputy sitting on top of the authority boundary the whole design is built around — a
Next server that connects as whoever started it and answers to whoever can reach it holds
strictly more authority than the person looking at the screen.
So: reads and writes both move to the daemon, the dashboard acquires a uid-bound identity, and
it goes live instead of polled. Lands as 4b (leaves the main tarball) and 13b (onto the
daemon). A side effect worth noting — 13b resolves open question 3 by making
rusqlitetheonly SQLite reader in the product.
Staying current — the update channel and drift detection
The reported problem: vendors change a schema and the system starts breaking, with no
automatic way to pick up the fix.
There is no update path in the product at all — no version check, no update notifier, no
updatecommand anywhere in the repo (new finding F9). The two install shapes rotdifferently, and backwards: project scope writes
npx -y failproofai, which re-resolvesand drifts forward on its own, paying a registry round trip on the hot path. User scope —
the recommended one — bakes the result of
which failproofaiinto the vendor's settings fileat install time and then sits there until a human upgrades it manually. A machine set up in
March is running March's parsers today.
Two halves, deliberately separated:
stablechannel by default, with channels,pinning, and an org disable. Health-gated swap: verify signature, stage, exec the staged
binary against a canned deny vector, swap atomically, keep N-1, and roll back
automatically if the post-swap health check fails. Drain and hand over the listener rather
than dropping in-flight requests — under fail-closed, a refused connection blocks the user's
agent. Plus
failproofai updatewith--check,--channel,--rollback. The hardconstraint: a release is only visible to updaters after every platform artifact passes the
verify-platformgate — otherwise auto-update turns R12's partial publish from "some newinstalls break" into "the fleet breaks within one check interval."
gap is the lag before anyone knows. Three signals: unrecognized transcript shapes counted
rather than silently skipped; vendor settings files re-read and compared against what we
wrote (self-healing, which is the agreed careless-human threat model); and silence — the
CLI is installed, its session file is growing, and zero hook events arrived. That last one
is "enforcement is off and nobody noticed" stated directly, and it is structurally
unobservable in a per-event process, because the process that would notice is the one that
never ran.
One fork flagged for an explicit decision (open question 9): shipping the parts that rot
fastest — hook config key names, event spellings, path layouts — as a signed data manifest on
a faster cadence than the binary. Recommended for parsing and detection only, never the verdict
path, because a manifest a root daemon fetches and acts on is one slip from being a remote code
channel.
Open question 8 was rewritten: it previously proposed no self-update by default,
deferring to the OS package manager. This reverses that. What still needs sign-off is the
signing key and its custody — that key now authorizes root code on every customer machine.
Note
.github/workflows/ci.ymlcurrently triggers only onmain, so PRs into1.0.0will runzero CI and appear green. (This PR gets CI because its own base is
main; the stage PRsbased on
1.0.0would not.) That fix is documented in the design but deliberately not madehere — this PR changes no workflow file and no
CHANGELOG.md; it is documentation only. Bothbelong to the first implementation PR.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ex49L3LF4YmcWicwwZWZXg