@skillstech/thunderlang 0.1.7 → 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/CHANGELOG.md +40 -0
- package/dist/core.cjs +111 -14
- package/dist/index.cjs +112 -15
- package/intent-graph.schema.json +227 -4
- package/package.json +1 -1
- package/src/cli.mjs +623 -20
- package/src/conformance.mjs +35 -0
- package/src/coverage.mjs +56 -0
- package/src/emit.mjs +3 -1
- package/src/expr.mjs +11 -7
- package/src/intent-graph.mjs +29 -0
- package/src/mutate.mjs +46 -0
- package/src/parse.mjs +82 -3
- package/src/property.mjs +108 -0
- package/src/target-cs.mjs +98 -0
- package/src/target-java.mjs +87 -0
- package/src/target-py.mjs +90 -0
- package/src/target-ts.mjs +66 -0
- package/src/target-util.mjs +95 -0
- package/src/testing.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,46 @@
|
|
|
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.2.0
|
|
7
|
+
|
|
8
|
+
The cross-language target execution release. Additive; no breaking changes.
|
|
9
|
+
|
|
10
|
+
Version alignment: starting with 0.2.0, ThunderLang and OpenThunder version in lockstep, since most
|
|
11
|
+
OpenThunder changes land in the language first. (0.1.8 and 0.1.9 were never published; the last
|
|
12
|
+
release on npm was 0.1.7.)
|
|
13
|
+
|
|
14
|
+
- **Live target adapters.** `thunder test <file> --target typescript|python|csharp|java` and
|
|
15
|
+
`thunder conform <file> --run <targets>` now compile the generated decision(s) and execute them
|
|
16
|
+
for real, grading the actual outputs instead of fed results. TypeScript/JS runs in-process,
|
|
17
|
+
Python through `python3`, C# through a throwaway `dotnet` console project, and Java through the
|
|
18
|
+
JDK 11+ single-file launcher (`java ThunderTarget.java`).
|
|
19
|
+
- **`--all-targets`.** `thunder test <file> --all-targets` and `thunder conform <file> --all-targets`
|
|
20
|
+
run every target whose toolchain is available in one pass, side by side, and skip any target whose
|
|
21
|
+
runtime is absent (it stays declared in the conformance matrix) rather than failing.
|
|
22
|
+
- **Static-target type inference.** Because C# and Java are statically typed, each decision
|
|
23
|
+
parameter's type is inferred from the test-case values it receives (numeric wins, then boolean,
|
|
24
|
+
else string), and each language gets its own equality translation.
|
|
25
|
+
- Missing toolchains are detected via cached smoke checks and skipped cleanly, so builds never fail
|
|
26
|
+
for a runtime that is not installed.
|
|
27
|
+
|
|
28
|
+
## 0.1.8
|
|
29
|
+
|
|
30
|
+
The testing and verification release. Additive; no breaking changes.
|
|
31
|
+
|
|
32
|
+
- **Rename to ThunderLang.** Package is `@skillstech/thunderlang`; the CLI binary is `thunder`
|
|
33
|
+
(with `intent` kept as a legacy alias). `.thunder` is the canonical source extension; `.tl` and
|
|
34
|
+
`.intent` are accepted. The Intent Graph and Intent-Oriented Programming vocabulary are retained.
|
|
35
|
+
- **`thunder prove`** emits an `intent-proof-v1` artifact with honest per-claim verdicts
|
|
36
|
+
(verified / failed / declared / needs_verification, with `provenBy`). An unverified claim never
|
|
37
|
+
reads as proven.
|
|
38
|
+
- **`thunder test --contracts [--strict]`** derives an obligation from every `guarantee` and
|
|
39
|
+
`never`, resolved against the specific test that verifies it. `--strict` fails CI on any unverified.
|
|
40
|
+
- **Verification classification**: `verify by assertion|static|runtime|evidence|tool|approval|formal`.
|
|
41
|
+
- **Stable IDs**: explicit `id INV-G-001` on guarantees / never-rules (slug fallback).
|
|
42
|
+
- **Proof freshness**: the proof records intent hash, compiler, git commit, dependency-lockfile hash,
|
|
43
|
+
and environment; `thunder verify` marks a proof STALE when the implementation, dependencies, or
|
|
44
|
+
compiler move since it was generated.
|
|
45
|
+
|
|
6
46
|
## 0.1.6
|
|
7
47
|
|
|
8
48
|
The agent-conformance release (skills on intents + 12-factor scoring). Additive; no breaking
|
package/dist/core.cjs
CHANGED
|
@@ -789,6 +789,60 @@ function parseDecision(name, node2) {
|
|
|
789
789
|
}
|
|
790
790
|
return dec;
|
|
791
791
|
}
|
|
792
|
+
function parseProperty(name, node2) {
|
|
793
|
+
const prop = { name, vars: [], decide: null, expects: [], line: node2.line };
|
|
794
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
795
|
+
const k = firstWord(c.text);
|
|
796
|
+
const a = rest(c.text);
|
|
797
|
+
if (k === "forAll" || k === "forall") {
|
|
798
|
+
for (const v of c.children.filter((x) => !isNote(x))) {
|
|
799
|
+
const m = v.text.trim().match(/^([A-Za-z_]\w*)\s*:\s*(\w+)(?:\s+where\s+(.+))?$/);
|
|
800
|
+
if (!m) continue;
|
|
801
|
+
const whereChild = (v.children || []).find((x) => firstWord(x.text) === "where");
|
|
802
|
+
prop.vars.push({ name: m[1], type: m[2], where: m[3] || (whereChild ? rest(whereChild.text) : null) });
|
|
803
|
+
}
|
|
804
|
+
} else if (k === "decide") prop.decide = a || c.children[0] && c.children[0].text.trim() || null;
|
|
805
|
+
else if (k === "when") {
|
|
806
|
+
const dm = a.match(/(?:decide|execute)\s+(\w+)/);
|
|
807
|
+
if (dm) prop.decide = dm[1];
|
|
808
|
+
} else if (k === "expect") for (const e of c.children.filter((x) => !isNote(x))) prop.expects.push(e.text.trim());
|
|
809
|
+
}
|
|
810
|
+
return prop;
|
|
811
|
+
}
|
|
812
|
+
function parseEvaluation(name, node2) {
|
|
813
|
+
const ev = { name, dataset: null, requires: [], line: node2.line };
|
|
814
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
815
|
+
const k = firstWord(c.text);
|
|
816
|
+
const a = rest(c.text);
|
|
817
|
+
if (k === "dataset") ev.dataset = a || c.children[0] && c.children[0].text.trim() || null;
|
|
818
|
+
else if (k === "require") {
|
|
819
|
+
for (const r of c.children.filter((x) => !isNote(x))) {
|
|
820
|
+
const m = r.text.trim().match(/^([A-Za-z_][\w.]*)\s*(>=|<=|==|!=|>|<)\s*(-?[\d.]+)$/);
|
|
821
|
+
if (m) ev.requires.push({ metric: m[1], op: m[2], threshold: parseFloat(m[3]) });
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return ev;
|
|
826
|
+
}
|
|
827
|
+
function parseScenario(name, node2) {
|
|
828
|
+
const sc = { name, given: [], when: [], then: [], never: [], eventually: [], line: node2.line };
|
|
829
|
+
const clausesOf = (c) => {
|
|
830
|
+
const items = leafItems(c);
|
|
831
|
+
return items.length ? items : rest(c.text) ? [rest(c.text)] : [];
|
|
832
|
+
};
|
|
833
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
834
|
+
const k = firstWord(c.text);
|
|
835
|
+
if (k === "given") sc.given.push(...clausesOf(c));
|
|
836
|
+
else if (k === "when") sc.when.push(...clausesOf(c));
|
|
837
|
+
else if (k === "then") sc.then.push(...clausesOf(c));
|
|
838
|
+
else if (k === "never") sc.never.push(...clausesOf(c));
|
|
839
|
+
else if (k === "eventually") {
|
|
840
|
+
const within = (rest(c.text).match(/within\s+(.+)/) || [])[1] || null;
|
|
841
|
+
sc.eventually.push({ within, clauses: leafItems(c) });
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return sc;
|
|
845
|
+
}
|
|
792
846
|
function parseLifecycle(name, node2) {
|
|
793
847
|
const lc = { name, states: [], transitions: [], terminals: [], line: node2.line };
|
|
794
848
|
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
@@ -821,7 +875,13 @@ function parseInvariant(name, node2) {
|
|
|
821
875
|
for (const c of kids(node2)) {
|
|
822
876
|
const k = firstWord(c.text);
|
|
823
877
|
if (k === "verify") inv.verify.push(rest(c.text));
|
|
824
|
-
else if (k === "
|
|
878
|
+
else if (k === "id") {
|
|
879
|
+
const v = rest(c.text);
|
|
880
|
+
if (v) {
|
|
881
|
+
inv.id = v;
|
|
882
|
+
inv.stableId = v;
|
|
883
|
+
}
|
|
884
|
+
} else if (k === "because") inv.because = rest(c.text);
|
|
825
885
|
else if (k === "statement") inv.statement = rest(c.text) || leafItems(c).join(" ");
|
|
826
886
|
else if (k === "scope") inv.scope = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
|
|
827
887
|
else if (k === "severity") inv.severity = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
|
|
@@ -907,11 +967,12 @@ function parseHandler(trigger, node2) {
|
|
|
907
967
|
actions
|
|
908
968
|
};
|
|
909
969
|
}
|
|
970
|
+
var VERIFY_KINDS = /* @__PURE__ */ new Set(["assertion", "static", "runtime", "evidence", "tool", "approval", "formal"]);
|
|
910
971
|
function upsertRule(list, statement, line) {
|
|
911
972
|
const id = slug(statement);
|
|
912
973
|
let r = list.find((x) => x.id === id);
|
|
913
974
|
if (!r) {
|
|
914
|
-
r = { id, statement, because: null, verify: [], notes: [], line };
|
|
975
|
+
r = { id, statement, because: null, verify: [], verifications: [], notes: [], line };
|
|
915
976
|
list.push(r);
|
|
916
977
|
}
|
|
917
978
|
return r;
|
|
@@ -920,8 +981,28 @@ function applyDetail(rule, node2) {
|
|
|
920
981
|
for (const c of node2.children) {
|
|
921
982
|
const kw = firstWord(c.text);
|
|
922
983
|
if (kw === "because") rule.because = rest(c.text) || c.children[0] && c.children[0].text || null;
|
|
923
|
-
else if (kw === "verify")
|
|
924
|
-
|
|
984
|
+
else if (kw === "verify") {
|
|
985
|
+
const a = rest(c.text);
|
|
986
|
+
const m = a && a.match(/^by\s+(\w+)/i);
|
|
987
|
+
if (m) {
|
|
988
|
+
const kind = m[1].toLowerCase();
|
|
989
|
+
const details = (c.children || []).filter((x) => !isNote(x)).map((x) => x.text.trim()).filter(Boolean);
|
|
990
|
+
rule.verifications.push({ kind: VERIFY_KINDS.has(kind) ? kind : "unknown", declared: kind, details });
|
|
991
|
+
rule.verify.push(details.length ? `${kind}: ${details.join("; ")}` : `by ${kind}`);
|
|
992
|
+
} else {
|
|
993
|
+
const v = a || c.children[0] && c.children[0].text || "";
|
|
994
|
+
if (v) {
|
|
995
|
+
rule.verify.push(v);
|
|
996
|
+
rule.verifications.push({ kind: "unclassified", declared: null, details: [v] });
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
} else if (kw === "id") {
|
|
1000
|
+
const v = rest(c.text) || c.children[0] && c.children[0].text;
|
|
1001
|
+
if (v) {
|
|
1002
|
+
rule.id = v;
|
|
1003
|
+
rule.stableId = v;
|
|
1004
|
+
}
|
|
1005
|
+
} else if (isNote(c)) rule.notes.push(parseNoteNode(c));
|
|
925
1006
|
}
|
|
926
1007
|
rule.verify = rule.verify.filter(Boolean);
|
|
927
1008
|
}
|
|
@@ -986,6 +1067,9 @@ function parseIntent(source) {
|
|
|
986
1067
|
handlers: [],
|
|
987
1068
|
// Decisions, rules, process (Gap 4)
|
|
988
1069
|
decisions: [],
|
|
1070
|
+
properties: [],
|
|
1071
|
+
scenarios: [],
|
|
1072
|
+
evaluations: [],
|
|
989
1073
|
// Governance: waivers , governed exceptions to blocking diagnostics (Gap 5)
|
|
990
1074
|
waivers: [],
|
|
991
1075
|
// Data purpose + privacy , governed data elements (Gap 6)
|
|
@@ -1214,6 +1298,15 @@ function parseIntent(source) {
|
|
|
1214
1298
|
case "decision":
|
|
1215
1299
|
ast.decisions.push(parseDecision(arg, node2));
|
|
1216
1300
|
break;
|
|
1301
|
+
case "property":
|
|
1302
|
+
ast.properties.push(parseProperty(arg, node2));
|
|
1303
|
+
break;
|
|
1304
|
+
case "scenario":
|
|
1305
|
+
ast.scenarios.push(parseScenario(arg, node2));
|
|
1306
|
+
break;
|
|
1307
|
+
case "evaluation":
|
|
1308
|
+
ast.evaluations.push(parseEvaluation(arg, node2));
|
|
1309
|
+
break;
|
|
1217
1310
|
// ── System profile ──
|
|
1218
1311
|
case "capability": {
|
|
1219
1312
|
const cap = { name: arg, description: null, implements: [], line: node2.line };
|
|
@@ -3198,21 +3291,23 @@ function compileExpr(src) {
|
|
|
3198
3291
|
function evalExpr(src, inputs = {}) {
|
|
3199
3292
|
return compileExpr(src)(inputs);
|
|
3200
3293
|
}
|
|
3294
|
+
var C_LOGIC = { and: (a, b) => `(${a} && ${b})`, or: (a, b) => `(${a} || ${b})`, not: (a) => `!${a}`, bool: (v) => String(v) };
|
|
3201
3295
|
var DIALECTS = {
|
|
3202
|
-
js: { eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
|
|
3203
|
-
csharp: { eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
|
|
3204
|
-
java: { eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" }
|
|
3296
|
+
js: { ...C_LOGIC, eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
|
|
3297
|
+
csharp: { ...C_LOGIC, eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
|
|
3298
|
+
java: { ...C_LOGIC, eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" },
|
|
3299
|
+
python: { and: (a, b) => `(${a} and ${b})`, or: (a, b) => `(${a} or ${b})`, not: (a) => `(not ${a})`, bool: (v) => v ? "True" : "False", eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `${x} in [${list.join(", ")}]`, nil: "None" }
|
|
3205
3300
|
};
|
|
3206
3301
|
function renderExpr(src, inputs, D) {
|
|
3207
3302
|
const known = new Set(inputs);
|
|
3208
3303
|
const r = (n) => {
|
|
3209
3304
|
switch (n.k) {
|
|
3210
3305
|
case "or":
|
|
3211
|
-
return
|
|
3306
|
+
return D.or(r(n.a), r(n.b));
|
|
3212
3307
|
case "and":
|
|
3213
|
-
return
|
|
3308
|
+
return D.and(r(n.a), r(n.b));
|
|
3214
3309
|
case "not":
|
|
3215
|
-
return
|
|
3310
|
+
return D.not(r(n.a));
|
|
3216
3311
|
case "neg":
|
|
3217
3312
|
return `-${r(n.a)}`;
|
|
3218
3313
|
case "arith":
|
|
@@ -3226,7 +3321,7 @@ function renderExpr(src, inputs, D) {
|
|
|
3226
3321
|
case "list":
|
|
3227
3322
|
return `[${n.items.map(r).join(", ")}]`;
|
|
3228
3323
|
case "lit":
|
|
3229
|
-
return typeof n.v === "string" ? JSON.stringify(n.v) : String(n.v);
|
|
3324
|
+
return typeof n.v === "string" ? JSON.stringify(n.v) : typeof n.v === "boolean" ? D.bool(n.v) : String(n.v);
|
|
3230
3325
|
case "ref":
|
|
3231
3326
|
return known.has(n.path) || known.has(n.path.split(".")[0]) ? n.path : JSON.stringify(n.path);
|
|
3232
3327
|
default:
|
|
@@ -4193,7 +4288,7 @@ function notesSummary(ast) {
|
|
|
4193
4288
|
byLens
|
|
4194
4289
|
};
|
|
4195
4290
|
}
|
|
4196
|
-
var COMPILER_VERSION = "0.
|
|
4291
|
+
var COMPILER_VERSION = "0.2.0";
|
|
4197
4292
|
var PROOF_SCHEMA_VERSION = "0.1.0";
|
|
4198
4293
|
var SOURCE_PRODUCT = "skillstech-compiler";
|
|
4199
4294
|
function buildContractGraph(ast, generatedAt) {
|
|
@@ -4686,13 +4781,15 @@ function buildProof(ast, { sourceFile, sourceHash, targetsRequested, targetsGene
|
|
|
4686
4781
|
id: g.id,
|
|
4687
4782
|
text: g.statement,
|
|
4688
4783
|
status: g.verify.length > 0 || verifiedText ? "planned" : "needs_verification",
|
|
4689
|
-
evidence: g.verify
|
|
4784
|
+
evidence: g.verify,
|
|
4785
|
+
verifications: g.verifications || []
|
|
4690
4786
|
})),
|
|
4691
4787
|
neverRules: ast.neverRules.map((n) => ({
|
|
4692
4788
|
id: n.id,
|
|
4693
4789
|
text: n.statement,
|
|
4694
4790
|
status: n.verify.length > 0 ? "planned" : "needs_verification",
|
|
4695
|
-
evidence: n.verify
|
|
4791
|
+
evidence: n.verify,
|
|
4792
|
+
verifications: n.verifications || []
|
|
4696
4793
|
})),
|
|
4697
4794
|
errors: (ast.errors || []).map((e) => ({ name: e.name })),
|
|
4698
4795
|
examples: (ast.examples || []).map((ex) => ({ given: ex.given, expect: ex.expect })),
|
package/dist/index.cjs
CHANGED
|
@@ -450,6 +450,60 @@ function parseDecision(name, node2) {
|
|
|
450
450
|
}
|
|
451
451
|
return dec;
|
|
452
452
|
}
|
|
453
|
+
function parseProperty(name, node2) {
|
|
454
|
+
const prop = { name, vars: [], decide: null, expects: [], line: node2.line };
|
|
455
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
456
|
+
const k = firstWord(c.text);
|
|
457
|
+
const a = rest(c.text);
|
|
458
|
+
if (k === "forAll" || k === "forall") {
|
|
459
|
+
for (const v of c.children.filter((x) => !isNote(x))) {
|
|
460
|
+
const m = v.text.trim().match(/^([A-Za-z_]\w*)\s*:\s*(\w+)(?:\s+where\s+(.+))?$/);
|
|
461
|
+
if (!m) continue;
|
|
462
|
+
const whereChild = (v.children || []).find((x) => firstWord(x.text) === "where");
|
|
463
|
+
prop.vars.push({ name: m[1], type: m[2], where: m[3] || (whereChild ? rest(whereChild.text) : null) });
|
|
464
|
+
}
|
|
465
|
+
} else if (k === "decide") prop.decide = a || c.children[0] && c.children[0].text.trim() || null;
|
|
466
|
+
else if (k === "when") {
|
|
467
|
+
const dm = a.match(/(?:decide|execute)\s+(\w+)/);
|
|
468
|
+
if (dm) prop.decide = dm[1];
|
|
469
|
+
} else if (k === "expect") for (const e of c.children.filter((x) => !isNote(x))) prop.expects.push(e.text.trim());
|
|
470
|
+
}
|
|
471
|
+
return prop;
|
|
472
|
+
}
|
|
473
|
+
function parseEvaluation(name, node2) {
|
|
474
|
+
const ev = { name, dataset: null, requires: [], line: node2.line };
|
|
475
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
476
|
+
const k = firstWord(c.text);
|
|
477
|
+
const a = rest(c.text);
|
|
478
|
+
if (k === "dataset") ev.dataset = a || c.children[0] && c.children[0].text.trim() || null;
|
|
479
|
+
else if (k === "require") {
|
|
480
|
+
for (const r of c.children.filter((x) => !isNote(x))) {
|
|
481
|
+
const m = r.text.trim().match(/^([A-Za-z_][\w.]*)\s*(>=|<=|==|!=|>|<)\s*(-?[\d.]+)$/);
|
|
482
|
+
if (m) ev.requires.push({ metric: m[1], op: m[2], threshold: parseFloat(m[3]) });
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return ev;
|
|
487
|
+
}
|
|
488
|
+
function parseScenario(name, node2) {
|
|
489
|
+
const sc = { name, given: [], when: [], then: [], never: [], eventually: [], line: node2.line };
|
|
490
|
+
const clausesOf = (c) => {
|
|
491
|
+
const items = leafItems(c);
|
|
492
|
+
return items.length ? items : rest(c.text) ? [rest(c.text)] : [];
|
|
493
|
+
};
|
|
494
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
495
|
+
const k = firstWord(c.text);
|
|
496
|
+
if (k === "given") sc.given.push(...clausesOf(c));
|
|
497
|
+
else if (k === "when") sc.when.push(...clausesOf(c));
|
|
498
|
+
else if (k === "then") sc.then.push(...clausesOf(c));
|
|
499
|
+
else if (k === "never") sc.never.push(...clausesOf(c));
|
|
500
|
+
else if (k === "eventually") {
|
|
501
|
+
const within = (rest(c.text).match(/within\s+(.+)/) || [])[1] || null;
|
|
502
|
+
sc.eventually.push({ within, clauses: leafItems(c) });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return sc;
|
|
506
|
+
}
|
|
453
507
|
function parseLifecycle(name, node2) {
|
|
454
508
|
const lc = { name, states: [], transitions: [], terminals: [], line: node2.line };
|
|
455
509
|
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
@@ -482,7 +536,13 @@ function parseInvariant(name, node2) {
|
|
|
482
536
|
for (const c of kids(node2)) {
|
|
483
537
|
const k = firstWord(c.text);
|
|
484
538
|
if (k === "verify") inv.verify.push(rest(c.text));
|
|
485
|
-
else if (k === "
|
|
539
|
+
else if (k === "id") {
|
|
540
|
+
const v = rest(c.text);
|
|
541
|
+
if (v) {
|
|
542
|
+
inv.id = v;
|
|
543
|
+
inv.stableId = v;
|
|
544
|
+
}
|
|
545
|
+
} else if (k === "because") inv.because = rest(c.text);
|
|
486
546
|
else if (k === "statement") inv.statement = rest(c.text) || leafItems(c).join(" ");
|
|
487
547
|
else if (k === "scope") inv.scope = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
|
|
488
548
|
else if (k === "severity") inv.severity = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
|
|
@@ -568,11 +628,12 @@ function parseHandler(trigger, node2) {
|
|
|
568
628
|
actions
|
|
569
629
|
};
|
|
570
630
|
}
|
|
631
|
+
var VERIFY_KINDS = /* @__PURE__ */ new Set(["assertion", "static", "runtime", "evidence", "tool", "approval", "formal"]);
|
|
571
632
|
function upsertRule(list, statement, line) {
|
|
572
633
|
const id = slug(statement);
|
|
573
634
|
let r = list.find((x) => x.id === id);
|
|
574
635
|
if (!r) {
|
|
575
|
-
r = { id, statement, because: null, verify: [], notes: [], line };
|
|
636
|
+
r = { id, statement, because: null, verify: [], verifications: [], notes: [], line };
|
|
576
637
|
list.push(r);
|
|
577
638
|
}
|
|
578
639
|
return r;
|
|
@@ -581,8 +642,28 @@ function applyDetail(rule, node2) {
|
|
|
581
642
|
for (const c of node2.children) {
|
|
582
643
|
const kw = firstWord(c.text);
|
|
583
644
|
if (kw === "because") rule.because = rest(c.text) || c.children[0] && c.children[0].text || null;
|
|
584
|
-
else if (kw === "verify")
|
|
585
|
-
|
|
645
|
+
else if (kw === "verify") {
|
|
646
|
+
const a = rest(c.text);
|
|
647
|
+
const m = a && a.match(/^by\s+(\w+)/i);
|
|
648
|
+
if (m) {
|
|
649
|
+
const kind = m[1].toLowerCase();
|
|
650
|
+
const details = (c.children || []).filter((x) => !isNote(x)).map((x) => x.text.trim()).filter(Boolean);
|
|
651
|
+
rule.verifications.push({ kind: VERIFY_KINDS.has(kind) ? kind : "unknown", declared: kind, details });
|
|
652
|
+
rule.verify.push(details.length ? `${kind}: ${details.join("; ")}` : `by ${kind}`);
|
|
653
|
+
} else {
|
|
654
|
+
const v = a || c.children[0] && c.children[0].text || "";
|
|
655
|
+
if (v) {
|
|
656
|
+
rule.verify.push(v);
|
|
657
|
+
rule.verifications.push({ kind: "unclassified", declared: null, details: [v] });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
} else if (kw === "id") {
|
|
661
|
+
const v = rest(c.text) || c.children[0] && c.children[0].text;
|
|
662
|
+
if (v) {
|
|
663
|
+
rule.id = v;
|
|
664
|
+
rule.stableId = v;
|
|
665
|
+
}
|
|
666
|
+
} else if (isNote(c)) rule.notes.push(parseNoteNode(c));
|
|
586
667
|
}
|
|
587
668
|
rule.verify = rule.verify.filter(Boolean);
|
|
588
669
|
}
|
|
@@ -647,6 +728,9 @@ function parseIntent(source) {
|
|
|
647
728
|
handlers: [],
|
|
648
729
|
// Decisions, rules, process (Gap 4)
|
|
649
730
|
decisions: [],
|
|
731
|
+
properties: [],
|
|
732
|
+
scenarios: [],
|
|
733
|
+
evaluations: [],
|
|
650
734
|
// Governance: waivers , governed exceptions to blocking diagnostics (Gap 5)
|
|
651
735
|
waivers: [],
|
|
652
736
|
// Data purpose + privacy , governed data elements (Gap 6)
|
|
@@ -875,6 +959,15 @@ function parseIntent(source) {
|
|
|
875
959
|
case "decision":
|
|
876
960
|
ast.decisions.push(parseDecision(arg, node2));
|
|
877
961
|
break;
|
|
962
|
+
case "property":
|
|
963
|
+
ast.properties.push(parseProperty(arg, node2));
|
|
964
|
+
break;
|
|
965
|
+
case "scenario":
|
|
966
|
+
ast.scenarios.push(parseScenario(arg, node2));
|
|
967
|
+
break;
|
|
968
|
+
case "evaluation":
|
|
969
|
+
ast.evaluations.push(parseEvaluation(arg, node2));
|
|
970
|
+
break;
|
|
878
971
|
// ── System profile ──
|
|
879
972
|
case "capability": {
|
|
880
973
|
const cap = { name: arg, description: null, implements: [], line: node2.line };
|
|
@@ -2194,7 +2287,7 @@ function notesSummary(ast) {
|
|
|
2194
2287
|
byLens
|
|
2195
2288
|
};
|
|
2196
2289
|
}
|
|
2197
|
-
var COMPILER_VERSION = "0.
|
|
2290
|
+
var COMPILER_VERSION = "0.2.0";
|
|
2198
2291
|
var PROOF_SCHEMA_VERSION = "0.1.0";
|
|
2199
2292
|
var SOURCE_PRODUCT = "skillstech-compiler";
|
|
2200
2293
|
function buildContractGraph(ast, generatedAt) {
|
|
@@ -2687,13 +2780,15 @@ function buildProof(ast, { sourceFile, sourceHash, targetsRequested, targetsGene
|
|
|
2687
2780
|
id: g.id,
|
|
2688
2781
|
text: g.statement,
|
|
2689
2782
|
status: g.verify.length > 0 || verifiedText ? "planned" : "needs_verification",
|
|
2690
|
-
evidence: g.verify
|
|
2783
|
+
evidence: g.verify,
|
|
2784
|
+
verifications: g.verifications || []
|
|
2691
2785
|
})),
|
|
2692
2786
|
neverRules: ast.neverRules.map((n) => ({
|
|
2693
2787
|
id: n.id,
|
|
2694
2788
|
text: n.statement,
|
|
2695
2789
|
status: n.verify.length > 0 ? "planned" : "needs_verification",
|
|
2696
|
-
evidence: n.verify
|
|
2790
|
+
evidence: n.verify,
|
|
2791
|
+
verifications: n.verifications || []
|
|
2697
2792
|
})),
|
|
2698
2793
|
errors: (ast.errors || []).map((e) => ({ name: e.name })),
|
|
2699
2794
|
examples: (ast.examples || []).map((ex) => ({ given: ex.given, expect: ex.expect })),
|
|
@@ -6088,21 +6183,23 @@ function compileExpr(src) {
|
|
|
6088
6183
|
function evalExpr(src, inputs = {}) {
|
|
6089
6184
|
return compileExpr(src)(inputs);
|
|
6090
6185
|
}
|
|
6186
|
+
var C_LOGIC = { and: (a, b) => `(${a} && ${b})`, or: (a, b) => `(${a} || ${b})`, not: (a) => `!${a}`, bool: (v) => String(v) };
|
|
6091
6187
|
var DIALECTS = {
|
|
6092
|
-
js: { eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
|
|
6093
|
-
csharp: { eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
|
|
6094
|
-
java: { eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" }
|
|
6188
|
+
js: { ...C_LOGIC, eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
|
|
6189
|
+
csharp: { ...C_LOGIC, eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
|
|
6190
|
+
java: { ...C_LOGIC, eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" },
|
|
6191
|
+
python: { and: (a, b) => `(${a} and ${b})`, or: (a, b) => `(${a} or ${b})`, not: (a) => `(not ${a})`, bool: (v) => v ? "True" : "False", eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `${x} in [${list.join(", ")}]`, nil: "None" }
|
|
6095
6192
|
};
|
|
6096
6193
|
function renderExpr(src, inputs, D) {
|
|
6097
6194
|
const known = new Set(inputs);
|
|
6098
6195
|
const r = (n) => {
|
|
6099
6196
|
switch (n.k) {
|
|
6100
6197
|
case "or":
|
|
6101
|
-
return
|
|
6198
|
+
return D.or(r(n.a), r(n.b));
|
|
6102
6199
|
case "and":
|
|
6103
|
-
return
|
|
6200
|
+
return D.and(r(n.a), r(n.b));
|
|
6104
6201
|
case "not":
|
|
6105
|
-
return
|
|
6202
|
+
return D.not(r(n.a));
|
|
6106
6203
|
case "neg":
|
|
6107
6204
|
return `-${r(n.a)}`;
|
|
6108
6205
|
case "arith":
|
|
@@ -6116,7 +6213,7 @@ function renderExpr(src, inputs, D) {
|
|
|
6116
6213
|
case "list":
|
|
6117
6214
|
return `[${n.items.map(r).join(", ")}]`;
|
|
6118
6215
|
case "lit":
|
|
6119
|
-
return typeof n.v === "string" ? JSON.stringify(n.v) : String(n.v);
|
|
6216
|
+
return typeof n.v === "string" ? JSON.stringify(n.v) : typeof n.v === "boolean" ? D.bool(n.v) : String(n.v);
|
|
6120
6217
|
case "ref":
|
|
6121
6218
|
return known.has(n.path) || known.has(n.path.split(".")[0]) ? n.path : JSON.stringify(n.path);
|
|
6122
6219
|
default:
|
|
@@ -6237,7 +6334,7 @@ function runTests(ast) {
|
|
|
6237
6334
|
const inputs = coerce(c.given);
|
|
6238
6335
|
const run = evaluateDecision(dec, inputs);
|
|
6239
6336
|
const pass = c.expect == null || String(run.result) === String(c.expect);
|
|
6240
|
-
results.push({ ...label, kind: "decision", expected: c.expect, actual: run.result, pass, ...run.ok ? {} : { note: "a condition failed to evaluate" } });
|
|
6337
|
+
results.push({ ...label, kind: "decision", expected: c.expect, actual: run.result, matched: run.matched, pass, ...run.ok ? {} : { note: "a condition failed to evaluate" } });
|
|
6241
6338
|
} else if (lc) {
|
|
6242
6339
|
const sim = simulateLifecycle(lc, c.events || []);
|
|
6243
6340
|
const passState = c.expect == null || sim.finalState === c.expect;
|