diff --git a/.github/workflows/hourly-nim-product-development.yml b/.github/workflows/hourly-nim-product-development.yml index 2561ecb26..1c69770a0 100644 --- a/.github/workflows/hourly-nim-product-development.yml +++ b/.github/workflows/hourly-nim-product-development.yml @@ -8,9 +8,6 @@ on: required: false default: false type: boolean - schedule: - - cron: "47 * * * *" - concurrency: group: hourly-nim-product-development-${{ github.repository }} cancel-in-progress: false @@ -20,17 +17,13 @@ permissions: env: DEFAULT_BRANCH: main - # Upstream 1.18.13 was observed during implementation, but this workflow keeps - # the independently checksum-verified 1.17.13 archive until a reviewed digest - # for the newer Linux x64 asset is committed. + # OpenCode remains independently checksum-pinned. Model/provider admission is + # not pinned here: it belongs to a released contextual-orchestrator contract. OPENCODE_VERSION: "1.17.13" OPENCODE_SHA256: 157afa289d1a8d9372de0ce19ac726119b937a1f6b201808d46f06e4e59bb348 - CONTEXTUAL_ORCHESTRATOR_COMMIT: e226e1197bdfc890c9d8e5b9b648c78857d7e465 - CONTEXTUAL_ORCHESTRATOR_SHA256: 964b22ff577e3862b761af847ccad65489bb3f8fc750c8f84fcf8628df096673 - CONTEXTUAL_ORCHESTRATOR_PORT: "18000" - OPENCODE_MODEL: contextual-orchestrator/contextual-orchestrator - OPENCODE_RUN_TIMEOUT_SECONDS: "900" - OPENCODE_KILL_GRACE_SECONDS: "30" + CONTEXTUAL_ORCHESTRATOR_RELEASE: ${{ vars.CONTEXTUAL_ORCHESTRATOR_RELEASE }} + CONTEXTUAL_ORCHESTRATOR_BASE_URL: ${{ vars.CONTEXTUAL_ORCHESTRATOR_BASE_URL }} + OPENCODE_MODEL: contextual-orchestrator/orchestrator/free MAX_CHANGED_FILES: "40" MAX_DIFF_BYTES: "500000" MAX_PR_TITLE_BYTES: "120" @@ -40,6 +33,8 @@ jobs: propose_product_increment: if: github.repository == 'ContextualWisdomLab/TEPP' runs-on: ubuntu-latest + # This is an explicit administrative workflow budget, not a model/reasoning + # timeout. OpenCode itself has no elapsed-time-only termination default here. timeout-minutes: 55 permissions: contents: read @@ -56,12 +51,12 @@ jobs: env: DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false }} steps: - - name: Enforce zero-open-PR-and-issue single-flight gate + - name: Enforce queue and released-orchestrator single-flight gate id: gate shell: bash env: GH_TOKEN: ${{ github.token }} - ORCHESTRATOR_KEYS_CONFIGURED: ${{ secrets.BYTEZ_API_KEY != '' && secrets.NVIDIA_NIM_API_KEY != '' && secrets.NVIDIA_NIM_API_KEY_SUB != '' && secrets.OPENROUTER_API_KEY != '' && secrets.OPENAI_API_KEY != '' }} + CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN: ${{ secrets.CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN }} APP_ID_CONFIGURED: ${{ vars.TEPP_MAINTAINER_APP_CLIENT_ID != '' }} APP_KEY_CONFIGURED: ${{ secrets.TEPP_MAINTAINER_APP_PRIVATE_KEY != '' }} run: | @@ -86,16 +81,36 @@ jobs: echo 'reason=open_issue' >>"$GITHUB_OUTPUT" exit 0 fi - if [ "$ORCHESTRATOR_KEYS_CONFIGURED" != true ] && [ "$DRY_RUN" != true ]; then - echo 'dispatch=false' >>"$GITHUB_OUTPUT" - echo 'reason=contextual_orchestrator_credentials_unavailable' >>"$GITHUB_OUTPUT" - exit 0 - fi - if { [ "$APP_ID_CONFIGURED" != true ] || [ "$APP_KEY_CONFIGURED" != true ]; } \ - && [ "$DRY_RUN" != true ]; then - echo 'dispatch=false' >>"$GITHUB_OUTPUT" - echo 'reason=maintainer_app_unavailable' >>"$GITHUB_OUTPUT" - exit 0 + if [ "$DRY_RUN" != true ]; then + if [ -z "$CONTEXTUAL_ORCHESTRATOR_RELEASE" ]; then + echo 'dispatch=false' >>"$GITHUB_OUTPUT" + echo 'reason=contextual_orchestrator_release_unavailable' >>"$GITHUB_OUTPUT" + exit 0 + fi + if ! release_json="$(gh api "repos/ContextualWisdomLab/contextual-orchestrator/releases/tags/${CONTEXTUAL_ORCHESTRATOR_RELEASE}")"; then + echo 'dispatch=false' >>"$GITHUB_OUTPUT" + echo 'reason=contextual_orchestrator_release_unavailable' >>"$GITHUB_OUTPUT" + exit 0 + fi + if ! jq -e --arg tag "$CONTEXTUAL_ORCHESTRATOR_RELEASE" \ + '.tag_name == $tag and .draft == false and .prerelease == false and .immutable == true' \ + <<<"$release_json" >/dev/null; then + echo 'dispatch=false' >>"$GITHUB_OUTPUT" + echo 'reason=contextual_orchestrator_release_unavailable' >>"$GITHUB_OUTPUT" + exit 0 + fi + if [ -z "$CONTEXTUAL_ORCHESTRATOR_BASE_URL" ] \ + || [[ "$CONTEXTUAL_ORCHESTRATOR_BASE_URL" != https://* ]] \ + || [ -z "$CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN" ]; then + echo 'dispatch=false' >>"$GITHUB_OUTPUT" + echo 'reason=contextual_orchestrator_gateway_unavailable' >>"$GITHUB_OUTPUT" + exit 0 + fi + if { [ "$APP_ID_CONFIGURED" != true ] || [ "$APP_KEY_CONFIGURED" != true ]; }; then + echo 'dispatch=false' >>"$GITHUB_OUTPUT" + echo 'reason=maintainer_app_unavailable' >>"$GITHUB_OUTPUT" + exit 0 + fi fi echo 'dispatch=true' >>"$GITHUB_OUTPUT" echo 'reason=ready' >>"$GITHUB_OUTPUT" @@ -125,14 +140,16 @@ jobs: Preserve standalone operation and modular MSA integration with ContextualWisdomLab/.github, naruon, contextual-orchestrator, and other - CWL services. Do not alter existing reviewer-agent identity, credentials, - or provider routing. Work test-first and preserve RED-to-GREEN evidence. - Maintain 100% production statement and branch coverage and 100% public - docstring coverage. Public behavior must be beginner-readable. - - For LLM paths, use or improve contextual-orchestrator and ground - test-time compute in Fugu, Conductor, and TRINITY. Allocate between - single-model routing and deep multi-agent orchestration; tune workflow + CWL services. Model-backed execution is through the released + contextual-orchestrator orchestrator/free route only. Do not select a + provider, concrete model, provider group, or paid fallback. Work + test-first and preserve RED-to-GREEN evidence. Maintain 100% production + statement and branch coverage and 100% public docstring coverage. + Public behavior must be beginner-readable. + + For LLM paths, use the released contextual-orchestrator contract and + ground test-time compute in Fugu, Conductor, and TRINITY. Allocate + between direct routing and deep multi-agent orchestration; tune workflow stages, recursive depth, task decomposition, and access lists; use role-specific reasoning effort; and perform reasoning-level ablation. Speed is not a priority. Deterministic calculation evidence remains immutable. @@ -193,7 +210,7 @@ jobs: run: | set -euo pipefail archive="$RUNNER_TEMP/opencode-linux-x64.tar.gz" - curl --fail --silent --show-error --location --proto '=https' \ + curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' \ --connect-timeout 10 --max-time 120 --output "$archive" \ "https://github.com/anomalyco/opencode/releases/download/v${OPENCODE_VERSION}/opencode-linux-x64.tar.gz" printf '%s %s\n' "$OPENCODE_SHA256" "$archive" | sha256sum -c - @@ -201,117 +218,95 @@ jobs: install -D -m 0755 "$RUNNER_TEMP/opencode" "$HOME/.opencode/bin/opencode" echo "$HOME/.opencode/bin" >>"$GITHUB_PATH" - - name: Install checksum-pinned contextual-orchestrator + - name: Verify released contextual-orchestrator gateway contract if: steps.gate.outputs.dispatch == 'true' && env.DRY_RUN != 'true' shell: bash + env: + CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN: ${{ secrets.CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN }} run: | set -euo pipefail - archive="$RUNNER_TEMP/contextual-orchestrator.tar.gz" - package_root="$RUNNER_TEMP/contextual-orchestrator" - install -d -m 0700 "$package_root" - curl --fail --silent --show-error --location --proto '=https' \ - --connect-timeout 10 --max-time 120 --output "$archive" \ - "https://github.com/ContextualWisdomLab/contextual-orchestrator/archive/${CONTEXTUAL_ORCHESTRATOR_COMMIT}.tar.gz" - printf '%s %s\n' "$CONTEXTUAL_ORCHESTRATOR_SHA256" "$archive" | sha256sum -c - - tar -xzf "$archive" --strip-components=1 -C "$package_root" - test -f "$package_root/contextual_orchestrator/__main__.py" - - - name: Configure OpenCode for contextual-orchestrator - if: steps.gate.outputs.dispatch == 'true' && env.DRY_RUN != 'true' - shell: bash - run: | - cat >"$RUNNER_TEMP/opencode.json" <<'CONFIG' - { - "$schema": "https://opencode.ai/config.json", - "share": "disabled", - "autoupdate": false, - "lsp": false, - "mcp": {}, - "enabled_providers": ["contextual-orchestrator"], - "permission": { - "*": "allow", - "external_directory": "deny", - "task": "deny", - "question": "deny", - "webfetch": "deny", - "websearch": "deny", - "bash": { - "*": "allow", - "curl *": "deny", - "wget *": "deny", - "ssh *": "deny", - "git clone *": "deny", - "git fetch *": "deny", - "git pull *": "deny", - "git commit *": "deny", - "git push *": "deny", - "git tag *": "deny", - "git remote *": "deny", - "gh *": "deny" - } - }, - "provider": { - "contextual-orchestrator": { - "npm": "@ai-sdk/openai-compatible", - "name": "Contextual Orchestrator", - "options": { - "baseURL": "http://127.0.0.1:18000/v1", - "apiKey": "{env:OPENCODE_GATEWAY_TOKEN}" - }, - "models": { - "contextual-orchestrator": {"tool_call": true} - } - } - } + [[ "$CONTEXTUAL_ORCHESTRATOR_BASE_URL" == https://* ]] || { + echo '::error::contextual_orchestrator_gateway_unavailable' + exit 1 } - CONFIG - chmod 0400 "$RUNNER_TEMP/opencode.json" + gateway_base="${CONTEXTUAL_ORCHESTRATOR_BASE_URL%/}" + curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 \ + --connect-timeout 10 --max-time 30 \ + "$gateway_base/healthz" >/dev/null || { + echo '::error::contextual_orchestrator_gateway_unavailable' + exit 1 + } + curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 \ + --connect-timeout 10 --max-time 30 \ + -H "Authorization: Bearer ${CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN}" \ + "$gateway_base/v1/models" >"$RUNNER_TEMP/contextual-orchestrator-models.json" || { + echo '::error::contextual_orchestrator_gateway_unavailable' + exit 1 + } + jq -e '.data[]? | select(.id == "orchestrator/free")' \ + "$RUNNER_TEMP/contextual-orchestrator-models.json" >/dev/null || { + echo '::error::contextual_orchestrator_gateway_unavailable' + exit 1 + } - - name: Start contextual-orchestrator with all-provider discovery + - name: Configure OpenCode for released contextual-orchestrator if: steps.gate.outputs.dispatch == 'true' && env.DRY_RUN != 'true' shell: bash - env: - BYTEZ_API_KEY: ${{ secrets.BYTEZ_API_KEY }} - NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} - NVIDIA_NIM_API_KEY_SUB: ${{ secrets.NVIDIA_NIM_API_KEY_SUB }} - OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | set -euo pipefail - umask 077 - token_file="$RUNNER_TEMP/contextual-orchestrator-token" - printf '%s' "$(openssl rand -hex 32)" >"$token_file" - report_file="$RUNNER_TEMP/contextual-orchestrator-discovery.json" - log_file="$RUNNER_TEMP/contextual-orchestrator.log" - gateway_token="$(cat "$token_file")" - export CONTEXTUAL_ORCHESTRATOR_INFERENCE_TOKEN="$gateway_token" - export PYTHONPATH="$RUNNER_TEMP/contextual-orchestrator" - python3 scripts/run_contextual_orchestrator.py \ - --host 127.0.0.1 --port "$CONTEXTUAL_ORCHESTRATOR_PORT" \ - --report "$report_file" >"$log_file" 2>&1 & - echo $! >"$RUNNER_TEMP/contextual-orchestrator.pid" - ready=false - for _ in $(seq 1 120); do - if curl --fail --silent --show-error "http://127.0.0.1:${CONTEXTUAL_ORCHESTRATOR_PORT}/healthz" >/dev/null; then - ready=true - break - fi - if ! kill -0 "$(cat "$RUNNER_TEMP/contextual-orchestrator.pid")" 2>/dev/null; then - cat "$log_file" - exit 1 - fi - sleep 1 - done - [ "$ready" = true ] - curl --fail --silent --show-error \ - -H "Authorization: Bearer $(cat "$token_file")" \ - "http://127.0.0.1:${CONTEXTUAL_ORCHESTRATOR_PORT}/v1/models" \ - >"$RUNNER_TEMP/contextual-orchestrator-models.json" - jq -e '.data | length > 0' "$RUNNER_TEMP/contextual-orchestrator-models.json" >/dev/null - jq -e '.discovered_count > 0 and (.selected_models | length > 0)' "$report_file" >/dev/null - echo "CONTEXTUAL_ORCHESTRATOR_TOKEN_FILE=$token_file" >>"$GITHUB_ENV" - - - name: Run bounded contextual-orchestrator agent + python3 - <<'PY' + import json + import os + from pathlib import Path + + base_url = os.environ["CONTEXTUAL_ORCHESTRATOR_BASE_URL"].rstrip("/") + "/v1" + config = { + "$schema": "https://opencode.ai/config.json", + "share": "disabled", + "autoupdate": False, + "lsp": False, + "mcp": {}, + "enabled_providers": ["contextual-orchestrator"], + "permission": { + "*": "allow", + "external_directory": "deny", + "task": "deny", + "question": "deny", + "webfetch": "deny", + "websearch": "deny", + "bash": { + "*": "allow", + "curl *": "deny", + "wget *": "deny", + "ssh *": "deny", + "git clone *": "deny", + "git fetch *": "deny", + "git pull *": "deny", + "git commit *": "deny", + "git push *": "deny", + "git tag *": "deny", + "git remote *": "deny", + "gh *": "deny", + }, + }, + "provider": { + "contextual-orchestrator": { + "npm": "@ai-sdk/openai-compatible", + "name": "Contextual Orchestrator", + "options": { + "baseURL": base_url, + "apiKey": "{env:OPENCODE_GATEWAY_TOKEN}", + }, + "models": {"orchestrator/free": {"tool_call": True}}, + } + }, + } + path = Path(os.environ["RUNNER_TEMP"]) / "opencode.json" + path.write_text(json.dumps(config, ensure_ascii=True), encoding="utf-8") + path.chmod(0o400) + PY + + - name: Run bounded released-orchestrator agent if: steps.gate.outputs.dispatch == 'true' && env.DRY_RUN != 'true' shell: bash env: @@ -319,21 +314,17 @@ jobs: OPENCODE_DISABLE_AUTOUPDATE: 'true' XDG_CONFIG_HOME: ${{ runner.temp }}/opencode-config XDG_CACHE_HOME: ${{ runner.temp }}/opencode-cache + CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN: ${{ secrets.CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN }} run: | set -euo pipefail prompt="$(cat "$RUNNER_TEMP/agent-prompt.md")" - gateway_token="$(cat "$CONTEXTUAL_ORCHESTRATOR_TOKEN_FILE")" - export OPENCODE_GATEWAY_TOKEN="$gateway_token" - timeout --kill-after="${OPENCODE_KILL_GRACE_SECONDS}s" \ - "${OPENCODE_RUN_TIMEOUT_SECONDS}s" \ - env -u GH_TOKEN -u GITHUB_TOKEN -u REPOSITORY_TOKEN \ + export OPENCODE_GATEWAY_TOKEN="$CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN" + env -u GH_TOKEN -u GITHUB_TOKEN -u REPOSITORY_TOKEN \ -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \ -u ACTIONS_RUNTIME_TOKEN -u ACTIONS_RUNTIME_URL \ -u ACTIONS_RESULTS_URL -u ACTIONS_CACHE_URL \ -u GITHUB_ENV -u GITHUB_OUTPUT -u GITHUB_PATH \ -u GITHUB_STATE -u GITHUB_STEP_SUMMARY \ - -u BYTEZ_API_KEY -u NVIDIA_NIM_API_KEY -u NVIDIA_NIM_API_KEY_SUB \ - -u OPENROUTER_API_KEY -u OPENAI_API_KEY \ opencode run "$prompt" --agent build --model "$OPENCODE_MODEL" - name: Verify, bound, and export the uncredentialed proposal @@ -637,7 +628,7 @@ jobs: fi [ "$(jq 'length' <<<"$open_issues")" -eq 0 ] || { echo '::error::open_issue_after_generation'; exit 1; } live_base="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/heads/${DEFAULT_BRANCH}" --jq '.object.sha')" - [ "$live_base" = "$expected_base" ] || { echo '::error::base_branch_advanced'; exit 1; } + [ "$live_base" = "$EXPECTED_BASE" ] || { echo '::error::base_branch_advanced'; exit 1; } - name: Package exactly one pull request in a trusted step env: diff --git a/AGENTS.md b/AGENTS.md index d33a7c6ea..c5695df19 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,14 +15,14 @@ TEPP is the Temporal Event Psychometrics Platform: a multilingual, temporal, rel 7. Multilingual measurement uses one shared latent semantic space. Language-specific morphology and lexical emissions may vary, but equivalent meanings must be aligned and tested for measurement invariance. 8. Production line and branch coverage are 100%. All public modules, traits, structs, enums, functions, methods, error variants, configuration fields, and safety contracts have complete docstrings. 9. Scientific acceptance requires realistic synthetic truth: parameter recovery, RMSE, bias, interval coverage, temporal ordering, graph recovery, invariance, and CPU/GPU parity. Skipped or ignored GPU tests are not evidence. -10. LLM live tests use `NVIDIA_NIM_API_KEY`. `COPILOT_GITHUB_TOKEN` is prohibited. Existing independent review-agent credentials must not be repurposed. -11. LLM orchestration allocates test-time computation between direct routing and deeper multi-agent workflows. Workflow depth, decomposition, access lists, recursion, role-specific reasoning effort, verification/adjudication, and comparable-budget ablations are recorded. LLM output never replaces deterministic/statistical scientific authority. +10. Every semantic LLM operation and every model-backed GitHub Actions workflow goes through a released, versioned `contextual-orchestrator` contract. Actions use the `orchestrator/free` route through the gateway credential only; TEPP must not select a provider/model/group, declare a paid fallback, call providers directly, or consume provider API keys such as NVIDIA NIM, OpenRouter, OpenAI, or Bytez credentials. If the released orchestrator contract cannot supply the required capability, fail closed and repair the canonical owner before consumer adoption. `COPILOT_GITHUB_TOKEN` is prohibited. Independent review-agent credentials must not be repurposed as execution credentials. +11. LLM orchestration allocates test-time computation between direct routing and deeper multi-agent workflows. Workflow depth, decomposition, access lists, recursion, role-specific reasoning effort, verification/adjudication, and comparable-budget ablations are recorded. LLM output never replaces deterministic/statistical scientific authority. Model timeout defaults must not terminate reasoning/stream/tool-call work merely because elapsed time is long; user cancellation, provider termination, and explicit administrative limits remain distinct outcomes. 12. Database object names contain at least two words and use `snake_case` by default. CamelCase or PascalCase is permitted only where language conventions require it. 13. Every scientific or standards claim is traced to an authoritative primary source and cited in APA 7th style in `docs/research/`. 14. Changes that alter latent-variable meaning, temporal semantics, event ontology, multilingual invariance, estimator targets, privacy authority, or service authority require an ADR and a PRD version change when the approved product/measurement target changes. 15. Do not blanket-mask PII when doing so destroys valid authorship, temporal, longitudinal, event, entity-role, or multiple-membership measurement. Use purpose-bound authorization, opaque analytical identifiers, separately protected identity mapping, encryption, selective disclosure, retention/deletion, and auditable privileged access. 16. Design toward CSAP and SOC 2 evidence readiness and align AI governance with current published ISO/NIST guidance where applicable, but never claim certification, attestation, conformance, or legal sufficiency without external evidence. -17. Preserve standalone operation and modular MSA composition. `naruon`, `contextual-orchestrator`, and other CWL services integrate through versioned APIs/artifacts; no direct cross-service application-table access is permitted. +17. Preserve standalone operation and modular MSA composition. `naruon`, `contextual-orchestrator`, and other CWL services integrate only through released/versioned APIs or immutable artifacts plus explicit ACLs; no direct cross-service application-table access or mutable sibling-head dependency is permitted. 18. Documents, external metadata, serialized payloads, model checkpoints, and LLM outputs are untrusted until their owning boundary validates identity, provenance, size/depth, authorization, and scientific semantics. 19. Figma/Product Design becomes authoritative only for a stable product interaction contract; UI design never overrides the PRD, data model, numerical/scientific contract, or protected-main implementation truth. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e6dafbcc2..1ae5e7bd6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,227 +2,173 @@ ## Product definition -TEPP is a Temporal Event Psychometrics Platform. It measures multilingual semantic evidence, links documents and event mentions through typed temporal relations, estimates shared latent topic and higher-order psychometric structures, and renders the resulting evidence, uncertainty, trajectories, and networks. +TEPP is a multilingual temporal relational psychometrics platform. It preserves source evidence and temporal availability, builds typed event/relation structures, estimates latent measurement models, composes longitudinal/event-time models, validates recovery and uncertainty, and projects only evidence appropriate to downstream consumers. + +Detailed scientific estimands, equations, citations, implementation evidence, and recovery status belong in `docs/TRACEABILITY.md` and the corresponding doctoring/research documents. This architecture document owns service/context responsibility and dependency direction; it does not duplicate estimator-by-estimator scientific prose. ```mermaid flowchart LR - A[Immutable documents and metadata] --> B[Evidence ingestion] - B --> C[Temporal and event normalization] - C --> D[Multilingual semantic units] - D --> E[Shared-latent temporal topic measurement] - C --> F[Typed document-event-entity graph] - E --> G[Posterior topic coordinates] - F --> G - G --> H[Longitudinal ESEM and DSEM] - G --> I[Topic and event networks] - H --> J[Evidence-grounded interpretation] - I --> J - J --> K[Accessible visual analytics and exports] + A[Evidence] --> B[Temporal Semantics] + A --> C[Event Ontology] + B --> C + C --> D[Measurement] + B --> E[Longitudinal Modeling] + D --> E + E --> F[Validation] + C --> F + F --> G[Projection] + D --> G + H[contextual-orchestrator released contract] --> I[Semantic LLM ACL] + A --> I + J[fast-mlsirm released contract] --> D + J --> E ``` -## Bounded services and Rust crates - -| Boundary | Primary responsibility | -|---|---| -| `evidence_ingestion` | immutable source bytes, hashes, layout, exact spans, metadata, provenance | -| `temporal_core` | instants, intervals, uncertain dates, partial orders, bitemporal availability and leakage gates | -| `event_ontology` | event mentions, event instances, roles, subevents, products, factors, places, and evidence links | -| `relation_graph` | typed document, segment, event, entity, revision, translation, evidence, and transition edges | -| `membership_model` | time-varying cross-classified and multiple-membership assignments | -| `semantic_preprocessor` | Unicode, segmentation, morphology, dependency phrases, LLM span contracts, validation | -| `concept_dictionary` | versioned multilingual concept alignment and unknown-concept review | -| `topic_measurement` | shared-latent temporal/relational topic estimation and uncertainty | -| `compute_backend` | CPU `f64`, fixed-pool multithreading, CUDA/WGPU, sparse streaming, VRAM budgeting | -| `model_selection` | fitted candidate-K scoring from the CPU reference, predictive fit, coherence, exclusivity, stability, alignment, fairness, blinded LLM review | -| `psychometric_core` | posterior-plausible-value ESEM, longitudinal invariance, DSEM, continuous-time paths | -| `event_intelligence` | TDT segmentation/link/detection/first-story/tracking and CHRONOS schema reasoning | -| `network_analysis` | log-ratio topic correlation, conditional networks, uncertainty, Leiden consensus clusters | -| `interpretation_gateway` | evidence-bounded LLM interpretation, independent verification, routing and ablations | -| `artifact_service` | model registry, manifests, JSON-LD, GraphML, Arrow/Parquet, tables, SVG/PDF exports | -| `visual_analytics` | bitemporal lens, event graph, topic river, drift, ESEM/DSEM builder, invariance and leakage audit | - -Every boundary must be independently usable and expose versioned contracts for integration with organization repositories, `naruon`, and `contextual-orchestrator`. - -The `analysis_engine` vertical slice is intentionally separate from `tepp_api`: -the API owns wire contracts while the engine owns deterministic execution. It -does not replace the future topic or psychometric estimators and does not read -another service's application tables. - -## Implemented foundation topology - -Task 1 materializes the first storage-independent workspace boundaries. The -crate names are stable implementation identifiers, while the broader service -boundaries above remain the target modular MSA architecture. - -| Rust crate | Initial responsibility | -|---|---| -| `evidence_core` | immutable evidence domain primitives | -| `semantic_core` | span-grounded semantic units; language is not identity | -| `location_membership` | location is not entity identity and not a language channel | -| `temporal_core` | typed clocks, intervals, and temporal reasoning | -| `event_core` | event instances, span-grounded `EventMention` (the only constructible mention type), roles, provenance, and CHRONOS occurrence-prediction calibration | -| `relation_graph` | typed relations and forward-transition validation | -| `membership_core` | time-varying cross-classified multiple membership, Kish ESS, nested ICC with non-nested refusal | -| `role_contradiction` | customer and competitor cannot occupy the same group | -| `relation_absence` | unobserved relation pairs are not evidence of no relationship | -| `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, 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 | -| `episode_membership` | event-time episode membership containment gate | -| `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 | -| `copied_text` | copied-text residue is not unique latent content and not stopword deletion | -| `style_source` | house-voice style residue is not unique latent content and not stopword deletion | -| `stopword_deletion` | default stopword deletion is not a valid method for repeated report language | -| `copy_identity` | a template copy is not the source document and not a state transition | -| `intake_authorization` | untrusted intake fails closed without a grant; bounds are not authorization | -| `summarizes_edge` | a summary is not a state transition and not the source document | -| `outcome_order` | input-process-outcome edges cannot move backward in event time | -| `retrospective_edge` | retrospective reporting cannot become a transition or a translation | -| `payload_bound` | untrusted documents, records, checkpoints, and LLM outputs fail closed without identity, provenance, size, and depth | -| `inferred_status` | inferred relations cannot be promoted to observed evidence or transitions | -| `support_edge` | support, contradiction, summary, and outcome_of edges are not state transitions | -| `system_clock` | system time cannot be replaced by event, assertion, document, available, or cutoff time | -| `event_clock` | event time cannot be replaced by assertion, system, document, or available time | -| `assertion_clock` | assertion time cannot be replaced by event, system, document, or available time | -| `cutoff_clock` | knowledge cutoff cannot be replaced by event, system, or availability time | -| `available_clock` | availability time cannot be replaced by event or system time | -| `document_clocks` | document rows must carry assertion time and document time | -| `revision_order` | later document revisions must have later system time | -| `encrypted_mapping` | purpose-bound in-memory AES-256-GCM identity mappings; no plaintext persistence or KMS integration | -| `citation_edge` | citation, revision, translation, and retrospective edges are not state transitions | -| `psychometric_fit` | CPU `f64` ESEM loading recovery and event-time DSEM lag gates | -| `subevent_containment` | subevent event-time intervals must stay inside the parent | -| `prediction_contradiction` | Allen promotion gate: `before`/`after` stay contradictory; `meets`/`met_by` stay unsupported; coverage is required before unmatched predicted mass may be authorized for promotion | -| `provider_receipt` | provider-disclosure field-code receipts; source text and identity are not disclosable | -| `operational_log` | operational logs; `try_record` is the only recording API; source text and source identity are not loggable; `persistence_postgres` `audit_event` inserts call the same gate | -| `service_tls` | production TLS bind gates and rustls server config | -| `derived_sensitivity` | derived topic/factor/relation outputs inherit source sensitivity | -| `longitudinal_core` | active-PR: within/between decomposition; refuse between-as-within; component RMSE | -| `topic_lineage` | global topic identity across active/dormant/reactivated states | -| `network_analysis` | compositional cluster-pair gates; raw simplex is not Euclidean | -| `interpretation_gateway` | evidence-bounded LLM interpretations; not estimators or observed facts | -| `orchestrator_live` | loopback interpretation HTTP/1.1 listener | -| `model_selection` | fitted candidate-`K` scoring from the CPU `f64` reference plus statistical/Pareto gates; LLM votes are not numerical authority | -| `checkpoint_authority` | a model checkpoint is not the CPU `f64` estimator | -| `compute_backend` | VRAM-budgeted streamed planning, executable OOM retry plans, and a compensated CPU `f64` reference | -| `episode_membership` | episode membership cannot escape the episode event-time interval | -| `membership_target` | language, episode, template, department, and opportunity-pool targets cannot collapse into entity or project | -| `topic_measurement` | logistic-normal ALR/ILR coordinates and the CPU `f64` TRSL-TM reference estimator | -| `analysis_engine` | bounded cutoff-safe temporal evidence readiness execution and digest-bound terminal artifacts | -| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | -| `validation_core` | RMSE, bias, coverage, graph, and Monte Carlo metrics | -| `tepp_api` | versioned DTO, schema, and export contracts | -| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; `TRAITVAR` is not the standardisation variance; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`; p. 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `CINT` is not `CINTstd`; `asymCINTstd` is not `CINTstd`; `discreteCINTstd` is not `CINTstd`; `κ / √(trait + p + added)` is not `CINTstd`;))))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | -| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; `TRAITVAR` is not the standardisation variance; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | - - -Foundation crates expose only tested contracts. Empty façades are not public -APIs. - -No crate exposes placeholder production behavior in Task 1. This prevents an -empty façade from becoming a de facto public API before its invariants and tests -exist. - -## Immutable evidence boundary - -Task 2 begins the executable `evidence_core` boundary. Stable RFC 9562 `UUIDv7` -identities are independent from canonical `SHA-256` content digests. Source -bytes and UTF-8 document text are copied into immutable owned storage, bounded -before allocation, and verified without exposing mutable fields. - -A source span records an owning document, a half-open UTF-8 byte range, the -matching half-open Unicode-scalar range, and optional page/layout geometry. It -fails closed for empty or reversed ranges, byte or scalar overflow, -mid-code-point boundaries, coordinate disagreement, cross-document use, -nonfinite geometry, nonpositive dimensions, and rectangles outside the page. -Scalar coordinates are evidence locations rather than grapheme, word, or -sentence boundaries; language-tailored segmentation remains a later module. - -The boundary now exposes a strict JSON wire version `1` without exposing private -Rust fields. Artifacts, documents, spans, and nested page locations are serialized -through explicit DTOs with unknown-field rejection. Reconstruction parses and -revalidates RFC 9562 identifiers, canonical digests, content limits, exact text -coordinates, document ownership, and page geometry. Artifact bytes and document -text are rehashed during reconstruction, and digest substitution fails closed. -Malformed JSON, unsupported versions, invalid byte values, and unknown nested -fields produce stable content-redacting errors. - -Persistence, JSON Schema publication, JSON-LD, GraphML, source acquisition -metadata, signatures, and W3C PROV remain outward adapters or later contracts. -They must depend inward on these validated domain values rather than defining -them. +## DDD context map -## Quality architecture +| Bounded context | Type | TEPP responsibility | Outbound dependency rule | +|---|---|---|---| +| Evidence | Core | immutable source identity/content digests, exact spans, provenance, evidence availability | no inferred scientific result becomes source evidence | +| Temporal Semantics | Core | event/valid, assertion, document, system, available time and knowledge cutoff; interval topology; leakage gates | OWL-Time/ISO-TimeML mappings are adapters, not internal identity | +| Event Ontology | Core | event instances/mentions, roles, subevents, temporal relations, forward-transition invariants | retrospective/citation/provenance edges never become reverse transitions | +| Measurement | Core | TEPP-specific multilingual shared-latent measurement composition and posterior artifact contracts | reusable static/generalized-mixed/dependence psychometric arithmetic comes from released `fast-mlsirm` contracts | +| Longitudinal Modeling | Core | irregular event-time/state composition, trajectories, temporal alignment, time-varying multilevel/cross-classified/multiple-membership composition | static reusable covariance/dependence kernels remain `fast-mlsirm` owned | +| Validation | Core | true-parameter recovery, RMSE, bias, coverage, convergence, calibration, leakage-safe rolling-origin evidence, parity receipts | no LLM judgment substitutes for numerical/scientific acceptance | +| Projection | Supporting | buyer/API/export projections, immutable manifests, exact-value representations | latent estimates are not promoted to enterprise-architecture facts | +| Semantic LLM ACL | Supporting | task/evidence/access policy, schema validation, semantic result provenance | provider discovery/routing/execution only through a released `contextual-orchestrator` contract | +| Persistence | Generic | repositories, 3NF relational persistence, bitemporal/interval constraints, audit/provenance storage | no cross-service SQL | +| Compute | Generic | Rust CPU `f64` reference, bounded parallelism, backend parity/receipts, OOM recovery | accelerator use cannot change the estimand | + +Shared Kernel is intentionally minimal: opaque identities, canonical digests, strict version identifiers, and clock/value types whose semantics are identical across participating TEPP contexts. Context-specific aggregates, repositories, policies, or scientific estimands do not enter the Shared Kernel. + +## Aggregate and invariant ownership -The workspace centralizes package metadata and Rust/Clippy lints. Every member -inherits `unsafe_code = "forbid"`, `missing_docs = "deny"`, and warning denial. -Repository contract scripts independently verify the approved crate set, -workspace inheritance, action SHA pinning, absence of LLM credentials from -ordinary CI, and complete Rust documentation. +Evidence owns immutable source artifacts/documents and exact evidence spans. Temporal Semantics owns clock values, uncertain intervals, and cutoff eligibility. Event Ontology owns event/relation consistency. Measurement owns TEPP measurement-run composition and posterior artifacts. Longitudinal Modeling owns event-time transition/state composition. Validation owns validation/recovery evidence and promotion refusal. Projection owns export/projection manifests. -Stable Rust 1.98.0 is the compile, lint, test, and line-coverage reference. -Branch coverage runs in a pinned nightly lane because LLVM branch coverage -remains unstable in Rust. `cargo-nextest` runs tests without retries, while -doctests remain a separate `cargo test --doc` gate. `cargo-deny` enforces -advisory, license, ban, and source policy. Failed Rust coverage gates print the -exact missing source locations from the same instrumented run without weakening -the 100% contract. +Cross-context behavior is coordinated by application services and domain events. A context does not reach into another context's tables or internal modules to make a scientific decision. Compatibility is through explicit ports/ACLs and versioned contracts. ## Temporal invariants -TEPP stores event/valid time, assertion time, document time, system time, available time, and knowledge cutoff independently. A historical analysis may include a document only when: +TEPP keeps these clocks distinct end to end: + +1. `event_time` / valid time; +2. `assertion_time`; +3. `document_time`; +4. `system_time`; +5. `available_time`; +6. `knowledge_cutoff`. + +Historical analysis admission requires `available_time <= knowledge_cutoff`. The Validation / Analysis Run invariant requires cutoff filtering before duplicate-identity, count, membership-total, inference, or terminal-state logic so future-unavailable evidence cannot change a historical result. Implementation maturity and active landing evidence are tracked in `docs/TRACEABILITY.md` and PR state; until the corresponding source/tests reach protected main, this paragraph is a target invariant rather than an `implemented-main` claim. + +Forward state-transition and input/process/outcome relations require a valid forward event-time partial order. Citation, revision, translation, support, contradiction, summary, and retrospective-reporting relations retain their own direction/provenance but do not become reverse transitions. + +## Measurement and Longitudinal Modeling ownership + +TEPP does not use `psychometric_core` as an architectural dumping ground. New temporal/state composition belongs to Longitudinal Modeling. Reusable static/generalized-mixed/dependence-aware psychometric arithmetic—including reusable LSIRM/MLSIRM/DLSJM kernels—belongs to `fast-mlsirm` and is consumed only from an immutable released/versioned Published Language through a TEPP ACL. + +TEPP preserves exact Rasch identity rather than flattening it into a generic 1PL label, and keeps 2PLM–5PLM, confirmatory/exploratory MIRT, ideal-point/GGUM, testlet, rater/facet, generalized-mixed, cross-classified, multiple-membership, LSIRM/MLSIRM/DLSJM candidate identities distinct. Cross-classification and multiple membership are separate semantics; membership weights are explicit, auditable, time-valid, and normalized or model-estimated according to the declared formulation. + +Known hierarchy/testlet/rater/method/item-family structure precedes residual latent-space dependence. Local item dependence, local person dependence, and residual person-item interaction are diagnosed separately. Temporal DLSJM composition keeps item- and person-dependence spaces distinct and requires temporal alignment for translation/rotation/reflection and cluster-label comparison. + +Scientific details and current implementation maturity are generated/traced in `docs/TRACEABILITY.md` rather than copied into responsibility-table cells. + +## Implemented Rust/application boundaries + +The repository contains fine-grained crates created during foundation work. Their existence does not mint new bounded contexts. Small rule/clock crates are implementation modules until consolidation; architecture authority remains the context map above. + +Representative inward domain modules include `evidence_core`, `temporal_core`, `event_core`, `relation_graph`, `membership_core`, `validation_core`, `longitudinal_core`, and `topic_measurement`. `analysis_engine` is an application service that composes admitted evidence into digest-bound analysis results. `tepp_api` owns versioned wire/DTO contracts. `persistence_postgres` is an infrastructure adapter. + +One-operation/profile modules such as location/episode/membership/edge refusal helpers remain implementation details of their owning context and should fold into coherent context/application vehicles rather than become permanent architectural service identities. -\[ -\operatorname{available\_time}(d) \leq \operatorname{knowledge\_cutoff}. -\] +## Semantic LLM boundary -When availability is an interval, every possible instant in that interval must satisfy the inequality. Unknown or open-ended availability that can extend past the cutoff fails closed; event time and document time cannot substitute for availability. +All semantic LLM work—semantic unitization, interpretation, verification, judging, label/explanation proposal, and model-backed automation—crosses the Semantic LLM ACL and consumes a **released, versioned `contextual-orchestrator` API/client/schema**. -Forward transition edges require a temporally valid partial order. Retrospective, revision, translation, citation, support, and contradiction relations retain their direction and provenance but do not create reverse state transitions. +TEPP owns: -## Measurement invariants +- semantic task and minimum evidence bundle; +- source-span/provenance requirements; +- tool/access policy; +- role/reasoning/verification policy; +- scientific-risk and abstention policy; +- schema validation and result admission. -All languages share global topic identities and latent document coordinates. Language-specific lexical emissions, morphology, script, and content deviations are modeled rather than forced to be identical. Validated, calibrated, provisional, and unresolved language profiles are reported separately. +`contextual-orchestrator` owns: -Repeated report vocabulary is modeled through corpus-background, template, section, style, copied-text, prompt, modality, and substantive-topic sources. It is not silently removed by stopword lists, TF-IDF, or BM25. +- provider credential/key auto-discovery; +- provider/model/group routing; +- `orchestrator/free` and paid/free admission policy; +- request-family adaptation for embeddings/responses/completions/audio/video/image/omni-modal capabilities; +- provider fallback and lifecycle handling; +- streaming/tool-call execution and provider termination semantics. -Topic proportions are compositional (Aitchison, 1982). ESEM and network analysis consume logistic-normal latent coordinates or orthonormal log-ratio coordinates, with posterior uncertainty propagated through plausible values or a joint model (Asparouhov & Muthén, 2009; Asparouhov et al., 2018; Marsh et al., 2014). The product topic-estimator contract is TRSL-TM (ADR 0012); an STM-style logistic-normal family is the reference, not a shipped-backend claim (Blei & Lafferty, 2006; Roberts et al., 2014, 2019). TDT/CHRONOS event intelligence remains an accepted-target boundary (Allan, 2002; Anagnostopoulos et al., 2013). +TEPP does not import provider SDKs or provider keys as a fallback, does not hard-code a provider/model/group, and does not choose a paid route. Model-backed GitHub Actions request `orchestrator/free` through the gateway credential only. If the released owner contract does not provide the required capability, the consumer fails closed and the owner must release the capability before adoption. + +A protected-main commit, open PR head, or checksum-pinned source snapshot without an immutable release is candidate evidence, not production dependency authority. Production semantic execution remains non-deployable whenever no compatible immutable `contextual-orchestrator` release and authenticated deployment identity are available; point-in-time release inventory belongs to operational/release evidence rather than this architecture authority. + +LLM outputs are untrusted proposals. They never perform numerical estimation, scientific acceptance, or authoritative activation. + +## External owner boundaries + +### fast-mlsirm + +`fast-mlsirm` owns reusable static/generalized-mixed/dependence-aware psychometric arithmetic and reusable LSIRM/MLSIRM/DLSJM kernels. TEPP consumes only released/versioned artifacts with deterministic manifest schema/version/digest and typed membership semantics. Mutable sibling PR heads are research/integration candidates only. + +### contextual-orchestrator + +`contextual-orchestrator` owns provider routing/execution and orchestration transport. TEPP consumes a released contract through the Semantic LLM ACL; direct provider calls and source-copy integration are forbidden. + +### context-graph-contracts / enterprise-architecture-core + +Context Graph contracts and EA projections are external owner paths. TEPP may prepare conformance fixtures against candidate schemas, but deployable integration requires released/versioned contracts with provenance. TEPP latent estimates, measurement scores, inferred event relations, or validity evidence do not become authoritative enterprise-architecture facts. EA receives product/service/lifecycle/dependency/risk/ownership/remediation/transformation projections only through released contracts. + +## Persistence and data architecture + +PostgreSQL is the reference relational store. Persistent design is 3NF by default, uses descriptive multiword `snake_case`, and preserves tenant, provenance, valid/event time, system time, availability, version, lifecycle, and audit dimensions where applicable. + +Bitemporal and interval validity constraints belong in the database as well as domain types. Multiple-membership assignments are explicit rows with auditable weights and validity intervals. UPSERT/idempotency semantics must be declared per repository operation; hot partitions and lock scope are tested rather than assumed. No service reads another service's application tables directly. ## Compute architecture -The CPU `f64` implementation is the numerical reference. Rayon-style fixed worker pools and thread-local sufficient statistics minimize context switching and oversubscription. GPU work is streamed; temporary responsibilities are never retained for the full corpus. The VRAM controller estimates peak allocation, reserves a safety margin, autotunes micro-batches, records telemetry, reduces batches after OOM, and falls back to CPU safely. +Production scientific arithmetic is Rust-first. Deterministic CPU `f64` is the reference. Parallel CPU work uses bounded worker pools and deterministic/stable reductions where the estimand requires them. GPU/accelerator paths are introduced only when materially justified and must prove parity against CPU reference evidence. OOM is a typed recoverable condition with bounded batch reduction/fallback rather than an unhandled state. -## Persistence +Python/R may serve interoperability, validation, or independent-oracle boundaries only where no practical Rust substitute exists; such use requires an ADR rationale and removal condition. Synthetic data is unit/recovery infrastructure, not product scientific acceptance by itself. -PostgreSQL is the reference relational store. Database objects use two-or-more-word `snake_case` names, including `document_record`, `temporal_interval`, `event_instance`, `event_mention`, `document_relation`, `segment_relation`, `entity_role_assignment`, `model_run`, `topic_definition`, `topic_correlation`, `topic_cluster`, `factor_solution`, `validation_metric`, and `audit_event`. `audit_event` inserts call `operational_log::try_record` before SQL is rendered so source text and source identity cannot enter the row. +## Validation architecture -## Security and trust boundaries +Each temporal/scientific candidate declares the parameters/states it must recover. Acceptance uses realistic true-parameter state/trajectory recovery, RMSE, bias, interval coverage, convergence, reproducibility, and Monte Carlo uncertainty. Leakage-safe rolling-origin evaluation preserves event time versus availability time, irregular gaps, delayed/retrospective reports, missing occasions, changing memberships, and language/source drift. -Documents and LLM outputs are untrusted. Exact spans, JSON Schema, size/depth limits, Unicode validity, prompt-injection isolation, provider allowlists, no-tool execution, tenant isolation, immutable audit events, dependency pinning, SBOM, provenance, and reproducible releases are mandatory. LLM live tests use `NVIDIA_NIM_API_KEY`; `COPILOT_GITHUB_TOKEN` is forbidden. +Skipped/ignored/xfail tests, source rewriting, sample shrinkage that changes the scientific target, or coverage-denominator tricks are not evidence. -## References +## Quality architecture -The full APA 7th register is [`docs/research/standards-and-literature.md`](docs/research/standards-and-literature.md). Method claims on this page use: +Repository quality gates enforce Rust format/build/Clippy/tests/rustdoc, production statement/branch coverage, public documentation, dependency/security policy, architecture/documentation fitness, and exact-head evidence. A queued/pending workflow is non-passing. -Aitchison, J. (1982). The statistical analysis of compositional data. *Journal of the Royal Statistical Society: Series B, 44*(2), 139–177. https://doi.org/10.1111/j.2517-6161.1982.tb01195.x +An open PR or ADR may be `active-PR` or `research-only`; it is not `implemented-main` merely because source exists. Implementation maturity and architecture decision status are separate authorities. Repository-wide ADR IDs are unique/immutable and deterministic fitness tests reject duplicate IDs/targets/index rows. -Allan, J. (Ed.). (2002). *Topic detection and tracking: Event-based information organization*. Kluwer Academic Publishers. +## Security and trust boundaries -Anagnostopoulos, E., Batsakis, S., & Petrakis, E. G. M. (2013). CHRONOS: A reasoning engine for qualitative temporal information in OWL. *Procedia Computer Science, 22*, 70–77. https://doi.org/10.1016/j.procs.2013.09.082 +Source documents, serialized payloads, checkpoints, connector responses, and LLM outputs are untrusted until their owning boundary validates identity, provenance, bounds, authorization, and semantics. Purpose-bound PII handling uses opaque analytical identities, protected identity mappings, encryption, selective disclosure, retention/deletion, and auditable privileged access without destroying valid authorship/temporal/membership evidence. -Asparouhov, T., Hamaker, E. L., & Muthén, B. (2018). Dynamic structural equation models. *Structural Equation Modeling, 25*(3), 359–388. https://doi.org/10.1080/10705511.2017.1406803 +Scientific integrity is a security property: temporal leakage, unsupported cross-language equivalence, failed uncertainty coverage, backend divergence, untracked mutable dependencies, or causal overclaiming fails closed. -Asparouhov, T., & Muthén, B. (2009). Exploratory structural equation modeling. *Structural Equation Modeling, 16*(3), 397–438. https://doi.org/10.1080/10705510903008204 +## Release architecture -Blei, D. M., & Lafferty, J. D. (2006). Dynamic topic models. In *Proceedings of the 23rd International Conference on Machine Learning* (pp. 113–120). ACM. https://doi.org/10.1145/1143844.1143859 +A TEPP release requires a coherent protected-head buyer/scientific vertical, version and CHANGELOG consistency, exact-head CI/security/recovery evidence, immutable package artifacts, SBOM/provenance/reproducibility, rollback/recovery evidence, and every production integration bound to released/versioned external contracts. No release is published merely to turn an active PR into authority. -Marsh, H. W., Morin, A. J. S., Parker, P. D., & Kaur, G. (2014). Exploratory structural equation modeling: An integration of the best features of exploratory and confirmatory factor analysis. *Annual Review of Clinical Psychology, 10*, 85–110. https://doi.org/10.1146/annurev-clinpsy-032813-153700 +## Canonical detail references -Roberts, M. E., Stewart, B. M., Tingley, D., Lucas, C., Leder-Luis, J., Gadarian, S. K., Albertson, B., & Rand, D. G. (2014). Structural topic models for open-ended survey responses. *American Journal of Political Science, 58*(4), 1064–1082. https://doi.org/10.1111/ajps.12103 +- Product requirements: `docs/product/prd-v0.4-approved.md` and active versioned amendments +- Technical requirements: `docs/TRD.md` +- Requirement/scientific/implementation traceability: `docs/TRACEABILITY.md` +- DDD/ADR authority: `docs/adr/README.md`, `docs/adr/ADR_POLICY.md` +- UML/runtime flows: `docs/UML.md` +- ERD: `docs/ERD.md` +- API contracts: `docs/API_CONTRACT.md` +- LLM boundary: `docs/LLM_ORCHESTRATION.md` +- Test/recovery strategy: `docs/TEST_STRATEGY.md` +- Operability/release: `docs/OPERABILITY.md` +- Security/threat model: `SECURITY.md`, `docs/THREAT_MODEL.md` +- Primary research/standards: `docs/research/standards-and-literature.md` and estimator-specific doctoring files -Roberts, M. E., Stewart, B. M., & Tingley, D. (2019). stm: An R package for structural topic models. *Journal of Statistical Software, 91*(2), 1–40. https://doi.org/10.18637/jss.v091.i02 +The Git history retains superseded architecture prose. Current architecture authority is this bounded-context/ownership map plus the code-current traceability documents above; estimator equations and evidence are intentionally not duplicated into architectural responsibility rows. diff --git a/CHANGELOG.md b/CHANGELOG.md index 062a69412..403ba5393 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] +- Hourly contextual-orchestrator bootstrap now admits only discovery rows whose provider-reported prompt and completion token prices are both present and exactly `0.0` before cheapest ranking. Paid, partial, missing, and fully unpriced rows stay out of the general-chat pool; an empty explicitly-free pool fails closed with no paid fallback. Secret-free discovery evidence records admitted free candidates and excluded non-free candidates. ADR 0017 and the hourly runbook document the same boundary. The checksum-pinned contextual-orchestrator revision is unchanged; unknown price is not treated as free. + - `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability. - `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, Table 2, p. 12 `MANIFESTTRAITVAR`; §7.1, p. 19; p. 16 `MANIFESTTRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T14:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised manifest-trait variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 83–84). Table 2 names `MANIFESTTRAITVAR` `Ψ_τ` the additional time-invariant variance-covariance on the measurement level and sets it `NULL` when there is no manifest trait. Equation 5 writes `Γ ~ N(τ, Ψ)` and names that covariance the manifest traits. Section 7.1 names manifest traits stable individual differences in indicator levels, distinct from process-level `TRAITVAR` `φ_ξ`. Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `MANIFESTTRAITVARstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named indicator-level correlation is `MANIFESTTRAITVAR`, not process-level `TRAITVAR` and not residual `MANIFESTVAR` `θ`. The 2017-era source forms `MANIFESTTRAITVARstd` only when `MANIFESTTRAITVAR != 0`, as `solve(sqrt(diag(MANIFESTTRAITVAR) + ridging)) %&% MANIFESTTRAITVAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. Unlike `TRAITVARstd`, that formation adds `diag(c(ridging), n.manifest)`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The scalar correlation is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR`. Form strictly positive `ψ` first, then `1 / √ψ`, then `(1 / √ψ) ψ (1 / √ψ)`. Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait; standardised `MANIFESTTRAITVAR` is not. Zero `MANIFESTTRAITVAR` skips forming `MANIFESTTRAITVARstd` in the 2017-era source and fails closed here. Indicator-level trait variance is an event-time structural quantity, so a non-event clock fails closed. `MANIFESTTRAITVAR` does not require stable `a < 0`. Distinct positive `ψ` recover the same 1. `trait / trait = 1` is `TRAITVARstd` and recovers the same number and remains a distinct named quantity. `θ` is `MANIFESTVAR` and is measurement error, not this correlation. Meredith (1993) remains unread (web search 2026-08-27T14:20Z: Springer/Cambridge Core paywalled; Unpaywall historically `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed27fdb3d..1ce57c670 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,10 +45,11 @@ Use primary papers, international standards, official specifications, and offici - Treat all model output as untrusted structured input. - Preserve exact source spans and evidence identifiers. -- Use `NVIDIA_NIM_API_KEY` for approved live tests. -- Never use or introduce `COPILOT_GITHUB_TOKEN`. -- Record provider, model, prompt hash, reasoning effort, workflow depth, tools/access list, seed where supported, latency, token usage, and cost. -- Include direct-routing versus orchestrated and reasoning-effort ablations. +- Route every semantic LLM operation and model-backed GitHub Actions workflow through a released, versioned `contextual-orchestrator` contract. GitHub Actions use only `orchestrator/free` through the contextual-orchestrator gateway credential. +- Do not select or hard-code a provider, model, provider group, or paid fallback in TEPP, and do not expose provider API keys to TEPP workflows. If a released orchestrator contract cannot provide the required capability, fail closed and repair the canonical owner before adopting the change here. +- Never use or introduce `COPILOT_GITHUB_TOKEN`, and never repurpose independent review-agent credentials as execution credentials. +- Record the contextual-orchestrator release/contract identity, route, prompt hash, reasoning effort, workflow depth, tools/access list, seed where supported, latency, token usage, and cost. Record provider/model identity only when the released orchestrator returns it as execution provenance; it is evidence, not TEPP routing authority. +- Include direct-routing versus orchestrated and reasoning-effort ablations where scientifically relevant. LLM output never replaces numerical estimation or scientific acceptance. ## Database naming diff --git a/docs/LLM_ORCHESTRATION.md b/docs/LLM_ORCHESTRATION.md index 29edf3590..5fec967af 100644 --- a/docs/LLM_ORCHESTRATION.md +++ b/docs/LLM_ORCHESTRATION.md @@ -1,13 +1,14 @@ # TEPP LLM Orchestration and Test-Time Compute Contract -**Status:** Partial — `tepp_api::route_orchestration` is the governed selector; live provider execution is not yet shipped. -**Last reviewed:** 2026-08-13 +**Status:** Partial — TEPP owns semantic-task policy/evidence contracts; provider routing requires a released `contextual-orchestrator` contract and is not yet deployable from the current repository state. + +**Last reviewed:** 2026-09-02 ## 1. Purpose TEPP uses LLMs only for bounded semantic unitization, candidate-model review, evidence-grounded interpretation, and independent claim verification. Statistical estimation, temporal eligibility, event-relation validity, measurement invariance, numerical acceptance, and release authority remain deterministic/Rust or governed human authority. -The product must allocate test-time compute adaptively rather than assume that either one frontier model or a large fixed multi-agent graph is always best. +The product may allocate test-time compute adaptively rather than assume that either one frontier model or a large fixed multi-agent graph is always best. TEPP decides the semantic task, evidence/access policy, scientific risk, and admissible orchestration mode; provider/model/group routing is a `contextual-orchestrator` responsibility. ## 2. Research basis @@ -21,21 +22,22 @@ These results motivate experiments; they do not prove that deeper orchestration ## 3. Orchestration modes -| Mode | Typical TEPP use | Default compute | +| Mode | Typical TEPP use | TEPP policy request | |---|---|---| -| direct | simple span classification, deterministic-schema fill, low-ambiguity label | one model call | +| direct | simple span classification, deterministic-schema fill, low-ambiguity label | one governed semantic call | | verify | interpretation or classification with material unsupported-claim risk | producer + independent verifier | | committee | K/model interpretation with scientific ambiguity | blinded parallel raters + adjudication | | conductor | complex evidence synthesis or multi-stage semantic reasoning | adaptive roles/topology under explicit budget | -| abstain | provider/evidence/validation insufficient | no forced answer | +| abstain | evidence/contract/capability insufficient | no forced answer | -`tepp_api::route_orchestration` is the governed selector. It chooses the cheapest mode expected to satisfy the quality/risk profile, but latency is not the primary objective. Quality, evidence support, calibration, disagreement, controllability, and reproducibility dominate. The returned plan is a proposal: `scientific_authority_code` remains `deterministic_statistical_gates`. +`tepp_api::route_orchestration` governs only the TEPP-side task/mode proposal. It does not select a provider, provider group, concrete model, or paid fallback. Provider-neutral routing and execution are delegated through a released `contextual-orchestrator` API/client/schema. Quality, evidence support, calibration, disagreement, controllability, and reproducibility dominate; `scientific_authority_code` remains `deterministic_statistical_gates`. ## 4. Explicit experimental variables Every orchestration benchmark records and can ablate: -- model/provider pool; +- the released orchestrator contract/version and routing receipt; +- orchestrator-selected provider/model identity as observed provenance, not TEPP routing configuration; - direct versus multi-agent topology; - workflow stage count; - worker count; @@ -47,9 +49,9 @@ Every orchestration benchmark records and can ablate: - total token/call/compute budget; - verification/adjudication policy; - stopping rule; -- provider failure/fallback behavior. +- failure/abstention outcome. -Comparisons must use approximately comparable budgets or report the budget difference explicitly. +Comparisons must use approximately comparable budgets or report the budget difference explicitly. Fugu/Conductor/TRINITY experiments are ablations over a released orchestration boundary; they do not authorize branch-local provider routing. ## 5. Role-specific effort @@ -66,11 +68,13 @@ The policy is empirically calibrated and versioned rather than hard-coded as a p ## 6. Evidence and trust boundary -LLM calls receive only the minimum evidence bundle needed for the assigned role. Documents are untrusted observations and cannot alter orchestration policy, tools, credentials, model pool, access lists, or scientific gates. +LLM calls receive only the minimum evidence bundle needed for the assigned role. Documents are untrusted observations and cannot alter orchestration policy, tools, credentials, routing, access lists, or scientific gates. Each call records: ```text +orchestrator_contract_version +orchestrator_receipt_id provider_id model_id model_revision_or_endpoint @@ -90,7 +94,7 @@ duration_record verdict_status ``` -Raw credentials are never model-visible. Model outputs are proposals, not source facts. +Provider/model fields are returned provenance. They are not TEPP provider-selection inputs. Raw provider credentials are never TEPP model-visible or required by TEPP semantic clients. Model outputs are proposals, not source facts. ## 7. Quality metrics @@ -105,33 +109,41 @@ At minimum evaluate: - prompt-injection success rate; - abstention quality; - token/call/compute cost; -- provider/model failure resilience; +- orchestrator failure resilience; - repeated-run variance. For model-selection review, statistical predictive/recovery/stability/invariance gates run before LLM judgment. LLM preference cannot rescue a statistically rejected candidate. ## 8. contextual-orchestrator boundary -`contextual-orchestrator` is the preferred CWL provider-neutral integration when available. TEPP owns the evidence bundle, statistical/model-selection policy, scientific acceptance, artifact provenance, and allowed role/access configuration. The orchestrator owns provider routing/orchestration execution within the supplied policy. Neither service reads the other's application database directly. +`contextual-orchestrator` is the canonical CWL owner of provider discovery, key auto-discovery, provider/model/group routing, paid/free policy, request-family adaptation, fallback, streaming/tool-call lifecycle, and provider execution. TEPP owns evidence bundles, semantic-task policy, statistical/model-selection policy, scientific acceptance, artifact provenance, and allowed role/access configuration. Neither service reads the other's application database directly. + +Production TEPP integration consumes only a **released, versioned** contextual-orchestrator API/client/schema through an ACL with immutable artifact identity and provenance. A mutable protected-main commit, open PR head, or checksum-pinned source snapshot without an immutable release is candidate evidence, not production dependency authority. At the 2026-09-02 review, contextual-orchestrator has no GitHub release, so production semantic execution through this boundary remains fail-closed until a compatible release exists and is verified/adopted. -`orchestrator_live` exposes a loopback-only `POST /v1/interpretation-runs` listener that records the selected mode and budget. Listener output is hypothetical and cannot become scientific authority. Production TLS and provider execution remain outside this crate. +`orchestrator_live` may expose a local adapter/listener for contract tests and hypothetical planning, but it is not a second provider router and cannot become scientific authority. Production provider execution stays behind the released contextual-orchestrator boundary. ## 9. Development and live-test credentials -Live model tests use GitHub Secret `NVIDIA_NIM_API_KEY` with the minimum runtime mapping required by the selected adapter. `COPILOT_GITHUB_TOKEN` is prohibited. Existing independent review-agent credentials are separate and must not be renamed, copied, or repurposed for TEPP model execution. +TEPP semantic clients use only the contextual-orchestrator gateway credential appropriate to the released contract. Model-backed GitHub Actions request `orchestrator/free`; they do not receive or select provider credentials, providers, models, provider groups, or paid fallbacks. `COPILOT_GITHUB_TOKEN` is prohibited. Independent review-agent credentials remain separate and cannot be renamed, copied, or repurposed for model execution. + +If the released orchestrator cannot expose a required capability, TEPP fails closed and the missing contract/capability is repaired at the contextual-orchestrator owner before consumer adoption. TEPP does not compensate by importing a provider SDK or secret. + +## 10. Acceptance, timeout, and failure semantics + +A workflow fails closed or abstains when required evidence is missing, the released orchestration contract is unavailable, results violate schema, model disagreement exceeds policy, injection tests trigger, or verifier support is insufficient. Provider outage/routing recovery is owned by contextual-orchestrator; TEPP receives the governed result or a typed unresolved/failure outcome and never silently chooses a replacement provider or paid route. -## 10. Acceptance and fallback +Reasoning, streaming, and tool-call work is not terminated merely because an arbitrary elapsed-time default expires. User cancellation, provider-declared termination, and explicit administrative timeout are separate typed outcomes. Long-running OpenCode/Strix/Noema-style work must remain possible when the released contract supports it. -A workflow fails closed or abstains when required evidence is missing, provider results violate schema, model disagreement exceeds policy, injection tests trigger, or verifier support is insufficient. Provider outage may route to an allowed alternative, return deferred/unresolved state, or use deterministic fallback where scientifically valid. It never silently changes the estimand or fabricates semantic evidence. +No LLM path may silently change an estimand, perform numerical scientific acceptance, authoritatively activate a candidate, or fabricate semantic evidence. ## 11. Required ablation before production claim Before claiming an orchestration mode materially improves TEPP, compare at least: -1. strongest approved single-model direct baseline; +1. strongest approved direct baseline exposed by the released orchestrator; 2. direct + verifier; 3. fixed role-based multi-agent workflow; 4. adaptive/learned-conductor-style workflow where available; 5. at least two reasoning-effort/budget settings. -Report uncertainty and failure modes, not only the best benchmark score. +Report uncertainty, routing receipts, contract version, budget, and failure modes, not only the best benchmark score. diff --git a/docs/TRD.md b/docs/TRD.md index 031c2113a..4fdcba602 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -1,7 +1,7 @@ # TEPP Technical Requirements Document -**Status:** Accepted technical baseline aligned to approved PRD v0.4 -**Last reviewed:** 2026-08-16 +**Status:** Accepted technical baseline aligned to approved PRD v0.4 and active PRD v0.4.1 LLM-routing amendment +**Last reviewed:** 2026-09-02 ## 1. Technical objective @@ -67,7 +67,11 @@ Every estimator/reasoner has synthetic known-truth tests appropriate to its clai ## 12. LLM boundary -Documents and LLM outputs are untrusted data. Live model tests use `NVIDIA_NIM_API_KEY`; `COPILOT_GITHUB_TOKEN` is prohibited. LLMs may assist semantic unitization, model review, interpretation, or verification only behind strict schemas/evidence bundles and cannot replace deterministic/statistical acceptance, mutate source evidence, execute document instructions, or gain merge/release authority. +Documents and LLM outputs are untrusted data. Semantic unitization, model review, interpretation, verification, judging, and model-backed automation consume only a **released, versioned `contextual-orchestrator` API/client/schema** through a TEPP ACL. TEPP owns semantic-task/evidence/access/scientific policy; contextual-orchestrator owns provider-key auto-discovery, provider/model/group routing, request-family adaptation, free/paid policy, fallback, stream/tool-call lifecycle, and provider execution. + +Model-backed GitHub Actions request `orchestrator/free` through the gateway credential only. TEPP does not choose a provider/model/group, declare paid fallback, receive provider API keys, or call providers directly. `COPILOT_GITHUB_TOKEN` is prohibited. If the released orchestrator lacks a required capability, TEPP fails closed until the canonical owner releases it; a mutable main/open PR/source snapshot is not production dependency authority. LLMs cannot replace deterministic/statistical acceptance, mutate source evidence, execute document instructions, perform numerical estimation, authoritatively activate a scientific candidate, or gain merge/release authority. + +At the 2026-09-02 review, contextual-orchestrator has no GitHub release, so production semantic execution remains non-deployable until a compatible immutable release and TEPP consumer-adoption evidence exist. The active PRD v0.4.1 amendment is the product authority for this service-boundary change. ## 13. Quality and release diff --git a/docs/adr/0017-hourly-contextual-orchestrator-gateway.md b/docs/adr/0017-hourly-contextual-orchestrator-gateway.md index 9cb32d1bd..aada6bdfc 100644 --- a/docs/adr/0017-hourly-contextual-orchestrator-gateway.md +++ b/docs/adr/0017-hourly-contextual-orchestrator-gateway.md @@ -1,80 +1,65 @@ # ADR 0017 — Hourly contextual-orchestrator gateway for autonomous proposals **Decision status:** Accepted -**Implementation maturity:** active-PR — workflow wiring and bootstrap contract are implemented on this PR and require exact-head Checks and protected-main integration +**Implementation maturity:** active-PR — released-owner workflow wiring requires exact-head Checks and protected-main integration **Date:** 2026-08-20 -**Supersedes:** None; this ADR clarifies the hourly proposal credential route without changing scientific or reviewer authority +**Last clarified:** 2026-09-02 +**Supersedes:** the earlier implementation interpretation that bootstrapped provider routing inside TEPP; Git history preserves that repair lineage **Related ADRs:** ADR 0010 (LLM test-time compute), ADR 0011 (modular MSA), ADR 0015 (autonomous development authority) ## Context -The hourly product-development workflow previously configured OpenCode directly -against one NVIDIA NIM endpoint. That did not exercise the repository's -contextual-orchestrator integration or its model-discovery contract, and it put a -provider key in the model-agent process. The scheduler must remain a proposal -producer: it may not merge, release, deploy, approve, or alter reviewer-agent -credentials. +The hourly product-development workflow originally configured OpenCode against a provider-specific endpoint. A first repair moved the provider keys into a locally started contextual-orchestrator gateway and added explicit-zero price filtering before local cheapest-model ranking. That repair established useful RED evidence for the hidden-spend defect, but it left two architecture violations in the consumer repository: + +1. TEPP still discovered, admitted, ranked, and selected provider models even though contextual-orchestrator owns provider/model/group routing and `orchestrator/free` policy. +2. The workflow consumed a checksum-pinned source commit rather than a released, versioned owner contract. A source checksum proves identity but does not establish immutable release authority, supportability, or contract provenance. + +The loopback implementation also sent its bearer token over plaintext HTTP and imposed a 900-second elapsed-time kill on OpenCode. Neither behavior belongs in the released gateway contract. ## Decision -The hourly proposal runner starts a pinned, ephemeral -`ContextualWisdomLab/contextual-orchestrator` loopback gateway. At bootstrap it -registers `BYTEZ_API_KEY`, `NVIDIA_NIM_API_KEY`, `NVIDIA_NIM_API_KEY_SUB`, -`OPENROUTER_API_KEY`, and `OPENAI_API_KEY` in the gateway KV, removes those -provider values from the gateway environment, and discovers models from all five -provider entries. The gateway records every discovered provider model, excludes -endpoint-only and safety-only model identifiers from the chat pool, and enables -the three lowest-cost general-chat candidates using contextual-orchestrator's -existing price-book selector. This prevents an embedding or image model from -being sent to an ordinary chat endpoint while preserving full discovery -evidence. - -OpenCode calls only `http://127.0.0.1:18000/v1` with a separately generated -loopback bearer token. It receives no provider credential. Liveness and -authenticated model-list checks must pass before the proposal agent runs. - -The workflow remains fail-closed when any provider credential is absent, the -PR or issue queue is unreadable, an open PR or issue exists, or the publication -App is unavailable. -The verifier and publisher receive neither provider nor model credentials. +The hourly proposal workflow consumes only a **released, versioned contextual-orchestrator contract**. Repository variable `CONTEXTUAL_ORCHESTRATOR_RELEASE` names the required owner release. Before semantic execution, the workflow queries the contextual-orchestrator GitHub release by tag and requires the matching release to be neither draft nor prerelease and to report `.immutable == true`. + +`CONTEXTUAL_ORCHESTRATOR_BASE_URL` is an HTTPS gateway base URL. The proposal runner uses only `CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN` to check gateway liveness and authenticated model discovery. The route catalog must expose `orchestrator/free`. OpenCode then requests `contextual-orchestrator/orchestrator/free` through the same released gateway. TEPP does not receive provider credentials and does not discover, rank, select, or fall back among providers/models/groups. + +If the owner release is absent, mutable, draft/prerelease, the gateway URL is not HTTPS, the gateway credential is unavailable, liveness/model-list checks fail, or `orchestrator/free` is absent, the proposal run fails closed. A mutable owner main commit, open PR head, source archive, or checksum-pinned snapshot is never substituted for the released contract. + +The previous TEPP-owned provider bootstrap is retired. Its explicit-zero tests remain useful historical RED evidence for issue #479, but the stronger invariant is now ownership: free/paid/provider admission lives in contextual-orchestrator and the TEPP workflow may only request `orchestrator/free`. + +OpenCode has no elapsed-time-only model timeout in this workflow. GitHub's proposal job retains a 55-minute explicit administrative job budget; administrative workflow exhaustion, user cancellation, provider termination, and model/stream/tool-call completion are distinct outcomes. + +The proposal/verifier/publisher separation remains unchanged. The proposal runner receives read-only repository authority plus the gateway credential; the verifier receives neither model nor publication credentials; the publisher mints the dedicated Maintainer App token only after immutable patch verification and never executes proposed code. ## Alternatives considered -1. **Direct NVIDIA NIM configuration** — rejected because it bypasses the - contextual-orchestrator discovery and routing boundary. -2. **One CLI registration process per provider** — rejected because the default - in-memory KV is process-local and would silently lose earlier registrations. -3. **Persistent shared credential database in the workflow** — rejected for this - ephemeral proposal path because it expands infrastructure and retention scope. -4. **One in-process gateway bootstrap with pinned source and loopback auth** — - accepted as the smallest auditable boundary. +1. **Direct provider configuration in TEPP** — rejected because it duplicates owner routing and credential policy. +2. **TEPP-side explicit-zero admission plus cheapest ranking** — rejected as the production design. It closes hidden spend only if consumer-side provider metadata is complete and still leaves routing authority in the wrong bounded context. +3. **Treat absent price metadata as zero** — rejected because unknown cost is not evidence of free service. +4. **Pin a contextual-orchestrator source commit/archive** — rejected as production authority because checksum identity is not a released contract. +5. **Start a loopback HTTP gateway with a local bearer token** — rejected because it creates consumer-owned gateway lifecycle/routing and cleartext bearer transport even on loopback. +6. **Consume immutable released `orchestrator/free` through an HTTPS gateway credential** — selected because provider routing, free policy, lifecycle, and credential discovery remain at the canonical owner while TEPP retains only semantic-task policy and consumer conformance. ## Consequences -- All configured provider discovery paths are exercised and auditable without - exposing provider keys to OpenCode. -- Discovery can fail for an individual provider while another provider supplies - a candidate; zero discovered candidates fail the proposal job. -- The three-candidate selection is cost-oriented, not a scientific quality - claim. TEPP deterministic evidence and human review remain authoritative. -- Provider terms, retention, region, and confidentiality must be reviewed for - every configured provider before the schedule is enabled. -- The workflow is not a Figma or visual interaction surface; no Figma file is - applicable to this headless scheduler decision. -- Figma File ID: N/A — this headless scheduler introduces no visual interaction - contract or design artifact. +- TEPP can be unavailable for live semantic automation while contextual-orchestrator has no compatible immutable release. That fail-closed availability loss is intentional. +- Provider/model identities may still appear in orchestrator receipts as observed provenance; they are not TEPP routing inputs. +- A release tag alone is insufficient. TEPP also requires immutable release metadata, HTTPS gateway availability, route presence, and exact consumer-side review/Checks after adoption. +- Provider retention, region, confidentiality, and commercial obligations remain owner/operator concerns and must be reviewed before enabling the gateway. +- The workflow is headless; no Figma artifact is applicable to this decision. ## Verification -The PR contract tests assert the hourly schedule, all five credential names, -immutable gateway source digest, loopback-only OpenCode configuration, health and -model-list probes, provider-key removal from OpenCode, no Copilot token, and -absence of merge/release commands. The bootstrap import and credential transfer -self-checks verify that the provider keys enter the KV and leave the process -environment before serving. +Repository contract tests require: + +- `CONTEXTUAL_ORCHESTRATOR_RELEASE`, `CONTEXTUAL_ORCHESTRATOR_BASE_URL`, and the gateway credential; +- GitHub release-tag lookup with `.immutable == true`; +- HTTPS-only gateway probes and `orchestrator/free` presence; +- no provider-key names, mutable contextual-orchestrator source pins, local provider-selection helper, loopback HTTP route, or elapsed-time OpenCode kill in the hourly workflow; +- no consumer-side provider-routing bootstrap source; +- queue/base revalidation, immutable proposal artifact identity, and exact publication authority separation. + +The historical explicit-zero admission regression is preserved in Git history and PR review lineage as the reproducer for issue #479. It is not retained as current production routing code because doing so would keep a second owner for provider/free policy. ## Rollback -Disable the schedule or revert this PR. Removing any required provider secret -causes a stable no-op. Rollback does not touch review-agent credentials, -protected-branch rules, or scientific runtime contracts. +Disable the schedule or revert the repair through a reviewed PR. Removing the released-contract tag, HTTPS gateway URL, gateway credential, or publication App configuration leaves the workflow fail-closed. Rollback does not touch review-agent credentials, protected-branch rules, or scientific runtime contracts. Do not restore direct provider access, local provider ranking, plaintext loopback bearer transport, or a mutable owner snapshot as an incident shortcut. diff --git a/docs/doctoring/hourly-nim-opencode-development.md b/docs/doctoring/hourly-nim-opencode-development.md index 7515ff86b..a28b12fc7 100644 --- a/docs/doctoring/hourly-nim-opencode-development.md +++ b/docs/doctoring/hourly-nim-opencode-development.md @@ -2,122 +2,80 @@ ## Claim boundary -This workflow can propose one bounded pull request only after both the pull-request -and issue queues are empty. It does not claim autonomous -semantic correctness, production safety, customer acceptance, scientific -validity, or permission to merge, release, deploy, or approve. Deterministic -tests, schemas, security scanners, CodeRabbit/OpenCode review, human judgment, -and exact-head branch rules remain independent controls. +This workflow can propose one bounded pull request only after both the pull-request and issue queues are empty. It does not claim autonomous semantic correctness, production safety, customer acceptance, scientific validity, or permission to merge, release, deploy, or approve. Deterministic tests, schemas, security scanners, independent review, human judgment, and exact-head rules remain separate controls. + +The current PR is also not evidence that live semantic automation is deployable. On the 2026-09-02 owner sweep, contextual-orchestrator protected `main` was `6d60c756b6481c59bd8fee95996315279bd708d5` and the repository had no GitHub release. TEPP therefore fails closed until an immutable compatible owner release exists and is adopted. ## Source-supported facts and project decisions | Topic | Source-supported fact | TEPP decision | |---|---|---| -| GitHub jobs | Jobs run on separate runner instances and receive scoped tokens | Model, verifier, and publisher are separate jobs | -| GitHub App authentication | A workflow can mint an installation token with explicit repository permissions | Publication token is minted only after non-executing validation | -| Artifact handoff | Artifact upload/download actions expose immutable IDs and digests | Patch ID, digest, base, size, count, and modes are checked twice | -| OpenCode | OpenCode is a programmable coding agent with provider configuration | One checksum-pinned binary calls only the loopback gateway | -| contextual-orchestrator | The pinned gateway discovers configured provider models, filters general-chat eligibility, and routes through its KV credential seam | All five provider keys are registered at bootstrap; endpoint-only models stay out of chat routing; OpenCode receives only a loopback token | -| Provider APIs | OpenAI-compatible and provider-specific model-list APIs expose discoverable models | Discovery is attempted for OpenAI, OpenRouter, both NVIDIA NIM keys, and Bytez before selection | -| SSDF | NIST SP 800-218 recommends protected build environments, review, provenance, and vulnerability response | Fresh verification and ordinary PR governance remain mandatory | -| AI risk | ISO/IEC 23894:2023 and 42001:2023 require contextual risk treatment and controlled change | Stable no-op reasons, bounded proposals, traceable decisions, and rollback are documented | -| Test-time compute | Fugu, Conductor, and TRINITY distinguish routing from deeper role-based workflows | Runtime LLM increments must preserve route/conduct and access-list controls | - -The GitHub, OpenCode, NVIDIA, NIST, ISO, and paper references below follow APA 7 -conventions as closely as the source type permits. - -## Upstream version evidence - -The official OpenCode GitHub release API identified OpenCode 1.18.13 as the -current upstream release on 2026-08-06. The workflow deliberately pins OpenCode 1.17.13 because its Linux x64 archive SHA-256 -`157afa289d1a8d9372de0ce19ac726119b937a1f6b201808d46f06e4e59bb348` -had already been independently reviewed in the CWL Noema workflow. The newer -archive is not adopted until its exact asset digest is independently captured, -reviewed, committed, and exercised. “Latest” is not allowed to mean -“unverified.” - -This is a supply-chain project decision, not a claim that OpenCode 1.17.13 is -functionally superior. The scheduled agent cannot auto-update itself. +| GitHub jobs | Jobs execute with explicitly scoped permissions and hosted job time limits | Proposal, verifier, and publisher remain separate jobs; the 55-minute proposal-job limit is an administrative budget, not a model reasoning timeout | +| GitHub App authentication | A workflow can mint an installation token with explicit repository permissions | Publication token is minted only in the publisher after immutable proposal validation | +| Artifact handoff | Artifact upload/download actions expose numeric IDs and digests | Patch ID, digest, base, size, count, and modes are checked before verification and publication | +| GitHub releases | Release records identify tag, draft/prerelease state, and immutable-release state where supported | Model-backed execution requires the configured contextual-orchestrator tag to resolve to a non-draft, non-prerelease release with `.immutable == true` | +| OpenCode | OpenCode can call an OpenAI-compatible endpoint and operate under bounded permissions | One checksum-pinned OpenCode binary calls only the configured HTTPS contextual-orchestrator gateway and requests `orchestrator/free` | +| contextual-orchestrator | The CWL owner is responsible for provider discovery, provider/model/group routing, free/paid admission, fallback, lifecycle, and provider execution | TEPP no longer bootstraps provider routing locally; it supplies only the semantic task/evidence policy and gateway credential | +| SSDF | NIST SP 800-218 recommends protected build environments, review, provenance, and vulnerability response | Fresh verification, immutable artifacts, least privilege, and ordinary PR governance remain mandatory | +| AI risk | ISO/IEC 23894:2023 and ISO/IEC 42001:2023 require contextual risk treatment and controlled change | Stable refusals, owner boundaries, bounded proposals, traceable decisions, and rollback are documented | +| Test-time compute | Fugu, Conductor, and TRINITY distinguish routing from deeper role-based workflows | Runtime experiments preserve routing-versus-conduct distinctions and record role/topology/reasoning ablations without replacing deterministic scientific gates | -## Orchestration research application +## RED → causal repair trace -**Fugu.** Fugu frames orchestration as selecting between direct model use and -deeper coordinated execution. TEPP already exposes explicit -`auto|route|conduct` organization modes; the autonomous prompt requires future -LLM changes to preserve this distinction. +Issue #479 first exposed a cost-admission defect: consumer-side cheapest ranking could admit paid or unpriced provider rows. The predecessor regression constrained local admission to explicit zero-valued pricing. Review of the same current feature then found that this “fix” still violated the canonical owner boundary because TEPP continued to discover/rank/select provider models and boot a mutable owner source snapshot. -**Conductor.** Conductor generates natural-language subtasks, worker assignment, -and access lists. The prompt therefore requires explicit workflow stages, -dependencies, least-privilege evidence access, and bounded recursive depth. +The stronger repair is architectural rather than another price predicate: -**TRINITY.** TRINITY emphasizes specialized thinker, worker, verifier, and -synthesis roles. The prompt requires role-specific reasoning effort and -reasoning-level ablation rather than one undifferentiated maximum-effort call. +1. retire `scripts/run_contextual_orchestrator.py` from TEPP production source; +2. require `CONTEXTUAL_ORCHESTRATOR_RELEASE` and query the owner release by tag; +3. require a non-draft, non-prerelease release with `.immutable == true`; +4. require an HTTPS `CONTEXTUAL_ORCHESTRATOR_BASE_URL` and gateway credential; +5. verify liveness and authenticated `/v1/models` over HTTPS and require `orchestrator/free`; +6. configure OpenCode only against the released gateway route; +7. remove consumer-side provider keys, provider discovery, price ranking, mutable owner source pins, plaintext loopback bearer transport, and the elapsed-time OpenCode kill; +8. preserve the GitHub job time limit as an explicit administrative budget; +9. correct publisher base revalidation to compare the live default-head SHA with `EXPECTED_BASE` rather than an unbound shell variable. -These papers motivate evaluation hypotheses. They do not prove that deeper -orchestration always improves TEPP reports. Deterministic grounding, -latency-insensitive quality tests, provider-reported usage where available, and -human review must compare forced routing and conducted cells. +The old explicit-zero test remains in Git history and PR review lineage as reproducer evidence. Keeping its provider-selection production helper would preserve the wrong owner, so current fitness instead asserts that the helper is absent and the workflow can only reach the released `orchestrator/free` boundary. -## APA 7th references +## Trust-boundary rationale + +The proposal runner receives read-only repository authority and the gateway credential. It has no publication token. The verifier receives neither gateway nor publication credentials and re-runs the release-quality gate against the immutable patch. The publisher applies the same patch without executing it and receives a narrow Maintainer App token only after metadata parsing and artifact checks. + +Gateway transport is HTTPS-only. Loopback binding alone was rejected as a confidentiality control because a bearer token over plaintext transport remains cleartext regardless of remote reachability. TEPP does not implement its own TLS/proxy for this lane because doing so would retain a second gateway lifecycle; the released contextual-orchestrator endpoint owns serving/transport while TEPP validates its HTTPS consumer boundary. -GitHub. (n.d.). *Security hardening for GitHub Actions*. GitHub Docs. Retrieved -August 6, 2026, from -https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions +Model execution has no fixed elapsed-time kill command. The proposal job's 55-minute GitHub limit is an explicit administrative resource boundary. User cancellation, owner/provider termination, model reasoning completion, stream completion, and tool-call completion remain semantically distinct. -GitHub. (n.d.). *Making authenticated API requests with a GitHub App in a GitHub -Actions workflow*. GitHub Docs. Retrieved August 6, 2026, from -https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow +## Orchestration research application + +**Fugu.** Fugu motivates query-adaptive choice between direct model use and deeper coordinated execution. It does not authorize consumer-side provider selection. + +**Conductor.** Conductor motivates task decomposition, worker assignment, access-list control, and topology experiments. TEPP records those as orchestration-policy variables passed through the released owner boundary. -ContextualWisdomLab. (2026). *contextual-orchestrator* (commit -e226e1197bdfc890c9d8e5b9b648c78857d7e465) [Computer software]. GitHub. -https://github.com/ContextualWisdomLab/contextual-orchestrator +**TRINITY.** TRINITY motivates specialized thinker/worker/verifier/synthesis roles and reasoning-level ablation. It does not replace evidence admission, numerical estimation, or scientific acceptance. -International Organization for Standardization, & International Electrotechnical -Commission. (2023a). *Information technology—Artificial intelligence—Guidance -on risk management* (ISO/IEC Standard No. 23894:2023). -https://www.iso.org/standard/77304.html +These sources motivate evaluation hypotheses; they do not prove deeper orchestration improves TEPP. Comparable-budget ablations, evidence support, failure/abstention behavior, and human review remain required. + +## APA 7th references -International Organization for Standardization, & International Electrotechnical -Commission. (2023b). *Information technology—Artificial -intelligence—Management system* (ISO/IEC Standard No. 42001:2023). -https://www.iso.org/standard/42001 +GitHub. (n.d.). *Security hardening for GitHub Actions*. GitHub Docs. Retrieved August 6, 2026, from https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions -National Institute of Standards and Technology. (2022). *Secure Software -Development Framework (SSDF) version 1.1: Recommendations for mitigating the -risk of software vulnerabilities* (NIST SP 800-218). -https://doi.org/10.6028/NIST.SP.800-218 +GitHub. (n.d.). *Making authenticated API requests with a GitHub App in a GitHub Actions workflow*. GitHub Docs. Retrieved August 6, 2026, from https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow -Nielsen, S., Cetin, E., Schwendeman, P., Sun, Q., Xu, J., & Tang, Y. (2025). -Learning to orchestrate agents in natural language with the Conductor. *arXiv*. -https://arxiv.org/abs/2512.04388 +International Organization for Standardization, & International Electrotechnical Commission. (2023a). *Information technology—Artificial intelligence—Guidance on risk management* (ISO/IEC Standard No. 23894:2023). https://www.iso.org/standard/77304.html -NVIDIA. (n.d.). *NVIDIA NIM APIs*. NVIDIA API Catalog. Retrieved August 6, 2026, -from https://build.nvidia.com/ +International Organization for Standardization, & International Electrotechnical Commission. (2023b). *Information technology—Artificial intelligence—Management system* (ISO/IEC Standard No. 42001:2023). https://www.iso.org/standard/42001 -OpenCode. (2026, August 4). *OpenCode 1.18.13* [Computer software release]. -GitHub. https://github.com/anomalyco/opencode/releases/tag/v1.18.13 +National Institute of Standards and Technology. (2022). *Secure Software Development Framework (SSDF) version 1.1: Recommendations for mitigating the risk of software vulnerabilities* (NIST SP 800-218). https://doi.org/10.6028/NIST.SP.800-218 -Sakana AI. (2026, June 22). *Sakana Fugu: One model to command them all*. -https://sakana.ai/fugu-release/ +Nielsen, S., Cetin, E., Schwendeman, P., Sun, Q., Xu, J., & Tang, Y. (2025). Learning to orchestrate agents in natural language with the Conductor. *arXiv*. https://arxiv.org/abs/2512.04388 -Xu, J., Sun, Q., Schwendeman, P., Nielsen, S., Cetin, E., & Tang, Y. (2025). -TRINITY: An evolved LLM coordinator. *arXiv*. -https://arxiv.org/abs/2512.04695 +OpenCode. (2026, August 4). *OpenCode 1.18.13* [Computer software release]. GitHub. https://github.com/anomalyco/opencode/releases/tag/v1.18.13 -## Tooling limitations recorded +Sakana AI. (2026, June 22). *Sakana Fugu: One model to command them all*. https://sakana.ai/fugu-release/ -Consensus search was requested for this increment but did not return a usable -paper record. Context7 reported its monthly quota exhausted. Primary paper -records, official documentation, and the existing Contextual Orchestrator -doctoring were therefore used directly. This limitation does not weaken the -deterministic workflow tests and must be revisited in a later literature update. +Xu, J., Sun, Q., Schwendeman, P., Nielsen, S., Cetin, E., & Tang, Y. (2025). TRINITY: An evolved LLM coordinator. *arXiv*. https://arxiv.org/abs/2512.04695 ## Residual risk -Provider keys exist inside the ephemeral gateway process during bootstrap, -proposed code is executed by an uncredentialed verifier with ordinary -hosted-runner egress, and artifact digests do not establish semantic safety. A -later broker can narrow provider egress; a later central reusable workflow can -remove duplicated repository policy. Neither future improvement may combine -model execution with publication authority. +A release record and HTTPS route prove a stronger ownership/transport boundary but not semantic correctness, provider commercial terms, or scientific validity. The verifier executes untrusted proposed code on an ephemeral hosted runner with ordinary egress. Artifact digests establish identity, not correctness. None of these controls authorizes TEPP to bypass an absent owner release, exact-head review, required workflows, or scientific acceptance gates. diff --git a/docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md b/docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md index cd78f1ca8..58507ec44 100644 --- a/docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md +++ b/docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md @@ -1,190 +1,99 @@ # Hourly Contextual Orchestrator Product Development -The hourly contextual-orchestrator product-development workflow proposes one bounded -commercial-quality increment when the repository has no open pull request or open -issue. It is -separate from the deterministic minute-17 quality sentinel and never merges, -releases, deploys, approves, or changes reviewer credentials. +The hourly product-development workflow proposes one bounded commercial-quality increment only when the repository has no open pull request or open issue. It is separate from deterministic quality gates and never merges, releases, deploys, approves, or changes reviewer credentials. ## Schedule and queue behavior -`.github/workflows/hourly-nim-product-development.yml` runs at minute 47 of every -hour and supports `workflow_dispatch` with `dry_run=true`. The nonzero minute -avoids the busiest scheduler boundary. A repository-scoped concurrency group -does not cancel an active run. - -Before checkout or model execution, the proposal job reads at most one open pull -request and one open issue. Unreadable inventory, any open PR or issue, any -missing provider key, or a missing Maintainer App configuration produces a stable -fail-closed no-op. A dry run may print the task contract without credentials. - -When a PR or issue exists, normal review → repair → exact-head Checks → merge -governance owns the hour. The scheduler does not create a competing branch. - -Current executable queue (2026-08-27T10:20Z snapshot; live state supersedes): - -1. Land the regression-fix authoring PR first: #274 restores the tepp_api - rustdoc contract, macOS-gates the mlx_native_receipt imports, closes the - branch/line coverage arms the #257/#266 slices left open (new tests and a - provably-dead-clause removal), and repairs the stale workspace contract - fixture. Merge only after exact-head required Checks and independent - approval. -2. Then the open Driver p.16 `std`-family restorations - #267/#268/#270/#271/#272, the TDT/CHRONOS composition #269, and the gap - baseline refresh #273 — each after a rerun on the fixed main base. -3. Do not open a competing hourly proposal while the open-PR inventory is - non-empty; review → repair → exact-head Checks → merge governs the hour. - -Preferred buyer-visible gaps once the queue drains: GAP-169 longitudinal -ESEM/DSEM composition beyond the recovered `std`-family, GAP-007 calibrated -TDT/CHRONOS workflow evidence, GAP-010 Storybook/tokens workspace -definitions with Figma, and GAP-011 multi-tenant release evidence. +`.github/workflows/hourly-nim-product-development.yml` runs at minute 47 of every hour and also supports `workflow_dispatch` with `dry_run=true`. Its repository-scoped concurrency group does not cancel an active run. + +Before checkout or model execution, the proposal job reads at most one open pull request and one open issue. Unreadable inventory, any open PR or issue, an unavailable immutable contextual-orchestrator release, an unavailable released gateway, or missing Maintainer App configuration produces a stable fail-closed no-op. A dry run may print the task contract without model or publication credentials. + +When a PR or issue exists, normal review → repair → exact-head Checks → merge governance owns the hour. The scheduler does not create a competing branch. + +The live queue is intentionally not duplicated here. `docs/product-technical-gap-baseline.md` and the queue-authority vehicle hold point-in-time queue evidence; GitHub is authoritative for current counts and exact heads. ## Required repository configuration Configure these repository or organization values: -- Secrets `BYTEZ_API_KEY`, `NVIDIA_NIM_API_KEY`, `NVIDIA_NIM_API_KEY_SUB`, - `OPENROUTER_API_KEY`, and `OPENAI_API_KEY` for gateway bootstrap only. -- Variable `TEPP_MAINTAINER_APP_CLIENT_ID`. -- Secret `TEPP_MAINTAINER_APP_PRIVATE_KEY`. -- A repository-scoped GitHub App installation with metadata read, contents - write, and pull-request write permissions only. +- variable `CONTEXTUAL_ORCHESTRATOR_RELEASE`: an immutable published contextual-orchestrator release tag; +- variable `CONTEXTUAL_ORCHESTRATOR_BASE_URL`: the HTTPS base URL for the released gateway; +- secret `CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN`: the gateway credential used by the proposal runner; +- variable `TEPP_MAINTAINER_APP_CLIENT_ID`; +- secret `TEPP_MAINTAINER_APP_PRIVATE_KEY`; +- a repository-scoped GitHub App installation with metadata read, contents write, and pull-request write permissions only. -Do not place GitHub App credentials in the proposal or verifier jobs. Do not -reuse the existing review App or alter its variable, secret, identity, or -provider route. Do not configure `COPILOT_GITHUB_TOKEN`. +TEPP does not configure or consume provider API credentials for this workflow. Provider discovery, provider/model/group selection, free/paid admission, fallback, and provider execution remain contextual-orchestrator responsibilities. The proposal job requests only `orchestrator/free`. -A manual dry run verifies scheduling, queue, and prompt contracts without model -or publication credentials. Missing production credentials leave the hourly -developer disabled rather than falling back to `GITHUB_TOKEN`. +Do not place GitHub App credentials in the proposal or verifier jobs. Do not reuse the existing review App or alter its variable, secret, identity, or route. A manual dry run verifies scheduling, queue, and prompt contracts without model or publication credentials. ## Three-runner trust boundary ### 1. Proposal runner -The proposal runner has read-only repository and pull-request permissions. -OpenCode is downloaded from an immutable versioned URL and checked against a -committed SHA-256. It calls only the loopback contextual-orchestrator gateway. - -The gateway source is downloaded from the pinned -`ContextualWisdomLab/contextual-orchestrator` commit and checked against a -committed SHA-256. At bootstrap, `scripts/run_contextual_orchestrator.py` -registers all five provider keys in the orchestrator KV, removes them from its -environment, discovers every provider model, records secret-free discovery -evidence, excludes endpoint-only and safety-only model identifiers from chat -routing, and enables the three lowest-cost general-chat candidates. OpenCode -receives only the gateway bearer token; it never receives a provider key. The -gateway's `/healthz` and authenticated `/v1/models` responses are checked before -the agent starts. - -The OpenCode process has provider keys, GitHub, OIDC, Actions runtime/cache, and -runner command-file variables removed. Network tools, GitHub -CLI, remote Git operations, commits, pushes, tags, external-directory access, -task delegation, interactive questions, and OpenCode web tools are denied. - -The model may edit the local working tree and run repository tests. The trusted -step stages the complete proposal, rejects whitespace errors, symbolic links, -gitlinks, excessive file count, and excessive patch bytes, then uploads one -binary full-index patch with a one-day retention period. +The proposal runner has read-only repository, issue, and pull-request permissions. OpenCode is downloaded from an immutable versioned URL and checked against the committed SHA-256. + +Before OpenCode runs, the workflow resolves `CONTEXTUAL_ORCHESTRATOR_RELEASE` with the GitHub Releases API and requires the matching release to be neither draft nor prerelease and to report `.immutable == true`. A mutable protected-main commit, open PR head, source archive, or checksum-pinned snapshot is not a production routing contract. + +The configured gateway URL must be HTTPS. The runner checks gateway liveness over HTTPS and makes an authenticated `/v1/models` request using only the gateway credential. The returned catalog must expose `orchestrator/free`; otherwise the workflow fails closed with `contextual_orchestrator_gateway_unavailable`. The workflow does not start a loopback gateway and does not perform local provider discovery, price ranking, or model selection. + +OpenCode receives only the gateway credential through its OpenAI-compatible adapter and requests `contextual-orchestrator/orchestrator/free`. Network tools, GitHub CLI, remote Git operations, commits, pushes, tags, external-directory access, task delegation, interactive questions, and OpenCode web tools remain denied by the generated configuration. + +There is no model/reasoning elapsed-time kill switch in the OpenCode command. The GitHub proposal job retains an explicit 55-minute administrative job budget. That administrative limit is distinct from user cancellation, provider termination, streaming/tool-call completion, and model reasoning duration. + +The model may edit the local working tree and run repository tests. A trusted step stages the complete proposal, rejects whitespace errors, symbolic links, gitlinks, excessive file count, and excessive patch bytes, then uploads one binary full-index patch with one-day retention. ### 2. Fresh verifier -A new runner checks out the exact base SHA and downloads by immutable numeric -artifact ID. It validates the upload digest, patch SHA-256, workflow-run -identity, expiration, base SHA, file count, byte count, and Git modes before -applying the patch. +A new runner checks out the exact proposal base SHA and downloads the artifact by immutable numeric ID. It validates the upload digest, patch SHA-256, workflow-run identity, expiration, base SHA, file count, byte count, and Git modes before applying the patch. -The verifier receives neither model nor publication credentials. It installs -the hash-locked Python environment, compiles the quality scripts and tests, -validates the workspace and documentation contracts, runs the quality tests -with 100% statement and branch coverage, and executes the pinned Rust format, -lint, test, documentation, dependency, line-coverage, and branch-coverage -gates. Coverage artifacts remain under `$RUNNER_TEMP`. It rejects any -verification mutation and proves the post-verification patch is byte-identical. +The verifier receives neither model nor publication credentials. It installs the hash-locked Python environment, compiles quality scripts and tests, validates workspace and documentation contracts, runs quality tests with 100% statement and branch coverage, and executes pinned Rust format, lint, test, documentation, dependency, line-coverage, and branch-coverage gates. Coverage artifacts remain under `$RUNNER_TEMP`. It rejects any verification mutation and proves the post-verification patch is byte-identical. ### 3. Fresh publisher -A third runner checks out the exact base and copies the trusted PR-message parser -to `RUNNER_TEMP` before applying the proposal. It repeats the immutable artifact -checks and applies the patch only as Git data. It executes no proposed tests, -build scripts, packages, binaries, or shell files. +A third runner checks out the exact base and copies the trusted PR-message parser to `RUNNER_TEMP` before applying the proposal. It repeats immutable artifact checks and applies the patch only as Git data. It executes no proposed tests, build scripts, packages, binaries, or shell files. -The copied parser rejects symlinks, non-regular files, malformed UTF-8, -unsupported controls, bidirectional spoofing, and byte-limit violations. Only -after bounded metadata is written does the publisher mint the repository-scoped -Maintainer App token. It then rechecks open-PR and open-issue inventory plus live -`main`, pushes one unique branch, and calls `gh pr create` exactly once. +The copied parser rejects symlinks, non-regular files, malformed UTF-8, unsupported controls, bidirectional spoofing, and byte-limit violations. Only after bounded metadata is written does the publisher mint the repository-scoped Maintainer App token. It then rechecks open-PR and open-issue inventory plus live `main`, pushes one unique branch, and calls `gh pr create` exactly once. The live default-branch SHA is compared with the same `EXPECTED_BASE` captured by the proposal runner; an advance fails with `base_branch_advanced`. ## Proposal contract -The autonomous prompt requires one buyer-visible gap, standalone and modular MSA -compatibility, realistic test-first evidence, 100% coverage and docstrings, -database naming policy, CHANGELOG and operations updates, and APA 7 doctoring. -After `audit_event` inserts are bound to `try_record`, the scheduled increment -is binding `tepp_api` privileged-export authorization decision records to -`operational_log::try_record`. It must not reopen a public -`OperationalLogRecord::new` constructor or mix `service_tls` or persistence -migration `0007` into that increment. -LLM work must use or improve Contextual Orchestrator and consider Fugu, -Conductor, TRINITY, workflow stages, access lists, bounded recursion, -role-specific reasoning effort, and ablation. - -The model must write `PR_MESSAGE.md` with a bounded title on the first line and a -body describing the product gap, design, RED-to-GREEN evidence, verification, -sources, version decision, and residual risk. Missing metadata receives a -minimal trusted fallback body. +The autonomous prompt requires one buyer-visible gap, standalone and modular MSA compatibility, realistic test-first evidence, 100% coverage and docstrings, database naming policy, CHANGELOG and operations updates, and APA 7 doctoring. Semantic LLM work crosses the released contextual-orchestrator boundary and requests `orchestrator/free`; TEPP does not select provider/model/group or a paid fallback. Fugu, Conductor, and TRINITY remain experimental orchestration hypotheses evaluated through reasoning-effort and topology ablations rather than substitutes for deterministic scientific gates. + +Operational logging preserves the existing audit boundary: privileged persistence inserts use `audit_event`; in-memory records are created only through `operational_log::try_record` / `OperationalLogRecord::new`; raw source content and source identity do not enter operational logs. + +The model writes `PR_MESSAGE.md` with a bounded title on the first line and a body describing the product gap, design, RED-to-GREEN evidence, verification, sources, version decision, and residual risk. Missing metadata receives a minimal trusted fallback body. ## Failure and recovery -Stable no-op reasons are: +Stable no-op or refusal reasons include: + +- `pull_request_inventory_unavailable`; +- `open_pull_request`; +- `issue_inventory_unavailable`; +- `open_issue`; +- `contextual_orchestrator_release_unavailable`; +- `contextual_orchestrator_gateway_unavailable`; +- `maintainer_app_unavailable`; +- `base_branch_advanced`; +- `open_pull_request_after_generation`; +- `open_issue_after_generation`. -- `pull_request_inventory_unavailable` -- `open_pull_request` -- `issue_inventory_unavailable` -- `open_issue` -- `issue_inventory_unavailable_after_generation` -- `open_issue_after_generation` -- `contextual_orchestrator_credentials_unavailable` -- `maintainer_app_unavailable` +There is no consumer-side provider, paid, unknown-price, or mutable-source fallback. If contextual-orchestrator has no compatible immutable release, scheduled semantic execution is intentionally unavailable until the owner publishes one and TEPP adopts its released contract. -A failed model candidate is discarded before a later candidate runs. A cleanup -or reinstall failure stops fallback. A failed verifier publishes nothing. A -publisher aborts if the artifact, base, queue, or metadata changed. If branch -push succeeds but PR creation fails, the error trap removes the orphan branch. +A failed verifier publishes nothing. A publisher aborts if the artifact, base, queue, or metadata changed. If branch push succeeds but PR creation fails, the error trap removes the orphan branch. -Investigate the exact run and job log, reproduce the relevant command on the -same commit, add or retain a failing regression, and repair through a normal PR. -Never bypass the verifier, substitute stale check evidence, or grant the model a -write token. +Investigate the exact run and job log, reproduce the relevant command on the same commit, add or retain a failing regression, and repair through a normal PR. Never bypass the verifier, substitute stale check evidence, grant the model a write token, or reintroduce consumer-side routing to make the schedule run. ## Disablement and rollback -Disable scheduled development by disabling the workflow, removing its schedule, -or removing any one of the five provider credentials. Removing any provider key -stops model execution; removing the Maintainer App values stops publication. -The minute-17 deterministic quality sentinel continues independently. +Disable scheduled development by disabling the workflow or removing its schedule. Removing the released-contract tag, HTTPS gateway URL, gateway credential, or Maintainer App configuration leaves production execution fail-closed. The deterministic quality sentinel continues independently. -Rollback a faulty workflow through a reviewed revert PR. Do not edit branch -protection, review workflows, or release workflows as an incident shortcut. -Delete orphan `nim-agent/product-dev-*` branches only after confirming no open PR -references them. +Rollback a faulty workflow through a reviewed revert PR. Do not edit branch protection, review workflows, release workflows, released-contract validation, or fail-closed routing as an incident shortcut. Delete orphan `nim-agent/product-dev-*` branches only after confirming no open PR references them. ## Residual risks -- The ephemeral gateway process receives provider credentials during bootstrap, - then removes them from its environment before serving. A future narrow - inference broker could keep upstream secrets outside the runner. -- Each configured provider may process repository source; operators must review - confidentiality, retention, regional, and contractual obligations for every - provider before enabling the schedule. -- The verifier executes untrusted code on an ephemeral hosted runner with - outbound network access, but receives no publication, provider, OIDC, - artifact/cache runtime, command-file, or reviewer credential. -- GitHub artifact storage, hosted runners, and pinned actions remain trusted - infrastructure. Digests prove identity, not semantic correctness. -- GitHub cannot atomically create a PR only when none exists. Final queue and - base revalidation, unique branches, review, and exact-head Checks bound the - race. -- The pinned OpenCode release intentionally trails the latest observed upstream - release until the exact Linux asset digest is independently reviewed. +- Gateway availability and the correctness of `orchestrator/free` admission are external owner responsibilities; TEPP validates release identity and route presence but cannot infer owner-side provider economics from consumer metadata. +- Each orchestrator-selected provider may process repository source. Operators must review confidentiality, retention, regional, and contractual obligations before enabling the released gateway. +- The verifier executes untrusted code on an ephemeral hosted runner with outbound network access, but receives no publication, gateway, OIDC, artifact/cache runtime, command-file, or reviewer credential. +- GitHub artifact storage, hosted runners, pinned actions, release metadata, and the configured HTTPS gateway remain trusted infrastructure. Digests and immutable-release metadata establish identity, not semantic correctness. +- GitHub cannot atomically create a PR only when none exists. Final queue and base revalidation, unique branches, review, and exact-head Checks bound that race. diff --git a/docs/product/prd-v0.4-approved.md b/docs/product/prd-v0.4-approved.md index 58d721ddd..17bcede0f 100644 --- a/docs/product/prd-v0.4-approved.md +++ b/docs/product/prd-v0.4-approved.md @@ -1,6 +1,6 @@ # Temporal Event Psychometrics Platform — Approved PRD v0.4 -**Status:** Approved design baseline +**Status:** Approved design baseline; §12 LLM execution target is superseded on the active PR by [`prd-v0.4.1-amendment-llm-routing.md`](prd-v0.4.1-amendment-llm-routing.md) until protected-main promotion **Approval date:** 2026-08-05 **Product name:** Temporal Event Psychometrics Platform (TEPP) **Measurement family:** Temporal Relational Shared-Latent Topic Measurement (TRSL-TM) @@ -206,13 +206,11 @@ branches, transitions, exact ties, and uncertainty are retained. LLMs may propose semantic units, concept mappings, candidate-model reviews, topic/cluster labels, explanations, and event-schema hypotheses. They do not perform authoritative numerical estimation or bypass statistical gates. -Every output is untrusted and must include approved structured fields, exact evidence identifiers, source spans, confidence, model/provider, prompt hash, reasoning effort, workflow depth, access list, and version. +Every output is untrusted and must include approved structured fields, exact evidence identifiers, source spans, confidence, model/provider provenance, prompt hash, reasoning effort, workflow depth, access list, and version. Interpretation is produced by an evidence-bounded interpreter and checked by an independent verifier for unsupported claims, direction reversals, causal overclaiming, group generalization, and omitted uncertainty. -`contextual-orchestrator` integration allocates test-time compute between direct single-model routing and deeper role-based orchestration. Evaluation varies decomposition, recursion, workflow stages, role-specific reasoning effort, and tool/access lists, with ablations informed by Fugu-, Conductor-, and TRINITY-style research directions. Speed is not the primary objective; calibrated quality and evidence are. - -Approved live tests use `NVIDIA_NIM_API_KEY`. `COPILOT_GITHUB_TOKEN` is prohibited. +The active PRD v0.4.1 routing amendment makes `contextual-orchestrator` the canonical provider-routing boundary. All semantic LLM work consumes a **released, versioned** contextual-orchestrator API/client/schema through a TEPP ACL. TEPP owns task/evidence/access/scientific policy but does not select provider, concrete model, provider group, or paid fallback and does not consume provider API keys. Model-backed GitHub Actions request `orchestrator/free` through the gateway credential only. If a compatible released contract is unavailable, semantic live execution fails closed rather than falling back to a direct provider path. `COPILOT_GITHUB_TOKEN` is prohibited. ## 13. Rust and compute requirements @@ -299,7 +297,7 @@ Documents and model outputs are untrusted. TEPP enforces tenant isolation, immut Scientific integrity is a security property. Silent temporal leakage, unsupported cross-language equivalence, failed uncertainty coverage, group bias, numerical backend divergence, or causal overclaiming fails closed. -Changes to latent-variable meaning, temporal semantics, event ontology, multilingual invariance, or estimator targets require an ADR and PRD version increase. +Changes to latent-variable meaning, temporal semantics, event ontology, multilingual invariance, estimator targets, or service authority require an ADR and PRD version increase/amendment. ## 18. Delivery phases @@ -329,4 +327,4 @@ The first release requires: ## 20. Approved baseline -This document is the approved v0.4 design baseline. The complete source PRD, preceding v0.2 and v0.3 designs, roadmap, implementation plan, validation report, instruction sources, and reproducibility manifest are retained in `docs/archive/source-material/`. +This document is the approved v0.4 design baseline. The active v0.4.1 LLM-routing amendment changes only the service-authority and released-contract boundary for §12 until its protected-main promotion; it does not alter TEPP's scientific estimands. The complete source PRD, preceding v0.2 and v0.3 designs, roadmap, implementation plan, validation report, instruction sources, and reproducibility manifest are retained in `docs/archive/source-material/`. diff --git a/docs/product/prd-v0.4.1-amendment-llm-routing.md b/docs/product/prd-v0.4.1-amendment-llm-routing.md new file mode 100644 index 000000000..8b889fe55 --- /dev/null +++ b/docs/product/prd-v0.4.1-amendment-llm-routing.md @@ -0,0 +1,55 @@ +# TEPP PRD v0.4.1 Amendment — Released contextual-orchestrator routing boundary + +**Status:** active-PR product-authority amendment; not implemented-main +**Amends:** `docs/product/prd-v0.4-approved.md` §12 LLM responsibilities +**Decision date:** 2026-09-02 +**Owning implementation vehicle:** PR #480 + +## Problem + +The approved v0.4 baseline once authorized live model tests with provider-specific execution. TEPP also carried an hourly bootstrap that discovered and ranked provider rows itself. Both behaviors put routing and credential policy in the consumer repository even though `contextual-orchestrator` is the canonical CWL owner of provider discovery/routing and `orchestrator/free` policy. + +A second constraint is material: on 2026-09-02 contextual-orchestrator protected `main@6d60c756b6481c59bd8fee95996315279bd708d5` has no GitHub release. A mutable main commit, open PR head, or checksum-pinned source snapshot cannot be promoted to production dependency authority merely because its source is reviewable. + +## Product requirement + +All TEPP semantic LLM work—semantic unitization, interpretation, verification, judging, label/explanation proposal, and model-backed automation—uses a **released, versioned contextual-orchestrator API/client/schema** through a TEPP ACL. + +TEPP owns the semantic task, minimum evidence bundle, access/tool policy, reasoning/verification policy, scientific-risk policy, provenance requirements, and abstention semantics. `contextual-orchestrator` owns provider-key auto-discovery, provider/model/group routing, request-family adaptation, free/paid policy, fallback, streaming/tool-call lifecycle, and provider execution. + +Model-backed GitHub Actions request `orchestrator/free` using the gateway credential only. TEPP does not select a provider, concrete model, provider group, or paid fallback and does not receive provider API keys. If the released orchestrator cannot provide a required capability, the consumer fails closed until the canonical owner releases that capability. + +LLM output never performs numerical estimation, scientific acceptance, or authoritative candidate activation. + +## Admission and release constraint + +The earlier TEPP-side explicit-zero admission regression is retained as historical defect evidence for issue #479, not as production routing authority. The local provider-discovery/ranking bootstrap is retired; keeping it would create a second owner for provider/free policy. + +Deployable semantic execution requires all of the following: + +1. a compatible immutable contextual-orchestrator release; +2. released contract/schema/client identity and reproducible artifact digest/provenance; +3. TEPP ACL compatibility fixtures and exact dependency review; +4. model-backed Actions using only `orchestrator/free` plus gateway credential over HTTPS; +5. exact-current TEPP CI/security/review evidence after the dependency adoption. + +Until those conditions hold, semantic live execution is fail-closed rather than silently reverting to direct provider access or a mutable source snapshot. + +## Alternatives considered + +- **Keep direct provider secrets in TEPP.** Rejected because it duplicates provider authority, leaks provider policy into a consumer, and makes key/routing changes branch-local. +- **Pin a mutable or unreleased contextual-orchestrator source commit.** Rejected as production authority because a source checksum proves identity, not release governance, supportability, or immutable published contract provenance. +- **Let TEPP rank provider models while using contextual-orchestrator only as transport.** Rejected because provider/model/group/free-policy routing belongs to the canonical owner. +- **Consume a released contextual-orchestrator contract through an ACL and fail closed when absent.** Selected because ownership, provenance, rollback, and consumer compatibility remain explicit. + +## Risks and effects + +A released-contract requirement can temporarily make live semantic features unavailable when the owner has not published a compatible release. That availability loss is intentional and preferable to hidden spend, mutable dependencies, or provider-specific execution outside the owner boundary. + +Routing receipts may still record the provider/model actually selected by contextual-orchestrator for reproducibility. Those values are observed provenance, not TEPP routing inputs. + +Long-running reasoning, streaming, and tool-call work must not be terminated solely by a short elapsed-time default. User cancellation, provider termination, and explicit administrative timeout remain distinct typed outcomes. + +## Acceptance evidence + +PR #480 must keep the canonical product/technical/architecture/LLM documents free of provider-key execution authority, remove TEPP-owned provider routing from the hourly path, require immutable released contextual-orchestrator metadata plus an HTTPS gateway and `orchestrator/free`, and keep deterministic documentation/workflow fitness tests. Protected-main promotion additionally requires the immutable owner release and consumer-adoption evidence above; an active PR cannot claim that release exists. diff --git a/scripts/run_contextual_orchestrator.py b/scripts/run_contextual_orchestrator.py deleted file mode 100644 index cf426514e..000000000 --- a/scripts/run_contextual_orchestrator.py +++ /dev/null @@ -1,179 +0,0 @@ -"""Bootstrap an ephemeral contextual-orchestrator gateway for CI agents. - -Provider credentials arrive only at bootstrap, are registered in the -orchestrator KV, and are removed from this process environment before the -gateway begins serving requests. Model discovery therefore exercises every -configured provider while OpenCode receives only the loopback bearer token. -""" - -from __future__ import annotations - -import argparse -from dataclasses import replace -import json -import os -from pathlib import Path -import re -from typing import Any - -PROVIDER_CREDENTIAL_NAMES = ( - "BYTEZ_API_KEY", - "NVIDIA_NIM_API_KEY", - "NVIDIA_NIM_API_KEY_SUB", - "OPENROUTER_API_KEY", - "OPENAI_API_KEY", -) - -_MODEL_TOKEN_RE = re.compile(r"[a-z0-9]+") -_NON_CHAT_MODEL_TOKENS = frozenset( - { - "bge", - "clip", - "dall", - "e5", - "embed", - "embedding", - "embeddings", - "image", - "images", - "moderation", - "realtime", - "rerank", - "reranker", - "siglip", - "sora", - "speech", - "transcribe", - "transcription", - "tts", - "whisper", - } -) -_NON_CHAT_MODEL_PREFIXES = ("embed", "moderat", "rerank", "transcrib") - - -def _is_general_chat_model(model_id: object) -> bool: - """Keep endpoint-only and safety-only catalog rows out of chat routing.""" - if not isinstance(model_id, str): - return False - tokens = tuple(_MODEL_TOKEN_RE.findall(model_id.casefold())) - if not tokens or any( - token in _NON_CHAT_MODEL_TOKENS - or token.startswith(_NON_CHAT_MODEL_PREFIXES) - for token in tokens - ): - return False - return not any( - token == "safety" - or token == "guard" - or token == "shieldgemma" - or token.startswith("nemoguard") - for token in tokens - ) - - -def _register_credential(name: str, value: str) -> None: - """Register one bootstrap secret through contextual-orchestrator's KV seam.""" - - from contextual_orchestrator import register_credential - - register_credential(name, value) - - -def _register_bootstrap_credentials() -> None: - """Move all provider keys from bootstrap environment into the KV registry.""" - - missing: list[str] = [] - for name in PROVIDER_CREDENTIAL_NAMES: - value = os.environ.pop(name, "") - if value: - _register_credential(name, value) - else: - missing.append(name) - if missing: - raise RuntimeError(f"missing provider credentials: {', '.join(missing)}") - - -def _selected_agents() -> tuple[list[Any], dict[str, object]]: - """Discover all providers and enable the three lowest-cost candidates.""" - - from contextual_orchestrator import InMemoryConfigStore, PriceBook - from contextual_orchestrator.model_discovery import ( - agent_from_discovered, - discover_all_models, - refresh_price_book, - select_top_n_cheapest_discovered_agents, - ) - - discovered, errors = discover_all_models() - if not discovered: - providers = ", ".join(sorted(error.provider_name for error in errors)) or "none" - raise RuntimeError(f"model discovery produced no candidates; providers_with_errors={providers}") - price_book = PriceBook(InMemoryConfigStore()) - chat_discovered = [ - model for model in discovered if _is_general_chat_model(model.model_id) - ] - if not chat_discovered: - raise RuntimeError("model discovery produced no general chat candidates") - priced_count = refresh_price_book(chat_discovered, price_book) - selected = select_top_n_cheapest_discovered_agents(chat_discovered, price_book, 3) - if not selected: - raise RuntimeError("model discovery selected no general chat candidates") - agents = [ - replace(agent_from_discovered(model, priority=3 - index), disabled=False) - for index, model in enumerate(selected) - ] - report = { - "discovered_count": len(discovered), - "chat_candidate_count": len(chat_discovered), - "excluded_non_chat_count": len(discovered) - len(chat_discovered), - "priced_count": priced_count, - "providers_discovered": sorted({model.provider_name for model in discovered}), - "providers_with_errors": sorted({error.provider_name for error in errors}), - "selected_models": [agent.model for agent in agents], - } - return agents, report - - -def _write_report(path: Path, report: dict[str, object]) -> None: - """Write secret-free discovery evidence with restrictive permissions.""" - - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(json.dumps(report, ensure_ascii=True, sort_keys=True) + "\n", encoding="utf-8") - path.chmod(0o600) - - -def _start_gateway(agents: list[Any], gateway_token: str, host: str, port: int) -> None: - """Construct and serve the contextual-orchestrator HTTP gateway.""" - - from contextual_orchestrator import TaskOrchestrator - from contextual_orchestrator.server import SecurityConfig, serve - - serve( - TaskOrchestrator(agents), - host=host, - port=port, - security=SecurityConfig(auth_token=gateway_token), - ) - - -def main() -> None: - """Start the authenticated gateway after provider discovery succeeds.""" - - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--host", default="127.0.0.1") - parser.add_argument("--port", type=int, default=18000) - parser.add_argument("--report", type=Path, required=True) - args = parser.parse_args() - - _register_bootstrap_credentials() - gateway_token = os.environ.pop("CONTEXTUAL_ORCHESTRATOR_INFERENCE_TOKEN", "") - if not gateway_token: - raise RuntimeError("CONTEXTUAL_ORCHESTRATOR_INFERENCE_TOKEN is required") - agents, report = _selected_agents() - _write_report(args.report, report) - _start_gateway(agents, gateway_token, args.host, args.port) - - -if __name__ == "__main__": # pragma: no cover - exercised by the workflow process - main() diff --git a/tests/quality/test_hourly_nim_product_development.py b/tests/quality/test_hourly_nim_product_development.py index e2a1a03a0..44147699f 100644 --- a/tests/quality/test_hourly_nim_product_development.py +++ b/tests/quality/test_hourly_nim_product_development.py @@ -1,20 +1,16 @@ -"""Lock the hourly contextual-orchestrator OpenCode security contract.""" +"""Lock the hourly released-orchestrator OpenCode security contract.""" from __future__ import annotations -from dataclasses import dataclass -import os -import sys +import tempfile import unittest from pathlib import Path -from types import ModuleType -from unittest.mock import patch WORKFLOW = Path(".github/workflows/hourly-nim-product-development.yml") -BOOTSTRAP = Path("scripts/run_contextual_orchestrator.py") PARSER = Path("scripts/prepare_agent_pr_message.py") RUNBOOK = Path("docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md") DOCTORING = Path("docs/doctoring/hourly-nim-opencode-development.md") +PRD_AMENDMENT = Path("docs/product/prd-v0.4.1-amendment-llm-routing.md") def _text(path: Path) -> str: @@ -24,69 +20,63 @@ def _text(path: Path) -> str: return path.read_text(encoding="utf-8") -def _parser_module() -> ModuleType: - """Load the trusted pull-request metadata parser as a covered module.""" +class HourlyOrchestratorProductDevelopmentContractTests(unittest.TestCase): + """Structural tests for released-contract, credential-separated automation.""" - assert PARSER.is_file() - import scripts.prepare_agent_pr_message as module - - return module - - -class HourlyNimProductDevelopmentContractTests(unittest.TestCase): - """Structural tests for the credential-separated product-development loop.""" - - def test_hourly_workflow_schedule_credentials_and_queue_gate(self) -> None: - """Run at minute 47 with provider discovery and fail closed around inventory.""" + def test_workflow_uses_released_orchestrator_free_gateway_only(self) -> None: + """Reject provider credentials, mutable source pins, and consumer-side routing.""" text = _text(WORKFLOW) - bootstrap = _text(BOOTSTRAP) for token in ( 'cron: "47 * * * *"', "workflow_dispatch:", "dry_run:", "hourly-nim-product-development-${{ github.repository }}", "cancel-in-progress: false", - "secrets.BYTEZ_API_KEY", - "secrets.NVIDIA_NIM_API_KEY", - "secrets.NVIDIA_NIM_API_KEY_SUB", - "secrets.OPENROUTER_API_KEY", - "secrets.OPENAI_API_KEY", - "issues: read", - "permission-issues: read", - "CONTEXTUAL_ORCHESTRATOR_COMMIT", - "CONTEXTUAL_ORCHESTRATOR_SHA256", - "run_contextual_orchestrator.py", - "/healthz", - "/v1/models", - "{env:OPENCODE_GATEWAY_TOKEN}", - "OPENCODE_VERSION", - "OPENCODE_SHA256", - "sha256sum -c", + "CONTEXTUAL_ORCHESTRATOR_RELEASE", + "CONTEXTUAL_ORCHESTRATOR_BASE_URL", + "secrets.CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN", + "orchestrator/free", + "repos/ContextualWisdomLab/contextual-orchestrator/releases/tags/", + "contextual_orchestrator_release_unavailable", + "contextual_orchestrator_gateway_unavailable", "pull_request_inventory_unavailable", "open_pull_request", - "gh issue list --repo", "issue_inventory_unavailable", "open_issue", - "contextual_orchestrator_credentials_unavailable", "maintainer_app_unavailable", "base_branch_advanced", "open_pull_request_after_generation", - "issue_inventory_unavailable_after_generation", "open_issue_after_generation", - "ContextualWisdomLab/TEPP", + "--proto '=https'", + '[[ "$CONTEXTUAL_ORCHESTRATOR_BASE_URL" == https://* ]]', ): self.assertIn(token, text) - for token in ("discover_all_models", "register_credential", "PROVIDER_CREDENTIAL_NAMES"): - self.assertIn(token, bootstrap) - self.assertNotIn("COPILOT_GITHUB_TOKEN", text) - self.assertNotIn("CONTEXTUAL_ORCHESTRATOR_TOKEN=", text) + + for forbidden in ( + "BYTEZ_API_KEY", + "NVIDIA_NIM_API_KEY", + "NVIDIA_NIM_API_KEY_SUB", + "OPENROUTER_API_KEY", + "OPENAI_API_KEY", + "CONTEXTUAL_ORCHESTRATOR_COMMIT", + "CONTEXTUAL_ORCHESTRATOR_SHA256", + "run_contextual_orchestrator.py", + "select_top_n_cheapest_discovered_agents", + "OPENCODE_RUN_TIMEOUT_SECONDS", + "timeout --kill-after", + "http://127.0.0.1", + "COPILOT_GITHUB_TOKEN", + ): + self.assertNotIn(forbidden, text) + + self.assertIn(".immutable == true", text) self.assertEqual(text.count("gh pr create"), 1) self.assertNotIn("gh pr merge", text) self.assertNotIn("gh release create", text) - def test_hourly_workflow_separates_three_runner_trust_boundaries(self) -> None: - """Separate model execution, verification, and late publication authority.""" + def test_workflow_separates_proposal_verification_and_publication_authority(self) -> None: + """Only the publisher receives repository mutation authority.""" text = _text(WORKFLOW) proposer = text.split("propose_product_increment:", 1)[1].split( @@ -97,14 +87,14 @@ def test_hourly_workflow_separates_three_runner_trust_boundaries(self) -> None: )[0] publisher = text.split("publish_product_increment:", 1)[1] - self.assertIn("BYTEZ_API_KEY", proposer) - self.assertIn("OPENAI_API_KEY", proposer) + self.assertIn("CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN", proposer) + self.assertIn("orchestrator/free", proposer) self.assertNotIn("create-github-app-token", proposer) self.assertNotIn("gh pr create", proposer) - self.assertNotIn("NVIDIA_NIM_API_KEY", verifier) + self.assertNotIn("CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN", verifier) self.assertNotIn("create-github-app-token", verifier) self.assertIn("Run every release-quality gate", verifier) - self.assertNotIn("NVIDIA_NIM_API_KEY", publisher) + self.assertNotIn("CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN", publisher) self.assertIn("create-github-app-token", publisher) self.assertIn("gh pr create", publisher) self.assertNotIn("cargo test", publisher) @@ -118,7 +108,7 @@ def test_hourly_workflow_separates_three_runner_trust_boundaries(self) -> None: text.index("Mint dedicated maintainer App token only for publication"), ) - def test_hourly_workflow_binds_artifacts_and_strips_runtime_channels(self) -> None: + def test_workflow_binds_artifacts_and_strips_runtime_mutation_channels(self) -> None: """Bind the patch exactly and remove untrusted GitHub mutation channels.""" text = _text(WORKFLOW) @@ -147,7 +137,6 @@ def test_hourly_workflow_binds_artifacts_and_strips_runtime_channels(self) -> No "-u GITHUB_PATH", "-u GITHUB_STATE", "-u GITHUB_STEP_SUMMARY", - "timeout --kill-after", '"webfetch": "deny"', '"websearch": "deny"', '"external_directory": "deny"', @@ -161,8 +150,16 @@ def test_hourly_workflow_binds_artifacts_and_strips_runtime_channels(self) -> No self.assertIn(token, text) self.assertEqual(text.count("artifact-ids:"), 2) - def test_hourly_prompt_and_verifier_keep_commercial_quality_gates(self) -> None: - """Require one buyer gap, research-grounded orchestration, and full checks.""" + def test_publisher_revalidates_the_exact_proposal_base(self) -> None: + """Reject a default-branch advance without an unbound shell variable.""" + + text = _text(WORKFLOW) + publisher = text.split("publish_product_increment:", 1)[1] + self.assertIn('[ "$live_base" = "$EXPECTED_BASE" ]', publisher) + self.assertNotIn('$expected_base', publisher) + + def test_prompt_and_verifier_keep_scientific_and_commercial_gates(self) -> None: + """Require one buyer gap, released orchestration, and full verification.""" text = _text(WORKFLOW) normalized = " ".join(text.casefold().split()) @@ -172,38 +169,28 @@ def test_hourly_prompt_and_verifier_keep_commercial_quality_gates(self) -> None: "gap id", "never invent weights", "no heuristics", - "rules of thumb", "primary source", "exactly one bounded pull request", "standalone", - "modular MSA", - "ContextualWisdomLab/.github", - "naruon", + "modular msa", "contextual-orchestrator", - "Fugu", - "Conductor", - "TRINITY", - "single-model", - "deep multi-agent", + "released", + "orchestrator/free", + "fugu", + "conductor", + "trinity", "reasoning effort", - "access lists", - "recursive depth", "ablation", - "Speed is not a priority", "100% production statement and branch coverage", "100% public docstring coverage", "two-word-or-longer snake_case", - "APA 7", - "CHANGELOG.md", - "Do not merge", - "Do not release", - "Do not deploy", - "Rust", - "audit_event", - "try_record", - "OperationalLogRecord::new", + "apa 7", + "do not merge", + "do not release", + "do not deploy", + "rust", ): - self.assertIn(token.casefold(), normalized) + self.assertIn(token, normalized) verifier = text.split("package_product_increment:", 1)[1].split( "publish_product_increment:", 1 @@ -216,23 +203,40 @@ def test_hourly_prompt_and_verifier_keep_commercial_quality_gates(self) -> None: "cargo deny check", 'line_coverage="$RUNNER_TEMP/coverage.lcov"', 'branch_coverage="$RUNNER_TEMP/coverage-branches.json"', - "cargo llvm-cov --workspace --all-features --lcov --output-path \"$line_coverage\" --ignore-filename-regex 'sqlx_live\\.rs'", 'python3 scripts/check_coverage.py "$line_coverage" --kind lines --format lcov', - "cargo +nightly-2026-08-21 llvm-cov --branch --workspace --all-features --json --output-path \"$branch_coverage\" --ignore-filename-regex 'sqlx_live\\.rs'", 'python3 scripts/check_coverage.py "$branch_coverage" --kind branches', ): self.assertIn(command, verifier) - self.assertNotIn( - "--json --summary-only --output-path \"$branch_coverage\"", - verifier, - ) + + def test_runbook_and_prd_amendment_match_released_owner_boundary(self) -> None: + """Keep operations and product authority free of provider-key bootstrap.""" + + runbook = _text(RUNBOOK) + amendment = _text(PRD_AMENDMENT) + doctoring = _text(DOCTORING) + combined = runbook + amendment + for token in ( + "released", + "contextual-orchestrator", + "orchestrator/free", + "gateway credential", + "fail closed", + ): + self.assertIn(token, combined.casefold()) + for forbidden in ( + "NVIDIA_NIM_API_KEY", + "OPENROUTER_API_KEY", + "OPENAI_API_KEY", + "BYTEZ_API_KEY", + ): + self.assertNotIn(forbidden, runbook) + self.assertIn("APA", doctoring) def test_parser_accepts_unicode_and_owner_only_outputs(self) -> None: """Parse realistic Korean metadata and protect trusted output files.""" - import tempfile + import scripts.prepare_agent_pr_message as parser - parser = _parser_module() with tempfile.TemporaryDirectory() as tmp: tmp_path = Path(tmp) source = tmp_path / "PR_MESSAGE.md" @@ -243,13 +247,7 @@ def test_parser_accepts_unicode_and_owner_only_outputs(self) -> None: "구매자가 체감하는 제품 Gap 하나를 안전하게 닫습니다.\r\n", encoding="utf-8", ) - parser.main( - [ - str(source), - str(title_path), - str(body_path), - ] - ) + parser.main([str(source), str(title_path), str(body_path)]) self.assertEqual( title_path.read_text(encoding="utf-8"), "feat: 시간별 오케스트레이터 제품 개발 루프 추가", @@ -258,181 +256,6 @@ def test_parser_accepts_unicode_and_owner_only_outputs(self) -> None: for path in (title_path, body_path): self.assertEqual(path.stat().st_mode & 0o777, 0o600) - def test_supporting_runbook_and_doctoring_exist(self) -> None: - """Keep operations and research doctoring discoverable.""" - - runbook = _text(RUNBOOK) - doctoring = _text(DOCTORING) - for token in ("OPENAI_API_KEY", "contextual-orchestrator", "proposal", "verification", "publication"): - self.assertIn(token, runbook) - self.assertIn("APA", doctoring) - self.assertIn("Do not configure `COPILOT_GITHUB_TOKEN`", runbook) - - def test_hourly_queue_reflects_current_open_inventory(self) -> None: - """The hourly runbook names the open queue; no merged lock PR is cited.""" - - runbook = _text(RUNBOOK) - self.assertIn("review → repair → exact-head checks → merge", runbook.casefold()) - self.assertIn("Driver p.16", runbook) - self.assertIn("TDT/CHRONOS", runbook) - for pull_request in (93, 94, 97, 101, 102, 104, 108, 109, 111, 112): - with self.subTest(pull_request=pull_request): - self.assertNotIn(f"PR #{pull_request}", runbook) - def test_bootstrap_registers_each_provider_key_and_removes_environment_values(self) -> None: - """Exercise the real bootstrap loop with a key-counting KV double.""" - - import scripts.run_contextual_orchestrator as bootstrap - - calls: list[tuple[str, str]] = [] - fake_package = ModuleType("contextual_orchestrator") - fake_package.register_credential = lambda name, value: calls.append((name, value)) - values = {name: f"test-value-{name}" for name in bootstrap.PROVIDER_CREDENTIAL_NAMES} - with patch.dict(sys.modules, {"contextual_orchestrator": fake_package}), patch.dict( - os.environ, values, clear=False - ): - bootstrap._register_bootstrap_credentials() - self.assertEqual( - calls, - [(name, values[name]) for name in bootstrap.PROVIDER_CREDENTIAL_NAMES], - ) - for name in bootstrap.PROVIDER_CREDENTIAL_NAMES: - self.assertNotIn(name, os.environ) - - def test_bootstrap_fails_closed_when_one_provider_key_is_missing(self) -> None: - """Reject incomplete provider bootstrap without silently selecting a subset.""" - - import scripts.run_contextual_orchestrator as bootstrap - - fake_package = ModuleType("contextual_orchestrator") - fake_package.register_credential = lambda _name, _value: None - values = { - name: ("present" if index == 0 else "") - for index, name in enumerate(bootstrap.PROVIDER_CREDENTIAL_NAMES) - } - with patch.dict(sys.modules, {"contextual_orchestrator": fake_package}), patch.dict( - os.environ, values, clear=False - ): - with self.assertRaisesRegex(RuntimeError, "missing provider credentials"): - bootstrap._register_bootstrap_credentials() - - def test_discovery_selection_and_empty_provider_fail_closed_paths(self) -> None: - """Select discovered candidates and reject a discovery result with no models.""" - - import scripts.run_contextual_orchestrator as bootstrap - - @dataclass(frozen=True) - class FakeAgent: - """Small dataclass matching the fields changed by dataclasses.replace.""" - - model: str - priority: int = 0 - disabled: bool = True - - class FakePriceBook: - """Minimal price-book constructor accepted by the selection seam.""" - - def __init__(self, _store: object) -> None: - pass - - sample = type("SampleModel", (), {"model_id": "model_one", "provider_name": "provider_one"})() - embedding = type( - "EmbeddingModel", - (), - {"model_id": "text-embedding-3-small", "provider_name": "provider_one"}, - )() - error = type("SampleError", (), {"provider_name": "provider_two"})() - fake_package = ModuleType("contextual_orchestrator") - fake_package.InMemoryConfigStore = object - fake_package.PriceBook = FakePriceBook - fake_discovery = ModuleType("contextual_orchestrator.model_discovery") - fake_discovery.agent_from_discovered = lambda model, priority=0: FakeAgent( - model.model_id, priority=priority - ) - fake_discovery.discover_all_models = lambda: ([embedding, sample], [error]) - fake_discovery.refresh_price_book = lambda models, _book: len(models) - fake_discovery.select_top_n_cheapest_discovered_agents = lambda models, _book, _limit: models - fake_modules = { - "contextual_orchestrator": fake_package, - "contextual_orchestrator.model_discovery": fake_discovery, - } - with patch.dict(sys.modules, fake_modules): - agents, report = bootstrap._selected_agents() - self.assertEqual([agent.model for agent in agents], ["model_one"]) - self.assertFalse(agents[0].disabled) - self.assertEqual(report["discovered_count"], 2) - self.assertEqual(report["chat_candidate_count"], 1) - self.assertEqual(report["excluded_non_chat_count"], 1) - self.assertEqual(report["providers_with_errors"], ["provider_two"]) - - fake_discovery.discover_all_models = lambda: ([], []) - with self.assertRaisesRegex(RuntimeError, "providers_with_errors=none"): - bootstrap._selected_agents() - - fake_discovery.discover_all_models = lambda: ([embedding], []) - with self.assertRaisesRegex(RuntimeError, "no general chat candidates"): - bootstrap._selected_agents() - - def test_report_gateway_and_main_contract_are_executable_with_seams(self) -> None: - """Cover report permissions, gateway construction, and CLI orchestration.""" - - import tempfile - - import scripts.run_contextual_orchestrator as bootstrap - - class FakeOrchestrator: - """Capture the selected agent pool passed to the gateway runtime.""" - - def __init__(self, agents: list[object]) -> None: - self.agents = agents - - class FakeSecurity: - """Capture the loopback bearer token passed to the HTTP server.""" - - def __init__(self, auth_token: str) -> None: - self.auth_token = auth_token - - server_calls: list[tuple[object, str, int, object]] = [] - fake_package = ModuleType("contextual_orchestrator") - fake_package.TaskOrchestrator = FakeOrchestrator - fake_server = ModuleType("contextual_orchestrator.server") - fake_server.SecurityConfig = FakeSecurity - fake_server.serve = lambda orchestrator, *, host, port, security: server_calls.append( - (orchestrator, host, port, security) - ) - with patch.dict( - sys.modules, - {"contextual_orchestrator": fake_package, "contextual_orchestrator.server": fake_server}, - ): - with tempfile.TemporaryDirectory() as tmp: - report_path = Path(tmp) / "nested" / "discovery.json" - bootstrap._write_report(report_path, {"discovered_count": 1}) - self.assertEqual(report_path.read_text(encoding="utf-8"), '{"discovered_count": 1}\n') - self.assertEqual(report_path.stat().st_mode & 0o777, 0o600) - - bootstrap._start_gateway(["agent"], "gateway-token", "127.0.0.1", 18000) - self.assertEqual(server_calls[0][0].agents, ["agent"]) - self.assertEqual(server_calls[0][1], "127.0.0.1") - self.assertEqual(server_calls[0][2], 18000) - self.assertEqual(server_calls[0][3].auth_token, "gateway-token") - - with patch.object(bootstrap, "_register_bootstrap_credentials"), patch.object( - bootstrap, "_selected_agents", return_value=([], {"discovered_count": 0}) - ) as selected, patch.object(bootstrap, "_write_report") as written, patch.object( - bootstrap, "_start_gateway" - ) as started, patch.dict( - os.environ, {"CONTEXTUAL_ORCHESTRATOR_INFERENCE_TOKEN": "gateway-token"}, clear=False - ), patch.object(sys, "argv", ["run_contextual_orchestrator.py", "--report", "report.json"]): - bootstrap.main() - selected.assert_called_once_with() - written.assert_called_once_with(Path("report.json"), {"discovered_count": 0}) - started.assert_called_once_with([], "gateway-token", "127.0.0.1", 18000) - - with patch.object(bootstrap, "_register_bootstrap_credentials"), patch.dict( - os.environ, {}, clear=False - ), patch.object(sys, "argv", ["run_contextual_orchestrator.py", "--report", "report.json"]): - with self.assertRaisesRegex(RuntimeError, "INFERENCE_TOKEN is required"): - bootstrap.main() - if __name__ == "__main__": unittest.main() diff --git a/tests/quality/test_hourly_orchestrator_free_admission.py b/tests/quality/test_hourly_orchestrator_free_admission.py new file mode 100644 index 000000000..b96cfbaed --- /dev/null +++ b/tests/quality/test_hourly_orchestrator_free_admission.py @@ -0,0 +1,100 @@ +"""Regression tests for released contextual-orchestrator routing ownership. + +The predecessor tests pinned TEPP-side provider discovery and explicit-zero price +filtering. That was useful RED evidence for issue #479, but it also proved the +consumer repository owned routing logic that belongs to contextual-orchestrator. +These tests preserve the defect boundary by requiring the wrong-owner bootstrap +to stay retired and the hourly workflow to consume only the released free route. +""" + +from __future__ import annotations + +from pathlib import Path +import unittest + + +ROOT = Path(__file__).resolve().parents[2] +WORKFLOW = ROOT / ".github/workflows/hourly-nim-product-development.yml" +RETIRED_BOOTSTRAP = ROOT / "scripts/run_contextual_orchestrator.py" + + +class HourlyOrchestratorFreeAdmissionTests(unittest.TestCase): + """Keep provider/model/free-policy authority outside the TEPP consumer.""" + + def test_consumer_side_provider_routing_bootstrap_is_retired(self) -> None: + """Do not keep a second provider discovery or price-ranking authority.""" + + self.assertFalse( + RETIRED_BOOTSTRAP.exists(), + "provider/model/free-route discovery belongs to released contextual-orchestrator", + ) + + def test_hourly_workflow_uses_only_released_free_route(self) -> None: + """Require immutable owner release, HTTPS gateway, and orchestrator/free.""" + + text = WORKFLOW.read_text(encoding="utf-8") + for required in ( + "CONTEXTUAL_ORCHESTRATOR_RELEASE", + "CONTEXTUAL_ORCHESTRATOR_BASE_URL", + "secrets.CONTEXTUAL_ORCHESTRATOR_GATEWAY_TOKEN", + "orchestrator/free", + ".immutable == true", + "contextual_orchestrator_release_unavailable", + "contextual_orchestrator_gateway_unavailable", + "--proto '=https'", + ): + self.assertIn(required, text) + for forbidden in ( + "run_contextual_orchestrator.py", + "select_top_n_cheapest_discovered_agents", + "prompt_price_per_1k", + "completion_price_per_1k", + "http://127.0.0.1", + ): + self.assertNotIn(forbidden, text) + + def test_authenticated_gateway_probes_allow_only_https_redirects(self) -> None: + """Prevent authenticated gateway probes from following an HTTP downgrade.""" + + text = WORKFLOW.read_text(encoding="utf-8") + secure_redirect_probe = ( + "curl --fail --silent --show-error --location --proto '=https' " + "--proto-redir '=https' --tlsv1.2" + ) + self.assertGreaterEqual( + text.count(secure_redirect_probe), + 2, + "both gateway probes must restrict redirect protocols to HTTPS", + ) + + def test_opencode_archive_download_allows_only_https_redirects(self) -> None: + """Keep the checksum-pinned CLI download from following an HTTP downgrade.""" + + text = WORKFLOW.read_text(encoding="utf-8") + self.assertIn( + "curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' \\", + text, + "OpenCode archive download must restrict redirect protocols to HTTPS", + ) + + def test_canonical_llm_authority_requires_released_orchestrator_contract(self) -> None: + """Keep normative product/technical docs from re-authorizing provider keys.""" + + canonical_paths = ( + ROOT / "AGENTS.md", + ROOT / "CONTRIBUTING.md", + ROOT / "docs/product/prd-v0.4-approved.md", + ROOT / "docs/TRD.md", + ROOT / "docs/LLM_ORCHESTRATION.md", + ROOT / "ARCHITECTURE.md", + ) + for path in canonical_paths: + with self.subTest(path=path.relative_to(ROOT)): + text = path.read_text(encoding="utf-8") + self.assertNotIn("NVIDIA_NIM_API_KEY", text) + self.assertIn("contextual-orchestrator", text) + self.assertIn("released", text.lower()) + + +if __name__ == "__main__": + unittest.main()