@mikeargento/bitgraph-audit 0.6.3 → 0.7.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/report-md.ts CHANGED
@@ -176,10 +176,10 @@ function executiveSummary(
176
176
  lines.push(
177
177
  "Proofs are grouped by the authority key, epoch, and chain they were " +
178
178
  "committed under; each group is called a partition. Every proof " +
179
- "occupies two counter positions: one reserved in advance (the slot) " +
180
- "and one at the moment of commit. Slot positions never produce " +
179
+ "occupies two counter positions: one reserved in advance " +
180
+ "and one at the moment of commit. Reserved positions never produce " +
181
181
  "stored proof files of their own, so a healthy chain shows only " +
182
- "committed proofs, each referencing its own slot position from " +
182
+ "committed proofs, each referencing its own reserved position from " +
183
183
  "inside its signed content."
184
184
  );
185
185
  lines.push("");
@@ -333,6 +333,26 @@ function executiveSummary(
333
333
  );
334
334
  lines.push("");
335
335
 
336
+ // Ceilings in time (bitgraph-ceiling/1), only when the bundle carries any.
337
+ const cl = report.ceilings;
338
+ if (cl && (cl.checks.length > 0 || cl.statuses.length > 0)) {
339
+ lines.push("### Ceilings in time (Base)");
340
+ lines.push("");
341
+ lines.push(`Checked against writer ${inlineCode(cl.writer)} on chain ${cl.chainId}.`);
342
+ lines.push("");
343
+ for (const c of cl.checks) {
344
+ if (c.status === "verified") {
345
+ const w = c.window!;
346
+ const width = w.widthSeconds != null ? `, ${w.widthSeconds} s after the floor block` : "";
347
+ lines.push(`- VERIFIED ${inlineCode(c.path)}: ${c.label ?? ""}${width}. ${c.onChainDetail ?? ""}.`);
348
+ } else {
349
+ lines.push(`- ${c.status.toUpperCase()} ${inlineCode(c.path)}: ${c.reason ?? ""}`);
350
+ }
351
+ }
352
+ for (const st of cl.statuses) lines.push(`- ${st.status} ${inlineCode(st.path)}: ${st.note}`);
353
+ lines.push("");
354
+ }
355
+
336
356
  // External time evidence.
337
357
  lines.push("### External time evidence");
338
358
  lines.push("");
@@ -412,10 +432,10 @@ function partitionSummarySentences(report: AuditJsonReport, partition: ReportPar
412
432
  const count = detail.count;
413
433
  const singular = count === "1";
414
434
  out.push(
415
- `${withCommas(count)} counter ${singular ? "position is neither a commit position nor a referenced slot position" : "positions are neither commit positions nor referenced slot positions"} ` +
435
+ `${withCommas(count)} counter ${singular ? "position is neither a commit position nor a referenced reserved position" : "positions are neither commit positions nor referenced reserved positions"} ` +
416
436
  "in the supplied bundle. An unexplained interior position may " +
417
- "mean a proof is absent from the bundle, or a slot that was " +
418
- "allocated but never committed (a routine, benign occurrence); " +
437
+ "mean a proof is absent from the bundle, or a position that was " +
438
+ "reserved but never committed (a routine, benign occurrence); " +
419
439
  "this offline audit cannot tell the two apart. It does not, by " +
420
440
  "itself, prove that the BitGraph authority failed to create or " +
421
441
  "withheld any proof."
@@ -449,7 +469,7 @@ function divergenceSummarySentence(divergence: DivergenceRecord): string {
449
469
  );
450
470
  case "slot-collision":
451
471
  return (
452
- `${head} ${n === 2 ? "both reference" : "all reference"} slot counter ` +
472
+ `${head} ${n === 2 ? "both reference" : "all reference"} reserved position ` +
453
473
  `${withCommas(divergence.contested["slotCounter"] ?? "?")} in the same epoch.` +
454
474
  tail
455
475
  );
@@ -457,7 +477,7 @@ function divergenceSummarySentence(divergence: DivergenceRecord): string {
457
477
  return (
458
478
  `${head} allocate one causal position ` +
459
479
  `${withCommas(divergence.contested["position"] ?? "?")} in the same epoch, at least one ` +
460
- "committing it and at least one reserving it as a slot." +
480
+ "committing it and at least one reserving it." +
461
481
  tail
462
482
  );
463
483
  case "predecessor-reuse":
@@ -558,7 +578,7 @@ function perPartitionChains(
558
578
  proof?.verificationStatus ?? "unverified",
559
579
  ]);
560
580
  }
561
- lines.push(...table(["Counter", "Slot", "Proof hash", "Predecessor", "Status"], rows));
581
+ lines.push(...table(["Counter", "Reserved", "Proof hash", "Predecessor", "Status"], rows));
562
582
  if (component.memberProofHashes.length > MAX_TABLE_ROWS) {
563
583
  lines.push("");
564
584
  lines.push(
@@ -675,7 +695,7 @@ function divergenceDetails(lines: string[], report: AuditJsonReport): void {
675
695
 
676
696
  function partyTable(parties: DivergenceRecord["parties"]): string[] {
677
697
  return table(
678
- ["Proof hash", "Counter", "Slot", "Predecessor", "Status", "Sources"],
698
+ ["Proof hash", "Counter", "Reserved", "Predecessor", "Status", "Sources"],
679
699
  parties.map((party) => [
680
700
  inlineCode(party.proofHash),
681
701
  party.counter ?? "",
@@ -963,13 +983,13 @@ function codeMeaning(code: AnomalyCode): string {
963
983
  case "manifest-contents-hash-mismatch":
964
984
  return "The manifest's declared contents hash does not match the computed one. The manifest is advisory; the computed value governs.";
965
985
  case "unexplained-counter-positions":
966
- return "Counter positions inside the observed range are neither commit positions nor referenced slot positions. Such a position may be a proof absent from the bundle, or a slot that was allocated but never committed (routine and benign); this offline audit cannot distinguish them, and it does not, by itself, prove the authority failed to create or withheld any proof.";
986
+ return "Counter positions inside the observed range are neither commit positions nor referenced reserved positions. Such a position may be a proof absent from the bundle, or a position that was reserved but never committed (routine and benign); this offline audit cannot distinguish them, and it does not, by itself, prove the authority failed to create or withheld any proof.";
967
987
  case "counter-collision":
968
988
  return "Two or more valid proofs claim the same commit counter. All are preserved; the audit tool does not choose between them.";
969
989
  case "slot-collision":
970
- return "Two or more valid proofs reference the same slot counter. All are preserved; the audit tool does not choose between them.";
990
+ return "Two or more valid proofs reference the same reserved position. All are preserved; the audit tool does not choose between them.";
971
991
  case "cross-kind-position-reuse":
972
- return "A commit counter in one proof equals a different valid proof's slot position in the same partition: one causal position allocated twice across kinds, which only enclave malfunction, replay, or compromise produces. All parties are preserved; the audit tool does not choose between them.";
992
+ return "A commit counter in one proof equals a different valid proof's reserved position in the same partition: one causal position allocated twice across kinds, which only enclave malfunction, replay, or compromise produces. All parties are preserved; the audit tool does not choose between them.";
973
993
  case "predecessor-reuse":
974
994
  return "Two or more valid proofs name the same predecessor: the chain forks at that point. All branches are preserved; the audit tool does not choose between them.";
975
995
  case "chain-break-missing":
@@ -981,7 +1001,7 @@ function codeMeaning(code: AnomalyCode): string {
981
1001
  case "multiple-genesis":
982
1002
  return "More than one proof in the same partition starts a chain without naming a predecessor. A single such proof is the normal start of an epoch; several need adjudication.";
983
1003
  case "slot-order-violation":
984
- return "A proof's slot position is not strictly before its commit position, violating the slot-then-commit ordering.";
1004
+ return "A proof's reserved position is not strictly before its commit position, violating the reserve-then-commit ordering.";
985
1005
  case "epochlink-terminal-missing":
986
1006
  return "An epoch's first proof links to a prior epoch that is observed, but the specific proof it names is absent from the bundle.";
987
1007
  case "epochlink-dangling":
package/src/types.ts CHANGED
@@ -406,6 +406,8 @@ export interface IngestCounts {
406
406
  artifacts: number;
407
407
  /** Anchor witness files. */
408
408
  witnesses: number;
409
+ /** Ceiling files (bitgraph-ceiling/1) and ceiling status notes (bitgraph-ceiling-status/1). */
410
+ ceilings?: number;
409
411
  /** Container entries skipped for unsafe paths. */
410
412
  skippedUnsafePaths: number;
411
413
  }
@@ -467,6 +469,10 @@ export interface IngestResult {
467
469
  artifacts: ArtifactRecord[];
468
470
  /** Anchor witness files in observation order. */
469
471
  witnesses: AnchorWitnessFile[];
472
+ /** Ceiling files (bitgraph-ceiling/1) in observation order. Optional so older embedders' IngestResults still type. */
473
+ ceilings?: CeilingFile[];
474
+ /** Ceiling status notes (bitgraph-ceiling-status/1): a package saying why a ceiling is absent. */
475
+ ceilingStatuses?: CeilingFile[];
470
476
  /** Present when a root manifest.json entry existed. */
471
477
  manifest?: ManifestReport;
472
478
 
@@ -1306,6 +1312,8 @@ export interface AuditOptions {
1306
1312
  * explicitly non-AWS deployments.
1307
1313
  */
1308
1314
  trustedRootCaDer?: Uint8Array;
1315
+ /** Ceilings in time: the declared writer, the chain, and an optional Base RPC for the one online check. */
1316
+ ceilings?: import("./ceilings.js").CeilingAuditOptions;
1309
1317
  }
1310
1318
 
1311
1319
  /**
@@ -1325,6 +1333,48 @@ export interface AuditRunMetadata {
1325
1333
  }
1326
1334
 
1327
1335
  /** Everything one full audit run produced, in pipeline order. */
1336
+ /** A ceiling file or a ceiling status note, as found in the bundle. Never an artifact. */
1337
+ export interface CeilingFile {
1338
+ path: string;
1339
+ fileSha256Hex: string;
1340
+ json: Record<string, unknown>;
1341
+ }
1342
+
1343
+ /** One ceiling file checked against its proof (verifyCeiling), offline. */
1344
+ export interface CeilingCheck {
1345
+ path: string;
1346
+ proofHash: string | null;
1347
+ /**
1348
+ * verified: every offline check passed. failed: the file is present and
1349
+ * wrong (a tampered or foreign ceiling). pending: the file says no Base
1350
+ * transaction yet. unmatched: no proof in the bundle has its proofHash.
1351
+ */
1352
+ status: "verified" | "failed" | "pending" | "unmatched";
1353
+ reason?: string;
1354
+ /** One line for people, e.g. "Ceiling: Base block N at hh:mm:ss UTC, settled on Ethereum." */
1355
+ label?: string;
1356
+ window?: {
1357
+ floorBlock: number | null;
1358
+ floorTime: number | null;
1359
+ ceilingChainId: number;
1360
+ ceilingBlock: number;
1361
+ ceilingTime: number;
1362
+ widthSeconds: number | null;
1363
+ };
1364
+ /** Online check against a Base RPC: null when not asked or unreachable. */
1365
+ onChain: boolean | null;
1366
+ onChainDetail?: string;
1367
+ }
1368
+
1369
+ export interface CeilingAnalysis {
1370
+ /** The writer address the ceilings were checked against. */
1371
+ writer: string;
1372
+ chainId: number;
1373
+ checks: CeilingCheck[];
1374
+ /** Status notes carried instead of a ceiling (path and note). */
1375
+ statuses: Array<{ path: string; status: string; note: string }>;
1376
+ }
1377
+
1328
1378
  export interface AuditResult {
1329
1379
  runMetadata: AuditRunMetadata;
1330
1380
  ingest: IngestResult;
@@ -1336,6 +1386,8 @@ export interface AuditResult {
1336
1386
  witnesses: AnchorWitnessAnalysis;
1337
1387
  temporal: TemporalAnalysis;
1338
1388
  attestations: AttestationAnalysis;
1389
+ /** Ceilings in time on Base. Absent from results made before this stage existed. */
1390
+ ceilings?: CeilingAnalysis;
1339
1391
  }
1340
1392
 
1341
1393
  /**
@@ -1577,6 +1629,8 @@ export interface AuditJsonReport {
1577
1629
  groups: AuthorityGroup[];
1578
1630
  sharedSignersAcrossEpochs: SignerEpochSpan[];
1579
1631
  };
1632
+ /** Ceilings in time on Base (bitgraph-ceiling/1). Absent when the bundle carries none. */
1633
+ ceilings?: import("./types.js").CeilingAnalysis;
1580
1634
  attestations: {
1581
1635
  records: ProofAttestationRecord[];
1582
1636
  counts: AttestationAnalysis["counts"];