Skip to content

ci(teeth): eight shards, the count read from the workflow, and stale PR runs cancelled - #481

Merged
TAIPANBOX merged 8 commits into
mainfrom
ci/teeth-eight-shards
Oct 26, 2026
Merged

TAIPANBOX merged 8 commits into
mainfrom
ci/teeth-eight-shards

Conversation

@TAIPANBOX

Copy link
Copy Markdown
Owner

What

The teeth harness is the long pole of every CI run here: four shards of 11.6 to 19.3 minutes each on main (run 39356312313). Its cases add up to 54.7 minutes across the shards (per-case timestamps of run 40259164936), 9.0 of them in the five stated-numbers cases, each of which runs the full suite.

  • Eight shards instead of four. Each shard now carries 30 cases instead of 59 or 60.
  • The shard count is written once. It used to be written twice, as the workflow matrix and as shards=4 in scripts/teeth-shards-cover-every-case.sh, and nothing checked that they agreed (invariant 113 named that gap). The gate now reads the count from ci.yml, where the teeth job's matrix, its TEETH_SHARD divisor and its name each state it, and refuses when the three disagree or the matrix is not 1..N.
  • Stale pull request runs are cancelled. concurrency cancels a pull request's older run when a newer push arrives. A push to main is never cancelled: its group is its own commit.

Proof

  • The gate on the real workflow: 240 cases, 8 shards of 30, each case run by exactly one shard.
  • Three new teeth cases, each planting one fault in ci.yml: a matrix that skips a shard, a divisor left at four under a matrix of eight, and the matrix gone (which must say it measured nothing). Each was also run by hand against the gate: all three fail with the expected message, and the unmutated workflow passes.
  • The wall time with eight shards is what this pull request's own CI measures.

Order

This branch sits on top of #478, which itself carries #480, so its diff shows their commits until they merge. Its own change is the last commit, ci(teeth): eight shards, ....

🤖 Generated with Claude Code

TAIPANBOX and others added 8 commits October 26, 2026 02:21
…e upstream

Four cases for the MCP broker, added before the fix so CI shows them red
on the current code:

- a JSON-RPC batch of two tools/call over HTTP is refused with 400 and
  -32600, nothing reaches the upstream and the PDP is never asked, with a
  policy that denies every tool;
- the stdio transport refuses the same batch;
- a bare string, a number, null, and an object whose method is not a
  string are refused the same way;
- one request and a client's JSON-RPC response (id and result, no method)
  still reach the upstream, so the fix cannot refuse too much.

Scenarios in features/a-batch-cannot-carry-a-call-past-the-gate.feature,
each bound to its test. README badge and PROGRESS.md count 2418.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every check the broker makes keys on `method` read from one JSON object.
A batch is an array, so its method read as empty and every tools/call
check was skipped: the policy decision, the identity requirement, drift,
the taint gate, the argument scans and the tool_call record, and a
batched tools/list answer was never scanned. The array was forwarded as
it came, over HTTP and stdio alike.

process_call, which both transports reach, now refuses first, before an
upstream is resolved or a secret read: a message that is not one JSON
object, or whose method is present and not a string, gets JSON-RPC
-32600 with a null id and HTTP 400. MCP removed batching in its
2025-06-18 revision. A JSON-RPC response (no method) is still carried.

Invariant 142 in CLAUDE.md. The previous commit's tests failed on the
unfixed broker in CI (run 40258757907: three refused-case tests red at
the "nothing reached the upstream" assertion, the must-not-fire case
green).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…visories

rustls-webpki 0.103.15 -> 0.103.16 (RUSTSEC-2026-0382) and webpki-roots
1.0.8 -> 1.0.11 (RUSTSEC-2026-0381) in both lockfiles, the workspace and
crates/cluster. wasmtime 49.0.3 -> 49.0.4 (RUSTSEC-2026-0378,
RUSTSEC-2026-0379) in the workspace lockfile only, staying on 49.x; the
cranelift and pulley crates move with it from 0.136.3 / 49.0.3 to
0.136.4 / 49.0.4.

Lockfile-only: no manifest changes, no new crate. crypto-bigint 0.5.5
(RUSTSEC-2026-0387) is the fifth advisory and needs the p256 0.14
migration in the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1
…026-0387)

p256 0.13 is pulled only by this workspace's own crates (cloud,
delegation, dpop, gateway). Moving all six manifest lines to 0.14 brings
elliptic-curve 0.14.3, ecdsa 0.17.0, pkcs8 0.11, sec1 0.8, signature 3.0
and rfc6979 0.6, and replaces crypto-bigint 0.5.5 with 0.7.9, so
`cargo tree -i crypto-bigint --all-features --target all` no longer shows
a 0.5.x.

The code changes are two renames, nothing else:
- `VerifyingKey::to_encoded_point(compress)` is `to_sec1_point(compress)`
  in ecdsa 0.17; `x()`, `y()` and `as_bytes()` on the result are unchanged.
- `SigningKey::random(&mut OsRng)` is deprecated, and rand_core is 0.10
  with no `OsRng`; the test fixtures that made a random key now call
  `<SigningKey as p256::elliptic_curve::Generate>::generate()`. Production
  key generation (gateway checkpoints) already drew bytes with getrandom
  0.2 and `SigningKey::from_slice`, which is unchanged.
`Signature::from_slice`, `to_bytes`, `VerifyingKey::from_sec1_bytes`,
`SecretKey::from_pkcs8_pem` / `from_sec1_pem` / `from_pkcs8_der` /
`from_sec1_der` keep their names and signatures. P-256 keeps
`NORMALIZE_S = false`, so a high-S signature still verifies and signing
still does not normalise s, as in 0.13.

One rustfmt collapse in an audit_sign test follows the shorter name; the
assertion is the same. The gateway manifest comment on hmac is corrected:
hmac 0.12 reached the lockfile through p256 0.13's rfc6979, and since 0.14
(which uses hmac 0.13) the gateway's own line is its only edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1
The external P-256 values the suite already checked were three keyryx
thumbprints (measured from keyryx's Go code) and agent-passport's
checkpoint vectors, which it verifies but does not produce. Nothing held a
signature this code produces, a SEC1 public key, or a PKCS#8 / SEC1
private key parse to an external value, so a curve library that changed
one of those could pass the suite as long as it agreed with itself.
Three tests now hold the bytes to RFC 6979
appendix A.2.5 (a P-256 key, its point, its deterministic SHA-256
signatures), with the base64 forms, the RFC 7638 thumbprint and the
PKCS#8 / SEC1 encodings written out by Python `cryptography` 50.0.1,
never by p256:

- gateway::checkpoints: the checkpoint key's JWK x and y, its kid, and the
  raw r||s over "sample" and "test".
- cloud::devices: the RFC signature verifies under the uncompressed and
  compressed SEC1 key; with its halves swapped, or in DER, it is refused.
- cloud::audit_sign: the RFC key loads from PKCS#8 PEM, SEC1 PEM, PKCS#8
  DER, SEC1 DER and a raw scalar, and each signs the same empty-chain
  manifest to the same public key and signature bytes.

Each went red against a deliberately broken encoding first (padded
standard base64 for x and y; s before r; a DER-only signature parser; a
compressed public key and a DER signature in the manifest) and green on
the real code. All three also pass unchanged on main with p256 0.13.2,
so 0.13 and 0.14 put the same bytes on the wire. Encoding the manifest's
public key compressed passed every older test; only the new one caught it.

Scenarios: features/the-p256-wire-format-is-unchanged.feature, five,
each bound. The suite goes from 2414 to 2417; README.md and PROGRESS.md
say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1
`secretscan / tree` flagged the PKCS#8 header of the RFC 6979 appendix
A.2.5 P-256 key that the audit-signing-key encoding test loads
(crates/cloud/src/audit_sign.rs, fingerprint 3021d90eb9437b2d, the same
header fingerprint already allowed for the test keys in
crates/cloud/tests/it/oidc.rs and crates/dpop/src/lib.rs). The key is a
published test vector and signs nothing. The SEC1 header in the same
file was already covered by its existing entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1
…cp-broker-refuses-batches

# Conflicts:
#	PROGRESS.md
#	README.md
…PR runs cancelled

The teeth harness ran as four shards of 11.6 to 19.3 minutes each on
main (run 39356312313), the long pole of every CI run; its cases add up
to 54.7 minutes (run 40259164936). Eight shards halve the cases each
runner carries.

The count was written twice, as the matrix and as `shards=4` in the
partition gate, and nothing checked that they agreed (invariant 113 said
so). The gate now reads it from ci.yml, where the matrix, TEETH_SHARD and
the job name each state it, and refuses when they disagree or the matrix
is not 1..N. Three teeth cases plant each fault: a matrix that skips a
shard, a divisor left at four, the matrix gone.

`concurrency` cancels a pull request's older run when a newer push
arrives; a push to main is never cancelled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TAIPANBOX
TAIPANBOX enabled auto-merge October 26, 2026 04:32
@TAIPANBOX
TAIPANBOX merged commit fa0fdf9 into main Oct 26, 2026
20 checks passed
@TAIPANBOX
TAIPANBOX deleted the ci/teeth-eight-shards branch October 26, 2026 04:32
TAIPANBOX added a commit that referenced this pull request Oct 26, 2026
A patch release: the MCP broker refuses a JSON-RPC batch and any message
it cannot judge (#478), five RustSec advisories cleared with p256 0.14
(#480), and the teeth harness on eight shards (#481).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants