@@ -65,27 +65,44 @@ If ANY are found, verification fails immediately and you must rewrite.
6565- \`SchemaAdapter\` or \`make_schema_adapter()\` wrapper classes
6666- \`create_model()\` from pydantic — no dynamic model creation
6767- Any "helper" or "utility" that builds models from schema dicts at runtime
68-
69- **Banned naming patterns:**
70- - \`Input2\`, \`Output2\`, \`Init2\` — meaningless suffixed names
71- - \`ErrorVariant1\`, \`ErrorVariant2\`, ... — numbered error classes
68+ - \`RiverTypeAdapter\` or any custom TypeAdapter subclass — use plain
69+ \`TypeAdapter\` only. Do NOT override \`json_schema()\` in any way.
70+ - \`schema_override_json\`, \`schema_override\`, or \`_schema_json\` — do NOT
71+ embed or cache raw JSON schemas to return from \`json_schema()\`
72+ - Redefining \`UncaughtError\`, \`UnexpectedDisconnectError\`,
73+ \`InvalidRequestError\`, or \`CancelError\` outside of \`_errors.py\` — these
74+ standard River errors must be defined ONCE and imported everywhere
75+
76+ **Banned naming patterns (the verifier enforces these with a regex):**
77+ - \`Variant\\d+\` anywhere in a class name — \`ErrorsVariant1\`, \`OutputVariant2\`,
78+ \`AgentExecErrorsVariant3\`, \`ConnectErrorsVariant9\` are ALL banned
79+ - \`Input2\`, \`Output2\`, \`Init2\`, \`Errors3\` — numbered type suffixes
7280- \`OutputVariant1Variant2\` — nested numbering
7381- \`Input2ArtifactServicesItemDevelopmentRunVariant1\` — path-based names
7482 derived from JSON Schema structure
7583- Any class name that a developer cannot understand without looking at
7684 the schema
7785
86+ The verifier scans every class definition with a regex. If ANY class
87+ name contains \`Variant\` followed by a digit, verification fails.
88+ Name error classes after their \`code\` literal, \`$kind\` variants after
89+ their kind value, and input/output types after the procedure or TS schema.
90+
7891**Required:**
7992- Every Input, Output, and Error type MUST be a concrete \`BaseModel\` subclass
8093 with explicitly declared, typed fields
8194- \`TypeAdapter(MyModel).json_schema()\` must produce correct schemas through
82- Pydantic's own schema generation — NOT through a hardcoded override
95+ Pydantic's own native schema generation — NOT through hardcoded overrides
96+ or JSON embedding
8397- Every class MUST have a meaningful name derived from reading the TypeScript
8498 source code. Examples:
8599 - Error with \`code: Literal['NOT_FOUND']\` → \`NotFoundError\`
86100 - Error with \`code: Literal['DISK_QUOTA_EXCEEDED']\` → \`DiskQuotaExceededError\`
87101 - Output with \`$kind: 'finished'\` → \`FinishedOutput\` or \`ExitInfo\` (from TS)
88102 - A ping procedure's output → \`PingOutput\`, not \`Output2\`
103+ - The four standard River error classes (UncaughtError, UnexpectedDisconnectError,
104+ InvalidRequestError, CancelError) must be defined ONLY in \`_errors.py\` and
105+ imported from there in every service module. Do NOT redefine them.
89106
90107
91108## File access scope
@@ -442,6 +459,17 @@ utility functions, schema helpers, or dynamic class factories.
442459
4434608. **Intersections.** For \`Type.Intersect([A, B])\`, flatten all properties
444461 into a single BaseModel. Do NOT try to represent \`allOf\` in Pydantic.
462+ The verifier normalises \`allOf\` by merging schemas, so a flat model
463+ is correct. Do NOT subclass TypeAdapter or embed raw JSON to handle
464+ \`allOf\` — just merge all properties into one model.
465+
466+ 9. **Error deduplication within a service.** Many procedures in a service
467+ share the same error types (e.g. all filesystem operations share
468+ \`NotFoundError\`, \`PermissionDeniedError\`, etc.). Define each unique
469+ error class ONCE at the top of the service file, then reference it in
470+ every procedure's error union. Do NOT create separate copies like
471+ \`ReadNotFoundError\`, \`WriteNotFoundError\`, \`MkdirNotFoundError\` — if
472+ they have the same \`code\` literal and fields, they are the same class.
445473
446474
447475### The _schema_map.py module
@@ -519,6 +547,17 @@ Repeat until verification passes.**
519547Because of these normalisations, use \`bytes\` for Uint8Array fields and your
520548preferred Literal style for string unions. The verifier handles the rest.
521549
550+ ### How the verifier handles allOf (intersections)
551+
552+ When comparing schemas with \`allOf\`, the verifier flattens/merges the
553+ \`allOf\` entries into a single object schema, then compares. This means
554+ if you flatten an \`allOf\` into a single BaseModel (as recommended), the
555+ schemas will match. You do NOT need to make Pydantic produce \`allOf\` —
556+ the verifier normalises both sides.
557+
558+ Do NOT create custom TypeAdapter subclasses or embed raw JSON schemas
559+ to handle \`allOf\` cases. Just flatten the properties into one model.
560+
522561
523562## How to approach this
524563
@@ -540,10 +579,29 @@ This is a reasonable way to handle 50+ services efficiently.
540579 within a service (e.g. filesystem errors) should be defined ONCE at the
541580 top of the service file and reused.
542581- Shared error types across ALL services (the four standard River errors)
543- must come from \`_errors.py\`.
582+ must come from \`_errors.py\` — do NOT redefine them locally .
544583
545584If your final output still has numbered names like \`ErrorVariant1\`,
546- \`Input2\`, \`OutputVariant1Variant2\`, it will be **discarded**.
585+ \`Input2\`, \`OutputVariant1Variant2\`, **the verifier will reject it**.
586+ The verifier enforces this with a regex — any class name containing
587+ \`Variant\` followed by a digit will fail.
588+
589+ ### Critical: the verifier WILL catch mechanical names
590+
591+ Previous attempts failed because a scaffolding script generated all files
592+ with numbered names (e.g. \`ReadErrorsVariant1\` through \`ReadErrorsVariant16\`)
593+ and then never renamed them using the TypeScript source.
594+
595+ The verifier now enforces:
596+ - **No \`Variant\\d+\` in any class name** (regex enforced)
597+ - **No redefinition of standard River errors** outside \`_errors.py\`
598+ - **No \`RiverTypeAdapter\` or \`schema_override\` patterns**
599+
600+ If you write a scaffolding script, it MUST produce clean names from the
601+ start. The easiest way: for error classes, read the \`code\` literal from
602+ the JSON Schema \`const\` field and convert it to PascalCase + "Error"
603+ (e.g. \`NOT_FOUND\` → \`NotFoundError\`). For \`$kind\` variants, use the
604+ kind value. For input/output, use \`<ProcedureName>Input/Output\`.
547605
548606### Where names come from
549607
0 commit comments