axi-toolkit 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- axi_toolkit-0.2.0/.githooks/commit-msg +25 -0
- axi_toolkit-0.2.0/.githooks/pre-commit +17 -0
- axi_toolkit-0.2.0/.github/workflows/ci.yml +183 -0
- axi_toolkit-0.2.0/.github/workflows/hygiene.yml +91 -0
- axi_toolkit-0.2.0/.github/workflows/release.yml +162 -0
- axi_toolkit-0.2.0/.gitignore +16 -0
- axi_toolkit-0.2.0/.release-please-manifest.json +3 -0
- axi_toolkit-0.2.0/AGENTS.md +201 -0
- axi_toolkit-0.2.0/CHANGELOG.md +13 -0
- axi_toolkit-0.2.0/CLAUDE.md +1 -0
- axi_toolkit-0.2.0/LICENSE +21 -0
- axi_toolkit-0.2.0/PKG-INFO +162 -0
- axi_toolkit-0.2.0/README.md +135 -0
- axi_toolkit-0.2.0/metaobjects/meta.axi-toolkit.yaml +488 -0
- axi_toolkit-0.2.0/pyproject.toml +74 -0
- axi_toolkit-0.2.0/release-please-config.json +13 -0
- axi_toolkit-0.2.0/scripts/commitcheck.py +1611 -0
- axi_toolkit-0.2.0/scripts/install-hooks.sh +12 -0
- axi_toolkit-0.2.0/scripts/leakcheck.py +1017 -0
- axi_toolkit-0.2.0/scripts/reqgen.py +547 -0
- axi_toolkit-0.2.0/src/axi_toolkit/__init__.py +28 -0
- axi_toolkit-0.2.0/src/axi_toolkit/envconfig.py +243 -0
- axi_toolkit-0.2.0/src/axi_toolkit/errors.py +458 -0
- axi_toolkit-0.2.0/src/axi_toolkit/redact.py +173 -0
- axi_toolkit-0.2.0/src/axi_toolkit/render/__init__.py +7 -0
- axi_toolkit-0.2.0/src/axi_toolkit/render/cli.py +191 -0
- axi_toolkit-0.2.0/src/axi_toolkit/render/prose.py +100 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon.py +349 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/LICENSE +21 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/PROVENANCE.md +69 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/__init__.py +224 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/checksums.txt +9 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/arrays-nested.json +132 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/arrays-objects.json +191 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/arrays-primitive.json +111 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/arrays-tabular.json +182 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/delimiters.json +253 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/objects-keyed.json +155 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/objects.json +297 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/primitives.json +278 -0
- axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/whitespace.json +51 -0
- axi_toolkit-0.2.0/tests/conformance/__init__.py +9 -0
- axi_toolkit-0.2.0/tests/conformance/capture.json +2066 -0
- axi_toolkit-0.2.0/tests/conformance/projections.py +930 -0
- axi_toolkit-0.2.0/tests/conformance/relations.py +187 -0
- axi_toolkit-0.2.0/tests/conformance/specs.py +96 -0
- axi_toolkit-0.2.0/tests/conformance/test_requirements_generated.py +461 -0
- axi_toolkit-0.2.0/tests/conftest.py +16 -0
- axi_toolkit-0.2.0/tests/test_envconfig.py +285 -0
- axi_toolkit-0.2.0/tests/test_errors.py +208 -0
- axi_toolkit-0.2.0/tests/test_purity.py +154 -0
- axi_toolkit-0.2.0/tests/test_redact.py +196 -0
- axi_toolkit-0.2.0/tests/test_relations.py +138 -0
- axi_toolkit-0.2.0/tests/test_render_cli.py +199 -0
- axi_toolkit-0.2.0/tests/test_render_prose.py +133 -0
- axi_toolkit-0.2.0/tests/test_reqgen.py +332 -0
- axi_toolkit-0.2.0/tests/test_toon.py +227 -0
- axi_toolkit-0.2.0/tests/test_toon_conformance.py +98 -0
- axi_toolkit-0.2.0/vendor/conventional-commits-parser/LICENSE +14 -0
- axi_toolkit-0.2.0/vendor/conventional-commits-parser/PROVENANCE.md +65 -0
- axi_toolkit-0.2.0/vendor/conventional-commits-parser/checksums.txt +5 -0
- axi_toolkit-0.2.0/vendor/conventional-commits-parser/lib/codes.js +10 -0
- axi_toolkit-0.2.0/vendor/conventional-commits-parser/lib/parser.js +431 -0
- axi_toolkit-0.2.0/vendor/conventional-commits-parser/lib/scanner.js +79 -0
- axi_toolkit-0.2.0/vendor/conventional-commits-parser/lib/type-checks.js +25 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Two things a commit message can get wrong, both invisible until much later.
|
|
3
|
+
#
|
|
4
|
+
# 1. It can publish library-specific data. File content and commit messages are
|
|
5
|
+
# separate channels; --staged only covers the former, and a message is just
|
|
6
|
+
# as public.
|
|
7
|
+
# 2. It can be one release-please cannot parse. That one is worse, because it
|
|
8
|
+
# fails silently and green: the release workflow logs "commit could not be
|
|
9
|
+
# parsed" at debug level, considers zero commits, skips the release and exits
|
|
10
|
+
# 0, and the fix sits on main unpublished. It has already cost one release
|
|
11
|
+
# here. See scripts/commitcheck.py for the exact grammar rule.
|
|
12
|
+
#
|
|
13
|
+
# Install with: scripts/install-hooks.sh
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
root=$(git rev-parse --show-toplevel)
|
|
17
|
+
python=$(command -v python3 || command -v python || true)
|
|
18
|
+
|
|
19
|
+
if [ -z "$python" ]; then
|
|
20
|
+
echo "commit-msg: no python interpreter found; cannot run the checks" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
"$python" "$root/scripts/leakcheck.py" --commit-msg "$1"
|
|
25
|
+
"$python" "$root/scripts/commitcheck.py" --commit-msg "$1" --root "$root"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Block a commit that would publish installation-specific data.
|
|
3
|
+
#
|
|
4
|
+
# Install with: scripts/install-hooks.sh
|
|
5
|
+
# The same scan runs in CI, so bypassing this hook only delays the failure.
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
root=$(git rev-parse --show-toplevel)
|
|
9
|
+
python=$(command -v python3 || command -v python || true)
|
|
10
|
+
|
|
11
|
+
if [ -z "$python" ]; then
|
|
12
|
+
echo "pre-commit: no python interpreter found; cannot run the leak check" >&2
|
|
13
|
+
echo "pre-commit: install python3, or run the scan manually before pushing" >&2
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
"$python" "$root/scripts/leakcheck.py" --staged --root "$root"
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Local CI — the heavy validation matrix, on the maintainer's self-hosted runner.
|
|
2
|
+
#
|
|
3
|
+
# SECURITY (public repository + self-hosted runner): this workflow must NEVER
|
|
4
|
+
# gain a `pull_request` trigger. The runner is the maintainer's own workstation,
|
|
5
|
+
# and a job on it executes repository code as a real user with that user's files
|
|
6
|
+
# and network. Every trigger below is reachable only by someone who already has
|
|
7
|
+
# write access — a push to `main`, the schedule, or a manual dispatch — so
|
|
8
|
+
# fork-submitted code cannot reach the machine. `pull_request` would hand any
|
|
9
|
+
# contributor on the internet code execution on it, in one line and with no
|
|
10
|
+
# other visible symptom. Add coverage for pull requests in `hygiene.yml`, which
|
|
11
|
+
# is GitHub-hosted, or not at all.
|
|
12
|
+
#
|
|
13
|
+
# WHERE VALIDATION ACTUALLY HAPPENS: every change here goes through the local
|
|
14
|
+
# no-mistakes gate — review, tests, lint, docs — before a PR is ever opened.
|
|
15
|
+
# GitHub-hosted CI is not the primary quality signal for this repository and
|
|
16
|
+
# should not be treated as one, which is why a pull request shows a single cheap
|
|
17
|
+
# check (the leak scan, the one gate that must run before a human reads a diff)
|
|
18
|
+
# rather than a matrix. This workflow is the full matrix; it runs on `main`,
|
|
19
|
+
# nightly, and on demand.
|
|
20
|
+
name: ci (self-hosted)
|
|
21
|
+
|
|
22
|
+
on:
|
|
23
|
+
push:
|
|
24
|
+
branches: [main]
|
|
25
|
+
# Nightly. Deliberately not 08:17 or 09:41 UTC: two sibling projects hold
|
|
26
|
+
# those slots on the same workstation.
|
|
27
|
+
schedule:
|
|
28
|
+
- cron: '23 10 * * *'
|
|
29
|
+
workflow_dispatch:
|
|
30
|
+
|
|
31
|
+
permissions:
|
|
32
|
+
contents: read
|
|
33
|
+
|
|
34
|
+
concurrency:
|
|
35
|
+
group: ci-${{ github.ref }}
|
|
36
|
+
cancel-in-progress: true
|
|
37
|
+
|
|
38
|
+
jobs:
|
|
39
|
+
leakcheck:
|
|
40
|
+
name: leak check
|
|
41
|
+
runs-on: [self-hosted, linux]
|
|
42
|
+
timeout-minutes: 10
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v4
|
|
45
|
+
with:
|
|
46
|
+
# The workspace on a self-hosted runner outlives the job; do not leave
|
|
47
|
+
# a credential behind in its .git/config.
|
|
48
|
+
persist-credentials: false
|
|
49
|
+
- uses: actions/setup-python@v5
|
|
50
|
+
with:
|
|
51
|
+
python-version: "3.12"
|
|
52
|
+
# Prove the scanner still detects what it claims to before trusting it.
|
|
53
|
+
- name: Self-test against a synthetic dirty fixture
|
|
54
|
+
run: python3 scripts/leakcheck.py --demo
|
|
55
|
+
- name: Scan every tracked file
|
|
56
|
+
run: python3 scripts/leakcheck.py
|
|
57
|
+
|
|
58
|
+
# A commit message release-please cannot parse is dropped from the changelog
|
|
59
|
+
# and the version bump while the release workflow exits 0. `release.yml` fails
|
|
60
|
+
# on that too, but it only runs on `main`; here it also runs nightly, so an
|
|
61
|
+
# allowance that has outlived its cause surfaces without waiting for a merge.
|
|
62
|
+
commits:
|
|
63
|
+
name: commit messages are readable
|
|
64
|
+
runs-on: [self-hosted, linux]
|
|
65
|
+
timeout-minutes: 10
|
|
66
|
+
permissions:
|
|
67
|
+
contents: read
|
|
68
|
+
# A pull request body can replace a commit message entirely, so the audit
|
|
69
|
+
# reads it. Read-only.
|
|
70
|
+
pull-requests: read
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@v4
|
|
73
|
+
with:
|
|
74
|
+
fetch-depth: 0
|
|
75
|
+
persist-credentials: false
|
|
76
|
+
- uses: actions/setup-python@v5
|
|
77
|
+
with:
|
|
78
|
+
python-version: "3.12"
|
|
79
|
+
- uses: actions/setup-node@v4
|
|
80
|
+
with:
|
|
81
|
+
node-version: "20"
|
|
82
|
+
- name: Self-test the checker
|
|
83
|
+
run: python3 scripts/commitcheck.py --demo
|
|
84
|
+
- name: Audit every commit since the last release
|
|
85
|
+
env:
|
|
86
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
87
|
+
run: python3 scripts/commitcheck.py --since-release --pull-requests require
|
|
88
|
+
|
|
89
|
+
# EVERY job that needs third-party packages installs into a fresh `.venv` and
|
|
90
|
+
# calls the tools by path out of it. That is not ceremony. This runner runs as
|
|
91
|
+
# the maintainer's own user, so `~/.local/lib/python3.X/site-packages` and
|
|
92
|
+
# `~/.local/bin` are on the interpreter's path and ahead of it on `PATH` — a
|
|
93
|
+
# bare `pytest` or `ruff` resolves to whatever the maintainer happens to have
|
|
94
|
+
# installed, against `/usr/bin/python3.X` rather than the interpreter this job
|
|
95
|
+
# set up. A venv excludes the user site by construction, so a job sees only
|
|
96
|
+
# what it just installed.
|
|
97
|
+
lint:
|
|
98
|
+
name: lint
|
|
99
|
+
runs-on: [self-hosted, linux]
|
|
100
|
+
timeout-minutes: 10
|
|
101
|
+
steps:
|
|
102
|
+
- uses: actions/checkout@v4
|
|
103
|
+
with:
|
|
104
|
+
persist-credentials: false
|
|
105
|
+
- uses: actions/setup-python@v5
|
|
106
|
+
with:
|
|
107
|
+
python-version: "3.12"
|
|
108
|
+
# --clear because the workspace outlives the job on a self-hosted runner.
|
|
109
|
+
- run: python -m venv --clear .venv
|
|
110
|
+
- run: .venv/bin/python -m pip install --upgrade pip
|
|
111
|
+
- run: .venv/bin/pip install -e ".[dev]"
|
|
112
|
+
- run: .venv/bin/ruff check .
|
|
113
|
+
- run: .venv/bin/ruff format --check .
|
|
114
|
+
|
|
115
|
+
test:
|
|
116
|
+
name: test (py${{ matrix.python-version }})
|
|
117
|
+
runs-on: [self-hosted, linux]
|
|
118
|
+
timeout-minutes: 20
|
|
119
|
+
strategy:
|
|
120
|
+
fail-fast: false
|
|
121
|
+
matrix:
|
|
122
|
+
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
|
123
|
+
steps:
|
|
124
|
+
- uses: actions/checkout@v4
|
|
125
|
+
with:
|
|
126
|
+
persist-credentials: false
|
|
127
|
+
# The runner's host provides only some of these; setup-python downloads
|
|
128
|
+
# the rest into the persistent tool cache, so only the first run pays.
|
|
129
|
+
- uses: actions/setup-python@v5
|
|
130
|
+
with:
|
|
131
|
+
python-version: ${{ matrix.python-version }}
|
|
132
|
+
- run: python -m venv --clear .venv
|
|
133
|
+
- run: .venv/bin/python -m pip install --upgrade pip
|
|
134
|
+
- run: .venv/bin/pip install -e ".[dev]"
|
|
135
|
+
# No credentials, no source checkouts, no network. The conformance layer
|
|
136
|
+
# reads the committed capture, which is the property that lets the whole
|
|
137
|
+
# suite run here at all — and the floor is 3.9 precisely because the
|
|
138
|
+
# metadata toolchain (3.11+) is not needed to run what it generated.
|
|
139
|
+
- run: .venv/bin/pytest
|
|
140
|
+
|
|
141
|
+
# The declaration is the source of truth, so a generated file that no longer
|
|
142
|
+
# matches it is a failure rather than a stale artefact somebody will notice.
|
|
143
|
+
# Separate from `test` because the metadata toolchain's floor is 3.11 while
|
|
144
|
+
# the matrix goes down to 3.9: the generator needs it, the generated checks
|
|
145
|
+
# never do.
|
|
146
|
+
requirements:
|
|
147
|
+
name: requirements are current
|
|
148
|
+
runs-on: [self-hosted, linux]
|
|
149
|
+
timeout-minutes: 10
|
|
150
|
+
steps:
|
|
151
|
+
- uses: actions/checkout@v4
|
|
152
|
+
with:
|
|
153
|
+
persist-credentials: false
|
|
154
|
+
- uses: actions/setup-python@v5
|
|
155
|
+
with:
|
|
156
|
+
python-version: "3.12"
|
|
157
|
+
- run: python -m venv --clear .venv
|
|
158
|
+
- run: .venv/bin/python -m pip install --upgrade pip
|
|
159
|
+
- run: .venv/bin/pip install -e ".[dev,reqgen]"
|
|
160
|
+
# Loads the declaration under the sealed-registry discipline and applies
|
|
161
|
+
# the binding rules — an invented attribute, a requirement with no check,
|
|
162
|
+
# a reference that no longer resolves, all fail here.
|
|
163
|
+
- run: .venv/bin/python scripts/reqgen.py list
|
|
164
|
+
- run: .venv/bin/python scripts/reqgen.py check
|
|
165
|
+
|
|
166
|
+
# Nothing watches a workflow that runs on main and overnight, so a failure has
|
|
167
|
+
# to come and find someone.
|
|
168
|
+
notify:
|
|
169
|
+
needs: [leakcheck, commits, lint, test, requirements]
|
|
170
|
+
if: always() && contains(needs.*.result, 'failure')
|
|
171
|
+
runs-on: [self-hosted, linux]
|
|
172
|
+
timeout-minutes: 5
|
|
173
|
+
steps:
|
|
174
|
+
- env:
|
|
175
|
+
# env-indirection: a commit message is untrusted text and must never be
|
|
176
|
+
# interpolated with ${{ }} directly into a script body.
|
|
177
|
+
HEAD_MSG: ${{ github.event.head_commit.message }}
|
|
178
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
179
|
+
run: |
|
|
180
|
+
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus"
|
|
181
|
+
notify-send -u critical "ci FAILED: axi-toolkit" \
|
|
182
|
+
"$(printf '%s' "$HEAD_MSG" | head -n1)
|
|
183
|
+
$RUN_URL" || echo "notify-send unavailable; GitHub email still fires"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# The one check a pull request gets on a GitHub-hosted runner.
|
|
2
|
+
#
|
|
3
|
+
# The leak scan takes seconds and it is the gate that must run before a human
|
|
4
|
+
# looks at a diff: this repository is public and it is built by reading two
|
|
5
|
+
# tools and the installations they were developed against, so an address, a
|
|
6
|
+
# token or a real entity name in a diff is the failure that matters. Everything
|
|
7
|
+
# heavier — lint, the Python matrix, the requirements gate — runs in `ci.yml` on
|
|
8
|
+
# the maintainer's self-hosted runner, on `main` rather than on pull requests.
|
|
9
|
+
#
|
|
10
|
+
# That is not a reduction in rigour, and a thin-looking PR check should not be
|
|
11
|
+
# read as one: every change goes through the local no-mistakes gate — review,
|
|
12
|
+
# tests, lint, docs — before the PR exists. GitHub-hosted CI is not the primary
|
|
13
|
+
# quality signal here. Keep this workflow to the one cheap job; adding the
|
|
14
|
+
# matrix back would restore the duplicate-run waste it was split up to end.
|
|
15
|
+
#
|
|
16
|
+
# The two steps that read the pull request itself are the one exception to
|
|
17
|
+
# that, and they are not coverage for their own sake: they check something
|
|
18
|
+
# that exists ONLY on a pull request. A title and a body are in no checkout,
|
|
19
|
+
# pass under no hook, and can be edited after every other check has run — so
|
|
20
|
+
# `edited` is in the trigger list, and two separate guards depend on it
|
|
21
|
+
# being there.
|
|
22
|
+
#
|
|
23
|
+
# The first is a leak scan of the title and body. This repository is public and
|
|
24
|
+
# the pipeline's own document step writes into the body, pasting captured pytest
|
|
25
|
+
# output that carries absolute paths on two lines: the header's `rootdir:` line,
|
|
26
|
+
# and the warnings summary, which prints the site-packages path of the
|
|
27
|
+
# interpreter that raised the warning — a path no choice of capture directory
|
|
28
|
+
# moves. The first line published a home directory three times across two
|
|
29
|
+
# sibling repositories, with every check green each time, because nothing
|
|
30
|
+
# scanned the one artefact that is neither a tracked file nor a commit message.
|
|
31
|
+
# The guard went in here from the first commit for exactly that, rather than
|
|
32
|
+
# after this repository's first leak — which came anyway, through the second
|
|
33
|
+
# line, in a body whose capture had been run from a scratch directory precisely
|
|
34
|
+
# so the `rootdir:` line would read harmless. The guard caught it, and `edited`
|
|
35
|
+
# re-ran it green on the fixed body. `edited` is what makes the check work at
|
|
36
|
+
# all: the document step writes the evidence in *after* the pull request is
|
|
37
|
+
# opened, so a check that fired only on `opened` would pass the empty original
|
|
38
|
+
# body.
|
|
39
|
+
#
|
|
40
|
+
# The second is release-please's: a body that names the commit-override marker
|
|
41
|
+
# replaces the merged commit message with whatever follows the word, and a
|
|
42
|
+
# paragraph *about* the mechanism is enough to do it. It has already cost a
|
|
43
|
+
# sibling project a release. Still one job, still seconds.
|
|
44
|
+
name: hygiene
|
|
45
|
+
|
|
46
|
+
on:
|
|
47
|
+
pull_request:
|
|
48
|
+
types: [opened, synchronize, reopened, edited]
|
|
49
|
+
|
|
50
|
+
permissions:
|
|
51
|
+
contents: read
|
|
52
|
+
|
|
53
|
+
concurrency:
|
|
54
|
+
group: hygiene-${{ github.ref }}
|
|
55
|
+
cancel-in-progress: true
|
|
56
|
+
|
|
57
|
+
jobs:
|
|
58
|
+
leakcheck:
|
|
59
|
+
name: leak check
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
timeout-minutes: 10
|
|
62
|
+
permissions:
|
|
63
|
+
contents: read
|
|
64
|
+
pull-requests: read
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/checkout@v4
|
|
67
|
+
- uses: actions/setup-python@v5
|
|
68
|
+
with:
|
|
69
|
+
python-version: "3.12"
|
|
70
|
+
# Prove the scanner still detects what it claims to before trusting it: a
|
|
71
|
+
# scanner that quietly stopped matching would otherwise pass every diff.
|
|
72
|
+
- name: Self-test against a synthetic dirty fixture
|
|
73
|
+
run: python3 scripts/leakcheck.py --demo
|
|
74
|
+
- name: Scan every tracked file
|
|
75
|
+
run: python3 scripts/leakcheck.py
|
|
76
|
+
# The surface no hook and no checkout can reach. Fails closed: without a
|
|
77
|
+
# token, or on a fetch that does not answer, it fails the check rather than
|
|
78
|
+
# reporting a clean it cannot support. It reports the field, line and
|
|
79
|
+
# offset of a match and never the match itself — this log is public.
|
|
80
|
+
- name: Scan the pull request title and body
|
|
81
|
+
env:
|
|
82
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
83
|
+
run: python3 scripts/leakcheck.py --pull-request "${{ github.event.pull_request.number }}"
|
|
84
|
+
# Same discipline as the scan above: prove the checker still tells the
|
|
85
|
+
# shapes apart before believing its verdict on this pull request.
|
|
86
|
+
- name: Self-test the commit checker
|
|
87
|
+
run: python3 scripts/commitcheck.py --demo
|
|
88
|
+
- name: Check what a merge would hand release-please
|
|
89
|
+
env:
|
|
90
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
91
|
+
run: python3 scripts/commitcheck.py --pull-request "${{ github.event.pull_request.number }}"
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
# release-please reports success when it cannot read a commit. It catches the
|
|
13
|
+
# parse error, logs `commit could not be parsed` at debug level, carries on
|
|
14
|
+
# with whatever it *could* read, and exits 0 — so a message its parser chokes
|
|
15
|
+
# on is dropped from the changelog and from the version bump while the run goes
|
|
16
|
+
# green. That is not hypothetical here: it is how a merged fix sat on `main`
|
|
17
|
+
# unreleased while this workflow reported success, with `Considering: 0
|
|
18
|
+
# commits` buried in the log.
|
|
19
|
+
#
|
|
20
|
+
# Every commit since the last release tag has to be readable by the parser
|
|
21
|
+
# release-please actually runs — a byte-for-byte copy of it lives in
|
|
22
|
+
# `vendor/conventional-commits-parser/`, so this needs no npm install and no
|
|
23
|
+
# network. A commit it cannot read is one release-please silently dropped, and
|
|
24
|
+
# zero readable commits with commits waiting is the exact state that shipped
|
|
25
|
+
# nothing; both fail here instead of passing quietly. Nothing since the tag is
|
|
26
|
+
# the healthy no-op and still passes, which is the distinction the log did not
|
|
27
|
+
# make.
|
|
28
|
+
#
|
|
29
|
+
# `--pull-requests require` is the correction to the first version of this job,
|
|
30
|
+
# which passed — in the sibling project — while release-please dropped every
|
|
31
|
+
# commit in the range. The message a commit carries is not necessarily the
|
|
32
|
+
# message release-please reads: a pull request body that names the
|
|
33
|
+
# commit-override marker — a paragraph *about* the mechanism is enough —
|
|
34
|
+
# replaces the whole message with whatever follows the word. That artefact is
|
|
35
|
+
# on GitHub and not in the checkout, so the audit needs a token, and `require`
|
|
36
|
+
# makes a missing one fail rather than quietly send it back to checking the
|
|
37
|
+
# wrong string.
|
|
38
|
+
#
|
|
39
|
+
# This job deliberately does NOT `needs:` the one below. It has to fail on its
|
|
40
|
+
# own account, including on a run where release-please itself errored, and its
|
|
41
|
+
# verdict does not depend on the outcome it is auditing.
|
|
42
|
+
commit-audit:
|
|
43
|
+
name: every commit is readable
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
timeout-minutes: 10
|
|
46
|
+
permissions:
|
|
47
|
+
contents: read
|
|
48
|
+
# The pull request body can replace the commit message, so the audit has
|
|
49
|
+
# to read it. Read-only: this job never writes anything.
|
|
50
|
+
pull-requests: read
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v4
|
|
53
|
+
with:
|
|
54
|
+
# The audit compares `main` against the last release tag, so it needs
|
|
55
|
+
# both the tags and the commits after them.
|
|
56
|
+
fetch-depth: 0
|
|
57
|
+
- uses: actions/setup-python@v5
|
|
58
|
+
with:
|
|
59
|
+
python-version: "3.12"
|
|
60
|
+
- uses: actions/setup-node@v4
|
|
61
|
+
with:
|
|
62
|
+
node-version: "20"
|
|
63
|
+
# A checker that stopped detecting anything reads exactly like a checker
|
|
64
|
+
# with nothing to report, so prove it still works before trusting it.
|
|
65
|
+
- name: Self-test the checker
|
|
66
|
+
run: python3 scripts/commitcheck.py --demo
|
|
67
|
+
- name: Audit every commit since the last release
|
|
68
|
+
env:
|
|
69
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
70
|
+
run: python3 scripts/commitcheck.py --since-release --pull-requests require
|
|
71
|
+
|
|
72
|
+
# Drives version bumps and the changelog from conventional commits, and opens
|
|
73
|
+
# a release PR. Merging that PR is what cuts a release.
|
|
74
|
+
release-please:
|
|
75
|
+
name: release PR
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
permissions:
|
|
78
|
+
contents: write
|
|
79
|
+
pull-requests: write
|
|
80
|
+
outputs:
|
|
81
|
+
release_created: ${{ steps.release.outputs.release_created }}
|
|
82
|
+
tag_name: ${{ steps.release.outputs.tag_name }}
|
|
83
|
+
steps:
|
|
84
|
+
- uses: googleapis/release-please-action@v4
|
|
85
|
+
id: release
|
|
86
|
+
with:
|
|
87
|
+
config-file: release-please-config.json
|
|
88
|
+
manifest-file: .release-please-manifest.json
|
|
89
|
+
|
|
90
|
+
# What the action decided, in the run summary rather than only in the log.
|
|
91
|
+
# "No release PR and no release" is a legitimate outcome and also what a
|
|
92
|
+
# silent parse failure looks like; `commit-audit` is what tells the two
|
|
93
|
+
# apart, so say here which one this run was.
|
|
94
|
+
- name: Say what release-please decided
|
|
95
|
+
env:
|
|
96
|
+
RELEASE_CREATED: ${{ steps.release.outputs.release_created }}
|
|
97
|
+
PRS_CREATED: ${{ steps.release.outputs.prs_created }}
|
|
98
|
+
TAG_NAME: ${{ steps.release.outputs.tag_name }}
|
|
99
|
+
run: |
|
|
100
|
+
{
|
|
101
|
+
echo "### release-please"
|
|
102
|
+
echo ""
|
|
103
|
+
echo "| | |"
|
|
104
|
+
echo "| --- | --- |"
|
|
105
|
+
echo "| release created | ${RELEASE_CREATED:-false} |"
|
|
106
|
+
echo "| release PR created or updated | ${PRS_CREATED:-false} |"
|
|
107
|
+
echo "| tag | ${TAG_NAME:-none} |"
|
|
108
|
+
echo ""
|
|
109
|
+
echo "A run with neither a release nor a release PR means nothing"
|
|
110
|
+
echo "releasable has landed. The \`every commit is readable\` job is"
|
|
111
|
+
echo "what proves that is the truth rather than a parse failure."
|
|
112
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
113
|
+
|
|
114
|
+
# NOT YET REACHABLE, on purpose. The PyPI project does not exist: registering
|
|
115
|
+
# the name is a one-time account action only the maintainer can perform, and
|
|
116
|
+
# nothing here should attempt it. The job is written now and gated behind
|
|
117
|
+
# `release_created`, which is only true after a release PR merges, so it
|
|
118
|
+
# cannot fire before the name is claimed. Trusted publishing needs the
|
|
119
|
+
# `pypi` environment and a PyPI publisher configured for this repository
|
|
120
|
+
# before the first release; until then, merging a release PR would fail this
|
|
121
|
+
# job and nothing else.
|
|
122
|
+
publish:
|
|
123
|
+
name: publish to PyPI
|
|
124
|
+
needs: release-please
|
|
125
|
+
if: needs.release-please.outputs.release_created == 'true'
|
|
126
|
+
runs-on: ubuntu-latest
|
|
127
|
+
environment:
|
|
128
|
+
name: pypi
|
|
129
|
+
url: https://pypi.org/p/axi-toolkit
|
|
130
|
+
permissions:
|
|
131
|
+
# The only credential involved: a short-lived OIDC token minted for this
|
|
132
|
+
# run. No long-lived PyPI token exists in this repository or anywhere else.
|
|
133
|
+
id-token: write
|
|
134
|
+
steps:
|
|
135
|
+
- uses: actions/checkout@v4
|
|
136
|
+
- uses: actions/setup-python@v5
|
|
137
|
+
with:
|
|
138
|
+
python-version: "3.12"
|
|
139
|
+
- run: python -m pip install --upgrade pip build
|
|
140
|
+
- run: python -m build
|
|
141
|
+
|
|
142
|
+
# Publishing a broken artifact is worse than not publishing. This package
|
|
143
|
+
# has no entry point to run, so the smoke test is the thing a consumer
|
|
144
|
+
# will actually do: install the wheel on its own, import every module, and
|
|
145
|
+
# run the vendored conformance rig out of the installed copy. That last
|
|
146
|
+
# part is why the fixtures ship inside the package — a wheel whose
|
|
147
|
+
# encoder and fixtures disagreed would go out green otherwise.
|
|
148
|
+
- name: Smoke-test the built wheel
|
|
149
|
+
run: |
|
|
150
|
+
python -m venv /tmp/verify
|
|
151
|
+
/tmp/verify/bin/pip install --quiet dist/*.whl
|
|
152
|
+
/tmp/verify/bin/python -c "
|
|
153
|
+
import axi_toolkit, axi_toolkit.errors, axi_toolkit.redact, axi_toolkit.envconfig
|
|
154
|
+
import axi_toolkit.toon, axi_toolkit.toon_spec
|
|
155
|
+
from axi_toolkit.render import cli, prose
|
|
156
|
+
report = axi_toolkit.toon_spec.run(axi_toolkit.toon.encode)
|
|
157
|
+
assert not report.failures, report.failures[:3]
|
|
158
|
+
assert not axi_toolkit.toon_spec.digest_mismatches()
|
|
159
|
+
print('axi-toolkit', axi_toolkit.__version__, 'TOON', report.score)
|
|
160
|
+
"
|
|
161
|
+
|
|
162
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
.venv/
|
|
7
|
+
venv/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.coverage
|
|
11
|
+
|
|
12
|
+
# Agent-local settings, which routinely contain absolute home paths. Keeping
|
|
13
|
+
# this out of .git/info/exclude matters: that file is local to one clone and
|
|
14
|
+
# never travels with the repository.
|
|
15
|
+
.claude/
|
|
16
|
+
.codex/
|