eyeprolog 1.6.15 → 1.6.16

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
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.6.15",
6
+ "version": "1.6.16",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -351,8 +351,10 @@ export function checkProofDocument(program, text) {
351
351
  failures.push({ condition: 'C4', conclusion: key(claim), detail: 'claimed, but no step concludes it' });
352
352
  }
353
353
  }
354
+ let uses = 0;
354
355
  for (const step of steps) {
355
356
  for (const use of step.uses) {
357
+ uses++;
356
358
  if (byConclusion.has(key(use)) || given.has(key(use))) continue;
357
359
  failures.push({ condition: 'C4', conclusion: key(step.conclusion), detail: `uses ${key(use)}, which nothing concludes and the program does not give` });
358
360
  }
@@ -360,7 +362,57 @@ export function checkProofDocument(program, text) {
360
362
 
361
363
  checkWellFounded(byConclusion, failures);
362
364
 
363
- return { valid: failures.length === 0, steps: steps.length, verified, redecided, trusted, failures, claims: claims.length };
365
+ const failed = (condition) => failures.filter((failure) => failure.condition === condition).length;
366
+ // What each condition covered, so a reader can see the shape of the check
367
+ // rather than only its verdict. A condition that examined nothing says so:
368
+ // "0 steps" is information, not a pass.
369
+ const conditions = [
370
+ { id: 'C1', name: 'Resolution', covered: verified, failed: failed('C1'),
371
+ summary: `${verified} step(s) re-performed against the source clause they cite` },
372
+ { id: 'C2', name: 'Well-founded', covered: steps.length, failed: failed('C2'),
373
+ summary: `${steps.length} step(s) checked for a conclusion resting on its own derivation` },
374
+ { id: 'C3', name: 'Justification', covered: steps.length, failed: failed('C3'),
375
+ summary: `${steps.length} step(s) carrying exactly one known justification` },
376
+ { id: 'C4', name: 'Coverage', covered: claims.length + uses, failed: failed('C4'),
377
+ summary: `${claims.length} claim(s) and ${uses} use(s) resolved to a step or to a statement the program gives` },
378
+ { id: 'C5', name: 'Re-decision', covered: redecided, failed: failed('C5'),
379
+ summary: `${redecided} step(s) recomputed against a program holding no clause of the theory` },
380
+ ];
381
+
382
+ return {
383
+ valid: failures.length === 0,
384
+ steps: steps.length,
385
+ verified,
386
+ redecided,
387
+ uses,
388
+ trusted,
389
+ failures,
390
+ claims: claims.length,
391
+ conditions,
392
+ };
393
+ }
394
+
395
+ // The condition-by-condition account, as lines. The obligations are listed
396
+ // under C5 because that is the condition they escaped: a step recorded as
397
+ // `builtin` that recomputation could not decide, or one whose justification
398
+ // puts it outside recomputation altogether.
399
+ export function conditionReport(report) {
400
+ const lines = [];
401
+ for (const condition of report.conditions ?? []) {
402
+ const mark = condition.failed > 0 ? `${condition.failed} failure(s)` : 'ok';
403
+ lines.push(` ${condition.id} ${condition.name.padEnd(13)} ${mark.padEnd(14)} ${condition.summary}`);
404
+ }
405
+ const obligations = report.trusted ?? [];
406
+ if (obligations.length > 0) {
407
+ const byReason = new Map();
408
+ for (const item of obligations) {
409
+ const reason = item.reason ?? item.kind;
410
+ byReason.set(reason, (byReason.get(reason) ?? 0) + 1);
411
+ }
412
+ const parts = [...byReason].map(([reason, count]) => `${count} ${reason}`).join(', ');
413
+ lines.push(` -- ${obligations.length} obligation(s) the check rests on rather than establishes: ${parts}`);
414
+ }
415
+ return lines;
364
416
  }
365
417
 
366
418
  // The program's clauses, numbered from 1 in load order -- the numbering
package/src/cli.js CHANGED
@@ -252,16 +252,20 @@ export async function main(argv) {
252
252
  }
253
253
 
254
254
  if (options.checkProof != null) {
255
- const { checkProofDocument, verdict } = await import('./check-proof.js');
255
+ const { checkProofDocument, conditionReport, verdict } = await import('./check-proof.js');
256
256
  const proofText = await fs.readFile(options.checkProof, 'utf8');
257
257
  const report = checkProofDocument(program, proofText);
258
258
  if (report.steps === 0) throw new Error(`no step/4 proof step found in ${options.checkProof}`);
259
259
  if (!report.valid) {
260
+ // The conditions go to stderr alongside the failures, so a failing run
261
+ // still says which of the five objected and which held.
262
+ for (const line of conditionReport(report)) process.stderr.write(`${line}\n`);
260
263
  for (const failure of report.failures.slice(0, 5)) {
261
264
  process.stderr.write(` [${failure.condition}] ${failure.conclusion} -- ${failure.detail}\n`);
262
265
  }
263
266
  throw new Error(`${options.checkProof} is not a valid proof for this program: ${report.failures.length} failure(s)`);
264
267
  }
268
+ if (!options.quiet) for (const line of conditionReport(report)) process.stdout.write(`${line}\n`);
265
269
  process.stdout.write(`${verdict(report)}.\n`);
266
270
  return;
267
271
  }
@@ -396,7 +400,10 @@ Input:
396
400
  Options:
397
401
  -h, --help Show this help text and exit.
398
402
  -p, --proof Enable proof explanations.
399
- --proof-detail mode Use abstract or expanded proof detail (implies --proof).
403
+ --proof-detail mode abstract stops at a bundled library predicate and records
404
+ it as one builtin step; expanded explains through it,
405
+ as ordinary source steps. Only differs for a program
406
+ that calls a library. (implies --proof)
400
407
  --check-proof file Check a saved proof document against the input program.
401
408
  -q, --quads Run embedded quad tests and fail if any do not hold.
402
409
  Note: -q is quads, not quiet; --quiet has no short form.
@@ -4348,15 +4348,28 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
4348
4348
  fs.writeFileSync(proofFile, generated.stdout);
4349
4349
  const verified = runCli(['--check-proof', proofFile, programFile]);
4350
4350
  assertEqual(verified.status, 0, 'verification status');
4351
- assertEqual(verified.stdout, 'checked: 2 steps.\n', 'verification stdout');
4351
+ // The run accounts for all five conditions and then gives its verdict,
4352
+ // so a reader sees which conditions the result rests on rather than
4353
+ // only that it passed.
4354
+ for (const condition of ['C1 Resolution', 'C2 Well-founded', 'C3 Justification', 'C4 Coverage', 'C5 Re-decision']) {
4355
+ assertIncludes(verified.stdout, condition, `verification reports ${condition}`);
4356
+ }
4357
+ assertEqual(verified.stdout.trimEnd().split('\n').pop(), 'checked: 2 steps.', 'verification verdict');
4358
+ assertEqual(verified.stdout.includes('obligation'), false, 'nothing was left trusted');
4359
+ const quiet = runCli(['--quiet', '--check-proof', proofFile, programFile]);
4360
+ assertEqual(quiet.stdout, 'checked: 2 steps.\n', 'quiet verification stdout is the verdict alone');
4352
4361
  const strictVerified = runCli(['--iso-strict', '--check-proof', proofFile, programFile]);
4353
4362
  assertEqual(strictVerified.status, 0, 'strict verification status');
4354
- assertEqual(strictVerified.stdout, 'checked: 2 steps.\n', 'strict verification stdout');
4363
+ assertEqual(strictVerified.stdout.trimEnd().split('\n').pop(), 'checked: 2 steps.', 'strict verification verdict');
4355
4364
  const tamperedFile = path.join(temp.dir, `proof-certificate-bad-${++temp.counter}.pl`);
4356
4365
  fs.writeFileSync(tamperedFile, generated.stdout.replace('step(p(a),', 'step(p(b),'));
4357
4366
  const rejected = runCli(['--check-proof', tamperedFile, programFile]);
4358
4367
  assertEqual(rejected.status, 1, 'tampered verification status');
4359
4368
  assertIncludes(rejected.stderr, 'is not a valid proof for this program', 'tampered verification stderr');
4369
+ // A failing run still accounts for every condition, so the report says
4370
+ // which one objected rather than only that something did.
4371
+ assertIncludes(rejected.stderr, 'C1 Resolution', 'tampered run still reports the conditions');
4372
+ assertIncludes(rejected.stderr, 'failure(s)', 'tampered run marks the objecting condition');
4360
4373
  },
4361
4374
  },
4362
4375
  {
@@ -1538,10 +1538,14 @@ Checking re-performs each step against the clause it cites: the clause must
1538
1538
  exist, its variables bound as the step recorded them must yield exactly this
1539
1539
  conclusion from exactly these uses, every claim must have a step, every use
1540
1540
  must resolve to a step or to a statement the program gives, and no conclusion
1541
- may rest on itself. `builtin`, `absent`, `collected` and `asserted` steps are
1542
- trusted rather than checked — deciding them again would mean running the
1543
- program, which is what a checker must not do — and the report says how many
1544
- there were rather than folding them into an undifferentiated success. An
1541
+ may rest on itself. A `builtin` step derives from no clause, so reading the
1542
+ document cannot decide it; it is computed again instead, against a program
1543
+ holding the bundled libraries and no clause of the theory under proof, and
1544
+ must agree. `absent`, `collected` and `asserted` steps stay trusted: the first
1545
+ two range over the theory that recomputation deliberately excludes, and a
1546
+ clause asserted at run time is in no source file to check against. The report
1547
+ counts what was verified, what was recomputed, and what remains an obligation
1548
+ rather than folding them into an undifferentiated success. An
1545
1549
  answer the solver found but the explanation cannot reproduce is recorded as
1546
1550
  `unproven`, which makes the document fail its check, because that is the
1547
1551
  truth about it.