Repository navigation
2.1: manifest covers every ts(export) type; coverage == closure-dtos.txt (on #43, merges #13) - #53
Open
cunninghamcard-bit wants to merge 8 commits into
Open
cunninghamcard-bit wants to merge 8 commits into
cunninghamcard-bit wants to merge 8 commits into
Conversation
… exp/2.1-manifest
…osure-dtos.txt - scripts/gen_closure_inventory.py writes docs/plan/closure-dtos.txt (name, category, file; derive(TS)-without-export and aimux-web wire in separate sections). Multi-line #[ts(\n export are counted. - tools/aimux-codegen: extract.rs (syn) builds the type model from aimux-core/src serde attributes, incl. discriminated unions; manifest.rs writes generated/manifest.json (sdk read from fixtures/aisdk/VERSIONS.json, generator, inputHash, schemaRevision) and generated/coverage.txt; both under --check. - Unregistered field types and hand-written serde without a codec fail generation with file:line; coverage mismatch fails and writes nothing. - tests/manifest_test.rs: coverage == list, mutations (removed type, fake list line, extra type, VERSIONS.json change, unregistered type), wire names cross-checked against bindings/node/src/types.
A CRLF checkout (core.autocrlf=true) now yields the same inputHash and a byte-identical manifest. Normalized in code rather than .gitattributes so it holds regardless of checkout. Test: crlf_checkout_gives_identical_manifest.
- closure_dto_list_is_reproducible_from_source: runs gen_closure_inventory.py --check; asserts [exported] 193 incl. the multi-line #[ts(\n export) ToolChoice. - serde_attribute_mutations_make_manifest_stale: skip_serializing_if, flatten, untagged, rename_all, field rename each make --check stale. - gen_closure_inventory.py skips tools/aimux-codegen in ts-rs scans (its docs/test fixtures quote the attributes as text); closure.md regenerated (extract.rs enters the >1000-line table).
…CS entry - extract.rs: a with/deserialize_with/serialize_with field without #[ts(type)] must match FIELD_CODECS (owner, field, serde fn), else generation fails at the field's file:line. - Register Recording.schema (currentRecordingSchema) and BooleanCriteria.true/false (presence): the manifest type is triState(EvaluationInput) = absent | null | value, not Option<Option>. - Tests: boolean_criteria_manifest_entry_is_tri_state, unregistered_serde_with_field_fails_generation. Manifest and closure.md regenerated.
…-only control serde_attribute_mutations_change_the_modelled_field compares manifests without inputHash: skip_serializing_if -> omitWhenEmpty, flatten -> flatten, untagged -> style untagged->external, rename_all -> wire inputSchema->input_schema, field rename -> wire true->yes. comment_only_edit_leaves_manifest_identical_except_input_hash is the control.
This was referenced Oct 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
2.1: manifest covers every type (phase 2)
Basis: PHASES.md:104 (2.1), arcships#200 §6.5 (
docs/aisdk-architecture-alignment.md:1317-1357@22580efd, esp. :1350), RFC-0042 §7.2 (:204-218), §7.3 (:222-224), D11 default a (DECISIONS.md:90-93).Stacked on #43 (
exp/u1.8-codegen-poc); #13 (exp/u1.5-closure-inventory) is merged in withgit merge(commit 9cbc73f), so this diff also shows #13's files until #13 lands: 3 files (docs/plan/closure.mdandscripts/gen_closure_inventory.pyfrom #13, plusdocs/plan/closure-dtos.txtthat the extended script now writes). Does not touch exp/restart.What changed
scripts/gen_closure_inventory.pydocs/plan/closure-dtos.txt:[exported]= every#[ts(export)]type as<name> <category> <file>;[derive-ts-without-export];[excluded]=tools/aimux-web/src/wire.rs(web-tool wire). Category per file (CATEGORY_BY_FILE, defaultcore). Built from tracked files at the branch's base, not frozen.--checkcovers both files.tools/aimux-codegen/src/extract.rsaimux-core/srcwith syn: serde renames/rename_all/rename_all_fields, enum representation (external / internaltag/ adjacent / untagged),skip,default,skip_serializing_if,flatten,alias,other,with,from/into/try_fromproxies, generics +ts(concrete),ts(rename),ts(type/as)codecs, non-generic type aliases reached by DTOs. A field type that is not a primitive/container/JSON/DTO/alias/registered codec fails generation with file:line. Hand-written serde without ats(type)codec fails too.tools/aimux-codegen/src/manifest.rsgenerated/manifest.json:schemaRevision,generator(name+crate version),sdk= all 7 entries read fromfixtures/aisdk/VERSIONS.jsonwhen generating,inputHash(sha256 over aimux-core/src + VERSIONS.json),types,auxiliaryTypes,aliases.generated/coverage.txt:<name> <category> <file>per type. Coverage ≠ list → error, nothing written.src/main.rs--checkverifies both;--src/--versions/--listoverrides.tests/manifest_test.rsDependencies only in
tools/aimux-codegen:syn2,quote,proc-macro2(span-locations for file:line),sha2(already used in the workspace).Why syn rather than serde-reflection
Deserializeimpls at runtime: it needs a sample value for every enum variant/untagged arm, cannot seeskip_serializing_if/default/aliases, and handlesuntagged, internally tagged enums,flattenand hand-written serde (ToolChoice) poorly or not at all. Those are exactly the cases RFC-0042:212-216 cares about.wire_names_match_the_current_ts_exportsguards this by checking 864 field and variant names against the ts-rs output that serde actually produces: 129 structs and 229 variant tags, 0 mismatches.Result
ToolChoice, plusAiMuxErrorwith ats(as)proxy).ToolChoice(aimux-core/src/tool.rs:210-213) splits#[ts(\n export, type = ..)]over lines, so the single-linegit grep '#[ts(export'count of 192 missed it. 193 + 1 non-exported (AiMuxErrorWire,error.rs) + 7 aimux-web = 201derive(TS)sites, matchingclosure.md.AiMuxErrorWireis reached throughAiMuxError'sfrom/intoproxy. It is in the manifest asauxiliaryTypes(not part of coverage) and appears in the list's[derive-ts-without-export]section.Mutation table (each must turn red)
#[ts(export)]ofHttpExchange(scratch copy)coverage: missing from manifest: HttpExchange recording aimux-core/src/recording.rs(testremoved_type_is_named)HttpExchangeline in closure-dtos.txt--check:coverage: not in closure-dtos.txt: HttpExchange …FakeDto core aimux-core/src/fake.rscoverage: missing from manifest: FakeDto …(fake_list_line_is_named)ExtraDtocoverage: not in closure-dtos.txt: ExtraDto core aimux-core/src/extra_dto.rs(extra_manifest_type_is_named)ai7.0.127 → 7.0.999--check:manifest.json is out of date; regenerated manifest contains 7.0.999 (versions_change_makes_manifest_stale)pub at: std::time::Instantaimux-core/src/extra_dto.rs:3: unregistered type \Instant` …; CLI exits 1 and writes no files (unregistered_field_type_fails_generation`)derive(TS)+ export, no serde deriveExtraDto has hand-written serde and no #[ts(type|as)] codectool.rsfirstrename_allcamelCase → snake_case--check:manifest.json is out of date"toolCallId"→"tool_call_id"--check: out of dateLocal evidence
cargo test -p aimux-codegen: 8 (1.8) + 16 (2.1) passed.cargo clippy -p aimux-codegen --all-targets -- -D warningsclean.cargo fmt --all -- --checkOK.aimux-codegen --checkup to date.python3 scripts/gen_closure_inventory.py --checkup to date. Builds were run one at a time with-p aimux-codegenonly.CRLF (commit 655b800)
inputHashnormalizes CRLF→LF for every hashed input (manifest.rslf()). Done in code rather than via.gitattributesbecause a code fix works for every checkout, while.gitattributesonly helps checkouts that respect it. Testcrlf_checkout_gives_identical_manifestconverts a scratch copy ofaimux-core/srcand VERSIONS.json to CRLF and asserts the sameinputHash, a byte-identical manifest/coverage, and that--checkpasses against the committed files. Mutation: makinglf()return its input unchanged turns the test red: hashsha256:db22a853…≠sha256:bce4b495…. LF hashes are unchanged, so the manifest did not need regenerating.Reviewer checklist (commit 6489f30)
193 is computed from source.
closure_dto_list_is_reproducible_from_sourcerunspython3 scripts/gen_closure_inventory.py --check, asserts[exported] 193, and asserts thatToolChoice(multi-line#[ts(\n export,attool.rs:210-213) is listed. Mutation: settingTS_EXPORTback to the single-line#[ts\(export→STALE: docs/plan/closure-dtos.txt, test red.#[derive(.., TS)]/#[ts(export)]written as text in the generator's own docs and test fixtures (a fakeTypeDefentry; 201→205 derives).tools/aimux-codegen/is now excluded from the ts-rs scans, which restores 201.closure.mdwas regenerated; the only change is thatextract.rs(now 1134 lines) enters the >1000-line table.Serde-rule mutations.
serde_attribute_mutations_change_the_modelled_fieldmakes one change per rule in a scratch copy. For each it compares the manifests withinputHashremoved, asserts that the specific field changed, and asserts that--checkreportsmanifest.json is out of date:FunctionTool.descriptionloses it (tool.rs:20)omitWhenEmpty: true → absentflatten(aimux-core has noflattentoday)flatten: absent → true#[serde(untagged)]onAudioInput(transcription_model.rs:25)representation.style: untagged → externalFunctionToolcamelCase → snake_case (tool.rs:13)input_schema.wire: inputSchema → input_schemaDecisionBooleanCriteriarename = "true"→"yes"(decision_model.rs:135)wire: true → yesControl:
comment_only_edit_leaves_manifest_identical_except_input_hashadds a comment line to tool.rs.inputHashchanges, and everything else in the manifest is byte-identical. Mutation proof: making the extractor ignoreflattenturns the test red atflatten: mutated(leftNull, righttrue).864-name ts-rs comparison is the committed test
wire_names_match_the_current_ts_exports(asserts >800 names checked, 0 problems). It runs in the CI job "Rust core (fmt + clippy + test)" viacargo test --workspace.No new dependencies in the library crates.
cargo tree -p aimux-core -e normal,buildand-p aimux-ffi, base8455db9avs this head: the same set of crates (210 / 258 lines). The only differences are workspace paths and patch versions from separate local lockfiles (cc1.7→1.8,serde_json.152→.154,syn3.0.7→3.0.8; Cargo.lock is not tracked).cargo tree -i syn@2.0.119 -e normal: aimux-codegen only. In aimux-core, syn 2 is still reached only through existing proc-macros (ts-rs-macros, thiserror-impl, strum_macros, tracing-attributes, async-stream-impl).cargo tree -i sha2 -e normal: aimux-codegen and aimux-provider-utils (already there). quote and proc-macro2 are direct dependencies of aimux-codegen only.serde
withcodecs (follow-up commit)with/deserialize_with/serialize_withmust have#[ts(type)]or aFIELD_CODECSentry (extract.rs, keyed by owner, field and serde fn). Otherwise generation fails at the field's file:line.Recording.schema(recording.rs:73) → codeccurrentRecordingSchema(u32; decode accepts onlyRECORDING_SCHEMA).BooleanCriteria.true/false(evaluation_model.rs:62-73) → codecpresence. The manifest type is{"triState": EvaluationInput}, meaning absent key | explicit null | value, instead ofoption(option(..)).decision_model.rs:23(with = "tagged_file_bytes") already has#[ts(type)].boolean_criteria_manifest_entry_is_tri_statechecks the tri-state encoding, the codec name,omitWhenEmpty/default, and Recording.schema's codec.unregistered_serde_with_field_fails_generationgivesextra_dto.rs:5: ExtraDto.a uses serde \deserialize_with=custom` but has no `#[ts(type)]` and no FIELD_CODECS registration`.Recording.schemaregistration →aimux-core/src/recording.rs:74: Recording.schema uses serde \deserialize_with=deserialize_current_schema` but has no …(line 74 is the field; its attribute is on :73).--check` and 8 manifest tests turn red.Not in scope / notes
--check(2.4). aimux-provider V4 types are out of scope (3.3).content.rs→ multimodal;model_catalogue.rs/model_id.rs→ provider; everything not listed → core). The table exists in both the Python script andextract.rs, and the coverage lines comparename category file, so if the two tables drift the check fails.inputHashcovers all ofaimux-core/src, so any edit there makes the manifest stale until regenerated (RFC-0042:222 "input hash").refwith the parameter name (genericslists them on the type).