@knolo/cli 5.5.1 → 6.1.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/README.md +11 -2
- package/bin/compose.mjs +739 -0
- package/bin/kar-benchmark.mjs +323 -0
- package/bin/kar-graph.mjs +469 -0
- package/bin/kar-limits.mjs +36 -0
- package/bin/kar-produce.mjs +299 -0
- package/bin/kar.mjs +528 -0
- package/bin/knolo.mjs +35 -0
- package/package.json +4 -4
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/Cargo.toml +1 -1
- package/templates/icp-knowledge-canister/canisters/knolo-icp-canister/Cargo.lock +2 -2
- package/templates/icp-knowledge-canister/canisters/knolo-icp-canister/Cargo.toml +2 -2
package/bin/kar.mjs
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { KAR_CLI_MAX_IMAGE_BYTES, readBounded, readBoundedJson, readBoundedText } from './kar-limits.mjs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
const USAGE = `Usage: knolo kar <evaluate|verify|explain|inspect|graph|package|produce|domain|dossier|coverage|benchmark|demo|conformance> [options]
|
|
10
|
+
|
|
11
|
+
Knolo Adversarial Retrieval over a Committed Evidence Graph. Semantics are kar-1-research-1.
|
|
12
|
+
This command does not change V4 query, BM25, or MMR.
|
|
13
|
+
CEG authoring compiles source into the frozen sidecar. It does not change retrieval.
|
|
14
|
+
Producers propose CEG Source. They do not write committed graph bytes.
|
|
15
|
+
Domain Pack relation rules are review-required until a human gold gate says otherwise.
|
|
16
|
+
|
|
17
|
+
knolo kar evaluate --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..."
|
|
18
|
+
knolo kar verify --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..." --result result.json
|
|
19
|
+
knolo kar explain --result result.json [--image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..."]
|
|
20
|
+
knolo kar inspect --image knowledge.knolo --graph knowledge.kar.json [--plan plan.json] [--query "..."]
|
|
21
|
+
knolo kar graph build --image knowledge.knolo --source knowledge.ceg.yaml --out knowledge.kar.json
|
|
22
|
+
knolo kar graph lint|review|inspect|diff|validate|check|rebuild
|
|
23
|
+
knolo kar graph import claim-graph|json
|
|
24
|
+
knolo kar package --image knowledge.knolo --graph knowledge.kar.json --out dist/my-knowledge-kar/
|
|
25
|
+
knolo kar package verify dist/my-knowledge-kar/
|
|
26
|
+
knolo kar produce rules --image knowledge.knolo --domain domains/contracts --out proposals.json
|
|
27
|
+
knolo kar produce review proposals.json
|
|
28
|
+
knolo kar produce apply --proposals proposals.json --out generated.ceg.yaml
|
|
29
|
+
knolo kar domain validate domains/contracts
|
|
30
|
+
knolo kar domain inspect domains/contracts
|
|
31
|
+
knolo kar domain test domains/contracts
|
|
32
|
+
knolo kar dossier build --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..." --out dossier/
|
|
33
|
+
knolo kar dossier verify dossier/
|
|
34
|
+
knolo kar coverage build --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..." --out coverage/
|
|
35
|
+
knolo kar coverage build --image knowledge.knolo --graph knowledge.kar.json --domain domains/contracts-v4 --query "..." --out coverage/
|
|
36
|
+
knolo kar coverage verify coverage/
|
|
37
|
+
knolo kar benchmark annotate --packet packet.json --decisions decisions.json --out labels.json --role human --reviewer-id <id>
|
|
38
|
+
knolo kar benchmark adjudicate --packet packet.json --a pass-a.json --b pass-b.json --decisions decisions.json --out gold.json --role human-adjudicator --reviewer-id <id>
|
|
39
|
+
knolo kar benchmark agreement --a pass-a.json --b pass-b.json
|
|
40
|
+
knolo kar demo
|
|
41
|
+
knolo kar conformance
|
|
42
|
+
|
|
43
|
+
--json Print the machine-readable evaluation, build, or inspection
|
|
44
|
+
--auto Write accepted deterministic producer output as CEG Source
|
|
45
|
+
|
|
46
|
+
Exit status is 0 for a completed command, including a KAR abstention.
|
|
47
|
+
coverage build exits 0 when the report is written, including gap and blocked rows.
|
|
48
|
+
verify, dossier verify, coverage verify, and conformance return non-zero when a check fails.
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
export async function runKarCli(argv) {
|
|
52
|
+
const { command, positionals, flags } = parseArgs(argv);
|
|
53
|
+
if (!command || command === 'help' || flags.help) {
|
|
54
|
+
console.log(USAGE.trim());
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (command === 'graph' || command === 'package') {
|
|
58
|
+
const { runKarAuthoring } = await import('./kar-graph.mjs');
|
|
59
|
+
return runKarAuthoring(command, positionals.slice(1), flags);
|
|
60
|
+
}
|
|
61
|
+
if (command === 'produce' || command === 'domain') {
|
|
62
|
+
const { runKarProduce } = await import('./kar-produce.mjs');
|
|
63
|
+
return runKarProduce(command, positionals.slice(1), flags);
|
|
64
|
+
}
|
|
65
|
+
if (command === 'benchmark') {
|
|
66
|
+
const { runKarBenchmark } = await import('./kar-benchmark.mjs');
|
|
67
|
+
return runKarBenchmark(positionals.slice(1), flags);
|
|
68
|
+
}
|
|
69
|
+
if (command === 'demo') return runRepoScript(path.join('examples', 'kar-v6', 'run.mjs'));
|
|
70
|
+
if (command === 'conformance') return runRepoScript(path.join('conformance', 'kar', 'v1', 'run.mjs'));
|
|
71
|
+
const kar = await loadKar();
|
|
72
|
+
if (command === 'dossier') return dossierCommand(kar, positionals.slice(1), flags);
|
|
73
|
+
if (command === 'coverage') return coverageCommand(kar, positionals.slice(1), flags);
|
|
74
|
+
if (command === 'evaluate') return evaluateCommand(kar, flags);
|
|
75
|
+
if (command === 'verify') return verifyCommand(kar, flags);
|
|
76
|
+
if (command === 'explain') return explainCommand(kar, flags);
|
|
77
|
+
if (command === 'inspect') return inspectCommand(kar, flags);
|
|
78
|
+
throw new Error(`Unknown kar command "${command}".\n${USAGE}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function evaluateCommand(kar, flags) {
|
|
82
|
+
const { session, plan, query } = openSession(kar, flags, true);
|
|
83
|
+
const result = kar.evaluateKar(session, { proposition: query, plan });
|
|
84
|
+
if (flags.json) {
|
|
85
|
+
console.log(JSON.stringify(result, null, 2));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
console.log(kar.renderKarEvaluation(result));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function verifyCommand(kar, flags) {
|
|
92
|
+
const image = readBytes(required(flags, 'image'));
|
|
93
|
+
const graph = readJson(required(flags, 'graph'));
|
|
94
|
+
const plan = readJson(required(flags, 'plan'));
|
|
95
|
+
const query = required(flags, 'query');
|
|
96
|
+
const result = readJson(required(flags, 'result'));
|
|
97
|
+
const verdict = kar.verifyKar({ image, graph, proposition: query, plan, result });
|
|
98
|
+
if (!verdict.ok) {
|
|
99
|
+
console.log(verdict.code);
|
|
100
|
+
console.log(verdict.reason);
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
console.log('VERIFIED');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function explainCommand(kar, flags) {
|
|
108
|
+
const result = readJson(required(flags, 'result'));
|
|
109
|
+
const wantsResolution = flags.image || flags.graph || flags.plan || flags.query;
|
|
110
|
+
if (wantsResolution && !(flags.image && flags.graph && flags.plan && flags.query)) {
|
|
111
|
+
throw new Error('Resolving evidence text requires --image, --graph, --plan, and --query together. Certificate-only explanation uses --result alone.');
|
|
112
|
+
}
|
|
113
|
+
const explanation = wantsResolution
|
|
114
|
+
? kar.explainKarResult(openSession(kar, flags, true).session, result)
|
|
115
|
+
: kar.explainKarCertificate(result);
|
|
116
|
+
if (flags.json) {
|
|
117
|
+
console.log(JSON.stringify(explanation, null, 2));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log(kar.renderKarExplanation(explanation));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function inspectCommand(kar, flags) {
|
|
124
|
+
const { session, plan } = openSession(kar, flags, false);
|
|
125
|
+
const graph = session.graph;
|
|
126
|
+
const symbols = [...new Set(graph.relations.map((relation) => relation.relation))].sort();
|
|
127
|
+
const complexity = plan ? kar.inspectKarComplexity(session, plan, flags.query ?? '') : null;
|
|
128
|
+
const report = {
|
|
129
|
+
experimental: true,
|
|
130
|
+
semantics: 'kar-1-research-1',
|
|
131
|
+
stateRoot: session.image.stateRoot,
|
|
132
|
+
knowledgeRoot: session.image.knowledgeRoot,
|
|
133
|
+
objectRoot: session.image.objectRoot,
|
|
134
|
+
commitDigest: session.image.commitDigest,
|
|
135
|
+
semanticRoot: session.roots.semanticRoot,
|
|
136
|
+
nodes: graph.nodes.length,
|
|
137
|
+
relations: graph.relations.length,
|
|
138
|
+
evidenceBindings: graph.bindings.length,
|
|
139
|
+
relationTypes: symbols,
|
|
140
|
+
cover: complexity,
|
|
141
|
+
bounds: plan ? plan.bounds : null,
|
|
142
|
+
};
|
|
143
|
+
if (flags.json) {
|
|
144
|
+
console.log(JSON.stringify(report, null, 2));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const lines = [
|
|
148
|
+
'KAR INSPECTION',
|
|
149
|
+
`V5 state root ${report.stateRoot ?? '(none)'}`,
|
|
150
|
+
`KAR knowledge root ${report.knowledgeRoot}`,
|
|
151
|
+
`CEG semantic root ${report.semanticRoot}`,
|
|
152
|
+
`object root ${report.objectRoot ?? '(none)'}`,
|
|
153
|
+
`commit digest ${report.commitDigest ?? '(none)'}`,
|
|
154
|
+
`nodes ${report.nodes}`,
|
|
155
|
+
`relations ${report.relations}`,
|
|
156
|
+
`evidence bindings ${report.evidenceBindings}`,
|
|
157
|
+
`relation types ${symbols.join(', ') || '(none)'}`,
|
|
158
|
+
];
|
|
159
|
+
if (!complexity) {
|
|
160
|
+
lines.push('cover complexity plan not supplied');
|
|
161
|
+
} else {
|
|
162
|
+
lines.push(`cover risk ${complexity.risk}`);
|
|
163
|
+
lines.push(`candidates ${complexity.candidates}`);
|
|
164
|
+
lines.push(`distinct masks ${complexity.distinctRequirementMasks}`);
|
|
165
|
+
lines.push(`cardinality bound ${complexity.cardinalityBound}`);
|
|
166
|
+
lines.push(`max cover visits ${complexity.maxCoverVisits}`);
|
|
167
|
+
lines.push(`combination bound ${complexity.estimatedCombinationUpperBound}`);
|
|
168
|
+
}
|
|
169
|
+
if (plan) {
|
|
170
|
+
lines.push(`resource bounds closureNodes ${plan.bounds.maxClosureNodes}, closureEdges ${plan.bounds.maxClosureEdges}, coverVisits ${plan.bounds.maxCoverVisits}`);
|
|
171
|
+
}
|
|
172
|
+
console.log(lines.join('\n'));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function dossierCommand(kar, args, flags) {
|
|
176
|
+
if (!kar.createKarDossier || !kar.renderKarDossier || !kar.verifyKarDossier) {
|
|
177
|
+
throw new Error('Could not load the KAR dossier API. Build packages/core first.');
|
|
178
|
+
}
|
|
179
|
+
const sub = args[0];
|
|
180
|
+
if (sub === 'build') return buildDossier(kar, flags);
|
|
181
|
+
if (sub === 'verify') return verifyDossier(kar, args[1]);
|
|
182
|
+
throw new Error(`Unknown dossier command "${sub ?? ''}".\n${USAGE}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function buildDossier(kar, flags) {
|
|
186
|
+
const out = path.resolve(process.cwd(), required(flags, 'out'));
|
|
187
|
+
if (existsSync(path.join(out, 'manifest.json'))) {
|
|
188
|
+
throw new Error(`Refusing to overwrite ${out}`);
|
|
189
|
+
}
|
|
190
|
+
const imagePath = path.resolve(process.cwd(), required(flags, 'image'));
|
|
191
|
+
const graphPath = path.resolve(process.cwd(), required(flags, 'graph'));
|
|
192
|
+
const planPath = path.resolve(process.cwd(), required(flags, 'plan'));
|
|
193
|
+
const proposition = required(flags, 'query');
|
|
194
|
+
const image = readBytes(imagePath);
|
|
195
|
+
const graph = readJson(graphPath);
|
|
196
|
+
const plan = readJson(planPath);
|
|
197
|
+
const dossier = kar.createKarDossier({ image, graph, plan, proposition });
|
|
198
|
+
mkdirSync(out, { recursive: true });
|
|
199
|
+
copyFileSync(imagePath, path.join(out, 'knowledge.knolo'));
|
|
200
|
+
copyFileSync(graphPath, path.join(out, 'knowledge.kar.json'));
|
|
201
|
+
copyFileSync(planPath, path.join(out, 'plan.json'));
|
|
202
|
+
writeFileSync(path.join(out, 'manifest.json'), `${JSON.stringify(dossier.manifest, null, 2)}\n`);
|
|
203
|
+
writeFileSync(path.join(out, 'certificate.json'), `${JSON.stringify(dossier.certificate, null, 2)}\n`);
|
|
204
|
+
writeFileSync(path.join(out, 'result.json'), `${JSON.stringify(dossier.evaluation, null, 2)}\n`);
|
|
205
|
+
writeFileSync(path.join(out, 'evidence.md'), kar.renderKarDossier(dossier));
|
|
206
|
+
if (flags.json) console.log(JSON.stringify(dossier.manifest, null, 2));
|
|
207
|
+
else console.log(`${dossier.statusStatement}\n${out}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function verifyDossier(kar, directory) {
|
|
211
|
+
if (!directory) throw new Error('Missing dossier directory.\nknolo kar dossier verify dossier/');
|
|
212
|
+
const dir = path.resolve(process.cwd(), directory);
|
|
213
|
+
const image = readBytes(path.join(dir, 'knowledge.knolo'));
|
|
214
|
+
const graph = readJson(path.join(dir, 'knowledge.kar.json'));
|
|
215
|
+
const plan = readJson(path.join(dir, 'plan.json'));
|
|
216
|
+
const manifest = readJson(path.join(dir, 'manifest.json'));
|
|
217
|
+
const certificate = readJson(path.join(dir, 'certificate.json'));
|
|
218
|
+
const evaluation = readJson(path.join(dir, 'result.json'));
|
|
219
|
+
const evidenceMarkdown = readBoundedText(path.join(dir, 'evidence.md'));
|
|
220
|
+
const proposition = evaluation && typeof evaluation.proposition === 'string' ? evaluation.proposition : '';
|
|
221
|
+
if (!proposition) {
|
|
222
|
+
console.log('KAR_RESULT_INVALID');
|
|
223
|
+
console.log('The dossier result has no proposition.');
|
|
224
|
+
process.exitCode = 1;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const verdict = kar.verifyKarDossier({
|
|
228
|
+
image,
|
|
229
|
+
graph,
|
|
230
|
+
plan,
|
|
231
|
+
proposition,
|
|
232
|
+
manifest,
|
|
233
|
+
certificate,
|
|
234
|
+
evaluation,
|
|
235
|
+
evidenceMarkdown,
|
|
236
|
+
});
|
|
237
|
+
if (!verdict.ok) {
|
|
238
|
+
console.log(verdict.code);
|
|
239
|
+
console.log(verdict.reason);
|
|
240
|
+
process.exitCode = 1;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
console.log('VERIFIED');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function coverageCommand(kar, args, flags) {
|
|
247
|
+
if (!kar.createKarCoverage || !kar.verifyKarCoverage || !kar.renderKarCoverage) {
|
|
248
|
+
throw new Error('Could not load the KAR coverage API. Build packages/core first.');
|
|
249
|
+
}
|
|
250
|
+
const sub = args[0];
|
|
251
|
+
if (sub === 'build') return buildCoverage(kar, flags);
|
|
252
|
+
if (sub === 'verify') return verifyCoverage(kar, args[1], flags);
|
|
253
|
+
throw new Error(`Unknown coverage command "${sub ?? ''}".\n${USAGE}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function buildCoverage(kar, flags) {
|
|
257
|
+
const out = path.resolve(process.cwd(), required(flags, 'out'));
|
|
258
|
+
if (existsSync(path.join(out, 'manifest.json'))) {
|
|
259
|
+
throw new Error(`Refusing to overwrite ${out}`);
|
|
260
|
+
}
|
|
261
|
+
const imagePath = path.resolve(process.cwd(), required(flags, 'image'));
|
|
262
|
+
const graphPath = path.resolve(process.cwd(), required(flags, 'graph'));
|
|
263
|
+
const image = readBytes(imagePath);
|
|
264
|
+
const graph = readJson(graphPath);
|
|
265
|
+
const prepared = await coverageInputs(flags);
|
|
266
|
+
if (!prepared.ok) {
|
|
267
|
+
console.log(prepared.code);
|
|
268
|
+
console.log(prepared.message);
|
|
269
|
+
process.exitCode = 1;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
let report;
|
|
273
|
+
try {
|
|
274
|
+
report = kar.createKarCoverage({
|
|
275
|
+
image,
|
|
276
|
+
graph,
|
|
277
|
+
cases: prepared.cases,
|
|
278
|
+
domainPack: prepared.domainPack,
|
|
279
|
+
});
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (error && typeof error.code === 'string') {
|
|
282
|
+
console.log(error.code);
|
|
283
|
+
console.log(error.message);
|
|
284
|
+
process.exitCode = 1;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
const ordered = [...prepared.cases].sort((left, right) => (left.id < right.id ? -1 : left.id > right.id ? 1 : 0));
|
|
290
|
+
mkdirSync(out, { recursive: true });
|
|
291
|
+
copyFileSync(imagePath, path.join(out, 'knowledge.knolo'));
|
|
292
|
+
copyFileSync(graphPath, path.join(out, 'knowledge.kar.json'));
|
|
293
|
+
const casesFile = { format: 'kar-coverage-cases-1', domainPack: report.domainPack, cases: ordered };
|
|
294
|
+
writeFileSync(path.join(out, 'cases.json'), `${JSON.stringify(casesFile, null, 2)}\n`);
|
|
295
|
+
writeFileSync(path.join(out, 'report.json'), `${JSON.stringify(report, null, 2)}\n`);
|
|
296
|
+
writeFileSync(path.join(out, 'coverage.md'), kar.renderKarCoverage(report));
|
|
297
|
+
writeFileSync(path.join(out, 'manifest.json'), `${JSON.stringify(report.manifest, null, 2)}\n`);
|
|
298
|
+
if (flags.json) console.log(JSON.stringify(report.manifest, null, 2));
|
|
299
|
+
else {
|
|
300
|
+
for (const row of report.cases) console.log(`${row.id} ${row.disposition} ${row.statement}`);
|
|
301
|
+
console.log(out);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function verifyCoverage(kar, directory, flags) {
|
|
306
|
+
if (!directory) throw new Error('Missing coverage directory.\nknolo kar coverage verify coverage/');
|
|
307
|
+
const dir = path.resolve(process.cwd(), directory);
|
|
308
|
+
const image = readBytes(path.join(dir, 'knowledge.knolo'));
|
|
309
|
+
const graph = readJson(path.join(dir, 'knowledge.kar.json'));
|
|
310
|
+
const stored = readJson(path.join(dir, 'cases.json'));
|
|
311
|
+
const manifest = readJson(path.join(dir, 'manifest.json'));
|
|
312
|
+
const report = readJson(path.join(dir, 'report.json'));
|
|
313
|
+
const rendering = readBoundedText(path.join(dir, 'coverage.md'));
|
|
314
|
+
if (!stored || stored.format !== 'kar-coverage-cases-1' || !Array.isArray(stored.cases)) {
|
|
315
|
+
console.log('KAR_COVERAGE_INVALID');
|
|
316
|
+
console.log('cases.json is not a coverage case file.');
|
|
317
|
+
process.exitCode = 1;
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (flags.domain) {
|
|
321
|
+
const authoring = await loadAuthoring();
|
|
322
|
+
const { loadDomainDirectory } = await import('./kar-produce.mjs');
|
|
323
|
+
const loaded = loadDomainDirectory(authoring, flags.domain);
|
|
324
|
+
if (!loaded.ok) {
|
|
325
|
+
console.log(loaded.diagnostics[0].code);
|
|
326
|
+
console.log(loaded.diagnostics[0].message);
|
|
327
|
+
process.exitCode = 1;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const root = authoring.domainPackRoot(loaded.pack);
|
|
331
|
+
const recorded = stored.domainPack;
|
|
332
|
+
if (!recorded || recorded.root !== root || recorded.id !== loaded.pack.id || String(recorded.version) !== String(loaded.pack.version)) {
|
|
333
|
+
console.log('KAR_RESULT_MISMATCH');
|
|
334
|
+
console.log('domain pack root mismatch');
|
|
335
|
+
process.exitCode = 1;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const verdict = kar.verifyKarCoverage({
|
|
340
|
+
image,
|
|
341
|
+
graph,
|
|
342
|
+
cases: stored.cases,
|
|
343
|
+
domainPack: stored.domainPack ?? null,
|
|
344
|
+
manifest,
|
|
345
|
+
report,
|
|
346
|
+
rendering,
|
|
347
|
+
});
|
|
348
|
+
if (!verdict.ok) {
|
|
349
|
+
console.log(verdict.code);
|
|
350
|
+
console.log(verdict.reason);
|
|
351
|
+
process.exitCode = 1;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
console.log('VERIFIED');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function coverageInputs(flags) {
|
|
358
|
+
const plans = flags.planList ?? [];
|
|
359
|
+
const queries = flags.queryList ?? [];
|
|
360
|
+
const templates = flags.templateList ?? [];
|
|
361
|
+
if (plans.length > 0 && flags.domain) {
|
|
362
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: 'Use either repeated --plan or --domain, not both.' };
|
|
363
|
+
}
|
|
364
|
+
if (templates.length > 0 && !flags.domain) {
|
|
365
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: '--template requires --domain.' };
|
|
366
|
+
}
|
|
367
|
+
if (flags.queries && !flags.domain) {
|
|
368
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: '--queries requires --domain.' };
|
|
369
|
+
}
|
|
370
|
+
if (plans.length > 0) {
|
|
371
|
+
if (flags.queries) {
|
|
372
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: 'Pair each --plan with --query. --queries is for --domain.' };
|
|
373
|
+
}
|
|
374
|
+
if (queries.length !== plans.length) {
|
|
375
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: 'Each --plan needs one --query in the same order.' };
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
ok: true,
|
|
379
|
+
domainPack: null,
|
|
380
|
+
cases: plans.map((file, index) => ({
|
|
381
|
+
id: path.basename(file, path.extname(file)),
|
|
382
|
+
proposition: queries[index],
|
|
383
|
+
plan: readJson(path.resolve(process.cwd(), file)),
|
|
384
|
+
})),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
if (!flags.domain) {
|
|
388
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: 'Coverage requires repeated --plan and --query, or --domain.' };
|
|
389
|
+
}
|
|
390
|
+
if (flags.queries && queries.length > 0) {
|
|
391
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: 'Use either --query or --queries.' };
|
|
392
|
+
}
|
|
393
|
+
if (!flags.queries && queries.length !== 1) {
|
|
394
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: 'Domain coverage needs one --query, or a --queries file.' };
|
|
395
|
+
}
|
|
396
|
+
const authoring = await loadAuthoring();
|
|
397
|
+
const { loadDomainDirectory } = await import('./kar-produce.mjs');
|
|
398
|
+
const loaded = loadDomainDirectory(authoring, flags.domain);
|
|
399
|
+
if (!loaded.ok) {
|
|
400
|
+
const diagnostic = loaded.diagnostics[0];
|
|
401
|
+
return { ok: false, code: diagnostic?.code ?? 'CEG_DOMAIN_INVALID', message: diagnostic?.message ?? 'Domain pack did not load.' };
|
|
402
|
+
}
|
|
403
|
+
let queryMap;
|
|
404
|
+
if (flags.queries) {
|
|
405
|
+
const parsed = readJson(path.resolve(process.cwd(), flags.queries));
|
|
406
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
407
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: '--queries must be a JSON object.' };
|
|
408
|
+
}
|
|
409
|
+
queryMap = parsed;
|
|
410
|
+
}
|
|
411
|
+
const expanded = authoring.coverageCasesFromDomainPack({
|
|
412
|
+
pack: loaded.pack,
|
|
413
|
+
proposition: queryMap ? undefined : queries[0],
|
|
414
|
+
templates: templates.length > 0 ? templates : undefined,
|
|
415
|
+
queries: queryMap,
|
|
416
|
+
});
|
|
417
|
+
if (!expanded.ok) {
|
|
418
|
+
const diagnostic = expanded.diagnostics[0];
|
|
419
|
+
return { ok: false, code: diagnostic.code, message: diagnostic.message };
|
|
420
|
+
}
|
|
421
|
+
return { ok: true, cases: expanded.cases, domainPack: expanded.domainPack };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function loadAuthoring() {
|
|
425
|
+
const candidates = [
|
|
426
|
+
'@knolo/core/kar/authoring',
|
|
427
|
+
path.resolve(__dirname, '../../core/dist/kar/authoring/index.js'),
|
|
428
|
+
path.resolve(__dirname, '../../core/dist/experimental/kar/authoring/index.js'),
|
|
429
|
+
'@knolo/core/experimental/kar/authoring',
|
|
430
|
+
];
|
|
431
|
+
for (const candidate of candidates) {
|
|
432
|
+
try {
|
|
433
|
+
const href = candidate.startsWith('@') ? candidate : pathToFileURL(candidate).href;
|
|
434
|
+
const loaded = await import(href);
|
|
435
|
+
if (loaded.coverageCasesFromDomainPack && loaded.domainPackRoot && loaded.parseAuthoringDocument) return loaded;
|
|
436
|
+
} catch {
|
|
437
|
+
// Try the next candidate.
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
throw new Error('Could not load @knolo/core/kar/authoring. Build packages/core first.');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function openSession(kar, flags, requirePlan) {
|
|
444
|
+
const image = readBytes(required(flags, 'image'));
|
|
445
|
+
const graph = readJson(required(flags, 'graph'));
|
|
446
|
+
const session = kar.createKarSession({ image, graph });
|
|
447
|
+
if (!requirePlan && !flags.plan) return { session, plan: null, query: flags.query ?? '' };
|
|
448
|
+
const plan = readJson(required(flags, 'plan'));
|
|
449
|
+
const query = requirePlan ? required(flags, 'query') : flags.query ?? '';
|
|
450
|
+
return { session, plan, query };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function runRepoScript(relative) {
|
|
454
|
+
const root = findRepoRoot(process.cwd()) ?? findRepoRoot(path.resolve(__dirname, '../../..'));
|
|
455
|
+
const script = root ? path.join(root, relative) : '';
|
|
456
|
+
if (!root || !existsSync(script)) {
|
|
457
|
+
throw new Error(`Could not find ${relative}. Run this command from a knolo-core checkout.`);
|
|
458
|
+
}
|
|
459
|
+
const result = spawnSync(process.execPath, [script], { stdio: 'inherit' });
|
|
460
|
+
process.exitCode = result.status === null ? 1 : result.status;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function findRepoRoot(start) {
|
|
464
|
+
let dir = start;
|
|
465
|
+
for (let hop = 0; hop < 8; hop += 1) {
|
|
466
|
+
if (existsSync(path.join(dir, 'packages', 'core', 'package.json')) && existsSync(path.join(dir, 'conformance', 'kar', 'v1', 'run.mjs'))) {
|
|
467
|
+
return dir;
|
|
468
|
+
}
|
|
469
|
+
const parent = path.dirname(dir);
|
|
470
|
+
if (parent === dir) return null;
|
|
471
|
+
dir = parent;
|
|
472
|
+
}
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async function loadKar() {
|
|
477
|
+
const candidates = [
|
|
478
|
+
'@knolo/core/kar',
|
|
479
|
+
path.resolve(__dirname, '../../core/dist/kar/index.js'),
|
|
480
|
+
path.resolve(__dirname, '../../core/dist/experimental/kar/index.js'),
|
|
481
|
+
'@knolo/core/experimental/kar',
|
|
482
|
+
];
|
|
483
|
+
for (const candidate of candidates) {
|
|
484
|
+
try {
|
|
485
|
+
const href = candidate.startsWith('@') ? candidate : pathToFileURL(candidate).href;
|
|
486
|
+
const loaded = await import(href);
|
|
487
|
+
if (loaded.createKarSession && loaded.evaluateKar && loaded.verifyKar) return loaded;
|
|
488
|
+
} catch {
|
|
489
|
+
// Try the next candidate.
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
throw new Error('Could not load @knolo/core/kar. Build packages/core first.');
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function parseArgs(argv) {
|
|
496
|
+
const flags = { planList: [], queryList: [], templateList: [] };
|
|
497
|
+
const positionals = [];
|
|
498
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
499
|
+
const arg = argv[index];
|
|
500
|
+
if (arg === '--json') flags.json = true;
|
|
501
|
+
else if (arg === '--auto') flags.auto = true;
|
|
502
|
+
else if (arg === '--help' || arg === '-h') flags.help = true;
|
|
503
|
+
else if (arg.startsWith('--')) {
|
|
504
|
+
const key = arg.slice(2);
|
|
505
|
+
const value = argv[index + 1];
|
|
506
|
+
if (value === undefined || value.startsWith('--')) throw new Error(`Missing value for --${key}`);
|
|
507
|
+
if (key === 'plan') flags.planList.push(value);
|
|
508
|
+
else if (key === 'query') flags.queryList.push(value);
|
|
509
|
+
else if (key === 'template') flags.templateList.push(value);
|
|
510
|
+
flags[key] = value;
|
|
511
|
+
index += 1;
|
|
512
|
+
} else positionals.push(arg);
|
|
513
|
+
}
|
|
514
|
+
return { command: positionals[0] ?? '', positionals, flags };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function required(flags, name) {
|
|
518
|
+
if (!flags[name]) throw new Error(`Missing --${name}`);
|
|
519
|
+
return flags[name];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function readBytes(file) {
|
|
523
|
+
return readBounded(file, KAR_CLI_MAX_IMAGE_BYTES);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function readJson(file) {
|
|
527
|
+
return readBoundedJson(file);
|
|
528
|
+
}
|
package/bin/knolo.mjs
CHANGED
|
@@ -72,6 +72,10 @@ const SUBCOMMANDS = new Set([
|
|
|
72
72
|
'semantic:index',
|
|
73
73
|
'semantic:inspect',
|
|
74
74
|
'semantic:validate',
|
|
75
|
+
'kar',
|
|
76
|
+
'compose',
|
|
77
|
+
'attest',
|
|
78
|
+
'verify-attestation',
|
|
75
79
|
]);
|
|
76
80
|
const ICP_SUBCOMMANDS = new Set([
|
|
77
81
|
'init',
|
|
@@ -128,6 +132,18 @@ async function loadCore() {
|
|
|
128
132
|
);
|
|
129
133
|
}
|
|
130
134
|
|
|
135
|
+
function printVersion() {
|
|
136
|
+
const cli = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
|
137
|
+
let coreVersion = 'unavailable';
|
|
138
|
+
try {
|
|
139
|
+
coreVersion = JSON.parse(readFileSync(require.resolve('@knolo/core/package.json'), 'utf8')).version;
|
|
140
|
+
} catch {
|
|
141
|
+
coreVersion = 'unavailable';
|
|
142
|
+
}
|
|
143
|
+
console.log(`@knolo/cli ${cli.version}`);
|
|
144
|
+
console.log(`@knolo/core ${coreVersion}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
131
147
|
function printRootHelp() {
|
|
132
148
|
console.log(`KnoLo CLI
|
|
133
149
|
|
|
@@ -154,11 +170,15 @@ Commands:
|
|
|
154
170
|
diff <pack-a> <pack-b> Compare pack identities and source/chunk counts
|
|
155
171
|
dev Watch config/sources and rebuild on change
|
|
156
172
|
v5 Inspect, compress, decompress, and health-check a V5 Knowledge Image
|
|
173
|
+
kar KAR evidence-set retrieval over a Committed Evidence Graph
|
|
174
|
+
compose Compose Knowledge Images, packs, and attestations offline
|
|
175
|
+
attest Create or verify a composition attestation
|
|
157
176
|
icp Scaffold and operate an ICP-native knowledge canister
|
|
158
177
|
|
|
159
178
|
Global options:
|
|
160
179
|
--debug Print stack traces for errors
|
|
161
180
|
-h, --help Show help
|
|
181
|
+
-v, --version Print the CLI and @knolo/core versions
|
|
162
182
|
|
|
163
183
|
Run "knolo <command> --help" for command details.`);
|
|
164
184
|
}
|
|
@@ -188,6 +208,10 @@ function printCommandHelp(command) {
|
|
|
188
208
|
diff: 'Usage: knolo diff <pack-a.knolo> <pack-b.knolo>',
|
|
189
209
|
dev: 'Usage: knolo dev',
|
|
190
210
|
v5: 'Usage: knolo v5 <info|health|studio|query|compress|decompress> <image.v5> [--out <file>] [--mode fast|balanced|max] [--attach-index]\n\nFor query: knolo v5 query <image.v5> <EQL> [--json] [--receipt <file>].',
|
|
211
|
+
kar: 'Usage: knolo kar <evaluate|verify|explain|inspect|graph|package|produce|domain|dossier|coverage|benchmark|demo|conformance> [options]\n\nKnolo Adversarial Retrieval. Semantics are frozen at kar-1-research-1.\n\n knolo kar evaluate --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..."\n knolo kar verify --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..." --result result.json\n knolo kar explain --result result.json\n knolo kar inspect --image knowledge.knolo --graph knowledge.kar.json [--plan plan.json]\n knolo kar graph build --image knowledge.knolo --source knowledge.ceg.yaml --out knowledge.kar.json\n knolo kar package --image knowledge.knolo --graph knowledge.kar.json --out dist/my-knowledge-kar/\n knolo kar produce rules --image knowledge.knolo --domain domains/contracts-v4 --out proposals.json\n knolo kar domain validate domains/contracts-v4\n knolo kar dossier build --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..." --out dossier/\n knolo kar dossier verify dossier/\n knolo kar coverage build --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..." --out coverage/\n knolo kar coverage verify coverage/\n knolo kar benchmark annotate --packet packet.json --decisions decisions.json --out labels.json --role human --reviewer-id <id>\n knolo kar demo\n knolo kar conformance\n\n--json prints the machine-readable result. verify exits non-zero when the certificate does not match. Abstention is a result and exits 0. graph and package author a Committed Evidence Graph without changing retrieval. produce proposes CEG Source and does not write committed bytes. dossier packages one evaluation and fails closed when committed fields change. coverage reports met requirements, gaps, and plans that did not decide. A gap is not a false claim. benchmark labels gold without showing producer output. demo and conformance run from a knolo-core checkout.',
|
|
212
|
+
compose: 'Usage: knolo compose <create|verify|excerpt|query> [options]\n\nOffline composition of Knowledge Images, packs, KAR sidecars, Domain Packs, and bounded excerpts.\n\n knolo compose create --members members.json --out composition/ [--host knowledge.knolo] [--availability material|digest] [--policy policy.json]\n knolo compose verify composition/ [--require-material] [--member alias=path]\n knolo compose excerpt --image knowledge.knolo --object <id> --out excerpt.json\n knolo compose query --composition composition/ --eql "FROM chunk SEARCH \\"valve\\"" [--question "..."] [--json]\n\ncompositionRoot does not change a V5 state root or a KAR root. statement used means a query or evaluation ran. It does not mean SATISFIED, and it does not mean a claim is true or false. Hub attested: true is a listing flag and is not an attestation root. The CLI does not fetch Hub.',
|
|
213
|
+
attest: 'Usage: knolo attest <create|verify> [options]\n\n knolo attest create --composition composition/composition.json --subject composition --statement mount|used|cited --out attestation.json [--sign-key key.json --principal <id> --key-id <id> --issued-at <ms> --expires-at <ms>]\n knolo attest verify attestation.json [--composition composition.json] [--public-key key.json] [--now <ms>] [--content-only]\n\nstatement used means a query or KAR evaluation ran. It does not mean the claim is true or false. Hub attested: true is unrelated. Signing uses the supplied times. The CLI does not read the clock.',
|
|
214
|
+
'verify-attestation': 'Usage: knolo verify-attestation <attestation.json> [--composition composition.json] [--public-key key.json] [--keyring keyring.json] [--now <ms>] [--content-only]\n\nSame check as knolo attest verify. A bad signature exits 1 even when the content root matches. --content-only checks the content root and ignores the envelope. statement used is not a truth verdict. Hub attested: true is unrelated.',
|
|
191
215
|
icp: 'Usage: knolo icp <command> [options]',
|
|
192
216
|
'semantic:index':
|
|
193
217
|
'Usage: knolo semantic:index --pack <path> [--out <path>] [--model <id>] [--endpoint <url>]',
|
|
@@ -288,6 +312,8 @@ function parseArgv(argv) {
|
|
|
288
312
|
}
|
|
289
313
|
if (args[0] === '--help' || args[0] === '-h')
|
|
290
314
|
return { global, command: 'help', commandArgs: [] };
|
|
315
|
+
if (args[0] === '--version' || args[0] === '-v')
|
|
316
|
+
return { global, command: 'version', commandArgs: [] };
|
|
291
317
|
return { global, command: args[0] || 'help', commandArgs: args.slice(1) };
|
|
292
318
|
}
|
|
293
319
|
|
|
@@ -1816,6 +1842,7 @@ async function main() {
|
|
|
1816
1842
|
const { global, command, commandArgs } = parseArgv(process.argv.slice(2));
|
|
1817
1843
|
try {
|
|
1818
1844
|
if (command === 'help') return printRootHelp();
|
|
1845
|
+
if (command === 'version') return printVersion();
|
|
1819
1846
|
if (command === 'icp') {
|
|
1820
1847
|
if (
|
|
1821
1848
|
commandArgs.length === 0 ||
|
|
@@ -1830,6 +1857,14 @@ async function main() {
|
|
|
1830
1857
|
if (SUBCOMMANDS.has(command)) {
|
|
1831
1858
|
if (commandArgs.includes('--help') || commandArgs.includes('-h'))
|
|
1832
1859
|
return printCommandHelp(command);
|
|
1860
|
+
if (command === 'kar') {
|
|
1861
|
+
const { runKarCli } = await import('./kar.mjs');
|
|
1862
|
+
return runKarCli(commandArgs);
|
|
1863
|
+
}
|
|
1864
|
+
if (command === 'compose' || command === 'attest' || command === 'verify-attestation') {
|
|
1865
|
+
const { runComposeCli } = await import('./compose.mjs');
|
|
1866
|
+
return runComposeCli(command, commandArgs);
|
|
1867
|
+
}
|
|
1833
1868
|
if (command === 'init') return await cmdInit();
|
|
1834
1869
|
if (command === 'add') return await cmdAdd(commandArgs);
|
|
1835
1870
|
if (command === 'search') return await runHubSearch(commandArgs);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knolo/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"knolo": "bin/knolo.mjs"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"bin/",
|
|
10
10
|
"templates/"
|
|
11
11
|
],
|
|
12
|
-
"description": "CLI for
|
|
12
|
+
"description": "CLI for Knolo V4 packs, V5 Knowledge Images, KAR evidence-set retrieval, and offline composition.",
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "node -e \"console.log('cli is executable via bin/knolo.mjs')\"",
|
|
31
|
-
"test": "npm run build --prefix ../core && node --test test/cli.test.mjs test/hub.integration.test.mjs",
|
|
31
|
+
"test": "npm run build --prefix ../core && node --test test/cli.test.mjs test/kar.test.mjs test/kar-graph.test.mjs test/kar-produce.test.mjs test/hub.integration.test.mjs test/compose.test.mjs",
|
|
32
32
|
"test:hub": "node --test test/hub.integration.test.mjs"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@knolo/core": "^
|
|
35
|
+
"@knolo/core": "^6.1.0"
|
|
36
36
|
},
|
|
37
37
|
"private": false
|
|
38
38
|
}
|
|
@@ -245,11 +245,11 @@ dependencies = [
|
|
|
245
245
|
|
|
246
246
|
[[package]]
|
|
247
247
|
name = "knolo-core-rust"
|
|
248
|
-
version = "5.5.
|
|
248
|
+
version = "5.5.1"
|
|
249
249
|
|
|
250
250
|
[[package]]
|
|
251
251
|
name = "knolo-icp-canister"
|
|
252
|
-
version = "5.5.
|
|
252
|
+
version = "5.5.1"
|
|
253
253
|
dependencies = [
|
|
254
254
|
"candid",
|
|
255
255
|
"ic-cdk",
|