feat(guards): release-composition check, and a runner for the edition fence (7.6/7.8) - #23
Open
damonleelcx wants to merge 2 commits into
Open
feat(guards): release-composition check, and a runner for the edition fence (7.6/7.8)#23damonleelcx wants to merge 2 commits into
damonleelcx wants to merge 2 commits into
Conversation
…robe P4 (4.1-4.10). One flag on the existing `aikey add`, plus src/provider_selfdesc.rs, five source fences and seven real-socket tests. 🔴 THE FILE IS A CLAIM AND THE PROBE IS THE FACT `.well-known/aikey-provider.json` is written by the operator of the endpoint we are about to send a credential to. It pre-fills the form and nothing else: every protocol it declares is probed, the two are shown side by side, and only what answered is stored. A relay that names a protocol it does not serve would otherwise produce a credential that exists, looks configured, and cannot route — which the three-axis linkage already shipped once (D-3), and which surfaces days later as "my key does not work" rather than at the moment it was created. The comparison is rendered, not just computed, and disagreements are marked. A table where both columns look the same is a table people skim; the value of this step is that the user SEES us disagree with the relay's own file, because that is a fact about a service they are about to depend on and they are the one who can go and ask about it. 🔴 THIS GATE IS DELIBERATELY WEAKER THAN THE CHECK SITE'S (R-3b) dgcheck refuses private addresses and refuses http outright. Copying that here would delete the Production edition's normal onboarding path: an enterprise customer's relay lives inside their own network, and `--from-url http://10.4.2.9:8443` is what onboarding looks like there. Three questions decide the strength — whose machine is dialling, whose address is it, whose key is being spent — and all three answer differently on a user's laptop than on our server. So: private and loopback allowed, http allowed with a warning that names the consequence, cloud metadata refused on every boundary. T-EDN-3b is the reverse fence and it guards the future rather than the code: tighten the gate "for consistency" and it goes red naming R-3b and the workflow that just died. Two things the metadata rule gained on the way: the whole of 169.254.0.0/16 and fe80::/10 rather than three literals (nobody runs a relay on link-local, and a literal list keeps missing the variants), and a userinfo case — `http://evil.example@169.254.169.254/` — because the host is what follows the LAST '@'. 🔴 THE GATE RE-RUNS ON EVERY HOP Hop 2 is a URL the far end chose after we approved hop 1, which is the shape of a redirect-to-metadata attack. A version that checked only the first hop looks identical in review and stops nothing. MODEL ENUMERATION COMES OFF THE PROBE THAT ALREADY RAN The API probe is a `GET /v1/models` against the same base URL with the same credential. Issuing a second request "to enumerate models" would be a second code path that can disagree with the first: one green cell and one empty list, with nothing saying why. Parsed before the 512-char truncation, because model lists are routinely longer than that, and in three shapes — a parser that knew only the OpenAI envelope returns an empty list for Gemini, which renders as "this relay serves no models" rather than "we did not recognise the answer". FOUND BY RUNNING IT - Redirects were mis-parsed as bodies. With following disabled, ureq hands a 3xx back as an ordinary success, so the loop that looked for redirects under `Err` neither followed nor re-gated them — it fed an empty body to the JSON parser and told the user their relay serves invalid JSON. No amount of unit testing the gate would have shown it; a live socket did, immediately. - "The relay did not answer" and "we could not ask" are different sentences. The suite gates API and Chat on the LOCAL PROXY reaching the upstream, because that is the path real traffic takes — so on a machine where the proxy is not running, every row fails for a reason that has nothing to do with the endpoint. Reporting that as "this relay declared things we could not confirm" blames a third party for our own missing process, which is the same mistake as counting our outages against somebody's uptime. Now its own branch, pointing at `aikey proxy start`. - The glyph fence caught 🔴 inside three user-visible strings (Win10 conhost renders it as □). Comments keep it; messages do not. FENCES One write path (`apply_add_core_on_conn` exactly once — a second writer is a row that skipped alias validation and provider normalisation); the measurement and not the declaration reaches the writer; the probe is not TTY-gated (the ordinary probe is a convenience, this one is the reason the flag is safe); no edition branch anywhere on the path, which is how three-edition parity is asserted without conjuring a Production install; and the explanation of why the two gates differ must stay next to the gate. Four mutations each proved red: writing the declaration instead of the measurement, TTY-gating the probe, tightening the gate to match dgcheck, and adding `deny_unknown_fields` for tidiness (which would break every v1 CLI in the field the day a relay adds a v2 field). Branched from develop-v1.0.5 rather than the checked-out feature branch: the files this touches are identical between the two, and P4 does not belong on top of unrelated work. 1266 unit tests + every integration suite green; clippy -D warnings clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… fence (7.6/7.8) Task 7.8 is "offline package self-proof (if a CLI release is involved)". The conditional has an answer nobody had given: it IS involved -- P4's `aikey add --from-url` ships in this crate. And the feature is on this branch only. `git branch --contains` returns feat/public-trust-check-platform and nothing else -- not develop-v1.0.5, not develop-v1.0.4, not main. Cut a release from the RC line today and the offline package contains none of it. The self-proof cannot catch that. It proves the package INSTALLS, not what is in it: a package built from a branch that never received this change deploys perfectly, passes every check, and ships without the feature the release was cut for. Nothing in the pipeline knows what the release was supposed to contain. check-release-composition.sh asserts the DECLARATIONS are present on the branch a release would build. Declarations, not identifiers, because the obvious check reports the opposite: `git grep from_url develop-v1.0.5` hits `infer_provider_from_url`, an unrelated import-parser helper, and a substring match here turns "absent" into "present" -- worse than no check. A self-test pins that exact false positive. Also wires check-from-url-edition-parity.sh (T-EDN-1, delivered for 7.6) into make and CI. It had NO runner: not in the Makefile, not in CI, never run outside the session that wrote it. A fence that cannot fail looks exactly like a fence that is satisfied. CI runs only the composition SELF-TEST. The check itself is expected to be red while the change sits on a feature branch -- that is the true state, not a CI failure. What must stay green is the checker's ability to tell present from absent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Task 7.8 is "offline package self-proof (if a CLI release is involved)". The conditional had an answer nobody had given.
It is involved — and the release branch does not have it
aikey add --from-url(P4) ships in this crate. Andgit branch --containsreturnsfeat/public-trust-check-platformand nothing else — notdevelop-v1.0.5, notdevelop-v1.0.4, notmain.Cut a release from the RC line today and the offline package contains none of it.
The self-proof cannot catch that
Hard rule 4 / §3.0 is "包先证明自己,GitHub 才接收制品" — the package proves it installs, not what is in it. A package built from a branch that never received this change deploys perfectly, passes every health check, satisfies the gate, and ships without the feature the release was cut for. Nothing in the pipeline notices, because nothing in it knows what the release was supposed to contain.
And the obvious check reports the opposite
That hit is
infer_provider_from_url, an unrelated import-parser helper. A substring match on this exact question turns "absent" into "present" — worse than no check.check-release-composition.shmatches declarations (the struct field,pub mod, theuse, the module), never bare identifiers. 4/4 against this branch, 4/4 red againstdevelop-v1.0.5with the consequence and both fixes spelled out. Self-tests 3/3, one of them pinning that exact false positive.This PR is also the fix
It carries
a38afd5—aikey add --from-url— so merging it lands the feature ondevelop-v1.0.5and turns the composition check green there. Until then the check is legitimately red, which is a true state and not a CI failure.A hole I left earlier, closed
check-from-url-edition-parity.sh(T-EDN-1, delivered for task 7.6) had no runner: not in the Makefile, not in CI, never run outside the session that wrote it. A fence that cannot fail looks exactly like a fence that is satisfied. Both fences are now inmake guard-releaseandci.yml.CI runs only the composition self-test — what must stay green is the checker's ability to tell present from absent, not the current state of the branch.
The bug that took two rounds to see
git show … | grep -qunderset -o pipefailturns a match into a miss:grep -qexits on first hit, the writer takes SIGPIPE, pipefail fails the pipeline. It only bites on large files —lib.rs(40 lines) "passed" whilemain.rs(8,624 lines) "failed", reporting three real declarations as missing from the branch that has them. Piping fromprintfinstead just moves which process gets the signal; a herestring has no writer to kill.Not done
Actually building the offline package and deploying it to the pre-release environment — that needs the full build chain and the cluster. And until this merges, a successful self-proof would only prove a package that does not contain the change.
🤖 Generated with Claude Code