MAS-WP-0008: v0.2 wording pass, statute-aware conformance corpus in the bundle
conformance/statute-aware.json (vendored as mas_conformance/statute_aware.json) covers the 6.3 stand-in table, resource_id_case equivalence and the 12.1 destination results and refusals; mas test runs it. Misread refusals now name the field that would change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 5787@bnt-lap001 Assistant-Session: 5db26b82-138b-40cc-9856-4d5eb0f752fd
This commit is contained in:
parent
499ddce015
commit
c4719a238d
11 changed files with 988 additions and 41 deletions
|
|
@ -24,6 +24,11 @@ New features, opt-in per statute with `"mas_version": "0.2"`
|
|||
unquoted form. Receivers treat them identically; generators emit the
|
||||
unquoted form; canonical parsers still reject the quoted form (§4).
|
||||
Fixture p1-040 note updated.
|
||||
- **Conformance (MAS-WP-0008).** `conformance/statute-aware.json` and
|
||||
the `MAS-V0.2-STATUTE-AWARE` surface cover the stand-in table, the
|
||||
`resource_id_case` equivalence and destination-transform refusals.
|
||||
The text inherited from v0.1.1 no longer says "v0.1" for rules that are
|
||||
unchanged.
|
||||
|
||||
## v0.1.1 (errata)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# MAS v0.1 Conformance
|
||||
# MAS v0.2 Conformance (draft)
|
||||
|
||||
## 1. Test surfaces
|
||||
|
||||
|
|
@ -6,8 +6,9 @@ An implementation can claim any of the following independently:
|
|||
|
||||
- `MAS-P1-PARSER`
|
||||
- `MAS-P2-PARSER`
|
||||
- `MAS-V0.1-GENERATOR`
|
||||
- `MAS-V0.1-STATUTE`
|
||||
- `MAS-V0.2-GENERATOR`
|
||||
- `MAS-V0.2-STATUTE`
|
||||
- `MAS-V0.2-STATUTE-AWARE`
|
||||
|
||||
## 2. Parser requirements
|
||||
|
||||
|
|
@ -30,7 +31,7 @@ A generator:
|
|||
- MUST emit `+` before `!`;
|
||||
- MUST NOT emit more than one `+` or one `!`;
|
||||
- MUST NOT emit `$` or `^`;
|
||||
- MUST NOT emit both `+` and `!` in v0.1;
|
||||
- MUST NOT emit both `+` and `!`;
|
||||
- MUST keep the encoded SMTP local-part at or below 64 octets.
|
||||
|
||||
Fixtures with `generator_allowed: false` may still be accepted by a P2 parser,
|
||||
|
|
@ -39,7 +40,8 @@ but a conforming generator MUST NOT create them.
|
|||
## 4. Statute requirements
|
||||
|
||||
A statute implementation MUST validate discovery documents using
|
||||
`schema/mas-statute-v0.1.schema.json`.
|
||||
`schema/mas-statute-v0.2.schema.json` when `mas_version` is `0.2`. A `0.1`
|
||||
statute is validated with the v0.1.1 schema and gets no v0.2 features.
|
||||
|
||||
It MUST NOT apply:
|
||||
|
||||
|
|
@ -51,7 +53,23 @@ It MUST NOT apply:
|
|||
|
||||
unless the exact-host statute successfully validates and declares them.
|
||||
|
||||
## 5. Discovery failure
|
||||
## 5. Statute-aware requirements (v0.2)
|
||||
|
||||
An implementation claiming `MAS-V0.2-STATUTE-AWARE` MUST produce the
|
||||
expected result for every case in `conformance/statute-aware.json`. Each
|
||||
case names one of the file's inline statutes, whose `namespace` is the host.
|
||||
|
||||
- `interpretation`: the received local-part is read as `canonical`, using
|
||||
the listed `stand_ins` (§6.3). The parser alone never interprets
|
||||
stand-ins.
|
||||
- `equivalence`: `a` and `b` have the same equivalence key exactly when
|
||||
`equivalent` is true (§8, `resource_id_case`).
|
||||
- `destination`: applying the transforms in `needs` to `local_part` yields
|
||||
`expect`. When `expect` is `null`, the generator MUST refuse (§12.1).
|
||||
`reason` is informative, and refusal codes are not normative.
|
||||
`allow_lossy` marks an explicit request for a drop transform.
|
||||
|
||||
## 6. Discovery failure
|
||||
|
||||
Failure to retrieve or validate a statute:
|
||||
|
||||
|
|
@ -59,7 +77,7 @@ Failure to retrieve or validate a statute:
|
|||
- does invalidate any claim that MAS namespace semantics were discovered;
|
||||
- falls back to ordinary email handling plus exact local configuration.
|
||||
|
||||
## 6. Inheritance
|
||||
## 7. Inheritance
|
||||
|
||||
Implementations are not required to implement `inherits`.
|
||||
|
||||
|
|
@ -71,7 +89,7 @@ Implementations that do implement it MUST:
|
|||
- never inherit explicit `addresses` registry entries;
|
||||
- let the child override inherited resource types and interaction modes.
|
||||
|
||||
## 7. Test command
|
||||
## 8. Test command
|
||||
|
||||
The bundled reference parser can be run with:
|
||||
|
||||
|
|
|
|||
|
|
@ -11,17 +11,18 @@ This bundle is the **draft** of MAS v0.2. It extends v0.1.1 (see `CHANGELOG.md`)
|
|||
- `examples/mas-statute.example.json` — example statute
|
||||
- `CONFORMANCE.md` — parser/generator/statute conformance rules
|
||||
- `conformance/addresses.json` — positive and negative address fixtures
|
||||
- `conformance/statute-aware.json` — v0.2 stand-in, equivalence and destination-transform cases
|
||||
- `reference/mas_parser.py` — small non-normative reference parser
|
||||
|
||||
## Profiles
|
||||
|
||||
- **P1 / Naming + Resource:** naming hints, `=` resource binding, `+` facet
|
||||
- **P2 / Interaction:** optional `!` interaction preference
|
||||
- **P3 / Economic:** reserved only; `$` and `^` have no v0.1 semantics
|
||||
- **P3 / Economic:** reserved only; `$` and `^` have no v0.2 semantics
|
||||
|
||||
The normative machine grammar is deliberately ASCII-only. SMTPUTF8/Unicode
|
||||
addresses may exist, but Unicode MAS semantics are not part of the canonical
|
||||
v0.1 grammar.
|
||||
grammar.
|
||||
|
||||
## Discovery status
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ contractual commitments.
|
|||
|
||||
## 2. Scope
|
||||
|
||||
MAS v0.1 standardizes:
|
||||
MAS standardizes:
|
||||
|
||||
1. non-structural naming hints for people, actors, and functions;
|
||||
2. structured resource targets using `=`;
|
||||
|
|
@ -28,15 +28,16 @@ MAS v0.1 standardizes:
|
|||
4. an optional interaction profile using `!`;
|
||||
5. exact-host statute discovery;
|
||||
6. conservative equivalence behavior;
|
||||
7. parser and generator conformance.
|
||||
7. parser and generator conformance;
|
||||
8. statute-enabled stand-ins and destination transforms (v0.2).
|
||||
|
||||
`$` and `^` are reserved. They have no MAS v0.1 meaning.
|
||||
`$` and `^` are reserved. They have no MAS v0.2 meaning.
|
||||
|
||||
## 3. Profiles
|
||||
|
||||
### 3.1 Profile P1 — Naming + Resource
|
||||
|
||||
P1 is the base v0.1 profile.
|
||||
P1 is the base profile.
|
||||
|
||||
- `.` MAY be used as a person-style naming separator.
|
||||
- `-` MAY be used as an actor-style naming separator.
|
||||
|
|
@ -58,22 +59,22 @@ constitute acceptance. Acceptance is external to the address.
|
|||
|
||||
### 3.3 Profile P3 — Economic
|
||||
|
||||
P3 is not defined in v0.1.
|
||||
P3 is not defined in v0.2.
|
||||
|
||||
`$` and `^` are reserved so that later profiles may define consideration and
|
||||
conditions without creating v0.1 parsing ambiguity.
|
||||
conditions without creating parsing ambiguity.
|
||||
|
||||
## 4. Canonical ASCII local-part
|
||||
|
||||
The normative v0.1 machine grammar is the ABNF in
|
||||
The normative machine grammar is the ABNF in
|
||||
`grammar/mas-v0.2.abnf`.
|
||||
|
||||
Canonical MAS v0.1 local-parts are unquoted ASCII dot-atom-like strings.
|
||||
Quoted local-parts and SMTPUTF8 local-parts are outside the canonical v0.1
|
||||
Canonical MAS local-parts are unquoted ASCII dot-atom-like strings.
|
||||
Quoted local-parts and SMTPUTF8 local-parts are outside the canonical
|
||||
machine grammar.
|
||||
|
||||
A MAS-aware implementation MAY transport such non-canonical email addresses
|
||||
as ordinary email, but MUST NOT claim that they are canonical MAS v0.1
|
||||
as ordinary email, but MUST NOT claim that they are canonical MAS
|
||||
addresses.
|
||||
|
||||
A quoted local-part whose content, after removing the quotes and with no
|
||||
|
|
@ -144,7 +145,7 @@ A parser implementing P2 MUST be able to parse the canonical combined form:
|
|||
|
||||
TARGET+FACET!INTERACTION
|
||||
|
||||
A v0.1 generator MUST NOT emit both `+` and `!` in the same generated
|
||||
A generator MUST NOT emit both `+` and `!` in the same generated
|
||||
local-part. Implementations requiring both semantics SHOULD move one of them
|
||||
to external metadata.
|
||||
|
||||
|
|
@ -153,15 +154,16 @@ while retaining one canonical parse.
|
|||
|
||||
### 4.5 Reserved characters
|
||||
|
||||
Within canonical MAS v0.1 structured values:
|
||||
Within canonical MAS structured values:
|
||||
|
||||
+ ! $ ^
|
||||
|
||||
are reserved.
|
||||
|
||||
MAS v0.1 defines no escaping mechanism.
|
||||
MAS defines no escaping mechanism. The stand-ins (§6.3) are alternate
|
||||
forms enabled by a statute, not escapes.
|
||||
|
||||
A percent-looking sequence such as `%2B` is ordinary data in v0.1 and MUST NOT
|
||||
A percent-looking sequence such as `%2B` is ordinary data and MUST NOT
|
||||
be percent-decoded by the MAS parser.
|
||||
|
||||
## 5. Naming semantics
|
||||
|
|
@ -339,15 +341,15 @@ DNS stack; MAS does not invent a second IDN mapping algorithm.
|
|||
|
||||
## 9. Unicode
|
||||
|
||||
SMTPUTF8 allows non-ASCII local-parts, but MAS v0.1 does not define a
|
||||
SMTPUTF8 allows non-ASCII local-parts, but MAS v0.2 does not define a
|
||||
canonical Unicode local-part grammar.
|
||||
|
||||
A statute may declare `unicode_local_part: true` for experimental/private
|
||||
extensions. Such addresses MUST NOT be advertised as canonical MAS v0.1
|
||||
extensions. Such addresses MUST NOT be advertised as canonical MAS
|
||||
machine syntax.
|
||||
|
||||
A future MAS version may define NFC normalization and Unicode comparison
|
||||
rules. v0.1 deliberately avoids doing so.
|
||||
rules. v0.2 deliberately avoids doing so.
|
||||
|
||||
## 10. Statute discovery
|
||||
|
||||
|
|
@ -383,7 +385,9 @@ A statute-aware client:
|
|||
1. constructs the HTTPS URI for the exact host;
|
||||
2. performs an HTTPS GET;
|
||||
3. requires a successful 2xx response;
|
||||
4. requires a document conforming to the v0.1 statute schema;
|
||||
4. requires a document conforming to the statute schema for its
|
||||
`mas_version` (`schema/mas-statute-v0.2.schema.json` for `0.2`; a `0.1`
|
||||
statute is validated and interpreted under v0.1.1);
|
||||
5. verifies that `namespace` equals the queried host after normal domain-name
|
||||
canonicalization;
|
||||
6. otherwise treats discovery as failed.
|
||||
|
|
@ -398,13 +402,13 @@ Clients SHOULD NOT follow cross-origin redirects for statute discovery.
|
|||
Same-origin redirects MAY be followed.
|
||||
|
||||
A cross-origin policy transfer requires an explicit future delegation
|
||||
mechanism and is not defined in v0.1.
|
||||
mechanism and is not defined in v0.2.
|
||||
|
||||
### 10.4 Inheritance
|
||||
|
||||
No parent-domain inheritance is implicit.
|
||||
|
||||
A child statute MAY contain an `inherits` URI. v0.1 inheritance is
|
||||
A child statute MAY contain an `inherits` URI. Inheritance is
|
||||
**defaults-only**:
|
||||
|
||||
- the parent document is fetched and validated independently;
|
||||
|
|
@ -510,7 +514,7 @@ An SMTP local-part is limited to 64 octets.
|
|||
Generators MUST enforce the underlying transport limit after encoding and
|
||||
after destination transforms. `--` adds one octet and `---` adds two.
|
||||
|
||||
Because the budget is small, v0.1 permits at most one facet and one
|
||||
Because the budget is small, MAS permits at most one facet and one
|
||||
interaction, and generators MUST NOT emit both simultaneously.
|
||||
|
||||
## 14. Security
|
||||
|
|
@ -522,7 +526,7 @@ interaction, and generators MUST NOT emit both simultaneously.
|
|||
expired resources, verification traffic, and abuse.
|
||||
5. Receivers MUST avoid backscatter behavior.
|
||||
6. Syntax-derived classifications MUST NOT substitute for statute metadata.
|
||||
7. `$` and `^` MUST NOT be rendered as payment/contract semantics in v0.1.
|
||||
7. `$` and `^` MUST NOT be rendered as payment/contract semantics in v0.2.
|
||||
8. Authentication remains external to MAS.
|
||||
9. A statute endpoint is security-relevant configuration and SHOULD be
|
||||
protected with the same care as other origin-wide metadata.
|
||||
|
|
@ -558,7 +562,7 @@ A generator:
|
|||
- emits no reserved operator within a value;
|
||||
- emits at most one `+`;
|
||||
- emits at most one `!`;
|
||||
- MUST NOT emit both `+` and `!` in v0.1;
|
||||
- MUST NOT emit both `+` and `!`;
|
||||
- enforces the SMTP local-part octet limit.
|
||||
|
||||
### 15.4 Statute-aware implementation
|
||||
|
|
|
|||
426
mas-v0.2-spec/conformance/statute-aware.json
Normal file
426
mas-v0.2-spec/conformance/statute-aware.json
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
{
|
||||
"description": "MAS v0.2 statute-aware cases: stand-in interpretation (6.3), resource_id_case equivalence (8), destination-transform results and refusals (12.1). Each case names a statute in 'statutes'; the host is the statute's namespace.",
|
||||
"statutes": {
|
||||
"stand-ins": {
|
||||
"mas_version": "0.2",
|
||||
"namespace": "great-service.example",
|
||||
"profiles": {
|
||||
"p1": true,
|
||||
"p2": true
|
||||
},
|
||||
"resource_types": {
|
||||
"ticket": {
|
||||
"enabled": true,
|
||||
"description": "Customer-service ticket",
|
||||
"identifier_policy": "non-guessable-preferred"
|
||||
}
|
||||
},
|
||||
"interaction_modes": {
|
||||
"callback": {
|
||||
"enabled": true,
|
||||
"description": "Continue by a voice callback"
|
||||
}
|
||||
},
|
||||
"equivalence": {
|
||||
"local_part_case": "exact",
|
||||
"dot_folding": false,
|
||||
"resource_id_case": "insensitive"
|
||||
},
|
||||
"addresses": {
|
||||
"support": {
|
||||
"kind": "function"
|
||||
},
|
||||
"ticket": {
|
||||
"kind": "function",
|
||||
"intake_for": "ticket"
|
||||
}
|
||||
},
|
||||
"alternate_syntax": {
|
||||
"resource": true,
|
||||
"interaction": true
|
||||
}
|
||||
},
|
||||
"stand-ins-exact-ids": {
|
||||
"mas_version": "0.2",
|
||||
"namespace": "great-service.example",
|
||||
"profiles": {
|
||||
"p1": true,
|
||||
"p2": true
|
||||
},
|
||||
"resource_types": {
|
||||
"ticket": {
|
||||
"enabled": true,
|
||||
"description": "Customer-service ticket",
|
||||
"identifier_policy": "non-guessable-preferred"
|
||||
}
|
||||
},
|
||||
"interaction_modes": {
|
||||
"callback": {
|
||||
"enabled": true,
|
||||
"description": "Continue by a voice callback"
|
||||
}
|
||||
},
|
||||
"equivalence": {
|
||||
"local_part_case": "exact",
|
||||
"dot_folding": false,
|
||||
"resource_id_case": "exact"
|
||||
},
|
||||
"addresses": {
|
||||
"support": {
|
||||
"kind": "function"
|
||||
},
|
||||
"ticket": {
|
||||
"kind": "function",
|
||||
"intake_for": "ticket"
|
||||
}
|
||||
},
|
||||
"alternate_syntax": {
|
||||
"resource": true,
|
||||
"interaction": true
|
||||
}
|
||||
},
|
||||
"no-stand-ins": {
|
||||
"mas_version": "0.2",
|
||||
"namespace": "great-service.example",
|
||||
"profiles": {
|
||||
"p1": true,
|
||||
"p2": true
|
||||
},
|
||||
"resource_types": {
|
||||
"ticket": {
|
||||
"enabled": true,
|
||||
"description": "Customer-service ticket",
|
||||
"identifier_policy": "non-guessable-preferred"
|
||||
}
|
||||
},
|
||||
"interaction_modes": {
|
||||
"callback": {
|
||||
"enabled": true,
|
||||
"description": "Continue by a voice callback"
|
||||
}
|
||||
},
|
||||
"equivalence": {
|
||||
"local_part_case": "exact",
|
||||
"dot_folding": false,
|
||||
"resource_id_case": "exact"
|
||||
},
|
||||
"addresses": {
|
||||
"support": {
|
||||
"kind": "function"
|
||||
},
|
||||
"ticket": {
|
||||
"kind": "function",
|
||||
"intake_for": "ticket"
|
||||
}
|
||||
},
|
||||
"alternate_syntax": {
|
||||
"resource": false,
|
||||
"interaction": false
|
||||
}
|
||||
},
|
||||
"v0.1": {
|
||||
"mas_version": "0.1",
|
||||
"namespace": "great-service.example",
|
||||
"profiles": {
|
||||
"p1": true,
|
||||
"p2": true
|
||||
},
|
||||
"resource_types": {
|
||||
"ticket": {
|
||||
"enabled": true,
|
||||
"description": "Customer-service ticket",
|
||||
"identifier_policy": "non-guessable-preferred"
|
||||
}
|
||||
},
|
||||
"interaction_modes": {
|
||||
"callback": {
|
||||
"enabled": true,
|
||||
"description": "Continue by a voice callback"
|
||||
}
|
||||
},
|
||||
"equivalence": {
|
||||
"local_part_case": "exact",
|
||||
"dot_folding": false
|
||||
},
|
||||
"addresses": {
|
||||
"support": {
|
||||
"kind": "function"
|
||||
},
|
||||
"ticket": {
|
||||
"kind": "function",
|
||||
"intake_for": "ticket"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"interpretation": [
|
||||
{
|
||||
"id": "si-001",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket---callback",
|
||||
"canonical": "ticket!callback",
|
||||
"stand_ins": [
|
||||
"interaction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "si-002",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket--gs1---callback",
|
||||
"canonical": "ticket=gs1!callback",
|
||||
"stand_ins": [
|
||||
"interaction",
|
||||
"resource"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "si-003",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket--a---b",
|
||||
"canonical": "ticket=a---b",
|
||||
"stand_ins": [
|
||||
"resource"
|
||||
],
|
||||
"note": "b is not an enabled mode"
|
||||
},
|
||||
{
|
||||
"id": "si-004",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket--gs1+comments",
|
||||
"canonical": "ticket=gs1+comments",
|
||||
"stand_ins": [
|
||||
"resource"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "si-005",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "support---callback",
|
||||
"canonical": "support!callback",
|
||||
"stand_ins": [
|
||||
"interaction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "si-006",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "invoice--x1",
|
||||
"canonical": "invoice--x1",
|
||||
"stand_ins": [],
|
||||
"note": "undeclared resource type"
|
||||
},
|
||||
{
|
||||
"id": "si-007",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=gs1!callback",
|
||||
"canonical": "ticket=gs1!callback",
|
||||
"stand_ins": [],
|
||||
"note": "already canonical"
|
||||
},
|
||||
{
|
||||
"id": "si-008",
|
||||
"statute": "no-stand-ins",
|
||||
"local_part": "ticket--gs1",
|
||||
"canonical": "ticket--gs1",
|
||||
"stand_ins": [],
|
||||
"note": "switch off"
|
||||
},
|
||||
{
|
||||
"id": "si-009",
|
||||
"statute": "no-stand-ins",
|
||||
"local_part": "ticket---callback",
|
||||
"canonical": "ticket---callback",
|
||||
"stand_ins": []
|
||||
},
|
||||
{
|
||||
"id": "si-010",
|
||||
"statute": "v0.1",
|
||||
"local_part": "ticket--gs1",
|
||||
"canonical": "ticket--gs1",
|
||||
"stand_ins": [],
|
||||
"note": "v0.1 statute has no stand-ins"
|
||||
}
|
||||
],
|
||||
"equivalence": [
|
||||
{
|
||||
"id": "eq-001",
|
||||
"statute": "stand-ins",
|
||||
"a": "ticket=GS-1",
|
||||
"b": "ticket=gs-1",
|
||||
"equivalent": true,
|
||||
"note": "resource_id_case insensitive"
|
||||
},
|
||||
{
|
||||
"id": "eq-002",
|
||||
"statute": "stand-ins",
|
||||
"a": "ticket--GS-85QJ8WC09P",
|
||||
"b": "ticket=gs-85qj8wc09p",
|
||||
"equivalent": true,
|
||||
"note": "stand-in + insensitive id"
|
||||
},
|
||||
{
|
||||
"id": "eq-003",
|
||||
"statute": "stand-ins-exact-ids",
|
||||
"a": "ticket=GS-1",
|
||||
"b": "ticket=gs-1",
|
||||
"equivalent": false,
|
||||
"note": "resource_id_case exact"
|
||||
},
|
||||
{
|
||||
"id": "eq-004",
|
||||
"statute": "stand-ins",
|
||||
"a": "Support",
|
||||
"b": "support",
|
||||
"equivalent": false,
|
||||
"note": "only resource ids fold; local_part_case exact"
|
||||
},
|
||||
{
|
||||
"id": "eq-005",
|
||||
"statute": "stand-ins",
|
||||
"a": "ticket=gs-1+comments",
|
||||
"b": "ticket=gs-1",
|
||||
"equivalent": true,
|
||||
"note": "facet does not change the target"
|
||||
}
|
||||
],
|
||||
"destination": [
|
||||
{
|
||||
"id": "dt-001",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=GS-1+comments",
|
||||
"needs": [],
|
||||
"expect": "ticket=GS-1+comments"
|
||||
},
|
||||
{
|
||||
"id": "dt-002",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=GS-85QJ8WC09P+comments",
|
||||
"needs": [
|
||||
"resource-alternate",
|
||||
"lowercase"
|
||||
],
|
||||
"expect": "ticket--gs-85qj8wc09p+comments",
|
||||
"note": "IONOS forwarding form"
|
||||
},
|
||||
{
|
||||
"id": "dt-003",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=GS-85QJ8WC09P+comments",
|
||||
"needs": [
|
||||
"lowercase"
|
||||
],
|
||||
"expect": "ticket=gs-85qj8wc09p+comments",
|
||||
"note": "Google Groups"
|
||||
},
|
||||
{
|
||||
"id": "dt-004",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket!callback",
|
||||
"needs": [
|
||||
"interaction-alternate"
|
||||
],
|
||||
"expect": "ticket---callback"
|
||||
},
|
||||
{
|
||||
"id": "dt-005",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "support",
|
||||
"needs": [
|
||||
"resource-alternate",
|
||||
"lowercase"
|
||||
],
|
||||
"expect": "support",
|
||||
"note": "transforms apply only where they change something"
|
||||
},
|
||||
{
|
||||
"id": "dt-006",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket!callback",
|
||||
"needs": [
|
||||
"drop-interaction"
|
||||
],
|
||||
"allow_lossy": true,
|
||||
"expect": "ticket"
|
||||
},
|
||||
{
|
||||
"id": "dt-101",
|
||||
"statute": "no-stand-ins",
|
||||
"local_part": "ticket=GS-1",
|
||||
"needs": [
|
||||
"resource-alternate"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "statute does not enable the stand-in"
|
||||
},
|
||||
{
|
||||
"id": "dt-102",
|
||||
"statute": "v0.1",
|
||||
"local_part": "ticket=GS-1",
|
||||
"needs": [
|
||||
"resource-alternate"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "v0.1 statute"
|
||||
},
|
||||
{
|
||||
"id": "dt-103",
|
||||
"statute": "stand-ins-exact-ids",
|
||||
"local_part": "ticket=GS-1",
|
||||
"needs": [
|
||||
"lowercase"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "lower-casing changes an exact id"
|
||||
},
|
||||
{
|
||||
"id": "dt-104",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=RE=2026-4711",
|
||||
"needs": [
|
||||
"resource-alternate"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "id contains '='; TYPE--ID would split at the first --"
|
||||
},
|
||||
{
|
||||
"id": "dt-105",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=gs-1+Comments",
|
||||
"needs": [
|
||||
"lowercase"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "facet case would change"
|
||||
},
|
||||
{
|
||||
"id": "dt-106",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"needs": [
|
||||
"resource-alternate"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "65 octets after the transform"
|
||||
},
|
||||
{
|
||||
"id": "dt-107",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=gs-1+comments",
|
||||
"needs": [
|
||||
"drop-facet"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "lossy transform without explicit request"
|
||||
},
|
||||
{
|
||||
"id": "dt-108",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=gs-1!chat",
|
||||
"needs": [
|
||||
"interaction-alternate"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "mode not enabled by the statute"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -220,8 +220,9 @@ def cmd_lint(args):
|
|||
|
||||
def cmd_test(args):
|
||||
from mas_conformance import statute_cases, statute_document
|
||||
from mas_conformance.runner import run_parser_corpus
|
||||
from mas_conformance.runner import run_parser_corpus, run_statute_aware_corpus
|
||||
rep = run_parser_corpus()
|
||||
aware = run_statute_aware_corpus()
|
||||
st_fail = []
|
||||
for c in statute_cases():
|
||||
fs = lint(statute_document(c["file"]), c["host"])
|
||||
|
|
@ -238,7 +239,10 @@ def cmd_test(args):
|
|||
print(f"statute corpus: {n_st - len(st_fail)}/{n_st} passed")
|
||||
for fid, file, why in st_fail:
|
||||
print(f" FAIL {fid} {file}: {why}")
|
||||
return 0 if rep.ok and not st_fail else 1
|
||||
print(f"v0.2 statute-aware corpus: {aware.passed}/{aware.passed + len(aware.failures)} passed")
|
||||
for fid, subject, why in aware.failures:
|
||||
print(f" FAIL {fid} {subject!r}: {why}")
|
||||
return 0 if rep.ok and not st_fail and aware.ok else 1
|
||||
|
||||
|
||||
def build_parser():
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
`addresses.json` is vendored unchanged from the spec bundle
|
||||
(`mas-v0.1-spec/conformance/addresses.json`) and is normative.
|
||||
`properties.json` adds implementation-independent property and boundary
|
||||
cases. `statutes/` holds statute validation cases. Every language
|
||||
cases. `statutes/` holds statute validation cases. `statute_aware.json` is
|
||||
vendored unchanged from the v0.2 draft bundle
|
||||
(`mas-v0.2-spec/conformance/statute-aware.json`). Every language
|
||||
implementation should consume these files unchanged.
|
||||
"""
|
||||
|
||||
|
|
@ -32,3 +34,8 @@ def statute_cases() -> list[dict]:
|
|||
|
||||
def statute_document(file: str) -> dict:
|
||||
return _load(f"statutes/{file}")
|
||||
|
||||
|
||||
def statute_aware_fixtures() -> dict:
|
||||
"""v0.2: {statutes, interpretation, equivalence, destination}."""
|
||||
return _load("statute_aware.json")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from dataclasses import dataclass, field
|
|||
|
||||
from mas_core.parser import MasSyntaxError, parse_for_profile
|
||||
|
||||
from . import address_fixtures, property_fixtures
|
||||
from . import address_fixtures, property_fixtures, statute_aware_fixtures
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -48,3 +48,45 @@ def run_parser_corpus() -> Report:
|
|||
else:
|
||||
report.passed += 1
|
||||
return report
|
||||
|
||||
|
||||
def run_statute_aware_corpus() -> Report:
|
||||
"""v0.2 stand-ins, resource_id_case equivalence and destination transforms."""
|
||||
from mas_core.generator import CannotExpress, for_destination
|
||||
from mas_core.parser import parse_address
|
||||
from mas_core.policy import resolve
|
||||
from mas_core.statute import build_statute
|
||||
|
||||
fx = statute_aware_fixtures()
|
||||
statutes = {k: build_statute(doc, doc["namespace"]) for k, doc in fx["statutes"].items()}
|
||||
|
||||
def sem(st, lp):
|
||||
return resolve(parse_address(f"{lp}@{st.namespace}"), st)
|
||||
|
||||
report = Report()
|
||||
|
||||
def record(f, subject, problem):
|
||||
if problem:
|
||||
report.failures.append((f["id"], subject, problem))
|
||||
else:
|
||||
report.passed += 1
|
||||
|
||||
for f in fx["interpretation"]:
|
||||
d = sem(statutes[f["statute"]], f["local_part"]).to_dict()
|
||||
got = (d["canonical_local_part"], d["stand_ins"])
|
||||
record(f, f["local_part"], None if got == (f["canonical"], f["stand_ins"])
|
||||
else f"got {got}, expected {(f['canonical'], f['stand_ins'])}")
|
||||
for f in fx["equivalence"]:
|
||||
st = statutes[f["statute"]]
|
||||
same = sem(st, f["a"]).equivalence_key == sem(st, f["b"]).equivalence_key
|
||||
record(f, f"{f['a']} ~ {f['b']}", None if same == f["equivalent"]
|
||||
else f"equivalent={same}, expected {f['equivalent']}")
|
||||
for f in fx["destination"]:
|
||||
try:
|
||||
got = for_destination(f["local_part"], statutes[f["statute"]], f["needs"],
|
||||
allow_lossy=f.get("allow_lossy", False)).local_part
|
||||
except CannotExpress as e:
|
||||
got, why = None, e.code
|
||||
record(f, f["local_part"], None if got == f["expect"]
|
||||
else f"got {got!r}" + (f" ({why})" if got is None else "") + f", expected {f['expect']!r}")
|
||||
return report
|
||||
|
|
|
|||
426
mas_conformance/statute_aware.json
Normal file
426
mas_conformance/statute_aware.json
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
{
|
||||
"description": "MAS v0.2 statute-aware cases: stand-in interpretation (6.3), resource_id_case equivalence (8), destination-transform results and refusals (12.1). Each case names a statute in 'statutes'; the host is the statute's namespace.",
|
||||
"statutes": {
|
||||
"stand-ins": {
|
||||
"mas_version": "0.2",
|
||||
"namespace": "great-service.example",
|
||||
"profiles": {
|
||||
"p1": true,
|
||||
"p2": true
|
||||
},
|
||||
"resource_types": {
|
||||
"ticket": {
|
||||
"enabled": true,
|
||||
"description": "Customer-service ticket",
|
||||
"identifier_policy": "non-guessable-preferred"
|
||||
}
|
||||
},
|
||||
"interaction_modes": {
|
||||
"callback": {
|
||||
"enabled": true,
|
||||
"description": "Continue by a voice callback"
|
||||
}
|
||||
},
|
||||
"equivalence": {
|
||||
"local_part_case": "exact",
|
||||
"dot_folding": false,
|
||||
"resource_id_case": "insensitive"
|
||||
},
|
||||
"addresses": {
|
||||
"support": {
|
||||
"kind": "function"
|
||||
},
|
||||
"ticket": {
|
||||
"kind": "function",
|
||||
"intake_for": "ticket"
|
||||
}
|
||||
},
|
||||
"alternate_syntax": {
|
||||
"resource": true,
|
||||
"interaction": true
|
||||
}
|
||||
},
|
||||
"stand-ins-exact-ids": {
|
||||
"mas_version": "0.2",
|
||||
"namespace": "great-service.example",
|
||||
"profiles": {
|
||||
"p1": true,
|
||||
"p2": true
|
||||
},
|
||||
"resource_types": {
|
||||
"ticket": {
|
||||
"enabled": true,
|
||||
"description": "Customer-service ticket",
|
||||
"identifier_policy": "non-guessable-preferred"
|
||||
}
|
||||
},
|
||||
"interaction_modes": {
|
||||
"callback": {
|
||||
"enabled": true,
|
||||
"description": "Continue by a voice callback"
|
||||
}
|
||||
},
|
||||
"equivalence": {
|
||||
"local_part_case": "exact",
|
||||
"dot_folding": false,
|
||||
"resource_id_case": "exact"
|
||||
},
|
||||
"addresses": {
|
||||
"support": {
|
||||
"kind": "function"
|
||||
},
|
||||
"ticket": {
|
||||
"kind": "function",
|
||||
"intake_for": "ticket"
|
||||
}
|
||||
},
|
||||
"alternate_syntax": {
|
||||
"resource": true,
|
||||
"interaction": true
|
||||
}
|
||||
},
|
||||
"no-stand-ins": {
|
||||
"mas_version": "0.2",
|
||||
"namespace": "great-service.example",
|
||||
"profiles": {
|
||||
"p1": true,
|
||||
"p2": true
|
||||
},
|
||||
"resource_types": {
|
||||
"ticket": {
|
||||
"enabled": true,
|
||||
"description": "Customer-service ticket",
|
||||
"identifier_policy": "non-guessable-preferred"
|
||||
}
|
||||
},
|
||||
"interaction_modes": {
|
||||
"callback": {
|
||||
"enabled": true,
|
||||
"description": "Continue by a voice callback"
|
||||
}
|
||||
},
|
||||
"equivalence": {
|
||||
"local_part_case": "exact",
|
||||
"dot_folding": false,
|
||||
"resource_id_case": "exact"
|
||||
},
|
||||
"addresses": {
|
||||
"support": {
|
||||
"kind": "function"
|
||||
},
|
||||
"ticket": {
|
||||
"kind": "function",
|
||||
"intake_for": "ticket"
|
||||
}
|
||||
},
|
||||
"alternate_syntax": {
|
||||
"resource": false,
|
||||
"interaction": false
|
||||
}
|
||||
},
|
||||
"v0.1": {
|
||||
"mas_version": "0.1",
|
||||
"namespace": "great-service.example",
|
||||
"profiles": {
|
||||
"p1": true,
|
||||
"p2": true
|
||||
},
|
||||
"resource_types": {
|
||||
"ticket": {
|
||||
"enabled": true,
|
||||
"description": "Customer-service ticket",
|
||||
"identifier_policy": "non-guessable-preferred"
|
||||
}
|
||||
},
|
||||
"interaction_modes": {
|
||||
"callback": {
|
||||
"enabled": true,
|
||||
"description": "Continue by a voice callback"
|
||||
}
|
||||
},
|
||||
"equivalence": {
|
||||
"local_part_case": "exact",
|
||||
"dot_folding": false
|
||||
},
|
||||
"addresses": {
|
||||
"support": {
|
||||
"kind": "function"
|
||||
},
|
||||
"ticket": {
|
||||
"kind": "function",
|
||||
"intake_for": "ticket"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"interpretation": [
|
||||
{
|
||||
"id": "si-001",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket---callback",
|
||||
"canonical": "ticket!callback",
|
||||
"stand_ins": [
|
||||
"interaction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "si-002",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket--gs1---callback",
|
||||
"canonical": "ticket=gs1!callback",
|
||||
"stand_ins": [
|
||||
"interaction",
|
||||
"resource"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "si-003",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket--a---b",
|
||||
"canonical": "ticket=a---b",
|
||||
"stand_ins": [
|
||||
"resource"
|
||||
],
|
||||
"note": "b is not an enabled mode"
|
||||
},
|
||||
{
|
||||
"id": "si-004",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket--gs1+comments",
|
||||
"canonical": "ticket=gs1+comments",
|
||||
"stand_ins": [
|
||||
"resource"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "si-005",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "support---callback",
|
||||
"canonical": "support!callback",
|
||||
"stand_ins": [
|
||||
"interaction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "si-006",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "invoice--x1",
|
||||
"canonical": "invoice--x1",
|
||||
"stand_ins": [],
|
||||
"note": "undeclared resource type"
|
||||
},
|
||||
{
|
||||
"id": "si-007",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=gs1!callback",
|
||||
"canonical": "ticket=gs1!callback",
|
||||
"stand_ins": [],
|
||||
"note": "already canonical"
|
||||
},
|
||||
{
|
||||
"id": "si-008",
|
||||
"statute": "no-stand-ins",
|
||||
"local_part": "ticket--gs1",
|
||||
"canonical": "ticket--gs1",
|
||||
"stand_ins": [],
|
||||
"note": "switch off"
|
||||
},
|
||||
{
|
||||
"id": "si-009",
|
||||
"statute": "no-stand-ins",
|
||||
"local_part": "ticket---callback",
|
||||
"canonical": "ticket---callback",
|
||||
"stand_ins": []
|
||||
},
|
||||
{
|
||||
"id": "si-010",
|
||||
"statute": "v0.1",
|
||||
"local_part": "ticket--gs1",
|
||||
"canonical": "ticket--gs1",
|
||||
"stand_ins": [],
|
||||
"note": "v0.1 statute has no stand-ins"
|
||||
}
|
||||
],
|
||||
"equivalence": [
|
||||
{
|
||||
"id": "eq-001",
|
||||
"statute": "stand-ins",
|
||||
"a": "ticket=GS-1",
|
||||
"b": "ticket=gs-1",
|
||||
"equivalent": true,
|
||||
"note": "resource_id_case insensitive"
|
||||
},
|
||||
{
|
||||
"id": "eq-002",
|
||||
"statute": "stand-ins",
|
||||
"a": "ticket--GS-85QJ8WC09P",
|
||||
"b": "ticket=gs-85qj8wc09p",
|
||||
"equivalent": true,
|
||||
"note": "stand-in + insensitive id"
|
||||
},
|
||||
{
|
||||
"id": "eq-003",
|
||||
"statute": "stand-ins-exact-ids",
|
||||
"a": "ticket=GS-1",
|
||||
"b": "ticket=gs-1",
|
||||
"equivalent": false,
|
||||
"note": "resource_id_case exact"
|
||||
},
|
||||
{
|
||||
"id": "eq-004",
|
||||
"statute": "stand-ins",
|
||||
"a": "Support",
|
||||
"b": "support",
|
||||
"equivalent": false,
|
||||
"note": "only resource ids fold; local_part_case exact"
|
||||
},
|
||||
{
|
||||
"id": "eq-005",
|
||||
"statute": "stand-ins",
|
||||
"a": "ticket=gs-1+comments",
|
||||
"b": "ticket=gs-1",
|
||||
"equivalent": true,
|
||||
"note": "facet does not change the target"
|
||||
}
|
||||
],
|
||||
"destination": [
|
||||
{
|
||||
"id": "dt-001",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=GS-1+comments",
|
||||
"needs": [],
|
||||
"expect": "ticket=GS-1+comments"
|
||||
},
|
||||
{
|
||||
"id": "dt-002",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=GS-85QJ8WC09P+comments",
|
||||
"needs": [
|
||||
"resource-alternate",
|
||||
"lowercase"
|
||||
],
|
||||
"expect": "ticket--gs-85qj8wc09p+comments",
|
||||
"note": "IONOS forwarding form"
|
||||
},
|
||||
{
|
||||
"id": "dt-003",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=GS-85QJ8WC09P+comments",
|
||||
"needs": [
|
||||
"lowercase"
|
||||
],
|
||||
"expect": "ticket=gs-85qj8wc09p+comments",
|
||||
"note": "Google Groups"
|
||||
},
|
||||
{
|
||||
"id": "dt-004",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket!callback",
|
||||
"needs": [
|
||||
"interaction-alternate"
|
||||
],
|
||||
"expect": "ticket---callback"
|
||||
},
|
||||
{
|
||||
"id": "dt-005",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "support",
|
||||
"needs": [
|
||||
"resource-alternate",
|
||||
"lowercase"
|
||||
],
|
||||
"expect": "support",
|
||||
"note": "transforms apply only where they change something"
|
||||
},
|
||||
{
|
||||
"id": "dt-006",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket!callback",
|
||||
"needs": [
|
||||
"drop-interaction"
|
||||
],
|
||||
"allow_lossy": true,
|
||||
"expect": "ticket"
|
||||
},
|
||||
{
|
||||
"id": "dt-101",
|
||||
"statute": "no-stand-ins",
|
||||
"local_part": "ticket=GS-1",
|
||||
"needs": [
|
||||
"resource-alternate"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "statute does not enable the stand-in"
|
||||
},
|
||||
{
|
||||
"id": "dt-102",
|
||||
"statute": "v0.1",
|
||||
"local_part": "ticket=GS-1",
|
||||
"needs": [
|
||||
"resource-alternate"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "v0.1 statute"
|
||||
},
|
||||
{
|
||||
"id": "dt-103",
|
||||
"statute": "stand-ins-exact-ids",
|
||||
"local_part": "ticket=GS-1",
|
||||
"needs": [
|
||||
"lowercase"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "lower-casing changes an exact id"
|
||||
},
|
||||
{
|
||||
"id": "dt-104",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=RE=2026-4711",
|
||||
"needs": [
|
||||
"resource-alternate"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "id contains '='; TYPE--ID would split at the first --"
|
||||
},
|
||||
{
|
||||
"id": "dt-105",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=gs-1+Comments",
|
||||
"needs": [
|
||||
"lowercase"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "facet case would change"
|
||||
},
|
||||
{
|
||||
"id": "dt-106",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"needs": [
|
||||
"resource-alternate"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "65 octets after the transform"
|
||||
},
|
||||
{
|
||||
"id": "dt-107",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=gs-1+comments",
|
||||
"needs": [
|
||||
"drop-facet"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "lossy transform without explicit request"
|
||||
},
|
||||
{
|
||||
"id": "dt-108",
|
||||
"statute": "stand-ins",
|
||||
"local_part": "ticket=gs-1!chat",
|
||||
"needs": [
|
||||
"interaction-alternate"
|
||||
],
|
||||
"expect": null,
|
||||
"reason": "mode not enabled by the statute"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -128,8 +128,9 @@ def for_destination(local_part: str, statute: "Statute", needs: Iterable[str] =
|
|||
expected = resolve(ParsedLocalPart(raw=_render(target, facet, interaction), target=target,
|
||||
facet=facet, interaction=interaction), statute)
|
||||
got = resolve(out, statute)
|
||||
if (got.equivalence_key, got.facet, got.interaction) != \
|
||||
(expected.equivalence_key, expected.facet, expected.interaction):
|
||||
raise CannotExpress("would-be-misread",
|
||||
f"{text!r} would be read as {got.to_dict()['canonical_local_part']!r}")
|
||||
fields = ("equivalence_key", "facet", "interaction")
|
||||
diff = [f"{k} {getattr(got, k)!r} instead of {getattr(expected, k)!r}"
|
||||
for k in fields if getattr(got, k) != getattr(expected, k)]
|
||||
if diff:
|
||||
raise CannotExpress("would-be-misread", f"{text!r} would be read with " + ", ".join(diff))
|
||||
return DestinationAddress(text, tuple(applied), tuple(warnings))
|
||||
|
|
|
|||
|
|
@ -138,3 +138,16 @@ def test_vendored_v02_schema_matches_draft_bundle():
|
|||
a = (ROOT / "mas-v0.2-spec/schema/mas-statute-v0.2.schema.json").read_bytes()
|
||||
b = (ROOT / "mas_core/schema/mas-statute-v0.2.schema.json").read_bytes()
|
||||
assert a == b
|
||||
|
||||
|
||||
def test_vendored_v02_statute_aware_corpus_matches_draft_bundle():
|
||||
a = (ROOT / "mas-v0.2-spec/conformance/statute-aware.json").read_bytes()
|
||||
b = (ROOT / "mas_conformance/statute_aware.json").read_bytes()
|
||||
assert a == b
|
||||
|
||||
|
||||
def test_v02_statute_aware_corpus():
|
||||
from mas_conformance.runner import run_statute_aware_corpus
|
||||
rep = run_statute_aware_corpus()
|
||||
assert rep.ok, rep.failures
|
||||
assert rep.passed == 29
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue