Skip to content

Features-completeness review: fix 11 HIGH shipped-but-inert defects, then reconcile all docs#101

Merged
RNT56 merged 2 commits into
mainfrom
claude/nilcore-features-review-931610
Jul 10, 2026
Merged

Features-completeness review: fix 11 HIGH shipped-but-inert defects, then reconcile all docs#101
RNT56 merged 2 commits into
mainfrom
claude/nilcore-features-review-931610

Conversation

@RNT56

@RNT56 RNT56 commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Two commits: a features-completeness remediation, then a full documentation reconciliation.

573a4df — fix every finding from the features review

An 11-agent adversarial audit of the whole tree found 11 HIGH defects (all independently verified, four reproduced with runnable proofs), ~48 MED and ~70 LOW. The recurring theme was features that were built, tested, and documented — and completely inert, with green tests actively masking them. Every fix carries a discriminating test that fails on the pre-fix code.

Verifier integrity (I2) — four false-greens:

  • code.test_passes built go test -- '<pkg>', which tests the current directory, not the selected package. Now go test <pkg>.
  • The vcache key skipped .nilcore and omitted the browser-verify command / pack list, so a rewritten artifact or a newly-enabled check replayed a stale green. Key now covers them.
  • Evidence verifiers were built before the backend ran, when the artifacts dir is empty, so they never checked anything. Now discovered lazily at Check time.
  • swarm --resume dropped skipped DAG dependents, converging exit 0 over work that never ran. Now re-seeds skipped/queued/running shards; red budget-exhausted shards block a clean converge.

Security / liveness: typed secret leaking through Observation.URL into the prompt and the append-only log (I3); a spinner deadlock that wedged interactive chat; MCP's write side blocking uncancellably under its mutex; conversation state that never persisted (drive() cancelled its own ctx then wrote with it); an empty-content model reply poisoning history and killing the run.

Shipped features that were structurally inert: graduated auto-approval could never fire (path.Match("*", "task/x") is false; trust/rate/$ windows keyed on per-run-unique scopes) — now reachable and bounded, floor still on the concrete branch; self-improve logged self_edit_merged while nothing merged — now a real merge; swarm shards ran --network none — now reach their preset's declared hosts via a role-intersected allowlist proxy; memory/lessons/steering/blast-fencing reached only run/watch, now every front door.

Two deliberate, announced behavior changes: NILCORE_SELFIMPROVE_AUTOAPPROVE=1 was a no-op and now really merges; swarm code/fix/research shards now reach preset hosts.

000cccb — reconcile every document with the code

A ground-truth pack was extracted from the code, all 30 docs audited against it, and each repair independently refuted by a second reader (15 surviving defects re-fixed). Headline corrections: router.Classify returns run|build|swarm not |decompose; ARCHITECTURE.md overstated container egress as SSRF-proof/IP-layer when it is proxy-env-only; metrics refreshed (120 pkgs, 375 src + 406 test files, ~89.8K non-test LOC); undocumented env vars added; upgrade notes for the two behavior changes. CHANGELOG.md history untouched (append-only). Markdown only.

Verification

make verify green (119 packages, 0 lint issues); go test -race clean across 17 concurrency-sensitive packages; -tags tui builds; default binary links zero Charm (I6); GOOS=linux sandbox cross-compiles.

🤖 Generated with Claude Code

RNT56 and others added 2 commits July 10, 2026 13:44
An 11-agent adversarial audit of the whole tree found 11 HIGH defects (all
independently verified, four reproduced with runnable proofs), ~48 MED and
~70 LOW. This fixes all of them. Every fix carries a discriminating test:
one that fails on the pre-fix code and passes on the fix.

The verifier is the only authority on "done" (I2), and it was compromised
four independent ways:

- code.test_passes built `go test -- '<pkg>'`, which go test reads as
  test-binary args for the CURRENT directory. The selected package was
  never tested; a green root greened a red package. Now `go test <pkg>`.
- The vcache key under-covered the composite it wrapped: the content hash
  skipped .nilcore (so a rewritten artifact replayed an old chain-verified
  pass) and the key omitted the browser-verify command, the verify-pack
  list, the evidence toggles and the sandbox image. Turning a check on
  replayed a green recorded with it off.
- Evidence verifiers were discovered EAGERLY, at env construction, before
  the backend ran — when .nilcore/artifacts/ is necessarily empty. The
  composite froze with zero evidence legs and never checked the run's own
  artifact. Now lazy, as the build path already was.
- swarm --resume re-seeded only failed and passed-unmerged shards. A DAG
  dependent skipped because its dep failed persists as StatusQueued, which
  neither source reads, so a resumed run converged exit 0 with planned work
  never executed. Red budget-exhausted shards now also block a clean converge.

Also: the tiered fast path armed on the wrong leg of a compound command and
dropped -run/-skip; validateVerifyPacks (the documented "explicit startup
signal") had no caller at all.

Security and liveness:

- browsersession scrubbed typed secrets from page text, title, console and
  element values but NOT Observation.URL — a secret typed into a GET form
  reached the model prompt and the append-only event log. Its test never
  asserted the URL, so it passed blind (I3).
- termui's spinner stop closed its channel and joined the animator while
  holding the console mutex the animator needed. Reproduced; fixed with
  TryLock so a dropped frame, never a wedge.
- MCP's stdio WRITE side was not ctx-cancellable: Encode blocked forever
  under the transport mutex against a peer that stopped draining stdin,
  permanently leaking a serve drive slot. Responses are now size-capped
  (a hostile server could OOM the host) and children are reaped.
- session persist ran on the ctx the drive had just cancelled, so
  conversation state never wrote and the error was swallowed. Detached via
  context.WithoutCancel. Session.Checkpoint had zero callers; now wired.
- An empty-content model reply was appended verbatim and poisoned history,
  killing the run terminally. Reachable: the Anthropic decoder drops
  server_tool_use blocks, so a web-search-only turn decodes to empty.

Shipped features that were structurally inert:

- Graduated auto-approval could never fire. Presets use AllowBranches ["*"],
  matched by path.Match, whose * never crosses '/' — but every real gate
  scope is a slash-y branch. Trust and the per-day rate/dollar windows keyed
  on per-run-unique scopes, so MinSuccesses was unsatisfiable and MaxPerDay
  and MaxDollarsDay never bound. Now * matches any non-empty scope and the
  three windows key on a stable scope FAMILY. The protected-base floor,
  DenyBranches, the kill-switch and the chain-verified requirement are
  unchanged and still evaluated on the CONCRETE branch. Fixing reachability
  exposed the dollar-window bug, which is fixed here too.
- self-improve had NO merge step: Propose logged self_edit_merged and
  returned merged=true while nothing merged. A real Flow.Merge seam now
  lands the verified branch, and merged=true means it landed.
- swarm derived each shard's egress and dropped it on the floor; every shard
  ran --network none, so the research preset could never verify green and
  --egress-allow was inert. The allowlist proxy now enforces the
  ROLE-INTERSECTED set (never the operator's wider tree) and the change to
  what a shard may reach is announced and audited.
- memory/lessons and operator steering reached only run/watch, never the
  doors people use (chat, TUI, serve). -blast-radius fenced neither chat nor
  serve native drives. A serve-restart-resumed run had no dollar ceiling.
  serve's --webhook opened a second SQLite handle on the DB serve owned.

BEHAVIOR CHANGE, deliberate and double-opt-in: with
NILCORE_SELFIMPROVE_AUTOAPPROVE=1 the flywheel previously performed a no-op
and now performs a real merge. DefaultScope still forbids the verifier of
record, the core loop and every contract file, the execution-time Changed
screen still fails closed, and the edit is still verifier-green first.

BEHAVIOR CHANGE, announced: code/fix/research swarm shards now reach the
hosts their preset declares (previously none). Deny-all presets stay
--network none; a proxy that cannot bind fails closed.

Docs reconciled to the code as it now is: AGENTS.md's constitution had been
corrupted by a find/replace into "the native loop, Codex, and Codex";
PREREQUISITES demanded Go 1.23 while go.mod requires 1.25; four merged PRs
had no CHANGELOG entry (CLAUDE.md §6); REFERENCE.md was a full phase stale;
dead references to pruned symbols (impact.Localize, policy.Gate, EgressWith,
blackboard) and two documented-but-never-implemented env gates were removed;
nine dark env vars are now documented.

make verify green (119 packages, 0 lint issues); go test -race clean across
17 concurrency-sensitive packages; -tags tui builds; the default binary
still links zero Charm (I6); GOOS=linux sandbox cross-compiles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extracted a ground-truth pack from the tree (CLI surface + per-command flags,
the full env-var inventory, package inventory + metrics, emitted event kinds,
invariant enforcement sites, shipped-vs-gated status), audited all 30 docs
against it, and had every repair independently REFUTED by a second reader.
Fifteen surviving defects were re-fixed. The code is untouched: markdown only.

Load-bearing corrections:

- router.Classify(goal) returns run|build|swarm — NOT |decompose. Decompose is
  a fourth Preset, opt-in only ("Classify never auto-selects it; it is not in
  All()"), reached solely via `nilcore decompose` / `do -as decompose`.
  CLAUDE.md, AGENTS.md and docs/TASKS.md all asserted the wrong return set.

- docs/ARCHITECTURE.md overstated the container egress control. It claimed an
  allowlist proxy "is the only way out" and summarized it as an "SSRF-safe
  egress allowlist". In fact Container.AllowEgressVia only sets
  --network bridge and HTTP(S)_PROXY, and no iptables/nftables filter exists
  anywhere in the tree: enforcement is at the PROXY layer, honored by
  proxy-aware clients, not at the packet layer. The proxy is the only
  *sanctioned* way out, not a hard network wall. Egress is off by default, and
  the namespace backend (the Linux default) has no proxy path and is
  unconditionally deny-all. Both the §Execution-model prose and the §Security
  summary now say this precisely.

- Metrics refreshed against the code everywhere they appear: 120 packages
  (111 under internal/), 375 source + 406 test files, ~89.8K non-test LOC,
  ~177.5K total, Go 1.25.

- Six operator env vars were documented only in suffix shorthand
  (NILCORE_OPENROUTER_{MODELS,REASONING,TRANSFORMS,PLUGINS},
  NILCORE_COMPAT_{AUTH_SCHEME,KEY_ENV}) and so were unfindable by their exact
  name; they are now spelled out, along with the four prefix-constructed
  NILCORE_{CODEX,CLAUDE}_{MODEL,EFFORT} that a literal grep misses entirely.

- docs/REFERENCE.md gained an upgrade note for the two settings whose MEANING
  changed at 573a4df: NILCORE_SELFIMPROVE_AUTOAPPROVE=1 was a no-op and now
  performs a real merge, and `nilcore swarm` shards now reach their preset's
  declared hosts (they previously ran --network none).

- Roadmap and plan docs keep their plans but now carry accurate SHIPPED /
  PARTIALLY SHIPPED / NOT BUILT status, and no longer name pruned symbols
  (impact.Localize, policy.Gate, EgressWith, internal/blackboard,
  emit.NopEmitter, objective.MarkRun, route.SingleRouter) as live. Where a doc
  claimed a feature worked that in fact never did — graduated auto-approval
  was structurally unreachable; self-improve logged a merge it never performed
  — it now says so plainly rather than quietly rewriting itself.

CHANGELOG.md history is untouched (append-only; 224 -> 225 entries, none
removed). No .go, Makefile or CI file changed; `go build ./...` still clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@RNT56 RNT56 merged commit e8418e5 into main Jul 10, 2026
7 checks passed
@RNT56 RNT56 deleted the claude/nilcore-features-review-931610 branch July 10, 2026 16:29
RNT56 pushed a commit that referenced this pull request Jul 10, 2026
Rebasing the audit-remediation + deferred-hardening branch (#100) onto
main pulled in #101's features-review, which independently fixed three of
the same defects #100 did. Dedupe to main's canonical implementation
(which fully subsumes #100's intent) so the gate passes:

- MCP response OOM guard: drop #100's boundedReader/maxMCPResponseBytes in
  favor of main's capReader/errResponseTooLarge (both cap at 8 MiB). Same
  guarantee, one implementation.
- Slack WS frame cap: take main's maxFramePayload (16 MiB) over #100's
  maxWSFrameBytes (64 MiB). main checks the raw uint64 before narrowing to
  int, so it rejects both the merely-huge and the high-bit wrap-negative
  length before make() — realign #100's discriminating ws_test.go to it.
- Artifact hollow-green: keep #100's H4 rename (urlClaim -> quoteClaim,
  moving the shared helper off the value-blind web.url_resolves onto the
  value-checking web.quote_exists) and point #101's two new EventSink
  tests (which only need a well-formed claim, then blank the title) at
  quoteClaim.

Also close a pre-existing CHANGELOG gap: the deferred Rule-of-Two and
suspend auto-reattach features shipped in #100 without ledger entries
(only egress-hard had one). Added both per CLAUDE.md §6.

No behavior change beyond the dedupe; make verify + test-race + tui-verify
all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RNT56 added a commit that referenced this pull request Jul 12, 2026
…~12 MED) + adversarial re-verification (#100)

* fix: remediate 2026-07-10 adversarial audit (1 CRITICAL + ~11 HIGH + ~12 MED)

Fix every finding from a fresh 12-agent adversarial audit at 63292d7, each with
a discriminating test. All three gates green: make verify, make test-race
(0 data races), make tui-verify.

CRITICAL: {{secret:NAME}} browser/desktop typing resolved any host env var
(confused-deputy exfil) -> per-task secret-name allowlist, default deny-all;
capguard axis-B counts a secret-capable session.

HIGH: git repo-local-config RCE closed via a .git write-guard + per-command
--no-ext-diff; container egress documented as cooperative (+ NILCORE_EGRESS_STRICT
fail-closed); vcache no longer replays stale-green past a changed evidence
artifact; artifact hollow-green refused; sleep/suspend preserves committed work;
race-recovered work keeps its branch under KeepBranch; desktop --mac-host gates
every mutation; webhook self-start rate/label-bounded; chat/tui resolve
-backend auto; circuit breaker not poisoned by steers/cancels.

MEDIUM: promote gate scopes on target base; MCP responses size-bounded; VAR=0
disables (not enables) AUTONOMY/FLYWHEEL/LIVE_INDEX/LESSONS; codeintel
O_NOFOLLOW + size cap; advisor metered against the budget wall; swarm
-blast-radius wired; memory table bounded; webhook shares serve's store;
clean cancel/error task lifecycle; converged build deliverable pinned.

LOW: eventlog redaction covers []string/json.RawMessage; Slack WS frame cap;
Telegram token can't ride a URL error.

Integration-seam fix: -c diff.external= (empty) made git exec "" and broke
every git diff -> removed; the .git write-guard is the real defense.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test+fix: adversarial re-verification hardening

Second-wave adversarial verification of the remediation diff (30 fixes
revert-tested in isolated worktrees + a completeness critic) found one live
regression and several under-tested fixes; addressed here.

fix(worktreefs): the .git write-guard was case-sensitive (comp == ".git"), so
.GIT/config bypassed it on case-insensitive filesystems (macOS APFS, Windows)
and landed in the REAL .git/config -- reopening the repo-local-config RCE. Now
folds case + strips trailing dots/spaces. Proven discriminating: with the
case-sensitive guard the new test plants a filter.*.clean into the real
.git/config and fails.

docs(agent): make the sleep/suspend comments honest -- committed work is
preserved on suspend/<id> as a durable RECOVERY anchor (never lost), but the
wake re-engages a fresh drive; auto-reattach + suspend/ GC are documented
follow-ups (was over-claiming reattach).

test: add discriminating tests for every previously-untested fix -- secret-name
allowlist axis-B, egress STRICT fail-closed, meteredAdvisor, swarm blast fence,
build deliverable pin, backend auto-resolve, redaction/slack-ws/telegram -- and
fix safeRev's test (it passed for the wrong reason in an empty tempdir).

All gates green: make verify, make test-race (0 races), make tui-verify.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(capguard): enforce Rule-of-Two trifecta on the main paths (chat/serve/swarm)

The lethal trifecta (untrusted web input ∧ private repo data ∧ open egress) was
evaluated only on browse/desktop; the main agent paths never checked it. Add a
shared enforceRuleOfTwo choke point evaluated once at each front door's startup
(where egress/web/repo are known), reusing capguard.Evaluate:
 - chat/tui/run (attended): a trifecta prompts once at the console.
 - serve/swarm (headless): a trifecta with no human FAILS CLOSED (Refuse) — the
   exact combination the Rule of Two exists to refuse; a graduated-auto-approval
   envelope may still auto-proceed inside its blast fence.
Default egress is deny-all/narrow, so the verdict is Allow and every normal run is
byte-identical; only a wide/wildcard egress config trips it. The audit event is
metadata-only (verdict + axes + labels, never the host list — I3/I7). Enforced by
default; NILCORE_RULE_OF_TWO=0 is the documented operator opt-out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(suspend): auto-reattach a woken drive to its preserved work + GC suspend/ refs

The audit fixed the sleep/suspend DATA-LOSS (committed work is pinned to a
durable suspend/<id> ref instead of being branch -D'd), but nothing read it back
(the wake re-drove from a fresh HEAD worktree) and the refs leaked.

AUTO-REATTACH: on a drive whose session has a suspended predecessor with a
preserved branch (correlated by the stable <conversation> prefix of the task id;
only serve drives can sleep, so run tasks never collide), the orchestrator now
bases the resumed worktree on that committed work via worktree.CreateFrom instead
of HEAD, retires the predecessor, deletes the consumed ref, and logs task_resumed.
A stale/unresolvable ref falls back to HEAD (today's behavior — no regression, no
data loss); no predecessor ⇒ byte-identical.

GC: Checkpoint.SweepSuspended (wired best-effort on serve boot) prunes suspend/
refs whose task is resolved and caps the survivors to the most-recent N, closing
the branch leak. New worktree.ListBranches helper. Backend contract I1 untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(sandbox): opt-in HARD container egress boundary via an --internal network + gateway

The container backend's egress allowlist was COOPERATIVE (--network bridge +
HTTP(S)_PROXY env) — bypassable by a command that ignores the proxy (curl
--noproxy, raw sockets, /dev/tcp). Add an OPT-IN hard mode (NILCORE_EGRESS_HARD):

 - a per-run --internal docker/podman network (NO default route) to which the
   sandbox attaches ONLY, so the proxy is the sole egress path — unbypassable at
   --cap-drop=ALL, no host root/NET_ADMIN;
 - the allowlist proxy runs as a dual-homed GATEWAY container via a new hidden
   "nilcore egress-gateway" verb (reuses policy.EgressProxy — no new module, I6);
 - the sandbox's --dns points at the gateway to blackhole in-sandbox DNS.
Fails CLOSED to --network none on any setup error (never cooperative-fallback);
idempotent teardown + a label-scoped reaper on serve boot reclaim orphans.

HONEST residuals (documented in docs/ARCHITECTURE.md + comments): DNS-tunnel exfil
is only mitigated, hard mode needs the nilcore/sandbox image (not bare debian), and
the lifecycle is Linux-container CI-validated (only the arg assembly + decision seam
+ gateway proxy are unit-tested on the host). The namespace backend's empty netns
stays the recommended hard boundary; NILCORE_EGRESS_STRICT stays the fail-closed
cooperative refusal. Default (opt-out) path is byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: satisfy golangci-lint (ineffassign + ST1005)

- orchestrator.go: drop the dead resumeBranch/resumedFrom reset on the resume-fallback
  path (wt stays nil ⇒ HEAD fallback runs; the vars are not read again) — ineffassign.
- ruleoftwo.go: lowercase the leading word of two error strings — ST1005.
Both are behavior-preserving.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: reconcile #100/#101 overlapping fixes after rebase onto main

Rebasing the audit-remediation + deferred-hardening branch (#100) onto
main pulled in #101's features-review, which independently fixed three of
the same defects #100 did. Dedupe to main's canonical implementation
(which fully subsumes #100's intent) so the gate passes:

- MCP response OOM guard: drop #100's boundedReader/maxMCPResponseBytes in
  favor of main's capReader/errResponseTooLarge (both cap at 8 MiB). Same
  guarantee, one implementation.
- Slack WS frame cap: take main's maxFramePayload (16 MiB) over #100's
  maxWSFrameBytes (64 MiB). main checks the raw uint64 before narrowing to
  int, so it rejects both the merely-huge and the high-bit wrap-negative
  length before make() — realign #100's discriminating ws_test.go to it.
- Artifact hollow-green: keep #100's H4 rename (urlClaim -> quoteClaim,
  moving the shared helper off the value-blind web.url_resolves onto the
  value-checking web.quote_exists) and point #101's two new EventSink
  tests (which only need a well-formed claim, then blank the title) at
  quoteClaim.

Also close a pre-existing CHANGELOG gap: the deferred Rule-of-Two and
suspend auto-reattach features shipped in #100 without ledger entries
(only egress-hard had one). Added both per CLAUDE.md §6.

No behavior change beyond the dedupe; make verify + test-race + tui-verify
all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: RNT56 <rnt56.2b@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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