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
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
Parfii-bot
eba5c8ea10
Merge feat/convergence-p2-pattern-command — PatternGate + CommandVerify traits
2026-04-23 04:47:30 +08:00
Parfii-bot
84319efcb6
feat(convergence/p3): Role expression (extends/relaxes) + DNA identity
...
Layer E + G. Role TOML gains extends/relaxes for parent-role
composition; agent spawn gets self-describing DNA identity alongside
UUID.
Role expression:
- _roles/*.toml gain optional `extends = "<parent>"` + `relaxes = [...]`
- compose.rs + verify.rs delegate to new role::resolve_role() with
recursive extends-chain resolution + cycle detection
- explorer.toml: 28→18 LOC (extends read-only)
- edit-shared.toml: 31→23 LOC (extends edit-local, relaxes
scope::files-whitelist for task-param override)
DNA identity:
- new dna.rs (159 LOC) — compose/render/parse round-trip
- AgentInvocation carries dna field (prepare.rs)
- Format: <role>::<caps-bitmap>::<sha4-scope>::<sha4-body>-<hex4-nonce>
- ≤ 80 chars total, greppable, parseable
- 11 capability codes in CAP_CODES table: NG, FW, FD, CP, CG, TG, ND,
RF, SG, DT, BA
kei-ledger schema v2:
- ADD COLUMN dna TEXT + prefix index
- `kei-ledger fork --dna <string>` optional flag
- AgentRow.dna: Option<String>
- Backward compat: schema migration detects + applies on open
Docs: AGENT-SUBSTRATE-SCHEMA.md Layer E + Layer G sections + CAP_CODES table.
New deps: sha2 (workspace), rand 0.8.
Tests: kei-agent-runtime 50 (was 41, +9: 4 role + 5 DNA), kei-ledger
10 (was 9, +1 DNA roundtrip).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 04:46:48 +08:00
Parfii-bot
360a20a942
feat(convergence/p2): PatternGate + CommandVerify unified traits
...
Layer C + D. 6 gate modules + 3 verify modules → 1 generic struct per
family + const declarations. Data-driven, not module-per-capability.
New:
- src/gates/pattern_gate.rs (192 LOC) — PatternGate struct with
GateMode::{DenyIfMatch, AllowIfMatch}, static regex compilation via
once_cell, bypass env support, Capability trait impl
- src/verifies/command_verify.rs (142 LOC) — CommandVerify struct with
WorkDir::{WorkspaceRoot, CrateDir, MainRepoSub}, subprocess exec +
exit-code check, extra_env support, Capability trait impl
Converted (const declarations, ~15-27 LOC each):
- gates/policy_no_git_ops.rs (49→22)
- gates/safety_no_dep_bump.rs (35→19)
- gates/scope_files_whitelist.rs (37→16)
- gates/scope_files_denylist.rs (35→16)
- gates/tools_bash_allowlist.rs (58→27)
- verifies/quality_cargo_check_green.rs (41→18)
- verifies/quality_tests_green.rs (75→80, folded common shape)
- verifies/safety_no_dep_bump.rs (39→47)
Kept separate (different shape, not PatternGate/CommandVerify):
- gates/tools_deny_tools.rs (tool-name match, not pattern)
- verifies/quality_constructor_pattern.rs (LOC walker)
- verifies/output/report_format.rs + severity_grade.rs (text parsers)
- verifies/scope_files_{whitelist,denylist}.rs (diff-walkers)
Registry.rs preserves alias table + deprecation warnings + all_names().
Tests: 57/57 green (was 41, +16: 10 pattern_gate_smoke + 6 command_verify_smoke).
LOC net: 5 gates 214→100 (-53%), shared PatternGate+CommandVerify 334
LOC absorb duplication. Amortization breaks even around 3-4 new gates
added later.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 04:46:47 +08:00
Parfii-bot
793b91bc43
feat(convergence/p1): kei-entity-store engine + schema fragments + kei-task pilot
...
Layer A + B of convergence wave. Extract common SQLite-CRUD + graph
logic into kei-entity-store engine; introduce JSON Schema fragments;
pilot-migrate kei-task to verify parity.
New crate _primitives/_rust/kei-entity-store/ (1151 LOC):
- src/schema.rs — EntitySchema + FieldDef + enabled_verbs + fts_columns
+ edge_table + custom_migrations
- src/engine.rs — Store::open with WAL pragma + migration runner
- src/verbs/ — 8 data-driven verb modules (create/get/list/search/
update/delete/link/rank) uniform JSON-in/JSON-out signature
- src/error.rs — typed VerbError enum
- tests/verb_smoke.rs — 10/10 green
New _schemas/fragments/ (83 LOC JSON):
- entity-base.json, titled.json, titled-content.json, edge.json
kei-task pilot migration:
- TASK_SCHEMA: EntitySchema static (67 LOC, was 58)
- store.rs becomes thin shim over engine::Store
- atoms/create.rs + atoms/search.rs delegate to engine verbs
- atoms/schemas/*.json use $ref to _schemas/fragments/ (DRY)
- Task-specific secondary tables (milestones, task_deps) stay via
schema.custom_migrations; cycle-detection in deps.rs stays
hand-rolled (domain logic, not generic CRUD)
- 9/9 tests green — full behavioural parity
Convergence delta:
- kei-task touched files: 342 → 389 LOC (+47 for JSON marshalling
boundary; net wash on pilot)
- BUT each remaining 5 sibling crate can shrink ~400-500 LOC on migration
- Expected total reduction when all 6 migrated: ~2500 LOC across the cluster
Follow-ups declared:
- Migrate kei-chat-store, kei-content-store, kei-social-store to engine
- Migrate kei-sage (needs string-id edge variant; currently generic
link/rank assume int ids)
- Migrate kei-crossdomain
- Expose list/delete atoms in kei-task (engine supports, atoms not yet)
- Fold kei-curator as engine::hygiene module (per P4 audit)
- Fold kei-search-core entities, keep workflow as thin kei-search-pipeline
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 04:46:47 +08:00
Parfii-bot
4658297232
Merge feat/convergence-u3-provision — unify hetzner+vultr into Rust crate
2026-04-23 03:43:51 +08:00
Parfii-bot
64ffe39e01
feat(convergence/u3): kei-provision Rust crate — unify hetzner+vultr provisioners
...
Pre-unlock wave U3 (highest-ROI). Task 7 from CONVERGENCE-PLAN —
consolidate 2 provision-*.sh scripts into Rust via Backend trait.
Old shells (provision-hetzner.sh, provision-vultr.sh) had identical
6-subcommand surface (create|status|destroy|list), log/die/check_deps
helpers, idempotency contract. Sole delta: hcloud vs vultr-cli. RULE 0.2
says Rust-first when >50 LOC + growth expected.
New crate _primitives/_rust/kei-provision/:
- src/backend.rs (58 LOC) — Backend trait: create/status/destroy/list;
CreateOpts and ServerInfo structs
- src/backends/hetzner.rs (143 LOC) — shells to `hcloud server ...`
--output=json, parses JSON response, honors HCLOUD_TOKEN env (RULE 0.8)
- src/backends/vultr.rs (189 LOC) — same pattern, `vultr-cli instance`,
honors VULTR_API_KEY env
- src/exec.rs (100 LOC) — Command runner + PATH-aware env preservation
- src/b64.rs (49 LOC) — minimal user-data base64 encoder; zero
transitive deps
- src/main.rs (141 LOC) — clap CLI `kei-provision <backend> <cmd>`
- tests/backend_smoke.rs (184 LOC) — tempdir PATH-inject fake hcloud +
fake vultr-cli, no real cloud. Mutex-serialized (Rust test parallelism).
Tests: 11/11 (3 b64 unit + 8 backend_smoke integration). Coverage:
hetzner status present/absent/list, vultr status found/absent/destroy
idempotent, unknown-backend error, CreateOpts default.
Old shells kept with superseded-v0.17 header — install.sh still copies
them, legacy scripts still work. New users get kei-provision binary.
harden-base.sh untouched (different lifecycle — runs on target VPS).
Backend trait factored to accept aws/doctl/linode follow-ups without
re-architecture.
Workspace Cargo.toml: +kei-provision member (1 line).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 03:43:40 +08:00
Parfii-bot
e4b64418fc
feat(convergence/u2): capability renames + back-compat aliases
...
Pre-unlock wave U2. Task 3 from CONVERGENCE-PLAN — rename misleading
capability names, keep old names as deprecated aliases.
Renames:
- tools::read-only → tools::deny-tools (mechanism is tool-name denial,
not "read-only" metaphor)
- tools::cargo-only-bash → tools::bash-allowlist (mechanism is Bash
pattern allow-list; cargo-only is one config value)
Back-compat via registry.resolve_alias():
- Old dir _capabilities/tools/{read-only,cargo-only-bash}/ retained with
capability.toml-only stub: `alias = "<new-name>"` + `deprecated` field
- registry.rs loads alias stubs, redirects lookup before dispatch
- warn_deprecated_once() emits single-shot stderr per alias per process
via OnceLock<Mutex<HashSet>>
- Zero breaking change to existing manifests / task.toml referencing
old names
Rust impl files renamed in place:
- gates/tools_read_only.rs → gates/tools_deny_tools.rs (struct
DenyTools)
- gates/tools_cargo_only_bash.rs → gates/tools_bash_allowlist.rs
(struct BashAllowlist)
- gates/mod.rs + registry.rs + gate_smoke.rs updated
Roles updated (3): read-only.toml, explorer.toml, edit-local.toml —
reference new names directly.
Tests: kei-agent-runtime 41/41 (was 40, +1 deprecated_aliases_resolve
_to_new_names), _assembler 40/40 unchanged (substrate role expansion
follows new paths).
Docs updated: AGENT-ROLES.md, AGENT-SUBSTRATE-SCHEMA.md, 4 _manifests
referencing the old names (comment-only annotations).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 03:43:40 +08:00
Parfii-bot
d72ae51f16
feat(agent-substrate/wrapper): kei-agent-runtime prepare — orchestrator ergonomics
...
Single-command "prepare spawn" that emits everything orchestrator needs
to invoke the Agent tool: composed prompt, subagent_type (from role's
new claude-subagent-type field), isolation mode, verify command,
ledger row.
Before this: orchestrator ran compose + read prompt + manually
constructed Agent tool call + manually built verify command. 4 steps.
After: `kei-agent-runtime prepare <task.toml> --format=human` outputs
a single copy-paste-ready block. Orchestrator pastes into Agent tool
and records the verify command for return.
Files:
- src/prepare.rs (170 LOC) — prepare() returns AgentInvocation struct
(agent_id, prompt, subagent_type, isolation, description,
verify_command, ledger_row)
- src/main.rs (+39 LOC) — Prepare subcommand with --format=human|json|toml
- src/lib.rs (+2 LOC — pub mod prepare)
- _roles/*.toml (5 files) — new optional claude-subagent-type field:
- edit-local / edit-shared → "code-implementer"
- read-only → "critic" (default; "architect" override possible)
- explorer → "Explore"
- git-ops → "NOT-SPAWNABLE" (refused by prepare with RULE 0.13)
- tests/prepare_smoke.rs (3 tests) — happy path, unknown role, non-spawnable refusal
- docs/AGENT-SUBSTRATE-SCHEMA.md (+ ## Orchestrator ergonomics section)
Tests: 40/40 (was 37, +3 prepare_smoke). Same path exercised in tempfile
fixtures that the real CLI would hit end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 03:25:14 +08:00
Parfii-bot
b82e3b039e
feat(agent-substrate/phase-3): kei-agent-runtime + kei-capability binaries
...
Two new crates implementing the substrate runtime per locked §Runtime
execution contract + §Capability trait contract (Rust) + §Verify
execution worktree→simulated-merge.
kei-agent-runtime — library + CLI binary:
- src/capability.rs — Capability trait (name/check/verify) + GateContext
+ GateDecision + VerifyContext + VerifyResult + RunMode + TaskSpec
- src/registry.rs — &str → &'static dyn Capability dispatch for 14 impls
- src/gates/ — 6 PreToolUse modules (policy::no-git-ops,
scope::files-{whitelist,denylist}, safety::no-dep-bump,
tools::read-only, tools::cargo-only-bash)
- src/verifies/ — 8 on-return modules (quality::constructor-pattern,
quality::cargo-check-green, quality::tests-green, safety::no-dep-bump,
scope::files-{whitelist,denylist}, output::{report-format,severity-grade})
- src/compose.rs — task.toml + role + capabilities → prompt.md
- src/spawn.rs — ledger fork + prompt write (actual Agent invocation
remains orchestrator's tool call)
- src/verify.rs — runs all capability verifies per role; collects
VerifyReport {passed, failed}
- src/simulated_merge.rs — git worktree add test-merge/<id> + apply diff
+ run verify; cleanup on Drop
- src/main.rs — clap CLI: compose | spawn | verify | run
kei-capability — thin CLI adapter crate:
- Depends on kei-agent-runtime path dep
- Subcommand `check <cap-name>` (PreToolUse gate; stdin JSON, exit 0|2)
- Subcommand `verify <cap-name>` (on-return; env-driven, exit 0 or fail)
- Pattern: shell hook = 3-line `exec kei-capability check "$CAP_NAME"`
Workspace Cargo.toml: both crates registered as members (under agent
substrate v1 marker).
cargo check --workspace: PASS
cargo test -p kei-agent-runtime: 37/37 green
- 6 capability_trait_smoke (registry lookups, unknown name → None)
- 3 compose_smoke (fixture role + caps → composed prompt)
- 12 gate_smoke (each gate: happy + deny + bypass)
- 4 simulated_merge_smoke (git worktree lifecycle)
- 12 verify_smoke (each verify: pass + fail + edge cases)
cargo test -p kei-capability: 0/0 (CLI binary, tested via lib)
(Agent completion report cut off by rate-limit at 60 tool-uses; code
itself is green — verified by orchestrator post-commit.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 02:35:53 +08:00
Parfii-bot
07b47dba7e
Merge feat/stream-g-sage-rules — atoms+rules unified graph
2026-04-23 01:21:12 +08:00
Parfii-bot
7fb562cfc5
Merge feat/stream-f-forge-pure-rust — eliminate shell-out
2026-04-23 01:21:12 +08:00
Parfii-bot
15bf40196b
feat(stream-g): kei-sage rules integration — atoms + rules unified graph
...
Unify atoms and rules in kei-sage's graph. Previously [[rules/...]]
wikilinks were filtered (explicit Stream C scope-deferral). Now they
resolve to rule-node units with rule_ref edges.
kei-atom-discovery extension (non-breaking):
- WikilinkTarget enum: Atom(String) | Rule(String) | Other(String)
- classify_wikilink(inner: &str) -> WikilinkTarget — exposed via lib.rs
- parse_wikilink unchanged for backwards-compat; new callers use
classify for richer semantics
kei-sage additions:
- rule_index.rs (129 LOC) — RuleRecord + discover_rules walking flat
*.md + extract_h1 for display name + index_rules (unit_type="rule",
vault_path="rule:<slug>") + index_rule_edges (walks atom.related,
emits rule_ref edges atom → rule node)
- atom_cli.rs: cmd_rules_discover + default_rules_root
- main.rs: AtomsRulesDiscover subcommand with --rules-root flag
- tests/rules_smoke.rs: 5 tests (discovery, heading extraction,
slug fallback for headingless files, empty-dir, atom→rule edge
persistence)
Tests: 12/12 kei-atom-discovery (+3 classify_wikilink),
28/28 kei-sage (+5 rules_smoke + unit tests now counted).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 01:21:00 +08:00
Parfii-bot
e84e9fc1fe
feat(stream-f): kei-forge pure-Rust templating — eliminate shell-out
...
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>
2026-04-23 01:21:00 +08:00
Parfii-bot
8626e23c22
feat(stream-e): invoke wire — kei-runtime subprocess → real atoms
...
Replace NotImplemented stub with real atom execution per schema
§Runtime invocation contract.
Convention: JSON-in/JSON-out over subprocess. Every refactored crate
exposes `<crate> run-atom <verb>` that reads JSON from stdin (or
--input), dispatches to atoms::<verb>::run, emits Output JSON on
stdout, exits per atom-error class.
Runtime side (kei-runtime):
- InvokeError: +AtomFailed{atom,code,stderr} +SubprocessError
+OutputParse +BinaryNotFound{crate_name}. NotImplemented kept as
legacy escape for atoms opting out of run-atom protocol.
- Output: now {atom: String, result: Value} — carries atom's actual
return value.
- invoke_exit_code: AtomFailed passes through child exit (0..=255),
Subprocess/OutputParse → 1, BinaryNotFound → 127, NotImplemented → 64.
- Binary resolution: KEI_RUNTIME_BIN_DIR env → PATH fallback.
kei-task side:
- New `pub mod run_atom` in lib.rs
- atoms/mod.rs: VERBS const + DispatchError enum wrapping per-atom errors
- src/run_atom.rs: read_input (stdin/@path/literal), dispatch, exit mapping
- main.rs: Cmd::RunAtom{verb, input} subcommand; collapsed three
classify_*_error helpers into single classify_dispatch. Legacy
create/search/add-dependency CLIs preserved.
Tests: 5/5 runtime (+1 invoke_real_atom integration), 9/9 kei-task
(+1 atoms::tests::verbs_list_matches_submodules).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 01:21:00 +08:00
Parfii-bot
f7e4725573
fix(substrate): amendment A-1 (input/output required all kinds) + integration test + jsonschema 0.18 relative-$id bug
...
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>
2026-04-23 00:56:27 +08:00
Parfii-bot
ca0635e0fa
Merge fix/e3-contract-compliance — exit codes + invoke Err + strict lint
...
# Conflicts:
# _primitives/_rust/kei-runtime/src/invoke.rs
2026-04-23 00:52:54 +08:00
Parfii-bot
4e9a7dddfb
Merge fix/e2-kei-forge-hardening — DNS/CSRF/injection + headers
2026-04-23 00:49:59 +08:00
Parfii-bot
1bc6fbf4e3
fix(substrate): E3 — CLI contract compliance (exit codes + invoke Err)
...
Four audit findings on CLI contract violations per locked §Runtime schema:
- crit#7: invoke returned Ok with error payload — now returns
Err(InvokeError::NotImplemented) → exit 64
- crit#5: typed errors collapsed via anyhow::anyhow!("{e}") in kei-task —
replaced with CliError { code, msg } + classify_*_error helpers;
validation errors exit 2, storage errors exit 1 (spec-compliant)
- crit#8: lint.rs wikilink parser accepted [[[foo]] — strict parse_wikilink
from kei-atom-discovery used; emits finding for malformed entries
- crit#15: draft-07 detection was substring match — is_draft07_uri exact
match against canonical URIs only
Tests: 4/4 kei-runtime (was 2; +2 invoke exit-code tests) + 8/8 kei-task
(was 7; +1 empty-title exit-2 test) = 12/12 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 00:49:49 +08:00
Parfii-bot
f7982f0415
fix(substrate): E2 — kei-forge security hardening (DNS rebind + CSRF + injection)
...
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>
2026-04-23 00:49:49 +08:00
Parfii-bot
990f5e3711
fix(substrate): E1 — kei-atom-discovery shared crate + 4 critical security fixes
...
Extracts authoritative atom discovery + frontmatter parsing into new crate
_primitives/_rust/kei-atom-discovery/. kei-sage and kei-runtime now both
consume the same implementation, eliminating Frontmatter drift.
Resolved findings:
- F-3/crit#3 : path traversal via md_dir.join() — safe_join helper rejects
absolute paths + .. components + post-canonicalise escapes (4 sites)
- crit#6/architect P0-a: Frontmatter drift — single AtomMeta struct
- SA supply-chain: serde_yaml archived — migrated to serde_yaml_ng 0.10
- crit#2: JSON Schema $ref SSRF — jsonschema 0.17→0.18 with resolve-file
feature only, custom LocalFileResolver denies non-file:// schemes
- F-4: symlink traversal — walkdir follow_links(false) explicit everywhere
- F-5: YAML billion-laughs — 64 KiB pre-parse cap
Tests: 9/9 new crate + 23/23 sage + 2/2 runtime + 7/7 kei-task = 41/41 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 00:49:49 +08:00
Parfii-bot
42fe08232e
Merge feat/stream-d-kei-runtime — invoke/list-atoms/schema-lint MVP
...
# Conflicts:
# _primitives/_rust/Cargo.lock
# _primitives/_rust/Cargo.toml
2026-04-23 00:13:16 +08:00
Parfii-bot
2361a21d15
Merge feat/stream-c-kei-sage-substrate — kei-sage walks atoms/*.md
2026-04-23 00:10:44 +08:00
Parfii-bot
ef48c9993b
Merge feat/stream-b-atoms-kei-task — 3 pilot atoms per locked schema
2026-04-23 00:10:44 +08:00
Parfii-bot
d68fddb59a
feat(stream-d): kei-runtime — discover + validate + lint (invoke stub)
...
New crate _primitives/_rust/kei-runtime/ implementing §Runtime invocation
contract from locked substrate schema.
CLI (clap-derive):
- list-atoms [--root] [--crate] [--kind] → walk + print
- invoke <atom-id> --input <json|@file> → discover + validate input (stub exec)
- schema-lint [--root] [--crate] → 6-check validator
- pipe <dag.toml> → "not yet implemented" stub
Modules (≤ 200 LOC each, largest lint.rs @ 171):
- src/discover.rs — walk_atoms walks <root>/*/atoms/*.md, parses frontmatter
- src/validate.rs — JSONSchema draft-07 via jsonschema 0.17.1
- src/invoke.rs — MVP stub: discover → parse → validate_input → boundary ack
- src/lint.rs — 6 checks: required fields, kind enum, side_effects shape,
schema path existence + draft-07 declaration, wikilink resolution
- src/main.rs — clap CLI, exit 0|1|2 per §Runtime contract
Intentional stub boundary: invoke returns structured JSON ack (exit 0),
wire-up to concrete atom impls deferred to integration pass (needs
Stream B atoms landed first).
Registered kei-runtime in workspace members.
Tests: 2/2 integration smoke (lint_smoke, discover_smoke) green.
Stream D of substrate v1 parallel build.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 00:09:58 +08:00