Skip to content

Commit 8fb4a81

Browse files
committed
Include hidden files in Pages artifact; smoke-test deploy
The first production deploy shipped 230 PDS records and all link tags but served 404 for /.well-known/site.standard.publication: actions/upload-pages-artifact excludes dot-prefixed entries from its tar by default, so the file was built, green through every gate, and silently dropped at packaging. No _site-level check can catch what the packaging step discards. Changes: - upload-pages-artifact: include-hidden-files: true. - deploy job: post-deploy smoke test curls the live .well-known URL and compares it to the URI in _config.yml, retrying for CDN propagation; checkout added for config access. - validate --site-dir also verifies _site/.well-known/... exists with byte-exact content (closes the adjacent generator-regression gap; the artifact gap is only provable post-deploy). - bluesky.md Phase 5 documents the incident and the three guards.
1 parent c1e6800 commit 8fb4a81

4 files changed

Lines changed: 107 additions & 8 deletions

File tree

.github/workflows/jekyll.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,15 @@ jobs:
174174
bundle exec ruby _bin/check_links.rb
175175
176176
- name: Upload artifact
177-
# Only upload if we are on main (where deployment happens)
177+
# Only upload if we are on main (where deployment happens).
178+
# include-hidden-files is required: the action's tar excludes
179+
# dot-prefixed entries by default, which silently dropped
180+
# .well-known/site.standard.publication from the first deploy
181+
# while every _site-level gate stayed green.
178182
if: github.ref == 'refs/heads/main'
179183
uses: actions/upload-pages-artifact@v5
184+
with:
185+
include-hidden-files: true
180186

181187
# Deployment job
182188
deploy:
@@ -191,6 +197,24 @@ jobs:
191197
# This matches the condition above, ensuring we only try to deploy if we uploaded
192198
if: github.ref == 'refs/heads/main'
193199
steps:
200+
- name: Checkout
201+
uses: actions/checkout@v7
202+
194203
- name: Deploy to GitHub Pages
195204
id: deployment
196205
uses: actions/deploy-pages@v5
206+
207+
# Artifact-side proof that dot-path files actually deployed; the
208+
# build gates can only see _site/, not what the packaging step kept.
209+
- name: Smoke-test deployed .well-known
210+
run: |
211+
expected=$(grep -A2 '^standard_site:' _config.yml | grep publication_uri | sed 's/.*"\(at:[^"]*\)".*/\1/')
212+
echo "Expecting: $expected"
213+
for i in 1 2 3 4 5; do
214+
got=$(curl -fsS "${{ steps.deployment.outputs.page_url }}.well-known/site.standard.publication" || true)
215+
[ "$got" = "$expected" ] && echo "OK" && exit 0
216+
echo "attempt $i: got '$got'; retrying in 15s (CDN propagation)"
217+
sleep 15
218+
done
219+
echo "ERROR: deployed .well-known does not serve the publication URI"
220+
exit 1

_scripts/atproto/publish.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,7 @@ def validate_documents(
732732
posts_dir: Path,
733733
books_dir: Path | None = None,
734734
site_dir: Path | None = None,
735+
expected_publication_uri: str = "",
735736
) -> bool:
736737
"""
737738
Validate all posts (and books) for AT Protocol compatibility.
@@ -777,6 +778,24 @@ def validate_documents(
777778
print(f"ERROR: {msg}", file=sys.stderr)
778779
error_count += 1
779780

781+
# The built site must carry the verification file with the exact
782+
# configured URI (no trailing newline — verifiers compare exactly).
783+
if expected_publication_uri:
784+
wk = site_dir / ".well-known" / "site.standard.publication"
785+
if not wk.is_file():
786+
print(
787+
f"ERROR: built site is missing {wk}",
788+
file=sys.stderr,
789+
)
790+
error_count += 1
791+
elif wk.read_text(encoding="utf-8") != expected_publication_uri:
792+
print(
793+
f"ERROR: {wk} content does not match "
794+
"standard_site.publication_uri",
795+
file=sys.stderr,
796+
)
797+
error_count += 1
798+
780799
return error_count == 0
781800

782801

@@ -921,10 +940,14 @@ def _dispatch(argv: list[str] | None = None) -> None:
921940

922941
args = parser.parse_args(argv)
923942

924-
# validate needs no credentials or config — dispatch before either check.
943+
# validate needs no credentials or network; it reads only local files
944+
# (including _config.yml for the expected publication URI).
925945
if args.cmd == "validate":
926946
ok = validate_documents(
927-
args.posts_dir, books_dir=args.books_dir, site_dir=args.site_dir
947+
args.posts_dir,
948+
books_dir=args.books_dir,
949+
site_dir=args.site_dir,
950+
expected_publication_uri=get_publication_uri(load_config()),
928951
)
929952
if not ok:
930953
sys.exit(1)

_scripts/tests/test_atproto_publish.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,3 +1641,51 @@ def test_update_without_cid_refuses(self, tmp_path: Path) -> None:
16411641
with pytest.raises(publish.PublishError) as exc_info:
16421642
sync_documents(client, tmp_path, tmp_path / "out.json", PUB_URI, TEST_CONFIG)
16431643
assert "non-atomic" in str(exc_info.value)
1644+
1645+
# ---------------------------------------------------------------------------
1646+
# Well-known file check in validate --site-dir
1647+
# ---------------------------------------------------------------------------
1648+
1649+
1650+
class TestWellKnownInSite:
1651+
def _site(self, tmp_path: Path) -> tuple[Path, Path]:
1652+
posts = tmp_path / "posts"
1653+
posts.mkdir()
1654+
site = tmp_path / "_site"
1655+
(site / "blog").mkdir(parents=True)
1656+
(site / "books").mkdir(parents=True)
1657+
return posts, site
1658+
1659+
def test_missing_well_known_fails(self, tmp_path: Path, capsys) -> None:
1660+
posts, site = self._site(tmp_path)
1661+
ok = validate_documents(posts, site_dir=site, expected_publication_uri=PUB_URI)
1662+
assert ok is False
1663+
assert "missing" in capsys.readouterr().err
1664+
1665+
def test_wrong_content_fails(self, tmp_path: Path, capsys) -> None:
1666+
posts, site = self._site(tmp_path)
1667+
wk = site / ".well-known"
1668+
wk.mkdir()
1669+
(wk / "site.standard.publication").write_text("at://did:plc:wrong/x/y")
1670+
ok = validate_documents(posts, site_dir=site, expected_publication_uri=PUB_URI)
1671+
assert ok is False
1672+
assert "does not match" in capsys.readouterr().err
1673+
1674+
def test_trailing_newline_fails(self, tmp_path: Path) -> None:
1675+
# Verifiers compare exactly; a newline is a mismatch.
1676+
posts, site = self._site(tmp_path)
1677+
wk = site / ".well-known"
1678+
wk.mkdir()
1679+
(wk / "site.standard.publication").write_text(PUB_URI + "\n")
1680+
assert validate_documents(posts, site_dir=site, expected_publication_uri=PUB_URI) is False
1681+
1682+
def test_exact_content_passes(self, tmp_path: Path) -> None:
1683+
posts, site = self._site(tmp_path)
1684+
wk = site / ".well-known"
1685+
wk.mkdir()
1686+
(wk / "site.standard.publication").write_text(PUB_URI)
1687+
assert validate_documents(posts, site_dir=site, expected_publication_uri=PUB_URI) is True
1688+
1689+
def test_no_expected_uri_skips_check(self, tmp_path: Path) -> None:
1690+
posts, site = self._site(tmp_path)
1691+
assert validate_documents(posts, site_dir=site) is True

bluesky.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,15 @@ exercised the unconfigured code. Before merging `standard-site-config`:
389389

390390
1. After the first `main` deploy:
391391
`curl https://alexgude.com/.well-known/site.standard.publication`
392-
→ exactly the publication AT-URI. This is also the first proof that
393-
GitHub Pages serves this repo's `.well-known/` path at all — Pages
394-
generally does, but it has never been verified here; if it 404s
395-
while `_site/` contains the file, that is a Pages serving issue,
396-
not a build issue.
392+
→ exactly the publication AT-URI. **This check caught a real bug on
393+
the first deploy**: `actions/upload-pages-artifact` excludes
394+
dot-prefixed entries from its tar by default, so `.well-known` was
395+
built and green through every gate, then silently dropped at
396+
packaging. Fixed with `include-hidden-files: true` on the upload
397+
step; a post-deploy smoke test in the deploy job now curls the live
398+
URL and compares content (with retries for CDN propagation), and
399+
`validate --site-dir` checks the file exists in `_site/` with exact
400+
content.
397401
2. `curl -s https://alexgude.com/blog/<recent-slug>/ | grep site.standard`
398402
→ both link tags present.
399403
3. Run the ecosystem validator at <https://site-validator.fly.dev>

0 commit comments

Comments
 (0)