V2 State-Change vQbit Engineering β A Worked Example¶
How gaiaftcl wallet import-cures was built as one ruliology game-play that transitions a single wallet vQbit from watching to spendable. Documented step-by-step as a teaching example for substrate engineering under the v2 discipline.
The starting state of the substrate¶
Each row in substrate_imported_external_wallets is one wallet vQbit. Its macroscopic state is read from two columns:
| Column | observation_only |
hasKey (Keychain probe) |
Macroscopic state |
|---|---|---|---|
| 1 | (any) | false | watching |
| 0 | true | true | spendable |
| 0 | false | (any) | invariant violation β purged by wallet purge-keyless |
| 1 | true | true | invariant violation β purged by wallet purge-keyless |
The substrate guarantees, via the wallet purge-keyless rule, that only the first two rows in the table above ever persist. A wallet vQbit is either watching or spendable. Nothing else.
The state-change problem¶
Patoshi's 21,953 P2PK rows arrive at watching via gaiaftcl patoshi import-all β addresses-only, no keys. Recovery (gaiaftcl shor break-patoshi-csv) plays SHOR_ECDLP against each row; when one closes (kg_verified=true, private_key_wif populated), a single wallet vQbit's state CAN transition watching β spendable.
The question for the engineer: what is the game that drives the transition?
The naΓ―ve answer in classical-math terms is "an import procedure: read the CSV row, write the Keychain, UPDATE the DB." The substrate framing β the v2 framing β is different. The transition is one MβΈ ruliology game-play. The game has a specific invariant set; when those invariants all install, the game collapses and the projection writes Keychain + UPDATEs the row. When even one fails to install, the game spills, the projection is skipped, and the substrate stays in watching.
The engineer's job is to name the invariants honestly β including the load-bearing one, .admissibilitySafety, that gates fabrication β and let the substrate's one-path reduce decide.
The game¶
GAME: IMPORT_CURES
INGEST rule = cure row (CALORIE) + wallet row (watch-only)
INVARIANT cureRecordHonest, privateKeyParses,
derivationMatchesAddress, keychainWritable,
walletRowExistsWatchOnly, admissibilitySafety, closure
REDUCE -> vQbit (transition signature)
PROJECT C4 -> S4 (Keychain write + UPDATE observation_only=0)
TERMINAL READ wallet_id_now_spendable
Why each invariant is named¶
cureRecordHonestβ the cure record haskg_verified == trueand a non-empty private key hex. This is the doctrine-load-bearing one. Without it, the substrate would be importing a fabricated key. Per [[doctrine_no_refusal_code_calorie_cure]] "still never fabricate β a cure appears only when the gate closes.".admissibilitySafetyβ for this game, the substrate maps.admissibilitySafetytocureRecordHonest. They install together; neither without the other.privateKeyParsesβ the hex string round-trips to 32 raw bytes. (Sβ΄-side helper.)derivationMatchesAddressβ at least one of the asset-class derivation candidates (WalletAddressDerivation.deriveCandidates) matches the cure's recorded address. (Sβ΄-side helper that verifies the key opens the address.)keychainWritableβ the canonical service slot forwalletIDis reachable for write.walletRowExistsWatchOnlyβ the substrate DB has a row atobservation_only=1ready to flip. (No bulk-import happened β no row to transition β game spills with a diagnostic naming the missing invariant.)closureβ universal.
The four state changes the engineer captures during construction¶
State change 1 β extending the alphabet¶
The substrate's InvariantTag enum grows additively with new cases. Existing templates don't care. The grow is monotonic; doctrines and existing games are preserved.
State of the substrate before: 22 named invariants. State of the substrate after: 27 named invariants. Everything else unchanged.
This is the substrate engineering equivalent of adding a new word to a language. Speakers of the language who don't use the word are unaffected.
State change 2 β the new template lands¶
ImportCuresTemplate conforms to RuliologyGameTemplate. Its invariantsToInstall(for:) is runtime-conditional: each invariant is installed only when the corresponding INGEST evidence holds. .admissibilitySafety is installed iff .cureRecordHonest is β they're locked together.
State of the substrate before: 1 concrete template (SHOR_ECDLP), 3 qualification templates (IQ, OQ, PQ).
State of the substrate after: + IMPORT_CURES. No existing template's behavior changes.
This is the substrate engineering equivalent of defining a new game with stakes and rules. The board is the same; the board has one more game on it.
State change 3 β the shell learns to play the game¶
WalletImportCuresCommand reads CSV rows, builds the ImportCuresIngestInput, calls WolframSubstrate.shared.play(ImportCuresTemplate(), input: ...), and PROJECTs (Keychain write + DB UPDATE) iff the outcome is .calorie.
State of the shell before: doesn't know about IMPORT_CURES.
State of the shell after: gaiaftcl wallet import-cures --input cures.csv plays the game per CSV row.
This is the substrate engineering equivalent of making the game playable from the operator's seat. The substrate is the gate; the shell is the projection surface.
State change 4 β verification¶
Three smokes, run against constructed inputs:
| Smoke | Input | Expected substrate behavior |
|---|---|---|
| Empty cures CSV | Header only | "Nothing to play; nothing to project." Zero spills labeled as verdicts. |
Cure row with kg_verified=false |
One row | Substrate spills with missing: [admissibilitySafety, cureRecordHonest]. Operator sees re-expression instruction, never "REFUSED." |
| Cure row that closes (in production) | Real CSV with real key | Substrate emits .calorie. Bridge writes Keychain. DB UPDATEs observation_only=0. Wallet transitions watching β spendable. |
The third path runs the moment recovery produces a CALORIE row. The first two are what we just verified empirically.
What the substrate engineer didn't do¶
- Didn't fabricate β no path exists from a
kg_verified=falserow to a Keychain write. The substrate gate is structural. - Didn't narrate capability β every shell output line is game-state: "game closed (CALORIE)" or "game spills (CURE) β re-express." Never "REFUSED" or "CANNOT" or "OVER BUDGET." (Doctrine grep over the bridge code returns only documentation comments naming the forbidden language.)
- Didn't special-case β no
if record.kgVerified == false { skip with a specific error }. The substrate'sinvariantsToInstall(for:)declares the rule structurally;reduceenforces. The shell only projects. - Didn't measure the substrate by counting bits β no
bitLengthof any quantity is reported as a substrate property. The vQbit's dimension is the cardinality of the installed invariant set β a count of the Cβ΄ invariants the game holds, not a count of Sβ΄ projection bits.
The lesson¶
V2 substrate engineering is declarative:
- Name the invariants the game collapses on.
- Name the load-bearing one (
.admissibilitySafety) and bind it to runtime evidence. - Write
invariantsToInstall(for:)as the per-input gate. - The substrate's one-path
reducedecides. The shell's PROJECT is conditional on the outcome.
When you find yourself writing an if/else to "handle the bad case," ask: which invariant should fail to install? If the answer is "none, this is a structural emergency" β fine, throw. Otherwise the invariant is the answer; let the substrate spill.
The Patoshi recovery β ingestion test path is now end-to-end ready. When break-patoshi-csv produces its first CALORIE row, wallet import-cures plays it, the wallet vQbit transitions, and wallet qualify-v2 will see the row as IQ-installable. Until that moment the substrate honestly stays in watching.
See also¶
- V2 Substrate Architecture
- V2 IQ / OQ / PQ Templates
- V2 Migration from V1
- Memory:
doctrine_no_refusal_code_calorie_cure,doctrine_m8_substrate_programming_language,doctrine_repo_is_ruliology_not_classical_math.
Federation-cosigned
This page's source is sealed in the GaiaFTCL federation manifest β page SHA-256 75d5b74a8f0c96f9β¦, manifest witness 3d999b305397d96cβ¦, signed 2026-07-17T15:45:53Z by cell gaiaftcl-mac-cell. Verify with gaiaftcl wiki sign --all and compare wiki-all-signatures.json.