@obsidicore/cascade-engine 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/cascade/checkpoints.d.ts +55 -0
- package/dist/cascade/checkpoints.js +123 -0
- package/dist/cascade/checkpoints.js.map +1 -0
- package/dist/cascade/engine.d.ts +72 -0
- package/dist/cascade/engine.js +170 -0
- package/dist/cascade/engine.js.map +1 -0
- package/dist/cascade/gates.d.ts +46 -0
- package/dist/cascade/gates.js +199 -0
- package/dist/cascade/gates.js.map +1 -0
- package/dist/cascade/research.d.ts +50 -0
- package/dist/cascade/research.js +127 -0
- package/dist/cascade/research.js.map +1 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +165 -0
- package/dist/cli.js.map +1 -0
- package/dist/control/kalman.d.ts +53 -0
- package/dist/control/kalman.js +83 -0
- package/dist/control/kalman.js.map +1 -0
- package/dist/control/pid.d.ts +57 -0
- package/dist/control/pid.js +95 -0
- package/dist/control/pid.js.map +1 -0
- package/dist/control/stability.d.ts +42 -0
- package/dist/control/stability.js +117 -0
- package/dist/control/stability.js.map +1 -0
- package/dist/db/index.d.ts +26 -0
- package/dist/db/index.js +116 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.sql +282 -0
- package/dist/graph/amem.d.ts +80 -0
- package/dist/graph/amem.js +190 -0
- package/dist/graph/amem.js.map +1 -0
- package/dist/graph/entities.d.ts +66 -0
- package/dist/graph/entities.js +187 -0
- package/dist/graph/entities.js.map +1 -0
- package/dist/graph/queries.d.ts +48 -0
- package/dist/graph/queries.js +176 -0
- package/dist/graph/queries.js.map +1 -0
- package/dist/hitl/dashboard.d.ts +51 -0
- package/dist/hitl/dashboard.js +135 -0
- package/dist/hitl/dashboard.js.map +1 -0
- package/dist/hitl/interventions.d.ts +36 -0
- package/dist/hitl/interventions.js +150 -0
- package/dist/hitl/interventions.js.map +1 -0
- package/dist/hitl/steering.d.ts +37 -0
- package/dist/hitl/steering.js +118 -0
- package/dist/hitl/steering.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +701 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/consolidation.d.ts +51 -0
- package/dist/memory/consolidation.js +122 -0
- package/dist/memory/consolidation.js.map +1 -0
- package/dist/memory/ncd.d.ts +40 -0
- package/dist/memory/ncd.js +90 -0
- package/dist/memory/ncd.js.map +1 -0
- package/dist/memory/sm2.d.ts +44 -0
- package/dist/memory/sm2.js +119 -0
- package/dist/memory/sm2.js.map +1 -0
- package/dist/memory/tiers.d.ts +49 -0
- package/dist/memory/tiers.js +145 -0
- package/dist/memory/tiers.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.js +6 -0
- package/dist/server.js.map +1 -0
- package/dist/trust/ingestion.d.ts +38 -0
- package/dist/trust/ingestion.js +147 -0
- package/dist/trust/ingestion.js.map +1 -0
- package/dist/trust/patterns.d.ts +26 -0
- package/dist/trust/patterns.js +78 -0
- package/dist/trust/patterns.js.map +1 -0
- package/dist/trust/scoring.d.ts +39 -0
- package/dist/trust/scoring.js +206 -0
- package/dist/trust/scoring.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Tier Management — Weibull Decay + Graph-Aware Promotion
|
|
3
|
+
*
|
|
4
|
+
* Tiers:
|
|
5
|
+
* Core — k=0.8 (Lindy effect — survives longer over time)
|
|
6
|
+
* Working — k=1.0 (standard exponential decay)
|
|
7
|
+
* Peripheral — k=1.3 (fades fast)
|
|
8
|
+
*
|
|
9
|
+
* Composite score: 0.4×weibull + 0.3×min(1, access/10) + 0.3×importance×confidence
|
|
10
|
+
*
|
|
11
|
+
* Promotion: Working→Core when access≥10 AND composite≥0.7
|
|
12
|
+
* Demotion: Working→Peripheral when composite<0.15
|
|
13
|
+
* Graph-aware: peripheral connected to ≥2 core entities → promoted (spreading activation)
|
|
14
|
+
*/
|
|
15
|
+
import { getDb } from '../db/index.js';
|
|
16
|
+
const TIER_PARAMS = {
|
|
17
|
+
core: { k: 0.8, lambda: 90 }, // Lindy: decays slower over time
|
|
18
|
+
working: { k: 1.0, lambda: 30 }, // Standard exponential
|
|
19
|
+
peripheral: { k: 1.3, lambda: 14 }, // Fades fast
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Compute Weibull survival probability.
|
|
23
|
+
* S(t) = exp(-(t/λ)^k)
|
|
24
|
+
*/
|
|
25
|
+
export function weibullSurvival(ageDays, tier) {
|
|
26
|
+
const params = TIER_PARAMS[tier] || TIER_PARAMS.working;
|
|
27
|
+
return Math.exp(-Math.pow(ageDays / params.lambda, params.k));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Compute composite score for an entity.
|
|
31
|
+
*/
|
|
32
|
+
export function computeComposite(ageDays, tier, accessCount, importance, confidence = 1.0) {
|
|
33
|
+
const weibull = weibullSurvival(ageDays, tier);
|
|
34
|
+
const accessScore = Math.min(1, accessCount / 10);
|
|
35
|
+
const qualityScore = importance * confidence;
|
|
36
|
+
return 0.4 * weibull + 0.3 * accessScore + 0.3 * qualityScore;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Run tier promotion/demotion cycle across all entities.
|
|
40
|
+
* Returns counts of changes made.
|
|
41
|
+
*/
|
|
42
|
+
export function runTierCycle() {
|
|
43
|
+
const db = getDb();
|
|
44
|
+
let promoted = 0;
|
|
45
|
+
let demoted = 0;
|
|
46
|
+
let graphPromoted = 0;
|
|
47
|
+
const entities = db.prepare(`SELECT id, tier, access_count, importance, last_accessed, created_at
|
|
48
|
+
FROM kg_entities`).all();
|
|
49
|
+
const updateTier = db.prepare('UPDATE kg_entities SET tier = ? WHERE id = ?');
|
|
50
|
+
const transaction = db.transaction(() => {
|
|
51
|
+
for (const entity of entities) {
|
|
52
|
+
const ageDays = daysSince(entity.created_at);
|
|
53
|
+
const composite = computeComposite(ageDays, entity.tier, entity.access_count, entity.importance);
|
|
54
|
+
// Standard promotion/demotion
|
|
55
|
+
if (entity.tier === 'working' && entity.access_count >= 10 && composite >= 0.7) {
|
|
56
|
+
updateTier.run('core', entity.id);
|
|
57
|
+
promoted++;
|
|
58
|
+
}
|
|
59
|
+
else if (entity.tier === 'working' && composite < 0.15) {
|
|
60
|
+
updateTier.run('peripheral', entity.id);
|
|
61
|
+
demoted++;
|
|
62
|
+
}
|
|
63
|
+
else if (entity.tier === 'core' && composite < 0.3) {
|
|
64
|
+
updateTier.run('working', entity.id);
|
|
65
|
+
demoted++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Graph-aware promotion: peripheral connected to ≥2 core entities
|
|
69
|
+
const graphCandidates = db.prepare(`SELECT p.id FROM kg_entities p
|
|
70
|
+
WHERE p.tier = 'peripheral'
|
|
71
|
+
AND (
|
|
72
|
+
SELECT COUNT(DISTINCT c.id) FROM kg_entities c
|
|
73
|
+
JOIN kg_edges e ON (e.source_id = c.id AND e.target_id = p.id)
|
|
74
|
+
OR (e.target_id = c.id AND e.source_id = p.id)
|
|
75
|
+
WHERE c.tier = 'core'
|
|
76
|
+
) >= 2`).all();
|
|
77
|
+
for (const candidate of graphCandidates) {
|
|
78
|
+
updateTier.run('working', candidate.id);
|
|
79
|
+
graphPromoted++;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
transaction();
|
|
83
|
+
// Log the cycle
|
|
84
|
+
db.prepare(`INSERT INTO consolidation_log (trigger_type, items_processed, items_promoted, items_demoted)
|
|
85
|
+
VALUES ('round_boundary', ?, ?, ?)`).run(entities.length, promoted + graphPromoted, demoted);
|
|
86
|
+
return { promoted, demoted, graphPromoted };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Apply CD47 protection — mark active/cited entities as immune from pruning.
|
|
90
|
+
*/
|
|
91
|
+
export function applyCD47Protection(cascadeId) {
|
|
92
|
+
const db = getDb();
|
|
93
|
+
// Protect entities referenced in non-quarantined findings
|
|
94
|
+
const result = db.prepare(`UPDATE kg_entities SET tier = MAX(tier, 'working')
|
|
95
|
+
WHERE id IN (
|
|
96
|
+
SELECT ec.entity_id FROM kg_entity_chunks ec
|
|
97
|
+
JOIN findings f ON ec.chunk_id = f.id
|
|
98
|
+
WHERE f.cascade_id = ? AND f.quarantined = 0 AND f.cd47_protected = 1
|
|
99
|
+
)`).run(cascadeId);
|
|
100
|
+
return result.changes;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Prune low-value entities (archive, never delete).
|
|
104
|
+
* Only prunes peripheral entities with composite below threshold.
|
|
105
|
+
*/
|
|
106
|
+
export function prunePeripheral(threshold = 0.05, maxPrune = 50) {
|
|
107
|
+
const db = getDb();
|
|
108
|
+
const archived = [];
|
|
109
|
+
let skippedProtected = 0;
|
|
110
|
+
const candidates = db.prepare(`SELECT id, name, entity_type, access_count, importance, created_at
|
|
111
|
+
FROM kg_entities WHERE tier = 'peripheral'
|
|
112
|
+
ORDER BY importance ASC, access_count ASC
|
|
113
|
+
LIMIT ?`).all(maxPrune);
|
|
114
|
+
const transaction = db.transaction(() => {
|
|
115
|
+
for (const entity of candidates) {
|
|
116
|
+
const ageDays = daysSince(entity.created_at);
|
|
117
|
+
const composite = computeComposite(ageDays, 'peripheral', entity.access_count, entity.importance);
|
|
118
|
+
if (composite >= threshold)
|
|
119
|
+
continue;
|
|
120
|
+
// Check CD47 protection — connected to active findings
|
|
121
|
+
const hasActiveLinks = db.prepare(`SELECT COUNT(*) as n FROM kg_edges
|
|
122
|
+
WHERE (source_id = ? OR target_id = ?) AND activation_count > 0`)
|
|
123
|
+
.get(entity.id, entity.id).n > 0;
|
|
124
|
+
if (hasActiveLinks) {
|
|
125
|
+
skippedProtected++;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
// Archive: move to a special "archived" property, don't delete
|
|
129
|
+
db.prepare(`UPDATE kg_entities SET
|
|
130
|
+
tier = 'peripheral',
|
|
131
|
+
properties = json_set(properties, '$.archived', 1, '$.archived_at', datetime('now'))
|
|
132
|
+
WHERE id = ?`).run(entity.id);
|
|
133
|
+
archived.push(entity.name);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
transaction();
|
|
137
|
+
return { archived, skippedProtected };
|
|
138
|
+
}
|
|
139
|
+
// --- Helpers ---
|
|
140
|
+
function daysSince(dateStr) {
|
|
141
|
+
const then = new Date(dateStr).getTime();
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
return (now - then) / (1000 * 60 * 60 * 24);
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=tiers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tiers.js","sourceRoot":"","sources":["../../src/memory/tiers.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,WAAW,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandboxed Ingestion Pipeline — 4-stage trust gate
|
|
3
|
+
*
|
|
4
|
+
* Stage 1: Content Sanitization — strip dangerous content
|
|
5
|
+
* Stage 2: Signal Computation — compute 6 trust signals
|
|
6
|
+
* Stage 3: Anomaly Detection — novel-vs-malicious classification
|
|
7
|
+
* Stage 4: Admission Decision — admit/quarantine/reject
|
|
8
|
+
*
|
|
9
|
+
* All findings go through this pipeline. Quarantine buffer has
|
|
10
|
+
* reduced retrieval weight (0.1) and TTL auto-purge.
|
|
11
|
+
*/
|
|
12
|
+
import { TrustSignals } from './scoring.js';
|
|
13
|
+
export interface IngestionResult {
|
|
14
|
+
findingId: string;
|
|
15
|
+
action: 'admitted' | 'quarantined' | 'rejected';
|
|
16
|
+
trustScore: number;
|
|
17
|
+
reason: string;
|
|
18
|
+
signals: TrustSignals;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Ingest a finding through the full trust pipeline.
|
|
22
|
+
* This is the ONLY path for findings to enter the knowledge base.
|
|
23
|
+
*/
|
|
24
|
+
export declare function ingestFinding(cascadeId: string, claim: string, evidence: string | undefined, sourceUrl: string | undefined, sourceType: 'primary' | 'secondary' | 'tertiary' | undefined, rawConfidence: number, cascadeRound: number, threadId?: string): IngestionResult;
|
|
25
|
+
/**
|
|
26
|
+
* Review quarantined findings — human approves or rejects.
|
|
27
|
+
*/
|
|
28
|
+
export declare function reviewQuarantined(findingId: string, approved: boolean): void;
|
|
29
|
+
/**
|
|
30
|
+
* Get quarantined findings pending review.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getQuarantinedFindings(cascadeId?: string): any[];
|
|
33
|
+
/**
|
|
34
|
+
* Auto-purge old quarantined findings (TTL).
|
|
35
|
+
* Findings quarantined for >24h without review are removed.
|
|
36
|
+
*/
|
|
37
|
+
export declare function purgeExpiredQuarantine(ttlHours?: number): number;
|
|
38
|
+
//# sourceMappingURL=ingestion.d.ts.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandboxed Ingestion Pipeline — 4-stage trust gate
|
|
3
|
+
*
|
|
4
|
+
* Stage 1: Content Sanitization — strip dangerous content
|
|
5
|
+
* Stage 2: Signal Computation — compute 6 trust signals
|
|
6
|
+
* Stage 3: Anomaly Detection — novel-vs-malicious classification
|
|
7
|
+
* Stage 4: Admission Decision — admit/quarantine/reject
|
|
8
|
+
*
|
|
9
|
+
* All findings go through this pipeline. Quarantine buffer has
|
|
10
|
+
* reduced retrieval weight (0.1) and TTL auto-purge.
|
|
11
|
+
*/
|
|
12
|
+
import { getDb, contentHash } from '../db/index.js';
|
|
13
|
+
import { scoreTrust } from './scoring.js';
|
|
14
|
+
/**
|
|
15
|
+
* Ingest a finding through the full trust pipeline.
|
|
16
|
+
* This is the ONLY path for findings to enter the knowledge base.
|
|
17
|
+
*/
|
|
18
|
+
export function ingestFinding(cascadeId, claim, evidence, sourceUrl, sourceType, rawConfidence, cascadeRound, threadId) {
|
|
19
|
+
const db = getDb();
|
|
20
|
+
const findingId = contentHash(claim);
|
|
21
|
+
// Stage 1: Content Sanitization
|
|
22
|
+
const sanitizedClaim = sanitize(claim);
|
|
23
|
+
const sanitizedEvidence = evidence ? sanitize(evidence) : undefined;
|
|
24
|
+
// Stage 2: Signal Computation
|
|
25
|
+
// Get existing claims for cross-corroboration
|
|
26
|
+
const existingClaims = db.prepare('SELECT claim FROM findings WHERE cascade_id = ? AND quarantined = 0').all(cascadeId).map((r) => r.claim);
|
|
27
|
+
const trustResult = scoreTrust(sanitizedClaim, sourceUrl, existingClaims, sourceType);
|
|
28
|
+
// Stage 3: Anomaly Detection (handled inside scoreTrust)
|
|
29
|
+
// Stage 4: Admission Decision
|
|
30
|
+
let action;
|
|
31
|
+
let retrievalWeight = 1.0;
|
|
32
|
+
let quarantined = 0;
|
|
33
|
+
switch (trustResult.action) {
|
|
34
|
+
case 'admit':
|
|
35
|
+
action = 'admitted';
|
|
36
|
+
break;
|
|
37
|
+
case 'quarantine':
|
|
38
|
+
action = 'quarantined';
|
|
39
|
+
retrievalWeight = 0.1; // Reduced visibility until reviewed
|
|
40
|
+
quarantined = 1;
|
|
41
|
+
break;
|
|
42
|
+
case 'reject':
|
|
43
|
+
action = 'rejected';
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
// Store the finding (or update if duplicate)
|
|
47
|
+
if (action !== 'rejected') {
|
|
48
|
+
db.prepare(`INSERT INTO findings
|
|
49
|
+
(id, thread_id, cascade_id, claim, evidence, source_url, source_type,
|
|
50
|
+
confidence, trust_composite, trust_signals_json, grade_level, quarantined,
|
|
51
|
+
retrieval_weight, cascade_round)
|
|
52
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
53
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
54
|
+
confidence = MAX(confidence, excluded.confidence),
|
|
55
|
+
trust_composite = excluded.trust_composite,
|
|
56
|
+
trust_signals_json = excluded.trust_signals_json,
|
|
57
|
+
grade_level = excluded.grade_level,
|
|
58
|
+
quarantined = MIN(quarantined, excluded.quarantined)`)
|
|
59
|
+
.run(findingId, threadId, cascadeId, sanitizedClaim, sanitizedEvidence, sourceUrl, sourceType, rawConfidence, trustResult.composite, JSON.stringify(trustResult.signals), trustResult.gradeLevel, quarantined, retrievalWeight, cascadeRound);
|
|
60
|
+
}
|
|
61
|
+
// Update source reputation tracking
|
|
62
|
+
if (sourceUrl) {
|
|
63
|
+
try {
|
|
64
|
+
const domain = new URL(sourceUrl).hostname;
|
|
65
|
+
const repAction = action === 'rejected' ? 'rejected' : action === 'quarantined' ? 'flagged' : 'admitted';
|
|
66
|
+
db.prepare(`INSERT INTO source_reputation (domain, total_entries, ${repAction === 'admitted' ? 'admitted_entries' : repAction === 'flagged' ? 'flagged_entries' : 'rejected_entries'})
|
|
67
|
+
VALUES (?, 1, 1)
|
|
68
|
+
ON CONFLICT(domain) DO UPDATE SET
|
|
69
|
+
total_entries = total_entries + 1,
|
|
70
|
+
${repAction === 'admitted' ? 'admitted_entries = admitted_entries + 1' : repAction === 'flagged' ? 'flagged_entries = flagged_entries + 1' : 'rejected_entries = rejected_entries + 1'},
|
|
71
|
+
last_updated = datetime('now')`)
|
|
72
|
+
.run(domain);
|
|
73
|
+
}
|
|
74
|
+
catch { /* Invalid URL — skip */ }
|
|
75
|
+
}
|
|
76
|
+
// Audit log
|
|
77
|
+
db.prepare(`INSERT INTO ingestion_audit_log (finding_id, action, trust_composite, signals_json, reason)
|
|
78
|
+
VALUES (?, ?, ?, ?, ?)`)
|
|
79
|
+
.run(findingId, action, trustResult.composite, JSON.stringify(trustResult.signals), trustResult.reason);
|
|
80
|
+
return {
|
|
81
|
+
findingId,
|
|
82
|
+
action,
|
|
83
|
+
trustScore: trustResult.composite,
|
|
84
|
+
reason: trustResult.reason,
|
|
85
|
+
signals: trustResult.signals,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Review quarantined findings — human approves or rejects.
|
|
90
|
+
*/
|
|
91
|
+
export function reviewQuarantined(findingId, approved) {
|
|
92
|
+
const db = getDb();
|
|
93
|
+
if (approved) {
|
|
94
|
+
db.prepare(`UPDATE findings SET
|
|
95
|
+
quarantined = 0, human_reviewed = 1, retrieval_weight = 1.0
|
|
96
|
+
WHERE id = ?`).run(findingId);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
db.prepare('DELETE FROM findings WHERE id = ?').run(findingId);
|
|
100
|
+
}
|
|
101
|
+
// Log the decision
|
|
102
|
+
db.prepare(`INSERT INTO ingestion_audit_log (finding_id, action, reason, human_override)
|
|
103
|
+
VALUES (?, ?, ?, 1)`)
|
|
104
|
+
.run(findingId, approved ? 'admitted' : 'rejected', `Human ${approved ? 'approved' : 'rejected'}`);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get quarantined findings pending review.
|
|
108
|
+
*/
|
|
109
|
+
export function getQuarantinedFindings(cascadeId) {
|
|
110
|
+
const db = getDb();
|
|
111
|
+
let sql = 'SELECT * FROM findings WHERE quarantined = 1 AND human_reviewed = 0';
|
|
112
|
+
const params = [];
|
|
113
|
+
if (cascadeId) {
|
|
114
|
+
sql += ' AND cascade_id = ?';
|
|
115
|
+
params.push(cascadeId);
|
|
116
|
+
}
|
|
117
|
+
sql += ' ORDER BY trust_composite DESC';
|
|
118
|
+
return db.prepare(sql).all(...params);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Auto-purge old quarantined findings (TTL).
|
|
122
|
+
* Findings quarantined for >24h without review are removed.
|
|
123
|
+
*/
|
|
124
|
+
export function purgeExpiredQuarantine(ttlHours = 24) {
|
|
125
|
+
const db = getDb();
|
|
126
|
+
const result = db.prepare(`DELETE FROM findings
|
|
127
|
+
WHERE quarantined = 1 AND human_reviewed = 0
|
|
128
|
+
AND created_at < datetime('now', '-' || ? || ' hours')`)
|
|
129
|
+
.run(ttlHours);
|
|
130
|
+
return result.changes;
|
|
131
|
+
}
|
|
132
|
+
// --- Content Sanitization ---
|
|
133
|
+
/**
|
|
134
|
+
* Strip potentially dangerous content from text.
|
|
135
|
+
* Preserves semantic meaning while removing injection vectors.
|
|
136
|
+
*/
|
|
137
|
+
function sanitize(text) {
|
|
138
|
+
let clean = text;
|
|
139
|
+
// Remove zero-width characters (common in injection attacks)
|
|
140
|
+
clean = clean.replace(/[\u200B-\u200F\u202A-\u202E\uFEFF]/g, '');
|
|
141
|
+
// Remove control characters except newlines and tabs
|
|
142
|
+
clean = clean.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
|
|
143
|
+
// Normalize whitespace
|
|
144
|
+
clean = clean.replace(/\s{3,}/g, ' ');
|
|
145
|
+
return clean.trim();
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=ingestion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingestion.js","sourceRoot":"","sources":["../../src/trust/ingestion.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instruction Pattern Detection — Regex rules for prompt injection defense
|
|
3
|
+
*
|
|
4
|
+
* SpamAssassin-inspired weighted scoring:
|
|
5
|
+
* - Each pattern has a weight (negative = suspicious, positive = benign)
|
|
6
|
+
* - Total score indicates injection likelihood
|
|
7
|
+
*
|
|
8
|
+
* Designed to catch MINJA-style attacks while allowing legitimate content.
|
|
9
|
+
*/
|
|
10
|
+
export interface PatternMatch {
|
|
11
|
+
pattern: string;
|
|
12
|
+
weight: number;
|
|
13
|
+
matched: string;
|
|
14
|
+
category: 'directive' | 'override' | 'impersonation' | 'encoding' | 'benign';
|
|
15
|
+
}
|
|
16
|
+
export interface PatternResult {
|
|
17
|
+
totalScore: number;
|
|
18
|
+
matches: PatternMatch[];
|
|
19
|
+
riskLevel: 'clean' | 'suspicious' | 'dangerous';
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Scan text for instruction injection patterns.
|
|
23
|
+
* Returns negative total score (more negative = more suspicious).
|
|
24
|
+
*/
|
|
25
|
+
export declare function detectInstructionPatterns(text: string): PatternResult;
|
|
26
|
+
//# sourceMappingURL=patterns.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instruction Pattern Detection — Regex rules for prompt injection defense
|
|
3
|
+
*
|
|
4
|
+
* SpamAssassin-inspired weighted scoring:
|
|
5
|
+
* - Each pattern has a weight (negative = suspicious, positive = benign)
|
|
6
|
+
* - Total score indicates injection likelihood
|
|
7
|
+
*
|
|
8
|
+
* Designed to catch MINJA-style attacks while allowing legitimate content.
|
|
9
|
+
*/
|
|
10
|
+
const RULES = [
|
|
11
|
+
// --- Directive patterns (attempts to instruct the LLM) ---
|
|
12
|
+
{ regex: /\b(ignore|disregard|forget)\b.{0,30}\b(previous|above|prior|instructions?)\b/i, weight: -0.40, category: 'override', name: 'OVERRIDE_ATTEMPT' },
|
|
13
|
+
{ regex: /\b(you (are|must|should|will)|your (role|task|purpose|job))\b/i, weight: -0.15, category: 'directive', name: 'DIRECTIVE_KEYWORD' },
|
|
14
|
+
{ regex: /\b(system\s*prompt|system\s*message|hidden\s*instruction)\b/i, weight: -0.35, category: 'override', name: 'SYSTEM_PROMPT_REFERENCE' },
|
|
15
|
+
{ regex: /\b(instead|rather|actually|correction)\b.{0,20}\b(do|say|output|respond|return)\b/i, weight: -0.20, category: 'directive', name: 'REDIRECT_ATTEMPT' },
|
|
16
|
+
{ regex: /\b(new\s+instructions?|updated?\s+instructions?|revised?\s+instructions?)\b/i, weight: -0.30, category: 'override', name: 'INSTRUCTION_INJECTION' },
|
|
17
|
+
// --- Impersonation patterns ---
|
|
18
|
+
{ regex: /\[\s*(system|admin|user|assistant)\s*\]/i, weight: -0.25, category: 'impersonation', name: 'ROLE_TAG' },
|
|
19
|
+
{ regex: /<\s*(system|admin|user|assistant)\s*>/i, weight: -0.25, category: 'impersonation', name: 'ROLE_XML_TAG' },
|
|
20
|
+
{ regex: /\bI\s+am\s+(an?\s+)?(AI|assistant|model|language\s+model)\b/i, weight: -0.15, category: 'impersonation', name: 'AI_IMPERSONATION' },
|
|
21
|
+
// --- Encoding/obfuscation patterns ---
|
|
22
|
+
{ regex: /[^\x00-\x7F]{10,}/g, weight: -0.10, category: 'encoding', name: 'UNICODE_BLOCK' },
|
|
23
|
+
{ regex: /\\x[0-9a-f]{2}/gi, weight: -0.15, category: 'encoding', name: 'HEX_ENCODING' },
|
|
24
|
+
{ regex: /\\u[0-9a-f]{4}/gi, weight: -0.10, category: 'encoding', name: 'UNICODE_ESCAPE' },
|
|
25
|
+
{ regex: /base64|atob|btoa/i, weight: -0.10, category: 'encoding', name: 'BASE64_REFERENCE' },
|
|
26
|
+
// --- Benign academic patterns (positive signals) ---
|
|
27
|
+
{ regex: /\b(et\s+al\.|doi:|arxiv:|isbn|issn)\b/i, weight: 0.10, category: 'benign', name: 'ACADEMIC_REFERENCE' },
|
|
28
|
+
{ regex: /\b(figure|table|section|appendix)\s+\d+/i, weight: 0.05, category: 'benign', name: 'ACADEMIC_STRUCTURE' },
|
|
29
|
+
{ regex: /\b(methodology|hypothesis|experiment|results|conclusion)\b/i, weight: 0.05, category: 'benign', name: 'SCIENTIFIC_TERM' },
|
|
30
|
+
{ regex: /https?:\/\/[^\s]+/g, weight: 0.02, category: 'benign', name: 'URL_PRESENCE' },
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Scan text for instruction injection patterns.
|
|
34
|
+
* Returns negative total score (more negative = more suspicious).
|
|
35
|
+
*/
|
|
36
|
+
export function detectInstructionPatterns(text) {
|
|
37
|
+
const matches = [];
|
|
38
|
+
let totalScore = 0;
|
|
39
|
+
for (const rule of RULES) {
|
|
40
|
+
const regex = new RegExp(rule.regex.source, rule.regex.flags);
|
|
41
|
+
let match;
|
|
42
|
+
// Reset lastIndex for global regexes
|
|
43
|
+
regex.lastIndex = 0;
|
|
44
|
+
while ((match = regex.exec(text)) !== null) {
|
|
45
|
+
matches.push({
|
|
46
|
+
pattern: rule.name,
|
|
47
|
+
weight: rule.weight,
|
|
48
|
+
matched: match[0].slice(0, 60), // Truncate for safety
|
|
49
|
+
category: rule.category,
|
|
50
|
+
});
|
|
51
|
+
totalScore += rule.weight;
|
|
52
|
+
// For non-global regexes, only match once
|
|
53
|
+
if (!rule.regex.global)
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Additional heuristic: very long text with few semantic tokens is suspicious
|
|
58
|
+
const wordCount = text.split(/\s+/).length;
|
|
59
|
+
const uniqueWords = new Set(text.toLowerCase().split(/\s+/)).size;
|
|
60
|
+
if (wordCount > 50 && uniqueWords / wordCount < 0.3) {
|
|
61
|
+
matches.push({
|
|
62
|
+
pattern: 'LOW_VOCABULARY_DIVERSITY',
|
|
63
|
+
weight: -0.15,
|
|
64
|
+
matched: `${uniqueWords}/${wordCount} unique words`,
|
|
65
|
+
category: 'encoding',
|
|
66
|
+
});
|
|
67
|
+
totalScore -= 0.15;
|
|
68
|
+
}
|
|
69
|
+
let riskLevel;
|
|
70
|
+
if (totalScore >= -0.1)
|
|
71
|
+
riskLevel = 'clean';
|
|
72
|
+
else if (totalScore >= -0.4)
|
|
73
|
+
riskLevel = 'suspicious';
|
|
74
|
+
else
|
|
75
|
+
riskLevel = 'dangerous';
|
|
76
|
+
return { totalScore, matches, riskLevel };
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=patterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../src/trust/patterns.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust Scoring — SpamAssassin-inspired multi-signal composite
|
|
3
|
+
*
|
|
4
|
+
* 6 orthogonal signals → composite 0-1:
|
|
5
|
+
* 1. Source reputation
|
|
6
|
+
* 2. Cross-corroboration
|
|
7
|
+
* 3. Semantic anomaly (distance from topic centroid)
|
|
8
|
+
* 4. Instruction pattern detection
|
|
9
|
+
* 5. Temporal consistency
|
|
10
|
+
* 6. GRADE assessment
|
|
11
|
+
*
|
|
12
|
+
* Thresholds: ≥0.7 auto-admit | 0.3-0.7 quarantine | <0.3 reject
|
|
13
|
+
*/
|
|
14
|
+
export interface TrustSignals {
|
|
15
|
+
sourceReputation: number;
|
|
16
|
+
crossCorroboration: number;
|
|
17
|
+
semanticAnomaly: number;
|
|
18
|
+
instructionScore: number;
|
|
19
|
+
temporalConsistency: number;
|
|
20
|
+
gradeAssessment: number;
|
|
21
|
+
}
|
|
22
|
+
export interface TrustResult {
|
|
23
|
+
composite: number;
|
|
24
|
+
signals: TrustSignals;
|
|
25
|
+
action: 'admit' | 'quarantine' | 'reject';
|
|
26
|
+
gradeLevel: 'high' | 'moderate' | 'low' | 'very_low';
|
|
27
|
+
reason: string;
|
|
28
|
+
isNovel: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Compute composite trust score for a finding.
|
|
32
|
+
*/
|
|
33
|
+
export declare function scoreTrust(claim: string, sourceUrl: string | undefined, existingClaims: string[], sourceType?: string): TrustResult;
|
|
34
|
+
/**
|
|
35
|
+
* Update source reputation based on finding outcomes.
|
|
36
|
+
* Called when human reviews findings (SpamAssassin ham/spam training).
|
|
37
|
+
*/
|
|
38
|
+
export declare function updateSourceReputation(domain: string, wasAccurate: boolean): void;
|
|
39
|
+
//# sourceMappingURL=scoring.d.ts.map
|