@sanning/proof 0.6.0 → 0.8.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/src/cli.ts CHANGED
@@ -21,6 +21,8 @@ import { fileURLToPath } from "node:url";
21
21
 
22
22
  import { isAgentProofSpec, verifyAgentProofBundle } from "./agent-proof.js";
23
23
  import type { AgentProofResult } from "./agent-proof.js";
24
+ import { CHAIN_READING_SCOPE, readChains, renderChainFinding } from "./chain.js";
25
+ import type { ChainReading } from "./chain.js";
24
26
  import { composeExport } from "./compose.js";
25
27
  import type { ComposeExportOptions } from "./compose.js";
26
28
  import { utf8 } from "./crypto.js";
@@ -167,7 +169,12 @@ export async function runCli(argv: string[], io: Cli): Promise<number> {
167
169
  ...(content ? { content } : {}),
168
170
  ...(io.fetchImpl ? { fetchImpl: io.fetchImpl } : {}),
169
171
  });
170
- const exit = reportEvidence(result, bundlePath, gateways, io);
172
+ // Read the chains from the SAME parsed bundle the verifier saw. Findings are
173
+ // evidence about the records in front of us, not a verdict: they are printed
174
+ // and they never move `exit`, because a chain is not a completeness proof and
175
+ // an exit code is exactly the kind of thing people read as one.
176
+ const chains = await readChains(parsed);
177
+ const exit = reportEvidence(result, bundlePath, gateways, io, chains);
171
178
  // Coverage is reported only once the bundle parsed far enough to have events
172
179
  // to compare against; over a MALFORMED bundle there is nothing to say.
173
180
  if (logs && result.status !== "malformed") reportLogsCoverage(logs, result, io);
@@ -664,6 +671,7 @@ function reportEvidence(
664
671
  path: string,
665
672
  gateways: string[],
666
673
  io: Cli,
674
+ chains: ChainReading,
667
675
  ): number {
668
676
  if (result.status === "malformed") {
669
677
  io.err(paint("MALFORMED", R) + ` ${path}`);
@@ -708,6 +716,8 @@ function reportEvidence(
708
716
  }
709
717
  }
710
718
 
719
+ reportChains(chains, io);
720
+
711
721
  if (result.export) {
712
722
  reportExport(result.export, io);
713
723
  }
@@ -725,6 +735,45 @@ function reportEvidence(
725
735
  return mapStatusToExit(result.status, result.errors);
726
736
  }
727
737
 
738
+ /**
739
+ * The chain block: what following each record's pointer to the record before it
740
+ * showed, over the records THIS bundle presents.
741
+ *
742
+ * Three deliberate choices, all of them about not overclaiming.
743
+ *
744
+ * 1. It prints the chains it READ, not just the findings. "3 chains, nothing to
745
+ * report" and silence are different statements, and a reader that quietly
746
+ * matched no records at all would produce the second one while covering
747
+ * nothing.
748
+ * 2. It prints the scope sentence whenever it read a chain, findings or not.
749
+ * "Delete an event and the next pointer dangles" is true; "so the bundle is
750
+ * complete" does not follow, and two customer-facing documents had already
751
+ * made that leap. The limit travels with the result or it gets dropped.
752
+ * 3. It does not touch the exit code. Findings are evidence; the exit code is
753
+ * the verdict surface, and the pinned 0/1/2/3 contract stays exactly what it
754
+ * was. A dangling link is something for a person to look at, not a machine
755
+ * decision this check is entitled to make.
756
+ */
757
+ function reportChains(reading: ChainReading, io: Cli): void {
758
+ if (reading.chains.length === 0) return;
759
+
760
+ io.out("");
761
+ io.out(paint(" Event chains", DIM));
762
+ for (const c of reading.chains) {
763
+ const where = c.namespace === undefined ? "record" : `${c.namespace} metadata`;
764
+ io.out(
765
+ paint(
766
+ ` ${c.chainKey} ${count(c.events, "record", "records")} present (linked in ${where})`,
767
+ DIM,
768
+ ),
769
+ );
770
+ }
771
+ for (const f of reading.findings) {
772
+ io.out(paint(` ! ${renderChainFinding(f, "this bundle")}`, Y));
773
+ }
774
+ io.out(paint(` ${CHAIN_READING_SCOPE}`, DIM));
775
+ }
776
+
728
777
  // Render the export-specific block (evidence-export.md §5): source-bundle
729
778
  // linkage, cached-vs-recomputed verdict agreement, and each embedded operator
730
779
  // attestation (RSA-PSS sig · operator-address binding · data_hash binding).