docs(lite): add v2 implementation plan, source-hygiene guard, and CLAUDE.md

- docs/lite-wallet-implementation-plan-v2-2026-06-04.md: vertical-slice plan that
  supersedes the v1 plan (now banner-marked); carries over the inherited artifact/
  signing/phase-2 design docs for reference.
- scripts/check-source-hygiene.sh: pre-commit/CI guard rejecting >80-char filenames
  and chained churn-token names, to stop the deleted "_plan"/"_batch" scaffolding
  from regrowing.
- CLAUDE.md: repository guidance for future sessions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 21:15:11 -05:00
parent e95ad50e41
commit a78a13edf3
12 changed files with 4290 additions and 0 deletions

79
CLAUDE.md Normal file
View File

@@ -0,0 +1,79 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
ObsidianDragon is a portable, full-node GUI wallet for DragonX (DRGX), written in C++17 using SDL3 + Dear ImGui (immediate-mode). It drives a `dragonxd` full node over JSON-RPC and can embed/extract the daemon itself. A separate **Lite** variant (`ObsidianDragonLite`) drops the full node and instead talks to an external lite-wallet backend library.
## Build & run
`build.sh` is the single entry point for all builds. `setup.sh` (repo root) installs/validates dependencies.
```bash
./build.sh # Dev build (native, no packaging) -> build/linux/bin/ObsidianDragon
./build.sh --lite # Dev build of the Lite variant -> build/linux/bin/ObsidianDragonLite
./build.sh --clean # Wipe the build dir first
./build.sh --linux-release # Release zip + AppImage -> release/linux/
./build.sh --win-release # Windows cross-compile (mingw-w64) -> release/windows/
./build.sh --mac-release # macOS .app bundle + DMG
./setup.sh --check # Report missing build deps without installing
```
Dev builds use `build/linux/` (or `build/mac/`). To re-build incrementally without re-running CMake config: `cmake --build build/linux -j$(nproc)`.
The wallet connects to the daemon using credentials in `~/.hush/DRAGONX/DRAGONX.conf` (`rpcuser`/`rpcpassword`/`rpcport`). It searches for `dragonxd`/`dragonx-cli` binaries in the **executable's own directory first**, so dropping custom node builds next to the wallet binary overrides the bundled ones.
## Tests
Tests live in `tests/test_phase4.cpp` — a single large translation unit using a custom assertion harness (`EXPECT_TRUE`/`EXPECT_EQ`/`EXPECT_NEAR` macros, one `main()`, exit code = failure count). `include(CTest)` enables `BUILD_TESTING=ON` by default, so the `ObsidianDragonTests` executable is built alongside the app.
```bash
cd build/linux && ctest --output-on-failure # run the suite
./build/linux/bin/ObsidianDragonTests # run the binary directly (same thing)
```
There is no per-test filtering — it is one binary that runs every assertion. The suite exercises the services layer, lite-wallet bridge, and pure helpers (parsers, formatters, model classes) without launching the GUI. Fixtures are under `tests/fixtures/` (path injected as `DRAGONX_TEST_FIXTURE_DIR`).
## Architecture
**Entry & main loop.** `src/main.cpp` owns SDL3 window creation, ImGui/OpenGL(or DX11 on Windows) setup, and the frame loop. The `App` class is the central controller; because it is large it is split across four files that all implement the same class:
- `src/app.cpp` — core lifecycle, the per-frame `render()`, tab dispatch
- `src/app_network.cpp` — RPC orchestration, sync, peers, daemon lifecycle
- `src/app_security.cpp` — encryption, PIN/lock screen, key import/export, backup
- `src/app_wizard.cpp` — first-run wizard
**RPC.** All daemon calls go through `src/rpc/` (`rpc_client`, `connection`, `rpc_worker`). **Never block the main/UI thread with synchronous network I/O — dispatch through `RPCWorker`** (async). `rpc/types.h` holds the shared DTOs.
**Services** (`src/services/`) hold the non-UI state machines that the `App` owns: `NetworkRefreshService` + `RefreshScheduler` (polling/refresh of balance, peers, txs on intervals) and the `WalletSecurity*` controller/workflow stack (encryption & unlock flows).
**Data model** (`src/data/`): `WalletState`, `address_book`, `transaction_history_cache`, `exchange_info`. UI reads from these.
**UI** (`src/ui/`): `windows/` are the tabs and dialogs (one pair per screen, e.g. `send_tab`, `mining_tab`, `console_tab`), `pages/` are multi-section screens (Settings), `screens/` are layout headers, `material/` is the design-system layer, `schema/` loads the TOML UI schema/skins, `effects/` is GL post-processing (blur/acrylic).
**Lite wallet** (`src/wallet/`): the bridge to an external `litelib_*` C-ABI backend (`lite_client_bridge`, `lite_bridge_runtime`, `lite_connection_service`, `lite_sync_service`, result parsers, and the artifact-contract/resolver that validates a prebuilt backend library). The real frontend entry points are `lite_wallet_lifecycle_ui_adapter` and `lite_wallet_server_selection_adapter` (used by `src/ui/pages/settings_page.cpp`); everything else is reachable through them.
> ⚠️ **Do not regrow the `_plan`/`_batch` churn.** This directory previously held ~160 dead `lite_wallet_*_plan` / `*_batch*_receipt_custody_acceptance_confirmation_archive_handoff_*` files (filenames up to 250 chars) — auto-generated scaffolding that never reached the shipping binary. They were deleted. When extending lite-wallet behavior, **edit the named service/bridge/runtime files in place**; never add another "promotion/receipt/custody/handoff/stewardship" wrapper layer. `scripts/check-source-hygiene.sh` (wired as a `.git/hooks/pre-commit` hook) blocks >80-char filenames and chained churn-token names — run it in CI too.
**Chat** (`src/chat/chat_protocol.cpp`): experimental HushChat protocol, compiled in only when `DRAGONX_ENABLE_CHAT=ON`.
## Build variants & feature gating
Variants are selected with CMake options (set by `build.sh` flags), surfaced to C++ as compile definitions:
- `DRAGONX_BUILD_LITE` (`--lite`) → `DRAGONX_LITE_BUILD` define; renames the app to `ObsidianDragonLite` and excludes embedded-daemon / full-node assets (Sapling params, asmap, dragonxd).
- `DRAGONX_ENABLE_LITE_BACKEND` → links a real external lite backend. Requires `--lite`, link mode `imported`, ABI `sdxl-c-v1`, and a symbols inventory file (built by `scripts/build-lite-backend-artifact.sh`); CMake hard-fails if any required `litelib_*` symbol is missing.
- `DRAGONX_ENABLE_CHAT``DRAGONX_ENABLE_CHAT` define gating the chat module.
Guard full-node-only code paths with `#if DRAGONX_LITE_BUILD` / chat code with `DRAGONX_ENABLE_CHAT`.
## Versioning
The version has a **single source of truth**: `project(... VERSION 1.2.0 ...)` plus `DRAGONX_VERSION_SUFFIX` in `CMakeLists.txt`. CMake generates `build/.../generated/dragonx_generated_version.h` from `src/config/version.h.in`. Do not hand-edit generated version output or hardcode version strings — bump the `project()` version in `CMakeLists.txt`.
## Conventions
- **C++17.** Match the surrounding code's style per file.
- **Icons:** use the Material Design icon font defines (`ICON_MD_*`); never raw Unicode glyphs.
- **UI layout values** belong in `res/themes/ui.toml`, read via `schema::UI()` — do not hardcode pixel sizes/offsets in code.
- **i18n:** user-facing strings are translated via `src/util/i18n`; translation JSON lives in `res/lang/` (`de`, `es`, `fr`, `ja`, `ko`, `pt`, `ru`, `zh`, English fallback in code). Translation/font helper scripts are in `scripts/` (`gen_*.py`, CJK subset tooling).
- **Commits:** the history uses Conventional Commits (`feat(scope): …`, `fix(scope): …`). PRs target `master`.

View File

@@ -0,0 +1,628 @@
# HushChat Protocol Spec (2026-05-06)
Scope:
- Document the memo protocol observed in `external/SilentDragonXLite` for a future safe ImGui implementation.
- Batch 1 implements header parsing/validation only.
- Batch 2 implements transaction-output grouping for validated header/payload memo pairs only.
- Batch 3 implements read-only transaction extraction as sanitized refresh metadata behind `DRAGONX_ENABLE_CHAT`.
- Batch 4 implements pre-decryption validation/error typing only.
- Batch 5 implements decrypt-input byte boundary preparation and fixture readiness only.
- Batch 6 implements non-sensitive cross-wallet fixture shape verification only.
- Batch 7 implements fixture-file loading for pending and ready compatibility vectors.
- Batch 8 implements a strict import checklist for replacing pending fixture placeholders only when all required categories are supplied.
- Batch 9 implements a no-plaintext SDXL seed/public-key projection verifier for ready fixture files.
- Batch 10 implements an inert corrupted-authentication-failure readiness scaffold for future secretstream auth failure checks.
- Batch 11 implements a strict ready-fixture replacement dry-run report for real non-sensitive SDXL vectors.
- Batch 12 implements a redacted capture-manifest validator for staged real-vector directories.
- These batches do not decrypt, store, render, or send chat messages.
## Reference Sources
- `external/SilentDragonXLite/src/chatmodel.cpp`
- `MainWindow::createHeaderMemo(...)`
- `MainWindow::createTxFromChatPage()`
- `MainWindow::createTxForSafeContactRequest()`
- `external/SilentDragonXLite/src/controller.cpp`
- `Controller::refreshTransactions()` outgoing/incoming memo handling
- `external/SilentDragonXLite/src/DataStore/ChatDataStore.*`
- `external/SilentDragonXLite/src/Model/ChatItem.*`
- `external/SilentDragonXLite/src/Model/ContactItem.*`
- `external/SilentDragonXLite/src/Model/ContactRequest.*`
## Transaction Shape
SilentDragonXLite sends HushChat data as shielded transactions with paired memo outputs:
1. Header memo: UTF-8 JSON object beginning with `{`.
2. Payload memo: non-JSON string paired with the same transaction.
For encrypted chat messages, the payload memo is libsodium secretstream ciphertext encoded as hex. For contact requests, the payload memo is plaintext request text in the reference implementation.
The reference may also add unrelated dust/noise memo outputs around the chat pair, so a future parser must group by transaction, output order/position, and validated header metadata rather than assuming the transaction contains only two outputs.
## Header JSON
`createHeaderMemo(...)` emits short field names to preserve memo space:
```json
{
"h": 1,
"v": 0,
"z": "zs-reply-address",
"cid": "conversation-id",
"t": "Memo",
"e": "48 hex chars for message headers, empty for contact requests",
"p": "64 hex chars"
}
```
Fields:
- `h`: integer header number, starting at `1`.
- `v`: integer HushChat version. Current reference uses `0`.
- `z`: sender reply z-address.
- `cid`: conversation/contact id.
- `t`: type string. Known values are `Memo` and `Cont`.
- `e`: hex-encoded `crypto_secretstream_xchacha20poly1305_HEADERBYTES` value. Present for `Memo`, empty for `Cont` in the reference.
- `p`: hex-encoded `crypto_kx_PUBLICKEYBYTES` public key generated from `crypto_kx_seed_keypair()`.
## Type Semantics
`Memo`:
- Header includes non-empty `e` with 24 bytes encoded as 48 hex characters.
- Payload memo is ciphertext encoded as hex.
- Reference derives a deterministic keypair from the wallet/chat password, derives a shared session key with the peer public key, and decrypts via `crypto_secretstream_xchacha20poly1305_pull()`.
`Cont`:
- Header includes empty `e`.
- Payload memo is the contact request text.
- Header `z`, `cid`, and `p` are used to create/update a contact record.
## SilentDragonXLite Decryption Audit
Reference initialization:
- `main.cpp` calls `sodium_init()` at startup and aborts if libsodium cannot initialize.
Reference chat key material:
- Wallet/chat passphrase input is wrapped with fixed strings (`DRGX` + user passphrase + `SDXL`).
- The wrapped passphrase is hashed with `blake3_PW(...)`.
- `crypto_pwhash(...)` is then called with `crypto_pwhash_OPSLIMIT_SENSITIVE`, `crypto_pwhash_MEMLIMIT_SENSITIVE`, and `crypto_pwhash_ALG_DEFAULT`, producing `crypto_box_SEEDBYTES` bytes.
- The resulting bytes are hex-encoded and stored in `ChatDataStore::_password` for later HushChat operations.
Reference `Memo` send path:
- `ChatDataStore::getPassword()` returns the stored hex chat key string.
- The Qt code passes `passphraseHash.toUtf8()` directly as the seed input to `crypto_kx_seed_keypair(pk, sk, seed)`.
- The peer public key is loaded from contact/address-book metadata using `QByteArray::fromHex(...)`.
- Sending uses `crypto_kx_server_session_keys(server_rx, server_tx, pk, sk, peer_pk)` and encrypts with `server_tx`.
- `crypto_secretstream_xchacha20poly1305_init_push(&state, header, server_tx)` creates the 24-byte stream header.
- `crypto_secretstream_xchacha20poly1305_push(..., TAG_FINAL)` encrypts the UTF-8 message, producing `crypto_secretstream_xchacha20poly1305_ABYTES + plaintext_length` bytes.
- The header memo stores `e` as the stream header hex and `p` as the sender public key hex.
- The payload memo stores ciphertext hex.
Reference outgoing-history decrypt path:
- Re-derives the local deterministic keypair from `ChatDataStore::getPassword()`.
- Looks up the peer public key by address.
- Uses `crypto_kx_server_session_keys(...)` and decrypts with `server_tx`.
- Uses `crypto_secretstream_xchacha20poly1305_init_pull(&state, header, server_tx)` and `crypto_secretstream_xchacha20poly1305_pull(...)`.
- Stores decrypted text as `ChatItem` in `ChatDataStore`.
Reference incoming decrypt path:
- Parses header memo fields `cid`, `t`, `z`, `e`, and `p` from transaction memo JSON.
- Stores/caches `cid`, reply z-address, secretstream header, and sender public key.
- Re-derives the local deterministic keypair from `ChatDataStore::getPassword()`.
- Uses `crypto_kx_client_session_keys(client_rx, client_tx, pk, sk, peer_pk)` and decrypts with `client_rx`.
- Uses `crypto_secretstream_xchacha20poly1305_init_pull(&state, header, client_rx)` and `crypto_secretstream_xchacha20poly1305_pull(...)`.
- Stores decrypted text/contact request text as `ChatItem` in `ChatDataStore`.
Open questions before real decryption:
- Confirm the `crypto_pwhash(...)` salt input length and derivation semantics from the BLAKE3 output. The reference passes a pointer derived from a string without an explicit salt object.
- Confirm outgoing-vs-incoming role selection (`server_tx` for sends/outgoing history, `client_rx` for received messages) against cross-wallet fixtures.
- Decide whether new DragonX-originated messages must preserve SilentDragonXLite's UTF-8-hex seed behavior forever or can support a versioned decoded-byte seed later.
- Decide whether Batch 3 metadata fields `cid` and reply z-address are safe to persist, hash, or keep transient only.
- Decide how corrupted ciphertext/header failures should surface to users without logging memo contents or plaintext.
## Batch 1 Parser Contract
Implemented parser: `dragonx::chat::parseHushChatHeaderMemo()`.
The parser accepts only the clear, structural header format:
- memo must be non-empty JSON object text beginning with `{`
- maximum memo size is 512 bytes
- required fields must exist and have expected JSON types
- only version `0` is accepted
- only `Memo` and `Cont` types are accepted
- public key must be 32 bytes encoded as 64 hex characters
- `Memo` must include a 24 byte secretstream header encoded as 48 hex characters
- `Cont` must have an empty secretstream header
The parser intentionally does not:
- decrypt ciphertext
- derive keys
- parse contact request payload text
- write chat storage
- expose UI
- log memo contents
## Batch 2 Transaction Grouping Contract
Implemented grouping helper: `dragonx::chat::groupHushChatMemoOutputs()`.
Inputs:
- one transaction's memo outputs as `(position, memo)` records
- output `position` should be the wallet/RPC output position where available
- equal positions retain caller order as a stable fallback
Output:
- zero or more validated HushChat header/payload pairs
- grouping issues for malformed or incomplete HushChat-looking data
- ignored memo count for unrelated dust/noise payloads
Grouping rules:
- outputs are processed in ascending output position
- non-header memos before any valid header are ignored as dust/noise
- memos larger than 512 bytes produce `OversizedMemo` and are skipped
- memos beginning with `{` must parse as valid HushChat headers or produce `InvalidHeader`
- a valid header opens a pending pair
- a valid `Memo` header pairs with the next non-header memo that is non-empty, <=512 bytes, even-length hex
- a valid `Cont` header pairs with the next non-header memo that is non-empty and <=512 bytes
- non-matching payload candidates after a pending `Memo` header are ignored so unrelated dust/noise can appear before ciphertext
- a second valid header before a payload produces `DuplicateHeader`; the previous header also produces `MissingPayload`, then the new header becomes pending
- a valid header left pending at end-of-transaction produces `MissingPayload`
The grouping helper intentionally does not:
- decrypt ciphertext
- inspect contact request payload text
- decide whether a pair belongs to the local wallet
- write storage
- trigger notifications/UI
- run from the transaction refresh pipeline while `DRAGONX_ENABLE_CHAT` is off
## Batch 3 Transaction Extraction Contract
Implemented extractor: `dragonx::chat::extractHushChatTransactionMetadata()`.
Inputs:
- one transaction id
- one transaction's memo outputs as `(position, memo)` records
- an explicit feature-enabled switch, defaulting to the build-time `DRAGONX_ENABLE_CHAT` value
Output when enabled:
- zero or more sanitized transaction metadata records
- sanitized grouping issues for HushChat-looking malformed data
- ignored memo count for unrelated dust/noise payloads
Sanitized metadata fields:
- transaction id
- HushChat type (`Memo` or `Cont`)
- conversation id
- reply z-address
- header output position
- payload output position
- payload memo byte length only
The extractor intentionally does not expose:
- payload memo contents
- secretstream header hex
- peer public key hex
- decrypted plaintext
- derived keys
Refresh integration:
- `NetworkRefreshService::TransactionRefreshResult` carries transient `hushChatMetadata` records.
- Received shielded memo outputs and outgoing `z_viewtransaction` memo outputs are eligible for extraction only while `DRAGONX_ENABLE_CHAT` is enabled.
- The default build leaves `hushChatMetadata` empty and does not change transaction history, caches, wallet state, UI, sends, or logs.
- `applyTransactionRefreshResult()` currently ignores chat metadata, so no Batch 3 data is persisted.
## Batch 4 Pre-Decryption Safety Contract
Implemented preflight helper: `dragonx::chat::validateHushChatMemoDecryptPreflight()`.
Inputs:
- parsed HushChat header metadata
- ciphertext payload memo hex
- an explicit feature-enabled switch, defaulting to the build-time `DRAGONX_ENABLE_CHAT` value
Output:
- `ok=true` only when the input is structurally safe to hand to a future decrypt implementation
- typed errors for feature-disabled, non-`Memo` header, invalid header number/version, missing reply address/conversation id, malformed secretstream header, malformed public key, empty ciphertext, oversized ciphertext, odd-length ciphertext, non-hex ciphertext, and truncated ciphertext
- decoded ciphertext byte length only on success
Validation rules:
- no work is performed while `DRAGONX_ENABLE_CHAT` is off
- only `Memo` headers are valid for decrypt preflight; `Cont` remains plaintext/contact-request handling for later batches
- secretstream header metadata must remain 24 bytes encoded as 48 hex characters
- peer public key metadata must remain 32 bytes encoded as 64 hex characters
- ciphertext memo must be non-empty, <=512 bytes as memo text, even-length hex, and decode to more than `crypto_secretstream_xchacha20poly1305_ABYTES` bytes
The preflight helper intentionally does not:
- call libsodium
- derive keypairs or session keys
- decode or return ciphertext bytes
- decrypt plaintext
- store chat/contact data
- render UI
- send messages
- log memo contents or plaintext
## Batch 5 Decrypt-Input Boundary Contract
Implemented helpers:
- `dragonx::chat::decodeHushChatHexBytes()`
- `dragonx::chat::prepareHushChatDecryptInput()`
- `dragonx::chat::inspectHushChatDecryptFixtureReadiness()`
- `dragonx::chat::hushChatSessionKeySelectionForDirection()`
Inputs:
- stored chat key material as the 64-character hex string currently stored by SilentDragonXLite `ChatDataStore::getPassword()`
- parsed `Memo` header metadata
- ciphertext payload memo hex
- decrypt direction (`Incoming` or `Outgoing`)
- an explicit feature-enabled switch, defaulting to the build-time `DRAGONX_ENABLE_CHAT` value
Prepared fields:
- `stored_chat_key_bytes`: decoded 32 bytes from the stored chat key hex string
- `seed_bytes`: SilentDragonXLite-compatible `crypto_kx_seed_keypair()` seed bytes
- `peer_public_key_bytes`: decoded 32 bytes from header field `p`
- `stream_header_bytes`: decoded 24 bytes from header field `e`
- `ciphertext_bytes`: decoded ciphertext payload bytes
- `session_key_selection`: `ClientRx` for incoming messages, `ServerTx` for outgoing-history messages
- `plaintext_capacity`: ciphertext byte length minus `crypto_secretstream_xchacha20poly1305_ABYTES`
Seed compatibility decision:
- SilentDragonXLite stores a 32-byte `crypto_pwhash(...)` output as 64 lowercase hex characters.
- SilentDragonXLite then passes `ChatDataStore::getPassword().toUtf8()` directly to `crypto_kx_seed_keypair(...)`.
- Because `crypto_kx_seed_keypair(...)` consumes 32 bytes, DragonX's compatibility contract prepares `seed_bytes` as the first 32 UTF-8 bytes of the stored 64-character hex string.
- The decoded 32-byte stored key is kept separate as `stored_chat_key_bytes` so future code cannot accidentally conflate the two interpretations.
Validation rules:
- no work is performed while `DRAGONX_ENABLE_CHAT` is off
- stored chat key hex must decode to exactly 32 bytes
- Batch 4 preflight must pass before any payload decoding
- peer public key must decode to exactly 32 bytes
- stream header must decode to exactly 24 bytes
- ciphertext must decode to the preflight-reported byte length
- incoming direction selects `client_rx`; outgoing-history direction selects `server_tx`
- fixture readiness requires all prepared byte fields to have exact lengths and ciphertext to contain more than secretstream authentication overhead
The Batch 5 helpers intentionally do not:
- call libsodium
- derive keypairs or session keys
- decrypt plaintext
- store key material, ciphertext, chat, or contact data outside temporary result objects
- render UI
- send messages
- log key material, memo contents, ciphertext bytes, or plaintext
Required cross-wallet fixtures before real decryption:
- SilentDragonXLite outgoing `Memo` fixture with stored chat key hex, local public key, peer public key, header memo, ciphertext memo, direction, expected plaintext byte length, and a non-sensitive plaintext hash.
- SilentDragonXLite incoming `Memo` fixture with the same metadata plus role expectation (`client_rx`).
- Round-trip fixture between SilentDragonXLite and DragonX confirming the UTF-8-hex seed projection produces the observed public key.
- Corrupted fixture variants for bad peer public key, bad stream header, truncated ciphertext, and authentication failure.
- Contact-request fixture proving `Cont` remains outside encrypted `Memo` decrypt handling.
## Batch 6 Cross-Wallet Fixture Contract
Implemented verifier: `dragonx::chat::verifyHushChatCompatibilityFixture()`.
Static fixture fields:
- fixture id
- stored chat key hex
- local public key hex
- peer public key hex
- header memo
- ciphertext memo
- direction (`Incoming` or `Outgoing`)
- expected session key selection (`ClientRx` or `ServerTx`)
- expected stored chat key byte length
- expected seed byte length
- expected local public key byte length
- expected peer public key byte length
- expected stream header byte length
- expected ciphertext byte length
- expected plaintext byte length
- optional expected plaintext hash hex
Verifier behavior:
- no work is performed while `DRAGONX_ENABLE_CHAT` is off
- fixture id must be present so test/report output can identify the vector without logging memo contents
- local public key hex must decode to exactly 32 bytes
- peer public key hex must decode to exactly 32 bytes
- header memo is parsed through `parseHushChatHeaderMemo()`
- `(header memo, ciphertext memo)` is grouped through `groupHushChatMemoOutputs()`
- only `Memo` fixtures are accepted; `Cont` fixtures are rejected as outside encrypted decrypt handling
- for incoming fixtures, header field `p` must match the fixture peer public key
- for outgoing-history fixtures, header field `p` must match the fixture local public key
- decrypt input is prepared through `prepareHushChatDecryptInput()` with the fixture peer public key kept separate from the header public key
- readiness is checked through `inspectHushChatDecryptFixtureReadiness()`
- expected byte lengths and expected role must match the prepared/readiness output exactly
- optional plaintext hash must be hex text only; the verifier records its byte length but never evaluates plaintext
The Batch 6 verifier intentionally does not:
- derive a keypair
- derive session keys
- call libsodium
- decrypt ciphertext
- compare plaintext
- persist fixture data
- render UI
- send messages
- log key material, memo contents, ciphertext bytes, plaintext hashes, or plaintext
Remaining requirements before real decryption:
- collect real SilentDragonXLite incoming and outgoing `Memo` fixtures using non-sensitive plaintext and recorded hashes
- verify that the observed local public key matches the UTF-8-hex seed projection in an isolated test vector
- add corrupted authentication-failure fixtures that pass structural validation but must fail future `crypto_secretstream_xchacha20poly1305_pull()`
- decide how fixture hashes will be generated and compared without retaining plaintext in the repository
- keep contact-request fixtures separate from encrypted `Memo` fixtures
## Batch 7 Fixture File Import Contract
Checked-in fixture files live under `tests/fixtures/hushchat/`. The current files are intentionally `pending` placeholders for real, non-sensitive SilentDragonXLite vectors. They prove the import format and required categories without committing key material, memo contents, ciphertext bytes, plaintext, or made-up compatibility data.
The loader-supported format is JSON. The field names are intentionally TOML-friendly and the equivalent TOML shape is documented below for future fixture capture tooling.
Required top-level fields:
- `schema`: must be `dragonx.hushchat.compat-fixture.v1`
- `kind`: one of `incoming_memo`, `outgoing_memo`, `seed_public_key_projection`, `corrupted_auth_failure`, or `cont_exclusion`
- `status`: `pending` or `ready`
- `id`: required for `pending` files
- `pending_reason`: required for `pending` files and must explain what real SDXL data is still missing
- `fixture`: required for `ready` files
Ready JSON shape:
```json
{
"schema": "dragonx.hushchat.compat-fixture.v1",
"status": "ready",
"kind": "incoming_memo",
"fixture": {
"id": "sdxl-incoming-memo-example",
"stored_chat_key_hex": "<64 hex chars from SDXL test wallet>",
"local_public_key_hex": "<64 hex chars>",
"peer_public_key_hex": "<64 hex chars>",
"header_memo": "<header memo JSON string>",
"ciphertext_memo": "<payload memo hex string>",
"direction": "Incoming",
"expected": {
"session_key_selection": "ClientRx",
"stored_chat_key_bytes": 32,
"seed_bytes": 32,
"local_public_key_bytes": 32,
"peer_public_key_bytes": 32,
"stream_header_bytes": 24,
"ciphertext_bytes": 21,
"plaintext_bytes": 4,
"plaintext_hash_hex": "<optional 64 hex chars>"
}
}
}
```
Equivalent TOML shape for future tooling:
```toml
schema = "dragonx.hushchat.compat-fixture.v1"
status = "ready"
kind = "incoming_memo"
[fixture]
id = "sdxl-incoming-memo-example"
stored_chat_key_hex = "<64 hex chars from SDXL test wallet>"
local_public_key_hex = "<64 hex chars>"
peer_public_key_hex = "<64 hex chars>"
header_memo = "<header memo JSON string>"
ciphertext_memo = "<payload memo hex string>"
direction = "Incoming"
[fixture.expected]
session_key_selection = "ClientRx"
stored_chat_key_bytes = 32
seed_bytes = 32
local_public_key_bytes = 32
peer_public_key_bytes = 32
stream_header_bytes = 24
ciphertext_bytes = 21
plaintext_bytes = 4
plaintext_hash_hex = "<optional 64 hex chars>"
```
Ready `incoming_memo`, `outgoing_memo`, `seed_public_key_projection`, and `corrupted_auth_failure` fixture files are accepted only after the Batch 6 verifier succeeds. That means the file is parsed through the Batch 1 header parser, Batch 2 memo grouper, Batch 4 preflight checks, Batch 5 decrypt-input preparation, and Batch 6 readiness/byte-length/role checks. `cont_exclusion` is accepted only when the verifier rejects the fixture as `NonMemoHeader`, proving contact requests stay outside encrypted Memo decrypt handling.
The fixture loader does not derive keys, call libsodium, decrypt, compare plaintext, persist chat data, render UI, send messages, or log key material/memo contents/plaintext/ciphertext bytes. It reports `pending` for missing real vectors and `verified` only for structurally ready vectors.
Exact data still needed from SilentDragonXLite before changing pending files to ready:
- a non-sensitive incoming `Memo` vector with stored chat key hex, local public key hex, peer public key hex, header memo, ciphertext memo, expected `ClientRx` role, expected byte lengths, and optional plaintext hash
- a non-sensitive outgoing-history `Memo` vector with the same structural fields and expected `ServerTx` role
- a seed/public-key projection vector proving the SDXL UTF-8-hex seed input produces the recorded local public key, without committing passphrase or plaintext
- a corrupted authentication-failure vector that passes structural validation but must fail future secretstream pull authentication
- a `Cont` vector proving contact requests remain excluded from encrypted Memo decrypt preparation
- the exact capture command/procedure from SDXL so future vectors are reproducible without exposing sensitive wallet data
## Batch 8 Fixture Import Checklist Contract
Implemented import gate:
- `dragonx::chat::hushChatRequiredCompatibilityFixtureKinds()`
- `dragonx::chat::inspectHushChatCompatibilityFixtureImportChecklist()`
- developer tool target `HushChatFixtureCheck`
The import checklist is the conservative replacement path for the pending JSON placeholders. It accepts a caller-supplied candidate file for each expected category and loads every file through `loadHushChatCompatibilityFixtureFile()`. It reports replacement-ready only when:
- all five required categories are supplied exactly once
- no required category is missing
- no duplicate category candidate is supplied
- no supplied file is malformed or unreadable
- no file remains `pending`
- each non-`Cont` ready file passes the Batch 7 loader and Batch 6 structural verifier
- each non-`Cont` ready file passes the Batch 9 seed/public-key projection verifier
- the `corrupted_auth_failure` ready file is marked structurally ready for a future secretstream authentication-failure check, without decrypting or authenticating it now
- the `cont_exclusion` ready file is accepted only by being excluded from encrypted Memo handling
The current checked-in placeholder directory is expected to pass only with pending allowed:
```sh
./build/bin/HushChatFixtureCheck --allow-pending tests/fixtures/hushchat
```
Before replacing pending placeholders with real vectors, the strict check must pass:
```sh
./build/bin/HushChatFixtureCheck tests/fixtures/hushchat
```
The tool intentionally prints only category names, file basenames, status/error names, and aggregate counts. It does not print key material, memo contents, plaintext, ciphertext bytes, plaintext hashes, or derived bytes. The helper/tool does not derive session keys, decrypt, persist chat data, render UI, send messages, or enable `DRAGONX_ENABLE_CHAT` in the app; Batch 9 uses libsodium only for the local seed/public-key projection check.
Capture checklist for real SDXL fixtures is tracked in `tests/fixtures/hushchat/IMPORT_CHECKLIST.md`. Pending files may be replaced only with non-sensitive, disposable-wallet vectors that pass the strict checklist. If any category is pending, missing, malformed, mismatched, duplicated, or not verified, the replacement gate remains closed.
## Batch 9 Seed/Public-Key Projection Contract
Implemented verifier:
- `dragonx::chat::verifyHushChatSeedPublicKeyProjection()`
The projection verifier is a no-plaintext compatibility check for ready non-`Cont` fixture files. It accepts the existing fixture fields only:
- fixture id
- stored SDXL chat key hex string
- declared local public key hex
- expected stored chat key byte length
- expected seed byte length
- expected local public key byte length
Verification rules:
- no work is performed while `DRAGONX_ENABLE_CHAT` is off unless a caller explicitly supplies the test/import feature switch
- fixture id must be present
- stored chat key hex must decode to the expected 32-byte stored key length
- seed bytes are exactly the first 32 UTF-8 bytes of the stored 64-character SDXL chat key hex string
- seed byte length must match the fixture's declared expected seed length
- local public key hex must decode to the expected 32-byte local public key length
- libsodium is initialized only for this local public-key projection check
- `crypto_kx_seed_keypair()` is called with the SDXL-compatible seed bytes
- only the projected public key is compared to the declared local public key
- the derived secret key, temporary seed bytes, and temporary projected public key buffer are zeroed before returning
The verifier intentionally does not:
- derive session keys
- decrypt ciphertext
- compare plaintext
- store chat/contact data
- render UI
- send messages
- log or return key material, memo contents, plaintext, ciphertext bytes, plaintext hashes, seed bytes, public key bytes, or secret key bytes
Batch 9 integration:
- `inspectHushChatCompatibilityFixtureImportChecklist()` now requires all four non-`Cont` ready fixtures to pass the seed/public-key projection verifier.
- The developer checker reports only the aggregate `seed_projected` count.
- Pending files still block strict replacement until real SilentDragonXLite vectors are supplied.
## Batch 10 Corrupted Authentication-Failure Scaffold
Implemented scaffold:
- `dragonx::chat::inspectHushChatCorruptedAuthFailureReadiness()`
The corrupted-auth scaffold is an inert marker for ready `corrupted_auth_failure` fixture files. It exists so a real non-sensitive SilentDragonXLite corrupted vector can be carried through the current structural pipeline and reserved for a future `crypto_secretstream_xchacha20poly1305_pull()` authentication-failure assertion.
Readiness rules:
- no work is performed while `DRAGONX_ENABLE_CHAT` is off unless a caller explicitly supplies the test/import feature switch
- pending fixtures are rejected as not ready
- only `corrupted_auth_failure` fixture files are accepted
- the fixture file must already be parsed and verified through the Batch 7 loader and Batch 6 fixture verifier
- the fixture must already pass the Batch 9 seed/public-key projection verifier
- success sets `structurally_ready_for_future_auth_check=true`
- success sets `requires_future_secretstream_auth_failure=true`
- success keeps `decrypted=false` and `authenticated=false`
The scaffold intentionally does not:
- derive session keys
- initialize a secretstream pull state
- decrypt ciphertext
- authenticate ciphertext
- compare plaintext
- store chat/contact data
- render UI
- send messages
- log or return key material, memo contents, plaintext, ciphertext bytes, plaintext hashes, seed bytes, public key bytes, or secret key bytes
Batch 10 integration:
- `inspectHushChatCompatibilityFixtureImportChecklist()` now requires the corrupted fixture to be structurally ready for future auth-failure validation before replacement can be ready.
- The developer checker reports aggregate `future_auth_required` and `auth_structural_ready` counts, and per-file labels only for those states.
- These counts do not mean the fixture has decrypted or authenticated; they mean it is ready for a future auth-failure assertion once decrypt support exists.
## Batch 11 Strict Replacement Dry-Run Report
Implemented dry-run workflow:
- `dragonx::chat::inspectHushChatCompatibilityFixtureReplacementDryRun()`
- `./build/bin/HushChatFixtureCheck --replacement-dry-run <fixture-file-or-directory>...`
The dry-run workflow wraps the Batch 10 import checklist and reports whether a supplied ready-vector directory would be eligible to replace the checked-in pending placeholders. It is strict by design: the CLI refuses `--replacement-dry-run` when `--allow-pending` is also supplied, and the dry-run result only sets `would_replace=true` when the underlying checklist is fully `replacement_ready`.
Replacement is refused if any required vector is:
- missing
- duplicated
- unreadable or malformed
- supplied under the wrong expected kind
- still marked `pending`
- not verified through the fixture loader and structural contracts
- unable to pass the SDXL seed/public-key projection check
- a `corrupted_auth_failure` vector that is not structurally ready for a future auth-failure check
- a `cont_exclusion` vector that is not excluded from encrypted `Memo` handling
The replacement report is intentionally redacted. It may print only category names, file basenames, status/error names, boolean decision flags, and aggregate counts. It must not print fixture ids, stored chat key material, memo contents, secretstream headers, public key bytes, plaintext hashes, plaintext, ciphertext bytes, seed bytes, derived secret keys, or derived session keys.
The dry-run workflow intentionally does not:
- copy or overwrite fixture files
- derive session keys
- initialize a secretstream pull state
- decrypt ciphertext
- authenticate ciphertext
- compare plaintext
- store chat/contact data
- render UI
- send messages
- enable `DRAGONX_ENABLE_CHAT` in the app
## Batch 12 Capture Manifest Workflow
Implemented manifest validator:
- `dragonx::chat::validateHushChatCaptureManifest()`
- `dragonx::chat::loadHushChatCaptureManifestFile()`
- `./build/bin/HushChatFixtureCheck --validate-capture-manifest <manifest-file-or-directory>`
The capture manifest is a redacted provenance and handling record for a staged directory of real disposable SilentDragonXLite fixture files. It does not validate fixture contents and does not replace the Batch 11 strict replacement dry run. Instead, it verifies that the staged directory has the required metadata before strict fixture validation is used.
The checked-in template is `tests/fixtures/hushchat/templates/capture-manifest.template.json`. The template is intentionally marked `template`; a staged copy must be named `capture-manifest.json`, set top-level `status` to `staged`, and set each required category status to `ready` before the validator accepts it.
Required manifest fields:
- schema `dragonx.hushchat.capture-manifest.v1`
- top-level status `staged`
- redacted manifest id
- staged fixture directory name
- dry-run command containing `HushChatFixtureCheck --replacement-dry-run`
- provenance object with source client `SilentDragonXLite`, source client version or commit, capture date, network, and capture method
- handling object with all required safety flags set to `true`
- exactly one category entry for each required fixture kind, each with a staged filename and status `ready`
Rejected manifest conditions:
- unreadable, non-object, or malformed JSON
- wrong schema
- template or unknown status
- missing provenance, handling, dry-run, fixture-directory, or category metadata
- missing, duplicate, or unknown required category entries
- any known prohibited fixture-data field such as `fixture`, `stored_chat_key_hex`, `header_memo`, `ciphertext_memo`, `plaintext`, `passphrase`, private-key fields, wallet-file fields, derived-key fields, or session-key fields
The manifest validator prints only redacted status/error names, counts, category names, basenames, and boolean safety flags. It intentionally does not inspect or print fixture contents, key material, memo contents, plaintext, ciphertext bytes, plaintext hashes, derived keys, session keys, or wallet files. It also does not copy files, decrypt, authenticate, derive session keys, store chat/contact data, render UI, send messages, or enable `DRAGONX_ENABLE_CHAT` in the app.
The staged-vector workflow is:
```sh
./build/bin/HushChatFixtureCheck --validate-capture-manifest /path/to/staged/hushchat-fixtures
./build/bin/HushChatFixtureCheck --replacement-dry-run /path/to/staged/hushchat-fixtures
```
Both commands must succeed before replacing the checked-in pending placeholders.
## Remaining Implementation Batches
1. Capture real non-sensitive SilentDragonXLite fixture files and replace pending placeholders only after the Batch 12 manifest validator and Batch 11 strict dry-run report both succeed.
2. Add the real secretstream auth-failure assertion for ready corrupted fixtures after decrypt support exists.
3. Implement encrypted message decryption only after fixture confirmation.
4. Add wallet-scoped chat/contact storage.
5. Add read-only ImGui Chat UI.
6. Add contact request actions.
7. Add send/composer flow after receive-side validation.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,97 @@
# Lite Wallet Backend Artifact Link Contract - 2026-05-18
## Phase 1 Decision
The first production backend path is an explicitly configured imported CMake library. Runtime dynamic loading remains a Phase 2 bridge-runtime-owner task. External executable bridge mode is not part of the Phase 1 production contract.
The default build remains unchanged: `DRAGONX_ENABLE_LITE_BACKEND=OFF`. Full-node builds do not require Rust, SDXL, or lite backend artifacts.
## CMake Gate
The enabled lite backend path requires:
- `DRAGONX_BUILD_LITE=ON`
- `DRAGONX_ENABLE_LITE_BACKEND=ON`
- `DRAGONX_LITE_BACKEND_LIBRARY=<path to static or shared library>`
- `DRAGONX_LITE_BACKEND_SYMBOLS_FILE=<path to generated symbol inventory>`
- `DRAGONX_LITE_BACKEND_LINK_MODE=imported`
- `DRAGONX_LITE_BACKEND_ABI=sdxl-c-v1`
- `DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE=ON` only when a release builder wants CMake to require verified signature metadata in the manifest
- `DRAGONX_LITE_BACKEND_EXTRA_LIBS=<verified platform extras>` only when a platform linker proves additional backend-native dependencies are required
When those values are accepted, CMake imports the library as `dragonx_lite_backend` and links it into `ObsidianDragonLite`. No runtime dynamic loading is attempted by this Phase 1 contract.
`DRAGONX_LITE_BACKEND_MANIFEST=<path to generated artifact manifest>` is optional, but if supplied it must exist. The symbol inventory is mandatory so enabled backend builds fail closed before link-time or runtime when the artifact does not expose the expected C ABI. If `DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE=ON`, the manifest becomes mandatory and CMake rejects it unless the read-only signature metadata is verified and matches the artifact SHA-256.
`DRAGONX_LITE_BACKEND_EXTRA_LIBS` is intentionally a cache string instead of hardcoded platform guesses. Release builders should keep it empty for platforms that link successfully with the imported artifact and existing app/system libraries, and should document any non-empty value next to the platform artifact manifest that required it.
## Artifact Production
`scripts/build-lite-backend-artifact.sh` builds or inventories the SDXL-compatible backend artifact and writes:
- the copied backend library artifact,
- `lite-backend-symbols.txt`, one exported symbol per line,
- `lite-backend-artifact-manifest.json`, including SHA-256, byte size, source revisions, builder label, ABI, platform, and provenance facts.
See `docs/lite-wallet-backend-artifact-production-2026-05-18.md` for platform commands and the no-build inventory mode.
The relative backend source layout and signature metadata boundary are recorded in `docs/lite-wallet-backend-source-signature-plan-2026-05-20.md`. The maintained dependency source is now vendored under `external/SilentDragonXLite/silentdragonxlite-cli`, and the wrapper uses `silentdragonxlitelib = { path = "../silentdragonxlite-cli/lib" }`. The portable override remains available for comparison against an external checkout, but release verification should move to the relative source layout. Signature metadata is read-only verification inventory under `docs/lite-wallet-backend-signing-policy-2026-05-22.md`; signing itself stays outside CMake, runtime code, and Phase 1 artifact production.
## Platform Artifact Status
As of 2026-05-22, artifact production has moved past local Linux proof into clean-builder reproducibility and imported-link checks:
- Linux static artifact production is reproducible when built with `--reproducible`, an isolated `--cargo-target-dir`, and a remap for the current external `silentdragonxlitelib` path. Two clean Linux builds produced byte-identical archives with SHA-256 `12228f5b895db6cdafb0f643ecb5fbad1a3760b57b7fe98d7084a34644f34622`.
- Windows GNU static artifact production is reproducible on this host with `x86_64-pc-windows-gnu` and MinGW tools. Two clean Windows builds produced byte-identical archives with SHA-256 `bdf1e0175a89560d15232f6430d162c6c009806e048062c124ec40c0744e49be`.
- The artifact script now has a portable dependency override through `--silentdragonxlitelib-dir`, which prepares a generated backend wrapper under the output directory and records the override in artifact provenance instead of relying on the local absolute dependency path in the checked-in wrapper manifest.
- Clean Linux and Windows GNU builds using `--silentdragonxlitelib-dir /home/d/external/silentdragonxlite-cli/lib` are reproducible. The portable Linux archive SHA-256 is `aaef46b99fdc304be88427852797d6674ac330209b377be5447c1b0805635ea2`; the portable Windows GNU archive SHA-256 is `635a9e9bf3254955e63d1e9032e714488fa433cc249c8aabe636b5f18d2d1e3b`.
- The checked-in backend source now carries the relative `silentdragonxlitelib` layout. A Linux build without `--silentdragonxlitelib-dir` produced `build/lite-backend-relative-linux-b/linux/libsilentdragonxlite.a` with SHA-256 `8fd6c66ff661e13f768754de69d39e1a15ee55b6fdd530625a6018c867edde10`; its manifest records `portable_dependency_override: false`, `silentdragonxlitelib_revision: 6a178c8d08d9c1c153fb22759a68177cdb787be7`, `reproducible: true`, and `signing_requested: false`.
- Windows GNU clean builds from the checked-in relative source layout are reproducible. Two clean builds produced byte-identical `build/lite-backend-relative-windows-gnu-a/windows/libsilentdragonxlite.a` and `build/lite-backend-relative-windows-gnu-b/windows/libsilentdragonxlite.a` archives with SHA-256 `ca7677af58f61de4bd56311e76e32961d977da8fac2a3c5d158c1702f8807439`; both manifests record `portable_dependency_override: false`, `silentdragonxlitelib_revision: 6a178c8d08d9c1c153fb22759a68177cdb787be7`, `rust_target: x86_64-pc-windows-gnu`, `reproducible: true`, and `signing_requested: false`.
- The portable Linux artifact was accepted by CMake in `build/lite-backend-portable-verify`, linked into `ObsidianDragonTests`, and the focused suite printed `Focused service tests passed`. The same artifact also linked into the `ObsidianDragon` app target with `DRAGONX_LITE_BACKEND_EXTRA_LIBS` empty, producing `build/lite-backend-portable-verify/bin/ObsidianDragonLite` for inspection without launch.
- Windows GNU imported app linking is verified for the portable artifact from `/tmp/od-win-lite-link`. The first no-extra link proved missing `AcquireCredentialsHandleA`, `FreeCredentialsHandle`, `FreeContextBuffer`, and `GetUserProfileDirectoryW`; the successful builder value is `DRAGONX_LITE_BACKEND_EXTRA_LIBS=secur32;userenv`, producing `/tmp/od-win-lite-link/bin/ObsidianDragonLite.exe` as a PE32+ GUI x86-64 Windows executable. The executable was inspected but not run.
- Windows GNU imported app linking is also verified for the relative-source artifact from `/tmp/od-win-relative-link` with the same `DRAGONX_LITE_BACKEND_EXTRA_LIBS=secur32;userenv` value, producing `/tmp/od-win-relative-link/bin/ObsidianDragonLite.exe` as a PE32+ GUI x86-64 Windows executable. The import table includes `Secur32.dll` and `USERENV.dll`; the executable was inspected but not run.
- The Windows GNU link run also proved the need for a Windows-only short CMake wrapper for the overlong Batch 90 generated source basename, because MinGW dependency files append `.obj.d` and exceed the filename component limit otherwise.
- macOS artifact production is blocked on this Linux workspace until an Apple Rust target and Apple/osxcross linker toolchain are available, and is deferred for now by operator request. The blocker was rechecked twice on 2026-05-20 and again on 2026-05-22; the host still has no installed Apple Rust targets, no Xcode, no `/opt/osxcross`, no Apple/osxcross compiler wrappers, and no Apple linker environment variables. The 2026-05-22 checked-in relative-source attempt with `--rust-target x86_64-apple-darwin` reached Cargo and failed with missing `compiler_builtins`, `core`, `alloc`, and `std` for that target. No macOS archive, symbol inventory, manifest, imported-link result, or extra-link-library value has been produced yet. The macOS builder prerequisites, command shape, acceptance criteria, and platform link-library planning notes remain recorded in the artifact production guide for a future continuation.
- Platform native link libraries are not being guessed in CMake. Linux test/app linking currently succeeds with `DRAGONX_LITE_BACKEND_EXTRA_LIBS` empty, Windows GNU app linking succeeds with `secur32;userenv`, and macOS imported-link verification must record any actual extra backend-specific libraries after Darwin linker proof.
Both verified platform artifacts expose `blake3_PW` plus all eight required `litelib_*` symbols. These checks still do not load, resolve, or call the backend at runtime.
## Required C ABI
The artifact must export these C symbols, matching `LiteClientBridgeApi`:
| Symbol | Purpose | Return ownership |
| --- | --- | --- |
| `litelib_wallet_exists` | Check wallet existence for a chain | caller does not free |
| `litelib_initialize_new` | Create a new wallet | returned string must be freed |
| `litelib_initialize_new_from_phrase` | Restore wallet from seed phrase | returned string must be freed |
| `litelib_initialize_existing` | Open an existing wallet | returned string must be freed |
| `litelib_execute` | Execute SDXL command strings such as sync, syncstatus, balance, list, send, import, export, save, shield, and encryption commands | returned string must be freed |
| `litelib_rust_free_string` | Free strings returned by the Rust backend | cleanup function |
| `litelib_check_server_online` | Check lite server availability | caller does not free |
| `litelib_shutdown` | Shut down backend resources | no returned value |
The supported ABI version label is `sdxl-c-v1`.
## Contract Helper
`src/wallet/lite_backend_artifact_contract.h/.cpp` adds `LiteBackendArtifactContract`. It validates caller-supplied artifact metadata and exported-symbol inventory, then produces a `LiteBackendArtifactResolverInput` for the existing read-only resolver.
The helper verifies:
- contract owner and read-only gate are ready,
- link mode is the Phase 1 imported-library path,
- artifact path is configured,
- artifact kind is static or shared library,
- ABI version is `sdxl-c-v1`,
- required signature metadata is complete and verified when release policy requires it,
- symbol inventory owner is ready,
- all required C ABI symbols are present,
- no artifact mutation, dynamic library load/unload, symbol resolution, SDXL call, bridge call, server check, lifecycle, sync, worker, `WalletState`, persistence, upload, signing, or publication action is requested.
The helper never loads the artifact, resolves symbols, calls the bridge, calls SDXL, or enables runtime activation.
## Remaining Phase 1 Work
- macOS artifact and imported-link verification is deferred for now by operator request; when resumed on a macOS host or configured osxcross builder, record any proven `DRAGONX_LITE_BACKEND_EXTRA_LIBS` values next to the artifact manifests.
- Read-only signature metadata capture and the optional CMake signature gate are implemented; signing itself remains outside wallet runtime code. Capture real signed-artifact evidence only when a release builder provides sidecar signatures.

View File

@@ -0,0 +1,553 @@
# Lite Wallet Backend Artifact Production - 2026-05-18
## Purpose
Phase 1 now has a concrete external artifact-production entry point for the SDXL-compatible lite backend. The wallet runtime still does not load, resolve, call, sign, upload, publish, or mutate backend artifacts.
The production handoff is:
1. Build or inventory the Rust backend from `external/SilentDragonXLite/lib`.
2. Capture a one-symbol-per-line exported-symbol inventory.
3. Capture SHA-256, size, file metadata, source revision, builder label, and artifact-set provenance in a JSON manifest.
4. Optionally capture read-only signature verification metadata after a release builder has already verified a sidecar signature.
5. Pass the artifact path and symbol inventory into CMake when explicitly enabling the lite backend.
## Script
Use `scripts/build-lite-backend-artifact.sh`.
Linux host build:
```bash
scripts/build-lite-backend-artifact.sh --platform linux
```
Clean reproducible Linux host build:
```bash
scripts/build-lite-backend-artifact.sh \
--platform linux \
--silentdragonxlitelib-dir /path/to/silentdragonxlite-cli/lib \
--reproducible \
--cargo-target-dir build/lite-backend-cargo-repro \
--out-dir build/lite-backend-repro \
--builder phase1-repro
```
Windows cross build with the GNU target:
```bash
scripts/build-lite-backend-artifact.sh --platform windows --rust-target x86_64-pc-windows-gnu
```
macOS builds must run on macOS or provide an explicit Apple Rust target from a configured cross toolchain:
```bash
scripts/build-lite-backend-artifact.sh \
--platform macos \
--rust-target x86_64-apple-darwin \
--silentdragonxlitelib-dir /path/to/silentdragonxlite-cli/lib
```
To inventory an already-built artifact without running Cargo:
```bash
scripts/build-lite-backend-artifact.sh --platform linux --artifact /path/to/libsilentdragonxlite.a --no-build
```
To require read-only signature verification metadata after an external verifier has already checked the artifact:
```bash
scripts/build-lite-backend-artifact.sh \
--platform linux \
--artifact /path/to/libsilentdragonxlite.a \
--no-build \
--signature-required \
--signature-file /path/to/libsilentdragonxlite.a.minisig \
--signature-format minisign \
--signature-verification-tool "minisign 0.11" \
--signature-key-fingerprint "<reviewed-public-key-fingerprint>" \
--signature-verified-sha256 "<artifact-sha256>"
```
The script writes:
- `build/lite-backend/<platform>/<artifact>`
- `build/lite-backend/<platform>/lite-backend-symbols.txt`
- `build/lite-backend/<platform>/lite-backend-artifact-manifest.json`
It uses `cargo build --locked --lib --release`, `CARGO_INCREMENTAL=0`, and `SOURCE_DATE_EPOCH` when building. If `SOURCE_DATE_EPOCH` is not already set, the project commit timestamp is used.
`--cargo-target-dir` isolates Cargo output for clean-builder checks. `--reproducible` appends deterministic Rust `--remap-path-prefix` flags for the project, backend source, discovered or overridden `silentdragonxlitelib` source, Cargo home, and selected Cargo target directory. Use repeated `--remap-path-prefix FROM=TO` values only for additional source paths outside those defaults. Reproducibility should be checked by running the same command twice with different clean target directories and comparing the artifact SHA-256 and the filtered symbol inventory.
## Portable Backend Dependency Path Plan
The checked-in wrapper at `external/SilentDragonXLite/lib/Cargo.toml` now references `silentdragonxlitelib` through the relative path `../silentdragonxlite-cli/lib`. The maintained dependency source is vendored under `external/SilentDragonXLite/silentdragonxlite-cli` from revision `6a178c8d08d9c1c153fb22759a68177cdb787be7`, with no `.git` or `target/` build output imported and stale workflow conflict-backup files pruned from the vendored copy.
The Phase 1 portable builder contract is:
- Preferred current path: build with `--backend-dir external/SilentDragonXLite/lib` and no `--silentdragonxlitelib-dir`; the script discovers the relative dependency, validates that it is package `silentdragonxlitelib`, remaps it in reproducible mode, and records it in manifest provenance with `portable_dependency_override: false`.
- Acceptable comparison path: pass `--silentdragonxlitelib-dir /path/to/silentdragonxlite-cli/lib` to compare against an external maintained checkout. The script validates that directory, creates a generated wrapper under `<out>/.prepared-backend/<platform>`, patches only that generated `Cargo.toml`, and records `portable_dependency_override: true`.
- Source cleanup status: the absolute dependency path has been removed from the checked-in wrapper; release runbooks should move from the override path to the relative source layout after Linux/Windows/macOS platform verification is refreshed.
- Rejected release path: depending on `/home/d/external/silentdragonxlite-cli/lib` or any other builder-local absolute path in the committed backend source.
The artifact manifest records `portable_dependency_override`, `cargo_build_source`, `silentdragonxlitelib_source`, and `silentdragonxlitelib_revision` under `provenance` for both the relative source layout and the override path. Reproducible mode remaps the dependency source to `/dragonx-lite-backend-dependency`, so release commands do not need a local `/home/d/external` remap for that dependency.
The target source cleanup and signature metadata boundary are detailed in `docs/lite-wallet-backend-source-signature-plan-2026-05-20.md`. The relative source layout is now implemented; signature metadata remains read-only artifact inventory to add only after a signing policy exists. Phase 1 artifact production must not sign, upload, publish, or mutate artifacts.
The first Linux build from the relative source layout succeeded without `--silentdragonxlitelib-dir`: `build/lite-backend-relative-linux-b/linux/libsilentdragonxlite.a` has SHA-256 `8fd6c66ff661e13f768754de69d39e1a15ee55b6fdd530625a6018c867edde10`, exported `blake3_PW` plus all eight required `litelib_*` symbols, and produced a manifest with `portable_dependency_override: false`, `silentdragonxlitelib_revision: 6a178c8d08d9c1c153fb22759a68177cdb787be7`, `reproducible: true`, and `signing_requested: false`.
## Signature Metadata Plan
The signing policy is defined in `docs/lite-wallet-backend-signing-policy-2026-05-22.md`. The manifest now always includes a `signature_verification` object using policy `dragonx-lite-backend-signature-policy-v1` while keeping `signing_requested: false`.
For unsigned local inventory, `metadata_provided` is `false` and `verification_status` is `not-provided`. When any signature metadata flag is supplied, or when `--signature-required` is used, the script fails closed unless the sidecar signature exists, the format is accepted, the verifier tool is recorded, a reviewed key fingerprint or certificate identity is present, and the verified artifact SHA-256 matches the artifact bytes recorded in the manifest.
The wallet runtime, `LiteBackendArtifactContract`, CMake gate, and release readiness scaffolds continue to reject signing requests and do not create signatures. `DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE=ON` is available for release builders that want CMake to require verified signature metadata before importing the backend library.
## Required Symbols
The exported-symbol inventory must contain the Phase 1 ABI surface required by `LiteClientBridgeApi`:
- `litelib_wallet_exists`
- `litelib_initialize_new`
- `litelib_initialize_new_from_phrase`
- `litelib_initialize_existing`
- `litelib_execute`
- `litelib_rust_free_string`
- `litelib_check_server_online`
- `litelib_shutdown`
The inventory may contain extra SDXL symbols such as `blake3_PW`, but CMake and `LiteBackendArtifactContract` require the eight symbols above.
## CMake Use
When the lite backend is explicitly enabled, CMake now requires the generated symbol inventory:
```bash
cmake -S . -B build/lite \
-DDRAGONX_BUILD_LITE=ON \
-DDRAGONX_ENABLE_LITE_BACKEND=ON \
-DDRAGONX_LITE_BACKEND_LIBRARY=build/lite-backend/linux/libsilentdragonxlite.a \
-DDRAGONX_LITE_BACKEND_SYMBOLS_FILE=build/lite-backend/linux/lite-backend-symbols.txt \
-DDRAGONX_LITE_BACKEND_MANIFEST=build/lite-backend/linux/lite-backend-artifact-manifest.json \
-DDRAGONX_LITE_BACKEND_LINK_MODE=imported \
-DDRAGONX_LITE_BACKEND_ABI=sdxl-c-v1
```
`DRAGONX_LITE_BACKEND_MANIFEST` is optional metadata for release traceability, but if supplied it must exist. `DRAGONX_LITE_BACKEND_SYMBOLS_FILE` is mandatory when `DRAGONX_ENABLE_LITE_BACKEND=ON`.
`DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE=ON` is optional and requires `DRAGONX_LITE_BACKEND_MANIFEST`. When enabled, CMake requires `signature_verification.verification_status` to be `verified` and requires `signature_verification.verified_artifact_sha256` to match `artifact.sha256` in the manifest before importing the backend library.
`DRAGONX_LITE_BACKEND_EXTRA_LIBS` is the Phase 1 escape hatch for platform-native or backend-specific static-link dependencies that are discovered by real linker runs. The current contract is to keep it empty until a platform linker proves an additional library or framework is required, then record the exact value used by the release builder.
## Linux Verification
On 2026-05-18, the Linux artifact path was verified on this workspace with:
```bash
scripts/build-lite-backend-artifact.sh \
--platform linux \
--out-dir build/lite-backend-real \
--builder phase1-real-build
```
Result:
- artifact: `build/lite-backend-real/linux/libsilentdragonxlite.a`
- size: `126158088` bytes
- SHA-256: `1a568ac1e90908adeede28e26f478f7b0dce7cf5bd7b9893ce7efa32e842e1ee`
- manifest: `build/lite-backend-real/linux/lite-backend-artifact-manifest.json`
- symbols: `build/lite-backend-real/linux/lite-backend-symbols.txt`
The symbol inventory contains `blake3_PW` plus all eight required `litelib_*` symbols. The manifest reports schema `dragonx.lite.backend-artifact.v1`, ABI `sdxl-c-v1`, link mode `imported`, platform `linux`, kind `static-library`, builder `phase1-real-build`, and no missing required symbols.
The generated artifact was then accepted by CMake:
```bash
cmake -S . -B build/lite-backend-verify \
-DDRAGONX_BUILD_LITE=ON \
-DDRAGONX_ENABLE_LITE_BACKEND=ON \
-DDRAGONX_LITE_BACKEND_LIBRARY="$PWD/build/lite-backend-real/linux/libsilentdragonxlite.a" \
-DDRAGONX_LITE_BACKEND_SYMBOLS_FILE="$PWD/build/lite-backend-real/linux/lite-backend-symbols.txt" \
-DDRAGONX_LITE_BACKEND_MANIFEST="$PWD/build/lite-backend-real/linux/lite-backend-artifact-manifest.json" \
-DDRAGONX_LITE_BACKEND_LINK_MODE=imported \
-DDRAGONX_LITE_BACKEND_ABI=sdxl-c-v1
```
`cmake --build build/lite-backend-verify --target ObsidianDragonTests` linked successfully against the real static backend artifact, and `./build/lite-backend-verify/bin/ObsidianDragonTests` printed `Focused service tests passed`.
This verification proves the Linux imported-library path, symbol inventory gate, manifest handoff, and focused test link path. It does not execute runtime SDXL wallet/network behavior.
## Clean Linux Reproducibility Verification
On 2026-05-18, isolated Linux builds without path remapping produced identical ABI symbol inventories but different static archive bytes because the archive embedded Cargo target-directory paths and LLVM-suffixed internal symbols. Reproducible mode was added for artifact production and then verified with two clean target directories:
```bash
scripts/build-lite-backend-artifact.sh \
--platform linux \
--reproducible \
--remap-path-prefix /home/d/external=/dragonx-external \
--cargo-target-dir build/lite-backend-cargo-repro-a \
--out-dir build/lite-backend-repro-a \
--builder phase1-repro-a
scripts/build-lite-backend-artifact.sh \
--platform linux \
--reproducible \
--remap-path-prefix /home/d/external=/dragonx-external \
--cargo-target-dir build/lite-backend-cargo-repro-b \
--out-dir build/lite-backend-repro-b \
--builder phase1-repro-b
```
Result:
- artifact: `build/lite-backend-repro-a/linux/libsilentdragonxlite.a`
- size: `126147788` bytes
- SHA-256: `12228f5b895db6cdafb0f643ecb5fbad1a3760b57b7fe98d7084a34644f34622`
- manifest reproducible flag: `true`
- second clean artifact SHA-256: `12228f5b895db6cdafb0f643ecb5fbad1a3760b57b7fe98d7084a34644f34622`
- archive byte comparison: identical
- filtered symbol inventory comparison: identical
The filtered inventory contains `blake3_PW` plus all eight required `litelib_*` symbols.
The reproducible Linux artifact was also accepted by CMake in `build/lite-backend-repro-verify`, linked into `ObsidianDragonTests`, and `./build/lite-backend-repro-verify/bin/ObsidianDragonTests` printed `Focused service tests passed`.
On 2026-05-19, the portable dependency override path was verified with two clean Linux builds that supplied `silentdragonxlitelib` through `--silentdragonxlitelib-dir` instead of relying on the checked-in absolute path:
```bash
scripts/build-lite-backend-artifact.sh \
--platform linux \
--silentdragonxlitelib-dir /home/d/external/silentdragonxlite-cli/lib \
--reproducible \
--cargo-target-dir build/lite-backend-cargo-portable-linux-a \
--out-dir build/lite-backend-portable-linux-a \
--builder phase1-portable-linux-a
scripts/build-lite-backend-artifact.sh \
--platform linux \
--silentdragonxlitelib-dir /home/d/external/silentdragonxlite-cli/lib \
--reproducible \
--cargo-target-dir build/lite-backend-cargo-portable-linux-b \
--out-dir build/lite-backend-portable-linux-b \
--builder phase1-portable-linux-b
```
Result:
- artifact: `build/lite-backend-portable-linux-a/linux/libsilentdragonxlite.a`
- size: `126148614` bytes
- SHA-256: `aaef46b99fdc304be88427852797d6674ac330209b377be5447c1b0805635ea2`
- manifest reproducible flag: `true`
- manifest portable dependency override: `true`
- `silentdragonxlitelib` revision: `6a178c8d08d9c1c153fb22759a68177cdb787be7`
- second clean artifact SHA-256: `aaef46b99fdc304be88427852797d6674ac330209b377be5447c1b0805635ea2`
- archive byte comparison: identical
- filtered symbol inventory comparison: identical
The filtered inventory contains `blake3_PW` plus all eight required `litelib_*` symbols. This run proves the portable dependency override can replace the previous `/home/d/external` source-path remap for the dependency itself during Linux artifact production.
The portable Linux artifact was also accepted by CMake in `build/lite-backend-portable-verify` with `DRAGONX_LITE_BACKEND_LIBRARY`, `DRAGONX_LITE_BACKEND_SYMBOLS_FILE`, `DRAGONX_LITE_BACKEND_MANIFEST`, `DRAGONX_LITE_BACKEND_LINK_MODE=imported`, and `DRAGONX_LITE_BACKEND_ABI=sdxl-c-v1` pointing at `build/lite-backend-portable-linux-a/linux/`. `cmake --build build/lite-backend-portable-verify --target ObsidianDragonTests` linked successfully against the portable static backend artifact, and `./build/lite-backend-portable-verify/bin/ObsidianDragonTests` printed `Focused service tests passed`.
On 2026-05-20, the same portable Linux artifact was also linked into the app target without extra backend libraries:
```bash
cmake --build build/lite-backend-portable-verify --target ObsidianDragon
```
Result:
- binary: `build/lite-backend-portable-verify/bin/ObsidianDragonLite`
- size: `100026080` bytes
- file type: ELF 64-bit LSB PIE x86-64 Linux executable, dynamically linked
- backend extras: `DRAGONX_LITE_BACKEND_EXTRA_LIBS:STRING=`
- dynamic dependencies: normal Linux/system/app dependencies including `libcurl.so.4`, `libsodium.so.23`, `libOpenGL.so.0`, `libstdc++.so.6`, `libssl.so.3`, `libcrypto.so.3`, and standard system libraries
This proves the Linux release app target can link the portable imported backend artifact. The app binary was inspected but not launched.
## Windows GNU Verification
On 2026-05-18, this workspace had the `x86_64-pc-windows-gnu` Rust target and MinGW symbol tools available, including `/usr/bin/x86_64-w64-mingw32-gcc` and `/usr/bin/x86_64-w64-mingw32-nm`. The Windows GNU artifact was built with reproducible mode:
```bash
scripts/build-lite-backend-artifact.sh \
--platform windows \
--rust-target x86_64-pc-windows-gnu \
--reproducible \
--remap-path-prefix /home/d/external=/dragonx-external \
--cargo-target-dir build/lite-backend-cargo-windows \
--out-dir build/lite-backend-windows \
--builder phase1-windows-gnu
```
A second clean Windows GNU build with a separate Cargo target directory produced the same archive bytes and the same filtered symbol inventory.
Result:
- artifact: `build/lite-backend-windows/windows/libsilentdragonxlite.a`
- rust target: `x86_64-pc-windows-gnu`
- size: `105565566` bytes
- SHA-256: `bdf1e0175a89560d15232f6430d162c6c009806e048062c124ec40c0744e49be`
- symbol tool: `x86_64-w64-mingw32-nm`
- manifest reproducible flag: `true`
- archive byte comparison with second clean build: identical
- filtered symbol inventory comparison with second clean build: identical
The filtered inventory contains `blake3_PW` plus all eight required `litelib_*` symbols.
On 2026-05-19, the Windows GNU reproducibility check was repeated with `--silentdragonxlitelib-dir` so the wrapper dependency path was supplied through portable builder input instead of the checked-in absolute path:
```bash
scripts/build-lite-backend-artifact.sh \
--platform windows \
--rust-target x86_64-pc-windows-gnu \
--silentdragonxlitelib-dir /home/d/external/silentdragonxlite-cli/lib \
--reproducible \
--cargo-target-dir build/lite-backend-cargo-portable-windows-a \
--out-dir build/lite-backend-portable-windows-a \
--builder phase1-portable-windows-a
scripts/build-lite-backend-artifact.sh \
--platform windows \
--rust-target x86_64-pc-windows-gnu \
--silentdragonxlitelib-dir /home/d/external/silentdragonxlite-cli/lib \
--reproducible \
--cargo-target-dir build/lite-backend-cargo-portable-windows-b \
--out-dir build/lite-backend-portable-windows-b \
--builder phase1-portable-windows-b
```
Result:
- artifact: `build/lite-backend-portable-windows-a/windows/libsilentdragonxlite.a`
- rust target: `x86_64-pc-windows-gnu`
- size: `105565096` bytes
- SHA-256: `635a9e9bf3254955e63d1e9032e714488fa433cc249c8aabe636b5f18d2d1e3b`
- symbol tool: `x86_64-w64-mingw32-nm`
- manifest reproducible flag: `true`
- manifest portable dependency override: `true`
- `silentdragonxlitelib` revision: `6a178c8d08d9c1c153fb22759a68177cdb787be7`
- second clean artifact SHA-256: `635a9e9bf3254955e63d1e9032e714488fa433cc249c8aabe636b5f18d2d1e3b`
- archive byte comparison: identical
- filtered symbol inventory comparison: identical
The filtered inventory contains `blake3_PW` plus all eight required `litelib_*` symbols.
On 2026-05-22, the Windows GNU reproducibility check was repeated from the checked-in relative source layout without `--silentdragonxlitelib-dir`:
```bash
scripts/build-lite-backend-artifact.sh \
--platform windows \
--rust-target x86_64-pc-windows-gnu \
--backend-dir external/SilentDragonXLite/lib \
--reproducible \
--cargo-target-dir build/lite-backend-relative-target-windows-gnu-a \
--out-dir build/lite-backend-relative-windows-gnu-a \
--builder phase1-relative-windows-gnu-a
scripts/build-lite-backend-artifact.sh \
--platform windows \
--rust-target x86_64-pc-windows-gnu \
--backend-dir external/SilentDragonXLite/lib \
--reproducible \
--cargo-target-dir build/lite-backend-relative-target-windows-gnu-b \
--out-dir build/lite-backend-relative-windows-gnu-b \
--builder phase1-relative-windows-gnu-b
```
Result:
- artifact: `build/lite-backend-relative-windows-gnu-a/windows/libsilentdragonxlite.a`
- rust target: `x86_64-pc-windows-gnu`
- SHA-256: `ca7677af58f61de4bd56311e76e32961d977da8fac2a3c5d158c1702f8807439`
- symbol tool: `x86_64-w64-mingw32-nm`
- manifest reproducible flag: `true`
- manifest portable dependency override: `false`
- `silentdragonxlitelib` revision: `6a178c8d08d9c1c153fb22759a68177cdb787be7`
- second clean artifact SHA-256: `ca7677af58f61de4bd56311e76e32961d977da8fac2a3c5d158c1702f8807439`
- archive byte comparison: identical
- filtered symbol inventory comparison: identical
The filtered inventory contains `blake3_PW` plus all eight required `litelib_*` symbols.
On 2026-05-20, the portable Windows GNU artifact was accepted by CMake and linked into the app target from a short build directory:
```bash
cmake -S . -B /tmp/od-win-lite-link \
-DCMAKE_TOOLCHAIN_FILE="$PWD/build/windows/mingw-toolchain.cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OBJECT_PATH_MAX=128 \
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
-DDRAGONX_BUILD_LITE=ON \
-DDRAGONX_ENABLE_LITE_BACKEND=ON \
-DDRAGONX_LITE_BACKEND_LIBRARY="$PWD/build/lite-backend-portable-windows-a/windows/libsilentdragonxlite.a" \
-DDRAGONX_LITE_BACKEND_SYMBOLS_FILE="$PWD/build/lite-backend-portable-windows-a/windows/lite-backend-symbols.txt" \
-DDRAGONX_LITE_BACKEND_MANIFEST="$PWD/build/lite-backend-portable-windows-a/windows/lite-backend-artifact-manifest.json" \
-DDRAGONX_LITE_BACKEND_LINK_MODE=imported \
-DDRAGONX_LITE_BACKEND_ABI=sdxl-c-v1 \
'-DDRAGONX_LITE_BACKEND_EXTRA_LIBS=secur32;userenv'
cmake --build /tmp/od-win-lite-link --target ObsidianDragon -j $(nproc)
```
The first Windows GNU link attempt without extra backend libraries reached the real linker and failed on `AcquireCredentialsHandleA`, `FreeCredentialsHandle`, `FreeContextBuffer`, and `GetUserProfileDirectoryW`. The successful imported-link value is therefore `DRAGONX_LITE_BACKEND_EXTRA_LIBS=secur32;userenv`.
Result:
- binary: `/tmp/od-win-lite-link/bin/ObsidianDragonLite.exe`
- size: `106366143` bytes
- file type: PE32+ executable (GUI) x86-64, for MS Windows
- imported backend-extra DLL evidence: `Secur32.dll` and `USERENV.dll`
- other imported DLLs observed: `ADVAPI32.dll`, `CRYPT32.dll`, `D3DCOMPILER_47.dll`, `GDI32.dll`, `HID.DLL`, `IMM32.dll`, `IPHLPAPI.DLL`, `KERNEL32.dll`, `OLEAUT32.dll`, `PSAPI.DLL`, `SETUPAPI.dll`, `SHELL32.dll`, `USER32.dll`, `VERSION.dll`, `WINMM.dll`, `WS2_32.dll`, `bcrypt.dll`, `d3d11.dll`, `dcomp.dll`, `dwmapi.dll`, `msvcrt.dll`, and `ole32.dll`
MinGW also exposed a path-component issue before link: the generated Batch 90 source basename plus `.obj.d` exceeded the Windows filename component limit. CMake now uses a Windows-only generated wrapper source under `generated/short_sources/lite_batch90_receipt_plan.cpp` that includes the original source, and the successful build log confirmed `generated/short_sources/lite_batch90_receipt_plan.cpp.obj` was compiled. The Windows executable was inspected but not run.
On 2026-05-22, the relative-source Windows GNU artifact was accepted by CMake and linked into the app target from `/tmp/od-win-relative-link`:
```bash
cmake -S . -B /tmp/od-win-relative-link \
-DCMAKE_TOOLCHAIN_FILE="$PWD/build/windows/mingw-toolchain.cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OBJECT_PATH_MAX=128 \
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
-DDRAGONX_BUILD_LITE=ON \
-DDRAGONX_ENABLE_LITE_BACKEND=ON \
-DDRAGONX_LITE_BACKEND_LIBRARY="$PWD/build/lite-backend-relative-windows-gnu-a/windows/libsilentdragonxlite.a" \
-DDRAGONX_LITE_BACKEND_SYMBOLS_FILE="$PWD/build/lite-backend-relative-windows-gnu-a/windows/lite-backend-symbols.txt" \
-DDRAGONX_LITE_BACKEND_MANIFEST="$PWD/build/lite-backend-relative-windows-gnu-a/windows/lite-backend-artifact-manifest.json" \
-DDRAGONX_LITE_BACKEND_LINK_MODE=imported \
-DDRAGONX_LITE_BACKEND_ABI=sdxl-c-v1 \
'-DDRAGONX_LITE_BACKEND_EXTRA_LIBS=secur32;userenv'
cmake --build /tmp/od-win-relative-link --target ObsidianDragon -j $(nproc)
```
Result:
- binary: `/tmp/od-win-relative-link/bin/ObsidianDragonLite.exe`
- file type: PE32+ executable (GUI) x86-64, for MS Windows
- imported backend-extra DLL evidence: `Secur32.dll` and `USERENV.dll`
- other imported DLLs observed: `ADVAPI32.dll`, `CRYPT32.dll`, `D3DCOMPILER_47.dll`, `GDI32.dll`, `HID.DLL`, `IMM32.dll`, `IPHLPAPI.DLL`, `KERNEL32.dll`, `OLEAUT32.dll`, `PSAPI.DLL`, `SETUPAPI.dll`, `SHELL32.dll`, `USER32.dll`, `VERSION.dll`, `WINMM.dll`, `WS2_32.dll`, `bcrypt.dll`, `d3d11.dll`, `dcomp.dll`, `dwmapi.dll`, `msvcrt.dll`, and `ole32.dll`
This verifies that the checked-in relative source layout preserves the Windows GNU imported app link contract with the previously proven `secur32;userenv` backend extras. The executable was inspected but not run.
## macOS Verification Status
macOS artifact production was not attempted on 2026-05-18 because this Linux workspace is missing the required Darwin cross-build inputs:
- no installed Rust Apple target such as `x86_64-apple-darwin` or `aarch64-apple-darwin`,
- no Apple/osxcross compiler wrapper such as `o64-clang`, `x86_64-apple-darwin-clang`, or `x86_64-apple-darwin20.4-clang`,
- no `osxcross-conf`,
- no `/opt/osxcross` toolchain directory.
macOS remains a Phase 1 platform-artifact verification blocker until a macOS host or configured osxcross builder is available.
The blocker was rechecked on 2026-05-20 from this Linux/WSL workspace. No Apple Rust targets are installed, Xcode is absent, `/opt/osxcross` is absent, and the Apple/osxcross compiler wrappers `o64-clang`, `x86_64-apple-darwin-clang`, `x86_64-apple-darwin20.4-clang`, `aarch64-apple-darwin-clang`, and `aarch64-apple-darwin20.4-clang` are missing. `llvm-nm` is available for future symbol inventory work, but it is not enough without an Apple target and linker. The artifact script also fails closed for a Linux-hosted macOS request without an explicit Rust target: `macOS artifacts require --rust-target when not running on macOS`.
The macOS/osxcross builder preflight was repeated later on 2026-05-20 for the Phase 1 macOS verification slice. This shell is still Linux/WSL, has Rust/Cargo `1.63.0`, `clang`, and `llvm-nm`, but has no installed Apple/Darwin Rust targets, no `xcodebuild`/`xcrun`, no `otool`/`lipo`, no `/opt/osxcross`, no osxcross compiler/archive wrappers, and no Apple linker environment variables such as `CARGO_TARGET_*APPLE_DARWIN_LINKER`, `CC_x86_64_apple_darwin`, or `CC_aarch64_apple_darwin`. The attempted artifact command failed closed before producing a macOS artifact:
```bash
scripts/build-lite-backend-artifact.sh \
--platform macos \
--silentdragonxlitelib-dir /home/d/external/silentdragonxlite-cli/lib \
--reproducible \
--cargo-target-dir /tmp/od-lite-macos-cargo-preflight \
--out-dir /tmp/od-lite-macos-artifact-preflight \
--builder phase1-macos-preflight
```
Result: `[lite-backend] ERROR: macOS artifacts require --rust-target when not running on macOS`. No macOS archive, symbol inventory, manifest, CMake imported-link result, or `DRAGONX_LITE_BACKEND_EXTRA_LIBS` value was produced on this host.
The macOS/osxcross preflight was repeated on 2026-05-22 from the checked-in relative source layout. This shell still has Rust/Cargo `1.63.0`, `clang`, and `llvm-nm`, but has no installed Apple/Darwin Rust targets, no `xcodebuild`/`xcrun`, no `otool`/`lipo`, no `/opt/osxcross`, no osxcross compiler/archive wrappers, and no Apple linker environment variables. The explicit relative-source artifact attempt reached Cargo with `--rust-target x86_64-apple-darwin` and failed because the target standard libraries are unavailable:
```bash
scripts/build-lite-backend-artifact.sh \
--platform macos \
--rust-target x86_64-apple-darwin \
--backend-dir external/SilentDragonXLite/lib \
--out-dir /tmp/od-lite-macos-relative-artifact-preflight \
--cargo-target-dir /tmp/od-lite-macos-relative-cargo-preflight \
--reproducible \
--builder phase1-macos-relative-preflight
```
Result: Cargo exited with code `101` and reported missing `compiler_builtins`, `core`, `alloc`, and `std` for `x86_64-apple-darwin`, with the expected `rustup target add x86_64-apple-darwin` hint. No `/tmp/od-lite-macos-relative-artifact-preflight/macos/libsilentdragonxlite.a` archive or `lite-backend-artifact-manifest.json` manifest was produced, so macOS imported-link verification still cannot proceed on this host.
On 2026-05-22, macOS artifact and imported-link verification was deferred by operator request. The builder prerequisites, command shape, and acceptance criteria remain below for a future macOS/osxcross continuation, but current Phase 1 local work should not keep retrying macOS tasks until that deferral is lifted.
## macOS Artifact Verification Plan
The macOS verification run must be performed on a macOS host or on a Linux builder with a configured osxcross/Apple SDK toolchain. The repository does not vendor the Apple SDK or handle Apple license acceptance.
Builder prerequisites:
- Rust Apple target installed, at minimum `x86_64-apple-darwin`; add `aarch64-apple-darwin` when Apple Silicon artifacts are in scope.
- Apple linker/compiler wrappers available, for example `clang` on macOS or osxcross tools such as `o64-clang`, `x86_64-apple-darwin-clang`, and matching `ar`/`ranlib` tools.
- `llvm-nm` or a Darwin-capable `nm` available for static archive symbol inventory.
- The checked-in relative source layout under `external/SilentDragonXLite/lib` and `external/SilentDragonXLite/silentdragonxlite-cli`; use `--silentdragonxlitelib-dir` only for comparison against an external maintained checkout.
- Isolated Cargo target directories for two clean reproducibility runs.
Command shape for an Intel macOS artifact:
```bash
scripts/build-lite-backend-artifact.sh \
--platform macos \
--rust-target x86_64-apple-darwin \
--backend-dir external/SilentDragonXLite/lib \
--reproducible \
--cargo-target-dir build/lite-backend-relative-target-macos-x64-a \
--out-dir build/lite-backend-relative-macos-x64-a \
--builder phase1-relative-macos-x64-a
```
Repeat the same command with a second clean Cargo target and output directory, then compare the archive SHA-256 values and filtered `lite-backend-symbols.txt` files. For osxcross builders, set the target linker/compiler environment before running the same script command, for example `CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER=o64-clang` and `CC_x86_64_apple_darwin=o64-clang`.
macOS acceptance criteria:
- The script generates a `dragonx.lite.backend-artifact.v1` manifest with platform `macos`, ABI `sdxl-c-v1`, link mode `imported`, and reproducible provenance for build runs that use `--reproducible`.
- `lite-backend-symbols.txt` contains `blake3_PW` if exported plus all eight required `litelib_*` symbols.
- Two clean macOS builds are byte-identical, or any remaining nondeterminism is documented with identical required-symbol inventories and a follow-up owner.
- On a macOS builder capable of linking the wallet tests, CMake accepts the generated artifact through `DRAGONX_LITE_BACKEND_LIBRARY`, `DRAGONX_LITE_BACKEND_SYMBOLS_FILE`, `DRAGONX_LITE_BACKEND_MANIFEST`, `DRAGONX_LITE_BACKEND_LINK_MODE=imported`, and `DRAGONX_LITE_BACKEND_ABI=sdxl-c-v1`, then `ObsidianDragonTests` links and prints `Focused service tests passed`.
- Cross-only osxcross runs that cannot execute the test binary must still prove Cargo build success, Darwin symbol inventory, manifest generation, and CMake configure/link success where the toolchain permits it.
## Platform Link-Library Planning
CMake links the imported `dragonx_lite_backend` target into both `ObsidianDragon` and `ObsidianDragonTests` only when `DRAGONX_ENABLE_LITE_BACKEND=ON` and the artifact gate has passed. The same link sites append `DRAGONX_LITE_BACKEND_EXTRA_LIBS`, so platform release builders can add verified backend-native dependencies without changing full-node defaults or enabling runtime dynamic loading.
Current planning status:
- Linux: the portable static backend artifact linked into both `ObsidianDragonTests` and the `ObsidianDragon` app target on this workspace with `DRAGONX_LITE_BACKEND_EXTRA_LIBS` empty. The app target produced `build/lite-backend-portable-verify/bin/ObsidianDragonLite` and was inspected without launch.
- Windows GNU: artifact production, symbol inventory, clean reproducibility, CMake configure, and app imported-link verification are complete for both the portable override artifact and the checked-in relative source artifact on this host. The first no-extra portable link proved missing `secur32`/`userenv` APIs; the successful release-builder value remains `DRAGONX_LITE_BACKEND_EXTRA_LIBS=secur32;userenv`, producing `/tmp/od-win-lite-link/bin/ObsidianDragonLite.exe` for the portable artifact and `/tmp/od-win-relative-link/bin/ObsidianDragonLite.exe` for the relative artifact without running either executable.
- macOS: artifact and link verification are deferred by operator request after the blocked 2026-05-22 relative-source preflight. The first resumed macOS linker pass should use the existing local `libs/libsodium-mac`/`libs/libsodium` search path plus the app's platform frameworks, then add backend-specific libraries or frameworks through `DRAGONX_LITE_BACKEND_EXTRA_LIBS` only when the Darwin linker proves they are needed.
Example shape for a platform builder after it has identified real extra dependencies:
```bash
cmake -S . -B build/lite-platform-verify \
-DDRAGONX_BUILD_LITE=ON \
-DDRAGONX_ENABLE_LITE_BACKEND=ON \
-DDRAGONX_LITE_BACKEND_LIBRARY=/path/to/libsilentdragonxlite.a \
-DDRAGONX_LITE_BACKEND_SYMBOLS_FILE=/path/to/lite-backend-symbols.txt \
-DDRAGONX_LITE_BACKEND_MANIFEST=/path/to/lite-backend-artifact-manifest.json \
-DDRAGONX_LITE_BACKEND_LINK_MODE=imported \
-DDRAGONX_LITE_BACKEND_ABI=sdxl-c-v1 \
-DDRAGONX_LITE_BACKEND_EXTRA_LIBS="<verified-extra-lib-1>;<verified-extra-lib-2>"
```
If no backend-specific extras are needed, omit `DRAGONX_LITE_BACKEND_EXTRA_LIBS` and keep the manifest plus symbol inventory as the evidence of the artifact that was linked.
## Guardrails
The script performs build and read-only artifact inspection only. It does not:
- load dynamic libraries,
- resolve runtime symbols,
- call SDXL or bridge APIs,
- check servers,
- create/open/restore wallets,
- start sync or poll `syncstatus`,
- mutate `WalletState`,
- persist wallet files,
- sign, upload, or publish artifacts.

View File

@@ -0,0 +1,61 @@
# Lite Wallet Backend Signing Policy - 2026-05-22
## Purpose
This Phase 1 policy defines how backend artifact signature verification evidence is recorded without making the wallet sign, upload, publish, mutate, load, resolve, or call backend artifacts.
Policy identifier: `dragonx-lite-backend-signature-policy-v1`.
## Scope
The artifact producer may capture read-only metadata about a signature verification that has already been performed by a release builder. It does not create signatures and does not run wallet runtime code.
The default development and local verification path does not require a signature. Release builders that require signed artifacts must opt in with `--signature-required` when generating the manifest and `DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE=ON` when configuring CMake.
## Accepted Evidence
When signature metadata is supplied, all of the following are required:
- an existing sidecar signature file,
- signature format `minisign`, `gpg`, `sigstore`, `external`, or `other`,
- verifier tool and version used by the release builder,
- reviewed public-key fingerprint or reviewed certificate identity,
- verified artifact SHA-256 equal to the artifact bytes recorded in the manifest.
The manifest records the signature sidecar SHA-256, optional verifier command, optional certificate issuer, optional transparency log URL, and `verification_status: "verified"`. Missing or inconsistent required metadata fails before manifest generation when `--signature-required` or any signature metadata flag is supplied.
## Script Usage
Example metadata capture after an external verifier has already checked the artifact:
```bash
scripts/build-lite-backend-artifact.sh \
--platform linux \
--artifact build/lite-backend-relative-linux-b/linux/libsilentdragonxlite.a \
--no-build \
--out-dir build/lite-backend-signed-inventory \
--signature-required \
--signature-file /path/to/libsilentdragonxlite.a.minisig \
--signature-format minisign \
--signature-verification-tool "minisign 0.11" \
--signature-verification-command "minisign -Vm libsilentdragonxlite.a -P <reviewed-public-key>" \
--signature-key-fingerprint "<reviewed-public-key-fingerprint>" \
--signature-verified-sha256 "<artifact-sha256>"
```
For unsigned local inventory, omit all signature flags. The manifest still includes `signature_verification.policy_defined: true`, `metadata_provided: false`, and `verification_status: "not-provided"`, while `signing_requested` remains `false`.
## CMake Gate
`DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE=ON` is optional and fail-closed. When enabled, CMake requires `DRAGONX_LITE_BACKEND_MANIFEST`, parses `signature_verification`, requires `verification_status: "verified"`, and checks that `signature_verification.verified_artifact_sha256` matches `artifact.sha256` before importing the backend library.
## Contract Helper
`LiteBackendArtifactContract` accepts unsigned local metadata by default. If `signatureVerification.requiredForRelease` is true, the helper requires a defined policy, complete metadata, a reviewed trust identity, performed verification, verified status, and a matching artifact SHA-256 before producing resolver input.
## Guardrails
- No signing is performed by the artifact script, CMake, contract helper, or wallet runtime.
- No artifact upload or publication is performed.
- No runtime dynamic loading, symbol resolution, SDXL calls, wallet lifecycle, sync, `WalletState` mutation, or persistence is enabled.
- Signature evidence is release inventory only and must describe the same artifact bytes identified by the manifest SHA-256.

View File

@@ -0,0 +1,106 @@
# Lite Wallet Backend Source And Signature Metadata Plan - 2026-05-20
## Purpose
This Phase 1 note closes the planning gap between the verified portable dependency override and the eventual release-source layout. It also defines the signature metadata boundary for backend artifacts without enabling signing, upload, publication, runtime loading, SDXL calls, or wallet mutation.
## Current Source State
The checked-in backend wrapper is `external/SilentDragonXLite/lib`. Its Cargo package is `qtlib`, the library name is `silentdragonxlite`, and the crate type is `staticlib`.
The wrapper now depends on `silentdragonxlitelib` through the reviewed relative path:
```toml
silentdragonxlitelib = { path = "../silentdragonxlite-cli/lib" }
```
The maintained dependency branch was imported from `/home/d/external/silentdragonxlite-cli` into `external/SilentDragonXLite/silentdragonxlite-cli` from git revision `6a178c8d08d9c1c153fb22759a68177cdb787be7`. Build outputs, `.git`, and `target/` were not imported, and stale workflow conflict-backup files were pruned from the vendored copy. The imported source carries `DRAGONX_SOURCE_REVISION` so artifact manifests can keep reporting the maintained dependency revision even though the dependency is now vendored under the wrapper source tree.
The release-builder-safe path is now to build from `external/SilentDragonXLite/lib` without `--silentdragonxlitelib-dir`. The override remains available as an escape hatch for comparing against an external maintained checkout, but release builders should prefer the checked-in relative layout.
## Implemented Relative Layout
The reviewed backend source branch should make the dependency relative inside a single release source tree. The preferred layout is:
```text
external/SilentDragonXLite/
lib/ # qtlib C ABI wrapper, crate type staticlib
silentdragonxlite-cli/
Cargo.toml # dependency workspace root
lib/ # package silentdragonxlitelib
```
With that layout, `external/SilentDragonXLite/lib/Cargo.toml` uses:
```toml
silentdragonxlitelib = { path = "../silentdragonxlite-cli/lib" }
```
Acceptable variants are allowed only if they keep both crates inside the reviewed release source tree and use a relative path. Absolute builder-local paths, symlinks to paths outside the tree, generated source patches committed back into the repo, or reliance on `/home/d/external` are not release-acceptable.
## Implementation Status
Completed through 2026-05-22:
1. Imported the maintained `silentdragonxlite-cli` source into the reviewed backend source tree.
2. Changed the wrapper dependency path from the absolute local path to `../silentdragonxlite-cli/lib`.
3. Left `external/SilentDragonXLite/lib/Cargo.lock` unchanged.
4. Updated artifact provenance so the script discovers relative `silentdragonxlitelib` sources, remaps them for reproducible builds, and records `portable_dependency_override: false` unless `--silentdragonxlitelib-dir` is explicitly used.
5. Built a Linux artifact without `--silentdragonxlitelib-dir` from `external/SilentDragonXLite/lib`.
6. Built two Windows GNU artifacts without `--silentdragonxlitelib-dir`, compared them byte-for-byte, and linked the app target against the relative-source artifact.
7. Rechecked macOS from the relative source layout on 2026-05-22; this Linux host still lacks Apple Rust targets and Apple/osxcross linker tooling, so the `x86_64-apple-darwin` attempt failed before artifact production.
8. Defined the Phase 1 signing policy in `docs/lite-wallet-backend-signing-policy-2026-05-22.md`, added read-only signature metadata capture to `scripts/build-lite-backend-artifact.sh`, added optional `DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE` CMake enforcement, and taught `LiteBackendArtifactContract` to validate required signature metadata before producing resolver input.
## Acceptance Criteria
- `external/SilentDragonXLite/lib/Cargo.toml` contains no absolute `silentdragonxlitelib` dependency path.
- The dependency source is present inside the reviewed source tree and is covered by source-control revision/provenance review.
- `scripts/build-lite-backend-artifact.sh --platform linux --backend-dir external/SilentDragonXLite/lib --reproducible ...` succeeds without `--silentdragonxlitelib-dir`.
- The generated manifest records `portable_dependency_override: false`, `cargo_build_source` equal to the reviewed backend source, and reproducible provenance.
- The refreshed Linux no-override artifact is `build/lite-backend-relative-linux-b/linux/libsilentdragonxlite.a` with SHA-256 `8fd6c66ff661e13f768754de69d39e1a15ee55b6fdd530625a6018c867edde10`; its manifest records `portable_dependency_override: false`, `silentdragonxlitelib_revision: 6a178c8d08d9c1c153fb22759a68177cdb787be7`, `reproducible: true`, and `signing_requested: false`.
- The refreshed Windows GNU no-override artifacts are byte-identical at SHA-256 `ca7677af58f61de4bd56311e76e32961d977da8fac2a3c5d158c1702f8807439`; their manifests record `portable_dependency_override: false`, `silentdragonxlitelib_revision: 6a178c8d08d9c1c153fb22759a68177cdb787be7`, `reproducible: true`, `rust_target: x86_64-pc-windows-gnu`, and `signing_requested: false`.
- The relative Windows GNU artifact links into `ObsidianDragon` from `/tmp/od-win-relative-link` with `DRAGONX_LITE_BACKEND_EXTRA_LIBS=secur32;userenv`, producing `/tmp/od-win-relative-link/bin/ObsidianDragonLite.exe` as a PE32+ GUI x86-64 Windows executable. The import table includes `Secur32.dll` and `USERENV.dll`; the executable was inspected but not run.
- macOS uses the same relative layout once a macOS/osxcross builder exists; the 2026-05-22 Linux preflight failed with missing `x86_64-apple-darwin` standard libraries and produced no artifact or manifest, and macOS verification is deferred for now by operator request.
- All artifacts still expose the eight required `litelib_*` symbols for ABI `sdxl-c-v1`.
## Signature Metadata Boundary
Phase 1 backend artifact production records signature verification metadata under policy `dragonx-lite-backend-signature-policy-v1`, defined in `docs/lite-wallet-backend-signing-policy-2026-05-22.md`. It must not create signatures, mutate artifacts, upload artifacts, publish artifacts, or make signatures a runtime wallet concern.
When policy exists, the artifact manifest should add a read-only metadata object with these fields or direct equivalents:
```json
{
"signature_verification": {
"policy_defined": true,
"required_for_release": true,
"verification_performed": true,
"verification_status": "verified",
"signature_format": "minisign|gpg|sigstore|external|other",
"signature_path": "/path/to/artifact.signature",
"signature_file_sha256": "sha256 of sidecar signature file",
"verification_tool": "tool name and version",
"verification_command": "command already run by release builder when recorded",
"key_fingerprint": "reviewed public key fingerprint when applicable",
"certificate_identity": "certificate identity when applicable",
"certificate_issuer": "certificate issuer when applicable",
"transparency_log_url": "transparency log entry when applicable",
"verified_artifact_sha256": "sha256 that was verified"
}
}
```
Unsigned local manifests keep `signing_requested: false`, `metadata_provided: false`, and `verification_status: "not-provided"`. Signed release-builder manifests may set `metadata_provided: true` only when the sidecar signature exists, verifier metadata is recorded, a reviewed trust identity is present, and `verified_artifact_sha256` matches the artifact SHA-256.
## Signature Metadata Acceptance Criteria
- Signature metadata is read-only inventory attached to the artifact manifest.
- The metadata verifies the same artifact bytes identified by the manifest SHA-256.
- If `--signature-required` or `DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE=ON` is used, missing or invalid signature metadata fails before CMake imported-link use.
- Signature verification tooling and public-key or certificate trust roots are documented outside wallet runtime code.
- `LiteBackendArtifactContract` and runtime bridge code continue to reject signing requests and never sign, upload, publish, or mutate artifacts.
## Remaining Phase 1 Work
- Capture real signed-artifact evidence when a release builder provides sidecar signatures and reviewed trust roots.
- macOS artifact and imported-link verification is deferred by operator request until a macOS host or configured osxcross builder is available and the deferral is lifted.

View File

@@ -0,0 +1,140 @@
# Lite Wallet Implementation Plan v2 — 2026-06-04
**Status:** Active. **Supersedes** `docs/full-lite-wallet-implementation-plan-2026-05-18.md` (archived).
## Context — why this plan replaces the v1 plan
The v1 ("Full Lite Wallet Implementation Plan", 2026-05-18) drove the lite effort for ~6 weeks and produced a large amount of code, but **zero end-to-end wallet functionality**. Its method — build every layer of every phase in a *typed-disabled* form and "promote one disabled scaffold at a time" through readiness/custody/handoff governance — generated ~160 dead `lite_wallet_*_plan`/`*_batch*` files (filenames up to 250 chars) and a 33k-line test file that exercised only disabled scaffolding. Those files were deleted on 2026-06-04 (branch `cleanup/lite-plan-churn`); a `scripts/check-source-hygiene.sh` pre-commit guard now blocks their regrowth.
A verified audit (8-agent review, 2026-06-04) established the real current state below. This plan keeps the v1 plan's **dependency ordering and ground rules** (which were sound) but discards its **method**: it switches from *horizontal disabled scaffolding* to *vertical working slices* — each milestone makes one capability work **end-to-end and demoable**, gated by a deterministic fake-backend test, before the next begins.
## Verified current state (2026-06-04)
**Real and working when enabled:**
- C-ABI bridge `lite_client_bridge.{h,cpp}` — makes real `litelib_*` calls via function pointers with copy-before-free Rust string ownership (`lite_client_bridge.cpp:120-183`). Gated by `#if DRAGONX_ENABLE_LITE_BACKEND` (default 0).
- CMake import contract — validates build coupling, imported-only link mode, ABI `sdxl-c-v1`, all 8 required symbols, optional signature (`CMakeLists.txt:62-145`).
- Capability gating — `wallet_capabilities.h` correctly gates lite features at compile + runtime.
- Result parsers + state mapper — `lite_result_parsers.{h,cpp}`, `lite_wallet_state_mapper.{h,cpp}` convert JSON → typed state (real, but never called).
- Backend artifact script — `scripts/build-lite-backend-artifact.sh` (reproducible; Rust source vendored at `external/SilentDragonXLite/`). Built Linux/Windows artifacts during dev, but `build/lite-backend/` is absent and CMake never invokes it.
**Real but unreachable (the core gap is integration):**
- `LiteWalletLifecycleService` (real `bridge_.initialize*`), `LiteSyncService`, `LiteConnectionService`, `LiteWalletGateway` (real `bridge_.execute`) — all contain real bridge calls but are **never constructed anywhere**, default to `allowBridgeCalls=false`, and the only wired UI path is a **validation-only adapter** that returns `RuntimeExecutionDisabled` for every real action.
**Not started / disabled:** sync loop (`startSync` returns "not implemented"), `WalletState` application (explicit `StateMutationDisabled`), send/import/export, wallet-file persistence, lite data UI, dynamic loading (only an unbuilt scaffold; imported-link only), macOS (operator-deferred).
**Known defects to fix while wiring:** (1) seed/passphrase flow through request structs but are only *flagged* redacted, never zeroed in memory; (2) the lifecycle bridge response JSON is discarded, never parsed, so `walletReady` is permanently false.
## Ground rules (carried over from v1 — still binding)
- Keep full-node runtime behavior unchanged; share only through narrow abstractions.
- Never fake balances, sync state, transactions, send results, wallet existence, or server connectivity. A disabled feature must look disabled, not fake-succeed.
- Never log seed phrases, passphrases, private keys, decrypted memos, or raw bridge responses. **Additionally: zero secret buffers (`sodium_memzero`) after use** — fixing the v1 gap.
- Copy Rust-returned strings before free; free exactly once; no raw pointer escape (already done in `lite_client_bridge`).
- Each milestone ships a deterministic test against an **injected fake backend** before any real-backend smoke test.
- Keep release packaging honest: lite artifacts bundle no daemon/Sapling/asmap assets; full-node artifacts stay intact.
## Architectural decisions (resolve up front)
1. **Canonical bridge seam = `lite_client_bridge`.** It already makes real `litelib_*` calls and is what the services use. The elaborate `lite_bridge_runtime` (~1.7 MB cpp, fake-only dry-dispatch + disabled phase-N scaffolds) is **not** the execution path. Keep only its genuinely-useful primitives (`LiteBridgeOwnedString` copy-before-free, idempotent teardown — already referenced by `lite_client_bridge.cpp:182`) and retire the disabled dispatch/promotion scaffolding (subject to the hygiene guard, same as the earlier churn cleanup). Do **not** invest further in `lite_bridge_runtime` dry-dispatch.
2. **App-owned `LiteWalletController`, mirroring the full-node ownership pattern.** The full-node path owns `WalletState state_` (`app.h:421`) as the single source of truth, driven by `NetworkRefreshService` + `RefreshScheduler` via enqueue → `RPCWorker` → callback → apply-to-`state_` (`app_network.cpp:912-944`, `refreshCoreData` ~`:1001-1043`), and every tab reads `app->state()` (`app.h:157-159`). The lite controller constructs `LiteClientBridge::linkedSdxl()` + the lite services with `allowBridgeCalls=true`, runs the same enqueue/worker/apply loop, and **feeds the same `WalletState`** — so the existing Balance/Send/Receive/Transactions tabs light up with no per-tab changes.
3. **Implement the existing `LiteWalletBackend` abstraction** (`wallet_backend.h:59-82`: `setServer/openWallet/startSync/cancelSync/sendTransaction`). It exists and is unused; the controller is its first implementation. Branch in `App::init()` on `supportsLiteBackend()` (`app.h:134-140`).
4. **`WalletState` is backend-agnostic and stays the single source of truth.** Lite-sourced data lands in the same fields the tabs already read: balances, `addresses` (`AddressInfo`), `transactions` (`TransactionInfo`), `sync` (`SyncInfo`) in `data/wallet_state.h`.
5. **Deterministic fake SDXL `.so`.** Build a tiny fake backend implementing the 8 `litelib_*` symbols with canned JSON, used to link a test target and drive every milestone's acceptance test without a real backend or network (satisfies the "injected fake bridge before real smoke test" rule). Real-backend smoke tests follow per milestone.
## Milestones (vertical slices)
Each milestone is independently demoable and gated by a fake-backend test. Order respects the v1 dependency chain (bridge → lifecycle → sync → state → UI → send → ship).
### M0 — Foundation: real artifact, linked build, fake-backend harness
**Goal:** A lite build that links a backend and a test harness that can drive the bridge deterministically.
> **Status (2026-06-04): mostly complete.**
> - ✅ Real Linux artifact built — `scripts/build-lite-backend-artifact.sh --platform linux` → `build/lite-backend/linux/libsilentdragonxlite.a` (126 MB) with all 8 `litelib_*` symbols + manifest (sha256 `c06f5679…`). `build/` is gitignored.
> - ✅ `build.sh --lite-backend` flag added (auto-discovers the artifact; `DRAGONX_LITE_BACKEND_DIR` override).
> - ✅ CMake import contract validated against the real artifact; `ObsidianDragonLite` (93 MB) **links** it (`nm` shows `litelib_execute/initialize_new/shutdown` as defined `T`).
> - ✅ Deterministic injectable fake harness — `tests/fake_lite_backend.h` (`makeFakeLiteApi()`, owned-string alloc/free accounting) + `testLiteBackendInjectableFakeBridge()` in the suite (`ctest` green). This is the harness M1 service tests reuse — it needs neither the real `.so` nor Rust.
> - ⏳ Deferred to a focused follow-up: (a) a standalone fake `.so` link-target for Rust-less CI (the real artifact covers the link path locally); (b) **retiring the `lite_bridge_runtime` disabled-dispatch scaffolding** — large/risky surgical cleanup, not required to unblock M1; do it with the same care as the churn cleanup.
- Run `scripts/build-lite-backend-artifact.sh` against `external/SilentDragonXLite` to produce a Linux `litelib` artifact + symbols file + manifest.
- Add a `build.sh --lite-backend` path that sets `-DDRAGONX_ENABLE_LITE_BACKEND=ON` with the library/symbols/manifest paths (today `--lite` only sets `DRAGONX_BUILD_LITE=ON`).
- Add a small fake SDXL `.so` (8 symbols, canned responses) + a CMake test target that links it; port the existing dry-dispatch tests toward real-symbol-table calls against the fake.
- Retire the `lite_bridge_runtime` disabled-dispatch scaffolding per decision (1).
- **Exit demo / test:** `ObsidianDragonLite` links the real artifact and `currentWalletCapabilities()` reports lite available; a test links the fake `.so` and the bridge reports `available()` and round-trips one `execute()` call.
### M1 — Wallet lifecycle end-to-end (create / open / restore) ← highest-leverage spike
**Goal:** A user creates/opens/restores a **real** wallet from Settings.
> **Status (2026-06-04): implemented.**
> - ✅ New `src/wallet/lite_wallet_controller.{h,cpp}` — App-owned, constructs `LiteWalletLifecycleService` with `allowBridgeCalls=true`, executes real create/open/restore, **`sodium_memzero`-wipes seed/passphrase** after each call, tracks `walletOpen()`, fires a persist callback on success. `createLinked()` uses `LiteClientBridge::linkedSdxl()`; an injecting ctor takes a fake bridge for tests.
> - ✅ Response-discard defect fixed — `lite_wallet_lifecycle_service.cpp` `bridgeResult()` now sets `walletReady = nlohmann::json::accept(response)` instead of hardcoded `false`.
> - ✅ App wiring — `app.cpp::init()` constructs the controller when `supportsLiteBackend()` with `persist → settings_->save()`; `App::liteWallet()` accessor (null in full-node / unlinked-lite).
> - ✅ UI reroute — `settings_page.cpp` "Validate" handler calls the controller for real when present (shows "Wallet ready"; wipes the UI seed/passphrase buffers after submit), else falls back to the validation-only adapter.
> - ✅ Tests — `testLiteWalletControllerLifecycle()` on the M0 fake harness: create/restore → ready + `walletOpen()` + persist fires + no owned-string leak; empty-seed rejected pre-backend; `allowBridgeCalls=false` and full-node caps fail closed; secret-wipe helper verified. `ctest` green. Builds verified: lite+backend (`build/lite`), lite-no-backend (`build/linux`), and full-node (`build/fullnode`, clean, 0 litelib symbols linked — no regression).
> - ⏳ Deferred refinement: the controller is built at startup from saved connection settings, so a *live* in-session server change isn't honored until restart. Fold into M2/M3 (set the request's `serverUrl` from the field, or rebuild the controller on server-selection save).
>
> **Real-backend smoke (2026-06-04): PASS.** Added `tools/lite_smoke.cpp` + a backend-guarded `lite_smoke` CMake target (built in `build/lite`). Against the live `https://lite.dragonx.is`: `available()=true`, `walletExists(main)=false`, `checkServerOnline()=true`, and `initializeNew()` **created a real wallet** (`silentdragonxlite-wallet.dat`, "Starting Mempool"), seed never logged, isolated `HOME`, exit 0. The create path works end-to-end on the real `litelib`, not just the fake.
>
> Two findings from the smoke run:
> 1. **FIXED — chain-name bug.** Our default chain name was the ticker `"DRAGONX"`, but the SDXL backend hard-`panic!`s on anything outside `{main,test,regtest}` (`lightclient.rs:166`). Changed `kDragonXLiteChainName` and `Settings::lite_chain_name_` to `"main"`. **Migration landed** (`settings.cpp` load): any saved `chain_name` outside `{main,test,regtest}` is rewritten to `"main"` + flagged for re-save, so existing users with `"DRAGONX"` don't hit the panic. Covered by `testLiteChainNameMigration()`.
> 2. **OPEN — panic-across-FFI aborts the app (hardening).** The Rust backend uses `panic!` for error conditions; a panic across the C FFI boundary is UB and `SIGABRT`s the whole process (we saw a core dump). The C++ bridge cannot catch it. Before production: wrap the litelib FFI exports in `std::panic::catch_unwind` (rebuild the vendored lib) and/or validate all inputs before calling. Tracked in M5 (production enablement).
- Add `LiteWalletController` owned by `App` (construct in `App::init()` when `supportsLiteBackend()`), owning `LiteClientBridge::linkedSdxl()` + `LiteConnectionService` + `LiteWalletLifecycleService` with `allowBridgeCalls=true`.
- Reroute the Settings "Validate" button (`settings_page.cpp:1661-1663``evaluateLiteLifecycleRequestFromPageState` → validation-only `executeLiteWalletLifecycleUiRequest`, `:237`) to the controller's real `createWallet/openWallet/restoreWallet`. The request structs are already populated correctly (`settings_page.cpp:219-235`).
- Parse the lifecycle response JSON via `lite_result_parsers` and set wallet-ready state (fixes the discarded-response defect).
- Persist server selection + wallet path on success (wire to `config::Settings::save()`; today `settingsWriteRequested` is blocked).
- Zero seed/passphrase buffers after the bridge call (`sodium_memzero`) — fixes the secret-handling defect.
- **Exit demo / test:** Against the fake backend (then a real one), create a wallet → status shows ready; reopen after restart; restore-from-seed succeeds. Fake-backend test asserts the lifecycle path calls the bridge and produces a ready state.
### M2 — Sync loop + WalletState population
**Goal:** After open, balances/addresses/history populate the existing tabs.
> **Status (2026-06-04): data pipeline landed; live wiring (M2b) remains.**
> - ✅ **Last hop implemented + tested** — `applyLiteRefreshModelToWalletState(model, WalletState&)` in `lite_wallet_controller.{h,cpp}`: zatoshi→DRGX balances, z/t address split, transaction typing + confirmations (`chainHeight - blockHeight + 1`), sync progress. Mutates `WalletState` in place (it's non-copyable). `testLiteRefreshModelAppliesToWalletState()` drives a bundle through the existing `mapLiteWalletRefreshBundle` → apply → asserts the populated `WalletState`. `ctest` green.
> - The fetch/parse/assemble pipeline already exists and works: `LiteWalletGateway::refresh()` → `LiteWalletRefreshBundle` → `mapLiteWalletRefreshBundle()` → `LiteWalletAppRefreshModel`. M2 just needed the final `→ WalletState` hop (above) plus live wiring.
> - ⏳ **M2b (remaining) — live wiring.** Blocked on a design decision surfaced by the M1 smoke run: `litelib` is a **global singleton** and every `LiteClientBridge` calls `litelib_shutdown()` (stops the live client) on destruction, so the controller cannot own a second owning bridge for the gateway/sync. **Decision: refactor the lite services (`LiteWalletLifecycleService`, `LiteWalletGateway`, `LiteConnectionService`, `LiteSyncService`) to take a non-owning bridge (`LiteClientBridge*`/shared handle); the controller owns the one bridge.** Then: implement `LiteSyncService::startSync` (replace the "not implemented" stub) + a background worker polling `syncstatus` and running `gateway.refresh()` (mirror `NetworkRefreshService`/`RefreshScheduler`: enqueue → worker → apply on main thread), apply into `App` `WalletState`, and hook into `App::update()`. Note: `litelib_execute` is already panic-safe (`catch_unwind`), so the polling workhorse won't abort the app. Per-address balances need notes-correlation (currently aggregate-only).
- Implement `LiteSyncService::startSync` (replace the "not implemented" stub) + a background worker polling `syncstatus`, mirroring `NetworkRefreshService`/`RefreshScheduler` (enqueue → worker → apply on main thread).
- Drive `LiteWalletGateway` refresh (info/height/balance/addresses/notes/list/transactions) through `lite_result_parsers``lite_wallet_state_mapper``App` `WalletState` (`privateBalance`, `transparentBalance`, `addresses`, `transactions`, `sync`).
- Hook the controller into `App::update()`'s refresh dispatch alongside (not inside) the full-node path.
- **Exit demo / test:** After open, Balance/Receive/Transactions tabs show real lite data with no per-tab code changes (they already read `app->state()`); sync progress advances. Fake-backend test asserts a canned balance/tx set lands in `WalletState`.
### M3 — Wallet data UI completeness
**Goal:** A complete read-only wallet UX.
- Sync status/progress indicator; loading/empty states; new shielded/transparent address generation via the gateway (`receive_tab`); capability-gated surfaces verified (`isUiSurfaceAvailable`, `settings_page.cpp:1494` lite/full branch).
- **Exit demo / test:** Full read-only experience — balances, address book, history, live sync progress — against fake then real backend.
### M4 — Send / import / export / shield
**Goal:** A user can spend and back up.
- Wire `send_tab` (`:780-787` `sendTransaction`) to `litelib_execute` (`send`/`z_sendmany`) via the gateway, with fee + confirmation UI, result parsing, and tx-status polling that updates `WalletState`.
- Import (keys), export (wallet backup), shield (t→z).
- **Exit demo / test:** Send a transaction and watch it confirm; export a backup. Fake-backend test drives a send and asserts the result/tx-status flow.
### M5 — Persistence, recovery, packaging, production enablement
**Goal:** Shippable.
- Wallet-file durability + crash/recovery + error/retry UX.
- Lite release packaging (zip/AppImage/exe, no daemon assets — `build.sh:285`); build + (read-only-verify) sign the backend artifact in CI per `lite-wallet-backend-signing-policy`.
- Lift macOS deferral; optionally implement the dynamic-loader sublane if shared-library distribution is required (today imported-link only).
- Runtime kill-switch / feature flag / staged rollout.
- **Exit demo / test:** A downloadable `ObsidianDragonLite` that creates, syncs, sends, and persists against a real backend.
## What we explicitly drop from the v1 plan
- The "promote one disabled scaffold at a time" methodology and all `promotion → activation → post-closure → custody → handoff → stewardship → receipt` governance layers.
- Building disabled/typed-only versions of every stack layer ahead of real execution. (Vertical slices replace this.)
- Further investment in `lite_bridge_runtime` dry-dispatch as an execution path.
- "Readiness ceiling" / "Batch N" framing. Progress is measured by demoable capabilities, not batches.
We retain v1's ground rules, dependency ordering, and the artifact/ABI/signing reference docs (`lite-wallet-backend-artifact-link-contract`, `-production`, `-signing-policy`, `-source-signature-plan`).
## Verification
- **Per milestone:** a deterministic test linking the fake SDXL `.so` (no network) that asserts the new capability end-to-end, then a manual real-backend smoke test. Use `/verify` or `/run` to launch `ObsidianDragonLite` and confirm the exit demo.
- **Regression:** `cmake --build build/linux && (cd build/linux && ctest)` stays green; full-node build + behavior unchanged.
- **Hygiene:** `scripts/check-source-hygiene.sh` (pre-commit) keeps the churn from regrowing.
## References
- v1 (superseded/archived): `docs/full-lite-wallet-implementation-plan-2026-05-18.md`
- ABI / artifact / signing: `docs/lite-wallet-backend-artifact-link-contract-2026-05-18.md`, `docs/lite-wallet-backend-artifact-production-2026-05-18.md`, `docs/lite-wallet-backend-signing-policy-2026-05-22.md`, `docs/lite-wallet-backend-source-signature-plan-2026-05-20.md`
- Deferred runtime dynamic-loader design (only if M5 needs it): `docs/lite-wallet-phase2-runtime-bridge-dynamic-loader-sublane-plan-2026-05-23.md`, `docs/lite-wallet-phase2-runtime-bridge-loading-symbol-resolution-plan-2026-05-22.md`

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,106 @@
# Lite Wallet Runtime Promotion Matrix - 2026-05-18
## Baseline Record
Phase 0 starts from Batch 100 as the disabled readiness ceiling. The focused baseline was verified on 2026-05-18 with:
- `cmake --build build --target ObsidianDragonTests`
- `./build/linux/bin/ObsidianDragonTests`
Result: build passed and the test binary printed `Focused service tests passed`.
## Freeze Decision
Batch 100 is the readiness ceiling for runtime promotion work. Additional post-closure disabled batches should stay frozen unless a concrete promotion blocker requires one more typed proof surface. The next work should promote existing facts into reviewed runtime owners.
## Promotion Matrix
| Batches | Disabled readiness surface | Runtime owner to feed | Minimum Batch 100 fact |
| --- | --- | --- | --- |
| 32-34 | Lifecycle bridge-call preflight and post-lifecycle sync handoff | `LiteWalletLifecycleRuntime` | `lifecycleRuntimeFeedReady` |
| 35-37 | SDXL artifact, bridge owner, dispatcher, and runtime owner readiness | `LiteBridgeRuntime` artifact contract | `syncArtifactInput` and `syncBackendInput` |
| 38-41 | Dynamic link, dispatch table, call result cleanup, and shutdown planning | `LiteBridgeRuntime` dispatch table | reviewed disabled `runtimePlan` boundary |
| 42-43 | Execution attempt and status handoff planning | `LiteRuntimeWorkerBridge` | `runtimeExecutionRefused` and `noRuntimeAttempted` |
| 44-47 | State apply, persistence commit, recovery, and refresh queue planning | `LiteWalletStateApplyRuntime` | `stateApplyFeedReady` |
| 48-52 | UI model, view refresh, completion, and telemetry planning | `LiteWalletUiRuntimeModel` | `noUiMutation` and `noUiRefreshExecution` |
| 53-57 | Enablement review, rollout policy, kill switch, release, and production readiness | `LiteRuntimeEnablementPolicy` | runtime activation remains blocked |
| 58-63 | Observability, release closure, operations, maintenance, and promotion readiness | `LiteRuntimeOperationsReadiness` | telemetry, status, and audit gates |
| 64-72 | Promotion decision, activation preflight/runbook/approval/change control/release observation | `LiteRuntimePromotionRunbook` | `futureRuntimeBoundaryCouldBeEnabled` |
| 73-80 | Post-closure operations handoff through receipt custody acceptance | Historical evidence only | Batch 100 accepted ancestor chain |
| 81-89 | Receipt custody acceptance confirmation through archive handoff confirmation | Historical evidence only | Batch 100 accepted ancestor chain |
| 90-100 | Final receipt/custody/archive handoff disabled ceiling | Batch 100 readiness ceiling | final operation and step ready counts |
## Minimum Runtime Consumers
Future runtime owners should consume the compact Phase 0 surface instead of re-reading the whole Batch 32-100 chain:
- `LiteRuntimePromotionBatch100Result`
- `runtimePlan.disabled` guardrail flags
- `syncArtifactInput`
- `syncBackendInput`
- `syncOwnershipInput`
- `syncCancellationInput`
- `syncShutdownInput`
- `lifecycleBridgeAbiFacts`
- `flowRuntimeInput`
- `stateApplyExecution`
- final operation and step records from the Batch 100 runtime plan
## Checklist Helper
The Phase 0 code helper is `LiteRuntimePromotionChecklist` in `src/wallet/lite_wallet_runtime_promotion_checklist.*`. It requires:
- Batch 100 result present, ok, and disabled.
- Batch 100 acknowledged as the readiness ceiling.
- Post-closure disabled growth frozen.
- Required lifecycle, sync, send/import/export, and state-apply feeds ready.
- No dynamic loading, symbol resolution, bridge call, SDXL call, cleanup, shutdown, sync, lifecycle, UI, persistence, publication, or `WalletState` mutation observed.
- Final operation and step records present and fully ready.
The helper never enables runtime activation. A passing checklist means Phase 1 implementation work may start while runtime calls remain blocked.
## Post-Ceiling Owner Consumption
The first post-ceiling Phase 2 owner slice is `LiteWalletBridgeRuntimeOwnerReadiness` consuming `LiteRuntimePromotionBatch100Result` directly. The projection requires a passing `LiteRuntimePromotionChecklist`, converts the Batch100 feeds into a disabled dispatcher report and per-operation owner inputs, and keeps runtime activation blocked. It covers lifecycle create/open/restore, sync start/status, server check, wallet exists, shutdown, send, shield, create-address, export, import, save, and encryption operation ownership without enabling dynamic loading, symbol resolution, bridge/SDXL calls, persistence, publication, UI mutation, or `WalletState` mutation.
The second post-ceiling owner slice is `LiteWalletBridgeRuntimeExecutionOwner` consuming that Batch100-gated owner-readiness result. The projection marks disabled execution ownership and attempt gates ready for dynamic-library, symbol-resolution, Rust-string cleanup, shutdown, and the same operation set, while preserving `executionAttempted=false`, `executionAccepted=false`, `executionRefused=true`, and all runtime permission flags false.
The third post-ceiling owner slice is `LiteWalletBridgeRuntimeDynamicLinkPlan` consuming the Batch100 execution-owner projection. The projection maps execution-owner bridge ABI facts into the artifact symbol snapshot, marks disabled dynamic-library and symbol-table planning gates ready, and keeps dynamic loading, unloading, symbol resolution, bridge/SDXL calls, publication, persistence, UI mutation, artifact mutation, and `WalletState` mutation refused.
The fourth post-ceiling owner slice is `LiteWalletBridgeRuntimeDispatchTablePlan` consuming the Batch100 dynamic-link projection. The projection marks disabled operation routing, function-pointer slots, symbol-binding review, dispatch envelopes, response ownership, runtime-call ownership, argument marshalling, return-value ownership, error mapping, timeout/cancellation, post-call cleanup, and dispatch/runtime-call gates ready while preserving function-pointer binding, bridge calls, SDXL calls, Rust-string free, shutdown, dynamic loading, symbol resolution, persistence, publication, UI mutation, artifact mutation, and `WalletState` mutation as refused.
The fifth post-ceiling owner slice is `LiteWalletBridgeRuntimeCallResultCleanupPlan` consuming the Batch100 dispatch-table projection. The projection marks disabled Rust-string ownership, copy-before-free, free-once, null/error classification, temporary-copy wipe, raw-pointer escape prevention, result-envelope, result parser/classification, error mapping, response redaction, cancellation cleanup, shutdown cleanup, and cleanup/free attempt gates ready while preserving call-result cleanup, Rust-string free, bridge calls, SDXL calls, shutdown, dynamic loading, symbol resolution, persistence, publication, UI mutation, artifact mutation, and `WalletState` mutation as refused.
The sixth post-ceiling owner slice is `LiteWalletBridgeRuntimeExecutionAttemptPlan` consuming the Batch100 call-result cleanup projection. The projection marks disabled runtime-attempt ownership, runtime preflight, dispatch-attempt, result-cleanup attempt, status-feed, state-write, worker-queue, persistence, and attempt gates ready while preserving runtime attempts, function-pointer binding, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, worker queue, persistence, publication, UI mutation, artifact mutation, and `WalletState` mutation as refused.
The seventh post-ceiling owner slice is `LiteWalletBridgeRuntimeStatusHandoffPlan` consuming the Batch100 execution-attempt projection. The projection marks disabled status-publication owner, user-visible status, operation-status mapper, worker-handoff owner, worker-queue lane/backpressure, state-write, persistence, status-publish, and worker-handoff gates ready while preserving status publication, user-visible status publication, worker handoff, worker queue, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, persistence, publication, UI mutation, artifact mutation, and `WalletState` mutation as refused.
The eighth post-ceiling owner slice is `LiteWalletBridgeRuntimeStateApplyPlan` consuming the Batch100 status-handoff projection. The projection produces a disabled dry-run `LiteWalletStateApplyExecutionResult`, marks disabled state-apply owner, result-to-refresh-model mapper, state-apply plan, state-apply executor, dry-run acceptance, persistence-write plan, WalletState write gate, persistence-write gate, and post-apply status gates ready while preserving WalletState apply execution, WalletState mutation, wallet/settings persistence, status publication, worker queue, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, UI mutation, and artifact mutation as refused.
The ninth post-ceiling owner slice is `LiteWalletBridgeRuntimePersistenceCommitPlan` consuming the Batch100 state-apply projection. The projection marks disabled commit-owner, wallet persistence, settings persistence, commit-ordering, rollback-marker, recovery-marker, post-commit-status, durability-audit, wallet persistence gate, settings persistence gate, artifact mutation gate, and state commit gate ready while preserving persistence commits, wallet/settings writes, artifact mutation, state commits, WalletState mutation, state-apply execution, status publication, worker queue, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
The tenth post-ceiling owner slice is `LiteWalletBridgeRuntimePostCommitRecoveryPlan` consuming the Batch100 persistence-commit projection. The projection marks disabled recovery-status owner, rollback-status, recovery-status, durability-audit-status, post-commit UI status, worker-status fanout, sync-refresh fanout, send/import/export status fanout, status-publication gate, worker-queue gate, sync-refresh gate, lifecycle-execution gate, and WalletState mutation gate ready while preserving post-commit recovery execution, status publication, user-visible status publication, worker handoff, worker queue, sync refresh, lifecycle execution, persistence commits, durable writes, artifact mutation, WalletState mutation, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
The eleventh post-ceiling owner slice is `LiteWalletBridgeRuntimeRefreshQueuePlan` consuming the Batch100 post-commit recovery projection. The projection marks disabled refresh-queue owner, queue policy, queue lane, queue payload, refresh timer owner, refresh timer snapshot, status consumer owner, UI status consumer, worker status consumer, sync status consumer, send/import/export status consumer, refresh-queue gate, refresh-timer gate, status-consumer gate, worker-queue gate, and WalletState mutation gate ready while preserving refresh queue insertion, timer mutation, status-consumer publication, worker queue, WalletState mutation, status publication, sync refresh, post-commit recovery execution, persistence commits, durable writes, artifact mutation, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
The twelfth post-ceiling owner slice is `LiteWalletBridgeRuntimeUiModelProjectionPlan` consuming the Batch100 refresh-queue projection. The projection marks disabled UI model owner, balance model projection, transaction model projection, address model projection, projection snapshot, refresh payload projection, status-consumer projection, UI mutation gate, status-consumer gate, refresh-queue gate, refresh-timer gate, and WalletState mutation gate ready while preserving UI model mutation, balance/transaction/address model mutation, refresh queue insertion, timer mutation, status-consumer publication, worker queue, WalletState mutation, status publication, sync refresh, post-commit recovery execution, persistence commits, durable writes, artifact mutation, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
The thirteenth post-ceiling owner slice is `LiteWalletBridgeRuntimeUiViewRefreshHandoffPlan` consuming the Batch100 UI model projection. The projection marks disabled view-refresh owner, balance view adapter, address-list view adapter, recent-transaction view adapter, render invalidation plan, view-refresh snapshot, status-consumer handoff, UI mutation gate, view-invalidation gate, render-invalidation gate, status-consumer gate, refresh-queue gate, refresh-timer gate, and WalletState mutation gate ready while preserving UI mutation, balance/address-list/recent-transaction view mutation, view invalidation, render invalidation, refresh queue insertion, timer mutation, status-consumer publication, worker queue, WalletState mutation, status publication, sync refresh, post-commit recovery execution, persistence commits, durable writes, artifact mutation, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
The fourteenth post-ceiling owner slice is `LiteWalletBridgeRuntimeUiRefreshDispatchPlan` consuming the Batch100 UI view refresh handoff projection. The projection marks disabled refresh dispatcher owner, balance refresh route, address-list refresh route, recent-transaction refresh route, render scheduler plan, view invalidation router plan, status-consumer dispatch plan, dispatch snapshot plan, UI mutation gate, view-invalidation gate, render-invalidation gate, status-consumer gate, refresh-queue gate, refresh-timer gate, and WalletState mutation gate ready while preserving UI refresh dispatch, UI mutation, balance/address-list/recent-transaction view mutation, view invalidation, render invalidation, refresh queue insertion, timer mutation, status-consumer publication, worker queue, WalletState mutation, status publication, sync refresh, post-commit recovery execution, persistence commits, durable writes, artifact mutation, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
The fifteenth post-ceiling owner slice is `LiteWalletBridgeRuntimeUiRefreshCompletionPlan` consuming the Batch100 UI refresh dispatch projection. The projection marks disabled completion owner, completion acknowledgement plan, stale-view suppression plan, post-dispatch status summary plan, completion snapshot plan, UI mutation gate, view-invalidation gate, render-invalidation gate, status-consumer gate, refresh-queue gate, refresh-timer gate, and WalletState mutation gate ready while preserving UI refresh completion acknowledgement, stale-view suppression, post-dispatch status summary publication, UI refresh dispatch, UI mutation, balance/address-list/recent-transaction view mutation, view invalidation, render invalidation, refresh queue insertion, timer mutation, status-consumer publication, worker queue, WalletState mutation, status publication, sync refresh, post-commit recovery execution, persistence commits, durable writes, artifact mutation, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
The sixteenth post-ceiling owner slice is `LiteWalletBridgeRuntimeUiRefreshTelemetryPlan` consuming the Batch100 UI refresh completion projection. The projection marks disabled telemetry owner, completion metrics plan, suppressed-stale-view counter plan, post-refresh diagnostic summary plan, completion audit snapshot plan, telemetry redaction plan, telemetry publication gate, status-publication gate, audit-persistence gate, UI mutation gate, view-invalidation gate, render-invalidation gate, refresh-queue gate, refresh-timer gate, and WalletState mutation gate ready while preserving telemetry publication, completion metrics publication, suppressed stale-view counter publication, post-refresh diagnostic summary publication, completion audit writes, status publication, UI refresh completion acknowledgement, stale-view suppression, post-dispatch status summary publication, UI refresh dispatch, UI mutation, balance/address-list/recent-transaction view mutation, view invalidation, render invalidation, refresh queue insertion, timer mutation, status-consumer publication, worker queue, WalletState mutation, sync refresh, post-commit recovery execution, persistence commits, durable writes, artifact mutation, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
The seventeenth post-ceiling owner slice is `LiteWalletBridgeRuntimeUiRefreshEnablementReviewPlan` consuming the Batch100 UI refresh telemetry projection. The projection marks disabled enablement-review owner, main-thread ownership plan, runtime disable-switch plan, rollback plan, operator review plan, operator approval record plan, dispatch/completion/telemetry review, guardrail snapshot plan, telemetry publication gate, status-publication gate, UI mutation gate, view-invalidation gate, render-invalidation gate, refresh-queue gate, refresh-timer gate, and WalletState mutation gate ready while preserving UI refresh runtime enablement, main-thread handoff, runtime disable-switch mutation, rollback execution, operator approval commits, telemetry publication, completion metrics publication, suppressed stale-view counter publication, post-refresh diagnostic summary publication, completion audit writes, status publication, UI refresh completion acknowledgement, stale-view suppression, post-dispatch status summary publication, UI refresh dispatch, UI mutation, balance/address-list/recent-transaction view mutation, view invalidation, render invalidation, refresh queue insertion, timer mutation, status-consumer publication, worker queue, WalletState mutation, sync refresh, post-commit recovery execution, persistence commits, durable writes, artifact mutation, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
The eighteenth post-ceiling owner slice is `LiteWalletBridgeRuntimeUiRefreshRolloutPolicyPlan` consuming the Batch100 UI refresh enablement-review projection. The projection marks disabled rollout-policy owner, staged rollout plan, default-off feature flag plan, operator acknowledgement snapshot, rollback drill plan, deployment cohort plan, rollout percentage plan, metrics gate plan, guardrail snapshot plan, telemetry publication gate, status-publication gate, UI mutation gate, view-invalidation gate, render-invalidation gate, refresh-queue gate, refresh-timer gate, and WalletState mutation gate ready while preserving rollout activation, feature-flag mutation, cohort enrollment, rollout percentage mutation, operator acknowledgement commits, rollback drill execution, metrics publication, UI refresh runtime enablement, main-thread handoff, runtime disable-switch mutation, rollback execution, operator approval commits, telemetry publication, status publication, UI refresh completion acknowledgement, stale-view suppression, post-dispatch status summary publication, UI refresh dispatch, UI mutation, view invalidation, render invalidation, refresh queue insertion, timer mutation, status-consumer publication, worker queue, WalletState mutation, sync refresh, post-commit recovery execution, persistence commits, durable writes, artifact mutation, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
The nineteenth post-ceiling owner slice is `LiteWalletBridgeRuntimeUiRefreshKillSwitchPlan` consuming the Batch100 UI refresh rollout-policy projection. The projection marks disabled emergency-disable owner, incident acknowledgement snapshot, feature-flag kill-switch plan, rollback kill-switch plan, kill-switch drill plan, operator escalation gate, post-incident audit plan, guardrail snapshot plan, telemetry publication gate, status-publication gate, audit-persistence gate, UI mutation gate, view-invalidation gate, render-invalidation gate, refresh-queue gate, refresh-timer gate, and WalletState mutation gate ready while preserving emergency-disable activation, feature-flag kill-switch mutation, rollback kill-switch execution, operator escalation publication, incident acknowledgement commits, post-incident audit writes, incident status publication, rollout activation, feature-flag mutation, cohort enrollment, rollout percentage mutation, operator acknowledgement commits, rollback drill execution, metrics publication, UI refresh runtime enablement, main-thread handoff, runtime disable-switch mutation, rollback execution, operator approval commits, telemetry publication, status publication, UI refresh completion acknowledgement, stale-view suppression, post-dispatch status summary publication, UI refresh dispatch, UI mutation, view invalidation, render invalidation, refresh queue insertion, timer mutation, status-consumer publication, worker queue, WalletState mutation, sync refresh, post-commit recovery execution, persistence commits, durable writes, artifact mutation, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
The twentieth post-ceiling owner slice is `LiteWalletBridgeRuntimeUiRefreshReleaseReadinessPlan` consuming the Batch100 UI refresh kill-switch projection. The projection marks disabled operator runbook owner, release checklist snapshot, release approval snapshot, support escalation owner, monitoring handoff plan, post-release verification plan, guardrail snapshot plan, telemetry publication gate, status-publication gate, audit-publication gate, UI mutation gate, view-invalidation gate, render-invalidation gate, refresh-queue gate, refresh-timer gate, and WalletState mutation gate ready while preserving release activation, operator runbook publication, support escalation publication, monitoring handoff publication, post-release verification writes, release approval commits, emergency-disable activation, feature-flag kill-switch mutation, rollback kill-switch execution, operator escalation publication, incident acknowledgement commits, post-incident audit writes, incident status publication, telemetry publication, status publication, UI refresh dispatch, UI refresh completion acknowledgement, UI mutation, view invalidation, render invalidation, refresh queue insertion, timer mutation, status-consumer publication, worker queue, WalletState mutation, sync refresh, post-commit recovery execution, persistence commits, durable writes, artifact mutation, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
The twenty-first post-ceiling owner slice is `LiteWalletBridgeRuntimeUiRefreshProductionReadinessPlan` consuming the Batch100 UI refresh release-readiness projection. The projection marks disabled cutover owner, rollback-freeze checklist snapshot, production support handoff, monitoring verification snapshot, post-cutover audit plan, guardrail snapshot plan, telemetry publication gate, status-publication gate, audit-publication gate, UI mutation gate, view-invalidation gate, render-invalidation gate, refresh-queue gate, refresh-timer gate, and WalletState mutation gate ready while preserving cutover activation, rollback-freeze publication, production support handoff publication, monitoring verification publication, post-cutover audit writes, cutover approval commits, release activation, operator runbook publication, support escalation publication, monitoring handoff publication, post-release verification writes, release approval commits, telemetry publication, status publication, UI refresh dispatch, UI refresh completion acknowledgement, UI mutation, view invalidation, render invalidation, refresh queue insertion, timer mutation, status-consumer publication, worker queue, WalletState mutation, sync refresh, post-commit recovery execution, persistence commits, durable writes, artifact mutation, runtime attempts, bridge calls, SDXL calls, result parsing/redaction, call-result cleanup, Rust-string free, shutdown, dynamic loading, and symbol resolution as refused.
Next command: `continue lite wallet Phase 2 real bridge runtime owner implementation from Batch100 UI refresh production readiness`.

56
scripts/check-source-hygiene.sh Executable file
View File

@@ -0,0 +1,56 @@
#!/bin/bash
# Source-tree hygiene guard.
#
# Blocks two failure modes that an AI coding session previously introduced in
# src/wallet/ (the lite-wallet "_plan"/"_batch" churn): pathologically long
# filenames (which also break the Windows MAX_PATH 260-char limit during the
# cross-build) and the runaway "receipt/custody/handoff/stewardship" naming
# explosion where each session wrapped the previous artifact in one more layer.
#
# Usage:
# scripts/check-source-hygiene.sh # check working-tree src/
# scripts/check-source-hygiene.sh --staged # check staged files (pre-commit)
#
# Install as a git pre-commit hook:
# ln -sf ../../scripts/check-source-hygiene.sh .git/hooks/pre-commit
# # (the hook invokes it with --staged automatically when named pre-commit)
set -euo pipefail
MAX_LEN=80
# Naming-explosion tokens. Two or more chained in one basename is the smell.
CHURN_RE='receipt|custody|handoff|stewardship|promotion_activation|acceptance_confirmation|archive_handoff|post_closure'
mode="${1:-}"
if [[ "$mode" == "--staged" || "$(basename "$0")" == "pre-commit" ]]; then
mapfile -t files < <(git diff --cached --name-only --diff-filter=AR | grep -E '\.(cpp|h|hpp|cc)$' || true)
else
mapfile -t files < <(git ls-files 'src/**/*.cpp' 'src/**/*.h' 2>/dev/null; \
find src -type f \( -name '*.cpp' -o -name '*.h' \) 2>/dev/null)
# de-dup
mapfile -t files < <(printf '%s\n' "${files[@]}" | sort -u)
fi
fail=0
for f in "${files[@]}"; do
[[ -z "$f" ]] && continue
base="$(basename "$f")"
len=${#base}
if (( len > MAX_LEN )); then
echo "✗ filename too long ($len > $MAX_LEN chars): $f" >&2
fail=1
fi
# count distinct churn tokens in the basename ( || true: grep exits 1 on no match)
n=$(printf '%s' "$base" | grep -oE "$CHURN_RE" | sort -u | wc -l || true)
if (( n >= 2 )); then
echo "✗ runaway naming pattern ($n churn tokens) — refactor in place, don't add a layer: $f" >&2
fail=1
fi
done
if (( fail )); then
echo "" >&2
echo "Source hygiene check failed. See docs in scripts/check-source-hygiene.sh." >&2
exit 1
fi
echo "source hygiene OK (${#files[@]} files checked)"