Skip to content

Publish to PyPI via trusted publishing; the scaffold ships inside the package - #12

Merged
dmarx merged 1 commit into
mainfrom
claude/pypi-publishing
Aug 4, 2026
Merged

Publish to PyPI via trusted publishing; the scaffold ships inside the package#12
dmarx merged 1 commit into
mainfrom
claude/pypi-publishing

Conversation

@dmarx

@dmarx dmarx commented Aug 4, 2026

Copy link
Copy Markdown
Owner

The publishing half of #3, matched to the trusted publisher you registered (publish.yml, pypi environment). Recorded as ADR-027.

The bug the pre-publish check found

The existing packaging worked by accident. Setuptools' parent-relative package data (luria = ["../template/**/*"]) landed template/ at the wheel root — a bare, unnamespaced directory installed straight into site-packages, where it would collide with any other package shipping one. luria init found it only because Path(__file__).parent.parent from inside site-packages happens to be site-packages. Green smoke test, collision-prone packaging.

Fix: hatchling force-include maps top-level template/luria/template/ in the wheel. The repo keeps template/ at the top level where a visitor browses it (ADR-021); the install keeps to its namespace; init._template_dir() tries the packaged location first, checkout fallback second. Verified: nothing outside luria/ in the wheel; site-packages contains only luria and deps.

The workflow

  • Build and publish are separate jobs — the artifact that ships is byte-identical to the one tested, handed over via upload/download; the pypi environment gate covers the smallest surface, with id-token: write and nothing else. No token to leak, rotate, or forget.
  • Fires on GitHub release (notes make "what shipped" browsable) plus workflow_dispatch as escape hatch.
  • The release gate is a cold-install smoke test: pip install dist/*.whl into a fresh venv, then init → index → journal new → index → lint in an empty directory. This is the only configuration that exercises the packaged-template path — a checkout always has the fallback, so the dogfooding repo structurally can't catch this class.

Run by hand before writing the workflow, the smoke test immediately caught the scaffolded CLAUDE.md's illustrative wikilinks ([[ADR-013]], [[SG-DP-18]]) leaking as day-one dangling-code warnings in a fresh project — third instance of the example-leak pattern. The examples now use codes whose resolution status is identical in both contexts (ADR-777 annotated everywhere, DP-3 and LU-DP-9 resolving everywhere), so a fresh project lints with zero warnings and the annotation is never stale in this repo.

Also

  • Real PyPI metadata: classifiers, project URLs, keywords, author by GitHub handle (no email published).
  • README: pip install luria.
  • sdist carries build inputs (package, template, tests), not the record — the record's browsing surface is the repository, and shipping ~300 KB of history per pip download would be segregate meta-project memory #3's deadweight relocated.

Verification

  • twine check passes; wheel contains 16 template files under luria/template/, nothing outside luria/.
  • Full cold-install smoke: fresh venv → init → index → journal new → lint, zero warnings.
  • 234 tests, luria lint clean.

After merge

Cut a GitHub release (v0.1.0, matching pyproject.toml) and publish.yml takes it from there. I can draft the release once this merges — say the word.


Generated by Claude Code

… package

The pre-publish check found a wheel that passed its smoke test for the
wrong reason: setuptools' parent-relative package data landed template/
at the WHEEL ROOT — a bare unnamespaced directory installed straight
into site-packages, found by luria init only because parent.parent from
inside site-packages happens to BE site-packages. Functional, and one
neighbouring package's template/ away from a file fight.

Fixed with hatchling force-include: the wheel carries luria/template/,
the repo keeps template/ at the top level where a visitor browses it
(ADR-021), and init._template_dir() tries the packaged location first
with the checkout as fallback.

publish.yml: build and publish as separate jobs — the artifact that
ships is byte-identical to the one tested — with the publish job in the
`pypi` environment holding id-token: write and nothing else. Trusted
publishing means the credential is an identity, not a secret. Fires on
GitHub release + workflow_dispatch.

The release gate is a cold-install smoke test: pip install the built
wheel into a fresh venv, then init → index → journal new → lint in an
empty directory — the only configuration that exercises the packaged
template path, since a checkout always has the fallback. Run by hand
first, it caught the scaffolded CLAUDE.md's illustrative wikilinks
leaking as day-one warnings; the examples now use codes whose resolution
status is identical in both contexts (ADR-777 annotated, DP-3 and
LU-DP-9 resolving everywhere), so a fresh project lints with zero
warnings.

Also: real PyPI metadata (classifiers, URLs, keywords), README says
pip install luria, sdist carries build inputs not the record.

ADR-027 records the decision.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014H7APrKgWA23SVZMNF8H86
@dmarx
dmarx merged commit a288449 into main Aug 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants