feat(secrets): S1 — secret:// core (parser, Provider iface, registry, batched Resolve) - #14
Merged
Conversation
… 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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Chunk S1 (M4) — secrets foundation
The base of M4, free of any concrete backend. Per spec 04.
ParseRefforsecret://<provider>/<path>#<key>?opt=val(fragment-before-query per spec) +IsRef.Refkeeps theRawstring — the substitution key and the Resolve result key. (The spec'smap[Ref]stringcan't compile —Ref.Optsis a map → Ref isn't a valid map key — so results are keyed byRaw.)Providerinterface with a single batchResolve(every backend is naturally batched);Factory+Registrymapping 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'sResolveexactly once over its unique refs (N refs → one call), keyed byRaw; a ref a provider omits is a hard error (a missing secret never passes as empty).Collectscans strings for distinct valid refs (the post-render pass); a typo'dsecret://surfaces as an error.Redactscrubs 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
Collect→Resolve.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 cigreen (race included).🤖 Generated with Claude Code