Add audited application resolution

This commit is contained in:
2026-09-06 20:14:55 +00:00
parent cc41653bfd
commit 0da4260ad6
16 changed files with 909 additions and 57 deletions

View File

@@ -47,8 +47,8 @@ version checker, contributes no source, and continues through defaults, decode,
projection. A missing file remains an error; it is never interpreted as no input.
The current `Options` boundary accepts a defaults factory and `VersionRule`.
Imports, active profiles, filesystem context, overrides, resolver and semantic
validator callbacks, and dynamic secret classification return an explicit
Imports, active profiles, filesystem context, overrides, semantic validator
callbacks, and dynamic secret classification return an explicit
`options.unavailable` error because their lifecycle stages are not implemented.
The reserved profile spelling `default` selects no active profile. `NoSource`
additionally rejects imports, active profiles, and filesystem context as invalid
@@ -84,6 +84,44 @@ source coordinates are retained. `History` records the Go-zero or application
default baseline followed by exact base definitions, including equal-valued input.
All metadata queries and returned nested records are detached.
### Recorded application resolution
An optional `Definition.Resolve` callback runs after successful typed decoding. It
receives the working configuration and a per-load `Resolution` context. The context's
`IsExplicit` query uses surviving input presence, so explicit false, zero, empty, and
null are never confused with absence or static defaults.
`Default` assigns and records a derived value only when its path is not explicit.
`Normalize` always assigns and records. For transformations performed directly by
application code, `Resolution.Record` declares a derived-default or normalization
operation; a recorded container covers changes to its descendants. Records require
a schema-valid path, valid kind, safe nonempty reason, and schema-valid dependency
paths. Repeated records at one path are coalesced: reasons and dependencies retain
their first occurrence, the last helper value is authoritative, and any normalization
makes the combined operation a normalization.
ConfigKit projects the configuration before and after the callback. Every net change
must have an exact or ancestor record. Helper-assigned values must agree with the
final value at their declared path, which supports ordinary map-entry copy/writeback
while detecting forgotten writeback or unrelated targets. Invalid paths, kinds,
values, records, helper targets, and unrecorded mutations fail the load. Equal-valued
derived assignments may still record application intent even when there is no
structural change.
`Resolution.Error` and `Resolution.Warning` attach exact or nearest available input
locations. Reported errors fail the load; warnings survive on a successful result. A
returned resolver error adds one generic `resolve.hook_failed` error only when the
callback did not report an error, and its text is never exposed. Callback panics are
programming failures. Resolution and validation contexts are callback-scoped; using
a nil, zero, or retained context outside its active callback panics with fixed,
value-free text.
Successful records update effective origins and append detached metadata history
without changing input explicitness. Removed paths leave effective metadata but
retain history. ConfigKit privately snapshots each recorded before/after subtree for
later safe explanation; caller mutations of `Result.Config` cannot alter those
snapshots.
## YAML documents
The private YAML boundary currently accepts exactly one nonempty document whose root

View File

@@ -3,8 +3,8 @@
This is the contributor entry point for ConfigKit. Read the task-specific owners
below before making changes. The repository is in early implementation: its public
loader currently covers one strict source, typed defaults, schema-version checks,
and base/default metadata. The [component overview](internal/overview.md) owns the
implementation inventory.
recorded application resolution, and metadata. The
[component overview](internal/overview.md) owns the implementation inventory.
## Initial orientation

View File

@@ -192,6 +192,17 @@ later lifecycle option. No application loader or production line was copied.
Narratio's resolution, validation, import/profile, campaign/session, and command
behavior remains assigned to its later owning work.
Resolution implementation record (2026-09-06): ConfigKit adapted the intent of
Narratio's `Resolve`, `applyPipelineDefaults`, component default helpers,
`mergeCampaignSession`, and `selectStableInput` into a generic recorded resolver in
`resolution.go`. The synthetic timeout/artifact cases preserve explicit-input
decisions, derived dependencies, normalization, and map copy/writeback without
copying campaign/session algorithms or concrete defaults. ConfigKit adds structural
mutation auditing, container coverage, helper/path consistency checks, equal-value
intent, coalesced repeated writes, private before/after snapshots, deterministic
record ordering, and safe hook diagnostics. Semantic validation remains assigned to
the next lifecycle work.
### Secrets bootstrap
Assigned to the explicit bootstrap operation:

View File

@@ -19,10 +19,14 @@ module and package documentation boundary. Its implemented public primitives are
baseline/final trees, and initial default/input origins; and
- public single-source lifecycle coordination in `load.go`, version-report contexts
in `validation.go`, and detached base/default metadata assembly in
`load_metadata.go`.
`load_metadata.go`; and
- recorded application defaulting and normalization in `resolution.go`, including
explicitness queries, helper verification, structural mutation audit, coalesced
intent, private before/after snapshots, and resolution metadata integration.
The [configuration contract](../config.md) owns implemented input, schema, decoding,
single-source lifecycle, version checking, metadata, and ownership behavior.
single-source lifecycle, version checking, recorded resolution, metadata, and
ownership behavior.
The source-extraction inventory records the application foundations assigned to
future implementation work:

View File

@@ -2,7 +2,7 @@
## Status and ownership
Status: implementation in progress; Stages 1–7 are complete and later stages are
Status: implementation in progress; Stages 1–8 are complete and later stages are
pending.
Audience: a `gpt-5.6-terra` coding agent implementing one stage per prompt, in
@@ -684,7 +684,36 @@ Acceptance:
## Stage 8 — Implement recorded application resolution
Status: pending.
Status: complete.
Completion evidence (2026-09-06):
- Implemented per-load `Resolution`, `IsExplicit`, `Record`, `Default`, `Normalize`,
`Error`, and `Warning` in [`resolution.go`](../../resolution.go), and enabled the
resolver in `Load` after successful decode while leaving semantic validation gated.
- Added schema-aware path/kind/dependency checks, safe reason requirements, dynamic
and typed helper projection, final helper/path consistency checks, before/after
structural comparison, exact/ancestor record coverage, and distinct safe failures
for invalid values, targets, records, and unrecorded changes.
- Coalesced repeated path intent deterministically, retaining unique reasons and
dependencies, using the last helper value, and promoting mixed operations to
normalization. Equal-valued derived intent remains recorded; map copy/writeback,
container coverage, deletions, and new dynamic/map descendants are supported.
- Integrated resolution origins and history without changing surviving input
explicitness, retained private detached before/after snapshots, sorted records by
semantic path, and made retained callback-context use panic with fixed value-free
text. Resolver errors are suppressed unless explicitly reported through the
context; warnings remain on successful results. The
[current resolution contract](../config.md#recorded-application-resolution) owns
this behavior.
- Focused tests execute the timeout/artifact consumer shape, explicit null/zero and
absent-baseline behavior, normalization, map writeback, equal and repeated helper
intent, stable map-derived record order, subtree removal, snapshot detachment,
unrelated targets, invalid paths/kinds/values, unrecorded mutation, safe hook
failures, and closed resolution/version contexts. 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;
semantic validation remains later work.
Read: feature [resolution callbacks](initial.md#resolution-and-validation-callbacks).

View File

@@ -571,37 +571,16 @@ rendering remain target work below.
## Recorded resolution and validation
Application-owned resolution runs after decoding and may derive absent values
or normalize explicitly configured ones. Provide a narrow resolution context
that exposes metadata and records each changed semantic path, operation kind
(derived default or normalization), and a safe reason. Dependency paths may be
recorded when a value is derived from other fields.
Application-owned recorded resolution is implemented. The
[current resolution contract](../config.md#recorded-application-resolution) owns
explicitness queries, helpers and manual records, structural mutation auditing,
coalescing, diagnostics, origins, history, and snapshot ownership. Layered input
history, redacted explanation, and secret dependency propagation remain target work.
A resolver may mutate the typed configuration, but ConfigKit compares its
before/after configuration projection and requires records for changed paths.
Unrecorded changes are resolution errors. Container replacement records cover
changed descendants. Capture previous/final values internally from the actual
configuration rather than trusting separately supplied values. Application
resolvers own meaningful reasons; a diff alone cannot explain intent. Reasons
must not embed secrets. The proposed [resolution API](#resolution-and-validation-callbacks)
provides helpers to combine assignments and records. Snapshot comparison verifies
net changes during the hook; it does not reconstruct intermediate assignments.
A record describes the hook's before/final transition and declared intent, not an
automatically recovered event log. Equal-valued derived-default assignments may
still have a record identifying their intent. Multiple records at the same path
are coalesced for that hook, retaining declared reasons and dependencies.
The final configuration, effective output, presence queries, and explanation
records must agree. Do not render the pre-resolution YAML tree as the final
configuration. ConfigKit promises an independently owned snapshot it does not
subsequently mutate, not language-enforced immutability of exported Go values.
Callers should treat returned configuration as read-only; later caller mutations
are outside the captured metadata snapshot.
Canonical typed projection, detached cloning, and structural comparison are now
implemented as private foundations. The [current ownership contract](../config.md#typed-projection-and-ownership)
owns those mechanics; publishing independently captured results remains lifecycle
work.
Canonical typed projection, detached cloning, structural comparison, and publishing
independently captured resolved results are implemented. The
[current ownership contract](../config.md#typed-projection-and-ownership) owns those
mechanics.
Application semantic validation runs after successful resolution and collects
independent errors in a standard diagnostic form. Validators are read-only and
@@ -613,7 +592,9 @@ is outside this lifecycle. Do not run later hooks on an invalid typed value.
Safe diagnostic/provenance value records, canonical diagnostic rendering, and
`LoadError` access are implemented. Their current contracts are owned by
[`records.go`](../../records.go) and [`diagnostic.go`](../../diagnostic.go).
Attaching those records to the loading lifecycle remains target work below.
Base/default and resolution records are attached to the current loading lifecycle.
Additional layers, safe historical values, redaction, and rendering remain target
work below.
Retain input sources, layer, line/column where available, import sites, effective
origins, overridden definition records, and resolution operations. Container
@@ -814,6 +795,10 @@ behavior. Layered callback history and safe historical values remain target beha
### Resolution and validation callbacks
The resolution declarations and behavior below are implemented; the
[current recorded-resolution contract](../config.md#recorded-application-resolution)
is authoritative. Semantic validation behavior in this section remains target work.
```go
func (r *Resolution) IsExplicit(path Path) bool
func (r *Resolution) Record(path Path, kind ChangeKind, reason string, dependencies ...Path)