@terpjs/spec 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.22.0
1
+ 0.23.0
@@ -0,0 +1,27 @@
1
+ {
2
+ "id": "backend/schemas_avoid_positional_tuples",
3
+ "surface": "backend",
4
+ "title": "A schema field never crosses the wire as a positional tuple",
5
+ "intent": "A fixed-length tuple annotation (tuple[str, str], list[tuple[str, int]], tuple[str, ...]) on a schema a client can see or send serialises into the contract as an array whose element types are positional (prefixItems, or the list form of items). Client generators do not agree on that shape: one emits the positional form and another the widened element array, so the two descriptions of the same field are structurally unrelated and the app cannot type its own calls against its own API — the failure surfaces at the call site as an opaque generic-instantiation mismatch, far from the field that caused it, and only with error truncation disabled. A tuple is also a poor contract in its own right: the positions carry meaning that no name records. Name the shape instead — a nested model with named fields when the positions differ in meaning, or a homogeneous sequence (list[str]) when they do not.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_schemas_avoid_positional_tuples"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "_reject_positional_tuple_schemas"
17
+ }
18
+ ],
19
+ "runtime": {
20
+ "applicability": "required",
21
+ "rationale": "The invariant survives into the running app: the generated OpenAPI document is the artifact the client is built from, so the boot-time contract validation walks every component schema and refuses a positional array shape (prefixItems, or items as a list) fail-closed. This catches the vector the source check cannot see — a tuple reaching the contract through a type alias, a generic parameter, or a custom __get_pydantic_core_schema__ — and both halves name the same fix."
22
+ },
23
+ "reference": "Schema fields annotated with a nested BaseSchema model or a homogeneous list[...]; tuple[...] annotations on BaseSchema subclasses and on any class used as a route body or response_model are refused.",
24
+ "opt_out": "# arch-allow-schemas-avoid-positional-tuples: <reason>",
25
+ "guide_topic": "module",
26
+ "corpus": true
27
+ }
@@ -0,0 +1,6 @@
1
+ class Point(BaseSchema):
2
+ lat: float
3
+ lon: float
4
+ class NoteRead(BaseSchema):
5
+ coordinates: Point
6
+ tag_names: list[str]
@@ -0,0 +1,2 @@
1
+ class NoteRead(BaseSchema):
2
+ coordinates: tuple[float, float]
@@ -0,0 +1,2 @@
1
+ class NoteCreate(BaseSchema):
2
+ tags: list[tuple[str, str]]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terpjs/spec",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "The Terp Standard — stack-neutral rule catalog, violation corpus, finding format, and refused-surface declaration (ADRs 0080/0081; packaged per ADR 0082, published per ADR 0086). Data only: consumers resolve the spec root via require.resolve('@terpjs/spec/package.json').",
5
5
  "files": [
6
6
  "VERSION",