avouch 0.1.22

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.
Files changed (54) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +356 -0
  4. package/SPEC.md +379 -0
  5. package/dist/cli/avouch.d.ts +2 -0
  6. package/dist/cli/avouch.js +68 -0
  7. package/dist/cli/check.d.ts +86 -0
  8. package/dist/cli/check.js +333 -0
  9. package/dist/cli/facts.d.ts +3 -0
  10. package/dist/cli/facts.js +157 -0
  11. package/dist/cli/gate.d.ts +3 -0
  12. package/dist/cli/gate.js +149 -0
  13. package/dist/cli/init.d.ts +21 -0
  14. package/dist/cli/init.js +282 -0
  15. package/dist/cli/jsonl.d.ts +5 -0
  16. package/dist/cli/jsonl.js +20 -0
  17. package/dist/cli/query.d.ts +2 -0
  18. package/dist/cli/query.js +138 -0
  19. package/dist/cli/reconcile.d.ts +9 -0
  20. package/dist/cli/reconcile.js +131 -0
  21. package/dist/src/checker.d.ts +3 -0
  22. package/dist/src/checker.js +1136 -0
  23. package/dist/src/contract.d.ts +135 -0
  24. package/dist/src/contract.js +3 -0
  25. package/dist/src/coverage.d.ts +65 -0
  26. package/dist/src/coverage.js +237 -0
  27. package/dist/src/evaluate.d.ts +66 -0
  28. package/dist/src/evaluate.js +535 -0
  29. package/dist/src/expr.d.ts +209 -0
  30. package/dist/src/expr.js +497 -0
  31. package/dist/src/index.d.ts +11 -0
  32. package/dist/src/index.js +11 -0
  33. package/dist/src/ledger.d.ts +96 -0
  34. package/dist/src/ledger.js +241 -0
  35. package/dist/src/markdown.d.ts +66 -0
  36. package/dist/src/markdown.js +701 -0
  37. package/dist/src/patch.d.ts +6 -0
  38. package/dist/src/patch.js +37 -0
  39. package/dist/src/query.d.ts +333 -0
  40. package/dist/src/query.js +739 -0
  41. package/dist/src/reconcile.d.ts +61 -0
  42. package/dist/src/reconcile.js +151 -0
  43. package/dist/src/stage1-check.d.ts +31 -0
  44. package/dist/src/stage1-check.js +329 -0
  45. package/dist/src/touched.d.ts +1 -0
  46. package/dist/src/touched.js +9 -0
  47. package/dist/src/unknown.d.ts +10 -0
  48. package/dist/src/unknown.js +173 -0
  49. package/docs/adapter-guide.md +187 -0
  50. package/docs/markdown-convention.md +635 -0
  51. package/ontology.schema.json +1757 -0
  52. package/package.json +57 -0
  53. package/templates/agents-block.md +12 -0
  54. package/vocab.ttl +524 -0
@@ -0,0 +1,131 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { parse } from "yaml";
4
+ import { isObj } from "../src/expr.js";
5
+ import { reconcile } from "../src/reconcile.js";
6
+ import { resolveProject } from "./check.js";
7
+ import { jsonLines } from "./jsonl.js";
8
+ export const RECONCILE_USAGE = "usage: avouch reconcile [-f <ontology.yaml>] [--config <avouch.config.json>] --cases <cases.jsonl>";
9
+ const REASONS = ["missing", "unknown_param", "type", "not_found"];
10
+ // Options in any position. Returns the positional arguments and the value of each option.
11
+ export function parseOptions(args, values, flags) {
12
+ const pos = [];
13
+ const opts = new Map();
14
+ for (let i = 0; i < args.length; i++) {
15
+ const a = args[i];
16
+ if (opts.has(a))
17
+ throw new Error(`repeated option ${a}`);
18
+ if (flags.includes(a))
19
+ opts.set(a, true);
20
+ else if (values.includes(a)) {
21
+ if (i + 1 >= args.length || args[i + 1] === "")
22
+ throw new Error(`${a} needs a file`);
23
+ opts.set(a, args[++i]);
24
+ }
25
+ else if (a.startsWith("-"))
26
+ throw new Error(`unknown option ${a}`);
27
+ else
28
+ pos.push(a);
29
+ }
30
+ return { pos, opts };
31
+ }
32
+ // True when --help or -h is a whole argument that is not the value of an option in `values`.
33
+ export function wantsHelp(args, values) {
34
+ for (let i = 0; i < args.length; i++) {
35
+ if (args[i] === "--help" || args[i] === "-h")
36
+ return true;
37
+ if (values.includes(args[i]))
38
+ i++;
39
+ }
40
+ return false;
41
+ }
42
+ // Exactly n positional arguments. A word of the old form (an ontology file as an argument) gets a pointer to -f.
43
+ export function expectPositionals(pos, n) {
44
+ if (pos.length === n)
45
+ return;
46
+ const old = pos.find((a) => /\.ya?ml$/.test(a));
47
+ throw new Error(old !== undefined ? `unexpected argument ${old}: give the ontology with -f` : `expected ${n} argument(s), found ${pos.length}`);
48
+ }
49
+ export function readOntology(file) {
50
+ const ont = parse(readFileSync(resolve(file), "utf8"));
51
+ if (!isObj(ont))
52
+ throw new Error(`${file}: the root is not a mapping`);
53
+ return ont;
54
+ }
55
+ // The shape of a RuntimeCase (SPEC section 9). Extra keys are allowed; the snapshot content is not checked.
56
+ function checkCase(x) {
57
+ if (!isObj(x))
58
+ return "case is not an object";
59
+ const c = x;
60
+ if (typeof c.id !== "string" || c.id === "")
61
+ return "id is not a non-empty string";
62
+ if (typeof c.action !== "string" || c.action === "")
63
+ return "action is not a non-empty string";
64
+ if (!Object.hasOwn(c, "snapshot"))
65
+ return "snapshot is missing";
66
+ if (c.snapshot !== null && !isObj(c.snapshot))
67
+ return "snapshot is not null or an object";
68
+ const i = c.inputs;
69
+ if (!isObj(i))
70
+ return "inputs is not an object";
71
+ if (!isObj(i.bindings))
72
+ return "inputs.bindings is not an object";
73
+ if (!isObj(i.actor))
74
+ return "inputs.actor is not an object";
75
+ if (typeof i.actor.id !== "string")
76
+ return "inputs.actor.id is not a string";
77
+ if (typeof i.actor.kind !== "string")
78
+ return "inputs.actor.kind is not a string";
79
+ if (!Array.isArray(i.actor.keys) || !i.actor.keys.every((k) => typeof k === "string"))
80
+ return "inputs.actor.keys is not a list of strings";
81
+ if (i.scope !== null && typeof i.scope !== "string")
82
+ return "inputs.scope is not a string or null";
83
+ if (typeof i.now !== "number" || !Number.isFinite(i.now))
84
+ return "inputs.now is not a finite number";
85
+ for (const k of ["reported", "expected"]) {
86
+ if (!Object.hasOwn(c, k))
87
+ return `${k} is missing`;
88
+ const r = c[k];
89
+ if (r === null)
90
+ continue;
91
+ if (!isObj(r))
92
+ return `${k} is not null or an object`;
93
+ if (typeof r.result !== "string" || r.result === "")
94
+ return `${k}.result is not a non-empty string`;
95
+ if (r.reason !== undefined && !REASONS.includes(r.reason))
96
+ return `${k}.reason is not one of ${REASONS.join(", ")}`;
97
+ }
98
+ return null;
99
+ }
100
+ async function main(args) {
101
+ const { pos, opts } = parseOptions(args, ["-f", "--config", "--cases"], []);
102
+ expectPositionals(pos, 0);
103
+ const cases = opts.get("--cases");
104
+ if (typeof cases !== "string")
105
+ throw new Error("--cases is required");
106
+ const ont = readOntology(resolveProject({ file: opts.get("-f"), config: opts.get("--config"), check: false }).file);
107
+ const lines = jsonLines(cases);
108
+ for (const { line, value } of lines) {
109
+ const problem = checkCase(value);
110
+ if (problem !== null)
111
+ throw new Error(`${cases}:${line}: ${problem}`);
112
+ }
113
+ const rows = reconcile(ont, lines.map((x) => x.value));
114
+ process.stdout.write(rows.map((r) => `${JSON.stringify(r)}\n`).join(""));
115
+ return 0;
116
+ }
117
+ // Exit 0: one row per case (the command does not judge the rows); 2: a usage, file, parse or shape error (one stderr line).
118
+ export async function runReconcile(args) {
119
+ if (wantsHelp(args, ["-f", "--config", "--cases"])) {
120
+ console.log(RECONCILE_USAGE);
121
+ return 0;
122
+ }
123
+ try {
124
+ return await main(args);
125
+ }
126
+ catch (error) {
127
+ const msg = (error instanceof Error ? error.message : String(error)).replace(/[\r\n]+/g, " ");
128
+ console.error(`${msg}; ${RECONCILE_USAGE}`);
129
+ return 2;
130
+ }
131
+ }
@@ -0,0 +1,3 @@
1
+ import { IDENT, type Report, type SourceFacts } from "./contract.js";
2
+ export { IDENT };
3
+ export declare function check(ontology: unknown, facts: SourceFacts): Report;