@@ -85,13 +85,30 @@ code and every \`$kind\` value found in schema.json:
8585 "output": "Output",
8686 "result": "Result",
8787 ...
88+ },
89+ "tsExportNames": {
90+ "shellExec": ["ExitInfo", "OutputChunk", "MonitorResponse", "ShellOutput", ...],
91+ "artifact": ["Artifact", "CreateArtifactOptions", ...],
92+ ...
93+ },
94+ "tsSharedExportNames": {
95+ "lib/fs": ["FilesystemError"],
96+ "lib/shell": ["ShellOutput", "ShellError"],
97+ ...
8898 }
8999}
90100\`\`\`
91101
92102**You MUST use the exact class names from this file** for error classes and
93103\`$kind\` variants. Do NOT invent your own PascalCase conversion.
94104
105+ **For data types**, check \`tsExportNames\` and \`tsSharedExportNames\`. These
106+ are names extracted from the TypeScript source (with the "Schema" suffix
107+ removed). When you see a TypeBox schema in a service's \`schemas.ts\` named
108+ e.g. \`ExitInfo\`, the matching entry will appear in
109+ \`tsExportNames["shellExec"]\` as \`"ExitInfo"\`. **Use these names for your
110+ Python classes** instead of mechanical path-derived names.
111+
95112The verifier rejects class names with 4+ consecutive uppercase letters
96113(e.g. \`NOTFOUNDError\`, \`CGROUPCLEANUPERRORError\` — these are BANNED).
97114
@@ -112,10 +129,14 @@ verification fails immediately.
112129- \`WithJsonSchema\` — do NOT attach raw JSON schemas to \`Any\` via annotations
113130- \`json.loads(\` — do NOT embed raw JSON schema strings in generated code
114131- Loading \`schema.json\` at runtime in \`_schema_map.py\` or any module
132+ - Monkey-patching \`.json_schema\` on TypeAdapter instances (\`.json_schema = \`)
133+ - \`_bind_reference\`, \`_load_reference\`, \`frozen_schema\` — do NOT replace
134+ adapter schemas with reference data from schema.json
115135- Raw JSON Schema dicts embedded as Python dict literals or JSON strings
116136- Any helper/utility that builds models from schema dicts at runtime
117137
118138**Banned naming patterns (regex-enforced):**
139+ - \`Duplicate\` or \`Triplicate\` in a class name — reuse the same class instead
119140- \`Variant\\d+\` anywhere in a class name — ALL banned
120141- \`Input2\`, \`Output2\`, \`Errors3\` — numbered suffixes
121142- 4+ consecutive uppercase letters: \`NOTFOUNDError\`, \`PTYERRORError\` — banned
@@ -406,7 +427,10 @@ generated/
4064278. **Intersections.** Flatten all properties into a single BaseModel.
407428
4084299. **Error deduplication.** Same \`code\` literal + same fields = same class.
409- Define once at the top of the service file.
430+ Define once at the top of the service file. The verifier deduplicates
431+ structurally identical \`anyOf\` variants, so if the same error appears
432+ multiple times in a composed union, **reuse the same class** — do NOT
433+ create \`FooDuplicate\` or \`FooTriplicate\` copies.
410434
411435
412436### The _schema_map.py module
@@ -430,6 +454,16 @@ SCHEMA_MAP: dict = {
430454
431455Every service and procedure from schema.json must be represented.
432456
457+ **CRITICAL:** \`_schema_map.py\` must ONLY import TypeAdapter instances from
458+ the service modules and assemble them into the dict. It must NOT:
459+ - Load \`schema.json\` at runtime
460+ - Monkey-patch \`.json_schema\` methods on adapters
461+ - Use \`copy.deepcopy\` to cache reference schemas
462+ - Contain any function named \`_bind_reference_schemas\` or \`_load_reference_services\`
463+
464+ The adapters' \`.json_schema()\` output must come entirely from the Pydantic
465+ models they wrap. If verification fails, fix the **models**, not the schema map.
466+
433467
434468## Verification
435469
@@ -452,6 +486,13 @@ Exit codes: 0 = success, 1 = mismatches, 2 = import error or banned patterns.
452486- \`additionalProperties\` — stripped
453487- \`enum\` → normalised to \`anyOf\` with \`const\` entries
454488- \`allOf\` → flattened into merged object
489+ - \`anyOf\` / \`oneOf\` → structurally identical variants deduplicated
490+
491+ The verifier deduplicates \`anyOf\` variants, so if the same error type
492+ (e.g. \`UncaughtError\`) appears multiple times in a composed error union,
493+ **reuse the same Python class** — do NOT create \`UncaughtErrorDuplicate\`
494+ or similar copies. Use a plain union (not discriminated) when the same
495+ discriminator value appears more than once.
455496
456497Use \`bytes\` for Uint8Array fields. The verifier handles the rest.
457498
@@ -721,6 +762,27 @@ actual models are never tested through the schema map. After refactoring,
721762Many files import \`StringConstraints\`, \`ConfigDict\`, etc. without using them.
722763Clean up.
723764
765+ ### 7. Duplicate/Triplicate error classes
766+ Classes like \`UncaughtErrorDuplicate\`, \`NotFoundErrorDuplicate\`,
767+ \`UncaughtErrorTriplicate\` exist because the same error code appears
768+ multiple times in composed \`anyOf\` unions, and Pydantic discriminated
769+ unions require unique types. The verifier now **deduplicates structurally
770+ identical anyOf variants**, so you can reuse the same error class.
771+ **Delete all \`*Duplicate\` and \`*Triplicate\` classes.** Use the original
772+ class in a plain union (without \`Field(discriminator=...)\`) when the same
773+ discriminator value appears more than once.
774+
775+ ### 8. Duplicate field declarations
776+ Some classes have the same field declared twice (e.g. \`extras: Foo\` on two
777+ consecutive lines). Python silently shadows the first. The verifier now
778+ catches this. **Remove duplicate field declarations.**
779+
780+ ### 9. Data type names not from TypeScript source
781+ Top-level schema names like \`ExitInfo\`, \`MonitorResponse\`, \`ShellOutput\`
782+ from the TypeScript source don't appear — everything has path-derived names.
783+ Check \`naming_hints.json\` → \`tsExportNames\` for pre-extracted names per
784+ service, and \`tsSharedExportNames\` for shared lib/ types.
785+
724786
725787## File access scope
726788
@@ -739,25 +801,35 @@ Python is \`./verify\`. Do NOT write or run Python scripts.
739801
740802## How to approach the refactoring
741803
742- ### Phase 1: Study the TypeScript source to build a naming map
804+ ### Phase 1: Study naming_hints.json and TypeScript source
805+
806+ Before changing any Python code:
743807
744- Before changing any Python code, read the TypeScript source systematically:
808+ 1. **Read \`naming_hints.json\`** — it contains:
809+ - \`errorCodeToClassName\`: error code → Python class name mapping
810+ - \`kindValueToClassName\`: \`$kind\` value → class name prefix
811+ - \`tsExportNames\`: per-service list of TypeBox schema names from TS source
812+ (e.g. \`"shellExec": ["ExitInfo", "OutputChunk", "MonitorResponse"]\`)
813+ - \`tsSharedExportNames\`: shared lib/ TypeBox schema names
814+ (e.g. \`"lib/fs": ["FilesystemError"]\`)
745815
746- 1. Read \`${ opts . serverSrcPath } /index.ts\` to get the service list.
816+ **Use these names** to rename Python classes. The \`tsExportNames\` entries
817+ are the authoritative TypeScript names with "Schema" suffix already removed.
747818
748- 2. For each service, read its \`schemas.ts\` and \`index.ts\`. Build a mental
749- map of:
750- - **Named exports**: \`export const ExitInfo = Type.Object({...})\` →
751- the Python class should be called \`ExitInfo\`
819+ 2. Read \`${ opts . serverSrcPath } /index.ts\` to get the service list.
820+
821+ 3. For each service, read its \`schemas.ts\` and \`index.ts\`. Cross-reference
822+ with \`tsExportNames[serviceName]\` to confirm which TS names map to which
823+ Python classes. Look for:
824+ - **Named exports**: match to entries in \`tsExportNames\`
752825 - **Union variant names**: if TS has \`const MonitorResponse = Type.Union([
753826 NotStartedState, RunningState, FinishedState])\`, those variant names
754827 should appear in Python
755- - **Shared types in lib/**: \`lib/fs/errors.ts\`, \`lib/shell/schemas.ts\`
756- — these define types used across many services
828+ - **Shared types in lib/**: match to entries in \`tsSharedExportNames\`
757829
758- 3 . Read the shared \`lib/\` directories:
830+ 4 . Read the shared \`lib/\` directories:
759831 - \`${ opts . serverSrcPath } /../lib/\` or \`${ opts . serverSrcPath } /lib/\` if it exists
760- - Note which error types and data types are shared
832+ - Cross-reference with \`tsSharedExportNames\`
761833
762834### Phase 2: Refactor shared types
763835
@@ -806,8 +878,15 @@ For **each** service file:
806878
807879### Phase 4: Rebuild _schema_map.py and verify
808880
809- 14. **Rewrite \`_schema_map.py\`** to import TypeAdapters from the refactored
810- service modules. Do NOT use monkey-patched adapters or raw dicts.
881+ 14. **Rewrite \`_schema_map.py\`** to be a simple import-and-assemble module:
882+ - Import TypeAdapter instances from each service module
883+ - Assemble them into the \`SCHEMA_MAP\` dict
884+ - Do NOT load \`schema.json\` at runtime
885+ - Do NOT use AST parsing, \`inspect\`, or runtime introspection
886+ - Do NOT monkey-patch \`.json_schema\` on any adapter
887+ - Do NOT use \`_bind_reference_schemas\`, \`_load_reference_services\`,
888+ \`frozen_schema\`, \`copy.deepcopy\`, or any schema override mechanism
889+ - The \`_schema_map.py\` should be ~200 lines of straightforward imports
811890
81289115. **Update \`__init__.py\`** if any service class names changed.
813892
@@ -855,6 +934,25 @@ Exit 0 = pass. Must pass when you're done.
855934- Don't skip services. Every service file must be reviewed and improved.
856935- Don't break imports. If you move a class from \`service.py\` to
857936 \`_errors.py\`, update every file that used the local definition.
937+
938+ ### Run 10 failures (most recent):
939+
940+ - **\`*Duplicate\` / \`*Triplicate\` error classes** — \`UncaughtErrorDuplicate\`,
941+ \`NotFoundErrorDuplicate\`, etc. These are BANNED. The verifier now
942+ deduplicates \`anyOf\` variants, so just reuse the same class. If the same
943+ discriminator value appears more than once in a union, use a plain union
944+ (no \`Field(discriminator=...)\`).
945+
946+ - **Monkey-patched \`_schema_map.py\`** — \`_bind_reference_schemas()\` loaded
947+ \`schema.json\` and replaced each adapter's \`.json_schema\` with a lambda.
948+ This is BANNED. Write a simple import-and-assemble schema map.
949+
950+ - **Duplicate field declarations** — \`ParseError\` had \`extras: ParseErrorExtras\`
951+ on two consecutive lines. This is now caught by the verifier.
952+
953+ - **TS export names still missing** — Classes were still named with path-derived
954+ mechanical names instead of \`ExitInfo\`, \`MonitorResponse\`, etc. Use
955+ \`naming_hints.json\` → \`tsExportNames\` for the correct names.
858956` . trim ( ) ;
859957}
860958
0 commit comments