Skip to content

Link rules, an optional coc field, and fail-loud branch sync - #497

Merged
JesperDramsch merged 7 commits into
mainfrom
ai-agent/exciting-tesla-g7fn0r
Oct 14, 2026
Merged

JesperDramsch merged 7 commits into
mainfrom
ai-agent/exciting-tesla-g7fn0r

Conversation

@JesperDramsch

Copy link
Copy Markdown
Owner

First PR of the desk stack. It holds the deterministic rules that every writer runs through, so no LLM is involved here.

Changes

  • Bare-root sub-links are dropped (utils/tidy_conf/links.py). A cfp_link, sponsor or finaid that is only the root of the same site, or of a parent domain, is now removed. Example: https://pycon.de/ next to link: https://2027.pycon.de/. This is what the python-organizers import kept writing back onto PyCon Germany. The existing rule against a sub-link that equals link is unchanged. Nothing in the current data matches the new rule.
  • Cross-site redirects stop counting as healthy (check_link_availability).
    • Before, a link that redirected to a different site and returned 200 was kept as fine. That is how pytennessee.org ended up serving a gambling site.
    • Now the original URL is kept unchanged, with no Wayback swap, and the redirect is logged and recorded.
    • When DESK_QUEUE_OUT names a file, each record is also appended there as one JSON line, ready for the queue a later PR adds.
    • "Same site" means:
      • the same host, ignoring www.;
      • a subdomain of the other host;
      • the same registrable domain, using a small list of two-level suffixes such as co.uk and org.il.
    • Short links that land on their expected target (forms.gle to Google Forms, lu.ma to luma.com) behave as before.
  • Optional coc field for the conference's Code of Conduct URL. It is added to utils/schema.yml, the pydantic model, the key order (right after finaid), the README field table, CONTRIBUTING and the validation columns. Nothing sets it yet; the desk will fill it in on its visits.
  • Branch sync fails loudly. enrich-tba-conferences.yml and canonical-source-update.yml used to fall back to git merge origin/main -X theirs on a conflict, which silently kept main's side and dropped the bot's edits. They now abort, print ::error title=Branch Sync Conflict::<files> and fail the job. check-conference-update.yml is left alone because a later PR rewrites it.

Needs your call

  • pixi run links isn't scheduled in any workflow. Until a later PR in this stack adds a scheduled, report-only redirect scan, redirects are only found when someone runs it by hand.

Checks

  • Pinned pytest (uv run --python 3.11 --with "pandas<2.3",..., as in REVIEW.md): 775 passed, 0 failed on this branch. On top of the whole stack: 1092 passed, 0 failed.
  • uvx ruff@0.4.4 check utils tests: all checks passed.
  • pre-commit on the changed files: every hook passed.
  • python utils/sort_yaml.py --skip_links exits 0 and leaves _data/ unchanged.
  • The new branch-sync block was run against a scratch repo with a deliberate conflict. It printed the error line, left a clean tree and exited 1.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr


Generated by Claude Code

drop_redundant_link_fields already removed a cfp_link, sponsor or
finaid that repeats the main link. It now also removes one that is only
the bare root of the same site: an empty or "/" path with no query or
fragment, on the same host as link (ignoring "www.") or on a parent
domain of it. "cfp_link: https://pycon.de/" next to
"link: https://2027.pycon.de/" says nothing about the CfP, so it goes.

Sub-pages, #anchors and query strings on the parent domain stay, as do
links to other sites and a year subdomain next to a series link, which
is more specific than the link itself.

The current data has no such links, so the sort leaves _data unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
check_link_availability treated any redirect that ended on a 200 as a
healthy link, even when it landed on another site: pythoncamp.de now
redirects to barcamps.eu and pytennessee.org to a casino site. When the
final URL is on a different site, the original URL is now returned as
it is (no Wayback lookup or save, no replacement), the redirect is
logged, and a record is kept for review.

Different site means the hosts differ after dropping "www.", neither is
a subdomain of the other, and their registrable domains differ. The
registrable domain is the last two labels, or three under a known
two-level public suffix such as co.uk or org.au, so ug.pycon.org to
africa.pycon.org stays the same site. Same-site redirects behave as
before.

Records go to the module-level REDIRECT_RECORDS list and, when the
DESK_QUEUE_OUT environment variable names a file, are appended to it as
JSON lines shaped like a "link-check" "redirect" queue item: source,
kind, received, title, url, year and a payload with conference, year,
field, final_url and status. sort_yaml.check_links passes the
conference, year and field as keyword arguments, which default to None,
so other callers keep working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
Conference entries can now carry "coc", a URL to the conference's Code
of Conduct. It is validated and serialised like finaid and sponsor
(an http or https URL, written back as a plain string) and sits right
after finaid in key order: in utils/schema.yml, which order_keywords
follows, and in the pydantic model, whose field order the YAML writer
follows. It is also listed with the other optional columns in
tidy_conf/validation.py and documented in README.md and
CONTRIBUTING.md.

No entry uses it yet, so the sort leaves _data unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
When the accumulator branch could not be rebased or merged onto main
cleanly, the enrich-tba-conferences and canonical-source-update jobs
retried with "git merge origin/main -X theirs", which resolves every
conflict in favour of main and silently throws away the data changes
already waiting on the branch.

Both jobs now collect the conflicted files, abort the merge, report them
as "::error title=Branch Sync Conflict::<files>" and exit 1, so a person
resolves the conflict. check-conference-update.yml has the same
fallback and is left alone here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
registrable_domain only keeps three labels under a suffix listed in
TWO_LEVEL_PUBLIC_SUFFIXES. PyCon Tanzania (pycon.or.tz) and PyCon
Israel (pycon.org.il, cfp.pycon.org.il) use suffixes that were not
listed, so their registrable domain collapsed to the bare suffix and a
redirect to any other site under it, such as a parked page on
another .org.il domain, counted as the same site and went unrecorded.

or.tz and org.il are the two-level suffixes current entries use that
were missing; co.il is added with org.il. Regression cases cover both
hosts in TestIsDifferentSite and TestRegistrableDomain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
Every forms.gle link redirects to docs.google.com, so the cross-site
check recorded the three forms.gle cfp_links in conferences.yml (and
the ones on recent archive entries) as different-site redirects on
every link check. A person would review the same known-good items again
after each run, and upcoming forms.gle links lost the Wayback save that
any other healthy link gets.

is_expected_redirect now covers links that redirect to another site by
design. EXPECTED_REDIRECT_TARGETS lists hosts with a known target:
forms.gle must land on google.com and lu.ma on luma.com, so a forms.gle
link that lands anywhere else is still recorded. URL_SHORTENERS lists
general shorteners (bit.ly, goo.gl, tinyurl.com), whose links may land
anywhere. Such a redirect is not recorded and the link is checked as it
was before the cross-site check: on a 200 the URL is kept and, for an
upcoming conference, saved to the Wayback Machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
The record_redirect docstring and the earlier commit called each record
a queue item, but a queue item also carries an "id" (a sha1 of source,
kind, url and payload), which the records leave out. The docstring and
the comment on REDIRECT_RECORDS_ENV now say that the JSON lines are not
complete items: the queue writer assigns the id, and the series key of
the file the item goes in, when it adds a line, so both are defined in
one place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr

Copy link
Copy Markdown
Owner Author

Routine review

1. Request

No issue is linked, so the request is the PR description. It asks for four things:

  • Bare-root drop in drop_redundant_link_fields: a cfp_link, sponsor or finaid that is only the root of the same site or of a parent domain is removed.
  • Cross-site redirects in check_link_availability: the URL is kept, there is no Wayback swap, and the redirect is recorded. Records also go to DESK_QUEUE_OUT when that is set. forms.gle and lu.ma are exempt.
  • Optional coc field in utils/schema.yml, schema.py, validation.py, README and CONTRIBUTING.
  • Fail-loud branch sync in enrich-tba-conferences.yml and canonical-source-update.yml. check-conference-update.yml is deliberately left out.

2. Checked against

  • Date: Wed Oct 14 07:01:03 UTC 2026.
  • unverified: git merge -X theirs resolves only conflicting hunks in favour of the merged-in branch (here origin/main) and keeps non-conflicting changes from both sides. git-scm.com did not resolve from the sandbox (getaddrinfo ENOTFOUND git-scm.com), and no local man page was available. If this is right, the old fallback lost the bot's edits only where they conflicted, not all of them. The PR description's wording ("dropped the bot's edits") overstates that a little. Failing loudly is still the right call.

3. Needs your decision

  1. coc is only half-wired. It is in the schema, key order and docs. It is not in check_links (utils/sort_yaml.py:233, which only checks link/cfp_link/sponsor/finaid), REDUNDANT_LINK_FIELDS (utils/tidy_conf/links.py:21) or enrich_tba.URL_FIELDS (utils/enrich_tba.py:53). It is also not rendered anywhere under _layouts/ or _includes/. So once the desk fills it in, a dead or hijacked CoC link is never checked and never shown. Should those wirings land in this PR, or with the later desk PR that first writes coc? Recommendation: the later PR, since nothing sets the field yet. Add coc to the check_links tuple there at the latest.
  2. The redirect scan is unscheduled. The PR description raises this itself: pixi run links runs in no workflow, so cross-site redirects are only found by hand until the follow-up adds a scheduled report-only scan. Recommendation: accept it for this PR. The new code changes nothing in the weekly sort, which runs --skip_links.

4. Findings

Important: None.

Nits:

  • registrable_domain (utils/tidy_conf/links.py:145) uses a last-two-labels heuristic, so tenants of shared hosting suffixes count as the same site. Checked in this review: is_different_site('https://pyconx.github.io/2026/', 'https://squatter.github.io/') → False, and is_different_site('https://pycon.org.pl/', 'https://casino.org.pl/') → False. These are missed flags, not regressions: before this PR none of these redirects were flagged either. If it matters, add github.io, netlify.app, pages.dev and org.pl to TWO_LEVEL_PUBLIC_SUFFIXES, or note the gap in its comment.
  • REDIRECT_RECORDS (utils/tidy_conf/links.py:72) is a module-level list that is appended to and never read outside tests in this PR. This is possible dead state until the queue PR consumes it.

5. Tests

Invariant coverage, per instance:

  • Bare-root drop: tests/test_redundant_links.py::TestDropBareSiteRoot covers all three fields the request names (cfp_link, sponsor, finaid). It also covers the negative cases: a sub-page, an anchor, a query, a look-alike host, and the reverse subdomain direction.
  • Cross-site redirect: tests/test_link_redirects.py covers keep-and-record for pythoncamp.de → barcamps.eu and for pytennessee.org → casino. It also checks that there is no Wayback lookup or save, that both short-link exemptions hold, that forms.gle to an unexpected site is recorded, the JSON-lines output and its append behaviour, and that check_links passes its context through.
  • coc: tests/test_schema_validation.py checks that it is optional, that it accepts a URL and rejects non-URLs.
  • Workflows: no automated test. The PR reports a manual run against a scratch repository.

Checks run on f0f561b, using the commands from REVIEW.md:

uv run --python 3.11 --with "pandas<2.3",pytest,... pytest tests/ -q -x -p no:cacheprovider
799 passed, 5 skipped, 3 xfailed, 2 xpassed, 153 warnings in 27.96s

uvx ruff@0.4.4 check utils tests
All checks passed!

uv run --python 3.11 ... python utils/sort_yaml.py --skip_links
exit 0; git diff --stat -- _data: (empty)

npm ci && npm test
Test Suites: 15 passed, 15 total
Tests:       423 passed, 423 total

The empty _data diff confirms the PR description's claim that no current entry matches the new bare-root rule.

6. Bounds

  • Read in full: the diff of utils/tidy_conf/links.py, utils/sort_yaml.py, schema.py, validation.py, schema.yml, both workflow files, README and CONTRIBUTING; the body of check_link_availability and order_keywords on the head; tests/test_redundant_links.py::TestDropBareSiteRoot.
  • Skimmed: tests/test_link_redirects.py (test names and structure) and the new tests in tests/test_schema_validation.py.
  • Not done:
    • No live HTTP check against current conference sites, because check_links was not run.
    • The pre-commit set beyond ruff was not rerun. REVIEW.md says to quote the pre-commit.ci - pr status instead, and this review did not fetch it.
  • Pre-existing: none noted.

Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Answers to the routine review:


Generated by Claude Code

@JesperDramsch
JesperDramsch added this pull request to stack #502 October 14, 2026 14:19
@JesperDramsch
JesperDramsch merged commit 6e2de09 into main Oct 14, 2026
11 checks passed
@JesperDramsch
JesperDramsch deleted the ai-agent/exciting-tesla-g7fn0r branch October 14, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants