feat: add Ironwood support, AKA Bump to nu6.3#278
Open
zancas wants to merge 26 commits into
Open
Conversation
bip0039 existed only to re-derive the hardcoded ABANDON_ART_SEED constant inside a unit test, dragging ~18 crates (zerocopy, the old rand 0.8/getrandom 0.2 stack, pbkdf2/hmac/password-hash, zeroize, unicode-normalization) into every consumer's deploy graph. The test now validates the constant's structure against the canonical BIP-39 zero-entropy vector directly. The unmaintained json crate had a single call site in the zcashd validator; migrated to serde_json, matching the idiom already used in get_activation_heights. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The getblocktemplate -> proposal_block_bytes -> submitblock sequence existed in three copies: Zebrad::generate_blocks and both of regtest-launcher's mining loops (bootstrap and steady-state). Any change to proposal assembly or submission semantics had to land in all three or they drifted silently. One public round trip now serves all callers, returning a BlockSubmission (height, block hash, raw response) whose accepted() documents the duplicate-response ambiguity that generate_blocks' chain-advance polling exists to resolve. regtest-launcher no longer hex-encodes anything; drop its hex dep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Launch path (findings #1-#3 of the duplication survey): - PortPins trait on launch configs replaces the per-daemon read/clear closure pairs fed to with_retry_on_collision; the three ceremonial single-field *Ports structs are gone (ZebradPorts stays - its four-field atomic pick rationale is real). Collision signatures remain per-daemon, next to their contract docs. - launch::spawn_and_wait owns pipe-setup -> spawn -> readiness wait (with uniform timing instrumentation) for all four daemons; launch::wait is now private. - MAX_LAUNCH_ATTEMPTS hoisted; the take/re-Some dance on the additional-log accumulator in wait() collapsed via as_mut(). Shared helpers: - activation_heights_from_getblockchaininfo now owns the upgrades extraction both validators duplicated. - zebra_rpc decode_hex/fixed_bytes<N> replace three hand-rolled InvalidHex mappings. - JsonLine wrapper unifies the balance/get-info line-scan parsers in the devtool client. - config.rs write_config_file is the single config write path. - The inherent logs_dir() getters duplicated the LogsToDir impls byte-for-byte on five structs; the trait is now the one definition. Left alone deliberately: the six identical Drop impls (Rust forbids blanket Drop; a macro would trade explicitness for less than it saves) and the per-endpoint RPC probe tests (separate tests preserve failure granularity). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Function-first triage: the data_dir() getters on both validators duplicated their Validator::data_dir trait impls byte-for-byte, so those die by plain deletion, no macro needed. What remains is exactly what helper fns cannot express - trait impl blocks and method definitions: - macros::impl_stop_on_drop! replaces the five identical drop-delegates-to-stop impls (Zcashd, Zebrad, Zainod, Lightwalletd, Empty). Opt-in per type because the language forbids the blanket impl<T: Process> Drop for T this would otherwise be. LocalNet's generic Drop stays hand-written. - macros::ref_getters!/copy_getters! generate the ~20 remaining field accessors across the daemon and devtool structs, doc comments preserved per field. - zingo-consensus: local height_getters!/height_setters! collapse the 11 activation-height getters and 11 builder setters. These are in-repo macro_rules, not a derive dependency - the getset removal's objection was the dependency, not macros. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lt feature
Phase 1 of the two-phase excision recorded in
docs/adr/0001-excise-legacy-stack.md: everything zcashd- and
lightwalletd-shaped moves behind a new non-default `legacy-stack`
cargo feature; phase 2 (within two weeks) deletes the gated code and
the feature entirely. zcash_local_net bumps 0.6.0 -> 0.7.0.
Gated under `legacy-stack` (UNSUPPORTED and UNTESTED — CI never
enables it; it exists only as a short-lived migration stopgap):
- validator::zcashd and indexer::lightwalletd
- ProcessId::{Zcashd, Lightwalletd}
- LaunchError::{UnsupportedZcashdCapability, CapabilityProbeFailed}
- Validator::get_zcashd_conf_path + zebrad's impl and the
compatibility zcash.conf zebrad wrote solely for lightwalletd
- config::{write_zcashd_config, write_lightwalletd_config} and the
ZCASHD/LIGHTWALLETD filename constants, logs::LIGHTWALLETD_LOG
- all legacy integration and unit tests (kept, not deleted, so one
manual `--features legacy-stack` smoke remains possible pre-release)
Deleted outright (no consumer even under the gate):
- the checked-in zcashd-generated chain cache
(chain_cache/client_rpc_tests/) and generate_zcashd_chain_cache;
in-repo tests only use the zebrad-generated client_rpc_tests_large
- cert/cert.pem (no consumer anywhere; lightwalletd runs
--no-tls-very-insecure)
- the CI step symlinking zcashd/zcash-cli/lightwalletd binaries and
the legacy lines in the utils/*.sh binary scripts
Also: generate_zebrad_large_chain_cache launches a bare Zebrad
instead of LocalNet<Zebrad, Lightwalletd>; the executable_finder
ignored test probes zebrad instead of zcashd; crate docs/README
rewritten around the zebrad + zainod + zcash-devtool core stack.
New: CONTEXT.md (glossary: Legacy stack, Core stack, Compatibility
conf) and docs/adr/0001-excise-legacy-stack.md capturing why one
feature instead of two, why the gate is deliberately untested, and
why the Validator/Indexer abstractions stay.
Verified: cargo check --workspace --all-targets (-D warnings),
clippy, fmt, and cargo doc clean in both feature configs; lib unit
tests pass 24/24 (default) and 35/35 (legacy-stack).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Findings from a dependency audit of the workspace:
- zcash_local_net: delete the [build-dependencies] section (hex, tokio
with three features). The crate has no build.rs, so the section was
inert manifest noise. cargo-machete does not flag this class.
- regtest-launcher: drop assert_cmd (-9 lockfile entries). Its entire
usage was one macro call locating the test binary; cargo provides
this natively to integration tests via env!("CARGO_BIN_EXE_<name>").
- regtest-launcher: drop insta (-4 lockfile entries) and its orphaned
[profile.dev.package] opt-level override. The single snapshot
assertion is now a plain golden-file compare against
tests/golden/regtest_first_mine.txt; set UPDATE_GOLDEN=1 to re-bless
in place of `cargo insta review`.
Audited and deliberately kept: sha2 (crypto, never hand-roll), hex and
owo-colors (zero transitive deps), regex/nix/anyhow (dev, properly
used), serde (required by serde_json integration). A reqwest
replacement (~70 entries hang off two localhost HTTP calls) was
prototyped and rejected: not worth the breaking public API surface
(RpcClientError::Transport, RpcRequestClient::call, Zebrad
healthy/ready signatures).
Verified: cargo check --workspace --all-targets in both feature
configs, 24/24 lib unit tests, cargo fmt --check, and the
regtest-launcher e2e golden test passing against real zebrad + zainod.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Retitle the Unreleased section to 0.7.0 (2026-07-03) and add the entries missing since 0.6.0: the rpc_client and zebra_rpc modules, the zingo-consensus type migration and MinerPool (both breaking), the getset -> hand-written accessor swap, and the removal of every zebra/librustzcash/zcash ecosystem dependency plus bip0039, json, and getset. Also correct the RUSTSEC reference in the macros.rs module doc: the advisory this repo actually tracked (and whose cargo-deny ignore the getset removal deleted) is RUSTSEC-2026-0173 against proc-macro-error2 itself, not RUSTSEC-2024-0370 against its predecessor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fig writer The zebrad config writer emits `"NU6.3" = <height>` (requires zebrad >= 6.0.0) and now rejects activation heights it cannot express — upgrades through Canopy must be Some(1), and a configured NU7 panics until the writer gains NU7 emission — instead of silently dropping them, the failure mode zingolabs/zaino#1368 reconstructs against this writer at v0.7.0. `activation_heights_from_getblockchaininfo` reads "NU6.3" back. The devtool client's canonical heights gain nu6_3 = 2, staying in lockstep with devtool DEFAULT_REGTEST (zcash/zcash-devtool#205, ADR 0002); its activation-heights TOML writer emits nu6_3, hard-requiring a devtool binary whose schema knows the key. The height-5 fixture and the launcher default co-activate NU6.3 with NU6.1/NU6.2; the launcher's `all=` sweep includes nu6_3 and main.rs gains the previously dropped set_nu6_3 conversion (its mirror test already had it). CONTEXT.md gains the Devtool contract and Canonical heights terms. Unit tests pin NU6.3 emission and omission, the canopy/NU7 rejections, and the CLI-string <-> fixture equivalence. Verified live: the devtool faucet send flow passes end-to-end on an NU6.3-active chain (zebra 6.0.0-rc.0, zaino PR #1362, devtool PR #205 re-pinned to librustzcash dw/ironwood-scan-model); shielding remains blocked upstream (fees::common::OutputManifest has no ironwood field, reported toward librustzcash#2539). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zingolabs/zcash-devtool 8eccaceb (PR #209 line) added the field; WalletBalance carries it as a required key per the ADR-0002 hard-require pattern, and the contract stamps now name that commit as the tested devtool (its 0.1.0 package version does not distinguish branches). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With NU6.3 active for the whole test window, consensus forbids value entering orchard: coinbase subsidies and the shielded output land in ironwood, so faucet_shields asserts ironwood_spendable and pins orchard_spendable to zero. Verified green against zebra 6.0.0-rc.0, zainod PR #1362, and devtool PR #209 (librustzcash basic_ironwood @ d7270910, which completes the Ironwood change-output path). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… enforced at compile time (ADR 0003) Remove the devtool client's canonical-heights equality guard and delete the ClientError::UnsupportedActivationHeights variant. A height vector can no longer be written into a wallet config at all: ZcashDevtoolConfig::network becomes the new client::WalletNetwork enum, whose regtest variant demands a client::ValidatorHeights — an opaque type whose only public constructor, WalletNetwork::from_validator, queries the running validator for its schedule. A wallet whose heights disagree with its validator is therefore unrepresentable (ADR 0003, new in docs/adr/). The faucet and recipient constructors take the network as a parameter, the config's Default impl and the ClientConfig Default bound are removed, and network_flag() becomes infallible. The client serializes the derived heights into the devtool's --activation-heights TOML, omitting keys for inactive upgrades, and now panics on a configured NU7 instead of silently dropping it. ZainodConfig::network narrows to the new payload-free zingo_consensus::NetworkKind: an Indexer config that accepts activation heights is a false affordance under the same invariant, and the config writer only ever emitted the kind string. Golden unit tests pin a mid-chain fixture (NU6.3 at height six) to its acceptance TOML byte for byte and pin absent-key omission; the integration tests derive every wallet's network from the validator, and a new cross-boundary orchard-to-ironwood test is added, ignored until the Indexer side of the invariant lands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nvergence barrier Replace kernel-ephemeral allocation in network::pick_unused_port. Ports now come from a fixed band below every default ephemeral range (16384–32767), partitioned into per-process slices by process id, walked sequentially, and bind-checked before they are returned. The band prevents the kernel from reusing a picked port for an outgoing connection in the pick-to-child-bind window; the slices keep parallel nextest processes out of each other's territory; the bind-checked walk steps over squatted ports deterministically. The launch-time retry-on-collision machinery remains as the backstop for the residue partitioning cannot remove. Add LocalNet::generate_blocks_converged and LocalNet::await_indexer_convergence. Mining returns as soon as the Validator has the blocks, while the Indexer syncs on its own cadence, so tests that read through the Indexer right after mining race it; the barrier blocks until the Indexer's chain index reports the Validator's tip, retiring that class of wallet-side polling workaround. The observation channel is zainod's "Syncing block, height: N" stdout line, read by Zainod::logged_sync_height after ANSI stripping, because the fetch backend proxies the protocol's height answers straight to the validator and the log is the only view of the Indexer's own progress. Failure is loud and precise by design: the new IndexerSyncError surfaces an unreadable log, a drifted log format carrying the offending line, or a timeout carrying the target height, the last observed height, and the log tail — never a silent hang. The log contract is captured from zainod 0.4.3-ironwood.1, pinned offline by parser unit tests against a recorded line, and pinned live by the generate_blocks_converged_reaches_validator_tip integration test. CONTEXT.md gains the "Indexer convergence" glossary term. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The External Types CI gate rejects any public API that references an external type missing from the crate's allowlist, and the previous commit exposed the new zingo_consensus::NetworkKind in two places: the ZainodConfig::network field and the protocol module's re-export. Add NetworkKind to zcash_local_net's allowed_external_types beside its sibling NetworkType. Verified locally with the same nightly toolchain and cargo check-external-types invocation the CI job runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI's nextest profile excludes integration tests by name (test(zebra) | test(zaino) | test(devtool_client)) because the runner image provisions no Zcash binaries, and the new convergence test's name matched no filter despite driving zebrad and zainod. Rename it to zainod_converges_to_validator_tip_after_generate_blocks so the existing filter covers it, and point the documentation references at the new name. Verified with cargo nextest list --profile ci that the test no longer runs under that profile, and locally that it still passes against the real binaries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Seventeen of the repository's eighteen shell scripts were dead weight: six operated on the long-gone fetched_resources binary tree, one inspected artifacts of the equally retired fetcher build script, and the whole zcash_local_net/utils directory was byte-identical copies of the root utils scripts plus a one-line nextest wrapper. The remaining one-line cargo wrappers had no references from CI, documentation, or code; the only still-meaningful one, the chain-cache generator, survives as the generate-chain-caches justfile recipe. The one script CI actually runs, utils/trailing-whitespace.sh, stays for the next commit, which ports it to a Rust workbench crate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The contributor guidelines require checked-in tooling that collaborators or CI run to be Rust in a workbench crate, and utils/trailing-whitespace.sh was the last shell script standing — the only one CI executed. Introduce the workbench workspace member (no dependencies, publish = false) with a trailing-whitespace subcommand preserving the script's exact contract: fix and reject modes, the .rs/.md/.toml/.yaml file set, .git and target pruned, spaces-only matching (tabs and CRLF are content), repo-root anchoring, and exit 1 on findings. Files are processed as bytes end to end, so non-UTF-8 content passes through untouched. Unit tests pin the stripping, detection, and walk-pruning behavior; a live run against the tree agrees with the shell script's verdict. The Trailing Whitespace CI workflow now runs the crate, and the script and the emptied utils directory are deleted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The whitespace job now compiles the workbench crate, so it gains the same Swatinem/rust-cache step the repository's other cargo-running workflows use. The first run on a branch pays the toolchain install and build once; subsequent runs restore the cache. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The justfile held two pieces of logic: the pinned-nightly cargo-check-external-types invocations for the two allowlisted crates, and the chain-cache generator recipe. Both become workbench subcommands. check-external-types checks every manifest even after a failure, so one run reports every offending crate, and the pinned nightly lives in one Rust constant whose documentation names the coupled cargo-check-external-types version and the CI job that installs both. generate-chain-caches wraps the ignored generator test. The External Types job now runs the workbench under its already-installed nightly and no longer installs just; the justfile is deleted, which leaves the repository with no shell scripts and no just glue — checked-in tooling is Rust in the workbench, per the contributor guidelines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rename the client layer to the Wallet abstraction: the client module becomes wallet, the Client and ClientConfig traits become Wallet and WalletConfig, and ClientError becomes WalletError, whose messages now name the operation rather than one binary. The trait is the interface through which the harness drives any wallet implementation, and implementations live with their binaries: the zcash-devtool one remains in-tree for now, and a zingo-cli implementation lands in the zingolib repository against zingolabs/zingolib#2419 (the request spec is added as zingolib-wallet-impl-spec.md; nothing implements the second wallet here). Add LocalNet::launch_wallet::<W>(make_config), the generic actuation path: it mints the WalletNetwork from the running Validator, wires the Indexer connection, and launches whichever implementation the caller names. The integration tests' helper now routes through it. Add ValidatorHeights::activation_heights(), a read-only accessor, because a foreign implementation must serialize the reported schedule into its own binary's configuration; construction remains private, so the ADR 0003 provenance guarantee is untouched. CONTEXT.md gains the Wallet glossary term. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat!: define the Wallet abstraction the harness actuates generically
…-validator hop zingolib's test suite runs a pipeline observatory: recording TCP relays on every hop of wallet <-> zainod <-> zebrad <-> harness, built to adjudicate an investigation into warm-run chain-cache failures whose signature is cross-wired RPC traffic between concurrently launching tests. Two hops are tappable from zingolib because it mints both endpoints, but the zainod->zebrad hop was not: LocalNet's launch wires the indexer to the validator internally, between the two process launches, so a consumer had no seam for interposing a relay. LocalNet::from_parts(validator, indexer) closes that gap. It is purely additive: the caller launches the validator, wires the indexer config's validator connection itself (possibly to a proxy), launches the indexer, and assembles; the assembled net's Drop stops both processes exactly as with launch_from_two_configs, whose behavior is unchanged. A regression test pins the seam end to end against the real binaries: it launches zebrad, interposes a minimal in-test tokio TCP relay in front of its JSON-RPC port, launches zainod against the relay, assembles via from_parts, mines to Indexer convergence, and asserts nonzero bytes crossed the relay — proving zainod really spoke to zebrad through the interposed hop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every published port and address accessor of the core stack now returns a per-listener front proxy instead of the process's own listener. The front binds 127.0.0.1:0 before its backend starts, so the public port is assigned atomically by the kernel and the public surface carries no check-then-bind race; the backend's real endpoint becomes a private detail of launch plumbing. Because the harness's own launch-time clients — the RPC readiness probe, the gRPC listener probe, and the regtest launch-mine — obtain their addresses through the same accessors, their traffic crosses the front too. That closes the structural blind spot motivating this change: traffic issued inside Process::launch used to dial the backend's real port before any external tap could exist, and no userspace relay can interpose a connection between endpoints that already know each other's real address (spec: front-proxy inversion, serving zingolib's pipeline observatory). The front is a transparent TCP relay with an observer registration point. A front::FrontObserver registered through the new ZebradConfig::rpc_front_observer or ZainodConfig::grpc_front_observer fields is in place before the backend starts and receives one front::ChunkEvent per relayed chunk (timestamp, connection id, direction, byte count, payload); the default is passthrough. The relay and the observer hook are built from std and tokio primitives already in the dependency tree, and no manifest changed. Each front relays on a dedicated thread driving a private single-threaded tokio runtime. Relaying must not depend on the launching runtime staying responsive: this crate's own wallet layer blocks its runtime thread in synchronous subprocess waits while the subprocess's traffic crosses the fronts, which deadlocked a runtime-hosted relay in the devtool wallet suite and would do the same to any consumer with a blocking wait. The front machinery is defined against a minimal crate-internal backend abstraction, backend::Backend, which adds log access and raw listener endpoints as socket addresses — never bare ports — to the Process lifecycle. A future container backend whose endpoints are published host mappings could implement it without changing a line of the front layer; processes remain the only implementors, and the trait stays crate-internal because exporting raw endpoints would reopen the hole the fronts close. Empirical port-0 findings reshape the allocator. zebrad 6.0.0-rc.0 binds every listener on port 0 and logs each assigned address, so unpinned zebrad listeners now bind port 0 and the harness discovers the raw addresses from the launch log — a log contract pinned by unit tests, with discovery failures surfacing as the new LaunchError::ListenerEndpointsUndiscovered. pick_unused_port is no longer used for any zebrad listener. zainod 0.4.3-ironwood.1 binds port 0 happily but never logs the assigned address, so its raw gRPC listener retains the picked-port allocator. The collision-retry machinery remains as the backstop for explicitly pinned ports, the only remaining collision surface. New tests pin the design's two claims. The decisive one, observer_on_zebrad_front_captures_the_launch_mine, registers an observer before launch and asserts the launch-mine's submitblock call appears in its record — the previously unobservable window, now observed. concurrent_zebrad_launches_are_collision_free launches six zebrads concurrently and asserts every published endpoint is distinct, the :0 guarantee. The existing launch, convergence, from_parts, collision-recovery, and devtool wallet suites pass behaviorally unchanged, now transiting fronts invisibly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ci nextest profile hid binary-requiring tests by test-name substring, which failed in both directions. The new from_parts integration test contains no marker substring, so CI ran it and it panicked spawning zebrad; meanwhile fifteen pure unit tests that merely mention a binary in their names were silently skipped. The profile now excludes the two binaries whose tests spawn real zcashd/zebrad/zainod/lightwalletd/devtool processes, zcash_local_net::integration and regtest-launcher::e2e, and the redundant -E exclusion leaves the test.yaml run command. The ci profile runs 71 tests locally, all passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Atop #277
NU6.3 activated across the regtest stack (breaking). The zebrad config writer emits "NU6.3" = when configured and omits the key when unset (so pre-6.0.0 zebrad configs stay parseable); the devtool activation-heights TOML gains nu6_3; activation_heights_from_getblockchaininfo reads "NU6.3" back. Canonical heights advance in lockstep (new ADR 0002): devtool-supported heights put NU6.3 at 2, test heights co-activate NU6.1/6.2/6.3 at 5, and the regtest-launcher's defaults and all= sweep include it.
Hardened the zebrad config writer (breaking). It now rejects activation heights it cannot express instead of silently dropping or rewriting them: upgrades through Canopy must be 1, and a configured NU7 panics until the writer gains NU7 emission. Regression tests pin NU6.3 emission/omission and both rejections (the silent-drop failure mode is what cost First NU6.3 block's coinbase is served as Orchard, not Ironwood (zebrad routing off-by-one, or a zaino pool-swap?) zaino#1368 three diagnostic rounds).
WalletBalance.ironwood_spendable, consumed from devtool balance --json. Requires zcash-devtool at or past 8eccaceb (support_ironwood_scan_model).
Shield test asserts the ironwood pool. With NU6.3 active, the shielded value plus coinbase subsidies accrue to ironwood_spendable, and orchard_spendable is asserted to stay 0 — consensus forbids value entering orchard from NU6.3 onward.
Binary floor / known gap: zebrad ≥ 6.0.0; zainod ≤ 0.4.2 cannot parse zebra 6.x getblockchaininfo (fixed 5-entry valuePools), so indexer-sync paths wait on a NU6.3-aware zainod (Regtest fixture has zero NU6.1 coverage — depends on zcash_local_net lockbox disbursements zaino#1076).
Docs: ADR 0002, CONTEXT.md, CHANGELOG Unreleased entries.