@mark1russell7/coeval 0.24.0 → 0.25.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/coeval",
4
- "version": "0.24.0",
4
+ "version": "0.25.0",
5
5
  "description": "coeval: the kernel of declarative languages. Documents in envelopes, a ladder that finds the evaluator, kind contracts, compiled plans, a static kit, a formula kit and a run side with capabilities.",
6
6
  "type": "module",
7
7
  "main": "./build/index.js",
@@ -36,8 +36,8 @@
36
36
  ],
37
37
  "devDependencies": {
38
38
  "@mark1russell7/cue": "^2.1.1",
39
- "@mark1russell7/rigor-bench": "^0.2.15",
40
- "@mark1russell7/rigor-statistics": "^0.1.12",
39
+ "@mark1russell7/rigor-bench": "^0.2.29",
40
+ "@mark1russell7/rigor-statistics": "^0.1.16",
41
41
  "@types/node": "^26.6.4",
42
42
  "esbuild": "^0.28.2",
43
43
  "typescript": "^7.0.2",
package/src/agree.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import { type Key, docEquals, equalWith } from "./doc.ts";
2
2
  import { type Context, isHostThing } from "./deref.ts";
3
- import { type EvalError, type Result, isStackOverflow, thrown } from "./errors.ts";
4
- import { type Effect, type Machine, type Perform, type Read, JOURNAL, evaluate, makeContext, prepare, run, sharedCopy } from "./evaluate.ts";
3
+ import { type EvalError, type Result, describe, isStackOverflow, thrown } from "./errors.ts";
4
+ import { type CheckOptions, typeCheck } from "./static.ts";
5
+ import { isPrecise, member, typeText } from "./types.ts";
6
+ import { boundariesOf } from "./typing.ts";
7
+ import { type Effect, type Machine, type Perform, type Read, type TraceNode, JOURNAL, evaluate, makeContext, prepare, run, sharedCopy } from "./evaluate.ts";
5
8
  import { Held, isClosure } from "./formula.ts";
6
9
  import { type Definitions, reference, referenceRun } from "./reference.ts";
7
10
  import { type ReplaySettle, recorder, relay, replayer } from "./replay.ts";
@@ -392,6 +395,15 @@ function resolutions(reads: readonly Read[]): readonly Read[] {
392
395
  return reads.filter((read) => read.free && read.kept !== true);
393
396
  }
394
397
 
398
+ /** This function gives the text of a value of a trace. It does not throw for a bigint or a record with a cycle. */
399
+ function valueText(value: unknown): string {
400
+ try {
401
+ return JSON.stringify(value) ?? describe(value);
402
+ } catch {
403
+ return describe(value);
404
+ }
405
+ }
406
+
395
407
  function pathText(path: readonly unknown[] | undefined): string {
396
408
  if (path === undefined) return "no read";
397
409
  try {
@@ -537,3 +549,94 @@ export async function agreeRun(machine: Machine, doc: unknown, context: Context
537
549
  }
538
550
  return compared === undefined ? { ok: true, result, effects: first.effects } : { ok: true, result, effects: first.effects, reads: compared };
539
551
  }
552
+
553
+ /** The result of `agreeTypes`: the agreement of a check of types and a traced evaluation (STATIC.TYPES-AGREE). */
554
+ export type TypeAgreement =
555
+ | {
556
+ readonly ok: true;
557
+ /** `false` when the document has an issue, or a value breaks a position of an imprecise type. The rule then tells nothing. */
558
+ readonly checked: boolean;
559
+ /** The number of the values of the trace that the rule examined. */
560
+ readonly values: number;
561
+ }
562
+ | {
563
+ readonly ok: false;
564
+ /** `value`: a value is not in the type of its place. `argument`: it is not in the type that its position needs. `error`: a type error of the kernel. */
565
+ readonly clause: "value" | "argument" | "error";
566
+ /** The keys of the place, as texts. */
567
+ readonly place: readonly string[];
568
+ readonly reason: string;
569
+ };
570
+
571
+ /** The options of `agreeTypes`: the options of `check`, and `guards`. */
572
+ export interface AgreeTypesOptions extends CheckOptions {
573
+ /** `true`: the evaluation tests each boundary (PLAN.GUARD). The rule then holds for each context. The default is `false`. */
574
+ readonly guards?: boolean;
575
+ }
576
+
577
+ /** The type errors of the kernel, by the evaluator that gives them (STATIC.TYPES-AGREE). */
578
+ const TYPE_ERRORS: Readonly<Record<string, string>> = {
579
+ "formula.op": "#NAME?",
580
+ "formula.if": "#VALUE!",
581
+ "formula.and": "#VALUE!",
582
+ "formula.or": "#VALUE!",
583
+ };
584
+
585
+ /**
586
+ * This function examines the agreement rule for types (STATIC.TYPES-AGREE, D148). A document with no issue evaluates
587
+ * with a trace. Each value of the trace is a member of the type of its place. A value of a precise type is also a member
588
+ * of the type that its position needs. No document with precise parts gives a type error of the kernel.
589
+ *
590
+ * A language imports this function for its own property tests, with `agree`. A failure tells that a signature, a type
591
+ * rule, a cover or a root type does not hold. A value that breaks a position of an imprecise type ends the promise, and
592
+ * the result then has `checked: false` (TYPE.GRADUAL).
593
+ *
594
+ * @param machine - The world and the code of the evaluators, with their type rules.
595
+ * @param doc - The document.
596
+ * @param context - The context. Its roots are members of the root types.
597
+ * @param options - The options of `check`, with the root types, and `guards`.
598
+ * @returns The agreement, or the first place that breaks it.
599
+ */
600
+ export function agreeTypes(machine: Machine, doc: unknown, context: Context = makeContext(), options: AgreeTypesOptions = {}): TypeAgreement {
601
+ const typing = typeCheck(machine, doc, options);
602
+ if (typing.issues.length > 0) return { ok: true, checked: false, values: 0 };
603
+ const keyOf = (path: readonly unknown[]): string => JSON.stringify(path.map(String));
604
+ const places = new Map(typing.places.map((item) => [keyOf(item.place), item]));
605
+ const domains = machine.domains;
606
+ const guards = options.guards === true ? boundariesOf(typing, domains) : undefined;
607
+ const given = { ...(options.declaredType === undefined ? {} : { declaredType: options.declaredType }), ...(guards === undefined ? {} : { guards }) };
608
+ const stack: TraceNode[] = [evaluate(machine, doc, context, given).trace];
609
+ const nodes: TraceNode[] = [];
610
+ while (stack.length > 0) {
611
+ const node = stack.pop() as TraceNode;
612
+ nodes.push(node);
613
+ stack.push(...node.children);
614
+ }
615
+ // A value that breaks a position of an imprecise type leaves the promise of each later type (K28).
616
+ for (const node of nodes) {
617
+ const place = places.get(keyOf(node.path));
618
+ if (place?.expected === undefined || !node.result.ok || isPrecise(place.type, domains)) continue;
619
+ if (!member(node.result.value, place.expected, domains)) return { ok: true, checked: false, values: 0 };
620
+ }
621
+ // The walk goes from the end, thus a part comes before the kind that reads it, and a finding names the origin.
622
+ let values = 0;
623
+ for (const node of nodes.reverse()) {
624
+ const place = places.get(keyOf(node.path));
625
+ if (place === undefined) continue;
626
+ const at = node.path.map(String);
627
+ if (!node.result.ok) {
628
+ const parts = node.children.map((child) => places.get(keyOf(child.path)));
629
+ const decided = node.children.every((child) => child.result.ok) && parts.every((part) => part?.expected !== undefined && isPrecise(part.type, domains));
630
+ const code = TYPE_ERRORS[node.answer.evaluator ?? ""];
631
+ if (decided && node.result.error.code === code) return { ok: false, clause: "error", place: at, reason: `The document gives ${code}, but it checks with no issue.` };
632
+ continue;
633
+ }
634
+ const value = node.result.value;
635
+ values += 1;
636
+ if (!member(value, place.type, domains)) return { ok: false, clause: "value", place: at, reason: `The value ${valueText(value)} is not a member of ${typeText(place.type)}.` };
637
+ if (place.expected !== undefined && !member(value, place.expected, domains)) {
638
+ return { ok: false, clause: "argument", place: at, reason: `The value ${valueText(value)} is not a member of the needed type ${typeText(place.expected)}.` };
639
+ }
640
+ }
641
+ return { ok: true, checked: true, values };
642
+ }
package/src/evaluate.ts CHANGED
@@ -2,8 +2,8 @@ import { type Envelope, type Json, type Key, type TypeRef, isEnvelope, isKey, ty
2
2
  import { type PartSlot, type Split, partsOf, pathMatches, scopesOf } from "./parts.ts";
3
3
  import { answerOf, ask, type Answer } from "./ladder.ts";
4
4
  import type { CapsRule, Kind, KindFacts, Side } from "./world.ts";
5
- import type { Domain } from "./types.ts";
6
- import type { TypeRule } from "./typing.ts";
5
+ import { type Domain, type ValueType, member, typeText } from "./types.ts";
6
+ import type { Boundary, TypeRule } from "./typing.ts";
7
7
  import type { StaticRead } from "./facts.ts";
8
8
  import { type ErrorCode, type EvalError, type Result, Failure, fail, ok, placed, thrown, describe } from "./errors.ts";
9
9
  import {
@@ -993,6 +993,8 @@ export interface PlanRunOptions {
993
993
  export interface RunOptions extends PlanRunOptions {
994
994
  /** The type of a bare root value. */
995
995
  readonly declaredType?: string;
996
+ /** The boundaries that test their values (PLAN.GUARD). The default is none. */
997
+ readonly guards?: readonly Boundary[];
996
998
  /** The verb of `run`, for example `format`. The default is `eval` (VERB.PREPARE). */
997
999
  readonly verb?: string;
998
1000
  }
@@ -1023,6 +1025,8 @@ export interface EvaluateOptions {
1023
1025
  readonly perform?: Perform;
1024
1026
  /** A list that gets each read, in the order of the resolutions (READS.RECORD). */
1025
1027
  readonly reads?: Read[];
1028
+ /** The boundaries that test their values (PLAN.GUARD). The default is none. */
1029
+ readonly guards?: readonly Boundary[];
1026
1030
  }
1027
1031
 
1028
1032
  /** The options of a resolution without a record of hops, and with one. One object of each serves each call. */
@@ -1700,6 +1704,11 @@ export interface PrepareOptions {
1700
1704
  * the code without the fold. A plan without folds ignores this option.
1701
1705
  */
1702
1706
  readonly fixed?: { readonly names?: Readonly<Record<string, unknown>> };
1707
+ /**
1708
+ * The boundaries of the document, from `boundariesOf` (PLAN.GUARD, D149). A value that is not a member of the type of
1709
+ * its boundary gives `#VALUE!` at that place. The default is none, and a plan then has no test.
1710
+ */
1711
+ readonly guards?: readonly Boundary[];
1703
1712
  }
1704
1713
 
1705
1714
  /** A compiled document. It evaluates many times, and it does the work of the ladder one time. */
@@ -1740,6 +1749,8 @@ interface Compiler {
1740
1749
  readonly fold: boolean;
1741
1750
  /** The fixed names and their values, or `undefined` when the plan has none (PLAN.FIXED). */
1742
1751
  readonly fixed: Readonly<Record<string, unknown>> | undefined;
1752
+ /** The types of the boundaries by their place, or `undefined` when the plan has no guard (PLAN.GUARD). */
1753
+ readonly guards: ReadonlyMap<string, ValueType> | undefined;
1743
1754
  /** The runtime of the folds of this compilation. The first fold makes it. */
1744
1755
  probe?: Probe;
1745
1756
  }
@@ -1885,7 +1896,8 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
1885
1896
  } catch (error) {
1886
1897
  return failingWith(c, meta, placed(thrown(error, `the runner ${name}`), place));
1887
1898
  }
1888
- const run = placedRun(inner, place, name);
1899
+ const guard = guardOf(c, place);
1900
+ const run = placedRun(guard === undefined ? inner : guardRun(inner, guard, c.machine.domains), place, name);
1889
1901
  return finish(c, meta, runnerCode(run, place, kindText), run);
1890
1902
  }
1891
1903
 
@@ -1895,8 +1907,9 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
1895
1907
  } catch (error) {
1896
1908
  return failingWith(c, meta, placed(thrown(error, `the evaluator ${name}`), place));
1897
1909
  }
1910
+ const guard = guardOf(c, place);
1898
1911
  // Code that cannot fail needs no place and no catch, and it does not read the context of its grant (PLAN.TOTAL).
1899
- const done = finish(c, meta, TOTAL.has(inner) ? inner : placedCode(inner, place, name), undefined);
1912
+ const done = finish(c, meta, guard !== undefined ? placedCode(guardCode(inner, guard, c.machine.domains), place, name) : TOTAL.has(inner) ? inner : placedCode(inner, place, name), undefined);
1900
1913
  if (frees === undefined || kind === undefined || !pureCode(c, envelope, kind, typePath, name, quick)) return done;
1901
1914
  const free = freeOf(doc, kind, split.parts, frees);
1902
1915
  if (free === undefined) return done;
@@ -2308,6 +2321,34 @@ function gated(rules: readonly CapsRule[], part: Compiled): Compiled {
2308
2321
  * This function gives the steps of a runner, with the place of each error and of each exception. The steps get the
2309
2322
  * context that its grant permits (CAP.NARROW-CODE).
2310
2323
  */
2324
+ /** This function gives the key of a place in the guards of a plan. */
2325
+ function guardKey(place: readonly Key[]): string {
2326
+ return JSON.stringify(place.map(String));
2327
+ }
2328
+
2329
+ /** This function gives the type of the boundary at a place, when the plan has one (PLAN.GUARD). */
2330
+ function guardOf(c: Compiler, place: readonly Key[]): ValueType | undefined {
2331
+ return c.guards?.get(guardKey(place));
2332
+ }
2333
+
2334
+ /** This function gives the result of a part of a boundary: a value that is not a member gives `#VALUE!` (PLAN.GUARD). */
2335
+ function guarded(result: Result, type: ValueType, domains: Readonly<Record<string, Domain>> | undefined): Result {
2336
+ if (!result.ok || member(result.value, type, domains)) return result;
2337
+ return fail("#VALUE!", `The value is not a member of ${typeText(type)}, the type of its boundary.`);
2338
+ }
2339
+
2340
+ /** This function adds the test of a boundary to the code of a part. */
2341
+ function guardCode(inner: Code, type: ValueType, domains: Readonly<Record<string, Domain>> | undefined): Code {
2342
+ return (ctx, rt) => guarded(inner(ctx, rt), type, domains);
2343
+ }
2344
+
2345
+ /** This function adds the test of a boundary to the steps of a part. */
2346
+ function guardRun(inner: RunCode, type: ValueType, domains: Readonly<Record<string, Domain>> | undefined): RunCode {
2347
+ return function* (ctx, rt) {
2348
+ return guarded(yield* inner(ctx, rt), type, domains);
2349
+ };
2350
+ }
2351
+
2311
2352
  function placedRun(inner: RunCode, place: readonly Key[], name: string): RunCode {
2312
2353
  return function* (ctx, rt) {
2313
2354
  try {
@@ -2520,7 +2561,8 @@ export function prepare(machine: Machine, doc: unknown, options: PrepareOptions
2520
2561
  const traced = options.trace === true;
2521
2562
  const fold = !traced && options.fold !== false;
2522
2563
  const fixed = fold ? fixedNamesOf(options.fixed?.names) : undefined;
2523
- const c: Compiler = { machine, traced, maxDepth: machine.maxDepth ?? 1000, verb, noAnswer, fold, fixed };
2564
+ const guards = options.guards === undefined || options.guards.length === 0 ? undefined : new Map(options.guards.map((each) => [guardKey(each.place), each.type]));
2565
+ const c: Compiler = { machine, traced, maxDepth: machine.maxDepth ?? 1000, verb, noAnswer, fold, fixed, guards };
2524
2566
  let root: Compiled;
2525
2567
  try {
2526
2568
  root = compileNode(c, doc, [], options.declaredType, 0);
@@ -2643,6 +2685,7 @@ export function evaluate(machine: Machine, doc: unknown, context: Context = make
2643
2685
  trace: true,
2644
2686
  ...(declaredType === undefined ? {} : { declaredType }),
2645
2687
  ...(given.verb === undefined ? {} : { verb: given.verb }),
2688
+ ...(given.guards === undefined ? {} : { guards: given.guards }),
2646
2689
  });
2647
2690
  const ctx = rootContext(machine, context);
2648
2691
  const s = new Session(machine, ctx.caps, false, true, given.perform, undefined, undefined, readingOf(given.reads, ctx));
@@ -2665,6 +2708,7 @@ export async function run(machine: Machine, doc: unknown, context: Context = mak
2665
2708
  const plan = prepare(machine, doc, {
2666
2709
  trace: true,
2667
2710
  ...(options.declaredType === undefined ? {} : { declaredType: options.declaredType }),
2711
+ ...(options.guards === undefined ? {} : { guards: options.guards }),
2668
2712
  ...(options.verb === undefined ? {} : { verb: options.verb }),
2669
2713
  });
2670
2714
  const ctx = rootContext(machine, context);
package/src/formula.ts CHANGED
@@ -5,8 +5,8 @@ import { type CompiledPath, type Context, type Resolution, compilePath, valueOfT
5
5
  import { type EvalError, type Failed, type Result, Failure, fail, ok, thrown } from "./errors.ts";
6
6
  import { type Code, type CompileInput, type CompiledEvaluator, type CompiledPart, type Evaluator, type Machine, type Runtime, resolveCompiled, totalCode } from "./evaluate.ts";
7
7
  import { type MachinePart, mergeMachines } from "./merge.ts";
8
- import { type Domain, type ValueType, HEADER_KEYS, STD_DOMAINS, callable, fieldOf, fits, fnOf, holdsEnvelope, isPrecise, isUnknown, itemOf, join, member, stepType, typeIssues, typeText } from "./types.ts";
9
- import type { Declared, PartType, TypeInput, TypeRule } from "./typing.ts";
8
+ import { type Domain, type Proof, type ValueType, HEADER_KEYS, STD_DOMAINS, callable, fieldOf, fits, fnOf, holdsEnvelope, isPrecise, isUnknown, itemOf, join, member, stepType, typeIssues, typeText } from "./types.ts";
9
+ import type { Declared, PartType, Proven, TypeInput, TypeRule } from "./typing.ts";
10
10
  import { decodeDomain, domainTypeOf } from "./codec.ts";
11
11
  import { type StaticRead, factsOf } from "./facts.ts";
12
12
 
@@ -80,8 +80,7 @@ export interface FormulaProfile {
80
80
 
81
81
  /**
82
82
  * The signature of an operation: the value types of its parameters and of its result (TYPE.SIGNATURE). A parameter at
83
- * the end of `params` is optional when `undefined` is a member of its type, for example `unknown`. A later version can add a key, for example the type that a
84
- * true result proves.
83
+ * the end of `params` is optional when `undefined` is a member of its type, for example `unknown`. A later version can add a key.
85
84
  */
86
85
  export interface Signature {
87
86
  /** The types of the parameters, in order. */
@@ -90,8 +89,21 @@ export interface Signature {
90
89
  readonly rest?: ValueType;
91
90
  /** The type of the result. */
92
91
  readonly to: ValueType;
92
+ /**
93
+ * The type that a true result proves of the first argument, and a false result refutes (STATIC.OCCURRENCE). The
94
+ * operation gives a true result for each member of the type, and for no other value.
95
+ */
96
+ readonly proves?: ValueType;
97
+ /** `true` when a true result tells that the first argument has a value, and a false result that it has none. */
98
+ readonly present?: boolean;
93
99
  }
94
100
 
101
+ /** The most proofs that one test keeps. A proof only adds knowledge, thus the check can leave one out. */
102
+ const MAX_PROOFS = 32;
103
+
104
+ /** The result of a test that proves nothing. */
105
+ const NO_PROOFS: Proven = { yes: [], no: [] };
106
+
95
107
  /** The mark of a field that the profile leaves out of a record. */
96
108
  const SKIPPED: unique symbol = /* @__PURE__ */ Symbol("skipped");
97
109
 
@@ -400,6 +412,7 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
400
412
  sig.params.forEach((param, position) => out.push({ at: [...at, "params", position], type: param, what: `parameter ${position + 1} of a signature of ops.${name}` }));
401
413
  if (sig.rest !== undefined) out.push({ at: [...at, "rest"], type: sig.rest, what: `rest parameter of a signature of ops.${name}` });
402
414
  out.push({ at: [...at, "to"], type: sig.to, what: `result of a signature of ops.${name}` });
415
+ if (sig.proves !== undefined) out.push({ at: [...at, "proves"], type: sig.proves, what: `proved type of a signature of ops.${name}` });
403
416
  const earlier = list.slice(0, index).findIndex((other) => isSignature(other) && sameParams(other, sig));
404
417
  if (earlier >= 0) out.push({ at, problem: `The overload ${index + 1} of ops.${name} has the parameters of the overload ${earlier + 1}. Thus the check never selects it.` });
405
418
  });
@@ -407,7 +420,21 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
407
420
  return out;
408
421
  };
409
422
  const logic = (word: "and" | "or"): TypeRule => ({
410
- part: (input, path) => truthPart(input, `term ${Number(path[0]) + 1} of ${word}`),
423
+ usesProofs: true,
424
+ // A later term runs only after a term that did not decide, thus it reads the proof of that result (STATIC.OCCURRENCE-LOGIC).
425
+ part: (input, path) => {
426
+ const index = Number(path[0]);
427
+ const part = truthPart(input, `term ${index + 1} of ${word}`);
428
+ if (index === 0) return part;
429
+ const before = input.provenBy([index - 1]);
430
+ return { ...part, narrow: word === "and" ? before.yes : before.no, keep: true };
431
+ },
432
+ proves: (input) => {
433
+ const each = input.paths.map((path) => input.provenBy(path));
434
+ const all = (side: "yes" | "no"): readonly Proof[] => each.flatMap((proven) => proven[side]).slice(0, MAX_PROOFS);
435
+ const one = each.length === 1 ? (each[0] as Proven) : undefined;
436
+ return word === "and" ? { yes: all("yes"), no: one?.no ?? [] } : { yes: one?.yes ?? [], no: all("no") };
437
+ },
411
438
  synth: (input) => {
412
439
  if (!logicValue) return "bool";
413
440
  // With the logic "value", the result is the term that decided, or the last term.
@@ -420,6 +447,12 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
420
447
  "formula.lit": { synth: (input) => valueType(input.doc.value) },
421
448
 
422
449
  "formula.ref": {
450
+ read: true,
451
+ reads: (input) => {
452
+ const path = input.doc.value;
453
+ if (input.address || !Array.isArray(path) || path.length === 0) return undefined;
454
+ return path.every((step): step is string => typeof step === "string") ? path : undefined;
455
+ },
423
456
  synth: (input, report) => {
424
457
  const path = input.doc.value;
425
458
  // An address gives a thing, not a value. The check gives #VALUE! for a path that is not a list.
@@ -541,9 +574,13 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
541
574
  "formula.if": {
542
575
  // The rule of if declares the truth type of the profile, thus one bad truth type gives one issue in checkWorld.
543
576
  declared: () => (declared ? [{ at: ["truthType"], type: truth, what: "truth type of the profile" }] : []),
577
+ first: "test",
578
+ usesProofs: true,
544
579
  part: (input, path) => {
545
580
  if (path[0] === "test") return truthPart(input, "test of if");
546
- return isUnknown(input.expected, input.machine.domains) ? {} : { expect: input.expected, what: `${String(path[0])} branch of if` };
581
+ const proven = input.provenBy(["test"]);
582
+ const narrow = path[0] === "then" ? proven.yes : proven.no;
583
+ return isUnknown(input.expected, input.machine.domains) ? { narrow } : { expect: input.expected, what: `${String(path[0])} branch of if`, narrow };
547
584
  },
548
585
  synth: (input) => {
549
586
  const value = input.doc.value;
@@ -557,6 +594,25 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
557
594
 
558
595
  "formula.op": {
559
596
  declared: sigsDeclared,
597
+ proves: (input) => {
598
+ // Overloads select after the check of the arguments, thus only one signature proves.
599
+ const sigs = signatures(input)?.sigs;
600
+ const sig = sigs?.length === 1 ? (sigs[0] as Signature) : undefined;
601
+ if (sig === undefined || (sig.proves === undefined && sig.present !== true) || input.paths.length === 0) return NO_PROOFS;
602
+ const path = input.readOf(input.paths[0] as readonly Key[]);
603
+ if (path === undefined) return NO_PROOFS;
604
+ const yes: Proof[] = [];
605
+ const no: Proof[] = [];
606
+ if (sig.proves !== undefined && typeIssues(input.contract(sig.proves, `proved type of ops.${nameOf(input)}`), input.machine.domains).length === 0) {
607
+ yes.push({ path, fact: "is", type: sig.proves });
608
+ no.push({ path, fact: "not", type: sig.proves });
609
+ }
610
+ if (sig.present === true) {
611
+ yes.push({ path, fact: "present" });
612
+ no.push({ path, fact: "absent" });
613
+ }
614
+ return { yes, no };
615
+ },
560
616
  part: (input, path) => {
561
617
  const read = signatures(input);
562
618
  // Only one signature gives the types of the arguments before their check. Overloads select after it.
package/src/index.ts CHANGED
@@ -79,6 +79,7 @@ export {
79
79
  } from "./world.ts";
80
80
  export {
81
81
  type DomainExtension,
82
+ type VerbEntries,
82
83
  type MachinePart,
83
84
  type MergeConflict,
84
85
  type MergePath,
@@ -197,6 +198,7 @@ export {
197
198
  STD_EVALUATORS,
198
199
  STD_MACHINE,
199
200
  STD_RUNNERS,
201
+ ifRunner,
200
202
  STD_WALKERS,
201
203
  STD_WORLD,
202
204
  WORLD_SYNTAX,
@@ -227,6 +229,7 @@ export {
227
229
  } from "./context.ts";
228
230
  export {
229
231
  type CheckOptions,
232
+ type DocChange,
230
233
  type DocVisit,
231
234
  type Issue,
232
235
  type RefUse,
@@ -237,6 +240,7 @@ export {
237
240
  type WorldOptions,
238
241
  check,
239
242
  checkWorld,
243
+ mapDocs,
240
244
  mapRefs,
241
245
  refsOf,
242
246
  typeCheck,
@@ -289,7 +293,7 @@ export {
289
293
  syntaxFormat,
290
294
  } from "./syntax.ts";
291
295
  export { type Definitions, type ReferenceOptions, type ReferenceRunOptions, STD_DEFINITIONS, formulaDefinitions, reference, referenceRun } from "./reference.ts";
292
- export { type AgreeOptions, type AgreeRunOptions, type Agreement, type Tier, type TierInput, type TierResult, agree, agreeRun, sameValue } from "./agree.ts";
296
+ export { type AgreeOptions, type AgreeRunOptions, type Agreement, type AgreeTypesOptions, type TypeAgreement, agreeTypes, type Tier, type TierInput, type TierResult, agree, agreeRun, sameValue } from "./agree.ts";
293
297
  export { type ConformOptions, type ConformProbe, type Finding, type Obligation, conforms } from "./conform.ts";
294
298
  export { sameResult } from "./same.ts";
295
299
  export { type PrintOptions, type ReadPrints, type ReadsCheck, type VerifyOptions, printReads, verifyReads } from "./reads.ts";
@@ -345,6 +349,6 @@ export {
345
349
  replayer,
346
350
  } from "./replay.ts";
347
351
  export { type GenerateOptions, type Random, generate, random, typesOf } from "./generate.ts";
348
- export { type Domain, type DomainCodec, type TypeIssue, type ValueType, STD_DOMAINS, fits, join, member, typeIssues, typeText } from "./types.ts";
349
- export { type Declared, type PartType, type RootTypes, type TypeInput, type TypeProblem, type TypeRule, type Typed, CAPS_TYPE } from "./typing.ts";
352
+ export { type Domain, type DomainCodec, type Proof, type TypeIssue, type ValueType, STD_DOMAINS, fits, join, member, typeIssues, typeText } from "./types.ts";
353
+ export { type Boundary, type Declared, type PartType, type Proven, type RootTypes, type TypeInput, type TypeProblem, type TypeRule, type Typed, CAPS_TYPE, boundariesOf } from "./typing.ts";
350
354
  export { type SampleOptions, type TypeReading, typeFromSamples, typeFromSchema } from "./providers.ts";