Skip to content

feat(secrets): S1 — secret:// core (parser, Provider iface, registry, batched Resolve) - #14

Merged
gustavobertoi merged 1 commit into
mainfrom
nightly/S1
Jun 29, 2026
Merged

feat(secrets): S1 — secret:// core (parser, Provider iface, registry, batched Resolve)#14
gustavobertoi merged 1 commit into
mainfrom
nightly/S1

Conversation

@gustavobertoi

Copy link
Copy Markdown
Contributor

Chunk S1 (M4) — secrets foundation

The base of M4, free of any concrete backend. Per spec 04.

  • ParseRef for secret://<provider>/<path>#<key>?opt=val (fragment-before-query per spec) + IsRef. Ref keeps the Raw string — the substitution key and the Resolve result key. (The spec's map[Ref]string can't compile — Ref.Opts is a map → Ref isn't a valid map key — so results are keyed by Raw.)
  • Provider interface with a single batch Resolve (every backend is naturally batched); Factory + Registry mapping kind→factory and name→config, building & caching each named provider lazily with clear errors for undeclared name / unknown kind.
  • Resolve — group refs per provider, call each provider's Resolve exactly once over its unique refs (N refs → one call), keyed by Raw; a ref a provider omits is a hard error (a missing secret never passes as empty).
  • Collect scans strings for distinct valid refs (the post-render pass); a typo'd secret:// surfaces as an error. Redact scrubs resolved values from debug output (short values left alone).

Concrete providers (SOPS+age S2, AWS S3, Infisical S4, keyring S5) register here; the saga secrets phase + valueless-env coupling (S6) consume CollectResolve.

Tests

Fake provider: parse happy/error, collect dedupe, lazy build+cache, one-call-per-provider batching (dups collapse), missing-ref error, provider-error propagation, redaction.

Gate

make ci green (race included).

🤖 Generated with Claude Code

… batched Resolve) (spec 04)

The foundation of M4, free of any concrete backend:

- ParseRef for `secret://<provider>/<path>#<key>?opt=val` (fragment-before-query
  per spec 04) + IsRef. Ref keeps the Raw string — the substitution key and the
  Resolve result key (Ref isn't a valid map key: Opts is a map, so the spec's
  map[Ref]string is keyed by Raw instead).
- Provider interface with a single BATCH Resolve (every backend is naturally
  batched); Factory + Registry mapping kind→factory and name→config, building &
  caching each named provider lazily with clear errors for undeclared
  name/unknown kind.
- Resolve: group refs per provider, call each provider's Resolve exactly ONCE
  over its unique refs (N refs → one call), keyed by Raw; a ref a provider omits
  is a hard error (a missing secret never passes as empty).
- Collect scans strings for distinct valid refs (the post-render pass feeds it to
  Resolve); a typo'd secret:// surfaces as an error. Redact scrubs resolved
  values from debug output (short values left alone).

Concrete providers (SOPS+age S2, AWS S3, Infisical S4, keyring S5) register
against this registry; the saga's secrets phase + the valueless-env coupling
(S6) consume Collect→Resolve.

Unit-tested with a fake provider: parse happy/error paths, collect dedupe, lazy
build+cache, one-call-per-provider batching (dups collapse), missing-ref error,
provider-error propagation, redaction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gustavobertoi
gustavobertoi merged commit bdba2ab into main Jun 29, 2026
17 of 18 checks passed
@gustavobertoi
gustavobertoi deleted the nightly/S1 branch June 29, 2026 14:02
gustavobertoi added a commit that referenced this pull request Jun 30, 2026
… (spec 25) (#79)

* docs(specs): add specs 22-25 + FEATURES/ROADMAP M8 beta-DX lane

Design the four newly requested features as spec-quality docs:
- 22 interactive `init` wizard (workspace + shared services)
- 23 interactive template & Dockerfile authoring (TUI)
- 24 `.env` ingestion -> secrets/vars (no more committed .env)
- 25 release automation + 0.x conventional-commit versioning

All TUIs are Bubble Tea v2 + the Charm plugin stack (bubbles/lipgloss/huh v2,
CGO-free) behind one shared internal/prompt theme, each with a --json/flag
fallback. Wire them into FEATURES (#14-17) and a new ROADMAP M8 beta-DX lane
that stays on the 0.x line (next release v0.2.0, never an automated 1.0.0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(release): conventional-commit versioning (svu) + fix self-update v-prefix bug

Implements spec 25's thin slice (the v0.2.0 gate):
- .svu.yaml (v0:true): keeps BETA on the 0.x line (a BREAKING change bumps the
  MINOR, never 1.0.0).
- .github/workflows/tag.yml: push-to-main -> `svu next --v0` (pinned v3.4.1) ->
  push a v* tag, gated on an owner-provisioned RELEASE_TOKEN (absent = kill
  switch) with a CI v0.* guard, which fires the unchanged release.yml/goreleaser.
- .github/workflows/pr-title.yml: pure-shell conventional-commit PR-title lint
  (squash-merge makes the PR title the commit svu reads).
- .goreleaser.yaml: grouped changelog (Features/Bug fixes/Performance) under
  `use: github`, and the load-bearing ldflags fix Version=v{{.Version}}.

The ldflags fix is a real regression, not cosmetic: goreleaser's {{.Version}} is
v-stripped ("0.2.0"), which golang.org/x/mod/semver rejects, so internal/
selfupdate.IsDevBuild() returns true for a real release -> the update notifier
and `self update` silently treat it as a dev build and never fire. Verified
against the real code: IsDevBuild("0.2.0")=true vs IsDevBuild("v0.2.0")=false;
goreleaser snapshot now stamps v0.1.1-dev-<sha>.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(release): single-workflow release, built-in token (no PAT)

Per owner decision, drop the two-workflow + RELEASE_TOKEN (PAT/App token) split
in favor of ONE release.yml that computes -> tags -> releases in a single job
using the built-in GITHUB_TOKEN. Rationale: contents:write lets a workflow push
a tag, but a tag pushed with GITHUB_TOKEN does NOT re-trigger another workflow
(GitHub's recursion guard) -- so the only way to avoid a separate token is to run
tag-compute and goreleaser in the same job, never depending on a re-trigger.

- Remove .github/workflows/tag.yml.
- Rewrite .github/workflows/release.yml: triggers on push:main (svu compute +
  0.x guard + tag, gated on the RELEASE_ENABLED repo *variable*) AND on push of a
  v* tag (a human hand-cut release) AND workflow_dispatch; goreleaser runs once,
  guarded by github.ref so the two paths never double-release.
- Kill-switch is now the RELEASE_ENABLED repo variable (no secret to rotate);
  default unset = compute + log, never release.
- Update spec 25 / FEATURES #17 / ROADMAP M8.0 to the no-token design.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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