Skip to content

Cloud sweep: named-secrets stubs by default, push-back replay fix, py3.11 import fix, package doctests in CI, PEP 639 licenses - #99

Merged
thorwhalen merged 11 commits into
masterfrom
cloud-sweep-2026-09-26
Sep 27, 2026
Merged

thorwhalen merged 11 commits into
masterfrom
cloud-sweep-2026-09-26

Conversation

@thorwhalen

@thorwhalen thorwhalen commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Tests

Command, which is CI's exact invocation run from the repo root with .[create,docs,skills,test] installed:

python -m pytest --doctest-modules -o doctest_optionflags='ELLIPSIS IGNORE_EXCEPTION_DETAIL' --ignore=examples --ignore=scrap
Python Baseline (master) Final
3.12 650 passed, 1 skipped 788 passed, 12 skipped
3.11 collection error: 2 errors, 0 run (wads.licence_check import fails) 787 passed, 13 skipped
3.10 659 passed, 1 skipped (includes the first commit's new tests) 787 passed, 13 skipped

The final count is larger because package doctests are now collected (73) and new tests were added. The 3.12 venv has twine installed, so one twine-gated test runs there instead of skipping.

The local triage reported two TestPushBackRecovery failures on macOS. They pass on Linux. The cause is BSD sed -i -E, where -E becomes the backup suffix, which made the version replay a silent no-op. That is the #98 bug, and the rewrite no longer uses sed -i.

Dependent i2mint/isee: 67 passed, 2 skipped at baseline and after every change.

Changes

Issues

Not done, and why

Adversarial review (Opus subagent)

Three rounds. Final verdict: APPROVE.

Round 1 (APPROVE, 8 non-blocking findings), all addressed in 39dbed6:

  1. Widened collection turned the non-blocking Windows leg red through two doctests. Fixed: a real Windows bug in _get_org_slash_proj, which stripped os.sep from a URL, plus a path-agnostic extract_pkg_dir_and_name doctest.
  2. Workflow content passed as old_ci lost its JSON transport. Fixed.
  3. Converting an existing inline repo could produce a named stub that can't start (out-of-superset secret names). Fixed: automatic fallback to JSON.
  4. A malformed optional-dependencies could crash read-ci-config fleet-wide. Fixed: robust parsing plus a guard around the warning.
  5. populate crashed on a custom ci_tpl_path. Fixed: only the bundled stub is rewritten.
  6. pep639_license crashed on non-strings. Fixed.
  7. The replay rewrote the first version = line anywhere in the file. Fixed: it is now section-aware ([project] / [metadata]).
  8. Formatting. Fixed.

Round 2 (APPROVE), addressed in cb8b4f1:

  • The CLI paths (ci-to-stub, ci-on-demand) still forced named.
  • Header-regex edge cases: [ project ], ; comment, [[tables]], nested arrays.
  • Nested license table.

Round 3 (APPROVE): two nits.

  • The documented exception for broken named stubs is now noted in CLAUDE.md (3061e80).
  • A bare nested-array element with no trailing comma, and a quoted ["project"] header, still hide the version. Both are practically unreachable: no PEP 621 field is an array of arrays. Left as is.

For whoever lands it

  • Merging publishes a wads release to PyPI.
  • uv-ci.yml is unchanged. actions/git-commit goes live for the fleet at merge.
  • The read-ci-config warning reaches CI logs only after the release is on PyPI.
  • New repos get named-transport stubs from the next release on.

Fixes #98
Fixes #100
Fixes #74
Fixes #88

… version

The push-retry conflict resolution rewrote the version with a sed that only
matched `version = "X.Y.Z"` verbatim. Any other valid spelling (no spaces,
single quotes, indentation, `version=X.Y.Z` in setup.cfg) made it a silent
no-op, so git kept the concurrent run's older version while PyPI had the new
one. The same sed also rewrote every other table's `version = "..."` line, and
it relied on GNU `sed -i` flags (on BSD sed `-E` becomes the backup suffix).

Now an awk rewrite changes only the X.Y.Z of the first `version =` line (the
line the version is read from), keeping the file's formatting, and the step
verifies the version landed: if it did not (e.g. upstream went dynamic), it
aborts the rebase with an ::error:: naming the file and version instead of
pushing a desynced commit. A version file deleted upstream now gets the
step's own error and a clean rebase abort instead of git's bare message.

Fixes #98

Copy link
Copy Markdown
Member Author

cloud-status: started — baseline 650/0/1 on py3.12 (py3.11 can't collect: licence_check dataclass default); plan: fix #98 replay (pushed), fix py3.11 import, named-transport default (#74/#88), extras auto-detect (#59)


Generated by Claude Code

…lags

LicencePolicy.exceptions defaulted to types.MappingProxyType({}). Python
3.11's dataclasses reject unhashable defaults (mappingproxy only became
hashable in 3.12; 3.10 only rejected list/dict/set), so `import
wads.licence_check` raised ValueError on 3.11 and the test module could not
be collected. CI's matrix (3.10, 3.12) skips exactly that version. Use a
default_factory, and add a test that checks the 3.11 rule (every dataclass
field default is hashable) on whatever interpreter runs it.

Also make three licence_check doctests pass under the flags run-tests-uv
passes (`-o doctest_optionflags=...` replaces the repo's NORMALIZE_WHITESPACE):
they failed on whitespace only.

Fixes #100
run-tests-uv calls pytest with no path, so testpaths alone decides what CI
collects. With testpaths = ["wads/tests"], none of the package's 73 doctests
ran in CI, and two had drifted into failing (fixed in the previous commit).

- testpaths = ["wads"]; a root conftest.py ignores wads/data (templates such
  as test_smoke_tpl.py are not valid Python until rendered).
- test_collection_scope.py pins the scope: package doctests collected, test
  modules still collected, nothing under wads/data.
- test_light_install's fixture now restores sys.modules exactly. It left a
  fresh `wads` package object behind, which broke mock.patch("wads.project_
  setup...") on Python 3.10 once package collection imported that submodule
  early (5 failures on 3.10 only).

This changes only wads's own collection. The fleet-wide change #56 proposes
still needs the owner's sign-off, so #56 stays open.
…t-in

GitHub's malicious-workflow scanner holds runs of a stub that serialises
the whole secrets context into a cross-repo workflow
(WADS_CI_SECRETS_JSON: toJSON(toJSON(secrets))) on NEW repositories:
every run ends action_required with zero jobs and no log, and the REST
approve endpoint refuses it. Reproduced on four new repos; the named
transport ran on the next push every time.

- populate / setup_project write a named-transport stub (PYPI_PASSWORD
  plus the [tool.wads.ci.env]-declared secrets) and warn about names
  outside the frozen superset.
- migrate_ci_to_stub(transport=None) keeps the transport of an existing
  stub file and otherwise uses named, so fleet_migrate / ci-to-stub /
  ci-on-demand never silently flip a repo that already runs JSON.
  `--transport json` still selects the JSON transport.
- Shared helpers in wads.ci_secrets: stub_with_named_transport and
  warn_names_outside_superset.
- wads-secrets: the out-of-superset advice named `wads-migrate
  ci-to-stub`, which keeps a named stub named; it now says
  `--transport json`.
- Golden python_lib ci.yml regenerated (only the secrets block and its
  comment changed); README, CLAUDE.md, the stub template's comments and
  the wads-migrate skill updated to match.

Fixes #74
Fixes #88
When [tool.wads.ci.install].extras is unset, CI installs core dependencies
only, so a dev/test/tests/testing/ci extra carrying real test tooling
(httpx, pytest-asyncio, hypothesis...) is silently absent from the test job.
read-ci-config now prints a ::warning:: naming the extra and the missing
packages, with the one-line fix. Packages CI provides anyway (pytest,
pytest-cov, coverage, ruff) and the project's own self-references are
ignored, and any explicit `extras` value (including "") silences it.

This is the additive part of #59. Installing such extras automatically
would change the CI of every repo without an explicit setting, which needs
the owner's decision, so #59 stays open for that.
…ints

Items 1, 2, 4 and 5 of #52:

1. setup-to-pyproject and populate write `license = "<SPDX>"` (PEP 639)
   instead of the deprecated `[project.license] text = ...` table, via a
   new wads.toml_util.pep639_license. A name that is not valid SPDX keeps
   the table form, because Hatchling rejects a non-SPDX license string;
   the same fallback applies when packaging is older than 24.2. Verified:
   a freshly populated project builds with hatchling 1.32 (License-
   Expression: MIT) and passes `twine check`. Golden pyproject regenerated.
2. An empty [tool.wads.ci].project_name already falls back to
   [project].name in CIConfig; a test now pins that.
4. (Already fixed upstream: the stub comment uses `@0.2.15`.)
5. fleet-stub's --pin help and the ci-to-stub hint no longer suggest
   `@v...` pins or `gh release list` (wads publishes bare-version tags, not
   releases); a test guards the source.

Item 3 (testpaths for doctest-only repos) waits on the #56 rollout decision.
- license = "Apache-2.0" (PEP 639) replaces the deprecated
  [project.license] text = "Apache Software License" table; no License ::
  classifier, as PEP 639 requires.
- Trove classifiers (Python 3.10-3.13, build-tool topics), broader keywords,
  and Repository / Documentation / Issues URLs (all three resolve).

Built from a clean copy of the tracked files: sdist and wheel pass
`twine check`, the wheel carries License-Expression: Apache-2.0, and all 12
pip-shipped skills are still in it.
…ills links

- README opens with what wads does and a "For AI agents" section: the 12
  pip-shipped skills and how to enable them (wads-install-skills, gh skill),
  where CLAUDE.md is, what an agent can do, and a minimal example that
  test_readme.py executes. A "For carbon-based contributors" section at the
  end replaces "Development" with dev setup, CI's exact test command, design
  rationale and where to ask. The old "Claude Code Skills" section (it
  listed 2 of 12 skills) is folded into the agent section.
- Fixed two broken links (docs/SYSTEM_DEPENDENCIES.md and CLAUDE.md pointed
  at files that do not exist); test_readme.py now checks every relative
  link and in-page anchor.
- New maintainer skill skills/wads-dev-workflow (repo-root skills/, not
  pip-shipped, per the skill-package-setup layout rule): CI-exact tests on
  3.10/3.11/3.12, golden regeneration (snippet run and verified to reproduce
  the goldens), the push-back harness, what must change together.
  `skill validate` passes for it.
- .claude/skills gains the missing relative links for wads-migrate and
  setup-py-project, plus wads-dev-workflow.
- CLAUDE.md's Testing section now gives CI's exact command.
- Windows / checkout-name portability: _get_org_slash_proj stripped a
  trailing os.sep (backslash on Windows) from a URL, so it broke on
  Windows; it now strips "/". The extract_pkg_dir_and_name doctest no
  longer assumes the checkout directory is named "wads" or uses "/".
- migrate_ci_to_stub: `old_ci` given as content (not a path) keeps its
  transport; with no explicit transport, a stub whose secrets fall outside
  the named superset keeps the JSON transport instead of producing a stub
  that cannot start (#63), with a note on stderr.
- populate only rewrites the bundled stub's transport; a custom
  ci_tpl_path is left as is (it used to crash on a missing comment anchor).
- uninstalled_test_extras skips malformed optional-dependencies, non-string
  entries and requirements whose markers do not apply, and read_ci_config
  can never fail on it.
- pep639_license passes non-strings through as the table form.
- git-commit replay: the version is read and rewritten only in [project]
  (pyproject.toml) or [metadata] (setup.cfg), or before any table, so a
  tool table's `version` key placed earlier is left alone.
- ruff format on the touched files.
- `wads-migrate ci-to-stub` and `ci-on-demand` no longer force an explicit
  transport, so converting an inline workflow whose secrets fall outside
  the named superset gets the JSON transport instead of an unstartable
  named stub; an existing stub still keeps its own transport.
- git-commit replay: a section header may have inner spaces
  (`[ project ]`), a `;` comment (setup.cfg) or be an array-of-tables
  (`[[tool.x]]`), and only complete header lines switch sections, so a
  nested-array line inside [project] no longer hides the version.
- pep639_license returns an existing table unchanged.
@thorwhalen thorwhalen changed the title WIP: Cloud sweep (2026-09-26) Cloud sweep: named-secrets stubs by default, push-back replay fix, py3.11 import fix, package doctests in CI, PEP 639 licenses Sep 27, 2026
@thorwhalen
thorwhalen merged commit b87ec3d into master Sep 27, 2026
12 checks passed
@thorwhalen
thorwhalen deleted the cloud-sweep-2026-09-26 branch September 27, 2026 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment