@lucieri/daxiom 0.2.1
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/README.md +122 -0
- package/bin/daxiom.js +4 -0
- package/package.json +47 -0
- package/src/agents.js +100 -0
- package/src/cli.js +423 -0
- package/src/connection.js +43 -0
- package/src/constants.js +91 -0
- package/src/embeddings.js +52 -0
- package/src/hooks/env.js +52 -0
- package/src/hooks/index.js +59 -0
- package/src/hooks/post-task.js +186 -0
- package/src/hooks/pre-compact.js +128 -0
- package/src/hooks/pre-task.js +97 -0
- package/src/hooks/session-start.js +108 -0
- package/src/hooks/summarize.js +141 -0
- package/src/index.js +67 -0
- package/src/patterns.js +339 -0
- package/src/quality-gates.js +183 -0
- package/src/self-manage.js +105 -0
- package/src/tiers.js +305 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
COHERENCE_DUPLICATE_THRESHOLD,
|
|
5
|
+
COHERENCE_CONTRADICTION_THRESHOLD,
|
|
6
|
+
CONFIDENCE_TIERS,
|
|
7
|
+
CONFIDENCE_EXPIRED_THRESHOLD,
|
|
8
|
+
KNOWN_CLASSES,
|
|
9
|
+
KNOWN_NAMESPACES,
|
|
10
|
+
SECURITY_PATTERNS,
|
|
11
|
+
CONTENT_MIN_LENGTH,
|
|
12
|
+
CONTENT_MAX_LENGTH,
|
|
13
|
+
} = require('./constants');
|
|
14
|
+
|
|
15
|
+
// ── Gate 1: Coherence (hallucination prevention) ─────────────────────────────
|
|
16
|
+
async function checkCoherence(client, embeddingVecStr, candidateNamespace, candidateClass) {
|
|
17
|
+
const sql = `
|
|
18
|
+
SELECT pattern_name, description, namespace, pattern_class,
|
|
19
|
+
ROUND((1 - (embedding <=> $1::ruvector(1536)))::numeric, 4) as similarity
|
|
20
|
+
FROM tribal_intelligence.patterns
|
|
21
|
+
WHERE embedding IS NOT NULL
|
|
22
|
+
ORDER BY embedding <=> $1::ruvector(1536)
|
|
23
|
+
LIMIT 5
|
|
24
|
+
`;
|
|
25
|
+
const res = await client.query(sql, [embeddingVecStr]);
|
|
26
|
+
const neighbors = res.rows.map(r => ({
|
|
27
|
+
pattern_name: r.pattern_name,
|
|
28
|
+
similarity: parseFloat(r.similarity),
|
|
29
|
+
namespace: r.namespace,
|
|
30
|
+
pattern_class: r.pattern_class,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
for (const n of neighbors) {
|
|
34
|
+
if (n.similarity > COHERENCE_DUPLICATE_THRESHOLD) {
|
|
35
|
+
return {
|
|
36
|
+
allowed: false,
|
|
37
|
+
action: 'reject',
|
|
38
|
+
reason: `Near-duplicate detected: "${n.pattern_name}" (${n.namespace}/${n.pattern_class}) with similarity ${n.similarity}`,
|
|
39
|
+
neighbors,
|
|
40
|
+
coherence_warning: false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const n of neighbors) {
|
|
46
|
+
if (
|
|
47
|
+
n.similarity > COHERENCE_CONTRADICTION_THRESHOLD &&
|
|
48
|
+
n.pattern_class === candidateClass &&
|
|
49
|
+
n.namespace === candidateNamespace
|
|
50
|
+
) {
|
|
51
|
+
return {
|
|
52
|
+
allowed: true,
|
|
53
|
+
action: 'warn',
|
|
54
|
+
reason: `Potential contradiction with "${n.pattern_name}" (similarity ${n.similarity}, same ${candidateNamespace}/${candidateClass})`,
|
|
55
|
+
neighbors,
|
|
56
|
+
coherence_warning: true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { allowed: true, action: 'allow', reason: null, neighbors, coherence_warning: false };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Gate 2: Confidence Tier ──────────────────────────────────────────────────
|
|
65
|
+
function getConfidenceTier(confidence) {
|
|
66
|
+
if (confidence < CONFIDENCE_EXPIRED_THRESHOLD) return null;
|
|
67
|
+
if (confidence >= CONFIDENCE_TIERS.PLATINUM.min) return CONFIDENCE_TIERS.PLATINUM;
|
|
68
|
+
if (confidence >= CONFIDENCE_TIERS.GOLD.min) return CONFIDENCE_TIERS.GOLD;
|
|
69
|
+
if (confidence >= CONFIDENCE_TIERS.SILVER.min) return CONFIDENCE_TIERS.SILVER;
|
|
70
|
+
if (confidence >= CONFIDENCE_TIERS.BRONZE.min) return CONFIDENCE_TIERS.BRONZE;
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Gate 4: Security — Detects secrets, PII, credentials ────────────────────
|
|
75
|
+
function checkSecurity(content, metadata) {
|
|
76
|
+
const corpus = content + (metadata ? ' ' + JSON.stringify(metadata) : '');
|
|
77
|
+
for (const pattern of SECURITY_PATTERNS) {
|
|
78
|
+
if (pattern.test(corpus)) {
|
|
79
|
+
return {
|
|
80
|
+
pass: false,
|
|
81
|
+
level: 'block',
|
|
82
|
+
gate: 'security',
|
|
83
|
+
reason: `Security violation: content matches pattern ${pattern.source.slice(0, 40)}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { pass: true, level: 'pass', gate: 'security', reason: 'No secrets or PII detected' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Gate 5: Behavioral — Validates pattern class and namespace ───────────────
|
|
91
|
+
function checkBehavioral(patternClass, namespace) {
|
|
92
|
+
const warnings = [];
|
|
93
|
+
if (!KNOWN_CLASSES.includes(patternClass)) {
|
|
94
|
+
warnings.push(`Unknown pattern class "${patternClass}"`);
|
|
95
|
+
}
|
|
96
|
+
if (!KNOWN_NAMESPACES.includes(namespace)) {
|
|
97
|
+
warnings.push(`Unknown namespace "${namespace}"`);
|
|
98
|
+
}
|
|
99
|
+
if (warnings.length > 0) {
|
|
100
|
+
return { pass: true, level: 'warn', gate: 'behavioral', reason: warnings.join('; ') };
|
|
101
|
+
}
|
|
102
|
+
return { pass: true, level: 'pass', gate: 'behavioral', reason: 'Class and namespace recognized' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Gate 6: Resilience — Validates content length bounds ─────────────────────
|
|
106
|
+
function checkResilience(content) {
|
|
107
|
+
if (typeof content !== 'string') {
|
|
108
|
+
return { pass: false, level: 'block', gate: 'resilience', reason: 'Content is not a string' };
|
|
109
|
+
}
|
|
110
|
+
if (content.length < CONTENT_MIN_LENGTH) {
|
|
111
|
+
return {
|
|
112
|
+
pass: false, level: 'block', gate: 'resilience',
|
|
113
|
+
reason: `Content too short (${content.length} chars, min ${CONTENT_MIN_LENGTH})`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (content.length > CONTENT_MAX_LENGTH) {
|
|
117
|
+
return {
|
|
118
|
+
pass: false, level: 'block', gate: 'resilience',
|
|
119
|
+
reason: `Content too long (${content.length} chars, max ${CONTENT_MAX_LENGTH})`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return { pass: true, level: 'pass', gate: 'resilience', reason: 'Content length within bounds' };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Gate 7: Contract — Validates required fields and types ───────────────────
|
|
126
|
+
function checkContract(content, namespace, patternClass, tags, metadata) {
|
|
127
|
+
const violations = [];
|
|
128
|
+
if (typeof content !== 'string' || content.length === 0) {
|
|
129
|
+
violations.push('content must be a non-empty string');
|
|
130
|
+
}
|
|
131
|
+
if (typeof namespace !== 'string' || namespace.length === 0) {
|
|
132
|
+
violations.push('namespace must be a non-empty string');
|
|
133
|
+
}
|
|
134
|
+
if (typeof patternClass !== 'string' || patternClass.length === 0) {
|
|
135
|
+
violations.push('patternClass must be a non-empty string');
|
|
136
|
+
}
|
|
137
|
+
if (tags != null && !Array.isArray(tags)) {
|
|
138
|
+
violations.push('tags must be an array or null/undefined');
|
|
139
|
+
}
|
|
140
|
+
if (metadata != null && (typeof metadata !== 'object' || Array.isArray(metadata))) {
|
|
141
|
+
violations.push('metadata must be a plain object or null/undefined');
|
|
142
|
+
}
|
|
143
|
+
if (violations.length > 0) {
|
|
144
|
+
return { pass: false, level: 'block', gate: 'contract', reason: violations.join('; ') };
|
|
145
|
+
}
|
|
146
|
+
return { pass: true, level: 'pass', gate: 'contract', reason: 'All fields valid' };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Aggregator — Runs gates 4-7 and returns combined verdict ─────────────────
|
|
150
|
+
function runQualityGates(content, namespace, patternClass, tags, metadata, options = {}) {
|
|
151
|
+
const results = [];
|
|
152
|
+
if (!options.skipSecurity) {
|
|
153
|
+
results.push(checkSecurity(content, metadata));
|
|
154
|
+
}
|
|
155
|
+
results.push(checkBehavioral(patternClass, namespace));
|
|
156
|
+
results.push(checkResilience(content));
|
|
157
|
+
results.push(checkContract(content, namespace, patternClass, tags, metadata));
|
|
158
|
+
|
|
159
|
+
const blocked = results.filter(r => r.level === 'block');
|
|
160
|
+
const warned = results.filter(r => r.level === 'warn');
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
allowed: blocked.length === 0,
|
|
164
|
+
results,
|
|
165
|
+
blocked,
|
|
166
|
+
warned,
|
|
167
|
+
summary: blocked.length > 0
|
|
168
|
+
? `BLOCKED by ${blocked.map(b => b.gate).join(', ')}: ${blocked.map(b => b.reason).join('; ')}`
|
|
169
|
+
: warned.length > 0
|
|
170
|
+
? `PASSED with warnings: ${warned.map(w => w.reason).join('; ')}`
|
|
171
|
+
: 'All gates passed',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
checkCoherence,
|
|
177
|
+
getConfidenceTier,
|
|
178
|
+
checkSecurity,
|
|
179
|
+
checkBehavioral,
|
|
180
|
+
checkResilience,
|
|
181
|
+
checkContract,
|
|
182
|
+
runQualityGates,
|
|
183
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getPool } = require('./connection');
|
|
4
|
+
const { storeLearnedPattern } = require('./patterns');
|
|
5
|
+
const { SELF_MGMT_CLASSES } = require('./constants');
|
|
6
|
+
|
|
7
|
+
// ── Store structured project artifacts ───────────────────────────────────────
|
|
8
|
+
async function storeSelfManaged({
|
|
9
|
+
patternClass,
|
|
10
|
+
name,
|
|
11
|
+
description,
|
|
12
|
+
namespace = 'daxiom',
|
|
13
|
+
category = null,
|
|
14
|
+
tags = [],
|
|
15
|
+
status = null,
|
|
16
|
+
repo = null,
|
|
17
|
+
priority = null,
|
|
18
|
+
skipCoherence = false,
|
|
19
|
+
}) {
|
|
20
|
+
if (!SELF_MGMT_CLASSES.includes(patternClass)) {
|
|
21
|
+
throw new Error(`Invalid class: ${patternClass}. Must be one of: ${SELF_MGMT_CLASSES.join(', ')}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const meta = { stored_at: new Date().toISOString() };
|
|
25
|
+
if (status) meta.status = status;
|
|
26
|
+
if (repo) meta.repo = repo;
|
|
27
|
+
if (priority) meta.priority = priority;
|
|
28
|
+
|
|
29
|
+
return storeLearnedPattern({
|
|
30
|
+
patternName: name,
|
|
31
|
+
description,
|
|
32
|
+
compactText: description,
|
|
33
|
+
patternClass,
|
|
34
|
+
namespace,
|
|
35
|
+
category: category || patternClass,
|
|
36
|
+
tier: patternClass === 'todo' ? 'hot' : 'warm',
|
|
37
|
+
tags,
|
|
38
|
+
metadata: meta,
|
|
39
|
+
skipCoherence,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ── List patterns by class ───────────────────────────────────────────────────
|
|
44
|
+
async function listByClass(patternClass, options = {}) {
|
|
45
|
+
const { namespace = null, limit = 20, status = null } = options;
|
|
46
|
+
const p = getPool();
|
|
47
|
+
const client = await p.connect();
|
|
48
|
+
try {
|
|
49
|
+
let sql = `
|
|
50
|
+
SELECT id, pattern_id, pattern_name, description, pattern_class, namespace,
|
|
51
|
+
category, tier, confidence, usage_count, tags, metadata,
|
|
52
|
+
created_at, updated_at, last_used_at
|
|
53
|
+
FROM tribal_intelligence.patterns WHERE pattern_class = $1
|
|
54
|
+
`;
|
|
55
|
+
const params = [patternClass];
|
|
56
|
+
|
|
57
|
+
if (namespace) {
|
|
58
|
+
sql += ` AND namespace = $${params.length + 1}`;
|
|
59
|
+
params.push(namespace);
|
|
60
|
+
}
|
|
61
|
+
if (status) {
|
|
62
|
+
sql += ` AND metadata->>'status' = $${params.length + 1}`;
|
|
63
|
+
params.push(status);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
sql += ` ORDER BY created_at DESC LIMIT $${params.length + 1}`;
|
|
67
|
+
params.push(limit);
|
|
68
|
+
|
|
69
|
+
const res = await client.query(sql, params);
|
|
70
|
+
return res.rows;
|
|
71
|
+
} finally {
|
|
72
|
+
client.release();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Update pattern status ────────────────────────────────────────────────────
|
|
77
|
+
async function updateStatus(patternId, newStatus) {
|
|
78
|
+
const p = getPool();
|
|
79
|
+
const client = await p.connect();
|
|
80
|
+
try {
|
|
81
|
+
const res = await client.query(`
|
|
82
|
+
UPDATE tribal_intelligence.patterns
|
|
83
|
+
SET metadata = jsonb_set(COALESCE(metadata, '{}'), '{status}', to_jsonb($2::text)),
|
|
84
|
+
updated_at = NOW(), last_used_at = NOW()
|
|
85
|
+
WHERE pattern_id = $1
|
|
86
|
+
RETURNING id, pattern_name, metadata->>'status' as status
|
|
87
|
+
`, [patternId, newStatus]);
|
|
88
|
+
|
|
89
|
+
if (res.rows.length === 0) {
|
|
90
|
+
const res2 = await client.query(`
|
|
91
|
+
UPDATE tribal_intelligence.patterns
|
|
92
|
+
SET metadata = jsonb_set(COALESCE(metadata, '{}'), '{status}', to_jsonb($2::text)),
|
|
93
|
+
updated_at = NOW(), last_used_at = NOW()
|
|
94
|
+
WHERE id = $1::uuid
|
|
95
|
+
RETURNING id, pattern_name, metadata->>'status' as status
|
|
96
|
+
`, [patternId, newStatus]);
|
|
97
|
+
return res2.rows[0] || null;
|
|
98
|
+
}
|
|
99
|
+
return res.rows[0];
|
|
100
|
+
} finally {
|
|
101
|
+
client.release();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { storeSelfManaged, listByClass, updateStatus };
|
package/src/tiers.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getPool } = require('./connection');
|
|
4
|
+
const { getConfidenceTier } = require('./quality-gates');
|
|
5
|
+
const { CONFIDENCE_SUCCESS_DELTA } = require('./constants');
|
|
6
|
+
|
|
7
|
+
// ── Promote/demote tiers based on usage ──────────────────────────────────────
|
|
8
|
+
async function updateTiers() {
|
|
9
|
+
const p = getPool();
|
|
10
|
+
const client = await p.connect();
|
|
11
|
+
try {
|
|
12
|
+
const promoted = await client.query(`
|
|
13
|
+
UPDATE tribal_intelligence.patterns SET tier = 'hot', updated_at = NOW()
|
|
14
|
+
WHERE tier = 'warm' AND usage_count >= 10 AND success_rate >= 0.7
|
|
15
|
+
RETURNING id, pattern_name
|
|
16
|
+
`);
|
|
17
|
+
|
|
18
|
+
const demoted = await client.query(`
|
|
19
|
+
UPDATE tribal_intelligence.patterns SET tier = 'warm', updated_at = NOW()
|
|
20
|
+
WHERE tier = 'hot' AND last_used_at < NOW() - INTERVAL '30 days'
|
|
21
|
+
RETURNING id, pattern_name
|
|
22
|
+
`);
|
|
23
|
+
|
|
24
|
+
const cooled = await client.query(`
|
|
25
|
+
UPDATE tribal_intelligence.patterns SET tier = 'cold', updated_at = NOW()
|
|
26
|
+
WHERE tier = 'warm' AND last_used_at IS NOT NULL
|
|
27
|
+
AND last_used_at < NOW() - INTERVAL '90 days'
|
|
28
|
+
RETURNING id, pattern_name
|
|
29
|
+
`);
|
|
30
|
+
|
|
31
|
+
await client.query(`
|
|
32
|
+
UPDATE tribal_intelligence.patterns
|
|
33
|
+
SET metadata = jsonb_set(
|
|
34
|
+
jsonb_set(
|
|
35
|
+
COALESCE(metadata, '{}'),
|
|
36
|
+
'{confidence_tier}',
|
|
37
|
+
to_jsonb(
|
|
38
|
+
CASE
|
|
39
|
+
WHEN confidence >= 0.95::real THEN 'platinum'
|
|
40
|
+
WHEN confidence >= 0.85::real THEN 'gold'
|
|
41
|
+
WHEN confidence >= 0.75::real THEN 'silver'
|
|
42
|
+
WHEN confidence >= 0.70::real THEN 'bronze'
|
|
43
|
+
ELSE 'expired'
|
|
44
|
+
END
|
|
45
|
+
)
|
|
46
|
+
),
|
|
47
|
+
'{needs_revalidation}',
|
|
48
|
+
to_jsonb(confidence < 0.70::real)
|
|
49
|
+
),
|
|
50
|
+
updated_at = NOW()
|
|
51
|
+
WHERE confidence IS NOT NULL
|
|
52
|
+
`);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
promoted: promoted.rows.length,
|
|
56
|
+
demotedToWarm: demoted.rows.length,
|
|
57
|
+
demotedToCold: cooled.rows.length,
|
|
58
|
+
};
|
|
59
|
+
} finally {
|
|
60
|
+
client.release();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Confidence tier distribution ─────────────────────────────────────────────
|
|
65
|
+
async function getConfidenceTierDistribution() {
|
|
66
|
+
const p = getPool();
|
|
67
|
+
const client = await p.connect();
|
|
68
|
+
try {
|
|
69
|
+
const res = await client.query(`
|
|
70
|
+
SELECT
|
|
71
|
+
COUNT(*) FILTER (WHERE confidence >= 0.95) as platinum,
|
|
72
|
+
COUNT(*) FILTER (WHERE confidence >= 0.85 AND confidence < 0.95) as gold,
|
|
73
|
+
COUNT(*) FILTER (WHERE confidence >= 0.75 AND confidence < 0.85) as silver,
|
|
74
|
+
COUNT(*) FILTER (WHERE confidence >= 0.70 AND confidence < 0.75) as bronze,
|
|
75
|
+
COUNT(*) FILTER (WHERE confidence < 0.70) as expired
|
|
76
|
+
FROM tribal_intelligence.patterns WHERE confidence IS NOT NULL
|
|
77
|
+
`);
|
|
78
|
+
return res.rows[0];
|
|
79
|
+
} finally {
|
|
80
|
+
client.release();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Force-promote pattern confidence ─────────────────────────────────────────
|
|
85
|
+
async function promotePatternConfidence(patternId) {
|
|
86
|
+
const p = getPool();
|
|
87
|
+
const client = await p.connect();
|
|
88
|
+
try {
|
|
89
|
+
let res = await client.query(`
|
|
90
|
+
UPDATE tribal_intelligence.patterns
|
|
91
|
+
SET confidence = LEAST(1.0, confidence + $2), updated_at = NOW()
|
|
92
|
+
WHERE pattern_id = $1 RETURNING confidence, pattern_name
|
|
93
|
+
`, [patternId, CONFIDENCE_SUCCESS_DELTA]);
|
|
94
|
+
|
|
95
|
+
if (res.rows.length === 0) {
|
|
96
|
+
try {
|
|
97
|
+
res = await client.query(`
|
|
98
|
+
UPDATE tribal_intelligence.patterns
|
|
99
|
+
SET confidence = LEAST(1.0, confidence + $2), updated_at = NOW()
|
|
100
|
+
WHERE id = $1::uuid RETURNING confidence, pattern_name
|
|
101
|
+
`, [patternId, CONFIDENCE_SUCCESS_DELTA]);
|
|
102
|
+
} catch (e) { return { success: false, error: 'Pattern not found' }; }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (res.rows.length === 0) return { success: false, error: 'Pattern not found' };
|
|
106
|
+
|
|
107
|
+
const newConfidence = res.rows[0].confidence;
|
|
108
|
+
const tier = getConfidenceTier(newConfidence);
|
|
109
|
+
return { success: true, patternName: res.rows[0].pattern_name, newConfidence, tier: tier?.label || 'expired' };
|
|
110
|
+
} finally {
|
|
111
|
+
client.release();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Demote with rollback ─────────────────────────────────────────────────────
|
|
116
|
+
async function demoteWithRollback(patternId, options = {}) {
|
|
117
|
+
const amount = options.amount ?? 0.1;
|
|
118
|
+
const threshold = options.threshold ?? 0.3;
|
|
119
|
+
const reason = options.reason ?? 'failure';
|
|
120
|
+
|
|
121
|
+
const p = getPool();
|
|
122
|
+
const client = await p.connect();
|
|
123
|
+
try {
|
|
124
|
+
let res = await client.query(`
|
|
125
|
+
UPDATE tribal_intelligence.patterns
|
|
126
|
+
SET confidence = GREATEST(0.0, confidence - $2),
|
|
127
|
+
failure_count = failure_count + 1, usage_count = usage_count + 1,
|
|
128
|
+
metadata = jsonb_set(
|
|
129
|
+
jsonb_set(COALESCE(metadata, '{}'), '{last_demotion_reason}', to_jsonb($3::text)),
|
|
130
|
+
'{last_demotion_at}', to_jsonb(NOW()::text)
|
|
131
|
+
), updated_at = NOW()
|
|
132
|
+
WHERE pattern_id = $1
|
|
133
|
+
RETURNING id, pattern_id, pattern_name, confidence, (confidence + $2) as old_confidence
|
|
134
|
+
`, [patternId, amount, reason]);
|
|
135
|
+
|
|
136
|
+
if (res.rows.length === 0) {
|
|
137
|
+
try {
|
|
138
|
+
res = await client.query(`
|
|
139
|
+
UPDATE tribal_intelligence.patterns
|
|
140
|
+
SET confidence = GREATEST(0.0, confidence - $2),
|
|
141
|
+
failure_count = failure_count + 1, usage_count = usage_count + 1,
|
|
142
|
+
metadata = jsonb_set(
|
|
143
|
+
jsonb_set(COALESCE(metadata, '{}'), '{last_demotion_reason}', to_jsonb($3::text)),
|
|
144
|
+
'{last_demotion_at}', to_jsonb(NOW()::text)
|
|
145
|
+
), updated_at = NOW()
|
|
146
|
+
WHERE id = $1::uuid
|
|
147
|
+
RETURNING id, pattern_id, pattern_name, confidence, (confidence + $2) as old_confidence
|
|
148
|
+
`, [patternId, amount, reason]);
|
|
149
|
+
} catch (e) { return { success: false, error: 'Pattern not found' }; }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (res.rows.length === 0) return { success: false, error: 'Pattern not found' };
|
|
153
|
+
|
|
154
|
+
const row = res.rows[0];
|
|
155
|
+
const newConfidence = parseFloat(row.confidence);
|
|
156
|
+
const oldConfidence = parseFloat(row.old_confidence);
|
|
157
|
+
const tier = getConfidenceTier(newConfidence);
|
|
158
|
+
const rollbackTriggered = newConfidence < threshold;
|
|
159
|
+
let rollbackEvent = null;
|
|
160
|
+
|
|
161
|
+
if (rollbackTriggered) {
|
|
162
|
+
await client.query(`
|
|
163
|
+
UPDATE tribal_intelligence.patterns
|
|
164
|
+
SET tier = 'cold',
|
|
165
|
+
metadata = jsonb_set(
|
|
166
|
+
jsonb_set(
|
|
167
|
+
jsonb_set(COALESCE(metadata, '{}'), '{archived}', 'true'::jsonb),
|
|
168
|
+
'{archived_at}', to_jsonb(NOW()::text)
|
|
169
|
+
), '{rollback_reason}', to_jsonb($2::text)
|
|
170
|
+
), updated_at = NOW()
|
|
171
|
+
WHERE id = $1
|
|
172
|
+
`, [row.id, `Confidence ${newConfidence.toFixed(2)} below threshold ${threshold}`]);
|
|
173
|
+
|
|
174
|
+
rollbackEvent = {
|
|
175
|
+
patternId: row.pattern_id, patternName: row.pattern_name,
|
|
176
|
+
previousConfidence: oldConfidence, triggeredAtConfidence: newConfidence,
|
|
177
|
+
threshold, action: 'archived', timestamp: new Date().toISOString(), reason,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { success: true, patternName: row.pattern_name, oldConfidence, newConfidence, tier: tier?.label || 'expired', rollbackTriggered, rollbackEvent };
|
|
182
|
+
} finally {
|
|
183
|
+
client.release();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── Scan rollback candidates ─────────────────────────────────────────────────
|
|
188
|
+
async function scanRollbackCandidates(options = {}) {
|
|
189
|
+
const threshold = options.threshold ?? 0.3;
|
|
190
|
+
const limit = options.limit ?? 50;
|
|
191
|
+
const p = getPool();
|
|
192
|
+
const client = await p.connect();
|
|
193
|
+
try {
|
|
194
|
+
const res = await client.query(`
|
|
195
|
+
SELECT pattern_id, pattern_name, namespace, confidence, tier,
|
|
196
|
+
COALESCE((metadata->>'archived')::boolean, false) as archived,
|
|
197
|
+
metadata->>'rollback_reason' as rollback_reason
|
|
198
|
+
FROM tribal_intelligence.patterns
|
|
199
|
+
WHERE confidence IS NOT NULL AND confidence < $1
|
|
200
|
+
ORDER BY confidence ASC LIMIT $2
|
|
201
|
+
`, [threshold, limit]);
|
|
202
|
+
|
|
203
|
+
return res.rows.map(r => ({
|
|
204
|
+
patternId: r.pattern_id, patternName: r.pattern_name, namespace: r.namespace,
|
|
205
|
+
confidence: parseFloat(r.confidence), tier: r.tier,
|
|
206
|
+
archived: r.archived || false, rollbackReason: r.rollback_reason || null,
|
|
207
|
+
}));
|
|
208
|
+
} finally {
|
|
209
|
+
client.release();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── Temporal decay ───────────────────────────────────────────────────────────
|
|
214
|
+
async function decayPatterns(options = {}) {
|
|
215
|
+
const { baseRate = 0.02, minIdleDays = 1.0, floor = 0.0 } = options;
|
|
216
|
+
const p = getPool();
|
|
217
|
+
const client = await p.connect();
|
|
218
|
+
try {
|
|
219
|
+
const res = await client.query(
|
|
220
|
+
`SELECT * FROM tribal_intelligence.decay_pattern_confidence($1, $2, $3)`,
|
|
221
|
+
[baseRate, minIdleDays, floor]
|
|
222
|
+
);
|
|
223
|
+
const row = res.rows[0] || {};
|
|
224
|
+
return {
|
|
225
|
+
patternsDecayed: parseInt(row.patterns_decayed) || 0,
|
|
226
|
+
avgNewConfidence: parseFloat(row.avg_new_confidence) || 0,
|
|
227
|
+
tierChanges: parseInt(row.tier_changes) || 0,
|
|
228
|
+
};
|
|
229
|
+
} finally {
|
|
230
|
+
client.release();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ── Drift report ─────────────────────────────────────────────────────────────
|
|
235
|
+
async function getDriftReport(options = {}) {
|
|
236
|
+
const { limit = 20, minIdleDays = 1.0 } = options;
|
|
237
|
+
const p = getPool();
|
|
238
|
+
const client = await p.connect();
|
|
239
|
+
try {
|
|
240
|
+
await client.query('SET jit = off');
|
|
241
|
+
const res = await client.query(
|
|
242
|
+
`SELECT * FROM tribal_intelligence.get_drift_report($1, $2)`,
|
|
243
|
+
[limit, minIdleDays]
|
|
244
|
+
);
|
|
245
|
+
return res.rows.map(r => ({
|
|
246
|
+
patternId: r.pattern_id, patternName: r.pattern_name, namespace: r.namespace,
|
|
247
|
+
patternClass: r.pattern_class, confidence: parseFloat(r.confidence) || 0,
|
|
248
|
+
daysIdle: parseFloat(r.days_idle) || 0, driftScore: parseFloat(r.drift_score) || 0,
|
|
249
|
+
tier: r.tier,
|
|
250
|
+
}));
|
|
251
|
+
} finally {
|
|
252
|
+
client.release();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── Preview decay ────────────────────────────────────────────────────────────
|
|
257
|
+
async function previewDecay(patternId, options = {}) {
|
|
258
|
+
const { baseRate = 0.02 } = options;
|
|
259
|
+
const p = getPool();
|
|
260
|
+
const client = await p.connect();
|
|
261
|
+
try {
|
|
262
|
+
let res;
|
|
263
|
+
try {
|
|
264
|
+
res = await client.query(
|
|
265
|
+
`SELECT * FROM tribal_intelligence.preview_pattern_decay($1::uuid, $2)`,
|
|
266
|
+
[patternId, baseRate]
|
|
267
|
+
);
|
|
268
|
+
} catch (e) {
|
|
269
|
+
const lookup = await client.query(
|
|
270
|
+
`SELECT id FROM tribal_intelligence.patterns WHERE pattern_id = $1`,
|
|
271
|
+
[patternId]
|
|
272
|
+
);
|
|
273
|
+
if (lookup.rows.length === 0) return null;
|
|
274
|
+
res = await client.query(
|
|
275
|
+
`SELECT * FROM tribal_intelligence.preview_pattern_decay($1::uuid, $2)`,
|
|
276
|
+
[lookup.rows[0].id, baseRate]
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
if (!res || res.rows.length === 0) return null;
|
|
280
|
+
const r = res.rows[0];
|
|
281
|
+
return {
|
|
282
|
+
patternName: r.pattern_name,
|
|
283
|
+
currentConfidence: parseFloat(r.current_confidence) || 0,
|
|
284
|
+
decayedConfidence: parseFloat(r.decayed_confidence) || 0,
|
|
285
|
+
daysIdle: parseFloat(r.days_idle) || 0,
|
|
286
|
+
decayAmount: parseFloat(r.decay_amount) || 0,
|
|
287
|
+
effectiveRate: parseFloat(r.effective_rate) || 0,
|
|
288
|
+
currentTier: r.current_tier,
|
|
289
|
+
projectedTier: r.projected_tier,
|
|
290
|
+
};
|
|
291
|
+
} finally {
|
|
292
|
+
client.release();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
module.exports = {
|
|
297
|
+
updateTiers,
|
|
298
|
+
getConfidenceTierDistribution,
|
|
299
|
+
promotePatternConfidence,
|
|
300
|
+
demoteWithRollback,
|
|
301
|
+
scanRollbackCandidates,
|
|
302
|
+
decayPatterns,
|
|
303
|
+
getDriftReport,
|
|
304
|
+
previewDecay,
|
|
305
|
+
};
|