Skip to content

feat: add interactive policy-file wizard (kosli create policy-file)#766

Open
dangrondahl wants to merge 54 commits intomainfrom
create_policy_file
Open

feat: add interactive policy-file wizard (kosli create policy-file)#766
dangrondahl wants to merge 54 commits intomainfrom
create_policy_file

Conversation

@dangrondahl
Copy link
Copy Markdown
Contributor

@dangrondahl dangrondahl commented Apr 8, 2026

Why

Writing Kosli environment policy files by hand is harder than it should be. The YAML format, exception syntax, expression operators (==, !=, matches(), exists(), not, and, or), and available context fields (flow.name, flow.tags.<key>, artifact.name, artifact.fingerprint) are easy to forget — especially when you're creating your first policy or haven't touched one in a while.

This is an attempt to lower the barrier to creating the first (and many subsequent) policy files by providing a guided, interactive experience.

Summary

  • Add kosli create policy-file command that launches an interactive TUI wizard to build environment policy YAML files conforming to the policy schema
  • Side-by-side layout: form on left, live YAML preview on right (using charmbracelet/huh + bubbletea + lipgloss)
  • Guided expression builder for policy conditions (${{ }} syntax) with support for:
    • Flow name matching
    • Flow tag comparison
    • Artifact name regex (matches())
    • Field existence checks (exists())
    • Custom comparisons with all operators
    • Logical combining (and, or, not)
    • Raw expression fallback
  • Optional API fetch (spinner on startup) to populate flow names and custom attestation types in wizard selects
  • File validation: .yaml/.yml extension required, no silent overwrites

New packages

Package Purpose
internal/policy Policy model structs, YAML generation, expression builder helpers
internal/policywizard Bubbletea model, form builders, state machine, styles

Dependencies added

  • charmbracelet/huh — form components
  • charmbracelet/bubbletea — TUI framework
  • charmbracelet/lipgloss — styling
  • charmbracelet/bubbles — spinner

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 8, 2026

Claude encountered an error —— View job


PR Review: feat: add interactive policy-file wizard (kosli create policy-file)

  • Phase 1: Static analysis (build, vet, mod tidy)
  • Phase 2: Read and review all changed files
  • Phase 3: Post inline comments and summary

Comment thread cmd/kosli/createPolicyFile.go Outdated
Comment thread cmd/kosli/createPolicyFile.go
Comment thread internal/policy/expression.go
Comment thread internal/policywizard/forms.go Outdated
Comment thread internal/policywizard/forms.go
Comment thread internal/policywizard/model.go
Comment thread internal/policywizard/forms.go
@dangrondahl
Copy link
Copy Markdown
Contributor Author

Addressed 5 of the 7 review comments in commits 8330ac02a7405272. Leaving the remaining two unaddressed:

  1. out io.Writer unused (createPolicyFile.go:45) — This is a codebase-wide convention; every newCreate*Cmd function accepts out io.Writer even when unused. Changing just this command would break the pattern. Not worth addressing in isolation.

  2. extractFormValues conflates keys via firstNonEmpty (forms.go:250) — Acknowledged, but each wizard step only ever uses one of "value", "filename", or "mode", so there's no ambiguity today. Adding per-step extraction would add complexity without a concrete benefit. If a future step uses multiple keys we can revisit.

Comment thread internal/policywizard/forms.go Outdated
Comment thread internal/policywizard/forms.go
Comment thread cmd/kosli/createPolicyFile.go
Comment thread internal/policywizard/forms.go
Comment thread internal/policy/expression.go
Comment thread internal/policywizard/forms.go
Comment thread internal/policy/expression.go
Comment thread internal/policywizard/model.go Outdated
Comment thread internal/policy/expression.go
Comment thread internal/policywizard/forms.go Outdated
Comment thread internal/policywizard/forms.go
Comment thread internal/policywizard/forms.go
Comment thread internal/policywizard/forms.go
Comment thread internal/policywizard/model.go
Comment thread internal/policywizard/forms.go
Comment thread cmd/kosli/createPolicyFile.go
@dangrondahl
Copy link
Copy Markdown
Contributor Author

Reviewed the latest 5 inline comments — all are informational observations, none blocking:

  1. fetchNameList silent empty result — Graceful degradation by design; the wizard falls back to free-text input.
  2. validateYAMLExtension allows empty — Intentional: empty = accept placeholder default ("policy.yaml"), handled in applyFormValues.
  3. form.WithWidth() not updated on resizeformWidth is a constant 45; only the preview panel is responsive to width changes.
  4. OutputFile not cleaned in modelfilepath.Clean is applied in runCreatePolicyFile before writing; the model value is only consumed there.
  5. validationErr persists on abort — Not a bug; abort exits the program immediately via tea.Quit.

No changes needed for these.

Comment thread internal/policywizard/forms.go
Comment thread internal/policy/expression.go Outdated
Comment thread internal/policy/expression.go
These operators need special syntax (list literals, function form) that
ComparisonExpr doesn't produce. They already have dedicated wizard paths
(flow name match, artifact name regex). Replace with numeric comparison
operators that work correctly with the quoted-value format.
The matches operator needs function syntax matches(ctx, "pattern")
rather than infix syntax. Extract MatchesExpr and use it when the
custom comparison operator is "matches". Also replace "in" with
numeric comparison operators that work with quoted values.
Add exists() as an operator choice in the custom comparison wizard path.
Unlike other operators, exists() takes only a context field and no value.
Generates ${{ exists(field) }} syntax per the policy reference docs.
After building a sub-expression, the wizard now asks whether to negate
it (not) and whether to combine it with another condition (and/or).
This allows building compound expressions like:
  ${{ flow.name == "prod" and not(matches(artifact.name, "^datadog:.*")) }}

New steps: stepExprNegate, stepExprCombineConfirm, stepExprCombineOp.
Sub-expressions accumulate in pendingExprs and are combined on finalize.
Change NegateExpr to produce "not expr" instead of "not(expr)"
to match the policy expression language syntax.
Use term.GetSize to read the actual terminal width, falling back to 80.
This prevents a brief layout glitch on narrow terminals before the first
WindowSizeMsg arrives from bubbletea.
Handle expressions like ${{expr}} (no inner spaces) in addition to
the standard ${{ expr }} format.
Catch invalid regex patterns (e.g. [unclosed) at input time rather
than letting them silently end up in the policy file.
The go.mod conflict resolution during rebase accidentally downgraded
kind from v0.31.0 to v0.11.1, breaking ExportKubeConfig API.
not flow.name == "prod" could be parsed as (not flow.name) == "prod".
Using not (flow.name == "prod") is unambiguous.
Without clearing, a failed matches regex validation would leave
validationErr set, causing an infinite loop when the user retried
with a valid regex or switched to a different operator.
Empty values produced expressions like ${{ flow.name == "" }} which
are technically valid but likely unintended. The exists operator
correctly allows empty values since it takes no argument.
Move the os.Stat existence check into validateYAMLExtension so users
get immediate feedback at stepSaveFile time, avoiding lost work if the
target file already exists.
Comment thread internal/policywizard/forms.go
Comment thread internal/policywizard/forms.go
Comment thread cmd/kosli/createPolicyFile.go
Comment thread internal/policywizard/model.go
Comment thread internal/policywizard/forms.go
Comment thread internal/policywizard/forms.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant