@sanning/proof 0.16.0 → 0.17.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/README.md +49 -4
- package/dist/agent-proof.js +50 -23
- package/dist/case-file.d.ts +24 -4
- package/dist/case-file.js +62 -5
- package/dist/case.d.ts +104 -8
- package/dist/case.js +195 -53
- package/dist/cli.d.ts +105 -2
- package/dist/cli.js +664 -123
- package/dist/compose.js +32 -0
- package/dist/evidence.js +108 -38
- package/dist/file-match.d.ts +64 -5
- package/dist/file-match.js +113 -22
- package/dist/grading.d.ts +7 -0
- package/dist/grading.js +14 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/merkle.js +13 -7
- package/dist/verifier.js +24 -19
- package/dist/zip-reader.d.ts +7 -0
- package/dist/zip-reader.js +7 -0
- package/package.json +1 -1
- package/src/agent-proof.ts +53 -27
- package/src/case-file.ts +61 -8
- package/src/case.ts +293 -64
- package/src/cli.ts +703 -132
- package/src/compose.ts +33 -0
- package/src/evidence.ts +109 -43
- package/src/file-match.ts +160 -26
- package/src/grading.ts +15 -2
- package/src/index.ts +6 -0
- package/src/merkle.ts +13 -7
- package/src/verifier.ts +26 -22
- package/src/zip-reader.ts +7 -0
- package/vectors/README.md +5 -1
- package/vectors/case-file-index.json +88 -2
- package/vectors/file-match.json +135 -1
package/src/grading.ts
CHANGED
|
@@ -476,6 +476,17 @@ export function accountDisclosure(
|
|
|
476
476
|
return { expected, present, missing, unreadable, extra };
|
|
477
477
|
}
|
|
478
478
|
|
|
479
|
+
/**
|
|
480
|
+
* A JSON number read by its value: an integer within 2**53 - 1, or null.
|
|
481
|
+
* `Number.isSafeInteger`, mirrored by `_js_safe_integer` in
|
|
482
|
+
* `src/sanning_proof/chain.py`, so `2.0` is 2 in both kernels and
|
|
483
|
+
* `9007199254740993`, which this parser rounds, is unreadable in both.
|
|
484
|
+
*/
|
|
485
|
+
export function safeInteger(value: unknown): number | null {
|
|
486
|
+
// `+ 0` folds -0 into 0, which Python's `int(-0.0)` already is.
|
|
487
|
+
return typeof value === "number" && Number.isSafeInteger(value) ? value + 0 : null;
|
|
488
|
+
}
|
|
489
|
+
|
|
479
490
|
/** The `leaf_count` a checkpoint's SIGNED record commits to, or null if unreadable. */
|
|
480
491
|
export function signedLeafCount(checkpoint: { event_bytes?: unknown } | null | undefined): number | null {
|
|
481
492
|
const hex = checkpoint?.event_bytes;
|
|
@@ -490,8 +501,10 @@ export function signedLeafCount(checkpoint: { event_bytes?: unknown } | null | u
|
|
|
490
501
|
const record = jcsParse(hexToBytes(hex)) as {
|
|
491
502
|
event?: { leaf_count?: unknown };
|
|
492
503
|
};
|
|
493
|
-
|
|
494
|
-
|
|
504
|
+
// Read BY VALUE, the model JCS signs: a record spelling `3.0`, `3e0` or
|
|
505
|
+
// `30e-1` commits to 3, and since A38 (proof#197) this count decides a
|
|
506
|
+
// verdict, not only a note. Past 2**53 - 1 it is unreadable, as in Python.
|
|
507
|
+
return safeInteger(record?.event?.leaf_count);
|
|
495
508
|
} catch {
|
|
496
509
|
return null;
|
|
497
510
|
}
|
package/src/index.ts
CHANGED
|
@@ -219,6 +219,7 @@ export {
|
|
|
219
219
|
CASE_EXCEPTION_KINDS,
|
|
220
220
|
CASE_QUARANTINED_PROFILES,
|
|
221
221
|
CASE_RECORD_PROFILES,
|
|
222
|
+
CASE_RECORD_STATUSES,
|
|
222
223
|
CASE_NOTE_KINDS,
|
|
223
224
|
CASE_RELIANCE,
|
|
224
225
|
CASE_RELIANCE_HANDOFF_CLAUSE,
|
|
@@ -285,6 +286,7 @@ export type {
|
|
|
285
286
|
CasePackReading,
|
|
286
287
|
CaseReading,
|
|
287
288
|
CaseRecordReading,
|
|
289
|
+
CaseRecordStatus,
|
|
288
290
|
CaseVerdict,
|
|
289
291
|
WitnessLine,
|
|
290
292
|
WitnessState,
|
|
@@ -316,9 +318,11 @@ export type {
|
|
|
316
318
|
export {
|
|
317
319
|
FILE_MATCH_SCOPE,
|
|
318
320
|
NOT_READ_REASONS,
|
|
321
|
+
SKIPPED_REASONS,
|
|
319
322
|
fileMatchToJson,
|
|
320
323
|
matchCaseFiles,
|
|
321
324
|
matchFiles,
|
|
325
|
+
notAccountedFor,
|
|
322
326
|
renderFileMatch,
|
|
323
327
|
} from "./file-match.js";
|
|
324
328
|
export type {
|
|
@@ -329,6 +333,8 @@ export type {
|
|
|
329
333
|
NotRead,
|
|
330
334
|
NotReadReason,
|
|
331
335
|
OfferedFile,
|
|
336
|
+
SkippedFile,
|
|
337
|
+
SkippedReason,
|
|
332
338
|
UnmatchedFile,
|
|
333
339
|
} from "./file-match.js";
|
|
334
340
|
|
package/src/merkle.ts
CHANGED
|
@@ -78,25 +78,31 @@ export async function verifyInclusion(
|
|
|
78
78
|
}
|
|
79
79
|
if (totalLeaves === 1) return auditPath.length === 0 && bytesEqual(leaf, expectedRoot);
|
|
80
80
|
|
|
81
|
+
// Arithmetic, not bitwise: JavaScript's `&` and `>>=` truncate to 32 bits,
|
|
82
|
+
// so a tree past 2**31 leaves walked a different tree here than in the
|
|
83
|
+
// Python kernel, whose integers are unbounded. `% 2` and a halving floor
|
|
84
|
+
// are exact for every safe integer, and the stated index and count reach
|
|
85
|
+
// this walk as safe integers.
|
|
81
86
|
let fn = leafIndex;
|
|
82
87
|
let sn = totalLeaves - 1;
|
|
83
88
|
let r = leaf;
|
|
89
|
+
const half = (v: number): number => Math.floor(v / 2);
|
|
84
90
|
|
|
85
91
|
for (const p of auditPath) {
|
|
86
92
|
if (sn === 0) return false; // path longer than the tree depth — malformed
|
|
87
|
-
if (
|
|
93
|
+
if (fn % 2 === 1 || fn === sn) {
|
|
88
94
|
r = await nodeHash(p, r);
|
|
89
|
-
if (
|
|
90
|
-
while (
|
|
91
|
-
fn
|
|
92
|
-
sn
|
|
95
|
+
if (fn % 2 === 0) {
|
|
96
|
+
while (fn % 2 === 0 && fn !== 0) {
|
|
97
|
+
fn = half(fn);
|
|
98
|
+
sn = half(sn);
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
} else {
|
|
96
102
|
r = await nodeHash(r, p);
|
|
97
103
|
}
|
|
98
|
-
fn
|
|
99
|
-
sn
|
|
104
|
+
fn = half(fn);
|
|
105
|
+
sn = half(sn);
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
return sn === 0 && bytesEqual(r, expectedRoot);
|
package/src/verifier.ts
CHANGED
|
@@ -708,7 +708,7 @@ export async function verifyEnvelope(
|
|
|
708
708
|
// exotic object.
|
|
709
709
|
try {
|
|
710
710
|
return await verifyEnvelopeChecked(env, opts);
|
|
711
|
-
} catch
|
|
711
|
+
} catch {
|
|
712
712
|
return {
|
|
713
713
|
ok: false,
|
|
714
714
|
specVersionOk: false,
|
|
@@ -718,7 +718,7 @@ export async function verifyEnvelope(
|
|
|
718
718
|
signatureOk: false,
|
|
719
719
|
contentHashOk: opts.expectedContentHash === undefined ? null : false,
|
|
720
720
|
contentRole: null,
|
|
721
|
-
errors: [
|
|
721
|
+
errors: ["envelope could not be read"],
|
|
722
722
|
};
|
|
723
723
|
}
|
|
724
724
|
}
|
|
@@ -761,7 +761,15 @@ async function verifyEnvelopeChecked(
|
|
|
761
761
|
}
|
|
762
762
|
|
|
763
763
|
const specVersionOk = specVersionSupported(env.spec_version, opts.acceptedSpecMajors);
|
|
764
|
-
if (!specVersionOk)
|
|
764
|
+
if (!specVersionOk) {
|
|
765
|
+
// Absent or null reads "missing", as in the Python kernel. Everything else
|
|
766
|
+
// is quoted the way both kernels quote a value in a finding.
|
|
767
|
+
errors.push(
|
|
768
|
+
env.spec_version === undefined || env.spec_version === null
|
|
769
|
+
? "missing spec_version"
|
|
770
|
+
: `unsupported spec_version: ${JSON.stringify(env.spec_version)}`,
|
|
771
|
+
);
|
|
772
|
+
}
|
|
765
773
|
|
|
766
774
|
// Skeleton field format (envelope-spec §2, proof#95): a present `signed_at`
|
|
767
775
|
// or `environment` that is not what §2 says fails the verdict. There is no
|
|
@@ -791,8 +799,8 @@ async function verifyEnvelopeChecked(
|
|
|
791
799
|
// line, or an ANSI escape that paints `VERIFIED` green, into what the
|
|
792
800
|
// verifier prints. No other field in this file echoes its value, the Python
|
|
793
801
|
// twin names the same failure without one, and the value is in the envelope
|
|
794
|
-
// the auditor already holds.
|
|
795
|
-
//
|
|
802
|
+
// the auditor already holds. Neither is the recomputed digest: the reason is
|
|
803
|
+
// the Python kernel's sentence, word for word (proof#150).
|
|
796
804
|
let payloadHashOk: boolean | null;
|
|
797
805
|
if (typeof env.payload_hash !== "string") {
|
|
798
806
|
payloadHashOk = false;
|
|
@@ -803,26 +811,22 @@ async function verifyEnvelopeChecked(
|
|
|
803
811
|
try {
|
|
804
812
|
const recomputed = await sha256Hex(utf8(jcs(env.payload)));
|
|
805
813
|
checks.push(recomputed === env.payload_hash);
|
|
806
|
-
|
|
807
|
-
errors.push(
|
|
808
|
-
`payload_hash mismatch: the envelope's payload_hash is not SHA-256(JCS(payload)), recomputed=${recomputed}`,
|
|
809
|
-
);
|
|
810
|
-
}
|
|
811
|
-
} catch (e) {
|
|
814
|
+
} catch {
|
|
812
815
|
checks.push(false);
|
|
813
|
-
errors.push(
|
|
816
|
+
errors.push("payload is not JCS-canonicalizable");
|
|
814
817
|
}
|
|
815
818
|
}
|
|
816
819
|
if (opts.payloadBytes !== undefined) {
|
|
817
820
|
const external = await sha256Hex(opts.payloadBytes);
|
|
818
821
|
checks.push(external === env.payload_hash);
|
|
819
|
-
if (external !== env.payload_hash) {
|
|
820
|
-
errors.push(
|
|
821
|
-
`payload_hash does not match the committed bytes: bytes=${external}`,
|
|
822
|
-
);
|
|
823
|
-
}
|
|
824
822
|
}
|
|
825
823
|
payloadHashOk = checks.length > 0 ? checks.every(Boolean) : null;
|
|
824
|
+
// One sentence for a mismatch against the inline payload, the supplied
|
|
825
|
+
// bytes or both, and none when the payload could not be canonicalized: the
|
|
826
|
+
// Python kernel's rule, word for word (proof#150).
|
|
827
|
+
if (payloadHashOk === false && !errors.some((e) => e.startsWith("payload is not"))) {
|
|
828
|
+
errors.push("payload_hash does not match the committed bytes");
|
|
829
|
+
}
|
|
826
830
|
}
|
|
827
831
|
|
|
828
832
|
// Check 2 — Signature Confirmed: Ed25519 over the signed scope, which is
|
|
@@ -836,8 +840,11 @@ async function verifyEnvelopeChecked(
|
|
|
836
840
|
const { signature: _signature, co_signatures: _coSignatures, ...envelopeForSig } = env;
|
|
837
841
|
signatureOk = await ed25519Verify(utf8(jcs(envelopeForSig)), env.signature, env.public_key);
|
|
838
842
|
if (!signatureOk) errors.push("Ed25519 signature verification failed");
|
|
839
|
-
} catch
|
|
840
|
-
|
|
843
|
+
} catch {
|
|
844
|
+
// The sentence the Python kernel writes for the same failure. What was
|
|
845
|
+
// thrown is the host's wording (`jcs: input nesting exceeds ...`), and a
|
|
846
|
+
// public result carries the kernel's own words only (proof#150).
|
|
847
|
+
errors.push("Ed25519 signature verification failed");
|
|
841
848
|
}
|
|
842
849
|
|
|
843
850
|
// Check 3 (optional) — Content Bind: the user's bytes match a hash this
|
|
@@ -895,6 +902,3 @@ async function verifyEnvelopeChecked(
|
|
|
895
902
|
};
|
|
896
903
|
}
|
|
897
904
|
|
|
898
|
-
function stringifyErr(e: unknown): string {
|
|
899
|
-
return e instanceof Error ? e.message : String(e);
|
|
900
|
-
}
|
package/src/zip-reader.ts
CHANGED
|
@@ -32,6 +32,13 @@ import { utf8Strict } from "./verifier.js";
|
|
|
32
32
|
export const MAX_ZIP_ENTRIES = 50_000;
|
|
33
33
|
/** More uncompressed bytes than this, across every entry, refuse the archive. */
|
|
34
34
|
export const MAX_ZIP_BYTES = 2 ** 30;
|
|
35
|
+
/**
|
|
36
|
+
* An archive file larger than this is refused before it is read: the
|
|
37
|
+
* uncompressed cap, and room for the headers, the central directory and the
|
|
38
|
+
* deflate framing of an archive that holds that much. The caller checks the
|
|
39
|
+
* size on disk, so the archive is never read whole to find out it is too big.
|
|
40
|
+
*/
|
|
41
|
+
export const MAX_ZIP_ARCHIVE_BYTES = MAX_ZIP_BYTES + 256 * 2 ** 20;
|
|
35
42
|
/** A name nested deeper than this many folders refuses the archive. */
|
|
36
43
|
export const MAX_ZIP_DEPTH = 32;
|
|
37
44
|
|
package/vectors/README.md
CHANGED
|
@@ -173,7 +173,11 @@ matched by its SHA-256 and by nothing else**, and **no match moves a verdict**:
|
|
|
173
173
|
a surface that matched by name, or took a hash from a field no record's key
|
|
174
174
|
signed, would vouch for a file the pack never committed to. Written by
|
|
175
175
|
`tools/gen-vectors/gen_file_match_vectors.py`, whose expectations are written
|
|
176
|
-
by hand from what it built.
|
|
176
|
+
by hand from what it built. Rule version 2 (A43, proof#204) adds `pack_index`
|
|
177
|
+
and `record_index` to every commitment and every `not_read` entry: the pack's
|
|
178
|
+
position among the row's packs in `mode: case` (`null` in `mode: pack`) and
|
|
179
|
+
the record's position among the events the pack presents, both read off the
|
|
180
|
+
packs the row names.
|
|
177
181
|
|
|
178
182
|
## `case-split.json`
|
|
179
183
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"rule": "case-file-index",
|
|
3
|
-
"ruleVersion":
|
|
4
|
-
"statement": "How a reader compares a case file's index, contents.json, with the folder it sits at the root of (evidence-bundle.md §5.6.3 v1.20, A25). The index is never an input to the case reading, and no finding here moves a verdict, a note, a contribution or an exit code. Findings come in this order: case_index_unreadable (and nothing else), case_index_pack_missing, case_index_pack_unlisted, case_index_file_missing, case_index_file_unlisted, case_index_file_differs, case_index_names_absent; within a kind by the UTF-8 bytes of the ref or path, and names_absent in the index's own order. Only files under a listed pack's folder or under report/ are reported as unlisted.",
|
|
3
|
+
"ruleVersion": 2,
|
|
4
|
+
"statement": "How a reader compares a case file's index, contents.json, with the folder it sits at the root of (evidence-bundle.md §5.6.3 v1.20, A25). The index is never an input to the case reading, and no finding here moves a verdict, a note, a contribution or an exit code. Findings come in this order: case_index_unreadable (and nothing else), case_index_pack_missing, case_index_pack_unlisted, case_index_file_missing, case_index_file_unlisted, case_index_file_differs, case_index_file_too_large, case_index_names_absent; within a kind by the UTF-8 bytes of the ref or path, and names_absent in the index's own order. Only files under a listed pack's folder or under report/ are reported as unlisted.",
|
|
5
5
|
"howToUse": "Call readCaseFileIndex / read_case_file_index with the bytes of index_hex (null: no index in the folder) and `files`, and compare the result with `expected`. A finding lists only the fields it carries.",
|
|
6
6
|
"rows": [
|
|
7
7
|
{
|
|
@@ -465,6 +465,92 @@
|
|
|
465
465
|
]
|
|
466
466
|
}
|
|
467
467
|
},
|
|
468
|
+
{
|
|
469
|
+
"id": "file-too-large",
|
|
470
|
+
"why": "A file the index lists is here and was too large for the caller to hash (the caller passes {path, too_large: true} in place of a digest). It is neither a match nor missing: one case_index_file_too_large finding, and no differs or missing for it. Every other file is compared as usual.",
|
|
471
|
+
"index_hex": "7b22616273656e74223a5b5d2c22636173655f6964223a22636173652d346231396465222c22636f6e74656e74735f76657273696f6e223a2273616e6e696e672e636173652d66696c652f7631222c227061636b73223a5b7b2266696c6573223a5b7b2270617468223a227061636b732f612f62756e646c652e6a736f6e222c22736861323536223a2263386261656230383636353834333538646464303634316264353938626239623862383838303935336232613135356366363235303062386338396137333463227d2c7b2270617468223a227061636b732f612f6c6f67732f782e6a736f6e222c22736861323536223a2235303431626631663731336466323034373834333533653832663661346135333539333163623634663166346234613561656166666362373230393138623232227d5d2c22726566223a2261227d2c7b2266696c6573223a5b7b2270617468223a227061636b732f622f62756e646c652e6a736f6e222c22736861323536223a2230653834326632353331333234373838636138353663616264363733306438623432663935373031616535323862313061623165643332343132356231356365227d5d2c22726566223a2262227d5d2c227265706f7274223a5b7b2270617468223a227265706f72742f524541444d452e747874222c22736861323536223a2230316563313839373665373637653831346165653962316361363966366239643331323963343336393063626234623434383463396539323331366362666437227d5d7d",
|
|
472
|
+
"files": [
|
|
473
|
+
{
|
|
474
|
+
"path": "HOW-TO-VERIFY.txt",
|
|
475
|
+
"sha256": "76b25bc6207548c6bd05b1ab2e364ff8c493f6fb04e6a69d731a6f158482b7d9"
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
"path": "contents.json",
|
|
479
|
+
"sha256": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
"path": "report/README.txt",
|
|
483
|
+
"sha256": "01ec18976e767e814aee9b1ca69f6b9d3129c43690cbb4b4484c9e92316cbfd7"
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
"path": "packs/b/bundle.json",
|
|
487
|
+
"sha256": "0e842f2531324788ca856cabd6730d8b42f95701ae528b10ab1ed324125b15ce"
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
"path": "packs/a/logs/x.json",
|
|
491
|
+
"too_large": true
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
"path": "packs/a/bundle.json",
|
|
495
|
+
"sha256": "c8baeb0866584358ddd0641bd598bb9b8b8880953b2a155cf62500b8c89a734c"
|
|
496
|
+
}
|
|
497
|
+
],
|
|
498
|
+
"expected": {
|
|
499
|
+
"index": "read",
|
|
500
|
+
"case_id": "case-4b19de",
|
|
501
|
+
"findings": [
|
|
502
|
+
{
|
|
503
|
+
"kind": "case_index_file_too_large",
|
|
504
|
+
"path": "packs/a/logs/x.json"
|
|
505
|
+
}
|
|
506
|
+
]
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
"id": "unlisted-file-too-large",
|
|
511
|
+
"why": "A file under report/ that the index does not list, too large to hash. Unlisted needs no digest, so it is reported as unlisted, exactly as a file of any size would be; nothing else is said of it.",
|
|
512
|
+
"index_hex": "7b22616273656e74223a5b5d2c22636173655f6964223a22636173652d346231396465222c22636f6e74656e74735f76657273696f6e223a2273616e6e696e672e636173652d66696c652f7631222c227061636b73223a5b7b2266696c6573223a5b7b2270617468223a227061636b732f612f62756e646c652e6a736f6e222c22736861323536223a2263386261656230383636353834333538646464303634316264353938626239623862383838303935336232613135356366363235303062386338396137333463227d2c7b2270617468223a227061636b732f612f6c6f67732f782e6a736f6e222c22736861323536223a2235303431626631663731336466323034373834333533653832663661346135333539333163623634663166346234613561656166666362373230393138623232227d5d2c22726566223a2261227d2c7b2266696c6573223a5b7b2270617468223a227061636b732f622f62756e646c652e6a736f6e222c22736861323536223a2230653834326632353331333234373838636138353663616264363733306438623432663935373031616535323862313061623165643332343132356231356365227d5d2c22726566223a2262227d5d2c227265706f7274223a5b7b2270617468223a227265706f72742f524541444d452e747874222c22736861323536223a2230316563313839373665373637653831346165653962316361363966366239643331323963343336393063626234623434383463396539323331366362666437227d5d7d",
|
|
513
|
+
"files": [
|
|
514
|
+
{
|
|
515
|
+
"path": "HOW-TO-VERIFY.txt",
|
|
516
|
+
"sha256": "76b25bc6207548c6bd05b1ab2e364ff8c493f6fb04e6a69d731a6f158482b7d9"
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
"path": "contents.json",
|
|
520
|
+
"sha256": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
"path": "report/README.txt",
|
|
524
|
+
"sha256": "01ec18976e767e814aee9b1ca69f6b9d3129c43690cbb4b4484c9e92316cbfd7"
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"path": "packs/b/bundle.json",
|
|
528
|
+
"sha256": "0e842f2531324788ca856cabd6730d8b42f95701ae528b10ab1ed324125b15ce"
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
"path": "packs/a/logs/x.json",
|
|
532
|
+
"sha256": "5041bf1f713df204784353e82f6a4a535931cb64f1f4b4a5aeaffcb720918b22"
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
"path": "packs/a/bundle.json",
|
|
536
|
+
"sha256": "c8baeb0866584358ddd0641bd598bb9b8b8880953b2a155cf62500b8c89a734c"
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
"path": "report/huge.bin",
|
|
540
|
+
"too_large": true
|
|
541
|
+
}
|
|
542
|
+
],
|
|
543
|
+
"expected": {
|
|
544
|
+
"index": "read",
|
|
545
|
+
"case_id": "case-4b19de",
|
|
546
|
+
"findings": [
|
|
547
|
+
{
|
|
548
|
+
"kind": "case_index_file_unlisted",
|
|
549
|
+
"path": "report/huge.bin"
|
|
550
|
+
}
|
|
551
|
+
]
|
|
552
|
+
}
|
|
553
|
+
},
|
|
468
554
|
{
|
|
469
555
|
"id": "no-index",
|
|
470
556
|
"why": "A folder with no contents.json is a folder of packs, with no finding.",
|