Commit graph

137 commits

Author SHA1 Message Date
Parfii-bot
ca8833b582 feat(p-install): register 8 new crates in MANIFEST.toml + profile selection
Added [primitive.*] entries for kei-agent-runtime, kei-capability,
kei-provision, kei-entity-store, kei-pipe, kei-cache, kei-spawn,
kei-replay. Profile memberships:
- ops: +kei-provision (total 9)
- dev: +7 substrate+automation primitives (total 17)
- full: +8 (total 46)

docs/INSTALL.md + README.md updated with new counts.

Not registered (lib-only, no main.rs): kei-atom-discovery.
Flag for follow-up: kei-forge + kei-runtime are in workspace but not
in MANIFEST (were before my scope). regen-counts.sh will soft-warn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:26:11 +08:00
Parfii-bot
3cfefa7dfc Merge W10B — kei-sage facet-query includes _roles/ 2026-04-23 13:59:18 +08:00
Parfii-bot
ec205d5ee5 feat(w10a): engine TextPairWithMetadata extra_columns + kei-crossdomain re-migrated
EdgeKeyKind::TextPairWithMetadata extended with:
- from_col / to_col (custom column names, default src_path/dst_path)
- extra_columns: &[(name, FieldKind)] for domain-specific edge metadata

kei-crossdomain fully re-migrated via engine:
- edge_table: Some('cross_edges') + TextPairWithMetadata variant with
  from_col='from_uri', to_col='to_uri', has_id/has_weight/has_created_at,
  extra_columns=[evidence, metadata]
- Custom edges DDL dropped from custom_migrations (engine owns it now)
- edges.rs query_edges SELECT uses edge_id (engine-emitted PK)

Tests: 42/42 kei-entity-store (+2), 5/5 kei-crossdomain preserved.
Sister crates (task/chat/content/social/sage) no regression.

Closes HANDOFF-WAKE deferred item #2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:59:06 +08:00
Parfii-bot
cd7dc94512 feat(w10b): kei-sage facet-query walker includes _roles/
Extended discover_primitives to optionally walk _roles/ root (with
backward-compat thin wrapper preserving 3 existing facet tests).

RoleDoc parser handles [role] name= shape (distinct from [capability]
+ flat manifests). Role IDs namespaced as role::<name>.

CLI: default_roles_root() = ~/.claude/_roles; FacetQuery.roles_root
flag optional.

Tests: 36/36 (was 34, +2 role-taxonomy + backward-compat).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:57:50 +08:00
Parfii-bot
4f08832b61 Merge W9E — kei-replay NEW crate 2026-04-23 13:37:02 +08:00
Parfii-bot
7dd5c0796d Merge W9D — kei-spawn drive stub 2026-04-23 13:37:02 +08:00
Parfii-bot
38ceab0913 feat(w9e): NEW kei-replay crate — reconstruct spawn from DNA
kei-replay <dna> parses DNA, looks up ledger row, loads task.toml
from worktree, re-runs compose_prompt, recomputes body hash, reports
match/drift.

kei-replay diff <dna-1> <dna-2> flags every changed facet between
two DNAs.

6 cubes (main/lib/replay/diff/ledger_lookup), all ≤114 LOC.

Direct SQLite access in ledger_lookup.rs (kei-ledger has no lib.rs).
v4 schema-compatible (reads id/dna/worktree_path/spec_sha).

Tests: 6/6 (≥4 required): happy path, missing DNA, drift detection,
diff differing bodies, diff identical, explicit --task override.

Workspace Cargo.toml: +kei-replay member.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:34:16 +08:00
Parfii-bot
6562b581f7 feat(w9d): kei-spawn drive subcommand + AnthropicDriver trait stub
kei-spawn drive <task.toml> internally calls spawn pipeline, emits
SpawnOutput JSON to stdout, returns exit 64 (NotImplemented) from
ManualDriver with stderr instruction to use manual Agent-tool path.

AnthropicDriver trait: ManualDriver (current) + HttpDriver (future
reqwest+tokio+KEI_ANTHROPIC_KEY). Extensibility preserved without
breaking-change deps.

Tests: 10/10 (was 6, +4: 2 drive unit + 2 drive_smoke binary integration).

Exit code contract: 0 success, 1 spawn-fail, 2 verify-fail, 64
NotImplemented (matches kei-runtime::invoke NotImplemented convention).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:34:16 +08:00
Parfii-bot
b57c022ea1 feat(w9b): kei-chat-store cost REAL reinstated via FieldKind::RealDefault
E1 unblocked: cost column added back to chat_messages via
FieldKind::RealDefault(0.0). save_message passes through f64; search
restores via r['cost'].as_f64().

Tests: 6/6 (was 5, +1 cost_roundtrips_via_search asserts 0.00777 f64).

Known deferred (flagged for future):
1. chat_sessions TEXT-PK requires engine multi-schema support (Store::open
   takes single schema) — bespoke DDL retained.
2. kei-crossdomain re-migration deferred — engine TextPairWithMetadata
   extra_columns field not yet shipped; edge DDL hard-codes src_path/
   dst_path vs from_uri/to_uri. Bespoke DDL retained. Follow-up: extend
   engine edge variant with extra_columns + column name customization.

kei-crossdomain tests unchanged 5/5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:34:16 +08:00
Parfii-bot
26e74cfa15 Merge SP1 — kei-spawn automation envelope 2026-04-23 10:22:38 +08:00
Parfii-bot
948f07bfa8 Merge E2 — kei-capability fork subcommand 2026-04-23 10:22:38 +08:00
Parfii-bot
11b85c2a61 Merge TX3 — ledger creator/fork columns 2026-04-23 10:22:38 +08:00
Parfii-bot
3b549988ea Merge TX2 — kei-sage facet-query + lineage 2026-04-23 10:22:38 +08:00
Parfii-bot
355f01b929 Merge E1 — engine improvements (TextPk/Real/TextArchiveEnum/TextPairWithMetadata) 2026-04-23 10:22:38 +08:00
Parfii-bot
eac09a6354 feat(e1): engine improvements — TextPk + Real + TextArchiveEnum + TextPairWithMetadata
4 additive FieldKind/EdgeKeyKind variants addressing M1 dogfood +
M4/M5 flags. Backward-compat — existing schemas unchanged.

FieldKind::TextPk — TEXT PRIMARY KEY (kei-chat-store UUID sessions)
FieldKind::Real + RealDefault(f64) — REAL columns (kei-chat-store cost)
FieldKind::TextArchiveEnum — archive verb writes string sentinel instead of 1
EdgeKeyKind::TextPairWithMetadata — src_path/dst_path + id/weight/created_at/extra

Archive verb now dispatches: IntegerFlag writes 1, TextArchiveEnum writes sentinel.
Link/rank handle TextPairWithMetadata via generic weight+id propagation.
PK helpers in verbs/pk.rs abstract integer vs text primary key.

Engine decomposed to stay under 200 LOC:
- schema.rs (149) + field.rs (94, new) + ddl.rs (157, new)
- verbs/pk.rs + create_defaults.rs split from create.rs

Tests: 40/40 (was 32, +8 real_text_pk_smoke).
Sister crates verified backward-compat:
- kei-task 9/9, kei-chat-store 5/5, kei-content-store 4/4
- kei-social-store 5/5, kei-crossdomain 5/5, kei-sage 28/28

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:22:22 +08:00
Parfii-bot
02451f5f49 feat(sp1): NEW kei-spawn crate — automation envelope
spawn <task.toml> internally calls prepare + ledger fork, emits
JSON ready for Agent tool invocation. verify wraps post-return
check+ledger update. list-pending shows running forks.

kei-ledger invoked via subprocess (no lib.rs in kei-ledger).
KEI_SPAWN_LEDGER_NOOP=1 test escape hatch for CI without binary.

spec_sha = SHA-256 of task.toml bytes (workspace sha2 dep).

Tests: 6/6 integration (happy, explicit-id, unknown-role, non-spawnable,
verify-missing, end-to-end roundtrip).

Step 3 (Anthropic API) stays with orchestrator — next iteration adds
kei-spawn drive <task.toml> for HTTP automation.

Workspace Cargo.toml: +kei-spawn member.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:21:45 +08:00
Parfii-bot
6e7e517f83 feat(e2): kei-capability fork subcommand + lineage stamping
New 'fork' subcommand copies capability dir + rewrites capability.toml
with [lineage].fork_from + parents + creator + created. Refuses
clobber, validates slug regex.

Tests: 4 integration + 2 unit (epoch_to_iso, split_cap_name) = 6/6.

Doc update in AGENT-SUBSTRATE-SCHEMA.md §Orchestrator ergonomics.

Zero-chrono ISO-8601 via Hinnant's algorithm (single-file).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:21:45 +08:00
Parfii-bot
55606b176f feat(tx3): kei-ledger v4 migration — creator_id + fork_parent_id + descendants
Schema v4 adds creator_id TEXT + fork_parent_id TEXT columns with
indexes. Migration one-txn matches v2/v3 pattern.

fork() gains --creator + --fork-parent flags. New Descendants
subcommand walks fork_parent_id OR creator_id chain.

Extracted row.rs (AgentRow + SELECT_COLS) + descendants.rs +
dispatch.rs to stay ≤200 LOC.

Tests: 18/18 (was 13, +5: creator roundtrip, fork-parent lineage,
descendants chain, pre-v4 NULL backward-compat, v4 idempotent).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:21:45 +08:00
Parfii-bot
5a34c35311 feat(tx2): kei-sage facet query + lineage traversal
3 new subcommands:
- facet-query <key=value> [<k2=v2>...] — AND-filter walks primitives
- lineage <primitive-id> [--depth N] — BFS ancestors/descendants/forks
- author <creator-id> [--limit N] — all primitives by creator

facet_query.rs walks _capabilities/*/*/capability.toml + _manifests/*.toml
via toml parser. Handles missing sections correctly (None != specific).

lineage.rs BFS over parents[] wikilinks + fork-from + created-by edges.

Tests: 34/34 (was 28, +6: 3 facet_smoke + 3 lineage_smoke).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:21:45 +08:00
Parfii-bot
e418f1247c feat(tx1): taxonomy + lineage facets schema extension
Optional [taxonomy] + [lineage] TOML sections on capability.toml and
atom .md frontmatter. Backward-compat — all fields optional, existing
files parse unchanged.

TaxonomyFacets struct (7 facets): kingdom, mechanism, domain, layer,
stage, stability, language.
Lineage struct: parents[], creator, created, fork_from.

AtomMeta extended with taxonomy: Option<TaxonomyFacets> + lineage:
Option<Lineage>.

docs/TAXONOMY.md — canonical vocabulary. Graph-based (DAG with typed
edges), not tree. Multi-faceted nodes allowed.

3 pilot primitives tagged: policy::no-git-ops, quality::cargo-check-green,
tools::bash-allowlist.

Tests: 16/16 (was 12, +4: full/partial/no-facets/parents-array).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 06:10:58 +08:00
Parfii-bot
010def05ad Merge R2 — kei-cache
# Conflicts:
#	_primitives/_rust/Cargo.toml
2026-04-23 05:56:12 +08:00
Parfii-bot
b823a99812 Merge R1 — kei-pipe DAG runtime 2026-04-23 05:55:35 +08:00
Parfii-bot
41eec8d5b1 Merge M5 — kei-sage migration 2026-04-23 05:55:35 +08:00
Parfii-bot
91f9f050e1 Merge M4 — kei-crossdomain migration 2026-04-23 05:55:35 +08:00
Parfii-bot
af16066793 Merge M3 — kei-social-store migration 2026-04-23 05:55:35 +08:00
Parfii-bot
a28ce2b36c Merge M2 — kei-content-store migration 2026-04-23 05:55:35 +08:00
Parfii-bot
76dcdc5c87 feat(r2): new kei-cache crate — deterministic result cache
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>
2026-04-23 05:55:13 +08:00
Parfii-bot
0b948ca07c feat(r1): new kei-pipe crate — atom DAG runtime
The critical missing substrate composition layer.

kei-pipe run <dag.toml> — reads DAG spec, topo-sorts atoms, executes
sequentially, pipes JSON between steps via $step.path.to.field
resolver. 6 Constructor-Pattern cubes: dag/resolve/exec/report/lib/main.

5/5 smoke tests: happy path + cycle detection + unknown dep +
nested path resolver + unreadable file.

Resolver envelope matches kei-runtime Output — atoms round-trip
identically through either runtime.

Workspace Cargo.toml: +kei-pipe member.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 05:55:13 +08:00
Parfii-bot
5f72f6a0a8 feat(m5): migrate kei-sage to kei-entity-store engine (largest migration)
28/28 tests preserved — most complex migration.

Primary entity = unit via engine; edges + FTS stay sage-local in
custom_migrations (engine TextPair minimal: lacks id/weight/created_at/
UNIQUE constraint that sage's graph operations require).

pagerank.rs + bfs.rs preserved as sage-local (graph semantics tied to
typed edge_type + weight, not generic).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 05:55:13 +08:00
Parfii-bot
59c30603f3 feat(m4): migrate kei-crossdomain to kei-entity-store engine (edges-only)
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>
2026-04-23 05:55:13 +08:00
Parfii-bot
0b645db646 feat(m3): migrate kei-social-store to kei-entity-store engine
5/5 tests preserved. Primary entity = person; orgs + interactions +
relationship_graph stay custom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 05:55:13 +08:00
Parfii-bot
6ad8fd81ed feat(m2): migrate kei-content-store to kei-entity-store engine
4/4 tests preserved. Primary entity = content_units; prompts +
campaigns + campaign_assets in custom_migrations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 05:55:13 +08:00
Parfii-bot
519600d1bf feat(m1-dogfood): migrate kei-chat-store to kei-entity-store engine
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>
2026-04-23 05:55:12 +08:00
Parfii-bot
e075ae8df1 fix(compose): render resolved scope block — agent sees concrete paths, not generic text
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>
2026-04-23 05:44:15 +08:00
Parfii-bot
68e37ecf68 fix(a1-integration): add EdgeKeyKind + archived_field defaults to bug_fixes_smoke fixture
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>
2026-04-23 05:33:21 +08:00
Parfii-bot
70867a7fd0 Merge fix/b5-textpair — EdgeKeyKind + archive verb 2026-04-23 05:31:03 +08:00
Parfii-bot
a5c5a68627 Merge fix/a1-entity-store — C1+C2+FTS5+M3+TEXT-cap+M2 2026-04-23 05:31:03 +08:00
Parfii-bot
bf5c4d3dce Merge fix/b2-dna-role — DNA + role + traversal 2026-04-23 05:31:03 +08:00
Parfii-bot
ba75a075e3 Merge fix/b1-pattern-gate — hardening 2026-04-23 05:31:03 +08:00
Parfii-bot
a78f1aaa5f Merge fix/b3-ledger — cycle + txn + length cap 2026-04-23 05:31:03 +08:00
Parfii-bot
da147c9a1b Merge fix/b4-provision — exec redaction 2026-04-23 05:31:03 +08:00
Parfii-bot
2d2c9881de feat(entity-store/b5): EdgeKeyKind::TextPair + archive verb — unblock kei-sage + kei-chat-store migration
schema.rs: EdgeKeyKind enum (IntegerPair default, TextPair) + archived_field:
Option<&'static str> on EntitySchema. Backward-compat via Default impl.

engine.rs: ddl_edge_table_for() dispatches integer vs text edge DDL.
Existing ddl_edge_table() untouched; new ddl_edge_table_text() adds
src_path/dst_path TEXT columns.

verbs/link.rs (rewrite): dispatches on edge_key_kind. Input JSON
{from:int, to:int} for IntegerPair OR {from:str, to:str} for TextPair.

verbs/rank.rs (rewrite): generic pagerank<K: Hash + Eq + Clone> over
node key type. Same algorithm, polymorphic in key.

verbs/archive.rs (new, 64 LOC): soft-delete via UPDATE <tbl> SET
<archived_field>=1 + optional <archived_field>_at=now(). Schema
without archived_field declared returns VerbError.

Tests: 20/20 kei-entity-store (was 10, +10: 5 text_pair + 5 archive).
kei-task 9/9 preserved (IntegerPair still default, backward-compat
verified).

Enables: kei-sage migration (TextPair edges) + kei-chat-store
migration (archive).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 05:30:33 +08:00
Parfii-bot
4546239e8b fix(ledger/b3): S2 tree cycle DoS + migration txn + length cap
S2: tree() had no visited set; cyclic parent_branch rows → infinite
loop. Added HashSet visited + MAX_TREE_DEPTH=1024 breaker. Returns
LedgerError::MaxDepthExceeded instead of OOM.

M2 migration txn: apply_one() wraps DDL + user_version bump in
BEGIN IMMEDIATE / COMMIT / ROLLBACK. Partial failure can no longer
leave user_version at N-1 with N's schema applied.

L1 length cap: branch + parent_branch strings capped to 256 chars
via 3-layer defence: clap value_parser!(parse_branch), client-side
check_branch_lens, schema v3 BEFORE INSERT/UPDATE triggers.

New src/error.rs (46 LOC) — LedgerError + MAX_TREE_DEPTH. SELECT_COLS
const DRY'd 4 duplicated column lists (list, by_id, children_of).

Schema v3 uses triggers (not table CHECK — SQLite can't retrofit
CHECK on existing tables without rebuild).

Tests: 13/13 (was 10, +3 audit). All 3 fixes exercised.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 05:30:33 +08:00
Parfii-bot
fdb6939015 fix(provision/b4): exec.rs redacts args + truncates stderr
MEDIUM info-disclosure: run_json_strict + run_void formatted error
messages with full argv + full stderr. Today argv has no secrets
(env-only per RULE 0.8) but:
- Future refactor could pass --api-key inline → secret in logs
- vultr-cli stderr echoes request URLs with query params → enumeration

Fix:
- redact_args() → "bin_name <N args>" (argv hidden)
- truncate_stderr() → first 200 chars + "... (truncated)", UTF-8 safe
- Docstring: // DO NOT pass secrets as CLI args — env-only per RULE 0.8

Tests: 11/11 (was 8, +3: redaction asserts no argv in error, stderr
truncation + Cyrillic UTF-8 safety)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 05:30:33 +08:00
Parfii-bot
02e4b25af2 fix(entity-store/a1): C1 type coercion + C2 FTS transaction + FTS5 injection + M3 SQL escape + TEXT cap + M2 migration version
6 critical/high bugs from post-convergence audit fixed in one pass.

New src/verbs/validate.rs (95 LOC) — shared typed validator:
- coerce() returns VerbError::InvalidType{field,expected,got} on wrong-kind JSON
- Preserves "missing key → default" semantics (additive, not breaking)
- MAX_TEXT_BYTES 64 KiB cap enforced on all text fields (M2 audit)

verbs/create.rs + update.rs (C1 + C2):
- Typed validation replaces silent 0/""/empty coercion
- insert_tx() + update_tx() wrap INSERT + FTS DELETE+INSERT in
  conn.unchecked_transaction(); mid-flight failure rolls back both
  main row + FTS sidecar together (no orphan FTS rows)

verbs/search.rs — fts5_quote() defends FTS5 syntax injection:
- User query wrapped in "..." phrase quotes, internal " doubled
- Column-prefix `title:secret`, NEAR(), wildcards become literal
- Integration + unit tests

engine.rs:
- ddl_column() escapes ' → '' on TextDefault values (M3)
- apply_user_version() stamps CURRENT_USER_VERSION=1 via PRAGMA;
  idempotent (won't downgrade). Opens migration path (M2).

Tests: 22/22 (was 10, +12). kei-task 9/9 preserved — no regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 05:27:59 +08:00
Parfii-bot
4983d38636 fix(agent-runtime/b2): DNA entropy 32-bit + role path traversal + recursion cap + warnings
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>
2026-04-23 05:27:59 +08:00
Parfii-bot
f9bfcc23b7 fix(agent-runtime/b1): PatternGate hardening — RwLock, Result, UTF-8, explicit deny
H1 lock contention: Mutex<HashMap> → RwLock<HashMap>. Hot path cheap
read-lock, write-lock only on first compile per pattern.

H2 panic removed: compile_checked() returns Result<Regex, regex::Error>;
all call sites propagate as GateDecision::Deny{reason:"misconfigured
regex..."}. No .expect()/.unwrap() on compile anywhere.

H3 dead branch: AllowIfMatch + TaskWhitelist/TaskDenylist combo was
silently NotApplicable (allow-everything). Now explicit Deny with
"misconfigured" + "AllowIfMatch" in reason. Fail-closed.

S4 UTF-8 panic: &s[..60] byte-slice → s.chars().take(60). Cyrillic/
emoji safe. 60-char budget by code point.

L2 render chain: String::replace chain → single-pass render_template()
that scans {token} markers once. Substituted text cannot bleed into
later tokens.

Tests: 70/70 (was 66, +4). pattern_gate_smoke 10 → 14.
Follow-up (out of file-boundary scope): per-pattern Lazy<Regex> variant
would eliminate the RwLock entirely but requires editing sibling gate
consts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 05:27:59 +08:00
Parfii-bot
9c8c7c3598 fix(prepare): auto-generate agent-id when absent + dogfood ergonomics
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>
2026-04-23 05:23:55 +08:00
Parfii-bot
324ad5d53e fix(p1-integration): validate.rs allows _schemas/fragments $ref + drop additionalProperties on fragment-composed atom schemas
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>
2026-04-23 04:53:26 +08:00
Parfii-bot
30a07e22ca Merge feat/convergence-p3-role-dna — role expression + DNA identity 2026-04-23 04:47:30 +08:00