KeiSeiKit-1.0/install/lib-profile-outcome-only.sh
Parfii-bot 88de01cae0 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 1207cf5) ==

- _primitives/_rust/Cargo.toml — workspace tokio gains `io-std` feature
  (needed by kei-mcp/src/main.rs which calls tokio::io::{stdin,stdout})
- _primitives/_rust/kei-mcp/Cargo.toml — dev-deps tokio gains `test-util`
  feature (needed by tests/tools_call_timeout.rs for tokio::time::advance
  and Builder::start_paused). Both verified locally:
  `cargo check -p kei-mcp` ✓
  `cargo test --no-run -p kei-mcp` ✓ (3 test binaries link)
  [REAL: ran 2026-05-03 in this session]

== HIGH-tier audit fixes (consensus across waves) ==

1. SQLi escape in agent-outcome-backfill.sh:110
   - 4 of 12 agents flagged: TOOL_USE_ID was JSON-derived and
     interpolated raw into SQL. Allowlist on $SHIPPED protected today
     but a future case-statement removal opened the surface.
   - Fix: tiny `_sql_esc` helper that doubles single-quotes (SQL-99
     standard escape), applied to SHIPPED + TOOL_USE_ID. STUBS already
     integer-validated.

2. PRAGMA user_version=9 in install/sql/outcome-only-schema.sql
   - W1 outcome-only critic flagged: the SQL fallback installed a
     v9-equivalent flat schema but left user_version=0. A LATER
     `kei-ledger init` (e.g. when user upgrades to full kit) would
     re-run migrations v1-v9 and ALTER TABLE ADD COLUMN duplicate-error
     mid-migration → broken DB.
   - Fix: set PRAGMA user_version=9 before COMMIT so the binary's
     migration runner sees current ≥ target and short-circuits.

3. backup_file mv→cp + uninstall macOS-portable awk
   - W1+W2 outcome-only flagged: lib-backup.sh uses `mv` which DELETES
     the target before _jq_merge_hooks runs; `|| true` swallowed the
     subsequent jq read-error → silent settings.json loss.
   - Fix in lib-profile-outcome-only.sh: `cp -p` aside, drop `|| true`,
     return 1 on merge failure (trap restores).
   - PROFILE-OUTCOME-ONLY.md uninstall used GNU sed `,+1` extension
     which BSD sed (macOS) does not support — uninstall silently
     no-op'd on macOS, leaving orphan CLAUDE.md text.
   - Fix: replace with portable `awk` recipe; also added `rm -f` for
     the agent-toolstats.jsonl sidecar (privacy completeness).

== Doc honesty pass (RULE 0.18 numerics + RULE 0.4 citations) ==

4. README.md count drift — verified all values against filesystem:
   * 102→105 Rust crates (Cargo.toml workspace `members` count)
   * 67→68 skills (`ls skills/ | wc -l`)
   * 35→38 hooks (`grep -c '"command":' settings-snippet.json`)
   * 37→38 agent manifests (`ls _manifests/*.toml | wc -l`)
   * 82→85 substrate blocks (`find _blocks/ -name '*.md' | wc -l`)
   * 18 capability atoms VERIFIED via `find _capabilities/ -name '*.md'`
     (encyclopedia §3 row count of 17 is in a separate file and is a
     known internal display issue, not changed in this commit)
   * 495→565 active DNAs (per docs/DNA-INDEX.md header 2026-05-03)
   Each value now carries a `[REAL: <command>]` style trailer per
   RULE 0.18.

5. README.md DNA "80-char identity" → "≥33-char variable-length"
   - W1+W2 reviewer-pass flagged FALSE: docs/DNA-FORMAT.md SSoT says
     minimum 33 chars; 80 was nowhere in code or spec
   - Fix in README.md:36 + docs/PHILOSOPHY.md:39 + docs/DNA-INDEX.md:1352

6. README.md "Eleven install profiles (... Cursor / Continue / Zed /
   Aider / Docker / Nix)" — Cursor/Continue/Zed/Aider/Docker/Nix were
   never install profiles, they were bridge targets
   - Fix: list 12 actual profiles from _primitives/MANIFEST.toml,
     mention bridges as separate concept

7. .claude-plugin/plugin.json license MIT → Apache-2.0
   - W2-Sonnet reviewer flagged: LICENSE file is Apache-2.0 (since
     2026-04-30 per NOTICE), but plugin.json still declared MIT —
     plugin marketplace would show wrong license

8. docs/ARCHITECTURE.md:318 placeholder URL `https://example.invalid/...`
   - W2-Sonnet reviewer flagged: dead link in published docs
   - Fix: remove the bad href, describe ssl-rule-file as per-user
     install outside the public repo

9. skills/sleep-on-it/SKILL.md Wagner et al. 2004 citation
   - W1+W2 reviewer flagged RULE 0.4 violation: citation without
     verification marker
   - Fix: added [VERIFIED: doi:10.1038/nature02223] + clarification
     that the original paper showed slow-wave-sleep (not strictly REM)
     insight gain — our metaphor is a loose mapping

10. encyclopedia/substrate-overview.md §5 fabricated TS deps
    - W1-Opus doc-consistency flagged RULE 0.4.b violation: 5 of 6
      package rows had INVENTED dependency strings
      (`recall-ai-sdk ^1.0.0`, `nodemailer-mock ^2.0.0`,
       `telegram-typings ^4.10.0`, etc — none exist in the actual
      package.json files)
    - Fix: regenerated table from real `package.json` reads via
      `node -p "require(...).dependencies"` for each of the 6 packages
    - Fix: also corrected version drift (5 packages all 0.14.0 now)

Verification:
- Outcome-only end-to-end install against fake $HOME succeeds:
  hooks installed, ledger schema at user_version=9, settings.json
  created cleanly, all 5 documented files present
  [REAL: ran 2026-05-03 in this session]
- `cargo check -p kei-mcp` + `cargo test --no-run -p kei-mcp` clean

Audit findings NOT yet addressed (deferred to next batch):
- README:65 git clone github URL — repo is private; reviewer flagged
  external strangers cannot clone; will resolve via Quick Start rewrite
- npm.pkg.github.com / @keisei84 leftover sweep — both waves verified
  ZERO refs, no fix needed
- safeEqual timing leak in TS server (W2 sec MEDIUM)
- HTTP server bind 0.0.0.0 (W2 sec MEDIUM)
- Unbounded request body (W2 ci MEDIUM)
- --dry-run silent ignored on non-outcome profiles (W1+W2 MEDIUM)
- Doc-link missing for MEMORY/DNA/LEDGER format specs from README

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:09:59 +08:00

154 lines
6.3 KiB
Bash

# shellcheck shell=bash
# lib-profile-outcome-only.sh — the "outcome-only" minimal install profile.
#
# Installs 5 files: 2 hooks + ledger.sqlite + 1 CLAUDE.md line + (deferred)
# kei-model-router binary. Skips cortex daemon, Forgejo, launchd plists,
# no-github-push hook, 100+ Rust crates, skills, agents.
#
# Usage:
# ./install.sh --profile=outcome-only # install
# ./install.sh --profile=outcome-only --dry-run # print plan, exit 0
#
# Requires say/warn/err (lib-log), backup_file (lib-backup),
# _jq_merge_hooks (lib-hooks). Reads $KIT_DIR / $HOME_DIR /
# $HOOKS_DIR / $AGENTS_DIR. Sets $OUTCOME_DRY_RUN_FILES.
# See docs/PROFILE-OUTCOME-ONLY.md for the user-facing pitch.
OUTCOME_DRY_RUN_FILES=""
# Append a path to the dry-run list, one per line.
_outcome_dr_add() { OUTCOME_DRY_RUN_FILES="${OUTCOME_DRY_RUN_FILES}${1}"$'\n'; }
# Single-line CLAUDE.md instruction. No behavioural rewrite — one rule.
_outcome_marker_line() {
printf 'At the end of every subagent invocation, emit a STATUS-TRUTH MARKER block (RULE 0.16) so the outcome-only ledger can backfill the agents row.\n'
}
# Minimal settings-snippet — only this profile's two hooks.
_outcome_write_snippet() {
local out="$1"
cat > "$out" <<'JSON'
{
"_comment": "outcome-only profile — registers only agent-outcome-backfill + error-spike-detector",
"hooks": {
"PostToolUse": [
{ "matcher": "Agent",
"hooks": [{ "type": "command",
"command": "~/.claude/hooks/agent-outcome-backfill.sh",
"statusMessage": "outcome-backfill (RULE 0.16)..." }] },
{ "matcher": "*",
"hooks": [{ "type": "command",
"command": "~/.claude/hooks/error-spike-detector.sh",
"statusMessage": "error-spike rolling window (RULE 0.14)..." }] }
]
}
}
JSON
}
# Initialise ledger.sqlite. Tries (a) kei-ledger CLI on PATH, (b) prebuilt
# kei-ledger binary, (c) sqlite3 with embedded DDL. Warns if all three miss
# (hooks exit cleanly on missing DB so the profile is still usable).
_outcome_install_ledger() {
local db="$AGENTS_DIR/ledger.sqlite"
mkdir -p "$AGENTS_DIR"
local kl="$KIT_DIR/_primitives/_rust/kei-ledger/target/release/kei-ledger"
if command -v kei-ledger >/dev/null 2>&1; then
kei-ledger --db "$db" init >/dev/null 2>&1 \
&& say "ledger initialised via kei-ledger CLI" && return 0
fi
if [ -x "$kl" ]; then
"$kl" --db "$db" init >/dev/null 2>&1 \
&& say "ledger initialised via prebuilt kei-ledger binary" && return 0
fi
if command -v sqlite3 >/dev/null 2>&1; then
sqlite3 "$db" < "$KIT_DIR/install/sql/outcome-only-schema.sql" \
&& say "ledger initialised via sqlite3 ($db)" && return 0
fi
warn "no kei-ledger or sqlite3 found; ledger NOT initialised."
warn " install one of: brew install sqlite, or rerun after a full kit install."
return 0
}
# Append STATUS-TRUTH MARKER instruction to CLAUDE.md (idempotent: skip
# if marker phrase is already present).
_outcome_install_claude_md() {
local cm="$HOME_DIR/.claude/CLAUDE.md"
mkdir -p "$HOME_DIR/.claude"
if [ -f "$cm" ] && grep -q "STATUS-TRUTH MARKER" "$cm" 2>/dev/null; then
say "CLAUDE.md already contains STATUS-TRUTH MARKER instruction; skipping"
return 0
fi
backup_file "$cm" 2>/dev/null || true
{
[ -f "$cm" ] && printf '\n'
printf '<!-- outcome-only profile (KeiSeiKit) -->\n'
_outcome_marker_line
} >> "$cm"
say "appended STATUS-TRUTH MARKER instruction to $cm"
}
# Build kei-model-router if cargo on PATH; otherwise deferred.
_outcome_install_router_if_cargo() {
command -v cargo >/dev/null 2>&1 || {
warn "cargo not found; skipping kei-model-router build (deferred)"
return 0
}
local crate_dir="$KIT_DIR/_primitives/_rust/kei-model-router"
[ -d "$crate_dir" ] || { warn "kei-model-router crate dir missing; skipped"; return 0; }
say "building kei-model-router (release)..."
( cd "$crate_dir" && cargo build --release --quiet 2>&1 ) \
|| warn "cargo build failed; router not installed (rerun manually if desired)"
}
# Public entry — called from install.sh when --profile=outcome-only.
install_profile_outcome_only() {
local hook_src hook_dst snippet
if [ "${OUTCOME_DRY_RUN:-0}" = "1" ]; then
_outcome_dr_add "$HOOKS_DIR/agent-outcome-backfill.sh"
_outcome_dr_add "$HOOKS_DIR/error-spike-detector.sh"
_outcome_dr_add "$AGENTS_DIR/ledger.sqlite"
_outcome_dr_add "$HOME_DIR/.claude/CLAUDE.md (append 1 line)"
_outcome_dr_add "$HOME_DIR/.claude/settings.json (jq-merge 2 hooks)"
say "DRY RUN — files that WOULD be touched in \$HOME:"
printf '%s' "$OUTCOME_DRY_RUN_FILES" | sed '/^$/d' | nl -ba
return 0
fi
mkdir -p "$HOOKS_DIR" "$AGENTS_DIR"
for hook_src in \
"$KIT_DIR/hooks/agent-outcome-backfill.sh" \
"$KIT_DIR/hooks/error-spike-detector.sh" ; do
[ -f "$hook_src" ] || { err "missing source hook: $hook_src"; return 2; }
hook_dst="$HOOKS_DIR/$(basename "$hook_src")"
backup_file "$hook_dst" 2>/dev/null || true
cp -f "$hook_src" "$hook_dst" && chmod +x "$hook_dst"
say "installed hook -> $hook_dst"
done
_outcome_install_ledger
_outcome_install_claude_md
_outcome_install_router_if_cargo
snippet="$(mktemp -t outcome-snippet.XXXXXX)"
_outcome_write_snippet "$snippet"
if [ ! -f "$HOME_DIR/.claude/settings.json" ]; then
cp -f "$snippet" "$HOME_DIR/.claude/settings.json" \
&& say "created settings.json from outcome-only snippet"
else
# Audit fix 2026-05-03: backup_file MOVES the target. We need the file
# still in place for jq to read during merge — copy aside instead so
# _jq_merge_hooks reads the original. The trap-pair entry registered
# by backup_file restores on rollback if the merge fails.
cp -p "$HOME_DIR/.claude/settings.json" \
"$HOME_DIR/.claude/settings.json.bak-$(date +%s)"
if ! _jq_merge_hooks "$snippet" "$HOME_DIR/.claude/settings.json"; then
err "settings.json merge failed; the .bak file remains as recovery"
rm -f "$snippet"
return 1
fi
fi
rm -f "$snippet"
say "outcome-only profile installed."
say " hooks: agent-outcome-backfill.sh, error-spike-detector.sh"
say " ledger: $AGENTS_DIR/ledger.sqlite"
say " CLAUDE.md updated (1 line appended)"
say " router: built (if cargo present), else deferred — see docs/PROFILE-OUTCOME-ONLY.md"
}