Add explicit secrets directory bootstrap

This commit is contained in:
2026-09-06 21:29:25 +00:00
parent 828fdd38eb
commit 16f4212770
7 changed files with 845 additions and 3 deletions

View File

@@ -144,6 +144,41 @@ configuration values. Applications must still keep source labels, resolution
reasons, and diagnostic messages value-free because ConfigKit cannot infer and
sanitize secrets embedded in arbitrary application prose.
## Explicit secrets-directory bootstrap
`LoadSecretsDir(directory)` is a separate, opt-in startup operation. It is never
called by `Load`, is not a YAML option, and does not participate in configuration
validation. Applications choose the native directory path and should invoke the
operation once before starting concurrent work. The selected path may itself be a
symlink; ConfigKit resolves it to an existing canonical directory before examining
its immediate entries.
The bootstrap enumerates at most 1,024 entries and sorts them by filename. Every
entry must have an ASCII environment-variable name and be a regular, nonsymlink
file. Metadata for all entries is validated before file content is read or process
state is changed. Existing environment variables win, including variables whose
value is empty; their files are not opened. Each eligible file is limited to 1 MiB,
and eligible content is limited to 8 MiB in total. ConfigKit removes exactly one
terminal LF or CRLF, preserves all other whitespace, accepts an empty value, and
rejects NUL bytes.
All eligible files are read successfully before application begins. Presence is
checked again immediately before each `os.Setenv`: a variable populated since
preflight is skipped. A write failure stops application without rollback, so prior
writes remain and later eligible entries are `not_applied`. This operation is not
atomic and does not provide compare-and-set semantics for concurrent environment
mutation.
`SecretsReport.Entries` returns detached, filename-ordered `SecretFileResult`
records with `loaded`, `skipped_existing`, `not_applied`, or `failed` status.
`ExistingEmpty` distinguishes an empty existing winner. `SecretsReport.WriteTo`
renders only quoted names, canonical paths, statuses, and that empty-state flag;
it never retains or renders file contents. Failures return the non-nil partial
report with a `*SecretsError` containing detached `secrets.*` diagnostics and no
raw filesystem or environment error chain. Applications should retain the report
alongside the later configuration result and explicitly present blocked and failed
files when appropriate; ConfigKit stores no implicit global report.
### Recorded application resolution
An optional `Definition.Resolve` callback runs after successful typed decoding. It

View File

@@ -300,6 +300,16 @@ Assigned to the explicit bootstrap operation:
fixed budgets. Logging, configuration-driven implicit invocation, private-mode
conventions, and application API-key plumbing remain in Narratio.
Implementation record (2026-09-06): `LoadSecretsDir`, `readSecretsEntries`,
`readSecretValue`, and the private environment/file seams in `secrets.go` adapt
Narratio's bounded-read and preserve-existing mechanisms at revision
`b68435128be1e24b94fc7ec259d6e9093669fd99`. The corresponding tests retain its
valid-file, existing-variable (including empty), missing/permission/read,
nonregular, symlink, and byte-limit cases. ConfigKit validates invalid entries
rather than skipping them, permits ordinary deployment permission modes, resolves
the selected directory symlink, performs complete preflight before mutation, and
emits per-file safe records without application configuration or logging.
## WeatherReporter evidence
### Secrets bootstrap
@@ -324,6 +334,15 @@ Assigned to the explicit bootstrap operation:
entries are not read. Bootstrap is explicitly invoked and remains independent of
YAML loading and semantic validation.
Implementation record (2026-09-06): the same `secrets.go` state machine adapts
WeatherReporter's `loadSecrets`/`applySecrets` separation, environment-name check,
regular-file rejection, and exact one-LF/CRLF removal at revision
`5d306a202471e10fe98923a1df27d0b0fce5c666`. Tests adapt its newline matrix,
invalid-entry and preflight-no-write cases, and controlled application failure.
The overwrite/rollback expectation is intentionally replaced by existing-winner,
presence-recheck, and partial-progress statuses; no configuration loader invokes
the bootstrap.
### Defaults, explicit presence, and module options
Assigned to consumer-shaped examples and migration checks:

View File

@@ -1164,7 +1164,36 @@ Acceptance:
## Stage 16 — Extract explicit secrets-directory bootstrap
Status: pending.
Status: complete.
Completion evidence (2026-09-06):
- Added the separate, opt-in `LoadSecretsDir` startup boundary in
[`secrets.go`](../../secrets.go), with detached per-file results, stable status
values, safe `SecretsError` diagnostics, and a deterministic quoted report
renderer. `Load` remains process-state-free and has no secrets-directory option.
- Adapted WeatherReporter's read/apply separation, filename/type checks, exact
one-LF/CRLF removal, and failure cases together with Narratio's bounded reads and
existing-variable preservation. The [extraction inventory](../internal/extraction.md)
records both inspected revisions, reused symbols/cases, and the intentional
removal of overwrite, rollback, permission-mode, implicit configuration, and
logging policies.
- Implemented bounded immediate enumeration (1,024 entries), canonical directory
selection, sorted metadata validation, 1 MiB eligible-file and 8 MiB aggregate
reads, NUL rejection, full preflight before writes, and a per-write presence
recheck. Existing empty values are distinguished, skipped files are never opened,
and apply failures retain accurate partial progress without rollback.
- Added real filesystem/environment and private-seam coverage in
[`secrets_test.go`](../../secrets_test.go) for canonical ordering, selected
directory symlinks, newline/empty values, invalid names/types, all bounds,
missing/permission/read failures, no-write preflight failures, newly populated
variables, controlled apply failure, defensive copies, non-disclosure, and writer
failures. The [configuration contract](../config.md#explicit-secrets-directory-bootstrap)
documents startup-only mutation, non-atomicity, reporting blocked files, and
retaining reports alongside configuration results. Verified `go test ./...`,
`go test -race ./...`, `go vet ./...`, `go build ./...`, `go mod tidy -diff`, and
`git diff --check` with Go 1.26.7 on linux/amd64. No deviations were required;
environment-backed typed secret references remain later work.
Read: [secret bootstrap contract](initial.md#secret-references-and-explicit-bootstrap),
architecture responsibility boundary, testing policy, and WeatherReporter's