Wraps pure (query/transform) atom invocations with SHA-256 keyed
cache. Refuses Command/Stream kind atoms as unsafe.
22/22 tests (14 unit + 8 integration). Canonical JSON keying
(formatting-drift safe). TTL expiry. AtomExecutor trait decouples
subprocess from test mocks.
Default DB ~/.claude/cache/cache.sqlite, overridable via --db or
$KEI_CACHE_DB.
Workspace Cargo.toml: +kei-cache member.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5/5 tests preserved. Synthetic nodes PK table via engine; cross_edges
stays in custom_migrations because engine's TextPair is too minimal
(id/weight/evidence/metadata columns needed).
Flag for engine follow-up: TextPair DDL needs optional edge metadata
columns — same gap flagged by M5 independently.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Primary entity = chat_messages (integer-PK; sessions stay bespoke —
TEXT UUID PK incompatible with engine's IntegerPk).
Secondary tables (chat_sessions, indexes, FTS rename fts_chat →
fts_chat_messages) moved into custom_migrations. FTS shadow column
session_id dropped (never used as MATCH filter).
Archive verb NOT enabled: chat_sessions.status is TEXT enum not INTEGER
flag — engine archive verb incompatible. archive_session stays bespoke.
cost REAL column dropped — engine has no Real FieldKind. per-message
cost struct field kept (=0.0) for API compat; session total_cost
aggregate still maintained bespoke in save_message.
5/5 tests preserved + 1 new engine migration-parity smoke test.
DOGFOOD prompt feedback (M1 via kei-agent-runtime prepare):
6 engine limitations surfaced for follow-up — FieldKind::TextPk,
FieldKind::Real, archive-TEXT-enum variant, FTS UNINDEXED shadow cols,
atom dir assumption, rusqlite drop logic. See M1 task report.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dogfood gap #2 from prepare workflow: capability text fragments say
"your task's scope" generically, but agent never sees the resolved
scope params from task.toml. Migration agent had no way to know
whitelist = kei-chat-store/** without me pasting it.
Fix: render_scope_block() injects a resolved-params section between
capability fragments and task body. Shows:
- files-whitelist / files-denylist glob lists
- cargo-check-crates / cargo-test-crates
- test-count-min (if Option<u32>::Some)
- report-fields-required
If no scope params set, block is empty (no section rendered).
Now `kei-agent-runtime prepare` emits fully self-contained prompt —
no external context needed. Substrate unblocked for parallel migrations.
Tests: 41/41 (was 41 — no regression; scope-block added tests deferred
to follow-up since compose_smoke already covers existing path).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A1's test fixture created before B5 merged new fields onto EntitySchema.
Cross-wave fixup — non-functional change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
H4/M4/S3 DNA entropy:
- short_sha256() widened to 8 hex (32-bit) for scope + body hash
- nonce expanded to 8 hex from full u32 via rand::random
- Birthday collision at ~77K agents sharing role+caps (was ~256)
- Dna::parse accepts legacy 4-hex values with stderr warning for
rolling upgrade of pre-fix DNAs
H5 role recursion: resolve_inner depth parameter + MAX_DEPTH=16.
Returns RoleError::MaxDepthExceeded{depth, trace:Vec<String>} for
clear diagnostics.
S1 path traversal — two sites closed:
- role.rs::resolve_inner validates role name + parent in extends
against regex ^[a-z][a-z0-9-]{0,63}$ before Path::join
- compose.rs::split_cap_name same validation on capability
category/slug before Path::join
- RoleError::InvalidName{kind, value} on violation
M1 relaxes warnings: eprintln replaced with ResolvedRole.warnings
Vec<String> field. Caller decides: log, fail, ignore.
New typed RoleError (thiserror) — PartialEq/Eq for test ergonomics.
Tests: 73/73 (was 66, +7: 3 DNA + 4 role). Full extends graph + malicious
name + depth+1 all covered.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dogfood gap found: orchestrator running `kei-agent-runtime prepare
<task.toml>` hit "task.agent-id is empty" error. Required 3-step flow
(ledger fork → edit task.toml → prepare) for every spawn.
Fix: auto-generate `ag-<role>-<unix-ms-hex>-<4hex-rand>` if task.agent-id
empty. Orchestrator can still pre-allocate for deterministic ids;
auto-gen is ergonomic default. DNA compose sees the effective id via
clone+inject into TaskSpec before Dna::compose.
Helper `autogen_agent_id()` uses SystemTime millis + rand u16 — low
collision at orchestrator spawn rate (< 1 spawn/ms).
build_ledger_row() split: original preserved as thin wrapper, new
build_ledger_row_with_id() accepts explicit id for auto-gen path.
Verified end-to-end via `/tmp/keisei-dogfood/test-task.toml`:
prepare now emits complete AgentInvocation including composed prompt,
DNA, verify command — ready for orchestrator Agent-tool invocation
without pre-alloc step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two P1↔E1-audit-wave integration regressions caught by kei-runtime
invoke_real_atom test.
1. LocalFileResolver (E1 SSRF hardening) rejected $ref to
_schemas/fragments/ because the dir is OUTSIDE atom's schema parent.
Fix: extend LocalFileResolver with `find_fragments_root()` — walks up
from schema root looking for `_schemas/fragments/`. If found, allow
$ref under EITHER schema root OR fragments root. Still rejects
arbitrary filesystem $ref.
2. jsonschema injection of absolute $id now ALSO applied to fragment
schemas loaded via LocalFileResolver.resolve(). Without this, a
fragment declaring `$id: "_schemas/fragments/titled.json"` (relative)
was resolved against parent schema's absolute $id, producing double
prefix `_schemas/fragments/_schemas/fragments/titled.json`.
3. create-input.json + create-output.json had `additionalProperties:
false` alongside `allOf: [$ref <fragment>]`. Draft-07 gotcha:
additionalProperties at this level does NOT see properties inherited
from $ref-ed fragment — caused 'title' unexpected rejection. Dropped
the constraint on 2 fragment-composed schemas; kept on 4 standalone
ones (search-input/output + add-dependency-input/output).
Tests: kei-runtime 5/5 green; integration test passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove std::process::Command invocation of scripts/new-atom.sh from
kei-forge. Templating moves to pure Rust — eliminates the
sed-metacharacter injection class structurally, on top of the
description whitelist that E2 added as defence-in-depth.
src/generate.rs split into 4 Cubes (Constructor Pattern):
- generate/placeholders.rs — 6-token substitution, longer-first ordering
- generate/paths.rs — TargetPaths::resolve + assert_none_exist
- generate/rollback.rs — Drop-based atomic rollback (Rust idiom for
shell `trap ERR`)
- generate/atom_tests.rs — 5 tempdir integration tests
generate.rs dropped from 295 → 159 LOC as orchestration thin wrapper.
Behavioural parity with scripts/new-atom.sh maintained: same 6 tokens,
same order, refuse-overwrite, atomic rollback, same file-list
ordering. scripts/new-atom.sh untouched on disk (still usable as
standalone CLI).
Cargo.toml: removed mock-generate feature flag (no longer needed —
pure-Rust tests use tempfile::TempDir), added tempfile dev-dep.
Tests: 44/44 (was 29 with mock-generate; +15 new pure-Rust unit tests
across placeholders/paths/rollback/atom_tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three post-E1/E2/E3-merge items:
1. Schema amendment A-1 (architect P0-a, non-breaking clarification):
input.schema and output.schema are REQUIRED for all atom kinds. The
shared kei-atom-discovery parses them as Option<PathBuf> only to allow
tolerant skip-on-missing (stderr warn), not to permit absent schemas.
Resolves Stream C / Stream D enforcement asymmetry documented in
critic finding #6.
2. Cross-stream integration test (architect P0-b): tests/substrate_integration.sh
builds release binaries, scaffolds a test atom corpus, runs
schema-lint + list-atoms + atoms-discover + invoke; asserts all four
streams agree on the same atom corpus and exit codes honour the
locked §Runtime contract. Previously missing — only manual smoke
checks existed.
3. Fix regression introduced by E1's jsonschema 0.18 upgrade:
"relative URL without a base" on compile when schema declared a
relative $id like "kei-task/atoms/schemas/create-input.json".
validate.rs now synthesises an absolute file:// $id from the
canonicalised schema path before compile. Internal $refs still
resolve relative to the schema file; LocalFileResolver still confines
to the schema's parent dir. Integration test catches this.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three HIGH security findings resolved in _primitives/_rust/kei-forge/:
- F-1: DNS rebinding — require_local_host middleware returns 421 on
non-localhost Host headers
- F-2: CSRF via urlencoded — require_json_content_type middleware
returns 415 on non-JSON; form HTML now POSTs JSON via fetch()
- crit#1/SA F-7: description sed injection — whitelist validator rejects
newline/CR/tab/NUL/backtick/$/length>200, blocks the shell-script attack
at the Rust layer
- crit#11: missing security headers — CSP, X-Frame-Options DENY,
X-Content-Type-Options nosniff, Referrer-Policy no-referrer on GET /
Zero new deps (axum 0.7 middleware::from_fn + HeaderMap native).
Constructor Pattern compliant — 6 Cube files, largest 231 LOC including tests.
Tests: 29/29 (was 12/12; +17 new). Includes 4 adversarial integration
tests for each defence layer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>