Skip to content

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
develop-v1.0.5from
feat/release-composition-guard
Open

feat(guards): release-composition check, and a runner for the edition fence (7.6/7.8)#23
damonleelcx wants to merge 2 commits into
develop-v1.0.5from
feat/release-composition-guard

Conversation

@damonleelcx

Copy link
Copy Markdown
Contributor

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. And 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

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

git grep from_url develop-v1.0.5   # → hit

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.sh matches declarations (the struct field, pub mod, the use, the module), never bare identifiers. 4/4 against this branch, 4/4 red against develop-v1.0.5 with 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 a38afd5aikey add --from-url — so merging it lands the feature on develop-v1.0.5 and 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 in make guard-release and ci.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 -q under set -o pipefail turns a match into a miss: grep -q exits on first hit, the writer takes SIGPIPE, pipefail fails the pipeline. It only bites on large files — lib.rs (40 lines) "passed" while main.rs (8,624 lines) "failed", reporting three real declarations as missing from the branch that has them. Piping from printf instead 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

Damon and others added 2 commits August 10, 2026 09:38
…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>
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.

1 participant