Add CI + complete packaging, and fix two latent ledger bugs it surfaced - #1
Merged
Conversation
Bring the packaging metadata and the wads CI SSOT block up to the ecosystem standard. The repo already had a pyproject (no setup.cfg/setup.py to convert), so this is a completion pass rather than a conversion: - license: replace the deprecated `[project.license] text = ...` table with the PEP 639 SPDX string `license = "Apache-2.0"` + `license-files = ["LICENSE"]`. Verified: the built wheel now carries `License-Expression: Apache-2.0` and ships `dist-info/licenses/LICENSE`. - classifiers: added (development status, audience, OS, Python versions, topics). - testpaths: `["tests"]` -> `["enlace_metering", "tests"]`. wads CI runs `pytest --doctest-modules` with NO path argument, so collection is driven entirely by testpaths; with only `tests` the package's own doctests would run nowhere while CI still reported green. - `[tool.wads.ci]`: explicit `project_name` (it is the `ruff check` target and the `--cov` target), plus testing / build / publish / env / quality / docs sections so nothing falls through to a moving default. - `[tool.wads.ci.publish].enabled = false`: this name has never been published to PyPI. First publication of a new name is a deliberate act, not a side effect of merging a modernization PR. `[tool.wads.ci.install].extras = "dev"` was already correct and is kept: the dev extra carries fastmcp, which `--doctest-modules` needs to import enlace_metering.middleware in CI's clean environment. Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475
Nothing tested or built this repo. Adds `.github/workflows/ci.yml` as the 5-line stub calling the reusable workflow `i2mint/wads/.github/workflows/uv-ci.yml@master`, generated from the wads template with the secrets pass-through block rendered from `[tool.wads.ci.env]` (PYPI_PASSWORD only — no test secrets are needed here). All configuration lives in `[tool.wads.ci.*]` in pyproject.toml. Publishing is disabled there, so a merge to the default branch runs validation and docs only. Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475
The pyproject declared Apache-2.0 but no LICENSE file existed, so the built distributions carried no licence text. Adds the standard Apache-2.0 text used across the ecosystem, plus the wads-template .editorconfig. Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475
Pure formatting, no behaviour change — brings the tree in line with what the wads CI's `ruff format` step produces (whitespace/line-wrapping only; the README's hand-aligned example block is deliberately left untouched). Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475
…amp TypeError)
Both surfaced while reviewing the package during the packaging/CI modernization
pass. Both are silent in the existing suite.
1. Traversal via the entry id. The ledger key is
`{principal}/{month}/{id}.json` and the module docstring claimed the key was
traversal-checked — but only `principal` was. The middleware builds the id as
`{ns}-{seq}-{tool}`, and `tool` is the *client-supplied* name from a
`tools/call`, which reaches `on_call_tool` BEFORE the tool is resolved. So an
authorized caller could name a nonexistent tool `../../../etc/pwn` and the
write-ahead row would be written to
`a@b.com/2026-08/<ns>-0-../../../etc/pwn.json` — outside the ledger prefix on
any path-backed store (the documented production case is a dol file store).
Fix, in two layers:
- `ledger.safe_key_component` (extracted from `safe_principal`) now validates
the `month` and `id` components too, so any producer writing to the ledger
is checked, not just the middleware;
- the middleware slugifies the tool component (`[^A-Za-z0-9._-]` -> `_`,
capped at 64 chars) so a hostile *or* merely unknown name is still recorded
rather than refused. The raw name stays in the entry's `tool` field, so the
audit trail is unchanged.
2. Naive timestamps raise instead of being tolerated. `_entry_ts` parsed `ts`
with `datetime.fromisoformat` and returned it as-is; a value written without
a UTC offset yields a naive datetime, and `ts >= since` then raises
`TypeError: can't compare offset-naive and offset-aware datetimes`. That
propagates out of `spend_since`, through the gate policy, into the
fail-closed middleware — so ONE badly-stamped row would deny every subsequent
gated call for that principal. The module already treats an unparseable `ts`
as "include" (conservative), so raising here was inconsistent as well as
fragile. `_as_utc` now reads a naive datetime as UTC, applied to both the
entry timestamp and the caller-supplied `since`.
Regression tests added for both (traversal in `id`/`month`, the middleware
slug, naive `ts`, naive `since`).
Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rollout pass over this repo.
pyproject.toml(SPDX license, classifiers, wads CI config, testpaths); add the Apache-2.0LICENSEand.editorconfig.TypeErrorwhen compared against an aware one.ruff formatto the Python sources.https://claude.ai/code/session_01Uz2YYxAfa1DX8UcCDmeKC5