diff --git a/docs/adr/0020-keep-primitive-literals-shadow-only-after-dual-run.md b/docs/adr/0020-keep-primitive-literals-shadow-only-after-dual-run.md index 9107ed31a..8074f06bf 100644 --- a/docs/adr/0020-keep-primitive-literals-shadow-only-after-dual-run.md +++ b/docs/adr/0020-keep-primitive-literals-shadow-only-after-dual-run.md @@ -36,6 +36,16 @@ completeness, recovery, truncation, unsupported states, mapping, and classified differences. Unexplained semantic, transport, or mapping differences fail the command. +Issue #52 resolves the seven named rollout observations without broadening the +producer category. Conformance schema v5 keeps every selected fact in a second, +CI-enforced accounting denominator, which must remain exactly 100%. The +supported-record compatibility metric therefore cannot hide unsupported, +budget, or mapping selections. Each of the four unsupported selections and +three recovery-file mapping gaps is a stable regression fixture with a +machine-readable owner and concrete action required for reclassification. A +changed code, state, diagnostic, or mapping outcome is unexplained and blocks +the gate. + Runtime and output sizes are retained as two ordered measurement samples. Artifact sizes and the controller's peak or current resident memory are recorded with their measurement method and scope. These measurements are @@ -49,11 +59,23 @@ Rust remains shadow-only. Go remains both the serving semantic authority and the production fallback. There is no authority switch, TS7 producer protocol change, external consumer behavior change, or Palamedes change. -## Remaining blockers +## Resolved rollout observations + +- The local enum literal remains owned by `rust-primitive-literal-producer` + until local enum declaration and member-value resolution is implemented. +- The value and type import selections remain owned by + `rust-project-resolution` until project-aware cross-file resolution exists. +- The object selection remains owned by `object-category-rollout`; object + semantics are intentionally not folded into the primitive/literal producer. +- The three recovery selections remain owned by `oxc-occurrence-mapping` until + OXC parses or recovers the file and supplies each exact `NodeId` mapping. + +These are resolved, explicit limitations rather than authority-readiness +blockers or hidden exclusions. Their fixture contracts remain CI-blocking when +the observed behavior changes. + +## Remaining authority blockers -- Four explicitly classified selections remain outside the supported Rust - slice. -- Three recovery selections retain the expected OXC parser/mapping gap. - Rust is not integrated into the serving path, so production fallback, rollback, and shadow observation at that boundary have not been exercised. - Runtime and output-size samples do not yet use one production-equivalent @@ -82,3 +104,4 @@ that proposal. - [ADR-0019](0019-compute-primitive-literals-independently-in-rust.md) - [Checked rollout evidence](../evidence/primitive-literal-rollout-2026-08-17.json) - [Issue #47](https://github.com/swernerx/typescript-semantic-kernel/issues/47) +- [Issue #52](https://github.com/swernerx/typescript-semantic-kernel/issues/52) diff --git a/docs/evidence/README.md b/docs/evidence/README.md index 54024ee1e..6226d7c6c 100644 --- a/docs/evidence/README.md +++ b/docs/evidence/README.md @@ -49,5 +49,11 @@ separate measurement section because those values or their comparison boundary vary by host. The report is a shadow rollout artifact, not an authority switch or a general benchmark. +Conformance schema v5 reports both the exact supported-record compatibility +and a CI-enforced 100% accounting denominator over every selected fact. The +four unsupported primitive/literal observations and three recovery-file +mapping gaps remain present as stable regression limitations with explicit +owners and reclassification actions. + [ADR-0020](../adr/0020-keep-primitive-literals-shadow-only-after-dual-run.md) records why the category is not ready for a later authority decision. diff --git a/docs/evidence/primitive-literal-rollout-2026-08-17.json b/docs/evidence/primitive-literal-rollout-2026-08-17.json index 98984a027..5d40981eb 100644 --- a/docs/evidence/primitive-literal-rollout-2026-08-17.json +++ b/docs/evidence/primitive-literal-rollout-2026-08-17.json @@ -1,11 +1,11 @@ { - "schemaVersion": 1, + "schemaVersion": 2, "evidenceKind": "primitive-literal-controlled-go-rust-dual-run", "command": "./internal/oxc_reference/run-rollout.sh --output ", "environment": { "operatingSystem": "macos", "architecture": "aarch64", - "rustc": "rustc 1.97.1 (8bab26f4f 2026-07-14)", + "rustc": "rustc 1.95.0 (59807616e 2026-04-14)", "go": "go version go1.26.6 darwin/arm64", "buildProfile": "release" }, @@ -20,15 +20,15 @@ "determinism": { "completeRuns": 2, "conformanceReportsByteEqual": true, - "compactConformanceReportBytes": 52766 + "compactConformanceReportBytes": 54311 }, "conformance": { - "schemaVersion": 4, + "schemaVersion": 5, "gateKind": "go-vs-independent-rust-semantic-conformance", "candidate": "independent-primitive-literal-v2", "shadowOnly": true, "execution": { - "repositoryRevision": "24c138ae4c7a2b9a42c1e68eb25329fbf8a3a5a9", + "repositoryRevision": "20d726c587bc612b5be8311530a553f70b0ed79b", "typescriptVersion": "7.1.0-dev", "typescriptRevision": "1bcfa18d79a3be41772223d5c05dfe4480e614ff", "requestSchemaVersion": 1, @@ -41,6 +41,7 @@ "threshold": { "minimumSupportedRecords": 15, "requiredSupportedCompatibilityPpm": 1000000, + "requiredSelectionAccountingPpm": 1000000, "maxUnexplainedSemanticDifferences": 0, "maxUnexplainedTransportDifferences": 0, "maxUnexplainedMappingDifferences": 0, @@ -977,6 +978,11 @@ "proves": "enum-like literal values remain explicitly outside the narrow Rust category", "expectedClassification": "unsupported", "expectedCode": "enum-literal-outside-category", + "limitationResolution": { + "stability": "stable", + "owner": "rust-primitive-literal-producer", + "action": "Implement local enum declaration and member-value resolution before reclassifying this selection as supported." + }, "expectationMatched": true, "goOracle": { "occurrence": { @@ -1074,6 +1080,11 @@ "proves": "imported value literals are observed without pretending cross-file Rust resolution", "expectedClassification": "unsupported", "expectedCode": "cross-file-import-outside-category", + "limitationResolution": { + "stability": "stable", + "owner": "rust-project-resolution", + "action": "Implement project-aware value import resolution before reclassifying this selection as supported." + }, "expectationMatched": true, "goOracle": { "occurrence": { @@ -1171,6 +1182,11 @@ "proves": "imported literal aliases are observed without pretending cross-file Rust resolution", "expectedClassification": "unsupported", "expectedCode": "cross-file-import-outside-category", + "limitationResolution": { + "stability": "stable", + "owner": "rust-project-resolution", + "action": "Implement project-aware type-alias import resolution before reclassifying this selection as supported." + }, "expectationMatched": true, "goOracle": { "occurrence": { @@ -3115,6 +3131,11 @@ "proves": "out-of-category type remains explicit", "expectedClassification": "unsupported", "expectedCode": "object-outside-category", + "limitationResolution": { + "stability": "stable", + "owner": "object-category-rollout", + "action": "Keep object types outside this primitive/literal producer and cover them in a separately gated object-category rollout." + }, "expectationMatched": true, "goOracle": { "occurrence": { @@ -3359,6 +3380,11 @@ "proves": "type-error recovery remains an explicit fact state", "expectedClassification": "mapping", "expectedCode": "oxc-parse-error", + "limitationResolution": { + "stability": "stable", + "owner": "oxc-occurrence-mapping", + "action": "Reclassify only when OXC parses the recovery file and produces an exact NodeId mapping for this type-error selection." + }, "expectationMatched": true, "goOracle": { "occurrence": { @@ -3452,6 +3478,11 @@ "proves": "syntax-error recovery remains queryable", "expectedClassification": "mapping", "expectedCode": "oxc-parse-error", + "limitationResolution": { + "stability": "stable", + "owner": "oxc-occurrence-mapping", + "action": "Reclassify only when OXC recovers the malformed initializer and produces an exact NodeId mapping for this syntax-error selection." + }, "expectationMatched": true, "goOracle": { "occurrence": { @@ -3545,6 +3576,11 @@ "proves": "recursive advanced graph hits the configured depth budget", "expectedClassification": "mapping", "expectedCode": "oxc-parse-error", + "limitationResolution": { + "stability": "stable", + "owner": "oxc-occurrence-mapping", + "action": "Reclassify only when OXC parses the recovery file and produces an exact NodeId mapping for this recursive-budget selection." + }, "expectationMatched": true, "goOracle": { "occurrence": { @@ -3907,6 +3943,8 @@ "summary": { "cases": 4, "facts": 28, + "accountedSelections": 28, + "selectionAccountingPpm": 1000000, "candidateRecords": 42, "supportedRecords": 29, "matchedSupportedRecords": 29, @@ -3942,28 +3980,28 @@ { "ordinal": 1, "cases": 4, - "goOracleNanoseconds": 486867126, - "rustProducerNanoseconds": 1284832, - "rustDeterminismCheckNanoseconds": 197668, - "totalNanoseconds": 490094084, - "goSnapshotBytes": 36799, + "goOracleNanoseconds": 700402249, + "rustProducerNanoseconds": 1367000, + "rustDeterminismCheckNanoseconds": 236041, + "totalNanoseconds": 704086084, + "goSnapshotBytes": 36671, "rustCandidateBytes": 22615 }, { "ordinal": 2, "cases": 4, - "goOracleNanoseconds": 124067458, - "rustProducerNanoseconds": 598000, - "rustDeterminismCheckNanoseconds": 191250, - "totalNanoseconds": 125660750, - "goSnapshotBytes": 36799, + "goOracleNanoseconds": 127294084, + "rustProducerNanoseconds": 789167, + "rustDeterminismCheckNanoseconds": 237917, + "totalNanoseconds": 129383625, + "goSnapshotBytes": 36671, "rustCandidateBytes": 22615 } ], "artifacts": { "goExecutableBytes": 25670354, - "rustExecutableBytes": 3521008, - "peakOrCurrentControllerResidentBytes": 5226496, + "rustExecutableBytes": 3345040, + "peakOrCurrentControllerResidentBytes": 4964352, "residentMeasurement": "macos-getrusage-peak", "memoryScope": "Rust rollout controller including decoded Go snapshots; excludes child Go process RSS" } @@ -3971,9 +4009,128 @@ "readiness": { "readyForLaterAuthorityDecision": false, "status": "not-ready", + "resolvedRolloutLimitations": [ + { + "case": "primitive-literal-conformance", + "factIndex": 6, + "occurrence": { + "file": "src/matrix.ts", + "span": { + "start": 657, + "end": 670 + }, + "syntaxKind": "KindIdentifier" + }, + "classification": "unsupported", + "code": "enum-literal-outside-category", + "stability": "stable", + "owner": "rust-primitive-literal-producer", + "action": "Implement local enum declaration and member-value resolution before reclassifying this selection as supported." + }, + { + "case": "primitive-literal-conformance", + "factIndex": 7, + "occurrence": { + "file": "src/matrix.ts", + "span": { + "start": 672, + "end": 687 + }, + "syntaxKind": "KindIdentifier" + }, + "classification": "unsupported", + "code": "cross-file-import-outside-category", + "stability": "stable", + "owner": "rust-project-resolution", + "action": "Implement project-aware value import resolution before reclassifying this selection as supported." + }, + { + "case": "primitive-literal-conformance", + "factIndex": 8, + "occurrence": { + "file": "src/matrix.ts", + "span": { + "start": 689, + "end": 701 + }, + "syntaxKind": "KindIdentifier" + }, + "classification": "unsupported", + "code": "cross-file-import-outside-category", + "stability": "stable", + "owner": "rust-project-resolution", + "action": "Implement project-aware type-alias import resolution before reclassifying this selection as supported." + }, + { + "case": "primitive-literal-independent", + "factIndex": 10, + "occurrence": { + "file": "src/primitives.ts", + "span": { + "start": 624, + "end": 635 + }, + "syntaxKind": "KindIdentifier" + }, + "classification": "unsupported", + "code": "object-outside-category", + "stability": "stable", + "owner": "object-category-rollout", + "action": "Keep object types outside this primitive/literal producer and cover them in a separately gated object-category rollout." + }, + { + "case": "recovery-budgets", + "factIndex": 0, + "occurrence": { + "file": "src/recovery.ts", + "span": { + "start": 247, + "end": 256 + }, + "syntaxKind": "KindIdentifier" + }, + "classification": "mapping", + "code": "oxc-parse-error", + "stability": "stable", + "owner": "oxc-occurrence-mapping", + "action": "Reclassify only when OXC parses the recovery file and produces an exact NodeId mapping for this type-error selection." + }, + { + "case": "recovery-budgets", + "factIndex": 1, + "occurrence": { + "file": "src/recovery.ts", + "span": { + "start": 258, + "end": 264 + }, + "syntaxKind": "KindIdentifier" + }, + "classification": "mapping", + "code": "oxc-parse-error", + "stability": "stable", + "owner": "oxc-occurrence-mapping", + "action": "Reclassify only when OXC recovers the malformed initializer and produces an exact NodeId mapping for this syntax-error selection." + }, + { + "case": "recovery-budgets", + "factIndex": 2, + "occurrence": { + "file": "src/recovery.ts", + "span": { + "start": 266, + "end": 270 + }, + "syntaxKind": "KindIdentifier" + }, + "classification": "mapping", + "code": "oxc-parse-error", + "stability": "stable", + "owner": "oxc-occurrence-mapping", + "action": "Reclassify only when OXC parses the recovery file and produces an exact NodeId mapping for this recursive-budget selection." + } + ], "blockers": [ - "4 explicitly classified primitive/literal selections remain unsupported", - "3 recovery selections retain expected OXC mapping gaps", "the Rust producer is not integrated into the serving path, so production fallback and rollback have not been exercised", "runtime and output measurements compare a one-shot Go process with an in-process Rust shadow path; a production-equivalent boundary is not selected", "controller RSS excludes the child Go process, so per-producer peak-memory parity is not established" diff --git a/docs/porting/migration-contract.md b/docs/porting/migration-contract.md index 25cd28885..67333a948 100644 --- a/docs/porting/migration-contract.md +++ b/docs/porting/migration-contract.md @@ -105,6 +105,15 @@ memory, artifact size, and producer-output size remain separately scoped characterization evidence. The lane is shadow-only and does not exercise or authorize production routing. +Issue #52 adds conformance schema v5's all-selection accounting gate. Every +selected fact must be classified, the classification count must remain equal +to the fact count, and the accounting ratio must remain 1,000,000 ppm. The +four unsupported primitive/literal selections and three recovery-file mapping +gaps are retained as stable regression limitations with machine-readable +owners and concrete reclassification actions. They are neither dropped from +the corpus nor counted as supported. Go remains semantic authority and the +production fallback. + ## Gate ladder 1. Focused `internal/semanticfacts` and `internal/tsfacts` tests pass, including @@ -124,7 +133,8 @@ authorize production routing. 9. `./internal/oxc_reference/run-conformance.sh --output ` emits the byte-stable ADR-0019 report and fails on any unexplained semantic, transport, or mapping mismatch while retaining named unsupported, budget, and expected - mapping cases outside the supported denominator. + mapping cases. Its supported-record metric is paired with a mandatory 100% + all-selection accounting denominator, so no classified case is hidden. 10. `./internal/oxc_reference/run-rollout.sh --output ` executes two release-profile dual-runs, requires byte-identical embedded conformance, records the controlled measurements in ADR-0020, and retains Go as serving diff --git a/internal/oxc_reference/README.md b/internal/oxc_reference/README.md index cc0a1b61b..ef2979035 100644 --- a/internal/oxc_reference/README.md +++ b/internal/oxc_reference/README.md @@ -72,6 +72,10 @@ contains a structured Go expectation and exactly one Rust classification: facts remain individually visible as expected `oxc-parse-error` mapping gaps. Unexplained semantic, transport, or mapping differences fail the command; expected unsupported, budget, and mapping cases remain separately reported. +Schema v5 additionally requires every selected fact to participate in a +100%-accounted selection denominator. Each unsupported selection and recovery +mapping gap carries a stable owner and concrete action in its regression +fixture and in the emitted report; none is silently removed from coverage. The wrapper executes the complete report twice and requires byte-identical files. The gate is a shadow comparison and does not change the producer or production routing. See @@ -139,8 +143,9 @@ negotiation in Go. The intended migration sequence is: Occurrence identity and attachment plumbing is the first approved mechanical port category. Primitive/literal construction is now independently implemented for the narrow, tagged conformance corpus and satisfies the shadow threshold in -ADR-0019. The controlled dual-run in ADR-0020 retains explicit unsupported, -recovery, serving-integration, measurement-boundary, and memory blockers. This +ADR-0019. The controlled dual-run in ADR-0020 resolves the four unsupported +and three recovery observations as stable, owned, actionable limitations. The +serving-integration, measurement-boundary, and memory blockers remain. This does not transfer semantic authority: the implementation is not wired into production, Go remains the fallback, and broader TypeScript semantics remain Go-authoritative. diff --git a/internal/oxc_reference/crates/occurrence-consumer/src/conformance.rs b/internal/oxc_reference/crates/occurrence-consumer/src/conformance.rs index 0261a7c1b..a744cca7d 100644 --- a/internal/oxc_reference/crates/occurrence-consumer/src/conformance.rs +++ b/internal/oxc_reference/crates/occurrence-consumer/src/conformance.rs @@ -27,8 +27,8 @@ use crate::{ }, }; -pub const CONFORMANCE_SCHEMA_VERSION: u32 = 4; -pub const ROLLOUT_SCHEMA_VERSION: u32 = 1; +pub const CONFORMANCE_SCHEMA_VERSION: u32 = 5; +pub const ROLLOUT_SCHEMA_VERSION: u32 = 2; #[derive(Clone, Debug, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -139,9 +139,23 @@ pub struct RolloutArtifacts { pub struct AuthorityReadiness { pub ready_for_later_authority_decision: bool, pub status: &'static str, + pub resolved_rollout_limitations: Vec, pub blockers: Vec, } +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ResolvedRolloutLimitation { + pub case: String, + pub fact_index: usize, + pub occurrence: Occurrence, + pub classification: ExpectedClassification, + pub code: String, + pub stability: LimitationStability, + pub owner: String, + pub action: String, +} + #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CorpusCoverage { @@ -156,6 +170,7 @@ pub struct CorpusCoverage { pub struct CompatibilityThreshold { pub minimum_supported_records: usize, pub required_supported_compatibility_ppm: u64, + pub required_selection_accounting_ppm: u64, pub max_unexplained_semantic_differences: usize, pub max_unexplained_transport_differences: usize, pub max_unexplained_mapping_differences: usize, @@ -227,6 +242,8 @@ pub struct SelectionEvidence { pub expected_classification: ExpectedClassification, #[serde(skip_serializing_if = "Option::is_none")] pub expected_code: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub limitation_resolution: Option, pub expectation_matched: bool, pub go_oracle: GoOracleFactObservation, pub rust_candidate: PrimitiveLiteralCandidate, @@ -287,6 +304,8 @@ pub struct MappingCoverage { pub struct ConformanceSummary { pub cases: usize, pub facts: usize, + pub accounted_selections: usize, + pub selection_accounting_ppm: u64, pub candidate_records: usize, pub supported_records: usize, pub matched_supported_records: usize, @@ -303,6 +322,8 @@ impl Default for ConformanceSummary { Self { cases: 0, facts: 0, + accounted_selections: 0, + selection_accounting_ppm: 0, candidate_records: 0, supported_records: 0, matched_supported_records: 0, @@ -400,9 +421,25 @@ struct SelectionExpectation { classification: ExpectedClassification, #[serde(default)] code: Option, + #[serde(default)] + limitation_resolution: Option, go_oracle: GoOracleExpectation, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LimitationResolution { + pub stability: LimitationStability, + pub owner: String, + pub action: String, +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum LimitationStability { + Stable, +} + #[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] #[serde(rename_all = "lowercase")] pub enum ExpectedClassification { @@ -489,7 +526,7 @@ pub fn run_rollout( .map_err(|error| format!("transport: serialize repeated conformance report: {error}"))?; let reports_equal = first_bytes == repeated_bytes; let conformance_passes = first.report.passes && repeated.report.passes; - let readiness = authority_readiness(&first.report.summary); + let readiness = authority_readiness(&first.report.cases); let (resident_bytes, resident_measurement) = resident_memory(); let rust_executable_bytes = std::env::current_exe() .ok() @@ -691,6 +728,7 @@ fn compatibility_threshold() -> CompatibilityThreshold { CompatibilityThreshold { minimum_supported_records: 15, required_supported_compatibility_ppm: 1_000_000, + required_selection_accounting_ppm: 1_000_000, max_unexplained_semantic_differences: 0, max_unexplained_transport_differences: 0, max_unexplained_mapping_differences: 0, @@ -701,6 +739,7 @@ fn compatibility_threshold() -> CompatibilityThreshold { fn threshold_passes(summary: &ConformanceSummary, threshold: CompatibilityThreshold) -> bool { summary.supported_records >= threshold.minimum_supported_records && summary.supported_compatibility_ppm >= threshold.required_supported_compatibility_ppm + && summary.selection_accounting_ppm == threshold.required_selection_accounting_ppm && difference_count(summary, "semantic") <= threshold.max_unexplained_semantic_differences && difference_count(summary, "transport") <= threshold.max_unexplained_transport_differences && difference_count(summary, "mapping") <= threshold.max_unexplained_mapping_differences @@ -732,35 +771,37 @@ fn compiler_identity(cases: &[ConformanceCase]) -> Result<(String, String), Stri Ok((version, revision)) } -fn authority_readiness(summary: &ConformanceSummary) -> AuthorityReadiness { - let mut blockers = Vec::new(); - if summary.classifications.unsupported > 0 { - blockers.push(format!( - "{} explicitly classified primitive/literal selections remain unsupported", - summary.classifications.unsupported - )); - } - if summary.classifications.mapping > 0 { - blockers.push(format!( - "{} recovery selections retain expected OXC mapping gaps", - summary.classifications.mapping - )); - } - blockers.push( +fn authority_readiness(cases: &[ConformanceCase]) -> AuthorityReadiness { + let resolved_rollout_limitations = cases + .iter() + .flat_map(|case| { + case.selections.iter().filter_map(|selection| { + let resolution = selection.limitation_resolution.as_ref()?; + Some(ResolvedRolloutLimitation { + case: case.name.clone(), + fact_index: selection.fact_index, + occurrence: selection.go_oracle.occurrence.clone(), + classification: selection.expected_classification, + code: selection.expected_code.clone().unwrap_or_default(), + stability: resolution.stability, + owner: resolution.owner.clone(), + action: resolution.action.clone(), + }) + }) + }) + .collect(); + let blockers = vec![ "the Rust producer is not integrated into the serving path, so production fallback and rollback have not been exercised" .to_owned(), - ); - blockers.push( "runtime and output measurements compare a one-shot Go process with an in-process Rust shadow path; a production-equivalent boundary is not selected" .to_owned(), - ); - blockers.push( "controller RSS excludes the child Go process, so per-producer peak-memory parity is not established" .to_owned(), - ); + ]; AuthorityReadiness { ready_for_later_authority_decision: false, status: "not-ready", + resolved_rollout_limitations, blockers, } } @@ -1043,6 +1084,7 @@ impl<'a> Comparison<'a> { proves: proves[fact_index].clone(), expected_classification: expectation.classification, expected_code: expectation.code.clone(), + limitation_resolution: expectation.limitation_resolution.clone(), expectation_matched, go_oracle: GoOracleFactObservation { occurrence, @@ -1553,6 +1595,10 @@ fn summarize(cases: &[ConformanceCase]) -> ConformanceSummary { }; for case in cases { summary.facts += case.facts; + summary.accounted_selections += case.classifications.supported + + case.classifications.unsupported + + case.classifications.budget + + case.classifications.mapping; summary.candidate_records += case.candidate_states.complete + case.candidate_states.truncated + case.candidate_states.unsupported @@ -1585,6 +1631,7 @@ fn summarize(cases: &[ConformanceCase]) -> ConformanceSummary { } summary.supported_compatibility_ppm = ratio_ppm(summary.matched_supported_records, summary.supported_records); + summary.selection_accounting_ppm = ratio_ppm(summary.accounted_selections, summary.facts); summary } @@ -1727,6 +1774,33 @@ fn validate_expectations(manifest: &CorpusManifest) -> Result<(), String> { if requires_code { "provide" } else { "omit" } )); } + let requires_limitation = matches!( + expectation.classification, + ExpectedClassification::Unsupported | ExpectedClassification::Mapping + ); + if requires_limitation != expectation.limitation_resolution.is_some() { + return Err(format!( + "transport: case {:?} selections[{index}] must {} a stable limitation resolution", + manifest.name, + if requires_limitation { + "provide" + } else { + "omit" + } + )); + } + if expectation + .limitation_resolution + .as_ref() + .is_some_and(|resolution| { + resolution.owner.trim().is_empty() || resolution.action.trim().is_empty() + }) + { + return Err(format!( + "transport: case {:?} selections[{index}] limitation owner and action must be non-empty", + manifest.name + )); + } if expectation.go_oracle.type_view_states.actual != TypeViewState::Available { return Err(format!( "transport: case {:?} selections[{index}] Go actual view must be available", @@ -1814,11 +1888,13 @@ mod tests { } #[test] - fn expected_mapping_gaps_do_not_hide_or_block_supported_denominator() { + fn expected_mapping_gaps_are_accounted_without_blocking_supported_records() { let mut summary = ConformanceSummary { supported_records: 15, matched_supported_records: 15, supported_compatibility_ppm: 1_000_000, + accounted_selections: 13, + selection_accounting_ppm: 1_000_000, ..ConformanceSummary::default() }; summary @@ -1835,6 +1911,11 @@ mod tests { .unexplained_differences_by_category .insert("mapping".to_owned(), 1); assert!(!threshold_passes(&summary, compatibility_threshold())); + summary + .unexplained_differences_by_category + .insert("mapping".to_owned(), 0); + summary.selection_accounting_ppm = 999_999; + assert!(!threshold_passes(&summary, compatibility_threshold())); } #[test] @@ -1863,24 +1944,16 @@ mod tests { budget: 1, mapping: 3, }, + accounted_selections: 28, + selection_accounting_ppm: 1_000_000, ..ConformanceSummary::default() }; assert!(threshold_passes(&summary, compatibility_threshold())); - let readiness = authority_readiness(&summary); + let readiness = authority_readiness(&[]); assert!(!readiness.ready_for_later_authority_decision); assert_eq!(readiness.status, "not-ready"); - assert!( - readiness - .blockers - .iter() - .any(|blocker| blocker.starts_with("4 explicitly classified")) - ); - assert!( - readiness - .blockers - .iter() - .any(|blocker| blocker.starts_with("3 recovery selections")) - ); + assert!(readiness.resolved_rollout_limitations.is_empty()); + assert_eq!(readiness.blockers.len(), 3); } } diff --git a/internal/semanticfacts/testdata/corpus/v0/primitive-literal-conformance/README.md b/internal/semanticfacts/testdata/corpus/v0/primitive-literal-conformance/README.md index 667649b59..1e4aa2917 100644 --- a/internal/semanticfacts/testdata/corpus/v0/primitive-literal-conformance/README.md +++ b/internal/semanticfacts/testdata/corpus/v0/primitive-literal-conformance/README.md @@ -3,3 +3,8 @@ Exercises declarations and expressions, widening, `as const`, `satisfies`, enum-like values, imports, contextual values, literal unions, and null-like values through the shared Go-oracle and independent Rust/OXC shadow path. + +The enum-like value and both imported literals are stable regression fixtures, +not denominator exclusions. Their manifest entries pin an unsupported code, +the owning migration category, and the concrete implementation needed before +each selection may be reclassified as supported. diff --git a/internal/semanticfacts/testdata/corpus/v0/primitive-literal-conformance/case.json b/internal/semanticfacts/testdata/corpus/v0/primitive-literal-conformance/case.json index a260f4b96..21505a257 100644 --- a/internal/semanticfacts/testdata/corpus/v0/primitive-literal-conformance/case.json +++ b/internal/semanticfacts/testdata/corpus/v0/primitive-literal-conformance/case.json @@ -124,6 +124,11 @@ "conformance": { "classification": "unsupported", "code": "enum-literal-outside-category", + "limitationResolution": { + "stability": "stable", + "owner": "rust-primitive-literal-producer", + "action": "Implement local enum declaration and member-value resolution before reclassifying this selection as supported." + }, "goOracle": { "complete": false, "recovered": false, @@ -141,6 +146,11 @@ "conformance": { "classification": "unsupported", "code": "cross-file-import-outside-category", + "limitationResolution": { + "stability": "stable", + "owner": "rust-project-resolution", + "action": "Implement project-aware value import resolution before reclassifying this selection as supported." + }, "goOracle": { "complete": false, "recovered": false, @@ -158,6 +168,11 @@ "conformance": { "classification": "unsupported", "code": "cross-file-import-outside-category", + "limitationResolution": { + "stability": "stable", + "owner": "rust-project-resolution", + "action": "Implement project-aware type-alias import resolution before reclassifying this selection as supported." + }, "goOracle": { "complete": false, "recovered": false, diff --git a/internal/semanticfacts/testdata/corpus/v0/primitive-literal-independent/README.md b/internal/semanticfacts/testdata/corpus/v0/primitive-literal-independent/README.md index 73f7dd284..7fd23ea25 100644 --- a/internal/semanticfacts/testdata/corpus/v0/primitive-literal-independent/README.md +++ b/internal/semanticfacts/testdata/corpus/v0/primitive-literal-independent/README.md @@ -7,4 +7,6 @@ an out-of-category object type. Every selection pins a structured Go-oracle expectation. Supported Rust/OXC observations remain in the exact semantic denominator, while the object is a -named expected unsupported case. +named expected unsupported case. The object selection is a stable regression +fixture owned by the separately gated object-category rollout; it remains in +the all-selection accounting denominator. diff --git a/internal/semanticfacts/testdata/corpus/v0/primitive-literal-independent/case.json b/internal/semanticfacts/testdata/corpus/v0/primitive-literal-independent/case.json index 6baf5c851..4d0eaa0a0 100644 --- a/internal/semanticfacts/testdata/corpus/v0/primitive-literal-independent/case.json +++ b/internal/semanticfacts/testdata/corpus/v0/primitive-literal-independent/case.json @@ -183,6 +183,11 @@ "conformance": { "classification": "unsupported", "code": "object-outside-category", + "limitationResolution": { + "stability": "stable", + "owner": "object-category-rollout", + "action": "Keep object types outside this primitive/literal producer and cover them in a separately gated object-category rollout." + }, "goOracle": { "complete": false, "recovered": true, "truncated": true, "typeViewStates": { "actual": "available", "contextual": "unavailable", "widened": "same-as-actual", "apparent": "same-as-actual", "declared": "same-as-actual" }, diff --git a/internal/semanticfacts/testdata/corpus/v0/recovery-budgets/README.md b/internal/semanticfacts/testdata/corpus/v0/recovery-budgets/README.md index 3a5c6be24..a17206c32 100644 --- a/internal/semanticfacts/testdata/corpus/v0/recovery-budgets/README.md +++ b/internal/semanticfacts/testdata/corpus/v0/recovery-budgets/README.md @@ -7,3 +7,8 @@ The three selected facts also pin the current OXC parser-panic boundary as three expected `oxc-parse-error` mapping classifications. They remain visible individually in the conformance report and any change to that classification fails the gate until the fixture expectation is reviewed. + +Each manifest entry also pins `oxc-occurrence-mapping` as owner and an +actionable condition for reclassification: OXC must parse or recover the file +and produce the exact `NodeId` for that selection. A different diagnostic, +candidate state, or mapping outcome is unexplained and CI-blocking. diff --git a/internal/semanticfacts/testdata/corpus/v0/recovery-budgets/case.json b/internal/semanticfacts/testdata/corpus/v0/recovery-budgets/case.json index 115502bdd..09f208985 100644 --- a/internal/semanticfacts/testdata/corpus/v0/recovery-budgets/case.json +++ b/internal/semanticfacts/testdata/corpus/v0/recovery-budgets/case.json @@ -28,6 +28,11 @@ "conformance": { "classification": "mapping", "code": "oxc-parse-error", + "limitationResolution": { + "stability": "stable", + "owner": "oxc-occurrence-mapping", + "action": "Reclassify only when OXC parses the recovery file and produces an exact NodeId mapping for this type-error selection." + }, "goOracle": { "complete": false, "recovered": true, @@ -45,6 +50,11 @@ "conformance": { "classification": "mapping", "code": "oxc-parse-error", + "limitationResolution": { + "stability": "stable", + "owner": "oxc-occurrence-mapping", + "action": "Reclassify only when OXC recovers the malformed initializer and produces an exact NodeId mapping for this syntax-error selection." + }, "goOracle": { "complete": false, "recovered": true, @@ -62,6 +72,11 @@ "conformance": { "classification": "mapping", "code": "oxc-parse-error", + "limitationResolution": { + "stability": "stable", + "owner": "oxc-occurrence-mapping", + "action": "Reclassify only when OXC parses the recovery file and produces an exact NodeId mapping for this recursive-budget selection." + }, "goOracle": { "complete": false, "recovered": true,