feat(profile): distribution profile system (Phase 4)#55
Open
TechAlchemistX wants to merge 2 commits intomainfrom
Open
feat(profile): distribution profile system (Phase 4)#55TechAlchemistX wants to merge 2 commits intomainfrom
TechAlchemistX wants to merge 2 commits intomainfrom
Conversation
…/ uninstall (Phase 4) The v0.4 headline feature. A "profile" is a TOML config fragment served over HTTPS (default base `https://secretenv.io/profiles`, overridable via `SECRETENV_PROFILE_URL` or per-invocation `--url`) that teams can publish once and every engineer installs with: secretenv profile install acme-defaults Profiles land in `<config_dir>/profiles/<name>.toml` and are auto-merged into the active `Config` on load. They fill gaps — never override: the user's own `config.toml` always wins where both define the same key. Among profiles, alphabetical filename order decides conflicts. This makes profiles a safe default-defining layer that can't silently change local behavior. New CLI surface: secretenv profile install <name> [--url <url>] secretenv profile list [--json] secretenv profile update [<name>] # no name → update all secretenv profile uninstall <name> Storage: profiles/<name>.toml ← the profile body profiles/<name>.meta.json ← source_url + ETag + installed_at `update` uses `If-None-Match: <stored-etag>` for conditional re-fetch. On 304 the local file is untouched (`UpdateOutcome::UpToDate`); on 200 the file + sidecar are replaced (`UpdateOutcome::Refreshed`). Fetching uses `curl` (subprocess) rather than a new HTTP client dep — consistent with every backend's CLI-spawn pattern. Fetched bodies are validated as `Config` fragments via `toml::from_str::<Config>` before being written, so a malformed profile never lands on disk. Config side: `Config::load` + `Config::load_from` now walk the `profiles/` directory adjacent to the active config path, merging every `*.toml` in alphabetical order. New public helpers `default_config_path_xdg()` and `profiles_dir_for(config_path)` give the CLI one source of truth for where profiles live relative to a given config file — no XDG logic duplicated in the CLI layer. A missing `profiles/` dir is a silent no-op; a missing `config.toml` with a populated `profiles/` still merges cleanly (load() path only). Security posture (v0.4): unsigned profiles over HTTPS. Signing + central index + `list --available` are deferred to v0.5+ — rationale + mitigations in `docs/profiles.md` §"Security considerations". Tests: - 5 new core tests exercise the merge semantics (gap-fill, user-wins conflict, alphabetical profile order, malformed-profile error surfaces filename, missing-dir silent). - 14 new CLI-crate unit tests cover name validation, URL resolution (explicit > env > default), install/update/uninstall/list against `file://` URLs, ETag parsing edge cases, RFC-3339 formatter. - 4 new integration tests (tests/cli.rs) drive the subprocess against a tempdir profile served via `file://`: install → list → doctor round-trip + JSON list + path-traversal rejection + uninstall errors. Workspace tests: 505 → 531 (+26). DNS side is user-provisioned. `secretenv.io/profiles/*` will be stood up via S3 + CloudFront once nameserver propagation completes; until then users can test via `--url file://` or `--url <any-HTTPS>`. Phase 4 DoD items deferred to a follow-up PR: - 3 sample profiles committed to secretenv-site (pending DNS). - Live end-to-end `install acme-defaults` via the canonical host. Both will close as part of the Phase 7 aggregate release smoke. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Local clippy passed with --all-features form; the CI form (`cargo clippy --all-targets --workspace`) surfaces `manual_map` pedantic lint on `SystemTime::duration_since` result. Same CI-vs-local discipline pattern as v0.4 Phase 2 (see feedback_git_workflow memory — always run CI's exact clippy form locally before pushing). Co-Authored-By: Claude Opus 4.7 (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.
Summary
The v0.4 headline feature. A "profile" is a TOML config fragment served over HTTPS that teams publish once and every engineer installs with one command:
secretenv profile install acme-defaults secretenv doctor # now sees every team backendProfiles land in
<config_dir>/profiles/<name>.tomland are auto-merged into the activeConfigon load. They fill gaps — never override: the user's ownconfig.tomlalways wins on conflicts.New CLI surface
profile install <name> [--url <url>]https://secretenv.io/profiles/<name>.toml(or--url), validate, persistprofile list [--json]profile update [<name>]If-None-Matchfor conditional fetch (no name = all)profile uninstall <name>.tomland.meta.jsonsidecarMerge model
config.tomlalways wins.HashMap::entry().or_insert()) fill gaps.profiles/dir = silent no-op.Storage
Fetching
Uses
curlsubprocess — consistent with the backends' CLI-spawn pattern, no new HTTP dep. Response body validates as aConfigfragment before touching disk.file://URLs are supported (for offline testing + local staging).Security posture (v0.4)
Unsigned profiles over HTTPS. Signing + central index +
list --availableare deferred to v0.5+. Full threat-model discussion indocs/profiles.md§"Security considerations".Tests
file://URLs, ETag parsing, RFC-3339 formatter).Out of scope (deferred)
secretenv-site/profiles/(needs DNS live).profile install acme-defaultsagainst the canonical host (same blocker).Dependencies
Hits on main first: PR #53 (secretenv.io rename) and PR #54 (Phase 5 harness). Rebase onto those once merged — the overlap is the
secretenv.iostring inDEFAULT_BASE_URLwhich this PR already writes as.io.Test plan
cargo fmt --all -- --checkcleancargo clippy --workspace --all-targets -- --deny warningscleancargo test --workspace→ 531 passfile://, verifydoctorauto-sees the profile backend, uninstall cleanlysecretenv profile list --jsonemits parseable JSONsecretenv profile install ../evilrejects the path-traversal name🤖 Generated with Claude Code