@skillstech/thunderlang 0.4.1 → 0.4.2
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/CHANGELOG.md +15 -0
- package/README.md +25 -25
- package/dist/core.cjs +2 -2
- package/dist/index.cjs +109 -4
- package/package.json +3 -3
- package/src/cli.mjs +48 -1
- package/src/emit.mjs +1 -1
- package/src/evidence.mjs +116 -0
- package/src/index.mjs +1 -0
- package/src/lift.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
All notable changes to `@skillstech/thunderlang`. Pre-1.0: the language and the
|
|
4
4
|
`intent-graph-v1` schema version independently and may still change.
|
|
5
5
|
|
|
6
|
+
## 0.4.2
|
|
7
|
+
|
|
8
|
+
The evidence-graph release. Additive; no breaking changes.
|
|
9
|
+
|
|
10
|
+
- **Evidence projections.** ThunderLang can now project its verification into `evidence-event-v1`
|
|
11
|
+
events (`evidenceType: tool_verified`) for the shared SkillsTech proof spine: `thunder evidence
|
|
12
|
+
<file>` emits `intent.proven`, and `--evidence` on `verify-diff` / `conform` / `drift` emits
|
|
13
|
+
`change.gated` / `conformance.verified` / `intent.drift`. The projections are pure, offline, and
|
|
14
|
+
safe-derived (ids, hashes, verdicts, diagnostic codes, and counts only , never source, secret
|
|
15
|
+
values, finding messages, or expected/actual values). The library exports `toEvidenceEvents`,
|
|
16
|
+
`verifyDiffToEvidence`, `conformToEvidence`, and `driftToEvidence`. The network push to the spine
|
|
17
|
+
is intentionally left to the org-layer/CI, so the compiler stays offline and deterministic.
|
|
18
|
+
- **Fix: `thunder lift` emits canonical `.thunder` drafts** instead of the legacy `.intent`
|
|
19
|
+
extension, on both single-file and repo lifts.
|
|
20
|
+
|
|
6
21
|
## 0.4.1
|
|
7
22
|
|
|
8
23
|
The coherence release. Additive; no breaking changes. Version chosen to align with OpenThunder
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Part of [ThunderLang](https://thunderlang.dev), the intent language for AI-era s
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npm install -g @skillstech/thunderlang # then:
|
|
14
|
+
npm install -g @skillstech/thunderlang # then: thunder check path/to/Mission.intent
|
|
15
15
|
npx @skillstech/thunderlang help # or run without installing
|
|
16
16
|
```
|
|
17
17
|
|
|
@@ -19,36 +19,36 @@ npx @skillstech/thunderlang help # or run without installing
|
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
21
|
# Author & check
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
thunder init [Name] # scaffold a runnable starter mission
|
|
23
|
+
thunder check <file|dir> # semantic diagnostics (exit 1 on error); a dir gates the repo
|
|
24
|
+
thunder fmt <file|dir> -w # canonical formatting (whitespace only; comments preserved)
|
|
25
|
+
thunder build <file> # docs, contract graph, test plan, .intent-proof.json
|
|
26
|
+
thunder graph <file> # the canonical Intent Graph (intent-graph-v1)
|
|
27
|
+
thunder check . --format sarif # SARIF 2.1.0 for GitHub/GitLab code scanning
|
|
28
|
+
thunder schema # emit the graph schema + diagnostic catalog
|
|
29
|
+
thunder rules · thunder explain <CODE> # the full diagnostic catalog / one code
|
|
30
|
+
thunder proof --schema # the canonical proof envelope schema (intent-proof-v1)
|
|
31
31
|
|
|
32
32
|
# Execute (no AI, no generated code)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
thunder run <file> --inputs '{"age":20}' # evaluate the decision(s) against inputs
|
|
34
|
+
thunder simulate <file> --events submit,approve # walk the lifecycle(s)
|
|
35
|
+
thunder test <file> # run in-file test blocks (case/scenario)
|
|
36
|
+
thunder outcomes <file> # evaluate outcome contracts vs results
|
|
37
|
+
thunder style <file> # resolve style intents vs the canonical token space
|
|
38
38
|
|
|
39
39
|
# Interop (9 export formats)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
thunder export <file> --format dmn|bpmn|smv|jsonschema|openapi|tokens|css|mermaid|playwright
|
|
41
|
+
thunder import <file> [--format dmn|bpmn] [--json] # lift DMN/BPMN into intent
|
|
42
|
+
thunder source <file|graph.json> # regenerate .intent from a graph
|
|
43
|
+
thunder migrate <graph.json> [--to <ver>] # upgrade a persisted graph
|
|
44
|
+
thunder verify <proof.json> [src] # confirm a proof matches its source
|
|
45
45
|
|
|
46
46
|
# Navigate & compare
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
thunder atlas <dir> [--search q] thunder index <dir>
|
|
48
|
+
thunder diff <before> <after> thunder merge <base> <ours> <theirs>
|
|
49
49
|
|
|
50
50
|
# Code <-> intent
|
|
51
|
-
|
|
51
|
+
thunder lift <file> thunder approve <file> thunder drift <file>
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
Run `thunder help` for the full reference.
|
|
@@ -75,8 +75,8 @@ test Eligibility
|
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
thunder run mission.intent --inputs '{"age":20,"score":90}' # -> Eligible, with a trace
|
|
79
|
+
thunder test mission.intent # -> 1/1 passed
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
No AI, no generated code. The intent itself decides.
|
package/dist/core.cjs
CHANGED
|
@@ -4290,7 +4290,7 @@ function notesSummary(ast) {
|
|
|
4290
4290
|
byLens
|
|
4291
4291
|
};
|
|
4292
4292
|
}
|
|
4293
|
-
var COMPILER_VERSION = "0.4.
|
|
4293
|
+
var COMPILER_VERSION = "0.4.2";
|
|
4294
4294
|
var PROOF_SCHEMA_VERSION = "0.1.0";
|
|
4295
4295
|
var SOURCE_PRODUCT = "skillstech-compiler";
|
|
4296
4296
|
function buildContractGraph(ast, generatedAt) {
|
|
@@ -5713,7 +5713,7 @@ function liftRepo(files, { language } = {}) {
|
|
|
5713
5713
|
const base = slug(r.lifted.mission);
|
|
5714
5714
|
const n = (usedNames.get(base) || 0) + 1;
|
|
5715
5715
|
usedNames.set(base, n);
|
|
5716
|
-
const outName = n === 1 ? `${base}.
|
|
5716
|
+
const outName = n === 1 ? `${base}.thunder` : `${base}-${n}.thunder`;
|
|
5717
5717
|
missions.push({
|
|
5718
5718
|
mission: r.lifted.mission,
|
|
5719
5719
|
sourceFile: file,
|
package/dist/index.cjs
CHANGED
|
@@ -160,6 +160,7 @@ __export(index_exports, {
|
|
|
160
160
|
comprehensionLevel: () => comprehensionLevel,
|
|
161
161
|
comprehensionReport: () => comprehensionReport,
|
|
162
162
|
confidenceFromClassification: () => confidenceFromClassification,
|
|
163
|
+
conformToEvidence: () => conformToEvidence,
|
|
163
164
|
contractHash: () => contractHash,
|
|
164
165
|
contradictionsView: () => contradictionsView,
|
|
165
166
|
correctionsFor: () => correctionsFor,
|
|
@@ -168,6 +169,7 @@ __export(index_exports, {
|
|
|
168
169
|
detectFormat: () => detectFormat,
|
|
169
170
|
diffGraphs: () => diffGraphs,
|
|
170
171
|
draftIntent: () => draftIntent,
|
|
172
|
+
driftToEvidence: () => driftToEvidence,
|
|
171
173
|
dropNodeField: () => dropNodeField,
|
|
172
174
|
emptyApprovals: () => emptyApprovals,
|
|
173
175
|
emptyEventLog: () => emptyEventLog,
|
|
@@ -288,6 +290,7 @@ __export(index_exports, {
|
|
|
288
290
|
toCss: () => toCss,
|
|
289
291
|
toDMN: () => toDMN,
|
|
290
292
|
toDesignTokens: () => toDesignTokens,
|
|
293
|
+
toEvidenceEvents: () => toEvidenceEvents,
|
|
291
294
|
toFinding: () => toFinding,
|
|
292
295
|
toJSONSchema: () => toJSONSchema,
|
|
293
296
|
toJava: () => toJava,
|
|
@@ -309,6 +312,7 @@ __export(index_exports, {
|
|
|
309
312
|
validateIR: () => validateIR,
|
|
310
313
|
validateProof: () => validateProof,
|
|
311
314
|
verifyDiff: () => verifyDiff,
|
|
315
|
+
verifyDiffToEvidence: () => verifyDiffToEvidence,
|
|
312
316
|
verifyLedger: () => verifyLedger,
|
|
313
317
|
violatesArchitecture: () => violatesArchitecture,
|
|
314
318
|
whyBuilt: () => whyBuilt
|
|
@@ -328,8 +332,8 @@ function intentRefId(astOrName, { sourceHash } = {}) {
|
|
|
328
332
|
const name = typeof astOrName === "string" ? astOrName : subjectName(astOrName);
|
|
329
333
|
const base = `intent:${slug(name || "mission")}`;
|
|
330
334
|
if (!sourceHash) return base;
|
|
331
|
-
const
|
|
332
|
-
return
|
|
335
|
+
const short2 = String(sourceHash).replace(/^sha256:/, "").slice(0, 8);
|
|
336
|
+
return short2 ? `${base}@${short2}` : base;
|
|
333
337
|
}
|
|
334
338
|
function skillRefId(name) {
|
|
335
339
|
return `skill:${slug(name || "unknown")}`;
|
|
@@ -1225,6 +1229,103 @@ function parseIntent(source) {
|
|
|
1225
1229
|
return ast;
|
|
1226
1230
|
}
|
|
1227
1231
|
|
|
1232
|
+
// src/evidence.mjs
|
|
1233
|
+
var COUNTED_STATUSES = ["verified", "failed", "planned", "needs_verification"];
|
|
1234
|
+
var stripHash = (h) => String(h || "").replace(/^sha256:/, "");
|
|
1235
|
+
var short = (h, n = 16) => stripHash(h).slice(0, n);
|
|
1236
|
+
function envelope(eventType, evidenceId, ctx = {}, payload = {}) {
|
|
1237
|
+
return {
|
|
1238
|
+
schema: "evidence-event-v1",
|
|
1239
|
+
sourceProduct: "thunderlang",
|
|
1240
|
+
producer: { name: "thunderlang", version: ctx.compilerVersion || null },
|
|
1241
|
+
eventType,
|
|
1242
|
+
evidenceType: "tool_verified",
|
|
1243
|
+
evidenceId,
|
|
1244
|
+
occurredAt: ctx.occurredAt || null,
|
|
1245
|
+
visibility: ctx.visibility || "private",
|
|
1246
|
+
subject: { missionName: ctx.missionName || null, intentHash: ctx.intentHash || null },
|
|
1247
|
+
payload
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
var safeClaim = (kind, c) => ({ id: c.id, kind, status: c.status, provenBy: c.provenBy || null });
|
|
1251
|
+
function countByStatus(claims) {
|
|
1252
|
+
const counts = Object.fromEntries(COUNTED_STATUSES.map((s) => [s, 0]));
|
|
1253
|
+
for (const c of claims) if (c && Object.prototype.hasOwnProperty.call(counts, c.status)) counts[c.status]++;
|
|
1254
|
+
return counts;
|
|
1255
|
+
}
|
|
1256
|
+
var safeFinding = (f) => ({ code: f.code, level: f.level, ...f.regression !== void 0 ? { regression: !!f.regression } : {}, ...f.line !== void 0 ? { line: f.line } : {} });
|
|
1257
|
+
function toEvidenceEvents(proof) {
|
|
1258
|
+
if (!proof || typeof proof !== "object") return [];
|
|
1259
|
+
const guarantees = Array.isArray(proof.guarantees) ? proof.guarantees : [];
|
|
1260
|
+
const neverRules = Array.isArray(proof.neverRules) ? proof.neverRules : [];
|
|
1261
|
+
const claims = [
|
|
1262
|
+
...guarantees.map((c) => safeClaim("guarantee", c)),
|
|
1263
|
+
...neverRules.map((c) => safeClaim("prohibition", c))
|
|
1264
|
+
];
|
|
1265
|
+
const fr = proof.freshness || {};
|
|
1266
|
+
const intentHash2 = proof.freshness?.intentHash || proof.sourceHash || null;
|
|
1267
|
+
return [envelope("intent.proven", `tl-proof-${short(intentHash2)}`, {
|
|
1268
|
+
compilerVersion: proof.compilerVersion,
|
|
1269
|
+
occurredAt: proof.generatedAt,
|
|
1270
|
+
missionName: proof.missionName,
|
|
1271
|
+
intentHash: intentHash2
|
|
1272
|
+
}, {
|
|
1273
|
+
proofStatus: proof.proofStatus || null,
|
|
1274
|
+
total: claims.length,
|
|
1275
|
+
counts: countByStatus(claims),
|
|
1276
|
+
freshness: { implementation: fr.implementation || null, dependencies: fr.dependencies ? fr.dependencies.hash || null : null, environment: fr.environment || null },
|
|
1277
|
+
claims
|
|
1278
|
+
})];
|
|
1279
|
+
}
|
|
1280
|
+
function verifyDiffToEvidence(verdict2, ctx = {}) {
|
|
1281
|
+
if (!verdict2 || typeof verdict2 !== "object") return [];
|
|
1282
|
+
const findings2 = Array.isArray(verdict2.findings) ? verdict2.findings : [];
|
|
1283
|
+
const id = `tl-verifydiff-${short(ctx.intentHash)}${ctx.changeHash ? `-${short(ctx.changeHash, 8)}` : ""}`;
|
|
1284
|
+
return [envelope("change.gated", id, ctx, {
|
|
1285
|
+
verdict: verdict2.verdict || null,
|
|
1286
|
+
ok: !!verdict2.ok,
|
|
1287
|
+
blocking: verdict2.blocking || 0,
|
|
1288
|
+
regressions: verdict2.summary?.regressions ?? findings2.filter((f) => f.regression).length,
|
|
1289
|
+
findings: findings2.map(safeFinding)
|
|
1290
|
+
})];
|
|
1291
|
+
}
|
|
1292
|
+
function conformToEvidence(report, ctx = {}) {
|
|
1293
|
+
if (!report || typeof report !== "object") return [];
|
|
1294
|
+
const cases = Array.isArray(report.cases) ? report.cases : [];
|
|
1295
|
+
const columns = Array.isArray(report.columns) ? report.columns : [];
|
|
1296
|
+
const targets = {};
|
|
1297
|
+
for (const col of columns) {
|
|
1298
|
+
const t = { pass: 0, fail: 0, declared: 0 };
|
|
1299
|
+
for (const c of cases) {
|
|
1300
|
+
const s = c.targets?.[col]?.status;
|
|
1301
|
+
if (s === "pass") t.pass++;
|
|
1302
|
+
else if (s === "fail") t.fail++;
|
|
1303
|
+
else t.declared++;
|
|
1304
|
+
}
|
|
1305
|
+
targets[col] = t;
|
|
1306
|
+
}
|
|
1307
|
+
return [envelope("conformance.verified", `tl-conform-${short(ctx.intentHash)}`, ctx, {
|
|
1308
|
+
total: report.total || cases.length,
|
|
1309
|
+
columns,
|
|
1310
|
+
semanticFailures: report.semanticFailures || 0,
|
|
1311
|
+
graded: !!report.graded,
|
|
1312
|
+
targets,
|
|
1313
|
+
// failure locations by name only (case + target), never expected/actual values.
|
|
1314
|
+
failures: (Array.isArray(report.failures) ? report.failures : []).map((f) => ({ target: f.target, case: f.case }))
|
|
1315
|
+
})];
|
|
1316
|
+
}
|
|
1317
|
+
function driftToEvidence(report, ctx = {}) {
|
|
1318
|
+
if (!report || typeof report !== "object") return [];
|
|
1319
|
+
const findings2 = Array.isArray(report.findings) ? report.findings : [];
|
|
1320
|
+
const id = `tl-drift-${short(ctx.intentHash)}${ctx.codeHash ? `-${short(ctx.codeHash, 8)}` : ""}`;
|
|
1321
|
+
return [envelope("intent.drift", id, ctx, {
|
|
1322
|
+
status: report.status || null,
|
|
1323
|
+
total: findings2.length,
|
|
1324
|
+
blocking: report.summary?.blocking ?? 0,
|
|
1325
|
+
findings: findings2.map(safeFinding)
|
|
1326
|
+
})];
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1228
1329
|
// src/hash.mjs
|
|
1229
1330
|
var K = new Uint32Array([
|
|
1230
1331
|
1116352408,
|
|
@@ -2288,7 +2389,7 @@ function notesSummary(ast) {
|
|
|
2288
2389
|
byLens
|
|
2289
2390
|
};
|
|
2290
2391
|
}
|
|
2291
|
-
var COMPILER_VERSION = "0.4.
|
|
2392
|
+
var COMPILER_VERSION = "0.4.2";
|
|
2292
2393
|
var PROOF_SCHEMA_VERSION = "0.1.0";
|
|
2293
2394
|
var SOURCE_PRODUCT = "skillstech-compiler";
|
|
2294
2395
|
function buildContractGraph(ast, generatedAt) {
|
|
@@ -4087,7 +4188,7 @@ function liftRepo(files, { language } = {}) {
|
|
|
4087
4188
|
const base = slug(r.lifted.mission);
|
|
4088
4189
|
const n = (usedNames.get(base) || 0) + 1;
|
|
4089
4190
|
usedNames.set(base, n);
|
|
4090
|
-
const outName = n === 1 ? `${base}.
|
|
4191
|
+
const outName = n === 1 ? `${base}.thunder` : `${base}-${n}.thunder`;
|
|
4091
4192
|
missions.push({
|
|
4092
4193
|
mission: r.lifted.mission,
|
|
4093
4194
|
sourceFile: file,
|
|
@@ -9648,6 +9749,7 @@ function startLspServer({ readable = process.stdin, writable = process.stdout }
|
|
|
9648
9749
|
comprehensionLevel,
|
|
9649
9750
|
comprehensionReport,
|
|
9650
9751
|
confidenceFromClassification,
|
|
9752
|
+
conformToEvidence,
|
|
9651
9753
|
contractHash,
|
|
9652
9754
|
contradictionsView,
|
|
9653
9755
|
correctionsFor,
|
|
@@ -9656,6 +9758,7 @@ function startLspServer({ readable = process.stdin, writable = process.stdout }
|
|
|
9656
9758
|
detectFormat,
|
|
9657
9759
|
diffGraphs,
|
|
9658
9760
|
draftIntent,
|
|
9761
|
+
driftToEvidence,
|
|
9659
9762
|
dropNodeField,
|
|
9660
9763
|
emptyApprovals,
|
|
9661
9764
|
emptyEventLog,
|
|
@@ -9776,6 +9879,7 @@ function startLspServer({ readable = process.stdin, writable = process.stdout }
|
|
|
9776
9879
|
toCss,
|
|
9777
9880
|
toDMN,
|
|
9778
9881
|
toDesignTokens,
|
|
9882
|
+
toEvidenceEvents,
|
|
9779
9883
|
toFinding,
|
|
9780
9884
|
toJSONSchema,
|
|
9781
9885
|
toJava,
|
|
@@ -9797,6 +9901,7 @@ function startLspServer({ readable = process.stdin, writable = process.stdout }
|
|
|
9797
9901
|
validateIR,
|
|
9798
9902
|
validateProof,
|
|
9799
9903
|
verifyDiff,
|
|
9904
|
+
verifyDiffToEvidence,
|
|
9800
9905
|
verifyLedger,
|
|
9801
9906
|
violatesArchitecture,
|
|
9802
9907
|
whyBuilt
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillstech/thunderlang",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Deterministic ThunderLang compiler, CLI, and Language Server. No AI required. Turns .thunder files into the canonical Intent Graph, docs, and a proof, and EXECUTES the intent: run decisions, simulate lifecycles, and check outcome contracts. Interops with DMN, BPMN, JSON Schema, and OpenAPI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -70,11 +70,11 @@
|
|
|
70
70
|
"homepage": "https://thunderlang.dev",
|
|
71
71
|
"repository": {
|
|
72
72
|
"type": "git",
|
|
73
|
-
"url": "git+https://github.com/SkillsTechTalk/
|
|
73
|
+
"url": "git+https://github.com/SkillsTechTalk/thunderlang.git",
|
|
74
74
|
"directory": "compiler"
|
|
75
75
|
},
|
|
76
76
|
"bugs": {
|
|
77
|
-
"url": "https://github.com/SkillsTechTalk/
|
|
77
|
+
"url": "https://github.com/SkillsTechTalk/thunderlang/issues"
|
|
78
78
|
},
|
|
79
79
|
"author": "Skills Tech Talk, LLC",
|
|
80
80
|
"license": "Apache-2.0",
|
package/src/cli.mjs
CHANGED
|
@@ -84,6 +84,7 @@ function runLiveTarget(ast, target) {
|
|
|
84
84
|
}
|
|
85
85
|
import { importIntent, importReport, detectFormat, IMPORT_FORMATS } from './importers.mjs';
|
|
86
86
|
import { runTests } from './testing.mjs';
|
|
87
|
+
import { toEvidenceEvents, verifyDiffToEvidence, conformToEvidence, driftToEvidence } from './evidence.mjs';
|
|
87
88
|
import { evaluateOutcomes } from './outcome.mjs';
|
|
88
89
|
import { analyzeStyle } from './style.mjs';
|
|
89
90
|
import { intentProofJsonSchema, validateProof } from './proof-schema.mjs';
|
|
@@ -261,6 +262,7 @@ function parseArgs(argv) {
|
|
|
261
262
|
else if (a === '--governed') args.governed = true;
|
|
262
263
|
else if (a === '--target') args.target = argv[++i];
|
|
263
264
|
else if (a === '--all-targets') args.allTargets = true;
|
|
265
|
+
else if (a === '--evidence') args.evidence = true;
|
|
264
266
|
else if (a === '--env') args.env = argv[++i];
|
|
265
267
|
else if (a === '--brief') args.brief = argv[++i];
|
|
266
268
|
else if (a === '--after') args.after = argv[++i];
|
|
@@ -370,6 +372,8 @@ Prove & verify intent
|
|
|
370
372
|
proof <file> the .thunder-proof.json artifact (see also: prove, which emits verdicts)
|
|
371
373
|
proof --schema emit the canonical proof envelope JSON Schema (intent-proof-v1)
|
|
372
374
|
verify <proof.json> [src] re-check a proof; reports STALE when impl/deps/compiler moved
|
|
375
|
+
evidence <file> emit evidence-event-v1 JSON (tool_verified) for the shared proof spine
|
|
376
|
+
(also: prove/verify-diff/conform/drift accept --evidence to emit their own event)
|
|
373
377
|
conform <file> [--targets a,b] [--run typescript,python,csharp,java | --all-targets] [--results <json>] conformance matrix across targets
|
|
374
378
|
|
|
375
379
|
Real code vs intent (drift)
|
|
@@ -860,7 +864,7 @@ test Example
|
|
|
860
864
|
if (!res.ok) { console.error(res.error); process.exit(1); }
|
|
861
865
|
if (args.json) { console.log(JSON.stringify(res.summary, null, 2)); return; }
|
|
862
866
|
if (args.out) {
|
|
863
|
-
const p = writeText(args.out, `${slug(res.lifted.mission)}.
|
|
867
|
+
const p = writeText(args.out, `${slug(res.lifted.mission)}.thunder`, res.intentText);
|
|
864
868
|
console.log(`thunder lift ${basename(file)} -> ${p.replace(process.cwd() + '/', '')}`);
|
|
865
869
|
} else {
|
|
866
870
|
console.log(res.intentText);
|
|
@@ -901,6 +905,11 @@ test Example
|
|
|
901
905
|
const codeText = readFileSync(file, 'utf8');
|
|
902
906
|
const language = args.from && args.from !== 'repo' ? args.from : languageForFile(file);
|
|
903
907
|
const res = checkDrift(intentText, codeText, { language });
|
|
908
|
+
if (args.evidence) {
|
|
909
|
+
const ctx = { compilerVersion: COMPILER_VERSION, occurredAt: new Date().toISOString(), missionName: parseIntent(intentText).mission || null, intentHash: sha256(intentText), codeHash: sha256(codeText) };
|
|
910
|
+
console.log(JSON.stringify(driftToEvidence(res, ctx), null, 2));
|
|
911
|
+
process.exit(res.status === 'drift' ? 1 : 0); return;
|
|
912
|
+
}
|
|
904
913
|
if (args.json) { console.log(JSON.stringify(res, null, 2)); }
|
|
905
914
|
else {
|
|
906
915
|
console.log(`thunder drift: ${res.status.toUpperCase()} (${res.summary.blocking} blocking)`);
|
|
@@ -1505,6 +1514,34 @@ test Example
|
|
|
1505
1514
|
return;
|
|
1506
1515
|
}
|
|
1507
1516
|
|
|
1517
|
+
// `thunder evidence <file>` , project the intent's proof into evidence-event-v1 events for the
|
|
1518
|
+
// shared SkillsTech evidence graph (STW spine). Deterministic, offline, safe-derived; prints JSON.
|
|
1519
|
+
// The network push to the spine belongs to the org-layer/CI, not this CLI.
|
|
1520
|
+
if (cmd === 'evidence') {
|
|
1521
|
+
if (!file || !existsSync(file)) { console.error('usage: thunder evidence <file> # emit evidence-event-v1 JSON for the shared proof spine'); process.exit(2); return; }
|
|
1522
|
+
const source = readFileSync(file, 'utf8');
|
|
1523
|
+
const ast = parseIntent(source);
|
|
1524
|
+
const diagnostics = semanticDiagnostics(ast);
|
|
1525
|
+
const resolved = resolveObligations(ast);
|
|
1526
|
+
const proof = buildProof(ast, {
|
|
1527
|
+
sourceFile: basename(file),
|
|
1528
|
+
sourceHash: sha256(source),
|
|
1529
|
+
targetsRequested: ast.targets || [],
|
|
1530
|
+
targetsGenerated: [],
|
|
1531
|
+
diagnostics,
|
|
1532
|
+
generatedAt: new Date().toISOString(),
|
|
1533
|
+
});
|
|
1534
|
+
const CLAIM_MAP = { verified: 'verified', failed: 'failed', declared: 'planned', unverified: 'needs_verification' };
|
|
1535
|
+
const byId = new Map(resolved.obligations.map((o) => [o.id, o]));
|
|
1536
|
+
const applyStatus = (claim) => { const o = byId.get(claim.id); return o ? { ...claim, status: CLAIM_MAP[o.status], provenBy: o.provenBy || null } : claim; };
|
|
1537
|
+
proof.guarantees = proof.guarantees.map(applyStatus);
|
|
1538
|
+
proof.neverRules = proof.neverRules.map(applyStatus);
|
|
1539
|
+
proof.freshness = freshnessFor(file, proof, args.env);
|
|
1540
|
+
const events = toEvidenceEvents(proof);
|
|
1541
|
+
console.log(JSON.stringify(events, null, 2));
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1508
1545
|
// CONFORMANCE: the same test cases run against every target. The engine defines the canonical
|
|
1509
1546
|
// result each case must produce; target outputs fed via --results are graded against it.
|
|
1510
1547
|
// `thunder conform <file> [--targets ts,py] [--results <json|path>] [--json]`.
|
|
@@ -1542,6 +1579,11 @@ test Example
|
|
|
1542
1579
|
const rep = buildConformance(ast, { targets, results });
|
|
1543
1580
|
if (skippedTargets.length) rep.skipped = Array.from(new Set(skippedTargets));
|
|
1544
1581
|
const bad = rep.semanticFailures > 0 || rep.failures.length > 0;
|
|
1582
|
+
if (args.evidence) {
|
|
1583
|
+
const ctx = { compilerVersion: COMPILER_VERSION, occurredAt: new Date().toISOString(), missionName: ast.mission || null, intentHash: sha256(readFileSync(file, 'utf8')) };
|
|
1584
|
+
console.log(JSON.stringify(conformToEvidence(rep, ctx), null, 2));
|
|
1585
|
+
process.exit(bad ? 1 : 0); return;
|
|
1586
|
+
}
|
|
1545
1587
|
if (args.json) { console.log(JSON.stringify(rep, null, 2)); process.exit(bad ? 1 : 0); return; }
|
|
1546
1588
|
if (!rep.total) { console.log(`thunder conform ${basename(file)}: no test cases to conform.`); return; }
|
|
1547
1589
|
|
|
@@ -1752,6 +1794,11 @@ test Example
|
|
|
1752
1794
|
const before = args.before ? readFileSync(args.before, 'utf8') : null;
|
|
1753
1795
|
const language = args.from || languageForFile(args.after);
|
|
1754
1796
|
const r = verifyDiff(intentText, { before, after, language });
|
|
1797
|
+
if (args.evidence) {
|
|
1798
|
+
const ctx = { compilerVersion: COMPILER_VERSION, occurredAt: new Date().toISOString(), missionName: parseIntent(intentText).mission || null, intentHash: sha256(intentText), changeHash: sha256(after) };
|
|
1799
|
+
console.log(JSON.stringify(verifyDiffToEvidence(r, ctx), null, 2));
|
|
1800
|
+
process.exit(r.ok ? 0 : 1); return;
|
|
1801
|
+
}
|
|
1755
1802
|
if (args.json) { console.log(JSON.stringify(r, null, 2)); process.exit(r.ok ? 0 : 1); return; }
|
|
1756
1803
|
console.log(`thunder verify-diff ${basename(file)} vs ${basename(args.after)}: ${r.verdict} (${r.blocking} blocking, ${r.summary.regressions} regression(s))`);
|
|
1757
1804
|
for (const f of r.findings) {
|
package/src/emit.mjs
CHANGED
|
@@ -30,7 +30,7 @@ export function notesSummary(ast) {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export const COMPILER_VERSION = '0.4.
|
|
33
|
+
export const COMPILER_VERSION = '0.4.2';
|
|
34
34
|
export const PROOF_SCHEMA_VERSION = '0.1.0';
|
|
35
35
|
// Identifies which ecosystem product emitted this proof. Consumed by SkillsTech
|
|
36
36
|
// Certified to key cert proofs to the compiler. Stable slug per the coordination bus.
|
package/src/evidence.mjs
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// ThunderLang -> shared evidence graph projection.
|
|
2
|
+
//
|
|
3
|
+
// Maps ThunderLang's verification artifacts into evidence-event-v1 events for the SkillsTech proof
|
|
4
|
+
// spine (owned by STW: siblings emit evidence-event-v1, STW composes proof-bundle-v1). ThunderLang is
|
|
5
|
+
// the deterministic, machine-checked layer, so its evidence is evidenceType `tool_verified`.
|
|
6
|
+
//
|
|
7
|
+
// These projections are PURE and OFFLINE. They perform no network I/O: the transport (POST to the
|
|
8
|
+
// spine) belongs to the hosted org-layer / CI, never the end-user CLI, so the compiler stays
|
|
9
|
+
// deterministic and phone-home-free. They are also SAFE-DERIVED: events carry only ids, hashes,
|
|
10
|
+
// verdicts, diagnostic codes, and counts , never source code, never secret values, never claim
|
|
11
|
+
// statement text, never data-element contents (no finding `message`, no expected/actual values).
|
|
12
|
+
//
|
|
13
|
+
// NOTE: the evidence-event-v1 envelope below is ThunderLang's PROPOSED shape, pending STW's canonical
|
|
14
|
+
// schema (see the TL->STW proposal on the comms bus). Field names may adjust once STW confirms.
|
|
15
|
+
|
|
16
|
+
const COUNTED_STATUSES = ['verified', 'failed', 'planned', 'needs_verification'];
|
|
17
|
+
const stripHash = (h) => String(h || '').replace(/^sha256:/, '');
|
|
18
|
+
const short = (h, n = 16) => stripHash(h).slice(0, n);
|
|
19
|
+
|
|
20
|
+
// The shared evidence-event-v1 envelope. `ctx` carries the cross-artifact context the individual
|
|
21
|
+
// artifacts do not all include (missionName, intentHash, compilerVersion, occurredAt).
|
|
22
|
+
function envelope(eventType, evidenceId, ctx = {}, payload = {}) {
|
|
23
|
+
return {
|
|
24
|
+
schema: 'evidence-event-v1',
|
|
25
|
+
sourceProduct: 'thunderlang',
|
|
26
|
+
producer: { name: 'thunderlang', version: ctx.compilerVersion || null },
|
|
27
|
+
eventType,
|
|
28
|
+
evidenceType: 'tool_verified',
|
|
29
|
+
evidenceId,
|
|
30
|
+
occurredAt: ctx.occurredAt || null,
|
|
31
|
+
visibility: ctx.visibility || 'private',
|
|
32
|
+
subject: { missionName: ctx.missionName || null, intentHash: ctx.intentHash || null },
|
|
33
|
+
payload,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const safeClaim = (kind, c) => ({ id: c.id, kind, status: c.status, provenBy: c.provenBy || null });
|
|
38
|
+
function countByStatus(claims) {
|
|
39
|
+
const counts = Object.fromEntries(COUNTED_STATUSES.map((s) => [s, 0]));
|
|
40
|
+
for (const c of claims) if (c && Object.prototype.hasOwnProperty.call(counts, c.status)) counts[c.status]++;
|
|
41
|
+
return counts;
|
|
42
|
+
}
|
|
43
|
+
// Safe-derived finding: diagnostic code + level (+ flags), never the human message (which can quote code).
|
|
44
|
+
const safeFinding = (f) => ({ code: f.code, level: f.level, ...(f.regression !== undefined ? { regression: !!f.regression } : {}), ...(f.line !== undefined ? { line: f.line } : {}) });
|
|
45
|
+
|
|
46
|
+
// intent.proven , from an intent-proof-v1 artifact.
|
|
47
|
+
export function toEvidenceEvents(proof) {
|
|
48
|
+
if (!proof || typeof proof !== 'object') return [];
|
|
49
|
+
const guarantees = Array.isArray(proof.guarantees) ? proof.guarantees : [];
|
|
50
|
+
const neverRules = Array.isArray(proof.neverRules) ? proof.neverRules : [];
|
|
51
|
+
const claims = [
|
|
52
|
+
...guarantees.map((c) => safeClaim('guarantee', c)),
|
|
53
|
+
...neverRules.map((c) => safeClaim('prohibition', c)),
|
|
54
|
+
];
|
|
55
|
+
const fr = proof.freshness || {};
|
|
56
|
+
const intentHash = proof.freshness?.intentHash || proof.sourceHash || null;
|
|
57
|
+
return [envelope('intent.proven', `tl-proof-${short(intentHash)}`, {
|
|
58
|
+
compilerVersion: proof.compilerVersion, occurredAt: proof.generatedAt, missionName: proof.missionName, intentHash,
|
|
59
|
+
}, {
|
|
60
|
+
proofStatus: proof.proofStatus || null,
|
|
61
|
+
total: claims.length,
|
|
62
|
+
counts: countByStatus(claims),
|
|
63
|
+
freshness: { implementation: fr.implementation || null, dependencies: fr.dependencies ? fr.dependencies.hash || null : null, environment: fr.environment || null },
|
|
64
|
+
claims,
|
|
65
|
+
})];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// change.gated , from a verify-diff verdict ({ verdict, ok, findings, blocking, summary }).
|
|
69
|
+
export function verifyDiffToEvidence(verdict, ctx = {}) {
|
|
70
|
+
if (!verdict || typeof verdict !== 'object') return [];
|
|
71
|
+
const findings = Array.isArray(verdict.findings) ? verdict.findings : [];
|
|
72
|
+
const id = `tl-verifydiff-${short(ctx.intentHash)}${ctx.changeHash ? `-${short(ctx.changeHash, 8)}` : ''}`;
|
|
73
|
+
return [envelope('change.gated', id, ctx, {
|
|
74
|
+
verdict: verdict.verdict || null,
|
|
75
|
+
ok: !!verdict.ok,
|
|
76
|
+
blocking: verdict.blocking || 0,
|
|
77
|
+
regressions: verdict.summary?.regressions ?? findings.filter((f) => f.regression).length,
|
|
78
|
+
findings: findings.map(safeFinding),
|
|
79
|
+
})];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// conformance.verified , from a thunder-conformance-v1 report.
|
|
83
|
+
export function conformToEvidence(report, ctx = {}) {
|
|
84
|
+
if (!report || typeof report !== 'object') return [];
|
|
85
|
+
const cases = Array.isArray(report.cases) ? report.cases : [];
|
|
86
|
+
const columns = Array.isArray(report.columns) ? report.columns : [];
|
|
87
|
+
// Per-target pass/fail/declared counts, derived without leaking any expected/actual values.
|
|
88
|
+
const targets = {};
|
|
89
|
+
for (const col of columns) {
|
|
90
|
+
const t = { pass: 0, fail: 0, declared: 0 };
|
|
91
|
+
for (const c of cases) { const s = c.targets?.[col]?.status; if (s === 'pass') t.pass++; else if (s === 'fail') t.fail++; else t.declared++; }
|
|
92
|
+
targets[col] = t;
|
|
93
|
+
}
|
|
94
|
+
return [envelope('conformance.verified', `tl-conform-${short(ctx.intentHash)}`, ctx, {
|
|
95
|
+
total: report.total || cases.length,
|
|
96
|
+
columns,
|
|
97
|
+
semanticFailures: report.semanticFailures || 0,
|
|
98
|
+
graded: !!report.graded,
|
|
99
|
+
targets,
|
|
100
|
+
// failure locations by name only (case + target), never expected/actual values.
|
|
101
|
+
failures: (Array.isArray(report.failures) ? report.failures : []).map((f) => ({ target: f.target, case: f.case })),
|
|
102
|
+
})];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// intent.drift , from a checkDrift report ({ status, findings, summary }).
|
|
106
|
+
export function driftToEvidence(report, ctx = {}) {
|
|
107
|
+
if (!report || typeof report !== 'object') return [];
|
|
108
|
+
const findings = Array.isArray(report.findings) ? report.findings : [];
|
|
109
|
+
const id = `tl-drift-${short(ctx.intentHash)}${ctx.codeHash ? `-${short(ctx.codeHash, 8)}` : ''}`;
|
|
110
|
+
return [envelope('intent.drift', id, ctx, {
|
|
111
|
+
status: report.status || null,
|
|
112
|
+
total: findings.length,
|
|
113
|
+
blocking: report.summary?.blocking ?? 0,
|
|
114
|
+
findings: findings.map(safeFinding),
|
|
115
|
+
})];
|
|
116
|
+
}
|
package/src/index.mjs
CHANGED
package/src/lift.mjs
CHANGED
|
@@ -696,7 +696,7 @@ export function liftRepo(files, { language } = {}) {
|
|
|
696
696
|
const base = slug(r.lifted.mission);
|
|
697
697
|
const n = (usedNames.get(base) || 0) + 1;
|
|
698
698
|
usedNames.set(base, n);
|
|
699
|
-
const outName = n === 1 ? `${base}.
|
|
699
|
+
const outName = n === 1 ? `${base}.thunder` : `${base}-${n}.thunder`;
|
|
700
700
|
missions.push({
|
|
701
701
|
mission: r.lifted.mission, sourceFile: file, outName,
|
|
702
702
|
intentText: r.intentText, summary: r.summary, diagnostics: r.diagnostics,
|