@knolo/cli 6.2.0 → 6.2.1
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 +46 -0
- package/bin/flight.mjs +367 -0
- package/bin/knolo.mjs +29 -0
- package/bin/skill.mjs +698 -0
- package/package.json +2 -2
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/closure.rs +395 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/cover.rs +692 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/evaluate.rs +343 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/json.rs +486 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/mod.rs +14 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/model.rs +1068 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/session.rs +1156 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/lib.rs +7 -1
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
//! Research JSON canonical form.
|
|
2
|
+
//! Object keys sort by UTF-16 code units. Arrays keep their order.
|
|
3
|
+
//! Solidus, U+2028, and U+2029 stay literal, matching `JSON.stringify`.
|
|
4
|
+
|
|
5
|
+
use std::cmp::Ordering;
|
|
6
|
+
|
|
7
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
8
|
+
pub enum Json {
|
|
9
|
+
Null,
|
|
10
|
+
Bool(bool),
|
|
11
|
+
Int(i64),
|
|
12
|
+
String(String),
|
|
13
|
+
Array(Vec<Json>),
|
|
14
|
+
Object(Vec<(String, Json)>),
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
impl Json {
|
|
18
|
+
pub fn object(pairs: Vec<(&str, Json)>) -> Json {
|
|
19
|
+
Json::Object(
|
|
20
|
+
pairs
|
|
21
|
+
.into_iter()
|
|
22
|
+
.map(|(key, value)| (key.to_string(), value))
|
|
23
|
+
.collect(),
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub fn get(&self, key: &str) -> Option<&Json> {
|
|
28
|
+
match self {
|
|
29
|
+
Json::Object(pairs) => pairs
|
|
30
|
+
.iter()
|
|
31
|
+
.find(|(name, _)| name == key)
|
|
32
|
+
.map(|(_, value)| value),
|
|
33
|
+
_ => None,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub fn as_object(&self) -> Option<&[(String, Json)]> {
|
|
38
|
+
match self {
|
|
39
|
+
Json::Object(pairs) => Some(pairs),
|
|
40
|
+
_ => None,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pub fn as_str(&self) -> Option<&str> {
|
|
45
|
+
match self {
|
|
46
|
+
Json::String(value) => Some(value),
|
|
47
|
+
_ => None,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub fn as_int(&self) -> Option<i64> {
|
|
52
|
+
match self {
|
|
53
|
+
Json::Int(value) => Some(*value),
|
|
54
|
+
_ => None,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pub fn parse_json(input: &str) -> Result<Json, String> {
|
|
60
|
+
let mut parser = Parser { input, index: 0 };
|
|
61
|
+
parser.skip();
|
|
62
|
+
let value = parser.value()?;
|
|
63
|
+
parser.skip();
|
|
64
|
+
if parser.index != parser.input.len() {
|
|
65
|
+
return Err("trailing JSON".into());
|
|
66
|
+
}
|
|
67
|
+
Ok(value)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn canonicalize(value: &Json) -> String {
|
|
71
|
+
let mut out = String::new();
|
|
72
|
+
write_canonical(value, &mut out);
|
|
73
|
+
out
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pub fn stringify(value: &Json) -> String {
|
|
77
|
+
let mut out = String::new();
|
|
78
|
+
write_stringify(value, &mut out);
|
|
79
|
+
out
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pub fn digest_json(value: &Json) -> String {
|
|
83
|
+
let raw = crate::sha256(canonicalize(value).as_bytes());
|
|
84
|
+
let mut hex = String::with_capacity(64);
|
|
85
|
+
for byte in raw {
|
|
86
|
+
hex.push_str(&format!("{byte:02x}"));
|
|
87
|
+
}
|
|
88
|
+
format!("sha256-{hex}")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
pub fn utf16_cmp(left: &str, right: &str) -> Ordering {
|
|
92
|
+
left.encode_utf16().cmp(right.encode_utf16())
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
struct Parser<'a> {
|
|
96
|
+
input: &'a str,
|
|
97
|
+
index: usize,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
impl<'a> Parser<'a> {
|
|
101
|
+
fn skip(&mut self) {
|
|
102
|
+
while let Some(byte) = self.input.as_bytes().get(self.index).copied() {
|
|
103
|
+
if byte == b' ' || byte == b'\n' || byte == b'\r' || byte == b'\t' {
|
|
104
|
+
self.index += 1;
|
|
105
|
+
} else {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn value(&mut self) -> Result<Json, String> {
|
|
112
|
+
self.skip();
|
|
113
|
+
let byte = *self
|
|
114
|
+
.input
|
|
115
|
+
.as_bytes()
|
|
116
|
+
.get(self.index)
|
|
117
|
+
.ok_or_else(|| "truncated JSON".to_string())?;
|
|
118
|
+
match byte {
|
|
119
|
+
b'n' => self.literal("null", Json::Null),
|
|
120
|
+
b't' => self.literal("true", Json::Bool(true)),
|
|
121
|
+
b'f' => self.literal("false", Json::Bool(false)),
|
|
122
|
+
b'"' => Ok(Json::String(self.string()?)),
|
|
123
|
+
b'[' => self.array(),
|
|
124
|
+
b'{' => self.object(),
|
|
125
|
+
b'-' | b'0'..=b'9' => self.number(),
|
|
126
|
+
_ => Err("invalid JSON value".into()),
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fn literal(&mut self, text: &str, value: Json) -> Result<Json, String> {
|
|
131
|
+
if self.input[self.index..].starts_with(text) {
|
|
132
|
+
self.index += text.len();
|
|
133
|
+
Ok(value)
|
|
134
|
+
} else {
|
|
135
|
+
Err(format!("invalid JSON literal {text}"))
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fn array(&mut self) -> Result<Json, String> {
|
|
140
|
+
self.index += 1;
|
|
141
|
+
let mut items = Vec::new();
|
|
142
|
+
self.skip();
|
|
143
|
+
if self.eat(b']') {
|
|
144
|
+
return Ok(Json::Array(items));
|
|
145
|
+
}
|
|
146
|
+
loop {
|
|
147
|
+
items.push(self.value()?);
|
|
148
|
+
self.skip();
|
|
149
|
+
if self.eat(b']') {
|
|
150
|
+
return Ok(Json::Array(items));
|
|
151
|
+
}
|
|
152
|
+
if !self.eat(b',') {
|
|
153
|
+
return Err("invalid JSON array".into());
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn object(&mut self) -> Result<Json, String> {
|
|
159
|
+
self.index += 1;
|
|
160
|
+
let mut pairs = Vec::new();
|
|
161
|
+
self.skip();
|
|
162
|
+
if self.eat(b'}') {
|
|
163
|
+
return Ok(Json::Object(pairs));
|
|
164
|
+
}
|
|
165
|
+
loop {
|
|
166
|
+
self.skip();
|
|
167
|
+
if !self.eat(b'"') {
|
|
168
|
+
return Err("JSON object key must be a string".into());
|
|
169
|
+
}
|
|
170
|
+
let key = self.string_body()?;
|
|
171
|
+
self.skip();
|
|
172
|
+
if !self.eat(b':') {
|
|
173
|
+
return Err("JSON object missing colon".into());
|
|
174
|
+
}
|
|
175
|
+
let value = self.value()?;
|
|
176
|
+
if pairs.iter().any(|(name, _): &(String, Json)| name == &key) {
|
|
177
|
+
return Err("duplicate JSON key".into());
|
|
178
|
+
}
|
|
179
|
+
pairs.push((key, value));
|
|
180
|
+
self.skip();
|
|
181
|
+
if self.eat(b'}') {
|
|
182
|
+
return Ok(Json::Object(pairs));
|
|
183
|
+
}
|
|
184
|
+
if !self.eat(b',') {
|
|
185
|
+
return Err("invalid JSON object".into());
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fn number(&mut self) -> Result<Json, String> {
|
|
191
|
+
let start = self.index;
|
|
192
|
+
if self.eat(b'-') {}
|
|
193
|
+
if self.eat(b'0') {
|
|
194
|
+
if self
|
|
195
|
+
.input
|
|
196
|
+
.as_bytes()
|
|
197
|
+
.get(self.index)
|
|
198
|
+
.is_some_and(|byte| byte.is_ascii_digit())
|
|
199
|
+
{
|
|
200
|
+
return Err("invalid JSON number".into());
|
|
201
|
+
}
|
|
202
|
+
} else if self
|
|
203
|
+
.input
|
|
204
|
+
.as_bytes()
|
|
205
|
+
.get(self.index)
|
|
206
|
+
.is_some_and(|byte| byte.is_ascii_digit())
|
|
207
|
+
{
|
|
208
|
+
while self
|
|
209
|
+
.input
|
|
210
|
+
.as_bytes()
|
|
211
|
+
.get(self.index)
|
|
212
|
+
.is_some_and(|byte| byte.is_ascii_digit())
|
|
213
|
+
{
|
|
214
|
+
self.index += 1;
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
return Err("invalid JSON number".into());
|
|
218
|
+
}
|
|
219
|
+
let mut fractional = false;
|
|
220
|
+
if self.eat(b'.') {
|
|
221
|
+
fractional = true;
|
|
222
|
+
if !self
|
|
223
|
+
.input
|
|
224
|
+
.as_bytes()
|
|
225
|
+
.get(self.index)
|
|
226
|
+
.is_some_and(|byte| byte.is_ascii_digit())
|
|
227
|
+
{
|
|
228
|
+
return Err("invalid JSON number".into());
|
|
229
|
+
}
|
|
230
|
+
while self
|
|
231
|
+
.input
|
|
232
|
+
.as_bytes()
|
|
233
|
+
.get(self.index)
|
|
234
|
+
.is_some_and(|byte| byte.is_ascii_digit())
|
|
235
|
+
{
|
|
236
|
+
self.index += 1;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if self
|
|
240
|
+
.input
|
|
241
|
+
.as_bytes()
|
|
242
|
+
.get(self.index)
|
|
243
|
+
.is_some_and(|byte| *byte == b'e' || *byte == b'E')
|
|
244
|
+
{
|
|
245
|
+
fractional = true;
|
|
246
|
+
self.index += 1;
|
|
247
|
+
if self
|
|
248
|
+
.input
|
|
249
|
+
.as_bytes()
|
|
250
|
+
.get(self.index)
|
|
251
|
+
.is_some_and(|byte| *byte == b'+' || *byte == b'-')
|
|
252
|
+
{
|
|
253
|
+
self.index += 1;
|
|
254
|
+
}
|
|
255
|
+
if !self
|
|
256
|
+
.input
|
|
257
|
+
.as_bytes()
|
|
258
|
+
.get(self.index)
|
|
259
|
+
.is_some_and(|byte| byte.is_ascii_digit())
|
|
260
|
+
{
|
|
261
|
+
return Err("invalid JSON number".into());
|
|
262
|
+
}
|
|
263
|
+
while self
|
|
264
|
+
.input
|
|
265
|
+
.as_bytes()
|
|
266
|
+
.get(self.index)
|
|
267
|
+
.is_some_and(|byte| byte.is_ascii_digit())
|
|
268
|
+
{
|
|
269
|
+
self.index += 1;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
let token = &self.input[start..self.index];
|
|
273
|
+
if !fractional {
|
|
274
|
+
let parsed = token
|
|
275
|
+
.parse::<i64>()
|
|
276
|
+
.map_err(|_| format!("JSON integer out of range: {token}"))?;
|
|
277
|
+
return Ok(Json::Int(parsed));
|
|
278
|
+
}
|
|
279
|
+
let number: f64 = token
|
|
280
|
+
.parse()
|
|
281
|
+
.map_err(|_| format!("invalid JSON number: {token}"))?;
|
|
282
|
+
if number.fract() == 0.0 && number >= i64::MIN as f64 && number <= i64::MAX as f64 {
|
|
283
|
+
let whole = number as i64;
|
|
284
|
+
if whole as f64 == number && (whole as f64).abs() <= 9007199254740991.0 {
|
|
285
|
+
return Ok(Json::Int(whole));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
Err("KAR JSON numbers must be safe integers".into())
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
fn string(&mut self) -> Result<String, String> {
|
|
292
|
+
if !self.eat(b'"') {
|
|
293
|
+
return Err("JSON string expected".into());
|
|
294
|
+
}
|
|
295
|
+
self.string_body()
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
fn string_body(&mut self) -> Result<String, String> {
|
|
299
|
+
let mut out = String::new();
|
|
300
|
+
loop {
|
|
301
|
+
let byte = *self
|
|
302
|
+
.input
|
|
303
|
+
.as_bytes()
|
|
304
|
+
.get(self.index)
|
|
305
|
+
.ok_or_else(|| "unterminated JSON string".to_string())?;
|
|
306
|
+
if byte == b'"' {
|
|
307
|
+
self.index += 1;
|
|
308
|
+
return Ok(out);
|
|
309
|
+
}
|
|
310
|
+
if byte == b'\\' {
|
|
311
|
+
self.index += 1;
|
|
312
|
+
let escape = *self
|
|
313
|
+
.input
|
|
314
|
+
.as_bytes()
|
|
315
|
+
.get(self.index)
|
|
316
|
+
.ok_or_else(|| "truncated JSON escape".to_string())?;
|
|
317
|
+
self.index += 1;
|
|
318
|
+
match escape {
|
|
319
|
+
b'"' => out.push('"'),
|
|
320
|
+
b'\\' => out.push('\\'),
|
|
321
|
+
b'/' => out.push('/'),
|
|
322
|
+
b'b' => out.push('\u{0008}'),
|
|
323
|
+
b'f' => out.push('\u{000c}'),
|
|
324
|
+
b'n' => out.push('\n'),
|
|
325
|
+
b'r' => out.push('\r'),
|
|
326
|
+
b't' => out.push('\t'),
|
|
327
|
+
b'u' => out.push(self.unicode_escape()?),
|
|
328
|
+
_ => return Err("invalid JSON string escape".into()),
|
|
329
|
+
}
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if byte < 0x20 {
|
|
333
|
+
return Err("raw control character in JSON string".into());
|
|
334
|
+
}
|
|
335
|
+
let ch = self.input[self.index..]
|
|
336
|
+
.chars()
|
|
337
|
+
.next()
|
|
338
|
+
.ok_or_else(|| "invalid UTF-8 in JSON".to_string())?;
|
|
339
|
+
self.index += ch.len_utf8();
|
|
340
|
+
out.push(ch);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
fn unicode_escape(&mut self) -> Result<char, String> {
|
|
345
|
+
let unit = self.hex4()?;
|
|
346
|
+
if (0xD800..=0xDBFF).contains(&unit) {
|
|
347
|
+
if self.input[self.index..].starts_with("\\u") {
|
|
348
|
+
self.index += 2;
|
|
349
|
+
let second = self.hex4()?;
|
|
350
|
+
if (0xDC00..=0xDFFF).contains(&second) {
|
|
351
|
+
let point =
|
|
352
|
+
0x10000 + (((unit as u32 - 0xD800) << 10) | (second as u32 - 0xDC00));
|
|
353
|
+
return char::from_u32(point)
|
|
354
|
+
.ok_or_else(|| "invalid JSON surrogate pair".to_string());
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return Err("lone JSON surrogate".into());
|
|
358
|
+
}
|
|
359
|
+
if (0xDC00..=0xDFFF).contains(&unit) {
|
|
360
|
+
return Err("lone JSON surrogate".into());
|
|
361
|
+
}
|
|
362
|
+
char::from_u32(unit as u32).ok_or_else(|| "invalid JSON unicode escape".to_string())
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
fn hex4(&mut self) -> Result<u16, String> {
|
|
366
|
+
let end = self.index + 4;
|
|
367
|
+
let text = self
|
|
368
|
+
.input
|
|
369
|
+
.get(self.index..end)
|
|
370
|
+
.ok_or_else(|| "truncated JSON unicode escape".to_string())?;
|
|
371
|
+
let value =
|
|
372
|
+
u16::from_str_radix(text, 16).map_err(|_| "invalid JSON unicode escape".to_string())?;
|
|
373
|
+
self.index = end;
|
|
374
|
+
Ok(value)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
fn eat(&mut self, byte: u8) -> bool {
|
|
378
|
+
if self.input.as_bytes().get(self.index) == Some(&byte) {
|
|
379
|
+
self.index += 1;
|
|
380
|
+
true
|
|
381
|
+
} else {
|
|
382
|
+
false
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
fn write_canonical(value: &Json, out: &mut String) {
|
|
388
|
+
match value {
|
|
389
|
+
Json::Array(items) => {
|
|
390
|
+
out.push('[');
|
|
391
|
+
for (index, item) in items.iter().enumerate() {
|
|
392
|
+
if index > 0 {
|
|
393
|
+
out.push(',');
|
|
394
|
+
}
|
|
395
|
+
write_canonical(item, out);
|
|
396
|
+
}
|
|
397
|
+
out.push(']');
|
|
398
|
+
}
|
|
399
|
+
Json::Object(pairs) => {
|
|
400
|
+
let mut keys: Vec<&(String, Json)> = pairs.iter().collect();
|
|
401
|
+
keys.sort_by(|left, right| utf16_cmp(&left.0, &right.0));
|
|
402
|
+
out.push('{');
|
|
403
|
+
for (index, (key, child)) in keys.iter().enumerate() {
|
|
404
|
+
if index > 0 {
|
|
405
|
+
out.push(',');
|
|
406
|
+
}
|
|
407
|
+
write_string(key, out);
|
|
408
|
+
out.push(':');
|
|
409
|
+
write_canonical(child, out);
|
|
410
|
+
}
|
|
411
|
+
out.push('}');
|
|
412
|
+
}
|
|
413
|
+
other => write_scalar(other, out),
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
fn write_stringify(value: &Json, out: &mut String) {
|
|
418
|
+
match value {
|
|
419
|
+
Json::Array(items) => {
|
|
420
|
+
out.push('[');
|
|
421
|
+
for (index, item) in items.iter().enumerate() {
|
|
422
|
+
if index > 0 {
|
|
423
|
+
out.push(',');
|
|
424
|
+
}
|
|
425
|
+
write_stringify(item, out);
|
|
426
|
+
}
|
|
427
|
+
out.push(']');
|
|
428
|
+
}
|
|
429
|
+
Json::Object(pairs) => {
|
|
430
|
+
out.push('{');
|
|
431
|
+
for (index, (key, child)) in pairs.iter().enumerate() {
|
|
432
|
+
if index > 0 {
|
|
433
|
+
out.push(',');
|
|
434
|
+
}
|
|
435
|
+
write_string(key, out);
|
|
436
|
+
out.push(':');
|
|
437
|
+
write_stringify(child, out);
|
|
438
|
+
}
|
|
439
|
+
out.push('}');
|
|
440
|
+
}
|
|
441
|
+
other => write_scalar(other, out),
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
fn write_scalar(value: &Json, out: &mut String) {
|
|
446
|
+
match value {
|
|
447
|
+
Json::Null => out.push_str("null"),
|
|
448
|
+
Json::Bool(true) => out.push_str("true"),
|
|
449
|
+
Json::Bool(false) => out.push_str("false"),
|
|
450
|
+
Json::Int(value) => out.push_str(&value.to_string()),
|
|
451
|
+
Json::String(value) => write_string(value, out),
|
|
452
|
+
Json::Array(_) | Json::Object(_) => write_canonical(value, out),
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
fn write_string(value: &str, out: &mut String) {
|
|
457
|
+
out.push('"');
|
|
458
|
+
for ch in value.chars() {
|
|
459
|
+
match ch {
|
|
460
|
+
'"' => out.push_str("\\\""),
|
|
461
|
+
'\\' => out.push_str("\\\\"),
|
|
462
|
+
'\u{0008}' => out.push_str("\\b"),
|
|
463
|
+
'\u{000c}' => out.push_str("\\f"),
|
|
464
|
+
'\n' => out.push_str("\\n"),
|
|
465
|
+
'\r' => out.push_str("\\r"),
|
|
466
|
+
'\t' => out.push_str("\\t"),
|
|
467
|
+
other if (other as u32) < 0x20 => {
|
|
468
|
+
out.push_str(&format!("\\u{:04x}", other as u32));
|
|
469
|
+
}
|
|
470
|
+
other => out.push(other),
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
out.push('"');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
#[cfg(test)]
|
|
477
|
+
mod tests {
|
|
478
|
+
use super::*;
|
|
479
|
+
|
|
480
|
+
#[test]
|
|
481
|
+
fn line_separators_stay_literal() {
|
|
482
|
+
let value = Json::String("\u{2028}\u{2029}".into());
|
|
483
|
+
assert_eq!(canonicalize(&value), "\"\u{2028}\u{2029}\"");
|
|
484
|
+
assert_eq!(stringify(&value), "\"\u{2028}\u{2029}\"");
|
|
485
|
+
}
|
|
486
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//! Native evaluate and verify for public KAR V5 conformance vectors.
|
|
2
|
+
//! Authoring, coverage reports, Cortex, and composition stay in TypeScript.
|
|
3
|
+
|
|
4
|
+
mod closure;
|
|
5
|
+
mod cover;
|
|
6
|
+
mod evaluate;
|
|
7
|
+
mod json;
|
|
8
|
+
mod model;
|
|
9
|
+
mod session;
|
|
10
|
+
|
|
11
|
+
pub use session::{
|
|
12
|
+
create_kar_dossier_v5, evaluate_kar_v5, render_kar_dossier, verify_kar_dossier_v5,
|
|
13
|
+
verify_kar_v5, KarDossier, KarEvaluation, KarRunError, KarVerification,
|
|
14
|
+
};
|