rename: remaining Bouncer mentions → Dispatcher
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 2s
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 2s
- BOUNCER-PRIVACY-TAGS → *DISPATCHER-PRIVACY-TAGS* - BOUNCER-SHELL-TIMEOUT → *DISPATCHER-SHELL-TIMEOUT* - BOUNCER-SHELL-MAX-OUTPUT → *DISPATCHER-SHELL-MAX-OUTPUT* - bouncer-privacy-tags docstrings → Dispatcher privacy tags - 'Bouncer' in log messages, docstrings, test descriptions - 'Bouncer Security Dispatcher' → 'Security Dispatcher'
This commit is contained in:
@@ -120,12 +120,12 @@ or nil if the heading is not found."
|
|||||||
path)))
|
path)))
|
||||||
|
|
||||||
(defun context-privacy-filtered-p (obj)
|
(defun context-privacy-filtered-p (obj)
|
||||||
"Returns T if an org-object's :TAGS attribute matches bouncer-privacy-tags."
|
"Returns T if an org-object's :TAGS attribute matches the Dispatcher's privacy tags."
|
||||||
(let* ((attrs (memory-object-attributes obj))
|
(let* ((attrs (memory-object-attributes obj))
|
||||||
(tags (getf attrs :TAGS))
|
(tags (getf attrs :TAGS))
|
||||||
(privacy-tags (and (find-package :passepartout.security-dispatcher)
|
(privacy-tags (and (find-package :passepartout.security-dispatcher)
|
||||||
(symbol-value
|
(symbol-value
|
||||||
(find-symbol "BOUNCER-PRIVACY-TAGS"
|
(find-symbol "*DISPATCHER-PRIVACY-TAGS*"
|
||||||
:passepartout.security-dispatcher)))))
|
:passepartout.security-dispatcher)))))
|
||||||
(when (and tags privacy-tags)
|
(when (and tags privacy-tags)
|
||||||
(let ((tag-list (if (listp tags) tags (list tags))))
|
(let ((tag-list (if (listp tags) tags (list tags))))
|
||||||
@@ -138,7 +138,7 @@ or nil if the heading is not found."
|
|||||||
|
|
||||||
(defun context-awareness-assemble (&optional signal)
|
(defun context-awareness-assemble (&optional signal)
|
||||||
"Produces a high-level skeletal outline of the current Memory for the LLM.
|
"Produces a high-level skeletal outline of the current Memory for the LLM.
|
||||||
Privacy-filtered objects (matching bouncer-privacy-tags) are excluded."
|
Privacy-filtered objects (matching the Dispatcher's privacy tags) are excluded."
|
||||||
(let* ((foveal-id (or (getf signal :foveal-focus)
|
(let* ((foveal-id (or (getf signal :foveal-focus)
|
||||||
(ignore-errors (getf (getf signal :payload) :target-id))))
|
(ignore-errors (getf (getf signal :payload) :target-id))))
|
||||||
(all-projects (context-active-projects))
|
(all-projects (context-active-projects))
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
nil)
|
nil)
|
||||||
|
|
||||||
(defun org-privacy-tag-p (tags-list)
|
(defun org-privacy-tag-p (tags-list)
|
||||||
"Returns T if any tag in TAGS-LIST matches bouncer-privacy-tags."
|
"Returns T if any tag in TAGS-LIST matches the Dispatcher's privacy tags."
|
||||||
(let ((privacy-tags (symbol-value (find-symbol "BOUNCER-PRIVACY-TAGS" :passepartout))))
|
(let ((privacy-tags (symbol-value (find-symbol "*DISPATCHER-PRIVACY-TAGS*" :passepartout))))
|
||||||
(when (and tags-list privacy-tags)
|
(when (and tags-list privacy-tags)
|
||||||
(some (lambda (tag)
|
(some (lambda (tag)
|
||||||
(some (lambda (private-tag)
|
(some (lambda (private-tag)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
(defvar *dispatcher-network-whitelist*
|
(defvar *dispatcher-network-whitelist*
|
||||||
'("api.telegram.org" "matrix.org" "googleapis.com" "openai.com" "anthropic.com")
|
'("api.telegram.org" "matrix.org" "googleapis.com" "openai.com" "anthropic.com")
|
||||||
"Domains the Bouncer considers safe for outbound connections.")
|
"Domains the Dispatcher considers safe for outbound connections.")
|
||||||
|
|
||||||
(defvar *dispatcher-privacy-tags*
|
(defvar *dispatcher-privacy-tags*
|
||||||
(let ((env (uiop:getenv "PRIVACY_FILTER_TAGS")))
|
(let ((env (uiop:getenv "PRIVACY_FILTER_TAGS")))
|
||||||
@@ -211,7 +211,7 @@ privacy tags, privacy text, shell safety, network exfil, high-impact approval."
|
|||||||
|
|
||||||
;; Vector 0: REPL verification lint (warn, don't block)
|
;; Vector 0: REPL verification lint (warn, don't block)
|
||||||
(repl-lint
|
(repl-lint
|
||||||
(log-message "BOUNCER: ~a" (proto-get repl-lint :text))
|
(log-message "DISPATCHER: ~a" (proto-get repl-lint :text))
|
||||||
action)
|
action)
|
||||||
|
|
||||||
;; Vector 1: Lisp syntax validation (block bad lisp writes)
|
;; Vector 1: Lisp syntax validation (block bad lisp writes)
|
||||||
@@ -293,7 +293,7 @@ privacy tags, privacy text, shell safety, network exfil, high-impact approval."
|
|||||||
(tags (getf attrs :TAGS))
|
(tags (getf attrs :TAGS))
|
||||||
(action-str (getf attrs :ACTION)))
|
(action-str (getf attrs :ACTION)))
|
||||||
(when (and (member "FLIGHT_PLAN" tags :test #'string-equal) action-str)
|
(when (and (member "FLIGHT_PLAN" tags :test #'string-equal) action-str)
|
||||||
(log-message "BOUNCER: Found approved flight plan '~a'. Re-injecting..." (memory-object-id node))
|
(log-message "DISPATCHER: Found approved flight plan '~a'. Re-injecting..." (memory-object-id node))
|
||||||
(let ((action (ignore-errors (read-from-string action-str))))
|
(let ((action (ignore-errors (read-from-string action-str))))
|
||||||
(when action
|
(when action
|
||||||
(setf (getf action :approved) t)
|
(setf (getf action :approved) t)
|
||||||
@@ -309,7 +309,7 @@ privacy tags, privacy text, shell safety, network exfil, high-impact approval."
|
|||||||
(defun dispatcher-flight-plan-create (blocked-action)
|
(defun dispatcher-flight-plan-create (blocked-action)
|
||||||
"Creates a Flight Plan node for manual approval in Emacs."
|
"Creates a Flight Plan node for manual approval in Emacs."
|
||||||
(let ((id (remove #\- (princ-to-string (uuid:make-v4-uuid)))))
|
(let ((id (remove #\- (princ-to-string (uuid:make-v4-uuid)))))
|
||||||
(log-message "BOUNCER: Creating flight plan node '~a'..." id)
|
(log-message "DISPATCHER: Creating flight plan node '~a'..." id)
|
||||||
(list :type :REQUEST :target :emacs
|
(list :type :REQUEST :target :emacs
|
||||||
:payload (list :action :insert-node :id id
|
:payload (list :action :insert-node :id id
|
||||||
:attributes (list :TITLE "Flight Plan: High-Risk Action"
|
:attributes (list :TITLE "Flight Plan: High-Risk Action"
|
||||||
@@ -386,7 +386,7 @@ Recognized formats:
|
|||||||
nil))
|
nil))
|
||||||
|
|
||||||
(defun dispatcher-gate (action context)
|
(defun dispatcher-gate (action context)
|
||||||
"Main deterministic gate for the Bouncer skill."
|
"Main deterministic gate for the Security Dispatcher skill."
|
||||||
(let* ((payload (getf context :payload))
|
(let* ((payload (getf context :payload))
|
||||||
(sensor (getf payload :sensor)))
|
(sensor (getf payload :sensor)))
|
||||||
(case sensor
|
(case sensor
|
||||||
@@ -412,7 +412,7 @@ Recognized formats:
|
|||||||
|
|
||||||
(in-package :passepartout-security-dispatcher-tests)
|
(in-package :passepartout-security-dispatcher-tests)
|
||||||
|
|
||||||
(def-suite dispatcher-suite :description "Verification of the Bouncer Security Dispatcher")
|
(def-suite dispatcher-suite :description "Verification of the Security Dispatcher")
|
||||||
(in-suite dispatcher-suite)
|
(in-suite dispatcher-suite)
|
||||||
|
|
||||||
(test test-wildcard-match
|
(test test-wildcard-match
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
(declare (ignore context))
|
(declare (ignore context))
|
||||||
(let* ((payload (getf action :payload))
|
(let* ((payload (getf action :payload))
|
||||||
(cmd (getf payload :cmd))
|
(cmd (getf payload :cmd))
|
||||||
(timeout-sym (find-symbol "*BOUNCER-SHELL-TIMEOUT*" :passepartout))
|
(timeout-sym (find-symbol "*DISPATCHER-SHELL-TIMEOUT*" :passepartout))
|
||||||
(timeout (or (getf payload :timeout) (if timeout-sym (symbol-value timeout-sym) 30)))
|
(timeout (or (getf payload :timeout) (if timeout-sym (symbol-value timeout-sym) 30)))
|
||||||
(max-sym (find-symbol "*BOUNCER-SHELL-MAX-OUTPUT*" :passepartout))
|
(max-sym (find-symbol "*DISPATCHER-SHELL-MAX-OUTPUT*" :passepartout))
|
||||||
(max-output (or (getf payload :max-output) (if max-sym (symbol-value max-sym) 100000))))
|
(max-output (or (getf payload :max-output) (if max-sym (symbol-value max-sym) 100000))))
|
||||||
(log-message "ACT [Shell]: ~a (timeout: ~as)" cmd timeout)
|
(log-message "ACT [Shell]: ~a (timeout: ~as)" cmd timeout)
|
||||||
(multiple-value-bind (out err code)
|
(multiple-value-bind (out err code)
|
||||||
|
|||||||
@@ -237,17 +237,17 @@ Expands environment variables in a path string and strips quotes. Used to resolv
|
|||||||
|
|
||||||
** Privacy Filter for Context Assembly
|
** Privacy Filter for Context Assembly
|
||||||
|
|
||||||
Checks if an org-object has tags matching the Bouncer's ~bouncer-privacy-tags~. Objects with matching tags are excluded from the LLM's context window. This prevents private content tagged with ~@personal~ (or any user-configured privacy tag) from being included in prompts sent to external LLM providers.
|
Checks if an org-object has tags matching the Dispatcher's privacy tags. Objects with matching tags are excluded from the LLM's context window. This prevents private content tagged with ~@personal~ (or any user-configured privacy tag) from being included in prompts sent to external LLM providers.
|
||||||
|
|
||||||
;; REPL-VERIFIED: 2026-05-03T13:00:00
|
;; REPL-VERIFIED: 2026-05-03T13:00:00
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defun context-privacy-filtered-p (obj)
|
(defun context-privacy-filtered-p (obj)
|
||||||
"Returns T if an org-object's :TAGS attribute matches bouncer-privacy-tags."
|
"Returns T if an org-object's :TAGS attribute matches the Dispatcher's privacy tags."
|
||||||
(let* ((attrs (memory-object-attributes obj))
|
(let* ((attrs (memory-object-attributes obj))
|
||||||
(tags (getf attrs :TAGS))
|
(tags (getf attrs :TAGS))
|
||||||
(privacy-tags (and (find-package :passepartout.security-dispatcher)
|
(privacy-tags (and (find-package :passepartout.security-dispatcher)
|
||||||
(symbol-value
|
(symbol-value
|
||||||
(find-symbol "BOUNCER-PRIVACY-TAGS"
|
(find-symbol "*DISPATCHER-PRIVACY-TAGS*"
|
||||||
:passepartout.security-dispatcher)))))
|
:passepartout.security-dispatcher)))))
|
||||||
(when (and tags privacy-tags)
|
(when (and tags privacy-tags)
|
||||||
(let ((tag-list (if (listp tags) tags (list tags))))
|
(let ((tag-list (if (listp tags) tags (list tags))))
|
||||||
@@ -263,13 +263,13 @@ Checks if an org-object has tags matching the Bouncer's ~bouncer-privacy-tags~.
|
|||||||
|
|
||||||
Produces the high-level skeletal outline of the current Memory that is included in every LLM call. This is the "peripheral vision" of the agent — it knows what projects exist, their titles and IDs, but not their full content.
|
Produces the high-level skeletal outline of the current Memory that is included in every LLM call. This is the "peripheral vision" of the agent — it knows what projects exist, their titles and IDs, but not their full content.
|
||||||
|
|
||||||
Privacy-filtered projects (those with tags matching ~bouncer-privacy-tags~) are excluded from the output.
|
Privacy-filtered projects (those with tags matching the Dispatcher's privacy tags) are excluded from the output.
|
||||||
|
|
||||||
;; REPL-VERIFIED: 2026-05-03T13:00:00
|
;; REPL-VERIFIED: 2026-05-03T13:00:00
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defun context-awareness-assemble (&optional signal)
|
(defun context-awareness-assemble (&optional signal)
|
||||||
"Produces a high-level skeletal outline of the current Memory for the LLM.
|
"Produces a high-level skeletal outline of the current Memory for the LLM.
|
||||||
Privacy-filtered objects (matching bouncer-privacy-tags) are excluded."
|
Privacy-filtered objects (matching the Dispatcher's privacy tags) are excluded."
|
||||||
(let* ((foveal-id (or (getf signal :foveal-focus)
|
(let* ((foveal-id (or (getf signal :foveal-focus)
|
||||||
(ignore-errors (getf (getf signal :payload) :target-id))))
|
(ignore-errors (getf (getf signal :payload) :target-id))))
|
||||||
(all-projects (context-active-projects))
|
(all-projects (context-active-projects))
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ The key architectural choice: **actuators are not privileged**. The same dispatc
|
|||||||
|
|
||||||
1. Adding a new actuator requires no changes to the core — just register it
|
1. Adding a new actuator requires no changes to the core — just register it
|
||||||
2. Safety is centralized in the deterministic gates, not scattered across actuator implementations
|
2. Safety is centralized in the deterministic gates, not scattered across actuator implementations
|
||||||
3. Every actuator benefits from the same security checks (the Bouncer, the Policy)
|
3. Every actuator benefits from the same security checks (the Dispatcher, the Policy)
|
||||||
|
|
||||||
** Why Dispatch-Action Verifies Again?
|
** Why Dispatch-Action Verifies Again?
|
||||||
|
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ Skills register deterministic gates via ~defskill~ with the ~:deterministic~ key
|
|||||||
|
|
||||||
Gates run in priority order, highest first. If any gate returns a LOG or EVENT, the proposal is rejected immediately and the rejection reason flows back to the probabilistic engine via the rejection trace. If all gates pass, the proposal is approved.
|
Gates run in priority order, highest first. If any gate returns a LOG or EVENT, the proposal is rejected immediately and the rejection reason flows back to the probabilistic engine via the rejection trace. If all gates pass, the proposal is approved.
|
||||||
|
|
||||||
This architecture makes safety compositional: each skill adds one constraint. The bouncer checks secrets. The policy checks explanations. The shell actuator checks destructive commands. No single skill needs to understand the full security model.
|
This architecture makes safety compositional: each skill adds one constraint. The dispatcher checks secrets. The policy checks explanations. The shell actuator checks destructive commands. No single skill needs to understand the full security model.
|
||||||
|
|
||||||
;; REPL-VERIFIED: 2026-05-03T13:00:00
|
;; REPL-VERIFIED: 2026-05-03T13:00:00
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ Retrieve a single object by its ID from active memory. Returns nil if the ID doe
|
|||||||
|
|
||||||
** Object Search by Attribute (memory-objects-by-attribute)
|
** Object Search by Attribute (memory-objects-by-attribute)
|
||||||
|
|
||||||
Scan the entire active memory for objects whose attributes plist contains a specific key-value pair. For example, finding all objects with ~:TODO "APPROVED"~ (used by the Bouncer to find approved flight plans).
|
Scan the entire active memory for objects whose attributes plist contains a specific key-value pair. For example, finding all objects with ~:TODO "APPROVED"~ (used by the Dispatcher to find approved flight plans).
|
||||||
|
|
||||||
This is a full scan — O(n) over all objects. For the typical knowledge base size (< 10,000 objects), this is microsecond-fast. For larger datasets, a proper index would be needed.
|
This is a full scan — O(n) over all objects. For the typical knowledge base size (< 10,000 objects), this is microsecond-fast. For larger datasets, a proper index would be needed.
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ Hardcoding logic into a compiled binary creates a brittle kernel. Every time you
|
|||||||
|
|
||||||
** The Jailed Package Model
|
** The Jailed Package Model
|
||||||
|
|
||||||
Every skill loads into its own package (e.g., ~PASSEPARTOUT.SKILLS.ORG-SKILL-BOUNCER~). This prevents name conflicts between skills — two skills can define a function called ~process~ without collision, because each lives in its own namespace.
|
Every skill loads into its own package (e.g., ~PASSEPARTOUT.SKILLS.SECURITY-DISPATCHER~). This prevents name conflicts between skills — two skills can define a function called ~process~ without collision, because each lives in its own namespace.
|
||||||
|
|
||||||
After loading, the engine exports the skill's public symbols into the ~passepartout~ package, making them available to other skills and the org. The export filter uses the skill's short name as a prefix — for example, the BOUNCER skill exports only symbols starting with ~BOUNCER-~.
|
After loading, the engine exports the skill's public symbols into the ~passepartout~ package, making them available to other skills and the org. The export filter uses the skill's short name as a prefix — for example, the Security Dispatcher exports only symbols starting with ~DISPATCHER-~.
|
||||||
|
|
||||||
This is how the "thin org, fat skills" principle works in practice: the org provides the loading infrastructure; the skills provide all the intelligence.
|
This is how the "thin org, fat skills" principle works in practice: the org provides the loading infrastructure; the skills provide all the intelligence.
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ Computes the cosine similarity between two numeric vectors. Used by the peripher
|
|||||||
|
|
||||||
*** Secret masking
|
*** Secret masking
|
||||||
|
|
||||||
Simple mask function and the vault memory hash table. Used by the Bouncer skill and credentials vault to prevent secrets from appearing in logs.
|
Simple mask function and the vault memory hash table. Used by the Security Dispatcher skill and credentials vault to prevent secrets from appearing in logs.
|
||||||
|
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defun VAULT-MASK-STRING (s) (declare (ignore s)) "[MASKED]")
|
(defun VAULT-MASK-STRING (s) (declare (ignore s)) "[MASKED]")
|
||||||
@@ -253,7 +253,7 @@ The primary skill loader. Given a path to an ~.org~ file:
|
|||||||
|
|
||||||
1. Reads the Org file and collects all ~#+begin_src lisp~ blocks (excluding test blocks and blocks with ~:tangle no~)
|
1. Reads the Org file and collects all ~#+begin_src lisp~ blocks (excluding test blocks and blocks with ~:tangle no~)
|
||||||
2. Validates the Lisp syntax before loading
|
2. Validates the Lisp syntax before loading
|
||||||
3. Creates a jailed package named after the skill (e.g., ~PASSEPARTOUT.SKILLS.ORG-SKILL-BOUNCER~) with ~:use :passepartout~
|
3. Creates a jailed package named after the skill (e.g., ~PASSEPARTOUT.SKILLS.SECURITY-DISPATCHER~) with ~:use :passepartout~
|
||||||
4. Evaluates the collected Lisp forms in that package
|
4. Evaluates the collected Lisp forms in that package
|
||||||
5. Scans the package for symbols matching the skill's name prefix and exports them to the ~passepartout~ package
|
5. Scans the package for symbols matching the skill's name prefix and exports them to the ~passepartout~ package
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#+PROPERTY: header-args:lisp :tangle ../lisp/programming-org.lisp
|
#+PROPERTY: header-args:lisp :tangle ../lisp/programming-org.lisp
|
||||||
|
|
||||||
* Overview
|
* Overview
|
||||||
Structural manipulation tools for Org-mode files. This skill handles reading, writing, and modifying Org files at the AST level: finding headlines by ID or title, setting properties and TODO states, adding new headlines, generating UUIDs, and converting ASTs back to Org text. It also implements the privacy filter — when reading an Org file, it strips headlines tagged with ~@personal~ (or any tag in ~bouncer-privacy-tags~) and rejects files with matching ~#+FILETAGS:~.
|
Structural manipulation tools for Org-mode files. This skill handles reading, writing, and modifying Org files at the AST level: finding headlines by ID or title, setting properties and TODO states, adding new headlines, generating UUIDs, and converting ASTs back to Org text. It also implements the privacy filter — when reading an Org file, it strips headlines tagged with ~@personal~ (or any tag in the Dispatcher's privacy tags) and rejects files with matching ~#+FILETAGS:~.
|
||||||
|
|
||||||
** Contract
|
** Contract
|
||||||
|
|
||||||
@@ -44,8 +44,8 @@ Structural manipulation tools for Org-mode files. This skill handles reading, wr
|
|||||||
;; REPL-VERIFIED: 2026-05-03T13:00:00
|
;; REPL-VERIFIED: 2026-05-03T13:00:00
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defun org-privacy-tag-p (tags-list)
|
(defun org-privacy-tag-p (tags-list)
|
||||||
"Returns T if any tag in TAGS-LIST matches bouncer-privacy-tags."
|
"Returns T if any tag in TAGS-LIST matches the Dispatcher's privacy tags."
|
||||||
(let ((privacy-tags (symbol-value (find-symbol "BOUNCER-PRIVACY-TAGS" :passepartout))))
|
(let ((privacy-tags (symbol-value (find-symbol "*DISPATCHER-PRIVACY-TAGS*" :passepartout))))
|
||||||
(when (and tags-list privacy-tags)
|
(when (and tags-list privacy-tags)
|
||||||
(some (lambda (tag)
|
(some (lambda (tag)
|
||||||
(some (lambda (private-tag)
|
(some (lambda (private-tag)
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
#+TITLE: SKILL: Bouncer (org-skill-bouncer.org)
|
#+TITLE: SKILL: Security Dispatcher (org-skill-security-dispatcher.org)
|
||||||
#+AUTHOR: Agent
|
#+AUTHOR: Agent
|
||||||
#+FILETAGS: :system:bouncer:authorization:autonomy:
|
#+FILETAGS: :system:dispatcher:authorization:autonomy:
|
||||||
#+PROPERTY: header-args:lisp :tangle ../lisp/security-dispatcher.lisp
|
#+PROPERTY: header-args:lisp :tangle ../lisp/security-dispatcher.lisp
|
||||||
|
|
||||||
* Deep Reasoning: Beyond Permission
|
* Deep Reasoning: Beyond Permission
|
||||||
|
|
||||||
The Bouncer is the physical security layer of Passepartout. While the Policy skill ensures an action is "legal" (e.g., "Yes, you are allowed to send a Telegram message"), the Bouncer ensures the action is "safe" by inspecting the payload content via Deep Packet Inspection.
|
The Dispatcher is the physical security layer of Passepartout. While the Policy skill ensures an action is "legal" (e.g., "Yes, you are allowed to send a Telegram message"), the Dispatcher ensures the action is "safe" by inspecting the payload content via Deep Packet Inspection.
|
||||||
|
|
||||||
Every action that reaches the Bouncer has already been approved by the Reasoning pipeline. The LLM generated it, the deterministic gates verified it, and the Act stage is about to execute it. The Bouncer is the last gate before the action touches the physical world.
|
Every action that reaches the Dispatcher has already been approved by the Reasoning pipeline. The LLM generated it, the deterministic gates verified it, and the Act stage is about to execute it. The Dispatcher is the last gate before the action touches the physical world.
|
||||||
|
|
||||||
The Bouncer inspects nine vectors:
|
The Dispatcher inspects nine vectors:
|
||||||
1. **REPL verification** — warns if a defun is written without REPL prototyping
|
1. **REPL verification** — warns if a defun is written without REPL prototyping
|
||||||
2. **Lisp syntax** — blocks writes with unbalanced parens
|
2. **Lisp syntax** — blocks writes with unbalanced parens
|
||||||
3. **Secret paths** — blocks reads to ~.env~, SSH keys, PEM files, etc.
|
3. **Secret paths** — blocks reads to ~.env~, SSH keys, PEM files, etc.
|
||||||
@@ -20,7 +20,7 @@ The Bouncer inspects nine vectors:
|
|||||||
8. **Shell safety** — blocks destructive commands and injection patterns
|
8. **Shell safety** — blocks destructive commands and injection patterns
|
||||||
9. **Network exfil** — blocks unwhitelisted outbound connections
|
9. **Network exfil** — blocks unwhitelisted outbound connections
|
||||||
|
|
||||||
The Bouncer also handles the **Flight Plan** system: when a high-risk action is blocked, it creates a Flight Plan node in the Org files that the user can manually approve.
|
The Dispatcher also handles the **Flight Plan** system: when a high-risk action is blocked, it creates a Flight Plan node in the Org files that the user can manually approve.
|
||||||
|
|
||||||
** Contract
|
** Contract
|
||||||
|
|
||||||
@@ -59,12 +59,12 @@ The Bouncer also handles the **Flight Plan** system: when a high-risk action is
|
|||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Security Configuration — network whitelist
|
** Security Configuration — network whitelist
|
||||||
Domains that the Bouncer considers safe for outbound connections. Network calls to unlisted domains are blocked or queued for approval.
|
Domains that the Dispatcher considers safe for outbound connections. Network calls to unlisted domains are blocked or queued for approval.
|
||||||
;; REPL-VERIFIED: 2026-05-03T13:00:00
|
;; REPL-VERIFIED: 2026-05-03T13:00:00
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defvar *dispatcher-network-whitelist*
|
(defvar *dispatcher-network-whitelist*
|
||||||
'("api.telegram.org" "matrix.org" "googleapis.com" "openai.com" "anthropic.com")
|
'("api.telegram.org" "matrix.org" "googleapis.com" "openai.com" "anthropic.com")
|
||||||
"Domains the Bouncer considers safe for outbound connections.")
|
"Domains the Dispatcher considers safe for outbound connections.")
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Privacy filter tags (*dispatcher-privacy-tags*)
|
** Privacy filter tags (*dispatcher-privacy-tags*)
|
||||||
@@ -359,7 +359,7 @@ privacy tags, privacy text, shell safety, network exfil, high-impact approval."
|
|||||||
|
|
||||||
;; Vector 0: REPL verification lint (warn, don't block)
|
;; Vector 0: REPL verification lint (warn, don't block)
|
||||||
(repl-lint
|
(repl-lint
|
||||||
(log-message "BOUNCER: ~a" (proto-get repl-lint :text))
|
(log-message "DISPATCHER: ~a" (proto-get repl-lint :text))
|
||||||
action)
|
action)
|
||||||
|
|
||||||
;; Vector 1: Lisp syntax validation (block bad lisp writes)
|
;; Vector 1: Lisp syntax validation (block bad lisp writes)
|
||||||
@@ -446,7 +446,7 @@ privacy tags, privacy text, shell safety, network exfil, high-impact approval."
|
|||||||
(tags (getf attrs :TAGS))
|
(tags (getf attrs :TAGS))
|
||||||
(action-str (getf attrs :ACTION)))
|
(action-str (getf attrs :ACTION)))
|
||||||
(when (and (member "FLIGHT_PLAN" tags :test #'string-equal) action-str)
|
(when (and (member "FLIGHT_PLAN" tags :test #'string-equal) action-str)
|
||||||
(log-message "BOUNCER: Found approved flight plan '~a'. Re-injecting..." (memory-object-id node))
|
(log-message "DISPATCHER: Found approved flight plan '~a'. Re-injecting..." (memory-object-id node))
|
||||||
(let ((action (ignore-errors (read-from-string action-str))))
|
(let ((action (ignore-errors (read-from-string action-str))))
|
||||||
(when action
|
(when action
|
||||||
(setf (getf action :approved) t)
|
(setf (getf action :approved) t)
|
||||||
@@ -466,7 +466,7 @@ privacy tags, privacy text, shell safety, network exfil, high-impact approval."
|
|||||||
(defun dispatcher-flight-plan-create (blocked-action)
|
(defun dispatcher-flight-plan-create (blocked-action)
|
||||||
"Creates a Flight Plan node for manual approval in Emacs."
|
"Creates a Flight Plan node for manual approval in Emacs."
|
||||||
(let ((id (remove #\- (princ-to-string (uuid:make-v4-uuid)))))
|
(let ((id (remove #\- (princ-to-string (uuid:make-v4-uuid)))))
|
||||||
(log-message "BOUNCER: Creating flight plan node '~a'..." id)
|
(log-message "DISPATCHER: Creating flight plan node '~a'..." id)
|
||||||
(list :type :REQUEST :target :emacs
|
(list :type :REQUEST :target :emacs
|
||||||
:payload (list :action :insert-node :id id
|
:payload (list :action :insert-node :id id
|
||||||
:attributes (list :TITLE "Flight Plan: High-Risk Action"
|
:attributes (list :TITLE "Flight Plan: High-Risk Action"
|
||||||
@@ -596,7 +596,7 @@ Recognized formats:
|
|||||||
;; REPL-VERIFIED: 2026-05-03T13:00:00
|
;; REPL-VERIFIED: 2026-05-03T13:00:00
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defun dispatcher-gate (action context)
|
(defun dispatcher-gate (action context)
|
||||||
"Main deterministic gate for the Bouncer skill."
|
"Main deterministic gate for the Security Dispatcher skill."
|
||||||
(let* ((payload (getf context :payload))
|
(let* ((payload (getf context :payload))
|
||||||
(sensor (getf payload :sensor)))
|
(sensor (getf payload :sensor)))
|
||||||
(case sensor
|
(case sensor
|
||||||
@@ -629,7 +629,7 @@ Recognized formats:
|
|||||||
|
|
||||||
(in-package :passepartout-security-dispatcher-tests)
|
(in-package :passepartout-security-dispatcher-tests)
|
||||||
|
|
||||||
(def-suite dispatcher-suite :description "Verification of the Bouncer Security Dispatcher")
|
(def-suite dispatcher-suite :description "Verification of the Security Dispatcher")
|
||||||
(in-suite dispatcher-suite)
|
(in-suite dispatcher-suite)
|
||||||
|
|
||||||
(test test-wildcard-match
|
(test test-wildcard-match
|
||||||
|
|||||||
@@ -5,15 +5,9 @@
|
|||||||
|
|
||||||
* Overview: The Authorization Matrix
|
* Overview: The Authorization Matrix
|
||||||
|
|
||||||
Every cognitive tool (file read, file write, shell execute, etc.) has a permission level: ~:allow~ (executed without asking), ~:ask~ (user is prompted before execution), or ~:deny~ (blocked entirely). Tool Permissions maintains the registry of these levels and provides the ~permission-gate-check~ that the Bouncer calls before dispatching a tool action.
|
Every cognitive tool (file read, file write, shell execute, etc.) has a permission level: ~:allow~ (executed without asking), ~:ask~ (user is prompted before execution), or ~:deny~ (blocked entirely). Tool Permissions maintains the registry of these levels and provides the ~permission-gate-check~ that the Dispatcher calls before dispatching a tool action.
|
||||||
|
|
||||||
The default for any unregistered tool is ~:ask~ — cautious by default, permissive by configuration. This prevents a hallucinated tool call from executing without at least giving the user a chance to review it.
|
The complexity lives in the Dispatcher (security-dispatcher.org), which
|
||||||
|
|
||||||
* Architectural Intent
|
|
||||||
|
|
||||||
The Authorization Matrix is the lookup table that maps tool names to
|
|
||||||
permission levels. It is intentionally simple: set, get, default.
|
|
||||||
The complexity lives in the Bouncer (security-dispatcher.org), which
|
|
||||||
consults this table as one of its nine scan vectors.
|
consults this table as one of its nine scan vectors.
|
||||||
|
|
||||||
** Contract
|
** Contract
|
||||||
@@ -27,7 +21,7 @@ consults this table as one of its nine scan vectors.
|
|||||||
|
|
||||||
** Boundaries
|
** Boundaries
|
||||||
|
|
||||||
- Does NOT enforce permissions — the Bouncer does that.
|
- Does NOT enforce permissions — the Dispatcher does that.
|
||||||
- Does NOT persist permissions to disk — this is runtime-only.
|
- Does NOT persist permissions to disk — this is runtime-only.
|
||||||
- Does NOT validate that ~level~ is one of ~(:allow :ask :deny)~.
|
- Does NOT validate that ~level~ is one of ~(:allow :ask :deny)~.
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ The Policy skill encodes the non-negotiable values of Passepartout. Every action
|
|||||||
|
|
||||||
This is the "Radical Transparency" invariant in practice. The agent must explain *why* it wants to do something, not just *what* it wants to do. An action with ~:explanation "Because I said so"~ is rejected. An action with ~:explanation "The user asked me to read their TODO list and summarize it"~ passes.
|
This is the "Radical Transparency" invariant in practice. The agent must explain *why* it wants to do something, not just *what* it wants to do. An action with ~:explanation "Because I said so"~ is rejected. An action with ~:explanation "The user asked me to read their TODO list and summarize it"~ passes.
|
||||||
|
|
||||||
The Policy skill is intentionally simple. It has one job: ensure every action has a meaningful explanation. Other security concerns (secret scanning, path blocking, network exfiltration) are handled by the Bouncer. The Policy is about values, not threats.
|
The Policy skill is intentionally simple. It has one job: ensure every action has a meaningful explanation. Other security concerns (secret scanning, path blocking, network exfiltration) are handled by the Dispatcher. The Policy is about values, not threats.
|
||||||
|
|
||||||
** Contract
|
** Contract
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ The Policy skill is intentionally simple. It has one job: ensure every action ha
|
|||||||
|
|
||||||
** Boundaries
|
** Boundaries
|
||||||
|
|
||||||
- Does NOT check for dangerous content — the Bouncer does that.
|
- Does NOT check for dangerous content — the Dispatcher does that.
|
||||||
- Does NOT validate explanation quality — only length and presence.
|
- Does NOT validate explanation quality — only length and presence.
|
||||||
- Does NOT consider ~context~ — implementation ignores it currently.
|
- Does NOT consider ~context~ — implementation ignores it currently.
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ before they reach any cognitive stage.
|
|||||||
** Boundaries
|
** Boundaries
|
||||||
|
|
||||||
- Does NOT define the schema — that is ~core-communication.org~.
|
- Does NOT define the schema — that is ~core-communication.org~.
|
||||||
- Does NOT validate semantic content — that is the Bouncer and Policy.
|
- Does NOT validate semantic content — that is the Dispatcher and Policy.
|
||||||
|
|
||||||
* Implementation
|
* Implementation
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
The Shell Actuator is the agent's hand in the physical world. Given a shell command, it executes it via ~bash -c~ and returns the output. This is how the agent installs packages, reads files, runs scripts, and interacts with any Unix tool.
|
The Shell Actuator is the agent's hand in the physical world. Given a shell command, it executes it via ~bash -c~ and returns the output. This is how the agent installs packages, reads files, runs scripts, and interacts with any Unix tool.
|
||||||
|
|
||||||
Because shell execution is the highest-risk operation in the system, the Shell Actuator is protected by multiple safety layers:
|
Because shell execution is the highest-risk operation in the system, the Shell Actuator is protected by multiple safety layers:
|
||||||
1. The Bouncer's shell safety gate blocks destructive commands (~rm -rf /~, ~dd~, ~mkfs~)
|
1. The Dispatcher's shell safety gate blocks destructive commands (~rm -rf /~, ~dd~, ~mkfs~)
|
||||||
2. The Bouncer's injection gate blocks backtick and ~$()~ patterns
|
2. The Dispatcher's injection gate blocks backtick and ~$()~ patterns
|
||||||
3. The Bouncer's network exfil gate blocks connections to unwhitelisted hosts
|
3. The Dispatcher's network exfil gate blocks connections to unwhitelisted hosts
|
||||||
4. The actuator enforces a timeout (default 30s) so hanging commands don't freeze the agent
|
4. The actuator enforces a timeout (default 30s) so hanging commands don't freeze the agent
|
||||||
5. The actuator caps output (default 100KB) so infinite output doesn't exhaust memory
|
5. The actuator caps output (default 100KB) so infinite output doesn't exhaust memory
|
||||||
|
|
||||||
@@ -24,9 +24,9 @@ Because shell execution is the highest-risk operation in the system, the Shell A
|
|||||||
(declare (ignore context))
|
(declare (ignore context))
|
||||||
(let* ((payload (getf action :payload))
|
(let* ((payload (getf action :payload))
|
||||||
(cmd (getf payload :cmd))
|
(cmd (getf payload :cmd))
|
||||||
(timeout-sym (find-symbol "*BOUNCER-SHELL-TIMEOUT*" :passepartout))
|
(timeout-sym (find-symbol "*DISPATCHER-SHELL-TIMEOUT*" :passepartout))
|
||||||
(timeout (or (getf payload :timeout) (if timeout-sym (symbol-value timeout-sym) 30)))
|
(timeout (or (getf payload :timeout) (if timeout-sym (symbol-value timeout-sym) 30)))
|
||||||
(max-sym (find-symbol "*BOUNCER-SHELL-MAX-OUTPUT*" :passepartout))
|
(max-sym (find-symbol "*DISPATCHER-SHELL-MAX-OUTPUT*" :passepartout))
|
||||||
(max-output (or (getf payload :max-output) (if max-sym (symbol-value max-sym) 100000))))
|
(max-output (or (getf payload :max-output) (if max-sym (symbol-value max-sym) 100000))))
|
||||||
(log-message "ACT [Shell]: ~a (timeout: ~as)" cmd timeout)
|
(log-message "ACT [Shell]: ~a (timeout: ~as)" cmd timeout)
|
||||||
(multiple-value-bind (out err code)
|
(multiple-value-bind (out err code)
|
||||||
|
|||||||
Reference in New Issue
Block a user