@heretek-ai/epistemic-swarm 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/brainstorming/SKILL.md +13 -0
- package/.agents/skills/code_audit/SKILL.md +13 -0
- package/.agents/skills/epistemic_search/SKILL.md +13 -0
- package/.agents/skills/grilling/SKILL.md +13 -0
- package/.agents/skills/oss_scout/SKILL.md +13 -0
- package/.agents/skills/research_cache/SKILL.md +13 -0
- package/.agents/skills/swarm_config/SKILL.md +13 -0
- package/.claude-plugin/plugin.json +15 -5
- package/.omp/README.md +39 -0
- package/.omp/SYSTEM.md +12 -0
- package/.omp/commands/audit.md +10 -0
- package/.omp/commands/brainstorming.md +12 -0
- package/.omp/commands/grill.md +9 -0
- package/.omp/commands/scout.md +10 -0
- package/.omp/commands/swarm-config.md +10 -0
- package/.omp/commands/swarm.md +9 -0
- package/.omp/hooks/post/epistemic-audit.ts +16 -0
- package/.omp/hooks/pre/epistemic-redirect.ts +21 -0
- package/.omp/prompts/brainstorming.md +8 -0
- package/.omp/prompts/swarm.md +8 -0
- package/MARKETPLACE.md +8 -0
- package/README.md +53 -8
- package/bin/cli.js +27 -3
- package/config/domain_packs/biopharma.json +23 -0
- package/config/domain_packs/legal.json +19 -0
- package/config/domain_packs/quant.json +19 -0
- package/config/mcp-research-servers.json +7 -0
- package/config/mcp_launcher.py +48 -137
- package/config/opencode-snippet.json +58 -3
- package/config/searxng_mcp.py +42 -83
- package/extensions/pi/index.js +196 -28
- package/install.sh +20 -4
- package/package.json +40 -5
- package/plugins/antigravity/README.md +28 -0
- package/plugins/antigravity/agents/alpha-thesis.md +6 -0
- package/plugins/antigravity/agents/beta-antithesis.md +7 -0
- package/plugins/antigravity/agents/brainstormer.md +7 -0
- package/plugins/antigravity/agents/epistemic-auditor.md +5 -0
- package/plugins/antigravity/hooks.json +23 -0
- package/plugins/antigravity/mcp_config.json +33 -0
- package/plugins/antigravity/plugin.json +21 -0
- package/plugins/antigravity/rules/epistemic-integrity.md +6 -0
- package/plugins/antigravity/skills/brainstorming/SKILL.md +13 -0
- package/plugins/antigravity/skills/code_audit/SKILL.md +13 -0
- package/plugins/antigravity/skills/epistemic_search/SKILL.md +13 -0
- package/plugins/antigravity/skills/grilling/SKILL.md +13 -0
- package/plugins/antigravity/skills/oss_scout/SKILL.md +13 -0
- package/plugins/antigravity/skills/research_cache/SKILL.md +13 -0
- package/plugins/antigravity/skills/swarm_config/SKILL.md +13 -0
- package/plugins/codex/AGENTS.md.snippet +10 -0
- package/plugins/codex/README.md +37 -0
- package/plugins/codex/config.toml.snippet +28 -0
- package/plugins/codex/openai.yaml +24 -0
- package/plugins/codex/skills/brainstorming/SKILL.md +13 -0
- package/plugins/codex/skills/code_audit/SKILL.md +13 -0
- package/plugins/codex/skills/epistemic_search/SKILL.md +13 -0
- package/plugins/codex/skills/grilling/SKILL.md +13 -0
- package/plugins/codex/skills/oss_scout/SKILL.md +13 -0
- package/plugins/codex/skills/research_cache/SKILL.md +13 -0
- package/plugins/codex/skills/swarm_config/SKILL.md +13 -0
- package/plugins/gemini/GEMINI.md +15 -0
- package/plugins/gemini/README.md +19 -0
- package/plugins/gemini/commands/audit.toml +6 -0
- package/plugins/gemini/commands/brainstorming.toml +10 -0
- package/plugins/gemini/commands/grill.toml +6 -0
- package/plugins/gemini/commands/scout.toml +7 -0
- package/plugins/gemini/commands/swarm-config.toml +7 -0
- package/plugins/gemini/commands/swarm.toml +8 -0
- package/plugins/gemini/gemini-extension.json +38 -0
- package/plugins/gemini/hooks/hooks.json +11 -0
- package/plugins/gemini/skills/brainstorming/SKILL.md +13 -0
- package/plugins/gemini/skills/code_audit/SKILL.md +13 -0
- package/plugins/gemini/skills/epistemic_search/SKILL.md +13 -0
- package/plugins/gemini/skills/grilling/SKILL.md +13 -0
- package/plugins/gemini/skills/oss_scout/SKILL.md +13 -0
- package/plugins/gemini/skills/research_cache/SKILL.md +13 -0
- package/plugins/gemini/skills/swarm_config/SKILL.md +13 -0
- package/plugins/opencode/index.js +335 -118
- package/prompts/agent_brainstormer.md +97 -0
- package/runner/__pycache__/__init__.cpython-311.pyc +0 -0
- package/runner/__pycache__/auctioneer.cpython-311.pyc +0 -0
- package/runner/__pycache__/auditor_engine.cpython-311.pyc +0 -0
- package/runner/__pycache__/claim_store.cpython-311.pyc +0 -0
- package/runner/__pycache__/claim_witness.cpython-311.pyc +0 -0
- package/runner/__pycache__/living_dossiers.cpython-311.pyc +0 -0
- package/runner/__pycache__/mcp_protocol.cpython-311.pyc +0 -0
- package/runner/__pycache__/mcp_server.cpython-311.pyc +0 -0
- package/runner/__pycache__/pcrb.cpython-311.pyc +0 -0
- package/runner/__pycache__/pcrb_verify.cpython-311.pyc +0 -0
- package/runner/__pycache__/refinement.cpython-311.pyc +0 -0
- package/runner/__pycache__/research_swarm.cpython-311.pyc +0 -0
- package/runner/__pycache__/state_machine.cpython-311.pyc +0 -0
- package/runner/auctioneer.py +169 -0
- package/runner/auditor_engine.py +127 -12
- package/runner/claim_store.py +361 -0
- package/runner/claim_witness.py +183 -0
- package/runner/living_dossiers.py +355 -0
- package/runner/mcp_protocol.py +188 -0
- package/runner/mcp_server.py +567 -0
- package/runner/pcrb.py +212 -0
- package/runner/pcrb_verify.py +237 -0
- package/runner/refinement.py +335 -0
- package/runner/research_swarm.py +378 -94
- package/runner/tests/__pycache__/test_auction_order.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_claim_store.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_claim_witness.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_domain_packs.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_fleet_seam.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_living_dossiers.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_mcp_server.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_pcrb.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_refinement.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_swarm.cpython-311.pyc +0 -0
- package/runner/tests/fixtures/auction_objective.json +35 -0
- package/runner/tests/fixtures/borderline_claims.json +27 -0
- package/runner/tests/fixtures/divergence_objectives.json +31 -0
- package/runner/tests/test_auction_order.py +173 -0
- package/runner/tests/test_claim_store.py +162 -0
- package/runner/tests/test_claim_witness.py +186 -0
- package/runner/tests/test_domain_packs.py +217 -0
- package/runner/tests/test_fleet_seam.py +113 -0
- package/runner/tests/test_living_dossiers.py +204 -0
- package/runner/tests/test_mcp_server.py +212 -0
- package/runner/tests/test_pcrb.py +240 -0
- package/runner/tests/test_refinement.py +255 -0
- package/runner/tests/test_swarm.py +173 -15
- package/scripts/auction_experiment.py +180 -0
- package/scripts/build_adapters.py +183 -0
- package/scripts/divergence_experiment.py +184 -0
- package/skills/brainstorming/SKILL.md +106 -0
- package/skills/brainstorming/__init__.py +1 -0
- package/skills/brainstorming/scripts/brainstorm.py +200 -0
- package/skills/research_cache/__pycache__/__init__.cpython-311.pyc +0 -0
- package/skills/research_cache/__pycache__/hasher.cpython-311.pyc +0 -0
- package/skills/swarm_config/SKILL.md +1 -1
- package/skills/swarm_config/__pycache__/__init__.cpython-311.pyc +0 -0
- package/skills/swarm_config/__pycache__/configure.cpython-311.pyc +0 -0
- package/skills/swarm_config/configure.py +45 -11
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Frontier Markets: scope auctioneer (Stream F).
|
|
4
|
+
|
|
5
|
+
Replaces static top-down DAG ordering with expected-information-gain bidding.
|
|
6
|
+
Each ready scope carries a bid; the runner picks the highest bid first and
|
|
7
|
+
records per-scope telemetry (tokens_used, verified_claims) so the allocation
|
|
8
|
+
can be scored afterwards.
|
|
9
|
+
|
|
10
|
+
Bid v1 heuristic (deterministic, zero-cost):
|
|
11
|
+
bid = explicit_bid if the scope/manifest carries one (orchestrator may emit
|
|
12
|
+
a "bid" field — prompt schema extension only, no behavior
|
|
13
|
+
change for existing runs)
|
|
14
|
+
else bid = unresolved_frontier_questions * (1 / (1 + dependency_depth))
|
|
15
|
+
|
|
16
|
+
Rationale: questions whose prerequisites are settled but still open are the
|
|
17
|
+
most information-dense work; deeper-dependency scopes are less ready to pay off
|
|
18
|
+
now. Explicit bids win so a human/orchestrator can override the heuristic.
|
|
19
|
+
|
|
20
|
+
Allocation modes live in config `allocation`: "dag" (default, legacy
|
|
21
|
+
behavior) or "auction". See runner/research_swarm.py.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
|
28
|
+
|
|
29
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
30
|
+
PROJECT_ROOT = SCRIPT_DIR.parent
|
|
31
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
32
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def load_frontier(base_dir: Path) -> Dict[str, Any]:
|
|
36
|
+
"""Load .research/frontier.json if present (Socratic grilling output)."""
|
|
37
|
+
path = Path(base_dir) / "frontier.json"
|
|
38
|
+
if not path.exists():
|
|
39
|
+
return {}
|
|
40
|
+
try:
|
|
41
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
42
|
+
data = json.load(f)
|
|
43
|
+
return data if isinstance(data, dict) else {}
|
|
44
|
+
except (OSError, json.JSONDecodeError):
|
|
45
|
+
return {}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def unresolved_frontier_questions(frontier: Dict[str, Any]) -> int:
|
|
49
|
+
"""Count unsettled frontier nodes in a grilling frontier document."""
|
|
50
|
+
if not frontier:
|
|
51
|
+
return 0
|
|
52
|
+
nodes = frontier.get("nodes") or {}
|
|
53
|
+
if isinstance(nodes, dict):
|
|
54
|
+
return sum(1 for n in nodes.values() if isinstance(n, dict) and not n.get("settled_answer"))
|
|
55
|
+
if isinstance(nodes, list):
|
|
56
|
+
return sum(1 for n in nodes if isinstance(n, dict) and not n.get("settled_answer"))
|
|
57
|
+
return 0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def dependency_depth(scope: Dict[str, Any], all_scopes: Optional[Iterable[Dict[str, Any]]] = None) -> int:
|
|
61
|
+
"""Longest dependency chain ending at this scope (0 if no deps)."""
|
|
62
|
+
by_id = {s.get("scope_id"): s for s in (all_scopes or []) if isinstance(s, dict)}
|
|
63
|
+
|
|
64
|
+
def depth_of(scope_id: str, seen: frozenset) -> int:
|
|
65
|
+
s = by_id.get(scope_id)
|
|
66
|
+
if not s or scope_id in seen:
|
|
67
|
+
return 0
|
|
68
|
+
deps = s.get("dependencies") or []
|
|
69
|
+
if not deps:
|
|
70
|
+
return 0
|
|
71
|
+
return 1 + max(depth_of(d, seen | {scope_id}) for d in deps)
|
|
72
|
+
|
|
73
|
+
return depth_of(scope.get("scope_id", ""), frozenset())
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def bid_scope(
|
|
77
|
+
scope: Dict[str, Any],
|
|
78
|
+
all_scopes: Optional[Iterable[Dict[str, Any]]] = None,
|
|
79
|
+
frontier: Optional[Dict[str, Any]] = None,
|
|
80
|
+
base_dir: Optional[Path] = None,
|
|
81
|
+
) -> float:
|
|
82
|
+
"""Expected-information-gain bid for one scope. Deterministic."""
|
|
83
|
+
explicit = scope.get("bid")
|
|
84
|
+
if explicit is not None:
|
|
85
|
+
try:
|
|
86
|
+
return float(explicit)
|
|
87
|
+
except (TypeError, ValueError):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
if frontier is None:
|
|
91
|
+
frontier = load_frontier(base_dir) if base_dir is not None else {}
|
|
92
|
+
open_qs = unresolved_frontier_questions(frontier)
|
|
93
|
+
depth = dependency_depth(scope, all_scopes)
|
|
94
|
+
|
|
95
|
+
# No frontier data: fall back to shallow-dependency preference so the
|
|
96
|
+
# auction still differentiates ordering.
|
|
97
|
+
if open_qs <= 0:
|
|
98
|
+
return 1.0 / (1.0 + depth)
|
|
99
|
+
return float(open_qs) * (1.0 / (1.0 + depth))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def score_scopes(
|
|
103
|
+
scopes: List[Dict[str, Any]],
|
|
104
|
+
frontier: Optional[Dict[str, Any]] = None,
|
|
105
|
+
base_dir: Optional[Path] = None,
|
|
106
|
+
) -> List[Tuple[float, Dict[str, Any]]]:
|
|
107
|
+
"""Return (bid, scope) pairs sorted by descending bid, then scope_id."""
|
|
108
|
+
scored = [
|
|
109
|
+
(bid_scope(s, all_scopes=scopes, frontier=frontier, base_dir=base_dir), s)
|
|
110
|
+
for s in scopes
|
|
111
|
+
]
|
|
112
|
+
scored.sort(key=lambda pair: (-pair[0], str(pair[1].get("scope_id", ""))))
|
|
113
|
+
return scored
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def pick_next(
|
|
117
|
+
scopes: List[Dict[str, Any]],
|
|
118
|
+
budget: Optional[float] = None,
|
|
119
|
+
frontier: Optional[Dict[str, Any]] = None,
|
|
120
|
+
base_dir: Optional[Path] = None,
|
|
121
|
+
) -> Optional[Dict[str, Any]]:
|
|
122
|
+
"""Pick the highest-bid scope under an optional budget cap."""
|
|
123
|
+
for bid, scope in score_scopes(scopes, frontier=frontier, base_dir=base_dir):
|
|
124
|
+
if budget is None or bid <= budget:
|
|
125
|
+
return scope
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def order_scope_ids(
|
|
130
|
+
scopes: List[Dict[str, Any]],
|
|
131
|
+
frontier: Optional[Dict[str, Any]] = None,
|
|
132
|
+
base_dir: Optional[Path] = None,
|
|
133
|
+
) -> List[str]:
|
|
134
|
+
"""Convenience: just the ordered scope_ids."""
|
|
135
|
+
return [s.get("scope_id", "") for _b, s in score_scopes(scopes, frontier=frontier, base_dir=base_dir)]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ---- telemetry ---------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def estimate_tokens(text: str) -> int:
|
|
142
|
+
"""Rough token estimate (chars/4). FLAGGED APPROXIMATION, not a measurement."""
|
|
143
|
+
return max(1, len(text) // 4)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def record_scope_telemetry(
|
|
147
|
+
base_dir: Path,
|
|
148
|
+
scope_id: str,
|
|
149
|
+
tokens_used: int,
|
|
150
|
+
verified_claims: int,
|
|
151
|
+
bid: Optional[float] = None,
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Persist auction telemetry into the scope manifest (optional fields)."""
|
|
154
|
+
path = Path(base_dir) / "scratchpads" / scope_id / "manifest.json"
|
|
155
|
+
if not path.exists():
|
|
156
|
+
return
|
|
157
|
+
try:
|
|
158
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
159
|
+
manifest = json.load(f)
|
|
160
|
+
except (OSError, json.JSONDecodeError):
|
|
161
|
+
return
|
|
162
|
+
telemetry = manifest.get("telemetry") or {}
|
|
163
|
+
telemetry["tokens_used"] = tokens_used
|
|
164
|
+
telemetry["verified_claims"] = verified_claims
|
|
165
|
+
if bid is not None:
|
|
166
|
+
telemetry["bid"] = bid
|
|
167
|
+
manifest["telemetry"] = telemetry
|
|
168
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
169
|
+
json.dump(manifest, f, indent=2)
|
package/runner/auditor_engine.py
CHANGED
|
@@ -15,6 +15,7 @@ from typing import Dict, Any, List, Tuple, Optional
|
|
|
15
15
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
16
16
|
from skills.research_cache.hasher import SourceHasher
|
|
17
17
|
from runner.state_machine import ResearchStateMachine, ScopeStatus
|
|
18
|
+
from runner.refinement import compute_epistemic_score
|
|
18
19
|
|
|
19
20
|
class EpistemicAuditorEngine:
|
|
20
21
|
def __init__(self, base_dir: Optional[Path] = None):
|
|
@@ -22,8 +23,18 @@ class EpistemicAuditorEngine:
|
|
|
22
23
|
self.hasher = SourceHasher(base_dir=self.base_dir)
|
|
23
24
|
self.state_machine = ResearchStateMachine(base_dir=self.base_dir)
|
|
24
25
|
|
|
25
|
-
def audit_scope(self, scope_id: str) -> Dict[str, Any]:
|
|
26
|
-
"""Runs the audit pipeline on a scope with ready dossiers.
|
|
26
|
+
def audit_scope(self, scope_id: str, constitution: Optional[Any] = None) -> Dict[str, Any]:
|
|
27
|
+
"""Runs the audit pipeline on a scope with ready dossiers.
|
|
28
|
+
|
|
29
|
+
`constitution` (Stream G) is an optional Domain Pack. Omitting it
|
|
30
|
+
preserves legacy behavior exactly (flat 1.0 weights, 0.65 threshold,
|
|
31
|
+
no domain/tag rules). Passing one enables tier-weighted scoring and
|
|
32
|
+
the pack's per-claim gates (banned domains, mandatory tags,
|
|
33
|
+
retraction policy).
|
|
34
|
+
"""
|
|
35
|
+
from runner.refinement import LEGACY_CONSTITUTION
|
|
36
|
+
|
|
37
|
+
constitution = constitution or LEGACY_CONSTITUTION
|
|
27
38
|
scope_dir = self.state_machine.get_scope_dir(scope_id)
|
|
28
39
|
alpha_file = scope_dir / "alpha_dossier.json"
|
|
29
40
|
beta_file = scope_dir / "beta_dossier.json"
|
|
@@ -40,17 +51,17 @@ class EpistemicAuditorEngine:
|
|
|
40
51
|
|
|
41
52
|
# 1. Audit Alpha Claims
|
|
42
53
|
alpha_results, alpha_verified, alpha_rejected = self._verify_claims(
|
|
43
|
-
alpha_dossier.get("affirmative_claims", [])
|
|
54
|
+
alpha_dossier.get("affirmative_claims", []), constitution=constitution
|
|
44
55
|
)
|
|
45
56
|
|
|
46
57
|
# 2. Audit Beta Claims
|
|
47
58
|
beta_results, beta_verified, beta_rejected = self._verify_claims(
|
|
48
|
-
beta_dossier.get("falsification_claims", [])
|
|
59
|
+
beta_dossier.get("falsification_claims", []), constitution=constitution
|
|
49
60
|
)
|
|
50
61
|
|
|
51
62
|
total_verified = alpha_verified + beta_verified
|
|
52
63
|
total_rejected = alpha_rejected + beta_rejected
|
|
53
|
-
|
|
64
|
+
|
|
54
65
|
# Negative knowledge counts
|
|
55
66
|
neg_knowledge_alpha = len(alpha_dossier.get("negative_knowledge", []))
|
|
56
67
|
neg_knowledge_beta = len(beta_dossier.get("negative_knowledge", []))
|
|
@@ -59,10 +70,71 @@ class EpistemicAuditorEngine:
|
|
|
59
70
|
total_inferred = len(alpha_dossier.get("inferred_implications", []))
|
|
60
71
|
total_hypotheses = len(beta_dossier.get("hypotheses", []))
|
|
61
72
|
|
|
62
|
-
# 3. Calculate Epistemic Score
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
# 3. Calculate Epistemic Score via the pure refinement function.
|
|
74
|
+
# NOTE (docs/impl mismatch, deliberate): docs/SYSTEM_ARCHITECTURE.md
|
|
75
|
+
# §1.2 publishes tier-weighted V(c_i); the runtime has always scored
|
|
76
|
+
# flat 1.0 per verified claim. LEGACY_CONSTITUTION preserves that;
|
|
77
|
+
# tier weighting is opt-in via Domain Packs (Stream G).
|
|
78
|
+
if constitution is not LEGACY_CONSTITUTION:
|
|
79
|
+
from runner.claim_witness import STATUS_REJECTED, claims_from_dossier
|
|
80
|
+
from runner.refinement import compute_epistemic_score_from_claims
|
|
81
|
+
|
|
82
|
+
results_by_id = {
|
|
83
|
+
r["claim_id"]: r for r in (alpha_results + beta_results)
|
|
84
|
+
}
|
|
85
|
+
score_claims = claims_from_dossier(alpha_dossier) + claims_from_dossier(beta_dossier)
|
|
86
|
+
for c in score_claims:
|
|
87
|
+
r = results_by_id.get(c.claim_id)
|
|
88
|
+
if r is not None and r.get("audited_tag") == "UNVERIFIED_REJECTED":
|
|
89
|
+
# Reflect the auditor's verdict, not the dossier's claim.
|
|
90
|
+
c.tag = "UNVERIFIED_REJECTED"
|
|
91
|
+
c.status = STATUS_REJECTED
|
|
92
|
+
epistemic_score, _breakdown = compute_epistemic_score_from_claims(
|
|
93
|
+
score_claims, constitution=constitution
|
|
94
|
+
)
|
|
95
|
+
else:
|
|
96
|
+
epistemic_score, _breakdown = compute_epistemic_score({
|
|
97
|
+
"verified": total_verified,
|
|
98
|
+
"rejected": total_rejected,
|
|
99
|
+
"inferred": total_inferred,
|
|
100
|
+
"hypotheses": total_hypotheses,
|
|
101
|
+
"neg_knowledge": total_neg_knowledge,
|
|
102
|
+
})
|
|
103
|
+
accept_threshold = constitution.accept_threshold
|
|
104
|
+
|
|
105
|
+
# 3b. Living Dossiers (Stream C): a VERIFIED claim is a time-bounded
|
|
106
|
+
# loan against its source. Join against retraction events —
|
|
107
|
+
# RETRACTED => STALE even when the quote still matches locally.
|
|
108
|
+
degradation = {"events": [], "degraded_scopes": []}
|
|
109
|
+
try:
|
|
110
|
+
from runner.living_dossiers import (
|
|
111
|
+
apply_degradation,
|
|
112
|
+
append_ledger,
|
|
113
|
+
load_retractions,
|
|
114
|
+
queue_requeue,
|
|
115
|
+
)
|
|
116
|
+
from runner.claim_witness import claims_from_dossier
|
|
117
|
+
|
|
118
|
+
retractions = load_retractions(self.base_dir)
|
|
119
|
+
if retractions:
|
|
120
|
+
all_claims = (
|
|
121
|
+
claims_from_dossier(alpha_dossier)
|
|
122
|
+
+ claims_from_dossier(beta_dossier)
|
|
123
|
+
)
|
|
124
|
+
_degraded, events = apply_degradation(
|
|
125
|
+
all_claims, retractions, scope_id=scope_id
|
|
126
|
+
)
|
|
127
|
+
if events:
|
|
128
|
+
append_ledger(self.base_dir, events)
|
|
129
|
+
queue_requeue(
|
|
130
|
+
self.base_dir, scope_id, reason="claim degradation (retraction event)"
|
|
131
|
+
)
|
|
132
|
+
degradation = {
|
|
133
|
+
"events": [e.to_dict() for e in events],
|
|
134
|
+
"degraded_scopes": [scope_id] if events else [],
|
|
135
|
+
}
|
|
136
|
+
except Exception as exc: # noqa: BLE001 - degradation must not break audit
|
|
137
|
+
print(f"[auditor] living-dossiers pass skipped: {exc}", file=sys.stderr)
|
|
66
138
|
|
|
67
139
|
# 4. Calculate Divergence Score
|
|
68
140
|
divergence_score, divergence_matrix = self._compute_divergence(alpha_dossier, beta_dossier)
|
|
@@ -79,11 +151,12 @@ class EpistemicAuditorEngine:
|
|
|
79
151
|
"negative_knowledge_count": total_neg_knowledge,
|
|
80
152
|
"epistemic_score": epistemic_score,
|
|
81
153
|
"divergence_score": divergence_score,
|
|
82
|
-
"verdict": "CERTIFIED" if epistemic_score >=
|
|
154
|
+
"verdict": "CERTIFIED" if epistemic_score >= accept_threshold else "WARNING_LOW_GROUNDING"
|
|
83
155
|
},
|
|
84
156
|
"alpha_claims_audit": alpha_results,
|
|
85
157
|
"beta_claims_audit": beta_results,
|
|
86
|
-
"divergence_matrix": divergence_matrix
|
|
158
|
+
"divergence_matrix": divergence_matrix,
|
|
159
|
+
"degradation": degradation
|
|
87
160
|
}
|
|
88
161
|
|
|
89
162
|
# Save audit_report.json
|
|
@@ -105,11 +178,36 @@ class EpistemicAuditorEngine:
|
|
|
105
178
|
|
|
106
179
|
return audit_report
|
|
107
180
|
|
|
108
|
-
def _verify_claims(
|
|
181
|
+
def _verify_claims(
|
|
182
|
+
self,
|
|
183
|
+
claims: List[Dict[str, Any]],
|
|
184
|
+
constitution: Optional[Any] = None,
|
|
185
|
+
) -> Tuple[List[Dict[str, Any]], int, int]:
|
|
109
186
|
audited_claims = []
|
|
110
187
|
verified_count = 0
|
|
111
188
|
rejected_count = 0
|
|
112
189
|
|
|
190
|
+
# Domain Pack gate (Stream G): per-claim rules beyond quote matching
|
|
191
|
+
# (banned domains, mandatory tags, zero-tolerance retraction). Legacy
|
|
192
|
+
# constitution adds no rules, so default behavior is unchanged.
|
|
193
|
+
claim_gate = None
|
|
194
|
+
retracted_hashes = None
|
|
195
|
+
if constitution is not None:
|
|
196
|
+
from runner.claim_witness import normalize_claim
|
|
197
|
+
from runner.living_dossiers import load_retractions
|
|
198
|
+
from runner.refinement import claim_verdict
|
|
199
|
+
|
|
200
|
+
retractions = load_retractions(self.base_dir)
|
|
201
|
+
retracted_hashes = {
|
|
202
|
+
h for h, ev in retractions.items() if ev.event == "RETRACTED"
|
|
203
|
+
}
|
|
204
|
+
claim_gate = lambda raw: claim_verdict( # noqa: E731
|
|
205
|
+
normalize_claim(raw),
|
|
206
|
+
hasher=self.hasher,
|
|
207
|
+
constitution=constitution,
|
|
208
|
+
retracted_hashes=retracted_hashes,
|
|
209
|
+
)
|
|
210
|
+
|
|
113
211
|
for claim in claims:
|
|
114
212
|
cid = claim.get("claim_id", "UNKNOWN")
|
|
115
213
|
shash = claim.get("source_hash", "")
|
|
@@ -129,6 +227,23 @@ class EpistemicAuditorEngine:
|
|
|
129
227
|
continue
|
|
130
228
|
|
|
131
229
|
passed, conf, msg = self.hasher.verify_quote(shash, quote)
|
|
230
|
+
|
|
231
|
+
if passed and claim_gate is not None:
|
|
232
|
+
verdict = claim_gate(claim)
|
|
233
|
+
if verdict["verdict"] == "REJECTED":
|
|
234
|
+
audited_claims.append({
|
|
235
|
+
"claim_id": cid,
|
|
236
|
+
"statement": statement,
|
|
237
|
+
"original_tag": claim.get("tag", "VERIFIED"),
|
|
238
|
+
"audited_tag": "UNVERIFIED_REJECTED",
|
|
239
|
+
"source_hash": shash,
|
|
240
|
+
"rejected_quote": quote,
|
|
241
|
+
"reason": "; ".join(verdict["reasons"]),
|
|
242
|
+
"confidence": conf
|
|
243
|
+
})
|
|
244
|
+
rejected_count += 1
|
|
245
|
+
continue
|
|
246
|
+
|
|
132
247
|
if passed:
|
|
133
248
|
audited_claims.append({
|
|
134
249
|
"claim_id": cid,
|