Round 2 is up. I went through every comment against the Tangled source (lexicon, ingester, the spindle engine) and you were right on basically all of it. Thanks for the close read.
The big one is the artifact record. repo wants an at:// URI and the ingester runs ParseATURI on whatever it gets, so the HTTPS clone URL I was sending was just rejected and nothing ever ingested. That's why Sheaf and HeCAPTe only show source links on their tag pages. Fixed by switching to repoDid -- the ingester checks that field first and skips the at-uri parse, and spindle already exports it as TANGLED_REPO_REPO_DID. Records should actually render on the tag page now, downloads and all.
The rest:
- Lightweight tags used to build, publish to pages, upload the blob, and only then die at
git rev-parse $TAG^{tag}. There's a gate as the very first step now (git cat-file -e "${TANGLED_REF_NAME}^{tag}"), so it fails before anything goes out.
- Dropped the
ssh -T probe. It doesn't honor GIT_SSH_COMMAND (that's git-only), so it was reading an empty ~/.ssh and accomplishing nothing.
- The deploy key goes to
write-ssh-key.sh over stdin instead of argv (it was sitting in /proc/<pid>/cmdline), and umask 077 runs before any key material hits disk so a failure can't leave a world-readable file behind.
make-indexes.py sorts semver for real now. Prereleases were ranking above the stable release and rc.10 sorted below rc.2. Both fixed per semver 2.0, and the docstring no longer claims it's idempotent, because it wasn't -- re-runs re-stamped dates and resurrected deleted versions.
- The "steps share one shell" thing was just wrong. Each step is a fresh
bash -lc starting in /workspace/repo, which is exactly why nothing carries over. Corrected everywhere, including the L2 lesson that was teaching the wrong mechanism.
- Reframed the whole skill. It used to say Tangled has no releases and the pages site was the mechanism. That's backwards: the artifact record is the canonical release and pages is a mirror. Rewrote the intro, description, and architecture diagram to match.
On the things I documented but didn't change:
- The shared account-wide deploy key. You're right that it can push to every repo the account can. I kept the mechanism (it's the simple default) but added a note spelling out the blast radius and the ladder up to per-project keys or a dedicated publisher account.
- Tag re-cutting. The pipeline has no other re-run mechanism, so for pre-publish failures re-cutting is still the move. But once pages publish succeeds the tag should be treated as immutable, because re-cutting orphans the record (different tag object hash) and leaves a dangling artifact. For post-publish failures the advice is now fix-forward or cut the record for the existing tag.
- App passwords. Fixed the bsky.social-assuming wording and noted they grant broad account access with no scoped CI option yet. Recommendation is a dedicated publishing account. And yes, the PDS gets resolved from the handle now instead of assuming bsky.social.