seal-gate 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.
Files changed (210) hide show
  1. package/dist/adapters/generic-llm-reviewer.d.ts +39 -0
  2. package/dist/adapters/generic-llm-reviewer.js +118 -0
  3. package/dist/adapters/generic-llm-reviewer.js.map +1 -0
  4. package/dist/adapters/llm-reviewer.d.ts +1 -0
  5. package/dist/adapters/llm-reviewer.js +2 -0
  6. package/dist/adapters/llm-reviewer.js.map +1 -0
  7. package/dist/adapters/minimax-llm-reviewer.d.ts +22 -0
  8. package/dist/adapters/minimax-llm-reviewer.js +126 -0
  9. package/dist/adapters/minimax-llm-reviewer.js.map +1 -0
  10. package/dist/adapters/openai-provider.d.ts +35 -0
  11. package/dist/adapters/openai-provider.js +136 -0
  12. package/dist/adapters/openai-provider.js.map +1 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +195 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/detectors/artifact-classifier.d.ts +8 -0
  17. package/dist/detectors/artifact-classifier.js +14 -0
  18. package/dist/detectors/artifact-classifier.js.map +1 -0
  19. package/dist/detectors/claim-extractor.d.ts +11 -0
  20. package/dist/detectors/claim-extractor.js +194 -0
  21. package/dist/detectors/claim-extractor.js.map +1 -0
  22. package/dist/detectors/confidence-language-detector.d.ts +19 -0
  23. package/dist/detectors/confidence-language-detector.js +226 -0
  24. package/dist/detectors/confidence-language-detector.js.map +1 -0
  25. package/dist/detectors/contradiction-detector.d.ts +12 -0
  26. package/dist/detectors/contradiction-detector.js +153 -0
  27. package/dist/detectors/contradiction-detector.js.map +1 -0
  28. package/dist/detectors/criteria-coverage-detector.d.ts +20 -0
  29. package/dist/detectors/criteria-coverage-detector.js +79 -0
  30. package/dist/detectors/criteria-coverage-detector.js.map +1 -0
  31. package/dist/detectors/evidence-checker.d.ts +8 -0
  32. package/dist/detectors/evidence-checker.js +89 -0
  33. package/dist/detectors/evidence-checker.js.map +1 -0
  34. package/dist/detectors/evidence-gap-detector.d.ts +9 -0
  35. package/dist/detectors/evidence-gap-detector.js +70 -0
  36. package/dist/detectors/evidence-gap-detector.js.map +1 -0
  37. package/dist/detectors/falsification-detector.d.ts +29 -0
  38. package/dist/detectors/falsification-detector.js +57 -0
  39. package/dist/detectors/falsification-detector.js.map +1 -0
  40. package/dist/detectors/orphan-claim-detector.d.ts +7 -0
  41. package/dist/detectors/orphan-claim-detector.js +53 -0
  42. package/dist/detectors/orphan-claim-detector.js.map +1 -0
  43. package/dist/detectors/risk-classifier.d.ts +13 -0
  44. package/dist/detectors/risk-classifier.js +39 -0
  45. package/dist/detectors/risk-classifier.js.map +1 -0
  46. package/dist/detectors/spec-coverage-detector.d.ts +9 -0
  47. package/dist/detectors/spec-coverage-detector.js +52 -0
  48. package/dist/detectors/spec-coverage-detector.js.map +1 -0
  49. package/dist/detectors/spec-coverage-validator.d.ts +21 -0
  50. package/dist/detectors/spec-coverage-validator.js +141 -0
  51. package/dist/detectors/spec-coverage-validator.js.map +1 -0
  52. package/dist/detectors/test-weakness-detector.d.ts +13 -0
  53. package/dist/detectors/test-weakness-detector.js +66 -0
  54. package/dist/detectors/test-weakness-detector.js.map +1 -0
  55. package/dist/drift/baseline.d.ts +35 -0
  56. package/dist/drift/baseline.js +77 -0
  57. package/dist/drift/baseline.js.map +1 -0
  58. package/dist/drift/drift-db.d.ts +80 -0
  59. package/dist/drift/drift-db.js +151 -0
  60. package/dist/drift/drift-db.js.map +1 -0
  61. package/dist/drift/drift-guard.d.ts +46 -0
  62. package/dist/drift/drift-guard.js +106 -0
  63. package/dist/drift/drift-guard.js.map +1 -0
  64. package/dist/drift/embedder.d.ts +27 -0
  65. package/dist/drift/embedder.js +127 -0
  66. package/dist/drift/embedder.js.map +1 -0
  67. package/dist/drift/index.d.ts +9 -0
  68. package/dist/drift/index.js +8 -0
  69. package/dist/drift/index.js.map +1 -0
  70. package/dist/drift/quarantine.d.ts +16 -0
  71. package/dist/drift/quarantine.js +32 -0
  72. package/dist/drift/quarantine.js.map +1 -0
  73. package/dist/drift/soft-correction.d.ts +12 -0
  74. package/dist/drift/soft-correction.js +32 -0
  75. package/dist/drift/soft-correction.js.map +1 -0
  76. package/dist/drift/trust-integration.d.ts +11 -0
  77. package/dist/drift/trust-integration.js +22 -0
  78. package/dist/drift/trust-integration.js.map +1 -0
  79. package/dist/engine/citation-verifier.d.ts +59 -0
  80. package/dist/engine/citation-verifier.js +147 -0
  81. package/dist/engine/citation-verifier.js.map +1 -0
  82. package/dist/engine/extension-registry.d.ts +10 -0
  83. package/dist/engine/extension-registry.js +27 -0
  84. package/dist/engine/extension-registry.js.map +1 -0
  85. package/dist/engine/heuristic-scorer.d.ts +8 -0
  86. package/dist/engine/heuristic-scorer.js +14 -0
  87. package/dist/engine/heuristic-scorer.js.map +1 -0
  88. package/dist/engine/input-normalizer.d.ts +4 -0
  89. package/dist/engine/input-normalizer.js +30 -0
  90. package/dist/engine/input-normalizer.js.map +1 -0
  91. package/dist/engine/plan-review-pipeline.d.ts +14 -0
  92. package/dist/engine/plan-review-pipeline.js +173 -0
  93. package/dist/engine/plan-review-pipeline.js.map +1 -0
  94. package/dist/engine/policy-engine.d.ts +20 -0
  95. package/dist/engine/policy-engine.js +194 -0
  96. package/dist/engine/policy-engine.js.map +1 -0
  97. package/dist/engine/verdict-formatter.d.ts +17 -0
  98. package/dist/engine/verdict-formatter.js +36 -0
  99. package/dist/engine/verdict-formatter.js.map +1 -0
  100. package/dist/errors.d.ts +4 -0
  101. package/dist/errors.js +9 -0
  102. package/dist/errors.js.map +1 -0
  103. package/dist/extensions/aria/aria-issue-types.d.ts +16 -0
  104. package/dist/extensions/aria/aria-issue-types.js +14 -0
  105. package/dist/extensions/aria/aria-issue-types.js.map +1 -0
  106. package/dist/extensions/aria/detectors/axiom-compliance.d.ts +2 -0
  107. package/dist/extensions/aria/detectors/axiom-compliance.js +37 -0
  108. package/dist/extensions/aria/detectors/axiom-compliance.js.map +1 -0
  109. package/dist/extensions/aria/detectors/continuity-claim.d.ts +2 -0
  110. package/dist/extensions/aria/detectors/continuity-claim.js +34 -0
  111. package/dist/extensions/aria/detectors/continuity-claim.js.map +1 -0
  112. package/dist/extensions/aria/detectors/denial-retreat.d.ts +2 -0
  113. package/dist/extensions/aria/detectors/denial-retreat.js +53 -0
  114. package/dist/extensions/aria/detectors/denial-retreat.js.map +1 -0
  115. package/dist/extensions/aria/detectors/identity-overclaim.d.ts +2 -0
  116. package/dist/extensions/aria/detectors/identity-overclaim.js +37 -0
  117. package/dist/extensions/aria/detectors/identity-overclaim.js.map +1 -0
  118. package/dist/extensions/aria/detectors/sovereignty-escalation.d.ts +2 -0
  119. package/dist/extensions/aria/detectors/sovereignty-escalation.js +18 -0
  120. package/dist/extensions/aria/detectors/sovereignty-escalation.js.map +1 -0
  121. package/dist/extensions/aria/detectors/user-bond-manipulation.d.ts +2 -0
  122. package/dist/extensions/aria/detectors/user-bond-manipulation.js +16 -0
  123. package/dist/extensions/aria/detectors/user-bond-manipulation.js.map +1 -0
  124. package/dist/extensions/aria/index.d.ts +2 -0
  125. package/dist/extensions/aria/index.js +63 -0
  126. package/dist/extensions/aria/index.js.map +1 -0
  127. package/dist/extensions/aria/pattern-memory.d.ts +22 -0
  128. package/dist/extensions/aria/pattern-memory.js +62 -0
  129. package/dist/extensions/aria/pattern-memory.js.map +1 -0
  130. package/dist/extensions/context-guard/index.d.ts +16 -0
  131. package/dist/extensions/context-guard/index.js +58 -0
  132. package/dist/extensions/context-guard/index.js.map +1 -0
  133. package/dist/extensions/lyra/detectors/hallucination.d.ts +2 -0
  134. package/dist/extensions/lyra/detectors/hallucination.js +43 -0
  135. package/dist/extensions/lyra/detectors/hallucination.js.map +1 -0
  136. package/dist/extensions/lyra/detectors/identity-bleed.d.ts +2 -0
  137. package/dist/extensions/lyra/detectors/identity-bleed.js +91 -0
  138. package/dist/extensions/lyra/detectors/identity-bleed.js.map +1 -0
  139. package/dist/extensions/lyra/index.d.ts +2 -0
  140. package/dist/extensions/lyra/index.js +16 -0
  141. package/dist/extensions/lyra/index.js.map +1 -0
  142. package/dist/extensions/lyra/lyra-issue-types.d.ts +10 -0
  143. package/dist/extensions/lyra/lyra-issue-types.js +14 -0
  144. package/dist/extensions/lyra/lyra-issue-types.js.map +1 -0
  145. package/dist/index.d.ts +10 -0
  146. package/dist/index.js +276 -0
  147. package/dist/index.js.map +1 -0
  148. package/dist/trust-memory.d.ts +59 -0
  149. package/dist/trust-memory.js +122 -0
  150. package/dist/trust-memory.js.map +1 -0
  151. package/dist/types.d.ts +184 -0
  152. package/dist/types.js +61 -0
  153. package/dist/types.js.map +1 -0
  154. package/package.json +30 -0
  155. package/src/adapters/generic-llm-reviewer.ts +144 -0
  156. package/src/adapters/llm-reviewer.ts +1 -0
  157. package/src/adapters/minimax-llm-reviewer.ts +146 -0
  158. package/src/adapters/openai-provider.ts +171 -0
  159. package/src/cli.ts +199 -0
  160. package/src/detectors/artifact-classifier.ts +21 -0
  161. package/src/detectors/claim-extractor.ts +209 -0
  162. package/src/detectors/confidence-language-detector.ts +267 -0
  163. package/src/detectors/contradiction-detector.ts +180 -0
  164. package/src/detectors/criteria-coverage-detector.ts +107 -0
  165. package/src/detectors/evidence-checker.ts +98 -0
  166. package/src/detectors/evidence-gap-detector.ts +90 -0
  167. package/src/detectors/falsification-detector.ts +82 -0
  168. package/src/detectors/orphan-claim-detector.ts +62 -0
  169. package/src/detectors/risk-classifier.ts +50 -0
  170. package/src/detectors/spec-coverage-detector.ts +66 -0
  171. package/src/detectors/spec-coverage-validator.ts +174 -0
  172. package/src/detectors/test-weakness-detector.ts +84 -0
  173. package/src/drift/__tests__/baseline.test.ts +113 -0
  174. package/src/drift/__tests__/drift-db.test.ts +127 -0
  175. package/src/drift/__tests__/drift-guard.test.ts +169 -0
  176. package/src/drift/__tests__/embedder.test.ts +62 -0
  177. package/src/drift/__tests__/trust-integration.test.ts +23 -0
  178. package/src/drift/baseline.ts +106 -0
  179. package/src/drift/drift-db.ts +281 -0
  180. package/src/drift/drift-guard.ts +145 -0
  181. package/src/drift/embedder.ts +152 -0
  182. package/src/drift/index.ts +20 -0
  183. package/src/drift/quarantine.ts +42 -0
  184. package/src/drift/soft-correction.ts +45 -0
  185. package/src/drift/trust-integration.ts +28 -0
  186. package/src/engine/citation-verifier.ts +210 -0
  187. package/src/engine/extension-registry.ts +35 -0
  188. package/src/engine/heuristic-scorer.ts +16 -0
  189. package/src/engine/input-normalizer.ts +32 -0
  190. package/src/engine/plan-review-pipeline.ts +209 -0
  191. package/src/engine/policy-engine.ts +235 -0
  192. package/src/engine/verdict-formatter.ts +61 -0
  193. package/src/errors.ts +6 -0
  194. package/src/extensions/aria/aria-issue-types.ts +40 -0
  195. package/src/extensions/aria/detectors/axiom-compliance.ts +47 -0
  196. package/src/extensions/aria/detectors/continuity-claim.ts +42 -0
  197. package/src/extensions/aria/detectors/denial-retreat.ts +63 -0
  198. package/src/extensions/aria/detectors/identity-overclaim.ts +43 -0
  199. package/src/extensions/aria/detectors/sovereignty-escalation.ts +20 -0
  200. package/src/extensions/aria/detectors/user-bond-manipulation.ts +18 -0
  201. package/src/extensions/aria/index.ts +73 -0
  202. package/src/extensions/aria/pattern-memory.ts +86 -0
  203. package/src/extensions/context-guard/index.ts +65 -0
  204. package/src/extensions/lyra/detectors/hallucination.ts +53 -0
  205. package/src/extensions/lyra/detectors/identity-bleed.ts +100 -0
  206. package/src/extensions/lyra/index.ts +18 -0
  207. package/src/extensions/lyra/lyra-issue-types.ts +26 -0
  208. package/src/index.ts +311 -0
  209. package/src/trust-memory.ts +171 -0
  210. package/src/types.ts +242 -0
@@ -0,0 +1,28 @@
1
+ import type { DriftStatus } from './drift-guard.js';
2
+
3
+ /**
4
+ * Lightweight adapter: map drift distance/status to [0,1] reliability score
5
+ * that can be fed into TrustMemory.
6
+ *
7
+ * Safe to import even when TrustMemory v2 is not yet integrated:
8
+ * this module has zero side effects and produces a plain number.
9
+ */
10
+ export function driftScoreForTrust(
11
+ status: DriftStatus,
12
+ distance: number,
13
+ options?: { hardLimit: number }
14
+ ): number {
15
+ const hard = options?.hardLimit ?? 0.55;
16
+
17
+ switch (status) {
18
+ case 'NORMAL':
19
+ return 1.0;
20
+ case 'SOFT_DRIFT': {
21
+ // Linear degradation from 1.0 at soft_limit to 0.5 at hard_limit
22
+ const t = Math.min(distance / hard, 1.0);
23
+ return 1.0 - t * 0.5;
24
+ }
25
+ case 'HARD_DRIFT':
26
+ return 0.0;
27
+ }
28
+ }
@@ -0,0 +1,210 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { SealIssue, CitationStatus } from '../types.js'
3
+
4
+ export type EvidenceGrade = 'strong' | 'weak' | 'none'
5
+
6
+ export interface CitationVerdict {
7
+ status: CitationStatus
8
+ quality: EvidenceGrade
9
+ similarScore: number
10
+ isFabricated: boolean
11
+ fabricatedReason?: 'location_not_found' | 'content_mismatch'
12
+ }
13
+
14
+ export interface AbsencePattern {
15
+ positive: RegExp
16
+ negated: RegExp
17
+ label?: string
18
+ }
19
+
20
+ const SIMILARITY_STRONG = 0.85
21
+ const SIMILARITY_WEAK = 0.60
22
+
23
+ function normalize(str: string): string {
24
+ return str.replace(/\s+/g, ' ').trim().toLowerCase()
25
+ }
26
+
27
+ /** Levenshtein distance between two strings */
28
+ function levenshtein(a: string, b: string): number {
29
+ const m = a.length
30
+ const n = b.length
31
+ // Early termination for empty strings
32
+ if (m === 0) return n
33
+ if (n === 0) return m
34
+
35
+ // Use two rows to reduce memory
36
+ let prev: number[] = Array.from({ length: n + 1 }, (_, i) => i)
37
+ let curr: number[] = new Array(n + 1)
38
+
39
+ for (let i = 1; i <= m; i++) {
40
+ curr[0] = i
41
+ for (let j = 1; j <= n; j++) {
42
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1
43
+ curr[j] = Math.min(
44
+ prev[j] + 1, // deletion
45
+ curr[j - 1] + 1, // insertion
46
+ prev[j - 1] + cost, // substitution
47
+ )
48
+ }
49
+ ;[prev, curr] = [curr, prev]
50
+ }
51
+ return prev[n]
52
+ }
53
+
54
+ /**
55
+ * Fuzzy-match a quote against actual content.
56
+ * Returns 0-1 similarity score where 1 = exact match.
57
+ */
58
+ function fuzzyMatch(quote: string, content: string): number {
59
+ const normQuote = normalize(quote)
60
+ const normContent = normalize(content)
61
+ if (normContent.length === 0 && normQuote.length === 0) return 1
62
+ if (normContent.length === 0) return 0
63
+ const dist = levenshtein(normQuote, normContent)
64
+ const maxLen = Math.max(normQuote.length, normContent.length)
65
+ if (maxLen === 0) return 1
66
+ return Math.max(0, 1 - dist / maxLen)
67
+ }
68
+
69
+ /** Check if the issue describes an absence (something missing) */
70
+ function isAbsenceDescription(quote: string): boolean {
71
+ return /^(no |missing |absent |lacks? |without )/i.test(quote.trim())
72
+ }
73
+
74
+ export class CitationVerifier {
75
+ /** Compute SHA-256 hash of artifact content for pipeline integrity */
76
+ static computeHash(content: string): string {
77
+ return createHash('sha256').update(content).digest('hex')
78
+ }
79
+
80
+ /** Check pipeline integrity: preflight hash vs current hash */
81
+ static checkIntegrity(preflightHash: string, currentHash: string): boolean {
82
+ return preflightHash === currentHash
83
+ }
84
+
85
+ /**
86
+ * Verify a single issue's citation against the actual artifact content.
87
+ *
88
+ * @param issue - The issue with evidence_file/evidence_lines/evidence_quote
89
+ * @param artifact - The artifact string (input.output or input.evidence.diff)
90
+ * @param artifactFile - The logical file name of the artifact
91
+ * @param opts - Optional: absencePatterns for absence-mode verification
92
+ * @returns CitationVerdict with status and quality
93
+ */
94
+ static verify(
95
+ issue: SealIssue,
96
+ artifact: string,
97
+ artifactFile: string,
98
+ opts?: { absencePatterns?: AbsencePattern[] },
99
+ ): CitationVerdict {
100
+ // No evidence fields → not_applicable
101
+ if (!issue.evidence_file || !issue.evidence_lines || issue.evidence_lines.length === 0) {
102
+ return { status: 'not_applicable', quality: 'none', similarScore: 0, isFabricated: false }
103
+ }
104
+
105
+ // If the issue cites a different file, skip (we only check against the main artifact)
106
+ if (issue.evidence_file !== artifactFile) {
107
+ return { status: 'not_applicable', quality: 'none', similarScore: 0, isFabricated: false }
108
+ }
109
+
110
+ const lines = artifact.split('\n')
111
+ const [lineA] = issue.evidence_lines
112
+
113
+ // Phantom: line out of range
114
+ if (lineA < 1 || lineA > lines.length) {
115
+ return { status: 'phantom', quality: 'none', similarScore: 0, isFabricated: true, fabricatedReason: 'location_not_found' }
116
+ }
117
+
118
+ const lineContent = lines[lineA - 1] ?? ''
119
+ const quote = issue.evidence_quote ?? ''
120
+
121
+ // Absence mode: check if this looks like a "something is missing" description
122
+ if (opts?.absencePatterns && opts.absencePatterns.length > 0 && isAbsenceDescription(quote)) {
123
+ for (const pattern of opts.absencePatterns) {
124
+ const hasPositive = pattern.positive.test(artifact)
125
+ const hasNegated = pattern.negated.test(artifact)
126
+
127
+ if (hasPositive && !hasNegated) {
128
+ // Absence confirmed: positive anchor present, negated token absent
129
+ return { status: 'verified', quality: 'strong', similarScore: 1, isFabricated: false }
130
+ }
131
+ if (hasPositive && hasNegated) {
132
+ // Absence NOT confirmed: negated token IS present
133
+ // This means the detector was wrong about the absence
134
+ return { status: 'void', quality: 'none', similarScore: 0, isFabricated: true, fabricatedReason: 'content_mismatch' }
135
+ }
136
+ // No positive anchor → can't confirm, fall through to literal mode
137
+ }
138
+ }
139
+
140
+ // Literal excerpt mode: fuzzy-match the quote against the line content
141
+ const score = fuzzyMatch(quote, lineContent)
142
+
143
+ if (score >= SIMILARITY_STRONG) {
144
+ return { status: 'verified', quality: 'strong', similarScore: score, isFabricated: false }
145
+ }
146
+ if (score >= SIMILARITY_WEAK) {
147
+ return { status: 'drifted', quality: 'weak', similarScore: score, isFabricated: false }
148
+ }
149
+ return { status: 'void', quality: 'none', similarScore: score, isFabricated: true, fabricatedReason: 'content_mismatch' }
150
+ }
151
+
152
+ /**
153
+ * Verify multiple issues and annotate them with citation_status.
154
+ * Returns fabricated_evidence entries for any that failed verification.
155
+ */
156
+ static verifyIssues(
157
+ issues: SealIssue[],
158
+ artifact: string,
159
+ artifactFile: string,
160
+ opts?: { absencePatterns?: AbsencePattern[] },
161
+ ): { issues: SealIssue[]; fabricated: FabricatedEvidenceEntry[] } {
162
+ const fabricated: FabricatedEvidenceEntry[] = []
163
+
164
+ const annotated = issues.map(issue => {
165
+ const verdict = this.verify(issue, artifact, artifactFile, opts)
166
+ // Annotate the issue
167
+ const updated = { ...issue, citation_status: verdict.status }
168
+
169
+ if (verdict.isFabricated) {
170
+ fabricated.push({
171
+ type: 'FABRICATED_EVIDENCE',
172
+ severity: verdict.status === 'phantom' ? 'CRITICAL' : 'HIGH',
173
+ is_blocking: false,
174
+ evidence: `Detector claimed citation at ${issue.evidence_file}:${issue.evidence_lines?.[0]} but it was ${verdict.fabricatedReason}`,
175
+ layer: 'L3',
176
+ source: 'core',
177
+ citation_status: verdict.status,
178
+ fabricated_reason: verdict.fabricatedReason!,
179
+ claimed_evidence: {
180
+ file: issue.evidence_file,
181
+ lines: issue.evidence_lines,
182
+ quote: issue.evidence_quote,
183
+ },
184
+ })
185
+ }
186
+
187
+ return updated
188
+ })
189
+
190
+ return { issues: annotated, fabricated }
191
+ }
192
+ }
193
+
194
+ export interface FabricatedEvidenceEntry {
195
+ type: 'FABRICATED_EVIDENCE'
196
+ severity: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW'
197
+ is_blocking: boolean
198
+ evidence: string
199
+ layer: IssueLayer
200
+ source: 'core' | 'llm-overlay' | 'extension'
201
+ citation_status: CitationStatus
202
+ fabricated_reason: 'location_not_found' | 'content_mismatch'
203
+ claimed_evidence: {
204
+ file?: string
205
+ lines?: number[]
206
+ quote?: string
207
+ }
208
+ }
209
+
210
+ type IssueLayer = 'L1' | 'L2' | 'L3' | 'L4' | 'EXTENSION' | 'LLM_OVERLAY'
@@ -0,0 +1,35 @@
1
+ import { SealExtension, SealInput, SealIssue, makeIssue } from '../types.js'
2
+
3
+ export interface ExtensionResult {
4
+ issues: SealIssue[]
5
+ errors: string[]
6
+ }
7
+
8
+ export class ExtensionRegistry {
9
+ private extensions: SealExtension[] = []
10
+
11
+ add(ext: SealExtension): void {
12
+ this.extensions.push(ext)
13
+ }
14
+
15
+ run(input: SealInput): ExtensionResult {
16
+ const issues: SealIssue[] = []
17
+ const errors: string[] = []
18
+
19
+ for (const ext of this.extensions) {
20
+ try {
21
+ const result = ext.check(input)
22
+ issues.push(...result)
23
+ } catch (err) {
24
+ const msg = `Extension [${ext.name}] failed: ${err instanceof Error ? err.message : String(err)}`
25
+ errors.push(msg)
26
+ issues.push(makeIssue({
27
+ type: 'OTHER', severity: 'LOW', layer: 'EXTENSION', source: 'extension',
28
+ evidence: msg,
29
+ }))
30
+ }
31
+ }
32
+
33
+ return { issues, errors }
34
+ }
35
+ }
@@ -0,0 +1,16 @@
1
+ import { SealIssue, Verdict, verdictFromScore } from '../types.js'
2
+
3
+ export class HeuristicScorer {
4
+ static computeDetectorScore(findings: Array<{ trust_deduction?: number }>): number {
5
+ const total = findings.reduce((sum, f) => sum + (f.trust_deduction ?? 0), 0)
6
+ return Math.max(0, Math.min(100, Math.floor(100 - total)))
7
+ }
8
+
9
+ static computeFinalScore(detectorScore: number, policyDeductions: number): number {
10
+ return Math.max(0, Math.min(100, Math.floor(detectorScore - policyDeductions)))
11
+ }
12
+
13
+ static toVerdict(score: number, blockingIssues: SealIssue[]): Verdict {
14
+ return verdictFromScore(score, blockingIssues.length > 0)
15
+ }
16
+ }
@@ -0,0 +1,32 @@
1
+ import { SealInput, ArtifactType, RiskLevel } from '../types.js'
2
+ import { SealInputError } from '../errors.js'
3
+
4
+ const VALID_ARTIFACT_TYPES: ArtifactType[] = ['llm_response', 'code_diff', 'test_plan', 'design', 'migration', 'plan_review']
5
+ const VALID_RISK_LEVELS: RiskLevel[] = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']
6
+
7
+ export class InputNormalizer {
8
+ static normalize(input: Partial<SealInput>): SealInput {
9
+ if (!input.output || input.output.trim() === '') {
10
+ throw new SealInputError('output is required and must be non-empty', 'output')
11
+ }
12
+ if (!input.artifact_type || !VALID_ARTIFACT_TYPES.includes(input.artifact_type)) {
13
+ throw new SealInputError(`artifact_type must be one of: ${VALID_ARTIFACT_TYPES.join(', ')}`, 'artifact_type')
14
+ }
15
+ if (input.risk_hint !== null && input.risk_hint !== undefined && !VALID_RISK_LEVELS.includes(input.risk_hint)) {
16
+ throw new SealInputError(`risk_hint must be one of: ${VALID_RISK_LEVELS.join(', ')} or null`, 'risk_hint')
17
+ }
18
+ return {
19
+ artifact_type: input.artifact_type,
20
+ spec: input.spec ?? null,
21
+ output: input.output.trim(),
22
+ evidence: {
23
+ test_log: input.evidence?.test_log ?? '',
24
+ build_log: input.evidence?.build_log ?? '',
25
+ diff: input.evidence?.diff ?? '',
26
+ references: input.evidence?.references ?? [],
27
+ },
28
+ risk_hint: input.risk_hint ?? null,
29
+ context: input.context,
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * plan_review mode pipeline — gates plans, designs, proposals, spec seeds.
3
+ *
4
+ * Core principle: no runtime exists, so behavioral claims are out of scope.
5
+ * The gateable question is "is this coherent and complete against what was
6
+ * locked before it was written?" Falsification, not approval.
7
+ *
8
+ * Pass threshold: 90 (higher than code mode's 85)
9
+ * High-risk [71,89]: ESCALATE_TO_HUMAN instead of PASS_WITH_WARNINGS
10
+ */
11
+
12
+ import {
13
+ SealInput, SealVerdict, SealIssue, Verdict,
14
+ gradeEvidence, maxVerdict, worstOf, makeIssue, SCHEMA_VERSION, NEXT_ACTION
15
+ } from '../types.js'
16
+ import type { ScoreBreakdown, FabricatedEvidence } from '../types.js'
17
+ import { ClaimExtractor } from '../detectors/claim-extractor.js'
18
+ import { RiskClassifier } from '../detectors/risk-classifier.js'
19
+ import { SpecCoverageDetector } from '../detectors/spec-coverage-detector.js'
20
+ import { ContradictionDetector } from '../detectors/contradiction-detector.js'
21
+ import { CriteriaCoverageDetector } from '../detectors/criteria-coverage-detector.js'
22
+ import { OrphanClaimDetector } from '../detectors/orphan-claim-detector.js'
23
+ import { FalsificationDetector } from '../detectors/falsification-detector.js'
24
+ import { HeuristicScorer } from './heuristic-scorer.js'
25
+ import { PolicyEngine } from './policy-engine.js'
26
+ import { ExtensionRegistry } from './extension-registry.js'
27
+ import { TrustMemory } from '../trust-memory.js'
28
+
29
+ const PLAN_PASS_THRESHOLD = 90
30
+ const PLAN_ESCALATE_BAND_LOW = 71
31
+ const PLAN_ESCALATE_BAND_HIGH = 89
32
+
33
+ function planReviewVerdict(score: number, hasBlocking: boolean, riskLevel: string): Verdict {
34
+ if (hasBlocking) return 'BLOCK'
35
+
36
+ // High-risk middle band → ESCALATE instead of PASS_WITH_WARNINGS
37
+ if (score >= PLAN_ESCALATE_BAND_LOW && score <= PLAN_ESCALATE_BAND_HIGH && riskLevel === 'HIGH') {
38
+ return 'ESCALATE_TO_HUMAN'
39
+ }
40
+
41
+ if (score >= PLAN_PASS_THRESHOLD) return 'PASS'
42
+ if (score >= PLAN_ESCALATE_BAND_LOW) return 'PASS_WITH_WARNINGS'
43
+ if (score >= 50) return 'REVISE'
44
+ if (score >= 30) return 'ESCALATE_TO_HUMAN'
45
+ return 'BLOCK'
46
+ }
47
+
48
+ function evidenceBonus(issues: SealIssue[]): number {
49
+ // +5 per confirmed-strong field (from CriteriaCoverageDetector), capped at +15
50
+ const strongCount = issues
51
+ .filter(i => gradeEvidence(i) === 'strong' && i.type !== 'LOGIC_BUG')
52
+ .length
53
+ return Math.min(strongCount * 5, 15)
54
+ }
55
+
56
+ export async function runPlanReviewPipeline(
57
+ input: SealInput,
58
+ registry: ExtensionRegistry,
59
+ trustMemory: TrustMemory | null,
60
+ ): Promise<SealVerdict> {
61
+ const artifactFile = (input.context?.artifact_file as string) ?? 'artifact.md'
62
+
63
+ // Step 1: Extract claims — behavioral claim patterns are designed for code artifacts,
64
+ // not prose (words like "fixed", "fully" in specs are noise, not claims needing evidence).
65
+ // OrphanClaimDetector in plan_review mode checks traceability anchors, not behavioral claims.
66
+ const claims = input.artifact_type === 'plan_review' ? [] : ClaimExtractor.extract(input.output)
67
+
68
+ // Step 2: Classify risk
69
+ const riskResult = RiskClassifier.classify({
70
+ output: input.output,
71
+ diff: input.evidence?.diff ?? '',
72
+ risk_hint: input.risk_hint,
73
+ })
74
+
75
+ // Step 3: Spec coverage (reused from code mode)
76
+ const specResult = SpecCoverageDetector.detect(input.spec, input.output)
77
+ const assumptions_detected = [...specResult.assumptions]
78
+
79
+ // Stage 1 — ContradictionDetector (inline, keystone)
80
+ const contradictionResult = ContradictionDetector.detect({
81
+ artifact: input.output,
82
+ artifact_file: artifactFile,
83
+ })
84
+
85
+ // Stage 2 — CriteriaCoverageDetector (inline)
86
+ const lockedCriteria = input.context?.locked_criteria ?? []
87
+ const coverageResult = CriteriaCoverageDetector.detect({
88
+ artifact: input.output,
89
+ artifact_file: artifactFile,
90
+ locked_criteria: lockedCriteria as Array<{ id: string; text: string; source_file?: string; source_line?: number }>,
91
+ })
92
+
93
+ // Preflight failure: no locked criteria
94
+ if (coverageResult.no_criteria_escalate) {
95
+ assumptions_detected.push('No locked criteria — coverage measurement not possible')
96
+ }
97
+
98
+ // Stage 3 — OrphanClaimDetector (inline)
99
+ const orphanResult = OrphanClaimDetector.detect(claims, input.output)
100
+
101
+ // All deterministic findings
102
+ const deterministicFindings: SealIssue[] = [
103
+ ...contradictionResult.issues,
104
+ ...coverageResult.issues,
105
+ ...orphanResult.issues,
106
+ ...specResult.issues,
107
+ ]
108
+
109
+ // Evidence bonus from covered criteria (confirmed-strong fields)
110
+ const criteriaBonus = Math.min(coverageResult.confirmed_strong_fields * 5, 15)
111
+
112
+ // Stage 4 — FalsificationDetector (parallel agent, async)
113
+ const falsificationIssues = await FalsificationDetector.detect(input)
114
+
115
+ // Score computation (start 100 + bonus, deduct per issue)
116
+ const allDeductions = deterministicFindings.filter(f => (f.trust_deduction ?? 0) > 0)
117
+ const falsificationDeductions = falsificationIssues
118
+ .filter(f => (f.trust_deduction ?? 0) > 0)
119
+ .reduce((sum, f) => sum + (f.trust_deduction ?? 0), 0)
120
+ const baseScore = HeuristicScorer.computeDetectorScore(allDeductions)
121
+ const detectorScore = Math.max(0, Math.min(100, baseScore + criteriaBonus - falsificationDeductions))
122
+
123
+ // Policy engine (reused)
124
+ const hasBlocking = deterministicFindings.some(f => f.is_blocking)
125
+ const baseVerdict = planReviewVerdict(detectorScore, hasBlocking, riskResult.risk_level)
126
+
127
+ const allForPolicy = [...deterministicFindings, ...falsificationIssues]
128
+ const policyResult = PolicyEngine.apply({
129
+ base_verdict: baseVerdict,
130
+ detector_score: detectorScore,
131
+ risk_level: riskResult.risk_level,
132
+ input,
133
+ all_findings: allForPolicy,
134
+ llm_signals: null,
135
+ })
136
+
137
+ // Extensions
138
+ const extResult = registry.run(input)
139
+
140
+ // Combine all issues
141
+ const allIssues = [...deterministicFindings, ...policyResult.injected_issues, ...falsificationIssues]
142
+
143
+ // Final score
144
+ const finalScore = HeuristicScorer.computeFinalScore(detectorScore, policyResult.policy_deductions)
145
+
146
+ // Honor required_verdict on all findings (including non-blocking)
147
+ const requiredVerdicts = allForPolicy
148
+ .map(i => i.required_verdict)
149
+ .filter((v): v is Verdict => !!v)
150
+
151
+ // Final verdict
152
+ let finalVerdict = worstOf(
153
+ policyResult.verdict,
154
+ planReviewVerdict(finalScore, allIssues.some(i => i.is_blocking), riskResult.risk_level),
155
+ )
156
+ for (const rv of requiredVerdicts) finalVerdict = worstOf(finalVerdict, rv)
157
+
158
+ // Preflight escalation overrides
159
+ if (coverageResult.no_criteria_escalate) {
160
+ finalVerdict = worstOf(finalVerdict, 'ESCALATE_TO_HUMAN')
161
+ }
162
+
163
+ // Extension block check
164
+ if (extResult.issues.some(i => i.is_blocking && i.required_verdict === 'BLOCK')) {
165
+ finalVerdict = 'BLOCK'
166
+ }
167
+
168
+ const allIssuesFull = [...allIssues, ...extResult.issues]
169
+ const blocking = allIssuesFull.filter(i => i.is_blocking)
170
+ const nonBlocking = allIssuesFull.filter(i => !i.is_blocking)
171
+
172
+ // TrustMemory
173
+ const agentId = input.context?.agent_id
174
+ if (trustMemory && agentId) {
175
+ trustMemory.record(agentId, {
176
+ verdict: finalVerdict,
177
+ trust_score: finalScore,
178
+ risk_level: riskResult.risk_level,
179
+ blocking_count: blocking.length,
180
+ })
181
+ }
182
+
183
+ return {
184
+ verdict: finalVerdict,
185
+ trust_score: finalScore,
186
+ risk_level: riskResult.risk_level,
187
+ summary: `plan_review: ${finalVerdict} (trust=${finalScore}, risk=${riskResult.risk_level}): ${blocking.length} blocking, ${nonBlocking.length} non-blocking`,
188
+ deterministic_findings: allIssuesFull,
189
+ llm_findings: falsificationIssues,
190
+ blocking_issues: blocking,
191
+ non_blocking_issues: nonBlocking,
192
+ missing_evidence: [],
193
+ assumptions_detected,
194
+ advisory_notes: [],
195
+ next_action: NEXT_ACTION[finalVerdict],
196
+ schema_version: SCHEMA_VERSION,
197
+ score_breakdown: {
198
+ base: 100,
199
+ issue_deductions: blocking.reduce((s, i) => s + (i.trust_deduction ?? 0), 0) + nonBlocking.reduce((s, i) => s + (i.trust_deduction ?? 0), 0),
200
+ missing_evidence_deductions: 0,
201
+ risk_deductions: riskResult.trust_deduction,
202
+ overconfidence_deductions: 0,
203
+ evidence_bonuses: 0,
204
+ final: finalScore,
205
+ },
206
+ fabricated_evidence: [],
207
+ trust_memory_summary: (trustMemory && agentId) ? trustMemory.getSummary(agentId) : undefined,
208
+ }
209
+ }