Skip to content

Commit 5713b10

Browse files
author
Matt Davis
committed
[docs] Documentation pass: scrub leftover internal refs + rewrite stale findings
The scrubber on the initial migrations missed several patterns: - Hyphenated task IDs (T7-IRON, T1-swarm) — regex required \.\d after T<n> - Investigation-phase labels (Followup A/B/C/E/G, Stage \d) - Tilde-rooted absolute paths (~/xdna-bringup/...) - Project-internal filename refs (gaps.yaml, umbrella PRD, gap-id) Pass: - 47 files mass-scrubbed of the missed patterns (34 code + 13 docs). - src/bionpu/iron_extensions/INVENTORY.md: rewritten from a 405-line internal investigation report into a brief module note. The cascade-stream feasibility outcome is now upstream aie.iron.CascadeFifo (Xilinx/mlir-aie#3039); this module is documented as a back-compat shim that will be removed once the upstream merge is the mlir-aie floor. - src/bionpu/kernels/basecalling/lstm_cell_bf16_acc_cascade/DESIGN.md: rewritten from a 406-line investigation log (Followup A-G stages, WEDGE-vs-PASS status flips, hypothesis falsification entries) into a 60-line design note covering the architectural rationale (FP32-cascade vs bf16-writeback precision wall, AM020 references, topology diagram, file inventory, known limitations). - README.md: dropped the dead 'v0.1 release notes' link to a tag that doesn't exist; rewrote the Status section to point at the new docs/STATUS.md per-subsystem inventory. - docs/STATUS.md: NEW — explicit per-subsystem table of what works end-to-end vs what's an extracted module that needs a v0.2 driver to drive on hardware. Distinguishes ✅ working / ✅ extracted / ⚠️ deprecated / ⚠️ v0.2 scope. - docs/REPRODUCE.md: rewrote from placeholder text into a real reproduction recipe — hardware prerequisites, software install, pip-install bionpu, byte-equality smoke check + negative control, manual kernel build, host-runner invocation, energy-methodology pointer, sanity-log discipline. - CHANGELOG.md: NEW — v0.1 release notes covering what landed and what's deferred to v0.2. Other per-kernel DESIGN.md files were checked: only the one cascade file carried a substantive investigation log; the others were already clean after the earlier scrub passes. All 18 verify-harness tests still pass; 70/70 Python files parse.
1 parent c83a4b6 commit 5713b10

51 files changed

Lines changed: 466 additions & 932 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Changelog
2+
3+
## v0.1.0.dev0 — Initial public release (in progress)
4+
5+
The first public release of `bionpu` — extracted from the internal
6+
genetics development tree, re-licensed to GPL-3.0, scrubbed of
7+
project-internal task IDs, and structured as a defensible asset.
8+
9+
### Added
10+
11+
- **`bionpu.verify`** — byte-equality harness with documented public
12+
API, frozen and tested before extraction:
13+
- `compare_against_cas_offinder()` for CRISPR off-target hits TSVs
14+
(sorts by canonical key, normalises line endings, computes
15+
SHA-256 over the canonical wire form, reports first-N
16+
divergences with chrom/position/strand).
17+
- `compare_against_dorado()` for Nanopore basecaller FASTQs
18+
(sorts by `read_id`, normalises CRLF, field-level divergence
19+
diagnostics distinguishing `read_id` / sequence / quality /
20+
header mismatches).
21+
- 18 tests covering equality, sort-order invariance, CRLF
22+
handling, divergence reporting, max-divergences cap, and
23+
pinned SHA-256 regression guards.
24+
25+
- **`bionpu.dispatch`** — NPU dispatch infrastructure:
26+
- `npu_silicon_lock` — process-level mutex on
27+
`/tmp/bionpu-npu-silicon.lock` with pre-flight wedge detection
28+
(scans `dmesg` for `aie2_dump_ctx` / `Firmware timeout` /
29+
`aie2_tdr_work` signatures BEFORE submitting work).
30+
- `NpuBackend` abstraction — `pyxrt` in-process path (default) +
31+
`subprocess` fallback for environments where `pyxrt` cannot be
32+
imported.
33+
34+
- **`bionpu.kernels`** — 18 AIE2P kernels:
35+
- 6 CRISPR: PAM filter (filter-early + filter-late variants),
36+
match (singletile / multitile / multitile-memtile),
37+
`crispr_net`, pktmerge variant.
38+
- 12 basecalling: conv stem, linear projection, LSTM cell
39+
(bf16 / int8 / acc / acc-cascade / compressed variants),
40+
LSTM stack (3 variants).
41+
42+
- **`bionpu.bench`** — energy + timing measurement:
43+
- Three per-device readers: AMD RAPL (CPU), `nvidia-smi` (GPU),
44+
`xrt-smi` (NPU AIE-partition firmware estimate).
45+
- Three-phase sustained-load measurement shape (pre-warmup,
46+
measurement window, drift-detection window).
47+
- `POWER_DOMAINS.md` per-device specification (includes,
48+
excludes, sampling rates, sources, known issues).
49+
50+
- **`bionpu.data`** — Cas-OFFinder canonical TSV normaliser, public
51+
dataset fetchers (Doench 2016, GUIDE-seq, HG002 pod5, reference
52+
genomes), in-repo smoke fixture loaders.
53+
54+
- **`bionpu.quant`** — quantization passport schema, `onnxruntime`
55+
calibration driver, Peano IR export hook.
56+
57+
- **`bionpu.iron_extensions.cascade_stream`** — back-compat shim
58+
for cascade-chain topology helpers. **Superseded** by upstream
59+
`aie.iron.CascadeFifo` ([Xilinx/mlir-aie #3039]); will be removed
60+
once that PR's `mlir-aie` floor is set in `pyproject.toml`.
61+
62+
- **`bionpu.cli`**`bionpu verify {crispr,basecalling}` end-to-end
63+
CLI; exits 0 on byte-equality, 1 on divergence.
64+
65+
- **`reference/`** — Cas-OFFinder canonical reference (422 rows),
66+
chr22 ten-guide fixture, basecalling smoke FASTQ.
67+
68+
- **`docs/`** — energy methodology, reproduction recipe, per-subsystem
69+
v0.1 status.
70+
71+
[Xilinx/mlir-aie #3039]: https://github.com/Xilinx/mlir-aie/pull/3039
72+
73+
### Deferred to v0.2
74+
75+
- End-to-end driver scripts for `bionpu scan` / `bionpu basecall`
76+
(kernels are extracted and individually buildable; what's missing
77+
is the top-level Python that ties scan input → NPU dispatch →
78+
output → verify into one CLI invocation).
79+
80+
- Pre-computed `benchmarks/results/{crispr,basecalling}/*.json`
81+
snapshots for chr1 / chr19 / chr22 / a representative pod5.
82+
83+
- Conversion of per-kernel `DESIGN.md` files from
84+
internal-investigation logs into upstream-style architectural
85+
reference docs (one done so far —
86+
`lstm_cell_bf16_acc_cascade/DESIGN.md`).
87+
88+
- Removal of the `bionpu.iron_extensions` back-compat shim once
89+
`aie.iron.CascadeFifo` reaches a tagged `mlir-aie` release.

README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,21 @@ tests/ pytest suite
7676

7777
## Status
7878

79-
This is the v0.1 release of `bionpu` as a public asset. Active
80-
development happens upstream in opensensor's wider AMD-XDNA stack;
81-
this repo is a reproducible snapshot suitable for evaluation,
82-
verification, and external collaboration.
83-
84-
See the [v0.1 release notes](https://github.com/opensensor/bionpu/releases/tag/v0.1)
85-
for the headline benchmark numbers.
79+
This repository is the public release of work that's developed
80+
internally; the public version ships the kernels, dispatch infrastructure,
81+
verification harness, energy methodology, and the bench module. See
82+
[`docs/STATUS.md`](docs/STATUS.md) for the per-subsystem state — what
83+
runs end-to-end vs what's an extracted module that needs a driver
84+
script to drive it on hardware.
85+
86+
The headline contribution as of v0.1 is the byte-equality harness
87+
(`bionpu.verify`) — see [`src/bionpu/verify/README.md`](src/bionpu/verify/README.md).
88+
The energy methodology that backs every `J/Mbp` number we publish is
89+
in [`docs/ENERGY_METHODOLOGY.md`](docs/ENERGY_METHODOLOGY.md).
90+
91+
Pre-computed benchmark results across multiple chromosomes are
92+
v0.2 scope and not yet committed; the methodology and reproduction
93+
scripts are in place so other hosts can produce comparable numbers.
8694

8795
## License
8896

docs/REPRODUCE.md

Lines changed: 108 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,131 @@
11
# Reproducing `bionpu` results
22

3-
> Status: shell — full reproduction notes will land during the v0.1
4-
> extraction. This document tracks what's required end-to-end so a
5-
> reader can run the benchmarks on their own machine.
3+
End-to-end reproduction recipe for everything in `bionpu` that's
4+
runnable today. v0.1 ships the byte-equality harness end-to-end; the
5+
full scan / basecall pipelines are v0.2 scope (see
6+
[`STATUS.md`](STATUS.md)) — this document covers what works now and
7+
documents the v0.2 driver shape so you can drive the kernels manually
8+
in the meantime.
69

7-
## Hardware
10+
## 1. Hardware
811

9-
- AMD Ryzen AI 9 HX (Strix family) or other AIE2P-equipped silicon.
10-
- Linux (kernel ≥ 6.10 with `amdxdna` available).
12+
- AMD Ryzen AI 9 HX (Strix family) or other AIE2P-equipped silicon
13+
with the `amdxdna` accelerator exposed at `/dev/accel/accel0`.
14+
- Linux kernel ≥ 6.10 with `amdxdna.ko` loaded.
1115

12-
## Software prerequisites
16+
## 2. Software prerequisites
1317

14-
- `xdna-driver` built and `amdxdna.ko` loaded.
15-
- XRT (Xilinx Runtime) installed (`/opt/xilinx/xrt`).
16-
- `mlir-aie` built; `aiecc` on `$PATH`.
17-
- Peano (LLVM-AIE) installed; `$PEANO_INSTALL_DIR` set.
18-
- Python ≥ 3.11.
18+
- **`xdna-driver`** built and `amdxdna.ko` loaded. Verify:
19+
```sh
20+
lsmod | grep amdxdna
21+
ls -l /dev/accel/accel0
22+
```
23+
- **XRT** installed at `/opt/xilinx/xrt`:
24+
```sh
25+
source /opt/xilinx/xrt/setup.sh
26+
xrt-smi examine # must list the NPU device
27+
```
28+
- **`mlir-aie`** built; `aiecc` on `$PATH`. The recommended `mlir-aie`
29+
is the wheel-built `ironenv` — see the [opensensor/genetics
30+
bring-up
31+
guide](https://github.com/opensensor/genetics/blob/main/docs/xdna-driver-build.md)
32+
for the canonical setup.
33+
- **Peano (LLVM-AIE)** installed; `$PEANO_INSTALL_DIR` points at the
34+
install tree.
35+
- **Python ≥ 3.11**.
1936

20-
See `bionpu`'s upstream documentation in
21-
[opensensor/genetics](https://github.com/opensensor/genetics) for the
22-
NPU bring-up steps if the above are not yet on your system.
23-
24-
## Install bionpu
37+
## 3. Install bionpu
2538

2639
```sh
2740
git clone https://github.com/opensensor/bionpu.git
2841
cd bionpu
2942
pip install -e ".[test]"
3043
```
3144

32-
## Run a single benchmark
45+
This installs the `bionpu` CLI on `$PATH` and exposes the
46+
`bionpu.{verify,kernels,dispatch,bench,data,quant}` modules to your
47+
Python interpreter.
48+
49+
## 4. Reproduce the byte-equality smoke check
3350

34-
CRISPR off-target scan against chr22, with byte-equality vs cas-offinder:
51+
This is the simplest end-to-end demonstration of the verify harness.
52+
No NPU required — just the committed reference data.
3553

3654
```sh
37-
benchmarks/crispr/run_chr.sh chr22
55+
# Compare the canonical reference TSV against itself; expect EQUAL.
56+
bionpu verify crispr \
57+
reference/crispr/casoffinder-canonical.tsv \
58+
reference/crispr/casoffinder-canonical.tsv
59+
# Expected: result EQUAL, 422 records, matching SHA-256s. Exit code 0.
60+
61+
# Negative control: mutate the input and expect DIVERGENT.
62+
sed -i.bak 's/^chr/chr_DIRTY_/' /tmp/dirty.tsv 2>/dev/null
63+
sed 's/^chr/chr_DIRTY_/' reference/crispr/casoffinder-canonical.tsv > /tmp/dirty.tsv
64+
bionpu verify crispr /tmp/dirty.tsv reference/crispr/casoffinder-canonical.tsv
65+
# Expected: result DIVERGENT, exit code 1, first divergence reported.
3866
```
3967

40-
Basecalling against a small pod5 fixture, with byte-equality vs Dorado:
68+
## 5. Reproduce a kernel build (any one)
69+
70+
Pick any kernel under `src/bionpu/kernels/`. For the CRISPR PAM filter:
71+
72+
```sh
73+
cd src/bionpu/kernels/crispr/pam_filter
74+
export MLIR_AIE_DIR=<path/to/mlir-aie>
75+
export PEANO_INSTALL_DIR=<path/to/llvm-aie>
76+
make NPU2=1
77+
# Expected: build/final.xclbin + build/insts.bin produced.
78+
```
79+
80+
Each kernel directory ships `MANIFEST.md` describing the inputs,
81+
outputs, expected on-tile placement, and any kernel-specific
82+
`make` flags.
83+
84+
## 6. Run a kernel against silicon (manual driver, v0.2-scope)
85+
86+
The v0.2 `bionpu scan` / `bionpu basecall` drivers are not yet wired,
87+
so end-to-end pipeline runs go through the per-kernel host runner:
4188

4289
```sh
43-
benchmarks/basecalling/run_pod5.sh reference/basecalling/smoke.pod5
90+
cd src/bionpu/kernels/crispr/pam_filter
91+
# After 'make NPU2=1':
92+
./host_runner --xclbin build/final.xclbin \
93+
--insts build/insts.bin \
94+
--in <input.bin> \
95+
--out /tmp/npu_hits.tsv
96+
bionpu verify crispr /tmp/npu_hits.tsv reference/crispr/casoffinder-chr22-10guides.tsv
4497
```
4598

46-
Pre-computed results for chr1, chr19, chr22 are checked into
47-
`benchmarks/results/`. To regenerate, run the same scripts and they
48-
will overwrite the JSON snapshots in place.
99+
The `host_runner` argv shape varies per kernel — see each kernel's
100+
`MANIFEST.md`.
101+
102+
## 7. Energy methodology
103+
104+
For the per-device energy figures the bench harness produces, read
105+
[`ENERGY_METHODOLOGY.md`](ENERGY_METHODOLOGY.md) first. The TL;DR:
106+
107+
- CPU rail = AMD RAPL package counter, package-only, no DRAM.
108+
- GPU rail = `nvidia-smi` total board (compute + memory + VRMs).
109+
- NPU rail = `xrt-smi` AIE-partition firmware-internal estimate.
110+
111+
A figure caption that compares any two of these without listing the
112+
includes / excludes is not an honest comparison.
113+
114+
## 8. Sanity-log discipline
115+
116+
Calibration evidence — which counters are AVAILABLE / UNAVAILABLE on
117+
your host, what the probe path returned, what the resolution path
118+
was — is recorded in
119+
[`src/bionpu/bench/energy/SANITY-LOG.md`](../src/bionpu/bench/energy/SANITY-LOG.md).
120+
**Append, never overwrite.** A run on a different host (different
121+
kernel / driver / governor) is a different measurement; record it
122+
as a new entry rather than editing in place.
123+
124+
## What's deferred to v0.2
125+
126+
- `bionpu scan` and `bionpu basecall` end-to-end drivers (the kernels
127+
are migrated and buildable; what's missing is the Python that
128+
drives them as a single CLI invocation).
129+
- Pre-computed `benchmarks/results/{crispr,basecalling}/*.json`
130+
snapshots for chr1 / chr19 / chr22 / a representative pod5.
131+
- A tagged `v0.1` GitHub release with the headline numbers.

docs/STATUS.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# `bionpu` v0.1 status
2+
3+
What works end-to-end vs what's an extracted module that still needs
4+
a driver script before it's usable on hardware.
5+
6+
## Subsystem status
7+
8+
| Subsystem | Status | Notes |
9+
|---|---|---|
10+
| `bionpu.verify.crispr` — byte-equality vs Cas-OFFinder TSV | ✅ working | 18 tests in `tests/test_verify_crispr.py`. Smoke-tested on the 422-row canonical reference at `reference/crispr/casoffinder-canonical.tsv` via `bionpu verify crispr`. |
11+
| `bionpu.verify.basecalling` — byte-equality vs Dorado FASTQ | ✅ working | 10 tests in `tests/test_verify_basecalling.py`. Field-level divergence diagnostics (`read_id` / sequence / quality / header). |
12+
| `bionpu.dispatch` — silicon-serialisation lock + NPU backend | ✅ extracted | The `npu_silicon_lock` mutex with pre-flight wedge detection is the canonical way to serialise `/dev/accel/accel0`. The `NpuBackend` abstraction covers `pyxrt` (in-process) and `subprocess` (fallback) paths. |
13+
| `bionpu.kernels.{crispr,basecalling}` — AIE2P MLIR-AIE kernels | ✅ extracted, build via `make` | Six CRISPR kernels (PAM filter, match singletile, match multitile, match multitile memtile, crispr_net, pktmerge variant) and twelve basecalling kernels (conv stem, LSTM cell variants, linear projection, LSTM stack). Each ships `Makefile`, Python topology, kernel C++, and a `MANIFEST.md` describing the inputs/outputs. Pre-built `host_runner` binaries are gitignored — users `make` to rebuild. |
14+
| `bionpu.bench.energy` — RAPL / nvsmi / xrt energy readers | ✅ extracted | Three per-device readers + the measurement harness. `bionpu/bench/POWER_DOMAINS.md` is the per-device specification. `bionpu/bench/energy/SANITY-LOG.md` is the calibration evidence log. |
15+
| `bionpu.data.canonical_sites` — Cas-OFFinder TSV normaliser | ✅ working | The canonical home for the `(chrom, start, mismatch_count, guide_id, strand)` sort + LF-newline canonicalisation that the `verify` module depends on. |
16+
| `bionpu.data.fetchers` — public dataset fetchers | ✅ extracted | SHA-pinned fetchers for Doench 2016, GUIDE-seq, HG002 pod5, reference genomes. The fetcher framework documents what every dataset entry must satisfy. |
17+
| `bionpu.quant` — quantization passport / calibration | ✅ extracted | Calibration driver around `onnxruntime.quantization`, plus the passport schema that every quantized model in the repo carries (calibration source + op recipe + reproducibility hash). |
18+
| `bionpu.iron_extensions.cascade_stream` | ⚠️ deprecated; superseded by upstream `aie.iron.CascadeFifo` ([Xilinx/mlir-aie #3039](https://github.com/Xilinx/mlir-aie/pull/3039)) | Kept as a back-compat shim for designs written against the pre-upstream API. Will be removed once that PR's `mlir-aie` floor is set in `pyproject.toml`. |
19+
| `bionpu.cli``bionpu verify {crispr,basecalling}` | ✅ working | Returns exit code 0 on byte-equality, 1 on divergence. |
20+
| `bionpu.cli``bionpu scan` / `bionpu basecall` / `bionpu bench` | ⚠️ v0.2 scope | Stub subcommands that print a "v0.2 scope" message. The kernels they would invoke are extracted (and individually buildable via `make`); what's missing is the top-level Python that ties scan input → NPU dispatch → output → verify into one command. Expected to land in v0.2. |
21+
| Pre-computed `benchmarks/results/` snapshots | ⚠️ v0.2 scope | Empty until the `bionpu scan` / `bionpu basecall` driver is wired (above). The reproduction scripts at `benchmarks/{crispr,basecalling}/run_*.sh` are skeletons that document the v0.2 driver shape. |
22+
23+
## What's in v0.1 but you should know about it
24+
25+
- **Per-kernel `DESIGN.md` files** carry historical design notes from the
26+
internal investigations that produced each kernel. They're useful
27+
background for someone reading the kernel source, but they're written
28+
in the voice of an investigation log rather than reference docs. A
29+
cleanup pass to convert these into upstream-style architectural
30+
references is on the v0.2 list.
31+
32+
- **`bionpu.iron_extensions`** ships, but new code should use
33+
`aie.iron.CascadeFifo` from upstream `mlir-aie` instead.
34+
35+
- **`bionpu.cli`'s argparse skeleton** has the v0.2 subcommands listed
36+
with stub implementations. They print to stderr with a clear "v0.2
37+
scope" message rather than silently failing.
38+
39+
## v0.2 plan
40+
41+
1. Wire `bionpu scan` and `bionpu basecall` into a real driver pipeline.
42+
2. Run `benchmarks/{crispr,basecalling}/run_*.sh` against chr1 / chr19 /
43+
chr22 (and a small representative pod5) to populate
44+
`benchmarks/results/`.
45+
3. Tag `v0.2` once the headline numbers are reproducible from the
46+
committed scripts on a clean machine.
47+
4. Remove the `bionpu.iron_extensions` shim if upstream `mlir-aie` ships
48+
a release containing the `CascadeFifo` PR.
49+
5. Convert kernel `DESIGN.md` investigation logs into architectural
50+
reference docs.

src/bionpu/bench/UNITS.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# that every metric below has unit + formula + scope + percentile_method where
44
# applicable). Do not remove keys; add new metrics by appending entries.
55
spec_version: "1.0.0"
6-
spec_purpose: "Lock measurement units before any track measures anything (umbrella PRD §4.2; risk row 'apples-to-oranges measurements')."
6+
spec_purpose: "Lock measurement units before any track measures anything ( §4.2; risk row 'apples-to-oranges measurements')."
77
generated: "2026-04-25"
88
owner: ""
99
consumed_by: ["", "", "", "", "", ""]
@@ -89,9 +89,9 @@ metrics:
8989

9090
This document fixes every metric unit and computation rule that the bench
9191
harness, the energy readers, the basecalling track, and the
92-
CRISPR track must conform to. It exists to discharge umbrella PRD §4.2's
92+
CRISPR track must conform to. It exists to discharge §4.2's
9393
requirement that cross-track numbers be comparable, and to neutralize the
94-
"apples-to-oranges measurements" risk row in the umbrella PRD risk table.
94+
"apples-to-oranges measurements" risk row in the risk table.
9595

9696
If a number is reported in this repo without conforming to this file, the
9797
writeup pipeline MUST refuse to render it.
@@ -251,7 +251,7 @@ energy can be recomputed from the raw counter samples post-hoc.
251251
exact value used so a reviewer can reproduce.
252252
- **PRD CRISPR §3.4 evaluation:** the NPU figure must be lower than the GPU
253253
figure to claim the energy thesis. Otherwise the negative result is
254-
reported per umbrella PRD §3.4.
254+
reported per §3.4.
255255

256256
---
257257

src/bionpu/bench/energy/SANITY-LOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ external reviewer asks.
376376

377377
```
378378
source /opt/xilinx/xrt/setup.sh
379-
source ~/xdna-bringup/ironenv/bin/activate
379+
source <ironenv>/bin/activate
380380
pytest tests/test_t44_xrt_energy.py -m "not npu" -q # all GREEN (fast)
381381
pytest tests/test_t44_xrt_energy.py -m npu -q # all GREEN (10-20 s)
382382
python -m bionpu bench --all --device npu --iters 3 # writes results/u-m2/measurements.json

src/bionpu/data/fetchers/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
"""Public dataset fetcher framework.
1717
18-
Per umbrella PRD §4.4: each public dataset has a fetcher with checksum
18+
Per §4.4: each public dataset has a fetcher with checksum
1919
verification, license/citation in code, and a manifest entry. Network
2020
failures must record URL + HTTP status + retry guidance.
2121

0 commit comments

Comments
 (0)