# p0151c: passive observation bus + preamble extension

- [Architecture] `ObservationBusProjector` as a stateless transient service, not a static helper. It is injected into `ApiSkillPromptStrategy` (and future strategies) the same way other prompt-side collaborators are. Stateless because the projection is a pure function over the input list; transient because that is the established lifetime for prompt-strategy collaborators.
- [Architecture] Projection lives at the prompt-strategy layer, not at the buffer-dispatcher layer. Two reasons. First, the bus consumer is the prompt itself — the projector's output is a substring of the prompt, not a piece of pipeline state. Second, putting it in the buffer dispatcher would couple the dispatcher to a particular consumer format. Strategies that consume the bus differently (or not at all) keep the dispatcher unchanged.
- [Architecture] Projection omits `rationale`, `suggestion`, and `details`. The bus is a pointer ("there is a finding at this anchor; here is the headline"), not the long-form report. Keeping it compact bounds token cost at pipeline scale — a bus of 50 observations stays under 5k tokens; including the long fields could push it past 50k.
- [Implementation] `EvidenceMode` is rendered to snake_case by hand in the projector (`analyzed_from_source` / `confirmed` / `potential`) rather than via `JsonNamingPolicy.SnakeCaseLower`. The hand-roll documents the wire convention at the place where it matters and avoids any future drift if the serializer policy changes. The enum has only three values; the switch is trivial.
- [Implementation] `start_line == 0` is treated as "no line known" and omitted from the projection. This matches the convention in `SkillObservation.DisplayLocation` and the `8c3537d fix: nullable start_line` commit — line 0 is the sentinel for unknown, not a real source location. Same with empty strings: they round-trip as omitted via `DefaultIgnoreCondition.WhenWritingNull` (after the explicit `NullIfEmpty` mapping).
- [Implementation] Prompt section is gated on the observations list being non-empty. First round in a pipeline sees no "Observations So Far" section at all; subsequent rounds see the accumulated list. This avoids cluttering the first prompt with an empty `[]` and a paragraph of instructions for a bus that doesn't exist yet.
- [Implementation] `SourceAnchoringPreamble` extended with one sentence about treating prior bus observations as hints, not gospel. Kept in the same single-paragraph format so the LLM's attention stays on the rule rather than on a list of subordinate guidelines.
- [TradeOff] Bus carries every prior observation across all rounds — not filtered by role, concern, or relevance. A skill in round 5 sees observations from rounds 1-4 regardless of whether they relate to its task. The bet is that token cost stays manageable (the projection is compact) and that filtering would risk hiding useful cross-role correlations. If/when a real pipeline exceeds the bus token budget, per-skill projection control is the natural extension — but per the umbrella's "no premature coupling" decision, not yet.
- [TradeOff] Strategies other than `ApiSkillPromptStrategy` (default + security-scan) do not yet consume the bus. This is deliberate: p0151c's spec restricts scope to the api-security-scan pipeline as the proving ground. Other pipelines benefit from p0151a/b's runtime contracts without needing the bus today. Extending coverage is a follow-up phase if usage justifies it.
