63 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 4e5e6bd2c0 |
feat(phase-C): cross-CLI hook enforcement via kei_bash/kei_edit/kei_write MCP tools
Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
Closes the "hooks only fire on Claude" gap. Phase C extends KeiSeiKit safety
enforcement (no-github-push, safety-guard, destructive-guard, citation-verify,
numeric-claims-guard) to any MCP-capable LLM CLI through a 3-tier honesty model.
## 3-tier model
TIER 1 (full native): claude (existing), grok (port hooks to grok settings.json)
TIER 2 (MCP-wrapped): copilot (--excluded-tools=shell + force kei_bash via MCP)
TIER 3 (advisory): agy + kimi (cannot disable native shell; prompt-level only)
## Design (Constructor Pattern)
1. hooks/_lib/policy-chain.toml — SSoT: which hooks gate which tool (bash/edit/write)
2. _primitives/_rust/kei-mcp/src/handlers/safe_tools.rs — new module, 3 built-in
MCP tools that synthesize Claude PreToolUse JSON, run hook chain, abort on
exit-2, exec on all-pass. Same input contract → hooks reused as-is, no rewrite.
3. tools.rs short-circuit: kei_bash/kei_edit/kei_write dispatched before atom layer
4. 6 wire scripts: orchestrator + one per CLI (Constructor Pattern, no mixin)
5. bin/kei mcp-wire arm
6. docs/encyclopedia/cross-cli-policy.md — honest 3-tier matrix + verification
## Double-enforcement guard
If kei-mcp invoked from a process with $CLAUDECODE=1 or $GROKCODE=1, the chain
SKIPS — native hooks already fired. Wire scripts set these env vars in the
MCP server registration for claude/grok respectively. On copilot/agy/kimi the
env is unset → chain runs.
## Smoke (verified live)
Block: kei_bash{command: forbidden-push-pattern}
→ JSON-RPC error -32603 with full "BLOCK — RULE 0.1 NO GITHUB PUSH" stderr ✓
Pass: kei_bash{command: "echo HELLO-FROM-KEI-BASH"}
→ result.content[0].text = "HELLO-FROM-KEI-BASH" ✓
tools/list: 4 built-ins present (spawn_agent + kei_bash + kei_edit + kei_write) ✓
## Tests
kei-mcp: 3/3 (tools_list assertions updated for atoms+4 built-ins).
Build clean with toml = "0.8" dep added.
## Out of scope (deferred)
- Codex CLI wiring (not installed locally)
- ACP middleware proxy (transport, not middleware — ruled out at research)
- Container/firejail sandboxing for agy/kimi (heavy; documented limit instead)
- Native Rust PatternGate migration (optimization, separate phase)
|
|||
| 3fec43ea7e |
feat(orchestrator): kei pick + spawn_agent MCP tool — true multi-LLM shell
Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
Closes the "Claude Code as single primary" gap. Now `kei` (no args) execs
whichever CLI is configured as primary, and ANY MCP-capable orchestrator
can spawn KeiSeiKit agents on any backend via the built-in spawn_agent tool.
## A — orchestrator picker
bin/kei now reads ~/.claude/config/primary.toml and execs that CLI instead
of hardcoding claude. New arms:
kei pick interactive menu → set primary → launch it
kei --on=<backend> one-shot launch of <backend> (no primary write)
kei primary [<b>] get/set primary
Splash shows `primary CLI: <backend>` so the orchestrator is visible.
Failure mode: if primary's CLI isn't on PATH, prints install hint + offers
`kei pick` recovery.
scripts/kei-pick.sh — Constructor Pattern picker (<140 LOC). Lists all 6
backends with install status (✓/✗), highlights current primary, writes
choice to primary.toml, execs the picked CLI. Honors stdin TTY gate
(RULE TTY-INTERACTIVITY-GATE — -t 0, not -t 1) for non-interactive safety.
## B — spawn_agent MCP tool
_primitives/_rust/kei-mcp/src/handlers/tools.rs gains a built-in
`spawn_agent` tool, exposed alongside discovered atoms:
- inputSchema: { name: str, task: str, on?: backend-enum }
- Calls kei-agent-cli.sh internally with same DNA resolution
- 60s timeout, kill-on-drop
- Honors KEI_AGENT_CLI env for testing
Smoke 2026-05-26 (MCP stdio JSON-RPC round-trip):
spawn_agent(name=smoke-test, on=claude) → "SMOKE-OK" ✅
spawn_agent(name=smoke-test, on=grok) → "SMOKE-OK" ✅
Why it matters: Claude Code has a native Agent tool. Grok / Agy / Copilot /
Kimi don't have an equivalent native sub-agent surface — but they all speak
MCP. spawn_agent gives them KeiSeiKit's sub-agent capability when they're
the orchestrator. The chosen orchestrator no longer caps the sub-agent fleet.
## Other
_primitives/_rust/kei-mcp/Cargo.toml: tokio gains "io-std" feature (was
missing — main.rs uses tokio::io::stdin/stdout). This fixes a latent build
error unrelated to this PR (kei-mcp wasn't building cleanly before).
Tests: tools_list assertions updated for the +1 built-in tool (3 total
instead of 2 with atoms; 1 instead of 0 on empty root). All MCP tests pass.
Assembler 3/3 golden tests still pass (provider field is optional).
|
|||
| e4980f6ad7 |
feat(dna): provider+model in agent DNA; kei primary; smoke-tested 4/5 CLIs
Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
Makes KeiSeiKit truly multi-LLM: any agent can declare its preferred backend
in its manifest. The DNA resolver picks the right CLI; `kei primary` swaps the
fleet-wide default. KeiSeiKit is no longer tied to Claude Code single-model.
Resolution order: --on=<backend> → manifest provider → primary.toml → claude
Files:
_assembler/src/manifest.rs + Option<String> provider field
_assembler/src/assembler.rs emit provider: in frontmatter (when set)
scripts/kei-agent-cli.sh DNA resolver; `kei primary` get/set; `kei agent`
arm (DNA-driven); honest kimi handling (TUI-only)
bin/kei new arms: agent, primary
_primitives/cli-backends.toml mark kimi as tui-only
docs/encyclopedia/multi-cli-agents.md rewritten with DNA flow, smoke
results, rule-enforcement caveat
Smoke 2026-05-26 (real CLI invocations):
claude ✓ via `claude -p`
grok ✓ via `grok --print` (DNA: manifest provider=grok)
agy ✓ via `agy --print` (Antigravity / Gemini)
copilot ✓ via `copilot --prompt` (1 Premium / 9s / 20.6k tok)
kimi ⚠ TUI-only, no print mode; need `kimi acp` JSON-RPC client
codex — register-only (not installed locally)
Rule-enforcement caveat documented: KeiSeiKit hooks fire only inside Claude
Code's PreToolUse pipeline. Non-claude backends carry the agent's PROMPT but
not the hook layer. For tool-level policy on non-claude, route through MCP.
ALSO: fix(stop-hook) — RULE 0.14 session-end-dump.sh "Recombobulating..."
4-minute hang on 18MB+ transcripts. Root cause: kei-memory ingest + frustration-
matrix scan + kei-sleep-sync ran sync at session end. Now async-detached with
per-op portable timeout (timeout/gtimeout/perl alarm). Hook returns in 0.03s.
Raw JSONL saved sync; only index/embedding step deferred (idempotent on
session_id so safe).
|
|||
| 3be9a8bf71 |
feat(multi-cli): kei run-via <backend> — agents over external LLM CLIs
Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
Adds a uniform launcher that lets any KeiSeiKit agent run on whichever
LLM CLI you have a subscription to. Pick by familiarity, pricing, or
to get a second opinion on the same prompt.
Backends (locally installed, by subscription):
claude Claude Code (claude -p)
grok xAI Grok (grok --print; native --agent supported)
agy Antigravity (agy --print) alias: antigravity
copilot GitHub Copilot (copilot --prompt)
kimi Moonshot Kimi (stdin, TUI primary)
codex OpenAI Codex (codex -p) register-only
Files:
_primitives/cli-backends.toml SSoT backend table
scripts/kei-agent-cli.sh launcher; loads ~/.claude/agents/<n>.md,
strips frontmatter, composes with task,
execs backend non-interactive
bin/kei new arm: run-via | via | run | agent-via
docs/encyclopedia/multi-cli-agents.md user-facing docs + usage
Auto-installed via lib-scaffold.sh:77 glob (no install code change needed).
Test plan:
kei run-via list # status + agents
kei run-via grok critic "review src/auth.rs" # via Grok
kei run-via agy critic "review src/auth.rs" # via Antigravity
kei run-via copilot critic "review src/auth.rs" # via Copilot
KEI_NATIVE_AGENT=1 kei run-via grok critic "..." # native --agent
|
|||
| abae256c1d |
feat(install): opt-in hook packs + stack profiles (public posture)
A fresh install now activates only the safety pack; discipline hooks and agents are opt-in via an onboarding step (step 6) or `kei configure`. "People don't need Rust-only" — they pick their own stack. - _primitives/hook-packs.toml: SSoT mapping pack -> hooks, stack -> packs + agent groups. safety always on; evidence/observability/epistemic/ orchestration/git-guard/stack-rust opt-in. rust-first/no-python only under the systems stack; git-guard (no-github-push) opt-in only, pulled by no stack. - lib-profile: extract generic _toml_array (reused by lib-packs); profile_members becomes a thin wrapper (no behavior change). - lib-packs: pack/stack/agent resolvers + selection loader. - lib-hooks: filter_snippet_by_packs (install-time allowlist) + prune_kit_hooks (reconfigure removes deselected kit hooks, keeps foreign ones); activate_hooks rewired to prune + filter + merge. No custom settings.json fields (/doctor safe). - lib-agents: install_manifests filters by stack agent set (empty = install all). - onboarding: pick_stack step (reuse _onb_read_choice), persists stack_profile + enabled_packs to onboarding.toml; i18n STR_* added. - bin/kei configure -> scripts/kei-configure.sh (re-pick without reinstall); install stamps ~/.claude/.kei-kit-dir. - numeric-claims-guard: money regex no longer matches shell positionals ($1..$9); requires decimal / unit / 2+ digits / tilde. Real money + time still caught. - gate one-liner added to 8 discipline hooks (runtime toggle via hooks-control). Verified end-to-end (scratch HOME): fresh=safety only; evidence pack adds numeric+citation; systems stack wires rust-first + 14 base/systems agents (no data-science/swift); reconfigure-shrink prunes kit hooks but keeps a foreign hook; settings schema clean; assembler golden 3/3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 3aff00290f |
fix: pre-public audit — critical install regression + 7 blockers
Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
CRITICAL: lib-hooks.sh had an apostrophe ("user's") inside the jq
program's bash single-quote, closing the quote and producing a parse
error so EVERY install aborted at source time (install.sh:71). Caught
by a full minimal e2e (rc=2 then rc=0 after fix). Reworded the jq
comment to drop the apostrophe.
Audit blockers fixed:
- MANIFEST: drop cortex-ui (no such primitive) from 4 profiles + block;
lib-menu desc no longer references it. Profile resolution verified clean.
- lib-dev-hub-forgejo / -zoekt: source lib-launchd.sh (register_launchd
was undefined, so full-hub dev-hub install would fail at runtime).
- kei-message: portable 16-digit id. BSD date prints literal "N" for %N;
fall back to /dev/urandom. Verified numeric in both code paths.
- bootstrap non-TTY default cortex to minimal (matches install.sh; avoids
divergent curl-bash vs direct-install behaviour and 105-crate surprise).
- install.sh stamps ~/.claude/.kei-profile; bin/kei reads it (splash
showed "profile: ?" before, since .installed holds only primitive names).
- README hook count 38 to 54 (real: ls hooks star dot sh).
- web-install warns before it discards local edits in the managed clone.
Verified: 106 shell files bash -n clean; minimal e2e rc=0 (38 agents,
57 hooks, 69 skills, profile stamped, mailbox present).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 305787fae3 |
fix(install): make fresh install complete + ship tamagotchi (#1)
Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
|
|||
| fc0758d2bb |
chore: версия 0.38.0 единая + warning-fixes + mold для release-job
Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
1. Версии npm-пакетов приведены к 0.38.0 (был зоопарк 0.14.0/0.14.6):
_ts_packages/{,packages/{gmail,grok,mcp-server,recall,telegram,youtube}-adapter}
2. Rust warnings (cargo check workspace):
- kei-cortex: deprecated validate_path → validate_path_lexical,
удалён orphan-wrapper в read.rs, struct Input → pub(crate)
- frustration-matrix: #[allow(dead_code)] на confusion_* поля
EvalReport + train_from_dir (будущий CLI)
3. CI release.yml job 'release' падал на Build kei-changelog:
clang invalid linker '-fuse-ld=mold' — в .cargo/config.toml
жёстко прописан mold для linux. Добавлен Install mold шаг
(как уже сделано в build-release matrix).
|
|||
| d1cbdd8892 |
fix(ci): vendored openssl-sys для cross-compile x86_64-apple-darwin
Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
CI run 26014515768 — `x86_64-apple-darwin` упал на webauthn-attestation-ca
→ openssl-sys 0.9.115: pkg-config не настроен для cross-compile
(macos-latest = arm64, target = x86_64).
Фикс: добавлен прямой dep `openssl-sys = { version = "0.9", features =
["vendored"] }` в kei-auth-webauthn — компилит openssl из исходников,
не требует системного. Транзитивно достаточно одного объявления feature
во всём workspace (Cargo unifies features).
|
|||
| a8101b8f60 |
fix: install audit 2026-05-18 — cortex profile + TTY in web-install
Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
audit-2026-05-18 (соседняя сессия на gx10, 3 бага из 5 подтверждены): #1 [HIGH] cortex profile сломан — kei-cortex depends on kei-router path-dep'ом, а kei-router → kei-model. kei-model не было в profile list, cargo build падал с «failed to read kei-model/ Cargo.toml». Добавил kei-model в cortex profile. #3 [HIGH] cortex-ui отсутствует в репо — был в profile list, но исходника нет ни в _primitives/_rust/, ни в _ts_packages/. Удалил из profile. #4 [MEDIUM] web-install.sh теряет /dev/tty — exec | tee + curl|bash делают stdin pipe, wizard'у read нечего читать, onboarding падает на первом prompt. Переподключаю stdin к /dev/tty перед exec bootstrap.sh, если /dev/tty доступен. #2 (linux-arm64 prebuild) — tag v0.38.0 не запушен на github, CI release.yml не сработал. Требует RULE 0.1 triple-confirm. #5 (non-TTY default = minimal) — уже корректно, install.sh:141 `PROFILE="${PROFILE:-minimal}"` + lib-menu.sh skip on non-TTY. |
|||
| 1d958b3587 |
fix(security): patent-leak + classical-safety audit fixes
Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
PATENT-LEAK (HIGH):
- hooks/no-python-without-approval.sh: genesis-verify пример → my-project
- docs/encyclopedia/rust-crates-H-N.md: убран термин «Genesis IP, ITAR»
PATENT-LEAK (MEDIUM):
- CHANGELOG: project-vortex → reduced scope
- _blocks/registries (submodule bump): убраны имена приватных
project-specialists из комментария agent-profiles.toml
- docs/encyclopedia/skills-and-agents.md: ML/RL/CfC → ML/RL
CLASSICAL-SAFETY (MEDIUM):
- install/lib-preflight.sh: eval "$version_cmd" → bash -c "..."
(защита от инъекции если providers.toml расширят)
- _primitives/provision-{vultr,hetzner}.sh: /tmp/$$ → mktemp
(устраняет symlink TOCTOU race)
- web-install.sh: chmod 600 + umask 077 на ~/.keisei-install.log
(Forgejo admin creds + токены в логе)
- scripts/regen-counts.sh: eval "$1" → bash -c
NOT FIXED (требуют действий юзера):
- HIGH: @keisei scope не зарегистрирован на npmjs.org — typosquat
возможен пока не задан NPM_TOKEN и не сделан publish
- HIGH: install.keisei.app DNS не настроен — DNS-hijack возможен
- LOW: parfionovich@keilab.io в SECURITY.md, plugin.json, ~40 Cargo
файлах — intentional contact, оставлен
Локальный git author установлен на parfionovich@keilab.io вместо
parfionovichd@icloud.com (только для будущих коммитов в этом репо).
|
|||
|
|
a3ffaed374 |
fix(install,router): close 5 HIGH audit findings
1. HIGH-1: onboarding ↔ kei-model-router связка
До: onboarding мастер писал ~/.claude/config/onboarding.toml,
но router его не читал — выбор провайдера декоративный.
После: lib-onboarding.sh::onboarding_write_config доп. пишет
~/.claude/config/user-model-override.toml; registry.rs::Registry
получил load_user_override() возвращающий UserModelOverride.
Приоритет: --pinned > user-override > agent-profiles default_model_ref.
2 новых теста (round-trip TOML, optional transport).
2. HIGH-2: eval "$install_cmd" → bash -c "$install_cmd"
До: lib-preflight.sh::preflight_offer_install делал eval.
После: bash -c с явным subshell + печать команды юзеру до запуска.
3. HIGH-3: codex.sh regex false-pass
До: grep -qiE "logged.in|active" пропускал "not logged in" как pass.
После: сначала negative-pattern (not logged|signed out|please log in),
потом positive (\blogged in\b|status: active|auth: yes).
4. HIGH-4: path traversal в source preflight
До: lib-preflight.sh::preflight_run делал source без валидации
provider id — `../../../evil` сработал бы.
После: whitelist regex ^[a-z0-9][a-z0-9_-]{0,63}$ + realpath
проверка что resolved путь не вышел за PREFLIGHT_DIR.
5. HIGH-5: curl|sh без verification
ollama-local.sh + google-vertex.sh теперь печатают предупреждение
что Linux-установка тянет shell-скрипт с внешнего сервера без
проверки хэша/подписи, и предлагают альтернативу.
MEDIUM попутно:
- anthropic-bedrock.sh: один вызов aws sts get-caller-identity
вместо двух (экономит 1-3с), различает cred-error от network
по тексту stderr, маскирует account ID в ARN перед печатью.
- mlx-local.sh: pip install --user mlx-lm вместо global pip install
(не требует sudo, не загрязняет system Python).
Тесты: cargo test --lib 80/80, bash -n всех изменённых файлов чисто.
|
||
|
|
c844524f68 |
fix(kei-buddy): close 3 HIGH audit findings from session multi-critic swarm
1. OID-check в parse_x25519_pkcs8_pem
До: брался последний 32-байтный slice любого PKCS#8 DER, OID не
проверялся. RSA/EC/Ed25519 ключ молча давал 32 неправильных байта
→ decrypt падал с generic "wrong key" без объяснения.
После: строгая проверка длины (48 байт) + OID 1.3.101.110 (X25519,
byte slice 9..12 = 0x2b,0x65,0x6e). Внешний openssl ключ другого
алгоритма теперь даёт явную ошибку с указанием реального OID.
Константы X25519_OID + X25519_PKCS8_DER_LEN.
RFC 8410 §3 + §7 ссылка в doc-комментарии.
2. x25519-dalek feature `zeroize`
До: features=["static_secrets"] — StaticSecret хранил priv-ключ
в куче без затирания при Drop. Локальный priv_raw.zeroize() стирал
только стек-копию, оригинал в куче оставался до GC.
После: features=["static_secrets","zeroize"] — StaticSecret сам
реализует ZeroizeOnDrop, ключ затирается при выходе из scope.
3. Два новых теста:
- parse_rejects_wrong_length_der — 32-байтный DER (вместо 48)
отклоняется с сообщением про "48 bytes"
- parse_rejects_wrong_oid — DER с OID Ed25519 (0x2b,0x65,0x70)
отклоняется с сообщением про "X25519"
8/8 тестов модуля проходят, cargo check workspace чисто.
Старая 0.14.5 mcp-server (с source maps содержавшими /Users/
denisparfionovich/...) удалена с keigit.com отдельной операцией
через Forgejo DELETE API.
|
||
|
|
86834b82af |
feat(kei-buddy): provision_decrypt — VPS-side blob decryption
Mirrors keisei-marketplace/src/lib/crypto-box.ts::sealBoxToVps.
Two new subcommands on kei-buddy bin:
- genkeys --key <path> → writes PKCS#8 PEM x25519 priv,
prints standard-base64 pub (44 char)
- decrypt-and-export --vps-key <pem> --blob <json> --env-out <env>
→ ECDH(vps_priv, ephPub) → HKDF-SHA256
info=keibuddy-token-v1 → XChaCha20-Poly1305
decrypt → append BOT_TOKEN/TELEGRAM_BOT_TOKEN
to env file (replaces stale, keeps other lines)
Cloud-init in hetzner.ts already calls these. Without this commit the
VPS could decode its own pubkey but had no way to recover the sealed
bot-token blob — the bot would never log into Telegram.
Crypto stack (mirror of @noble in TS):
- x25519-dalek 2 (static_secrets feature)
- chacha20poly1305 0.10 (XChaCha20Poly1305)
- hkdf 0.12, sha2 0.10
- base64 0.22 (accepts URL_SAFE_NO_PAD + STANDARD)
- zeroize 1 for priv-key wipe
Tests (6/6 pass):
- roundtrip_seal_then_decrypt — re-implement marketplace sealing in Rust,
verify our decryption recovers plaintext
- decrypt_and_export_writes_env_file — full e2e through CLI surface
- decrypt_and_export_replaces_existing_token — stale BOT_TOKEN replaced,
other env lines preserved
- decrypt_rejects_wrong_key — XChaCha20 AEAD tag fails on wrong key
- pem_roundtrip — write_pkcs8 + parse_pkcs8 round-trip
- b64decode_accepts_urlsafe_and_standard — handles both encodings
Cross-verified end-to-end:
$ node marketplace_seal.mjs <pub> <token> → /tmp/blob.json
$ kei-buddy decrypt-and-export --vps-key ... → BOT_TOKEN matches input
Constructor Pattern: 1 file (provision_decrypt.rs, 344 LOC), 1 module,
1 responsibility (token-blob decryption + key generation).
=== STATUS-TRUTH MARKER ===
shipped: functional
stubs: 0
cargo-check: PASS
behaviour-verified: yes (e2e marketplace-seal → kei-buddy-decrypt round-trip)
follow-up-required:
- none
|
||
|
|
40a5c2e55f |
fix(audit-r2): HIGH+MEDIUM closures from second round audit
HIGH-1: submodule URL ssh → https + shallow (DNS spoofing surface, both repos)
HIGH-3: docs/DNA-MIGRATION.md — two-format coexistence policy (4-seg legacy
task-class vs 5-seg agent-shell marketplace)
HIGH-5: agent_shell_dna doc — explicit consumer = marketplace, planned ledger
integration; module-doc clarification
MEDIUM: Haiku model id pinned to claude-haiku-4-5-20251001 across
pricing.rs::from_slug + ::name + escalate.rs tests + select_posterior
fixture + kei-registries submodule (pushed c39e528→7aaa6a7)
|
||
|
|
7281e7ecea |
fix(registries+router): MEDIUM/LOW audit batch
closes MEDIUM/LOW from feat/agent-dna-three-layer audit: - models.toml: cache_write_1h_per_mtok_micro added to all 11 entries (Anthropic: 200M/600M/1000M micro = $2/$6/$10 per MTok per pricing page; other providers: 0 placeholder) - main.rs (kei-model-router): WAL pragma + busy_timeout errors now logged to stderr instead of silently dropped (previously .ok() swallowed both) - models.toml: Haiku id pin TODO documented (router still hardcodes alias) |
||
|
|
7cd74071a1 |
feat(kei-model-router): agent_shell_dna parser for 5-segment marketplace DNA
Adds `agent_shell_dna` cube parsing the new agent-shell::<p>:<m>:<c>::<scope>::<body>-<nonce> format emitted by keisei-marketplace/src/lib/cryptoid.ts::agentDna. Companion to legacy 4-segment `dna_class` (untouched per RULE Don't-Rewrite). Accepts both 8-hex (legacy) and 16-hex (current) lengths for forward-compat. - new file: src/agent_shell_dna.rs (235 LOC, 13 tests all pass) - lib.rs: pub mod agent_shell_dna + module doc Closes HIGH-2 (dna-three-layer audit). |
||
|
|
766504d345 |
fix(kei-model-router): close 2 HIGH regressions from re-audit
Re-audit (codex + critic-bug) on commit
|
||
|
|
187661714f |
fix(kei-model-router): close 10 audit-blocker findings
Codex CRITICAL + 4 HIGH + 5 MEDIUM/LOW from RULE 0.23 dual-review and
RULE 0.25 multi-critic swarm — all closed.
CRITICAL fix
- Model::slug() ledger compatibility: posterior.rs + select_kernel.rs
query `WHERE model = ?2 OR model = ?3`, binding canonical + legacy
slug pair via new `Model::legacy_slug()`. Production ledger rows
written under "haiku"/"sonnet"/"opus" remain visible to posterior
aggregation. Regression test ledger_legacy_slug_counted.
HIGH fixes
- cmd_select(): no longer early-returns on profile match. Profile's
default_model_ref now becomes DecisionInput.fallback; select() always
runs, posterior/kernel evidence wins if present. RULE 0.20 cost
optimisation restored for all 18 registered agents.
- Registry pricing SSoT: DecisionInput now carries Option<Arc<Registry>>.
estimated_cost() tries registry first; hardcoded match is documented
fallback only. select_posterior.rs no longer duplicates models.toml
constants.
- registry.rs portability: include_str!() embeds the three TOMLs at
compile time. load_embedded() new; disk path tried first via
KEI_REGISTRIES_DIR, embedded as fallback. `cargo install`d binaries
now find registries unconditionally. embedded_registry_matches_disk
test ensures embedded ≡ disk source.
- next_model() ambiguity: replaced Option<&Model> with EscalationResult
enum (Next(&Model) / AtTop / NotFound). Callers can distinguish typo
from ceiling. 5 new tests.
MEDIUM fixes
- posterior.rs u32 overflow: `(n_plus + n_minus) as u32` →
`u32::try_from(n_plus.saturating_add(n_minus)).unwrap_or(u32::MAX)`.
overflow_guard_on_huge_n test with i64::MAX.
- pick() unknown-model: now returns None when default_model_ref's model
is absent from registry. Inverted the deprecation guard.
- HOME unset: disk_registries_dir() returns None on empty HOME and
falls through to embedded registries. open_ledger() logs warning
and returns None instead of opening at malformed path.
- SQLite WAL + busy_timeout: applied to ledger connection in
open_ledger() — concurrent CLI invocations no longer SQLITE_BUSY.
LOW fixes
- impl Model consolidation: next_tier() moved to pricing.rs.
escalate.rs uses current.next_tier() instead of duplicating logic.
- complexity.rs: removed duplicate "ml-implementer" in HEAVY_ROLES.
- dna_class.rs: role("") now returns None instead of Some("").
Verification (orchestrator-side, RULE 0.13 §Verify-before-commit):
- cargo check → clean
- cargo test --release → 63 passed / 0 failed (was 58 → +5 new tests
cover legacy-slug, EscalationResult, overflow, unknown-model, embedded)
- Constructor Pattern → all files ≤ 200 LOC (max registry.rs 196)
- Largest fn from_ledger 28 LOC / limit 30
DNA-INDEX.md regenerated by kei-registry hook (cosmetic).
=== STATUS-TRUTH MARKER ===
shipped: functional
stubs: 0
cargo-check: PASS
behaviour-verified: yes
follow-up-required:
- (none from this commit; next audit pass before merge to main)
|
||
|
|
4d79049eff |
feat(kei-model-router): registry-driven, three-layer DNA
Removes hardcoded Claude-only Model enum. Pricing constants now read
from _blocks/registries/models.toml at startup; provider/model lookup
goes through a typed Registry returned by registry.rs.
New API surface:
- Registry::load(dir) → (providers, models, profiles)
- pick(profile_id, &Registry) → Result<(provider_id, model_id)>
- cost_micro_cents(model_id, in, out, &Registry) → Option<u64>
- next_model(model_id, &Registry) → Option<&Model> (ascending cost,
same provider, skip deprecated)
Files:
- registry_types.rs new 107 LOC (Provider/Model/Profile structs)
- registry.rs new 152 LOC (TOML load + lookups)
- pricing.rs rew 127 LOC (registry-backed, no constants)
- escalate.rs rew 181 LOC (registry-backed ladder + skip deprecated)
- select.rs rew 131 LOC
- select_kernel.rs new 74 LOC (Constructor-Pattern split)
- select_posterior.rs new 178 LOC (Constructor-Pattern split)
- posterior.rs rew 197 LOC
- calibrate.rs rew 175 LOC
- lib.rs rew 53 LOC
- main.rs rew 163 LOC (CLI updated to new API)
- Cargo.toml dep added toml 0.8
Verification (orchestrator-side, RULE 0.13 §Verify-before-commit):
- cargo check → clean
- cargo test --release → 58 passed / 0 failed / 0 ignored
- LOC limit (Constructor) → max 197 / limit 200
- largest fn cmd_select → ~27 LOC / limit 30
DNA-INDEX.md regenerated by kei-registry hook (primitive count
144 → 150 reflects the 6 new/split modules).
=== STATUS-TRUTH MARKER ===
shipped: functional
stubs: 0
cargo-check: PASS
behaviour-verified: yes
follow-up-required:
- select.rs `estimated_cost` still embeds inline cost constants
mirroring models.toml; if non-Anthropic providers need dynamic
pricing in select-time estimation, thread Registry through.
- External callers of old `cost_micro_cents(Model, ...)` signature
will break — intentional, no external callers in this workspace.
|
||
|
|
b87c7685c6 |
feat(install): add profile=buddy for marketplace 1-click bot provisioning
Profile bundles the crates kei-buddy needs at runtime: kei-buddy, kei-telegram-webhook, kei-shared, kei-chat-store, kei-social-store, kei-memory-sqlite, kei-router, kei-llm-bridge-mlx. Used by keisei-marketplace cloud-init when user clicks 'хочу своего KeiBuddy' on /keibuddy/setup — Hetzner VPS spawn pulls KeiSeiKit and runs install.sh --profile=buddy --yes. |
||
|
|
b5d093fbec |
fix(kei-cortex/test): serial_test on env-mutating openai tests + wiremock warm-up
Previous wiremock conversion fixed the listener-lifecycle race but left the underlying problem unsolved: `ensure_env()` mutates the process-global ANTHROPIC_ENDPOINT, and parallel `cargo test` threads race on that write. Manifested as 502 / "error sending request for url …" on the first concurrent test pair under both macOS and Linux. Annotate every #[tokio::test] in openai_loop_wiring.rs + openai_compat.rs with `#[serial_test::serial]` — these are the only tests that touch ANTHROPIC_ENDPOINT via shared_mock_anthropic. serial_test enforces process-wide ordering so the env mutation + HTTP request pair is atomic per test. All other tests stay parallel. Stress: 5 parallel `cargo test` runs all green. |
||
|
|
b103a9aa64 |
fix(kei-cortex/test): replace hand-rolled mock with wiremock — closes macOS CI flake
Previous `tests/common/mod.rs` spawned a mock Anthropic upstream via hand-rolled axum + std:🧵:spawn + own current-thread tokio runtime bound to 127.0.0.1:0. Stable on Linux runner; flaked on macOS GitHub Actions runners: thread 'streaming_responses_runs_real_loop_not_stub' panicked at kei-cortex/tests/openai_loop_wiring.rs:277:5: no responses delta event in stream: event: response.error data: {"error":"model: anthropic request: error sending request for url (http://127.0.0.1:49312/v1/messages)"} Root cause traced to macOS-runner loopback / fd-limit pressure on the dedicated-thread current-thread runtime. wiremock crate runs a production-quality hyper-based mock server, manages its own listener lifecycle, and survives the macOS runner constraints. ## Change - `Cargo.toml`: add wiremock = workspace dev-dep (already 0.6 in workspace) - `tests/common/mod.rs::MockAnthropicServer` rebuilt over wiremock::MockServer - `build_mock(text)` mounts `POST /v1/messages → 200 + canned body` on a wiremock instance - `mock_anthropic_responding_with()` spins one per call on a parked helper thread (preserves `MockAnthropicServer: 'static` lifetime for `shared_mock_anthropic` `OnceLock` singleton) - `shared_mock_anthropic()` API unchanged; existing test sites in `tests/openai_loop_wiring.rs` + `tests/openai_compat.rs` continue to work without modification ## Verification `cargo test -p kei-cortex --test openai_loop_wiring`: 7/7 pass locally `cargo test -p kei-cortex`: full suite green (428 lib + integration) Also includes DNA-INDEX regenerate (auto-encyclopedia hook artefact; 0 vortex matches preserved). |
||
|
|
a3f8e1f847 |
chore(prod-prep): root docs (CHANGELOG/CONTRIBUTING/SECURITY) + cargo update
Root-level docs added per production-readiness audit: - CHANGELOG.md — unreleased + pointer to git tags - CONTRIBUTING.md — setup + PR checklist + Constructor Pattern - SECURITY.md — reporting channel + threat model + known RUSTSEC list cargo update applied: 19 patch/minor bumps (base64urlsafedata, blake3, cc, crc-catalog, digest, filetime, h2, hashbrown, hybrid-array, idna_adapter, js-sys, kqueue-sys, libc, nix, openssl, openssl-sys, pin-project, pin-project-internal, redox_syscall). 9 RUSTSEC advisories from transitive deps remain (rsa 0.9 Marvin, rustls-webpki x5, sqlx 0.8 Binary Protocol, async-std discontinued, lru unsound IterMut, fxhash/instant unmaintained) — require major-version bumps in direct deps, tracked in SECURITY.md "Known advisories" section. |
||
|
|
b61b17ea7b |
feat(kei-buddy): conversational LLM-driven flow + kei-sage retrieval (graph-RAG)
Replaces the rigid FSM after Intro/AskLanguage with a single LLM call per
turn that sees:
* persona (what's already known — slots not re-asked)
* recent 10 chat_log messages (history)
* top-5 kei-sage atoms relevant to user_text (graph-RAG, not embeddings)
* raw user_text
LLM returns JSON {slot_updates, response_text, done, focus} which drives
the next state + persona patch + reply. No embeddings, no vector store —
kei-sage's FTS5 + Obsidian-style atom graph is the retrieval layer.
New files:
* src/retrieval.rs (101 LOC) — retrieve_context(chat_log, topics,
chat_id, query, history_n, atoms_k) -> RetrievalContext
* src/conversational.rs (157 LOC) — conversational_step
(state, persona, context, text, extractor, lang) -> StepOutput
Modified:
* src/serve.rs::run_fsm — branch on state: Intro/AskLanguage still go
through legacy handle_step (jump-start); everything else routes to
conversational_step with retrieval context.
* src/lib.rs — module declarations.
Tests (5 new, 60 total passing):
* parses_well_formed_llm_response
* done_true_transitions_to_ready
* invalid_json_falls_back_gracefully
* retrieve_returns_empty_on_empty_stores
* retrieve_finds_seeded_data
Verify:
* cargo check -p kei-buddy: PASS
* cargo test -p kei-buddy --lib: 60/0 (was 55, +5)
Why graph-RAG instead of embeddings: kei-sage already in tree (atoms +
edges + BFS + PageRank + FTS5). Explicit edges (message → topic →
contact) beat opaque cosine similarity for personal-assistant memory
where relationships are typed. No sqlite-vec dep, no embedding cost.
NOT deployed yet — needs server rebuild.
|
||
|
|
f354aaccfc |
fix(kei-conflict-scan): close 3 backlog bugs + Phase C draft emission
Closes engine bugs #1, #2, #3 from the user's backlog.md entry dated 2026-05-11 "kei-refactor-engine — 4 false-positive bugs". Bug #4 was fixed in |
||
|
|
26dc8c85f7 |
feat(kei-buddy): AskLanguage i18n + real proposeTopicSources + voice handling
Three follow-up atomics on top of the contacts/topics/sync wave.
## 1. AskLanguage state + ru/en localisation (default en)
New state `AskLanguage` inserted between `Intro` and `AskName`. Intro now
sends a bilingual greeting + language picker. AskLanguage parses
en/english/1/ru/русский/2/etc → persona_patch{"language":"<code>"} →
transitions to AskName with that language's prompt.
All later prompts (AskName / AskTone / AskInterests / AskHobbies /
TopicSpecifics / TopicNowLater / TopicResearch / AskSchedule / Ready)
read persona.language via Lang::from_persona and dispatch through
Strings::* helpers — two language tables, no fallthrough.
Back-compat migration: existing chats without `language` key (like the
user currently in topic_now_later) get an implicit "ru" patch on next
turn so their Russian onboarding continues without regression.
New files: strings.rs (164), machine_lang.rs (145).
Modified: state.rs (+AskLanguage variant), machine.rs (Intro→AskLanguage,
AskLanguage arm, migration guard), machine_helpers.rs, machine_tests.rs.
5 new tests (intro_to_ask_language, ask_language_en, ask_language_ru,
ask_language_invalid, migration_sets_ru_when_language_missing).
## 2. Real proposeTopicSources — removed TODO(phase2) stub
machine_lang.rs::step_topic_research now calls
extractor.extract(prompt, topic_title) with a {name, url, why} schema.
Parses JSON, formats numbered source list, transitions to TopicSources.
Failure paths (LLM error, empty array): graceful fallback prompt asking
user to suggest their own — still transitions to TopicSources so flow
doesn't deadlock.
3 new tests in machine_tests_topic_research.rs:
topic_research_yes_proposes_sources,
topic_research_yes_empty_sources_still_advances,
topic_research_no_skips_topic_sources.
## 3. Voice-message handling (Telegram voice/audio → STT → text pipeline)
kei-telegram-webhook: added Voice/Audio sub-structs on Message and
WebhookEvent::Voice variant. classify() detects message.voice OR
message.audio. 2 new tests in event.rs.
kei-buddy/src/voice.rs (178 LOC):
VoiceHandler { bot_token, stt: Arc<dyn SttBackend>, http }
transcribe_file(file_id, mime_type) does:
1. GET https://api.telegram.org/bot{token}/getFile?file_id=...
2. GET https://api.telegram.org/file/bot{token}/{file_path}
3. SttRequest { audio_bytes, mime_type, language: None } → backend.transcribe
4. Returns transcript text.
2 wiremock tests (download chain + 500 error mapping).
serve.rs adds voice: Option<Arc<VoiceHandler>> to BuddyContext;
on_event Voice arm: whitelist check → transcribe → handle_text (same
pipeline as if user typed). Voice unavailable: warn + ignore.
serve_runner.rs builds VoiceHandler from KEI_BUDDY_STT_BACKEND env.
kei-stt added as optional dep gated by serve feature. Default backend
whisper-local (no extra build deps).
TTS reply path deferred (next atomic).
## Verify
* cargo check --workspace: PASS
* cargo test -p kei-buddy --lib: 55 passed / 0 failed (was 41 → 50 → 53 → 55)
* cargo test -p kei-telegram-webhook --lib: 7 passed (was 5, +2 voice)
* cargo build -p kei-buddy --release: PASS (23.7s)
NOT deployed yet — three new things to roll out next:
* новые миграции (нет — БД без изменений)
* новые env: KEI_BUDDY_STT_BACKEND (optional)
* установка faster-whisper / piper-tts на сервер для STT
(без него Voice event просто warn-логируется и игнорируется)
|
||
|
|
06bcce9981 |
feat(contacts): glue sync + Google pagination + Apple discovery & folding
Three atomics finish phase 3 of kei-buddy contacts integration:
## kei-buddy: contact-sync glue + slash commands (+5 tests)
New src/contacts_sync.rs (146 LOC):
* SyncReport { fetched, added, skipped, errors }
* sync_from_google(access_token, contacts) — builds GooglePeopleClient,
list_connections, dedups by (name+email) via search_contacts,
add_contact in loop
* sync_from_apple(apple_id, app_pw, addressbook_url, contacts) — same
pattern over ICloudCardDavClient.list_contacts
* All errors collected into report.errors; never panics, never propagates
New slash commands in commands.rs / command_exec.rs:
* /sync-google — reads GOOGLE_OAUTH_ACCESS_TOKEN env, calls sync_from_google,
Russian-formatted summary "Google: загружено N, добавлено M, пропущено K"
* /sync-apple — reads APPLE_ID + APPLE_APP_PASSWORD + APPLE_CARDDAV_URL,
calls sync_from_apple
* Missing env → human-readable "не настроено: …" response
* /help text updated
Deps added: kei-contacts-google + kei-contacts-apple as path deps.
## kei-contacts-google: pagination via nextPageToken (+1 test)
Refactor: client.rs 182→56 LOC; pagination logic + deserialization moved
to new src/pagination.rs (188 LOC). list_connections unchanged
(back-compat, returns first page only). New list_all_connections loops
via fetch_page(Some(token)) until token=None; hard cap 50 pages with
tracing::warn on cap.
Test list_all_connections_two_pages: wiremock returns page 1 with
nextPageToken="abc" + page 2 without; assert len = sum AND second
request carries pageToken=abc query.
## kei-contacts-apple: vCard line-folding + CardDAV auto-discovery (+2 tests)
vcard.rs +unfold() helper applied in parse_vcard per RFC 6350 §3.2:
continuation lines starting with space/tab strip the prefix and append
to previous line. Test parse_folded_vcard.
New src/discovery.rs (199 LOC): discover_addressbook() walks
.well-known/carddav → current-user-principal → addressbook-home-set →
first addressbook with C:addressbook resourcetype. Three PROPFIND
requests with canned XML bodies. Regex-based extract_first_href_under +
extract_addressbook_href helpers. Test discover_walks_three_propfinds
against 3-step wiremock fixture.
client.rs adds discover_addressbook_url() method calling discovery.
## Verify-before-commit
* cargo check --workspace: PASS
* cargo test -p kei-buddy --lib: 46/0 (was 41)
* cargo test -p kei-contacts-google: 5/0 (was 4, +1 pagination)
* cargo test -p kei-contacts-apple: 9/0 (was 7, +1 folding +1 discovery)
NOT deployed — user still in live conversation with bot.
Follow-up (deferred, non-blocking):
* Real iCloud smoke test for discover_addressbook_url — regex parser
may need adjustment for deeply-nested namespace prefixes
* Wiremock-backed integration test for sync_from_google glue (HTTP
layer already covered in kei-contacts-google tests)
|
||
|
|
6cd999820c |
fix(kei-conflict-scan): wikilink path-norm + drop handoff false-positives
Two architectural bugs in orphans scanner — both surfaced by morning
/sleep-review of deep-sleep/2026-05-12-0400 (108 false-positive
orphan-wikilinks; the engine was scanning sync-repo MEMORY.md and
flagging every `[[../../../rules/X]]` cross-repo ref as broken).
1. Asymmetric normalization in extract_wikilinks
- `all_basenames(root)` indexed file_stem (lowercase, no path)
- `extract_wikilinks` returned lowercased FULL link text including
`../../../`-prefix and `subdir/` segments
- Result: `[[chatlogs/X/Y]]` never matched `Y.md` in index, every
`[[../../../rules/X]]` always flagged orphan
Fix: `normalize_target(raw) -> Option<String>` strips path prefix,
strips `.md` suffix, returns None for `../`-rooted refs that escape
the scan tree (engine cannot validate cross-repo targets).
2. extract_handoffs scanner removed
- Regex `^\s*-\s*\*\*([a-z0-9][a-z0-9_-]{2,})\*\*` was matching every
prose bold-bullet, e.g. `- **english-jargon** — last 7d:` in
backlog.md or `- **L1-Path-C**:` in chatlogs.
- sync-repo scan: 0 real handoff sections present, 100% of matches
were prose. Real handoff syntax in agent-graph repos uses YAML
frontmatter, not prose markdown bullets.
- Scanner deleted along with its helper; wikilink scanner alone
covers the explicit `[[...]]` ref use case.
## Result on sync-repo (live data)
| Metric | Before | After |
|----------------|-------:|------:|
| orphan refs | 108 | 1 |
| false-positive | 107 | 0 |
Remaining 1 = legitimate `[[wikilink]]` literal in backlog.md prose.
## Tests added (already present in HEAD via prior fleet commit)
- `tests::cross_repo_ref_skipped` — `../../../foo` -> None
- `tests::path_prefixed_target_basenamed` — `chatlogs/X/Y` -> "Y"
- `tests::plain_basename_passes_through`
- `tests::md_suffix_stripped`
- integration `cross_repo_wikilink_not_flagged` (E2E)
- integration `path_prefixed_wikilink_matches_basename` (E2E)
12/12 tests pass. Release binary rebuilt + installed to ~/.cargo/bin/.
Private mirror at ~/Projects/KeiSeiKit/_primitives/... synced.
Closes backlog.md "engine bug #4" (added by user via prior /sleep-review).
|
||
|
|
450156a476 |
feat(kei-buddy fleet): 5 atomics — google/apple contacts + classifier + tick + slash-commands
Parallel agent batch. All five tasks delivered functional + tested.
NOT deployed — user is in live conversation with the bot.
## Crates added (2 new)
### kei-contacts-google (466 LOC, 5 tests)
Thin Google People API client. Takes pre-acquired access_token from
kei-auth-google's OAuth flow; calls /v1/people/me/connections?personFields=...,
parses 200-entry first page (TODO: pagination via nextPageToken), maps
to kei_social_store::Person. Errors: Http / Auth(401) / Parse.
### kei-contacts-apple (593 LOC, 7 tests + 1 doc-test)
CardDAV client for iCloud Contacts using Basic Auth (Apple ID +
app-specific password). Sends REPORT with addressbook-query XML body,
parses multistatus → embedded vCards → AppleContact. Tiny vCard
parser (~150 LOC) handles FN/N/EMAIL/TEL/ORG/NOTE/UID, single-line
only (no line-folding for MVP). Discovery (PROPFIND .well-known/carddav
→ principal → addressbook-home-set) deferred — user supplies
addressbook URL via with_addressbook_url().
Both crates registered in workspace members.
## kei-buddy crate additions
### src/topic_classify.rs (116 LOC, 3 tests)
Free fn classify_and_store_topic(extractor, topics, chat_id, text)
called from process_text when state == OnboardState::Ready. Builds
classifier prompt → LLM → parses {slug, title} → validates slug
shape (kebab-case, ascii) → Topics::add_topic + add_digest. All
failure paths log + return; conversation never blocks.
### src/tick.rs (188 LOC, 3 integration tests) + src/bin/kei-buddy-tick.rs (67 LOC)
Second binary. Oneshot CLI for systemd timer: walks all known
chat_ids in BuddyStore → lists topics → searches recent chat
messages per topic (configurable window/limit) → LLM digest →
Topics::add_digest. Outputs JSON TickReport to stdout. Env-driven
config. NoOpExtractor fallback when no LLM creds (graceful degradation).
### src/commands.rs (146 LOC) + src/command_exec.rs (111 LOC, 7 tests)
Slash-commands intercepted BEFORE handle_step in process_text:
/whois <name> contacts.search_contacts + common_connections for hits
/find <q> chat_log.search scoped to chat_id
/topics topics.list_topics
/contacts contacts.search_contacts("", 10)
/help static usage text (Russian)
If command parsed, response built from stores, sent, logged to
chat_log — FSM skipped for that turn.
### src/serve_runner.rs (69 LOC) — refactor
run_serve + start_listener + init_tracing extracted out of serve.rs
to bring serve.rs back to 189 LOC (was 248 after previous wave).
### Wiring
BuddyContext gains `contacts: Arc<Contacts>` and `topics: Arc<Topics>`.
ServeConfig gains contacts_db_path + topics_db_path. Binary reads
KEI_BUDDY_CONTACTS_DB_PATH + KEI_BUDDY_TOPICS_DB_PATH env (defaults
./kei-buddy-contacts.db, ./kei-buddy-topics.db). cmd_migrate applies
schema for all three side-stores (chat_log + contacts + topics).
## Verify-before-commit (RULE 0.13 §)
* cargo check -p kei-buddy (default + extractor-openai): PASS
* cargo test -p kei-buddy --lib: 41 passed / 0 failed (was 31)
* cargo test -p kei-buddy --tests: 3 passed (tick integration)
* cargo build -p kei-buddy --features extractor-openai: PASS
(builds both kei-buddy + kei-buddy-tick binaries)
* cargo check -p kei-contacts-google: PASS (5 tests)
* cargo check -p kei-contacts-apple: PASS (7 + 1 doc)
* cargo check --workspace: PASS
## STATUS-TRUTH from all 5 agents: shipped=functional, behaviour-verified=yes
## Follow-up (deferred, non-blocking)
* Google People API pagination (nextPageToken loop) — first 200 only
* CardDAV auto-discovery (PROPFIND .well-known/carddav)
* vCard line-folding (RFC 6350 §3.2)
* Wire kei-contacts-google + kei-contacts-apple → Contacts.add_contact
sync command (no glue yet)
* systemd timer file for kei-buddy-tick (not shipped here — config only)
|
||
|
|
fa7a20d572 |
feat(kei-buddy): wire kei-social-store + kei-sage — contacts + topics + FTS5
Two parallel atoms in one commit. Both reuse existing KeiSeiKit
primitives (zero new crates) per RULE feedback_inventory_before_decompose.
## src/contacts.rs (200 LOC, +4 tests)
Adapter over kei-social-store. Address book + interaction log + relationship
graph for shared connections.
API:
* Contacts::from_path / from_memory
* add_contact / get_contact / search_contacts
* log_meet(person_id, target_id, channel, note) / interactions_for
* relationship_graph — returns Vec<Pair>, the kei-social-store output
* common_connections(a, b) — post-filters relationship_graph to find
target_ids that appear in pairs with BOTH a and b. This is the
"у нас с Денисом общий друг X" feature.
Pattern: Arc<Mutex<kei_social_store::Store>> + tokio::spawn_blocking,
mirroring chat_log.rs. Errors map to BuddyError::Memory.
Tests: add_and_get_contact_roundtrip / search_contacts_finds_by_name /
log_meet_and_list_interactions / common_connections_finds_shared_target.
## src/topics.rs (200 LOC, +4 tests)
Adapter over kei-sage. Topics + digest notes + FTS5 search. Each topic
is a sage Unit{unit_type="buddy_topic", category="kei-buddy",
source_path="kei-buddy/chat-{chat_id}/topic/{slug}"}. Digests are
Unit{unit_type="buddy_digest"} linked via add_edge(topic→digest,
edge_type="digest_for").
API:
* Topics::from_path / from_memory
* add_topic(chat_id, slug, title, content) — idempotent via path lookup
* add_digest(chat_id, topic_slug, timestamp, content) — creates Unit +
edge
* search(query, limit) — fts_search over all kei-buddy units
* digests_for(chat_id, topic_slug) — follows outgoing edges
* list_topics(chat_id) — raw SELECT scoped by source_path LIKE prefix
Tests: add_topic_then_search_finds_it / add_topic_is_idempotent /
add_digest_creates_edge_and_dest / list_topics_scopes_per_chat.
## Dependencies added
kei-social-store + kei-sage as local path deps. Both already in workspace,
no new external crates.
## Verify-before-commit
* cargo check -p kei-buddy: PASS
* cargo test -p kei-buddy --lib: 31/0 (was 23, +4 contacts +4 topics)
Net change: 4 files touched, ~400 LOC added across the two adapters.
NOT deployed. User still in active bot conversation.
|
||
|
|
fb7c1bf859 |
feat(kei-buddy): wire kei-chat-store — log every user/bot message with FTS5
After-Ready conversation was going to /dev/null. With this change every
inbound Telegram text + every bot response is persisted to a SQLite +
FTS5 archive via the existing kei-chat-store primitive (no new crate).
Each Telegram chat_id maps 1:1 to a kei-chat-store session
(project="kei-buddy", title="tg-<chat_id>", model="telegram"). Cache
prevents per-message session lookups.
New file:
* src/chat_log.rs (198 LOC) — ChatLog adapter wrapping
kei_chat_store::Store + a chat_id→session_id Mutex cache.
API: from_path / from_memory / ensure_session / log_user /
log_bot / search(query, chat_id?, limit). Errors map to
BuddyError::Memory and never propagate from on_event — chat-log
failure is logged but does not block the conversation.
Modified:
* Cargo.toml — kei-chat-store path dep added.
* src/lib.rs — pub mod chat_log + re-export ChatLog.
* src/serve.rs — BuddyContext gains Arc<ChatLog>;
process_text calls log_user before handle_step + log_bot after
send_message; ServeConfig gains chat_log_db_path.
* src/bin/kei-buddy.rs — KEI_BUDDY_CHAT_LOG_PATH env
(default ./kei-buddy-chat.db); migrate subcommand applies the
chat-store schema alongside buddy_state schema.
Tests (3 new in src/chat_log.rs, all pass):
* log_user_creates_session_and_message
* log_bot_uses_same_session_as_log_user
* different_chats_get_different_sessions
Verify-before-commit:
* cargo check -p kei-buddy (default): PASS
* cargo check -p kei-buddy --features extractor-openai: PASS
* cargo test -p kei-buddy --lib: 23 passed / 0 failed
(was 20 before this commit; 3 new ChatLog tests)
NOT deployed — user is in active conversation with the live bot.
Will roll forward when user signals readiness.
|
||
|
|
0045b6ac77 |
feat(kei-buddy): wire OpenAiExtractor + chat_id whitelist + env-configurable LLM
Two additions on top of the MVP serve binary:
1. Whitelist by chat_id (KEI_BUDDY_ALLOWED_CHAT_IDS env, CSV).
* BuddyContext gains Arc<Option<Vec<i64>>> allowed_chat_ids
* chat_allowed() check fires before process_text
* Non-whitelisted chats: warn-log + ignore (no response sent)
* None or empty list = accept all (back-compat with prior behaviour)
2. Real LLM wiring (KEI_BUDDY_LLM_PROXY / _LLM_KEY / _LLM_MODEL).
* When extractor-openai feature compiled in AND both proxy+key set,
run_serve instantiates OpenAiExtractor instead of MockExtractor
* Defaults: proxy=https://api.openai.com, key=OPENAI_API_KEY env,
model=gpt-4o-mini
* Fallback: warns + MockExtractor (state machine still walks, but
LLM-extracted fields are empty)
* extractor::OpenAiExtractor gains new_with_model(proxy, key, model);
model is now per-instance instead of compile-time DEFAULT_MODEL
3. start_listener extracted as helper — keeps run_serve readable across
the two feature-gated branches.
Verify-before-commit:
* cargo check -p kei-buddy (default): PASS
* cargo check -p kei-buddy --features extractor-openai: PASS
* cargo test -p kei-buddy --lib: 20/0 unchanged
|
||
|
|
7414d14cc7 |
feat(kei-buddy): functional MVP — store + state-machine port + serve binary
Three atoms landed in one commit (memory binding, state machine port, real serve binary). Tracked separately in TaskList (#5 #7 #6). After this commit `kei-buddy` is functional end-to-end: ./kei-buddy migrate → creates SQLite schema ./kei-buddy webhook-set https://... → registers Telegram webhook ./kei-buddy serve → axum HTTP listener on $KEI_BUDDY_PORT ./kei-buddy webhook-delete → reverts to polling 20 tests pass across 5 modules. Binary builds clean (default + extractor-openai). ## Memory binding (task #5) New files: * src/schema.rs (56) — buddy_state table DDL, idempotent * src/store.rs (164) — BuddyStore trait + SqliteBuddyStore * src/store_ops.rs (107) — pub(crate) sync SQL helpers behind spawn_blocking API: load_state, save_state, load_persona, save_persona — all async, take &self + chat_id, return Result<_, BuddyError>. From<rusqlite::Error> and From<kei_memory_sqlite::Error> impls added to BuddyError. ## State-machine port (task #7) New files: * src/transition.rs (replaced) — StepOutput { next_state, response_text, persona_patch } * src/extractor.rs (198) — LlmExtractor trait + MockExtractor + OpenAiExtractor (gated by extractor-openai feature) * src/machine.rs (250) — handle_step async fn, 11-arm state machine * src/machine_helpers.rs (171) — per-state helper fns * src/machine_tests.rs (103) — 7 FSM tests with MockExtractor Each TS branch from chat-onboard.ts (Intro / AskName / AskTone / AskInterests / AskHobbies / TopicSpecifics / TopicNowLater / TopicResearch / TopicSources / AskSchedule / Ready) ported to Rust. Russian-language responses preserved verbatim. Topic queue stored in persona_patch.__topic_state for caller round-tripping. machine.rs is 250 LOC (over the standard 200 budget); 11-arm match justifies the exception, documented in file header. ## Serve binary (task #6) New files: * src/persona_merge.rs (85) — JSON deep-merge helper * src/serve_telegram.rs (128) — sendMessage / setWebhook / deleteWebhook HTTP helpers * src/serve.rs (162) — axum Router, BuddyContext impl, run_serve * src/bin/kei-buddy.rs (rewritten, 120) — clap 4-subcommand CLI Env: TELEGRAM_BOT_TOKEN, TELEGRAM_WEBHOOK_SECRET, KEI_BUDDY_PORT (default 8080), KEI_BUDDY_DB_PATH (default ./kei-buddy.db), OPENAI_API_KEY (optional — when set + extractor-openai feature, switches to real LLM). axum + tracing-subscriber gated behind `serve` feature (default ON). Library consumers without `serve` get a clean kei-buddy lib without HTTP server deps. ## Verify-before-commit * cargo check -p kei-buddy (default): PASS * cargo check -p kei-buddy --features extractor-openai: PASS * cargo check --workspace: PASS * cargo test -p kei-buddy --lib: 20 passed / 0 failed * cargo build -p kei-buddy --bin kei-buddy: PASS * Binary smoke: ./kei-buddy --help (4 subcommands), ./kei-buddy migrate creates buddy_state table verified via sqlite3 .tables ## Follow-up (deferred, non-blocking) * Wire OpenAiExtractor in run_serve when OPENAI_API_KEY set (currently always MockExtractor — smoke-only, no real LLM yet) * proposeTopicSources path needs real LLM call (MockExtractor returns empty) * Schedule timezone fallback map for "Москва"/"Bali" etc — currently fully delegated to LLM prompt * End-to-end Telegram integration test — requires real bot token |
||
|
|
b5da1940e1 |
feat(kei-tts + kei-stt): TTS/STT abstractions with 4+3 backends
Two parallel atomars in the kei-buddy phase-1 plan. Mirror each other's
architecture: trait + feature-gated backend modules + env-driven dispatch
+ wiremock tests for HTTP backends + subprocess-error test for local.
## kei-tts (text-to-speech)
LOC: 959 across 15 files (largest src/lib.rs 121).
Trait `TtsBackend` + 4 backends behind feature flags:
* elevenlabs — POST api.elevenlabs.io/v1/text-to-speech/{voice}/stream
* openai — POST api.openai.com/v1/audio/speech (tts-1, tts-1-hd)
* google — POST texttospeech.googleapis.com/v1/text:synthesize
(Wavenet voices, base64 audioContent)
* piper — local subprocess to piper-tts binary, raw PCM out
Default features: ["piper"]. all-backends feature gates the rest.
`from_env()` reads KEI_TTS_BACKEND (default piper). Returns Box<dyn TtsBackend>.
Tests: 9 passed (env routing + 3 wiremock backends + piper subprocess error).
## kei-stt (speech-to-text)
LOC: 935 across 13 files (largest whisper_local.rs 181).
Trait `SttBackend` + 3 backends:
* whisper-local — subprocess to `whisper` CLI / faster-whisper,
reads JSON output, parses segments
* deepgram — POST api.deepgram.com/v1/listen (Token auth header,
raw audio body, parses words → Segments)
* openai-whisper — POST api.openai.com/v1/audio/transcriptions
(multipart file + model=whisper-1 +
response_format=verbose_json)
Default features: ["whisper-local"]. all-backends gates the rest.
`from_env()` reads KEI_STT_BACKEND (default whisper-local).
Tests: 10 passed + 1 doc-test (env routing + 5 wiremock + 2 JSON parsers
+ 1 subprocess error + 1 auth-header check).
## Common architecture decisions
* `with_base_url(url)` constructor on each HTTP backend for wiremock
testability — same pattern as kei-llm-router and kei-notify-telegram.
* `tempfile` crate added to kei-stt for whisper-local audio scratch.
* `base64 = { version = "0.22", optional = true }` in kei-tts for
Google's base64-encoded audioContent.
## Verify-before-commit (RULE 0.13 §)
* cargo check -p kei-tts (default + all-backends): PASS
* cargo check -p kei-stt (default + all-backends): PASS
* cargo test -p kei-tts --features all-backends --lib: 9/0
* cargo test -p kei-stt --features all-backends --lib: 10/0
* cargo check --workspace: PASS
STATUS-TRUTH from both agents: shipped=functional, stubs=0,
behaviour-verified=yes.
## Follow-up (deferred, non-blocking)
* Real backend verification needs API keys for ElevenLabs / OpenAI /
Google / Deepgram and piper-tts binary + .onnx model on PATH.
* whisper-local language_detected always None — whisper CLI JSON
schema differs across versions, parse heuristic to be added.
* faster-whisper has different JSON schema from openai-whisper;
current parser covers openai-whisper convention only.
|
||
|
|
0267311087 |
feat(kei-telegram-webhook): inbound Telegram webhook handler
Sibling to kei-notify-telegram (outbound only). This crate is the inbound
half of the Telegram Bot API integration — receives POST /webhook from
Telegram, verifies secret token, parses Update, emits typed WebhookEvent.
Architecture: handler-only. The crate exposes `handle_webhook` and the
parsed types; the consumer owns the axum::Router and the HTTP server.
This keeps kei-telegram-webhook composable into kei-buddy, kei-gateway,
or any other consumer without forcing a server topology.
Files (9 new, 484 LOC total, all under 200/file):
* src/update.rs — lean Telegram Update / Message / User / Chat /
CallbackQuery structs (only fields KeiBuddy needs: chat_id, from,
text, message_id, date, callback_data; #[serde(default)] on optionals)
* src/event.rs — WebhookEvent enum (Text / Callback / Other) +
classify(update) -> WebhookEvent
* src/handler.rs — axum handler with X-Telegram-Bot-Api-Secret-Token
header verification (mismatch → 401)
* src/context.rs — WebhookContext trait (consumer provides
secret_token() + on_event())
* src/error.rs — WebhookError via thiserror
* src/lib.rs — module declarations + re-exports
* Cargo.toml — workspace member, maturity = "alpha"
* README.md — usage example (axum Router mount, 10-line snippet)
Tests (5 in src/event.rs + src/handler.rs, all pass):
* classify_text_message — text Update → WebhookEvent::Text
* classify_callback_query — callback Update → WebhookEvent::Callback
* classify_other_returns_other — edited_message-only Update → Other
* bad_secret_token_returns_401 — wrong header → 401 UNAUTHORIZED
* good_secret_token_returns_200 — matching header → 200 OK
Verify-before-commit (RULE 0.13 §):
* cargo check --offline -p kei-telegram-webhook: PASS
* cargo test --offline -p kei-telegram-webhook --lib: 5 passed / 0 failed
* cargo check --workspace --offline: PASS (no new warnings)
STATUS-TRUTH from agent: shipped=functional, stubs=0, behaviour-verified=yes.
Follow-up (deferred, not blocking):
* axum is direct dep "0.7" in this crate + kei-cortex + kei-forge —
workspace should adopt axum in [workspace.dependencies] for version
unification (separate consolidation wave)
* Unmodelled Telegram fields (edited_message, inline_query, photo,
document, reply_markup) — extend when KeiBuddy needs them
|
||
|
|
a2d4bc9206 |
feat(kei-buddy): scaffold runtime crate — 11-state onboarding FSM enum
First atom of the kei-buddy phase-1 plan. Pure scaffold — no business
logic; that comes in follow-up commits.
Crate location: _primitives/_rust/kei-buddy/
LOC: 262 across 7 files (largest src/state.rs 85 LOC; all <200).
Contents:
* src/state.rs — OnboardState enum with 11 variants matching the
TS state-machine in keisei-marketplace/src/lib/keibuddy/chat-onboard.ts:
Intro, AskName, AskTone, AskInterests, AskHobbies, TopicSpecifics,
TopicNowLater, TopicResearch, TopicSources, AskSchedule, Ready.
serde(rename_all = "snake_case") matches TS naming.
`next()` is a stub (returns self.clone(); real transitions TBD).
* src/transition.rs — TransitionInput struct (user_text +
extracted_fields json::Value). Struct only, no extraction yet.
* src/error.rs — BuddyError enum via thiserror (StateMachine /
Memory / Transport). No From impls yet.
* src/lib.rs — module declarations + re-exports.
* src/bin/kei-buddy.rs — minimal `kei-buddy serve` clap subcommand,
currently prints "not yet implemented".
* Cargo.toml — workspace member, maturity = "concept".
* README.md — crate-level README, roadmap of 4 follow-up bullets.
Workspace registration: _primitives/_rust/Cargo.toml members list
gains "kei-buddy". Lockfile updated accordingly.
Verify-before-commit (RULE 0.13 §):
* cargo check --offline -p kei-buddy: PASS
* cargo test --offline -p kei-buddy --lib: 1 passed / 0 failed
(state::tests::all_variants_serde_roundtrip)
* cargo check --workspace --offline: PASS
* STATUS-TRUTH MARKER from agent: shipped=scaffolding, stubs=1
(state.rs:50 next() returns self.clone(), expected for scaffold)
Follow-up tasks (tracked in TaskList):
* Port handleStep transition logic from chat-onboard.ts
* LLM extract via kei-cortex
* Memory binding via kei-memory-sqlite
* Telegram webhook driver (new crate kei-telegram-webhook)
* kei-tts trait + 4 backends (ElevenLabs / OpenAI / Google / Piper)
* kei-stt trait + 3 backends (Whisper local / Deepgram / OpenAI API)
|
||
|
|
94e975c92b |
fix(workspace): restore [workspace.package] keys + 3 missing workspace deps
Wave 2 audit (validator + critic-tech-debt + critic-bug, 2026-05-04) found two regressions causing `cargo check --workspace` to hard-fail at HEAD |
||
|
|
aaa8f36e10 |
perf(ci): P1+P2 — thin-LTO + cu=16 + mold linker (~17min → ~4-5min)
Critical-path math (cargo workspace 105 crates × 3 matrix targets): - Current profile: opt-level=z + lto=true + codegen-units=1 = compile cost ~10-20× over default; observed wall-time ~17min/release run - After P1+P2 stack: predicted ~4-5min cold, ~1.5min warm == P1 — _primitives/_rust/Cargo.toml profile.release == - lto: true → "thin" (full LTO is 3-5× slower; thin keeps most opts) - codegen-units: 1 → 16 (parallel codegen restored, was serial) - Binary size cost: ~10-15% larger (acceptable for non-embedded targets) - VERIFIED: cargo check --workspace exits clean [REAL: ran in this session; 0 errors, warnings only] == P2 — mold linker for Linux targets == - New: _primitives/_rust/.cargo/config.toml (7 LOC) * x86_64-unknown-linux-gnu + aarch64-unknown-linux-gnu use clang+mold * macOS targets unaffected (use system ld + LLVM) - New step in .github/workflows/release.yml::build-release: Install mold linker (Linux only) — apt-get mold clang Gate: `if: contains(matrix.target, 'linux')` - Inserted AFTER rust-toolchain BEFORE rust-cache - Predicted gain: link phase 60s → 6s on Linux entries == P3 — explicitly NOT applied == - Path-filter on docs-only commits considered + rejected per task spec: Release tags should always rebuild even if commit only touches docs. Files: - _primitives/_rust/Cargo.toml (+2/-2 LOC) - _primitives/_rust/.cargo/config.toml (NEW, 7 LOC) - .github/workflows/release.yml (+5/-0 LOC, mold install step) [ESTIMATE-HTC: rustc + mold benchmarks claim 3-5× and 5-10× respectively on full release builds — not re-benchmarked on this 105-crate workspace yet; will measure on next v* tag push] NOTE: this commit does NOT retag — keigit publish 401 issue is on the keigit-server side (verified: token works locally, 401 from runner IP) and requires user-side action (fail2ban/Caddy whitelist GitHub Actions IP ranges on 45.77.41.204). After user fixes that, next tag will verify both speed gain AND publish success. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
3759fb0f64 |
fix(audit-batch): CI green + RULE 0.4/0.16/0.18 honesty pass
12-agent audit (2 waves Opus+Sonnet, 6 slices each) flagged 3 HIGH-tier
issues that BOTH waves agreed on, plus 5 doc-honesty findings. This
batch fixes the lot.
== CI green (was failing on main
|
||
|
|
4e99057d2b |
fix(perf): bound per-user lock LRU + stream-cap atom subprocess output
Two resource-exhaustion fixes from Opus Rust + Sonnet Rust audits. 1. kei-cortex per_user_locks DashMap unbounded growth (HIGH) File: kei-cortex/src/state.rs Bug: per_user_locks: DashMap<String, Arc<Mutex<()>>> inserted on every distinct user_id; never evicted. Auth'd attacker with 1M unique user_ids could OOM the daemon (~150 bytes/entry = 15GB at 100M entries). Fix: replaced DashMap with tokio::sync::Mutex<LruCache<String, Arc<TokioMutex<()>>>> capped at PER_USER_LOCK_CAP = 1024. Eviction is safe because callers hold their own Arc clone for their critical section; dropping the registry slot retires only the registry's reference. Used tokio::sync::Mutex for the registry because LruCache::get mutates the recency list and requires &mut self. Constructor Pattern: state.rs split into state.rs (184 LOC) + state_factories.rs (64 LOC, new). Tests added: user_lock_evicts_past_cap (registry stays ≤1024 after 2048 inserts), user_lock_keeps_most_recent (LRU recency preserved). Existing user_lock_is_stable_per_user + user_lock_differs_per_user updated to async — sole call site (handlers/portrait.rs) gains .await. 2. kei-runtime stdout/stderr cap was post-hoc (HIGH) File: kei-runtime/src/invoke.rs Bug: wait_with_output() buffered ALL child stdout/stderr; only cap_bytes truncated AFTER the child finished. A malicious atom writing 10 GB stdout (or a buggy one looping infinitely) OOM'd the runtime BEFORE the cap fired. Fix: replaced wait_with_output() with two reader threads sharing KillHandle = Arc<Mutex<Option<Child>>>. Each reader appends bytes up to STREAM_CAP = 16 MiB; on cap exceedance the reader KILLS the child from inside the reader thread (critical — otherwise the unbounded writer would never EOF and a post-hoc kill would never fire). Both readers drain the closing pipe to EOF and return. Truncation surfaces as InvokeError::SubprocessError with explicit "exceeded N byte cap" message. Constructor Pattern: invoke.rs decomposed into invoke.rs (159 LOC) + invoke_io.rs (146 LOC, new) + invoke_error.rs (54 LOC, new). Test added: invoke_kills_runaway_atom — stages a kei-flood script running cat /dev/zero, verifies (a) non-zero exit, (b) stdout < 18 MiB, (c) "cap"/"subprocess" in stderr. cargo check --workspace: clean. cargo test -p kei-cortex -p kei-runtime --test-threads=1: 471 pass / 0 fail. Pre-existing openai_loop_wiring.rs parallel-run flake (state collision when test-threads>1) is unrelated and unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
06ff2f8ed4 |
fix(auth): Google OIDC account-takeover (CVE-2023-7028 class) — email_verified gate + sub as user_id + id_token cross-check
Opus Cross-cutting audit found a classic OIDC account-takeover hole in
kei-auth-google::verify(). Same class as the public Booking.com / Slack /
GitLab pattern.
Root cause: verify() accepted info.email from userinfo response as user_id
WITHOUT checking info.email_verified. A Google Workspace admin can mint
accounts with arbitrary unverified email aliases. Attacker then OAuth-flows
into the relying party using a victim's email as their alias and gets a
session bound to that user_id. No email verification = no auth.
Fix in 3 layers (defense in depth):
1. email_verified GATE
- client.rs: UserInfo gains email_verified: bool with #[serde(default)] —
absent field defaults to false (fail-closed).
- error.rs: new Error::EmailNotVerified variant.
- provider.rs::verify(): rejects with EmailNotVerified before any session
is built when email_verified != true.
2. sub AS PRIMARY user_id
- provider.rs::verify(): user_id = info.sub (Google's stable account id),
NOT info.email. Email is now mutable metadata only. Email reassignment
in Google Workspace cannot redirect an existing user_id binding.
3. id_token.sub CROSS-CHECK
- id_token.rs (new, 104 LOC): JWT-claims-only extract_sub() — parses
base64-payload without signature verification (signature verification
against Google JWKS is a documented follow-up atomar).
- provider.rs::verify(): when TokenResponse.id_token is present, decode
claims and require id_token.sub == userinfo.sub. New
Error::IdSubMismatch + IdTokenMalformed variants.
- This adds defense against a forged userinfo response even though
signature is not yet verified.
Constructor Pattern compliance: provider.rs split into provider.rs (181 LOC)
+ verify_helpers.rs (114 LOC, with unpack_challenge / check_state /
enforce_email_verified / cross_check_id_token_sub helpers). All files <200
LOC, all functions <30 LOC.
Tests added: tests/google_security_regression.rs (164 LOC, 5 dedicated
CVE-2023-7028 regression tests). All 26 tests pass:
- verify_rejects_unverified_email
- verify_rejects_missing_email_verified_field
- verify_uses_sub_not_email_as_user_id
- verify_rejects_id_token_sub_mismatch
- verify_accepts_matching_id_token_sub
cargo check --workspace clean. cargo test -p kei-auth-google: 26/26 pass.
Follow-up: JWT signature verification against Google's JWKS endpoint with
kid-based key cache + RS256/ES256 — separate atomar (~150 LOC).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
71f17337fe |
fix(security): cortex /term env_clear + bind guard, agent-stub-scan stdin, magiclink revoke
Three independent security hardenings from cross-cutting audits.
1. cortex /term PTY env leak + bind guard (HIGH — Sonnet Cross-cutting + Opus)
- kei-cortex/src/handlers/term_pty.rs: PTY spawn was inheriting daemon's
full process env (KEI_AUTH_KEY, ANTHROPIC_API_KEY, FAL_KEY, etc.) into
every authenticated /term shell. Combined with default cors_origin =
https://keisei.app, one stored XSS on keisei.app + one bearer token =
full local shell with all daemon secrets.
Added apply_safe_env() helper: env_clear() + re-set only HOME, PATH,
USER, LANG, TERM. Spawn helper invokes it before spawn_command.
- kei-cortex/src/main.rs: extracted build_config() helper; added
enforce_loopback_or_local_cors() guard called before serve.bind. Refuses
to start if bind addr is non-loopback AND cors_origin is a public
domain — prevents the XSS-to-shell scenario in production.
2. agent-stub-scan.sh stdin parsing (HIGH — multiple audits)
- hooks/agent-stub-scan.sh: previously read $CLAUDE_AGENT_TRANSCRIPT env
var which Claude Code does NOT set on PostToolUse:Agent. Hook silently
exited 0 — RULE 0.16 enforcement was dead-code in production.
Rewrote to read stdin JSON via jq, flatten .tool_response recursively
(string|array|object via the same pattern as agent-event-done.sh),
guard on .tool_name == "Agent" and command -v jq. Maintained WARN-tier
exit-0 with TODO marker for ENFORCE flip on 2026-05-05 (per RULE 0.16
§2 ladder).
3. magiclink revoke() silent no-op (HIGH — Opus Rust + Sonnet Cross-cutting)
- kei-auth-magiclink/src/{error,provider}.rs: revoke() previously returned
Ok(()) without doing anything. Operators expecting "revoke a session"
semantics from the AuthProvider trait got false success. Stolen magic-
link URLs remained valid until the 15-minute TTL.
Added Error::Unsupported variant. revoke() now returns
Err(Unsupported(...)) with explicit guidance: "rotate KEI_MAGICLINK_HMAC_
KEY to invalidate all live tokens, or maintain a deny-list at the caller
layer". Test provider_revoke_returns_unsupported_error confirms the
error variant is wired.
Tests: cargo check + cargo test both PASS. 444 functional tests across
kei-cortex (428 lib) + kei-auth-magiclink (16 lib + smoke). Pre-existing
openai_loop_wiring.rs 502 failures in routes/openai/{chat,responses}.rs are
NOT introduced by these fixes — separate unrelated triage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
a0b1eca6d9 |
chore: strip dangling sibling refs from Cargo.toml descriptions
Opus TOML audit found 7 crates whose Cargo.toml description fields
advertised sibling crates that don't exist in the workspace:
- kei-auth-magiclink, kei-auth-webauthn → mentioned kei-auth-{github,microsoft}
(workspace has only google + apple + magiclink + webauthn)
- kei-notify-discord → mentioned kei-notify-email (workspace has telegram /
discord / slack / sms only)
- kei-net-wireguard, kei-net-ipsec → mentioned kei-net-tailscale (workspace
has wireguard / openvpn / ipsec only)
- kei-git-forgejo → mentioned kei-git-keigit (workspace has forgejo / gitea /
gitlab / bitbucket)
- kei-compute-linode → mentioned kei-compute-hetzner (Hetzner removed per
rules/projects/project-vortex.md after TSPU blocks)
- kei-provision/Cargo.toml description + metadata → both mentioned Hetzner
Updated each description to mention only actually-existing siblings. cargo
metadata consumers, IDE tooltips, and any future crates.io publication will
no longer carry misleading sibling lists.
cargo check --workspace clean (only pre-existing warnings unrelated to this
change). Description-only metadata edits — zero functional impact.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
7cc544fd85 |
chore: author email + Cargo metadata SSoT (parfionovich@keilab.io)
Two related changes:
1. Author email update across the kit
- All `info@greendragon.info` references replaced with `parfionovich@keilab.io`
- Touched: NOTICE, README.md, _ts_packages/package.json (and 5 adapter packages),
plus 90+ Cargo.toml files
- Apache-2.0 attribution unchanged (Denis Parfionovich, 2026)
2. Cargo workspace.package SSoT for author/license/repository/homepage
- Added to [workspace.package]:
authors = ["Denis Parfionovich <parfionovich@keilab.io>"]
license = "Apache-2.0"
repository = "https://github.com/KeiSei84/KeiSeiKit-1.0"
homepage = "https://github.com/KeiSei84/KeiSeiKit-1.0"
- All ~89 member crates migrated from inline declarations to:
authors.workspace = true
license.workspace = true
(repository/homepage where applicable)
- Closes audit gap: kei-graph-stream, kei-cortex, kei-shared previously had no
license field at the crate level, blocking `cargo publish` on those.
Now they inherit Apache-2.0 from workspace.
- kei-scheduler/Cargo.toml: removed stray duplicate `authors` line introduced
by an earlier migration sweep.
cargo check --workspace: clean. No code changes; metadata-only migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
23b818a682 |
fix(auth): SecretString redacted Serialize + PKCE verifier wired
Two findings from KeiSeiKit2.0 pr-review (~/Projects/KeiSeiKit2.0/skills/pr-review)
applied to commit range 897d010..HEAD.
1. BLOCKER — SecretString silently leaked plaintext via Serialize.
File: _primitives/_rust/kei-runtime-core/src/secrets.rs
Was: derive(Serialize) + serde(transparent) -> serde_json::to_string(&secret)
emitted the raw plaintext in any parent struct with #[derive(Serialize)].
Debug was redacted but Serialize was not. Defeated the type's purpose.
Now: manual Serialize impl always emits literal "<redacted>". Deserialize
derive kept (callers need to read secrets from config/env).
Test serialize_emits_redacted_literal asserts JSON output is "\"<redacted>\"".
2. WARNING — PKCE code_verifier dropped before token exchange.
build_auth_url generated code_challenge = SHA256(verifier) but verify() never
threaded the verifier to the token endpoint. Token exchange submitted no
code_verifier, defeating the PKCE protection.
Files:
- _primitives/_rust/kei-runtime-core/src/traits/auth.rs:
AuthChallenge::OAuthCode now carries code_verifier: Option<String>.
Caller stores verifier alongside state in their session-store, exactly as
they already store state for CSRF check.
- _primitives/_rust/kei-auth-google/src/provider.rs:
verify() destructures code_verifier and passes to client.exchange_code(...).
- _primitives/_rust/kei-auth-apple/src/provider.rs:
same change.
Tests added (wiremock body assertions):
- google_smoke / apple_smoke: assert exchange request body contains
code_verifier=<value> when challenge carried Some(verifier).
- existing tests updated to construct OAuthCode { ..., code_verifier: None }.
Test split (Constructor Pattern 200 LOC):
- apple_smoke.rs grew over 200 LOC after PKCE test addition. Split into
apple_smoke.rs (provider tests) + apple_client_smoke.rs (client tests).
- same for google_smoke.rs / google_client_smoke.rs.
Test results: 31 passed; 0 failed across kei-auth, kei-auth-apple, kei-auth-google,
kei-runtime-core unit + integration tests. cargo check --workspace clean.
Breaking change: any caller that constructs AuthChallenge::OAuthCode outside this
workspace must add code_verifier field (None for legacy no-PKCE; Some for PKCE).
Compile-time surfaced gap, not runtime regression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
baf54250a9 |
fix(substrate): dangling handoffs + atomar manifest fill-out + validator extension
Group F — manifest, capability, role, and assembler cleanup (post-audit 2026-05-02).
Dangling handoff targets stripped:
- validator.toml: removed handoffs to physics-deriver, patent-compliance
- code-implementer.toml: removed physics-deriver handoff
- architect.toml: removed physics-deriver
- ml-implementer.toml: removed physics-deriver, fixed "multi-node multi-node" typo
- ml-researcher.toml: removed physics-deriver, patent-researcher
- researcher.toml: removed patent-researcher
None of those manifest files exist in _manifests/. Comments added explaining
the removal date for future re-authoring.
Validator extension (_assembler):
- src/validator.rs: extended validate() with check_handoff_targets — every
[[handoff]].target must point to existing _manifests/<name>.toml.
Future dangling handoffs blocked at validate time.
- src/validator_tests.rs (new, 133 LOC): unit tests for handoff-target check.
- tests/fixtures/_manifests/: added valid stubs for previously-missing manifests
(architect, critic, security-auditor, validator,
ml-implementer, ml-researcher, infra-implementer)
so existing fixtures pass the new validator gate.
- tests/snapshots/: insta snapshots updated for researcher + code-implementer.
Atomar manifest fill-out (replaced stock copy-paste with domain-specific):
- code-implementer-typescript: Drizzle/Zod/Next.js semantics
- code-implementer-go: mesh networking, embedded servers
- code-implementer-swift: SwiftUI, SPM, macOS menubar
- code-implementer-python: RULE 0.2 exception language
- code-implementer-flutter: Riverpod, Clean Architecture
- infra-implementer-cicd/iac/container/secrets: tool-specific bans + scopes
- researcher-web/code: output_extra_fields fixed (was code-implementer copy-paste
"Largest file LOC", "Tests pass count" — now sources cited /
evidence grade / gaps section)
Capability schema completeness:
- policy/no-git-ops + quality/cargo-check-green: added stage = "runtime"
- 8 capabilities: added explicit parents = [] (was missing/inconsistent)
Role schema:
- _roles/auditor.toml + merger.toml: added [taxonomy] + [lineage] (was missing)
- _roles/explorer.toml: added comment that "Explore" is the canonical Claude Code
subagent type (case-sensitive)
Reference path cleanup (manifest references):
- critic.toml: ~/.claude/skills/architecture-rules/... -> path:user-skills/...
- researcher.toml: stripped ~/.claude/agents/validator.md (machine-local)
Misc:
- frontend-validator.toml: renumbered duplicate step 6 -> step 7
kei-registry test fixture suppression:
- tests/fixtures/{atom-sample,fake-kit,mini-kit}/.kei-registry-ignore (3 new files)
- DNA-INDEX.md was inflating atom count by ~10% from test fixture rows; ignore-file
hooks ready, kei-registry walker implementation is a follow-up.
Tests: 59 passed; 0 failed; 1 ignored (pre-existing #[ignore]). cargo check clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
da4d88910a |
chore(workspace): SSoT inheritance + version unification
Group E — Cargo workspace hygiene (post-audit 2026-05-02).
Workspace dependency inheritance:
- 40+ member crates migrated from inline dep pinning to { workspace = true }.
Was: every crate redeclared clap/serde/rusqlite/tokio/etc inline, defeating
the [workspace.dependencies] SSoT and forcing N edits per upgrade.
Authoritative pins now live solely in _primitives/_rust/Cargo.toml.
Major version splits resolved:
- dashmap: 5 vs 6 (kei-cortex/kei-gateway) -> 6 in workspace
- tower: 0.4 vs 0.5 (kei-cortex/kei-forge) -> 0.5 in workspace
- notify: 6 vs 8 (kei-projects-watcher/kei-watch+kei-skills) -> 8 in workspace
- thiserror: 1 vs 2 (workspace/keisei) -> kept 1; keisei downgraded
Closed: dual-major compilation = wasted build time + ABI mismatch risk
at trait boundaries.
Profile / orphan cleanup:
- kei-changelog/Cargo.toml: deleted [profile.release] block (workspace member
profiles are silently ignored by Cargo since 1.0).
- kei-brain-view/Cargo.toml: removed dangling "[workspace] table stripped on
merge" comment (orphan from prior decomposition).
rust-version SSoT:
- 27+ member crates migrated from inline rust-version = "1.75" to
rust-version.workspace = true. Workspace declares 1.77; the inline 1.75 pins
were stale and misleading (with resolver 2 the workspace MSRV won anyway).
cargo check --workspace: clean (only pre-existing sqlx-postgres future-incompat
warning + frustration-matrix dead-code warning, neither introduced by this change).
Note: _assembler/ lives outside _primitives/_rust workspace, so its Cargo.toml
was not touched here. Remaining edition-2024 question for _assembler is a
separate decision.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
cb1090bef3 |
fix(security): RCE allowlist + WebSocket auth + SSH option-injection
Group D — three independent security primitives hardening (post-audit 2026-05-02).
kei-runtime — atom invoke RCE allowlist:
- invoke.rs: is_safe_crate_name validator (regex ^kei-[a-z][a-z0-9-]+$);
rejects /, \\, .., :, absolute paths, empty, >128 chars.
InvalidAtom error variant.
stdout/stderr capped at 16 MiB (was unbounded).
- main.rs: InvalidAtom mapped to exit code 2.
- tests/invoke_exit_codes_smoke.rs: invoke_unsafe_crate_name_exits_2 added.
- Closes: any user able to write atoms/*.md with crate_name: "rm" or "sudo"
triggered arbitrary command execution.
kei-graph-stream — WebSocket bearer + Origin:
- auth.rs (new, 142 LOC): token load + bearer extraction + Origin allowlist +
ConstantTimeEq compare; 8 unit tests.
- ws.rs: ws_handler validates Origin + bearer before upgrade (403/401 on failure).
- main.rs: --public-bind-i-accept-the-leak flag required for non-loopback bind;
else bail!() with explicit error.
- tests/smoke.rs: rewritten with Origin + bearer headers via connect_async_with_config.
- Closes: WebSocket /stream had zero auth, zero Origin check; browser CSWSH could
subscribe to agent activity broadcast; KEI_GRAPH_STREAM_BIND env silently
accepted any SocketAddr.
kei-compute-baremetal — SSH option injection (CVE-2023-51385 class):
- ssh.rs: is_safe_user + is_safe_host validators (alphanumeric + -_.; reject leading -;
max 64 chars; no @, :, /, \\, space).
- ssh.rs: -- sentinel before user@host argv (OpenSSH 9.6+ stops flag parsing).
- ssh.rs: StrictHostKeyChecking=yes default; KEI_BAREMETAL_ACCEPT_NEW=1 for TOFU.
- error.rs: InvalidRegion variant.
- provider.rs: validators applied in target_for_spec + target_for_handle.
- Closes: spec.region "-oProxyCommand=evil" triggered local RCE before TCP connect.
Test results: 29 passed; 0 failed across all three crates. cargo check clean.
Findings: RCE allowlist (Wave-A) + WebSocket auth (Wave-B) + SSH injection (Wave-B)
were unique-per-retest discoveries. None present in original wave-1 audit.
Note: kei-compute-baremetal/src/provider.rs at 300 LOC (was 268; +32 from validators).
Pre-existing >200 LOC violation, fix scope was security-additions only. Follow-up:
split provider.rs into provider.rs (<200) + provider_tests.rs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
9aa29aca15 |
fix(kei-cortex): SSRF + atomic token + body limits + capped reads
Group C — kei-cortex daemon security hardening (post-audit 2026-05-02).
- fal_ssrf.rs (new): validate_fal_url whitelist (fal.ai/.media/.run only).
Applied to upload_url, file_url, status_url, images[0].url,
and download_image. Closes SSRF where compromised fal response
could direct daemon to fetch IMDSv1 (169.254.169.254) and
stream cloud creds.
- fal_pipeline.rs (new): HTTP step functions extracted from fal.rs; fal.rs trimmed
to thin orchestrator (101 LOC, was over 200 LOC limit).
- auth.rs: save_token now writes to <path>.<nanos>.tmp + sync_all + rename. Was
non-atomic OpenOptions truncate+write — crash mid-write produced empty
token file -> bootstrap rotated -> stale clients locked out.
- routes.rs + routes_auth.rs (new): explicit DefaultBodyLimit per route — chat 256 KiB,
tool/apply 11 MiB, pet/interaction 64 KiB, tts 32 KiB.
Bearer auth middleware extracted to routes_auth.
- handlers/chat.rs: validate_body enforces MAX_MESSAGE_CHARS = 50_000. Closed cost
amplification where 1.99 MiB chat message billed 500K tokens
($1.50/turn at Sonnet pricing) on every send.
- anthropic_sse.rs: SseParser MAX_BUF = 1 MiB cap; was unbounded — peer streaming
1 GB without \\n\\n would OOM daemon.
- http_helpers.rs (new): HTTP_CLIENT: Lazy<reqwest::Client> shared across handlers
(was per-request Client::new() => 100-300ms TLS handshake
per chat turn, no HTTP/2 multiplexing, fd leak risk on
macOS TIME_WAIT).
- http_helpers.rs::read_capped: per-response body cap (16 KiB error / 64 MiB success).
Applied to anthropic, anthropic_invoker, elevenlabs,
fal_pipeline. Closed unbounded resp.text() / .bytes()
pattern that compromised upstream could exploit.
Test results: 462 passed; 0 failed (single-threaded). cargo check clean.
2 pre-existing port-binding flakes in openai_loop_wiring tests are unrelated.
Findings consensus: fal SSRF + body-size + bearer-token-atomicity appeared in
Wave-A retest; chat message cap + SSE buf cap appeared in Wave-A only. Would have
been missed by single audit pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|