Commit Graph

2 Commits

Author SHA1 Message Date
didericis c9825cf701 refactor(egress): write routes.yaml as actual YAML, not JSON-in-yml
test / unit (pull_request) Successful in 18s
test / integration (pull_request) Successful in 1m7s
`egress_render_routes` now emits hand-rolled YAML in the same style
as `pipelock_render_yaml`. The egress addon parses it via
`yaml_subset.parse_yaml_subset` — the same parser the manifest
loader + pipelock_apply use.

Why bother: routes.yaml is bind-mounted into the egress sidecar
AND surfaced to operators through `routes edit` (PRD 0019). JSON-
in-yml renders ugly in $EDITOR and signals "this is data" rather
than "this is config you can read at a glance". Real YAML reads
cleanly.

Mechanics:

  - `yaml_subset.py` drops its `claude_bottle.log` dependency.
    Errors now raise `YamlSubsetError` (a `ValueError`); the
    manifest loader + pipelock_apply catch it at the boundary
    and forward to `die` / `PipelockApplyError` so callers see
    the same behavior they did before.
  - `Dockerfile.egress` adds one COPY line for `yaml_subset.py`
    so it sits flat in `/app/` next to the addon. The addon
    uses an absolute-import-with-fallback shim so the same file
    works inside the container AND from the host's unit tests.
  - `egress_apply._merge_single_route` round-trips current
    routes.yaml through `parse_yaml_subset` + a new
    `_render_routes_payload` helper instead of `json.loads` +
    `json.dumps`.

End-to-end: rebuilt the egress image, ran `./cli.py start` to a
full bring-up, confirmed the addon's boot log shows `egress:
loaded 9 route(s)` — i.e., the YAML parses inside the container.
453 unit + 3 integration tests pass.
2026-05-26 02:17:42 -04:00
didericis 8c1e4d0220 feat(yaml_subset): hand-rolled YAML-subset + frontmatter parser
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 25s
claude_bottle/yaml_subset.py — stdlib-only, ~450 lines. Parses the
bounded shape claude-bottle's manifest files use:

  - Block mappings (top-level + nested via indentation)
  - Block lists (under a key, items can be scalars or block-style
    mappings whose keys align with the rest after the dash)
  - Inline lists `[a, b]` and inline dicts `{a: 1}` for one-level
    leaves
  - Quoted (single + double) and bare strings
  - Scalars: string, int, true/false, null/~

Rejects, each with a clear pointer at the line number:

  - `yes`/`no`/`on`/`off`/`Y`/`N`/`TRUE`/`FALSE` — only literal
    `true` / `false` are bools (the Norway problem stays solved by
    "quote your strings if they look like bools")
  - Bare strings that look like dates / octals / hex / floats
  - Anchors (`&`/`*`), aliases, YAML tags (`!!str`)
  - Multi-line block scalars (`|`, `>`)
  - Tabs in indentation
  - Nested flow style (only one level allowed)

Public API:

  parse_yaml_subset(text) -> dict[str, object]
    Top level must be a mapping.

  parse_frontmatter(text) -> (dict, body_text)
    Strips `---` delimiters, parses content as YAML subset, returns
    the verbatim body text after the closing fence.

46 unit tests covering every construct the real manifest files use
(the cred_proxy.routes structure, role-as-inline-list, nested
ExtraHosts dicts) plus every rejection case listed in PRD 0011.
2026-05-24 21:59:34 -04:00