char-width: contract 5, 4 tests (6 assertions), 100% pass
ASCII=1, CJK/Hangul/Kana/halfwidth=2, combining marks=0, tab=8
Pure Lisp, ~25 lines, no deps. Used by word-wrap for unicode.
status bar: contract 6, timestamp right-aligned at (- w 12)
Fixes overlap where focus map and timestamp both drew at :y 2 :x 1
Extracts annotated tag message via git tag --format and passes
it as body_path to action-gh-release. Fetch-depth: 0 ensures
tag data is available in checkout.
core-skills.lisp (and other files) have eval-when blocks that
ql:quickload :fiveam during compilation. If fiveam isn't installed
first, the CI fails with MISSING-COMPONENT.
RED: embedding-backend-native does not exist. No CFFI llama binding.
GREEN (REPL progress):
- cffi:define-foreign-library libllama → loaded
- defcstruct with correct sizes (verified via C sizeof program):
llama-mparams (72 bytes), llama-cparams (136 bytes), llama-batch (56)
- Field offsets verified via C offsetof program
- llama_backend_init discovered as required prerequisite
- llama-model-default-params correctly fills 72-byte struct (verified)
- llama-embedding CLI verified: 768-dim vectors, 22ms/4tokens
BLOCKED: llama_model_load_from_file segfaults via CFFI. Suspect struct-by-value
vs pointer ABI mismatch on x86-64. Needs interactive SBCL REPL to debug the
calling convention (structs >16 bytes passed by hidden reference on SysV).
CFFI bindings preserved in org/system-model-embedding-native.org for
continued REPL work. Includes: model load, context create, tokenize,
encode, embeddings-ith, batch init/free.
Model: nomic-embed-text-v1.5.Q4_K_M.gguf (80MB, 768-dim, nomic-bert)
at ~/.local/share/passepartout/models/
Adds Discord gateway: REST API POST /channels/{id}/messages for
sending, HTTP GET for polling messages. Maps Discord mentions to
:user-input signals. HITL commands intercepted before injection.
Adds Slack gateway: Web API chat.postMessage for sending,
conversations.history for polling. Uses SLACK_TOKEN from vault.
Each gateway registered in *gateway-registry* following the same
jail-loaded skill pattern as Telegram and Signal.
Registry now has 4 platforms: telegram, signal, discord, slack.
RED: Messaging suite had only 1 test (5 checks). No Telegram or Signal
integration tests existed.
GREEN: 4 new tests, 12 new checks (5 → 17):
test-telegram-send-format: verifies URL/body construction for
telegram-send — URL contains sendMessage + token, body encodes
chat_id + text as JSON.
test-telegram-poll-hits-interception: verifies HITL commands
(/approve, /deny, /approve <token>) are intercepted before
signal injection. Non-HITL messages pass through.
test-signal-send-format: verifies signal-send constructs correct
CLI args for signal-cli (account, send, -m, text, chat-id).
test-signal-poll-json-parse: verifies signal-cli JSON output is
parsed correctly — extracts envelope source and dataMessage text.
Test: 123/0 across 13 suites (messaging 17/0).
RED proofs (pre-v0.4.0):
- dispatcher-check-secret-path 'core-loop-reason.org' → NIL (unprotected)
- dispatcher-check-core-path function does not exist
- Write to core file passes through gate unchanged
- test-self-build-core-protection does not exist
- Dispatcher suite: 19/0
GREEN proofs (v0.4.0):
- dispatcher-check-core-path: T for core-*.org/lisp, NIL for others
- SELF_BUILD_MODE=true: core write → :approval-required Flight Plan
- SELF_BUILD_MODE=false (default): writes pass through
- Dispatcher suite: 24/0 (new test-self-build-core-protection)
Prose:
- New 'Self-Build Safety Boundary' section: explains thin harness/fat
skills corollary, regex-based core-* detection, Flight Plan vs LOG
blocking, SELF_BUILD_MODE env var semantics.
Gate trace: cognitive-verify accumulates (:gate name :result status) for
each deterministic gate. Trace prepended to action plist via list*.
TUI on-daemon-msg extracts :gate-trace and stores on message object.
add-msg accepts &key gate-trace for future rendering (collapsible Tab).
Rule counter: TUI actuator enriches response payload with :rule-count
=(hash-table-count *hitl-pending*). TUI status bar shows 'Rules:N'.
Focus map: TUI actuator adds :foveal-id from signal context. TUI stores
in state and renders second status line '[Focus: id]'.
Status bar: now two lines — line 1 (connection, mode, msgs, scroll,
rules, thinking spinner), line 2 (focus map, timestamp).
Test: 112/0 across 14 suites (reason 15/0 including gate-trace assertions)
Adds dispatcher-check-core-path: regex-based detection of core-*.org and
core-*.lisp files (Perceive-Reason-Act loop, Merkle-tree memory, skill
engine, Dispatcher gates).
Vector 2b in dispatcher-check: when SELF_BUILD_MODE=true and a core file
write is detected, produces :approval-required (Flight Plan HITL) instead
of allowing the write through. When SELF_BUILD_MODE=false (default),
writes pass through — development mode.
Core file protection is separate from secret-path protection
(*dispatcher-protected-paths*) which blocks credentials/keys/tokens.
Test test-self-build-core-protection:
- core-loop-reason.org, core-memory.lisp → protected
- gateway-tui-view.org → not protected
- SELF_BUILD_MODE=true → writes blocked as :approval-required
- SELF_BUILD_MODE=false → writes pass through
Test: 102/0 (dispatcher 24/0)
SIGWINCH: handle KEY_RESIZE (410) in main loop — re-measure screen,
re-create status/chat/input windows at new dimensions, force redraw.
Scroll clamp: PageUp clamped to (max 0 (- total 1)), prevents scrolling
past message list end. Status bar shows 'msgs:N scroll:0'.
/quit: saves :input-history to ~/.cache/passepartout/history (one line
per entry, most recent first), sends goodbye handshake, sets :running nil.
/reconnect: closes stale socket via disconnect-daemon, re-runs
connect-daemon with retry backoff. Connection-loss detection: reader-loop
counts consecutive nils; after 10, queues :disconnected event. Handler
clears :connected/:busy, shows red system message.
Load-history: reads ~/.cache/passepartout/history on startup, populates
:input-history for up-arrow recall.
Message vector: :messages init as adjustable vector with fill pointer.
add-msg uses vector-push-extend (O(1) append). view-chat uses aref
(O(1) access) instead of nth (O(n) for lists).
Adds word-wrap(text width) — splits strings into lines at word
boundaries respecting terminal width. Rewrites view-chat to:
- Wrap each message with word-wrap before rendering
- Render each wrapped line as a separate add-string call
- Account for wrapped line count in visible-message calculation
RED proof: tmux capture shows messages split mid-word at terminal edge.
GREEN proof: tmux capture shows clean word-boundary wrapping:
The quick brown fox jumps over the lazy dog while the cat naps
peacefully in the sunny garden
1. Shell actuator: remove double bash -c wrapping (format ~s produces
S-expression-safe strings, not shell-safe). Now passes cmd directly
to (timeout N bash -c cmd) via run-program arg list.
2. Dispatcher: extend high-impact approval gate to :system :eval.
Previously only :shell, :tool "shell", and :emacs :eval triggered
HITL. Now :system :eval also requires Flight Plan approval.
3. Skill sandbox: before promoting a skill from its jailed package to
:passepartout, scan for restricted symbol references (uiop:run-program,
uiop:shell, uiop:run-shell-command). Block promotion on violation.
New skill-entry status :sandbox-blocked for blocked skills.
Test: 91 pass, 0 fail across 13 suites.
Wrap read-from-string/read with (let ((*read-eval* nil)) ...) at three
untrusted-input code paths:
1. think() in core-loop-reason — LLM output parsing. LLM output is
untrusted by definition; #.(shell ...) in a response must not execute.
2. action-system-execute in core-loop-act — :system :eval path processes
untrusted payload code from the signal pipeline.
3. load-memory-from-disk in core-memory — memory.snap file could be
corrupted or planted in ~/, must not execute #. reader macros.
Adds test-read-eval-rce-blocked to pipeline-reason-suite: mocks a
backend returning malicious output containing #.(setf ...), verifies
no side effects occur and safe fallback is returned.
RED proof recorded: *read-eval* T + #.(setf ...) → :PWNED (RCE active)
GREEN proof: *read-eval* NIL → reader-error caught (RCE blocked)
Test: reason 12/0, full suite 88/0
Croatoan child windows don't inherit the screen's :input-blocking nil.
Without explicit (setf (input-blocking iw) nil), get-char blocks the
main loop indefinitely, preventing redraw from running. New agent
messages queued by the reader-loop thread were never rendered until
the user pressed a key.
Now the loop runs at 30fps and responses appear immediately.