# PRD 0037: Pipelock YAML Render Contract

- **Status:** Active
- **Author:** didericis-codex
- **Created:** 2026-06-02
- **Issue:** #130

## Summary

Lock down the contract between `pipelock_build_config` and
`pipelock_render_yaml` so hand-rendered pipelock YAML stays aligned with the
structured config bot-bottle builds. Keep the stdlib-only renderer, but add
shape validation and semantic tests for every supported section.

## Problem

`bot_bottle/pipelock.py` builds a structured dict and then renders a fixed YAML
shape by hand. This avoids a runtime YAML dependency, but it also means the
renderer directly indexes expected keys. If `pipelock_build_config` adds,
renames, or conditionalizes a section, rendering can fail at runtime or emit
YAML that no longer matches the config semantics.

Existing tests assert important rendered fragments, but they do not fully lock
the build/render contract or optional-section combinations. A mismatch here can
weaken DLP enforcement or break bottle launch after a future pipelock policy
change.

## Goals / Success Criteria

- Keep the renderer stdlib-only.
- Define the supported pipelock config shape in one place.
- Fail clearly when `pipelock_render_yaml` receives an unsupported or malformed
  config shape.
- Add tests covering all supported sections:
  - base allowlist and forward proxy.
  - seed phrase detection toggle.
  - DLP and request-body/header scanning.
  - TLS interception and passthrough domains.
  - SSRF IP allowlist.
- Add semantic tests that compare structured config values to rendered YAML
  output without relying only on brittle substring assertions.
- Preserve current rendered YAML for existing configs unless a clearer failure
  path requires an error message change.

## Non-goals

- No PyYAML or other runtime dependency.
- No change to pipelock policy defaults.
- No change to egress-to-pipelock topology.
- No change to pipelock image version or config schema beyond validation of the
  shape bot-bottle already emits.

## Scope

In scope:

- `bot_bottle/pipelock.py` render helpers and validation.
- Unit tests in `tests/unit/test_pipelock_yaml.py` and related focused
  pipelock tests.
- Small helper functions for typed access to config sections, if useful.

Out of scope:

- Launch/backend changes.
- Integration tests that start a real pipelock container.
- Changing the manifest schema for route-level pipelock policy.

## Design

Treat `pipelock_render_yaml` as a serializer for the narrow config shape
produced by `pipelock_build_config`, not as a generic YAML renderer. Before
rendering a section, validate that required keys exist with the expected
primitive/list/dict types. Missing or unsupported shapes should raise a clear
`ValueError` naming the section and key.

The supported top-level shape is `version`, `mode`, `enforce`,
`api_allowlist`, `seed_phrase_detection`, `forward_proxy`, `dlp`,
`request_body_scanning`, `tls_interception`, and `ssrf`. Required sections are
validated before rendering; optional sections keep the current omission
behavior. `request_body_scanning.scan_headers`,
`request_body_scanning.header_mode`, and
`tls_interception.passthrough_domains` remain optional for compatibility with
parsed running configs that only contain the older rendered subset.

Tests should cover both normal output and failure cases. Because the project is
stdlib-only, semantic tests can use a small purpose-built parser for the exact
rendered shape or compare rendered lines to values from the structured config
through helper assertions. The goal is to detect drift between config dict and
YAML without adding a general YAML dependency.

Optional sections should be exercised in combinations:

- no TLS and no SSRF.
- TLS enabled with empty and non-empty passthrough domains.
- SSRF enabled with one or more IP/CIDR entries.
- all optional sections enabled together.

## Testing Strategy

- Extend `tests/unit/test_pipelock_yaml.py` with semantic assertions tying each
  rendered section back to the config dict.
- Add malformed-config tests for missing required keys and wrong section types.
- Keep existing render fragment tests where they protect exact pipelock syntax.

Run:

- `python3 -m unittest tests.unit.test_pipelock_yaml`
- `python3 -m unittest tests.unit.test_pipelock_allowlist`
- `python3 -m unittest discover -s tests/unit`

## Open Questions

None.
