diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index dcde40ac7..67f595ee3 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -66,14 +66,10 @@ boundaries above remain the target modular MSA architecture. | `persistence_postgres` | PostgreSQL repositories and migrations | | `corpus_split` | cutoff-safe, relation-aware partitioning | | `tepp_simulation` | known-truth temporal/event data generation | -| `validation_core` | RMSE, bias, coverage, graph, and Monte Carlo metrics | +| `validation_core` | RMSE, bias, coverage, graph, Monte Carlo, and exact-head claim-promotion metrics | | `tepp_api` | versioned DTO, schema, terminal-result, and export contracts | | `analysis_engine` | bounded cutoff-safe temporal evidence readiness execution and digest-bound terminal artifacts | -| `location_membership` | location is not entity identity and not a language channel | -| `validation_core` | RMSE, bias, coverage, graph, Monte Carlo, and exact-head claim-promotion metrics | -| `tepp_api` | versioned DTO, schema, and export contracts | | `episode_membership` | event-time episode membership containment gate | -| `location_membership` | location is not entity identity and not a language channel | | `prompt_source` | prompt boilerplate is not unique latent content and not stopword deletion | | `corpus_background` | corpus-background wording is not unique latent content and not stopword deletion | | `modality_source` | non-lexical modality is not unique latent content and not stopword deletion | diff --git a/CHANGELOG.md b/CHANGELOG.md index 43a33fd2d..93f303d88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] +- Repaired post-consolidation merge fallout that left protected `main` red: restored the lost `return True` in the `check_coverage.py` match-guard branch, removed the shadowed duplicate `load_union_branch_totals` and `_is_multiline_match_guard` definitions plus duplicate workspace-crate entries (`episode_membership`, `analysis_engine`) from the contract tuple and Cargo member arrays, split two union-fused four-tuples back into `(variant, message)` pairs in the `event_core` error table, repaired the fused `identity_recovery_rate` body in `episode_membership::window`, deduplicated the checked-arithmetic eligible-count block in `analysis_engine`, fixed four-argument `unit()` test call sites, rebalanced the README crate-list fence around all 54 unique crates, and deduplicated the `location_membership`/`validation_core`/`tepp_api` architecture-table rows. Also documents private `PLAUSIBLE_IMAGE_MEDIA_TYPES` so `cargo doc -D warnings` passes. - Branch coverage JSON now unique-folds `files[].branches` True/False counts across instantiations. Nightly totals on #49 head `1e3e2eb` reported `event_time.rs` 505/506 while every unique site had both arms taken (253 sites × 2 instantiations). Summary-only reports without branch arrays still fail closed on totals. The 100% contract is unique production arms, matching the LCOV authored-line gate. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. - `psychometric_core` maps overflowing `expm1(a Δt)` / `expm1(2 a Δt)` in `recover_discrete_constant_predictor_effect` and `recover_discrete_process_noise` through the log-space rewrite without a redundant `if !argument.is_finite()` after overflow. Local crate llvm-cov on #49 head `559e7b399473ee90ba3234677dd9ef7f05f7fd2e` was 509/510: the same LLVM `exp`/`expm1` finite-argument proof as L768/L5040. Existing rewrite (`a = 800` / `a = 400`) and overflow (`a = 1e308`) tests remain the contract. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. - `psychometric_core` maps overflowing `e^{a Δt}` / `e^{a(t−u)}` through the log-space rewrite without redundant `if !argument.is_finite()` after `exp` overflow on lagged covariance, T0 TI/TD carry, and impulse carry. Nightly branch coverage on #49 head `7e669babcc54408dd8407bbac56be0f304fa99e5` was 1713/1714: LLVM counted `event_time.rs` L5040 True and treated the finite-argument overflow False as uncovered after proving `exp` of a finite argument is finite, which binary64 overflow falsifies. `fit_scalar_log_rate` now also skips a zero earlier residual and a negative lag while still recovering from a valid pair. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. diff --git a/Cargo.lock b/Cargo.lock index 3fe19cee5..4a3707814 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,11 +62,16 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" name = "analysis_engine" version = "0.1.0" dependencies = [ + "corpus_split", + "membership_core", + "relation_graph", "serde", "serde_json", "sha2", "temporal_core", "tepp_api", + "topic_measurement", + "uuid", ] [[package]] @@ -1138,6 +1143,10 @@ version = "0.1.0" name = "provider_receipt" version = "0.1.0" +[[package]] +name = "psychometric_core" +version = "0.1.0" + [[package]] name = "psychometric_fit" version = "0.1.0" @@ -1714,6 +1723,18 @@ dependencies = [ "uuid", ] +[[package]] +name = "topic_measurement" +version = "0.1.0" +dependencies = [ + "corpus_split", + "membership_core", + "relation_graph", + "temporal_core", + "uuid", + "validation_core", +] + [[package]] name = "tracing" version = "0.1.44" diff --git a/Cargo.toml b/Cargo.toml index 9e30b81a1..ffd31a215 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,11 +52,9 @@ members = [ "crates/checkpoint_authority", "crates/episode_membership", "crates/compute_backend", - "crates/episode_membership", "crates/membership_target", "crates/analysis_engine", "crates/topic_measurement", - "crates/analysis_engine", "crates/psychometric_core", ] default-members = [ @@ -111,11 +109,9 @@ default-members = [ "crates/checkpoint_authority", "crates/episode_membership", "crates/compute_backend", - "crates/episode_membership", "crates/membership_target", "crates/analysis_engine", "crates/topic_measurement", - "crates/analysis_engine", "crates/psychometric_core", ] diff --git a/README.md b/README.md index dc8daf3c4..f4cd991c9 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,10 @@ API exchange, compute planning, or evidence-grounded interpretation. Numerical and psychometric authority remains on the CPU `f64` reference path; streamed accelerator plans must preserve the full observation set and fail closed to the reference path when resources or validation are insufficient. -The repository currently implements 53 independently documented crates rather -than a full commercial release. The implemented crates include topic measurement -and the analysis engine; they do not claim a complete commercial estimator, +The repository currently implements 54 independently documented crates rather +than a full commercial release. The implemented crates include topic +measurement, the analysis engine, and psychometric input/recovery gates; they +do not claim a complete commercial estimator, operator workspace, or supported release. - `topic_measurement`: the first production topic-measurement crate. It @@ -40,47 +41,16 @@ operator workspace, or supported release. estimator contract; persists the run with the six TEPP clocks; and emits a typed terminal result. The crate does not claim buyer-visible product completeness. +- `psychometric_core`: validated statistical-recovery APIs on already-mapped + coordinates. It does not implement a full ESEM/DSEM estimator. -```text -crates/analysis_engine These are production contracts, not a claim that the complete commercial estimator, operator workspace, or supported release already exists. Read the [product and technical gap baseline](docs/product-technical-gap-baseline.md) before treating a crate as a shipped product capability. -This branch keeps the Rust workspace quality foundation and the bounded -foundation crates. Domain crates expose only tested contracts: immutable -evidence, six-clock temporal values, event mentions/instances, relations, -membership, persistence, splits, simulation, validation, API DTOs, and the -predicted-versus-observed promotion gate. -This branch establishes the Rust workspace, quality-gate foundation, and the -longitudinal within/between decomposition capability. The workspace bounded crates -compile independently. `longitudinal_core` exposes within/between decomposition -and component RMSE APIs; the remaining crates expose no placeholder production -APIs, and domain behavior for them begins in Task 2 with immutable evidence -identifiers and source records. -This branch establishes the Task 1 Rust workspace and quality-gate foundation. -The workspace bounded crates compile independently but intentionally expose no -The workspace bounded crates compile independently; Task 1 includes the -The twelve bounded crates compile independently but intentionally expose no -The eleven bounded crates compile independently; Task 1 includes the -implemented `encrypted_mapping` crate with AES-256-GCM sealing and -purpose-bound opening, while the remaining domain behavior begins in Task 2 -with immutable evidence identifiers and source records. -The workspace bounded crates compile independently. `derived_sensitivity` inherits -source Restricted/Internal classes onto topic, factor, and relation artifacts -and fails closed on unknown kinds; derivation and blanket PII masking are not -declassification. Other crates still begin domain behavior in Task 2 with -immutable evidence identifiers and source records. -The workspace bounded crates compile independently but intentionally expose no - -The eleven bounded crates compile independently but intentionally expose no -placeholder production APIs. Domain behavior begins in Task 2 with immutable -evidence identifiers and source records. -This branch establishes the Rust workspace and quality-gate foundation. The -bounded crates compile independently. Domain crates expose only validated -production APIs; placeholder surfaces are prohibited. ```text +crates/analysis_engine crates/assertion_clock crates/available_clock crates/checkpoint_authority @@ -96,46 +66,6 @@ crates/document_clocks crates/encrypted_mapping crates/episode_membership crates/event_clock -crates/evidence_core -crates/semantic_core -crates/temporal_core -crates/event_core -crates/evidence_core -crates/inferred_status -crates/intake_authorization -crates/interpretation_gateway -crates/location_membership -crates/longitudinal_core -crates/membership_core -crates/membership_target -crates/modality_source -crates/model_selection -crates/network_analysis -crates/operational_log -crates/outcome_order -crates/payload_bound -crates/persistence_postgres -crates/corpus_split -crates/tepp_simulation -crates/validation_core -crates/tepp_api -crates/analysis_engine -crates/episode_membership -crates/location_membership -crates/prediction_contradiction -crates/prompt_source -crates/provider_receipt -crates/corpus_background -crates/modality_source -crates/copied_text -crates/copy_identity -crates/corpus_background -crates/corpus_split -crates/cutoff_clock -crates/derived_sensitivity -crates/document_clocks -crates/encrypted_mapping -crates/event_clock crates/event_core crates/evidence_core crates/inferred_status @@ -161,7 +91,6 @@ crates/relation_graph crates/retrospective_edge crates/revision_order crates/semantic_core -crates/operational_log crates/service_tls crates/stopword_deletion crates/style_source @@ -175,13 +104,6 @@ crates/tepp_simulation crates/topic_lineage crates/topic_measurement crates/validation_core -crates/network_analysis -crates/interpretation_gateway -crates/model_selection -crates/checkpoint_authority -crates/compute_backend -crates/episode_membership -crates/membership_target ``` diff --git a/crates/analysis_engine/src/lib.rs b/crates/analysis_engine/src/lib.rs index 8f85e895b..91a11daf4 100644 --- a/crates/analysis_engine/src/lib.rs +++ b/crates/analysis_engine/src/lib.rs @@ -312,12 +312,6 @@ pub fn execute_analysis_run( sum.checked_add(u64::from(unit.membership_count)) .ok_or(AnalysisEngineError::ArithmeticOverflow) })?; - // The corpus bound makes this conversion and sum strictly smaller than - // `u64::MAX`: 100,000 * u32::MAX is below the 64-bit range. - let eligible_evidence_count = eligible.len() as u64; - let eligible_membership_count = eligible - .iter() - .fold(0_u64, |sum, unit| sum + u64::from(unit.membership_count)); let (earliest, latest) = eligible.iter().fold( (eligible[0].event_time, eligible[0].event_time), |(earliest, latest), unit| (earliest.min(unit.event_time), latest.max(unit.event_time)), @@ -387,7 +381,6 @@ mod tests { use super::{ ANALYSIS_ARTIFACT_SCHEMA_VERSION, ANALYSIS_STATISTIC_COUNT, AnalysisCorpus, AnalysisEngineError, AnalysisEvidenceUnit, MAX_ANALYSIS_IDENTIFIER_BYTES, - MAX_EVIDENCE_UNITS, execute_analysis_run, MAX_EVIDENCE_UNITS, TopicMeasurementError, execute_analysis_run, }; use temporal_core::{AvailableTime, EventTime}; @@ -539,6 +532,7 @@ mod tests { vec![unit( "evidence-1", "2026-07-01T00:00:00Z", + "2026-07-01T00:00:00Z", 1, )], ) @@ -554,6 +548,7 @@ mod tests { vec![unit( "evidence-1", "2026-07-01T00:00:00Z", + "2026-07-01T00:00:00Z", 1, )], ) @@ -594,6 +589,7 @@ mod tests { let evidence = unit( "evidence-accessor", "2026-07-01T00:00:00Z", + "2026-07-01T00:00:00Z", 4, ); assert_eq!(evidence.evidence_id(), "evidence-accessor"); @@ -668,6 +664,7 @@ mod tests { vec![unit( "evidence-1", "2026-07-01T00:00:00Z", + "2026-07-01T00:00:00Z", 1, )], ) diff --git a/crates/episode_membership/src/window.rs b/crates/episode_membership/src/window.rs index fd5fa0cc3..afe8798b9 100644 --- a/crates/episode_membership/src/window.rs +++ b/crates/episode_membership/src/window.rs @@ -72,12 +72,6 @@ pub fn identity_recovery_rate( if truth.is_empty() || truth.len() != decided.len() { return Err(EpisodeMembershipError::InvalidEpisodePayload); } - let matches = truth - .iter() - .zip(decided) - .filter(|(truth_flag, decided_flag)| truth_flag == decided_flag) - .count(); - Ok(matches as f64 / truth.len() as f64) let matches = count_matching_decisions(truth.iter().copied().zip(decided.iter().copied())); Ok(recovery_rate_from_tally(matches, truth.len())) } diff --git a/crates/event_core/src/error.rs b/crates/event_core/src/error.rs index 7ada37de3..cf6bd4f70 100644 --- a/crates/event_core/src/error.rs +++ b/crates/event_core/src/error.rs @@ -146,12 +146,16 @@ mod tests { ( EventError::UnknownEventLinkLabel, "unknown event link label", + ), + ( EventError::FirstStoryIsNotEventInstance, "first-story detection is not an event instance", ), ( EventError::UnknownFirstStoryLabel, "unknown first-story label", + ), + ( EventError::EventTrackIsNotEventInstance, "event track is not an event instance", ), diff --git a/crates/evidence_core/src/image_unit.rs b/crates/evidence_core/src/image_unit.rs index 84953336c..a35588b1e 100644 --- a/crates/evidence_core/src/image_unit.rs +++ b/crates/evidence_core/src/image_unit.rs @@ -7,6 +7,9 @@ const BASE64_MARK: &str = ";base64,"; /// Image media types accepted as plausible by [`embedded_image_units`]. /// +/// Kept private by design; the accepted set is surfaced only through the +/// parse contract of [`embedded_image_units`]. +/// /// The set is deliberately conservative and tracks widely registered or /// de facto standard image subtypes; anything else fails closed instead of /// yielding a bogus embedded-image unit. @@ -51,7 +54,7 @@ impl<'document> EmbeddedImageUnit<'document> { /// Locate `data:image/;base64,...` units and retain their original spans. /// /// Only plausible image media types are accepted: a candidate URI whose -/// declared media type is not in [`PLAUSIBLE_IMAGE_MEDIA_TYPES`] fails the +/// declared media type is not in the plausible-image set fails the /// whole parse so malformed bodies cannot produce bogus units. /// /// # Errors diff --git a/crates/persistence_postgres/src/entity_sql.rs b/crates/persistence_postgres/src/entity_sql.rs index fcb5c196a..0a576b39f 100644 --- a/crates/persistence_postgres/src/entity_sql.rs +++ b/crates/persistence_postgres/src/entity_sql.rs @@ -123,6 +123,9 @@ mod tests { "author;role".into(), "author\\".into(), "author\nrole".into(), + // Uppercase bytes pass the alphanumeric gate yet fail the lowercase + // gate, so this exercises the final clause of the validation chain. + "AUTHOR".into(), "a".repeat(129), ] { assert_eq!( diff --git a/crates/persistence_postgres/src/project_sql.rs b/crates/persistence_postgres/src/project_sql.rs index 1ea7daf26..b3ef010ae 100644 --- a/crates/persistence_postgres/src/project_sql.rs +++ b/crates/persistence_postgres/src/project_sql.rs @@ -123,6 +123,9 @@ mod tests { "active;closed".into(), "active\\".into(), "active\nclosed".into(), + // Uppercase bytes pass the alphanumeric gate yet fail the lowercase + // gate, so this exercises the final clause of the validation chain. + "ACTIVE".into(), "s".repeat(129), ] { assert_eq!( diff --git a/scripts/check_coverage.py b/scripts/check_coverage.py index fb543e5a9..5a7371bf7 100644 --- a/scripts/check_coverage.py +++ b/scripts/check_coverage.py @@ -37,45 +37,6 @@ def load_totals(path: Path) -> Mapping[str, Any]: return merged -def load_union_branch_totals(files: Sequence[object]) -> Mapping[str, int | float]: - """Merge LLVM branch outcomes by source coordinate across test binaries.""" - - outcomes: dict[tuple[str, int, int, int, int], list[int]] = {} - for file_record in files: - if not isinstance(file_record, Mapping): - raise ValueError("coverage file record must be an object") - filename = file_record.get("filename") - if "branches" not in file_record: - raise ValueError("coverage file record must contain branches") - branches = file_record["branches"] - if not isinstance(filename, str) or not filename: - raise ValueError("coverage file record must contain a filename") - if not isinstance(branches, list): - raise ValueError("coverage branches must be a list") - for branch in branches: - if not isinstance(branch, list) or len(branch) < 6: - raise ValueError("coverage branch record is malformed") - coordinates = branch[:4] - counts = branch[4:6] - if not all( - isinstance(value, int) and not isinstance(value, bool) and value >= 0 - for value in coordinates - ): - raise ValueError("coverage branch coordinates are invalid") - if not all( - isinstance(value, int) and not isinstance(value, bool) and value >= 0 - for value in counts - ): - raise ValueError("coverage branch counts are invalid") - key = (filename, *coordinates) - outcome = outcomes.setdefault(key, [0, 0]) - outcome[0] += counts[0] - outcome[1] += counts[1] - count = len(outcomes) * 2 - covered = sum(outcome > 0 for counts in outcomes.values() for outcome in counts) - return {"count": count, "covered": covered} - - def _parse_branch_record(record: object) -> tuple[tuple[int, int, int, int], int, int]: """Return ``(site, true_count, false_count)`` from one LLVM branch tuple. @@ -303,7 +264,6 @@ def is_executable_source_line( or text.startswith("if(") or " if(" in text ): - if text.startswith("if ") or text.startswith("if("): return True return _is_multiline_match_guard(lines, line_number) if text.startswith("pub struct ") or text.startswith("struct "): @@ -324,30 +284,6 @@ def is_executable_source_line( return True -def _is_multiline_match_guard(lines: list[str], line_number: int) -> bool: - """Recognize a guard continued onto the lines immediately before an arm.""" - - target_prefix = lines[line_number - 1].strip().partition("=>")[0] - brace_depth = target_prefix.count("}") - target_prefix.count("{") - guard_found = False - boundary_candidate = False - for candidate in reversed(lines[: line_number - 1]): - stripped = candidate.strip() - if brace_depth == 0 and "=>" in stripped: - return guard_found and not boundary_candidate - if brace_depth == 1 and stripped.endswith("=> {"): - boundary_candidate = True - brace_depth += stripped.count("}") - stripped.count("{") - if ( - (stripped.startswith("if ") or stripped.startswith("if(")) - and not stripped.endswith(("}", ";")) - and brace_depth == 0 - ): - guard_found = True - if stripped.startswith("match "): - return guard_found and not boundary_candidate - return guard_found and not boundary_candidate - def _is_structural_comma_continuation( lines: list[str], line_number: int, text: str ) -> bool: diff --git a/scripts/check_workspace_contract.py b/scripts/check_workspace_contract.py index b1d665c61..6b6b73ac2 100644 --- a/scripts/check_workspace_contract.py +++ b/scripts/check_workspace_contract.py @@ -64,11 +64,9 @@ "checkpoint_authority", "episode_membership", "compute_backend", - "episode_membership", "membership_target", "analysis_engine", "topic_measurement", - "analysis_engine", "psychometric_core", ) diff --git a/tests/quality/test_check_coverage.py b/tests/quality/test_check_coverage.py index 3416c87b8..12ead85f8 100644 --- a/tests/quality/test_check_coverage.py +++ b/tests/quality/test_check_coverage.py @@ -1241,32 +1241,33 @@ def test_structural_comma_continuation_edge_cases(self) -> None: with tempfile.TemporaryDirectory() as temporary: source = Path(temporary) / "commas.rs" - # Lines 211->215 and 212->211: loop skips blank lines and non-matching - source.write_text( - "fn example() {\n" - " let value = foo(\n" - "\n" - " 1,\n" - " );\n" - "}\n", - encoding="utf-8", - ) + # A comma-terminated line whose preceding lines are entirely blank + # exhausts the previous-line scan (arcs 211->215 and 212->211). + source.write_text("\n\nfoo,\n", encoding="utf-8") self.assertTrue( - coverage_contract.is_executable_source_line(str(source), 2) + coverage_contract.is_executable_source_line(str(source), 3) ) - # Line 318->311: while loop with backslash at end of line inside string - source.write_text( - 'fn path() {\n' - ' let s = "a\\\n' - 'b";\n' - "}\n", - encoding="utf-8", - ) - self.assertTrue( - coverage_contract.is_executable_source_line(str(source), 2) + # A blank candidate between the comma line and its previous + # non-empty line is skipped by the same scan. + source.write_text("bar(\n\n baz,\n", encoding="utf-8") + self.assertFalse( + coverage_contract.is_executable_source_line(str(source), 3) ) + def test_multiline_string_scanner_covers_escaped_char_literals(self) -> None: + """An escaped character inside a char literal keeps the scanner in loop. + + The backslash inside a character literal must clear through the + escape-tracking branch so a following quote cannot close the literal + early; this exercises the scanner's escaped-character arc (318->311). + """ + + lines = ["fn f() {", r" let newline = '\n';", "}"] + self.assertFalse( + coverage_contract._line_in_multiline_string(lines, 3) + ) + if __name__ == "__main__": # pragma: no cover unittest.main()