@aikdna/studio-core 0.6.0 → 0.7.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/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 +40 -2
- package/src/testlab/index.js +16 -0
- package/src/versioning/index.js +7 -2
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) {
|
|
@@ -70,14 +77,44 @@ function computeReadiness(project) {
|
|
|
70
77
|
const feynmanRatio = lockedAxioms.length > 0 ? lockedAxioms.filter(ax => ax.feynman_restatement).length / lockedAxioms.length : 0;
|
|
71
78
|
const allFeynman = lockedAxioms.every(ax => ax.feynman_restatement) && lockedMisunderstandings.every(ms => !ms.locked || ms.feynman_restatement);
|
|
72
79
|
|
|
80
|
+
// Feynman quality threshold (v0.6.2)
|
|
81
|
+
const feynmanQuality = lockedAxioms.every(ax => {
|
|
82
|
+
if (!ax.feynman_restatement?.score) return false;
|
|
83
|
+
return ax.feynman_restatement.score.total >= 4;
|
|
84
|
+
});
|
|
85
|
+
const misunderstandingFeynmanQuality = lockedMisunderstandings.length === 0 ||
|
|
86
|
+
lockedMisunderstandings.every(ms => {
|
|
87
|
+
if (!ms.feynman_restatement?.score) return false;
|
|
88
|
+
return ms.feynman_restatement.score.total >= 3;
|
|
89
|
+
});
|
|
90
|
+
if (allFeynman && !feynmanQuality) {
|
|
91
|
+
warnings.push('Feynman: axiom restatements should score ≥4/5 for publishable grade');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Compare test results requirements (v0.6.4)
|
|
95
|
+
const withKdnaBetter = ratedTests.filter(t => t.result === 'with_kdna_better').length;
|
|
96
|
+
const withoutKdnaBetter = ratedTests.filter(t => t.result === 'without_kdna_better').length;
|
|
97
|
+
if (ratedTests.length > 0 && withoutKdnaBetter > 0) {
|
|
98
|
+
warnings.push(`${withoutKdnaBetter} test(s) favored response WITHOUT KDNA — domain may not improve judgment`);
|
|
99
|
+
}
|
|
100
|
+
if (ratedTests.length > 0 && withKdnaBetter < 3 && ratedTests.length >= 5) {
|
|
101
|
+
warnings.push(`Only ${withKdnaBetter} tests favor KDNA — recommend ≥3 for confidence`);
|
|
102
|
+
}
|
|
103
|
+
|
|
73
104
|
let grade = 'draft_grade';
|
|
74
105
|
if (locked.length >= 3 && axiomsComplete && feynmanRatio >= 0.5) grade = 'human_controlled';
|
|
75
106
|
if (grade === 'human_controlled' && ratedTests.length >= 5 && lockedSelfChecks.length >= 3) grade = 'tested_grade';
|
|
76
|
-
if (grade === 'tested_grade' && ratedTests.length >= 10 && lockedAxioms.length >= 3 && lockedSelfChecks.length >= 5 && blocking.length === 0 && allFeynman) {
|
|
107
|
+
if (grade === 'tested_grade' && ratedTests.length >= 10 && lockedAxioms.length >= 3 && lockedSelfChecks.length >= 5 && blocking.length === 0 && allFeynman && feynmanQuality && misunderstandingFeynmanQuality) {
|
|
77
108
|
grade = 'publishable_grade';
|
|
78
109
|
}
|
|
79
110
|
|
|
80
|
-
|
|
111
|
+
// Downgrade if governance issues exist
|
|
112
|
+
if (grade === 'publishable_grade' && govResult && !govResult.valid) {
|
|
113
|
+
grade = 'tested_grade';
|
|
114
|
+
warnings.push('Governance checks not passed — publishable downgraded to tested');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return buildResult(grade, blocking, warnings, project, { feynmanRatio, allFeynman, governance: govResult });
|
|
81
118
|
}
|
|
82
119
|
|
|
83
120
|
function buildResult(grade, blocking, warnings, project, detail = {}) {
|
|
@@ -90,6 +127,7 @@ function buildResult(grade, blocking, warnings, project, detail = {}) {
|
|
|
90
127
|
blocking,
|
|
91
128
|
warnings,
|
|
92
129
|
score: Math.max(0, 100 - blocking.length * 15 - warnings.length * 3),
|
|
130
|
+
governance: detail.governance || null,
|
|
93
131
|
stats: {
|
|
94
132
|
total_cards: (project.cards || []).length,
|
|
95
133
|
locked_cards: lockedCount,
|
package/src/testlab/index.js
CHANGED
|
@@ -42,6 +42,22 @@ function linkTestToCards(testCase, cardIds) {
|
|
|
42
42
|
return testCase;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function applyTestResultsToCards(project, testCase) {
|
|
46
|
+
if (!testCase.result) return project;
|
|
47
|
+
const cards = project.cards || [];
|
|
48
|
+
for (const cardId of (testCase.linked_cards || [])) {
|
|
49
|
+
const card = cards.find(c => c.id === cardId);
|
|
50
|
+
if (!card) continue;
|
|
51
|
+
if (card.status === 'locked' && testCase.result === 'with_kdna_better') {
|
|
52
|
+
const { transitionCard } = require('../cards');
|
|
53
|
+
try {
|
|
54
|
+
transitionCard(card, 'tested', { by: testCase.rated_by || 'testlab', reason: `test ${testCase.id}: ${testCase.result}` });
|
|
55
|
+
} catch { /* card may have been already tested */ }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return project;
|
|
59
|
+
}
|
|
60
|
+
|
|
45
61
|
function generateTestSummary(project) {
|
|
46
62
|
const tests = project.tests || [];
|
|
47
63
|
const total = tests.length;
|
package/src/versioning/index.js
CHANGED
|
@@ -129,14 +129,19 @@ function bumpVersion(currentVersion, bumpType) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
function markBreakingChange(diff) {
|
|
132
|
+
const recommended = recommendVersionBump(diff);
|
|
132
133
|
const removedAxioms = diff.removed.filter(c => c.type === 'axiom');
|
|
133
134
|
const scopeWidening = diff.changed.filter(c => c.changes && 'applies_when' in c.changes &&
|
|
134
135
|
(c.changes.applies_when.after || []).length > (c.changes.applies_when.before || []).length);
|
|
136
|
+
const coreMeaningChanges = diff.changed.filter(c => c.changes &&
|
|
137
|
+
('one_sentence' in c.changes || 'full_statement' in c.changes));
|
|
138
|
+
|
|
135
139
|
return {
|
|
136
|
-
breaking:
|
|
140
|
+
breaking: recommended === 'major',
|
|
137
141
|
reason: removedAxioms.length > 0 ? `${removedAxioms.length} axiom(s) removed — breaking change` :
|
|
142
|
+
coreMeaningChanges.length > 0 ? `${coreMeaningChanges.length} core meaning change(s) — breaking change` :
|
|
138
143
|
scopeWidening.length > 0 ? `${scopeWidening.length} scope widening(s) — may affect existing behavior` : null,
|
|
139
|
-
recommended_bump:
|
|
144
|
+
recommended_bump: recommended,
|
|
140
145
|
};
|
|
141
146
|
}
|
|
142
147
|
|