Skip to content

v3.17.0

Choose a tag to compare

@github-actions github-actions released this 15 Jul 23:15
· 130 commits to main since this release

Changed

  • Dialogue reviews now gate raw weakest-link claim support at a
    matched-FPR operating point
    instead of squeezing bidirectional NLI
    through the 0.80 baseline (WCS-2a). The WCS-1 end-to-end proof
    (BENCHMARK_REPORT §16, tracked artefact
    e2e_nli_only_200_wcs1_wired.json) showed the squeeze absorbs
    evidence improvements — decisions were identical at catch 4.5 % —
    while the raw operating point measures catch 27 % at the same 4.5 %
    false-positive rate on HaluEval-dialogue. Each response claim is
    scored independently against the conversation context and the
    weakest claim decides; coherence for the dialogue route is that raw
    support. New config: nli_dialogue_scoring
    ("raw_support" default, "baseline_squeeze" restores the previous
    behaviour) and nli_dialogue_support_threshold (seeded 0.0091 from
    the FactCG sweep; recalibrate per deployment — see the calibration
    tool below).
  • Cache hits now resolve the review gate exactly like fresh scoring
    (task detection + adaptive per-task threshold + meta-classifier +
    raw-support operating point). Previously a cache hit re-gated the
    cached score on the global threshold, so a decision could differ
    between a cache miss and a cache hit of the same input.
  • Summarisation claim-coverage layers (MiniCheck and FactCG) now score
    against the whole source document instead of a 3 000-character
    prefix; each backend applies its own long-input chunking. The
    2026-07-15 evidence sweep (BENCHMARK_REPORT §16) showed the prefix
    truncation is dominated on both HaluEval and RAGTruth — catch at the
    matched false-positive rate rose from 0.049 to 0.289 on RAGTruth
    Summary with whole-document scoring. The previous behaviour is
    restorable via DirectorConfig.nli_summarization_premise_chars = 3000
    (DIRECTOR_NLI_SUMMARIZATION_PREMISE_CHARS).

Security

  • Defence-in-depth hardening from the validated 2026-07-15 external
    review intake: SQL DDL identifiers and column clauses are strictly
    validated before f-string composition (audit log chain migration,
    feedback-store column migration — internal constants today, now
    guarded against future callers); gRPC server reflection is an
    explicit opt-in (grpc_reflection_enabled, default off — the full
    service schema is a reconnaissance aid); the regulated-domain
    profiles (medical, finance, legal) enable privacy_mode so
    PII is redacted from logs and review queues by default.
  • Streaming trend statistics (core/runtime/streaming.py) now state
    their contract honestly: they are MANDATORY Rust accelerators
    (enforced 2026-05-22) — the unreachable pure-Python fallback bodies,
    the misleading _RUST_TREND dispatch flag, and the stale
    "falls back to Python" tests (inverted by a conftest hook) are
    gone; kernel-absent installs raise the actionable [rust]-extra
    error, and the tests assert that propagation directly.
  • Operator-supplied regexes (policy YAML patterns, sanitiser
    extra_patterns/allowlist) are validated before compilation:
    the classic catastrophic-backtracking shape — an unbounded repeat
    nested inside another unbounded repeat, e.g. (a+)+ — is rejected
    with an actionable error, as are patterns over 4 096 chars
    (core/safety/_regex_guard.py). Bounded repetitions pass.
  • WebSocket streaming sessions cap each prompt at 100 000 chars
    (mirrors the SSE cap; the per-connection char budget still applies).
  • Prompt/query hashes in logs are now keyed (HMAC-SHA256 with a
    per-process ephemeral key) instead of bare SHA-256, so exfiltrated
    log lines cannot be brute-forced offline against short prompts;
    in-run correlation is unchanged (the meta-guard decision log is
    in-memory, retrieval query hashes are log-line correlators).

Fixed

  • PII redaction was quadratic in the number of findings: the
    non-overlapping span selection rescanned every accepted span per
    candidate, so a 2 MB input of dense PII took 13 minutes to redact —
    a denial-of-service vector wherever privacy_mode is enabled. The
    selection is now linear (2.4 s on the same input, 324× faster),
    with the old implementation kept as a behavioural oracle in tests.

Added

  • Weakest-link summarisation aggregation
    (nli_summarization_aggregation="weakest_link" +
    nli_summarization_support_threshold): scores every summary claim
    against the whole source and gates the least-supported claim at a
    matched-FPR support operating point — the only WCS-1 configuration
    that improved on both HaluEval (catch 0.120 → 0.125) and RAGTruth
    (0.049 → 0.289). Default stays the coverage/Layer-A blend.

  • Matched-FPR operating-point calibration
    (director_ai.core.calibration.operating_points and
    director-ai operating-points <labeled.jsonl>): collects raw
    weakest-link supports through the production scorer
    (CoherenceScorer.raw_task_support), picks the largest support
    threshold whose false-positive rate on labelled good responses stays
    within a per-task target, and emits a ready-to-use config/env
    overlay. Shipped thresholds are sweep seeds — deployments should
    calibrate on their own traffic.

  • Rubric-scored, optionally ensembled LLM judge
    (DirectorConfig.llm_judge_rubric / llm_judge_ensemble): the
    escalation judge can score grounding / fabrication-risk /
    contradiction-risk dimensions (G-Eval-style, composite decides the
    verdict) instead of a bare YES/NO, and can aggregate 1–5
    independent calls — majority vote with confidence damped by the
    agreement fraction, so a split panel weakens the judge's influence
    on the blended score. Defaults unchanged (single-shot verdict);
    invalid or empty panels fall back to the NLI score exactly as
    before.

  • Self-consistency / semantic-entropy uncertainty signal
    (director_ai.core.scoring.self_consistency): SelfConsistencyScorer
    clusters caller-supplied alternative generations by bidirectional
    entailment (shipped NLI backend, lexical fallback labelled in the
    result) and scores normalised semantic entropy plus the primary
    response's consensus agreement. Opt-in fusion on the review
    pipeline: enable_self_consistency(weight=…) +
    review_with_samples(prompt, action, samples) — the fused score can
    revoke an approval but never approves a rejected review;
    CoherenceScore gains self_consistency_score, semantic_entropy
    and self_consistency_backend fields.

  • Hardened per-tenant isolation for shared vector indexes:
    TenantScopedBackend
    (director_ai.core.retrieval.vector_store.tenant_guard) binds any
    VectorBackend to one tenant — adds stamp the bound tenant into
    metadata (conflicting labels raise TenantIsolationError), an
    empty caller tenant can no longer widen a query to the whole
    index, and every returned row is verified against the bound tenant
    (foreign or unlabelled rows are dropped and counted in the
    tenant_isolation_violations metric, or raise with
    strict=True). VectorGroundTruthStore.grounded() gains
    enforce_tenant_isolation=True to wrap the whole retrieval stack;
    FAISSBackend tenant-filtered queries now expand the over-fetch
    window geometrically until enough tenant documents are found or
    the whole index is searched, instead of silently starving tenants
    whose documents sit beyond the previous fixed 3× window.

  • Hybrid retrieval fusion strategies beyond RRF
    (director_ai.core.retrieval.vector_store.fusion): convex
    (min-max normalised convex combination, CombSUM family), combmnz
    (CombSUM × cross-run agreement) and zscore (standardised-score
    sum) join the default weighted rrf. Selectable via
    HybridBackend(fusion_method=...),
    VectorGroundTruthStore.grounded(fusion_method=...) and the new
    DirectorConfig.hybrid_fusion_method /
    hybrid_sparse_weight / hybrid_dense_weight fields;
    HybridBackend.with_fusion() derives query views that share one
    BM25 + dense index so strategies can be compared without
    re-indexing. The BM25 run now carries native scores into the
    fusion layer, and the RRF docstring cites the actual source
    (Cormack, Clarke & Büttcher, SIGIR 2009).

  • NeMo Guardrails rails-as-config loader
    (director_ai.integrations.rails_config.load_rails_config): maps the
    honest subset of a NeMo config directory (config.yml + Colang .co
    files) onto Director's native Policy — Colang v1 topical refusal
    rails become forbidden phrases, recognised self-check/content-safety
    rails enable the dependency-free moderation detectors — and reports
    every unmapped construct in RailsLoadResult.unsupported instead of
    silently dropping it. RAIL XML deliberately raises with a pointer to
    the native Guardrails AI validator integration. New docs page
    docs-site/integrations/nemo-rails.md.

  • Computed AI-governance controls (director_ai.compliance. governance_controls, BUSL tier): compute_governance_controls()
    derives NIST AI RMF 1.0 / ISO/IEC 42001:2023 / EU AI Act crosswalk
    controls from observable deployment state — DirectorConfig guard and
    data-governance knobs, a live verify_chain() pass over the
    tamper-evident audit log, and documentation evidence artefacts under
    an operator-supplied evidence root. Six controls cover Articles 9
    (risk management), 10 (data governance), 11 (technical
    documentation), 12 (record-keeping), plus the Article 15 accuracy
    bridge and Article 14 human-oversight readiness; every status is
    derived from named ControlSignal observations, so missing inputs
    degrade honestly instead of aborting. New endpoint
    GET /v1/compliance/governance-controls (never 503s — an
    unconfigured audit log is itself the reported finding) and CLI
    subcommand director-ai compliance governance. Previously only
    Article 15 reporting was computed; SOC 2/ISO 27001/HIPAA remain the
    static readiness catalogue.

  • POST /v1/stream/sse — REST Server-Sent Events streaming endpoint
    (routers/streaming_sse.py). Serves the same two session shapes as
    the /v1/stream WebSocket — whole-answer result, or token-level
    pre-egress oversight with token/halt/complete frames — as
    text/event-stream, for server-to-server callers and clients behind
    WebSocket-hostile proxies. Auth/tenant binding ride the standard REST
    middleware (no ticket exchange); validation failures are plain HTTP
    errors before the stream starts; a per-process concurrent-stream cap
    mirrors the WebSocket connection budget.

  • OpenAI-compatible proxy: /v1/moderations (local mode analyses inputs
    with the shipped dependency-free toxicity/PII detectors and answers in
    the OpenAI moderations shape; moderations="upstream" forwards the
    request verbatim), /v1/completions (legacy text completions scored
    through the same review flow as chat, streaming included — halt chunks
    mirror the legacy text delta shape), and /v1/embeddings
    (passthrough). New CLI flag director-ai proxy --moderations local|upstream; audit entries record task_type="completion" for the
    legacy route. New guide page docs-site/guide/openai-proxy.md.

  • benchmarks/retrieval_model_refresh_ab.py — embedder/reranker refresh
    A/B evidence for the grounded() default pair (WCA-8). Eight arms
    through the public recipe on the internal retrieval evaluation set
    (30 queries, CPU): any cross-encoder lifts hit@1 from 0.733–0.767 to
    0.933–1.000; bge-m3 + bge-reranker-v2-m3 reaches hit@1 1.000 at
    ~2.2 s/query, bge-m3 + ms-marco-MiniLM-L-6-v2 reaches 0.967 at
    ~0.56 s/query. The internal set saturates near the ceiling, so the
    refresh decision defers to the public-benchmark run below. Artefact:
    benchmarks/results/retrieval_model_refresh_ab.json (committed) with
    exact model revisions and host environment.

  • benchmarks/beir_competitive_bench.py — BEIR NFCorpus + SciFact test
    splits through the shipped grounded() recipe (hybrid BM25+dense RRF,
    optional cross-encoder rerank of the top 30), scored with
    pytrec_eval (nDCG@10) and cross-checked against a built-in
    linear-gain implementation. The artefact embeds published baselines
    verified at source (BEIR paper Table 2; bge-large-en-v1.5 model-card
    MTEB metrics) so the numbers are comparable across systems.
    Measured (L4 GPU, artefact committed): the shipped hybrid pipeline
    without a reranker reaches nDCG@10 0.3703 (NFCorpus) / 0.7331
    (SciFact), above the BEIR paper's BM25 (0.325/0.665) and BM25+CE
    (0.350/0.688) rows; the candidate bge-m3 embedder is below the
    shipped bge-large default on both datasets, so the RECOMMENDED_*
    defaults stay unchanged — an evidence-based no-change. Full table,
    readings, and claim boundary in benchmarks/BENCHMARK_REPORT.md
    §11; the table is registered in
    benchmarks/public_accuracy_manifest.toml. Quality numbers
    cross-checked CPU vs GPU: the committed
    beir_competitive_bench_cpu_i5_11600K.json re-runs all five NFCorpus
    arms end-to-end on CPU and reproduces the GPU nDCG@10 values exactly,
    while measuring CPU rerank latency under load (ms-marco ~3 s/query,
    bge-reranker-v2-m3 ~70 s/query on 30 candidates).

  • DirectorConfig.finetune_models_dir (env: DIRECTOR_FINETUNE_MODELS_DIR)
    — the server now passes a configurable models/jobs directory to the
    fine-tuning router instead of always mounting the hard-coded
    ./director-models default.

Changed

  • Add native unit tests to the backfire-ffi crate (18 tests: Bulletproof
    range-proof round-trip/tamper/error paths, geometry containment,
    two-link inverse kinematics, Merkle root/auth-path/walk parity,
    HMAC challenge derivation, reality-anchor MAC verification, and the
    statistical helper contracts). The crate's extension-module flag is
    now a default crate feature so cargo test -p backfire-ffi --no-default-features can link libpython for the test binary; maturin
    wheel builds are unchanged.

  • Stop tracking the benchmarks/models/factcg-cb tokeniser blobs
    (tokenizer.json 8.4 MB, spm.model 2.4 MB) in git; they are inherited
    unchanged from the fine-tune's base model and the directory README now
    documents the one-line restore. Configs and training results stay
    tracked; nothing in src/ or tests/ reads the directory.

  • Consolidate the four typos configurations into the single canonical
    .typos.toml (union of all allow-lists and file excludes) and delete
    typos.toml, _typos.toml, and backfire-kernel/_typos.toml. The
    pre-commit hook, the CI Pre-commit workflow, and a loose typos run
    from any repo directory now read the same configuration — previously a
    loose run silently picked the stale typos.toml, which wins CLI
    discovery over the hook-pinned .typos.toml.

  • Decompose finetune_api.py by responsibility: request/response
    contracts move to _finetune_schemas, the local training worker to
    _finetune_worker, and the /managed/* endpoints to
    _finetune_managed; the finetune_api facade keeps the local lane,
    the router factory, and the historical import surface unchanged.

  • VectorGroundTruthStore.grounded() now builds the full retrieval stack by
    default: FAISS-indexed dense search (use_ann), BM25 + RRF hybrid fusion,
    and cross-encoder reranking (use_reranker), each degrading gracefully to
    the previous behaviour when its optional dependency is missing. On the
    committed benchmark (benchmarks/results/grounded_ann_bench.json, run via
    python -m benchmarks.grounded_ann_bench) the reranked default lifts hit@1
    from 0.767 to 0.967 and hit@3 to 1.000 on the retrieval evaluation set at
    ~57 ms per query on CPU, and flat FAISS answers 20 000-document queries in
    8.6 ms (p50) against 25.3 ms for the previous linear scan at identical
    recall.

Fixed

  • Align the Rust rust_standard_normal_quantile domain guard with the
    Python reference: p = 0.0 now raises instead of returning negative
    infinity (the documented domain is the open interval, and the Python
    dispatcher already rejected it).
  • SentenceTransformerBackend now selects a compatible torch device via
    select_torch_device(), so a visible-but-unsupported CUDA GPU (for
    example an sm_61 card next to a modern PyTorch wheel) no longer crashes
    dense retrieval at the first encode. The backend also accepts a preloaded
    model object for offline and embedded deployments, mirroring the
    reranker injection on RerankedBackend.