MAS-WP-0005-T02: SCP-2 lower-case resource types (spec v0.1.1)

Grammar, statute schema key pattern, parser (is_resource_type, also used
by statute lint), reference parser, spec 6.1 and a new CHANGELOG. Fixtures
p1-042..045; prop-case-001 Ticket=PR42 is now invalid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 252470@bnt-lap001
Assistant-Session: ad628c8f-4be2-4979-94c9-76c7ed6f8241
This commit is contained in:
tegwick 2026-10-18 18:08:36 +02:00
parent 6ee9e4dd54
commit 5b57a9941e
14 changed files with 155 additions and 25 deletions

View file

@ -11,6 +11,8 @@ class (P1 or P2) without re-parsing.
from __future__ import annotations
import re
from dataclasses import dataclass
from typing import Optional
@ -28,11 +30,16 @@ LOCAL_PART_MAX_OCTETS = 64
_ALNUM = frozenset("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
ATOM_CHARS = _ALNUM | frozenset("#%&'*-/?_`{|}~")
RESOURCE_TYPE_CHARS = _ALNUM | frozenset("-_")
# v0.1.1 (SCP-2): lower-case words joined by single "-" or "_".
_RESOURCE_TYPE = re.compile(r"[a-z0-9]+(?:[_-][a-z0-9]+)*")
VALUE_CHARS = ATOM_CHARS | frozenset("=") # resource-id-char and value-char
RESERVED_OPERATORS = frozenset("$^")
def is_resource_type(name: str) -> bool:
return _RESOURCE_TYPE.fullmatch(name) is not None
class MasSyntaxError(ValueError):
"""A local-part is not canonical MAS v0.1 syntax.
@ -102,7 +109,7 @@ def parse_local_part(value: str) -> ParsedLocalPart:
raise MasSyntaxError("empty-resource-type", "resource type is empty")
if not rid:
raise MasSyntaxError("empty-resource-id", "resource id is empty")
if not all(c in RESOURCE_TYPE_CHARS for c in rtype):
if not is_resource_type(rtype):
raise MasSyntaxError("invalid-resource-type", f"invalid resource type {rtype!r}")
if not _dot_string(rid, VALUE_CHARS):
raise MasSyntaxError("invalid-resource-id", f"invalid resource id {rid!r}")