Two release tags are not ancestors of main, and two published images have no tag at all #14
Labels
No labels
waiting-on-julian
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jlxq0/jmap-mcp#14
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two release tags are not ancestors of
main, two published images have no git tag at all, and nothing in CI checks either. Filed because a branch has no state and nothing will otherwise remind anyone.The measurement
feat/claude-redirects125f72efix/rfc8707-absolute-resourcecf08af4v0.2.8fix/stalwart-jmap-audienceb5b1f41v0.2.7Tag ancestry against
main:v0.2.7andv0.2.8are annotated tags, sogit rev-parse v0.2.7returns the tag object2deb270, not the commitb5b1f41. Compare with^{}or the ancestry answer is about the wrong object.Two more versions exist as images with no tag in either the local clone or the forge API. Both were built, both survive, and the tags that produced them are gone:
So the registry holds
v0.2.5throughv0.2.8whilemain's history accounts for none of the four. GHCR holds only0.2.10through0.2.14, so none of them ever reached the public mirror.Is a shipped fix missing from the current line?
No, and the work was superseded rather than lost. The two branch commits and
mainwere solving the same 2026-08InvalidAudienceoutage, andmainfixed it further upstream.b5b1f41(v0.2.7) forcedresource=stalwarton the OAuth proxy and widened inbound validation to acceptorigin,origin/mcpandstalwart.cf08af4(v0.2.8) then reverted the core of that a half hour later, because Logto rejects a bare API name under RFC 8707 withinvalid_target, and sent the origin instead.mainreached the same destination through8808b09, which canonicalisesJMAP_MCP_RESOURCE_URLto the bare origin so every derived URL follows, plusnormalize_resourceinsrc/oauth_proxy.rs:143, which strips a trailing/and a trailing/mcpfrom the RFC 8707 indicator before proxying. For the deployed configuration both designs put the same value on the wire.main's has one source of truth instead of a second env var.Observed on the live service rather than inferred from the source:
That is exactly the contract
8808b09documents: advertised resource is<origin>/mcp, JWT audience and RFC 8414 issuer are the bare origin.STALWART_AUDIENCEis not orphaned config. It appears insrc/config.rsandsrc/main.rson the branch and nowhere onmain, and the live Deployment does not set it. EveryJMAP_MCP_*variable the manifest sets is onemainreads, so nothing is being silently ignored.One real narrowing, stated precisely
The branch carries
expected_audiences: Vec<String>;maincarriesexpected_audience: String(src/logto_oidc.rs:112).maintherefore accepts exactly one audience where the branch accepted several.This is not a regression on the deployed path, because
mainnormalises the outgoingresourceso Logto only ever mints origin-audtokens. It is a narrowing for any token minted against Logto without passing through this proxy. Recording it rather than calling it equivalent.feat/claude-redirectsis fully supersededIts single commit changes only the fixture string in the
deployed_allowlist_parsestest.mainalready carries the identical nine entries atsrc/oauth_redirect.rs:281-283, includingclaude://claude.ai/oauth/callback,claude://oauth/callbackandcowork://oauth/callback, with the sameassert_eq!(allowed.len(), 9). Nothing is lost by deleting this branch.How a tag got cut off an unmerged branch
Nothing prevents it, and the workflow states the assumption it does not enforce.
.forgejo/workflows/ci.yml:130publishes on anyv*tag:The only gate is that the tag matches
Cargo.toml's version. Ancestry is never consulted:merge-base,is-ancestorandrefs/heads/mainappear nowhere in the workflow except an unrelated cache branch. Four lines below, atci.yml:135:That assumption is what
v0.2.7andv0.2.8violate. It is load-bearing for the buildcache reasoning and unenforced, which is the class of defect this repo keeps finding: the check that would have caught it does not exist, and every signal that does exist stayed green.v0.2.5andv0.2.6show the second half. A tag can be deleted after its image is published, leaving an artefact whose source commit is unresolvable from the repository.Proposed
feat/claude-redirects. Established above as superseded.fix/rfc8707-absolute-resourceandfix/stalwart-jmap-audience. On the evidence here both are superseded, but they carry the only commits behind two published images and should not be deleted on one session's reading.main. This is also what makes theci.yml:135cache assumption true rather than hoped for.v0.2.5andv0.2.6are. Either recover which commit built them or delete the images, since an artefact with no resolvable source is worse than no artefact.Job logs are not retrievable through this Forgejo instance, so which run published the
v0.2.7andv0.2.8images cannot be established from the API. The task list shows only failedcargoruns for both refs, and the images exist, so a re-run is the likely explanation and is not evidence I have.feat/claude-redirectsdeleted at0e73d2f. Its tip was125f72e, recorded above, so the commit is named here if it is ever wanted. The two tagged branches are untouched pending an answer on this issue.The
mergeableobservation from arming branch protection is recorded inAGENTS.mdrather than here, since it is a permanent property of the API and not something this issue resolves: PR #15, merged at0e73d2f.State, 2026-08-27
Two of the four proposals are done and two remain. Recording it here because the issue body still reads as if none were.
Done
feat/claude-redirectsdeleted at0e73d2f, tip125f72erecorded above first. Superseded: its only change was a test fixturemainalready carried verbatim.fix/rfc8707-absolute-resourceandfix/stalwart-jmap-audiencedeleted. Both tags survive the deletion because they are annotated:v0.2.7still resolves tob5b1f41andv0.2.8tocf08af4.tag-ancestryruns on every event anddockerneeds it, in both pipelines. Proven both directions on the real runner: an off-main tag gavetag-ancestry=failurewithcargo=successbeside it and no docker task and no image;v0.2.15andv0.2.16both gavetag-ancestry=successand published.Still open
v0.2.5andv0.2.6are images with no git tag and no resolvable source commit. Created 2026-08-16T01:35:18Z and 06:30:23Z. Neither the local clone nor the forge API has a tag for either, so a tag was deleted after its image was published.The ancestry gate cannot reach this and never could. It fires on a tag push; these have no tag to fire on. Recording that as the gate’s limit rather than as a reason to close the issue.
Either the source commit is recovered or the images are deleted. An artefact with no resolvable source is worse than no artefact, and it is worse than the tags-off-unmerged-branches case this issue opened with, because there at least the commits exist.
The audience narrowing (
expected_audiences: Vectoexpected_audience: String) stays a paragraph rather than an item, on the reasoning above: not a regression on the deployed path. If anything ever mints a token against Logto without passing through this proxy, it becomes an item then.Separately resolved and not part of this issue:
JMAP_MCP_TRUSTED_PROXY_HOPSis now 2, measured, shipped in v0.2.16.What still resolves
v0.2.5andv0.2.6, measured 2026-09-01Establishing this and nothing more. The images are untouched and deleting them is a separate decision.
Nothing references either
oddie-apps/platform, everyjmap/fonduepath in a 903-blob tree, for both digests and forjmap-mcp:v0.2.5/v0.2.6196ee90a…= v0.2.21latest196ee90a…, so not pointing at either0.2.10 0.2 latest 0.2.11 0.2.12 0.2.13 0.2.14 0.2.19 0.2.20 0.2.21— neither 0.2.5 nor 0.2.6The positive control matters: a scan that finds nothing and a scan pointed at the wrong place produce the same output, so the 1 jmap-mcp container it did find is what makes the two zeroes readable.
So: the forge registry is the only thing holding them. No manifest, no GitOps path, no pod, no public mirror, and
latestdoes not resolve to either.One new corroboration of the local-build finding
Both are single manifests (
application/vnd.docker.distribution.manifest.v2+json), while every CI-built release in this repository is anapplication/vnd.oci.image.index.v1+json. That is a third independent signal alongside the absent OCI labels and the absent Actions run, and it points the same way: these did not come out of thebuildctlpath in.forgejo/workflows/ci.yml.The finding that generalises
A gate inside the pipeline cannot see an artefact that arrived outside it. The tag-ancestry gate added in v0.2.15 closes tags cut off unmerged branches and closes nothing here, because it runs in CI and this path never entered CI. Anything holding registry push credentials can publish, and the result is indistinguishable in the registry from a released image.
Still open
Whether to delete them. On the evidence above nothing breaks, but an artefact with no resolvable source is a different question from an artefact nobody uses, and deleting published images is destructive.
Why this stays open after the reference scan
The scan above closes the usage half and settles nothing about the other one.
Nothing breaks if the two images go, but an artefact with no resolvable source is a different question from an artefact nobody uses, and only the second is what was measured. Two zeroes and a positive control establish that no manifest, no GitOps path, no pod and no public mirror resolves either digest. They say nothing about which commit produced them.
Closing this on the reference scan would read as having answered provenance, which is the thing the issue is about.
What is left, stated as a limit rather than as work pending:
org.opencontainers.image.revision. No Actions run, so nohead_sha. Both binaries reportjmap-mcp/0.0.1, becauseCargo.tomlcarried0.0.1until0ebc967, so the embedded version cannot discriminate between any commit before v0.2.10.Branch housekeeping, for the record.
fix/rfc8707-absolute-resourceandfix/stalwart-jmap-audienceare deleted locally. Verified immediately before thatgit tag --contains b5b1f41listsv0.2.7 v0.2.8andcf08af4listsv0.2.8, and immediately after that both are stillcommitobjects held by those annotated tags.feat/claude-redirects(125f72e) is kept: it is in no tag and on no remote branch, so its local ref is the only thing between that commit and being unreferenced everywhere, and this issue naming the sha is its only other record.Provenance recovered, 2026-09-02
Both unattributable images are now attributed, by rebuild-and-compare.
Each matched on two independent comparisons: the extracted binary's
sha256and the image's whole config digest.The calibration is why these numbers mean anything
Run first, against
v0.2.14, a pair whose answer was already known, before any candidate was built:Without that, a candidate matching nothing would have been indistinguishable from the build not being reproducible on this hardware. The prediction recorded before running was that it would fail, on the reasoning that this host is
arm64and the images arelinux/amd64. That reasoning was wrong: the target isamd64either way, so the output is determined by the digest-pinned builder image,Cargo.lock, and the fixed/buildpath, all three of which are in git.Compare the binary, not the layer
A
COPYlayer's digest is over a tar carrying mtime, uid, gid and mode per entry, so a byte-identical binary can land in a layer with a different digest for reasons unrelated to the compiler. That is a false negative built into the comparison. The binary hash and the config digest are the two that mean something.What a rebuild can and cannot identify
It identifies a tree, not a commit.
8555a9candc0a9353have the identical tree29637c1, sov0.2.6is attributable to that tree and not to one of those two commits. Checked before building rather than discovered after.What this does not change
These images still never went through CI, and the finding stands: six Actions tasks on 2026-08-16, all
cargo, allfailure, nodockerjob at all, and the firstdockersuccess after 2026-08-14 was two days later. Both are single manifests where every CI-built release here is an index. A gate inside the pipeline cannot see an artefact that arrived outside it.So provenance is recovered and the hole that produced it is not closed. Those are separate, and only the first was this comment.
Correction to the provenance comment above, and both attributions now rest on genuine rebuilds
The comment at
issuecomment-16198reported both matches and one of its two lines of evidence was wrong. Corrected here rather than edited, so the mistake stays legible.What was wrong
Those first candidate builds were not rebuilds.
docker buildwithout--no-cacheon a machine that had built these images on 2026-08-16 served them from a 93 GB build cache: the dep stub and the source compile were bothCACHED, and the run finishedrc=0 after 2.5swhere a genuine build takes ~79s. So the comparison was cache-hit-and-compare.And the config-digest match, offered there as independent corroboration, could only have passed in exactly the case it was meant to rule out. The config carries per-layer wall-clock times, 23 history entries each with its own
created, so an honest rebuild changes it by construction. A matching config digest means the layers are the originals. It is a cache detector, not a provenance check.Re-run with
--no-cache, and both still matchThe attributions hold.
v0.2.5is tree09b5f5b;v0.2.6is tree29637c1, shared by8555a9candc0a9353and therefore attributable to neither individually.The binary
sha256is the only comparison that survives, precisely because it is over content with no timestamp in it. The config digests differ on the honest rebuilds, as they must.Why the calibration could not have caught this
v0.2.14was built by CI, so this machine held no cache for it and the calibration genuinely compiled. The property that made the calibration honest is the property the subject lacked. A dry run against a known state validates the method and not its application, and the two differ wherever the known state and the subject differ.Still open, and unchanged by any of this
These images never entered CI, and the gate added in v0.2.15 cannot see them because it runs inside the pipeline they bypassed. Provenance is recovered; the hole is not closed. This issue stays open on the hole.
The two images are kept as the specimens for it. Deleting them while the finding is open would leave the next reader an assertion with nothing behind it.