mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(release): publish the MCP registry entry after the release is public
v0.10.3 published, then failed:
MCPB package '...codebase-memory-mcp-darwin-amd64.mcpb' is not publicly
accessible (status: 404)
publish-mcp-registry and publish-final both needed only publish-registries, so
they ran in PARALLEL — and publish-final is the job that un-drafts the release.
The registry validates every package URL it is handed by fetching it, and a
draft release's assets are not publicly readable. The registry lost the race by
five seconds; the identical URL served 200 once the release went public, and the
job passed on a plain re-run.
The registry now needs publish-final. This keeps the documented intent exactly:
publish-final still does NOT depend on the registry, so a registry-preview
outage can never block shipping — the registry simply runs after the assets it
validates exist to the outside world.
The gate-chain contract now pins BOTH directions, because either one alone is a
bug: the registry must depend on publish-final, and publish-final must never
depend on the registry. Verified by reverting the ordering — the contract goes
red with the exact v0.10.3 failure, and green again with it restored.
This is the second latent defect the .mcpb path produced on its first real
release (after the security-strings audit treating a manifest as a binary).
Both were invisible until a stable release ran; an end-to-end bundle gate is
worth adding before the next one.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
1649fa8344
commit
e4cb304a11
2 files changed
+39
-2
No files matched your search
@@ -409,11 +409,23 @@ jobs:
|
||||
# product, the registry entry is metadata. A registry-preview outage
|
||||
# must never block shipping. Re-run this job alone to retry — it does
|
||||
# not touch npm/PyPI, so retries are safe.
|
||||
#
|
||||
# It DOES depend on publish-final, which is the job that un-drafts the
|
||||
# release. Both used to need only publish-registries, so they raced: the
|
||||
# registry validates every package URL it is given by fetching it, and a
|
||||
# DRAFT release's assets are not publicly readable. In v0.10.3 the registry
|
||||
# ran 5s before the un-draft and was told its own .mcpb URL was a 404:
|
||||
# MCPB package '...codebase-memory-mcp-darwin-amd64.mcpb' is not publicly
|
||||
# accessible (status: 404)
|
||||
# The same URL served 200 once the release went public, and the job passed on
|
||||
# a plain re-run. Depending on publish-final keeps the documented intent
|
||||
# exactly — the registry still cannot block the release, it just runs after
|
||||
# the assets it validates actually exist to the outside world.
|
||||
publish-mcp-registry:
|
||||
needs: [publish-registries]
|
||||
needs: [publish-registries, publish-final]
|
||||
# Skipped for prereleases: the MCP Registry has no channel concept, so an
|
||||
# RC would present itself as the current version to every registry consumer.
|
||||
if: ${{ !cancelled() && !failure() && needs.publish-registries.result == 'success' && !contains(inputs.version, '-') }}
|
||||
if: ${{ !cancelled() && !failure() && needs.publish-registries.result == 'success' && needs.publish-final.result == 'success' && !contains(inputs.version, '-') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # GitHub OIDC auth to the MCP Registry
|
||||
|
||||
@@ -84,6 +84,31 @@ if "needs.soak.result" not in draft:
|
||||
" legitimately skipped soak (soak_level=none) is distinguishable\n"
|
||||
" from a soak that never ran.")
|
||||
|
||||
# The MCP registry validates every package URL it is handed by FETCHING it, and
|
||||
# a DRAFT release's assets are not publicly readable. publish-final is the job
|
||||
# that un-drafts. Both jobs used to need only publish-registries, so they raced
|
||||
# and the registry was told its own .mcpb URL was a 404 (v0.10.3). The registry
|
||||
# must therefore run AFTER the release is public — while still never gating it.
|
||||
def needs(job):
|
||||
"""The job's `needs:` list as a single line."""
|
||||
m = re.search(r"^ needs:\s*(.*)$", blocks.get(job, ""), re.M)
|
||||
return m.group(1).strip() if m else ""
|
||||
|
||||
if "publish-mcp-registry" not in blocks:
|
||||
failures.append("publish-mcp-registry: job missing from release.yml — update this contract")
|
||||
elif "publish-final" not in needs("publish-mcp-registry"):
|
||||
failures.append(
|
||||
"publish-mcp-registry: must `needs:` publish-final. The registry fetches\n"
|
||||
" the .mcpb URLs it validates, and a draft release's assets 404 —\n"
|
||||
" running it in parallel with the un-draft is a race it loses.")
|
||||
|
||||
# ...and the reverse must NEVER hold: a registry outage must not block shipping.
|
||||
if "publish-final" in blocks and "publish-mcp-registry" in needs("publish-final"):
|
||||
failures.append(
|
||||
"publish-final: must NOT depend on publish-mcp-registry. The binary\n"
|
||||
" release is the product and the registry entry is metadata; a\n"
|
||||
" registry-preview outage must never hold up a release.")
|
||||
|
||||
if failures:
|
||||
for f in failures:
|
||||
print("FAIL: " + f, file=sys.stderr)
|
||||
|
||||
Reference in new issue
Block a user