Skip to content

v1.2.0

Choose a tag to compare

@f3rdy f3rdy released this 27 Apr 21:32
· 17 commits to master since this release

v1.2.0 (2026-04-27)

Documentation

  • landscape: Introduce vaultctl landscape with first ADR (#43, f0728c0)

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)

Closes #42. Refs #40, #41.

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

  • schema: Vaultctl schema infer for baseline generation (#40 phase 1) (#41, 737f58a)

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