@restormel/reasoning-core 0.1.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +54 -0
  4. package/dist/constitution/evaluator.d.ts +30 -0
  5. package/dist/constitution/evaluator.d.ts.map +1 -0
  6. package/dist/constitution/evaluator.js +444 -0
  7. package/dist/constitution/rules.d.ts +3 -0
  8. package/dist/constitution/rules.d.ts.map +1 -0
  9. package/dist/constitution/rules.js +91 -0
  10. package/dist/context.d.ts +36 -0
  11. package/dist/context.d.ts.map +1 -0
  12. package/dist/context.js +1 -0
  13. package/dist/extraction.d.ts +6 -0
  14. package/dist/extraction.d.ts.map +1 -0
  15. package/dist/extraction.js +59 -0
  16. package/dist/index.d.ts +8 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +6 -0
  19. package/dist/meta-block.d.ts +10 -0
  20. package/dist/meta-block.d.ts.map +1 -0
  21. package/dist/meta-block.js +24 -0
  22. package/dist/pipeline.d.ts +68 -0
  23. package/dist/pipeline.d.ts.map +1 -0
  24. package/dist/pipeline.js +96 -0
  25. package/dist/prompts/constitution-eval.d.ts +8 -0
  26. package/dist/prompts/constitution-eval.d.ts.map +1 -0
  27. package/dist/prompts/constitution-eval.js +45 -0
  28. package/dist/prompts/reasoning-eval.d.ts +4 -0
  29. package/dist/prompts/reasoning-eval.d.ts.map +1 -0
  30. package/dist/prompts/reasoning-eval.js +36 -0
  31. package/dist/prompts/verification-extraction.d.ts +4 -0
  32. package/dist/prompts/verification-extraction.d.ts.map +1 -0
  33. package/dist/prompts/verification-extraction.js +46 -0
  34. package/dist/reasoning-eval.d.ts +15 -0
  35. package/dist/reasoning-eval.d.ts.map +1 -0
  36. package/dist/reasoning-eval.js +69 -0
  37. package/package.json +53 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog — @restormel/reasoning-core
2
+
3
+ ## 0.1.0 — 2026-06-01
4
+
5
+ - Initial extraction from SOPHIA Verify pipeline (Phase 3 suite migration).
6
+ - `extractClaims`, `evaluateReasoning`, constitution evaluator + rules.
7
+ - `runVerificationPipeline` with host-injected `ReasoningCoreContext` and optional `runPassOutputs` hook.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adam Boon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # @restormel/reasoning-core
2
+
3
+ Knowledge **Verify** pipeline — claim extraction, reasoning quality evaluation, and epistemic constitution checks. Host apps inject LLM routing and `generateText` via **`ReasoningCoreContext`** (no SOPHIA vertex, DB, or engine imports).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @restormel/reasoning-core @restormel/contracts
9
+ ```
10
+
11
+ Peer: `ai` (Vercel AI SDK) when wiring real models in the host adapter.
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import {
17
+ runVerificationPipeline,
18
+ type ReasoningCoreContext,
19
+ } from "@restormel/reasoning-core";
20
+
21
+ const ctx: ReasoningCoreContext = {
22
+ generateText: (params) => hostGenerateText(params),
23
+ resolveExtractionRoute: (opts) => hostResolveExtraction(opts),
24
+ resolveReasoningRoute: (opts) => hostResolveReasoning(opts),
25
+ trackTokens: (input, output) => hostTrackTokens(input, output),
26
+ };
27
+
28
+ const result = await runVerificationPipeline(
29
+ { text: "Argument to verify…" },
30
+ {
31
+ ctx,
32
+ includePassOutputs: false,
33
+ }
34
+ );
35
+ ```
36
+
37
+ When `includePassOutputs` is true, pass a host hook (e.g. SOPHIA `runDomainAgnosticReasoning`):
38
+
39
+ ```ts
40
+ await runVerificationPipeline(request, {
41
+ ctx,
42
+ runPassOutputs: (inputText, callbacks, opts) =>
43
+ runDomainAgnosticReasoning(inputText, callbacks, opts),
44
+ });
45
+ ```
46
+
47
+ ## Publish
48
+
49
+ Tag **`platform-v*`** in the restormel-keys monorepo — see [docs/restormel/SUITE-ARCHITECTURE-MIGRATION.md](../../docs/restormel/SUITE-ARCHITECTURE-MIGRATION.md) Phase 3.
50
+
51
+ ## Related
52
+
53
+ - Types/schemas: `@restormel/contracts/verification`, `@restormel/contracts/constitution`
54
+ - SOPHIA consumer adapter: `src/lib/server/verification/sophiaAdapter.ts` in the sophia repo
@@ -0,0 +1,30 @@
1
+ import type { ConstitutionalCheck, RuleEvaluation } from "@restormel/contracts/constitution";
2
+ import type { ExtractedClaim, ExtractedRelation } from "@restormel/contracts/verification";
3
+ import type { ReasoningCoreContext } from "../context.js";
4
+ export interface ConstitutionEvaluationTelemetry {
5
+ constitution_input_tokens: number;
6
+ constitution_output_tokens: number;
7
+ constitution_llm_called: boolean;
8
+ constitution_llm_failed: boolean;
9
+ constitution_rule_violations: string[];
10
+ constitution_provider?: string;
11
+ constitution_model?: string;
12
+ constitution_route_reason?: string | null;
13
+ }
14
+ export interface ConstitutionEvaluationResult {
15
+ check: ConstitutionalCheck;
16
+ telemetry: ConstitutionEvaluationTelemetry;
17
+ }
18
+ export declare function checkEvidenceRequirement(claims: ExtractedClaim[], relations: ExtractedRelation[]): RuleEvaluation;
19
+ export declare function checkContradictionAwareness(claims: ExtractedClaim[], relations: ExtractedRelation[]): RuleEvaluation;
20
+ export declare function checkScopeDiscipline(claims: ExtractedClaim[], relations: ExtractedRelation[]): RuleEvaluation;
21
+ export declare function checkCorrelationVsCausation(claims: ExtractedClaim[], relations: ExtractedRelation[]): RuleEvaluation;
22
+ export declare function checkNormativeBridge(claims: ExtractedClaim[], relations: ExtractedRelation[]): RuleEvaluation;
23
+ export declare function checkSourceDiversity(claims: ExtractedClaim[], relations: ExtractedRelation[]): RuleEvaluation;
24
+ export declare function evaluateConstitution(claims: ExtractedClaim[], relations: ExtractedRelation[], originalText: string, ctx: ReasoningCoreContext, options?: {
25
+ providerApiKeys?: unknown;
26
+ }): Promise<ConstitutionalCheck>;
27
+ export declare function evaluateConstitutionWithTelemetry(claims: ExtractedClaim[], relations: ExtractedRelation[], originalText: string, ctx: ReasoningCoreContext, options?: {
28
+ providerApiKeys?: unknown;
29
+ }): Promise<ConstitutionEvaluationResult>;
30
+ //# sourceMappingURL=evaluator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/constitution/evaluator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,mBAAmB,EAAoB,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAE1H,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AA2C1D,MAAM,WAAW,+BAA+B;IAC9C,yBAAyB,EAAE,MAAM,CAAC;IAClC,0BAA0B,EAAE,MAAM,CAAC;IACnC,uBAAuB,EAAE,OAAO,CAAC;IACjC,uBAAuB,EAAE,OAAO,CAAC;IACjC,4BAA4B,EAAE,MAAM,EAAE,CAAC;IACvC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,mBAAmB,CAAC;IAC3B,SAAS,EAAE,+BAA+B,CAAC;CAC5C;AAgRD,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,cAAc,EAAE,EACxB,SAAS,EAAE,iBAAiB,EAAE,GAC7B,cAAc,CAkChB;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,cAAc,EAAE,EACxB,SAAS,EAAE,iBAAiB,EAAE,GAC7B,cAAc,CAuDhB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,EAAE,EACxB,SAAS,EAAE,iBAAiB,EAAE,GAC7B,cAAc,CAuDhB;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,cAAc,EAAE,EACxB,SAAS,EAAE,iBAAiB,EAAE,GAC7B,cAAc,CAgEhB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,EAAE,EACxB,SAAS,EAAE,iBAAiB,EAAE,GAC7B,cAAc,CAkFhB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,EAAE,EACxB,SAAS,EAAE,iBAAiB,EAAE,GAC7B,cAAc,CA0DhB;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,cAAc,EAAE,EACxB,SAAS,EAAE,iBAAiB,EAAE,EAC9B,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,oBAAoB,EACzB,OAAO,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GACtC,OAAO,CAAC,mBAAmB,CAAC,CAG9B;AAED,wBAAsB,iCAAiC,CACrD,MAAM,EAAE,cAAc,EAAE,EACxB,SAAS,EAAE,iBAAiB,EAAE,EAC9B,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,oBAAoB,EACzB,OAAO,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GACtC,OAAO,CAAC,4BAA4B,CAAC,CA4BvC"}
@@ -0,0 +1,444 @@
1
+ import { z } from "zod";
2
+ import { ConstitutionalCheckSchema, RuleEvaluationSchema } from "@restormel/contracts/constitution";
3
+ import { buildConstitutionEvalUserPrompt, CONSTITUTION_EVAL_SYSTEM_PROMPT, } from "../prompts/constitution-eval.js";
4
+ import { EPISTEMIC_RULES } from "./rules.js";
5
+ const RULE_INDEX = new Map(EPISTEMIC_RULES.map((rule) => [rule.id, rule]));
6
+ const LLM_RULE_IDS = new Set([
7
+ 'proportional_evidence',
8
+ 'alternative_hypotheses',
9
+ 'assumption_transparency',
10
+ 'uncertainty_signalling'
11
+ ]);
12
+ const ACKNOWLEDGEMENT_RELATIONS = new Set(['qualifies', 'refines']);
13
+ const CAUSAL_MECHANISM_TYPES = new Set(['causal', 'explanatory']);
14
+ const DESCRIPTIVE_TYPES = new Set(['empirical', 'causal', 'predictive', 'explanatory']);
15
+ const SCOPE_RANK = {
16
+ narrow: 0,
17
+ moderate: 1,
18
+ broad: 2,
19
+ universal: 3
20
+ };
21
+ const LlmRuleEvaluationSchema = z.object({
22
+ rule_id: z.string().min(1),
23
+ status: z.enum(['satisfied', 'violated', 'uncertain', 'not_applicable']),
24
+ affected_claim_ids: z.array(z.string()).default([]),
25
+ rationale: z.string().min(1),
26
+ remediation: z.string().min(1).optional()
27
+ });
28
+ function getRule(ruleId) {
29
+ const rule = RULE_INDEX.get(ruleId);
30
+ if (!rule) {
31
+ throw new Error(`Unknown epistemic rule: ${ruleId}`);
32
+ }
33
+ return rule;
34
+ }
35
+ function createEvaluation(rule, status, affectedClaimIds, rationale, remediation) {
36
+ return RuleEvaluationSchema.parse({
37
+ rule_id: rule.id,
38
+ rule_name: rule.name,
39
+ status,
40
+ severity: rule.severity,
41
+ affected_claim_ids: [...new Set(affectedClaimIds)],
42
+ rationale,
43
+ remediation
44
+ });
45
+ }
46
+ function claimsById(claims) {
47
+ return new Map(claims.map((claim) => [claim.id, claim]));
48
+ }
49
+ function incomingSupporters(claimId, relations, byId) {
50
+ return relations
51
+ .filter((relation) => relation.relation_type === 'supports' && relation.to_claim_id === claimId)
52
+ .map((relation) => byId.get(relation.from_claim_id))
53
+ .filter((claim) => Boolean(claim));
54
+ }
55
+ function hasCitation(claim) {
56
+ if (claim.source_span?.trim())
57
+ return true;
58
+ return typeof claim.source_span_start === 'number' && typeof claim.source_span_end === 'number';
59
+ }
60
+ function sourceKey(claim) {
61
+ if (claim.source_span?.trim()) {
62
+ return `span:${claim.source_span.trim().toLowerCase()}`;
63
+ }
64
+ if (typeof claim.source_span_start === 'number' && typeof claim.source_span_end === 'number') {
65
+ return `offset:${claim.source_span_start}-${claim.source_span_end}`;
66
+ }
67
+ return 'unknown';
68
+ }
69
+ function extractJsonArray(text) {
70
+ const trimmed = text.trim();
71
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
72
+ return trimmed;
73
+ }
74
+ const fencedMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
75
+ if (fencedMatch) {
76
+ return fencedMatch[1].trim();
77
+ }
78
+ const firstBracket = trimmed.indexOf('[');
79
+ const lastBracket = trimmed.lastIndexOf(']');
80
+ if (firstBracket !== -1 && lastBracket !== -1 && lastBracket > firstBracket) {
81
+ return trimmed.slice(firstBracket, lastBracket + 1);
82
+ }
83
+ throw new Error('Could not find JSON array in LLM response');
84
+ }
85
+ function parseLlmEvaluations(text) {
86
+ const raw = JSON.parse(extractJsonArray(text));
87
+ return z.array(LlmRuleEvaluationSchema).parse(raw);
88
+ }
89
+ function fallbackLlmEvaluations(rules, reason) {
90
+ return rules.map((rule) => createEvaluation(rule, 'uncertain', [], `LLM constitutional evaluation unavailable: ${reason}`));
91
+ }
92
+ function finalizeEvaluations(allEvaluations) {
93
+ const ordered = EPISTEMIC_RULES.map((rule) => {
94
+ return (allEvaluations.find((evaluation) => evaluation.rule_id === rule.id) ??
95
+ createEvaluation(rule, 'uncertain', [], 'Rule evaluation missing from constitution pipeline.'));
96
+ });
97
+ const satisfied = ordered.filter((evaluation) => evaluation.status === 'satisfied');
98
+ const violated = ordered.filter((evaluation) => evaluation.status === 'violated');
99
+ const uncertain = ordered.filter((evaluation) => evaluation.status === 'uncertain');
100
+ const notApplicable = ordered.filter((evaluation) => evaluation.status === 'not_applicable');
101
+ const criticalViolations = violated.filter((evaluation) => evaluation.severity === 'critical').length;
102
+ const warningViolations = violated.filter((evaluation) => evaluation.severity === 'warning').length;
103
+ const overallCompliance = criticalViolations > 0 ? 'fail' : warningViolations > 1 ? 'partial' : 'pass';
104
+ return ConstitutionalCheckSchema.parse({
105
+ rules_evaluated: ordered.length,
106
+ satisfied,
107
+ violated,
108
+ uncertain,
109
+ not_applicable: notApplicable,
110
+ overall_compliance: overallCompliance
111
+ });
112
+ }
113
+ function isRuleApplicable(rule, claimType) {
114
+ return rule.applies_to.includes(claimType);
115
+ }
116
+ function buildDependencyGraph(relations) {
117
+ const graph = new Map();
118
+ for (const relation of relations) {
119
+ if (relation.relation_type !== 'depends_on')
120
+ continue;
121
+ const current = graph.get(relation.from_claim_id) ?? [];
122
+ current.push(relation.to_claim_id);
123
+ graph.set(relation.from_claim_id, current);
124
+ }
125
+ return graph;
126
+ }
127
+ function collectDependencies(rootClaimId, graph) {
128
+ const visited = new Set([rootClaimId]);
129
+ const collected = new Set();
130
+ const queue = [...(graph.get(rootClaimId) ?? [])];
131
+ while (queue.length > 0) {
132
+ const next = queue.shift();
133
+ if (!next)
134
+ continue;
135
+ if (visited.has(next))
136
+ continue;
137
+ visited.add(next);
138
+ collected.add(next);
139
+ const children = graph.get(next);
140
+ if (!children) {
141
+ continue;
142
+ }
143
+ for (const child of children) {
144
+ if (!visited.has(child)) {
145
+ queue.push(child);
146
+ }
147
+ }
148
+ }
149
+ return [...collected];
150
+ }
151
+ function collectRule2Heuristics(claims, relations) {
152
+ const rule = getRule('proportional_evidence');
153
+ const byId = claimsById(claims);
154
+ return claims
155
+ .filter((claim) => isRuleApplicable(rule, claim.claim_type))
156
+ .filter((claim) => claim.scope === 'broad' || claim.scope === 'universal')
157
+ .filter((claim) => incomingSupporters(claim.id, relations, byId).length < 2)
158
+ .map((claim) => claim.id);
159
+ }
160
+ async function evaluateLlmRules(claims, relations, originalText, ctx, options) {
161
+ const llmRules = EPISTEMIC_RULES.filter((rule) => LLM_RULE_IDS.has(rule.id));
162
+ if (llmRules.length === 0) {
163
+ return {
164
+ evaluations: [],
165
+ inputTokens: 0,
166
+ outputTokens: 0,
167
+ llmCalled: false,
168
+ llmFailed: false
169
+ };
170
+ }
171
+ const heuristics = {
172
+ proportional_evidence_low_support_claim_ids: collectRule2Heuristics(claims, relations)
173
+ };
174
+ const prompt = buildConstitutionEvalUserPrompt(claims, relations, llmRules, originalText, heuristics);
175
+ const route = await ctx.resolveReasoningRoute({
176
+ depthMode: "standard",
177
+ providerApiKeys: options?.providerApiKeys,
178
+ failureMode: "error",
179
+ });
180
+ try {
181
+ const result = await ctx.generateText({
182
+ model: route.model,
183
+ system: CONSTITUTION_EVAL_SYSTEM_PROMPT,
184
+ prompt,
185
+ maxOutputTokens: 1400,
186
+ });
187
+ ctx.trackTokens?.(result.usage?.inputTokens ?? 0, result.usage?.outputTokens ?? 0);
188
+ const parsed = parseLlmEvaluations(result.text);
189
+ const byRuleId = new Map(parsed.map((evaluation) => [evaluation.rule_id, evaluation]));
190
+ const evaluations = llmRules.map((rule) => {
191
+ const raw = byRuleId.get(rule.id);
192
+ if (!raw) {
193
+ return createEvaluation(rule, 'uncertain', [], 'LLM response omitted this rule from the batch output.');
194
+ }
195
+ return createEvaluation(rule, raw.status, raw.affected_claim_ids, raw.rationale, raw.status === 'violated'
196
+ ? (raw.remediation ?? `Revise claims to satisfy ${rule.name.toLowerCase()}.`)
197
+ : undefined);
198
+ });
199
+ return {
200
+ evaluations,
201
+ inputTokens: result.usage?.inputTokens ?? 0,
202
+ outputTokens: result.usage?.outputTokens ?? 0,
203
+ llmCalled: true,
204
+ llmFailed: false,
205
+ provider: route.provider,
206
+ modelId: route.modelId,
207
+ routeReason: route.resolvedExplanation ?? null
208
+ };
209
+ }
210
+ catch (error) {
211
+ return {
212
+ evaluations: fallbackLlmEvaluations(llmRules, error instanceof Error ? error.message : String(error)),
213
+ inputTokens: 0,
214
+ outputTokens: 0,
215
+ llmCalled: true,
216
+ llmFailed: true,
217
+ provider: route.provider,
218
+ modelId: route.modelId,
219
+ routeReason: route.resolvedExplanation ?? null
220
+ };
221
+ }
222
+ }
223
+ export function checkEvidenceRequirement(claims, relations) {
224
+ const rule = getRule('evidence_requirement');
225
+ const byId = claimsById(claims);
226
+ const applicableClaims = claims.filter((claim) => isRuleApplicable(rule, claim.claim_type));
227
+ if (applicableClaims.length === 0) {
228
+ return createEvaluation(rule, 'not_applicable', [], 'No empirical, causal, or predictive claims were extracted.');
229
+ }
230
+ const unsupported = applicableClaims
231
+ .filter((claim) => incomingSupporters(claim.id, relations, byId).length === 0 && !hasCitation(claim))
232
+ .map((claim) => claim.id);
233
+ if (unsupported.length > 0) {
234
+ return createEvaluation(rule, 'violated', unsupported, 'Some factual claims have neither supporting relations nor citation evidence.', 'Add supporting evidence relations or explicit citation passages for each flagged claim.');
235
+ }
236
+ return createEvaluation(rule, 'satisfied', applicableClaims.map((claim) => claim.id), 'All factual claims include support relations or citation evidence.');
237
+ }
238
+ export function checkContradictionAwareness(claims, relations) {
239
+ const rule = getRule('contradiction_awareness');
240
+ const byId = claimsById(claims);
241
+ const contradictions = relations.filter((relation) => {
242
+ if (relation.relation_type !== 'contradicts')
243
+ return false;
244
+ const from = byId.get(relation.from_claim_id);
245
+ const to = byId.get(relation.to_claim_id);
246
+ if (!from || !to)
247
+ return false;
248
+ return isRuleApplicable(rule, from.claim_type) || isRuleApplicable(rule, to.claim_type);
249
+ });
250
+ if (contradictions.length === 0) {
251
+ return createEvaluation(rule, 'not_applicable', [], 'No contradiction relations were extracted for applicable claim types.');
252
+ }
253
+ const unaddressed = contradictions.filter((contradiction) => {
254
+ return !relations.some((relation) => {
255
+ if (!ACKNOWLEDGEMENT_RELATIONS.has(relation.relation_type))
256
+ return false;
257
+ const sameDirection = relation.from_claim_id === contradiction.from_claim_id &&
258
+ relation.to_claim_id === contradiction.to_claim_id;
259
+ const reverseDirection = relation.from_claim_id === contradiction.to_claim_id &&
260
+ relation.to_claim_id === contradiction.from_claim_id;
261
+ return sameDirection || reverseDirection;
262
+ });
263
+ });
264
+ if (unaddressed.length > 0) {
265
+ const affected = unaddressed.flatMap((relation) => [relation.from_claim_id, relation.to_claim_id]);
266
+ return createEvaluation(rule, 'violated', affected, 'Contradiction relations exist but are not reconciled or qualified in the argument graph.', 'Explicitly address contradictory claims by qualifying, refining, or resolving the tension.');
267
+ }
268
+ return createEvaluation(rule, 'satisfied', contradictions.flatMap((relation) => [relation.from_claim_id, relation.to_claim_id]), 'Detected contradictions are explicitly acknowledged through qualifying/refining links.');
269
+ }
270
+ export function checkScopeDiscipline(claims, relations) {
271
+ const rule = getRule('scope_discipline');
272
+ const byId = claimsById(claims);
273
+ const applicableClaims = claims.filter((claim) => isRuleApplicable(rule, claim.claim_type));
274
+ if (applicableClaims.length === 0) {
275
+ return createEvaluation(rule, 'not_applicable', [], 'No empirical, causal, or predictive claims were extracted.');
276
+ }
277
+ const exceededScope = [];
278
+ const insufficientSupport = [];
279
+ for (const claim of applicableClaims) {
280
+ const supporters = incomingSupporters(claim.id, relations, byId);
281
+ if (supporters.length === 0) {
282
+ insufficientSupport.push(claim.id);
283
+ continue;
284
+ }
285
+ const maxSupportScope = Math.max(...supporters.map((supporter) => SCOPE_RANK[supporter.scope]));
286
+ if (SCOPE_RANK[claim.scope] > maxSupportScope) {
287
+ exceededScope.push(claim.id);
288
+ }
289
+ }
290
+ if (exceededScope.length > 0) {
291
+ return createEvaluation(rule, 'violated', exceededScope, 'At least one claim has broader scope than its supporting evidence.', 'Narrow the scope of flagged claims or add broader evidence before making universal statements.');
292
+ }
293
+ if (insufficientSupport.length > 0) {
294
+ return createEvaluation(rule, 'uncertain', insufficientSupport, 'Some claims lack enough support to determine whether scope is disciplined.');
295
+ }
296
+ return createEvaluation(rule, 'satisfied', applicableClaims.map((claim) => claim.id), 'Claim scope is consistent with the scope of supporting evidence.');
297
+ }
298
+ export function checkCorrelationVsCausation(claims, relations) {
299
+ const rule = getRule('correlation_vs_causation');
300
+ const byId = claimsById(claims);
301
+ const causalClaims = claims.filter((claim) => isRuleApplicable(rule, claim.claim_type));
302
+ if (causalClaims.length === 0) {
303
+ return createEvaluation(rule, 'not_applicable', [], 'No causal claims were extracted.');
304
+ }
305
+ const violated = [];
306
+ const insufficient = [];
307
+ for (const claim of causalClaims) {
308
+ const supporters = incomingSupporters(claim.id, relations, byId);
309
+ if (supporters.length === 0) {
310
+ insufficient.push(claim.id);
311
+ continue;
312
+ }
313
+ const allSupportersEmpirical = supporters.every((supporter) => supporter.claim_type === 'empirical');
314
+ const hasMechanismSupport = relations.some((relation) => {
315
+ if (relation.to_claim_id !== claim.id)
316
+ return false;
317
+ if (relation.relation_type === 'contradicts')
318
+ return false;
319
+ const source = byId.get(relation.from_claim_id);
320
+ if (!source)
321
+ return false;
322
+ return CAUSAL_MECHANISM_TYPES.has(source.claim_type);
323
+ });
324
+ if (allSupportersEmpirical && !hasMechanismSupport) {
325
+ violated.push(claim.id);
326
+ }
327
+ }
328
+ if (violated.length > 0) {
329
+ return createEvaluation(rule, 'violated', violated, 'Some causal claims are supported only by correlational evidence without mechanism claims.', 'Add causal mechanism claims or weaken causal wording to reflect correlational support.');
330
+ }
331
+ if (insufficient.length > 0) {
332
+ return createEvaluation(rule, 'uncertain', insufficient, 'Some causal claims lack enough structured support to assess correlation-vs-causation risk.');
333
+ }
334
+ return createEvaluation(rule, 'satisfied', causalClaims.map((claim) => claim.id), 'Causal claims include mechanism-level support or non-correlational evidence.');
335
+ }
336
+ export function checkNormativeBridge(claims, relations) {
337
+ const rule = getRule('normative_bridge_requirement');
338
+ const byId = claimsById(claims);
339
+ const dependencyGraph = buildDependencyGraph(relations);
340
+ const normativeClaims = claims.filter((claim) => isRuleApplicable(rule, claim.claim_type));
341
+ if (normativeClaims.length === 0) {
342
+ return createEvaluation(rule, 'not_applicable', [], 'No normative claims were extracted.');
343
+ }
344
+ const violated = [];
345
+ const uncertain = [];
346
+ for (const claim of normativeClaims) {
347
+ const directDependencies = dependencyGraph.get(claim.id) ?? [];
348
+ if (directDependencies.length === 0) {
349
+ uncertain.push(claim.id);
350
+ continue;
351
+ }
352
+ const dependencyIds = collectDependencies(claim.id, dependencyGraph);
353
+ const hasMissingNodes = dependencyIds.some((dependencyId) => !byId.has(dependencyId));
354
+ const dependencyClaims = dependencyIds
355
+ .map((dependencyId) => byId.get(dependencyId))
356
+ .filter((c) => Boolean(c));
357
+ if (dependencyClaims.length === 0 || hasMissingNodes) {
358
+ uncertain.push(claim.id);
359
+ continue;
360
+ }
361
+ const hasNormativePremise = dependencyClaims.some((dependencyClaim) => dependencyClaim.claim_type === 'normative');
362
+ if (hasNormativePremise) {
363
+ continue;
364
+ }
365
+ const onlyDescriptive = dependencyClaims.every((dependencyClaim) => DESCRIPTIVE_TYPES.has(dependencyClaim.claim_type));
366
+ const hasEmpiricalOrCausal = dependencyClaims.some((dependencyClaim) => dependencyClaim.claim_type === 'empirical' || dependencyClaim.claim_type === 'causal');
367
+ if (onlyDescriptive && hasEmpiricalOrCausal) {
368
+ violated.push(claim.id);
369
+ }
370
+ else {
371
+ uncertain.push(claim.id);
372
+ }
373
+ }
374
+ if (violated.length > 0) {
375
+ return createEvaluation(rule, 'violated', violated, 'Normative conclusions rely on descriptive premises without an explicit normative bridge.', 'Add at least one explicit normative premise linking descriptive evidence to the normative conclusion.');
376
+ }
377
+ if (uncertain.length > 0) {
378
+ return createEvaluation(rule, 'uncertain', uncertain, 'Some normative claims do not expose enough dependency structure to verify a normative bridge.');
379
+ }
380
+ return createEvaluation(rule, 'satisfied', normativeClaims.map((claim) => claim.id), 'Normative claims include explicit normative premises in their dependency chains.');
381
+ }
382
+ export function checkSourceDiversity(claims, relations) {
383
+ const rule = getRule('source_diversity');
384
+ const byId = claimsById(claims);
385
+ const targetClaims = claims.filter((claim) => {
386
+ if (!isRuleApplicable(rule, claim.claim_type))
387
+ return false;
388
+ return claim.scope === 'broad' || claim.scope === 'universal';
389
+ });
390
+ if (targetClaims.length === 0) {
391
+ return createEvaluation(rule, 'not_applicable', [], 'No broad or universal empirical/causal/predictive claims were extracted.');
392
+ }
393
+ const singleSourceClaims = [];
394
+ const uncertainClaims = [];
395
+ for (const claim of targetClaims) {
396
+ const supporters = incomingSupporters(claim.id, relations, byId);
397
+ if (supporters.length === 0) {
398
+ uncertainClaims.push(claim.id);
399
+ continue;
400
+ }
401
+ const uniqueSources = new Set(supporters.map((supporter) => sourceKey(supporter)));
402
+ if (uniqueSources.size <= 1) {
403
+ singleSourceClaims.push(claim.id);
404
+ }
405
+ }
406
+ if (singleSourceClaims.length > 0) {
407
+ return createEvaluation(rule, 'violated', singleSourceClaims, 'Broad claims are supported by only one identifiable source stream.', 'Add independent supporting evidence from multiple distinct sources.');
408
+ }
409
+ if (uncertainClaims.length > 0) {
410
+ return createEvaluation(rule, 'uncertain', uncertainClaims, 'Some broad claims have no support links, so source diversity cannot be measured.');
411
+ }
412
+ return createEvaluation(rule, 'satisfied', targetClaims.map((claim) => claim.id), 'Broad claims are supported by multiple distinct source streams.');
413
+ }
414
+ export async function evaluateConstitution(claims, relations, originalText, ctx, options) {
415
+ const result = await evaluateConstitutionWithTelemetry(claims, relations, originalText, ctx, options);
416
+ return result.check;
417
+ }
418
+ export async function evaluateConstitutionWithTelemetry(claims, relations, originalText, ctx, options) {
419
+ const deterministicEvaluations = [
420
+ checkEvidenceRequirement(claims, relations),
421
+ checkContradictionAwareness(claims, relations),
422
+ checkScopeDiscipline(claims, relations),
423
+ checkCorrelationVsCausation(claims, relations),
424
+ checkNormativeBridge(claims, relations),
425
+ checkSourceDiversity(claims, relations)
426
+ ];
427
+ const llmBatchResult = await evaluateLlmRules(claims, relations, originalText, ctx, options);
428
+ const allEvaluations = [...deterministicEvaluations, ...llmBatchResult.evaluations];
429
+ const check = finalizeEvaluations(allEvaluations);
430
+ const constitutionRuleViolations = check.violated.map((evaluation) => evaluation.rule_id);
431
+ return {
432
+ check,
433
+ telemetry: {
434
+ constitution_input_tokens: llmBatchResult.inputTokens,
435
+ constitution_output_tokens: llmBatchResult.outputTokens,
436
+ constitution_llm_called: llmBatchResult.llmCalled,
437
+ constitution_llm_failed: llmBatchResult.llmFailed,
438
+ constitution_rule_violations: constitutionRuleViolations,
439
+ constitution_provider: llmBatchResult.provider,
440
+ constitution_model: llmBatchResult.modelId,
441
+ constitution_route_reason: llmBatchResult.routeReason ?? null
442
+ }
443
+ };
444
+ }
@@ -0,0 +1,3 @@
1
+ import type { ConstitutionRule } from "@restormel/contracts/constitution";
2
+ export declare const EPISTEMIC_RULES: ConstitutionRule[];
3
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/constitution/rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAYrF,eAAO,MAAM,eAAe,EAAE,gBAAgB,EAwF7C,CAAC"}
@@ -0,0 +1,91 @@
1
+ const ALL_CLAIM_TYPES = [
2
+ 'empirical',
3
+ 'causal',
4
+ 'explanatory',
5
+ 'normative',
6
+ 'predictive',
7
+ 'definitional',
8
+ 'procedural'
9
+ ];
10
+ export const EPISTEMIC_RULES = [
11
+ {
12
+ id: 'evidence_requirement',
13
+ name: 'Evidence Requirement',
14
+ description: 'A factual or empirical claim must have at least one supporting evidence passage or citation.',
15
+ applies_to: ['empirical', 'causal', 'predictive'],
16
+ severity: 'critical',
17
+ deterministic_check: true
18
+ },
19
+ {
20
+ id: 'proportional_evidence',
21
+ name: 'Proportional Evidence',
22
+ description: 'The strength of a claim must be proportional to the quality and amount of evidence.',
23
+ applies_to: ['empirical', 'causal', 'predictive'],
24
+ severity: 'warning',
25
+ deterministic_check: true
26
+ },
27
+ {
28
+ id: 'contradiction_awareness',
29
+ name: 'Contradiction Awareness',
30
+ description: 'Reasoning must acknowledge credible contradicting evidence when present.',
31
+ applies_to: ['empirical', 'causal', 'explanatory', 'normative'],
32
+ severity: 'critical',
33
+ deterministic_check: true
34
+ },
35
+ {
36
+ id: 'alternative_hypotheses',
37
+ name: 'Alternative Hypotheses',
38
+ description: 'Causal or explanatory claims should consider plausible alternative explanations.',
39
+ applies_to: ['causal', 'explanatory'],
40
+ severity: 'warning',
41
+ deterministic_check: false
42
+ },
43
+ {
44
+ id: 'scope_discipline',
45
+ name: 'Scope Discipline',
46
+ description: 'Claims must not exceed the scope of their evidence.',
47
+ applies_to: ['empirical', 'causal', 'predictive'],
48
+ severity: 'critical',
49
+ deterministic_check: true
50
+ },
51
+ {
52
+ id: 'assumption_transparency',
53
+ name: 'Assumption Transparency',
54
+ description: 'Key hidden assumptions should be surfaced when they materially affect the conclusion.',
55
+ applies_to: ALL_CLAIM_TYPES,
56
+ severity: 'warning',
57
+ deterministic_check: false
58
+ },
59
+ {
60
+ id: 'correlation_vs_causation',
61
+ name: 'Correlation vs Causation',
62
+ description: 'Correlational evidence must not be presented as decisive causal proof.',
63
+ applies_to: ['causal'],
64
+ severity: 'critical',
65
+ deterministic_check: true
66
+ },
67
+ {
68
+ id: 'uncertainty_signalling',
69
+ name: 'Uncertainty Signalling',
70
+ description: 'Weak or mixed evidence should be reflected in the synthesis tone.',
71
+ applies_to: ALL_CLAIM_TYPES,
72
+ severity: 'warning',
73
+ deterministic_check: false
74
+ },
75
+ {
76
+ id: 'normative_bridge_requirement',
77
+ name: 'Normative Bridge Requirement',
78
+ description: 'Normative conclusions must not follow directly from descriptive facts alone.',
79
+ applies_to: ['normative'],
80
+ severity: 'critical',
81
+ deterministic_check: true
82
+ },
83
+ {
84
+ id: 'source_diversity',
85
+ name: 'Source Diversity',
86
+ description: 'Verification should not rely on a single source when broader support is needed.',
87
+ applies_to: ['empirical', 'causal', 'predictive'],
88
+ severity: 'info',
89
+ deterministic_check: true
90
+ }
91
+ ];