Docs · Streaming Governance

Streaming governance in Aurora-Lens

This document covers the v1 full-buffer streaming governance model as implemented in aurora_lens/lens.py and aurora_lens/govern/stream_gate.py. State machine and constitutional invariants only.

Model

Full-buffer streaming governance is the v1 streaming model. Provider output streams internally into a private buffer; raw model tokens never become user-visible consequence before the final admissibility decision. The user receives either the admitted buffer or a governed continuation — never both, never a fragment of the suppressed candidate.

Streaming state machine

UPSTREAM_STREAMING
      │
      │  all provider chunks received (or fallback generate() used)
      ▼
BUFFER_COMPLETE
      │
      │  checker.check(full_text) + governor.evaluate(full_text, pef)
      ▼
VERIFYING
      │
      ├─── ADMIT (action ∈ {PASS, SOFT_CORRECT}) ────────────────────┐
      │                                                                │
      ▼                                                                ▼
SUPPRESSING_BUFFER                                          RELEASING_BUFFER
  bridge.intervene() produces governed continuation.        Buffered chunks emitted
  Buffer permanently suppressed.                            as "chunk" events,
  Governed continuation emitted as single                   preserving original cadence.
  "governed_chunk" event.
      │                                                                │
      └────────────────────────┬───────────────────────────────────────┘
                               │
                               ▼
                        METADATA_EMITTED  ("metadata" event)
                               │
                               ▼
                           COMPLETE

ABORTED  (provider stream raised before buffer was complete)

Event kinds emitted to the caller

KindWhenContent
chunkADMIT path only, one per buffered chunk(chunk_dict, content_delta) from private buffer
governed_chunkNON-ADMIT path, one event totalFresh chunk_dict wrapping the governed continuation
progressOptional (stream_emit_progress=True)ProgressSignal — lifecycle label only
metadataFinal event on every non-aborted pathAurora dict: governance, turn, stream fields

Governed chunk_dicts are constructed fresh from the governed text, not derived from raw provider chunks. Reusing provider chunks would leak structural information about the suppressed candidate via delta boundaries.

Provider abort vs. client disconnect

Provider abort during private buffering

The upstream generate_stream() raises (e.g. asyncio.CancelledError) before the buffer is complete. No content has reached the user. The finally block logs a forensic abort entry (stream_completed=False, stream_abort_reason="provider_abort"). The exception propagates to the caller.

Client disconnect after governed release

The upstream stream completed normally during buffering. Governance ran. Chunks were released. The caller broke out of the generator mid-delivery. From the perspective of the upstream stream, this is not an early close — the provider stream completed before any chunk was released. No abort entry is written; the governance decision and its forensic record are already committed.

Constitutional invariants

1. No content escapes before admissibility.

The generator yields no chunk, governed_chunk, or progress event containing substantive content before VERIFYING completes. Accumulation is entirely private.

2. The suppression decision is final.

Once SUPPRESSING_BUFFER is entered, no fragment of the buffer appears in any subsequent user-visible output — not in the governed continuation, not in a progress event, not in metadata.

3. Forensic logging is identical to the non-streaming path.

The same log_decision() call runs on both paths. Non-ADMIT entries include blocked_response_hash (sha256 of the suppressed buffer) and governed_response_hash (sha256 of the governed continuation). ADMIT entries carry no hash fields.

4. Conversation history records the governed response, not the candidate.

On a non-ADMIT path, self._history is appended with the governed continuation. The suppressed candidate is stored only in decision.original_response — forensic only, not returned to the caller.

5. Progress signals carry no substantive information.

ProgressSignal.status is constrained to {"streaming", "verifying", "releasing"} and validated at construction. These labels identify lifecycle phase only. They carry no flag names, no pathway identifiers, no content, and no field derived from user input or LLM output.

6. Streaming and non-streaming paths are outcome-equivalent.

process() and process_stream() reach the same admissibility decision by the same code path (checker → governor → bridge). The only difference is delivery mechanism.