@agentsfleet/orly 0.12.0 → 0.12.1

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 CHANGED
@@ -42,10 +42,10 @@ Works with Claude Code, Codex, OpenCode, and Amp.
42
42
 
43
43
  ---
44
44
 
45
- ## 0.12.0: migration verification
45
+ ## 0.12: migration verification
46
46
 
47
- 0.12.0 is a local release candidate. Published 0.11.0 already provides the
48
- `.orly/` layout and the six existing `orly judge` questions.
47
+ 0.12.0 shipped migration verification and setup guidance. The `.orly/` layout
48
+ and six existing `orly judge` questions originally shipped in 0.11.0.
49
49
  This release focuses on package verification, migration evidence and clearer setup guidance; it adds no new runtime judgment question.
50
50
 
51
51
  The `agentsfleet` trial upgraded a dedicated worktree from 0.10.14 using the packed candidate.
@@ -57,7 +57,10 @@ Existing deterministic checks retain control of gate results.
57
57
  The comparison evaluator is parked for proposed 0.13 work; model accuracy and candidate benefits remain unmeasured.
58
58
  See the [judgment reference](docs/JUDGMENTS.md) for evidence selection, offline replay and explicit source upload.
59
59
 
60
- The install commands below fetch the published package. They do not select this unpublished candidate.
60
+ 0.12.1 avoids copying repository files for staged audits with no matching files
61
+ and makes hosted-check monitoring show job states and links before reporting a failure.
62
+ See [releases](https://github.com/agentsfleet/orly/releases) for published versions.
63
+ The install commands below fetch the published package.
61
64
 
62
65
  ---
63
66
 
@@ -48,7 +48,7 @@ set -euo pipefail
48
48
 
49
49
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
50
50
  audit_scope_init --all "$@"
51
- audit_index_snapshot "$@"
51
+ audit_index_snapshot '*.zig'
52
52
 
53
53
  MODE="${1:-${SCOPE:-all}}"
54
54
  ROOT="$(git rev-parse --show-toplevel)"
@@ -39,7 +39,7 @@ set -euo pipefail
39
39
 
40
40
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
41
41
  audit_scope_init --all "$@"
42
- audit_index_snapshot "$@"
42
+ audit_index_snapshot '*.tsx' '*.jsx' 'ui/packages/app/*' 'ui/packages/website/*' 'ui/packages/design-system/*'
43
43
 
44
44
  MODE="$AUDIT_MODE"
45
45
  ROOT="$(git rev-parse --show-toplevel)"
@@ -51,7 +51,7 @@ set -euo pipefail
51
51
 
52
52
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
53
53
  audit_scope_init --all "$@"
54
- audit_index_snapshot "$@"
54
+ audit_index_snapshot
55
55
 
56
56
  MODE="$AUDIT_MODE"
57
57
  ROOT="$(git rev-parse --show-toplevel)"
package/audits/logging.sh CHANGED
@@ -43,7 +43,7 @@ set -euo pipefail
43
43
 
44
44
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
45
45
  audit_scope_init --all "$@"
46
- audit_index_snapshot "$@"
46
+ audit_index_snapshot '*.zig' '*.rs' 'agentsfleet/src/*.js' 'agentsfleet/src/*.jsx' 'agentsfleet/src/*.ts' 'agentsfleet/src/*.tsx'
47
47
 
48
48
  MODE="--all"
49
49
  STRICT=0
@@ -44,7 +44,7 @@ set -euo pipefail
44
44
 
45
45
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
46
46
  audit_scope_init --all "$@"
47
- audit_index_snapshot "$@"
47
+ audit_index_snapshot '*.rs'
48
48
 
49
49
  MODE_STAGED="--staged"
50
50
  MODE_ALL="--all"
package/audits/scope.sh CHANGED
@@ -37,7 +37,7 @@ audit_scope_init() {
37
37
  # the index copy and checked-out files belong to this invocation alone.
38
38
  audit_index_snapshot() {
39
39
  [ "$AUDIT_MODE" = --staged ] || return 0
40
- local gitdir index
40
+ local gitdir index changed=0
41
41
  AUDIT_SNAPSHOT_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/orly-index.XXXXXX")" || return 1
42
42
  trap 'rm -rf -- "$AUDIT_SNAPSHOT_ROOT"' EXIT
43
43
  trap 'exit 130' INT
@@ -46,6 +46,14 @@ audit_index_snapshot() {
46
46
  index="$(git rev-parse --git-path index)" || exit 1
47
47
  cp "$index" "$AUDIT_SNAPSHOT_ROOT/index" || exit 1
48
48
  export GIT_DIR="$gitdir" GIT_INDEX_FILE="$AUDIT_SNAPSHOT_ROOT/index" GIT_OPTIONAL_LOCKS=0
49
+ if [ "$#" -gt 0 ]; then
50
+ git diff --cached --quiet --diff-filter=ACMRT -- "$@" || changed=$?
51
+ case "$changed" in
52
+ 0) return 0 ;;
53
+ 1) ;;
54
+ *) printf 'cannot select staged audit files (git exit %s)\n' "$changed" >&2; exit 2 ;;
55
+ esac
56
+ fi
49
57
  export GIT_WORK_TREE="$AUDIT_SNAPSHOT_ROOT/tree"
50
58
  mkdir "$GIT_WORK_TREE" || exit 1
51
59
  git checkout-index --all --prefix="$GIT_WORK_TREE/" || exit 1
package/audits/sql-mod.sh CHANGED
@@ -25,7 +25,7 @@ set -euo pipefail
25
25
 
26
26
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
27
27
  audit_scope_init --all "$@"
28
- audit_index_snapshot "$@"
28
+ audit_index_snapshot '*.zig'
29
29
 
30
30
  MODE="${1:-${SCOPE:---all}}"
31
31
  ROOT="$(git rev-parse --show-toplevel)"
package/audits/ufs.sh CHANGED
@@ -55,7 +55,7 @@ set -euo pipefail
55
55
 
56
56
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
57
57
  audit_scope_init --all "$@"
58
- audit_index_snapshot "$@"
58
+ audit_index_snapshot '*.zig' '*.ts' '*.tsx' '*.js' '*.jsx' '*.rs' '*.go'
59
59
 
60
60
  # Default: full-codebase scan (`--all` is an explicit alias). `--staged` is the
61
61
  # pre-commit lens — it narrows the per-file checks to `git diff --cached`. The
package/dispatch/lib.sh CHANGED
@@ -125,7 +125,7 @@ dispatch_managed_paths() {
125
125
  dispatch_resolve_files() {
126
126
  DISPATCH_FILES=()
127
127
  audit_scope_init --all "$@"
128
- audit_index_snapshot "$@"
128
+ audit_index_snapshot
129
129
  DISPATCH_STAGED=0
130
130
  if [ "${1:-}" = "--staged" ]; then
131
131
  DISPATCH_STAGED=1
package/docs/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.12.0 — unreleased
3
+ ## 0.12.1
4
+
5
+ Staged audits avoid copying repository files when their filename scope is empty.
6
+
7
+ - **Audit preflight** — selects filenames from a copied index before materialising repository files; relevant audits retain coherent staged reads.
8
+ - **Check diagnostics** — prints hosted job or pipeline states and links before reporting pending or failed checks.
9
+ - **Release status** — corrects documentation that still described published 0.12.0 as an unpublished candidate.
10
+
11
+ ## 0.12.0 — 2026-10-07
4
12
 
5
13
  Setup guidance now distinguishes the published package from the local release candidate.
6
14
 
@@ -84,6 +84,13 @@ Every criterion is mechanical — it reads an exit code or a file. Claims that
84
84
  cannot be proven that way stay prose and are graded by the spec's rubric; they
85
85
  never become fake criteria.
86
86
 
87
+ Filename-scoped staged audits copy Git's index before selecting relevant paths.
88
+ An empty selection skips the repository checkout; matching paths still trigger
89
+ a private checkout, dependency reads and indexed-link checks. Each audit owns
90
+ its temporary files and cleanup. Audits with unconditional registry checks
91
+ retain a complete checkout. Separate relevant audits retain separate snapshots;
92
+ this optimization does not introduce a shared snapshot cache.
93
+
87
94
  Four behaviours worth knowing:
88
95
 
89
96
  - **No spec, no problem — but closing is not escaping.** Spec criteria skip
package/llms.txt CHANGED
@@ -12,9 +12,11 @@ Package: `@agentsfleet/orly` (npm)
12
12
  Source: https://github.com/agentsfleet/orly
13
13
  License: Massachusetts Institute of Technology (MIT)
14
14
 
15
- This checkout prepares the unpublished 0.12.0 candidate. Published 0.11.0 already
16
- contains the same runtime commands and `.orly/` layout. The `bunx` commands below
17
- fetch the published package. Use the installed local tarball for candidate checks.
15
+ 0.12.0 shipped migration verification; the runtime commands and `.orly/` layout
16
+ originally shipped in 0.11.0. The 0.12.1 patch avoids irrelevant staged audit
17
+ snapshots and preserves hosted-check diagnostics. The `bunx` commands below
18
+ fetch the published package; available versions are listed at
19
+ https://github.com/agentsfleet/orly/releases. Use an installed local tarball for candidate checks.
18
20
  The comparison evaluator is parked for proposed 0.13 work; do not claim measured
19
21
  judgment improvement or enable new questions from its unfinished results.
20
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentsfleet/orly",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Repository-scoped engineering harness: renders agent rules, materialises the gates that enforce them, and proves the PR boundary \u2014 for any coding agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -93,8 +93,17 @@ Each cycle, after the review-thread walk, poll CI and act by cause:
93
93
 
94
94
  ```bash
95
95
  set -euo pipefail
96
- CHECKS=$(gh pr checks "$PR_NUMBER" --json name,state,bucket,link) \
97
- || { echo "BABYSIT: check fetch incomplete; reset quiet polls" >&2; exit 1; }
96
+ CHECK_STATUS=0
97
+ CHECKS=$(gh pr checks "$PR_NUMBER" --json name,state,bucket,link) || CHECK_STATUS=$?
98
+ printf '%s' "$CHECKS" | jq -e 'type == "array" and length > 0 and all(.[];
99
+ (.name | type == "string") and (.state | type == "string") and
100
+ (.link | type == "string") and (.bucket | IN("pass", "fail", "pending", "skipping", "cancel")))' >/dev/null \
101
+ || { printf 'BABYSIT: invalid check response (exit %s); reset quiet polls\n%s\n' "$CHECK_STATUS" "$CHECKS" >&2; exit 1; }
102
+ printf '%s' "$CHECKS" | jq -r '.[] | "\(.name) | \(.state) | \(.bucket) | \(.link)"'
103
+ case "$CHECK_STATUS" in
104
+ 0|1|8) ;; # GitHub reports failed and pending checks with nonzero statuses.
105
+ *) printf 'BABYSIT: check fetch incomplete (exit %s); reset quiet polls\n' "$CHECK_STATUS" >&2; exit 1 ;;
106
+ esac
98
107
  printf '%s' "$CHECKS" | jq -e 'type == "array" and length > 0 and all(.[]; .bucket == "pass" or .bucket == "skipping")' >/dev/null \
99
108
  || { echo "BABYSIT: checks empty or not green; reset quiet polls" >&2; exit 1; }
100
109
  CHECK_HEAD=$(gh pr view "$PR_NUMBER" --json headRefOid --jq '.headRefOid')
@@ -107,8 +116,14 @@ echo "BABYSIT: ci=green revision=$CHECK_HEAD"
107
116
 
108
117
  ```bash
109
118
  set -euo pipefail
110
- PIPELINES=$(glab api "projects/$PROJ_ENC/merge_requests/$MR_IID/pipelines" --paginate | jq -sc 'add') \
111
- || { echo "BABYSIT: pipeline fetch incomplete; reset quiet polls" >&2; exit 1; }
119
+ PIPELINE_STATUS=0
120
+ PIPELINES=$(glab api "projects/$PROJ_ENC/merge_requests/$MR_IID/pipelines" --paginate | jq -sc 'add') || PIPELINE_STATUS=$?
121
+ printf '%s' "$PIPELINES" | jq -e 'type == "array" and length > 0 and all(.[];
122
+ (.id | type == "number") and (.sha | type == "string") and (.status | type == "string"))' >/dev/null \
123
+ || { printf 'BABYSIT: invalid pipeline response (exit %s); reset quiet polls\n%s\n' "$PIPELINE_STATUS" "$PIPELINES" >&2; exit 1; }
124
+ printf '%s' "$PIPELINES" | jq -r '.[] | "pipeline \(.id) | \(.status) | \(.web_url // "URL not reported")"'
125
+ [ "$PIPELINE_STATUS" -eq 0 ] \
126
+ || { printf 'BABYSIT: pipeline fetch incomplete (exit %s); reset quiet polls\n' "$PIPELINE_STATUS" >&2; exit 1; }
112
127
  printf '%s' "$PIPELINES" | jq -e --arg revision "$POLL_HEAD" \
113
128
  '[.[] | select(.sha == $revision)] | sort_by(.id) | last | .status == "success"' >/dev/null \
114
129
  || { echo "BABYSIT: current revision has no successful pipeline; reset quiet polls" >&2; exit 1; }
@@ -30,7 +30,11 @@ else if(args[0]==="pr" && args[1]==="view") {
30
30
  else { const prior=(await Bun.file(process.env.POLL_CALLS).text()).split("\\n").filter(x=>x.includes("headRefOid")).length; console.log((process.env.POLL_MODE==="changed" && prior>1) || (process.env.POLL_MODE==="changed-check" && prior>2) ? "changed-revision" : process.env.POLL_HEAD); }
31
31
  } else if(args[0]==="pr" && args[1]==="checks") {
32
32
  if(process.env.POLL_STATE==="fetch-failed"){console.error("temporary fetch refusal");process.exit(2);}
33
- console.log(JSON.stringify(process.env.POLL_STATE==="empty" ? [] : [{name:"tests",state:"SUCCESS",bucket:process.env.POLL_STATE==="failed" ? "fail" : "pass",link:"https://example.invalid/check"}]));
33
+ const state=process.env.POLL_STATE;
34
+ const bucket=state==="failed" ? "fail" : state==="pending" ? "pending" : "pass";
35
+ console.log(JSON.stringify(state==="empty" ? [] : state==="malformed-check" ? [{name:"tests"}] : [{name:"tests",state:bucket==="pass" ? "SUCCESS" : bucket==="fail" ? "FAILURE" : "IN_PROGRESS",bucket,link:"https://example.invalid/check"}]));
36
+ if(state==="failed") process.exit(1);
37
+ if(state==="pending") process.exit(8);
34
38
  } else if(args[0]==="api") {
35
39
  if(process.env.POLL_MODE==="fetch-failed" && args[1].includes("/issues/")){console.error("temporary summary refusal");process.exit(2);}
36
40
  const revision=process.env.POLL_MODE==="stale" ? "old-revision" : process.env.POLL_HEAD;
@@ -75,7 +79,7 @@ for (const mode of ["fetch-failed", "malformed", "changed", "changed-check", "st
75
79
  });
76
80
  }
77
81
 
78
- for (const state of ["empty", "fetch-failed", "failed"]) {
82
+ for (const state of ["empty", "fetch-failed", "failed", "pending", "malformed-check"]) {
79
83
  test(`${state} check discovery stays incomplete`, () => {
80
84
  const result = poll("complete", state);
81
85
  expect(result.code).not.toBe(0);
@@ -84,6 +88,17 @@ for (const state of ["empty", "fetch-failed", "failed"]) {
84
88
  });
85
89
  }
86
90
 
91
+ for (const state of ["failed", "pending"]) {
92
+ test(`${state} GitHub checks retain job details despite nonzero status`, () => {
93
+ const result = poll("complete", state);
94
+ expect(result.code).not.toBe(0);
95
+ expect(result.output).toContain("tests |");
96
+ expect(result.output).toContain("https://example.invalid/check");
97
+ expect(result.output).toContain(state === "failed" ? "FAILURE | fail" : "IN_PROGRESS | pending");
98
+ expect(result.output).not.toContain("check fetch incomplete");
99
+ });
100
+ }
101
+
87
102
  function gitlabPoll(mode = "complete", state = "green") {
88
103
  const root = newRepository();
89
104
  const tools = join(root, "tools");
@@ -105,7 +120,7 @@ else if(args[0]==="mr") {
105
120
  if(process.env.POLL_MODE==="fetch-failed" && endpoint.endsWith("/notes")){console.error("temporary summary refusal");process.exit(2);}
106
121
  if(endpoint.endsWith("/pipelines")) {
107
122
  if(process.env.POLL_STATE==="fetch-failed"){console.error("temporary pipeline refusal");process.exit(2);}
108
- console.log(JSON.stringify(process.env.POLL_STATE==="empty" ? [] : [{id:3,sha:process.env.POLL_HEAD,status:process.env.POLL_STATE==="failed" ? "failed" : "success"}]));
123
+ console.log(JSON.stringify(process.env.POLL_STATE==="empty" ? [] : [{id:3,sha:process.env.POLL_HEAD,status:process.env.POLL_STATE==="failed" ? "failed" : process.env.POLL_STATE==="pending" ? "running" : "success",web_url:"https://example.invalid/pipeline"}]));
109
124
  } else if(process.env.POLL_MODE==="malformed") console.log(JSON.stringify({message:"not review evidence"}));
110
125
  else {
111
126
  const note=id=>({id,author:{username:"greptile"},body:id===1 ? "first-page finding" : "second-page finding",type:null});
@@ -148,7 +163,7 @@ for (const mode of ["fetch-failed", "malformed", "changed", "changed-check"]) {
148
163
  });
149
164
  }
150
165
 
151
- for (const state of ["empty", "fetch-failed", "failed"]) {
166
+ for (const state of ["empty", "fetch-failed", "failed", "pending"]) {
152
167
  test(`GitLab ${state} pipeline discovery stays incomplete`, () => {
153
168
  const result = gitlabPoll("complete", state);
154
169
  expect(result.code).not.toBe(0);
@@ -156,3 +171,12 @@ for (const state of ["empty", "fetch-failed", "failed"]) {
156
171
  expect(result.output).not.toContain("ci=green");
157
172
  });
158
173
  }
174
+
175
+ for (const state of ["failed", "pending"]) {
176
+ test(`GitLab ${state} pipeline retains its state and link`, () => {
177
+ const result = gitlabPoll("complete", state);
178
+ expect(result.code).not.toBe(0);
179
+ expect(result.output).toContain(`pipeline 3 | ${state === "failed" ? "failed" : "running"}`);
180
+ expect(result.output).toContain("https://example.invalid/pipeline");
181
+ });
182
+ }
@@ -0,0 +1,82 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ import { cleanupTemporaryDirectories, git, newRepository, ROOT, temporaryDirectory } from "./gates_test_support";
6
+ import { UNSCOPED_ENVIRONMENT } from "./git_env";
7
+
8
+ const SCOPED_AUDITS = ["ufs.sh", "logging.sh", "rust-error.sh", "deinit-pairs.sh", "sql-mod.sh", "design-tokens.sh"];
9
+ const SOURCE = "lib/odd 'quoted.ts";
10
+ const CLEAN = "export const MESSAGE = 'staged';\n";
11
+ const BAD = 'export function first() { return "duplicate value"; }\nexport function second() { return "duplicate value"; }\n';
12
+ const CHECKOUT = "checkout-index";
13
+ const TIMEOUT_MS = 30_000;
14
+ const TOOL_MODE = 0o755;
15
+
16
+ afterEach(cleanupTemporaryDirectories);
17
+
18
+ async function runScan(project: string, leaf: string, failPreflight = false) {
19
+ const tools = temporaryDirectory();
20
+ const scratch = temporaryDirectory();
21
+ const calls = join(tools, "calls.jsonl");
22
+ const realGit = Bun.which("git")!;
23
+ mkdirSync(join(tools, "bin"));
24
+ writeFileSync(join(tools, "bin/git"), `#!${process.execPath}
25
+ const args = Bun.argv.slice(2);
26
+ const file = Bun.file(process.env.SNAPSHOT_CALLS);
27
+ await Bun.write(file, (await file.exists() ? await file.text() : "") + JSON.stringify(args) + "\\n");
28
+ if (process.env.FAIL_PREFLIGHT === "1" && args.includes("--quiet")) process.exit(2);
29
+ const result = Bun.spawnSync([process.env.REAL_GIT, ...args], { stdin: "inherit", stdout: "inherit", stderr: "inherit" });
30
+ process.exit(result.exitCode);
31
+ `, { mode: TOOL_MODE });
32
+ const result = Bun.spawnSync(["/bin/bash", join(ROOT, "audits", leaf), "--staged"], {
33
+ cwd: project, env: { ...UNSCOPED_ENVIRONMENT, PATH: `${join(tools, "bin")}:${process.env.PATH}`, TMPDIR: scratch,
34
+ SNAPSHOT_CALLS: calls, REAL_GIT: realGit, FAIL_PREFLIGHT: failPreflight ? "1" : "0" },
35
+ stdout: "pipe", stderr: "pipe", timeout: TIMEOUT_MS,
36
+ });
37
+ const commands: string[][] = (await Bun.file(calls).text()).trim().split("\n").map((line) => JSON.parse(line));
38
+ return { code: result.exitCode, output: result.stdout.toString() + result.stderr.toString(),
39
+ checkouts: commands.filter((args) => args[0] === CHECKOUT).length, leftovers: readdirSync(scratch) };
40
+ }
41
+
42
+ test.each(SCOPED_AUDITS)("%s skips repository checkout for a documentation-only index", async (leaf) => {
43
+ const project = newRepository();
44
+ await Bun.write(join(project, "README.md"), "Changed documentation.\n");
45
+ git(project, "add", "README.md");
46
+ const result = await runScan(project, leaf);
47
+ expect(result.code, result.output).toBe(0);
48
+ expect(result.checkouts).toBe(0);
49
+ expect(result.leftovers).toEqual([]);
50
+ });
51
+
52
+ test.each([true, false])("relevant source snapshot checks staged bytes with violation %s", async (badIndex) => {
53
+ const project = newRepository();
54
+ await Bun.write(join(project, SOURCE), badIndex ? BAD : CLEAN);
55
+ git(project, "add", SOURCE);
56
+ await Bun.write(join(project, SOURCE), badIndex ? CLEAN : BAD);
57
+ const result = await runScan(project, "ufs.sh");
58
+ expect(result.code, result.output).toBe(badIndex ? 1 : 0);
59
+ expect(result.checkouts).toBe(1);
60
+ expect(result.leftovers).toEqual([]);
61
+ });
62
+
63
+ test("a failed staged filename query refuses instead of reporting an empty scope", async () => {
64
+ const project = newRepository();
65
+ const result = await runScan(project, "ufs.sh", true);
66
+ expect(result.code).toBe(2);
67
+ expect(result.output).toContain("cannot select staged audit files");
68
+ expect(result.checkouts).toBe(0);
69
+ expect(result.leftovers).toEqual([]);
70
+ });
71
+
72
+ test("a deleted source path needs no repository checkout", async () => {
73
+ const project = newRepository();
74
+ await Bun.write(join(project, SOURCE), CLEAN);
75
+ git(project, "add", SOURCE);
76
+ git(project, "commit", "-qm", "test: seed source");
77
+ git(project, "rm", SOURCE);
78
+ const result = await runScan(project, "ufs.sh");
79
+ expect(result.code, result.output).toBe(0);
80
+ expect(result.checkouts).toBe(0);
81
+ expect(result.leftovers).toEqual([]);
82
+ });