Merge pull request 'v0.6.0.1 feat: secrets which prints manifest version (EGB-700)' (#6) from brian/egb-700-which-manifest-version into main
This commit is contained in:
commit
633d19e5f7
6 changed files with 25 additions and 5 deletions
|
|
@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to a four-digit MAJOR.MINOR.PATCH.MICRO version scheme.
|
and this project adheres to a four-digit MAJOR.MINOR.PATCH.MICRO version scheme.
|
||||||
|
|
||||||
|
## [0.6.0.1] - 2026-06-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`secrets which` now prints the manifest version (EGB-700)** — the manifest
|
||||||
|
header line shows `version N` alongside the store format, so a single
|
||||||
|
`secrets which` tells you both the on-disk store format and the `.secrets.json`
|
||||||
|
schema version at a glance.
|
||||||
|
|
||||||
## [0.6.0.0] - 2026-06-07
|
## [0.6.0.0] - 2026-06-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ Single bash script (`secrets`) with subcommands: init, push, pull, list, rm, rek
|
||||||
- Storage: Private git repo at `~/.secrets/`
|
- Storage: Private git repo at `~/.secrets/`
|
||||||
- Convention: Tracks `.env`, `.env.*`, and `.dev.vars` (not `.envrc`, `.environment-*`)
|
- Convention: Tracks `.env`, `.env.*`, and `.dev.vars` (not `.envrc`, `.environment-*`)
|
||||||
- Manifest (EGB-677 stage 1): committed `.secrets.json` is the source of truth for what syncs — `dotenv[]` (project-relative, nested ok, `@` allowed; rail rejects `..`/absolute/symlink) + `external[]` (`properties`/`file`). Push discovery auto-adds (gated by committed `options.autoAdd`, default ON; `--frozen`/`--dry-run` overrides), bootstraps the manifest on first push (written only after ≥1 blob encrypts), and absorbs a legacy `.secrets-files` (gradle-properties → `properties`; on pull the legacy file is superseded with a warning). Store layout: nested dotenv entries land at `<project>/<relpath>.age` (relpath preserved — the store self-describes where a file restores). jq is a hard dep only when a manifest exists/is written; manifest-less projects run jq-free (manifest features skipped with a notice). `check_cmd` prints platform-aware install hints.
|
- Manifest (EGB-677 stage 1): committed `.secrets.json` is the source of truth for what syncs — `dotenv[]` (project-relative, nested ok, `@` allowed; rail rejects `..`/absolute/symlink) + `external[]` (`properties`/`file`). Push discovery auto-adds (gated by committed `options.autoAdd`, default ON; `--frozen`/`--dry-run` overrides), bootstraps the manifest on first push (written only after ≥1 blob encrypts), and absorbs a legacy `.secrets-files` (gradle-properties → `properties`; on pull the legacy file is superseded with a warning). Store layout: nested dotenv entries land at `<project>/<relpath>.age` (relpath preserved — the store self-describes where a file restores). jq is a hard dep only when a manifest exists/is written; manifest-less projects run jq-free (manifest features skipped with a notice). `check_cmd` prints platform-aware install hints.
|
||||||
- Store format (EGB-677 stage 2 / EGB-703): the store is self-describing via a committed one-line `$SECRETS_DIR/.secrets-format` file (`2`). Absence ⇒ v1 (every store predating EGB-703). v2's only on-disk change vs v1 is the external `properties` blob suffix: `.gradle-properties.age` → `.properties.age` (matching the manifest `type`); dotenv and `file` blobs are unchanged. `_store_format()` reads the marker; `_external_blob_suffix(type)` is the single source of truth for the suffix (push/pull/verify all route through it, so v1 and v2 stores never disagree on where a blob lives). `init` stamps a fresh store v2 (born-v2). `secrets which` prints `format: vN`. **Migration is copy-forward and non-destructive:** `secrets migrate --dry-run` (per project, reports old→new, writes nothing) → `secrets migrate` (per project, writes `.properties.age` twins beside the v1 blobs; needs the project manifest to know which externals are `properties`; idempotent) → `secrets migrate --finalize` (store-wide; the ONLY destructive step — gates on `verify --all` green + every v1 blob having a v2 twin, cuts a `pre-v2-migrate-<sha>` recovery tag, stamps the marker, then drops v1 blobs; refuses without `--yes`/operator confirmation since a lagging v1 client against a finalized store stops seeing `properties` externals until it upgrades). The deliberate flatten-to-basename naming the EGB-677 CEO plan sketched was dropped as lossy (it discards the restore relpath that makes the store self-describing) — see the EGB-703 eureka.
|
- Store format (EGB-677 stage 2 / EGB-703): the store is self-describing via a committed one-line `$SECRETS_DIR/.secrets-format` file (`2`). Absence ⇒ v1 (every store predating EGB-703). v2's only on-disk change vs v1 is the external `properties` blob suffix: `.gradle-properties.age` → `.properties.age` (matching the manifest `type`); dotenv and `file` blobs are unchanged. `_store_format()` reads the marker; `_external_blob_suffix(type)` is the single source of truth for the suffix (push/pull/verify all route through it, so v1 and v2 stores never disagree on where a blob lives). `init` stamps a fresh store v2 (born-v2). `secrets which` prints the store-format line `format: vN`, and (EGB-700) when a `.secrets.json` is present the manifest header line also carries its schema version (`manifest (.secrets.json at <path>, version N):`). **Migration is copy-forward and non-destructive:** `secrets migrate --dry-run` (per project, reports old→new, writes nothing) → `secrets migrate` (per project, writes `.properties.age` twins beside the v1 blobs; needs the project manifest to know which externals are `properties`; idempotent) → `secrets migrate --finalize` (store-wide; the ONLY destructive step — gates on `verify --all` green + every v1 blob having a v2 twin, cuts a `pre-v2-migrate-<sha>` recovery tag, stamps the marker, then drops v1 blobs; refuses without `--yes`/operator confirmation since a lagging v1 client against a finalized store stops seeing `properties` externals until it upgrades). The deliberate flatten-to-basename naming the EGB-677 CEO plan sketched was dropped as lossy (it discards the restore relpath that makes the store self-describing) — see the EGB-703 eureka.
|
||||||
- Verify (EGB-698): `secrets verify` is a read-only integrity check. Default mode (current project) cross-checks `$PWD/.secrets.json` against `$SECRETS_DIR/<project>/` both ways (declared-but-missing blobs + orphaned blobs) and decrypt-tests every blob (dotenv + external) by streaming plaintext to `/dev/null` (never written to disk). `secrets verify --all` decrypt-tests every blob in every project (integrity only — the store carries no manifests, so consistency can't be checked store-wide). Both recurse the whole project tree (`find -type f`, same as rekey/list). Exits non-zero on any finding so it can gate the stage-2 `migrate --finalize` and CI. The store deliberately holds no manifest — `.secrets.json` is committed in each project's own repo and read from `$PWD`.
|
- Verify (EGB-698): `secrets verify` is a read-only integrity check. Default mode (current project) cross-checks `$PWD/.secrets.json` against `$SECRETS_DIR/<project>/` both ways (declared-but-missing blobs + orphaned blobs) and decrypt-tests every blob (dotenv + external) by streaming plaintext to `/dev/null` (never written to disk). `secrets verify --all` decrypt-tests every blob in every project (integrity only — the store carries no manifests, so consistency can't be checked store-wide). Both recurse the whole project tree (`find -type f`, same as rekey/list). Exits non-zero on any finding so it can gate the stage-2 `migrate --finalize` and CI. The store deliberately holds no manifest — `.secrets.json` is committed in each project's own repo and read from `$PWD`.
|
||||||
- External files: `.secrets-files` manifest tracks designated keys from files outside the project (e.g. `~/.gradle/gradle.properties`, merged not overwritten — EGB-531) and whole binary files (type `file`, e.g. an Android upload keystore — EGB-652); see below
|
- External files: `.secrets-files` manifest tracks designated keys from files outside the project (e.g. `~/.gradle/gradle.properties`, merged not overwritten — EGB-531) and whole binary files (type `file`, e.g. an Android upload keystore — EGB-652); see below
|
||||||
- Workspaces: `--workspaces` flag reads `package.json` workspaces, requires `jq`
|
- Workspaces: `--workspaces` flag reads `package.json` workspaces, requires `jq`
|
||||||
|
|
@ -76,7 +76,7 @@ secrets # CLI script (~2000 lines bash)
|
||||||
hooks/pre-commit # Pre-commit hook template
|
hooks/pre-commit # Pre-commit hook template
|
||||||
test/
|
test/
|
||||||
secrets.bats # bats-core test suite (133 tests)
|
secrets.bats # bats-core test suite (133 tests)
|
||||||
manifest.bats # EGB-677 .secrets.json manifest tests (77 tests)
|
manifest.bats # EGB-677 .secrets.json manifest tests (78 tests)
|
||||||
migrate.bats # EGB-703 store-format-v2 migration tests (26 tests)
|
migrate.bats # EGB-703 store-format-v2 migration tests (26 tests)
|
||||||
test_helper.bash # Shared setup/teardown
|
test_helper.bash # Shared setup/teardown
|
||||||
README.md # User-facing documentation
|
README.md # User-facing documentation
|
||||||
|
|
|
||||||
|
|
@ -500,7 +500,7 @@ For complete rotation with no historical exposure, create a fresh `~/.secrets/`
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run the test suite (236 tests across three files)
|
# Run the test suite (237 tests across three files)
|
||||||
brew install bats-core
|
brew install bats-core
|
||||||
bats test/
|
bats test/
|
||||||
|
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.6.0.0
|
0.6.0.1
|
||||||
|
|
|
||||||
5
secrets
5
secrets
|
|
@ -1918,7 +1918,10 @@ cmd_which() {
|
||||||
local json_manifest="$PWD/$SECRETS_JSON_NAME"
|
local json_manifest="$PWD/$SECRETS_JSON_NAME"
|
||||||
if [ -e "$json_manifest" ]; then
|
if [ -e "$json_manifest" ]; then
|
||||||
_check_manifest_file "$json_manifest"
|
_check_manifest_file "$json_manifest"
|
||||||
echo "manifest ($SECRETS_JSON_NAME at $json_manifest):"
|
# EGB-700: surface the manifest schema version (validated == MANIFEST_VERSION
|
||||||
|
# by _check_manifest_file above) alongside the store format printed earlier.
|
||||||
|
local mver; mver=$(jq -r '.version // "?"' "$json_manifest")
|
||||||
|
echo "manifest ($SECRETS_JSON_NAME at $json_manifest, version $mver):"
|
||||||
local entry
|
local entry
|
||||||
while IFS= read -r entry; do
|
while IFS= read -r entry; do
|
||||||
[ -n "$entry" ] || continue
|
[ -n "$entry" ] || continue
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,14 @@ load test_helper
|
||||||
[[ "$output" == *".env"* ]] || false
|
[[ "$output" == *".env"* ]] || false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "which prints the manifest version (EGB-700)" {
|
||||||
|
create_project_dir manifestver
|
||||||
|
"$SECRETS_BIN" add .env >/dev/null
|
||||||
|
run "$SECRETS_BIN" which
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"version 2"* ]] || false
|
||||||
|
}
|
||||||
|
|
||||||
@test "malformed .secrets.json dies with a directed error naming the file" {
|
@test "malformed .secrets.json dies with a directed error naming the file" {
|
||||||
create_project_dir addproj
|
create_project_dir addproj
|
||||||
echo '{ not json' > .secrets.json
|
echo '{ not json' > .secrets.json
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue