Add explicit secrets directory bootstrap
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user