diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 1a9d69801..dcde40ac7 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -68,6 +68,7 @@ boundaries above remain the target modular MSA architecture. | `tepp_simulation` | known-truth temporal/event data generation | | `validation_core` | RMSE, bias, coverage, graph, and Monte Carlo 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 | diff --git a/CHANGELOG.md b/CHANGELOG.md index 54c65738f..43a33fd2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,16 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang - `persistence_postgres` entity/project target SQL now rejects empty, oversized, or hostile type/status labels before insert; interpolated codes are restricted to lowercase ASCII `snake_case` characters so membership foreign keys remain referentially safe (ADR 0003 / ADR 0013). - `persistence_postgres` live SQLx transport retains one pool-backed PostgreSQL connection per session so tenant binding and the following statement share a session, and closes the connection and owned runtime safely from another Tokio runtime (ADR 0013). - The authored-line coverage gate now filters LLVM-only literal and expression continuation records while retaining branch coverage for their executable decisions; Rust function signatures and structural branch lines are no longer counted as uncovered statements. +- Stacked `analysis_engine` vertical slice (ADR 0021): bounded Rust execution + from an accepted analysis run to a cutoff-safe, multiple-membership-aware, + SHA-256-digest-bound terminal artifact or redacted no-eligible-evidence + result. This remains active-PR evidence and does not claim estimator + authority. +- `tepp_api` request-bound terminal analysis results and typed analysis-run + status/read responses: accepted/running states cannot carry measurement + evidence, and terminal results bind exact request and receipt identities. +- Coverage classification now preserves multiline Rust `match` guard expression + lines while ignoring structural closing parentheses and match-arm labels. - `corpus_split` Unicode canonical identity: NFC/NFD-equivalent bodies produce `CanonicalEquivalent` leakage links and cannot occupy independent partitions; empty bodies and duplicate document identities fail closed (ADR 0004/0008/0013; PR #59). - `semantic_core` binds exact `evidence_core` source spans as semantic units. Language profiles are `unresolved` or a primary ISO 639 subtag with an IANA-registered ISO 3166-1 alpha-2 or UN M.49 region (RFC 5646; IANA File-Date 2026-08-08); private-use and unknown regions fail closed. Unresolved metadata keeps the caller-supplied Korean `측정` span and does not retokenize. `SemanticIdentity::from_language_tag` fails closed. Korean and English report sentences remain distinct units. Not concept alignment, not invariance, not a topic estimator (ADR 0020; issue #168). The APA register cites RFC 5646 once, in the Unicode/language-tags section; the slice-specific note remains `docs/research/span-grounded-semantic-units.md`. - `corpus_background` identity gate: corpus-level background wording is not unique latent content or a state transition; recovery tests distinguish background evidence from unique content. diff --git a/Cargo.lock b/Cargo.lock index 4a3707814..3fe19cee5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,16 +62,11 @@ 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]] @@ -1143,10 +1138,6 @@ 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" @@ -1723,18 +1714,6 @@ 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 c9bf316b4..9e30b81a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ members = [ "crates/compute_backend", "crates/episode_membership", "crates/membership_target", + "crates/analysis_engine", "crates/topic_measurement", "crates/analysis_engine", "crates/psychometric_core", @@ -112,6 +113,7 @@ default-members = [ "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 b9240a92e..dc8daf3c4 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,25 @@ implemented in Rust. ## Current implementation state +This branch preserves the protected-main Rust workspace and quality-gate +foundation. The workspace crates expose tested contracts only: immutable +evidence identities and exact spans, six-clock temporal values with Allen +algebra and cutoff eligibility, event mentions/instances with +evidence-layer intelligence gates, a forward-only relation graph, +cross-classified membership, bitemporal persistence, leakage-safe corpus +splits, simulation manifests, claim-promotion validation, API DTOs, the +purpose-bound privacy envelope, and longitudinal within/between +decomposition. It adds the independently usable `analysis_engine` vertical +slice: bounded cutoff-safe readiness work that emits a digest-bound terminal +artifact or a redacted no-eligible-evidence result. That slice is active-PR +evidence, not a psychometric estimator or a release claim. +The current workspace contains 50 independently documented Rust crates. Each +crate exposes a bounded, tested contract for evidence, temporal semantics, +event and relation reasoning, membership, persistence, simulation, validation, +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, @@ -100,6 +119,7 @@ crates/corpus_split crates/tepp_simulation crates/validation_core crates/tepp_api +crates/analysis_engine crates/episode_membership crates/location_membership crates/prediction_contradiction diff --git a/crates/analysis_engine/src/lib.rs b/crates/analysis_engine/src/lib.rs index b807e607d..8f85e895b 100644 --- a/crates/analysis_engine/src/lib.rs +++ b/crates/analysis_engine/src/lib.rs @@ -6,6 +6,7 @@ //! was unavailable at the requested knowledge cutoff, counts multiple-membership //! assignments without collapsing them, and emits a digest-bound terminal result //! through [`tepp_api`]. It deliberately does not claim latent-variable or topic +//! estimation authority; those estimators remain separate scientific crates. //! estimation authority; it invokes estimators through their scientific crate //! contracts and preserves their artifact meaning. @@ -303,6 +304,14 @@ pub fn execute_analysis_run( }); } + // The corpus bound makes this conversion strictly smaller than + // `u64::MAX`; the fold still fails closed through checked arithmetic so a + // future bound change cannot wrap membership totals silently. + let eligible_evidence_count = eligible.len() as u64; + let eligible_membership_count = eligible.iter().try_fold(0_u64, |sum, unit| { + 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; @@ -378,6 +387,7 @@ 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}; @@ -529,7 +539,6 @@ mod tests { vec![unit( "evidence-1", "2026-07-01T00:00:00Z", - "2026-07-01T00:00:00Z", 1, )], ) @@ -545,7 +554,6 @@ mod tests { vec![unit( "evidence-1", "2026-07-01T00:00:00Z", - "2026-07-01T00:00:00Z", 1, )], ) @@ -586,7 +594,6 @@ 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"); @@ -661,7 +668,6 @@ mod tests { vec![unit( "evidence-1", "2026-07-01T00:00:00Z", - "2026-07-01T00:00:00Z", 1, )], ) diff --git a/crates/analysis_engine/tests/end_to_end_contract.rs b/crates/analysis_engine/tests/end_to_end_contract.rs index 829e56f5d..e1a01258c 100644 --- a/crates/analysis_engine/tests/end_to_end_contract.rs +++ b/crates/analysis_engine/tests/end_to_end_contract.rs @@ -53,6 +53,39 @@ fn production_shape_run_excludes_future_available_evidence() { ); } +#[test] +fn evidence_available_exactly_at_cutoff_is_eligible_and_keeps_membership() { + let request = AnalysisRunRequest { + contract_version: 1, + idempotency_key: "boundary-run-2026-08-01".into(), + tenant_workspace_id: "workspace-opaque-1".into(), + snapshot_id: "snapshot-boundary-2026-08-01".into(), + knowledge_cutoff: "2026-08-01T00:00:00Z".into(), + model_contract_version: "temporal-evidence-v1".into(), + output_profile: "validation-report".into(), + }; + let accepted = + AnalysisRunAccepted::new("run-boundary-1", "accepted", "boundary-run-2026-08-01") + .expect("accepted"); + let corpus = AnalysisCorpus::new( + "snapshot-boundary-2026-08-01", + vec![ + evidence("invoice-on-cutoff", "2026-08-01T00:00:00Z", 3), + evidence("later-correction", "2026-08-01T00:00:01Z", 4), + ], + ) + .expect("snapshot"); + let execution = execute_analysis_run(&request, &accepted, &corpus, "2026-08-01T00:01:00Z") + .expect("execute"); + assert_eq!( + execution.terminal_result.run_state, + AnalysisRunTerminalState::Succeeded + ); + let artifact = execution.artifact.expect("artifact"); + assert_eq!(artifact.eligible_evidence_count, 1); + assert_eq!(artifact.eligible_membership_count, 3); +} + #[test] fn snapshot_identity_is_not_inferred_from_customer_payload() { let request = AnalysisRunRequest { diff --git a/docs/API_CONTRACT.md b/docs/API_CONTRACT.md index 9a64e2007..d97c65133 100644 --- a/docs/API_CONTRACT.md +++ b/docs/API_CONTRACT.md @@ -1,6 +1,7 @@ # TEPP API and Modular Integration Contract **Status:** Accepted target contract; exact endpoints are introduced only with executable services. +**Last reviewed:** 2026-08-24 **Last reviewed:** 2026-08-21 ## 1. Authority boundary @@ -25,6 +26,8 @@ Current protected main exposes Rust library/domain contracts. The active stack a | temporal-context ordering contract | `tepp_api` v1 wire DTOs | LineageWeave | active-PR | | cutoff-safe analysis-run readiness execution | `analysis_engine` bounded Rust crate | `tepp_api`, future HTTP/service adapters | active product branch | | project-history projection contract | `tepp_api` v1 wire DTOs | LineageWeave | active-PR | +| analysis-run status/terminal-result contracts | `tepp_api` v1 wire DTOs | naruon, orchestrator, UI | active-PR #157 | +| cutoff-safe analysis-run readiness execution | `analysis_engine` bounded Rust crate | `tepp_api`, future HTTP/service adapters | active-PR | ## 3. Versioning @@ -57,6 +60,18 @@ GET /v1/exports/{export_id} Long-running analysis is durable asynchronous work. `POST /v1/analysis-runs` accepts an idempotency key, immutable input snapshot identity, knowledge cutoff, versioned model contract/configuration, and requested output profile. A retry with the same principal/idempotency key and semantically identical request returns the same run identity; a conflicting body fails closed. +The typed status/read contract returns `accepted`, `running`, `succeeded`, or +`failed`. Accepted and running statuses contain no measurement result. A +terminal status contains exactly one request-bound `AnalysisRunTerminalResult`; +consumers validate its request, receipt, snapshot, cutoff, model, profile, and +idempotency bindings before treating it as measurement evidence. + +The stacked `analysis_engine` slice provides the first executable service-side +path behind these DTOs. It consumes a bounded identity-free snapshot, excludes +evidence unavailable at the historical cutoff, preserves multiple-membership +counts, and emits a digest-bound terminal result or a redacted failure. It is +not a substitute for approved topic or psychometric estimators. + `POST /v1/temporal-context` is a bounded LineageWeave read contract. It accepts only events whose availability time is at or before `knowledge_cutoff`, orders them by event time and opaque event ID, and emits adjacent forward temporal diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index 25dab9147..c6fdbb4fc 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -49,6 +49,8 @@ The full APA 7th standards/literature register remains `docs/research/standards- | recovery metrics (RMSE, bias, coverage, graph, temporal order, Monte Carlo SE gates) | PRD; Test Strategy; ADR 0007/0014 | `validation_core` on protected main (PR #19); SE-aware Monte Carlo gates included | implemented-main | | PostgreSQL bitemporal/lineage persistence | ADR 0013; Architecture/ERD | `persistence_postgres` migration contracts, in-memory adapters, live SQL session/document SQL port, tenant RLS (`0002` + session GUC/role helpers), `DATABASE_URL` SQLx gate, optional session-affine `live-sqlx` `PgPool` driver, exact-head live PostgreSQL CI with isolation proof, append-only immutability triggers (`0004`), temporal interval ordering CHECKs (`0005`), typed membership assignment (`0006` implemented-main), event-relation/mention/instance SQL (#37–#39 implemented-main), source-artifact SQL (#40 implemented-main), audit-event SQL (#41 implemented-main), concurrent document-write stress (#43 implemented-main), backup/restore integrity revalidation (#44 implemented-main), `revision_order` later-revision system-time ordering implemented-main, entity/project target SQL on PR #131; remaining physical ERD constraints | partial | | known-truth temporal/event simulation manifests | PRD; TRD; Test Strategy | `tepp_simulation` on protected main; recovery metrics in `validation_core` | implemented-main | +| versioned service/API contracts and exports | PRD; API contract; ADR 0011/0013 | `tepp_api` analysis-run/export/JSON-LD/GraphML contracts on protected main (PR #21); request-bound terminal result active in PR #157; HTTP service remains accepted-target | partial | +| executable cutoff-safe analysis-run readiness | ADR 0021; temporal research; API terminal-result contract | stacked `analysis_engine` PR on #157: availability cutoff, snapshot binding, multiple-membership aggregation, digest-bound artifact, realistic end-to-end tests | active-PR | | delayed-reporting cutoff eligibility in truth corpora | ADR 0002; research | `tepp_simulation` eligible-at-cutoff filter on the active PR | active-PR | | versioned service/API contracts and exports | PRD; API contract; ADR 0011/0013 | `tepp_api` analysis-run/export/JSON-LD/GraphML contracts on protected main (PR #21); HTTP service remaining accepted-target | partial | | versioned service/API contracts and exports | PRD; API contract; ADR 0011/0013 | `tepp_api` analysis-run/export/JSON-LD/GraphML contracts on protected main (PR #21); LineageWeave loopback contracts and request-bound terminal result are composed on the active product branch; production TLS remaining | partial | diff --git a/docs/adr/0003-relational-event-multiple-membership.md b/docs/adr/0003-relational-event-multiple-membership.md index 0ebe49067..25dadeb2e 100644 --- a/docs/adr/0003-relational-event-multiple-membership.md +++ b/docs/adr/0003-relational-event-multiple-membership.md @@ -5,7 +5,9 @@ **Implementation maturity:** partial — membership networks, event mention/instance separation, and the protected-main forward-transition foundation are implemented-main; `support_edge`, `outcome_order`, `retrospective_edge`, `inferred_status`, `copy_identity`, `summarizes_edge`, `subevent_containment`, `location_membership`, `episode_membership`, and typed target kinds are covered by this active consolidation PR; full multilevel/MMMC estimators and remaining persistence remain accepted-target. **Date:** 2026-08-05 **Implementation maturity:** partial — membership network, event mention/instance separation, inferred/evidential/retrospective status gates, summary/source identity separation, template-copy/source identity separation, typed forward-only relation graph, strict input-process-outcome ordering, nested ICC refusal, and subevent parent-window containment are implemented-main; full multilevel/MMMC estimators and remaining persistence remain accepted-target. -**Date:** 2026-08-24 +**Date:** 2026-08-24 +**Decision status:** Accepted +**Implementation maturity:** partial — membership network/roles with Kish ESS and nested ICC (cross-classified/multiple-membership refusal), event mention/instance separation, the typed forward-only relation graph, and the copy/summary/outcome-order/support/inferred-status/retrospective-reporting/location identity gates are implemented-main; typed target-kind membership identity in `membership_target` is on PR #131; multilevel psychometric estimators and remaining persistence details follow ADR 0013 and [`docs/TRACEABILITY.md`](../TRACEABILITY.md) as accepted-target. **Supersedes:** None. ADR 0016 owns TDT/CHRONOS event-intelligence task semantics; this ADR remains authoritative for ontology, relation, role, and membership structure. ## Context diff --git a/docs/adr/0011-standalone-modular-msa-boundary.md b/docs/adr/0011-standalone-modular-msa-boundary.md index d55aa8dde..42d744139 100644 --- a/docs/adr/0011-standalone-modular-msa-boundary.md +++ b/docs/adr/0011-standalone-modular-msa-boundary.md @@ -1,6 +1,7 @@ # ADR 0011 — Standalone operation and modular CWL MSA boundary **Decision status:** Accepted +**Implementation maturity:** partial — Rust crates are independently usable; naruon HTTP interchange is implemented-main, while the loopback live listener (`POST /v1/analysis-runs` and `/v1/exports`, fail-closed table-access, NIM/proxy headers, RFC 3339 cutoff, stream deadline) is active-PR #157; `service_tls` production rustls bind gates and remaining live HTTP listeners and persistence integrations remain accepted-target **Implementation maturity:** partial — Rust crates are independently usable; naruon HTTP interchange and loopback live listener (`POST /v1/analysis-runs` and `/v1/exports`, fail-closed table-access, NIM/proxy headers, RFC 3339 cutoff, stream deadline) is implemented-main; `service_tls` production rustls bind gates and orchestrator live-port refusal of loopback plaintext are on this active PR; the loopback consumer listener composition and terminal-result contract are composed on the active product branch; production TLS/`$PORT`, remaining live HTTP listeners, and remaining persistence integrations remain accepted-target **Date:** 2026-08-10 **Date:** 2026-08-10 diff --git a/docs/adr/0012-temporal-relational-shared-latent-topic-measurement.md b/docs/adr/0012-temporal-relational-shared-latent-topic-measurement.md index de41bde4b..d824ceb8a 100644 --- a/docs/adr/0012-temporal-relational-shared-latent-topic-measurement.md +++ b/docs/adr/0012-temporal-relational-shared-latent-topic-measurement.md @@ -4,6 +4,9 @@ **Implementation maturity:** partial — bounded identity, recovery, temporal lineage, network geometry, model-selection, preprocessing, method-effect, and modality gates are implemented across the foundation crates; the TRSL-TM estimator, global topic identity, method effects, and backend interchange remain accepted-target. **Implementation maturity:** partial — logistic-normal ALR/ILR coordinates, lexical-weight refusal, statistical/Pareto candidate-`K` gates, stable active/dormant/reactivated topic identity, and the bounded CPU `f64` reference estimator are implemented on the active product branch; `corpus_background`, `topic_lineage`, `network_analysis`, `model_selection`, `stopword_deletion`, `style_source`, `copied_text`, and `modality_source` implement bounded identity and recovery gates implemented-main; method effects, calibrated posterior acceptance, accelerated backends, and backend interchange remain accepted-target until implemented and protected-main integrated **Implementation maturity:** partial — `corpus_background`, `topic_lineage`, `network_analysis`, `model_selection`, `stopword_deletion`, `style_source`, `copied_text`, and `modality_source` implement bounded identity and recovery gates; the TRSL-TM estimator, method effects, global topic identity, and backend interchange remain accepted-target. +**Date:** 2026-08-24 +**Decision status:** Accepted +**Implementation maturity:** partial — the prompt-source, corpus-background, modality-source, copied-text, style-source, and default-stopword-deletion identity gates plus `topic_lineage` single-identity persistence across dormancy/reactivation are implemented-main; `network_analysis` cluster-pair scoring and `model_selection` candidate-K gates remain on their open PRs; the TRSL-TM estimator, global topic identity, method-effect model, and backend interchange remain accepted-target. **Date:** 2026-08-12 **Decision status:** Accepted **Implementation maturity:** accepted-target — prompt-versus-unique-content identity in `prompt_source` on the active PR; estimator-side method model remains accepted-target diff --git a/docs/adr/0021-deterministic-analysis-run-execution.md b/docs/adr/0021-deterministic-analysis-run-execution.md new file mode 100644 index 000000000..cf2dfcd0b --- /dev/null +++ b/docs/adr/0021-deterministic-analysis-run-execution.md @@ -0,0 +1,79 @@ +# ADR 0021 — Deterministic cutoff-safe analysis-run execution + +**Decision status:** Accepted +**Implementation maturity:** active-PR — stacked on PR #157; not implemented-main +**Date:** 2026-08-21 +**Supersedes:** None; complements ADR 0002, ADR 0003, ADR 0011, ADR 0013, and the terminal-result contract introduced by PR #157. +**Figma File ID:** N/A — this increment changes a Rust service crate and has no user-interface surface. +**Storybook inventory:** N/A — no reusable web object or interaction changed. + +## Context + +TEPP already accepts an analysis request and can describe a completed result, +but a buyer needs a demonstrable path between those contracts. Without one +bounded execution slice, an accepted run is only a receipt and consumers cannot +verify cutoff safety, multiple-membership preservation, or artifact identity. + +## Decision + +Add the standalone `analysis_engine` Rust crate as the first executable vertical +slice. It consumes a request, an accepted receipt, and a bounded identity-free +evidence snapshot. It: + +- excludes evidence whose `available_time` is later than the request's + `knowledge_cutoff`; +- preserves multiple-membership assignments by summing their counts rather than + reducing an evidence unit to one group; +- binds the result to the accepted run and source snapshot; +- verifies request/receipt idempotency identity before scanning the corpus; +- emits a canonical SHA-256-digested `AnalysisArtifact` and the versioned + `AnalysisRunTerminalResult` from `tepp_api`; +- returns a content-redacted failed terminal result when no evidence is + eligible; and +- remains a readiness/counting slice, not latent-variable, topic, or + psychometric estimator authority. + +The engine is deterministic, synchronous, bounded to `100_000` evidence units, +and CPU-only. Scientific estimators and their Rust CPU `f64`/GPU parity +contracts remain separate boundaries under ADR 0001 and ADR 0006. + +## Alternatives considered + +1. Keep the API as contracts only — rejected because an accepted run would not + produce a buyer-verifiable terminal outcome. +2. Put execution into `tepp_api` — rejected because transport contracts and + scientific execution would become one service boundary. +3. Add a bounded standalone engine behind the existing contracts — accepted + because it is independently testable and composable without shared tables. + +## Consequences + +Consumers can run a reproducible readiness check while seeing only opaque +identifiers, bounded counts, temporal extrema, and a digest. The engine does +not expose source text or identity mappings and does not claim a psychometric +measurement. The initial linear scan is intentionally simple; a production +large-corpus adapter must stream snapshots and preserve the same artifact +semantics before raising the bound. + +## Verification + +The stacked PR includes Rust unit and integration tests for cutoff exclusion, +multiple-membership summation, snapshot binding, duplicate identities, empty +eligibility, receipt validation, and package identity. Run: + +```text +cargo fmt --all -- --check +cargo test -p analysis_engine +cargo clippy -p analysis_engine --all-targets -- -D warnings +``` + +The supporting research and APA 7th citations are recorded in +`docs/doctoring/analysis-engine-v1.md` and the standards register. + +## Rollback and supersession + +Rollback removes the `analysis_engine` workspace member and stops publishing +the readiness artifact while preserving the request and terminal-result DTOs. +No persisted schema migration is introduced. Supersession requires a new ADR +if execution changes cutoff semantics, artifact authority, privacy fields, or +scientific estimands. diff --git a/docs/adr/README.md b/docs/adr/README.md index e659bf328..e265cb3f1 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -95,6 +95,7 @@ Read [`ADR_POLICY.md`](ADR_POLICY.md) first. **Decision status and implementatio | [0015](0015-autonomous-development-review-and-merge-authority.md) | Autonomous development, review, and merge authority separation | Accepted | active-PR | Separates model proposal, deterministic verification, publication, independent review, and merge/release authority. | | [0016](0016-tdt-chronos-event-intelligence-boundary.md) | TDT, CHRONOS, and Event Ontology intelligence boundary | Accepted | active-PR | TDT link precision/recall in existing `event_core`; remaining TDT/CHRONOS stack remains accepted-target. | | [0016](0016-tdt-chronos-event-intelligence-boundary.md) | TDT, CHRONOS, and Event Ontology intelligence boundary | Accepted | accepted-target | Separates observed evidence, detection/tracking, prediction/schema inference, temporal consistency, and promoted transition authority. | +| [0021](0021-deterministic-analysis-run-execution.md) | Deterministic cutoff-safe analysis-run execution | Accepted | active-PR | Stacked on PR #157; closes the first executable buyer path from accepted run to digest-bound terminal result without claiming estimator authority. | | [0016](0016-tdt-chronos-event-intelligence-boundary.md) | TDT, CHRONOS, and Event Ontology intelligence boundary | Accepted | active-PR | First-story FAR/miss in existing `event_core`; remaining TDT/CHRONOS stack remains accepted-target. | | [0020](0020-span-grounded-semantic-units.md) | Span-grounded semantic units; language tags are not identity | Accepted | active-PR | First ADR 0004 production slice. Does not claim concept alignment, invariance, or a topic estimator. | | [0017](0017-hourly-contextual-orchestrator-gateway.md) | Hourly contextual-orchestrator gateway and all-provider model discovery | Accepted | active-PR | Keeps proposal-model execution behind a pinned loopback gateway while preserving independent verifier, publisher, reviewer, and merge authority. | @@ -123,6 +124,7 @@ Use the narrowest owning ADR when decisions overlap: - **claim maturity / release evidence:** ADR 0014; - **autonomous development/review/merge authority:** ADR 0015; - **TDT/CHRONOS event intelligence:** ADR 0016; +- **accepted-run execution and terminal artifact production:** ADR 0021. - **hourly proposal gateway and provider discovery:** ADR 0017. - **modular consumer admission / replay identity:** ADR 0018. - **project-history wire-size symmetry:** ADR 0019. diff --git a/docs/connectors/naruon-artifact-consumer.md b/docs/connectors/naruon-artifact-consumer.md index d49a86b49..9c6f6d185 100644 --- a/docs/connectors/naruon-artifact-consumer.md +++ b/docs/connectors/naruon-artifact-consumer.md @@ -1,5 +1,6 @@ # naruon modular consumer contract for TEPP artifacts +**Status:** Partial — versioned DTO and HTTP interchange are implemented-main; the loopback live listener is active-PR #157; production TLS/`$PORT` remaining **Status:** Partial — versioned DTO and HTTP interchange are implemented-main at protected head `c45be17a9dbce95ef81cee230e9d128abc7160ac`; the loopback live listener and terminal-result contract are composed on the active product branch; production TLS/`$PORT` remaining **Last reviewed:** 2026-08-16 diff --git a/docs/doctoring/analysis-engine-v1.md b/docs/doctoring/analysis-engine-v1.md index a54c5dbd2..7c071d411 100644 --- a/docs/doctoring/analysis-engine-v1.md +++ b/docs/doctoring/analysis-engine-v1.md @@ -32,6 +32,9 @@ Technology, 2015). The local preflight for this slice passed with Rust 1.97.1: - `cargo fmt --all -- --check`; +- `cargo test -p analysis_engine` — 5 unit tests, 1 crate-contract test, 2 + end-to-end tests, and doctest collection; +- `cargo clippy -p analysis_engine --all-targets -- -D warnings`. - `cargo test -p analysis_engine` — 8 unit tests, 1 crate-contract test, 3 readiness integration tests, 2 topic-lineage integration tests, and doctest collection; diff --git a/scripts/check_coverage.py b/scripts/check_coverage.py index b7230153d..fb543e5a9 100644 --- a/scripts/check_coverage.py +++ b/scripts/check_coverage.py @@ -298,6 +298,11 @@ def is_executable_source_line( # Keep guarded match arms in the authored-line denominator: the guard # executes even though the arm label itself is structural. if text.endswith("=> {") and " if " not in text: + if ( + text.startswith("if ") + 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) @@ -496,6 +501,50 @@ def _line_in_multiline_string(lines: list[str], line_number: int) -> bool: ) return False +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 + inside_block = False + nested_arrow_seen = False + for candidate in reversed(lines[: line_number - 1]): + stripped = candidate.strip() + if brace_depth == 0 and "=>" in stripped: + return guard_found + if ( + inside_block + and brace_depth >= 1 + and stripped.endswith("=> {") + and not nested_arrow_seen + ): + # The opener of the preceding sibling arm sits directly above its + # body with no nested match between, so every guard token found so + # far belongs to that sibling rather than to this arm. + return guard_found + if "=>" in stripped and brace_depth >= 1: + nested_arrow_seen = True + next_depth = brace_depth + stripped.count("}") - stripped.count("{") + if brace_depth == 0 < next_depth: + inside_block = True + nested_arrow_seen = False + elif next_depth <= 0 < brace_depth: + inside_block = False + nested_arrow_seen = False + brace_depth = next_depth + if ( + (stripped.startswith("if ") or stripped.startswith("if(")) + and not stripped.endswith(("}", ";")) + and brace_depth == 0 + ): + guard_found = True + if stripped.startswith("match ") or ( + stripped.startswith("let ") and "= match " in stripped + ): + return guard_found + return guard_found + def _cfg_test_module_line_numbers(lines: list[str]) -> set[int]: """Return line numbers belonging to any ``#[cfg(test)] mod ... { ... }`` block.""" diff --git a/scripts/check_workspace_contract.py b/scripts/check_workspace_contract.py index 5542ace93..b1d665c61 100644 --- a/scripts/check_workspace_contract.py +++ b/scripts/check_workspace_contract.py @@ -66,6 +66,7 @@ "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 3e0edf726..3416c87b8 100644 --- a/tests/quality/test_check_coverage.py +++ b/tests/quality/test_check_coverage.py @@ -671,6 +671,16 @@ def test_multiline_guard_arm_is_executable(self) -> None: self.assertTrue(coverage_contract.is_executable_source_line(str(source), 4)) self.assertFalse(coverage_contract.is_executable_source_line(str(source), 7)) + source.write_text( + "match state {\n" + " State::Ready(value) if(value.is_valid()) => {\n" + " consume(value);\n" + " }\n" + "}\n", + encoding="utf-8", + ) + self.assertTrue(coverage_contract.is_executable_source_line(str(source), 2)) + def test_guard_after_brace_closing_pattern_is_executable(self) -> None: """Count a guard after a destructuring pattern that closes with a brace.""" @@ -816,6 +826,41 @@ def test_previous_arm_body_does_not_make_next_label_executable(self) -> None: 2, ) ) + self.assertFalse( + coverage_contract._is_multiline_match_guard( + [ + "match state {", + " if previous_guard", + " }", + " let nested = match input {", + " 0 => {", + ], + 5, + ) + ) + + guarded_after_block = Path(temporary) / "guarded_after_block.rs" + guarded_after_block.write_text( + "match state {\n" + " State::Previous => {\n" + " consume(value);\n" + " }\n" + " State::Ready(value)\n" + " if value.is_valid()\n" + " && value.is_fresh() => {\n" + " consume(value);\n" + " }\n" + "}\n", + encoding="utf-8", + ) + self.assertTrue( + coverage_contract.is_executable_source_line( + str(guarded_after_block), 7 + ) + ) + self.assertFalse( + coverage_contract.is_executable_source_line(str(guarded_after_block), 2) + ) def test_cfg_test_and_not_feature_block_helpers(self) -> None: """cfg(test) modules and cfg(not(feature)) blocks are fully recognized."""