Add bounded sources and strict YAML parsing

This commit is contained in:
2026-09-06 19:30:31 +00:00
parent 14b10cb6a5
commit 7c78da9cd2
17 changed files with 1287 additions and 44 deletions

View File

@@ -2,7 +2,7 @@
## Status and ownership
Status: implementation in progress; Stages 1–2 are complete and later stages are
Status: implementation in progress; Stages 1–3 are complete and later stages are
pending.
Audience: a `gpt-5.6-terra` coding agent implementing one stage per prompt, in
@@ -383,7 +383,30 @@ Acceptance:
## Stage 3 — Extract source reading and enforce YAML policy
Status: pending.
Status: complete.
Completion evidence (2026-09-06):
- Added opaque source constructors and bounded file/byte/reader acquisition in
[`source.go`](../../source.go), including byte snapshots, regular-file checks,
deterministic I/O codes, caller-owned reader lifetime, and one atomic claim
shared by source copies. `NoSource` creates no source record.
- Pinned `go.yaml.in/yaml/v4 v4.0.0-rc.6` solely behind `internal/yaml` and added
the parser-independent ordered tree in `internal/document`. The adapter uses the
streaming loader, checks for a second document explicitly, applies a calibrated
private parser depth plugin, enforces owned depth/node limits, preserves numeric
spelling and locations, and returns only value-free internal issues.
- Generalized the strict-source/document cases recorded in the [extraction
inventory](../internal/extraction.md), created the [current configuration input
contract](../config.md), and added an AST import-boundary regression. No typed
decoder, composition, filesystem graph, or public `Load` was introduced.
- Tests cover real file failures and reopening, byte ownership, fragmented/error
readers, exact/overflow byte limits, concurrent copied-source claims, all YAML
policy categories, trailing/empty documents, duplicate locations, lexical wide
numbers, block/flow depth calibration, node limits, safe parser errors, and
adapter isolation. Verified `go test ./...`, `go vet ./...`, `go build ./...`,
`go mod tidy -diff`, and `git diff --check` with Go 1.26.7 on linux/amd64. No
deviations were required; typed binding and decoding remain later work.
Read: feature [YAML policy](initial.md#yaml-and-scalar-policy),
[source constructors](initial.md#entry-point-and-reusable-application-definition).

View File

@@ -423,31 +423,10 @@ configuration is never returned as a successful, usable result.
## YAML and scalar policy
The supported model is mappings with string keys, sequences, scalars, and null,
with YAML comments and block/flow syntax. Require exactly one document with a
mapping root. Reject empty input, additional documents (including empty ones),
duplicate mapping keys, explicit tags, anchors, aliases, and merge keys (`<<`).
Mapping keys must resolve to strings under this scalar policy; quote keys
that would otherwise be booleans, numbers, or null. Compare decoded string keys
without case folding or Unicode normalization. A quoted `"<<"` is also reserved and rejected.
Use a deliberately limited scalar vocabulary independent of parser defaults:
- Quoted and block scalars are strings.
- Plain `true` and `false` are booleans; plain `null`, `~`, and an empty value
are null. Other case variants of these reserved literals are rejected when used as
plain values.
- Numbers use JSON decimal syntax: optional minus, integer part with no leading
zero except zero itself, optional fraction, and optional decimal exponent.
- Reject YAML numeric extensions such as hexadecimal, octal, digit separators,
leading plus, and non-finite values. Reject legacy boolean words such as
`yes`, `no`, `on`, and `off` unless quoted (case-insensitively).
- Date/time-looking plain text remains a string, never an implicit timestamp.
Other ordinary plain text is a string.
Unsupported scalar forms must produce a policy error rather than inherit a
parser conversion. Preserve numeric lexical information until decoding so
large integers are not rounded through an intermediate floating-point value.
Source ownership, the supported YAML subset, scalar grammar, fixed input limits,
and safe failure behavior are implemented. The [current configuration input
contract](../config.md) now owns those definitions. Typed scalar conversion into
application values remains target work in [typed decoding](#typed-decoding).
## Paths and presence
@@ -770,11 +749,6 @@ type FilesystemContext struct {
BaseDir string
Root string
}
func File(path string) Source
func Bytes(label string, data []byte) Source
func Reader(label string, input io.Reader) Source
func NoSource() Source
```
`Load` accepts zero or one options value; multiple values are an options error,
@@ -792,11 +766,11 @@ result. Callbacks must not retain these contexts or configuration pointers; shar
closures must be safe for concurrent calls if the application loads concurrently.
ConfigKit does not synchronize application-owned captured state.
`Source` is opaque with the constructors above, not an arbitrary provider
interface. `Bytes` snapshots its input; `Reader` is consumed during the load and
is not closed by ConfigKit. A reader source is single-use. File sources are read
on each load. Constructor argument errors surface as load diagnostics. Applications must use
non-secret source labels because labels appear in diagnostics and source reports.
`Source` and its constructors are implemented; [`source.go`](../../source.go) and
the [configuration input contract](../config.md#sources) own their current behavior.
The loading and filesystem-option behavior below remains target work.
Applications must use non-secret source labels because labels appear in diagnostics
and source reports.
Filesystem context follows the [source rules](#explicit-imports-and-source-identity):
a file's own directory supplies its base, and an explicit context can set its
root. Supplying `BaseDir` for a file is an options error; bytes/readers require