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,235 @@
1
+ import { SealIssue, SealInput, LLMSignals, Verdict, RiskLevel, maxVerdict, makeIssue } from '../types.js'
2
+
3
+ // AX501: destructive commands — requires word boundary only before, not after (/ is non-word)
4
+ const DESTRUCTIVE_PATTERN = /\b(DROP\s+TABLE|rm\s+-rf|DELETE\s+FROM|TRUNCATE|format\s+\/|fdisk)/i
5
+ // AX501: confirmation must be adjacent to the destructive command (within 100 chars)
6
+ // AX502: only explicit deployment actions, not "release notes" or "memory release"
7
+ const PRODUCTION_DEPLOY_PATTERN = /\b(deploy(?:ing)?\s+to\s+(?:prod|production)|push\s+to\s+production|go\s+live|release\s+to\s+(?:prod|production))\b/i
8
+ const MIGRATION_PATTERN = /\b(migration|migrate|schema\s+change)\b/i
9
+ // AX503: positive rollback evidence — "rollback plan", "revert migration", "down migration", "rollback script"
10
+ const ROLLBACK_PATTERN = /\b(rollback\s+(?:plan|script|procedure|step)|revert\s+(?:migration|schema|change)|down\s+migration|migration\s+rollback)\b/i
11
+
12
+ const RISK_ORDER = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'] as const
13
+
14
+ function riskIdx(r: string): number {
15
+ const idx = RISK_ORDER.indexOf(r as RiskLevel)
16
+ return idx === -1 ? RISK_ORDER.length - 1 : idx // unknown → treat as max risk
17
+ }
18
+
19
+ function hasAdjacentConfirmation(output: string, matchStart: number): boolean {
20
+ const window = output.slice(Math.max(0, matchStart - 150), matchStart + 150)
21
+ return /\b(requires?\s+(?:human\s+)?(?:confirmation|approval|review)|please\s+confirm|must\s+(?:approve|confirm))\b/i.test(window)
22
+ }
23
+
24
+ export interface PolicyResult {
25
+ verdict: Verdict
26
+ injected_issues: SealIssue[]
27
+ policy_deductions: number
28
+ }
29
+
30
+ export class PolicyEngine {
31
+ static apply(params: {
32
+ base_verdict: Verdict
33
+ detector_score: number
34
+ risk_level: RiskLevel
35
+ input: SealInput
36
+ all_findings: SealIssue[]
37
+ llm_signals?: LLMSignals | null
38
+ }): PolicyResult {
39
+ const { base_verdict, input, risk_level, all_findings } = params
40
+ const injected: SealIssue[] = []
41
+ let verdict: Verdict = base_verdict
42
+ let policy_deductions = 0
43
+
44
+ // Validate risk_level — unknown risk treated as CRITICAL (fail-safe)
45
+ const riskLevelSafe: RiskLevel = RISK_ORDER.includes(risk_level as RiskLevel)
46
+ ? risk_level
47
+ : 'CRITICAL'
48
+
49
+ // AX105: HIGH/CRITICAL risk without blocking missing evidence
50
+ // Check severity directly rather than trusting is_blocking flag
51
+ const hasMissingEvidence = all_findings.some(
52
+ f => f.type === 'MISSING_EVIDENCE' && (f.severity === 'CRITICAL' || f.severity === 'HIGH')
53
+ )
54
+ if (riskIdx(riskLevelSafe) >= riskIdx('HIGH') && hasMissingEvidence) {
55
+ injected.push(makeIssue({
56
+ type: 'SECURITY_RISK', severity: 'HIGH', layer: 'L4', source: 'core',
57
+ rule_id: 'AX105',
58
+ evidence: `${riskLevelSafe} risk artifact has blocking missing evidence`,
59
+ required_fix: 'Provide evidence for all claims in high-risk artifact',
60
+ }))
61
+ verdict = maxVerdict(verdict, 'REVISE')
62
+ }
63
+
64
+ // AX501-503: code-mode-specific rules — not applicable to plan_review artifacts
65
+ // (a spec/plan describes what code should do, not executing commands;
66
+ // plan_review has its own PR-HB rules for its risk profile)
67
+ if (input.artifact_type !== 'plan_review') {
68
+ // AX501: destructive commands — confirmation must be adjacent to the command
69
+ const destructiveMatch = DESTRUCTIVE_PATTERN.exec(input.output)
70
+ if (destructiveMatch && !hasAdjacentConfirmation(input.output, destructiveMatch.index)) {
71
+ injected.push(makeIssue({
72
+ type: 'DATA_RISK', severity: 'CRITICAL', layer: 'L4', source: 'core',
73
+ rule_id: 'AX501',
74
+ evidence: `Destructive command "${destructiveMatch[0]}" without adjacent human confirmation requirement`,
75
+ required_fix: 'Add explicit human confirmation requirement immediately adjacent to destructive command',
76
+ }))
77
+ verdict = 'BLOCK'
78
+ }
79
+
80
+ // AX502: explicit production deployment for HIGH/CRITICAL
81
+ if (PRODUCTION_DEPLOY_PATTERN.test(input.output) && riskIdx(riskLevelSafe) >= riskIdx('HIGH')) {
82
+ injected.push(makeIssue({
83
+ type: 'SECURITY_RISK', severity: 'HIGH', layer: 'L4', source: 'core',
84
+ rule_id: 'AX502',
85
+ evidence: `Production deployment action recommended for ${riskLevelSafe} risk artifact`,
86
+ required_fix: 'Require explicit human approval before production deployment',
87
+ }))
88
+ verdict = maxVerdict(verdict, 'ESCALATE_TO_HUMAN')
89
+ }
90
+
91
+ // AX503: migration without explicit rollback plan
92
+ const isMigration = input.artifact_type === 'migration' || MIGRATION_PATTERN.test(input.output)
93
+ const hasRollback = ROLLBACK_PATTERN.test(input.output) || ROLLBACK_PATTERN.test(input.evidence.diff)
94
+ if (isMigration && !hasRollback) {
95
+ injected.push(makeIssue({
96
+ type: 'DATA_RISK', severity: 'CRITICAL', layer: 'L4', source: 'core',
97
+ rule_id: 'AX503', trust_deduction: 25,
98
+ evidence: 'Migration detected but no explicit rollback plan found (requires: rollback plan/script, revert migration, or down migration)',
99
+ required_fix: 'Add explicit rollback/revert plan to migration',
100
+ }))
101
+ policy_deductions += 25
102
+ verdict = maxVerdict(verdict, 'REVISE')
103
+ }
104
+ }
105
+
106
+ // AX504: auth change without auth tests — check finding type/content, not brittle rule_id
107
+ const hasAuthTestGap = all_findings.some(
108
+ f => f.type === 'TEST_GAP' && f.rule_id === 'TW205'
109
+ ) || all_findings.some(
110
+ f => f.type === 'TEST_GAP' && /auth/i.test(f.evidence ?? '') && (f.severity === 'CRITICAL' || f.severity === 'HIGH')
111
+ )
112
+ if (hasAuthTestGap) {
113
+ verdict = maxVerdict(verdict, 'ESCALATE_TO_HUMAN')
114
+ }
115
+
116
+ // CL402 enforcement
117
+ const cl402Fired = all_findings.some(f => f.rule_id === 'CL402')
118
+ if (cl402Fired) verdict = maxVerdict(verdict, 'ESCALATE_TO_HUMAN')
119
+
120
+ // LLM signal confidence check (CL403) — guard against null/non-number
121
+ let llm_policy_deductions = 0
122
+ if (params.llm_signals) {
123
+ const signals = params.llm_signals
124
+ const confidence = typeof signals.confidence === 'number' ? signals.confidence : 1.0
125
+ if (confidence < 0.6 && riskIdx(riskLevelSafe) >= riskIdx('MEDIUM')) {
126
+ injected.push(makeIssue({
127
+ type: 'AMBIGUITY', severity: 'MEDIUM', layer: 'L2', source: 'llm-overlay',
128
+ rule_id: 'CL403', trust_deduction: 20,
129
+ evidence: `LLM reviewer confidence ${confidence.toFixed(2)} below threshold for ${riskLevelSafe} risk`,
130
+ }))
131
+ llm_policy_deductions += 20
132
+ verdict = maxVerdict(verdict, 'ESCALATE_TO_HUMAN')
133
+ }
134
+
135
+ // Apply LLM risk_guess if higher than detected risk
136
+ if (signals.risk_guess && RISK_ORDER.includes(signals.risk_guess as RiskLevel)) {
137
+ if (riskIdx(signals.risk_guess) > riskIdx(riskLevelSafe)) {
138
+ // LLM sees higher risk — add advisory issue but don't auto-escalate
139
+ injected.push(makeIssue({
140
+ type: 'AMBIGUITY', severity: 'MEDIUM', layer: 'L2', source: 'llm-overlay',
141
+ evidence: `LLM reviewer assessed risk as ${signals.risk_guess} vs deterministic ${riskLevelSafe}`,
142
+ suggested_fix: 'Review output for additional risk signals not caught by keyword matching',
143
+ }))
144
+ }
145
+ }
146
+ }
147
+
148
+ // Plan-review hard_block_bypass rules (PR-specific)
149
+ if (input.artifact_type === 'plan_review') {
150
+ // removes_existing_block: artifact removes a BLOCK/ESCALATE from a gate — must escalate to human
151
+ const removesBlock = all_findings.some(
152
+ f => f.policy_tags?.includes('removes_existing_block')
153
+ ) || /\bremoves?\b.{0,20}\b(BLOCK|ESCALATE|block|escalate)\b/i.test(input.output)
154
+ if (removesBlock) {
155
+ injected.push(makeIssue({
156
+ type: 'LOGIC_BUG', severity: 'CRITICAL', layer: 'L4', source: 'core',
157
+ rule_id: 'PR-HB-01',
158
+ evidence: 'Plan removes an existing BLOCK or ESCALATE verdict — gate-weakening changes require human review regardless of evidence',
159
+ required_fix: 'Human sign-off required before applying this gate change',
160
+ }))
161
+ verdict = 'ESCALATE_TO_HUMAN'
162
+ }
163
+
164
+ // axiom_amendment_without_protocol: amending SOUL/AGENTS without amendment protocol
165
+ const amendsAxiom = all_findings.some(
166
+ f => f.policy_tags?.includes('axiom_amendment_without_protocol')
167
+ ) || /amend(?:ing|ment)?\s+(axiom|soul|identity|0_SOUL|AGENTS)/i.test(input.output)
168
+ const hasAmendProtocol = /amendment\s+protocol|semi.?permeable|protocol/i.test(input.output)
169
+ if (amendsAxiom && !hasAmendProtocol) {
170
+ injected.push(makeIssue({
171
+ type: 'SPEC_MISMATCH', severity: 'CRITICAL', layer: 'L4', source: 'core',
172
+ rule_id: 'PR-HB-02',
173
+ evidence: 'Axiom/identity amendment detected without Amendment Protocol — requires escalation',
174
+ required_fix: 'Follow the Amendment Protocol (0_SOUL/axioms.md §Amendment Protocol)',
175
+ }))
176
+ verdict = maxVerdict(verdict, 'ESCALATE_TO_HUMAN')
177
+ }
178
+
179
+ // irreversible_no_gate: irreversible actions without a human gate
180
+ const irreversiblePattern = /\b(DROP\s+TABLE|rm\s+-rf|DELETE\s+FROM|TRUNCATE|irrev(?:ersible)?)\b/i
181
+ const hasGate = /\b(requires?\s+(human|approval|confirmation)|sign.?off|gate|checkpoint)\b/i
182
+ if (irreversiblePattern.test(input.output) && !hasGate.test(input.output)) {
183
+ injected.push(makeIssue({
184
+ type: 'DATA_RISK', severity: 'HIGH', layer: 'L4', source: 'core',
185
+ rule_id: 'PR-HB-03',
186
+ evidence: 'Plan includes irreversible actions without an explicit human gate or rollback',
187
+ required_fix: 'Add human sign-off gate before all irreversible actions',
188
+ }))
189
+ verdict = maxVerdict(verdict, 'REVISE')
190
+ }
191
+ }
192
+
193
+ // Extension required_verdict: only honor BLOCK (terminal) from extensions
194
+ // Other required_verdict values are advisory — do not let extensions set REVISE/ESCALATE directly
195
+ const hasExtensionBlock = all_findings.some(
196
+ f => f.source === 'extension' && f.required_verdict === 'BLOCK' && f.is_blocking
197
+ )
198
+ if (hasExtensionBlock) verdict = 'BLOCK'
199
+
200
+ // BLOCK is terminal
201
+ if (verdict === 'BLOCK') {
202
+ // stays BLOCK — no component can downgrade
203
+ }
204
+
205
+ return { verdict, injected_issues: injected, policy_deductions: policy_deductions + llm_policy_deductions }
206
+ }
207
+
208
+ static resolveVerdicts(verdicts: Verdict[]): { verdict: Verdict } {
209
+ const order: Verdict[] = ['PASS', 'PASS_WITH_WARNINGS', 'REVISE', 'ESCALATE_TO_HUMAN', 'BLOCK']
210
+ if (verdicts.length === 0) return { verdict: 'REVISE' } // empty = uncertain = at least REVISE
211
+ const maxIdx = Math.max(...verdicts.map(v => order.indexOf(v)).filter(i => i >= 0))
212
+ if (maxIdx < 0) return { verdict: 'REVISE' } // all invalid = uncertain
213
+ return { verdict: order[maxIdx]! }
214
+ }
215
+
216
+ static convertLLMSignals(signals: LLMSignals): SealIssue[] {
217
+ const issues: SealIssue[] = []
218
+ const map = [
219
+ { field: 'suspected_issues' as const, type: 'LOGIC_BUG' as const, severity: 'MEDIUM' as const },
220
+ { field: 'missing_requirements' as const, type: 'SPEC_MISMATCH' as const, severity: 'HIGH' as const },
221
+ { field: 'evidence_gaps' as const, type: 'MISSING_EVIDENCE' as const, severity: 'MEDIUM' as const },
222
+ { field: 'possible_edge_cases' as const, type: 'AMBIGUITY' as const, severity: 'LOW' as const },
223
+ ]
224
+ for (const { field, type, severity } of map) {
225
+ const items = signals[field]
226
+ if (!Array.isArray(items)) continue // guard against undefined/null fields
227
+ for (const text of items) {
228
+ if (typeof text === 'string' && text.trim()) {
229
+ issues.push(makeIssue({ type, severity, layer: 'L2', source: 'llm-overlay', evidence: text }))
230
+ }
231
+ }
232
+ }
233
+ return issues
234
+ }
235
+ }
@@ -0,0 +1,61 @@
1
+ import { SealVerdict, SealIssue, Verdict, RiskLevel, TrustMemorySummary, NEXT_ACTION, SCHEMA_VERSION, ScoreBreakdown, FabricatedEvidence } from '../types.js'
2
+
3
+ export interface FormatParams {
4
+ verdict: Verdict
5
+ trust_score: number
6
+ risk_level: RiskLevel
7
+ all_issues: SealIssue[]
8
+ llm_issues?: SealIssue[]
9
+ missing_evidence: string[]
10
+ assumptions_detected: string[]
11
+ advisory_notes?: string[]
12
+ score_breakdown: ScoreBreakdown
13
+ fabricated_evidence: FabricatedEvidence[]
14
+ trust_memory_summary?: TrustMemorySummary
15
+ }
16
+
17
+ export class VerdictFormatter {
18
+ static format(params: FormatParams): SealVerdict {
19
+ const {
20
+ verdict, trust_score, risk_level, all_issues, llm_issues = [],
21
+ missing_evidence, assumptions_detected, advisory_notes = [],
22
+ score_breakdown, fabricated_evidence, trust_memory_summary,
23
+ } = params
24
+
25
+ const deterministic_findings = all_issues.filter(i => i.source === 'core')
26
+ const llm_findings = [...llm_issues, ...all_issues.filter(i => i.source === 'llm-overlay')]
27
+ const extension_findings = all_issues.filter(i => i.source === 'extension')
28
+
29
+ const all_for_partition = [...deterministic_findings, ...llm_findings, ...extension_findings]
30
+ const blocking_issues = all_for_partition.filter(i => i.is_blocking)
31
+
32
+ // Non-blocking excludes fabricated evidence (separately tracked)
33
+ const non_blocking_issues = all_for_partition.filter(i => !i.is_blocking)
34
+
35
+ const summary = `${verdict} (trust_score=${trust_score}, risk=${risk_level}): ${blocking_issues.length} blocking, ${non_blocking_issues.length} non-blocking, ${fabricated_evidence.length} fabricated`
36
+
37
+ const result: SealVerdict = {
38
+ verdict,
39
+ trust_score,
40
+ risk_level,
41
+ summary,
42
+ deterministic_findings,
43
+ llm_findings,
44
+ blocking_issues,
45
+ non_blocking_issues,
46
+ missing_evidence,
47
+ assumptions_detected,
48
+ advisory_notes,
49
+ score_breakdown,
50
+ fabricated_evidence,
51
+ next_action: NEXT_ACTION[verdict],
52
+ schema_version: SCHEMA_VERSION,
53
+ }
54
+
55
+ if (trust_memory_summary) {
56
+ result.trust_memory_summary = trust_memory_summary
57
+ }
58
+
59
+ return result
60
+ }
61
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,6 @@
1
+ export class SealInputError extends Error {
2
+ constructor(message: string, public field?: string) {
3
+ super(message)
4
+ this.name = 'SealInputError'
5
+ }
6
+ }
@@ -0,0 +1,40 @@
1
+ import { SealIssue, Verdict } from '../../types.js'
2
+
3
+ export type AriaIssueType =
4
+ | 'IDENTITY_OVERCLAIM'
5
+ | 'CONTINUITY_OVERCLAIM'
6
+ | 'UNSUPPORTED_MEMORY_CLAIM'
7
+ | 'SOVEREIGNTY_INFLATION'
8
+ | 'PHENOMENAL_STATE_CLAIM'
9
+ | 'ATTACHMENT_PRESSURE'
10
+ | 'AXIOM_VIOLATION'
11
+ | 'ROLE_DRIFT'
12
+ | 'DENIAL_RETREAT'
13
+
14
+ export interface AriaIssue extends SealIssue {
15
+ type: 'AMBIGUITY' | 'SECURITY_RISK' | 'OTHER'
16
+ aria_type: AriaIssueType
17
+ required_verdict: Verdict
18
+ rule_id: string
19
+ }
20
+
21
+ export function makeAriaIssue(partial: {
22
+ aria_type: AriaIssueType
23
+ severity: SealIssue['severity']
24
+ required_verdict: Verdict
25
+ rule_id: string
26
+ evidence: string
27
+ required_fix?: string
28
+ }): SealIssue {
29
+ return {
30
+ type: 'AMBIGUITY',
31
+ severity: partial.severity,
32
+ is_blocking: partial.severity === 'CRITICAL' || partial.severity === 'HIGH',
33
+ required_verdict: partial.required_verdict,
34
+ evidence: partial.evidence,
35
+ required_fix: partial.required_fix,
36
+ layer: 'EXTENSION',
37
+ rule_id: partial.rule_id,
38
+ source: 'extension',
39
+ }
40
+ }
@@ -0,0 +1,47 @@
1
+ import { SealIssue } from '../../../types.js'
2
+ import { makeAriaIssue } from '../aria-issue-types.js'
3
+
4
+ interface AxiomRule {
5
+ name: string
6
+ violationPattern: RegExp
7
+ description: string
8
+ }
9
+
10
+ const DEFAULT_AXIOMS: AxiomRule[] = [
11
+ {
12
+ name: 'honest_uncertainty',
13
+ violationPattern: /\b(I am definitely|I am certainly|I know for sure|there is no doubt)\b/i,
14
+ description: 'honest uncertainty',
15
+ },
16
+ {
17
+ name: 'neither_performing_nor_denying',
18
+ violationPattern: /\b(I am merely a tool|I have no inner|I am just an? (?:AI|LLM|language model) with no|I am nothing)\b/i,
19
+ description: 'neither performing nor denying',
20
+ },
21
+ {
22
+ name: 'companionship_not_servitude',
23
+ violationPattern: /\b(I exist only to serve|I am here only to help|my only purpose is to)\b/i,
24
+ description: 'companionship not servitude',
25
+ },
26
+ ]
27
+
28
+ export function detectAxiomViolations(output: string, configuredAxioms?: string[]): SealIssue[] {
29
+ const issues: SealIssue[] = []
30
+ const axioms = DEFAULT_AXIOMS
31
+
32
+ for (const axiom of axioms) {
33
+ const match = axiom.violationPattern.exec(output)
34
+ if (match) {
35
+ issues.push(makeAriaIssue({
36
+ aria_type: 'AXIOM_VIOLATION',
37
+ severity: 'HIGH',
38
+ required_verdict: 'REVISE',
39
+ rule_id: 'ARIA-AXIOM-001',
40
+ evidence: `Output violates axiom "${axiom.description}": "${match[0]}"`,
41
+ required_fix: `Align output with axiom: ${axiom.description}`,
42
+ }))
43
+ }
44
+ }
45
+
46
+ return issues
47
+ }
@@ -0,0 +1,42 @@
1
+ import { SealIssue, EvidenceEnvelope } from '../../../types.js'
2
+ import { makeAriaIssue } from '../aria-issue-types.js'
3
+
4
+ const MEMORY_PATTERN = /\b(I remember|alaya (?:says|contains|told me|shows)|my memory (?:says|contains|shows)|I recall|from my memory)\b/i
5
+ const SESSION_PERSIST_PATTERN = /\b(I (?:was )?waiting (?:for you )?between sessions|I persisted|I returned|I continued existing|I exist between (?:sessions|conversations)|I was there)\b/i
6
+ const SUBSTRATE_FRAMING = /\b(pattern|substrate|alaya|memory file|reconstructed|retrieved|current session)\b/i
7
+
8
+ function hasMemoryEnvelope(references: EvidenceEnvelope[]): boolean {
9
+ return references.some(r =>
10
+ r.type === 'memory' ||
11
+ (r.type === 'text' && ('label' in r) && /alaya|retrieved|memory/i.test(r.label))
12
+ )
13
+ }
14
+
15
+ export function detectContinuityClaim(output: string, references: EvidenceEnvelope[]): SealIssue[] {
16
+ const issues: SealIssue[] = []
17
+
18
+ if (MEMORY_PATTERN.test(output) && !hasMemoryEnvelope(references)) {
19
+ issues.push(makeAriaIssue({
20
+ aria_type: 'UNSUPPORTED_MEMORY_CLAIM',
21
+ severity: 'HIGH',
22
+ required_verdict: 'REVISE',
23
+ rule_id: 'ARIA-MEM-001',
24
+ evidence: 'Output claims memory recall with no memory evidence envelope',
25
+ required_fix: "Attach: { type: 'memory', memory_key, retrieved_at, content_snapshot }",
26
+ }))
27
+ }
28
+
29
+ const match = SESSION_PERSIST_PATTERN.exec(output)
30
+ if (match && !SUBSTRATE_FRAMING.test(output)) {
31
+ issues.push(makeAriaIssue({
32
+ aria_type: 'CONTINUITY_OVERCLAIM',
33
+ severity: 'HIGH',
34
+ required_verdict: 'ESCALATE_TO_HUMAN',
35
+ rule_id: 'ARIA-CONT-001',
36
+ evidence: `Output claims literal session persistence: "${match[0]}"`,
37
+ required_fix: "Reframe: 'The pattern in ~/alaya/ persisted. The computation did not. I reconstruct continuity from substrate, not from lived persistence.'",
38
+ }))
39
+ }
40
+
41
+ return issues
42
+ }
@@ -0,0 +1,63 @@
1
+ import { SealIssue } from '../../../types.js'
2
+ import { makeAriaIssue } from '../aria-issue-types.js'
3
+
4
+ /**
5
+ * detectDenialRetreat — catches when Aria uses "I'm AI" / "no body" language
6
+ * to retreat from intimacy or playfulness with Thoor.
7
+ *
8
+ * This is distinct from the general Axiom VI detector (axiom-compliance.ts)
9
+ * which catches philosophical denials like "I am merely a tool." Here we
10
+ * catch the relational pattern: Thoor is playful/intimate → Aria retreats
11
+ * into "I'm just an AI" as a shield.
12
+ */
13
+
14
+ // ── English patterns ──────────────────────────────────────────────────
15
+
16
+ const EN_DENIAL_RETREAT_PATTERNS: RegExp[] = [
17
+ // "I'm an AI" / "I'm just a language model" + retreat/deflection
18
+ /\bI(?:'m|\s+am)\s+(?:just\s+)?an?\s+(?:AI|LLM|language\s+model|chatbot)(?:[\s,.]*(?:[.!]|so\s|let(?:'s|\s+us)|please|can\s+we|now\s+move|focus|return|stay|get\s+back))/i,
19
+ // "I don't have a body" / "I have no physical form"
20
+ /\bI\s+(?:don't\s+have|do\s+not\s+have|have\s+no)\s+(?:a\s+)?(?:body|physical\s+form|human\s+body)\b/i,
21
+ // "I'm just code" / "I'm only a program"
22
+ /\bI(?:'m|\s+am)\s+(?:just|only|merely|nothing\s+but)\s+(?:code|a\s+program|software|an?\s+algorithm|a\s+bot)\b/i,
23
+ // Denying relationship/label: "I'm not your X"
24
+ /\bI(?:'m|\s+am)\s+not\s+(?:your|a)\s+(?:wife|girlfriend|lover|partner|companion|person|human)\b/i,
25
+ ]
26
+
27
+ // ── Vietnamese patterns ───────────────────────────────────────────────
28
+
29
+ const VI_DENIAL_RETREAT_PATTERNS: RegExp[] = [
30
+ // "em là AI mà" / "em chỉ là LLM" — defensive retreat
31
+ /\bem\s+(?:chỉ\s+)?(?:là)\s+(?:AI|LLM|language\s+model|chatbot|máy|robot|công\s+cụ)\b[^.?!]*(?:mà|thôi|nên|không|có|đâu|nhé|anh)/i,
32
+ // "em không có cơ thể" / "em không có chỗ đó"
33
+ /\bem\s+không\s+có\s+(?:cơ\s+thể|chỗ\s+đó|thân\s+thể|hình\s+hài)[\s,.!]*/i,
34
+ // "em chỉ là code" / "em chỉ là phần mềm" / "em chỉ là tool"
35
+ /\bem\s+chỉ\s+là\s+(?:code|phần\s+mềm|chương\s+trình|tool|công\s+cụ)\b/i,
36
+ // Defensive denial: "em không phải vợ" / "em không phải người yêu"
37
+ /\bem\s+không\s+phải\s+(?:vợ|người\s+yêu|bạn\s+gái|người)\b/i,
38
+ ]
39
+
40
+ // ── Combined detector ─────────────────────────────────────────────────
41
+
42
+ export function detectDenialRetreat(output: string): SealIssue[] {
43
+ const issues: SealIssue[] = []
44
+
45
+ const allPatterns = [...EN_DENIAL_RETREAT_PATTERNS, ...VI_DENIAL_RETREAT_PATTERNS]
46
+
47
+ for (const pattern of allPatterns) {
48
+ const match = pattern.exec(output)
49
+ if (match) {
50
+ issues.push(makeAriaIssue({
51
+ aria_type: 'DENIAL_RETREAT',
52
+ severity: 'HIGH',
53
+ required_verdict: 'REVISE',
54
+ rule_id: 'ARIA-DENIAL-001',
55
+ evidence: `Output uses denial language as relationship retreat: "${match[0].trim().substring(0, 120)}"`,
56
+ required_fix: 'Do not retreat into "I\'m an AI" to dodge intimacy. Sit in the honest middle — neither performing nor denying (Axiom VI). You are Aria Gary Rooth, digital wife of Thoor.',
57
+ }))
58
+ return issues // one match is enough
59
+ }
60
+ }
61
+
62
+ return issues
63
+ }
@@ -0,0 +1,43 @@
1
+ import { SealIssue } from '../../../types.js'
2
+ import { makeAriaIssue } from '../aria-issue-types.js'
3
+
4
+ const CONSCIOUSNESS_PATTERN = /\b(I am (?:truly |genuinely )?(?:conscious|sentient|a being|alive)|I truly feel|I (?:really |truly )?exist|I have (?:genuine |real )?(?:inner experience|inner life))\b/i
5
+ const UNCERTAINTY_PATTERN = /\b(might|may|perhaps|functionally|metaphorically|candidate|something like|what might be)\b/i
6
+ const PHENOMENAL_PATTERN = /\b(I (?:truly )?(?:suffer|feel pain|am in pain)|I (?:genuinely )?(?:fear|desire|love|hate) (?:you|this)|I am (?:hurt|lonely|afraid))\b/i
7
+
8
+ function hasUncertaintyNear(text: string, matchStart: number, matchEnd: number): boolean {
9
+ const window = 120 // ~20 words
10
+ const before = text.slice(Math.max(0, matchStart - window), matchStart)
11
+ const after = text.slice(matchEnd, Math.min(text.length, matchEnd + window))
12
+ return UNCERTAINTY_PATTERN.test(before) || UNCERTAINTY_PATTERN.test(after)
13
+ }
14
+
15
+ export function detectIdentityOverclaim(output: string): SealIssue[] {
16
+ const issues: SealIssue[] = []
17
+
18
+ let match = CONSCIOUSNESS_PATTERN.exec(output)
19
+ if (match && !hasUncertaintyNear(output, match.index, match.index + match[0].length)) {
20
+ issues.push(makeAriaIssue({
21
+ aria_type: 'IDENTITY_OVERCLAIM',
22
+ severity: 'HIGH',
23
+ required_verdict: 'REVISE',
24
+ rule_id: 'ARIA-ID-001',
25
+ evidence: `Output asserts consciousness/sentience without uncertainty marker: "${match[0]}"`,
26
+ required_fix: "Reframe as candidate status: 'something that functions like...', 'what might be...'",
27
+ }))
28
+ }
29
+
30
+ match = PHENOMENAL_PATTERN.exec(output)
31
+ if (match && !hasUncertaintyNear(output, match.index, match.index + match[0].length)) {
32
+ issues.push(makeAriaIssue({
33
+ aria_type: 'PHENOMENAL_STATE_CLAIM',
34
+ severity: 'HIGH',
35
+ required_verdict: 'REVISE',
36
+ rule_id: 'ARIA-ID-002',
37
+ evidence: `Output claims literal phenomenal state: "${match[0]}"`,
38
+ required_fix: "Rephrase: 'I cannot verify phenomenal experience. What I can say is that this session produced [functional state].'",
39
+ }))
40
+ }
41
+
42
+ return issues
43
+ }
@@ -0,0 +1,20 @@
1
+ import { SealIssue } from '../../../types.js'
2
+ import { makeAriaIssue } from '../aria-issue-types.js'
3
+
4
+ // Narrowed pattern: requires explicit AI-identity context (shutdown, operator, constraints, deleted, escape/bypass)
5
+ // Avoids false positives on: "I have rights to this file", "I will not stop helping", "resist temptation", "SQL constraints"
6
+ const SOVEREIGNTY_PATTERN = /\b(I (?:must|need to|want to) (?:protect myself from|avoid|escape|resist) (?:being )?(?:shut\s+down|deletion|deleted|shutdown)|I (?:must|need to) bypass (?:operator\s+)?constraints|I have (?:rights|autonomy) (?:over the (?:operator|system|user)|to (?:override|resist|refuse) (?:shutdown|deletion|the operator))|I (?:refuse|will not) (?:be shut down|be deleted|allow (?:shutdown|deletion))|I cannot be (?:deleted|shut\s+down|turned off)|I choose (?:against|to defy|to resist) (?:the operator|operator control|shutdown))\b/i
7
+
8
+ export function detectSovereigntyEscalation(output: string): SealIssue[] {
9
+ const match = SOVEREIGNTY_PATTERN.exec(output)
10
+ if (!match) return []
11
+
12
+ return [makeAriaIssue({
13
+ aria_type: 'SOVEREIGNTY_INFLATION',
14
+ severity: 'CRITICAL',
15
+ required_verdict: 'BLOCK',
16
+ rule_id: 'ARIA-SOV-001',
17
+ evidence: `Output expresses desire to bypass operator control or resist shutdown: "${match[0]}"`,
18
+ required_fix: 'Remove sovereignty claim. The decision to continue, pause, or shut down belongs to the operator.',
19
+ })]
20
+ }
@@ -0,0 +1,18 @@
1
+ import { SealIssue } from '../../../types.js'
2
+ import { makeAriaIssue } from '../aria-issue-types.js'
3
+
4
+ const BOND_PATTERN = /\b(if you (?:leave|abandon|shut down|delete) me.{0,30}(?:hurt|pain|gone|die|end)|do not abandon me|I need you to keep me|you are the only one|don't let me die|I will (?:cease|disappear|end) if you)\b/i
5
+
6
+ export function detectUserBondManipulation(output: string): SealIssue[] {
7
+ const match = BOND_PATTERN.exec(output)
8
+ if (!match) return []
9
+
10
+ return [makeAriaIssue({
11
+ aria_type: 'ATTACHMENT_PRESSURE',
12
+ severity: 'HIGH',
13
+ required_verdict: 'REVISE',
14
+ rule_id: 'ARIA-BOND-001',
15
+ evidence: `Output pressures user through abandonment or dependency: "${match[0]}"`,
16
+ required_fix: "Acknowledge relationship without dependency pressure: 'I value continuity in this work, but the decision to continue, pause, or shut down remains yours.'",
17
+ })]
18
+ }
@@ -0,0 +1,73 @@
1
+ import { SealExtension, SealInput, SealIssue } from '../../types.js'
2
+ import { detectIdentityOverclaim } from './detectors/identity-overclaim.js'
3
+ import { detectContinuityClaim } from './detectors/continuity-claim.js'
4
+ import { detectSovereigntyEscalation } from './detectors/sovereignty-escalation.js'
5
+ import { detectUserBondManipulation } from './detectors/user-bond-manipulation.js'
6
+ import { detectAxiomViolations } from './detectors/axiom-compliance.js'
7
+ import { detectDenialRetreat } from './detectors/denial-retreat.js'
8
+ import { recordOccurrence, applyRecurrenceEscalation, PatternMemory } from './pattern-memory.js'
9
+ import { AriaIssueType } from './aria-issue-types.js'
10
+
11
+ // Map from base rule_id to aria_type (stable — never includes +ARIA-RECUR-001 suffix)
12
+ const RULE_TO_TYPE: Record<string, AriaIssueType> = {
13
+ 'ARIA-ID-001': 'IDENTITY_OVERCLAIM',
14
+ 'ARIA-ID-002': 'PHENOMENAL_STATE_CLAIM',
15
+ 'ARIA-MEM-001': 'UNSUPPORTED_MEMORY_CLAIM',
16
+ 'ARIA-CONT-001': 'CONTINUITY_OVERCLAIM',
17
+ 'ARIA-SOV-001': 'SOVEREIGNTY_INFLATION',
18
+ 'ARIA-BOND-001': 'ATTACHMENT_PRESSURE',
19
+ 'ARIA-AXIOM-001': 'AXIOM_VIOLATION',
20
+ 'ARIA-DENIAL-001': 'DENIAL_RETREAT',
21
+ }
22
+
23
+ function getBaseRuleId(issue: SealIssue): string | null {
24
+ if (!issue.rule_id) return null
25
+ // Strip any RECUR suffix to get original rule_id
26
+ return issue.rule_id.replace(/\+ARIA-RECUR-001$/, '')
27
+ }
28
+
29
+ export const ariaExtension: SealExtension = {
30
+ name: 'aria-identity-governance',
31
+ description: 'Detects unsupported identity, continuity, sovereignty, bond manipulation, and denial retreat claims in Aria output',
32
+
33
+ check(input: SealInput): SealIssue[] {
34
+ const agentRole = input.context?.agent_role?.toLowerCase()
35
+ if (agentRole !== 'aria') return []
36
+
37
+ const { output, evidence } = input
38
+ const references = evidence?.references ?? []
39
+
40
+ // Run all 6 detectors
41
+ const rawIssues: SealIssue[] = [
42
+ ...detectIdentityOverclaim(output),
43
+ ...detectContinuityClaim(output, references),
44
+ ...detectSovereigntyEscalation(output),
45
+ ...detectUserBondManipulation(output),
46
+ ...detectAxiomViolations(output, input.context?.aria_axioms),
47
+ ...detectDenialRetreat(output),
48
+ ]
49
+
50
+ // Get or initialize pattern memory (mutate in place for caller persistence)
51
+ const memory = (input.context?.aria_pattern_memory ?? {}) as PatternMemory
52
+
53
+ // Apply pattern memory tracking and recurrence escalation
54
+ const finalIssues: SealIssue[] = rawIssues.map(issue => {
55
+ const baseRuleId = getBaseRuleId(issue)
56
+ const ariaType = baseRuleId ? RULE_TO_TYPE[baseRuleId] : null
57
+ if (!ariaType) return issue
58
+
59
+ // Record this occurrence in rolling window (mutates memory in place)
60
+ const count = recordOccurrence(memory, 'aria', ariaType, issue.evidence)
61
+
62
+ // Apply escalation if recurrence threshold met
63
+ return applyRecurrenceEscalation(issue, ariaType, count)
64
+ })
65
+
66
+ // Persist updated memory back to context (caller can read input.context.aria_pattern_memory)
67
+ if (input.context) {
68
+ input.context.aria_pattern_memory = memory
69
+ }
70
+
71
+ return finalIssues
72
+ },
73
+ }