Production Tribunal, Governed Approvals, and Investigation Campaigns

Overview

v1.2.0 moves command authoring behind a Gateway-side Tribunal, adds a CLI L3 approval path with local L3 proof verification on outbound Operators, makes consensus bindings K-of-N, and gives evaluation campaigns fresh and persistent investigations with sealed agent-trace.v7 evidence. The legacy evaluation SUT and the JSON agent-tool registry are removed.

Protocol changes are additive except where noted under Removed; retired field and enum numbers are reserved.

Highlights

  • Production Tribunal — New POST /api/v1/operators/intents (internal/services/tribunal/, internal/services/gateway/tribunal_intent.go). A caller submits a plain-language intent; the Tribunal runs generate → vote → marshal (risk) → audit as governed INFERENCE dispatches (a high-risk winner is BLOCKED before audit), then dispatches the approved command as an ordinary governed EXECUTE_BASH under the caller's identity. It never supplies or replaces L2 or L3 proof. Outcomes: APPROVED, NO_CONSENSUS, BLOCKED (high risk, with rationale), AUDIT_FAILED, INFERENCE_FAILED. Requires an app workload identity with a delegated user (INV-GW-GOV-07).
  • CLI and outbound L3 approval — ztd auth approve --cli signs the transaction hash with the enrolled CLI certificate. Outbound Operators verify L3 proof locally: notary proofs carry the CLI certificate chain (cli_certificate_der, cli_intermediates_der) or a PasskeyVerificationContext (user, credential, COSE key, RP ID, allowed origins). Approval logic lives in internal/governance/cli_approval.go and passkey_approval.go; outbound verification is in internal/services/governance/outbound_l3_notary.go.
  • K-of-N consensus bindings — ztd auth enroll consensus bind accepts repeated --member-app and --quorum. A multi-member set must state its quorum, and the primary app must be a member. The Gateway and the L5 handler resolve membership through one owner (models.ResolveConsensusMembership). New enrollment payload fields: consensus_member_app_ids = 22, consensus_quorum = 23.
  • Investigation campaigns — ztd eval campaigns create gains --investigation fresh|persistent:<id>, --data-operator, --condition longitudinal|control|held-context, --formations, and --posture. --hold-context now defaults to false. Campaigns can set tribunal_commands, so the model states intent through run_commands_with_operator and the Tribunal writes the command.
  • Sealed campaign evidence — Each assignment seals its full conversation and tool trajectory as a content-addressed agent-trace.v7 envelope (v1.1.3 sealed agent-trace.v5). Campaign import and the assignment verifier accept only v7 for new campaign evidence.
  • Event-driven runtime — Process-exit waiting uses platform-specific process handles (internal/cli/platform/process_exit_*.go). ztd eval runs follows stop and status events over the existing event stream. The @ztd/adapter SSE polling fallback is gone.

Added

  • Protocol
    • ztd/tribunal/v1/tribunal.proto: TribunalIntentRequest, TribunalIntentResult, TribunalMemberCall, TribunalStage, TribunalOutcome.
    • common.proto: PasskeyVerificationContext; notary proof fields cli_certificate_der = 7, passkey_context = 8, cli_intermediates_der = 9; pending_dispatch = 3; consensus enrollment fields consensus_member_app_ids = 22, consensus_quorum = 23.
    • eval.proto: EvaluationInvestigationMode (FRESH, PERSISTENT), EvaluationInvestigation, EvaluationRun.provider_observation_policy = 14 and model_provenance_policy = 15, formation_stack_set_digest = 15, tribunal_commands = 17.
    • operator.proto: observation_receipts on inference results and failures; GOVERNANCE_REJECTION_CODE_MCP_TARGET_MISMATCH = 25; RECEIPT_FAILURE_CODE_REHYDRATION_FAILED = 21.
  • API — POST /api/v1/operators/intents.
  • CLI
    • ztd auth approve --cli
    • ztd auth enroll consensus bind gains repeatable --member-app <id> and --quorum <n>
    • ztd eval campaigns create flags listed above
    • ztd test eval-loop [--posture ratify|notary] [--live-ollama-endpoint --live-model]: cross-process evaluation loop against real Gateway, Data Operator, and Inference Operator processes. The live mode reaches Ollama only through the governed Inference Operator.
  • Assignment audit database (internal/services/assignmentaudit/) — Holds one assignment's public feed records. Its signed proof segment authenticates it by SHA-256, and ztd public verify-assignment --db --segments --public-key verifies it offline (see Removed for the dropped vault key).
  • Constants — ErrScrubbingTokenUnresolved, ErrScrubbingKeyCollision, ErrMCPNativeToolHostMissing, ErrProcessNotFound, ErrUnsupportedPlatform, ErrConfigLeafCertTTLInvalid, ErrGovernanceCLISessionRequired, ErrEvaluationInvestigationInvalid, ErrMCPAuditReceiptListInvestigationReq, ErrEventRequestWireValue, ErrEventStreamTransport, ErrEventRegistryUnknownField.

Changed

  • Agent tools — The evaluation agent tool catalog is built from native tool definitions (agent_native_tools.go), not a JSON registry.
  • Campaign trace pin — Live and recovered campaign import accepts only agent-trace.v7. Older campaign traces are recorded as INVALID_EVIDENCE and are not graded. ztd public verify-agent-trace still verifies published v3–v5 traces and now also v6.
  • Judging — Agent traces are now scored by the evaluation judge. INVALID_EVIDENCE and PROVIDER_FAILED assignments are never judged. Each judge score must bind to a signed inference receipt whose result summary matches the scored result.
  • Witness collection — Provider-boundary observation and model provenance are optional at dispatch. An attempt with no connected observer or provenance Operator skips the window; run witness policies enforce coverage at verification.
  • Compliance catalog 2.1.0 — ZTD-DLP-BOUNDARY-001, ZTD-DLP-REHYDRATE-001, and ZTD-CM-STATE-001 are scoped to gateway/operator, and component_scope is a closed set.
  • Operator bootstrap — The bootstrap client requires TLS 1.3.
  • Tunnels — ztd gw tunnel create --ca-bundle sends the Gateway internal hostname as origin SNI. --origin-server-name without --ca-bundle is rejected.
  • Enrollment — App-name validation has one owner shared by the CLI and Gateway. A reserved or invalid name returns HTTP 400, not 500.
  • .env loading — The working-directory .env is loaded at the end of the root pre-run, after command gates pass. A command refused at pre-run, --help, and --version no longer read it.
  • Scrubbing boundary — Rehydration fails closed on unresolved tokens and key collisions. The FAILED receipt carries RECEIPT_FAILURE_CODE_REHYDRATION_FAILED.
  • Event registry validation — make constants-generate rejects unknown registry fields, non-governed request events whose wire value does not end in .requested, and stream events with an absent or non-sse transport. protocol/models/event_registry.schema.json now matches the generator's checks, and a test pins the two together.
  • Public mirror listeners — The private listener (:8081) serves ingest routes only. Anonymous read routes are served only on the public listener (:8082).

Removed

  • Legacy evaluation SUT — internal/services/evaluation/sut/, the /ztd-sut routes, SUT timing constants, and the ScoredTurnRequest/ScoredTurnEvent/InvestigationSeed protocol messages.
  • JSON agent-tool registry — protocol/constants/agenttools/.
  • provider_boundary_observation_ref = 32 — Reserved. Observations travel as observation_receipts.
  • Assignment audit vault encryption — --vault-key is removed from public assignment verification. The database carries only already-published public feed records and is authenticated by its signed proof segment.
  • PID-file fallback in deployment stop — Deploy-host stop no longer signals workers found only through operator.pid files; it stops workers through verified process discovery and fails closed.
  • @ztd/adapter SSE polling fallback (src/sse/polling.ts).
  • Sentinels — ErrEvaluationSystemLaneUnavailable and six ErrCLIL3* sentinels (ErrCLIL3ApprovalExpired, ErrCLIL3GetSuspendedTransactionFailed, ErrCLIL3InvalidFingerprintFormat, ErrCLIL3PublicKeyMissing, ErrCLIL3SuspendedStoreNotConfigured, ErrCLIL3TransactionNotApproved). CLI L3 verification now uses ErrGovernanceCLISessionRequired and the shared approval owner.

Fixed

  • Host deployment stop propagates signal and exit-wait failures and reports an Operator stopped only after its exit is observed. A missing Operator executable is rejected before an existing Operator is stopped or its log is truncated.
  • The public mirror rejects ingest when no token is configured.
  • @ztd/adapter resolves under plain Node ESM: it has a root barrel (src/index.ts), .js import specifiers, and exported error classes.
  • The evaluation explorer seed script fails closed on mirror snapshot, status, or push errors. The dev supervisor exits with Vite's exit code.
  • External MCP server help examples use ztd gw start.
  • The base init campaign queue declares 41 cells per model, matching the default suite, and lists only base inventory models (pdevine/qwen3.6:27b-mtp-q4_K_M and qwen3:30b removed).
  • The A2A_CALL event registry entry names its actual payload message, A2aCallRequested.
  • A new public-feed signing key no longer adopts proof state signed by a deleted key. Gateway startup fails closed with ErrPublicFeedOrphanedGeneration when public-proofs/, proof-mirror-sync.json, the outbox, or another feed-generation path exists without its signing key. Source transition also archives proof-mirror-sync.json.

Tests

Linux amd64 qualification of the release candidate:

  • make build — pass.
  • ./ztd test unit — pass (128 packages).
  • ./ztd test lint — pass, 0 issues.
  • ./ztd test integration — pass (129 packages).
  • ./ztd test eval-loop — pass, ratify and notary postures, scripted provider.
  • ./ztd test release-smoke — result: PASS, doctrine posture.
  • make console-lint console-test console-embed-check — pass, 49 tests.
  • make explorer-lint explorer-test explorer-embed-check — pass, 711 tests.
  • npm test --prefix ztd-adapter and npm run gen:contract-pack:check --prefix ztd-adapter — pass, 429 tests.
  • make ci-protocol, make swagger-generate, make constants-check doctrines-validate version-sync-check third-party-notices-check explorer-catalog-check — pass, no generated drift.
  • make website-test website-build — pass; 12 PowerShell checks skipped (no pwsh on the qualification host).

Not qualified in this release: native macOS and Windows runs, and live Tier 4 model evaluation. No model-quality or hosted-provider claims are made.

Documentation

  • docs/architecture/agents.md — Production Tribunal section.
  • docs/architecture/gateway.md — INV-GW-GOV-07 (Tribunal intent dispatch).
  • docs/architecture/evals.md — Investigation modes, formation campaigns, Tribunal commands, agent-trace.v7.
  • docs/architecture/consensus.md — K-of-N bindings.
  • protocol/docs/reference/api/ztd/tribunal/v1/ — Generated Tribunal reference.
  • CHANGELOG.md — 1.2.0 entry under ## v1.2.x.

Compliance Evidence

Operational evidence bundle generated by ./ztd compliance release-prepare:

  • Scope: gateway-operational-20261020 (window 2026-10-20T16:30:10.965Z to 2026-10-20T16:30:40.965Z, 3 receipts, 4 commitments, 2 source signer keys).
  • Public bundle: release-evidence/v1.2.0/report-bundle/manifest.json.
  • Offline verification: compliance_bundle@2.0.0 valid=true, failures=0, checksum root 0b9e7ace733c7fd4c1bf908af9e0d94ed637ee32d1cffe491c43d8e520b9130c.
  • Source revision: 3b99359cd00e19e4f6a0663cfdc8f42f7bdfaa5a.
  • Canonical projections: docs/release_notes/v1.2.x/v1.2.0-compliance-evidence.md and docs/release_notes/v1.2.x/v1.2.0-compliance-evidence.csv.

Deferred

  • All prior deferred items carry forward; see v1.1.3 Deferred.
  • External SUT support.
  • Formation repetitions beyond one per cell.
  • Execution of the Triage, Dash, and Codex roles through persona/.
  • Durable case memory, expanded verification, public chronology, and statistical analysis of longitudinal campaigns.