Skip to content

feat: add charm-tech-baseline, the repo-setup audit tool - #2

Draft
tonyandrewmeyer wants to merge 5 commits into
canonical:move-ai-failure-notifierfrom
tonyandrewmeyer:feat/charm-tech-baseline
Draft

feat: add charm-tech-baseline, the repo-setup audit tool#2
tonyandrewmeyer wants to merge 5 commits into
canonical:move-ai-failure-notifierfrom
tonyandrewmeyer:feat/charm-tech-baseline

Conversation

@tonyandrewmeyer

Copy link
Copy Markdown
Collaborator

The checks and fixes have been living as 4,500 lines of loose scripts inside a skill in canonical/charm-tech, which gave them no lockfile, no CI, and nothing that ran their tests. This is the half that is code: 29 checks, 8 mechanical fixes, tier detection, and the templates and question batteries they read. The skill keeps the half that is prose, and drives this through uvx.

  • Stacked on feat: add an ai-failure-notifier workflow and script #1, since that is the PR that establishes the conventions this follows. One top-level directory, its own pyproject.toml, src/, tests/ and lockfile, ruff config left to the root, consumed by uvx --from "git+...#subdirectory=charm-tech-baseline".
  • The runner imports each check and calls it rather than shelling out and reparsing its stdout. emit_check hands the result over directly when a collector is active, and still prints when a check is invoked on its own, which is how the tests drive them.
  • The runner sets sys.argv for each check instead of letting it read the runner's own command line. That was a real defect rather than something the port introduced: a check only ever saw --tier because the runner happened to have been passed the same flag, so a detected tier never reached one, and only an explicit --tier worked.
  • PyYAML becomes a dependency rather than three # /// script blocks. The presence-only fallbacks those three carry are left alone, but can no longer trigger.

Ported rather than rewritten. The long message strings are wrapped to the root ruff config's 99 columns by implicit concatenation, so no message text changed, and a full report against canonical/pebble is byte-identical before and after: checks and notes both. That is the evidence that neither the reflow nor the restructuring changed any behaviour.

The skill side is a separate PR against canonical/charm-tech, and wants merging after this one so it never points at something that is not there.

tonyandrewmeyer and others added 5 commits August 27, 2026 10:04
The audit has been living as 4,500 lines of scripts inside a skill in
canonical/charm-tech, where it has no lockfile, no CI and no tests that
anything runs. This is the half that is code: 29 checks, 8 mechanical
fixes, tier detection, and the templates and question batteries they
read. The skill keeps the half that is prose - when a check applies,
what a finding means, and which decisions are already settled - and
drives this through uvx.

Ported rather than rewritten, so the report is byte-identical to what
the scripts produced. Three changes were needed to make it a package:

* Checks are imported and called by the runner instead of being shelled
  out to and having their stdout reparsed. emit_check hands the result
  straight over when a collector is active, and still prints when a
  check is invoked on its own, which is how the tests drive them.
* The runner sets sys.argv for each check rather than letting it read
  the runner's own command line. That was a real defect: a check only
  ever saw --tier because the runner happened to have been given the
  same flag, so a detected tier never reached one.
* PyYAML becomes a dependency instead of three `# /// script` blocks.

The long message strings are wrapped to the shared 99-column ruff
config by implicit concatenation, so no message text changed. Confirmed
by diffing a full report against canonical/pebble before and after: the
checks and notes are identical.
As of v4, actions/attest-build-provenance is documented as "simply a
wrapper on top of actions/attest", and upstream says new implementations
should use actions/attest instead. Point both trusted-publishing
templates at it, and rewrite the comment in the canonical template that
explained the two attestation calls in terms of the wrapper's missing
sbom-path input.

The attest-build-provenance check matched on the wrapper's name alone, so
it would have failed a workflow that took upstream's advice. It now
accepts either action, comparing the action name exactly rather than by
substring so that actions/attest-sbom does not pass a provenance check on
a prefix match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fRGz2wZnuTK1kXNnu7PYx
A separate test-publish workflow drifts from the real one, so the
rehearsal stops exercising the steps most likely to break. Both
trusted-publishing templates now take a tag push and a manual dispatch,
with the trigger selecting the environment, the repository URL and which
of the two mutually exclusive version steps runs. A reusable workflow
would be tidier, but PyPI validates the job_workflow_ref OIDC claim, so a
reusable workflow cannot be the workflow in a trusted publisher
(warehouse#11096); the header comments say so, to stop someone
refactoring into that shape later.

Both templates also gain the tag/version check that charmlint grew, since
a hand-maintained version in pyproject.toml is otherwise unchecked.

_targets_test_pypi matched TestPyPI hosts anywhere in the repository URL,
so the merged publish step, whose URL expression names both hosts, read as
TestPyPI-only. That made is_publish_step reject it, and the check then
reported 'na' rather than looking for an attestation - a workflow taking
the shape we now recommend would silently stop being checked. A step that
names a real PyPI host as well as a test one is no longer treated as
TestPyPI-only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fRGz2wZnuTK1kXNnu7PYx
The reasoning was only in a review thread, which the template does not
carry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fRGz2wZnuTK1kXNnu7PYx
…lates

The review on canonical/charmlint#199 landed four changes to the workflow
that this template is the source for, so bring them back here:

* Comment the `environment:` name and url, explaining that the name must
  match the environment registered with the trusted publisher on each
  index while the url is only the deployment link in the UI.
* Convert the tag/version check and the .dev suffix step from shell to
  `shell: python`, dropping the `python3 -c` round trip and the sed
  escaping.
* Attach the SBOM-upload comment to the step it explains.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015exA46csF9tmRnQ8mLrzvA
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.

1 participant