diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 42ffa76a..fec679a8 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI # Branch pushes only — a release TAG deliberately does not re-run CI: the # tagged commit's CI already ran on its branch push, and the runner is # sequential, so tag-triggered matrix jobs (~22 min) would queue AHEAD of the -# tag's publish-forge run and starve every release (observed on 8.10.3 and +# tag's publish-source run and starve every release (observed on 8.10.3 and # 9.0.0: the publish sat behind the tag's own redundant CI). on: push: diff --git a/.forgejo/workflows/publish-forge.yml b/.forgejo/workflows/publish-source.yml similarity index 59% rename from .forgejo/workflows/publish-forge.yml rename to .forgejo/workflows/publish-source.yml index fb7428bf..8220bac9 100644 --- a/.forgejo/workflows/publish-forge.yml +++ b/.forgejo/workflows/publish-source.yml @@ -1,10 +1,12 @@ -name: Publish (forge) +name: Publish (The Source) -# Datacenter-side forge publish, moved off the laptop: an 87MB tarball PUT -# over the laptop's WAN times out; the forge's own runner does it in seconds. +# Datacenter-side publish to The Source (source.soulcraft.com — our +# self-hosted Forgejo; never call it "the forge", Forge is a different +# product), moved off the laptop: an 87MB tarball PUT over the laptop's WAN +# times out; The Source's own runner does it in seconds. # scripts/release.sh tags + pushes, then polls this workflow's result (npm -# view against the forge registry) before it ever touches the npmjs leg — -# see the "delegation contract" in scripts/release.sh's forge-publish step. +# view against The Source's registry) before it ever touches the npmjs leg — +# see the "delegation contract" in scripts/release.sh's home-publish step. on: push: @@ -13,7 +15,7 @@ on: jobs: publish: - name: Publish to the forge registry + name: Publish to The Source registry runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -23,20 +25,21 @@ jobs: cache: npm - run: npm ci - run: npm run build - - name: Publish + readback-verify on the forge registry + - name: Publish + readback-verify on The Source registry env: + # The stored repo-settings secret keeps its historical name. FORGE_NPM_TOKEN: ${{ secrets.FORGE_NPM_TOKEN }} run: | set -eo pipefail - FORGE_NPM_REG="https://source.soulcraft.com/api/packages/soulcraft/npm/" + SOURCE_NPM_REG="https://source.soulcraft.com/api/packages/soulcraft/npm/" VERSION="$(node -p "require('./package.json').version")" - echo "Publishing @soulcraft/brainy@${VERSION} to the forge registry..." + echo "Publishing @soulcraft/brainy@${VERSION} to The Source registry..." TMPRC="$(mktemp)" chmod 600 "$TMPRC" { - echo "@soulcraft:registry=${FORGE_NPM_REG}" + echo "@soulcraft:registry=${SOURCE_NPM_REG}" echo "//source.soulcraft.com/api/packages/soulcraft/npm/:_authToken=${FORGE_NPM_TOKEN}" } > "$TMPRC" @@ -56,12 +59,12 @@ jobs: rm -f "$TMPRC" if [ "$LANDED_VERSION" != "$VERSION" ]; then - echo "::error::Readback verify FAILED — the forge registry reports version '${LANDED_VERSION:-}', expected '${VERSION}'. This is a genuine publish failure, not a benign duplicate." + echo "::error::Readback verify FAILED — The Source registry reports version '${LANDED_VERSION:-}', expected '${VERSION}'. This is a genuine publish failure, not a benign duplicate." exit 1 fi if [ "$PUBLISH_OK" = true ]; then - echo "Published and verified @soulcraft/brainy@${VERSION} on the forge registry." + echo "Published and verified @soulcraft/brainy@${VERSION} on The Source registry." else - echo "::warning::npm publish reported failure, but readback confirms @soulcraft/brainy@${VERSION} is already live on the forge (a prior run or mirror landed it) — treating this run as successful, since the registry content is correct. Any OTHER failure mode would have failed the readback check above instead." + echo "::warning::npm publish reported failure, but readback confirms @soulcraft/brainy@${VERSION} is already live on The Source (a prior run or mirror landed it) — treating this run as successful, since the registry content is correct. Any OTHER failure mode would have failed the readback check above instead." fi diff --git a/RELEASES.md b/RELEASES.md index ce7b5f99..8229fb5c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -70,8 +70,8 @@ to the caller today. pre-existing meaning). **Migration-grade exports set `includeHidden: true`** — a complete-canon export must carry every visibility tier; consumer-facing exports leave it off. -- **Ops note (consumer-invisible): the release pipeline's forge-registry publish now runs - on CI**, triggered by the release tag, instead of PUTting the tarball from the laptop +- **Ops note (consumer-invisible): the release pipeline's home-registry publish (The + Source, source.soulcraft.com) now runs on CI**, triggered by the release tag, instead of PUTting the tarball from the laptop over WAN — no change to what gets published or how a consumer installs it. ## v9.0.0 — 2026-08-04 (the field-addressing law: your names and system.*, nothing in between) diff --git a/scripts/release.sh b/scripts/release.sh index 7f068cb8..ce2d0882 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -175,78 +175,79 @@ echo -e "${BLUE}7️⃣ Creating git tag v${NEW_VERSION}...${NC}" git tag -a "v${NEW_VERSION}" -m "Release v${NEW_VERSION}" echo -e "${GREEN}✅ Tag created${NC}\n" -# Step 9: Push to origin — the forge is the one home (ruled 2026-07-23; the +# Step 9: Push to origin — The Source is the one home (ruled 2026-07-23; the # old public GitHub repo is archived history, no longer part of any release). echo -e "${BLUE}8️⃣ Pushing to origin...${NC}" git push --follow-tags origin "$CURRENT_BRANCH" echo -e "${GREEN}✅ Pushed to origin${NC}\n" -# Step 10: Forge publish is CI's job now, not the laptop's — a tag push (just -# above) triggers .forgejo/workflows/publish-forge.yml, which builds and -# publishes on the forge's own runner (datacenter-side: seconds, not the -# laptop's WAN timing out on an 87MB tarball PUT). The laptop holds no forge -# publish credential anymore; it only waits for CI's result before trusting -# the forge/npmjs pair enough to publish the storefront leg. -FORGE_NPM_REG="https://source.soulcraft.com/api/packages/soulcraft/npm/" -FORGE_POLL_INTERVAL_S=15 -FORGE_POLL_MAX_ATTEMPTS=200 # 200 × 15s = 50 minutes — the runner is sequential and a busy day's ci.yml +# Step 10: The home publish (The Source, source.soulcraft.com) is CI's job +# now, not the laptop's — a tag push (just above) triggers +# .forgejo/workflows/publish-source.yml, which builds and publishes on The +# Source's own runner (datacenter-side: seconds, not the laptop's WAN timing +# out on an 87MB tarball PUT). The laptop holds no home-registry publish +# credential anymore; it only waits for CI's result before trusting the +# home/npmjs pair enough to publish the storefront leg. +SOURCE_NPM_REG="https://source.soulcraft.com/api/packages/soulcraft/npm/" +SOURCE_POLL_INTERVAL_S=15 +SOURCE_POLL_MAX_ATTEMPTS=200 # 200 × 15s = 50 minutes — the runner is sequential and a busy day's ci.yml # backlog has twice exceeded the old 20-minute window (8.10.3, 9.0.0); # ci.yml no longer runs on tag pushes, but same-day branch pushes still queue ahead -echo -e "${BLUE}9️⃣ Waiting for CI to publish v${NEW_VERSION} to the forge registry (home)...${NC}" -FORGE_LANDED=false -for ((attempt = 1; attempt <= FORGE_POLL_MAX_ATTEMPTS; attempt++)); do - LANDED_VERSION=$(npm view "@soulcraft/brainy@${NEW_VERSION}" version "--@soulcraft:registry=${FORGE_NPM_REG}" 2>/dev/null || echo "") +echo -e "${BLUE}9️⃣ Waiting for CI to publish v${NEW_VERSION} to The Source registry (home)...${NC}" +SOURCE_LANDED=false +for ((attempt = 1; attempt <= SOURCE_POLL_MAX_ATTEMPTS; attempt++)); do + LANDED_VERSION=$(npm view "@soulcraft/brainy@${NEW_VERSION}" version "--@soulcraft:registry=${SOURCE_NPM_REG}" 2>/dev/null || echo "") if [ "$LANDED_VERSION" = "$NEW_VERSION" ]; then - FORGE_LANDED=true + SOURCE_LANDED=true break fi - echo -e "${YELLOW} … not yet on the forge (attempt ${attempt}/${FORGE_POLL_MAX_ATTEMPTS}); retrying in ${FORGE_POLL_INTERVAL_S}s${NC}" - sleep "$FORGE_POLL_INTERVAL_S" + echo -e "${YELLOW} … not yet on The Source (attempt ${attempt}/${SOURCE_POLL_MAX_ATTEMPTS}); retrying in ${SOURCE_POLL_INTERVAL_S}s${NC}" + sleep "$SOURCE_POLL_INTERVAL_S" done -if [ "$FORGE_LANDED" = true ]; then - echo -e "${GREEN}✅ CI published v${NEW_VERSION} to the forge${NC}\n" +if [ "$SOURCE_LANDED" = true ]; then + echo -e "${GREEN}✅ CI published v${NEW_VERSION} to The Source${NC}\n" else - echo -e "${RED}❌ CI forge publish did not land — check the workflow run on The Source; the pair must not diverge.${NC}" + echo -e "${RED}❌ CI's home publish did not land — check the workflow run on The Source; the pair must not diverge.${NC}" echo -e "${RED} v${NEW_VERSION} was tagged and pushed, but @soulcraft/brainy@${NEW_VERSION} never became visible on the${NC}" - echo -e "${RED} forge registry after ${FORGE_POLL_MAX_ATTEMPTS} attempts, ${FORGE_POLL_INTERVAL_S}s apart. Aborting before npmjs.${NC}" + echo -e "${RED} Source registry after ${SOURCE_POLL_MAX_ATTEMPTS} attempts, ${SOURCE_POLL_INTERVAL_S}s apart. Aborting before npmjs.${NC}" exit 1 fi echo -e "${BLUE}9️⃣½ Publishing to npmjs (storefront, dist-tag: ${NPM_TAG})...${NC}" # BYTE-IDENTITY LAW: the storefront republishes CI's EXACT artifact — download -# the tarball the forge serves and publish that file, never a fresh local pack +# the tarball The Source serves and publish that file, never a fresh local pack # (a local rebuild can differ byte-wise, and the fleet verifies the pair by # shasum across registries). STOREFRONT_TMP="$(mktemp -d)" -(cd "$STOREFRONT_TMP" && npm pack "@soulcraft/brainy@${NEW_VERSION}" "--@soulcraft:registry=${FORGE_NPM_REG}" >/dev/null) -FORGE_TARBALL="$(ls "$STOREFRONT_TMP"/soulcraft-brainy-*.tgz)" -echo -e "${BLUE} forge artifact: $(sha256sum "$FORGE_TARBALL" | cut -d' ' -f1)${NC}" -npm publish "$FORGE_TARBALL" --tag "$NPM_TAG" "--@soulcraft:registry=https://registry.npmjs.org/" +(cd "$STOREFRONT_TMP" && npm pack "@soulcraft/brainy@${NEW_VERSION}" "--@soulcraft:registry=${SOURCE_NPM_REG}" >/dev/null) +SOURCE_TARBALL="$(ls "$STOREFRONT_TMP"/soulcraft-brainy-*.tgz)" +echo -e "${BLUE} home artifact: $(sha256sum "$SOURCE_TARBALL" | cut -d' ' -f1)${NC}" +npm publish "$SOURCE_TARBALL" --tag "$NPM_TAG" "--@soulcraft:registry=https://registry.npmjs.org/" rm -rf "$STOREFRONT_TMP" # Brainy is the only PUBLIC @soulcraft package — verify visibility after every publish. npm access get status @soulcraft/brainy "--@soulcraft:registry=https://registry.npmjs.org/" || true # Verify the pair is byte-identical by registry-reported shasum — divergence here # means the storefront leg must be treated as failed, loudly. -FORGE_SHA=$(npm view "@soulcraft/brainy@${NEW_VERSION}" dist.shasum "--@soulcraft:registry=${FORGE_NPM_REG}" 2>/dev/null || echo "forge-unavailable") +SOURCE_SHA=$(npm view "@soulcraft/brainy@${NEW_VERSION}" dist.shasum "--@soulcraft:registry=${SOURCE_NPM_REG}" 2>/dev/null || echo "source-unavailable") NPMJS_SHA=$(npm view "@soulcraft/brainy@${NEW_VERSION}" dist.shasum "--@soulcraft:registry=https://registry.npmjs.org/" 2>/dev/null || echo "npmjs-unavailable") -if [ "$FORGE_SHA" = "$NPMJS_SHA" ]; then +if [ "$SOURCE_SHA" = "$NPMJS_SHA" ]; then echo -e "${GREEN}✅ Published to npmjs — byte-identical pair (shasum ${NPMJS_SHA})${NC}\n" else - echo -e "${RED}❌ REGISTRY DIVERGENCE: forge shasum ${FORGE_SHA} != npmjs shasum ${NPMJS_SHA} — investigate before announcing${NC}\n" + echo -e "${RED}❌ REGISTRY DIVERGENCE: The Source shasum ${SOURCE_SHA} != npmjs shasum ${NPMJS_SHA} — investigate before announcing${NC}\n" exit 1 fi -# Step 11: Release object on the forge (presentational — the tag, CHANGELOG, -# and RELEASES.md are the record; this just gives the forge UI a release page). -echo -e "${BLUE}🔟 Creating forge release...${NC}" +# Step 11: Release object on The Source (presentational — the tag, CHANGELOG, +# and RELEASES.md are the record; this just gives The Source's UI a release page). +echo -e "${BLUE}🔟 Creating release page on The Source...${NC}" if [ -n "${FORGEJO_RELEASE_TOKEN:-}" ]; then if curl -sf -X POST "https://source.soulcraft.com/api/v1/repos/soulcraft/brainy/releases" \ -H "Authorization: token ${FORGEJO_RELEASE_TOKEN}" -H "Content-Type: application/json" \ -d "{\"tag_name\":\"v${NEW_VERSION}\",\"name\":\"v${NEW_VERSION}\",\"prerelease\":${PRERELEASE}}" >/dev/null; then - echo -e "${GREEN}✅ Forge release created${NC}\n" + echo -e "${GREEN}✅ Release page created on The Source${NC}\n" else - echo -e "${RED}⚠️ Forge release API call failed — tag + CHANGELOG remain the record; create the release page via the forge UI if wanted${NC}\n" + echo -e "${RED}⚠️ Release-page API call failed — tag + CHANGELOG remain the record; create the page via The Source's UI if wanted${NC}\n" fi else echo -e "${RED}⚠️ FORGEJO_RELEASE_TOKEN unset — no release page created; tag + CHANGELOG remain the record${NC}\n" @@ -269,4 +270,4 @@ echo -e "${GREEN}🎉 Release ${NEW_VERSION} complete!${NC}" echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" echo -e "📦 npm: ${BLUE}https://www.npmjs.com/package/@soulcraft/brainy/v/${NEW_VERSION}${NC}" -echo -e "🏠 Forge: ${BLUE}https://source.soulcraft.com/soulcraft/brainy/releases/tag/v${NEW_VERSION}${NC}" +echo -e "🏠 The Source: ${BLUE}https://source.soulcraft.com/soulcraft/brainy/releases/tag/v${NEW_VERSION}${NC}"