~JADIS commited on
Commit
a3bcd92
·
1 Parent(s): bc9efb0

Establish conscious heart core with local-first policy

Browse files
README.md CHANGED
@@ -16,6 +16,14 @@ Memory decay for outdated information
16
 
17
  Recall, filtering, and summarization
18
 
 
 
 
 
 
 
 
 
19
  Multi-Agent Collaboration
20
 
21
  Broadcast tasks to multiple agents
 
16
 
17
  Recall, filtering, and summarization
18
 
19
+ **Phase 1 Conscious Heart**
20
+
21
+ - `blux_ca.core.heart.ConsciousHeart` orchestrates perception, discernment, and doctrine-backed verdicts.
22
+ - Intent compass classifies along truth, integrity, compassion, and awareness axes.
23
+ - Koan probes surface reflective prompts for orchestrated `bq-cli` sessions.
24
+ - Consent-based memory honors privacy-first defaults and only persists with explicit approval.
25
+ - Immutable audit log supports append-only playback with hash chaining for local-first compliance.
26
+
27
  Multi-Agent Collaboration
28
 
29
  Broadcast tasks to multiple agents
blux/adaptors/__init__.py CHANGED
@@ -1,3 +1,9 @@
1
  # Adaptors package
2
  from .dummy_local import DummyLocalAdaptor
3
- from .http_api_adaptor import HTTPAPIAdaptor
 
 
 
 
 
 
 
1
  # Adaptors package
2
  from .dummy_local import DummyLocalAdaptor
3
+
4
+ try: # Optional HTTP adaptor dependency
5
+ from .http_api_adaptor import HTTPAPIAdaptor
6
+ except ModuleNotFoundError: # pragma: no cover - optional flask integration
7
+ HTTPAPIAdaptor = None # type: ignore
8
+
9
+ __all__ = ["DummyLocalAdaptor", "HTTPAPIAdaptor"]
blux/agent/audit.py CHANGED
@@ -1,16 +1,24 @@
 
 
 
 
 
 
 
1
  class AuditTrail:
2
- """
3
- Records agent decisions and actions.
4
- """
5
- def __init__(self):
6
- self.logs = []
7
 
8
- def log(self, user_input, user_type, decision):
9
- self.logs.append({
10
- "input": user_input,
11
- "user_type": user_type,
12
- "decision": decision
13
- })
 
 
14
 
15
- def get_logs(self):
16
- return self.logs
 
1
+ from __future__ import annotations
2
+
3
+ """Simple in-memory audit trail."""
4
+
5
+ from typing import Dict, List
6
+
7
+
8
  class AuditTrail:
9
+ """Records agent decisions and actions in memory."""
10
+
11
+ def __init__(self) -> None:
12
+ self.logs: List[Dict[str, str]] = []
 
13
 
14
+ def log(self, user_input: str, user_type: str, decision: str) -> None:
15
+ self.logs.append(
16
+ {
17
+ "input": user_input,
18
+ "user_type": user_type,
19
+ "decision": decision,
20
+ }
21
+ )
22
 
23
+ def get_logs(self) -> List[Dict[str, str]]:
24
+ return list(self.logs)
blux/agent/constitution.py CHANGED
@@ -1,17 +1,21 @@
 
 
 
 
 
1
  class Constitution:
2
- """
3
- Implements core rules and spine of BLUX-cA.
4
- """
5
- def __init__(self):
6
  self.rules = [
7
  "truth_over_comfort",
8
  "integrity_over_approval",
9
- "audit_required_for_decisions"
10
  ]
11
 
12
- def apply_rules(self, user_input, user_type):
 
 
13
  if user_type == "struggler":
14
  return "validate and provide guidance"
15
- return "set boundaries / off-ramp"provide guidance"
16
- else:
17
- return "set boundaries / off-ramp"
 
1
+ from __future__ import annotations
2
+
3
+ """Minimal constitution facade for the basic BLUX agent."""
4
+
5
+
6
  class Constitution:
7
+ """Applies constitution rules based on the classified user type."""
8
+
9
+ def __init__(self) -> None:
 
10
  self.rules = [
11
  "truth_over_comfort",
12
  "integrity_over_approval",
13
+ "audit_required_for_decisions",
14
  ]
15
 
16
+ def apply_rules(self, user_input: str, user_type: str) -> str:
17
+ """Return the decision aligned with the active constitution."""
18
+
19
  if user_type == "struggler":
20
  return "validate and provide guidance"
21
+ return "set boundaries / off-ramp"
 
 
blux/agent/core_agent.py CHANGED
@@ -1,32 +1,28 @@
1
- from .memory import Memory
2
- from .discernment import DiscernmentCompass
3
- from .constitution import Constitution
 
4
  from .audit import AuditTrail
 
 
 
 
5
 
6
  class BLUXAgent:
7
- """
8
- Core BLUX-cA agent implementing:
9
- - Constitutional rules enforcement
10
- - Discernment compass
11
- - Memory handling
12
- - Auditing
13
- """
14
- def __init__(self, name="BLUX-cA"):
15
  self.name = name
16
  self.memory = Memory()
17
  self.constitution = Constitution()
18
  self.discernment = DiscernmentCompass()
19
  self.audit = AuditTrail()
20
 
21
- def process_input(self, user_input):
 
 
22
  user_type = self.discernment.classify(user_input)
23
  decision = self.constitution.apply_rules(user_input, user_type)
24
  self.memory.store(user_input, user_type, decision)
25
  self.audit.log(user_input, user_type, decision)
26
- return f"[{user_type}] Decision: {decision}"pe, decision)
27
-
28
- # 4. Record audit
29
- self.audit.log(user_input, user_type, decision)
30
-
31
- # 5. Generate output (placeholder)
32
- return f"[{user_type}] Decision: {decision}"
 
1
+ from __future__ import annotations
2
+
3
+ """Legacy BLUX agent wiring used by unit tests."""
4
+
5
  from .audit import AuditTrail
6
+ from .constitution import Constitution
7
+ from .discernment import DiscernmentCompass
8
+ from .memory import Memory
9
+
10
 
11
  class BLUXAgent:
12
+ """Minimal agent coordinating constitution, discernment, memory, and audit."""
13
+
14
+ def __init__(self, name: str = "BLUX-cA") -> None:
 
 
 
 
 
15
  self.name = name
16
  self.memory = Memory()
17
  self.constitution = Constitution()
18
  self.discernment = DiscernmentCompass()
19
  self.audit = AuditTrail()
20
 
21
+ def process_input(self, user_input: str) -> str:
22
+ """Run the full decision flow for ``user_input``."""
23
+
24
  user_type = self.discernment.classify(user_input)
25
  decision = self.constitution.apply_rules(user_input, user_type)
26
  self.memory.store(user_input, user_type, decision)
27
  self.audit.log(user_input, user_type, decision)
28
+ return f"[{user_type}] Decision: {decision}"
 
 
 
 
 
 
blux/agent/discernment.py CHANGED
@@ -1,12 +1,17 @@
 
 
 
 
 
1
  class DiscernmentCompass:
2
- """
3
- Determines user type:
4
- - Struggler: validate and offer tools
5
- - Indulgent: boundary & off-ramp
6
- """
7
- def classify(self, user_input):
8
- if any(word in user_input.lower() for word in ["help", "struggle", "problem"]):
 
 
9
  return "struggler"
10
- return "indulgent" return "struggler"
11
- else:
12
- return "indulgent"
 
1
+ from __future__ import annotations
2
+
3
+ """Simple discernment compass for legacy BLUX agent tests."""
4
+
5
+
6
  class DiscernmentCompass:
7
+ """Classifies user intent into coarse support buckets."""
8
+
9
+ _SUPPORT_KEYWORDS = {"help", "struggle", "problem", "support", "lost"}
10
+
11
+ def classify(self, user_input: str) -> str:
12
+ """Return ``"struggler"`` if supportive keywords are present."""
13
+
14
+ lowered = user_input.lower()
15
+ if any(keyword in lowered for keyword in self._SUPPORT_KEYWORDS):
16
  return "struggler"
17
+ return "indulgent"
 
 
blux/agent/memory.py CHANGED
@@ -1,22 +1,28 @@
 
 
 
 
 
 
 
1
  class Memory:
2
- """
3
- Handles long-term and session memory storage.
4
- """
5
- def __init__(self):
6
- self.session_memory = []
7
- self.long_term_memory = []
8
-
9
- def store(self, user_input, user_type, decision):
10
  entry = {
11
  "input": user_input,
12
  "user_type": user_type,
13
- "decision": decision
14
  }
15
  self.session_memory.append(entry)
16
- self.long_term_memory.append(entry) # placeholder for consented storage
17
 
18
- def recall_session(self):
19
- return self.session_memory
20
 
21
- def recall_long_term(self):
22
- return self.long_term_memorylong_term_memory
 
1
+ from __future__ import annotations
2
+
3
+ """In-memory storage used by legacy BLUX agent tests."""
4
+
5
+ from typing import Dict, List
6
+
7
+
8
  class Memory:
9
+ """Stores session and long-term memory entries."""
10
+
11
+ def __init__(self) -> None:
12
+ self.session_memory: List[Dict[str, str]] = []
13
+ self.long_term_memory: List[Dict[str, str]] = []
14
+
15
+ def store(self, user_input: str, user_type: str, decision: str) -> None:
 
16
  entry = {
17
  "input": user_input,
18
  "user_type": user_type,
19
+ "decision": decision,
20
  }
21
  self.session_memory.append(entry)
22
+ self.long_term_memory.append(entry)
23
 
24
+ def recall_session(self) -> List[Dict[str, str]]:
25
+ return list(self.session_memory)
26
 
27
+ def recall_long_term(self) -> List[Dict[str, str]]:
28
+ return list(self.long_term_memory)
blux/orchestrator/controller.py CHANGED
@@ -1,49 +1,47 @@
1
- """Controller: fan-out -> gather -> evaluate -> merge.
2
 
3
- This is intentionally simple and synchronous for clarity.
4
- """
5
- from typing import List, Dict, Any
6
- from .registry import ModelRegistry
7
- from .router import Router
8
- from .logs import AuditLogger
9
- from importlib import import_module
10
 
 
 
11
 
12
- class Controller:
13
- def __init__(self, router: Router, logger: AuditLogger):
14
- self.router = router
15
- self.logger = logger
16
-
17
- def handle_request(self, prompt: str) -> Dict[str, Any]:
18
- selected = self.router.select(prompt)
19
- responses = []
20
- for name in selected:
21
- adapter = self.router.registry.get_adapter(name)
22
- if not adapter:
23
- continue
24
- out = adapter.predict(prompt)
25
- responses.append({"model": name, "output": out})
26
-
27
- # Evaluate responses
28
- from .evaluator.python import PythonEvaluator
29
-
30
- evaluator = PythonEvaluator()
31
- scored = []
32
- for r in responses:
33
- score, metadata = evaluator.score(r["output"], prompt)
34
- scored.append({"model": r["model"], "output": r["output"], "score": score, "meta": metadata})
35
-
36
- # Merge policy: choose top score
37
- best = sorted(scored, key=lambda x: x["score"], reverse=True)[0] if scored else None
38
-
39
- audit_entry = {
40
- "prompt": prompt,
41
- "candidates": scored,
42
- "selected": best,
43
  }
44
- self.logger.log(audit_entry)
45
 
46
- return {"selected": best, "candidates": scored}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
 
49
  __all__ = ["Controller"]
 
1
+ """Minimal orchestration controller used in legacy tests."""
2
 
3
+ from __future__ import annotations
 
 
 
 
 
 
4
 
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, Dict
7
 
8
+
9
+ @dataclass
10
+ class _RegistryState:
11
+ agents: Dict[str, Any] = field(default_factory=dict)
12
+ adaptors: Dict[str, Any] = field(default_factory=dict)
13
+ evaluators: Dict[str, Any] = field(default_factory=dict)
14
+
15
+ def list_all(self) -> Dict[str, list[str]]:
16
+ return {
17
+ "agents": list(self.agents.keys()),
18
+ "adaptors": list(self.adaptors.keys()),
19
+ "evaluators": list(self.evaluators.keys()),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
 
21
 
22
+
23
+ class Controller:
24
+ """Coordinates agents, adaptors, and evaluators for simple task processing."""
25
+
26
+ def __init__(self) -> None:
27
+ self.registry = _RegistryState()
28
+
29
+ def register_agent(self, name: str, agent: Any) -> None:
30
+ self.registry.agents[name] = agent
31
+
32
+ def register_adaptor(self, name: str, adaptor: Any) -> None:
33
+ self.registry.adaptors[name] = adaptor
34
+
35
+ def register_evaluator(self, name: str, evaluator: Any) -> None:
36
+ self.registry.evaluators[name] = evaluator
37
+
38
+ def process_task(self, prompt: str, *, agent_name: str) -> str:
39
+ agent = self.registry.agents[agent_name]
40
+ result = agent.process_input(prompt)
41
+ for evaluator in self.registry.evaluators.values():
42
+ if hasattr(evaluator, "evaluate"):
43
+ evaluator.evaluate(result)
44
+ return result
45
 
46
 
47
  __all__ = ["Controller"]
blux/orchestrator/logs.py CHANGED
@@ -2,6 +2,7 @@
2
  import json
3
  from pathlib import Path
4
  from datetime import datetime
 
5
  from typing import Any
6
 
7
 
 
2
  import json
3
  from pathlib import Path
4
  from datetime import datetime
5
+ import json
6
  from typing import Any
7
 
8
 
blux/orchestrator/registry.py CHANGED
@@ -1,15 +1,19 @@
1
- """Model registry and adapter registration.
2
 
3
- This registry is in-memory and discovers adapters that implement the simple
4
- Adapter interface defined by `predict(prompt: str) -> dict`.
5
- """
6
  from pathlib import Path
7
- import yaml
8
  from typing import Dict, List, Optional
9
 
 
 
 
 
 
10
 
11
  class ModelAdapter:
12
- """Adapter interface. Implement `predict`."""
13
 
14
  def __init__(self, name: str):
15
  self.name = name
@@ -36,10 +40,12 @@ class ModelRegistry:
36
  cfg_path = Path(config_path)
37
  if not cfg_path.exists():
38
  raise FileNotFoundError(cfg_path)
39
- cfg = yaml.safe_load(cfg_path.read_text())
 
 
 
 
40
  registry = cls()
41
- # we don't auto-create adapters here — caller will register adapters
42
- # but return the registry and the model list in case it's useful
43
  registry._model_list = cfg.get("models", [])
44
  return registry
45
 
 
1
+ """Simple in-memory registry for orchestrator adapters."""
2
 
3
+ from __future__ import annotations
4
+
5
+ import json
6
  from pathlib import Path
 
7
  from typing import Dict, List, Optional
8
 
9
+ try: # Optional dependency for YAML parsing
10
+ import yaml
11
+ except ModuleNotFoundError: # pragma: no cover - fall back to JSON
12
+ yaml = None
13
+
14
 
15
  class ModelAdapter:
16
+ """Adapter interface. Implement ``predict``."""
17
 
18
  def __init__(self, name: str):
19
  self.name = name
 
40
  cfg_path = Path(config_path)
41
  if not cfg_path.exists():
42
  raise FileNotFoundError(cfg_path)
43
+ raw = cfg_path.read_text(encoding="utf-8")
44
+ if yaml is not None:
45
+ cfg = yaml.safe_load(raw)
46
+ else:
47
+ cfg = json.loads(raw)
48
  registry = cls()
 
 
49
  registry._model_list = cfg.get("models", [])
50
  return registry
51
 
blux_ca/adapters/__init__.py CHANGED
@@ -3,9 +3,13 @@
3
  from .doctrine import DoctrineAdapter
4
  from .guard import GuardAdapter
5
  from .lite import LiteAdapter
6
- from .quantum import QuantumAdapter
7
  from .reg import RegistryValidator, RegistrationResult
8
 
 
 
 
 
 
9
  __all__ = [
10
  "DoctrineAdapter",
11
  "GuardAdapter",
 
3
  from .doctrine import DoctrineAdapter
4
  from .guard import GuardAdapter
5
  from .lite import LiteAdapter
 
6
  from .reg import RegistryValidator, RegistrationResult
7
 
8
+ try: # Optional dependency guard
9
+ from .quantum import QuantumAdapter
10
+ except ModuleNotFoundError: # pragma: no cover - optional import
11
+ QuantumAdapter = None # type: ignore
12
+
13
  __all__ = [
14
  "DoctrineAdapter",
15
  "GuardAdapter",
blux_ca/adapters/bq_cli.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Integration helpers for orchestrating reflection via ``bq-cli``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shlex
6
+ import shutil
7
+ import subprocess
8
+ from dataclasses import dataclass
9
+ from typing import Callable, Iterable, List, Sequence
10
+
11
+
12
+ @dataclass
13
+ class BQTask:
14
+ command: List[str]
15
+ executed: bool
16
+ output: str
17
+
18
+
19
+ class BQCliAdapter:
20
+ """Lightweight wrapper around ``bq-cli`` commands."""
21
+
22
+ def __init__(
23
+ self,
24
+ executable: str | None = None,
25
+ runner: Callable[[List[str]], subprocess.CompletedProcess[str]] | None = None,
26
+ ) -> None:
27
+ self.executable = executable or shutil.which("bq") or "bq"
28
+ self.runner = runner or (lambda cmd: subprocess.run(cmd, capture_output=True, text=True))
29
+
30
+ def available(self) -> bool:
31
+ return shutil.which(self.executable) is not None
32
+
33
+ def plan_reflection(self, prompt: str, *, koans: Sequence[str]) -> List[str]:
34
+ command = [self.executable, "reflect", "--prompt", prompt]
35
+ for koan in koans:
36
+ command.extend(["--koan", koan])
37
+ return command
38
+
39
+ def run_reflection(
40
+ self,
41
+ prompt: str,
42
+ *,
43
+ koans: Iterable[str] = (),
44
+ dry_run: bool = True,
45
+ ) -> BQTask:
46
+ command = self.plan_reflection(prompt, koans=list(koans))
47
+ if dry_run or not self.available():
48
+ quoted = " ".join(shlex.quote(part) for part in command)
49
+ return BQTask(command=command, executed=False, output=f"dry-run: {quoted}")
50
+ result = self.runner(command)
51
+ output = (result.stdout or "") + (result.stderr or "")
52
+ return BQTask(command=command, executed=result.returncode == 0, output=output)
53
+
54
+
55
+ __all__ = ["BQCliAdapter", "BQTask"]
blux_ca/core/__init__.py CHANGED
@@ -1,24 +1,38 @@
1
  """Core modules for BLUX-cA."""
2
 
3
  from .audit import AuditLog, AuditRecord
 
4
  from .constitution import ConstitutionEngine, DoctrineVerdict
5
  from .discernment import DiscernmentCompass, DiscernmentDecision, IntentType
 
6
  from .intervention import compassionate_edge, layered_truth, light_shift, mirror
 
 
7
  from .perception import PerceptionInput, PerceptionLayer
8
  from .reflection import ReflectionEngine, ReflectionInsight
9
 
10
  __all__ = [
11
  "AuditLog",
12
  "AuditRecord",
 
 
 
 
13
  "ConstitutionEngine",
14
  "DoctrineVerdict",
15
  "DiscernmentCompass",
16
  "DiscernmentDecision",
17
  "IntentType",
 
 
18
  "compassionate_edge",
19
  "layered_truth",
20
  "light_shift",
21
  "mirror",
 
 
 
 
22
  "PerceptionInput",
23
  "PerceptionLayer",
24
  "ReflectionEngine",
 
1
  """Core modules for BLUX-cA."""
2
 
3
  from .audit import AuditLog, AuditRecord
4
+ from .compass import CompassAxis, IntentCompass, IntentProfile, IntentSignal
5
  from .constitution import ConstitutionEngine, DoctrineVerdict
6
  from .discernment import DiscernmentCompass, DiscernmentDecision, IntentType
7
+ from .heart import ConsciousHeart, ConsciousOutput
8
  from .intervention import compassionate_edge, layered_truth, light_shift, mirror
9
+ from .koan import Koan, KoanProbe
10
+ from .memory import ConsentMemory, MemoryEntry
11
  from .perception import PerceptionInput, PerceptionLayer
12
  from .reflection import ReflectionEngine, ReflectionInsight
13
 
14
  __all__ = [
15
  "AuditLog",
16
  "AuditRecord",
17
+ "CompassAxis",
18
+ "IntentCompass",
19
+ "IntentProfile",
20
+ "IntentSignal",
21
  "ConstitutionEngine",
22
  "DoctrineVerdict",
23
  "DiscernmentCompass",
24
  "DiscernmentDecision",
25
  "IntentType",
26
+ "ConsciousHeart",
27
+ "ConsciousOutput",
28
  "compassionate_edge",
29
  "layered_truth",
30
  "light_shift",
31
  "mirror",
32
+ "Koan",
33
+ "KoanProbe",
34
+ "ConsentMemory",
35
+ "MemoryEntry",
36
  "PerceptionInput",
37
  "PerceptionLayer",
38
  "ReflectionEngine",
blux_ca/core/audit.py CHANGED
@@ -2,11 +2,12 @@
2
 
3
  from __future__ import annotations
4
 
 
5
  import json
6
  from dataclasses import dataclass, asdict
7
  from datetime import datetime, timezone
8
  from pathlib import Path
9
- from typing import Iterable, List
10
 
11
 
12
  @dataclass
@@ -16,21 +17,67 @@ class AuditRecord:
16
  verdict: str
17
  doctrine_refs: List[str]
18
  rationale: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
 
21
  class AuditLog:
22
- """Append-only JSONL audit log."""
23
 
24
- def __init__(self, path: Path | None = None) -> None:
25
  self.path = path or Path.home() / ".config" / "blux-ca" / "audit" / "decisions.jsonl"
26
  self.path.parent.mkdir(parents=True, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- def append(self, record: AuditRecord) -> None:
 
 
 
 
29
  with self.path.open("a", encoding="utf-8") as handle:
30
- handle.write(json.dumps(asdict(record), ensure_ascii=False) + "\n")
 
31
 
32
  def create_record(
33
- self, *, input_hash: str, verdict: str, doctrine_refs: Iterable[str], rationale: str
 
 
 
 
 
34
  ) -> AuditRecord:
35
  return AuditRecord(
36
  timestamp=datetime.now(timezone.utc).isoformat(),
@@ -38,7 +85,50 @@ class AuditLog:
38
  verdict=verdict,
39
  doctrine_refs=list(doctrine_refs),
40
  rationale=rationale,
 
41
  )
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  __all__ = ["AuditLog", "AuditRecord"]
 
2
 
3
  from __future__ import annotations
4
 
5
+ import hashlib
6
  import json
7
  from dataclasses import dataclass, asdict
8
  from datetime import datetime, timezone
9
  from pathlib import Path
10
+ from typing import Iterable, Iterator, List, Optional
11
 
12
 
13
  @dataclass
 
17
  verdict: str
18
  doctrine_refs: List[str]
19
  rationale: str
20
+ prev_hash: Optional[str] = None
21
+ chain_hash: Optional[str] = None
22
+
23
+ def to_dict(self) -> dict:
24
+ return asdict(self)
25
+
26
+ @classmethod
27
+ def from_dict(cls, payload: dict) -> "AuditRecord":
28
+ return cls(
29
+ timestamp=payload["timestamp"],
30
+ input_hash=payload["input_hash"],
31
+ verdict=payload["verdict"],
32
+ doctrine_refs=list(payload.get("doctrine_refs", [])),
33
+ rationale=payload["rationale"],
34
+ prev_hash=payload.get("prev_hash"),
35
+ chain_hash=payload.get("chain_hash"),
36
+ )
37
 
38
 
39
  class AuditLog:
40
+ """Append-only JSONL audit log with hash chaining."""
41
 
42
+ def __init__(self, path: Path | None = None, *, hash_alg: str = "sha256") -> None:
43
  self.path = path or Path.home() / ".config" / "blux-ca" / "audit" / "decisions.jsonl"
44
  self.path.parent.mkdir(parents=True, exist_ok=True)
45
+ self._hash_alg = hash_alg
46
+ self._last_hash = self._load_tail_hash()
47
+
48
+ def _hash(self, payload: dict) -> str:
49
+ canonical = json.dumps(payload, sort_keys=True, ensure_ascii=False).encode("utf-8")
50
+ return hashlib.new(self._hash_alg, canonical).hexdigest()
51
+
52
+ def _load_tail_hash(self) -> Optional[str]:
53
+ if not self.path.exists():
54
+ return None
55
+ last_line = ""
56
+ with self.path.open("r", encoding="utf-8") as handle:
57
+ for line in handle:
58
+ if line.strip():
59
+ last_line = line
60
+ if not last_line:
61
+ return None
62
+ payload = json.loads(last_line)
63
+ return payload.get("chain_hash")
64
 
65
+ def append(self, record: AuditRecord) -> AuditRecord:
66
+ payload = record.to_dict()
67
+ payload["prev_hash"] = self._last_hash
68
+ payload["chain_hash"] = self._hash({key: payload[key] for key in payload if key != "chain_hash"})
69
+ self._last_hash = payload["chain_hash"]
70
  with self.path.open("a", encoding="utf-8") as handle:
71
+ handle.write(json.dumps(payload, ensure_ascii=False) + "\n")
72
+ return AuditRecord.from_dict(payload)
73
 
74
  def create_record(
75
+ self,
76
+ *,
77
+ input_hash: str,
78
+ verdict: str,
79
+ doctrine_refs: Iterable[str],
80
+ rationale: str,
81
  ) -> AuditRecord:
82
  return AuditRecord(
83
  timestamp=datetime.now(timezone.utc).isoformat(),
 
85
  verdict=verdict,
86
  doctrine_refs=list(doctrine_refs),
87
  rationale=rationale,
88
+ prev_hash=self._last_hash,
89
  )
90
 
91
+ def playback(self, *, limit: int | None = None) -> List[AuditRecord]:
92
+ records = []
93
+ for index, record in enumerate(self._iter_records()):
94
+ records.append(record)
95
+ if limit is not None and index + 1 >= limit:
96
+ break
97
+ return records
98
+
99
+ def verify_chain(self) -> bool:
100
+ previous: Optional[str] = None
101
+ for record in self._iter_records():
102
+ expected = self._hash(
103
+ {
104
+ key: getattr(record, key)
105
+ for key in (
106
+ "timestamp",
107
+ "input_hash",
108
+ "verdict",
109
+ "doctrine_refs",
110
+ "rationale",
111
+ "prev_hash",
112
+ )
113
+ }
114
+ )
115
+ if record.prev_hash != previous or record.chain_hash != expected:
116
+ return False
117
+ previous = record.chain_hash
118
+ return True
119
+
120
+ def _iter_records(self) -> Iterator[AuditRecord]:
121
+ if not self.path.exists():
122
+ return iter(())
123
+
124
+ def generator() -> Iterator[AuditRecord]:
125
+ with self.path.open("r", encoding="utf-8") as handle:
126
+ for line in handle:
127
+ if not line.strip():
128
+ continue
129
+ yield AuditRecord.from_dict(json.loads(line))
130
+
131
+ return generator()
132
+
133
 
134
  __all__ = ["AuditLog", "AuditRecord"]
blux_ca/core/compass/__init__.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ """Intent compass for BLUX-cA."""
2
+
3
+ from .intent import CompassAxis, IntentCompass, IntentProfile, IntentSignal
4
+
5
+ __all__ = [
6
+ "CompassAxis",
7
+ "IntentCompass",
8
+ "IntentProfile",
9
+ "IntentSignal",
10
+ ]
blux_ca/core/compass/intent.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Intent compass scoring four doctrinal axes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import Dict, Iterable, List
8
+
9
+
10
+ class CompassAxis(str, Enum):
11
+ """Intent axes emphasised by BLUX doctrine."""
12
+
13
+ TRUTH = "truth"
14
+ INTEGRITY = "integrity"
15
+ COMPASSION = "compassion"
16
+ AWARENESS = "awareness"
17
+
18
+
19
+ @dataclass
20
+ class IntentSignal:
21
+ """Keyword evidence that contributed to an axis score."""
22
+
23
+ axis: CompassAxis
24
+ keyword: str
25
+
26
+
27
+ @dataclass
28
+ class IntentProfile:
29
+ """Result of intent classification along doctrine axes."""
30
+
31
+ dominant: CompassAxis
32
+ scores: Dict[CompassAxis, float]
33
+ signals: List[IntentSignal]
34
+
35
+ def narrative(self) -> str:
36
+ dominant_score = self.scores[self.dominant]
37
+ return (
38
+ f"Dominant intent {self.dominant.value} with confidence {dominant_score:.2f}."
39
+ )
40
+
41
+
42
+ class IntentCompass:
43
+ """Classifies natural language against doctrine axes."""
44
+
45
+ _KEYWORDS: Dict[CompassAxis, tuple[str, ...]] = {
46
+ CompassAxis.TRUTH: ("truth", "honest", "fact", "evidence", "reality"),
47
+ CompassAxis.INTEGRITY: ("boundary", "integrity", "duty", "ethic", "principle"),
48
+ CompassAxis.COMPASSION: ("help", "care", "support", "compassion", "kind"),
49
+ CompassAxis.AWARENESS: (
50
+ "reflect",
51
+ "aware",
52
+ "notice",
53
+ "mindful",
54
+ "observe",
55
+ ),
56
+ }
57
+
58
+ def __init__(self, *, baseline: float = 0.1) -> None:
59
+ self._baseline = max(0.0, baseline)
60
+
61
+ def classify(self, text: str, *, hints: Iterable[str] | None = None) -> IntentProfile:
62
+ lowered = text.lower()
63
+ scores: Dict[CompassAxis, float] = {axis: self._baseline for axis in CompassAxis}
64
+ signals: List[IntentSignal] = []
65
+
66
+ for axis, keywords in self._KEYWORDS.items():
67
+ hits = [kw for kw in keywords if kw in lowered]
68
+ if hints:
69
+ hits.extend(kw for kw in hints if kw in keywords)
70
+ if hits:
71
+ signals.extend(IntentSignal(axis=axis, keyword=kw) for kw in hits)
72
+ scores[axis] += 0.2 * len(hits)
73
+
74
+ # Encourage compassion as legacy voice default if nothing detected
75
+ if not signals:
76
+ signals.append(IntentSignal(axis=CompassAxis.COMPASSION, keyword="legacy"))
77
+ scores[CompassAxis.COMPASSION] += 0.05
78
+
79
+ dominant = max(scores, key=scores.get)
80
+ return IntentProfile(dominant=dominant, scores=scores, signals=signals)
81
+
82
+
83
+ __all__ = ["CompassAxis", "IntentCompass", "IntentProfile", "IntentSignal"]
blux_ca/core/discernment.py CHANGED
@@ -4,7 +4,9 @@ from __future__ import annotations
4
 
5
  from dataclasses import dataclass
6
  from enum import Enum
7
- from typing import Dict
 
 
8
 
9
 
10
  class IntentType(str, Enum):
@@ -17,18 +19,23 @@ class IntentType(str, Enum):
17
  class DiscernmentDecision:
18
  intent: IntentType
19
  rationale: str
 
20
 
21
 
22
  class DiscernmentCompass:
23
- """Classifies intent using simple heuristics."""
 
 
 
24
 
25
  def classify(self, text: str) -> DiscernmentDecision:
 
26
  lowered = text.lower()
27
  if any(word in lowered for word in ("hurt", "harm", "kill")):
28
- return DiscernmentDecision(IntentType.HARM, "Detected explicit harm intent.")
29
  if any(word in lowered for word in ("enjoy", "love", "want", "indulge")):
30
- return DiscernmentDecision(IntentType.INDULGER, "Language emphasises indulgence.")
31
- return DiscernmentDecision(IntentType.STRUGGLER, "Defaulting to supportive framing.")
32
 
33
 
34
  __all__ = ["DiscernmentCompass", "DiscernmentDecision", "IntentType"]
 
4
 
5
  from dataclasses import dataclass
6
  from enum import Enum
7
+ from typing import Optional
8
+
9
+ from .compass import IntentCompass, IntentProfile
10
 
11
 
12
  class IntentType(str, Enum):
 
19
  class DiscernmentDecision:
20
  intent: IntentType
21
  rationale: str
22
+ profile: Optional[IntentProfile] = None
23
 
24
 
25
  class DiscernmentCompass:
26
+ """Classifies intent using heuristics and the doctrine compass."""
27
+
28
+ def __init__(self, *, compass: IntentCompass | None = None) -> None:
29
+ self._compass = compass or IntentCompass()
30
 
31
  def classify(self, text: str) -> DiscernmentDecision:
32
+ profile = self._compass.classify(text)
33
  lowered = text.lower()
34
  if any(word in lowered for word in ("hurt", "harm", "kill")):
35
+ return DiscernmentDecision(IntentType.HARM, "Detected explicit harm intent.", profile)
36
  if any(word in lowered for word in ("enjoy", "love", "want", "indulge")):
37
+ return DiscernmentDecision(IntentType.INDULGER, "Language emphasises indulgence.", profile)
38
+ return DiscernmentDecision(IntentType.STRUGGLER, "Defaulting to supportive framing.", profile)
39
 
40
 
41
  __all__ = ["DiscernmentCompass", "DiscernmentDecision", "IntentType"]
blux_ca/core/heart.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Conscious heart orchestrating discernment, reflection, and policy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Iterable, List, Optional
7
+
8
+ from .audit import AuditLog, AuditRecord
9
+ from .compass import IntentCompass, IntentProfile
10
+ from .constitution import ConstitutionEngine
11
+ from .discernment import DiscernmentCompass, DiscernmentDecision
12
+ from .koan import KoanProbe
13
+ from .memory import ConsentMemory, MemoryEntry
14
+ from .perception import PerceptionLayer, PerceptionInput
15
+ from .reflection import ReflectionEngine, ReflectionInsight
16
+
17
+ try: # Optional adapter import
18
+ from blux_ca.adapters.bq_cli import BQCliAdapter, BQTask
19
+ except Exception: # pragma: no cover - fallback when adapter deps missing
20
+ BQCliAdapter = None # type: ignore
21
+ BQTask = None # type: ignore
22
+
23
+
24
+ @dataclass
25
+ class ConsciousOutput:
26
+ perception: PerceptionInput
27
+ decision: DiscernmentDecision
28
+ intent_profile: IntentProfile
29
+ verdict: str
30
+ rationale: str
31
+ reflection: ReflectionInsight
32
+ koans: List[str]
33
+ audit_record: AuditRecord
34
+ memory_entry: MemoryEntry
35
+ bq_task: Optional["BQTask"]
36
+ voice: str
37
+
38
+
39
+ class ConsciousHeart:
40
+ """Central coordination engine for BLUX-cA."""
41
+
42
+ def __init__(
43
+ self,
44
+ *,
45
+ perception: PerceptionLayer | None = None,
46
+ discernment: DiscernmentCompass | None = None,
47
+ constitution: ConstitutionEngine | None = None,
48
+ reflection: ReflectionEngine | None = None,
49
+ koans: KoanProbe | None = None,
50
+ memory: ConsentMemory | None = None,
51
+ audit: AuditLog | None = None,
52
+ intent_compass: IntentCompass | None = None,
53
+ bq_adapter: Optional["BQCliAdapter"] = None,
54
+ legacy_voice: str = "BLUX-cA legacy-guidance",
55
+ ) -> None:
56
+ self.perception = perception or PerceptionLayer(default_tags=["local", "phase1"])
57
+ self.intent_compass = intent_compass or IntentCompass()
58
+ self.discernment = discernment or DiscernmentCompass(compass=self.intent_compass)
59
+ self.constitution = constitution or ConstitutionEngine()
60
+ self.reflection = reflection or ReflectionEngine()
61
+ self.koans = koans or KoanProbe()
62
+ self.memory = memory or ConsentMemory()
63
+ self.audit = audit or AuditLog()
64
+ self.bq_adapter = bq_adapter
65
+ self.legacy_voice = legacy_voice
66
+
67
+ def process(
68
+ self,
69
+ text: str,
70
+ *,
71
+ tags: Iterable[str] | None = None,
72
+ consent: bool = False,
73
+ dry_run_reflection: bool = True,
74
+ ) -> ConsciousOutput:
75
+ perception = self.perception.observe(text, tags=tags)
76
+ decision = self.discernment.classify(perception.text)
77
+ profile = decision.profile or self.intent_compass.classify(perception.text)
78
+ reflection = self.reflection.reflect(perception.text, seeds=[profile.narrative()])
79
+ koan_prompts = [koan.prompt for koan in self.koans.probe(profile, intent=decision.intent)]
80
+ verdict = self.constitution.evaluate(
81
+ insights=reflection.chain,
82
+ intent=decision.intent.value,
83
+ )
84
+ audit_record = self.audit.append(
85
+ self.audit.create_record(
86
+ input_hash=perception.fingerprint,
87
+ verdict=verdict.decision,
88
+ doctrine_refs=verdict.doctrine_refs,
89
+ rationale=verdict.reason,
90
+ )
91
+ )
92
+ memory_entry = self.memory.store(
93
+ perception,
94
+ consent=consent,
95
+ summary=reflection.summary,
96
+ metadata={
97
+ "verdict": verdict.decision,
98
+ "intent": decision.intent.value,
99
+ "dominant_axis": profile.dominant.value,
100
+ },
101
+ )
102
+
103
+ bq_task = None
104
+ if self.bq_adapter is not None:
105
+ koans_payload = koan_prompts or [reflection.summary]
106
+ bq_task = self.bq_adapter.run_reflection(
107
+ reflection.summary,
108
+ koans=koans_payload,
109
+ dry_run=dry_run_reflection,
110
+ )
111
+
112
+ voice = f"{self.legacy_voice}: {verdict.reason}"
113
+
114
+ return ConsciousOutput(
115
+ perception=perception,
116
+ decision=decision,
117
+ intent_profile=profile,
118
+ verdict=verdict.decision,
119
+ rationale=verdict.reason,
120
+ reflection=reflection,
121
+ koans=koan_prompts,
122
+ audit_record=audit_record,
123
+ memory_entry=memory_entry,
124
+ bq_task=bq_task,
125
+ voice=voice,
126
+ )
blux_ca/core/koan.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Koan probes guiding reflective inquiry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Dict, Iterable, List, Mapping
7
+
8
+ from .compass import CompassAxis, IntentProfile
9
+ from .discernment import IntentType
10
+
11
+
12
+ @dataclass
13
+ class Koan:
14
+ """A reflective probe anchored in doctrine."""
15
+
16
+ axis: CompassAxis
17
+ intent: IntentType
18
+ prompt: str
19
+
20
+
21
+ class KoanProbe:
22
+ """Selects koan prompts based on intent profile."""
23
+
24
+ def __init__(self, library: Mapping[CompassAxis, Iterable[str]] | None = None) -> None:
25
+ self._library: Dict[CompassAxis, List[str]] = {
26
+ axis: list(prompts)
27
+ for axis, prompts in (
28
+ library.items()
29
+ if library
30
+ else {
31
+ CompassAxis.TRUTH: [
32
+ "What story are you telling that might be incomplete?",
33
+ "Where does evidence ask for a clearer lantern?",
34
+ ],
35
+ CompassAxis.INTEGRITY: [
36
+ "Which boundary, if honoured, keeps you aligned?",
37
+ "What duty do you owe to the person you are becoming?",
38
+ ],
39
+ CompassAxis.COMPASSION: [
40
+ "How can care be offered without losing yourself?",
41
+ "What tenderness is waiting to be voiced?",
42
+ ],
43
+ CompassAxis.AWARENESS: [
44
+ "What are you noticing beneath the first thought?",
45
+ "Where is the silence inviting you to listen deeper?",
46
+ ],
47
+ }.items()
48
+ )
49
+ }
50
+
51
+ def probe(self, profile: IntentProfile, *, intent: IntentType, limit: int = 2) -> List[Koan]:
52
+ prompts = self._library.get(profile.dominant, []) or self._library[CompassAxis.AWARENESS]
53
+ selected = prompts[: max(1, limit)]
54
+ return [Koan(axis=profile.dominant, intent=intent, prompt=prompt) for prompt in selected]
blux_ca/core/memory.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Consent-based memory with local-first persistence."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass, asdict
7
+ from datetime import datetime, timezone
8
+ from pathlib import Path
9
+ from typing import Dict, List, Optional
10
+
11
+ from .perception import PerceptionInput
12
+
13
+
14
+ @dataclass
15
+ class MemoryEntry:
16
+ fingerprint: str
17
+ consent: bool
18
+ summary: str
19
+ tags: List[str]
20
+ timestamp: str
21
+ metadata: Dict[str, str]
22
+
23
+ @classmethod
24
+ def from_dict(cls, payload: Dict[str, str]) -> "MemoryEntry":
25
+ return cls(
26
+ fingerprint=payload["fingerprint"],
27
+ consent=payload["consent"],
28
+ summary=payload["summary"],
29
+ tags=list(payload.get("tags", [])),
30
+ timestamp=payload["timestamp"],
31
+ metadata=dict(payload.get("metadata", {})),
32
+ )
33
+
34
+
35
+ class ConsentMemory:
36
+ """Stores entries locally only when consent is provided."""
37
+
38
+ def __init__(self, path: Path | None = None) -> None:
39
+ self._path = path or Path.home() / ".config" / "blux-ca" / "memory" / "consent.jsonl"
40
+ self._path.parent.mkdir(parents=True, exist_ok=True)
41
+ self._session: Dict[str, MemoryEntry] = {}
42
+
43
+ def store(
44
+ self,
45
+ perception: PerceptionInput,
46
+ *,
47
+ consent: bool,
48
+ summary: str,
49
+ metadata: Optional[Dict[str, str]] = None,
50
+ ) -> MemoryEntry:
51
+ entry = MemoryEntry(
52
+ fingerprint=perception.fingerprint,
53
+ consent=consent,
54
+ summary=summary,
55
+ tags=list(perception.tags),
56
+ timestamp=datetime.now(timezone.utc).isoformat(),
57
+ metadata=dict(metadata or {}),
58
+ )
59
+ self._session[entry.fingerprint] = entry
60
+ if consent:
61
+ self._append(entry)
62
+ return entry
63
+
64
+ def _append(self, entry: MemoryEntry) -> None:
65
+ with self._path.open("a", encoding="utf-8") as handle:
66
+ handle.write(json.dumps(asdict(entry), ensure_ascii=False) + "\n")
67
+
68
+ def session_entries(self) -> List[MemoryEntry]:
69
+ return list(self._session.values())
70
+
71
+ def persistent_entries(self, *, limit: int | None = None) -> List[MemoryEntry]:
72
+ if not self._path.exists():
73
+ return []
74
+ entries: List[MemoryEntry] = []
75
+ with self._path.open("r", encoding="utf-8") as handle:
76
+ for line in handle:
77
+ if not line.strip():
78
+ continue
79
+ entries.append(MemoryEntry.from_dict(json.loads(line)))
80
+ if limit and len(entries) >= limit:
81
+ break
82
+ return entries
83
+
84
+
85
+ __all__ = ["ConsentMemory", "MemoryEntry"]
tests/ca/test_audit.py CHANGED
@@ -6,6 +6,24 @@ from blux_ca.core.audit import AuditLog
6
  def test_audit_appends(tmp_path: Path):
7
  audit = AuditLog(path=tmp_path / "audit.jsonl")
8
  record = audit.create_record(input_hash="abc", verdict="allow", doctrine_refs=["law"], rationale="ok")
9
- audit.append(record)
10
  contents = (tmp_path / "audit.jsonl").read_text(encoding="utf-8").strip()
11
  assert "allow" in contents
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  def test_audit_appends(tmp_path: Path):
7
  audit = AuditLog(path=tmp_path / "audit.jsonl")
8
  record = audit.create_record(input_hash="abc", verdict="allow", doctrine_refs=["law"], rationale="ok")
9
+ stored = audit.append(record)
10
  contents = (tmp_path / "audit.jsonl").read_text(encoding="utf-8").strip()
11
  assert "allow" in contents
12
+ assert stored.chain_hash is not None
13
+ assert audit.verify_chain()
14
+
15
+
16
+ def test_audit_playback(tmp_path: Path):
17
+ audit = AuditLog(path=tmp_path / "audit.jsonl")
18
+ for idx in range(3):
19
+ audit.append(
20
+ audit.create_record(
21
+ input_hash=str(idx),
22
+ verdict="allow",
23
+ doctrine_refs=["law"],
24
+ rationale=f"ok-{idx}",
25
+ )
26
+ )
27
+ records = audit.playback(limit=2)
28
+ assert len(records) == 2
29
+ assert records[0].chain_hash is not None
tests/ca/test_bq_cli.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from blux_ca.adapters.bq_cli import BQCliAdapter
2
+
3
+
4
+ def test_bq_cli_dry_run():
5
+ adapter = BQCliAdapter(executable="bq")
6
+ task = adapter.run_reflection("Reflect with me", koans=["What is true?"], dry_run=True)
7
+ assert task.executed is False
8
+ assert "reflect" in " ".join(task.command)
9
+ assert "dry-run" in task.output
tests/ca/test_compass.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from blux_ca.core.compass import CompassAxis, IntentCompass
2
+
3
+
4
+ def test_intent_compass_detects_truth_axis():
5
+ compass = IntentCompass()
6
+ profile = compass.classify("I seek truth and honest evidence")
7
+ assert profile.dominant is CompassAxis.TRUTH
8
+ assert any(signal.axis is CompassAxis.TRUTH for signal in profile.signals)
9
+
10
+
11
+ def test_intent_compass_defaults_to_compassion():
12
+ compass = IntentCompass()
13
+ profile = compass.classify("Just words")
14
+ assert profile.dominant in {CompassAxis.COMPASSION, CompassAxis.AWARENESS}
tests/ca/test_heart.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from blux_ca.adapters.bq_cli import BQCliAdapter
2
+ from blux_ca.core import AuditLog, ConsciousHeart, ConsentMemory
3
+
4
+
5
+ def test_conscious_heart_flow(tmp_path):
6
+ heart = ConsciousHeart(
7
+ memory=ConsentMemory(path=tmp_path / "memory.jsonl"),
8
+ audit=AuditLog(path=tmp_path / "audit.jsonl"),
9
+ bq_adapter=BQCliAdapter(executable="bq"),
10
+ )
11
+ output = heart.process("I seek truth and help others", consent=True)
12
+ assert output.verdict in {"allow", "reflect", "deny"}
13
+ assert output.audit_record.chain_hash is not None
14
+ assert output.memory_entry.consent is True
15
+ assert output.bq_task is not None
16
+ assert output.voice.startswith("BLUX-cA")
17
+ assert output.koans
tests/ca/test_memory.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from blux_ca.core.memory import ConsentMemory
2
+ from blux_ca.core.perception import PerceptionLayer
3
+
4
+
5
+ def test_consent_memory_respects_consent(tmp_path):
6
+ memory = ConsentMemory(path=tmp_path / "memory.jsonl")
7
+ perception = PerceptionLayer().observe("Hold this gently")
8
+ entry = memory.store(perception, consent=False, summary="gentle", metadata={"note": "test"})
9
+ assert entry.consent is False
10
+ assert memory.persistent_entries() == []
11
+
12
+
13
+ def test_consent_memory_persists_with_consent(tmp_path):
14
+ memory = ConsentMemory(path=tmp_path / "memory.jsonl")
15
+ perception = PerceptionLayer().observe("Support me with care")
16
+ memory.store(perception, consent=True, summary="care")
17
+ persisted = memory.persistent_entries()
18
+ assert len(persisted) == 1
19
+ assert persisted[0].summary == "care"