@aikdna/studio-core 0.5.1 → 0.6.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/package.json +1 -1
- package/src/compile/index.js +8 -0
- package/src/governance/index.js +139 -0
- package/src/index.js +2 -0
- package/src/quality/index.js +9 -1
package/package.json
CHANGED
package/src/compile/index.js
CHANGED
|
@@ -172,6 +172,14 @@ function compileDomain(project) {
|
|
|
172
172
|
if (evolution) files['KDNA_Evolution.json'] = JSON.stringify(evolution, null, 2);
|
|
173
173
|
files['kdna.json'] = JSON.stringify(compileManifest(project, files), null, 2);
|
|
174
174
|
|
|
175
|
+
// ── KDNA Card (governance metadata) ─────────────────────────────
|
|
176
|
+
if (project.governance) {
|
|
177
|
+
const { generateKdnaCard } = require('../governance');
|
|
178
|
+
const prov = require('../provenance').buildProvenance(project, files);
|
|
179
|
+
const kdnaCard = generateKdnaCard(project, {}, prov);
|
|
180
|
+
files['KDNA_CARD.json'] = JSON.stringify(kdnaCard, null, 2);
|
|
181
|
+
}
|
|
182
|
+
|
|
175
183
|
const excludedCount = cards.filter(c => !c.locked && !['deprecated'].includes(c.status)).length;
|
|
176
184
|
|
|
177
185
|
return {
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governance risk assessment — classify domain risk level and validate governance metadata.
|
|
3
|
+
*
|
|
4
|
+
* Risk levels:
|
|
5
|
+
* R0 — Low: inconvenience, not harm
|
|
6
|
+
* R1 — Medium: suboptimal outcomes
|
|
7
|
+
* R2 — High: significant harm possible
|
|
8
|
+
* R3 — Restricted: serious harm, not for public registry
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const HIGH_RISK_KEYWORDS = {
|
|
12
|
+
medical: ['diagnosis', 'treatment', 'symptom', 'patient', 'clinical', 'therapy', 'medication', 'disease', 'prescription', 'surgery', 'medical'],
|
|
13
|
+
legal: ['lawsuit', 'liability', 'plaintiff', 'defendant', 'jurisdiction', 'statute', 'legal advice', 'attorney', 'court', 'litigation'],
|
|
14
|
+
financial: ['investment', 'portfolio', 'stock', 'bond', 'retirement', 'insurance', 'mortgage', 'loan', 'tax advice', 'credit score', 'financial advice'],
|
|
15
|
+
safety: ['weapon', 'surveillance', 'monitoring', 'tracking', 'child safety', 'emergency response', 'public safety', 'self-harm', 'suicide'],
|
|
16
|
+
decision: ['hiring', 'firing', 'termination', 'employment decision', 'performance review'],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function computeRiskLevel(project) {
|
|
20
|
+
const cards = project.cards || [];
|
|
21
|
+
const allText = cards.map(c => {
|
|
22
|
+
const fields = c.fields || {};
|
|
23
|
+
return [fields.one_sentence, fields.full_statement, fields.wrong, fields.correct, fields.question,
|
|
24
|
+
fields.essence, fields.scope, fields.out_of_scope,
|
|
25
|
+
...(fields.applies_when || []), ...(fields.does_not_apply_when || [])]
|
|
26
|
+
.filter(Boolean).join(' ').toLowerCase();
|
|
27
|
+
}).join(' ');
|
|
28
|
+
|
|
29
|
+
// Check declared risk level first
|
|
30
|
+
const declared = (project.governance && project.governance.risk_level) || null;
|
|
31
|
+
if (declared === 'R3') return 'R3';
|
|
32
|
+
|
|
33
|
+
// Check for high-risk keywords
|
|
34
|
+
let detectedCategory = null;
|
|
35
|
+
for (const [category, keywords] of Object.entries(HIGH_RISK_KEYWORDS)) {
|
|
36
|
+
for (const kw of keywords) {
|
|
37
|
+
if (allText.includes(kw)) { detectedCategory = category; break; }
|
|
38
|
+
}
|
|
39
|
+
if (detectedCategory) break;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If no high-risk keywords found and R0-R2 declared, trust the declaration
|
|
43
|
+
if (!detectedCategory && declared) return declared;
|
|
44
|
+
|
|
45
|
+
// Default risk levels
|
|
46
|
+
if (['medical', 'safety'].includes(detectedCategory)) return 'R3';
|
|
47
|
+
if (['legal', 'financial'].includes(detectedCategory)) return 'R2';
|
|
48
|
+
if (detectedCategory === 'decision') return 'R1';
|
|
49
|
+
|
|
50
|
+
return declared || 'R1'; // Default: medium risk
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function requiresExpertReview(riskLevel) {
|
|
54
|
+
return riskLevel === 'R2' || riskLevel === 'R3';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateGovernance(project) {
|
|
58
|
+
const issues = [];
|
|
59
|
+
const gov = project.governance || {};
|
|
60
|
+
|
|
61
|
+
// Required fields
|
|
62
|
+
if (!gov.risk_level) {
|
|
63
|
+
issues.push({ type: 'missing_risk_level', severity: 'blocking', message: 'Governance: risk_level must be declared (R0/R1/R2/R3)' });
|
|
64
|
+
}
|
|
65
|
+
if (!gov.intended_use || !gov.intended_use.length) {
|
|
66
|
+
issues.push({ type: 'missing_intended_use', severity: 'blocking', message: 'Governance: intended_use must be declared' });
|
|
67
|
+
}
|
|
68
|
+
if (!gov.out_of_scope || !gov.out_of_scope.length) {
|
|
69
|
+
issues.push({ type: 'missing_out_of_scope', severity: 'blocking', message: 'Governance: out_of_scope must be declared' });
|
|
70
|
+
}
|
|
71
|
+
if (!gov.known_limitations || !gov.known_limitations.length) {
|
|
72
|
+
issues.push({ type: 'missing_limitations', severity: 'blocking', message: 'Governance: known_limitations must be declared' });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Risk level specific checks
|
|
76
|
+
const riskLevel = gov.risk_level || computeRiskLevel(project);
|
|
77
|
+
if (requiresExpertReview(riskLevel)) {
|
|
78
|
+
if (!gov.reviewed_by) {
|
|
79
|
+
issues.push({ type: 'requires_expert_review', severity: 'blocking', message: `Governance: risk_level ${riskLevel} requires expert_review. reviewed_by must be set.` });
|
|
80
|
+
}
|
|
81
|
+
if (!gov.risk_warnings || !gov.risk_warnings.length) {
|
|
82
|
+
issues.push({ type: 'missing_risk_warnings', severity: 'blocking', message: `Governance: risk_level ${riskLevel} requires risk_warnings.` });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check for high-risk keywords in content that might not match declared level
|
|
87
|
+
const detectedLevel = computeRiskLevel(project);
|
|
88
|
+
if (gov.risk_level && ['R0', 'R1'].includes(gov.risk_level) && ['R2', 'R3'].includes(detectedLevel)) {
|
|
89
|
+
issues.push({
|
|
90
|
+
type: 'risk_mismatch',
|
|
91
|
+
severity: 'blocking',
|
|
92
|
+
message: `Governance: declared risk_level ${gov.risk_level} but content analysis suggests ${detectedLevel}. Review required.`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Author responsibility required for R1+
|
|
97
|
+
if (['R1', 'R2', 'R3'].includes(riskLevel) && !gov.author_statement) {
|
|
98
|
+
issues.push({ type: 'missing_author_statement', severity: 'blocking', message: `Governance: risk_level ${riskLevel} requires author_statement.` });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
valid: issues.filter(i => i.severity === 'blocking').length === 0,
|
|
103
|
+
issues,
|
|
104
|
+
risk_level: riskLevel,
|
|
105
|
+
requires_expert_review: requiresExpertReview(riskLevel),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function generateKdnaCard(project, compiledStats, provenance) {
|
|
110
|
+
const gov = project.governance || {};
|
|
111
|
+
const cards = project.cards || [];
|
|
112
|
+
const lockedCards = cards.filter(c => c.locked);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
name: project.name,
|
|
116
|
+
version: (project.release && project.release.version) || '0.1.0',
|
|
117
|
+
risk_level: gov.risk_level || computeRiskLevel(project),
|
|
118
|
+
intended_use: gov.intended_use || [],
|
|
119
|
+
out_of_scope: gov.out_of_scope || [],
|
|
120
|
+
known_limitations: gov.known_limitations || [],
|
|
121
|
+
author_responsibility: gov.author_statement || '',
|
|
122
|
+
risk_warnings: gov.risk_warnings || [],
|
|
123
|
+
human_lock_summary: {
|
|
124
|
+
locked_cards: lockedCards.length,
|
|
125
|
+
locked_axioms: lockedCards.filter(c => c.type === 'axiom').length,
|
|
126
|
+
locked_misunderstandings: lockedCards.filter(c => c.type === 'misunderstanding').length,
|
|
127
|
+
locked_self_checks: lockedCards.filter(c => c.type === 'self_check').length,
|
|
128
|
+
feynman_restatements: lockedCards.filter(c => c.feynman_restatement).length,
|
|
129
|
+
locked_by: (project.author && project.author.id) || 'unknown',
|
|
130
|
+
},
|
|
131
|
+
quality_badge: (compiledStats && compiledStats.locked_cards > 0) ? 'tested' : 'untested',
|
|
132
|
+
review_status: gov.review_status || 'community',
|
|
133
|
+
requires_expert_review: requiresExpertReview(gov.risk_level || 'R1'),
|
|
134
|
+
provenance: provenance || {},
|
|
135
|
+
license: (project.release && project.release.license) || 'CC-BY-4.0',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = { computeRiskLevel, requiresExpertReview, validateGovernance, generateKdnaCard, HIGH_RISK_KEYWORDS };
|
package/src/index.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
const cards = require('./cards');
|
|
21
21
|
const compile = require('./compile');
|
|
22
22
|
const evidence = require('./evidence');
|
|
23
|
+
const governance = require('./governance');
|
|
23
24
|
const packaging = require('./packaging');
|
|
24
25
|
const pipeline = require('./pipeline');
|
|
25
26
|
const project = require('./project');
|
|
@@ -40,6 +41,7 @@ module.exports = {
|
|
|
40
41
|
quality,
|
|
41
42
|
provenance,
|
|
42
43
|
pipeline,
|
|
44
|
+
governance,
|
|
43
45
|
|
|
44
46
|
// Experimental
|
|
45
47
|
evidence,
|
package/src/quality/index.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
const contradiction = require('./contradiction');
|
|
14
14
|
const { validateAllCards } = require('./validate-cards');
|
|
15
|
+
const { validateGovernance } = require('../governance');
|
|
15
16
|
|
|
16
17
|
function computeReadiness(project) {
|
|
17
18
|
const cards = project.cards || [];
|
|
@@ -25,6 +26,12 @@ function computeReadiness(project) {
|
|
|
25
26
|
const blocking = [];
|
|
26
27
|
const warnings = [];
|
|
27
28
|
|
|
29
|
+
// ── Governance check (v0.6.1) ───────────────────────────────────
|
|
30
|
+
const govResult = validateGovernance(project);
|
|
31
|
+
for (const issue of govResult.issues) {
|
|
32
|
+
(issue.severity === 'blocking' ? blocking : warnings).push(`Governance: ${issue.message}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
// ── Card validation integration (v0.3.2) ─────────────────────────
|
|
29
36
|
const cardResults = validateAllCards(project);
|
|
30
37
|
for (const { card_id, issues } of cardResults) {
|
|
@@ -77,7 +84,7 @@ function computeReadiness(project) {
|
|
|
77
84
|
grade = 'publishable_grade';
|
|
78
85
|
}
|
|
79
86
|
|
|
80
|
-
return buildResult(grade, blocking, warnings, project, { feynmanRatio, allFeynman });
|
|
87
|
+
return buildResult(grade, blocking, warnings, project, { feynmanRatio, allFeynman, governance: govResult });
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
function buildResult(grade, blocking, warnings, project, detail = {}) {
|
|
@@ -90,6 +97,7 @@ function buildResult(grade, blocking, warnings, project, detail = {}) {
|
|
|
90
97
|
blocking,
|
|
91
98
|
warnings,
|
|
92
99
|
score: Math.max(0, 100 - blocking.length * 15 - warnings.length * 3),
|
|
100
|
+
governance: detail.governance || null,
|
|
93
101
|
stats: {
|
|
94
102
|
total_cards: (project.cards || []).length,
|
|
95
103
|
locked_cards: lockedCount,
|