Four findings, all real.
- No catalog-level bound. The per-skill caps bound what ONE entry can
cost; nothing bounded how many entries there are, and SEP-2640 puts no
ceiling on a catalog — every entry costs at least one resources/read,
so a large listing made `--verify` run indefinitely and transfer
unboundedly. Adds SKILL_MAX_CATALOG_SKILLS / SKILL_MAX_CATALOG_BYTES.
Entries past the budget are still reported, with their static findings
and an `incomplete` reason, rather than dropped or failed: they were
not checked, which is neither a pass nor a verdict against the server.
- `responseBytes` charged `text.length`, which counts UTF-16 code units.
That undercharges non-ASCII by up to 3x, so a decoy block of emoji kept
the counter under 16 MiB while the wire carried far more. Adds
`utf8Length`, an allocation-free UTF-8 byte count — deliberately not
TextEncoder, which would copy a payload the server chose the size of.
- `checkSkillNameCollisions` was O(N^2) in both work and output for a
group of N: every entry filtered all N URIs and embedded the other
N-1. Duplicate names are legal and a server controls N. Now names a
bounded sample and counts the rest.
- `--verify` help documented exit 7 but not exit 8.
The three-tier bounding (per skill, per skill on the wire, per run) is
now a table in clients/cli/README.md, since the run bound is this tool's
limit rather than the spec's and should not read as a conformance rule.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ND46Ci64DC3tzBy9aG81Nc
Signed-off-by: cliffhall <cliff@futurescale.com>