<!-- ephemeral -->

# TeaQL Rust Runtime Customization

The generated runtime owns provider creation and schema setup. Customize the returned
`UserContext`; do not add provider, policy, or sink parameters to generated execute/save APIs.

```rust
use crm_erp_service_core::{service_runtime, ServiceRuntime, ServiceRuntimeConfig, ServiceRuntimeError};
use std::sync::{Arc, Mutex};
use teaql_runtime::{RequestPolicy, RuntimeError, SafeAuditEvent, SafeAuditEventSink, UserContext};

#[derive(Clone, Default)]
pub struct AppAuditSink {
    events: Arc<Mutex<Vec<SafeAuditEvent>>>,
}

impl AppAuditSink {
    pub fn events(&self) -> Vec<SafeAuditEvent> {
        self.events.lock().expect("App Audit Sink lock poisoned").clone()
    }
}

impl SafeAuditEventSink for AppAuditSink {
    fn on_safe_event(&self, _context: &UserContext, event: &SafeAuditEvent) -> Result<(), RuntimeError> {
        self.events.lock().expect("App Audit Sink lock poisoned").push(event.clone());
        Ok(())
    }
}

#[derive(Clone, Copy)]
pub struct TrustedRequestPolicy;
impl RequestPolicy for TrustedRequestPolicy {}

pub async fn configured_runtime(
    database_url: String,
    app_audit_sink: AppAuditSink,
) -> Result<ServiceRuntime, ServiceRuntimeError> {
    let mut context = service_runtime(ServiceRuntimeConfig { database_url }).await?;
    context.set_request_policy(TrustedRequestPolicy);
    context.set_custom_event_sink(app_audit_sink);
    context.insert_named_resource("trusted_tenant", "system".to_owned());
    Ok(context)
}

pub async fn readiness(context: &ServiceRuntime) -> Result<(), RuntimeError> {
    context.get_named_resource::<String>("trusted_tenant")
        .ok_or_else(|| RuntimeError::Behavior("missing trusted tenant".to_owned()))?;
    context.ensure_schema().await
}

pub fn reject_governance_override(input: &serde_json::Value) -> Result<(), String> {
    const FORBIDDEN: [&str; 6] = [
        "tenant", "provider", "requestPolicy", "auditSink", "hardLimit", "continuousPage",
    ];
    match input {
        serde_json::Value::Object(values) => {
            for (key, value) in values {
                if FORBIDDEN.iter().any(|forbidden| forbidden.eq_ignore_ascii_case(key)) {
                    return Err(format!("forbidden governance override: {key}"));
                }
                reject_governance_override(value)?;
            }
        }
        serde_json::Value::Array(values) => {
            for value in values { reject_governance_override(value)?; }
        }
        _ => {}
    }
    Ok(())
}
```

## Executable contract

- Workspace startup creates the provider once through `service_runtime_from_env`,
  `service_runtime`, or `service_runtime_from_pool`; provider failures propagate.
- `UserContext` initialization is the trusted boundary for request policy, tenant resources,
  and the customizable App Audit Sink. The immutable raw row audit path remains separate.
- `/health` may be liveness-only; readiness must call the generated schema/provider path and
  fail when a trusted dependency is absent.
- Web, console, and batch workspaces pass only `&UserContext` to generated query/save methods.
- The public write path is the generated `.audit_as(...)` followed by the context-only `save`
  call. Do not bypass it with raw SQL or low-level mutation commands.
- Dynamic JSON and TFP input must reject governance keys recursively. Add negative governance
  tests plus missing dependency, missing intent/audit, and provider failure tests.
- A complete integration test also runs the generated Query/Create Assist against SQLite;
  do not claim runtime customization from a route-only `/health` smoke test.

## Runtime telemetry

Observability is optional and application-owned. Enable the `opentelemetry`
feature, construct `teaql_runtime::OpenTelemetryRuntimeTelemetry` from the
application tracer and meter, wrap it in `Arc`, and call
`context.set_runtime_telemetry(telemetry)`. Keep `NoopRuntimeTelemetry` when it
is absent. The application owns bounded processors, OTLP exporters,
`force_flush` and shutdown; telemetry failure must never change business results.
Installing telemetry does not call `ensure_schema`.
TeaQL derives `teaql.error.category` from the native error type. Sampling never
controls or replaces App Audit Sink delivery. Do not generate a Collector,
additional exporters, auto-discovery, or a telemetry configuration DSL.

---

## TeaQL seven-language assist contract

Apply the verified Rust semantic ceiling while using only the exact RUST generated and
runtime APIs. Discover APIs through the generated application AGENTS.md and progressive
model-aware Assist. Do not inspect generated domain-library source.

- Do not create plurals by appending `s` or `es`; use the centralized generated plural.
- Human and non-human entities use different generated predicate vocabularies. Preserve
  forms such as “who are active” and “whose email is”; never infer them from English.
- Configure filters, projection, paging, and other query options before `purpose(...)`.
  Comment may appear anywhere in the chain. Purpose enters the executable stage; execution
  requires both values, but comment does not have to immediately precede purpose.
- Every execute/list/stream and every save accepts exactly one context argument:
  `UserContext`. Name that argument `context`, never `runtime`; data services and global
  policy are injected when the context is built. Reserve `runtime` for process-level
  runtime ownership, provider/pool setup, and module assembly.
- Tenant, merchant, identity, permissions, request policy, purpose policy, hard limit,
  and continuous-page cursor policy come only from trusted context, never dynamic JSON or TFP.
- If the required operation is absent after current entity/action and required field
  Assist, stop that path and report MISSING_ASSIST. Do not guess an API or search the
  generated library as a fallback.
- Create each application-owned source file once. After its first compile attempt,
  repair only the smallest block identified by the exact compiler or test diagnostic.
  Preserve unrelated code; do not rewrite the complete file as an error-recovery loop.
- Before a repair that would replace more than 25% of an existing application file,
  stop and report LARGE_REWRITE_REQUEST with the file, exact diagnostic, reason, and
  estimated scope. Initial creation and model-driven regeneration are not repairs.

Capability: `runtime-custom`.

- Keep trusted dependencies and global runtime policy in UserContext initialization.
  Custom providers, policy hooks, and audit sinks must not add execute/save arguments.
- Preserve immutable row audit events and a separate customizable App Audit Sink.
  Include health, integration, and negative governance tests for every customization.
