Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ On a release, the maintainer moves the accumulated `[Unreleased]` entries into a
- **docs(state-of-the-project): reconcile every document with the code as it actually is.** A ground-truth pack was extracted from the tree (CLI surface, the full env-var inventory incl. the prefix-constructed `NILCORE_{CODEX,CLAUDE}_{MODEL,EFFORT}`, package inventory + metrics, emitted event kinds, invariant enforcement sites, shipped-vs-gated status), then all 30 docs were audited against it and each repair was **independently refuted** by a second reader; 15 surviving defects were re-fixed. Headline corrections: `router.Classify(goal)` returns `run|build|swarm` — **not** `|decompose` (`Decompose` is a fourth `Preset`, opt-in only, excluded from `All()`), a claim three contract files asserted wrongly; `docs/ARCHITECTURE.md` overstated the container egress control as SSRF-proof/IP-layer when `AllowEgressVia` only sets `--network bridge` + `HTTP(S)_PROXY` and no packet filter exists — the proxy is the only *sanctioned* way out, not a hard network wall; metrics refreshed everywhere (120 packages, 375 source + 406 test files, ~89.8K non-test LOC); the six `NILCORE_OPENROUTER_*` / `NILCORE_COMPAT_*` vars documented only in suffix shorthand are now spelled out; `docs/REFERENCE.md` gained an explicit upgrade note for the two settings whose MEANING changed at `573a4df` (`NILCORE_SELFIMPROVE_AUTOAPPROVE=1` was a no-op and now really merges; swarm shards now reach their preset's declared hosts). Roadmap/plan docs keep their plans but now carry accurate SHIPPED / PARTIALLY SHIPPED / NOT-BUILT status and no longer name pruned symbols as live. `CHANGELOG.md` history untouched (append-only). Markdown only — no `.go`, `Makefile` or CI file changed. _Owns:_ `*.md`, `docs/**`. _(Phase 16 — documentation)_

- **chore(features-review): reconcile shipped-but-inert subsystems, then a full docs pass over the assembled diff.** A features-review sweep found a cluster of built-but-silently-dead paths (several of which reported false green), fixed each with a discriminating test, and then reconciled the docs to the code as it now is. **Verifier integrity:** the `vcache` key now folds in the browser-verify command, `NILCORE_VERIFY_PACKS`, `NILCORE_EVIDENCE_VERIFY`, `NILCORE_EVIDENCE_MAX_AGE` and the sandbox image identity (a behavioral toggle can no longer serve a stale green), and the worktree content hash includes `.nilcore/artifacts` when evidence verification is on; evidence verifiers are now discovered **lazily at Check time** on run/chat/serve/resume (they were built eagerly and so never ran); tiered verify reads only the `go test` leg of a compound command and replicates `-run`/`-skip`; a new boot `validateVerifyEnv` exits 2 on a bad `NILCORE_VERIFY_PACKS`/`NILCORE_EVIDENCE_MAX_AGE`. **Artifact packs:** `code.test_passes` ran `go test -- '<pkg>'` (which never tested the selected package — a false green) → `go test '<pkg>'`; `code.build_passes` on an undetectable layout is now Unverifiable, not Pass; `schema_verify` events are actually emitted (the report's SchemaDefects section was permanently empty; `DefectMeta` gained `claim_id`/`reason` + json tags); `software.github_tag_exists` paginates; the audit pack rejects leading-dash patterns. **Swarm:** shards now actually receive their derived egress (an allowlist proxy is stood up and applied per shard box — previously every shard ran `--network none`, so the research preset could never verify green and `--egress-allow` was inert); `--resume` re-seeds skipped/queued/running shards (planned-DAG dependents were being dropped — a false green) and red budget-exhausted shards block a clean converge. **Graduated auto-approval (graapprove) was structurally unreachable:** `AllowBranches:["*"]` never matched slash-y branches (`path.Match`) and trust/rate keyed on per-run-unique scopes — now `*` matches any non-empty scope, and trust + the per-day rate window key on a stable scope **family** (`task/trig-123`→`task/*`, a bare sha→`#commit`); the protected-base floor (main/master/release/trunk/stable/prod*) and `DenyBranches` stay on the **concrete** branch; an empty scope never auto-approves. **Self-improve now really merges:** it had no merge step (it logged `self_edit_merged` and returned `merged=true` while nothing landed) — there is now a real `Flow.Merge` seam, `Run` returns the verified branch, `merged=true` means it actually landed, and new event kinds record the failure modes (`self_edit_merge_unwired`/`self_edit_no_branch`/`self_edit_merge_failed`). **Session / front door:** drive-terminal persist ran on an already-cancelled ctx so conversation state never wrote (now detached — `context.WithoutCancel` + 5s timeout); `Session.Checkpoint` (previously zero callers) is now called on chat exit and in serve's drainShutdown; steer markers (`!`/`/steer`) are stripped before the model sees the turn; memory/lessons (`MemoryContext`) and operator steering (`SteeringContext`) now reach chat/TUI and serve, not just run/watch; `-blast-radius` now fences chat and serve native drives (chat mints ONE shared blast budget for its proxy + sandboxes + gate; a serve-restart-resumed run gets the dollar ceiling); serve's `--webhook` no longer opens a second SQLite handle on the DB serve owns; the serve-embedded flywheel gate is deny-default (was bound to `os.Stdin`); a failed serve drive no longer renders "✓ not verified" over Telegram/Slack. **Security / robustness:** `browsersession.Observation.URL` is scrubbed for typed secrets (an I3 leak into the model prompt AND the append-only log); browseragent irreversible-signal, `router.Classify`, `trust.Classify` and `session.referencesGoal` moved from substring to word-boundary matching; Slack model text is escaped (was `<!channel>`-injectable), evidence-rich gates clip to Slack's 3000-char cap instead of silently auto-denying, 429/Retry-After is honored, and ws frame length is sanity-checked; Telegram scrubs the token from transport errors and clips long messages; MCP stdio writes are ctx-cancellable (a deadlock), responses size-capped, children reaped, stale resource/prompt wrappers pruned; one malformed SKILL.md no longer disables ALL skills; codeintel + live walks skip symlinks (I4) and vendor dirs (node_modules/vendor/.venv/dist/build), and several tool reads moved to `O_NOFOLLOW`; the termui spinner stop/animate deadlock is fixed (TryLock). **Backends / providers:** an empty-content native reply no longer poisons history and kills the run; Anthropic — a zero-frame clean-EOF stream is a retryable error, server-tool-only (`pause_turn`) turns are preserved, all beta headers collected; OpenAI — web plugin deduped, tool_result error signalled, max_tokens splice fixed; codex/claude-code `digText` matches the real claude-code stream-json (`result` key, `message.content[]`). **Also:** experience projection is rotation-aware (`Rebuild` drops stale keys); autosrc no longer kills a source permanently on a full queue; emit neutralizes ANSI in gate evidence; integrate stops on a failed `merge --abort`; the eventlog torn-tail heal won't drop committed lines; the report writer error-lists as json. **Docs reconciled to the code:** restored the AGENTS.md backend set (a find/replace had corrupted it to "Codex, Codex"), corrected Go 1.25 + dropped the never-adopted `sqlc` in PREREQUISITES, purged dead-symbol references (`impact.Localize`/SBFL, `policy.Gate`/`EgressWith`, `internal/blackboard`, `emit.NopEmitter`, `objective.MarkRun`), corrected the phantom `NILCORE_BROWSER_AGENT`/`NILCORE_DESKTOP_DARWIN` gates to the shipped gating, documented the previously-undocumented env vars in `docs/REFERENCE.md`, refreshed the stale REFERENCE.md metrics/CLI/package inventory, and stated the true Phase-15 status (web search shipped; the provider-compat eval P15-T13 was never built). _Owns:_ repo-wide — `internal/*`, `cmd/nilcore/*`, `AGENTS.md`, `CHANGELOG.md`, `README.md`, `STATE.md`, `docs/*`. _(features review)_
- **feat(capguard): enforce the Rule-of-Two lethal-trifecta gate on the main agent paths (chat/serve/swarm).** The browse/desktop tiers already evaluated capguard's lethal trifecta (untrusted web input ∧ private repo data ∧ open egress), but `do`/`run`/`chat`/`serve`/`swarm` never did even though they compute the same axes. A new `enforceRuleOfTwo` helper (`cmd/nilcore/ruleoftwo.go`) evaluates it once at each front door's startup, reusing `capguard.Evaluate`: **attended** chat/tui prompts a `ConsoleApprover` (a denial aborts); **headless** serve/swarm have no human present — the trifecta unattended is exactly what the Rule of Two exists to refuse, so they **fail closed** (deny-default approver ⇒ Refuse) unless a graduated-auto-approval envelope + earned trust auto-approves inside its blast fence; `run` is deny-all egress so the trifecta cannot form. Enforced by default with a single documented operator opt-out `NILCORE_RULE_OF_TWO=0` (new `envDisabled` helper, the negative twin of `envOptIn`). The shipped default egress is deny-all/narrow ⇒ axis C is false ⇒ verdict Allow ⇒ **byte-identical** for every normal run; only a genuinely wide egress (a wildcard or >`OpenEgressThreshold` hosts) combined with web tools + a mounted repo trips the gate — the exact configuration §2's Rule of Two targets. The `capguard` audit event is metadata-only (verdict + active axes + axis LABELS, never the resolved host list — I3/I7). _Owns:_ `cmd/nilcore/ruleoftwo.go`, `cmd/nilcore/{chat.go,swarm.go,main.go}`. _(Phase 16 / deferred hardening)_
- **feat(suspend): auto-reattach a woken serve drive to its preserved work + GC `suspend/` refs.** The 2026-07-10 audit stopped `sleep`/suspend from `git branch -D`-ing committed work (it is pinned to a durable `suspend/<id>` branch); this reads it back. `Checkpoint.ResumeBranch` correlates a suspended predecessor by the stable `<conversation>` prefix of the task id (`<conv>-<seq>`), and `orchestrator.executeSingle` bases the resumed worktree on that ref via a new `worktree.CreateFrom` instead of HEAD, retires the predecessor, deletes the consumed ref, and logs `task_resumed`. Correlation is safe because **only serve drives can sleep** (the `sleep` tool needs a Wake hook wired only in serve; `run` tasks `t-<time>` never suspend), and a stale/unresolvable ref falls back to `Create(HEAD)` — no regression, no data loss. GC: `Checkpoint.SweepSuspended` (best-effort on serve boot) prunes resolved refs and caps survivors to the N most-recent via a new `worktree.ListBranches`. I1 (`backend.Task`) untouched; the reattach test is proven discriminating (disabling `ResumeBranch` reddens four assertions). _Owns:_ `internal/agent/{durability.go,orchestrator.go}`, `internal/worktree/worktree.go`, `cmd/nilcore/main.go`. _(Phase 8 / deferred hardening)_
- **feat(egress-hard): OPT-IN hard egress boundary for the container sandbox backend (`NILCORE_EGRESS_HARD`).** The container egress allowlist is COOPERATIVE by default — a model command that ignores `HTTP(S)_PROXY` (`curl --noproxy`, raw sockets, `/dev/tcp`) reaches any host. Hard mode makes it UNBYPASSABLE: `applyContainerEgress` (under the opt-in) routes the box through the new `Container.AllowEgressViaHard`, which attaches the sandbox to a per-run `--internal` docker/podman network (no default route) and runs the allowlist proxy as a dual-homed GATEWAY container (a new hidden `nilcore egress-gateway` verb) the sandbox reaches by IP; a raw socket / `--noproxy` then has no route out and fails, while the sandbox keeps `--cap-drop=ALL`. Fail-closed on ANY setup error (stays `--network none`, never cooperative-fallback); the sandbox `--dns` is pinned at the gateway to blackhole in-sandbox DNS; an idempotent teardown + a label-scoped boot reaper (`reapHardEgress`, non-blocking, gated on the opt-in) reclaim orphans. Honest residuals documented (DNS-tunnel only mitigated; requires the nilcore image, not `debian:stable-slim`; Linux-container only, CI-validated). The namespace backend's empty netns remains the RECOMMENDED hard boundary; `NILCORE_EGRESS_STRICT` stays the fail-closed cooperative refusal. Default (opt-out) path is byte-identical. Unit-tested: the sandbox arg assembly (`AllowEgressViaHard`/`--dns`), the STRICT/hard decision seam (fake `setupHardEgressFn`), and the `egress-gateway` proxy logic; the container lifecycle is CI-only. _Owns:_ `internal/sandbox`, `cmd/nilcore/egress_gateway.go`, `cmd/nilcore/egress_hard.go`, `cmd/nilcore/chat.go` (applyContainerEgress), `cmd/nilcore/main.go` (dispatch + serve reaper), `docs/ARCHITECTURE.md`. _(Phase 7 / deferred hardening)_

- **chore(audit-2026-07-10-remediation): fresh 12-agent adversarial audit → fix every CRITICAL/HIGH/MEDIUM finding + the cheap LOWs; all three gates green.** A fresh adversarial audit (10 commissioned auditors + independent per-finding verification) at 63292d7 surfaced **1 CRITICAL, ~11 HIGH, ~12 MEDIUM** (plus LOW/hygiene), every one personally verified; all are fixed here with a discriminating test, and `make verify` + `make test-race` (0 data races) + `make tui-verify` are green. **CRITICAL:** the browser/desktop `{{secret:NAME}}` type action resolved ANY process env var (a confused-deputy exfil primitive — a hostile/injected page could type `{{secret:ANTHROPIC_API_KEY}}` into a form and submit it to an egress-allowed sink); it now honors an operator-declared per-task secret-name allowlist (`-secrets` / `NILCORE_{BROWSE,DESKTOP}_SECRETS`, default empty = deny-all) and capguard axis-B counts a secret-capable session. **HIGH:** the host-side git tool can no longer be tricked into repo-local-config RCE (writes inside `.git` are refused at the `worktreefs.writeNoFollow` chokepoint + the git tool passes per-command `--no-ext-diff`); container egress is documented as a cooperative proxy (not a hard boundary) with a `NILCORE_EGRESS_STRICT` fail-closed opt-in; vcache no longer replays a stale GREEN past a changed evidence artifact (the `.nilcore/artifacts` digest is folded into the key under evidence-verify); a value-bearing artifact claim can no longer ship a hollow green on a value-blind verifier (url_resolves/not_stale/variance_bounded); `sleep`/suspend preserves committed work on a durable `suspend/<id>` branch instead of `git branch -D`ing it; race-recovered verified work keeps its branch under KeepBranch; the desktop per-action gate is armed on `--mac-host` (every mutation gated); the webhook self-start is rate/label-bounded (denial-of-wallet); `nilcore chat`/`tui` resolve `-backend auto`; the circuit breaker is no longer poisoned by user steers/cancels. **MEDIUM:** the promote gate scopes on the target base (not the source tip); MCP responses are size-bounded (host OOM); `VAR=0` now DISABLES (not enables) NILCORE_{AUTONOMY,FLYWHEEL,LIVE_INDEX,LESSONS}; codeintel indexers read with O_NOFOLLOW + a size cap; the run/chat/serve advisor is metered against the budget wall; `swarm -blast-radius` is wired; the memory table is pruned/bounded; the webhook shares serve's one store handle; a cancelled drive unwinds cleanly (no false "Run errored"); a cleanly-errored task isn't re-executed on serve boot; the converged `nilcore build` deliverable is pinned before cleanup. **LOW:** eventlog redaction covers `[]string`/`json.RawMessage`; Slack WS frames are length-capped; the Telegram token can't ride a URL error. Integration-seam fix caught by the assembled-diff gate: a defense-in-depth `-c diff.external=` (empty) made git exec the empty string and broke every `git diff` — removed (the `.git` write-guard is the real defense). _Owns:_ repo-wide (see the diff). _(remediation)_

- **chore(license-apache-2.0)** — Add the Apache License 2.0, an RNT56/NilCore NOTICE attribution, and README license badge/section. _Owns:_ `LICENSE`, `NOTICE`, `README.md`, `CHANGELOG.md`. _(docs)_

Expand Down
Loading
Loading