Skip to content

spec 110 US-A: cacp-python reference parser (v0.1.0)#6

Merged
zenprocess merged 1 commit into
mainfrom
cacp-python-initial
Apr 23, 2026
Merged

spec 110 US-A: cacp-python reference parser (v0.1.0)#6
zenprocess merged 1 commit into
mainfrom
cacp-python-initial

Conversation

@zenprocess
Copy link
Copy Markdown
Owner

What

Adds a new cacp-python/ subdirectory containing the canonical Python
reference parser for CACP. The parser lives alongside the spec so the
two can evolve together.

Why

Spec 110 US-A of the switchyard
project. The goal is to have the canonical Python implementation
colocated with the canonical spec, rather than letting each downstream
consumer ship its own slightly-different parser (which is how we got
bugs like switchyard #941 — a parser that rejected output the spec said
was valid).

Size

  • Parser: ~145 LOC including docstrings (~80 LOC of core logic)
  • Models: ~45 LOC (dataclass + canonical value tuples)
  • Tests: ~245 LOC across 2 files
  • Zero runtime dependencies — pure stdlib

What's in it

  • src/cacp/parser.pyparse(text) -> CACPResponse | None. Seven
    compiled per-field regexes, each with [ \t]* (deliberately NOT
    \s* — that would let a blank line swallow the following field) and
    re.IGNORECASE | re.MULTILINE. STATUS alternates over the 9
    canonical values; TESTS/BUILD over the 3 canonical values.
  • src/cacp/models.pyCACPResponse dataclass + the two canonical
    tuples (CANONICAL_STATUS_VALUES, CANONICAL_TESTS_BUILD_VALUES).
  • tests/test_conformance.py — the 6-line tolerance vector from the
    spec verbatim, plus parametrized coverage of all 9 STATUS values and
    all 3 TESTS/BUILD values under every whitespace shape.
  • tests/test_roundtrip.py — feeds the literal response example and
    literal dispatch example from ../README.md through the parser;
    response round-trips, dispatch correctly returns None (strict — no
    STATUS field means not a response).

Test summary

42 passed in 0.06s

All 42 tests pass under Python 3.12. Test breakdown:

  • 9 × parametrized canonical STATUS values
  • 6 × tolerance vector from README conformance table
  • 4 × TESTS values (pass/fail/skip + pass:42)
  • 3 × TESTS whitespace tolerance (each value × 4 separators)
  • 3 × BUILD values
  • 3 × BUILD whitespace tolerance
  • 5 × STATUS field-name case variants
  • 3 × negative cases (empty, non-CACP prose, dispatch format)
  • 6 × round-trip + mixed-case + edge cases

Design notes

  • dataclass not Pydantic. The data model is a plain @dataclass
    to keep the dep tree empty. Callers that want runtime validation
    can wrap it. The tradeoff was discussed inline in the README.md.
  • Regex per field, not a generic FIELD:value splitter. This
    lets each field enforce its own grammar (STATUS restricted to the
    9 values, TESTS allows optional :N, FILES_* captures to EOL).
  • [ \t]* not \s* after the colon\s matches newline, so a
    lazy \s* would let one field's blank line get consumed into the
    next field's value. This is the same class of bug switchyard #941
    hit in v5.2.0 and the spec's "Why these rules exist" section calls
    out.

Next steps (out of scope for this PR)

  • Human operator handles PyPI release after merge.
  • switchyard v5.4.x will pull cacp>=0.1.0 as a real dependency and
    delete its in-tree parser copy.
  • Other language implementations (Rust, TypeScript, Go) should port
    tests/test_conformance.py against the same vector.

Reviewer ask

Please confirm the STATUS vocabulary and tolerance test vector match
the spec's intent. Everything in tests/test_conformance.py derives
from the canonical README.md in this repo — if any row there is wrong,
either the test is wrong or the spec is wrong, and I'd rather catch
that now than after downstream consumers pin the version.

Guardrails honored

  • Did NOT modify the existing README.md at the repo root — that's
    the authoritative spec.
  • Did NOT push to main.
  • Did NOT publish to PyPI — operator handles release.

🤖 Generated with Claude Code

New cacp-python/ subdirectory implementing the canonical Python parser
for CACP per the spec in ../README.md.

Parser (~145 LOC incl. docstrings; core logic ~80 LOC) covers:
- 9 STATUS values: ok|fail|partial|needs_decision|no_changes|
  decomposed|rejected|retry|fixture_gap
- 3 TESTS/BUILD values: pass|fail|skip (+ optional :N count for TESTS)
- Whitespace tolerance (space, multiple spaces, tab) via [ \t]* — not
  \s*, which would swallow newlines and cross field boundaries
- Case-insensitive field names; STATUS/TESTS/BUILD values normalized
  to lowercase

Zero runtime dependencies (pure stdlib + dataclass model). A dataclass
keeps the dep tree empty; callers wanting Pydantic-style validation can
wrap the returned record.

Tests (42 passing) cover: 9-value parametrized STATUS, the 6-line
tolerance vector from README.md verbatim, README response example
round-trip, dispatch-example negative (no STATUS field -> parse returns
None), case-insensitive field names across the full grid, and
whitespace tolerance for TESTS/BUILD.

Spec 110 US-A of the switchyard project — this package becomes the
canonical reference impl that downstream (switchyard v5.4.x, zendev)
consumes as `cacp>=0.1.0`. Other language implementations should port
tests/test_conformance.py against the same vector.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zenprocess zenprocess merged commit 7d2af9c into main Apr 23, 2026
2 checks passed
@zenprocess zenprocess deleted the cacp-python-initial branch April 23, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant