-
Notifications
You must be signed in to change notification settings - Fork 0
Add conformance.toml manifest and CI guard #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| # Conformance manifest for openarmature-python. | ||
| # | ||
| # Records which openarmature-spec proposals are implemented in this | ||
| # package, and starting at which release version. Intended to be read | ||
| # by external consumers (notably the openarmature-spec docs build, | ||
| # which surfaces a per-implementation status column on the proposals | ||
| # index page). | ||
| # | ||
| # Stable URL (read-only, fetched at docs-build time): | ||
| # https://raw.githubusercontent.com/LunarCommand/openarmature-python/main/conformance.toml | ||
| # | ||
| # Maintenance: keep in sync with CHANGELOG.md. The CI guard at | ||
| # scripts/check_conformance_manifest.py validates this file against the | ||
| # pinned spec submodule's proposals/ directory on every PR and release | ||
| # build; any Accepted proposal lacking an entry (or any entry pointing | ||
| # at a non-existent / non-Accepted proposal) fails the build. | ||
| # | ||
| # Scope: this file lists ONLY proposals visible in the pinned spec | ||
| # submodule (openarmature-spec at the SHA pinned by this repo's | ||
| # submodule). Proposals accepted on the spec's main branch after this | ||
| # repo's last spec bump are intentionally absent — surfacing the gap | ||
| # between pinned-spec and spec-head is the consumer's job (e.g., the | ||
| # spec docs site computes the difference and renders accordingly). | ||
| # | ||
| # Convention: this file is only updated as part of release PRs. Between | ||
| # releases, the manifest reflects the most-recently-published version | ||
| # so external readers never see a `since` referring to an unreleased | ||
| # pre-tag commit. | ||
|
|
||
| [manifest] | ||
| implementation = "openarmature-python" | ||
| spec_pin = "v0.22.1" | ||
|
|
||
| # Status values: | ||
| # implemented — shipped behavior matches the proposal's contract | ||
| # partial — partial impl; consult `note` for what's missing | ||
| # textual-only — accepted proposal is purely textual (reframe, | ||
| # clarification, template) with no module-level | ||
| # change required; CHANGELOG note explains why | ||
| # not-yet — accepted in spec, not yet shipped in this package | ||
| # | ||
| # Drafts and Superseded proposals are deliberately absent from this | ||
| # file. The CI guard requires entries only for proposals whose spec | ||
| # header reads `Status: Accepted`. | ||
|
|
||
| [proposals."0001"] | ||
| status = "implemented" | ||
| since = "0.5.0" | ||
|
|
||
| [proposals."0002"] | ||
| status = "implemented" | ||
| since = "0.5.0" | ||
|
|
||
| [proposals."0003"] | ||
| status = "implemented" | ||
| since = "0.5.0" | ||
|
|
||
| [proposals."0004"] | ||
| status = "implemented" | ||
| since = "0.5.0" | ||
|
|
||
| [proposals."0005"] | ||
| status = "implemented" | ||
| since = "0.5.0" | ||
|
|
||
| [proposals."0006"] | ||
| status = "implemented" | ||
| since = "0.5.0" | ||
|
|
||
| [proposals."0007"] | ||
| status = "implemented" | ||
| since = "0.5.0" | ||
|
|
||
| [proposals."0008"] | ||
| status = "implemented" | ||
| since = "0.5.0" | ||
|
|
||
| [proposals."0009"] | ||
| status = "implemented" | ||
| since = "0.9.0" | ||
|
|
||
| [proposals."0010"] | ||
| status = "implemented" | ||
| since = "0.9.0" | ||
|
|
||
| [proposals."0011"] | ||
| status = "implemented" | ||
| since = "0.6.0" | ||
|
|
||
| [proposals."0012"] | ||
| status = "implemented" | ||
| since = "0.5.0" | ||
|
|
||
| [proposals."0013"] | ||
| status = "implemented" | ||
| since = "0.5.0" | ||
|
|
||
| [proposals."0014"] | ||
| status = "implemented" | ||
| since = "0.6.0" | ||
|
|
||
| [proposals."0015"] | ||
| status = "implemented" | ||
| since = "0.6.0" | ||
|
|
||
| [proposals."0016"] | ||
| status = "implemented" | ||
| since = "0.6.0" | ||
|
|
||
| [proposals."0017"] | ||
| status = "implemented" | ||
| since = "0.6.0" | ||
|
|
||
| [proposals."0018"] | ||
| status = "implemented" | ||
| since = "0.6.0" | ||
|
|
||
| [proposals."0019"] | ||
| status = "textual-only" | ||
| since = "0.9.0" | ||
| note = "Purely textual reframe of llm-provider §8 as a catalog of wire-format mappings (OpenAI-compatible body nested under §8.1). No module-level change required." | ||
|
|
||
| [proposals."0024"] | ||
| status = "implemented" | ||
| since = "0.8.0" | ||
|
|
||
| [proposals."0025"] | ||
| status = "implemented" | ||
| since = "0.9.0" | ||
|
|
||
| [proposals."0026"] | ||
| status = "textual-only" | ||
| since = "0.9.0" | ||
| note = "Purely textual §8 framing paragraph; the existing OpenAI §8.1 mapping is the template's reference shape, so no module-level work was needed." | ||
|
|
||
| [proposals."0027"] | ||
| status = "implemented" | ||
| since = "0.9.0" | ||
|
|
||
| [proposals."0028"] | ||
| status = "implemented" | ||
| since = "0.9.0" | ||
|
|
||
| [proposals."0029"] | ||
| status = "implemented" | ||
| since = "0.9.0" | ||
|
|
||
| [proposals."0030"] | ||
| status = "textual-only" | ||
| since = "0.9.0" | ||
| note = "Drain snapshot semantic and timeout-input validation already implemented as part of the proposal 0010 impl PR (v0.9.0); no additional module-level work needed." |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| """Validate conformance.toml against the pinned spec submodule's proposals. | ||
|
|
||
| Failure modes caught: | ||
|
|
||
| - Accepted proposal in the spec has no entry in conformance.toml. | ||
| - Entry in conformance.toml refers to a proposal that doesn't exist in | ||
| the spec, or refers to a proposal whose Status is not "Accepted" | ||
| (e.g., Draft / Superseded — those are deliberately excluded from the | ||
| manifest so the docs site doesn't claim impl status for unsettled | ||
| proposals). | ||
| - Entry has an unknown `status` value or a malformed `since` version. | ||
|
|
||
| Read-only; intended for CI. Non-zero exit on any failure with a | ||
| human-readable diff. Runs under the repo's stdlib Python (>=3.12, so | ||
| `tomllib` is always available). | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import re | ||
| import sys | ||
| import tomllib | ||
| from pathlib import Path | ||
| from typing import Any, cast | ||
|
|
||
| REPO_ROOT = Path(__file__).resolve().parent.parent | ||
| MANIFEST_PATH = REPO_ROOT / "conformance.toml" | ||
| PROPOSALS_DIR = REPO_ROOT / "openarmature-spec" / "proposals" | ||
|
|
||
| ALLOWED_STATUSES = frozenset({"implemented", "partial", "textual-only", "not-yet"}) | ||
| PROPOSAL_FILENAME_RE = re.compile(r"^(\d{4})-[a-z0-9-]+\.md$") | ||
| STATUS_LINE_RE = re.compile(r"^- \*\*Status:\*\*\s*(.+?)\s*$", re.MULTILINE) | ||
| SINCE_RE = re.compile(r"^\d+\.\d+\.\d+$") | ||
|
|
||
|
|
||
| def parse_spec_proposals() -> dict[str, str]: | ||
| # Returns {proposal_id: status} for every proposal markdown file in | ||
| # the pinned spec submodule. proposal_id is the 4-digit string used | ||
| # as the manifest key; status is the literal value from the file's | ||
| # `- **Status:** ...` header line. | ||
| if not PROPOSALS_DIR.is_dir(): | ||
| sys.exit( | ||
| f"::error::proposals dir not found at {PROPOSALS_DIR} — " | ||
| "is the openarmature-spec submodule checked out?" | ||
| ) | ||
|
|
||
| result: dict[str, str] = {} | ||
| for path in sorted(PROPOSALS_DIR.iterdir()): | ||
| m = PROPOSAL_FILENAME_RE.match(path.name) | ||
| if not m: | ||
| continue | ||
| proposal_id = m.group(1) | ||
| text = path.read_text(encoding="utf-8") | ||
| status_match = STATUS_LINE_RE.search(text) | ||
| if not status_match: | ||
| sys.exit(f"::error::proposal {proposal_id} ({path.name}) has no `- **Status:** ...` header line") | ||
| result[proposal_id] = status_match.group(1).strip() | ||
| return result | ||
|
|
||
|
|
||
| def load_manifest() -> dict[str, Any]: | ||
| # Returns {proposal_id: entry} for every [proposals."NNNN"] section | ||
| # in conformance.toml. Each `entry` is typed as Any rather than | ||
| # dict because TOML lets a user accidentally write a scalar value | ||
| # under [proposals]; the caller validates the type per-entry and | ||
| # emits a structured error on shape drift. | ||
| if not MANIFEST_PATH.is_file(): | ||
| sys.exit(f"::error::manifest not found at {MANIFEST_PATH}") | ||
|
|
||
| with MANIFEST_PATH.open("rb") as f: | ||
| data = tomllib.load(f) | ||
|
|
||
| proposals = data.get("proposals", {}) | ||
| if not isinstance(proposals, dict): | ||
| sys.exit("::error::conformance.toml [proposals] table malformed") | ||
| return cast(dict[str, Any], proposals) | ||
|
|
||
|
|
||
| def main() -> int: | ||
| spec = parse_spec_proposals() | ||
| manifest = load_manifest() | ||
|
|
||
| accepted_ids = {pid for pid, status in spec.items() if status == "Accepted"} | ||
| manifest_ids = set(manifest.keys()) | ||
|
|
||
| errors: list[str] = [] | ||
|
|
||
| missing = sorted(accepted_ids - manifest_ids) | ||
| for pid in missing: | ||
| errors.append(f"Accepted spec proposal {pid} has no entry in conformance.toml") | ||
|
|
||
| extra = sorted(manifest_ids - accepted_ids) | ||
| for pid in extra: | ||
| if pid not in spec: | ||
| errors.append( | ||
| f"conformance.toml entry {pid} refers to a proposal that " | ||
| f"doesn't exist in openarmature-spec/proposals/" | ||
| ) | ||
| else: | ||
| errors.append( | ||
| f"conformance.toml entry {pid} refers to a proposal whose " | ||
| f"spec Status is {spec[pid]!r}, not 'Accepted' — " | ||
| f"drafts and superseded proposals should be omitted" | ||
| ) | ||
|
|
||
| for pid in sorted(manifest_ids): | ||
| raw_entry = manifest[pid] | ||
| if not isinstance(raw_entry, dict): | ||
| errors.append( | ||
| f"conformance.toml entry {pid} is not a table " | ||
| f'(got {type(raw_entry).__name__}); check the [proposals."{pid}"] ' | ||
| f"section is a table, not a scalar" | ||
| ) | ||
| continue | ||
| entry = cast(dict[str, Any], raw_entry) | ||
| status = entry.get("status") | ||
| if status not in ALLOWED_STATUSES: | ||
| errors.append( | ||
| f"conformance.toml entry {pid} has unknown status {status!r} " | ||
| f"(allowed: {sorted(ALLOWED_STATUSES)})" | ||
| ) | ||
| # `since` is required for every status except `not-yet`. | ||
| since = entry.get("since") | ||
| if status == "not-yet": | ||
| if since is not None: | ||
| errors.append( | ||
| f"conformance.toml entry {pid} has status=not-yet but " | ||
| f"also a `since` field — drop `since` for not-yet entries" | ||
| ) | ||
| else: | ||
| if since is None: | ||
| errors.append(f"conformance.toml entry {pid} has status={status!r} but no `since` field") | ||
| elif not isinstance(since, str): | ||
| errors.append( | ||
| f"conformance.toml entry {pid} `since` value {since!r} is not a string " | ||
| f"(got {type(since).__name__}); did you forget to quote it?" | ||
| ) | ||
| elif not SINCE_RE.match(since): | ||
| errors.append( | ||
| f"conformance.toml entry {pid} `since` value {since!r} is not in MAJOR.MINOR.PATCH form" | ||
| ) | ||
|
chris-colinsky marked this conversation as resolved.
chris-colinsky marked this conversation as resolved.
|
||
|
|
||
| if errors: | ||
| print("conformance.toml validation failed:", file=sys.stderr) | ||
| for err in errors: | ||
| print(f" - {err}", file=sys.stderr) | ||
| return 1 | ||
|
|
||
| print(f"OK: {len(accepted_ids)} accepted proposals, {len(manifest_ids)} manifest entries, all consistent") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) | ||
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.
Uh oh!
There was an error while loading. Please reload this page.