@heretek-ai/epistemic-swarm 0.7.11 → 0.7.13
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/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/research-cache/skills/research_cache/hasher.py +56 -14
- package/prompts/agent_brainstormer.md +14 -0
- package/prompts/agent_darkharvest.md +20 -1
- 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__/darkharvest_claims.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__/path_safety.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__/retrieval_log.cpython-311.pyc +0 -0
- package/runner/__pycache__/state_machine.cpython-311.pyc +0 -0
- package/runner/auditor_engine.py +267 -80
- package/runner/darkharvest_claims.py +326 -0
- package/runner/refinement.py +74 -0
- package/runner/research_swarm.py +119 -24
- package/runner/retrieval_log.py +90 -0
- package/runner/state_machine.py +35 -0
- package/runner/tests/__pycache__/test_auction_order.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_backends.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_bet1_spike.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_claude_plugin.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_darkharvest_audit.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_domain_packs.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_dossier_contract.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_factory.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_fleet_seam.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_grounding.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_living_dossiers.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_manifest_concurrency.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_mcp_server.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_opencode_ux.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_opencode_v2_registrars.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/__pycache__/test_swarm_config.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_sweep_regressions.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_webcache.cpython-311.pyc +0 -0
- package/runner/tests/test_darkharvest_audit.py +290 -0
- package/runner/tests/test_grounding.py +183 -0
- package/scripts/__pycache__/bet1_advisory_spike.cpython-311.pyc +0 -0
- package/scripts/__pycache__/build_adapters.cpython-311.pyc +0 -0
- package/scripts/__pycache__/divergence_experiment.cpython-311.pyc +0 -0
- package/skills/epistemic_search/scripts/__pycache__/search.cpython-311.pyc +0 -0
- package/skills/epistemic_search/scripts/search.py +16 -0
- package/skills/epistemic_search/scripts/webcache.py +11 -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/research_cache/hasher.py +56 -14
- package/skills/swarm_config/__pycache__/__init__.cpython-311.pyc +0 -0
- package/skills/swarm_config/__pycache__/configure.cpython-311.pyc +0 -0
package/runner/auditor_engine.py
CHANGED
|
@@ -5,6 +5,7 @@ Verifies quote authenticity against content-addressed source cache,
|
|
|
5
5
|
calculates divergence metrics, prunes ungrounded claims, and generates synthesis reports.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import os
|
|
8
9
|
import sys
|
|
9
10
|
import json
|
|
10
11
|
from pathlib import Path
|
|
@@ -17,6 +18,38 @@ from skills.research_cache.hasher import SourceHasher
|
|
|
17
18
|
from runner.state_machine import ResearchStateMachine, ScopeStatus
|
|
18
19
|
from runner.refinement import compute_epistemic_score
|
|
19
20
|
|
|
21
|
+
from runner.darkharvest_claims import (
|
|
22
|
+
RepoValidator,
|
|
23
|
+
cross_check_repos,
|
|
24
|
+
normalize_dossier_claims,
|
|
25
|
+
sanitize_self_reported,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _repo_validation_enabled() -> bool:
|
|
30
|
+
return os.environ.get("IUMBTEMS_REPO_VALIDATE", "1").strip().lower() not in (
|
|
31
|
+
"0",
|
|
32
|
+
"false",
|
|
33
|
+
"no",
|
|
34
|
+
"off",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _mark_rejected_claims(score_claims: list, *result_lists: list) -> None:
|
|
39
|
+
"""Mirror the auditor's UNVERIFIED_REJECTED verdict onto ClaimWitness records."""
|
|
40
|
+
from runner.claim_witness import STATUS_REJECTED
|
|
41
|
+
|
|
42
|
+
results_by_id: Dict[str, Any] = {}
|
|
43
|
+
for results in result_lists:
|
|
44
|
+
for r in results:
|
|
45
|
+
results_by_id[r["claim_id"]] = r
|
|
46
|
+
for c in score_claims:
|
|
47
|
+
r = results_by_id.get(c.claim_id)
|
|
48
|
+
if r is not None and r.get("audited_tag") == "UNVERIFIED_REJECTED":
|
|
49
|
+
c.tag = "UNVERIFIED_REJECTED"
|
|
50
|
+
c.status = STATUS_REJECTED
|
|
51
|
+
|
|
52
|
+
|
|
20
53
|
def _compute_scope_epistemic_score(
|
|
21
54
|
constitution: Any,
|
|
22
55
|
alpha_dossier: Dict[str, Any],
|
|
@@ -24,20 +57,32 @@ def _compute_scope_epistemic_score(
|
|
|
24
57
|
alpha_results: list,
|
|
25
58
|
beta_results: list,
|
|
26
59
|
counts: Dict[str, int],
|
|
60
|
+
mode: Optional[str] = None,
|
|
27
61
|
) -> float:
|
|
28
62
|
from runner.refinement import LEGACY_CONSTITUTION
|
|
29
63
|
|
|
64
|
+
if mode == "brainstorm":
|
|
65
|
+
# Lateral ideation is scored on well-formed speculation, not web quotes.
|
|
66
|
+
from runner.claim_witness import claims_from_dossier
|
|
67
|
+
from runner.refinement import compute_brainstorm_score_from_claims
|
|
68
|
+
|
|
69
|
+
score_claims = claims_from_dossier(alpha_dossier) + claims_from_dossier(
|
|
70
|
+
beta_dossier
|
|
71
|
+
)
|
|
72
|
+
_mark_rejected_claims(score_claims, alpha_results, beta_results)
|
|
73
|
+
epistemic_score, _breakdown = compute_brainstorm_score_from_claims(
|
|
74
|
+
score_claims, constitution=constitution
|
|
75
|
+
)
|
|
76
|
+
return epistemic_score
|
|
77
|
+
|
|
30
78
|
if constitution is not LEGACY_CONSTITUTION:
|
|
31
|
-
from runner.claim_witness import
|
|
79
|
+
from runner.claim_witness import claims_from_dossier
|
|
32
80
|
from runner.refinement import compute_epistemic_score_from_claims
|
|
33
81
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if r is not None and r.get("audited_tag") == "UNVERIFIED_REJECTED":
|
|
39
|
-
c.tag = "UNVERIFIED_REJECTED"
|
|
40
|
-
c.status = STATUS_REJECTED
|
|
82
|
+
score_claims = claims_from_dossier(alpha_dossier) + claims_from_dossier(
|
|
83
|
+
beta_dossier
|
|
84
|
+
)
|
|
85
|
+
_mark_rejected_claims(score_claims, alpha_results, beta_results)
|
|
41
86
|
epistemic_score, _breakdown = compute_epistemic_score_from_claims(
|
|
42
87
|
score_claims, constitution=constitution
|
|
43
88
|
)
|
|
@@ -65,11 +110,17 @@ def _apply_living_dossiers_degradation(
|
|
|
65
110
|
|
|
66
111
|
retractions = load_retractions(base_dir)
|
|
67
112
|
if retractions:
|
|
68
|
-
all_claims = claims_from_dossier(alpha_dossier) + claims_from_dossier(
|
|
69
|
-
|
|
113
|
+
all_claims = claims_from_dossier(alpha_dossier) + claims_from_dossier(
|
|
114
|
+
beta_dossier
|
|
115
|
+
)
|
|
116
|
+
_degraded, events = apply_degradation(
|
|
117
|
+
all_claims, retractions, scope_id=scope_id
|
|
118
|
+
)
|
|
70
119
|
if events:
|
|
71
120
|
append_ledger(base_dir, events)
|
|
72
|
-
queue_requeue(
|
|
121
|
+
queue_requeue(
|
|
122
|
+
base_dir, scope_id, reason="claim degradation (retraction event)"
|
|
123
|
+
)
|
|
73
124
|
degradation = {
|
|
74
125
|
"events": [e.to_dict() for e in events],
|
|
75
126
|
"degraded_scopes": [scope_id] if events else [],
|
|
@@ -80,19 +131,31 @@ def _apply_living_dossiers_degradation(
|
|
|
80
131
|
|
|
81
132
|
|
|
82
133
|
class EpistemicAuditorEngine:
|
|
83
|
-
def __init__(
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
base_dir: Optional[Path] = None,
|
|
137
|
+
repo_validator: Optional[Any] = None,
|
|
138
|
+
):
|
|
84
139
|
self.base_dir = base_dir or Path(".research")
|
|
85
140
|
self.hasher = SourceHasher(base_dir=self.base_dir)
|
|
86
141
|
self.state_machine = ResearchStateMachine(base_dir=self.base_dir)
|
|
142
|
+
# Ground-truth repo checks for darkharvest; injectable for tests/offline.
|
|
143
|
+
self.repo_validator = repo_validator
|
|
87
144
|
|
|
88
|
-
def audit_scope(
|
|
145
|
+
def audit_scope(
|
|
146
|
+
self,
|
|
147
|
+
scope_id: str,
|
|
148
|
+
constitution: Optional[Any] = None,
|
|
149
|
+
mode: Optional[str] = None,
|
|
150
|
+
) -> Dict[str, Any]:
|
|
89
151
|
"""Runs the audit pipeline on a scope with ready dossiers.
|
|
90
152
|
|
|
91
153
|
`constitution` (Stream G) is an optional Domain Pack. Omitting it
|
|
92
154
|
preserves legacy behavior exactly (flat 1.0 weights, 0.65 threshold,
|
|
93
155
|
no domain/tag rules). Passing one enables tier-weighted scoring and
|
|
94
156
|
the pack's per-claim gates (banned domains, mandatory tags,
|
|
95
|
-
retraction policy).
|
|
157
|
+
retraction policy). `mode` selects the scoring contract — brainstorm
|
|
158
|
+
credits well-formed speculation rather than web-quote grounding.
|
|
96
159
|
"""
|
|
97
160
|
from runner.refinement import LEGACY_CONSTITUTION
|
|
98
161
|
|
|
@@ -109,16 +172,26 @@ class EpistemicAuditorEngine:
|
|
|
109
172
|
with open(beta_file, "r", encoding="utf-8") as f:
|
|
110
173
|
beta_dossier = json.load(f)
|
|
111
174
|
|
|
175
|
+
# Darkharvest dossiers emit `candidate_repositories[]`, not the standard
|
|
176
|
+
# claim keys, and may self-report audit-shaped fields (#5 layers 1 & 3).
|
|
177
|
+
for label, dossier in (("alpha", alpha_dossier), ("beta", beta_dossier)):
|
|
178
|
+
_, renamed = sanitize_self_reported(dossier)
|
|
179
|
+
if renamed:
|
|
180
|
+
print(
|
|
181
|
+
f" [Audit] ignored agent self-reported audit fields "
|
|
182
|
+
f"({label}): {', '.join(sorted(renamed))}"
|
|
183
|
+
)
|
|
184
|
+
|
|
112
185
|
self.state_machine.update_scope_status(scope_id, ScopeStatus.AUDITING)
|
|
113
186
|
|
|
114
|
-
# 1. Audit Alpha Claims
|
|
187
|
+
# 1. Audit Alpha Claims (normalized from either schema)
|
|
115
188
|
alpha_results, alpha_verified, alpha_rejected = self._verify_claims(
|
|
116
|
-
alpha_dossier
|
|
189
|
+
normalize_dossier_claims(alpha_dossier, "alpha"), constitution=constitution
|
|
117
190
|
)
|
|
118
191
|
|
|
119
192
|
# 2. Audit Beta Claims
|
|
120
193
|
beta_results, beta_verified, beta_rejected = self._verify_claims(
|
|
121
|
-
beta_dossier
|
|
194
|
+
normalize_dossier_claims(beta_dossier, "beta"), constitution=constitution
|
|
122
195
|
)
|
|
123
196
|
|
|
124
197
|
counts = {
|
|
@@ -126,14 +199,25 @@ class EpistemicAuditorEngine:
|
|
|
126
199
|
"rejected": alpha_rejected + beta_rejected,
|
|
127
200
|
"inferred": len(alpha_dossier.get("inferred_implications", [])),
|
|
128
201
|
"hypotheses": len(beta_dossier.get("hypotheses", [])),
|
|
129
|
-
"neg_knowledge": len(alpha_dossier.get("negative_knowledge", []))
|
|
202
|
+
"neg_knowledge": len(alpha_dossier.get("negative_knowledge", []))
|
|
203
|
+
+ len(beta_dossier.get("negative_knowledge", [])),
|
|
130
204
|
}
|
|
131
205
|
|
|
132
206
|
# 3. Calculate Epistemic Score via the pure refinement function.
|
|
133
207
|
epistemic_score = _compute_scope_epistemic_score(
|
|
134
|
-
constitution,
|
|
208
|
+
constitution,
|
|
209
|
+
alpha_dossier,
|
|
210
|
+
beta_dossier,
|
|
211
|
+
alpha_results,
|
|
212
|
+
beta_results,
|
|
213
|
+
counts,
|
|
214
|
+
mode=mode,
|
|
135
215
|
)
|
|
136
216
|
accept_threshold = constitution.accept_threshold
|
|
217
|
+
if mode == "brainstorm":
|
|
218
|
+
from runner.refinement import BRAINSTORM_MODE_THRESHOLD
|
|
219
|
+
|
|
220
|
+
accept_threshold = min(accept_threshold, BRAINSTORM_MODE_THRESHOLD)
|
|
137
221
|
|
|
138
222
|
# 3b. Living Dossiers (Stream C): check degradation against retraction events
|
|
139
223
|
degradation = _apply_living_dossiers_degradation(
|
|
@@ -141,7 +225,31 @@ class EpistemicAuditorEngine:
|
|
|
141
225
|
)
|
|
142
226
|
|
|
143
227
|
# 4. Calculate Divergence Score
|
|
144
|
-
divergence_score, divergence_matrix = self._compute_divergence(
|
|
228
|
+
divergence_score, divergence_matrix = self._compute_divergence(
|
|
229
|
+
alpha_dossier, beta_dossier
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# 4b. Darkharvest: license cross-check + repo ground truth. A dialectic
|
|
233
|
+
# that recommends `depend-or-vendor` on an unstated license must not pass
|
|
234
|
+
# as CERTIFIED (observed: MIT vs NOASSERTION for the same repo, #5).
|
|
235
|
+
harvest_findings: List[Dict[str, Any]] = []
|
|
236
|
+
if mode == "darkharvest":
|
|
237
|
+
validator = self.repo_validator
|
|
238
|
+
if validator is None:
|
|
239
|
+
validator = RepoValidator(enabled=_repo_validation_enabled())
|
|
240
|
+
harvest_findings = cross_check_repos(
|
|
241
|
+
alpha_dossier, beta_dossier, validator=validator
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
blocking_findings = [
|
|
245
|
+
f for f in harvest_findings if f.get("severity") == "BLOCKING"
|
|
246
|
+
]
|
|
247
|
+
if blocking_findings:
|
|
248
|
+
verdict = "WARNING_LICENSE_CONFLICT"
|
|
249
|
+
elif epistemic_score >= accept_threshold:
|
|
250
|
+
verdict = "CERTIFIED"
|
|
251
|
+
else:
|
|
252
|
+
verdict = "WARNING_LOW_GROUNDING"
|
|
145
253
|
|
|
146
254
|
# 5. Build Audit Report
|
|
147
255
|
audit_report = {
|
|
@@ -155,12 +263,15 @@ class EpistemicAuditorEngine:
|
|
|
155
263
|
"negative_knowledge_count": counts["neg_knowledge"],
|
|
156
264
|
"epistemic_score": epistemic_score,
|
|
157
265
|
"divergence_score": divergence_score,
|
|
158
|
-
"
|
|
266
|
+
"mode": mode,
|
|
267
|
+
"blocking_findings": len(blocking_findings),
|
|
268
|
+
"verdict": verdict,
|
|
159
269
|
},
|
|
160
270
|
"alpha_claims_audit": alpha_results,
|
|
161
271
|
"beta_claims_audit": beta_results,
|
|
162
272
|
"divergence_matrix": divergence_matrix,
|
|
163
|
-
"
|
|
273
|
+
"harvest_findings": harvest_findings,
|
|
274
|
+
"degradation": degradation,
|
|
164
275
|
}
|
|
165
276
|
|
|
166
277
|
# Save audit_report.json
|
|
@@ -219,14 +330,16 @@ class EpistemicAuditorEngine:
|
|
|
219
330
|
statement = claim.get("statement", "")
|
|
220
331
|
|
|
221
332
|
if not shash or not quote:
|
|
222
|
-
audited_claims.append(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
333
|
+
audited_claims.append(
|
|
334
|
+
{
|
|
335
|
+
"claim_id": cid,
|
|
336
|
+
"statement": statement,
|
|
337
|
+
"original_tag": claim.get("tag", "VERIFIED"),
|
|
338
|
+
"audited_tag": "UNVERIFIED_REJECTED",
|
|
339
|
+
"reason": "Missing source_hash or verbatim_quote",
|
|
340
|
+
"confidence": 0.0,
|
|
341
|
+
}
|
|
342
|
+
)
|
|
230
343
|
rejected_count += 1
|
|
231
344
|
continue
|
|
232
345
|
|
|
@@ -235,47 +348,54 @@ class EpistemicAuditorEngine:
|
|
|
235
348
|
if passed and claim_gate is not None:
|
|
236
349
|
verdict = claim_gate(claim)
|
|
237
350
|
if verdict["verdict"] == "REJECTED":
|
|
238
|
-
audited_claims.append(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
351
|
+
audited_claims.append(
|
|
352
|
+
{
|
|
353
|
+
"claim_id": cid,
|
|
354
|
+
"statement": statement,
|
|
355
|
+
"original_tag": claim.get("tag", "VERIFIED"),
|
|
356
|
+
"audited_tag": "UNVERIFIED_REJECTED",
|
|
357
|
+
"source_hash": shash,
|
|
358
|
+
"rejected_quote": quote,
|
|
359
|
+
"reason": "; ".join(verdict["reasons"]),
|
|
360
|
+
"confidence": conf,
|
|
361
|
+
}
|
|
362
|
+
)
|
|
248
363
|
rejected_count += 1
|
|
249
364
|
continue
|
|
250
365
|
|
|
251
366
|
if passed:
|
|
252
|
-
audited_claims.append(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
367
|
+
audited_claims.append(
|
|
368
|
+
{
|
|
369
|
+
"claim_id": cid,
|
|
370
|
+
"statement": statement,
|
|
371
|
+
"original_tag": "VERIFIED",
|
|
372
|
+
"audited_tag": "VERIFIED",
|
|
373
|
+
"source_hash": shash,
|
|
374
|
+
"confidence": conf,
|
|
375
|
+
"verification_message": msg,
|
|
376
|
+
}
|
|
377
|
+
)
|
|
261
378
|
verified_count += 1
|
|
262
379
|
else:
|
|
263
|
-
audited_claims.append(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
380
|
+
audited_claims.append(
|
|
381
|
+
{
|
|
382
|
+
"claim_id": cid,
|
|
383
|
+
"statement": statement,
|
|
384
|
+
"original_tag": "VERIFIED",
|
|
385
|
+
"audited_tag": "UNVERIFIED_REJECTED",
|
|
386
|
+
"source_hash": shash,
|
|
387
|
+
"rejected_quote": quote,
|
|
388
|
+
"reason": msg,
|
|
389
|
+
"confidence": conf,
|
|
390
|
+
}
|
|
391
|
+
)
|
|
273
392
|
rejected_count += 1
|
|
274
393
|
|
|
275
394
|
return audited_claims, verified_count, rejected_count
|
|
276
395
|
|
|
277
|
-
def _compute_divergence(
|
|
278
|
-
|
|
396
|
+
def _compute_divergence(
|
|
397
|
+
self, alpha_dossier: Dict[str, Any], beta_dossier: Dict[str, Any]
|
|
398
|
+
) -> Tuple[float, List[Dict[str, Any]]]:
|
|
279
399
|
alpha_claims = alpha_dossier.get("affirmative_claims", [])
|
|
280
400
|
beta_claims = beta_dossier.get("falsification_claims", [])
|
|
281
401
|
critiques = beta_dossier.get("methodological_critiques", [])
|
|
@@ -285,12 +405,14 @@ class EpistemicAuditorEngine:
|
|
|
285
405
|
|
|
286
406
|
for crit in critiques:
|
|
287
407
|
target = crit.get("target_assertion", "")
|
|
288
|
-
matrix.append(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
408
|
+
matrix.append(
|
|
409
|
+
{
|
|
410
|
+
"tension_type": "METHODOLOGICAL_CHALLENGE",
|
|
411
|
+
"proponent_claim": target,
|
|
412
|
+
"adversary_critique": crit.get("critique", ""),
|
|
413
|
+
"counter_evidence_hash": crit.get("evidence_hash", "NONE"),
|
|
414
|
+
}
|
|
415
|
+
)
|
|
294
416
|
contradictions += 1
|
|
295
417
|
|
|
296
418
|
# Calculate divergence
|
|
@@ -303,10 +425,14 @@ class EpistemicAuditorEngine:
|
|
|
303
425
|
lines = ["## 1. Verified Empirical Grounding"]
|
|
304
426
|
for claim in alpha_audit:
|
|
305
427
|
if claim["audited_tag"] == "VERIFIED":
|
|
306
|
-
lines.append(
|
|
428
|
+
lines.append(
|
|
429
|
+
f"- `[VERIFIED: {claim['source_hash'][:8]}]` {claim['statement']}"
|
|
430
|
+
)
|
|
307
431
|
for claim in beta_audit:
|
|
308
432
|
if claim["audited_tag"] == "VERIFIED":
|
|
309
|
-
lines.append(
|
|
433
|
+
lines.append(
|
|
434
|
+
f"- `[VERIFIED: {claim['source_hash'][:8]}]` (Counter-Evidence) {claim['statement']}"
|
|
435
|
+
)
|
|
310
436
|
return lines
|
|
311
437
|
|
|
312
438
|
@staticmethod
|
|
@@ -316,26 +442,57 @@ class EpistemicAuditorEngine:
|
|
|
316
442
|
for item in matrix:
|
|
317
443
|
lines.append(f"### Tension: {item['tension_type']}")
|
|
318
444
|
lines.append(f"- **Thesis Assertion**: {item['proponent_claim']}")
|
|
319
|
-
lines.append(
|
|
445
|
+
lines.append(
|
|
446
|
+
f"- **Adversarial Critique**: {item['adversary_critique']}"
|
|
447
|
+
)
|
|
320
448
|
else:
|
|
321
|
-
lines.append(
|
|
449
|
+
lines.append(
|
|
450
|
+
"No active contradictions identified between primary literature and red-team findings."
|
|
451
|
+
)
|
|
322
452
|
return lines
|
|
323
453
|
|
|
324
454
|
@staticmethod
|
|
325
|
-
def _render_rejected_section(
|
|
455
|
+
def _render_rejected_section(
|
|
456
|
+
claims: list, summary: Optional[Dict[str, Any]] = None
|
|
457
|
+
) -> List[str]:
|
|
326
458
|
lines = ["\n## 3. Rejected & Unverified Assertions"]
|
|
327
459
|
rejected = [c for c in claims if c["audited_tag"] == "UNVERIFIED_REJECTED"]
|
|
328
460
|
if rejected:
|
|
329
461
|
for r in rejected:
|
|
330
|
-
lines.append(
|
|
462
|
+
lines.append(
|
|
463
|
+
f'- ⚠️ **PURGED**: "{r["statement"]}" — *Reason: {r["reason"]}*'
|
|
464
|
+
)
|
|
465
|
+
return lines
|
|
466
|
+
|
|
467
|
+
# No rejected claims is only "all verified" when the audit actually
|
|
468
|
+
# verified something. Otherwise this is static text contradicting the
|
|
469
|
+
# verdict (observed in darkharvest: 0 verified + "100% verified", #5).
|
|
470
|
+
summary = summary or {}
|
|
471
|
+
verified = summary.get("verified_passed", 0)
|
|
472
|
+
if summary.get("verdict") == "WARNING_LICENSE_CONFLICT":
|
|
473
|
+
lines.append(
|
|
474
|
+
f"No claims rejected, but {summary.get('blocking_findings', 0)} "
|
|
475
|
+
"blocking license/ground-truth finding(s) require review before acting."
|
|
476
|
+
)
|
|
477
|
+
elif verified:
|
|
478
|
+
lines.append(
|
|
479
|
+
f"No claims rejected. {verified} claim(s) verified against the source cache."
|
|
480
|
+
)
|
|
331
481
|
else:
|
|
332
|
-
lines.append(
|
|
482
|
+
lines.append(
|
|
483
|
+
"No claims rejected — but **0 claims were verified** (empty source "
|
|
484
|
+
"cache). Treat every assertion above as unverified."
|
|
485
|
+
)
|
|
333
486
|
return lines
|
|
334
487
|
|
|
335
488
|
@staticmethod
|
|
336
|
-
def _render_negative_knowledge_section(
|
|
489
|
+
def _render_negative_knowledge_section(
|
|
490
|
+
alpha: Dict[str, Any], beta: Dict[str, Any]
|
|
491
|
+
) -> List[str]:
|
|
337
492
|
lines = ["\n## 4. Negative Knowledge Catalog"]
|
|
338
|
-
all_neg = alpha.get("negative_knowledge", []) + beta.get(
|
|
493
|
+
all_neg = alpha.get("negative_knowledge", []) + beta.get(
|
|
494
|
+
"negative_knowledge", []
|
|
495
|
+
)
|
|
339
496
|
if all_neg:
|
|
340
497
|
for n in all_neg:
|
|
341
498
|
lines.append(f"- `[NEGATIVE_KNOWLEDGE: {n['query']}]` {n['finding']}")
|
|
@@ -343,15 +500,45 @@ class EpistemicAuditorEngine:
|
|
|
343
500
|
lines.append("No negative knowledge declarations logged.")
|
|
344
501
|
return lines
|
|
345
502
|
|
|
346
|
-
def _generate_synthesis_markdown(
|
|
347
|
-
|
|
503
|
+
def _generate_synthesis_markdown(
|
|
504
|
+
self,
|
|
505
|
+
scope_id: str,
|
|
506
|
+
alpha: Dict[str, Any],
|
|
507
|
+
beta: Dict[str, Any],
|
|
508
|
+
audit: Dict[str, Any],
|
|
509
|
+
) -> str:
|
|
348
510
|
summary = audit["summary"]
|
|
349
511
|
md = [
|
|
350
512
|
f"# Epistemic Synthesis: Scope {scope_id}\n",
|
|
351
513
|
f"**Audit Verdict**: `{summary['verdict']}` | **Epistemic Score**: `{summary['epistemic_score']}/1.0` | **Divergence Index**: `{summary['divergence_score']}`\n",
|
|
352
514
|
]
|
|
353
|
-
md.extend(
|
|
515
|
+
md.extend(
|
|
516
|
+
self._render_verified_section(
|
|
517
|
+
audit["alpha_claims_audit"], audit["beta_claims_audit"]
|
|
518
|
+
)
|
|
519
|
+
)
|
|
354
520
|
md.extend(self._render_tensions_section(audit.get("divergence_matrix", [])))
|
|
355
|
-
md.extend(
|
|
521
|
+
md.extend(
|
|
522
|
+
self._render_rejected_section(
|
|
523
|
+
audit["alpha_claims_audit"] + audit["beta_claims_audit"],
|
|
524
|
+
summary,
|
|
525
|
+
)
|
|
526
|
+
)
|
|
356
527
|
md.extend(self._render_negative_knowledge_section(alpha, beta))
|
|
528
|
+
md.extend(self._render_harvest_findings(audit.get("harvest_findings", [])))
|
|
357
529
|
return "\n".join(md)
|
|
530
|
+
|
|
531
|
+
@staticmethod
|
|
532
|
+
def _render_harvest_findings(findings: list) -> List[str]:
|
|
533
|
+
if not findings:
|
|
534
|
+
return []
|
|
535
|
+
lines = ["\n## 5. Harvest Safety Findings"]
|
|
536
|
+
for f in findings:
|
|
537
|
+
icon = "⛔" if f.get("severity") == "BLOCKING" else "⚠️"
|
|
538
|
+
detail = f.get("detail") or f.get("kind", "")
|
|
539
|
+
resolution = f.get("resolution")
|
|
540
|
+
suffix = f" → **{resolution}**" if resolution else ""
|
|
541
|
+
lines.append(
|
|
542
|
+
f"- {icon} `{f.get('repo', '?')}` — {f.get('kind', '')}: {detail}{suffix}"
|
|
543
|
+
)
|
|
544
|
+
return lines
|