v0.20.0 Phase 3 Dependency-Local Exact Selection Review
Result
Phase 3 is complete. No correctness or architecture blocker remains in the reviewed scope. A dependency entry in #dependencies is now the only authored syntax authority for both source intent and optional exact selection. Top-level #lock is rejected as an unknown package directive; there is no compatibility decoder, format version, prefixed source literal, or second authored alias map.
A repository-wide follow-up reopened the phase after the initial review found three package-state enforcement gaps below the syntax migration. The final model now verifies exact cache content through one shared cache-representation authority, rejects provisional source-kind mismatches before package lookup or transport, and makes package-state retry progress explicit. The same follow-up isolated eager semantic-stage helpers from production builds and repaired the active documentation indexes. These corrections did not restore a second authored selection source.
Git dependencies use optional commit, archive dependencies use optional sha256, and path dependencies accept neither. The source-specific fields are syntax-bearing declaration facts; resolution converts them into syntax-independent ExactDependencyLock values. The internal lock name remains intentional because --locked, provisional resolution overlays, acquisition verification, and format-1 graph output still model exact locks. It is not a second source syntax.
Accepted Model
#dependencies alias record
|
+---- DependencySource --------> acquisition intent
|
+---- optional exact field ----> exact selection
|
effective selection overlay
|
immutable resolved graph
|
missing-field source projection only
PackageDeclaration owns one DependencyDeclaration per alias. That value owns the dependency record syntax identity, one DependencySource, and one optional DependencyExactSelection. Resolution may overlay a provisional exact selection while validating acquisition, but the overlay cannot publish source. The resolved graph closes the effective selection before package state asks for a source update.
selection_source receives that closed graph evidence and the retained original syntax. It validates complete alias coverage, source-kind agreement, and agreement with every existing authored field. It then inserts only missing commit or sha256 fields. Package state separately publishes complete exact packages into the append-only cache, revalidates the graph, and compare-before-writes the root source. A cached package still carries no selection authority.
Findings Closed During Review
Cache identity trusted a writable directory name
Resolution previously accepted an existing <PackageId> directory after checking only that it contained a regular index.nct. nocter-package-cache now seals each staged tree with a deterministic streaming content manifest. Staged overlays, concurrent publication races, persistent stores, locked resolution, and offline resolution all consume the same verified-root capability. Changing package content invalidates the entry before it can enter a graph.
Provisional selections relied on later graph validation
An arbitrary lock kind could previously form a package ID and trigger transport before completed graph validation compared it with the dependency source. Resolution now validates every effective selection against DependencySource::exact_lock_kind before lookup. A path selection and a Git/archive kind mismatch are typed resolution failures and cannot reach acquisition.
Resolver retries trusted caller progress
Package-state previously retried an injected driver indefinitely if it ignored the supplied overlays and repeated one request. The operation now records completed lock and fetch requests and rejects an exact repeated request before acquisition runs again. The guard is identity-based rather than a global retry limit, so a valid large graph is not capped.
Eager stage helpers remained ambiguous production APIs
The non-recovering declaration-lowering and checking helpers existed primarily for focused tests, but one still described itself as the production entry and all were exported by ordinary builds. They now exist only in each defining crate's test build or behind an explicit test-api feature enabled by dev-dependencies. Normal builds cannot use them to bypass nocter-compiler-computation. The obsolete module-wide lint suppression in session was removed; only exact query-owner and test sites retain narrow suppressions. Native conformance tests compose the public stage contracts deliberately; command and workspace tests own coverage of the production compiler-computation entry.
Active documentation indexes stopped before Phase 3
The phase review existed but was absent from the contributor and review entry pages, while the handoff's next-work section still led with Phase 2. The indexes now name every active v0.20.0 review, and the handoff records the final Phase 3 package boundaries before the next public design.
Package source had two alias authorities
The old declaration decoder built independent dependency and lock maps, then repaired their relationship through alias, source-kind, and path-lock validation. The dependency declaration now owns its exact selection directly. The #lock decoder, lock_directive, lock format validation, unknown-lock matching, and path-lock cross-check were deleted.
Generated updates rewrote an unrelated directive
The old renderer replaced a complete sorted #lock block. The new updater records dependency record syntax identities and produces insertion edits only. It preserves dependency order, surrounding fields, comments, inline or multiline layout, LF or CRLF, and records with or without a trailing comma. Reapplying the same effective selection is byte-identical.
Source-kind knowledge was repeated across mutation layers
Graph validation, package-state acquisition, and source generation each selected Git versus archive lock kinds independently. DependencySource::exact_lock_kind is now the domain contract consumed by all three. The declaration decoder still owns field spelling and structural validation; later layers do not reinterpret commit or sha256 syntax.
Source generation trusted a correctly assembled map
A caller could previously rely on the graph to supply a complete compatible map. The new source updater rejects a missing remote selection, a selection for a path dependency, a source-kind mismatch, an unknown alias, or disagreement with an authored field. Correct source mutation is no longer a caller obligation.
Boundary Audit
PackageDeclarationcontains no independent authored lock map or lock-directive identity.- Package syntax is decoded once; resolution consumes structured source and selection values.
- The resolved graph owns effective exact selections, including provisional overlays, without becoming source mutation authority.
- Source generation consumes retained syntax and closed graph evidence. It does not parse source, resolve dependencies, fetch packages, or serialize the whole directive.
- Package state owns staging, cache publication, graph revalidation, and root-source commit. It does not know dependency-record layout.
- Exact-package cache visibility and dependency selection remain separate. Only
commitorsha256inside the dependency record persists selection authority. - LSP and graph inspection remain locked, offline, and read-only; they cannot invoke source generation.
- Historical release records retain their historical syntax. Active specification and development contracts describe only the new form.
Evidence
- Declaration tests cover valid Git/archive/path records, malformed commit and digest values,
source/selection mismatches, selected path dependencies, and rejection of top-level
#lock. - Source-update tests cover inline and multiline records, two source kinds, missing trailing comma, comments, CRLF, idempotence, incomplete maps, path selections, and source-kind mismatch.
- Package-state tests cover complete graph validation, cache publication, exact-field commit, concurrent root edits, post-commit revalidation, locked policy, rejected transitive state, cache-content corruption, and a non-progressing driver.
- Command integration tests cover fetch and build acquisition through generated archive
sha256fields; graph inspection remains read-only. - Complete workspace tests, warnings-denied all-target Clippy, warnings-denied rustdoc, formatting, generated documentation, and repository integrity gates passed on 2026-08-30.