Repository navigation
v1.2.0
v1.2.0 (2026-04-27)
Documentation
Summary
Adds `docs/landscapes/vaultctl/` with a LANDSCAPE.md entry point and the first Architecture Decision Record (ADR): the rationale for using a pure-Python walker for schema inference rather than shelling out to `cue import`.
Why a Landscape Here
Per global Claude Code guideline, tool repos typically don't have a landscape — landscapes group cross-repo work for client engagements (Zeiss, Aldi, ...). vaultctl is treated as a deliberate exception:
- Tool is at v1.x. - Has accumulated several cross-cutting design choices that are scattered across PR descriptions and CLAUDE.md sections. - A central, ADR-style location makes them discoverable for future contributors and the user's future self.
The LANDSCAPE.md flags this convention deviation explicitly and notes the directory can collapse to a flat `docs/decisions/` later without losing the ADR files.
What's in the First ADR
`0001-schema-inference-via-python-walker.md` captures the design relationship behind the implementation choice in PR #41:
- Why not `cue import`: it produces concrete data in CUE syntax, not a schema. We'd still need a value-to-type substitution pass — which is the bulk of the work — so wrapping it around a subprocess hop adds complexity without removing any. - Why pure Python: self-contained, no extra subprocess, testable without external binaries, deterministic output suitable for git-tracking. - Trade-off: we don't get CUE's parser for free. Edge cases surface as Python errors. Acceptable because vault content comes from `pyyaml` upstream, which already raises before the walker runs. - Alternatives explicitly rejected: `cue import` + AST post-processing, native Python CUE binding (none production-ready), JSON Schema indirection.
Backfill Backlog (Not in This PR)
LANDSCAPE.md lists existing-but-undocumented decisions worth ADR'ing later:
- Python over Go/Rust (originated in our session discussion before #36). - CUE validation via `cue` binary subprocess (rationale in #34/PR #39). - Project-local `.vaultctl/` layout, no backward compat (#37/PR #38).
These remain in their PR bodies and CLAUDE.md sections for now — promoting them to ADRs is a P4 chore I can pick up incrementally.
Files
- `docs/landscapes/vaultctl/LANDSCAPE.md` (new) - `docs/landscapes/vaultctl/decisions/0001-schema-inference-via-python-walker.md` (new) - `CLAUDE.md` (Landschaften-line referencing the new landscape, per global pattern)
Test Plan
- Markdown renders cleanly (verified locally). - [x] Cross-references work (`#41`, `#34`, `#40`). - [ ] CI green.
Co-authored-by: Fred Thiele 8555720+f3rdy@users.noreply.github.com
Features
Summary
First phase of the schema lifecycle (#40): derive a closed CUE schema baseline from the current vault content. Users now have a clear starting point that covers exactly the keys and shapes in their vault — without writing CUE by hand.
New Command
```bash vaultctl schema infer # writes .vaultctl/vault.cue (5 keys) vaultctl schema infer --force # overwrite an existing baseline vaultctl schema infer --output some.cue ```
The generated file:
```cue // AUTO-GENERATED by `vaultctl schema infer` — do not hand-edit. // Project-specific constraints (regex, ranges, required fields) belong in // vault.constraints.cue alongside this file. CUE merges both at validation time.
package vaultctl
#VaultFile: { api_token: string db_creds: { password: string type: string username: string } hosts: [...string] port: int enabled: bool } ```
The schema is closed — adding a new vault key without re-running `infer` (or extending the schema by hand) makes `vaultctl validate` fail. That is the value: structural changes become deliberate, reviewable steps.
Why Pure-Python, Not `cue import`
`cue import data.yml` produces concrete CUE values (`username: "admin"`), not type constraints (`username: string`). To get a schema, we'd still have to walk the imported AST and substitute types for values — that's the bulk of the work, and doing it in Python keeps the implementation self-contained without a second cue invocation per key.
The walker maps Python types directly: `bool` → `bool` (checked before `int` because of Python's class hierarchy), `str` → `string`, nested `dict` → nested struct, homogeneous `list[T]` → `[...T]`, mixed list → `[...(int | string)]` style disjunction. `_previous` backup keys are excluded; field names with non-identifier characters get quoted.
Baseline + Constraints Pattern
The auto-generated file is machine-managed — re-running `infer` overwrites it. Hand-edited rules belong next to it:
``` .vaultctl/ ├── vault.cue # auto-generated, can be overwritten by infer └── vault.constraints.cue # hand-edited, never touched by tooling ```
CUE's package merging unifies both at validation time. To make this work, `_run_cue_vet` now optionally passes sibling `.cue` files alongside the override schema. Bundled schemas are still loaded standalone (no spurious merging from unrelated bundled files).
A round-trip test (`test_infer_merges_with_user_constraints_file`) exercises the full path: infer → add constraint regex → validate fails on the constraint.
Files
- `src/vaultctl/schema.py` — `infer_vault_schema()`, `_render_type()`, `_quote_field()`, `_siblings_of()`. `_run_cue_vet` and `validate_yaml_against_schema` now take `include_siblings`. - `src/vaultctl/cli.py` — new `@main.group("schema")` with `infer` subcommand. Click subcommand groups are how we'll add `sync` and (later) `extend` without polluting the top-level command surface. - `tests/test_schema.py` — 11 inference tests (pure Python) + 3 cue-required round-trip tests including the constraints-merge case. - `tests/test_cli.py` — 3 CLI tests covering default path, `--output`, `--force` overwrite protection. - `README.md` — Schema Validation section now covers `infer` and the baseline + constraints pattern. - `CLAUDE.md` — Architecture decision 7 documents the inference design (why no `cue import`).
What's NOT in This PR
The remaining phases of #40:
- `vaultctl schema sync` — non-interactive drift detection (compare current schema to what `infer` would produce, optional `--apply`). Builds on this PR's inference function. Smaller scope, separate PR. - Schema-aware `set` — interactive prompt when `set` introduces a structure not covered by the schema. UX work, separate PR.
This staging keeps each PR reviewable and lets validation be useful even without sync/extend.
Test Plan
- `uv run pytest` — 360 passed (was 344), 88% coverage. - [x] `uv run mypy src/vaultctl` strict — clean. - [x] `uv run pre-commit run --all-files` — green. - [x] Manual: `vaultctl schema infer` against a fresh vault, then `vaultctl validate` round-trip — both green; adding an unexpected key reproducibly fails validate. - [ ] CI green.
Refs #40.
Co-authored-by: Fred Thiele 8555720+f3rdy@users.noreply.github.com
Detailed Changes: v1.1.0...v1.2.0