Skip to content

Logging and speech to text refactoring - #18

Open
vinniefalco wants to merge 14 commits into
cppalliance:masterfrom
vinniefalco:master
Open

Logging and speech to text refactoring#18
vinniefalco wants to merge 14 commits into
cppalliance:masterfrom
vinniefalco:master

Conversation

@vinniefalco

Copy link
Copy Markdown
Member

In the commit log and plans

Move asset and gateway-config route tests into child modules so the production modules stay below their ratchet ceilings without changing behavior. Record the measured parent and test-module sizes in `module-ceilings.toml`.
The gateway command line has one job, to serve, so the `serve` subcommand and the positional config argument come out: the bare invocation serves with boot discovery, and `--config PATH` names an explicit config, winning over `PROMPTFORGE_GATEWAY_CONFIG`. `parse_args` drops its subcommand match and rejects any non-flag argument as a usage error; every repository-owned caller - the systemd unit, the installer, the Workshop launcher, the tray autostart entries, the release-test workflow, and the guides - moves to the new shape in the same change.

- `init_logging` now runs after the already-running handoff check, so `--help`, `--version`, and a second-instance handoff never rotate the running gateway's log; the handoff's browser-open failure reports through `eprintln!` because no subscriber is installed yet.
- `--version` is accepted at any position in the argument list, and a second `--config` is a usage error.
- On the handoff path in `relaunch.rs`, the connection-file resolution warning is dropped with no subscriber installed; the boot that follows logs its own failure once logging is live.

Plan: 2026-09-05-1-gateway-logging-cli
Move the log pipeline out of `crates/gateway/src/main.rs` into a new `gateway-logging` crate so the queue, rotation, sink, and worker lifecycle are owned and tested in one place. The crate exports `LogConfig`, `LogRuntime`, `LogWriter`, and the opaque `LogError`; `LogRuntime::start` rotates and opens `gateway.log`, spawns one worker thread, and `shutdown` closes admission, drains, flushes, and joins. `main.rs` keeps global subscriber installation, holds the returned `LogRuntime`, and shuts the logger down last so fatal error chains logged through `log_error_chain` reach the disk.

- Queue policy is fixed in `queue.rs`: `CAPACITY` of 8192 records, drain `BATCH` of 256, one deque per `LogPriority` under one mutex. A full queue evicts the oldest Debug, then Trace, then Info; Warn and Error records are never evicted, and a producer with no eligible record blocks on a condition variable.
- `LogEventWriter` buffers every `Write` call for one event and enqueues on `Drop`, moving the buffer through `String::from_utf8` and paying the lossy copy only for invalid UTF-8. It is public but `#[doc(hidden)]` because `MakeWriter::Writer` cannot name a private type.
- A failed write or flush on the file sink falls back to synchronous stderr, and a worker panic surfaces from `shutdown` as a `LogError` that `is_io` classifies separately from filesystem and spawn failures.
- Rotation keeps one previous run: an existing `gateway.log` renames to `gateway.log.1`, overwriting the older rotation.

Plan: 2026-09-05-1-gateway-logging-cli
A failed gateway run must be discoverable without config knowledge, and no log record may carry secret material. The gateway gains a `diagnostics` subcommand that prints a read-only JSON report of the state dir, config, logs, and connection file, the log rotation retains five previous runs, and every queued record crosses a redaction pass that masks bearer tokens, authorization and cookie header values, and `api_key` assignments.

- The log layout gets one owner: `LogConfig::log_path` and `LogConfig::retained_log_paths` name every path, so `diagnostics_json` enumerates the logs without starting a runtime and `open_log_file` rotates the same chain.
- Redaction sits at the one chokepoint every record crosses: `LogEventWriter::drop` masks the formatted line with `redact_line` before the record enters the queue.
- `is_running` in `shared-sidecar` is read-only: a stale or corrupt connection file reads as not-running and stays on disk for the next launch to clean.
- `discover_in(explicit, gather)` splits the report's config discovery into a testable inner in the `resolve_in` pattern; unit tests pin the explicit, discovered, profile-fallback, and gather-failure branches, and both `diagnostics` integration tests assert `config.path` and `config.exists`.
- `diagnostics` runs before the handoff check and before logging starts; it never serves, rotates a log, parses a config, or mutates the state directory, and `parse_diagnostics_args` accepts only `--config PATH`.
- The generated config carries `# Diagnostics: promptforge-gateway diagnostics` as a comment, so the file stays parseable.
- New tests pin the sink's stderr fallback on rejected writes and flushes, saturation that never evicts or duplicates Warn or Error records, and a shutdown that writes every record in enqueue order before the join returns.
- `Sink::Null` and `Sink::is_stderr` are `#[cfg(test)]` seams for the fallback and latency tests.
- `production_logging_stays_within_latency_budget` is `#[ignore]`d; it runs only through `cargo test -p gateway-logging --release -- --ignored`.

Plan: 2026-09-05-1-gateway-logging-cli
The public `serve` docs linked the private `GRACEFUL_DRAIN_TIMEOUT` and `WORKER_JOIN_TIMEOUT` constants, which `RUSTDOCFLAGS="-D warnings" cargo doc` rejects as private intra-doc links. The constants are now plain backticked names. The break was introduced in 7f24bb0 and predates the logging work.
The logging contract now lives in the documentation, and the dependency boundary has a test that enforces it. A new integration test `the_manifest_declares_only_the_tracing_dependencies` reads the crate's own `Cargo.toml` and fails when any dependency other than `tracing` and `tracing-subscriber` appears. The `gateway-logging` `AGENTS.md`, the gateway `README.md`, and both gateway guides now describe the `gateway.log` rotation, the five-run retention, the redaction pass, and the `promptforge-gateway diagnostics` report.

- The boundary test rejects build, dev, and target-specific dependency tables in addition to extra `[dependencies]` entries, so the allowlist covers every way a crate can enter the build. It parses the manifest line by line and adds no TOML parser dependency.
- `AGENTS.md` records that `LogEventWriter` is public but `#[doc(hidden)]` because `MakeWriter::Writer` cannot name a private type, and that the test seams `Sink::Null` and `Sink::is_stderr` exist only under `cfg(test)`.

Plan: 2026-09-05-1-gateway-logging-cli
Plan: 2026-09-05-1-gateway-logging-cli
Characterize current batch routing and realtime transcription before the speech subsystem changes. Separate physical-model routing checks from legacy socket cases, and add deterministic coverage for stream policy, generation, origin, ordering, shutdown, and final-model authority.

- `crates/gateway-stt/tests/it/main.rs` now separates batch model selection from legacy socket characterization.
- `fixture_runtime_with_models` starts caller-selected interim and final fixture models on a dedicated thread, while `TestServer::shutdown` stops the server before blocking runtime shutdown.
- `batch_selects_each_loaded_physical_model_by_name` changes one vocabulary token to verify direct routing to each loaded physical model.
- `final_model_segments_and_tail_are_authoritative_at_stop` verifies that interim text stays provisional and that the final worker produces committed segments and the remaining tail.
- `crates/gateway-stt/tests/it/batch.rs` keeps its physical-model case ignored because it requires `tests/fixtures/`. Other native speech cases remain ignored for the same reason.

Design: extends oversized-unit @ crates/gateway-stt/tests/common/mod.rs
Design: new flag-parameter @ crates/gateway-stt/tests/common/mod.rs::fixture_runtime deps: bool
Design: replaces flag-parameter @ crates/gateway-stt/tests/common/mod.rs::fixture_server deps: bool was: crates/gateway-stt/tests/it/stt.rs::fixture_server
Design: new stringly-typed @ crates/gateway-stt/tests/common/mod.rs::multipart_body deps: &[u8],&str boundary: wire
Design: new stringly-typed @ crates/gateway-stt/tests/common/mod.rs::transcribe_batch deps: &[f32],&str,SttState boundary: wire
Design: replaces oversized-unit @ crates/gateway-stt/tests/it/legacy_stream.rs was: crates/gateway-stt/tests/it/stt.rs
Design: new pure-function @ crates/gateway-stt/tests/it/legacy_stream.rs::transcript_words deps: &str
Design: new pure-function @ crates/gateway-stt/tests/it/legacy_stream.rs::distinguishing_word deps: &str,&str
Deferred: physical-model characterizations remain ignored without whisper fixtures
Plan: 2026-09-05-2-generic-realtime-stt
Add an ignored native integration target that loads the packaged runtime and exact model fixture. It fixes interim, final, transcript-conditioning, glossary-prompt, silence-gating, and cleanup behavior so the upcoming engine split can be checked against one baseline.

- `packaged_runtime_preserves_native_transcription_contract` copies the exact tiny model into a temporary directory, loads the packaged library, and configures the same model for interim and final decoding.
- `JFK_TRANSCRIPT` anchors assertions for the full interim transcription, unprompted and transcript-conditioned final output, glossary bias, silence gating, and conditioning divergence.
- `std::fs::remove_file` verifies that dropping both engines releases the copied model.
- `#[ignore = "requires whisper test fixtures (tests/fixtures/)"]` keeps native characterization outside default test runs because it requires packaged runtime fixtures.

Design: new oversized-unit @ crates/gateway-transcribe/tests/native_whisper.rs::packaged_runtime_preserves_native_transcription_contract
Plan: 2026-09-05-2-generic-realtime-stt
Add canonical client, server, session, error, and sequence fixtures for realtime transcription. Validate one shared fixture set in Rust and the Workshop UI to pin strict fields, event ordering, capacity errors, item isolation, and hypothesis semantics before the subsystem changes.

- `realtime-wire-fixtures.mjs` consumes the Gateway-owned fixtures directly, which makes Rust and Workshop share one canonical corpus.
- `canonical_realtime_events_are_complete_strict_and_round_trip` enforces exact case sets, strict field sets, identifier separation, session defaults, and hypothesis composition.
- `canonical_realtime_sequences_cover_valid_and_invalid_contract_paths` validates event order, error correlation, minimum audio, and recovery metadata across all declared sequences.
- `crates/gateway-stt/tests/it/realtime_fixtures.rs` does not call production realtime parsers or session handlers, so these tests pin fixture consistency rather than implementation conformance.

Design: new oversized-unit @ crates/gateway-stt/tests/it/realtime_fixtures.rs::assert_server_event_fields deps: &Value,&str
Design: new oversized-unit @ crates/gateway-stt/tests/it/realtime_fixtures.rs::canonical_realtime_events_are_complete_strict_and_round_trip
Design: new oversized-unit @ crates/gateway-stt/tests/it/realtime_fixtures.rs::canonical_realtime_sequences_cover_valid_and_invalid_contract_paths
Violates: A2 - not determinable from diff
Violates: A96 - not determinable from diff
Deferred: batch transcription characterization is absent
Deferred: native two-model characterization is absent
Plan: 2026-09-05-2-generic-realtime-stt
Give the backend-neutral engine its intended role-specific identity. Update workspace metadata, runtime references, tests, fixture exclusions, and documentation while preserving the engine implementation.

- `crates/gateway-stt-engine` changes the package, path, and Rust import identity.
- `crates/gateway-stt-engine/src/engine.rs` and the other engine source files move with 100 percent similarity.
- `crates/gateway-stt-engine/tests/native_whisper.rs` changes only its import path. The commit adds no test assertions or compatibility crate.

Design: new shotgun-surgery @ crates/gateway-stt-engine/Cargo.toml
Violates: A2 - not determinable from diff
Violates: A96 - not determinable from diff
Plan: 2026-09-05-2-generic-realtime-stt
Move per-take speech state out of decode workers so each decode job is independent and every take owns its lifecycle. Carry immutable guidance and finalized history with each job, aggregate segment results in one ordered pipeline, and preserve interim fallback after failures.

- `Take` consolidates guidance, finalized history, segmentation, local agreement, transcript aggregation, completion, and failure behind one per-take state object.
- `FinalJob` carries all decode inputs and the reply channel, so final-model workers retain no take identity or transcript between jobs.
- `Segmenter` moves from the engine surface to the gateway speech surface with take orchestration.
- `run_final_pipeline` serializes closed segments and the closing tail, records only successful sample boundaries, and stops decode work after the first failure.
- `next_interim` promotes token prefixes confirmed by two hypotheses and keeps committed text append-only.
- `TestServer` now bounds fixture server and runtime cleanup at 30 seconds.
- `guidance` has no end-to-end assertion from runtime activation through both batch and streaming decode paths.

Design: new surface-growth @ crates/gateway-stt-engine/src/engine.rs::SttEngine::transcribe boundary: pub
Design: new surface-growth @ crates/gateway-stt-engine/src/engine.rs::SttEngine::transcribe_final boundary: pub
Design: new shared-parameter-cluster @ crates/gateway-stt-engine/src/engine.rs::SttEngine::transcribe_final boundary: pub
Design: new shared-parameter-cluster @ crates/gateway-stt-engine/src/final_pass.rs::FinalTranscriber::transcribe
Design: new surface-growth @ crates/gateway-stt/src/lib.rs::Segmenter boundary: pub
Design: new value-object @ crates/gateway-stt/src/take.rs::AgreementSnapshot
Design: new parameter-object @ crates/gateway-stt/src/take.rs::Take
Design: new shared-mutable-state @ crates/gateway-stt/src/take.rs::TakeState
Design: new message-passing @ crates/gateway-stt/src/take.rs::FinalPipeline
Design: new pure-function @ crates/gateway-stt/src/take.rs::matching_token_prefix_end deps: &str,&str
Design: new pure-function @ crates/gateway-stt/src/take.rs::token_spans deps: &str
Design: new pure-function @ crates/gateway-stt/src/take.rs::after_token_prefix deps: &str,usize
Design: new oversized-unit @ crates/gateway-stt/src/take.rs
Design: extends oversized-unit @ crates/gateway-stt/tests/common/mod.rs
Design: extends oversized-unit @ crates/gateway-stt/tests/it/legacy_stream.rs
Violates: A2 - not determinable from diff
Violates: A96 - not determinable from diff
Deferred: configured guidance propagation lacks an end-to-end assertion
Plan: 2026-09-05-2-generic-realtime-stt
Make speech decoding backend-neutral while keeping Whisper construction, prompting, progress, and error translation in a safe adapter. Inject model factories into dedicated workers so model creation and decoding stay on their owning threads. Preserve batch and native transcription behavior through relocated and expanded tests.

- `Decoder` and `ModelFactory` establish backend strategy contracts, while `WhisperModelFactory` contains safe model construction and decode policy.
- `SttEngine::new` constructs each decoder on its owning worker and returns initialization errors before activation.
- `native_whisper.rs` relocates native characterization and adds isolation, optional-final, and load-progress checks. These fixture-dependent tests remain ignored.
- `std::sync::mpsc::channel` leaves both worker job queues unbounded.
- `require_fixture` duplicates native fixture loading across unit and integration test support.

Design: new strategy @ crates/gateway-stt-engine/src/decoder.rs::Decoder boundary: pub
Design: new surface-growth @ crates/gateway-stt-engine/src/decoder.rs::Decoder boundary: pub
Design: new strategy @ crates/gateway-stt-engine/src/decoder.rs::ModelFactory boundary: pub
Design: new surface-growth @ crates/gateway-stt-engine/src/decoder.rs::ModelFactory boundary: pub
Design: new facade @ crates/gateway-stt-backend-whisper/src/lib.rs boundary: pub
Design: new constructor-injection @ crates/gateway-stt-engine/src/engine.rs::SttEngine::new
Design: new flag-parameter @ crates/gateway-stt-backend-whisper/src/model.rs::WhisperDecoder::load
Design: new flag-parameter @ crates/gateway-stt-engine/src/worker.rs::worker_loop deps: &dyn ModelFactory,&std::sync::mpsc::Receiver<Job>,&std::sync::mpsc::SyncSender<Result<bool, TranscribeError>>,bool
Design: replaces shared-mutable-state @ crates/gateway-stt/src/runtime.rs::SttSlot was: crates/gateway-stt-engine/src/slot.rs::SttSlot
Design: new global-state @ crates/gateway-stt-backend-whisper/src/prompt.rs::NATIVE_TEST
Design: new global-state @ crates/gateway-stt-backend-whisper/tests/native_whisper.rs::NATIVE_TEST
Design: new clone-block @ crates/gateway-stt/src/test_fixtures.rs
Design: new clone-block @ crates/gateway-stt/tests/common/mod.rs
Violates: A2 - crates/gateway-stt/src/runtime.rs is not determinable from diff
Pending: N9 - compounds
Deferred: model worker queues remain unbounded
Deferred: native Whisper characterization remains ignored behind external fixtures
Plan: 2026-09-05-2-generic-realtime-stt
Add mandatory checks for workspace edges, module cycles, public exports, source size, migration targets, and unsafe isolation. Split compiler-resolved checks from strict policy checks and run both in the normal continuous integration path.

- `parseCargoModulesDot` is a 110-line parser that collapses item edges into module edges and rejects malformed graph output.
- `module-ceilings.toml` files set strict source and public-root ceilings and name each planned migration target.
- `architecture` runs with pinned tool versions in the normal continuous integration job.

Design: new global-state @ crates/gateway-stt/tests/it/architecture.rs::workspace_metadata
Design: new oversized-unit @ tools/check-stt-architecture.mjs::parseCargoModulesDot deps: output
Violates: A2 - not determinable from diff
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant