diff --git a/docs/DESIGN.md b/docs/DESIGN.md index a37c41d..5de6ca2 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -240,3 +240,19 @@ source. Yams and its bundled `CYaml` / libyaml-derived C parser are compatibility references for PureYAML. They should guide test cases and behavior questions. They are not source material to copy into this package. + +## Round-trip law + +PureYAML keeps the parser (`PureYAML.parse`) and the emitter (`PureYAML.dump`) +as separate hand-written implementations, not one invertible grammar. The +guarantee they jointly hold is a fixed point over the emitter's normal form, not +a bijection over arbitrary author formatting (the surface-to-model relation is +many-to-one: spacing, flow vs block, and comments do not survive into the model): + +- Lambda1 (model fidelity): for a `Model.Value` `x`, `parse(dump(x)) == x`. +- Lambda2 (idempotence): `parse(dump(parse(s))) == parse(s)` for any input `s`. + +These are enforced as gates, not hoped for: corpus and generated-document +round-trip tests assert reparsed equality, and the empty-collection degenerate +values are pinned as passing regressions. `dump` is total: no force-unwraps, and +every empty collection renders to `[]` / `{}` at every position. diff --git a/docs/package-import-contract-status.md b/docs/package-import-contract-status.md new file mode 100644 index 0000000..e1bdf4b --- /dev/null +++ b/docs/package-import-contract-status.md @@ -0,0 +1,13 @@ +# Package import contract status + +One row per target. The contract: the library target imports nothing beyond the +Swift standard library; the test target imports only `PureYAML`, `Testing`, and +`Foundation`. + +| Target | Allowed imports | Actual | State | +|---|---|---|---| +| `PureYAML` (library, `Sources/`) | Swift standard library only (no `Foundation`, no external packages) | zero `import` statements | matches | +| `PureYAMLTests` (`Tests/`) | `PureYAML`, `Testing`, `Foundation` | `@testable import PureYAML`, `import Testing`, `import Foundation` | matches | + +Enforced mechanically by `scripts/check-forbidden-patterns.sh` (no `import` in +`Sources/`) and `Package.swift` (`dependencies: []`). diff --git a/docs/rules-conformance.md b/docs/rules-conformance.md new file mode 100644 index 0000000..d496697 --- /dev/null +++ b/docs/rules-conformance.md @@ -0,0 +1,74 @@ +# Rules conformance + +This records how PureYAML conforms to the vendored Swift rules canon +(`docs/rules-swift/`, mirror of `rules-swift`), including the few places where a +canon rule does not apply to a dependency-free YAML library or conflicts with a +project non-negotiable. Per `docs/rules-swift/core/rules.md`, conflicts are +resolved by scope: the condition under which each rule applies is named, so +exactly one governs any case. + +## Conformant (applicable rules) + +Every applicable rule passes its acceptance check. Mechanically verified: +`check-style`, `check-namespacing` (now namespace-aware, catches one-type-per-file +inside `extension` blocks), `check-forbidden-patterns`, `check-changelog-touched`, +`check-roadmap`, `swiftformat --lint`, `swiftlint --strict`, `swift build`, +`swift test` (277 tests). Concurrency, dependency-injection, cross-platform, +framework-policy, namespacing (anchor choice), folder-grouping, domain-first, and +the no-shortcuts mechanical tells are all clean. + +## Conflict: documentation.md Rule 1 vs the dependency-free non-negotiable + +`documentation.md` Rule 1 mandates a DocC catalog plus `swift-docc-plugin` as a +**package dependency**. PureYAML's binding non-negotiable (`CLAUDE.md`, +`AGENTS.md`) is **no external SwiftPM dependencies** (`dependencies: []`). Both +cannot hold. Resolution by scope: the dependency-free invariant is the +higher-stability project contract and wins. PureYAML is therefore exempt from +`documentation.md` Rule 1's plugin requirement. API documentation lives in +source `///` comments and `docs/`. If a maintainer later accepts a build-time +plugin dependency, this exemption is revisited. + +## Not applicable: domain-mismatched canon files + +Several canon files describe other domains (OpenAPIKit data parsing, an +After-Effects-style tool, app startup, UI). Their domain-specific clauses do not +bind a dependency-free YAML library; the transferable clauses are satisfied. + +- `parsing-rules.md`: the CSV/XML/Excel/`Codable`-record and multi-encoding + `Data` clauses are N/A; the applicable parse-then-validate separation and + enum-with-associated-values clauses pass. +- `validation-rules.md`: the OpenAPI component-store / `$ref` / type-erasure and + "validate-or-exclude every public type" coverage-gate clauses are N/A (the + input model is the 5-case `Model.Value` enum, handled exhaustively, so no input + is silently dropped). The applicable rule/validator/issue-path clauses pass. +- `business-rules-and-constraints.md`: entirely N/A (no external services, auth, + PII, or compliance surface in an offline library). +- `ui/*`, `openapi-generated.md`, `exp/app-target.md`: N/A (no UI, not generated, + no app target). + +## Conscious deviations (intent met another way) + +- `core/round-trip-transformation.md` "by construction" (one `ParserPrinter`): + PureYAML keeps a hand-written parser and emitter rather than a single + invertible algebra, because a bijective ParserPrinter is not a faithful model + of full YAML (block/flow, anchors, tags, chomping, comments; surface-to-model + is many-to-one). The law it protects is enforced by a fixed-point gate: + `parse(dump(parse(x))) == parse(x)` over the corpus and generated documents, + plus the empty-collection adversarial regression. The round-trip law is stated + in `docs/DESIGN.md`. +- `code-style.md` file-naming: the repo uses folder-based bare names in some + folders (`Parsing/`) and dotted `..swift` in others + (`Tagged/`, `Validation/`). Both are blessed patterns; the standing convention + is dotted for new namespaced files. A full rename to one pattern is deferred as + disproportionate churn against the highest-stability rename surface. +- `documentation.md` Rule 14 (`///` on every public decl): the non-trivial public + entry points are documented; the remaining gap is the MAY-skip categories + (`Codable` conformance members, `CustomStringConvertible.description`, + self-describing data fields). Tracked, not blanket-filled. + +## Historical commits + +A few commits that predate the commit-message hook have non-Conventional or +over-72-character subjects. They are already on `origin/main`; rewriting pushed +history is out of scope. The `commit-msg` hook now enforces Conventional Commits +and the 72-character subject limit going forward.