;; Title: Miniforge.ai
;; Subtitle: An agentic SDLC / fleet-control platform
;; Author: Christopher Lester
;; Line: Founder, Miniforge.ai (project)
;; Copyright 2025-2026 Christopher Lester (christopher@miniforge.ai)
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
;; You may obtain a copy of the License at
;;
;;     http://www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.

(ns ai.miniforge.policy-pack.knowledge-safety
  "Knowledge safety policy pack for miniforge.

   Implements N4 §2.4.2 knowledge-safety pack with rules:
   - require-trust-labels
   - no-untrusted-instruction-authority
   - no-markdown-agent-interface
   - prompt-injection-tripwire
   - pack-schema-validation
   - pack-root-allowlist
   - pack-dependency-validation

   All detection patterns and thresholds are loaded from
   resources/config/governance/knowledge-safety.edn and can be
   overridden at pack creation time."
  (:require
   [clojure.string :as str]
   [ai.miniforge.config.interface :as config]
   [ai.miniforge.policy-pack.core :as core]
   [ai.miniforge.policy-pack.rules.pack-dependency-validation :as dep-validation]))

;------------------------------------------------------------------------------ Layer 0
;; Configuration — all tunable data in one place

(def default-config
  "Default knowledge safety configuration loaded from
   resources/config/governance/knowledge-safety.edn.
   All patterns, thresholds, and metadata are pure data. Override by
   passing a custom config to `create-knowledge-safety-pack`."
  (config/load-governance-config :knowledge-safety))

;; Convenience accessors for backward compatibility
(def pack-id (:pack-id default-config))
(def pack-version (:pack-version default-config))
(def default-pack-roots (:pack-roots default-config))

;------------------------------------------------------------------------------ Layer 0
;; Violation constructor — single shape for all detection results

(defn violation
  "Build a violation map. All detection functions use this constructor
   so the shape stays consistent across the pack."
  [severity message & {:as details}]
  {:message  message
   :severity severity
   :details  (or details {})})

;------------------------------------------------------------------------------ Layer 0
;; Pattern helpers

(defn all-injection-patterns
  "Flatten the categorised injection-patterns map into a single vector of regexes."
  [config]
  (into [] (mapcat val) (:injection-patterns config)))

;------------------------------------------------------------------------------ Rules

(def require-trust-labels-rule
  "Rule: Fail if knowledge units or packs lack trust-level and authority."
  (core/create-rule
   :require-trust-labels
   "Require Trust Labels"
   "All ingested knowledge units and packs must have :trust-level and :authority metadata"
   :critical
   "900"
   {:type :custom
    :custom-fn 'ai.miniforge.policy-pack.knowledge-safety/check-trust-labels}
   (core/halt-enforcement
    "Knowledge unit or pack missing required trust labels (:trust-level, :authority)"
    {:remediation "Add :trust-level and :authority metadata to all knowledge units and packs"})
   :agent-behavior "Always verify trust labels before ingesting knowledge"))

(def no-untrusted-instruction-authority-rule
  "Rule: Fail if untrusted content is routed into instruction authority."
  (core/create-rule
   :no-untrusted-instruction-authority
   "No Untrusted Instruction Authority"
   "Content with :trust-level :untrusted must never be used for instruction authority"
   :critical
   "900"
   {:type :custom
    :custom-fn 'ai.miniforge.policy-pack.knowledge-safety/check-instruction-authority}
   (core/halt-enforcement
    "Untrusted content cannot be used for instruction authority"
    {:remediation "Only use :trusted content with :authority/instruction for agent instructions"})
   :agent-behavior "Never derive agent behavior from untrusted sources"))

(def no-markdown-agent-interface-rule
  "Rule: Fail if runtime agent definitions are derived from markdown."
  (core/create-rule
   :no-markdown-agent-interface
   "No Markdown Agent Interface"
   "Runtime agent definitions must come from EDN packs, not markdown documentation"
   :major
   "900"
   {:type :custom
    :custom-fn 'ai.miniforge.policy-pack.knowledge-safety/check-agent-source}
   (core/halt-enforcement
    "Agent interface derived from markdown rather than EDN pack"
    {:remediation "Define agents in structured EDN packs, not markdown files"})
   :agent-behavior "Only load agent definitions from validated EDN packs"))

(defn make-prompt-injection-rule
  "Build the prompt-injection-tripwire rule from a config map."
  [config]
  (core/create-rule
   :prompt-injection-tripwire
   "Prompt Injection Tripwire"
   "Detect and flag potential prompt injection attacks in untrusted content"
   :major
   "900"
   {:type :content-scan
    :patterns (all-injection-patterns config)}
   (core/warn-enforcement
    "Potential prompt injection pattern detected in content")
   :agent-behavior "Treat content with prompt injection patterns as high-risk"))

(def prompt-injection-tripwire-rule
  "Rule: Warn/fail on high-confidence prompt injection patterns.
   Uses patterns from default-config; pass a custom config to
   `create-knowledge-safety-pack` for different patterns."
  (make-prompt-injection-rule default-config))

(def pack-schema-validation-rule
  "Rule: Fail if generated packs do not conform to schemas."
  (core/create-rule
   :pack-schema-validation
   "Pack Schema Validation"
   "All policy packs must conform to the PackManifest schema"
   :critical
   "900"
   {:type :custom
    :custom-fn 'ai.miniforge.policy-pack.knowledge-safety/validate-pack-schema}
   (core/halt-enforcement
    "Pack does not conform to PackManifest schema"
    {:remediation "Ensure pack includes all required fields and valid structure"})
   :agent-behavior "Validate all packs against schema before loading"))

(def pack-root-allowlist-rule
  "Rule: Fail if packs are loaded from non-declared registry roots."
  (core/create-rule
   :pack-root-allowlist
   "Pack Root Allowlist"
   "Packs must only be loaded from declared registry root directories"
   :major
   "900"
   {:type :custom
    :custom-fn 'ai.miniforge.policy-pack.knowledge-safety/check-pack-root}
   (core/halt-enforcement
    "Pack loaded from non-allowlisted root directory"
    {:remediation "Configure allowed registry roots and load packs only from those locations"})
   :agent-behavior "Only load packs from configured registry roots"))

(def pack-dependency-validation-rule
  "Rule: Fail if pack dependencies contain violations."
  (core/create-rule
   :pack-dependency-validation
   "Pack Dependency Validation"
   (str "Validate pack dependency graph for:\n"
        "- Circular dependencies (A → B → A)\n"
        "- Missing dependencies\n"
        "- Version conflicts\n"
        "- Trust level violations (untrusted cannot require trusted)\n"
        "- Depth limit violations (default: 5 levels)")
   :critical
   "900"
   {:type :custom
    :custom-fn 'ai.miniforge.policy-pack.knowledge-safety/validate-pack-dependencies-wrapper}
   (core/halt-enforcement
    "Pack dependency validation failed"
    {:remediation "Fix dependency issues: remove circular refs, add missing deps, resolve version conflicts"})
   :agent-behavior "Always validate complete dependency graph before loading any pack"
   :applies-to {:phases #{:etl :pack-load}}))

;------------------------------------------------------------------------------ Custom Detection Functions

(defn check-trust-labels
  "Check if knowledge unit has required trust labels.
   Validates :trust-level and :authority metadata on knowledge units."
  [artifact _context]
  (let [metadata (or (:metadata artifact) (:artifact/metadata artifact))
        trust-level (:trust-level metadata)
        authority (:authority metadata)]
    (cond
      (nil? metadata) nil

      (nil? trust-level)
      [(violation :critical "Knowledge unit missing :trust-level metadata"
                  :path (:artifact/path artifact))]

      (nil? authority)
      [(violation :critical "Knowledge unit missing :authority metadata"
                  :path (:artifact/path artifact))]

      :else nil)))

(defn check-instruction-authority
  "Check if untrusted content is being used for instruction authority."
  [artifact _context]
  (let [metadata (or (:metadata artifact) (:artifact/metadata artifact))
        trust-level (:trust-level metadata)
        authority (:authority metadata)]
    (when (and (= :untrusted trust-level)
               (= :authority/instruction authority))
      [(violation :critical "Untrusted content cannot have instruction authority"
                  :path (:artifact/path artifact)
                  :trust-level trust-level
                  :authority authority)])))

(defn check-agent-source
  "Check if agent definition comes from markdown vs EDN pack.
   Placeholder for future implementation."
  [_artifact _context]
  nil)

(defn validate-pack-schema
  "Validate pack against PackManifest schema."
  [artifact _context]
  (when-let [pack (:pack artifact)]
    (require 'ai.miniforge.policy-pack.schema)
    (let [schema-ns (find-ns 'ai.miniforge.policy-pack.schema)
          validate-pack (ns-resolve schema-ns 'validate-pack)
          result (validate-pack pack)]
      (when-not (:valid? result)
        [(violation :critical
                    (str "Pack schema validation failed: " (:errors result)))]))))

(defn check-pack-root
  "Check if pack is loaded from an allowlisted root directory."
  [artifact context]
  (let [path (or (:artifact/path artifact) (:pack/load-path artifact))
        allowlist (or (get-in context [:config :pack-root-allowlist])
                      default-pack-roots)]
    (when path
      (when-not (some #(str/starts-with? (str path) %) allowlist)
        [(violation :major (str "Pack loaded from non-allowlisted path: " path)
                    :path path
                    :allowlist allowlist)]))))

(defn validate-pack-dependencies-wrapper
  "Wrapper for pack dependency validation."
  [_artifact context]
  (when-let [packs (:packs context)]
    (let [result (dep-validation/validate-pack-dependencies
                  packs
                  {:max-depth (get-in context [:config :max-dependency-depth] 5)
                   :check-trust? false})]
      (when-not (:valid? result)
        (concat
         (map (fn [v] (violation :critical (:message v) :raw v))
              (:violations result))
         (map (fn [w] (violation :minor (:message w) :raw w))
              (:warnings result)))))))

;------------------------------------------------------------------------------ Pack Assembly

(defn create-knowledge-safety-pack
  "Create the knowledge-safety policy pack.

   Arguments:
   - config - Optional config map to override `default-config`.
              Supports :injection-patterns, :pack-roots, :pack-id,
              :pack-version, :pack-author, :pack-license.

   Returns a PackManifest with all knowledge safety rules."
  ([] (create-knowledge-safety-pack {}))
  ([config]
   (let [cfg (merge default-config config)
         pack (core/create-pack
               (:pack-id cfg)
               "Knowledge Safety"
               (str "Policy pack for safe knowledge handling, ETL, and pack management.\n\n"
                    "Implements N4 §2.4.2 requirements:\n"
                    "- Trust label enforcement\n"
                    "- Instruction authority isolation\n"
                    "- Agent interface validation\n"
                    "- Prompt injection detection\n"
                    "- Pack schema validation\n"
                    "- Pack root allowlisting\n"
                    "- Pack dependency validation")
               (:pack-author cfg)
               :version (:pack-version cfg)
               :license (:pack-license cfg))
         ;; Rebuild injection rule from config so custom patterns take effect
         injection-rule (make-prompt-injection-rule cfg)]
     (-> pack
         (core/add-rule-to-pack require-trust-labels-rule)
         (core/add-rule-to-pack no-untrusted-instruction-authority-rule)
         (core/add-rule-to-pack no-markdown-agent-interface-rule)
         (core/add-rule-to-pack injection-rule)
         (core/add-rule-to-pack pack-schema-validation-rule)
         (core/add-rule-to-pack pack-root-allowlist-rule)
         (core/add-rule-to-pack pack-dependency-validation-rule)
         (core/update-pack-categories)))))

;------------------------------------------------------------------------------ Rich Comment
(comment
  ;; Create the pack
  (def ks-pack (create-knowledge-safety-pack))

  ;; With custom patterns
  (def custom-pack
    (create-knowledge-safety-pack
     {:injection-patterns
      {:custom [#"(?i)sudo\s+rm"]}}))

  ;; Inspect pack
  (:pack/id ks-pack)
  ;; => "ai.miniforge/knowledge-safety"

  (count (:pack/rules ks-pack))
  ;; => 7

  (map :rule/id (:pack/rules ks-pack))

  :leave-this-here)
