@vibgrate/cli 2026.613.1 → 2026.615.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/dist/{baseline-LTB3IFRV.js → baseline-WUHK4LQY.js} +3 -3
- package/dist/{chunk-74ZJFYEM.js → chunk-5IXVOEZN.js} +159 -96
- package/dist/{chunk-RPI2K62L.js → chunk-BOGC5GYS.js} +5060 -1782
- package/dist/{chunk-UNIBRNYG.js → chunk-HJ4D6B3B.js} +1 -1
- package/dist/cli.js +884 -6
- package/dist/hcs-worker.js +323 -12
- package/dist/index.js +2 -2
- package/dist/{semver-JBJZTHUX.js → semver-2FJFIYVN.js} +1 -1
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
pathExists,
|
|
7
7
|
readJsonFile,
|
|
8
8
|
writeTextFile
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-HJ4D6B3B.js";
|
|
10
10
|
import {
|
|
11
11
|
computeRepoFingerprint,
|
|
12
12
|
detectVcs,
|
|
@@ -16,10 +16,10 @@ import {
|
|
|
16
16
|
resolveRepositoryName,
|
|
17
17
|
runScan,
|
|
18
18
|
writeDefaultConfig
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-BOGC5GYS.js";
|
|
20
20
|
import {
|
|
21
21
|
require_semver
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-5IXVOEZN.js";
|
|
23
23
|
import {
|
|
24
24
|
parseExcludePatterns,
|
|
25
25
|
pathExists as pathExists2
|
|
@@ -49,7 +49,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
49
49
|
console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
|
|
50
50
|
}
|
|
51
51
|
if (opts.baseline) {
|
|
52
|
-
const { runBaseline } = await import("./baseline-
|
|
52
|
+
const { runBaseline } = await import("./baseline-WUHK4LQY.js");
|
|
53
53
|
await runBaseline(rootDir);
|
|
54
54
|
}
|
|
55
55
|
console.log("");
|
|
@@ -1667,7 +1667,7 @@ var updateCommand = new Command6("update").description("Update vibgrate to the l
|
|
|
1667
1667
|
console.error(chalk7.red("Could not reach the npm registry. Check your network connection."));
|
|
1668
1668
|
process.exit(1);
|
|
1669
1669
|
}
|
|
1670
|
-
const semver2 = await import("./semver-
|
|
1670
|
+
const semver2 = await import("./semver-2FJFIYVN.js");
|
|
1671
1671
|
if (!semver2.gt(latest, VERSION)) {
|
|
1672
1672
|
console.log(chalk7.green("\u2714") + ` You are on the latest version (${VERSION}).`);
|
|
1673
1673
|
return;
|
|
@@ -1885,6 +1885,859 @@ import { existsSync } from "fs";
|
|
|
1885
1885
|
import { spawn, spawnSync } from "child_process";
|
|
1886
1886
|
import { Command as Command8 } from "commander";
|
|
1887
1887
|
import chalk9 from "chalk";
|
|
1888
|
+
|
|
1889
|
+
// src/behavioural/derive.ts
|
|
1890
|
+
import { createHash as createHash2 } from "crypto";
|
|
1891
|
+
|
|
1892
|
+
// src/behavioural/hxl.ts
|
|
1893
|
+
import { createHash } from "crypto";
|
|
1894
|
+
var HxlTable = class {
|
|
1895
|
+
nodes = [];
|
|
1896
|
+
byKey = /* @__PURE__ */ new Map();
|
|
1897
|
+
intern(node) {
|
|
1898
|
+
const key = canonicalKey(node);
|
|
1899
|
+
const existing = this.byKey.get(key);
|
|
1900
|
+
if (existing !== void 0) return existing;
|
|
1901
|
+
const idx = this.nodes.length;
|
|
1902
|
+
this.nodes.push(node);
|
|
1903
|
+
this.byKey.set(key, idx);
|
|
1904
|
+
return idx;
|
|
1905
|
+
}
|
|
1906
|
+
/** Fraction of nodes that are faithfully represented (not Opaque). */
|
|
1907
|
+
fidelity() {
|
|
1908
|
+
if (this.nodes.length === 0) return 1;
|
|
1909
|
+
const opaque = this.nodes.filter((n) => n.k === "Opaque").length;
|
|
1910
|
+
return 1 - opaque / this.nodes.length;
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
function canonicalKey(n) {
|
|
1914
|
+
switch (n.k) {
|
|
1915
|
+
case "Lit":
|
|
1916
|
+
return `L:${n.type}:${JSON.stringify(n.value)}`;
|
|
1917
|
+
case "Ref":
|
|
1918
|
+
return `R:${n.name}`;
|
|
1919
|
+
case "Member":
|
|
1920
|
+
return `M:${n.obj}.${n.name}`;
|
|
1921
|
+
case "Call":
|
|
1922
|
+
return `C:${n.callee}(${n.args.join(",")})`;
|
|
1923
|
+
case "Unary":
|
|
1924
|
+
return `U:${n.op}:${n.x}`;
|
|
1925
|
+
case "Binary":
|
|
1926
|
+
return `B:${n.op}:${n.l}:${n.r}`;
|
|
1927
|
+
case "Opaque":
|
|
1928
|
+
return `O:${n.raw}`;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
var BIN_OPS = {
|
|
1932
|
+
"==": "eq",
|
|
1933
|
+
"===": "eq",
|
|
1934
|
+
"!=": "ne",
|
|
1935
|
+
"!==": "ne",
|
|
1936
|
+
"<": "lt",
|
|
1937
|
+
"<=": "le",
|
|
1938
|
+
">": "gt",
|
|
1939
|
+
">=": "ge",
|
|
1940
|
+
"&&": "and",
|
|
1941
|
+
"||": "or",
|
|
1942
|
+
"+": "add",
|
|
1943
|
+
"-": "sub",
|
|
1944
|
+
"*": "mul",
|
|
1945
|
+
"/": "div",
|
|
1946
|
+
"%": "mod"
|
|
1947
|
+
};
|
|
1948
|
+
function tokenize(src) {
|
|
1949
|
+
const toks = [];
|
|
1950
|
+
let i = 0;
|
|
1951
|
+
const ops3 = ["===", "!=="];
|
|
1952
|
+
const ops2 = ["==", "!=", "<=", ">=", "&&", "||"];
|
|
1953
|
+
while (i < src.length) {
|
|
1954
|
+
const c = src[i];
|
|
1955
|
+
if (c === " " || c === " " || c === "\n" || c === "\r") {
|
|
1956
|
+
i++;
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
if (src.startsWith("=>", i)) return null;
|
|
1960
|
+
const three = src.slice(i, i + 3);
|
|
1961
|
+
if (ops3.includes(three)) {
|
|
1962
|
+
toks.push({ t: "op", v: three });
|
|
1963
|
+
i += 3;
|
|
1964
|
+
continue;
|
|
1965
|
+
}
|
|
1966
|
+
const two = src.slice(i, i + 2);
|
|
1967
|
+
if (ops2.includes(two)) {
|
|
1968
|
+
toks.push({ t: "op", v: two });
|
|
1969
|
+
i += 2;
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1972
|
+
if ("<>+-*/%".includes(c)) {
|
|
1973
|
+
toks.push({ t: "op", v: c });
|
|
1974
|
+
i++;
|
|
1975
|
+
continue;
|
|
1976
|
+
}
|
|
1977
|
+
if (c === "!") {
|
|
1978
|
+
toks.push({ t: "not", v: "!" });
|
|
1979
|
+
i++;
|
|
1980
|
+
continue;
|
|
1981
|
+
}
|
|
1982
|
+
if (c === "(" || c === ")" || c === "." || c === ",") {
|
|
1983
|
+
toks.push({ t: c, v: c });
|
|
1984
|
+
i++;
|
|
1985
|
+
continue;
|
|
1986
|
+
}
|
|
1987
|
+
if (c === '"' || c === "'") {
|
|
1988
|
+
let j = i + 1;
|
|
1989
|
+
let s = "";
|
|
1990
|
+
while (j < src.length && src[j] !== c) {
|
|
1991
|
+
if (src[j] === "\\") {
|
|
1992
|
+
s += src[j + 1] ?? "";
|
|
1993
|
+
j += 2;
|
|
1994
|
+
} else {
|
|
1995
|
+
s += src[j];
|
|
1996
|
+
j++;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
if (j >= src.length) return null;
|
|
2000
|
+
toks.push({ t: "str", v: s });
|
|
2001
|
+
i = j + 1;
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
2004
|
+
if (/[0-9]/.test(c)) {
|
|
2005
|
+
let j = i;
|
|
2006
|
+
while (j < src.length && /[0-9.]/.test(src[j])) j++;
|
|
2007
|
+
toks.push({ t: "num", v: src.slice(i, j) });
|
|
2008
|
+
i = j;
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
if (/[A-Za-z_$]/.test(c)) {
|
|
2012
|
+
let j = i;
|
|
2013
|
+
while (j < src.length && /[A-Za-z0-9_$]/.test(src[j])) j++;
|
|
2014
|
+
toks.push({ t: "ident", v: src.slice(i, j) });
|
|
2015
|
+
i = j;
|
|
2016
|
+
continue;
|
|
2017
|
+
}
|
|
2018
|
+
return null;
|
|
2019
|
+
}
|
|
2020
|
+
return toks;
|
|
2021
|
+
}
|
|
2022
|
+
function parseGuard(src, table = new HxlTable()) {
|
|
2023
|
+
const toks = tokenize(src);
|
|
2024
|
+
if (!toks || toks.length === 0) return null;
|
|
2025
|
+
let p = 0;
|
|
2026
|
+
const peek = () => toks[p];
|
|
2027
|
+
const eat = () => toks[p++];
|
|
2028
|
+
function parseOr() {
|
|
2029
|
+
return parseBinLevel(["||"], parseAnd);
|
|
2030
|
+
}
|
|
2031
|
+
function parseAnd() {
|
|
2032
|
+
return parseBinLevel(["&&"], parseCmp);
|
|
2033
|
+
}
|
|
2034
|
+
function parseCmp() {
|
|
2035
|
+
const l = parseAdd();
|
|
2036
|
+
if (l === null) return null;
|
|
2037
|
+
const tk = peek();
|
|
2038
|
+
if (tk && tk.t === "op" && ["==", "===", "!=", "!==", "<", "<=", ">", ">="].includes(tk.v)) {
|
|
2039
|
+
eat();
|
|
2040
|
+
const r = parseAdd();
|
|
2041
|
+
if (r === null) return null;
|
|
2042
|
+
return table.intern({ k: "Binary", op: BIN_OPS[tk.v], l, r });
|
|
2043
|
+
}
|
|
2044
|
+
return l;
|
|
2045
|
+
}
|
|
2046
|
+
function parseAdd() {
|
|
2047
|
+
return parseBinLevel(["+", "-"], parseMul);
|
|
2048
|
+
}
|
|
2049
|
+
function parseMul() {
|
|
2050
|
+
return parseBinLevel(["*", "/", "%"], parseUnary);
|
|
2051
|
+
}
|
|
2052
|
+
function parseBinLevel(ops, next) {
|
|
2053
|
+
let l = next();
|
|
2054
|
+
if (l === null) return null;
|
|
2055
|
+
while (peek() && peek().t === "op" && ops.includes(peek().v)) {
|
|
2056
|
+
const op = eat().v;
|
|
2057
|
+
const r = next();
|
|
2058
|
+
if (r === null) return null;
|
|
2059
|
+
l = table.intern({ k: "Binary", op: BIN_OPS[op], l, r });
|
|
2060
|
+
}
|
|
2061
|
+
return l;
|
|
2062
|
+
}
|
|
2063
|
+
function parseUnary() {
|
|
2064
|
+
if (peek() && peek().t === "not") {
|
|
2065
|
+
eat();
|
|
2066
|
+
const x = parseUnary();
|
|
2067
|
+
if (x === null) return null;
|
|
2068
|
+
return table.intern({ k: "Unary", op: "not", x });
|
|
2069
|
+
}
|
|
2070
|
+
return parsePostfix();
|
|
2071
|
+
}
|
|
2072
|
+
function parsePostfix() {
|
|
2073
|
+
let node = parsePrimary();
|
|
2074
|
+
if (node === null) return null;
|
|
2075
|
+
for (; ; ) {
|
|
2076
|
+
const tk = peek();
|
|
2077
|
+
if (tk && tk.t === ".") {
|
|
2078
|
+
eat();
|
|
2079
|
+
const id = eat();
|
|
2080
|
+
if (!id || id.t !== "ident") return null;
|
|
2081
|
+
node = table.intern({ k: "Member", obj: node, name: id.v });
|
|
2082
|
+
continue;
|
|
2083
|
+
}
|
|
2084
|
+
if (tk && tk.t === "(") {
|
|
2085
|
+
eat();
|
|
2086
|
+
const args = [];
|
|
2087
|
+
if (peek() && peek().t !== ")") {
|
|
2088
|
+
for (; ; ) {
|
|
2089
|
+
const a = parseOr();
|
|
2090
|
+
if (a === null) return null;
|
|
2091
|
+
args.push(a);
|
|
2092
|
+
if (peek() && peek().t === ",") {
|
|
2093
|
+
eat();
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
break;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
if (!peek() || eat().t !== ")") return null;
|
|
2100
|
+
node = table.intern({ k: "Call", callee: node, args });
|
|
2101
|
+
continue;
|
|
2102
|
+
}
|
|
2103
|
+
break;
|
|
2104
|
+
}
|
|
2105
|
+
return node;
|
|
2106
|
+
}
|
|
2107
|
+
function parsePrimary() {
|
|
2108
|
+
const tk = peek();
|
|
2109
|
+
if (!tk) return null;
|
|
2110
|
+
if (tk.t === "num") {
|
|
2111
|
+
eat();
|
|
2112
|
+
return table.intern({ k: "Lit", value: Number(tk.v), type: tk.v.includes(".") ? "float" : "int" });
|
|
2113
|
+
}
|
|
2114
|
+
if (tk.t === "str") {
|
|
2115
|
+
eat();
|
|
2116
|
+
return table.intern({ k: "Lit", value: tk.v, type: "string" });
|
|
2117
|
+
}
|
|
2118
|
+
if (tk.t === "ident") {
|
|
2119
|
+
if (tk.v === "true" || tk.v === "false") {
|
|
2120
|
+
eat();
|
|
2121
|
+
return table.intern({ k: "Lit", value: tk.v === "true", type: "bool" });
|
|
2122
|
+
}
|
|
2123
|
+
if (tk.v === "null") {
|
|
2124
|
+
eat();
|
|
2125
|
+
return table.intern({ k: "Lit", value: null, type: "null" });
|
|
2126
|
+
}
|
|
2127
|
+
eat();
|
|
2128
|
+
return table.intern({ k: "Ref", name: tk.v });
|
|
2129
|
+
}
|
|
2130
|
+
if (tk.t === "(") {
|
|
2131
|
+
eat();
|
|
2132
|
+
const e = parseOr();
|
|
2133
|
+
if (e === null) return null;
|
|
2134
|
+
if (!peek() || eat().t !== ")") return null;
|
|
2135
|
+
return e;
|
|
2136
|
+
}
|
|
2137
|
+
return null;
|
|
2138
|
+
}
|
|
2139
|
+
const root = parseOr();
|
|
2140
|
+
if (root === null || p !== toks.length) return null;
|
|
2141
|
+
return { table, root };
|
|
2142
|
+
}
|
|
2143
|
+
function lowerGuard(src) {
|
|
2144
|
+
const parsed = parseGuard(src.trim());
|
|
2145
|
+
if (parsed) return parsed;
|
|
2146
|
+
const table = new HxlTable();
|
|
2147
|
+
const root = table.intern({ k: "Opaque", raw: src.trim() });
|
|
2148
|
+
return { table, root };
|
|
2149
|
+
}
|
|
2150
|
+
function renderNode(table, idx) {
|
|
2151
|
+
const n = table.nodes[idx];
|
|
2152
|
+
switch (n.k) {
|
|
2153
|
+
case "Lit":
|
|
2154
|
+
return n.type === "string" ? JSON.stringify(n.value) : String(n.value);
|
|
2155
|
+
case "Ref":
|
|
2156
|
+
return n.name;
|
|
2157
|
+
case "Member":
|
|
2158
|
+
return `${renderNode(table, n.obj)}.${n.name}`;
|
|
2159
|
+
case "Call":
|
|
2160
|
+
return `${renderNode(table, n.callee)}(${n.args.map((a) => renderNode(table, a)).join(", ")})`;
|
|
2161
|
+
case "Unary":
|
|
2162
|
+
return n.op === "not" ? `not ${renderNode(table, n.x)}` : `-${renderNode(table, n.x)}`;
|
|
2163
|
+
case "Binary":
|
|
2164
|
+
return `${renderNode(table, n.l)} ${n.op} ${renderNode(table, n.r)}`;
|
|
2165
|
+
case "Opaque":
|
|
2166
|
+
return `\xAB${n.raw}\xBB`;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
// src/behavioural/derive.ts
|
|
2171
|
+
var BEHAVIOUR_BEARING_TYPES = /* @__PURE__ */ new Set([
|
|
2172
|
+
"RouteDeclared",
|
|
2173
|
+
"DataAccessOperation",
|
|
2174
|
+
"EventEmitted",
|
|
2175
|
+
"EventConsumed",
|
|
2176
|
+
"ExternalServiceCall",
|
|
2177
|
+
"CicsTransactionDeclared"
|
|
2178
|
+
]);
|
|
2179
|
+
function detokenize(value, refs) {
|
|
2180
|
+
if (typeof value === "string") {
|
|
2181
|
+
if (!value.startsWith("@@")) return value;
|
|
2182
|
+
for (const [id, full] of refs) {
|
|
2183
|
+
if (value === id) return full;
|
|
2184
|
+
if (value.startsWith(id + "/")) return full + value.slice(id.length);
|
|
2185
|
+
}
|
|
2186
|
+
return value;
|
|
2187
|
+
}
|
|
2188
|
+
if (Array.isArray(value)) return value.map((v) => detokenize(v, refs));
|
|
2189
|
+
if (value && typeof value === "object") {
|
|
2190
|
+
const out = {};
|
|
2191
|
+
for (const [k, v] of Object.entries(value)) out[k] = detokenize(v, refs);
|
|
2192
|
+
return out;
|
|
2193
|
+
}
|
|
2194
|
+
return value;
|
|
2195
|
+
}
|
|
2196
|
+
function decompressFacts(lines) {
|
|
2197
|
+
const models = /* @__PURE__ */ new Map();
|
|
2198
|
+
const refs = /* @__PURE__ */ new Map();
|
|
2199
|
+
const parsed = [];
|
|
2200
|
+
for (const line of lines) {
|
|
2201
|
+
const trimmed = line.trim();
|
|
2202
|
+
if (!trimmed) continue;
|
|
2203
|
+
let obj;
|
|
2204
|
+
try {
|
|
2205
|
+
obj = JSON.parse(trimmed);
|
|
2206
|
+
} catch {
|
|
2207
|
+
continue;
|
|
2208
|
+
}
|
|
2209
|
+
if (obj.factType === "Models") {
|
|
2210
|
+
const list = obj.payload?.models ?? [];
|
|
2211
|
+
for (const m of list) models.set(m.id, m);
|
|
2212
|
+
continue;
|
|
2213
|
+
}
|
|
2214
|
+
if (obj.factType === "References") {
|
|
2215
|
+
const list = obj.payload?.references ?? [];
|
|
2216
|
+
for (const r of list) refs.set(r.id, r.value);
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
parsed.push(obj);
|
|
2220
|
+
}
|
|
2221
|
+
const out = [];
|
|
2222
|
+
for (const obj of parsed) {
|
|
2223
|
+
const payload = detokenize(obj.payload, refs);
|
|
2224
|
+
if (typeof obj.m === "string") {
|
|
2225
|
+
const model = models.get(obj.m);
|
|
2226
|
+
if (!model) continue;
|
|
2227
|
+
out.push({
|
|
2228
|
+
factId: String(obj.factId),
|
|
2229
|
+
factType: model.factType,
|
|
2230
|
+
language: model.language,
|
|
2231
|
+
scanner: model.scanner,
|
|
2232
|
+
scannerVersion: model.scannerVersion,
|
|
2233
|
+
emittedAt: "",
|
|
2234
|
+
payload
|
|
2235
|
+
});
|
|
2236
|
+
} else if (typeof obj.factType === "string" && typeof obj.factId === "string") {
|
|
2237
|
+
out.push({
|
|
2238
|
+
factId: obj.factId,
|
|
2239
|
+
factType: obj.factType,
|
|
2240
|
+
language: String(obj.language ?? ""),
|
|
2241
|
+
scanner: String(obj.scanner ?? ""),
|
|
2242
|
+
scannerVersion: String(obj.scannerVersion ?? ""),
|
|
2243
|
+
emittedAt: String(obj.emittedAt ?? ""),
|
|
2244
|
+
payload
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
return out;
|
|
2249
|
+
}
|
|
2250
|
+
var str = (v) => typeof v === "string" && v ? v : void 0;
|
|
2251
|
+
var pick = (p, ...keys) => {
|
|
2252
|
+
for (const k of keys) {
|
|
2253
|
+
const s = str(p[k]);
|
|
2254
|
+
if (s !== void 0) return s;
|
|
2255
|
+
}
|
|
2256
|
+
return void 0;
|
|
2257
|
+
};
|
|
2258
|
+
function buildIndex(facts) {
|
|
2259
|
+
const idx = {
|
|
2260
|
+
byType: /* @__PURE__ */ new Map(),
|
|
2261
|
+
entityById: /* @__PURE__ */ new Map(),
|
|
2262
|
+
fieldsByEntity: /* @__PURE__ */ new Map(),
|
|
2263
|
+
paramsByRoute: /* @__PURE__ */ new Map(),
|
|
2264
|
+
daoByHandler: /* @__PURE__ */ new Map(),
|
|
2265
|
+
entityIdByName: /* @__PURE__ */ new Map()
|
|
2266
|
+
};
|
|
2267
|
+
for (const f of facts) {
|
|
2268
|
+
(idx.byType.get(f.factType) ?? idx.byType.set(f.factType, []).get(f.factType)).push(f);
|
|
2269
|
+
const p = f.payload;
|
|
2270
|
+
switch (f.factType) {
|
|
2271
|
+
case "EntityDeclared": {
|
|
2272
|
+
const id = pick(p, "entityId");
|
|
2273
|
+
const name = pick(p, "name", "shortName");
|
|
2274
|
+
if (id) idx.entityById.set(id, f);
|
|
2275
|
+
if (id && name) idx.entityIdByName.set(name.toLowerCase(), id);
|
|
2276
|
+
break;
|
|
2277
|
+
}
|
|
2278
|
+
case "FieldDeclared": {
|
|
2279
|
+
const id = pick(p, "entityId");
|
|
2280
|
+
if (id) (idx.fieldsByEntity.get(id) ?? idx.fieldsByEntity.set(id, []).get(id)).push(f);
|
|
2281
|
+
break;
|
|
2282
|
+
}
|
|
2283
|
+
case "ParameterBinding": {
|
|
2284
|
+
const rid = pick(p, "routeId");
|
|
2285
|
+
if (rid) (idx.paramsByRoute.get(rid) ?? idx.paramsByRoute.set(rid, []).get(rid)).push(f);
|
|
2286
|
+
break;
|
|
2287
|
+
}
|
|
2288
|
+
case "DataAccessOperation": {
|
|
2289
|
+
const caller = pick(p, "callerSymbol");
|
|
2290
|
+
if (caller) idx.daoByHandler.set(caller, f);
|
|
2291
|
+
break;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
return idx;
|
|
2296
|
+
}
|
|
2297
|
+
function canonicalize(value) {
|
|
2298
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
2299
|
+
if (Array.isArray(value)) return "[" + value.map(canonicalize).join(",") + "]";
|
|
2300
|
+
const obj = value;
|
|
2301
|
+
const keys = Object.keys(obj).sort();
|
|
2302
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalize(obj[k])).join(",") + "}";
|
|
2303
|
+
}
|
|
2304
|
+
function sha16(seed) {
|
|
2305
|
+
return createHash2("sha256").update(seed).digest("hex").slice(0, 16);
|
|
2306
|
+
}
|
|
2307
|
+
function factId(factType, seed) {
|
|
2308
|
+
return `hcs:${factType}:${sha16(seed)}`;
|
|
2309
|
+
}
|
|
2310
|
+
function makeAssertion(kind, subjectFactIds, structuredPredicate, predicate, strength, label, surfaceRefs, expr) {
|
|
2311
|
+
const subjects = [...new Set(subjectFactIds)].sort();
|
|
2312
|
+
const assertionId = `ba:${kind}:${sha16(canonicalize({ kind, subjectFactIds: subjects, predicate: structuredPredicate }))}`;
|
|
2313
|
+
const payload = { assertionId, kind, subjectFactIds: subjects, predicate, derivedBy: "static", corpusRef: null, strength, label };
|
|
2314
|
+
if (expr) payload.expr = expr;
|
|
2315
|
+
return { payload, surfaceRefs };
|
|
2316
|
+
}
|
|
2317
|
+
function statusForException(ex) {
|
|
2318
|
+
const e = ex.toLowerCase();
|
|
2319
|
+
if (e.includes("notfound")) return 404;
|
|
2320
|
+
if (e.includes("unauthor") || e.includes("forbidden")) return 403;
|
|
2321
|
+
if (e.includes("unauthenticated")) return 401;
|
|
2322
|
+
if (e.includes("validation") || e.includes("argument") || e.includes("badrequest")) return 400;
|
|
2323
|
+
if (e.includes("conflict")) return 409;
|
|
2324
|
+
return 500;
|
|
2325
|
+
}
|
|
2326
|
+
var STATIC_CAP = 0.85;
|
|
2327
|
+
var INTEGRATION_STRENGTH = 0.55;
|
|
2328
|
+
var EVENT_STRENGTH = 0.5;
|
|
2329
|
+
function contractStrength(opts) {
|
|
2330
|
+
let s = 0.35;
|
|
2331
|
+
if (opts.hasParams) s += 0.1;
|
|
2332
|
+
if (opts.hasResponseEntity) s += 0.2;
|
|
2333
|
+
if (opts.hasErrorBranches) s += 0.15;
|
|
2334
|
+
return Math.min(STATIC_CAP, s);
|
|
2335
|
+
}
|
|
2336
|
+
function invariantStrength(fidelity) {
|
|
2337
|
+
return Math.min(STATIC_CAP, 0.5 + 0.3 * fidelity);
|
|
2338
|
+
}
|
|
2339
|
+
function synthesizeApiContracts(idx) {
|
|
2340
|
+
const routes = idx.byType.get("RouteDeclared") ?? [];
|
|
2341
|
+
const out = [];
|
|
2342
|
+
for (const route of routes) {
|
|
2343
|
+
const p = route.payload;
|
|
2344
|
+
const method = pick(p, "method", "httpMethod") ?? "ANY";
|
|
2345
|
+
const template = pick(p, "template", "routePath") ?? "";
|
|
2346
|
+
const routeId = pick(p, "routeId") ?? `${method}:${template}`;
|
|
2347
|
+
const handler = pick(p, "handlerSymbol", "handlerName");
|
|
2348
|
+
const subjects = [route.factId];
|
|
2349
|
+
const surfaceRefs = [route.factId];
|
|
2350
|
+
const params = (idx.paramsByRoute.get(routeId) ?? []).map((pb) => {
|
|
2351
|
+
subjects.push(pb.factId);
|
|
2352
|
+
return {
|
|
2353
|
+
name: pick(pb.payload, "paramName", "parameterName") ?? "",
|
|
2354
|
+
source: pick(pb.payload, "source", "bindingSource") ?? "unknown",
|
|
2355
|
+
type: pick(pb.payload, "typeName", "parameterType") ?? "unknown",
|
|
2356
|
+
required: pb.payload.required === true || pb.payload.isRequired === true
|
|
2357
|
+
};
|
|
2358
|
+
});
|
|
2359
|
+
let entityName;
|
|
2360
|
+
let fields = [];
|
|
2361
|
+
const errors = [];
|
|
2362
|
+
const dao = handler ? idx.daoByHandler.get(handler) : void 0;
|
|
2363
|
+
if (dao) {
|
|
2364
|
+
subjects.push(dao.factId);
|
|
2365
|
+
surfaceRefs.push(dao.factId);
|
|
2366
|
+
entityName = pick(dao.payload, "targetEntity");
|
|
2367
|
+
const steps = Array.isArray(dao.payload.steps) ? dao.payload.steps : [];
|
|
2368
|
+
for (const stepEntry of steps) {
|
|
2369
|
+
const kind = pick(stepEntry, "stepKind", "kind");
|
|
2370
|
+
const throwsOn = pick(stepEntry, "throwsOnFail");
|
|
2371
|
+
if (kind === "guard" && throwsOn) errors.push({ status: statusForException(throwsOn), when: throwsOn });
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
if (entityName) {
|
|
2375
|
+
const eid = idx.entityIdByName.get(entityName.toLowerCase());
|
|
2376
|
+
const entity = eid ? idx.entityById.get(eid) : void 0;
|
|
2377
|
+
if (entity) subjects.push(entity.factId);
|
|
2378
|
+
if (eid) {
|
|
2379
|
+
fields = (idx.fieldsByEntity.get(eid) ?? []).map((fd) => ({
|
|
2380
|
+
name: pick(fd.payload, "fieldName") ?? "",
|
|
2381
|
+
type: pick(fd.payload, "typeName", "hcsType") ?? "unknown"
|
|
2382
|
+
}));
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
const structured = { method, template, params, response: { status: 200, entity: entityName ?? null, fields }, errors };
|
|
2386
|
+
const predicate = renderContract(method, template, params, entityName, fields, errors);
|
|
2387
|
+
const label = `${method} ${template} contract`;
|
|
2388
|
+
const strength = contractStrength({ hasParams: params.length > 0, hasResponseEntity: !!entityName, hasErrorBranches: errors.length > 0 });
|
|
2389
|
+
out.push(makeAssertion("contract", subjects, structured, predicate, strength, label, surfaceRefs));
|
|
2390
|
+
}
|
|
2391
|
+
return out;
|
|
2392
|
+
}
|
|
2393
|
+
function renderContract(method, template, params, entity, fields, errors) {
|
|
2394
|
+
const paramStr = params.map((p) => `${p.name}:${p.type}`).join(", ");
|
|
2395
|
+
const fieldStr = fields.map((f) => `${f.name}:${f.type}`).join(", ");
|
|
2396
|
+
const ok = entity ? `200 ${entity}{ ${fieldStr} }` : "200";
|
|
2397
|
+
const errStr = errors.map((e) => `${e.status} when ${e.when}`).join(" | ");
|
|
2398
|
+
return `${method} ${template}(${paramStr}) -> ${ok}${errStr ? " | " + errStr : ""}`;
|
|
2399
|
+
}
|
|
2400
|
+
function synthesizeIntegrationContracts(idx) {
|
|
2401
|
+
const calls = idx.byType.get("ExternalServiceCall") ?? [];
|
|
2402
|
+
const out = [];
|
|
2403
|
+
for (const call of calls) {
|
|
2404
|
+
const p = call.payload;
|
|
2405
|
+
const target = pick(p, "targetUrl", "serviceRef", "urlOrPattern") ?? "unknown";
|
|
2406
|
+
const method = pick(p, "httpMethod", "method") ?? "ANY";
|
|
2407
|
+
const client = pick(p, "clientKind", "protocol") ?? "unknown";
|
|
2408
|
+
const structured = { type: "integration", method, target, client };
|
|
2409
|
+
const predicate = `calls ${method} ${target} via ${client}`;
|
|
2410
|
+
out.push(makeAssertion("contract", [call.factId], structured, predicate, INTEGRATION_STRENGTH, `outbound ${method} ${target}`, [call.factId]));
|
|
2411
|
+
}
|
|
2412
|
+
return out;
|
|
2413
|
+
}
|
|
2414
|
+
function synthesizeEventContracts(idx) {
|
|
2415
|
+
const out = [];
|
|
2416
|
+
for (const ev of idx.byType.get("EventEmitted") ?? []) {
|
|
2417
|
+
const p = ev.payload;
|
|
2418
|
+
const name = pick(p, "eventName") ?? "unknown";
|
|
2419
|
+
const kind = pick(p, "emitterKind") ?? "event";
|
|
2420
|
+
const payloadType = pick(p, "payloadType") ?? null;
|
|
2421
|
+
const structured = { type: "event", direction: "emit", name, kind, payloadType };
|
|
2422
|
+
const predicate = `emits ${name}${payloadType ? ` (${payloadType})` : ""} via ${kind}`;
|
|
2423
|
+
out.push(makeAssertion("contract", [ev.factId], structured, predicate, EVENT_STRENGTH, `emits ${name}`, [ev.factId]));
|
|
2424
|
+
}
|
|
2425
|
+
for (const ev of idx.byType.get("EventConsumed") ?? []) {
|
|
2426
|
+
const p = ev.payload;
|
|
2427
|
+
const name = pick(p, "eventName") ?? "unknown";
|
|
2428
|
+
const kind = pick(p, "consumerKind") ?? "handler";
|
|
2429
|
+
const payloadType = pick(p, "payloadType") ?? null;
|
|
2430
|
+
const structured = { type: "event", direction: "consume", name, kind, payloadType };
|
|
2431
|
+
const predicate = `consumes ${name}${payloadType ? ` (${payloadType})` : ""} via ${kind}`;
|
|
2432
|
+
out.push(makeAssertion("contract", [ev.factId], structured, predicate, EVENT_STRENGTH, `consumes ${name}`, [ev.factId]));
|
|
2433
|
+
}
|
|
2434
|
+
return out;
|
|
2435
|
+
}
|
|
2436
|
+
function validationRuleToExpr(field, fieldType, rule, arg) {
|
|
2437
|
+
const t = new HxlTable();
|
|
2438
|
+
const ref = t.intern({ k: "Ref", name: field });
|
|
2439
|
+
const len = () => t.intern({ k: "Member", obj: ref, name: "length" });
|
|
2440
|
+
const num = (s) => {
|
|
2441
|
+
const n = Number(s);
|
|
2442
|
+
return t.intern({ k: "Lit", value: Number.isFinite(n) ? n : 0, type: Number.isInteger(n) ? "int" : "float" });
|
|
2443
|
+
};
|
|
2444
|
+
const zero = () => t.intern({ k: "Lit", value: 0, type: "int" });
|
|
2445
|
+
const isString = fieldType === "string";
|
|
2446
|
+
const bin = (op, l, r) => t.intern({ k: "Binary", op, l, r });
|
|
2447
|
+
const format = (name) => t.intern({ k: "Call", callee: t.intern({ k: "Member", obj: ref, name: "matches" }), args: [t.intern({ k: "Lit", value: name, type: "string" })] });
|
|
2448
|
+
let root;
|
|
2449
|
+
switch (rule) {
|
|
2450
|
+
case "min":
|
|
2451
|
+
root = bin("ge", isString ? len() : ref, num(arg));
|
|
2452
|
+
break;
|
|
2453
|
+
case "max":
|
|
2454
|
+
root = bin("le", isString ? len() : ref, num(arg));
|
|
2455
|
+
break;
|
|
2456
|
+
case "gt":
|
|
2457
|
+
root = bin("gt", ref, num(arg));
|
|
2458
|
+
break;
|
|
2459
|
+
case "gte":
|
|
2460
|
+
root = bin("ge", ref, num(arg));
|
|
2461
|
+
break;
|
|
2462
|
+
case "lt":
|
|
2463
|
+
root = bin("lt", ref, num(arg));
|
|
2464
|
+
break;
|
|
2465
|
+
case "lte":
|
|
2466
|
+
root = bin("le", ref, num(arg));
|
|
2467
|
+
break;
|
|
2468
|
+
case "positive":
|
|
2469
|
+
root = bin("gt", ref, zero());
|
|
2470
|
+
break;
|
|
2471
|
+
case "nonnegative":
|
|
2472
|
+
root = bin("ge", ref, zero());
|
|
2473
|
+
break;
|
|
2474
|
+
case "length":
|
|
2475
|
+
root = bin("eq", len(), num(arg));
|
|
2476
|
+
break;
|
|
2477
|
+
case "nonempty":
|
|
2478
|
+
root = bin("gt", len(), zero());
|
|
2479
|
+
break;
|
|
2480
|
+
case "email":
|
|
2481
|
+
case "url":
|
|
2482
|
+
case "uuid":
|
|
2483
|
+
case "datetime":
|
|
2484
|
+
root = format(rule);
|
|
2485
|
+
break;
|
|
2486
|
+
case "regex":
|
|
2487
|
+
root = format(arg ?? "regex");
|
|
2488
|
+
break;
|
|
2489
|
+
default:
|
|
2490
|
+
return null;
|
|
2491
|
+
}
|
|
2492
|
+
return { table: t, root };
|
|
2493
|
+
}
|
|
2494
|
+
function schemaCandidates(typeName) {
|
|
2495
|
+
const base = typeName.replace(/<.*>/, "").replace(/\[\]$/, "").trim();
|
|
2496
|
+
const stripped = base.replace(/(Dto|Input|Request|Payload|Body)$/i, "");
|
|
2497
|
+
return [base, stripped, `${stripped}Schema`, `${base}Schema`];
|
|
2498
|
+
}
|
|
2499
|
+
function buildSchemaRouteLinks(idx) {
|
|
2500
|
+
const links = /* @__PURE__ */ new Map();
|
|
2501
|
+
const schemaNames = /* @__PURE__ */ new Set();
|
|
2502
|
+
for (const vr of idx.byType.get("ValidationRuleObserved") ?? []) {
|
|
2503
|
+
const s = pick(vr.payload, "schemaName");
|
|
2504
|
+
if (s) schemaNames.add(s);
|
|
2505
|
+
}
|
|
2506
|
+
if (schemaNames.size === 0) return links;
|
|
2507
|
+
for (const route of idx.byType.get("RouteDeclared") ?? []) {
|
|
2508
|
+
const rid = pick(route.payload, "routeId") ?? "";
|
|
2509
|
+
const body = (idx.paramsByRoute.get(rid) ?? []).find((p) => pick(p.payload, "source", "bindingSource") === "body");
|
|
2510
|
+
if (!body) continue;
|
|
2511
|
+
const ptype = pick(body.payload, "typeName", "parameterType");
|
|
2512
|
+
if (!ptype || ptype.startsWith("Record") || ptype === "any" || ptype === "unknown") continue;
|
|
2513
|
+
const cands = schemaCandidates(ptype);
|
|
2514
|
+
for (const sn of schemaNames) {
|
|
2515
|
+
const snBase = sn.replace(/Schema\d*$/, "").replace(/\d+$/, "");
|
|
2516
|
+
if (cands.includes(sn) || cands.includes(snBase) || cands.includes(`${snBase}Schema`)) {
|
|
2517
|
+
links.set(sn, route.factId);
|
|
2518
|
+
break;
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
return links;
|
|
2523
|
+
}
|
|
2524
|
+
function synthesizeValidationInvariants(idx, schemaRoute) {
|
|
2525
|
+
const out = [];
|
|
2526
|
+
for (const vr of idx.byType.get("ValidationRuleObserved") ?? []) {
|
|
2527
|
+
const p = vr.payload;
|
|
2528
|
+
const field = pick(p, "fieldName") ?? "field";
|
|
2529
|
+
const fieldType = pick(p, "fieldType") ?? "unknown";
|
|
2530
|
+
const rule = pick(p, "rule") ?? "";
|
|
2531
|
+
const arg = pick(p, "arg") ?? null;
|
|
2532
|
+
const schema = pick(p, "schemaName") ?? "";
|
|
2533
|
+
const built = validationRuleToExpr(`${schema}.${field}`, fieldType, rule, arg);
|
|
2534
|
+
if (!built) continue;
|
|
2535
|
+
const rendered = renderNode(built.table, built.root);
|
|
2536
|
+
const structured = { type: "validation", hxl: built.table.nodes, root: built.root };
|
|
2537
|
+
const routeRef = schemaRoute.get(schema);
|
|
2538
|
+
out.push(makeAssertion("invariant", [vr.factId], structured, rendered, invariantStrength(built.table.fidelity()), `rule ${rendered}`, routeRef ? [routeRef] : [], { nodes: built.table.nodes, root: built.root }));
|
|
2539
|
+
}
|
|
2540
|
+
return out;
|
|
2541
|
+
}
|
|
2542
|
+
function buildRouteIntervals(idx) {
|
|
2543
|
+
const map = /* @__PURE__ */ new Map();
|
|
2544
|
+
for (const route of idx.byType.get("RouteDeclared") ?? []) {
|
|
2545
|
+
const file = pick(route.payload, "filePath");
|
|
2546
|
+
const start = route.payload.handlerStartLine;
|
|
2547
|
+
const end = route.payload.handlerEndLine;
|
|
2548
|
+
if (!file || typeof start !== "number" || typeof end !== "number") continue;
|
|
2549
|
+
(map.get(file) ?? map.set(file, []).get(file)).push({ start, end, factId: route.factId });
|
|
2550
|
+
}
|
|
2551
|
+
return map;
|
|
2552
|
+
}
|
|
2553
|
+
function routeAt(intervals, file, line) {
|
|
2554
|
+
if (!file || typeof line !== "number") return void 0;
|
|
2555
|
+
for (const iv of intervals.get(file) ?? []) if (line >= iv.start && line <= iv.end) return iv.factId;
|
|
2556
|
+
return void 0;
|
|
2557
|
+
}
|
|
2558
|
+
function synthesizeMethodLogicInvariants(idx) {
|
|
2559
|
+
const out = [];
|
|
2560
|
+
const surfaceByContainer = /* @__PURE__ */ new Map();
|
|
2561
|
+
for (const dao of idx.byType.get("DataAccessOperation") ?? []) {
|
|
2562
|
+
const c = pick(dao.payload, "callerSymbol");
|
|
2563
|
+
if (c) surfaceByContainer.set(c, dao.factId);
|
|
2564
|
+
}
|
|
2565
|
+
for (const route of idx.byType.get("RouteDeclared") ?? []) {
|
|
2566
|
+
const h = pick(route.payload, "handlerSymbol", "handlerName");
|
|
2567
|
+
if (h) surfaceByContainer.set(h, route.factId);
|
|
2568
|
+
}
|
|
2569
|
+
const callsFrom = /* @__PURE__ */ new Map();
|
|
2570
|
+
for (const co of idx.byType.get("CallObserved") ?? []) {
|
|
2571
|
+
const caller = pick(co.payload, "callerId", "callerSymbol");
|
|
2572
|
+
const callee = pick(co.payload, "calleeId", "calleeSymbol");
|
|
2573
|
+
if (!caller || !callee) continue;
|
|
2574
|
+
const short = callee.split(".").pop();
|
|
2575
|
+
(callsFrom.get(caller) ?? callsFrom.set(caller, /* @__PURE__ */ new Set()).get(caller)).add(short);
|
|
2576
|
+
}
|
|
2577
|
+
const routeIntervals = buildRouteIntervals(idx);
|
|
2578
|
+
for (const m of idx.byType.get("MethodLogicObserved") ?? []) {
|
|
2579
|
+
const expr = pick(m.payload, "expression");
|
|
2580
|
+
if (!expr) continue;
|
|
2581
|
+
const container = pick(m.payload, "containerSymbol") ?? "<m>";
|
|
2582
|
+
const kind = pick(m.payload, "kind") ?? "guard";
|
|
2583
|
+
const { table, root } = lowerGuard(expr);
|
|
2584
|
+
if (table.fidelity() === 0) continue;
|
|
2585
|
+
const rendered = renderNode(table, root);
|
|
2586
|
+
let surfaceRef = surfaceByContainer.get(container);
|
|
2587
|
+
if (!surfaceRef) {
|
|
2588
|
+
const shortName = container.split(".").pop();
|
|
2589
|
+
for (const [cont, fid] of surfaceByContainer) {
|
|
2590
|
+
if (callsFrom.get(cont)?.has(shortName)) {
|
|
2591
|
+
surfaceRef = fid;
|
|
2592
|
+
break;
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
if (!surfaceRef) surfaceRef = routeAt(routeIntervals, pick(m.payload, "filePath"), m.payload.line);
|
|
2597
|
+
const structured = { type: "methodlogic", kind, hxl: table.nodes, root };
|
|
2598
|
+
out.push(makeAssertion("invariant", [m.factId], structured, rendered, invariantStrength(table.fidelity()), `${kind} ${rendered}`, surfaceRef ? [surfaceRef] : [], { nodes: table.nodes, root }));
|
|
2599
|
+
}
|
|
2600
|
+
return out;
|
|
2601
|
+
}
|
|
2602
|
+
function synthesizeEntityConstraints(idx) {
|
|
2603
|
+
const out = [];
|
|
2604
|
+
for (const fd of idx.byType.get("FieldDeclared") ?? []) {
|
|
2605
|
+
const field = pick(fd.payload, "fieldName");
|
|
2606
|
+
if (!field) continue;
|
|
2607
|
+
const base = `${pick(fd.payload, "entityId") ?? ""}.${field}`;
|
|
2608
|
+
if (fd.payload.isNullable === false) {
|
|
2609
|
+
const t = new HxlTable();
|
|
2610
|
+
const root = t.intern({ k: "Binary", op: "ne", l: t.intern({ k: "Ref", name: base }), r: t.intern({ k: "Lit", value: null, type: "null" }) });
|
|
2611
|
+
out.push(makeAssertion("invariant", [fd.factId], { type: "entity-constraint", hxl: t.nodes, root }, renderNode(t, root), invariantStrength(1), `nonnull ${base}`, [], { nodes: t.nodes, root }));
|
|
2612
|
+
}
|
|
2613
|
+
if (typeof fd.payload.maxLength === "number") {
|
|
2614
|
+
const t = new HxlTable();
|
|
2615
|
+
const ref = t.intern({ k: "Ref", name: base });
|
|
2616
|
+
const root = t.intern({ k: "Binary", op: "le", l: t.intern({ k: "Member", obj: ref, name: "length" }), r: t.intern({ k: "Lit", value: fd.payload.maxLength, type: "int" }) });
|
|
2617
|
+
out.push(makeAssertion("invariant", [fd.factId], { type: "entity-constraint", hxl: t.nodes, root }, renderNode(t, root), invariantStrength(1), `maxlen ${base}`, [], { nodes: t.nodes, root }));
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
return out;
|
|
2621
|
+
}
|
|
2622
|
+
function synthesizeDataOpContracts(idx) {
|
|
2623
|
+
const out = [];
|
|
2624
|
+
for (const dao of idx.byType.get("DataAccessOperation") ?? []) {
|
|
2625
|
+
const p = dao.payload;
|
|
2626
|
+
const kind = pick(p, "semanticKind") ?? "unknown";
|
|
2627
|
+
const entity = pick(p, "targetEntity");
|
|
2628
|
+
const returnType = pick(p, "returnType");
|
|
2629
|
+
const steps = Array.isArray(p.steps) ? p.steps : [];
|
|
2630
|
+
const collection = p.returnsCollection === true;
|
|
2631
|
+
let strength = 0.4;
|
|
2632
|
+
if (entity) strength += 0.15;
|
|
2633
|
+
if (steps.length > 0) strength += 0.1;
|
|
2634
|
+
if (returnType) strength += 0.1;
|
|
2635
|
+
strength = Math.min(STATIC_CAP, strength);
|
|
2636
|
+
const structured = { type: "dataop", kind, entity: entity ?? null, returnType: returnType ?? null, collection };
|
|
2637
|
+
const predicate = `${kind} ${entity ?? "?"}${collection ? "[]" : ""}`;
|
|
2638
|
+
out.push(makeAssertion("contract", [dao.factId], structured, predicate, strength, `data ${predicate}`, [dao.factId]));
|
|
2639
|
+
}
|
|
2640
|
+
return out;
|
|
2641
|
+
}
|
|
2642
|
+
function synthesizeInvariants(idx) {
|
|
2643
|
+
const out = [];
|
|
2644
|
+
for (const dao of idx.byType.get("DataAccessOperation") ?? []) {
|
|
2645
|
+
const steps = Array.isArray(dao.payload.steps) ? dao.payload.steps : [];
|
|
2646
|
+
for (const step of steps) {
|
|
2647
|
+
const expr = pick(step, "predicateExpression");
|
|
2648
|
+
if (!expr) continue;
|
|
2649
|
+
const { table, root } = lowerGuard(expr);
|
|
2650
|
+
const fidelity = table.fidelity();
|
|
2651
|
+
if (fidelity === 0) continue;
|
|
2652
|
+
const rendered = renderNode(table, root);
|
|
2653
|
+
const structured = { type: "invariant", hxl: table.nodes, root };
|
|
2654
|
+
out.push(makeAssertion("invariant", [dao.factId], structured, rendered, invariantStrength(fidelity), `invariant ${rendered}`, [dao.factId], { nodes: table.nodes, root }));
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
return out;
|
|
2658
|
+
}
|
|
2659
|
+
function deriveBehaviouralFacts(facts, opts) {
|
|
2660
|
+
const emittedAt = opts.emittedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2661
|
+
const idx = buildIndex(facts);
|
|
2662
|
+
const derived = [
|
|
2663
|
+
...synthesizeApiContracts(idx),
|
|
2664
|
+
...synthesizeIntegrationContracts(idx),
|
|
2665
|
+
...synthesizeEventContracts(idx),
|
|
2666
|
+
...synthesizeDataOpContracts(idx),
|
|
2667
|
+
...synthesizeInvariants(idx),
|
|
2668
|
+
...synthesizeValidationInvariants(idx, buildSchemaRouteLinks(idx)),
|
|
2669
|
+
...synthesizeMethodLogicInvariants(idx),
|
|
2670
|
+
...synthesizeEntityConstraints(idx)
|
|
2671
|
+
];
|
|
2672
|
+
const seenAssertion = /* @__PURE__ */ new Set();
|
|
2673
|
+
const unique = [];
|
|
2674
|
+
for (const d of derived) {
|
|
2675
|
+
if (seenAssertion.has(d.payload.assertionId)) continue;
|
|
2676
|
+
seenAssertion.add(d.payload.assertionId);
|
|
2677
|
+
unique.push(d);
|
|
2678
|
+
}
|
|
2679
|
+
unique.sort((a, b) => a.payload.assertionId.localeCompare(b.payload.assertionId));
|
|
2680
|
+
const envelopes = [];
|
|
2681
|
+
const assertionFactIds = [];
|
|
2682
|
+
const byKind = {};
|
|
2683
|
+
const assertedSurface = /* @__PURE__ */ new Map();
|
|
2684
|
+
for (const d of unique) {
|
|
2685
|
+
const fid = factId("BehaviouralAssertion", d.payload.assertionId);
|
|
2686
|
+
assertionFactIds.push(fid);
|
|
2687
|
+
byKind[d.payload.kind] = (byKind[d.payload.kind] ?? 0) + 1;
|
|
2688
|
+
for (const ref of d.surfaceRefs) {
|
|
2689
|
+
assertedSurface.set(ref, Math.max(assertedSurface.get(ref) ?? 0, d.payload.strength));
|
|
2690
|
+
}
|
|
2691
|
+
envelopes.push({
|
|
2692
|
+
factId: fid,
|
|
2693
|
+
factType: "BehaviouralAssertion",
|
|
2694
|
+
language: "multi",
|
|
2695
|
+
scanner: "hcs-behavioural",
|
|
2696
|
+
scannerVersion: "0.3.0",
|
|
2697
|
+
emittedAt,
|
|
2698
|
+
payload: d.payload
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
const surfaceFactIds = [...new Set(facts.filter((f) => BEHAVIOUR_BEARING_TYPES.has(f.factType)).map((f) => f.factId))];
|
|
2702
|
+
const surfaceFacts = surfaceFactIds.length;
|
|
2703
|
+
const assertedFacts = surfaceFactIds.filter((id) => assertedSurface.has(id)).length;
|
|
2704
|
+
const strengthSum = surfaceFactIds.reduce((acc, id) => acc + (assertedSurface.get(id) ?? 0), 0);
|
|
2705
|
+
const behaviouralConfidence = surfaceFacts === 0 ? 0 : Math.min(100, Math.round(strengthSum / surfaceFacts * 100));
|
|
2706
|
+
const sortedAssertionIds = [...new Set(assertionFactIds)].sort();
|
|
2707
|
+
const specId = `spec:${sha16(canonicalize({ scanId: opts.scanId, assertionIds: sortedAssertionIds }))}`;
|
|
2708
|
+
const specFactId = factId("BehaviouralSpec", specId);
|
|
2709
|
+
const invariants = unique.filter((d) => d.payload.kind === "invariant");
|
|
2710
|
+
const logicFacts = invariants.length;
|
|
2711
|
+
let nodes = 0;
|
|
2712
|
+
let opaque = 0;
|
|
2713
|
+
for (const inv of invariants) {
|
|
2714
|
+
const t = inv.payload.expr?.nodes ?? [];
|
|
2715
|
+
nodes += t.length;
|
|
2716
|
+
opaque += t.filter((n) => n.k === "Opaque").length;
|
|
2717
|
+
}
|
|
2718
|
+
const logicCoverage = nodes === 0 ? 0 : Math.round((1 - opaque / nodes) * 100);
|
|
2719
|
+
envelopes.push({
|
|
2720
|
+
factId: specFactId,
|
|
2721
|
+
factType: "BehaviouralSpec",
|
|
2722
|
+
language: "multi",
|
|
2723
|
+
scanner: "hcs-behavioural",
|
|
2724
|
+
scannerVersion: "0.3.0",
|
|
2725
|
+
emittedAt,
|
|
2726
|
+
payload: {
|
|
2727
|
+
specId,
|
|
2728
|
+
scanId: opts.scanId,
|
|
2729
|
+
applicationId: opts.applicationId,
|
|
2730
|
+
assertionIds: sortedAssertionIds,
|
|
2731
|
+
coverage: { surfaceFacts, assertedFacts, byKind },
|
|
2732
|
+
behaviouralConfidence,
|
|
2733
|
+
logicFacts,
|
|
2734
|
+
logicCoverage
|
|
2735
|
+
}
|
|
2736
|
+
});
|
|
2737
|
+
return { envelopes, summary: { assertions: unique.length, surfaceFacts, assertedFacts, behaviouralConfidence } };
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
// src/commands/extract.ts
|
|
1888
2741
|
var EXIT_SUCCESS = 0;
|
|
1889
2742
|
var EXIT_SCHEMA_FAILURE = 1;
|
|
1890
2743
|
var EXIT_PARSE_FAILURE = 2;
|
|
@@ -2553,7 +3406,7 @@ var ProgressTracker = class {
|
|
|
2553
3406
|
}
|
|
2554
3407
|
}
|
|
2555
3408
|
};
|
|
2556
|
-
var extractCommand = new Command8("extract").description("Analyze source code and emit validated HCS facts (NDJSON)").argument("[path]", "Path to source directory", ".").option("-o, --out <file>", "Write NDJSON to file (default: stdout)").option("--language <langs>", "Comma-separated languages to analyze (default: auto-detect)").option("--include-tests", "Include test files in analysis").option("--push", "Stream validated facts to dashboard API").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--concurrency <n>", "Number of parallel file workers").option("--timeout-mins <mins>", "Total analysis timeout in minutes", "60").option("--feedback <file>", "Load NDJSON diff artifact for refinement").option("--verbose", "Print worker stderr and summary statistics").action(async (targetPath, opts) => {
|
|
3409
|
+
var extractCommand = new Command8("extract").description("Analyze source code and emit validated HCS facts (NDJSON)").argument("[path]", "Path to source directory", ".").option("-o, --out <file>", "Write NDJSON to file (default: stdout)").option("--language <langs>", "Comma-separated languages to analyze (default: auto-detect)").option("--include-tests", "Include test files in analysis").option("--push", "Stream validated facts to dashboard API").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--concurrency <n>", "Number of parallel file workers").option("--timeout-mins <mins>", "Total analysis timeout in minutes", "60").option("--feedback <file>", "Load NDJSON diff artifact for refinement").option("--derive", "Derive behavioural spec facts (BehaviouralAssertion/BehaviouralSpec) from extracted facts").option("--scan-id <id>", 'Scan id for the derived BehaviouralSpec (default: "local")').option("--application-id <id>", "Application id for the derived BehaviouralSpec (default: directory name)").option("--verbose", "Print worker stderr and summary statistics").action(async (targetPath, opts) => {
|
|
2557
3410
|
const rootDir = path9.resolve(targetPath);
|
|
2558
3411
|
if (!await pathExists(rootDir)) {
|
|
2559
3412
|
process.stderr.write(chalk9.red(`Path does not exist: ${rootDir}
|
|
@@ -2678,6 +3531,7 @@ var extractCommand = new Command8("extract").description("Analyze source code an
|
|
|
2678
3531
|
const allPreamble = [];
|
|
2679
3532
|
const allFacts = [];
|
|
2680
3533
|
const allErrors = [];
|
|
3534
|
+
const derivedInputs = [];
|
|
2681
3535
|
let hasSchemaFailure = false;
|
|
2682
3536
|
let hasParseFailure = false;
|
|
2683
3537
|
let hasTimeout = false;
|
|
@@ -2703,6 +3557,12 @@ var extractCommand = new Command8("extract").description("Analyze source code an
|
|
|
2703
3557
|
}
|
|
2704
3558
|
const { preamble, facts } = splitHcsOutput(result.facts);
|
|
2705
3559
|
allPreamble.push(...preamble);
|
|
3560
|
+
if (opts.derive) {
|
|
3561
|
+
try {
|
|
3562
|
+
derivedInputs.push(...decompressFacts([...preamble, ...facts]));
|
|
3563
|
+
} catch {
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
2706
3566
|
let langFactCount = 0;
|
|
2707
3567
|
for (const line of [...preamble, ...facts]) {
|
|
2708
3568
|
const validation = validateFactLine(line);
|
|
@@ -2734,6 +3594,24 @@ var extractCommand = new Command8("extract").description("Analyze source code an
|
|
|
2734
3594
|
await Promise.all(workerPromises);
|
|
2735
3595
|
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
2736
3596
|
progress.finish(elapsed);
|
|
3597
|
+
if (opts.derive) {
|
|
3598
|
+
const scanId = opts.scanId || "local";
|
|
3599
|
+
const applicationId = opts.applicationId || path9.basename(rootDir);
|
|
3600
|
+
const { envelopes, summary } = deriveBehaviouralFacts(derivedInputs, { scanId, applicationId });
|
|
3601
|
+
for (const env of envelopes) {
|
|
3602
|
+
const line = JSON.stringify(env);
|
|
3603
|
+
if (validateFactLine(line).valid) {
|
|
3604
|
+
allFacts.push(line);
|
|
3605
|
+
} else {
|
|
3606
|
+
hasSchemaFailure = true;
|
|
3607
|
+
allErrors.push("[behavioural] derived fact failed schema validation");
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
process.stderr.write(chalk9.dim(
|
|
3611
|
+
`Derived ${summary.assertions} behavioural assertion(s) \xB7 confidence ${summary.behaviouralConfidence} (${summary.assertedFacts}/${summary.surfaceFacts} surface facts)
|
|
3612
|
+
`
|
|
3613
|
+
));
|
|
3614
|
+
}
|
|
2737
3615
|
allFacts.sort();
|
|
2738
3616
|
const allLines = [...allPreamble, ...allFacts];
|
|
2739
3617
|
const ndjsonOutput = allLines.join("\n") + (allLines.length > 0 ? "\n" : "");
|