diff --git a/AGENTS.md b/AGENTS.md index 3830975..4123a9e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -133,12 +133,15 @@ See `docs/standards/documentation.md` for the deployment contract and required c ## Compatibility and Release Qualification -Before 2026-10-01, each library must pass installation, import, tests, type checking, and package -builds on Python 3.12 through 3.14 on Linux, macOS, and Windows. Python 3.15 prereleases must pass -core installation and non-hardware-dependent tests on all three systems. +Each library must pass installation, import, tests, type checking, and package builds on Python 3.12 +through 3.14 on Linux, macOS, and Windows. Python 3.15 prereleases must pass core installation and +non-hardware-dependent tests on all three systems unless a dependency-blocked library has a +configured prerelease wait. A waiting library keeps a truthful upper bound and runs a visible +non-blocking canary at least monthly. An ecosystem failure blocks the affected release unless an approved exception records its scope, -rationale, approver, and expiration date. Hardware-specific features use separate matrices. +rationale, approver, and an expiration date or objective review triggers. Hardware-specific features +use separate matrices. See `docs/standards/compatibility.md` and `docs/standards/release-qualification.md`. diff --git a/README.md b/README.md index 0fd4c9f..7ee0512 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,9 @@ This repository does not contain library source code and is not a runtime depend ML4T supports every stable CPython release from Python 3.12 through the latest stable version on Linux, macOS, and Windows. Each supported combination must pass installation, import, tests, type checking, and package build checks before release. The next CPython prerelease becomes a blocking -compatibility target after beta 1 without being advertised as stable. +compatibility target after beta 1 unless a library's required dependencies cannot yet pass it. A +dependency-blocked library keeps a truthful Python upper bound and runs a visible non-blocking +canary until its documented review triggers occur. See [Compatibility](docs/standards/compatibility.md) and [Release qualification](docs/standards/release-qualification.md) for the normative criteria. diff --git a/config/libraries.toml b/config/libraries.toml index 99b5ab0..0960997 100644 --- a/config/libraries.toml +++ b/config/libraries.toml @@ -37,33 +37,34 @@ homepage_url = "https://www.ml4trading.io/" id = "python-315-polars" criterion = "python-3.15-prerelease" libraries = ["data", "engineer"] -affected_versions = ">=0.1.2,<=0.1.8" -rationale = "Polars 1.43.2 fails required expression and datetime operations on CPython 3.15.0rc1." +affected_versions = ">=0.1.2,<0.2" +rationale = "Polars 1.44.2 fails required series construction on CPython 3.15.0rc1." evidence = [ + "https://github.com/ml4t/data/issues/37", "https://github.com/pola-rs/polars/issues/28347", "https://github.com/pola-rs/polars/pull/28750", + "https://pypi.org/project/polars/1.44.2/#files", ] -user_impact = "These distributions cannot be installed on Python 3.15 while the exception is active." +user_impact = "These distributions cannot be installed on Python 3.15 while the prerelease wait applies." mitigation = "Use Python 3.12 through 3.14 until a compatible Polars release is qualified." approver = "Stefan Jansen" -expires_on = 2026-09-30 +review_triggers = ["python-3.15-final", "polars-release-after-1.44.2"] issue = "https://github.com/ml4t/ecosystem/issues/2" [[exceptions]] id = "python-315-scipy" criterion = "python-3.15-prerelease" libraries = ["diagnostic"] -affected_versions = ">=0.1.2,<0.1.7" -rationale = "The published dependency set lacks required CPython 3.15 wheels for PyArrow 25.0.1 and pydantic-core 2.46.5." +affected_versions = ">=0.1.2,<0.2" +rationale = "PyArrow 25.0.1 does not publish CPython 3.15 wheels, so the complete Diagnostic dependency set cannot install on CPython 3.15." evidence = [ "https://github.com/ml4t/diagnostic/issues/45", "https://pypi.org/project/pyarrow/25.0.1/#files", - "https://pypi.org/project/pydantic-core/2.46.5/#files", ] -user_impact = "The distribution cannot be installed on Python 3.15 while the exception is active." +user_impact = "The distribution cannot be installed on Python 3.15 while the prerelease wait applies." mitigation = "Use Python 3.12 through 3.14 until the complete dependency set is qualified on Python 3.15." approver = "Stefan Jansen" -expires_on = 2026-09-30 +review_triggers = ["python-3.15-final", "pyarrow-26"] issue = "https://github.com/ml4t/ecosystem/issues/2" [[libraries]] diff --git a/decisions/0010-current-python-315-dependency-exceptions.md b/decisions/0010-current-python-315-dependency-exceptions.md index f1ca87d..165ad5d 100644 --- a/decisions/0010-current-python-315-dependency-exceptions.md +++ b/decisions/0010-current-python-315-dependency-exceptions.md @@ -1,6 +1,6 @@ # Decision 0010: Current Python 3.15 dependency exceptions -**Status**: Accepted +**Status**: Superseded by Decision 0013 **Date**: 2026-09-19 **Approver**: Stefan Jansen **Expires**: 2026-09-30 diff --git a/decisions/0012-include-data-vcs-development-versions.md b/decisions/0012-include-data-vcs-development-versions.md index 80ef12e..3dc6b35 100644 --- a/decisions/0012-include-data-vcs-development-versions.md +++ b/decisions/0012-include-data-vcs-development-versions.md @@ -1,6 +1,6 @@ # Decision 0012: Include Data VCS development versions in the active exception -**Status**: Accepted +**Status**: Superseded by Decision 0013 **Date**: 2026-09-19 **Approver**: Stefan Jansen **Expires**: 2026-09-30 diff --git a/decisions/0013-python-315-waiting-policy.md b/decisions/0013-python-315-waiting-policy.md new file mode 100644 index 0000000..5f60f08 --- /dev/null +++ b/decisions/0013-python-315-waiting-policy.md @@ -0,0 +1,53 @@ +# Decision 0013: Python 3.15 waiting policy + +**Status**: Accepted +**Date**: 2026-09-20 +**Approver**: Stefan Jansen + +## Context + +[Python 3.15.0 final is scheduled for 2026-10-01](https://peps.python.org/pep-0790/). Data and +Engineer depend on [Polars 1.44.2](https://pypi.org/project/polars/1.44.2/), which still fails +required Data operations on CPython 3.15.0rc1. Diagnostic depends on +[PyArrow 25.0.1](https://pypi.org/project/pyarrow/25.0.1/#files), which publishes no CPython 3.15 +wheels. [Pydantic-core 2.49.0](https://pypi.org/project/pydantic-core/2.49.0/#files) now publishes +CPython 3.15 wheels and is no longer a current Diagnostic blocker. + +The existing exceptions expire on 2026-09-30. That date does not measure whether the libraries can +install and pass their full suites. Requiring a new exception for each patch release would block +Python 3.12 through 3.14 users without improving Python 3.15 compatibility. + +## Decision + +Python 3.12 through 3.14 remain the blocking stable matrix. A library whose complete dependency set +cannot pass Python 3.15 may retain a truthful `<3.15` upper bound for its 0.1 release line. The +library must run a visible non-blocking Python 3.15 canary at least monthly and keep an owning issue +with current evidence. + +Replace the 2026-09-30 expirations with these review triggers: + +- Data and Engineer: Python 3.15 final or a Polars release after 1.44.2. +- Diagnostic: Python 3.15 final or PyArrow 26. + +A trigger requires the owner to rerun installation, import, and the non-hardware-dependent suite on +Linux, macOS, and Windows. The result updates the owning issue and either removes the upper bound or +records why the wait continues. A trigger does not make Python 3.15 compatibility a condition for a +release that otherwise passes the stable matrix. + +The existing `prerelease_exception` configuration and workflow input names remain to avoid breaking +the seven library workflows. Their trigger-based records now represent prerelease waits. Date-bounded +exceptions remain supported for other qualification criteria. + +This decision supersedes the September 30 expiry and affected-version ranges in Decisions 0010 and +0012. Those decisions remain as the history of the evidence and earlier release scopes. + +## Consequences + +Data, Engineer, and Diagnostic can publish qualified 0.1 patch releases for Python 3.12 through +3.14 while their Python 3.15 dependencies remain unavailable. Their package metadata continues to +reject Python 3.15 rather than claiming support that has not passed qualification. + +Ecosystem issue [#2](https://github.com/ml4t/ecosystem/issues/2), Data issue +[#37](https://github.com/ml4t/data/issues/37), and Diagnostic issue +[#45](https://github.com/ml4t/diagnostic/issues/45) remain open until the affected libraries pass the +complete Python 3.15 matrix. diff --git a/decisions/0014-agent-guide-conformance.md b/decisions/0014-agent-guide-conformance.md new file mode 100644 index 0000000..bce179b --- /dev/null +++ b/decisions/0014-agent-guide-conformance.md @@ -0,0 +1,32 @@ +# Decision 0014: Agent guide conformance + +**Status**: Accepted +**Date**: 2026-09-20 + +## Context + +Decisions 0005 through 0009 established public root orientation, private sidecar plumbing, the ban +on public workspace state, the public ecosystem boundary, and optional development sidecars. The +release standard still contained one contradictory sidecar requirement, and the executable audit +checked only a root guide's word count and a few substrings. It could not discover stale nested +guides or volatile inventories. + +## Decision + +Adopt `docs/standards/agent-guides.md` as the public reference. Root guides describe the library, +source layout, public surface, change constraints, and current quality commands in no more than 200 +lines. Nested guides are optional, directory-specific, and no more than 120 lines. Public guides do +not contain private state, agent-tool setup, volatile counts, or current work. + +The workspace audit discovers tracked root and nested guides. It does not require empty discovery +files. Structural checks supplement, but do not replace, a library-level semantic review against +the package, documentation, and workflows. + +Public repositories do not track `CLAUDE.md`. Private sidecars keep `AGENTS.md` as the source of +truth and a one-line `CLAUDE.md` import for interoperability. + +## Consequences + +Changes to package boundaries, public imports, repository layout, quality commands, release rules, +or subsystem constraints trigger a guide review. Ordinary implementation changes do not. Each +library owns remediation for its public files through its issue and pull-request workflow. diff --git a/docs/standards/agent-guides.md b/docs/standards/agent-guides.md new file mode 100644 index 0000000..1678ed0 --- /dev/null +++ b/docs/standards/agent-guides.md @@ -0,0 +1,70 @@ +# Agent guides + +This page is the reference standard for public `AGENTS.md` files in the seven stable library +repositories. Public guides help an external contributor understand a source checkout. Private +sidecars hold agent configuration, work management, and persistent local state. + +## Root guide + +Every public library tracks one root `AGENTS.md`, no longer than 200 lines. It must identify: + +- the library's responsibility and public import package; +- the source tree, tests, documentation, examples, scripts, and major subsystems that exist; +- supported public imports or user workflows; +- compatibility and safety rules that affect changes; +- narrower nested guides, when they exist; and +- the repository's current `ruff`, format, `ty`, `pytest`, documentation, build, and pre-commit + commands as applicable. + +The guide points to authoritative code or documentation instead of copying API reference or +tutorial content. It does not include current work, issue queues, test or file counts, release +status, machine paths, credentials, or private sidecar content. + +## Nested guides + +A nested `AGENTS.md` is optional. Add one only when its directory has a distinct responsibility, +public surface, safety rule, compatibility constraint, or verification lane that the root guide +cannot state concisely. A nested guide: + +- names its directory or subsystem in the title; +- states the directory's responsibility and the rules that differ there; +- inherits repository-wide commands and rules instead of repeating them; +- stays below 120 lines and contains no volatile counts or status snapshots; and +- is removed when the directory no longer needs narrower guidance. + +No repository creates empty or placeholder guides for discovery. The conformance audit discovers +tracked files, so an absent nested guide means the root guide applies. + +## Claude interoperability + +Public repositories do not track `CLAUDE.md`. A one-line `@AGENTS.md` include configures one tool but +does not help a contributor understand the library. Public repositories also ignore `.claude/`, +`.codex/`, and `.workspace/` so local configuration cannot be published by a broad Git add. + +Each configured private development sidecar tracks `AGENTS.md` as its canonical instructions and a +`CLAUDE.md` containing exactly `@AGENTS.md`. Sidecar instructions may describe local work, issue +drafts, transitions, memory, and cross-repository coordination because the sidecar is private. + +## Maintenance triggers + +Review the root guide when a change alters the package responsibility, supported public import, +repository layout, quality commands, release process, compatibility policy, or safety boundary. +Review an affected nested guide when a subsystem moves, gains or loses a public surface, changes a +local constraint, or changes its focused verification lane. Deleting a subsystem also deletes its +guide. + +An ordinary implementation change does not require prose churn when these facts stay the same. +Reviewers compare the guide with the changed source and commands rather than updating dates or +counts. + +## Conformance + +`uv run ml4t-ecosystem audit-workspaces` discovers tracked guides in each local release checkout. It +checks root structure, public import and source-tree references, required quality commands, line +limits, private-state references, tool-mechanism prose, volatile counts, nested titles, and exact +duplicate nested content. The GitHub and PyPI collector applies the root checks to each default +branch. + +Automated checks cannot establish that a guide names the correct public surface. Each library audit +therefore compares the guide with its installed package, documentation, workflow commands, and +tracked nested files. A deviation uses an issue and pull request in the owning library. diff --git a/docs/standards/compatibility.md b/docs/standards/compatibility.md index 13d213c..c092a10 100644 --- a/docs/standards/compatibility.md +++ b/docs/standards/compatibility.md @@ -12,21 +12,29 @@ Each supported Python and operating-system combination must pass: 5. `ty` type checking; and 6. source distribution and wheel builds with metadata validation. -The next CPython prerelease enters CI after beta 1. Before final release, core installation and the -non-hardware-dependent tests must pass on Linux, macOS, and Windows. Prerelease support is tested but -not advertised as stable. +The next CPython prerelease enters CI after beta 1. Libraries whose complete dependency sets install +on the prerelease run blocking core installation and non-hardware-dependent tests on Linux, macOS, +and Windows. Prerelease support is tested but not advertised as stable. -When that CPython release becomes final, it joins the supported matrix and failures block releases. -Dropping an older Python version requires an ecosystem decision and a documented deprecation period. +A dependency-blocked library keeps a truthful `Requires-Python` upper bound and runs a visible +non-blocking canary at least monthly. Its configured prerelease wait records the affected release +line, evidence, user impact, mitigation, owner issue, and objective review triggers. A trigger starts +a new review; it does not claim compatibility or block an otherwise qualified stable release. -For releases before 2026-10-01, the stable matrix is Python 3.12 through 3.14 and the blocking -prerelease target is Python 3.15. A package metadata upper bound that prevents installation on the -prerelease target fails this standard. +When that CPython release becomes final, it joins the supported matrix for libraries without an +approved wait. A waiting library reviews fresh qualification evidence when final ships and records +whether the wait can end. Dropping an older Python version requires an ecosystem decision and a +documented deprecation period. + +For releases before Python 3.15 is qualified across a library's complete dependency set, the stable +matrix is Python 3.12 through 3.14. Python 3.15 remains a blocking target for libraries without a +configured prerelease wait. A library may temporarily retain an upper bound only through a machine-readable exception in -`config/libraries.toml`. The exception replaces the prerelease jobs with an explicit validation job; -it does not remove the stable operating-system matrix. The validation fails when the exception is -missing, applied to another repository, outside its version scope, or expired. +`config/libraries.toml`. The `prerelease_exception` name remains in the configuration and reusable +workflow for compatibility. For a trigger-based wait, the validation fails when the record is +missing, applied to another repository, outside its release-line scope, or lacks review triggers. +The wait never removes the stable operating-system matrix. Hardware-specific capabilities such as CUDA require a separate matrix. Passing the general matrix does not establish hardware support. diff --git a/docs/standards/index.md b/docs/standards/index.md index 80135f5..e325bc6 100644 --- a/docs/standards/index.md +++ b/docs/standards/index.md @@ -9,6 +9,8 @@ requirements but may not weaken a shared requirement locally. - [Issues and pull requests](issues-and-pull-requests.md) - [API compatibility](api-compatibility.md) - [Dependencies and security](dependencies-and-security.md) +- [Agent guides](agent-guides.md) -Exceptions must identify their scope, rationale, approver, and expiration date. An expired exception -does not qualify a release. +Exceptions must identify their scope, rationale, approver, and an expiration date or objective +review triggers. An expired exception does not qualify a release. A prerelease wait remains valid +until its owning review establishes that the complete dependency set passes the target matrix. diff --git a/docs/standards/release-qualification.md b/docs/standards/release-qualification.md index 11d4519..a4ec420 100644 --- a/docs/standards/release-qualification.md +++ b/docs/standards/release-qualification.md @@ -80,23 +80,10 @@ documentation. No new example or primary documentation may recommend a deprecate ## Agent orientation -Every public release repository has a root `AGENTS.md` for an external agent trying to understand -the library from a source checkout. It states the library's responsibility, identifies the source -tree and major subsystems, names supported public imports or workflows, points to deeper guides when -they exist, and gives the authoritative quality commands. The workspace audit checks for substantive -sections, the configured import package, source-tree navigation, and an executable import or quality -command. A placeholder file, tool-mechanism explanation, or import of private workspace state fails. - -The public root file stays below 200 lines and contains no internal work management, volatile file or -test counts, status snapshots, detailed API documentation, or tutorials. Nested `AGENTS.md` files are -justified when they provide subsystem orientation or rules that differ within their directory and do -not repeat the root. Public repositories do not track `CLAUDE.md`, `.claude/`, `.codex/`, or -`.workspace/`; those are local agent plumbing rather than library orientation. - -An instruction audit reports obsolete, duplicated, missing, or misplaced content and presents the -exact proposed edits. Editing an `AGENTS.md` or `CLAUDE.md` requires the user's explicit approval of -that reported scope. Approval for a library's instruction files does not authorize user-level or -other repositories' instruction changes. +Every public release repository follows the root and nested `AGENTS.md` requirements in +[Agent guides](agent-guides.md). The workspace audit discovers tracked guides and reports structural +or placement failures. Each library's qualification also compares guide content with its current +package, documentation, and workflows. ## Development sidecars @@ -129,14 +116,15 @@ other library development sandboxes. The library's default branch must pass the current ecosystem qualification before a tag can publish. A local library check cannot substitute for a failed shared check. -An exception is valid only when it records: +A release exception or prerelease wait is valid only when it records: - the exact criterion and affected library versions; - evidence explaining why the exception is necessary; - user impact and mitigation; - the approving maintainer; -- an expiration date; and +- an expiration date or objective review triggers; and - the issue that removes the exception. -Expired or incomplete exceptions fail qualification. Validation occurs before publication, so a -rejected release does not create a tag, artifact, GitHub release, or PyPI upload. +Expired or incomplete records fail qualification. A review trigger requires fresh evidence but does +not by itself fail a stable release. Validation occurs before publication, so a rejected release +does not create a tag, artifact, GitHub release, or PyPI upload. diff --git a/mkdocs.yml b/mkdocs.yml index cbf34d9..77bc8b0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,6 +24,7 @@ nav: - Issues and pull requests: standards/issues-and-pull-requests.md - API compatibility: standards/api-compatibility.md - Dependencies and security: standards/dependencies-and-security.md + - Agent guides: standards/agent-guides.md - Decisions: decisions.md - Reviews: reviews.md diff --git a/reviews/2026-09-20-backtest-closure.md b/reviews/2026-09-20-backtest-closure.md new file mode 100644 index 0000000..2a2eec7 --- /dev/null +++ b/reviews/2026-09-20-backtest-closure.md @@ -0,0 +1,36 @@ +# Backtest closure review + +Date: 2026-09-20 + +Scope: Backtest source, public and private agent guides, local sidecar issues, public issues and +pull requests, supported-version qualification, documentation, and packaging. + +## Result + +Backtest is clean on `main` at `a32a206`. Pull request +[#101](https://github.com/ml4t/backtest/pull/101) replaced stale package and file counts in nine +public agent guides with stable ownership boundaries, local constraints, and verification commands. +The public issue and pull-request queues are empty. + +The private sidecar is clean on `main` at `21dbaad`. Its local issue index now records every draft's +current disposition. Private issues #14 and #16 were closed after verifying the associated book +change and the published release history through 0.1.7. + +The timezone-alignment fix in current source changed installed behavior after 0.1.7. Release +[v0.1.8](https://github.com/ml4t/backtest/releases/tag/v0.1.8) therefore publishes exact `main` +commit `a32a20620b1a34cc5c8a657f358b4b145b1efa69` rather than leaving that fix unreleased. + +## Verification + +- Ruff lint and formatting, ty, actionlint, strict MkDocs, package builds, and every pre-commit hook + pass locally. +- The full local suite passes with 2,162 tests and 14 documented skips. +- Pull request #101 passed supported Python 3.12 through 3.14 qualification on Linux, macOS, and + Windows, plus its repository CI, before merge. +- The ecosystem agent-guide audit passes for the root guide and all nested guides. +- The built wheel installs and imports successfully in an isolated environment. +- Release run + [35553684066](https://github.com/ml4t/backtest/actions/runs/35553684066) passed the complete + candidate, licensed-comparison, documentation, publication, and post-publication gates. PyPI, + the GitHub release, and the deployed documentation all report version 0.1.8 and the exact source + commit. diff --git a/reviews/2026-09-20-data-closure.md b/reviews/2026-09-20-data-closure.md new file mode 100644 index 0000000..11c7d8c --- /dev/null +++ b/reviews/2026-09-20-data-closure.md @@ -0,0 +1,44 @@ +# Data closure review + +Date: 2026-09-20 + +Scope: Data source, public and private agent guides, local sidecar issues, public issues and pull +requests, supported-version qualification, documentation, packaging, and the Python 3.15 wait. + +## Result + +Data is clean on `main` at `9b28c5c`. Pull request +[#59](https://github.com/ml4t/data/pull/59) added the monthly Python 3.15 dependency canary and +closed the public agent-guide review. No agent-guide source edit was necessary: all five tracked +guides describe current package boundaries and commands, pass the executable ecosystem checks, and +contain no public private-state or tool-specific files. + +The private sidecar is clean on `main` at `9965979`. Its two records marked open were stale: + +| Record | Current evidence | Disposition | +|---|---|---| +| Strict documentation build failures | `uv run mkdocs build --strict` passes on current `main`. | Resolved | +| Hard-coded storage roots | Shared resolvers, provider layout tests, and the package-source guard pass. | Resolved | + +The dated website presentation review is superseded. Current website source uses responsive +workflow layout, renders library feature metadata, avoids the earlier production-maturity claim, +and lists Data provider counts consistently. The other sidecar defect records were already marked +resolved, fixed, or shipped. + +## Verification + +- Ruff lint and formatting, ty, actionlint, strict MkDocs, package builds, and every pre-commit hook + pass locally. +- Both full local test lanes pass: 3,649 tests on the standard lane and 3,649 tests with + `ResourceWarning` treated as an error. +- Pull request #59 passed supported Python 3.12 through 3.14 on Linux, macOS, and Windows, plus + security, dependency, documentation, and exception-validation checks. +- The built wheel and source distribution completed successfully. +- Data has no open pull request. Its only open issue is + [#37](https://github.com/ml4t/data/issues/37), the accepted external-dependency wait for Python + 3.15. It is not actionable library work until a recorded review trigger fires. + +The Python 3.15 canary runs automatically each month on Ubuntu and is not a pull-request or release +gate. A fresh CPython 3.15.0rc1 probe reproduces the current dependency limits. Python 3.12 through +3.14 remain the supported release range until Python 3.15 final or a stable Polars release after +1.44.2 triggers full three-platform qualification. diff --git a/reviews/2026-09-20-data-recovery-reconciliation.md b/reviews/2026-09-20-data-recovery-reconciliation.md new file mode 100644 index 0000000..60dc941 --- /dev/null +++ b/reviews/2026-09-20-data-recovery-reconciliation.md @@ -0,0 +1,44 @@ +# Data recovery reconciliation + +Date: 2026-09-20 + +Scope: dormant Data standardization worktree, local standardization branches, merged Data pull +request [#55](https://github.com/ml4t/data/pull/55), and the private recovery set captured on +2026-09-19. + +## Result + +The dormant state contains no requirement that should replace current Data `main`. Pull request #55 +implements the accepted issue #50 scope and adds release, security, documentation, and agent-guide +changes that the local branches lack. The remaining worktree differences are either byte-identical +to `main` or older content that current `main` intentionally replaced. + +## Recovery verification + +The recovery bundle passes `git bundle verify` and contains the complete +`codex/ecosystem-standardization` history at `a907fbf`. All four recorded SHA-256 checksums match the +live worktree: + +- branch bundle: `bf91f6c6154f2d355aef2350ffd222f51e6a997e4e6ae147601be0077e1412c3`; +- staged patch: `2784a3356395816492dc01fc8841af417b0ffdac679c6935acb71cc79a9e8504`; +- unstaged patch: `b9ef69c080f23c07359044984a7d37824fd8c6ece96473eeb34989c8c2dd10a8`; +- untracked migration file: `1223a375b348dd915744936823f118f4bc106972b531ccf3573bef2e89c4b44e`. + +A clean temporary clone reconstructed from the bundle, staged patch, unstaged patch, and untracked +file produced the same staged diff, unstaged diff, untracked-file digest, and short status as the +live worktree. + +## Requirement disposition + +| Recovered work | Disposition | +|---|---| +| Canonical package identity, `QLDM_DATA_ROOT`, and `QldmError` compatibility | Present on current `main` through pull request #55, with stronger discovery and precedence tests. | +| `ML4T_DATA_DIR` and `QLDM_LOG_LEVEL` compatibility proposed by `a907fbf` | Not accepted. Data issue #50 explicitly limited compatibility to `QLDM_DATA_ROOT` and `QldmError`; pull request #55 removed the other names from the public contract. | +| Five `codex/issue-50-standardization` commits | Superseded by pull request #55. Its reviewed head contains the same standardization tree plus newer workflow, security, documentation, agent-guide, and provider fixes. | +| Twenty-six staged documentation and example deletions | All twenty-six paths are absent from current `main`. | +| Sixty-six unstaged modifications | Fifty-six are byte-identical to current `main`. | +| Ten non-identical unstaged paths | Rejected as older content: a weaker alias-removal promise, stale getting-started material, unsupported production wording, incomplete metadata punctuation, weaker empty-environment handling, provider typos, removal of the Yahoo rate-limit fix, and removal of compatibility tests. | +| Untracked migration guide | Superseded by the current guide, which adds the 0.x compatibility guarantee and otherwise contains the same migration. | + +The two local branch tips and the worktree registration are obsolete. The verified recovery set +remains the recoverable source for the discarded dirty state. diff --git a/reviews/2026-09-20-diagnostic-closure.md b/reviews/2026-09-20-diagnostic-closure.md new file mode 100644 index 0000000..3a401ab --- /dev/null +++ b/reviews/2026-09-20-diagnostic-closure.md @@ -0,0 +1,28 @@ +# Diagnostic closure review + +Date: 2026-09-20 + +Scope: Diagnostic source, public and private agent guides, local sidecar issues, public issues and +pull requests, supported-version qualification, documentation, packaging, and the Python 3.15 wait. + +## Result + +Diagnostic is clean on `main` at `320b1f5`. Pull request +[#77](https://github.com/ml4t/diagnostic/pull/77) completed the public agent-guide review by adding +explicit semantic maintenance triggers. The public pull-request queue is empty. + +The private sidecar is clean on `main` at `71fdc2a`. Its 17 local records now match current source +and public history, including removal of stale claims that pull requests #62 and #63 remained open. + +## Verification + +- Ruff lint and formatting, ty, actionlint, strict MkDocs, package builds, and every pre-commit hook + pass locally. +- The full local suite passes with 5,378 tests and 61 documented skips. +- Pull request #77 passed supported Python 3.12 through 3.14 qualification on Linux, macOS, and + Windows, plus its full repository CI, before merge. +- The ecosystem agent-guide audit passes for the root guide and all nested guides. +- Diagnostic has no open pull request. Its only open issue is + [#45](https://github.com/ml4t/diagnostic/issues/45), the accepted external-dependency wait for + Python 3.15. PyArrow 25.0.1 has no CPython 3.15 wheels; the recorded triggers are Python 3.15 final + or PyArrow 26. diff --git a/reviews/2026-09-20-engineer-closure.md b/reviews/2026-09-20-engineer-closure.md new file mode 100644 index 0000000..58a3148 --- /dev/null +++ b/reviews/2026-09-20-engineer-closure.md @@ -0,0 +1,34 @@ +# Engineer closure review + +Date: 2026-09-20 + +Scope: Engineer source, public and private agent guides, local sidecar issues, public issues and +pull requests, supported-version qualification, documentation, packaging, and the Python 3.15 wait. + +## Result + +Engineer is clean on `main` at `a9f7f80`. Pull request +[#57](https://github.com/ml4t/engineer/pull/57) added the monthly Python 3.15 dependency canary, +hardened workflow checkouts and permissions, and completed the public agent-guide audit. The public +pull-request queue is empty. + +The private sidecar is clean on `main` at `a6739bb`. Its dated review and issue records now match +current source and public history. The public bar-sampler hierarchy and the `Original` compatibility +aliases remain available and pass their behavioral tests. + +Published `ml4t-engineer==0.1.4` remains the current installable release. Changes after its release +are confined to workflows, agent instructions, tests, dependency locks, and removal of a source +comment; installed behavior and published package metadata did not change, so this audit does not +create an empty patch release. + +## Verification + +- Ruff lint and formatting, ty, actionlint, strict MkDocs, package builds, and every pre-commit hook + pass locally. +- The full local suite passes with 3,925 tests. +- Pull request #57 passed supported Python 3.12 through 3.14 qualification on Linux, macOS, and + Windows, plus security, documentation, package, and ecosystem checks, before merge. +- The built wheel installs and imports successfully in an isolated environment. +- Engineer has no open pull request. Its only open issue is + [#56](https://github.com/ml4t/engineer/issues/56), the accepted external-dependency wait for + Python 3.15. Its triggers are Python 3.15 final or a stable Polars release after 1.44.2. diff --git a/reviews/2026-09-20-live-qualification.md b/reviews/2026-09-20-live-qualification.md new file mode 100644 index 0000000..154a31b --- /dev/null +++ b/reviews/2026-09-20-live-qualification.md @@ -0,0 +1,45 @@ +# Live qualification review + +Date: 2026-09-20 + +Scope: Live source, public and private agent guides, local sidecar issues, public issues and pull +requests, provider evidence, supported-version qualification, documentation, and release 0.1.3. + +## Completed work + +Live is clean on `main` at `43da4ca`. Pull request +[#101](https://github.com/ml4t/live/pull/101) completed the public agent-guide review, and the +private sidecar is clean on `main` at `3185527`. Its local issue index records current dispositions. + +Ruff, ty, actionlint, strict MkDocs, package builds, pre-commit, and the full local suite pass. The +suite has 1,051 passing tests and 29 documented skips. Pull request #101 also passed the complete +repository CI and ecosystem qualification before merge. + +Current retained provider evidence passes for Alpaca in run +[35473026566](https://github.com/ml4t/live/actions/runs/35473026566) and OKX in run +[35473028351](https://github.com/ml4t/live/actions/runs/35473028351). Their provider contracts are +unchanged at the current candidate. + +## Remaining release gate + +Release run [35553318363](https://github.com/ml4t/live/actions/runs/35553318363) passed preflight, +the complete stable matrix, and exact installed-artifact qualification, then stopped at the +provider-evidence gate. + +No retained IB artifact satisfies the current complete contract. The September IB runs predate the +required six-hour soak artifact. The older complete bundle targets a revision before changes to the +IB adapter, paper qualification, persistence, safety, and supported-runtime contract. Reusing it +would claim coverage it did not establish. + +The remaining sequence is exact and provider-specific: + +1. Start an authenticated IB Gateway or TWS paper session with API access. +2. Dispatch `paper.yml` for candidate `43da4caf90a12240c34143b3ea336b4d96013772`, qualification + run `35553318363`, and `extended-provider=ib`. +3. After the exercise, restart, and six-hour soak succeed, rerun `release.yml` for version 0.1.3 and + the same candidate commit. +4. Require the release workflow to verify PyPI, the GitHub release, and the deployed documentation + before closing Live issue #92 and ecosystem issues #36 and #40. + +The release gate remains fail closed. No other provider needs a fresh extended session unless its +recorded contract changes before publication. diff --git a/reviews/2026-09-20-models-closure.md b/reviews/2026-09-20-models-closure.md new file mode 100644 index 0000000..e8a7678 --- /dev/null +++ b/reviews/2026-09-20-models-closure.md @@ -0,0 +1,32 @@ +# Models closure review + +Date: 2026-09-20 + +Scope: Models source, public and private agent guides, local sidecar issues, public issues and pull +requests, numerical and hardware qualification, documentation, and packaging. + +## Result + +Models is clean on `main` at `786ca1b`. Pull request +[#51](https://github.com/ml4t/models/pull/51) completed the public agent-guide review by adding +explicit semantic maintenance triggers. The public issue and pull-request queues are empty. + +The private sidecar is clean on `main` at `a62f728`. Its four local records have verified current +dispositions: the CAE, IPCA, and SDF findings were fixed in pull request #5, and the timestamp +normalization defect belonged to downstream case-study code, where current source performs the +normalization. The sidecar guide now describes the released library, Python 3.12 through 3.14, and +the current work and issue conventions. + +Published `ml4t-models==0.1.4` remains current because the only source change after its release is +the public agent-guide update. + +## Verification + +- Ruff lint and formatting, ty, actionlint, strict MkDocs, package builds, and every pre-commit hook + pass locally. +- The full local suite passes with 422 tests and its configured coverage checks. +- Pull request #51 passed supported Python 3.12 through 3.14 qualification on Linux, macOS, and + Windows, non-hardware-dependent Python 3.15 qualification, dependency audit, coverage, and the + documented MPS qualification before merge. +- The built wheel installs and imports successfully in an isolated environment. +- The ecosystem agent-guide audit passes for the public guide. diff --git a/reviews/2026-09-20-skills-closure.md b/reviews/2026-09-20-skills-closure.md new file mode 100644 index 0000000..89eeb71 --- /dev/null +++ b/reviews/2026-09-20-skills-closure.md @@ -0,0 +1,30 @@ +# Skills closure review + +Date: 2026-09-20 + +Scope: Public skill catalogue, generated offerings, library API accuracy, executable examples, +public issues and pull requests, and repository hygiene. + +## Result + +Skills is clean on `main` at `f0ea019`. Pull request +[#5](https://github.com/ml4t/skills/pull/5) updated `build-bars` to the current +`ml4t.engineer.bars` API, regenerated the catalogue offerings, and made the generator fail when a +scraped cohort lacks reviewed descriptive copy. Pull request #3 was superseded and closed; its +generated README changes are included in #5. + +Issue #4 proposed a dependency on a third-party forecasting service. It was closed as not planned +because the public catalogue is a self-contained, vendor-neutral teaching layer. The repository now +has no open issue or pull request, and its merged or superseded topic branches have been deleted +locally and remotely. + +## Verification + +- All 61 skills pass the catalogue validator and the generated chapter map and offerings are + current. +- All 82 repository tests pass. The 26 numerical examples also pass with NumPy, SciPy, Polars, and + scikit-learn installed. +- Ruff passes, and API validation passes against the latest published stable-library wheels. +- The documented `DollarBarSampler` example executes against published `ml4t-engineer==0.1.4` and + produces ten exact $1,000 bars. +- Pull request #5 passed its hosted validation, example, and API-accuracy checks before merge. diff --git a/reviews/2026-09-20-specs-closure.md b/reviews/2026-09-20-specs-closure.md new file mode 100644 index 0000000..3bde318 --- /dev/null +++ b/reviews/2026-09-20-specs-closure.md @@ -0,0 +1,26 @@ +# Specs closure review + +Date: 2026-09-20 + +Scope: Specs source, public agent guide, public issues and pull requests, supported-version +qualification, documentation, and packaging. + +## Result + +Specs is clean on `main` at `3e0913f`. Pull request +[#26](https://github.com/ml4t/specs/pull/26) completed the public agent-guide review and added +explicit maintenance triggers for changes to supported Python versions, validation behavior, +commands, package layout, or ecosystem policy. Specs intentionally has no development sidecar; +the release checkout and the ecosystem repository own its work. + +The public queue is empty. No user-visible defect, pending feature, release obligation, or local +issue draft remains to reconcile. + +## Verification + +- Ruff lint and formatting, ty, strict MkDocs, package builds, and every pre-commit hook pass + locally. +- The full local suite passes with 382 tests and the configured coverage threshold. +- Pull request #26 passed its required hosted checks before merge. +- The ecosystem agent-guide audit passes for the root guide and all nested guides. +- The built wheel installs and imports successfully in an isolated environment. diff --git a/scripts/validate_exception.py b/scripts/validate_exception.py index 867eb37..90d974e 100644 --- a/scripts/validate_exception.py +++ b/scripts/validate_exception.py @@ -82,9 +82,14 @@ def main() -> None: package_version=package_version, on_date=datetime.now(UTC).date(), ) + if exception.review_triggers: + scope = "review triggers " + ", ".join(exception.review_triggers) + else: + assert exception.expires_on is not None + scope = f"expiration {exception.expires_on.isoformat()}" print( - f"Approved exception {exception.id} covers {library.key} {package_version} through " - f"{exception.expires_on.isoformat()}: {exception.issue}" + f"Approved exception {exception.id} covers {library.key} {package_version}; " + f"{scope}: {exception.issue}" ) diff --git a/src/ml4t_ecosystem/agent_orientation.py b/src/ml4t_ecosystem/agent_orientation.py index e18a01e..e53baa7 100644 --- a/src/ml4t_ecosystem/agent_orientation.py +++ b/src/ml4t_ecosystem/agent_orientation.py @@ -3,16 +3,52 @@ from __future__ import annotations import re +import subprocess +from pathlib import Path MINIMUM_ORIENTATION_WORDS = 75 +MINIMUM_NESTED_WORDS = 30 +MAXIMUM_ROOT_LINES = 200 +MAXIMUM_NESTED_LINES = 120 + +PRIVATE_STATE = re.compile( + r"(?:@?\.(?:workspace|claude|codex)(?:/|\b)|(?:^|\s)(?:/home/|~/)|@AGENTS\.md)", + flags=re.MULTILINE, +) +TOOL_MECHANISM = re.compile(r"\b(?:Claude|Codex)\b", flags=re.IGNORECASE) +VOLATILE_COUNT = re.compile( + r"\b\d[\d,]*(?:\s+|-)(?:lines?|tests?|files?|modules?)\b", + flags=re.IGNORECASE, +) + + +def _words(text: str) -> list[str]: + return re.findall(r"[A-Za-z0-9_][A-Za-z0-9_.-]*", text) + + +def _title_tokens(value: str) -> set[str]: + tokens = re.findall(r"[a-z0-9]+", value.lower()) + return {token[:-1] if len(token) > 3 and token.endswith("s") else token for token in tokens} + + +def _shared_issues(text: str) -> list[str]: + issues: list[str] = [] + if PRIVATE_STATE.search(text): + issues.append("references private or machine-local agent state") + if TOOL_MECHANISM.search(text): + issues.append("describes agent-tool mechanics instead of the library") + if VOLATILE_COUNT.search(text): + issues.append("contains a volatile file, line, module, or test count") + return issues def orientation_issues(text: str, import_package: str) -> tuple[str, ...]: """Return concrete reasons a public AGENTS.md does not orient an external agent.""" - issues: list[str] = [] - words = re.findall(r"[A-Za-z0-9_][A-Za-z0-9_.-]*", text) - if len(words) < MINIMUM_ORIENTATION_WORDS: + issues = _shared_issues(text) + if len(_words(text)) < MINIMUM_ORIENTATION_WORDS: issues.append(f"contains fewer than {MINIMUM_ORIENTATION_WORDS} words") + if len(text.splitlines()) > MAXIMUM_ROOT_LINES: + issues.append(f"contains more than {MAXIMUM_ROOT_LINES} lines") if re.search(r"^##\s+\S", text, flags=re.MULTILINE) is None: issues.append("has no navigable sections") if import_package not in text: @@ -21,6 +57,60 @@ def orientation_issues(text: str, import_package: str) -> tuple[str, ...]: issues.append("does not identify the source tree") if f"from {import_package}" not in text and "uv run" not in text: issues.append("contains neither a public import example nor a quality command") - if re.search(r"^\s*@\.workspace/", text, flags=re.MULTILINE): - issues.append("imports private workspace state") + missing_commands = [command for command in ("ruff", "ty", "pytest") if command not in text] + if missing_commands: + issues.append("does not name required quality commands: " + ", ".join(missing_commands)) return tuple(issues) + + +def discover_agent_guides(root: Path) -> tuple[Path, ...]: + """Return tracked agent guides, falling back to filesystem discovery outside Git.""" + completed = subprocess.run( + ["git", "-C", str(root), "ls-files", "-z", "--", "AGENTS.md", ":(glob)**/AGENTS.md"], + check=False, + capture_output=True, + ) + if completed.returncode == 0: + relative_paths = [Path(value.decode()) for value in completed.stdout.split(b"\0") if value] + return tuple(sorted(relative_paths)) + return tuple(sorted(path.relative_to(root) for path in root.rglob("AGENTS.md"))) + + +def nested_orientation_issues(root: Path) -> tuple[str, ...]: + """Return path-qualified issues for tracked nested AGENTS.md files.""" + findings: list[str] = [] + seen_content: dict[str, Path] = {} + root_guide = root / "AGENTS.md" + if root_guide.is_file(): + seen_content[" ".join(_words(root_guide.read_text(encoding="utf-8").lower()))] = Path( + "AGENTS.md" + ) + for relative in discover_agent_guides(root): + if relative == Path("AGENTS.md"): + continue + text = (root / relative).read_text(encoding="utf-8") + issues = _shared_issues(text) + if len(_words(text)) < MINIMUM_NESTED_WORDS: + issues.append(f"contains fewer than {MINIMUM_NESTED_WORDS} words") + if len(text.splitlines()) > MAXIMUM_NESTED_LINES: + issues.append(f"contains more than {MAXIMUM_NESTED_LINES} lines") + if re.search(r"^##\s+\S", text, flags=re.MULTILINE) is None: + issues.append("has no navigable sections") + directory_name = relative.parent.name + heading = next( + ( + line.removeprefix("#").strip().lower() + for line in text.splitlines() + if line.startswith("#") + ), + "", + ) + if not _title_tokens(directory_name) <= _title_tokens(heading): + issues.append(f"does not identify its directory {relative.parent.name!r} in the title") + normalized = " ".join(_words(text.lower())) + if duplicate := seen_content.get(normalized): + issues.append(f"duplicates {duplicate.as_posix()}") + else: + seen_content[normalized] = relative + findings.extend(f"{relative.as_posix()}: {issue}" for issue in issues) + return tuple(findings) diff --git a/src/ml4t_ecosystem/audit.py b/src/ml4t_ecosystem/audit.py index a4992bf..adc168b 100644 --- a/src/ml4t_ecosystem/audit.py +++ b/src/ml4t_ecosystem/audit.py @@ -331,6 +331,7 @@ def _check_pypi( exception = config.exception(library.prerelease_exception) exception_evidence = exception.issue if not exception.is_active(observed_at.date()): + assert exception.expires_on is not None exception_message = ( f"Exception {exception.id} expired on {exception.expires_on.isoformat()}" ) @@ -340,10 +341,18 @@ def _check_pypi( ) else: prerelease_allowed = True - exception_message = ( - f"Requires-Python {requires_text!r} is covered by active exception " - f"{exception.id} through {exception.expires_on.isoformat()}" - ) + if exception.review_triggers: + triggers = ", ".join(exception.review_triggers) + exception_message = ( + f"Requires-Python {requires_text!r} is covered by prerelease wait " + f"{exception.id}; review triggers: {triggers}" + ) + else: + assert exception.expires_on is not None + exception_message = ( + f"Requires-Python {requires_text!r} is covered by active exception " + f"{exception.id} through {exception.expires_on.isoformat()}" + ) report.checks.append( _result( "pypi.prerelease-install", diff --git a/src/ml4t_ecosystem/config.py b/src/ml4t_ecosystem/config.py index df0cd2e..4b4e794 100644 --- a/src/ml4t_ecosystem/config.py +++ b/src/ml4t_ecosystem/config.py @@ -69,10 +69,12 @@ def _optional_str(mapping: dict[str, Any], key: str) -> str | None: return value -def _required_date(mapping: dict[str, Any], key: str) -> date: +def _optional_date(mapping: dict[str, Any], key: str) -> date | None: value = mapping.get(key) + if value is None: + return None if not isinstance(value, date): - raise ValueError(f"{key} must be a TOML date") + raise ValueError(f"{key} must be a TOML date when provided") return value @@ -192,8 +194,9 @@ def load_config(path: Path) -> EcosystemConfig: user_impact=_required_str(raw_exception, "user_impact"), mitigation=_required_str(raw_exception, "mitigation"), approver=_required_str(raw_exception, "approver"), - expires_on=_required_date(raw_exception, "expires_on"), + expires_on=_optional_date(raw_exception, "expires_on"), issue=_required_str(raw_exception, "issue"), + review_triggers=_optional_string_tuple(raw_exception, "review_triggers"), ) ) @@ -205,6 +208,8 @@ def load_config(path: Path) -> EcosystemConfig: for exception in exceptions: if exception.criterion != expected_criterion: raise ValueError(f"exception {exception.id} criterion must be {expected_criterion!r}") + if exception.expires_on is None and not exception.review_triggers: + raise ValueError(f"exception {exception.id} must define expires_on or review_triggers") unknown = sorted(set(exception.libraries) - known_keys) if unknown: raise ValueError(f"exception {exception.id} has unknown libraries: {unknown}") diff --git a/src/ml4t_ecosystem/exceptions.py b/src/ml4t_ecosystem/exceptions.py index 531dda6..d7c3832 100644 --- a/src/ml4t_ecosystem/exceptions.py +++ b/src/ml4t_ecosystem/exceptions.py @@ -16,7 +16,7 @@ def validate_exception( package_version: str, on_date: date, ) -> QualificationException: - """Validate an exception against its library, repository, version, and expiration.""" + """Validate an exception against its library, repository, version, and date.""" library = config.library(library_key) exception = config.exception(exception_id) if library.prerelease_exception != exception.id or library.key not in exception.libraries: @@ -33,5 +33,6 @@ def validate_exception( f"approved scope is {exception.affected_versions}" ) if not exception.is_active(on_date): + assert exception.expires_on is not None raise ValueError(f"exception {exception.id} expired on {exception.expires_on.isoformat()}") return exception diff --git a/src/ml4t_ecosystem/models.py b/src/ml4t_ecosystem/models.py index a43c012..c9eb096 100644 --- a/src/ml4t_ecosystem/models.py +++ b/src/ml4t_ecosystem/models.py @@ -28,7 +28,7 @@ class Library: @dataclass(frozen=True) class QualificationException: - """One approved, time-limited release qualification exception.""" + """One approved release qualification exception or prerelease wait.""" id: str criterion: str @@ -39,12 +39,13 @@ class QualificationException: user_impact: str mitigation: str approver: str - expires_on: date + expires_on: date | None issue: str + review_triggers: tuple[str, ...] = () def is_active(self, on_date: date) -> bool: - """Return whether the exception remains valid on a date.""" - return on_date <= self.expires_on + """Return whether a date-bounded exception remains valid on a date.""" + return self.expires_on is None or on_date <= self.expires_on def covers_version(self, version: str) -> bool: """Return whether a published version is inside the approved scope.""" diff --git a/src/ml4t_ecosystem/workspaces.py b/src/ml4t_ecosystem/workspaces.py index f90e889..f8ba453 100644 --- a/src/ml4t_ecosystem/workspaces.py +++ b/src/ml4t_ecosystem/workspaces.py @@ -6,7 +6,7 @@ from collections.abc import Sequence from pathlib import Path -from ml4t_ecosystem.agent_orientation import orientation_issues +from ml4t_ecosystem.agent_orientation import nested_orientation_issues, orientation_issues from ml4t_ecosystem.models import CheckResult, EcosystemConfig, Library, WorkspaceReport CLAUDE_IMPORT = "@AGENTS.md\n" @@ -71,6 +71,18 @@ def audit_workspace(root: Path, library: Library) -> WorkspaceReport: ), ) ) + nested_issues = nested_orientation_issues(release) + report.checks.append( + _result( + "release.nested-agent-orientation", + not nested_issues, + ( + "Tracked nested AGENTS.md files provide stable subsystem orientation" + if not nested_issues + else "Nested AGENTS.md findings: " + "; ".join(nested_issues) + ), + ) + ) if library.development_workspace is None: report.checks.append( diff --git a/tests/test_agent_orientation.py b/tests/test_agent_orientation.py new file mode 100644 index 0000000..34c705f --- /dev/null +++ b/tests/test_agent_orientation.py @@ -0,0 +1,103 @@ +import subprocess +from pathlib import Path + +from ml4t_ecosystem.agent_orientation import ( + discover_agent_guides, + nested_orientation_issues, + orientation_issues, +) + +ROOT_GUIDE = """# ml4t-data contributor guide + +Market data acquisition and storage for quantitative research. + +## Repository map + +The `src/ml4t/data/` tree contains providers, storage adapters, and data managers. Tests live in +`tests/`, examples in `examples/`, and user documentation in `docs/`. Provider modules acquire and +normalize source data; storage modules persist validated tables; managers coordinate those layers. + +## Public surface + +```python +from ml4t.data import DataManager +``` + +Use public package exports before importing implementation modules. Keep credentials outside the +repository and use synthetic fixtures for offline tests. + +## Verification + +Run `uv run ruff check .`, `uv run ruff format --check .`, `uv run ty check`, and +`uv run pytest` after changes. Consult a nested guide when its directory has narrower constraints. +""" + + +def _git(path: Path, *arguments: str) -> None: + subprocess.run(["git", "-C", str(path), *arguments], check=True, capture_output=True) + + +def test_root_orientation_rejects_private_volatile_and_incomplete_content() -> None: + text = ROOT_GUIDE.replace("uv run ty check", "Codex uses @AGENTS.md for 42 tests") + + issues = orientation_issues(text, "ml4t.data") + + assert "references private or machine-local agent state" in issues + assert "describes agent-tool mechanics instead of the library" in issues + assert "contains a volatile file, line, module, or test count" in issues + assert "does not name required quality commands: ty" in issues + + +def test_discovery_uses_tracked_guides_and_does_not_require_nested_files(tmp_path: Path) -> None: + (tmp_path / "AGENTS.md").write_text(ROOT_GUIDE, encoding="utf-8") + (tmp_path / "untracked").mkdir() + (tmp_path / "untracked/AGENTS.md").write_text("placeholder\n", encoding="utf-8") + _git(tmp_path, "init", "--initial-branch=main") + _git(tmp_path, "add", "AGENTS.md") + + assert discover_agent_guides(tmp_path) == (Path("AGENTS.md"),) + assert nested_orientation_issues(tmp_path) == () + + +def test_nested_guides_are_directory_specific_and_stable(tmp_path: Path) -> None: + (tmp_path / "AGENTS.md").write_text(ROOT_GUIDE, encoding="utf-8") + nested = tmp_path / "src/ml4t/data/providers/AGENTS.md" + nested.parent.mkdir(parents=True) + nested.write_text( + """# providers subsystem + +Provider adapters normalize upstream responses behind the shared provider contract. + +## Change rules + +Keep imports credential-free and preserve the common schema. Use recorded or synthetic responses in +default tests. Live requests belong in the marked provider-contract lane. Reuse the shared retry and +rate-limit helpers instead of adding adapter-specific variants. +""", + encoding="utf-8", + ) + _git(tmp_path, "init", "--initial-branch=main") + _git(tmp_path, "add", "AGENTS.md", "src/ml4t/data/providers/AGENTS.md") + + assert nested_orientation_issues(tmp_path) == () + + nested.write_text(ROOT_GUIDE, encoding="utf-8") + assert any("duplicates AGENTS.md" in finding for finding in nested_orientation_issues(tmp_path)) + + nested.write_text( + """# implementation notes + +This directory has 42 files. Codex reads @AGENTS.md and ~/.workspace/private.md. + +## Current status + +The provider work is complete. Keep this file because discovery expects one in every directory. +""", + encoding="utf-8", + ) + + findings = nested_orientation_issues(tmp_path) + assert any("volatile" in finding for finding in findings) + assert any("agent-tool mechanics" in finding for finding in findings) + assert any("private or machine-local" in finding for finding in findings) + assert any("does not identify its directory 'providers'" in finding for finding in findings) diff --git a/tests/test_audit.py b/tests/test_audit.py index f15f312..74804f0 100644 --- a/tests/test_audit.py +++ b/tests/test_audit.py @@ -381,7 +381,7 @@ def test_audit_accepts_active_version_scoped_prerelease_exception() -> None: assert "python-315-polars" in check.message -def test_audit_rejects_expired_prerelease_exception() -> None: +def test_audit_accepts_prerelease_wait_after_former_expiration() -> None: ecosystem = config() report = audit_library( ecosystem, @@ -393,8 +393,8 @@ def test_audit_rejects_expired_prerelease_exception() -> None: ) check = next(check for check in report.checks if check.code == "pypi.prerelease-install") - assert check.status == "fail" - assert "expired" in check.message + assert check.status == "pass" + assert "review triggers" in check.message def test_audit_rejects_metadata_readme_and_instruction_drift() -> None: diff --git a/tests/test_config.py b/tests/test_config.py index f9eb773..075bfda 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -42,7 +42,13 @@ def test_load_repository_config() -> None: exception = config.exception("python-315-polars") assert exception.libraries == ("data", "engineer") + assert exception.expires_on is None + assert exception.review_triggers == ( + "python-3.15-final", + "polars-release-after-1.44.2", + ) assert exception.is_active(date(2026, 8, 11)) + assert exception.is_active(date(2026, 10, 1)) assert exception.covers_version("0.1.2") assert exception.covers_version("0.1.4") assert exception.covers_version("0.1.5") @@ -50,7 +56,8 @@ def test_load_repository_config() -> None: assert exception.covers_version("0.1.7") assert exception.covers_version("0.1.8.dev2+g9096f8941") assert exception.covers_version("0.1.8") - assert not exception.covers_version("0.1.9.dev1") + assert exception.covers_version("0.1.9.dev1") + assert not exception.covers_version("0.2.0.dev1") diagnostic_exception = config.exception("python-315-scipy") assert diagnostic_exception.libraries == ("diagnostic",) @@ -59,7 +66,8 @@ def test_load_repository_config() -> None: assert diagnostic_exception.covers_version("0.1.4") assert diagnostic_exception.covers_version("0.1.5") assert diagnostic_exception.covers_version("0.1.6") - assert not diagnostic_exception.covers_version("0.1.7") + assert diagnostic_exception.covers_version("0.1.7") + assert not diagnostic_exception.covers_version("0.2.0") def test_unknown_library_raises() -> None: @@ -162,7 +170,8 @@ def test_duplicate_and_incomplete_inventory_rejected(tmp_path: Path) -> None: ('id = "python-315-scipy"', "exception ids must be unique"), ('libraries = ["unknown"]', "unknown libraries"), ('prerelease_exception = "unknown"', "unknown prerelease exception"), - ('expires_on = "2026-09-30"', "expires_on must be a TOML date"), + ('review_triggers = [""]', "review_triggers"), + ("review_triggers = []", "expires_on or review_triggers"), ('affected_versions = "invalid"', "affected_versions"), ('criterion = "other"', "criterion"), ], @@ -173,8 +182,13 @@ def test_invalid_exception_config_rejected(tmp_path: Path, replacement: str, mes 'id = "python-315-scipy"': 'id = "python-315-polars"', 'libraries = ["unknown"]': 'libraries = ["data", "engineer"]', 'prerelease_exception = "unknown"': 'prerelease_exception = "python-315-polars"', - 'expires_on = "2026-09-30"': "expires_on = 2026-09-30", - 'affected_versions = "invalid"': 'affected_versions = ">=0.1.2,<=0.1.8"', + 'review_triggers = [""]': ( + 'review_triggers = ["python-3.15-final", "polars-release-after-1.44.2"]' + ), + "review_triggers = []": ( + 'review_triggers = ["python-3.15-final", "polars-release-after-1.44.2"]' + ), + 'affected_versions = "invalid"': 'affected_versions = ">=0.1.2,<0.2"', 'criterion = "other"': 'criterion = "python-3.15-prerelease"', } path = tmp_path / "invalid-exception.toml" @@ -184,6 +198,23 @@ def test_invalid_exception_config_rejected(tmp_path: Path, replacement: str, mes load_config(path) +def test_date_bounded_exception_remains_supported(tmp_path: Path) -> None: + content = Path("config/libraries.toml").read_text(encoding="utf-8") + content = content.replace( + 'review_triggers = ["python-3.15-final", "polars-release-after-1.44.2"]', + "expires_on = 2026-09-30", + 1, + ) + path = tmp_path / "date-bounded.toml" + path.write_text(content, encoding="utf-8") + + exception = load_config(path).exception("python-315-polars") + + assert exception.review_triggers == () + assert exception.is_active(date(2026, 9, 30)) + assert not exception.is_active(date(2026, 10, 1)) + + @pytest.mark.parametrize( ("content", "message"), [ diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 0d8c91f..6542d73 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -42,11 +42,10 @@ def test_validate_exception_accepts_matching_repository() -> None: ( "data", "ml4t/data", - "0.1.9.dev1", + "0.2.0.dev1", date(2026, 8, 11), "does not cover version", ), - ("data", "ml4t/data", "0.1.2", date(2026, 10, 1), "expired"), ], ) def test_validate_exception_rejects_invalid_scope( diff --git a/tests/test_workspaces.py b/tests/test_workspaces.py index 417b7f6..859085f 100644 --- a/tests/test_workspaces.py +++ b/tests/test_workspaces.py @@ -128,6 +128,7 @@ def test_workspace_without_sidecar_checks_only_release_orientation(tmp_path: Pat assert [check.code for check in report.checks] == [ "release.exists", "release.agent-orientation", + "release.nested-agent-orientation", "sidecar.not-required", ]