diff --git a/.github/workflows/entity-wallet-desktop.yml b/.github/workflows/entity-wallet-desktop.yml new file mode 100644 index 00000000..6d1b6ff5 --- /dev/null +++ b/.github/workflows/entity-wallet-desktop.yml @@ -0,0 +1,122 @@ +name: ENTITY Wallet Cross-Platform Builds + +on: + workflow_dispatch: + push: + branches: + - main + - wallet-native-asset-ingest-20261003 + paths: + - "src/31_Profiles/exchange_protocol.py" + - "src/33_Economic_Participation/**" + - "src/38_Global_Passports/**" + - "src/39_Implementation_Packages/**" + - "src/42_ENTITY_Wallet/**" + - "src/43_DCO_Factory/**" + - "src/45_ENTITY_Market/**" + - "sdk/global_passport_sdk/**" + - "protocol/profiles/**" + - "protocol/v3/ENTITY_ECONOMIC_MARKET_PROTOCOL.md" + - "tools/entity_wallet_desktop.py" + - "tools/build_entity_wallet_desktop.py" + - "ios/ENTITYWalletIOS/**" + - ".github/workflows/entity-wallet-desktop.yml" + pull_request: + paths: + - "src/31_Profiles/exchange_protocol.py" + - "src/33_Economic_Participation/**" + - "src/38_Global_Passports/**" + - "src/39_Implementation_Packages/**" + - "src/42_ENTITY_Wallet/**" + - "src/43_DCO_Factory/**" + - "src/45_ENTITY_Market/**" + - "sdk/global_passport_sdk/**" + - "protocol/profiles/**" + - "tools/entity_wallet_desktop.py" + - "tools/build_entity_wallet_desktop.py" + - "ios/ENTITYWalletIOS/**" + - ".github/workflows/entity-wallet-desktop.yml" + +permissions: + contents: read + +jobs: + desktop: + name: Desktop · ${{ matrix.label }} + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + label: Windows + artifact: ENTITY-Wallet-Windows + - os: ubuntu-latest + label: Linux + artifact: ENTITY-Wallet-Linux + - os: macos-latest + label: macOS + artifact: ENTITY-Wallet-macOS + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - run: python -m pip install --upgrade pip + - run: python -m pip install pytest pyinstaller cryptography PySide6 + - name: Wallet qualification + run: >- + python -m pytest + tests/test_wallet_onboarding.py + tests/test_entity_market_registry.py + tests/test_economic_intelligence.py + tests/test_exchange_lifecycle.py + tests/test_v3_economic_participation.py + tests/test_prelaunch_economic_withdrawal.py + tests/test_entity_wallet.py + tests/test_wallet_asset_ingest.py + tests/test_wallet_lineage_resolution.py + tests/test_v3_protocol_origin_lineage.py + tests/test_v3_global_passports.py + -q + - name: Build desktop wallet + run: python tools/build_entity_wallet_desktop.py + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + if-no-files-found: error + path: | + dist/ENTITY-Wallet/** + dist/ENTITY-Wallet.app/** + + ios-simulator: + name: iOS · Simulator client + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Install XcodeGen + run: brew install xcodegen + - name: Generate Xcode project + working-directory: ios/ENTITYWalletIOS + run: xcodegen generate + - name: Build iOS Simulator app + run: | + xcodebuild \ + -project ios/ENTITYWalletIOS/ENTITYWalletIOS.xcodeproj \ + -scheme ENTITYWalletIOS \ + -configuration Release \ + -sdk iphonesimulator \ + -derivedDataPath build/ios \ + CODE_SIGNING_ALLOWED=NO \ + build + - name: Package iOS Simulator app + run: | + mkdir -p dist + APP="build/ios/Build/Products/Release-iphonesimulator/ENTITYWalletIOS.app" + test -d "$APP" + ditto -c -k --sequesterRsrc --keepParent "$APP" dist/ENTITY-Wallet-iOS-Simulator.zip + - uses: actions/upload-artifact@v4 + with: + name: ENTITY-Wallet-iOS-Simulator + if-no-files-found: error + path: dist/ENTITY-Wallet-iOS-Simulator.zip diff --git a/ENTITY_CURRENT_RELEASE_ORIGIN.json b/ENTITY_CURRENT_RELEASE_ORIGIN.json new file mode 100644 index 00000000..314193e1 --- /dev/null +++ b/ENTITY_CURRENT_RELEASE_ORIGIN.json @@ -0,0 +1,29 @@ +{ + "body": { + "asset_provenance_is_separate_from_protocol_origin": true, + "automatic_protocol_royalty_bps": 0, + "created_at_ms": 1791032742763, + "economic_participation_requires_explicit_terms": true, + "historical_release_is_immutable_target": true, + "origin_lineage_id": "entity-origin:shawn-btg-entity@1.0", + "origin_lineage_sha256": "9d3bdc0ef2e6a27de5368541f6bc014fdec93b2c7af9770d163c804521b9efff", + "protocol_entity_id": "ent2-m3nofxtv3zwldhwuonw3h67hqjh2zambrhj4kaaulncpt4amkcua", + "release_commit_sha1": "528b70aabd05b1e930b77e4933f157731e47274f", + "release_ref": "entity-release:v3.4.3", + "release_tag": "v3.4.3", + "release_tree_sha1": "f13e42f515dcc4cd985e9b832e0ecfec416ce423", + "root_originator_entity_id": "ent2-6eoiiztjyhmyh2tbr5psmofcduwvjledubhoiqwo6mjfoqkn6ica", + "schema": "entity-protocol-release-origin-v1", + "steward_entity_id": "ent2-kkov66eoh23h3zgr4pe4njsbkayn2kxad6vfyirqvuqrty52clpa" + }, + "body_sha256": "150c4e02671b2175a45df921fcf6761119a6c587001c9b8fa039dd01ffe0e494", + "signature": { + "entity_id": "ent2-m3nofxtv3zwldhwuonw3h67hqjh2zambrhj4kaaulncpt4amkcua", + "key_id": "sig-a31ded371cfb5019", + "payload_sha256": "150c4e02671b2175a45df921fcf6761119a6c587001c9b8fa039dd01ffe0e494", + "signature": "cWSubQ8c3pJiZQArSlffOAZMlMfSkn6lb3lNDpWSv2Ol05zDY9Z0do7FoLu6GoBwf1xOZDIV6ekh6IHAhInsBg", + "signature_schema": "entity-signature-record-v2", + "signed_at_ms": 1791032742806, + "suite": "ENTITY-SIG-ED25519-v1" + } +} diff --git a/ENTITY_CURRENT_RELEASE_ORIGIN.json.sha256 b/ENTITY_CURRENT_RELEASE_ORIGIN.json.sha256 new file mode 100644 index 00000000..2311b76e --- /dev/null +++ b/ENTITY_CURRENT_RELEASE_ORIGIN.json.sha256 @@ -0,0 +1 @@ +aa4d7b32778bda263c097a8a46a3f654070b0ae48f3142664bfe485e6ce04c17 ENTITY_CURRENT_RELEASE_ORIGIN.json diff --git a/ENTITY_V3_EEP_CONFORMANCE_MANIFEST.json b/ENTITY_V3_EEP_CONFORMANCE_MANIFEST.json index 0a1f4ca1..bded6ee1 100644 --- a/ENTITY_V3_EEP_CONFORMANCE_MANIFEST.json +++ b/ENTITY_V3_EEP_CONFORMANCE_MANIFEST.json @@ -1,106 +1,106 @@ -{ - "based_on_local_commit": "68179328a2b87408af93a5caa1c64312e70247be", - "claim_boundaries": [ - "development conformance evidence is not a released protocol qualification", - "signature cryptography remains governed by the ENTITY identity layer", - "stateful market authorization and signature verification are tested by executable campaigns in addition to schema vectors", - "external independent EEP clean-room interoperability remains pending" - ], - "conformance": { - "complete_active_tree": { - "passed": 90, - "total": 90 - }, - "json_schema_draft": "2020-12", - "record_definitions": 13, - "reference_mirror_convergence_tests": { - "passed": 16, - "total": 16 - }, - "schema_and_vector_tests": { - "passed": 4, - "total": 4 - }, - "signed_wire_schemas": 11, - "validator_chain": [ - "JSON_SCHEMA_DRAFT_2020_12", - "ENTITY_CANONICAL_SIGNED_WIRE_RULES" - ], - "vectors_expected_invalid": 14, - "vectors_expected_valid": 13, - "vectors_total": 27 - }, - "created_date": "2026-09-23", - "files": [ - { - "path": "src/31_Profiles/exchange_protocol.py", - "sha256": "53761f15c7ae58726ce8cd01a28164d3a14ae29aac38a89859c408fc3c76b14a" - }, - { - "path": "src/32_V3_Hardening/exchange_protocol.py", - "sha256": "767e2a83a7ab5a71256c4f9535b75d91e7d33c97aaa7b7cefc617ffa7874bf41" - }, - { - "path": "protocol/v3/ENTITY_EXCHANGE_PROTOCOL_DRAFT.md", - "sha256": "1733dcf2055a4353abd611344582438a04cbae08b358451290885697c665dbd7" - }, - { - "path": "protocol/v3/ENTITY_EXCHANGE_PROTOCOL.schema.json", - "sha256": "146932a4598797e295ad7d86ac3ee3ee80261fc1675b1bfb4724df0e05fa3218" - }, - { - "path": "protocol/v3/eep_vectors/VECTOR_MANIFEST.json", - "sha256": "08fe381309dc122e0efb8544490e23287e53b3d37cea868b5cd359cc736c4b53" - }, - { - "path": "protocol/v3/eep_vectors/SHA256SUMS.txt", - "sha256": "68989a573a74046e55d6b2713f9fdee8304d8b709d22dd5ccd17f32791d927b7" - }, - { - "path": "protocol/v3/eep_vectors/README.md", - "sha256": "ff734344b2be9e523109c677b4aa746b19eb7e505bd4ba01be8f11ef44ca030e" - }, - { - "path": "tests/test_v3_eep_conformance.py", - "sha256": "d832cc585662a8722bb3ac95763c6f487285983818794a314826792a6c11390f" - }, - { - "path": "tests/test_v3_exchange_convergence.py", - "sha256": "079767d481e2dd09582bbf8b5e97a0ea4334d3fe06acbcc32e99c03caddf29fd" - }, - { - "path": "tests/test_v3_hardening_exchange.py", - "sha256": "f7a7ecf9fa7d748d3953eb68c426945273c42fcf3a952048f21f0def773ecd17" - }, - { - "path": "requirements.txt", - "sha256": "ba36584e5af40e46435d63315f94fb6d600e3d18af53d123e321763f98f22668" - }, - { - "path": "tools/run_v3_regression.ps1", - "sha256": "0bc9f4acc6d1fb8b6b274c305bd89750b78e68ad5568887ee46a577385d5f5f9" - } - ], - "invariants": [ - "signed wire values are not silently normalized after signature verification", - "JSON integer fields reject numeric strings and booleans", - "enumerated tokens and usage actions are canonical uppercase", - "SHA-256 values are canonical lowercase hexadecimal", - "rights action lists are non-empty, unique, and lexicographically sorted", - "revenue allocations do not exceed 10000 basis points", - "RFQ and quote expiry follows signed creation time", - "entitlement expiry semantics converge across reference and qualification mirror", - "executed but unsettled sell quantities remain reserved until clearing completes", - "trade revenue allocation is bound at execution and is not changed by later rule-set mutation", - "sell admission rechecks unreserved entitlement atomically under concurrent submissions", - "concurrent matchers cannot create duplicate trades from the same executable orders", - "concurrent settlement attempts commit at most one entitlement transfer", - "revenue-event persistence is atomic with hardening-profile settlement state", - "RFQ executions preserve a deterministic venue-signed source binding to signed RFQ, quote and acceptance records" - ], - "schema": "entity-v3-eep-conformance-manifest-v1", - "snapshot_sha256": "65224aa97bde5456c4079097a37284ce6e023f3e1c635f25456683c74e3df91a", - "status": "BTG_INTERNAL_QUALIFIED_RELEASE_COMPONENT", - "version": "3.0.0", - "working_tree_snapshot": false -} +{ + "based_on_local_commit": "dedd47eb032b5a95d88f092cf3346298ca3011eb", + "claim_boundaries": [ + "development conformance evidence is not a released protocol qualification", + "signature cryptography remains governed by the ENTITY identity layer", + "stateful market authorization and signature verification are tested by executable campaigns in addition to schema vectors", + "external independent EEP clean-room interoperability remains pending" + ], + "conformance": { + "complete_active_tree": { + "passed": 90, + "total": 90 + }, + "json_schema_draft": "2020-12", + "record_definitions": 13, + "reference_mirror_convergence_tests": { + "passed": 16, + "total": 16 + }, + "schema_and_vector_tests": { + "passed": 4, + "total": 4 + }, + "signed_wire_schemas": 11, + "validator_chain": [ + "JSON_SCHEMA_DRAFT_2020_12", + "ENTITY_CANONICAL_SIGNED_WIRE_RULES" + ], + "vectors_expected_invalid": 14, + "vectors_expected_valid": 13, + "vectors_total": 27 + }, + "created_date": "2026-09-23", + "files": [ + { + "path": "src/31_Profiles/exchange_protocol.py", + "sha256": "fa3c040b31d9cfd7ab37daddeb2c904f52f03a4fc110d77bdd8067fa1b9d547b" + }, + { + "path": "src/32_V3_Hardening/exchange_protocol.py", + "sha256": "767e2a83a7ab5a71256c4f9535b75d91e7d33c97aaa7b7cefc617ffa7874bf41" + }, + { + "path": "protocol/v3/ENTITY_EXCHANGE_PROTOCOL_DRAFT.md", + "sha256": "1733dcf2055a4353abd611344582438a04cbae08b358451290885697c665dbd7" + }, + { + "path": "protocol/v3/ENTITY_EXCHANGE_PROTOCOL.schema.json", + "sha256": "146932a4598797e295ad7d86ac3ee3ee80261fc1675b1bfb4724df0e05fa3218" + }, + { + "path": "protocol/v3/eep_vectors/VECTOR_MANIFEST.json", + "sha256": "08fe381309dc122e0efb8544490e23287e53b3d37cea868b5cd359cc736c4b53" + }, + { + "path": "protocol/v3/eep_vectors/SHA256SUMS.txt", + "sha256": "68989a573a74046e55d6b2713f9fdee8304d8b709d22dd5ccd17f32791d927b7" + }, + { + "path": "protocol/v3/eep_vectors/README.md", + "sha256": "ff734344b2be9e523109c677b4aa746b19eb7e505bd4ba01be8f11ef44ca030e" + }, + { + "path": "tests/test_v3_eep_conformance.py", + "sha256": "d832cc585662a8722bb3ac95763c6f487285983818794a314826792a6c11390f" + }, + { + "path": "tests/test_v3_exchange_convergence.py", + "sha256": "079767d481e2dd09582bbf8b5e97a0ea4334d3fe06acbcc32e99c03caddf29fd" + }, + { + "path": "tests/test_v3_hardening_exchange.py", + "sha256": "f7a7ecf9fa7d748d3953eb68c426945273c42fcf3a952048f21f0def773ecd17" + }, + { + "path": "requirements.txt", + "sha256": "ba36584e5af40e46435d63315f94fb6d600e3d18af53d123e321763f98f22668" + }, + { + "path": "tools/run_v3_regression.ps1", + "sha256": "0bc9f4acc6d1fb8b6b274c305bd89750b78e68ad5568887ee46a577385d5f5f9" + } + ], + "invariants": [ + "signed wire values are not silently normalized after signature verification", + "JSON integer fields reject numeric strings and booleans", + "enumerated tokens and usage actions are canonical uppercase", + "SHA-256 values are canonical lowercase hexadecimal", + "rights action lists are non-empty, unique, and lexicographically sorted", + "revenue allocations do not exceed 10000 basis points", + "RFQ and quote expiry follows signed creation time", + "entitlement expiry semantics converge across reference and qualification mirror", + "executed but unsettled sell quantities remain reserved until clearing completes", + "trade revenue allocation is bound at execution and is not changed by later rule-set mutation", + "sell admission rechecks unreserved entitlement atomically under concurrent submissions", + "concurrent matchers cannot create duplicate trades from the same executable orders", + "concurrent settlement attempts commit at most one entitlement transfer", + "revenue-event persistence is atomic with hardening-profile settlement state", + "RFQ executions preserve a deterministic venue-signed source binding to signed RFQ, quote and acceptance records" + ], + "schema": "entity-v3-eep-conformance-manifest-v1", + "snapshot_sha256": "5d9ad374f4bfdce3181166a057d18b8bcca0ad966a1518f461967ad2a23ef18f", + "status": "BTG_INTERNAL_QUALIFIED_RELEASE_COMPONENT", + "version": "3.0.0", + "working_tree_snapshot": false +} diff --git a/ENTITY_V3_MARKET_RECOVERY_MANIFEST.json b/ENTITY_V3_MARKET_RECOVERY_MANIFEST.json index 6a1fb7a1..87deca75 100644 --- a/ENTITY_V3_MARKET_RECOVERY_MANIFEST.json +++ b/ENTITY_V3_MARKET_RECOVERY_MANIFEST.json @@ -1,66 +1,66 @@ -{ - "based_on_local_commit": "68179328a2b87408af93a5caa1c64312e70247be", - "claim_boundaries": [ - "market recovery does not export private signing keys", - "market recovery composes with but does not replace ENTITY Core identity/key recovery", - "external settlement remains attestation evidence", - "development evidence is not release qualification", - "external independent market-recovery reimplementation remains pending" - ], - "created_date": "2026-09-23", - "files": [ - { - "path": "src/34_Market_Recovery/market_recovery.py", - "sha256": "7a96f1e44b7910e7c1560074591c5a5dba208bd66d5e10702d828bd57b35e606" - }, - { - "path": "tests/test_v3_market_recovery.py", - "sha256": "e7a89fa2381421b21b37b6238217f72f8cc9cdf91259e82688128327b9b940ff" - }, - { - "path": "protocol/v3/ENTITY_MARKET_STATE_RECOVERY_PROFILE.md", - "sha256": "70be04b633a305b7c09d1873533370b1b059a2bc4227ae64c5b74320545df8c4" - }, - { - "path": "protocol/v3/ENTITY_V3_GAP_CLOSURE_MATRIX.md", - "sha256": "9b026fb2f29059fe8cb2bfac34f16845dae863dd420622738de224188828775c" - }, - { - "path": "ENTITY_V3_EEP_CONFORMANCE_MANIFEST.json", - "sha256": "aa7dd20a8d396084ead3b677eda0251912cb7ccaf8a857bd54d1df84ec7679d1" - }, - { - "path": "ENTITY_V3_EOPP_HARDENING_MANIFEST.json", - "sha256": "25292ef3320e31243c0155ddf297c5b6908b8a8c4b50993b1c66c3aff8295403" - } - ], - "implemented": [ - "provider_independent_logical_eep_state_export", - "provider_independent_logical_eopp_state_export", - "per_table_and_per_database_semantic_roots", - "embedded_public_signer_manifests", - "venue_operator_and_treasury_owner_root_attestations", - "linked_core_sovereign_bundles", - "empty_destination_fail_closed_restore", - "atomic_cross_database_restore", - "post_restore_exact_state_root_comparison", - "destructive_market_database_recovery", - "tamper_detection_after_hash_recomputation", - "required_trade_revenue_binding_recovery" - ], - "schema": "entity-v3-market-recovery-evidence-manifest-v1", - "snapshot_sha256": "947a0b9bf2aa60078ec990e28962392d428d4fd751a6e2cfebf4f26378abdfd3", - "status": "BTG_INTERNAL_QUALIFIED_RELEASE_COMPONENT", - "validation": { - "complete_active_tree": { - "passed": 90, - "total": 90 - }, - "targeted_market_recovery": { - "passed": 7, - "total": 7 - } - }, - "version": "3.0.0", - "working_tree_snapshot": false -} +{ + "based_on_local_commit": "87cc4d9f8760f3495881296633e36c11236f7780", + "claim_boundaries": [ + "market recovery does not export private signing keys", + "market recovery composes with but does not replace ENTITY Core identity/key recovery", + "external settlement remains attestation evidence", + "development evidence is not release qualification", + "external independent market-recovery reimplementation remains pending" + ], + "created_date": "2026-09-23", + "files": [ + { + "path": "src/34_Market_Recovery/market_recovery.py", + "sha256": "7a96f1e44b7910e7c1560074591c5a5dba208bd66d5e10702d828bd57b35e606" + }, + { + "path": "tests/test_v3_market_recovery.py", + "sha256": "e7a89fa2381421b21b37b6238217f72f8cc9cdf91259e82688128327b9b940ff" + }, + { + "path": "protocol/v3/ENTITY_MARKET_STATE_RECOVERY_PROFILE.md", + "sha256": "70be04b633a305b7c09d1873533370b1b059a2bc4227ae64c5b74320545df8c4" + }, + { + "path": "protocol/v3/ENTITY_V3_GAP_CLOSURE_MATRIX.md", + "sha256": "9b026fb2f29059fe8cb2bfac34f16845dae863dd420622738de224188828775c" + }, + { + "path": "ENTITY_V3_EEP_CONFORMANCE_MANIFEST.json", + "sha256": "9d5e55aebe5de480f779791a931aff787d2b048fc12a6090bf868250af57cf10" + }, + { + "path": "ENTITY_V3_EOPP_HARDENING_MANIFEST.json", + "sha256": "25292ef3320e31243c0155ddf297c5b6908b8a8c4b50993b1c66c3aff8295403" + } + ], + "implemented": [ + "provider_independent_logical_eep_state_export", + "provider_independent_logical_eopp_state_export", + "per_table_and_per_database_semantic_roots", + "embedded_public_signer_manifests", + "venue_operator_and_treasury_owner_root_attestations", + "linked_core_sovereign_bundles", + "empty_destination_fail_closed_restore", + "atomic_cross_database_restore", + "post_restore_exact_state_root_comparison", + "destructive_market_database_recovery", + "tamper_detection_after_hash_recomputation", + "required_trade_revenue_binding_recovery" + ], + "schema": "entity-v3-market-recovery-evidence-manifest-v1", + "snapshot_sha256": "b1622bcf6e909271b06f8d19de8a724a485edf4dea417ebb7e52bbc402398d1a", + "status": "BTG_INTERNAL_QUALIFIED_RELEASE_COMPONENT", + "validation": { + "complete_active_tree": { + "passed": 90, + "total": 90 + }, + "targeted_market_recovery": { + "passed": 7, + "total": 7 + } + }, + "version": "3.0.0", + "working_tree_snapshot": false +} diff --git a/README.md b/README.md index 86ae41f8..155e702e 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ The remediation keeps authoritative occurrence identity separate from evidence i | Compiled Rust qualification | **PASS — inherited unchanged scope** | | Real-world BTDU training qualification | **PASS — inherited unchanged scope** | -Release merge commit: `528b70aabd05b1e930b77e4933f157731e47274f` +Release merge commit: `528b70aabd05b1e930b77e4933f157731e47274f` Immutable predecessor commit: `6dfa3d6cc738d9369cf092d2782676bf4f2a46e4` The v3.4.3 release does **not** rewrite the active 30-day wall-clock evidence campaign or immutable v3.4.2 evidence. @@ -144,18 +144,24 @@ Originator participation may be expressed through explicit terms such as issuanc ### ENTITY Economic Wallet -ENTITY v3.4.3 includes a **stock-style economic wallet application layer** over EEP and the Originator Participation Profile. +ENTITY v3.4.3 includes the **ENTITY Data Economy Terminal**, a native wallet application layer over DCO state, Global/Rights Passports, EEP and the Originator Participation Profile. -- **Participant Wallet:** rights positions, entitlements, usage, orders, last settled price, bid/ask, FIFO settled-trade cost basis, and economic receivables/payables. -- **Treasury Wallet:** reserve rights, participation policies, originator obligations and economic events for a distinct treasury ENTITY. -- **Fiat remains external:** the wallet does not custody CAD/USD or store payment credentials; bank/payment-service evidence feeds the existing settlement path. -- **No artificial value:** bids and offers remain quotes; only settled trades can create an observed last-price mark, and unpriced rights remain unpriced. +Current wallet behavior includes: + +- **Sovereign first-run onboarding:** create/adopt an ENTITY identity, claim a public `.entity` alias, optionally establish an organization, bind a separate Device ENTITY and authenticate through device-bound signing. +- **Digital Assets:** controller-held DCOs with lineage, signed Asset Disclosure state, Global Passport/profile stack, explicit domain and `NO TICKER` when no instrument exists. +- **Rights-Passport-driven issuance:** an economic instrument may select only a subset of actions already authorized by the active DCO Rights Passport; the canonical market registry re-validates this fail closed. +- **Issuer-scoped tickers/instruments:** ticker suggestions derive from the selected authorized rights and canonical asset short name rather than a generic commercial-right default. +- **Market / positions / orders:** instruments and positions remain separate from underlying DCO assets; listing and market activity do not manufacture intrinsic DCO value. +- **Software Engineering domain:** `entity-profile:software-engineering@1.0` explicitly covers software repositories, algorithms, libraries, build artifacts, test evidence and engineering-control DCOs. +- **Fiat remains external:** the wallet does not custody CAD/USD or store payment credentials. - **No token requirement:** `cryptocurrency_required = false` and `protocol_tax_bps = 0`. -- **No legal classification inference:** stock-style describes the market/portfolio interface; the wallet does not declare DCO/EEP rights to be corporate shares or securities. -The wallet does not create a second economic ledger. EEP balances, trades and entitlements remain authoritative, and transaction authority remains in the existing ENTITY/EEP signature path. +The final focused wallet/economy/lineage/onboarding/global-passport qualification completed on 2026-10-03 at **91/91 PASS**. The qualified production snapshot resolved all 12 current assets to explicit domains: **10 Software Engineering + 2 Robotics**. + +Windows, Linux and macOS are full PySide6 desktop builds of the canonical wallet. CI also builds a native Xcode iOS Simulator portable-snapshot client; native iOS authority/write parity is explicitly not claimed. -[**Wallet design, deployment and usage**](docs/v3.4/ENTITY_WALLET_20261002.md) · [**Public qualification**](docs/evidence/v3.4.3-entity-wallet-20261002/QUALIFICATION.json) +[**Wallet design and current state**](docs/v3.4/ENTITY_WALLET_20261002.md) · [**Cross-platform builds**](docs/v3.4/ENTITY_WALLET_CROSS_PLATFORM_BUILDS_20261003.md) · [**Identity onboarding**](docs/v3.4/ENTITY_WALLET_IDENTITY_ONBOARDING.md) · [**Software Engineering profile**](docs/v3.4/passports/SOFTWARE_ENGINEERING.md) --- diff --git a/RELEASE_V3_4_3.md b/RELEASE_V3_4_3.md index 7dbbc80e..ec867381 100644 --- a/RELEASE_V3_4_3.md +++ b/RELEASE_V3_4_3.md @@ -29,6 +29,16 @@ Causal tracing now validates both endpoints before traversal. Missing or empty e - v3.4.2 remains immutable historical evidence. - Production state and the active 30-day wall-clock qualification were not modified. +## Post-tag wallet application layer on main + +The immutable `v3.4.3` tag remains the bounded remediation release described above. Subsequent main-branch application-layer work adds the ENTITY Data Economy Terminal without changing the Protocol 1.0 freeze or silently rewriting the historical tag. + +The current wallet application layer includes sovereign first-run identity/device onboarding, DCO asset holdings, signed asset disclosures, issuer-scoped instruments/tickers, Rights-Passport-driven instrument issuance, the Software Engineering domain/profile and cross-platform wallet builds. + +The final focused wallet/economy/lineage/onboarding/global-passport campaign completed on 2026-10-03 at **91/91 PASS**. Production-state verification resolved 12 current assets to explicit domains: 10 Software Engineering and 2 Robotics. + +See `docs/v3.4/ENTITY_WALLET_20261002.md` and `docs/v3.4/ENTITY_WALLET_CROSS_PLATFORM_BUILDS_20261003.md`. + ## Claim boundary This release is limited to the two reproduced Issue #28 defects. It does not establish external settlement, payment, legal-entitlement, fair-value, or broader economic-causality claims beyond the qualified behavior above. diff --git a/docs/ENGINEERING_EVIDENCE.md b/docs/ENGINEERING_EVIDENCE.md index 5896db9b..73394ef1 100644 --- a/docs/ENGINEERING_EVIDENCE.md +++ b/docs/ENGINEERING_EVIDENCE.md @@ -1,9 +1,9 @@ # ENTITY Engineering Evidence -**Document class:** evidence index / current-runtime orientation -**Current supported runtime:** ENTITY v3.4.3 -**BTDU component:** Blackmore Technology Data Universe (BTDU) 3.4.2, unchanged -**Protocol 1.0:** separate frozen clean-room target +**Document class:** evidence index / current-runtime orientation +**Current supported runtime:** ENTITY v3.4.3 +**BTDU component:** Blackmore Technology Data Universe (BTDU) 3.4.2, unchanged +**Protocol 1.0:** separate frozen clean-room target **Normative effect:** none; this page indexes and explains evidence rather than changing it This document separates **what Blackmore Technology Group Limited (BTG) has directly qualified**, **what unrelated people have reproduced**, and **what still requires independent evidence outside BTG control**. @@ -28,8 +28,8 @@ A successful result at class 4 does not automatically become class 5 or 6. A BTG ## Current supported release — ENTITY v3.4.3 -**Release:** `v3.4.3` -**Release merge commit:** `528b70aabd05b1e930b77e4933f157731e47274f` +**Release:** `v3.4.3` +**Release merge commit:** `528b70aabd05b1e930b77e4933f157731e47274f` **Immutable predecessor:** v3.4.2 commit `6dfa3d6cc738d9369cf092d2782676bf4f2a46e4` ENTITY v3.4.3 is a bounded remediation release for the two defects reproduced in Issue #28: @@ -63,6 +63,20 @@ These are **inherited records**, not re-executed v3.4.3 campaigns. Authoritative release material: [`RELEASE_V3_4_3.md`](../RELEASE_V3_4_3.md) and [`RELEASE_V3_4_3.json`](../RELEASE_V3_4_3.json). +## Post-tag ENTITY Wallet qualification — 2026-10-03 + +The current main-branch wallet application layer is later than the immutable `v3.4.3` tag and is therefore recorded separately from the tagged release evidence. + +Final focused wallet/economy/lineage/onboarding/global-passport campaign: **91/91 PASS**. + +Wallet implementation baseline: `212ee56d34aee578e6df701ef9f9a675661b7162`. + +The locally rebuilt/installed Windows package passed its frozen-runtime self-test. Qualified production-state domain resolution contained 12 current assets: 10 Software Engineering and 2 Robotics, with no missing domain. + +Windows/Linux/macOS full desktop binaries are produced by the cross-platform wallet workflow. The iOS Simulator artifact is a native portable-snapshot inspection client and is not classified as an independently qualified native authority/runtime implementation. + +See [wallet state](v3.4/ENTITY_WALLET_20261002.md) and [cross-platform builds](v3.4/ENTITY_WALLET_CROSS_PLATFORM_BUILDS_20261003.md). + ## External reproduction evidence An unrelated GitHub contributor reproduced the published BTG-controlled Java v3.4.2 26-vector baseline on Windows 11 / OpenJDK 21 / Maven 3.9.6 and reported: @@ -103,7 +117,7 @@ A full external Protocol 1.0 interoperability PASS still requires the published ## v3.4.1 — historical protected release -**Release:** `v3.4.1` +**Release:** `v3.4.1` **Protected release commit:** `9822b1b65f8269ebc17208a342809720729ae2f8` Historical qualification retained exactly as v3.4.1 evidence: @@ -122,7 +136,7 @@ Protocol origin remains separate from user/asset provenance and does not create ## v3.4.0 — historical protected release and domain packages -**Release:** `v3.4.0` +**Release:** `v3.4.0` **Protected release commit:** `2db5bff64507b8d67642122a5ff2fc73dfef9152` Historical qualification: diff --git a/docs/v3.4/BIRFR1_DCO_000001_20261002.md b/docs/v3.4/BIRFR1_DCO_000001_20261002.md index 677a24b1..83b4c92c 100644 --- a/docs/v3.4/BIRFR1_DCO_000001_20261002.md +++ b/docs/v3.4/BIRFR1_DCO_000001_20261002.md @@ -69,7 +69,13 @@ They are real BTG-generated asset bytes and qualify the multimodal data path, bu High-fidelity simulation and real-hardware episodes belong in later versioned BIRFR releases rather than being silently mixed into v0.1.0. -## Economic family +## Asset orientation + +BIRFR-1 is first and foremost a **BTG-created robotics digital asset ingested into ENTITY**. Its dataset bytes, provenance, qualification, DCO registration and passport identity exist independently from any licence or market instrument. + +Creating or ingesting BIRFR-1 does not inherently create a price and does not require BTG to offer licences. The controlled prelaunch EEP instruments described below are qualification/demo market state only; market participation is optional and must be explicitly activated by the controller. + +## Optional economic family The BIRFR dataset-specific Strategic template uses eleven commercial archetypes: diff --git a/docs/v3.4/DCO_PRODUCTION_PIPELINE_20261002.md b/docs/v3.4/DCO_PRODUCTION_PIPELINE_20261002.md index ddf0d838..fa0e2b30 100644 --- a/docs/v3.4/DCO_PRODUCTION_PIPELINE_20261002.md +++ b/docs/v3.4/DCO_PRODUCTION_PIPELINE_20261002.md @@ -2,15 +2,15 @@ ENTITY display version: **3.4.3** -The DCO Factory now defines a mandatory application-layer production path for serious economic assets: +The DCO Factory now defines an asset-first application-layer production path for serious digital assets: -`BUILD ASSET → QUALIFY → SEAL PROVENANCE → REGISTER DCO → RIGHTS PASSPORT → GLOBAL PASSPORT → PROFILE + BTDU BINDING → ECONOMIC INSTRUMENTS → OPTIONAL SHARED BRIDGE → PUBLIC-SAFE EVIDENCE` +`BUILD ASSET → QUALIFY → SEAL PROVENANCE → REGISTER DCO → RIGHTS PASSPORT → GLOBAL PASSPORT → PROFILE + BTDU BINDING → PUBLIC-SAFE EVIDENCE`\n\nOptional after the asset exists: `ECONOMIC INSTRUMENTS / MARKET LISTING` and `SHARED BRIDGE EXPOSURE`. ## Why the order matters A DCO must not reach factory-managed economic issuance merely because an identifier and supply exist. -The production-pipeline registry blocks the `ECONOMIC_INSTRUMENTS_ISSUED` stage unless: +The production-pipeline registry blocks optional `ECONOMIC_INSTRUMENTS_ISSUED` unless: - the underlying asset exists; - qualification evidence exists; @@ -20,7 +20,7 @@ The production-pipeline registry blocks the `ECONOMIC_INSTRUMENTS_ISSUED` stage - a Global Passport is issued; - profile and BTDU binding is complete. -This is an application-layer issuance gate. It does not replace the canonical Universal Transaction Fabric, Rights Passport, Global Passport, BTDU, EEP, wallet or settlement stores. +This is an application-layer asset qualification gate. It does not replace the canonical Universal Transaction Fabric, Rights Passport, Global Passport, BTDU, EEP, wallet or settlement stores. **Ingesting or registering a DCO does not automatically issue a licence, create market supply, list an instrument or establish a price.** ## Rights Passport diff --git a/docs/v3.4/DOMAIN_PACKAGES.md b/docs/v3.4/DOMAIN_PACKAGES.md index 0011ccdb..7dc7c2b1 100644 --- a/docs/v3.4/DOMAIN_PACKAGES.md +++ b/docs/v3.4/DOMAIN_PACKAGES.md @@ -1,24 +1,37 @@ -# ENTITY v3.4.0 Domain Packages +# ENTITY v3.4 Domain Packages -The six repositories below are **deployable domain packages for the one ENTITY Global Passport**. They are not six separate passport protocols. +ENTITY uses **one Global Passport** with composable domain profiles and implementation packages. Domain packages do not create separate passport protocols or separate sovereignty systems. -| Domain | Repository | Included mappings | Package SHA-256 | +## Public domain packages and built-in profiles + +| Domain | Distribution | Included mappings | Status | | --- | --- | --- | --- | -| [Healthcare](passports/HEALTHCARE.md) | [ENTITY-HEALTHCARE](https://github.com/blackmore-technology-group/ENTITY-HEALTHCARE) | HL7 FHIR · DICOM | `b4e901ce37f696fa…` | -| [Finance](passports/FINANCE.md) | [ENTITY-FINANCE](https://github.com/blackmore-technology-group/ENTITY-FINANCE) | ISO 20022 · FIX · LEI | `768dd87fd5a29c7e…` | -| [Manufacturing](passports/MANUFACTURING.md) | [ENTITY-MANUFACTURING](https://github.com/blackmore-technology-group/ENTITY-MANUFACTURING) | OPC UA · Asset Administration Shell | `bc29a0de024cea22…` | -| [AI](passports/AI.md) | [ENTITY-AI](https://github.com/blackmore-technology-group/ENTITY-AI) | NIST AI RMF · SPDX 3 · CycloneDX | `4877cb5bc76ef080…` | -| [Robotics](passports/ROBOTICS.md) | [ENTITY-ROBOTICS](https://github.com/blackmore-technology-group/ENTITY-ROBOTICS) | ROS 2 · Open-RMF | `3a0e68fa6c63b2ef…` | -| [Defence / Public-Unclassified](passports/DEFENCE_PUBLIC.md) | [ENTITY-DEFENCE](https://github.com/blackmore-technology-group/ENTITY-DEFENCE) | Public Data Governance · Originator Control | `7edc52344822e370…` | +| [Healthcare](passports/HEALTHCARE.md) | [ENTITY-HEALTHCARE](https://github.com/blackmore-technology-group/ENTITY-HEALTHCARE) | HL7 FHIR · DICOM | first-party package | +| [Finance](passports/FINANCE.md) | [ENTITY-FINANCE](https://github.com/blackmore-technology-group/ENTITY-FINANCE) | ISO 20022 · FIX · LEI | first-party package | +| [Manufacturing](passports/MANUFACTURING.md) | [ENTITY-MANUFACTURING](https://github.com/blackmore-technology-group/ENTITY-MANUFACTURING) | OPC UA · Asset Administration Shell | first-party package | +| [AI](passports/AI.md) | [ENTITY-AI](https://github.com/blackmore-technology-group/ENTITY-AI) | NIST AI RMF · SPDX 3 · CycloneDX | first-party package | +| [Robotics](passports/ROBOTICS.md) | [ENTITY-ROBOTICS](https://github.com/blackmore-technology-group/ENTITY-ROBOTICS) | ROS 2 · Open-RMF | first-party package | +| [Defence / Public-Unclassified](passports/DEFENCE_PUBLIC.md) | [ENTITY-DEFENCE](https://github.com/blackmore-technology-group/ENTITY-DEFENCE) | Public Data Governance · Originator Control | first-party package | +| [Software Engineering](passports/SOFTWARE_ENGINEERING.md) | core ENTITY repository | SPDX 3 · CycloneDX · SLSA | built-in profile/package | + +The six historical external domain repositories retain their sealed package evidence. Software Engineering was added on 2026-10-03 as a built-in signed profile and implementation package so software repositories, algorithms, build artifacts, test evidence and engineering-control DCOs no longer fall back to an unspecified/global-only domain. ## Deployment model -**Select package → configure organization facts → connect systems/data → ingest → verify passport → run conformance → deploy.** +**Select package/profile → configure organization facts → connect systems/data → ingest → verify passport → run conformance → deploy.** + +Every domain mapping remains subordinate to the registered profile and underlying ENTITY authority/rights state. External standards remain externally authoritative: ENTITY mappings describe correspondence under a mapping version and do not claim normative equivalence. + +## Wallet domain behavior + +The ENTITY Wallet resolves the current Global Passport/profile stack for each DCO. Domain presentation does not create economic rights. -Every package contains schemas, mappings, templates, conformance fixtures, SDK material and a package verifier. External standards remain externally authoritative: ENTITY mappings describe correspondence under a mapping version and do not claim normative equivalence. +The 2026-10-03 production migration issued new immutable Global Passport versions for ten legacy `ENGINEERING_CONTROL` DCOs while preserving prior passports and underlying DCO state. The qualified production wallet then resolved all 12 current assets to explicit domains: -The sealed package contents are intentionally preserved. Additional documentation and GitHub wiki material may be published without modifying the sealed package hashes. +- 10 Software Engineering; +- 2 Robotics; +- 0 missing domains. ## Qualification boundary -Package verification demonstrates internal package integrity and binding to the v3.4 release materials. It does not establish regulatory compliance, legal title, objective external truth, accounting fair value, independent security review or unrelated third-party interoperability. +Package/profile verification demonstrates internal integrity and binding to ENTITY release/runtime material. It does not establish regulatory compliance, legal title, objective external truth, accounting fair value, independent security review or unrelated third-party interoperability. diff --git a/docs/v3.4/ENTITY_LINEAGE_WALLET_ECONOMY_ALIGNMENT_20261003.md b/docs/v3.4/ENTITY_LINEAGE_WALLET_ECONOMY_ALIGNMENT_20261003.md new file mode 100644 index 00000000..e33062b3 --- /dev/null +++ b/docs/v3.4/ENTITY_LINEAGE_WALLET_ECONOMY_ALIGNMENT_20261003.md @@ -0,0 +1,311 @@ +# ENTITY Lineage, Wallet, DCO and Economy Alignment + +Date: 2026-10-03 +ENTITY release: v3.4.3 +Status: canonical application-layer alignment + +## 1. The lineage model + +ENTITY has two independent lineage planes. They must never be collapsed. + +### 1.1 Protocol and domain lineage + +The canonical ENTITY protocol ancestry is: + +```text +Shawn Blackmore + ↓ FOUNDER_ORIGINATOR_CONTEXT +Blackmore Technology Group Limited + ↓ PROTOCOL_STEWARDSHIP +ENTITY + ↓ signed release origin +ENTITY v3.4.3 + ↓ ENTITY-issued domain profile +Robotics +``` + +The canonical origin identifier is: + +`entity-origin:shawn-btg-entity@1.0` + +This lineage identifies protocol origin and domain context. It does **not** transfer ownership of a user's data or asset to Shawn, BTG, ENTITY, or a profile. + +A Global Passport for a Robotics asset must therefore carry both: + +- a verified protocol-origin binding to the canonical ENTITY release; and +- `entity-profile:robotics@1.0` in its resolved profile stack. + +### 1.2 Profile composition is not parent/child lineage + +Robotics assets may also compose profiles such as AI or Manufacturing: + +```text +Primary domain: Robotics +Composed profiles: AI, Manufacturing +``` + +The wallet must not render this as: + +`Robotics → AI → Manufacturing` + +because those are composed profiles, not ancestry edges. + +### 1.3 Asset provenance and control + +Asset provenance is separate: + +```text +BTG + ↓ creates / controls +DCO-000001 BIRFR-1 + ↓ trained/derived relationship +DCO-000002 Failure Detection Engine +``` + +For another participant, that same asset plane may instead begin with that participant's ENTITY identity. + +Protocol origin does not infer asset ownership. Asset provenance does not redefine protocol origin. + +## 2. ENTITY, ADAM, NIKI and BTDU + +The system roles remain: + +- **ENTITY** — sovereign identity, authority, rights, policy, economic semantics and protocol origin. +- **ADAM** — deterministic governed execution, state transition and evidence substrate. +- **NIKI** — bounded reasoning and proposals. NIKI does not acquire sovereign authority merely by reasoning about an asset. +- **BTDU** — governed information topology and exact/semantic representation over ADAM. BTDU topology cannot create ownership, rights, royalties or economic entitlement. + +For wallet display, ADAM, NIKI and BTDU are capability/evidence bindings, not ownership ancestors. + +## 3. Canonical Digital Commodity ingest + +The desktop wallet must use the same canonical v3.4.3 stack as the deployment CLI. + +```text +SELECT / CREATE ASSET + ↓ +verify controller identity + ↓ +verify v3.4.3 release-origin attestation + ↓ +BTDU signed ingest + content hash + ↓ +register asset as DCO in Universal Transaction Fabric + ↓ +issue evidence + ↓ +grant controller rights + ↓ +issue Rights Passport + ↓ +resolve Global + domain profiles + ↓ +issue Global Passport + ↓ +embed protocol origin + ↓ +bind BTDU reference + ↓ +record zero POTENTIAL economic state + ↓ +MY DIGITAL ASSETS +``` + +Ingest must not automatically create: + +- an EEP instrument; +- a licence; +- a market listing; +- an ask price; +- a market value; +- an originator royalty; +- a protocol fee. + +The DCO is the governed digital commodity asset. Market rights are optional economic instruments created later by explicit controller action. + +## 4. Wallet separation + +The native wallet has two economically different holdings surfaces. + +### My Digital Assets + +Shows DCOs controlled by the wallet identity. + +Each asset can display: + +- title and content hash; +- real object type; +- commodity class; +- controller; +- canonical protocol/domain lineage; +- asset provenance parents; +- current Global Passport; +- BTDU binding state; +- whether the passport embeds current protocol origin. + +### Market Positions + +Shows EEP rights units that the participant owns or holds. + +A market position is not the underlying DCO. + +## 5. Economy + +The original market lifecycle remains: + +```text +DCO + → optional EEP instrument + → listing / disclosure + → order, RFQ or auction + → price discovery + → trade + → clearing + → settlement + → entitlement + → usage + → derived output + → economic consequence +``` + +The exchange continues to use actual orders and settled trades for market observations. + +Registration of a DCO does not establish market value. An offer does not establish realized value. Protocol tax remains zero and cryptocurrency is not required. + +## 6. Robotics correction + +The production Robotics assets are retained as the same signed objects. + +- **DCO-000001 — BIRFR-1** remains the BTG-controlled Robotics failure/recovery dataset. +- **DCO-000002 — Blackmore Real-Time Failure Detection Engine** remains the BTG-created Robotics algorithm derived from DCO-000001. + +Their historical Global Passport v1.0 records are preserved. + +Lineage-complete Global Passport v1.1 records now bind each asset to: + +- `entity-origin:shawn-btg-entity@1.0`; +- `entity-release:v3.4.3`; +- Global + Robotics + AI + Manufacturing profiles; +- the existing BTDU binding; +- the existing BTG controller and Rights Passport. + +The wallet renders the primary protocol/domain path as: + +`Shawn Blackmore → Blackmore Technology Group Limited → ENTITY → Robotics` + +AI and Manufacturing are shown as composed profiles. + +DCO-000003 is not promoted to the production asset registry while its qualification remains failed. + +## 7. v3.4.3 release-origin repair + +The v3.4.3 release originally lacked the post-tag `ENTITY_CURRENT_RELEASE_ORIGIN.json` sidecar required by the canonical v3.4.3 ingest path. + +A signed post-tag attestation was generated with the existing canonical ENTITY protocol identity and verified against: + +- release tag: `v3.4.3` +- commit: `528b70aabd05b1e930b77e4933f157731e47274f` +- tree: `f13e42f515dcc4cd985e9b832e0ecfec416ce423` +- origin lineage: `entity-origin:shawn-btg-entity@1.0` + +The attestation does not contain private key material. + +## 8. Locked invariants for Robotics 3 and later + +Future Robotics assets must: + +1. be built and qualified as actual assets before economic issuance; +2. retain their real asset type (SOFTWARE, MODEL, DATASET, DEVICE, etc.); +3. enter ENTITY through the canonical origin/passport/BTDU path; +4. use Robotics as the primary domain profile where applicable; +5. preserve explicit asset-to-asset provenance for derivations; +6. keep NIKI, ADAM and BTDU roles separate from ownership; +7. create market instruments only by explicit controller choice; +8. never invent value from registration or an asking price; +9. preserve historical signed records rather than rewriting them; +10. fail closed when required origin, identity, rights, evidence or qualification is missing. + +This alignment is an application/runtime correction. It does not require a new ENTITY protocol version. + + +## 9. Universal issuer-neutral market + +The corrected economy is not a BTG market implementation. + +Any valid ENTITY controller may create a DCO under its own asset lineage and explicitly issue bounded economic rights under the same registry rules. + +```text +ENTITY identity + ↓ +controlled DCO + ↓ +Rights Passport + ↓ +Global Passport / provenance + ↓ +optional canonical instrument + ↓ +optional listing + ↓ +venue-neutral EEP execution and settlement +``` + +BTG receives no hidden namespace, fee, market, settlement or ownership privilege. + +Human symbols such as `BTG:BRC22-COM`, `ACME:VM07-TRN` or `JSMITH:PHOTO26-COM` are signed issuer-scoped aliases. The immutable canonical instrument ID is authoritative. + +Instrument identity and listing identity are separate so the same canonical instrument may be published or listed through more than one approved venue. + +Every new universal listing requires a Listing Information Sheet and machine listing manifest bound to the canonical instrument, DCO, issuer, Rights Passport and Global Passport. + +Portable packages may be published on GitHub, websites, documentation or partner surfaces without creating duplicate instruments. + +## 10. Data Value Discovery + +Market identifiers become useful human measurement keys while analytics remain keyed by canonical `instrument_id`. + +ENTITY may measure settled activity for each bounded rights instrument, including: + +- last settled price; +- 24-hour / 30-day volume; +- unique buyers; +- trade count; +- high / low; +- active holders; +- circulating supply; +- externally verified settlement activity; +- royalties and participation actually settled. + +These observations may roll upward from instrument to DCO, rights class, asset class and primary domain where explicit passport/DCO classification supports the relationship. + +The wallet exposes a Data Value Discovery surface while preserving the boundary that instrument-rights prices do not establish intrinsic or accounting value for the entire underlying DCO. + +Market-integrity flags include self-trading, reciprocal flows and counterparty concentration. Related-wallet detection requires authorized relationship evidence and is not inferred from identity similarity. + +## 11. Robotics prelaunch economic cleanup + +The three historical robotics issuance campaigns were audited before cleanup planning: + +- synthetic robotics economic pilot: 21 instruments; +- DCO-000001 BIRFR-1: 20 instruments; +- DCO-000002 Failure Detection Engine: 16 instruments. + +The 57 instruments were prelaunch economic experiments. The production audit found no orders, trades, clearing, buyer entitlements or usage. Non-issuer balances were internal BTG treasury reserve allocations, not external holders. + +Because EEP 3.0 is already a qualified protocol component, instrument withdrawal is **not** being introduced as a new EEP signed-wire primitive merely to clean this application state. + +Instead, an application-layer fail-closed migration: + +1. verifies zero market/economic activity; +2. verifies every non-issuer balance is an exact internal issuer-owned treasury reserve; +3. preserves all signed historical rows; +4. marks unused instruments/listings withdrawn; +5. returns internal reserve units to the issuer; +6. supersedes the associated EOPP participation policies; +7. records DCO Factory issuance withdrawals; +8. leaves the underlying DCO assets active; +9. creates signed migration evidence and backups. + +If any real market history or unexplained holder exists, the migration refuses to run. + +This preserves EEP 3.0 conformance and economic history while removing the abandoned licence-oriented robotics direction from active state. diff --git a/docs/v3.4/ENTITY_WALLET_20261002.md b/docs/v3.4/ENTITY_WALLET_20261002.md index 817aa3fc..b13fbf80 100644 --- a/docs/v3.4/ENTITY_WALLET_20261002.md +++ b/docs/v3.4/ENTITY_WALLET_20261002.md @@ -2,7 +2,7 @@ Date: 2026-10-02 -ENTITY v3.4.3 now includes a repository-safe **economic wallet application layer** over the existing ENTITY Exchange Protocol (EEP) and Originator Participation Profile. +ENTITY v3.4.3 now includes a repository-safe **economic wallet application layer** over the Universal Transaction Fabric, ENTITY Exchange Protocol (EEP) and Originator Participation Profile. The wallet treats controller-held Digital Commodity Objects as first-class assets, independently from any later market instruments. This addition does **not** change the ENTITY protocol version, the EEP wire schema, the BTDU schema, or the no-token/no-protocol-tax model. @@ -111,3 +111,164 @@ The qualification covers: This repository publishes generic wallet source, tooling, tests and qualification evidence only. It does **not** publish production wallet databases, live wallet IDs, treasury identity material, private signing/recovery keys, local launchers, payment credentials, runtime SQLite state, bank details, production snapshots or other private operational state. + + +## Native desktop wallet and asset ingest + +The canonical user experience is now a native desktop application, not a generated HTML page. The source entry point is `tools/entity_wallet_desktop.py`. + +The native wallet provides: + +- **My Digital Assets** — controller-held DCOs from the Universal Transaction Fabric; +- **Upload / Ingest Digital Asset** — select a local file, hash it, copy it into the local wallet vault and register it as a DCO; +- **Market** — live instrument bid/ask/last observations and signed Buy/Sell order submission; +- **Market Positions** — rights positions kept separate from the underlying assets; +- **Orders** — active/recent exchange orders. + +Asset ingest is deliberately non-economic by default: + +`UPLOAD → SHA-256 → LOCAL VAULT → DCO REGISTRATION → MY DIGITAL ASSETS` + +It does **not** automatically create an EEP instrument, licence, listing, ask price or market value. The controller may later choose to create standardized tradeable rights. + +Cross-platform build tooling is provided by `tools/build_entity_wallet_desktop.py` and the `ENTITY Wallet Desktop` workflow for Windows, macOS and Linux. + + +## ENTITY Data Economy Terminal redesign — 2026-10-03 + +The native wallet is implemented with **PySide6/Qt**, replacing the earlier utility-style Tkinter presentation. + +The desktop experience is organized as a data-economy terminal: + +- **Overview** — sovereign portfolio state, market tape, asset holdings and settled rights-market activity; +- **Digital Assets** — first-class DCO holdings with lineage, passports, BTDU status and explicit `NO TICKER` state when no instrument exists; +- **Instruments** — canonical issuer-scoped economic instruments with ticker/market identifier, rights class, supply and underlying DCO; +- **ENTITY Market** — universal multi-issuer listings with ticker, issuer, rights, bid/ask/last, settled volume, buyers, holders and integrity flags; +- **Economic Intelligence** — settlement-aware Data Value Discovery by instrument and asset class; +- **Orders** — exchange instructions kept distinct from assets and positions. + +Ticker behavior is intentionally strict: + +DCO → optional economic instrument → issuer namespace + display symbol → ticker/market identifier + +A DCO without an issued economic instrument displays `NO TICKER`. The wallet does not manufacture a ticker or price merely because an asset exists. + +Before first issuance the wallet may display a collision-checked **suggested issuer namespace** such as `BTG`, marked `RESERVED ON ISSUE`. The alias is not authority and is not persisted until an issuer explicitly creates an instrument. Canonical instrument identity remains the immutable instrument_id. + +The header exposes canonical origin context separately from asset provenance: + +Shawn Blackmore → Blackmore Technology Group Limited → ENTITY + +Domain lineage such as `Robotics` comes from the asset Global Passport/profile stack. AI and Manufacturing remain composed profiles rather than false parent-child lineage nodes. + +The PySide6 build continues to preserve: + +- protocol tax = 0; +- no cryptocurrency requirement; +- external settlement evidence boundary; +- asset registration does not issue instruments; +- instrument issuance does not automatically create a listing; +- listing/market activity does not imply intrinsic DCO value; +- BTDU, ADAM and NIKI do not create ownership. + +An **earlier 2026-10-03 checkpoint** of the desktop/economy/lineage regression campaign passed **84/84** before the Qt package was built; later wallet changes expanded the focused qualification to the final 91/91 campaign recorded below. The packaged Windows executable then passed a production-state frozen-runtime self-test with exit code 0. + + +## Signed Asset Disclosure and Listing Information Sheet v2 — 2026-10-03 + +A market listing now requires a **signed, versioned Asset Disclosure** for the underlying DCO. The disclosure belongs to the asset, not to a ticker, and can therefore support multiple economic instruments without duplicating the buyer-facing description. + +The production path is: + +DCO → signed Asset Disclosure → Rights Passport / Global Passport → Economic Instrument → Listing → Listing Information Sheet v2 + +The Asset Disclosure supports: + +- detailed asset description; +- purpose / problem addressed; +- key capabilities; +- included content or components; +- intended uses; +- known limitations and exclusions; +- dependencies / prerequisites; +- validation and qualification notes; +- release/version notes; +- supporting source references. + +Issuer-authored disclosure fields are signed issuer statements. ENTITY records and verifies the statement but does not convert it into objective truth or independent certification. Publishing an Asset Disclosure does not expand a Rights Passport and does not create economic value. + +The Listing Information Sheet v2 snapshots the current signed Asset Disclosure and combines it with canonical DCO facts from the asset record and Global Passport, including DCO ID/code, version, object type, commodity class, measurement unit, content hash, controller, jurisdiction profiles, canonical technical metadata, industry context, profile stack, standards mappings, validation/maturity flags, provenance edges and references, evidence references, BTDU binding, rights terms, market terms and the market-value boundary. + +A later Asset Disclosure update does not silently rewrite an already-created listing sheet; the listing retains the disclosure/dossier hashes that were shown to the buyer. + +Portable instrument packages now include `asset-dossier.json` and, when present, `asset-disclosure.json` in addition to the human Listing Information PDF/Markdown and existing canonical manifests/passports. + +The wallet shows Asset Information status directly in the DCO portfolio. A DCO without a signed disclosure is marked **REQUIRED TO LIST**. The two current Robotics DCOs were issued signed Asset Disclosure v1 records using facts already present in their canonical records; no new performance claims were inferred. + + +## Universal first-run identity and lineage onboarding — 2026-10-03 + +The desktop wallet no longer assumes a BTG identity on a clean installation. A first-run setup now creates or adopts a sovereign principal ENTITY, claims a human-readable `.entity` public name, optionally creates an operating organization/business/project beneath the principal, creates or adopts a separate Device ENTITY, records explicit lineage/device relationships, creates the wallet, and authenticates through a device-bound signing-key challenge. + +The wallet header now resolves the active user's lineage dynamically. For the migrated BTG installation it displays `shawn.blackmore.entity → btg.entity`; another user receives their own lineage with no BTG-specific structural privilege. + +Device information may bootstrap only a system/device identity. It must never silently create a human or organization identity. Public names remain aliases; immutable Entity IDs and signatures remain authoritative. Protocol origin remains separate from user lineage and user-asset ownership. + +See `docs/v3.4/ENTITY_WALLET_IDENTITY_ONBOARDING.md` for the complete first-run and login model. + + +## Rights-Passport-driven instrument issuance — 2026-10-03 + +The desktop wallet no longer defaults every new economic instrument to `COMMERCIALIZE`. Instrument issuance now reads the selected DCO's current Rights Passport and presents only actions with an explicit `ALLOW` effect as selectable economic rights. + +Rules: + +- the Rights Passport is the maximum authority boundary; +- the instrument may contain only a subset of currently allowed Rights Passport actions; +- prohibited actions are shown as prohibited and cannot be selected; +- non-ALLOW/non-PROHIBIT actions are shown separately and are not directly issuable; +- the backend re-validates the selected actions immediately before canonical instrument creation; +- the canonical market registry remains the final enforcement layer and rejects any instrument whose actions exceed the Rights Passport; +- ticker/right-class/name suggestions are derived from the selected authorized actions rather than a generic commercial-right default; +- canonical asset short names are preferred for ticker suggestions where present (for example, BIRFR-1 training rights suggest `BIRFR1-TRN`). + +Production verification confirmed that BIRFR-1 exposes its allowed actions (BENCHMARK, CERTIFY, COMPUTE, DERIVE, EVALUATE, FINE_TUNE, INFER, OEM_DEPLOY, TRAIN) and does not expose COMMERCIALIZE, while the Blackmore Real-Time Failure Detection Engine does expose COMMERCIALIZE because that action is present in its Rights Passport. + +That **intermediate rights-driven checkpoint** of the wallet/economy/lineage/onboarding/market qualification campaign passed **89/89** after this change; later Software Engineering/profile packaging work expanded the final focused campaign to 91/91 as recorded below. The installed Windows executable then passed its frozen-runtime production self-test. + + +## Rights-Passport-driven instrument issuance and Software Engineering domain — 2026-10-03 + +Economic instrument issuance in the desktop wallet is now driven by the selected DCO's current Rights Passport. The wallet no longer defaults every asset to `COMMERCIALIZE`. It loads the active Rights Passport, presents only `ALLOW` actions as selectable instrument rights, displays prohibited/non-directly-issuable actions separately, validates the selected subset again at issuance, and derives the suggested rights class / ticker suffix from the selected actions. The canonical market validator remains the final fail-closed enforcement boundary. + +Example: BIRFR-1 currently exposes BENCHMARK, CERTIFY, COMPUTE, DERIVE, EVALUATE, FINE_TUNE, INFER, OEM_DEPLOY and TRAIN. `COMMERCIALIZE` is therefore not offered for BIRFR-1. Selecting TRAIN suggests `BIRFR1-TRN` and the TRAINING rights class. DCO-000002 separately allows COMMERCIALIZE because its Rights Passport explicitly grants that action. + +ENTITY now also includes a built-in `entity-profile:software-engineering@1.0` domain/profile and a `software-engineering` implementation package. This domain supports software repositories, source code, libraries, applications, algorithms, build artifacts, test evidence and engineering-control DCOs. It maps SPDX-3, CycloneDX and SLSA as non-normative interoperability mappings; the profile does not create authority or regulatory status. + +Ten active legacy ENGINEERING_CONTROL DCOs that previously carried only the Global profile were migrated by issuing new immutable Global Passport versions with the Software Engineering profile. Existing passports remain preserved. The migration did not change DCO IDs, controllers, Rights Passport IDs/hashes, evidence, provenance or economic state. The wallet now resolves all 12 current BTG assets to explicit domains: 10 Software Engineering and 2 Robotics. + +The Software Engineering profile is distributed as a pre-signed canonical profile record under `protocol/profiles`. Clean installations verify its `entity.entity` signature and import it at runtime; they do not mint or re-sign canonical profiles locally. This preserves the public-key-only trust boundary on new devices. + + +## Final 2026-10-03 wallet qualification and publication state + +The complete focused wallet/economy/lineage/onboarding/global-passport campaign was rerun from a clean committed worktree and passed **91/91** immediately before the final Windows package was rebuilt. + +Wallet implementation baseline: + +`212ee56d34aee578e6df701ef9f9a675661b7162` +`wallet: add software engineering domain and canonical instrument rights` + +The final locally installed Windows package from that baseline passed its frozen-runtime self-test and has SHA-256: + +`4F360D9F2C9DC51600B2C5D76FAA09364ADEF6762255D17F4DC528BDF3C069DD` + +The production-state domain verification resolved all 12 current wallet assets: + +- **10 Software Engineering**; +- **2 Robotics**; +- **0 missing domains**. + +The repository now includes [cross-platform build documentation](ENTITY_WALLET_CROSS_PLATFORM_BUILDS_20261003.md) and CI artifacts for Windows, Linux and macOS desktop wallets plus a native Xcode-built iOS Simulator portable-snapshot client. + +The iOS client is intentionally not represented as a native port of the Python authority runtime. Canonical identity, signing, DCO registration, Rights/Global Passport issuance, economic-instrument issuance, listing, orders and settlement mutations remain authoritative ENTITY runtime operations until a separately qualified native iOS authority implementation exists. diff --git a/docs/v3.4/ENTITY_WALLET_CROSS_PLATFORM_BUILDS_20261003.md b/docs/v3.4/ENTITY_WALLET_CROSS_PLATFORM_BUILDS_20261003.md new file mode 100644 index 00000000..0470d480 --- /dev/null +++ b/docs/v3.4/ENTITY_WALLET_CROSS_PLATFORM_BUILDS_20261003.md @@ -0,0 +1,105 @@ +# ENTITY Wallet cross-platform builds — 2026-10-03 + +**Runtime family:** ENTITY v3.4.3 +**Wallet architecture:** ENTITY Data Economy Terminal +**Canonical desktop implementation:** PySide6 / Qt + canonical ENTITY runtime modules +**Final desktop qualification before cross-platform build publication:** 91/91 PASS + +## Build surfaces + +The repository publishes four build artifacts from the `ENTITY Wallet Cross-Platform Builds` GitHub Actions workflow: + +| Artifact | Build environment | Scope | +| --- | --- | --- | +| `ENTITY-Wallet-Windows` | `windows-latest` | Full canonical desktop wallet | +| `ENTITY-Wallet-Linux` | `ubuntu-latest` | Full canonical desktop wallet | +| `ENTITY-Wallet-macOS` | `macos-latest` | Full canonical desktop wallet | +| `ENTITY-Wallet-iOS-Simulator` | `macos-latest` + Xcode | Native iOS portable-snapshot inspection client | + +Windows, Linux and macOS execute the same Python/PySide6 wallet source and package the same canonical runtime modules. + +## iOS boundary + +The current canonical wallet implementation is a Python/PySide6 desktop application. Its PyInstaller deployment path targets desktop operating systems and is not treated as an iOS authority runtime. + +The iOS target is therefore deliberately implemented as a native SwiftUI **portable wallet inspection client**. It can import a portable JSON wallet snapshot and display: + +- wallet lineage; +- issuer namespace and market-registry version when present; +- Digital Commodity Object holdings; +- asset codes and immutable object identifiers; +- explicit domain lineage; +- Rights-Passport `ALLOW` actions present in the imported snapshot; +- issued economic instruments, market identifiers/tickers, rights classes and instrument actions; +- the explicit `NO TICKER` condition when an asset has no economic instrument. + +The iOS client does **not** silently create a human identity from device information and does not locally invent authority. + +Canonical write operations remain ENTITY runtime operations, including: + +- sovereign identity creation/adoption; +- device-bound signing and authentication; +- DCO registration; +- Rights Passport / Global Passport issuance; +- economic instrument issuance; +- listings and signed market orders; +- settlement/economic-state mutation. + +This keeps the mobile build useful without falsely representing an unqualified native port as equivalent to the canonical desktop authority engine. + +## Rights-driven issuance retained across builds + +The current wallet rule is: + +`DCO Rights Passport = maximum authorized actions` + +`Economic Instrument = selected subset of those authorized actions` + +The desktop wallet presents only current Rights-Passport `ALLOW` actions as directly selectable economic rights. The canonical market registry independently rejects any instrument whose requested actions exceed the Rights Passport. + +The iOS client reflects imported Rights-Passport actions but cannot expand them. + +## Domain state + +The production wallet qualification used for the final Windows rebuild resolved all 12 current wallet assets to explicit domains: + +- 10 × **Software Engineering**; +- 2 × **Robotics**; +- 0 × missing domain. + +The Software Engineering profile is distributed as `entity-profile:software-engineering@1.0` with a pre-signed canonical profile record under `protocol/profiles`. + +## First-run identity model + +A clean desktop installation follows: + +`Install → create/adopt ENTITY identity → claim public .entity alias → optional organization → bind Device ENTITY → authenticate → open wallet` + +Device metadata is not allowed to silently become a human or organization identity. Public names are aliases; immutable Entity IDs and signatures remain authoritative. + +## Qualification and packaging + +The final focused wallet/economy/lineage/onboarding/global-passport suite passed **91/91** immediately before the final Windows package was rebuilt. + +The installed Windows package created from wallet implementation commit: + +`212ee56d34aee578e6df701ef9f9a675661b7162` + +passed its frozen-runtime self-test. + +Final locally installed Windows EXE SHA-256 from that qualification: + +`4F360D9F2C9DC51600B2C5D76FAA09364ADEF6762255D17F4DC528BDF3C069DD` + +That hash identifies the locally qualified Windows package. GitHub-hosted Windows/Linux/macOS/iOS artifacts are separately built by GitHub Actions and therefore have their own artifact hashes. + +## CI trigger + +The cross-platform workflow runs on: + +- manual dispatch; +- pushes to `main`; +- pushes to the wallet integration branch; +- relevant pull requests. + +A failing platform job does not cancel the other platform jobs. Every published artifact must come from a successful platform job. diff --git a/docs/v3.4/ENTITY_WALLET_IDENTITY_ONBOARDING.md b/docs/v3.4/ENTITY_WALLET_IDENTITY_ONBOARDING.md new file mode 100644 index 00000000..e699474b --- /dev/null +++ b/docs/v3.4/ENTITY_WALLET_IDENTITY_ONBOARDING.md @@ -0,0 +1,198 @@ +# ENTITY Wallet Identity & Lineage Onboarding + +Status: wallet/application layer +ENTITY release compatibility: v3.4.3 +Onboarding profile: `entity-wallet-onboarding-profile-v1` + +## Purpose + +A clean ENTITY Wallet installation must not assume the identity, lineage, organization, or economic authority of the machine on which it is installed. + +The wallet therefore separates four concepts: + +1. **Protocol origin** — the immutable historical ENTITY protocol origin. +2. **Principal ENTITY** — the person, business, organization, project, family, or community that the user creates during first-run setup, or that can be adopted from already-present valid ENTITY state. +3. **Operating ENTITY** — an optional organization/business/project under the principal that owns/controls the active wallet and DCOs. +4. **Device ENTITY** — the local computer installation used to authenticate and operate the wallet. + +These are not interchangeable. + +A device does not become a person. A public name does not become authority. Protocol origin does not become ownership of user assets. + +## First-run flow + +A clean desktop installation follows: + +``` +Install ENTITY Wallet + ↓ +Create separate Device ENTITY + ↓ +User creates Principal ENTITY + ↓ +Choose public .entity name + ↓ +Optionally create Operating ENTITY + ↓ +Establish signed user lineage + ↓ +Bind device to principal + ↓ +Create active wallet + ↓ +Enter ENTITY +``` + +Example: + +``` +jane.smith.entity + ↓ +acme.entity + ↓ +Acme participant wallet +``` + +The active wallet may be the root principal itself or the optional operating entity. + +## Public names + +A human-readable name such as: + +``` +jane.smith.entity +acme.entity +btg.entity +``` + +is a signed ENTITY name claim and display alias. + +It is **not** the canonical database key and is not the source of authority. + +The canonical identity remains the immutable Entity ID: + +``` +ent2-... +``` + +If two independent installations claim the same human-readable name, the identities remain distinct because their Entity IDs and signatures are distinct. A local name conflict is surfaced rather than silently granting first-claim ownership. + +## User lineage + +Wallet lineage is user-controlled identity/organization structure. It is not ENTITY protocol origin. + +For example: + +``` +shawn.blackmore.entity → btg.entity +``` + +means the wallet profile has Shawn's person ENTITY as principal root and BTG as the active operating ENTITY. + +Another installation could be: + +``` +jane.smith.entity → acme.entity +``` + +or simply: + +``` +independent.creator.entity +``` + +No BTG-specific identity is required. + +When an operating organization is created beneath a principal, the relationship is explicitly signed and recorded through the Sovereign Authority registry. Device possession is recorded separately and does not infer ownership or identity. + +## Device identity + +ENTITY Wallet may automatically bootstrap a **system/device ENTITY** using installation and platform context. + +The Device ENTITY may include: + +- generated installation identifier; +- platform family; +- platform release; +- architecture; +- local device display name. + +The device record explicitly states: + +- device information is not user identity; +- device possession is not ownership; +- hardware attestation is not implied unless separately established. + +The device never silently creates a human or organization identity. + +## Wallet login + +The desktop wallet uses a **device-bound signing-key login** rather than a centralized web username/password. + +Unlock requires: + +1. a valid signed wallet onboarding profile; +2. possession of the active ENTITY signing key; +3. possession of the bound Device ENTITY signing key; +4. successful challenge signatures from both identities. + +The login challenge is fresh for each authentication attempt. + +A new device therefore requires an explicit recovery/import/re-binding workflow rather than silently inheriting identity from the operating-system account. That recovery/import workflow is separate from the first-run identity-creation screen described here. + +The onboarding layer itself does not add a separate password-based key-encryption scheme. Private-key protection remains the responsibility of the canonical identity/key-storage layer and any configured hardware/platform protection. + +## Existing installations + +Existing identities are adopted without regeneration. + +For example the BTG production installation retains: + +- Shawn Blackmore's existing person Entity ID; +- Blackmore Technology Group Limited's existing business Entity ID; +- the existing BTG workstation system Entity ID. + +The wallet adds only the signed onboarding/profile bindings needed to express: + +``` +shawn.blackmore.entity → btg.entity +``` + +No DCO lineage, Global Passport, economic state, market history, or identity key is replaced by onboarding migration. + +## Wallet presentation + +The wallet header must resolve lineage from the active wallet profile. + +It must never hard-code: + +``` +SHAWN → BTG → ENTITY +``` + +for arbitrary users. + +The user lineage is shown separately from the ENTITY protocol and market context. + +## Security and authority rules + +- Public names are aliases, not authority. +- Entity IDs and signatures are authoritative. +- Device identity is separate from user identity. +- Device possession does not establish ownership. +- Protocol origin is separate from user lineage. +- Creating an identity does not create economic value. +- Creating a wallet does not create an instrument or listing. +- Creating an organization under a principal does not transfer unrelated rights. +- No cryptocurrency is required. +- Protocol tax remains zero. + +## Cross-platform requirement + +The onboarding path is part of the PySide6/Qt desktop wallet and is intended to operate consistently on: + +- Windows; +- macOS; +- Linux. + +Packaging must include the canonical identity, Sovereign Domain, Sovereign Authority, wallet, market, and passport modules required by first-run identity creation. diff --git a/docs/v3.4/GLOBAL_PASSPORT.md b/docs/v3.4/GLOBAL_PASSPORT.md index 6405517f..c62cb460 100644 --- a/docs/v3.4/GLOBAL_PASSPORT.md +++ b/docs/v3.4/GLOBAL_PASSPORT.md @@ -28,4 +28,4 @@ The v3.4 layer does not replace the existing data-rights lifecycle: Nor does it replace the v3.3 reality/evidence path. It composes those layers into a deployable passport surface. -See [Domain Packages](DOMAIN_PACKAGES.md) for the six first-party implementation packages. +See [Domain Packages](DOMAIN_PACKAGES.md) for the first-party domain packages and the built-in Software Engineering profile/package. diff --git a/docs/v3.4/README.md b/docs/v3.4/README.md index 0ae29008..09edfc6a 100644 --- a/docs/v3.4/README.md +++ b/docs/v3.4/README.md @@ -1,8 +1,8 @@ # ENTITY v3.4 Family Runtime Portal -**Document class:** current-runtime family orientation / non-normative to Protocol 1.0 -**Current supported runtime:** [ENTITY v3.4.3](https://github.com/blackmore-technology-group/ENTITY/releases/tag/v3.4.3) -**BTDU component:** Blackmore Technology Data Universe (BTDU) 3.4.2, unchanged +**Document class:** current-runtime family orientation / non-normative to Protocol 1.0 +**Current supported runtime:** [ENTITY v3.4.3](https://github.com/blackmore-technology-group/ENTITY/releases/tag/v3.4.3) +**BTDU component:** Blackmore Technology Data Universe (BTDU) 3.4.2, unchanged **Historical v3.4.0/v3.4.1/v3.4.2 evidence:** preserved under its original release targets This directory describes the **v3.4 runtime family**: Global Passport architecture, domain packages, deployment and historical closure material. It is not the normative ENTITY Protocol 1.0 clean-room specification. @@ -48,12 +48,24 @@ These runtime primitives and packages must not be mistaken for extra Protocol 1. ## Start here - [Global Passport runtime architecture](GLOBAL_PASSPORT.md) -- [Six executable domain packages](DOMAIN_PACKAGES.md) +- [Domain packages and profiles](DOMAIN_PACKAGES.md) - [Deployment model](DEPLOYMENT_MODEL.md) - [Historical v3.4.0 post-release recursive closure](RECURSIVE_CLOSURE.md) - [ENTITY v3.4.3 release record](../../RELEASE_V3_4_3.md) +- [ENTITY Wallet — current application layer](ENTITY_WALLET_20261002.md) +- [Wallet identity onboarding](ENTITY_WALLET_IDENTITY_ONBOARDING.md) +- [Wallet cross-platform builds](ENTITY_WALLET_CROSS_PLATFORM_BUILDS_20261003.md) +- [Software Engineering profile](passports/SOFTWARE_ENGINEERING.md) - [Engineering evidence](../ENGINEERING_EVIDENCE.md) +## Current wallet application layer + +The current wallet is the **ENTITY Data Economy Terminal**. It includes sovereign first-run identity/lineage onboarding, Digital Asset/DCO holdings, signed asset disclosures, issuer-scoped economic instruments and tickers, Rights-Passport-driven instrument issuance, market listings/positions/orders and explicit domain lineage. + +The final focused qualification completed on 2026-10-03 at **91/91 PASS**. The qualified production snapshot contained 12 current assets with no missing domains: 10 Software Engineering and 2 Robotics. + +Windows, Linux and macOS are full PySide6 desktop builds of the canonical wallet. The repository also builds a native iOS Simulator portable-snapshot inspection client; native iOS write-authority parity is not claimed. + ## Historical v3.4.1 qualification snapshot The following values remain historical v3.4.1 evidence and are **not rewritten** to make them look current: diff --git a/docs/v3.4/ROBOTICS_DCO_ASSET_ORIENTATION_20261003.md b/docs/v3.4/ROBOTICS_DCO_ASSET_ORIENTATION_20261003.md new file mode 100644 index 00000000..5be8f96e --- /dev/null +++ b/docs/v3.4/ROBOTICS_DCO_ASSET_ORIENTATION_20261003.md @@ -0,0 +1,28 @@ +# Robotics DCO Asset Orientation + +Date: 2026-10-03 +ENTITY display version: 3.4.3 + +The numbered robotics portfolio is an **asset portfolio inside ENTITY**, not a mandate to issue robotics licences. + +## Canonical orientation + +`ROBOTICS OUTPUT → QUALIFIED DIGITAL ASSET → DCO → WALLET ASSET HOLDING` + +Only if the controller chooses to expose economic rights does the path continue: + +`DCO → OPTIONAL EEP INSTRUMENT → OPTIONAL LISTING → MARKET PRICE DISCOVERY` + +A DCO may therefore exist indefinitely as a private or controlled digital commodity asset with no market supply and no price. + +## Wallet placement + +Robotics assets belong in **My Digital Assets**. Tradeable rights, if deliberately issued later, belong in **Market Positions**. The two are intentionally separate. + +## Existing BIRFR prelaunch instruments + +BIRFR-1 remains a valid BTG-originated DCO asset. Its previously generated EEP instruments are retained as controlled prelaunch qualification evidence and are not the definition of the asset. No market value exists until real settled market activity establishes one. + +## Universal rule + +The same model applies to code, research, patient-authorized data, farm observations, industrial data, creative assets, models, algorithms and other legitimate digital commodities. Uploading data to ENTITY registers the asset; it does not silently sell, license or price it. diff --git a/docs/v3.4/passports/SOFTWARE_ENGINEERING.md b/docs/v3.4/passports/SOFTWARE_ENGINEERING.md new file mode 100644 index 00000000..2758dedc --- /dev/null +++ b/docs/v3.4/passports/SOFTWARE_ENGINEERING.md @@ -0,0 +1,62 @@ +# ENTITY Software Engineering Profile + +**Profile reference:** `entity-profile:software-engineering@1.0` + +The Software Engineering profile supplies an explicit ENTITY domain for software repositories, source code, libraries, applications, algorithms, build artifacts, test evidence and engineering-control DCOs. + +It is a built-in ENTITY implementation package rather than a separate sovereignty system or a claim that an external software standard is redefined by ENTITY. + +## Object scope + +The profile supports Digital Commodity Objects representing: + +- software; +- source repositories; +- algorithms; +- libraries and packages; +- executable/build artifacts; +- test and qualification evidence; +- documents and datasets used in software engineering; +- engineering-control DCOs. + +## Standards mappings + +The profile carries non-normative interoperability mappings for: + +- SPDX 3; +- CycloneDX; +- SLSA. + +Those mappings are correspondence metadata only. They do not create certification, regulatory status, supply-chain authority or legal rights. + +## Wallet use + +The ENTITY Wallet resolves the profile through the current Global Passport and displays **Software Engineering** as the DCO's primary domain where this profile is authoritative for the asset. + +The 2026-10-03 production migration issued new immutable Global Passport versions for ten legacy `ENGINEERING_CONTROL` DCOs that previously carried only the Global profile. + +The migration preserved: + +- DCO IDs; +- controllers; +- Rights Passport IDs and hashes; +- evidence; +- provenance; +- existing economic state; +- prior Global Passport records. + +It added explicit domain context; it did not rewrite the underlying DCOs or manufacture economic rights. + +## Rights boundary + +Software Engineering domain classification does not grant instrument actions. + +Economic instruments may contain only actions already authorized by the active DCO Rights Passport. Domain profiles and Rights Passports remain separate authority surfaces. + +## Distribution + +The canonical signed profile record is published at: + +`protocol/profiles/ENTITY_SOFTWARE_ENGINEERING_PROFILE.json` + +Clean installations verify its signature and import it; they do not mint or re-sign the canonical profile locally. diff --git a/ios/ENTITYWalletIOS/ENTITYWalletIOS/ContentView.swift b/ios/ENTITYWalletIOS/ENTITYWalletIOS/ContentView.swift new file mode 100644 index 00000000..8e0fcf62 --- /dev/null +++ b/ios/ENTITYWalletIOS/ENTITYWalletIOS/ContentView.swift @@ -0,0 +1,165 @@ +import SwiftUI +import UniformTypeIdentifiers + +struct ContentView: View { + @State private var snapshot: WalletSnapshot? + @State private var showingImporter = false + @State private var importError: String? + + var body: some View { + TabView { + NavigationStack { + List { + Section("Wallet") { + LabeledContent("Lineage", value: snapshot?.walletLineage ?? "No snapshot imported") + LabeledContent("Issuer namespace", value: snapshot?.issuerNamespace ?? "—") + LabeledContent("Market registry", value: snapshot?.marketRegistryVersion ?? "—") + LabeledContent("Assets", value: "\(snapshot?.assets.count ?? 0)") + LabeledContent("Instruments", value: "\(snapshot?.instruments.count ?? 0)") + } + + Section("Reference build") { + LabeledContent("ENTITY runtime", value: "v3.4.3") + LabeledContent("Desktop qualification", value: "91/91 PASS") + LabeledContent("Issuance policy", value: "Rights-Passport-driven") + Text("The reference BTG production snapshot used for qualification resolved 12 current assets to explicit domains: 10 Software Engineering and 2 Robotics.") + .font(.footnote) + .foregroundStyle(.secondary) + } + + Section { + Button { + showingImporter = true + } label: { + Label("Import wallet snapshot", systemImage: "square.and.arrow.down") + } + } footer: { + Text("The iOS client imports portable JSON snapshots. It does not silently create identities from device metadata and it does not expand Rights Passport authority.") + } + + Section("Write boundary") { + Text("Canonical identity creation, device-bound signing, DCO registration, economic-instrument issuance, listing and order submission remain authoritative ENTITY runtime operations. This iOS simulator build is a portable wallet inspection client, not a replacement authority engine.") + .font(.footnote) + } + } + .navigationTitle("ENTITY Wallet") + } + .tabItem { Label("Overview", systemImage: "wallet.pass") } + + NavigationStack { + Group { + if let assets = snapshot?.assets, !assets.isEmpty { + List(assets) { asset in + NavigationLink { + AssetDetail(asset: asset) + } label: { + VStack(alignment: .leading, spacing: 4) { + Text(asset.title).font(.headline) + Text(asset.domain).font(.subheadline).foregroundStyle(.secondary) + Text(asset.assetCode ?? asset.objectId) + .font(.caption.monospaced()) + .foregroundStyle(.secondary) + } + } + } + } else { + ContentUnavailableView( + "No Assets", + systemImage: "shippingbox", + description: Text("Import a portable wallet snapshot to inspect DCO assets and domain lineage.") + ) + } + } + .navigationTitle("Digital Assets") + } + .tabItem { Label("Assets", systemImage: "shippingbox") } + + NavigationStack { + Group { + if let instruments = snapshot?.instruments, !instruments.isEmpty { + List(instruments) { instrument in + VStack(alignment: .leading, spacing: 5) { + Text(instrument.marketIdentifier).font(.headline.monospaced()) + Text(instrument.rightsClass ?? "Unclassified rights") + .font(.subheadline) + .foregroundStyle(.secondary) + if !instrument.actions.isEmpty { + Text(instrument.actions.joined(separator: " · ")) + .font(.caption) + .foregroundStyle(.secondary) + } + } + } + } else { + ContentUnavailableView( + "No Instruments", + systemImage: "chart.bar.doc.horizontal", + description: Text("A DCO may legitimately have no ticker. Import a wallet snapshot to inspect issued economic instruments.") + ) + } + } + .navigationTitle("Instruments") + } + .tabItem { Label("Instruments", systemImage: "chart.bar.doc.horizontal") } + } + .fileImporter( + isPresented: $showingImporter, + allowedContentTypes: [.json], + allowsMultipleSelection: false + ) { result in + do { + let url = try result.get().first! + let scoped = url.startAccessingSecurityScopedResource() + defer { if scoped { url.stopAccessingSecurityScopedResource() } } + let data = try Data(contentsOf: url) + snapshot = try JSONDecoder().decode(WalletSnapshot.self, from: data) + } catch { + importError = error.localizedDescription + } + } + .alert("Snapshot import failed", isPresented: Binding( + get: { importError != nil }, + set: { if !$0 { importError = nil } } + )) { + Button("OK", role: .cancel) { importError = nil } + } message: { + Text(importError ?? "Unknown error") + } + } +} + +private struct AssetDetail: View { + let asset: WalletAsset + + var body: some View { + List { + Section("Asset") { + LabeledContent("Title", value: asset.title) + LabeledContent("Domain", value: asset.domain) + if let code = asset.assetCode { + LabeledContent("Asset code", value: code) + } + Text(asset.objectId) + .font(.caption.monospaced()) + .textSelection(.enabled) + } + + Section { + if asset.rightsActions.isEmpty { + Text("No directly issuable ALLOW actions were present in the imported snapshot.") + .foregroundStyle(.secondary) + } else { + ForEach(asset.rightsActions, id: \.self) { action in + Label(action, systemImage: "checkmark.shield") + } + } + } header: { + Text("Issuable Rights Passport actions") + } footer: { + Text("Economic instruments may select only a subset of actions already authorized by the active Rights Passport. The mobile client does not create new authority.") + } + } + .navigationTitle("Asset") + .navigationBarTitleDisplayMode(.inline) + } +} diff --git a/ios/ENTITYWalletIOS/ENTITYWalletIOS/ENTITYWalletIOSApp.swift b/ios/ENTITYWalletIOS/ENTITYWalletIOS/ENTITYWalletIOSApp.swift new file mode 100644 index 00000000..5f0db610 --- /dev/null +++ b/ios/ENTITYWalletIOS/ENTITYWalletIOS/ENTITYWalletIOSApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct ENTITYWalletIOSApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/ios/ENTITYWalletIOS/ENTITYWalletIOS/WalletModel.swift b/ios/ENTITYWalletIOS/ENTITYWalletIOS/WalletModel.swift new file mode 100644 index 00000000..c0bf4242 --- /dev/null +++ b/ios/ENTITYWalletIOS/ENTITYWalletIOS/WalletModel.swift @@ -0,0 +1,137 @@ +import Foundation + +struct WalletSnapshot: Decodable { + let walletLineage: String? + let issuerNamespace: String? + let marketRegistryVersion: String? + let assets: [WalletAsset] + let instruments: [WalletInstrument] + + enum CodingKeys: String, CodingKey { + case walletLineage = "wallet_lineage" + case issuerNamespace = "issuer_namespace" + case marketRegistryVersion = "market_registry_version" + case assets + case instruments + } + + init(from decoder: Decoder) throws { + let c = try decoder.container(keyedBy: CodingKeys.self) + walletLineage = try c.decodeIfPresent(String.self, forKey: .walletLineage) + issuerNamespace = try c.decodeIfPresent(String.self, forKey: .issuerNamespace) + marketRegistryVersion = try c.decodeIfPresent(String.self, forKey: .marketRegistryVersion) + assets = (try? c.decode([WalletAsset].self, forKey: .assets)) ?? [] + instruments = (try? c.decode([WalletInstrument].self, forKey: .instruments)) ?? [] + } +} + +struct WalletAsset: Decodable, Identifiable { + let objectId: String + let title: String + let assetCode: String? + let domain: String + let rightsActions: [String] + + var id: String { objectId } + + enum CodingKeys: String, CodingKey { + case objectId = "object_id" + case title + case assetCode = "asset_code" + case domain + case rightsActions = "rights_actions" + case lineage + case rightsPassport = "rights_passport" + } + + private struct Lineage: Decodable { + let protocolLineage: ProtocolLineage? + enum CodingKeys: String, CodingKey { case protocolLineage = "protocol_lineage" } + } + + private struct ProtocolLineage: Decodable { + let primaryDomainProfile: String? + enum CodingKeys: String, CodingKey { case primaryDomainProfile = "primary_domain_profile" } + } + + private struct RightsPassport: Decodable { + let rights: [RightRule]? + } + + private struct RightRule: Decodable { + let effect: String? + let actions: [String]? + } + + init(from decoder: Decoder) throws { + let c = try decoder.container(keyedBy: CodingKeys.self) + objectId = (try? c.decode(String.self, forKey: .objectId)) ?? UUID().uuidString + title = (try? c.decode(String.self, forKey: .title)) ?? objectId + assetCode = try? c.decodeIfPresent(String.self, forKey: .assetCode) + let directDomain = (try? c.decodeIfPresent(String.self, forKey: .domain)) ?? nil + let lineage = (try? c.decodeIfPresent(Lineage.self, forKey: .lineage)) ?? nil + domain = directDomain ?? lineage?.protocolLineage?.primaryDomainProfile ?? "Unspecified" + + if let direct = (try? c.decodeIfPresent([String].self, forKey: .rightsActions)) ?? nil { + rightsActions = direct.sorted() + } else if let passport = (try? c.decodeIfPresent(RightsPassport.self, forKey: .rightsPassport)) ?? nil, + let rules = passport.rights { + rightsActions = rules + .filter { ($0.effect ?? "").uppercased() == "ALLOW" } + .flatMap { $0.actions ?? [] } + .reduce(into: Set()) { $0.insert($1) } + .sorted() + } else { + rightsActions = [] + } + } +} + +struct WalletInstrument: Decodable, Identifiable { + let instrumentId: String + let marketIdentifier: String + let rightsClass: String? + let actions: [String] + + var id: String { instrumentId } + + enum CodingKeys: String, CodingKey { + case instrumentId = "instrument_id" + case marketIdentifier = "market_identifier" + case displaySymbol = "display_symbol" + case rightsClass = "rights_class" + case actions + case rights + } + + private struct Rights: Decodable { + let actions: [String]? + let rightsClass: String? + let marketIdentifier: String? + + enum CodingKeys: String, CodingKey { + case actions + case rightsClass = "rights_class" + case marketIdentifier = "market_identifier" + } + } + + init(from decoder: Decoder) throws { + let c = try decoder.container(keyedBy: CodingKeys.self) + instrumentId = (try? c.decode(String.self, forKey: .instrumentId)) ?? UUID().uuidString + let rights = (try? c.decodeIfPresent(Rights.self, forKey: .rights)) ?? nil + let symbol = (try? c.decodeIfPresent(String.self, forKey: .displaySymbol)) ?? nil + marketIdentifier = + ((try? c.decodeIfPresent(String.self, forKey: .marketIdentifier)) ?? nil) ?? + rights?.marketIdentifier ?? + symbol ?? + "NO TICKER" + rightsClass = + ((try? c.decodeIfPresent(String.self, forKey: .rightsClass)) ?? nil) ?? + rights?.rightsClass + actions = + ((try? c.decodeIfPresent([String].self, forKey: .actions)) ?? nil) ?? + rights?.actions ?? + [] + } +} diff --git a/ios/ENTITYWalletIOS/project.yml b/ios/ENTITYWalletIOS/project.yml new file mode 100644 index 00000000..5b08de82 --- /dev/null +++ b/ios/ENTITYWalletIOS/project.yml @@ -0,0 +1,39 @@ +name: ENTITYWalletIOS +options: + bundleIdPrefix: technology.blackmore + deploymentTarget: + iOS: "17.0" + +settings: + base: + SWIFT_VERSION: "5.0" + CURRENT_PROJECT_VERSION: "1" + MARKETING_VERSION: "3.4.3" + +targets: + ENTITYWalletIOS: + type: application + platform: iOS + sources: + - path: ENTITYWalletIOS + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: technology.blackmore.entitywallet + PRODUCT_NAME: ENTITYWalletIOS + GENERATE_INFOPLIST_FILE: YES + INFOPLIST_KEY_CFBundleDisplayName: ENTITY Wallet + INFOPLIST_KEY_UIApplicationSceneManifest_Generation: YES + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents: YES + INFOPLIST_KEY_UILaunchScreen_Generation: YES + TARGETED_DEVICE_FAMILY: "1,2" + CODE_SIGN_STYLE: Automatic + +schemes: + ENTITYWalletIOS: + build: + targets: + ENTITYWalletIOS: all + run: + config: Debug + archive: + config: Release diff --git a/protocol/profiles/ENTITY_SOFTWARE_ENGINEERING_PROFILE.json b/protocol/profiles/ENTITY_SOFTWARE_ENGINEERING_PROFILE.json new file mode 100644 index 00000000..e4deecd1 --- /dev/null +++ b/protocol/profiles/ENTITY_SOFTWARE_ENGINEERING_PROFILE.json @@ -0,0 +1,63 @@ +{ + "body_sha256": "1afcb25c1a62814b6b561b54367460ab47e35faff6cf53c8954fe8d301a708c2", + "conflict_policy": "FAIL_CLOSED", + "created_at_ms": 1791049771367, + "issuer_entity_id": "ent2-m3nofxtv3zwldhwuonw3h67hqjh2zambrhj4kaaulncpt4amkcua", + "kind": "INDUSTRY", + "object_types": [ + "ALGORITHM", + "DATASET", + "DOCUMENT", + "MODEL", + "OTHER", + "SOFTWARE" + ], + "parent_refs": [ + "entity-profile:global@1.0" + ], + "policy": { + "external_standard_not_redefined": true, + "industry_profile": "software-engineering", + "profile_schema_version": "1.0", + "technical_interoperability_not_regulatory_compliance": true + }, + "profile_id": "entity-profile:software-engineering", + "profile_is_not_authority": true, + "profile_ref": "entity-profile:software-engineering@1.0", + "public_unclassified": true, + "required_evidence_types": [ + "DOCUMENT", + "OTHER", + "REGISTRY_RECORD" + ], + "schema": "entity-v3-global-profile-v1", + "schema_sha256": "33a5703c703526d2e99f347a6ce65556ca0d91c9fd5c1283207239aaaa33fa74", + "signature": { + "entity_id": "ent2-m3nofxtv3zwldhwuonw3h67hqjh2zambrhj4kaaulncpt4amkcua", + "key_id": "sig-a31ded371cfb5019", + "payload_sha256": "1afcb25c1a62814b6b561b54367460ab47e35faff6cf53c8954fe8d301a708c2", + "signature": "rhGmk75uVFy4YmeAlFSaYJRKP8ZOOeGwE5Z84M2vuHW91XROqCWp7QT1LnFQKQBezpktox0ioKEB0cOeQamBCg", + "signature_schema": "entity-signature-record-v2", + "signed_at_ms": 1791049771397, + "suite": "ENTITY-SIG-ED25519-v1" + }, + "standards": [ + { + "normative_equivalence_claimed": false, + "role": "MAPPING", + "standard": "CYCLONEDX" + }, + { + "normative_equivalence_claimed": false, + "role": "MAPPING", + "standard": "SLSA" + }, + { + "normative_equivalence_claimed": false, + "role": "MAPPING", + "standard": "SPDX-3" + } + ], + "standards_mapping_is_not_normative_equivalence": true, + "version": "1.0" +} diff --git a/protocol/v3/ENTITY_ECONOMIC_MARKET_PROTOCOL.md b/protocol/v3/ENTITY_ECONOMIC_MARKET_PROTOCOL.md new file mode 100644 index 00000000..4abbc627 --- /dev/null +++ b/protocol/v3/ENTITY_ECONOMIC_MARKET_PROTOCOL.md @@ -0,0 +1,468 @@ +# ENTITY Economic Market Protocol — Universal Instrument and Listing Registry + +Status: v3.4.3 application/registry profile +Date: 2026-10-03 + +## Purpose + +ENTITY is a general-purpose digital asset economy. It is not a BTG-only marketplace. + +Any valid ENTITY identity may control a DCO and, where its Rights Passport permits, issue an economic instrument against that DCO under the same protocol rules used by every other participant. + +ENTITY owns the **protocol and registry rules**, not the assets listed through them. + +## Universal market stack + +```text +USER / ORGANIZATION / OTHER VALID ENTITY CONTROLLER + ↓ +ENTITY identity + ↓ +Owned / controlled DCO + ↓ +Rights Passport + ↓ +Global Passport / lineage / evidence + ↓ +Economic Instrument + ↓ +Instrument Registry + ↓ +Listing + Listing Information Sheet + ↓ +ENTITY-compatible Market / Venue + ↓ +Wallet-to-wallet trade + ↓ +Clearing / settlement / entitlement + ↓ +Economic lineage +``` + +The five universal market primitives are: + +1. **Issuer** +2. **Instrument** +3. **Listing** +4. **Trade** +5. **Settlement** + +EEP remains the execution, clearing, settlement and entitlement engine. This profile adds the universal issuer namespace, canonical instrument package, canonical listing package and buyer-facing disclosure layer above EEP. + +## Issuer neutrality + +BTG has no special structural market privilege. + +Examples: + +```text +shawn.blackmore.entity +└── btg.entity + └── robotics.entity + └── controller-001.entity + +acme-corp.entity +└── ai.entity + └── vision-model-07.entity + +jane.smith.entity +└── photography.entity + └── collection-2026.entity +``` + +These are examples of participant/domain/asset structures. Protocol-origin ancestry remains distinct from participant asset provenance and control. + +## Issuer namespaces + +Every issuer may register one signed market namespace alias derived from or chosen for its ENTITY identity. + +Examples: + +```text +BTG +ACME +JSMITH +``` + +A namespace is a convenience alias only. It is never authority. + +The canonical issuer remains the cryptographic ENTITY ID. + +A namespace is unique in one registry. If a preferred alias is already held by another verified issuer, a collision-safe alias is allocated. + +## Instrument identity + +A human-readable market symbol is not globally authoritative. + +Example: + +```text +Instrument Name: +Vision Model 07 Commercial Training Rights + +Display Symbol: +VM07-TRN + +Market Identifier: +ACME:VM07-TRN + +Canonical Instrument ID: +entity.instrument:v1:::training:000001 +``` + +The three identity layers are: + +```text +Friendly name + ↓ +Issuer namespace + display symbol + ↓ +Canonical immutable instrument ID +``` + +Two issuers may independently use the same display symbol. Resolution is issuer-scoped. + +```text +ACME:SHARED-TRN +JSMITH:SHARED-TRN +``` + +These resolve to different canonical instrument IDs. + +## Required instrument package + +Every universal instrument package records at least: + +- canonical instrument ID; +- issuer ENTITY ID; +- issuer namespace alias; +- underlying DCO ID; +- instrument name; +- display symbol; +- market identifier; +- EEP instrument class; +- rights class; +- series; +- fungibility; +- divisibility; +- supply; +- Rights Passport ID; +- Global Passport ID; +- jurisdiction; +- transfer rules; +- economic terms; +- royalty/participation terms; +- plain-language rights the buyer receives; +- plain-language rights the buyer does not receive; +- evidence references; +- creation time; +- current status; +- issuer signature. + +The instrument package is not the underlying DCO and does not silently transfer ownership of the underlying asset. + +## Listing separation + +An instrument and a listing are different objects. + +One instrument may be listed on more than one approved venue without creating a new instrument. + +```text +DCO + │ + └── Canonical Instrument + │ + ├── ENTITY primary market + ├── issuer private market + └── partner marketplace +``` + +Each listing records at least: + +- canonical listing ID; +- canonical instrument ID; +- venue ID; +- market ID; +- display symbol; +- quote unit; +- trade mode; +- settlement method; +- minimum quantity; +- quantity precision; +- price precision; +- pricing method; +- Listing Information Sheet hash; +- machine manifest hash; +- listing status; +- listing time; +- issuer/lister signature. + +## Listing Information Sheet — REQUIRED + +A buyer must not be expected to understand an instrument from a ticker alone. + +Each listing therefore requires a buyer-facing **Listing Information Sheet** bound by hash to the canonical instrument and listing. + +It must answer: + +- What is this? +- Who is the issuer? +- What DCO is underneath it? +- What does the buyer receive? +- What does the buyer **not** receive? +- What actions are granted? +- What restrictions apply? +- Is commercial use permitted? +- Is AI training permitted? +- Are derivatives permitted? +- Is redistribution permitted? +- What is the supply? +- What is the divisibility? +- What pricing mechanism is being used? +- What royalties or participation obligations apply? +- What is the duration? +- What territory/jurisdiction applies? +- What transfer restrictions apply? +- Which Rights Passport applies? +- Which Global Passport applies? +- Which provenance/evidence references apply? +- What are the canonical instrument and listing IDs? +- What is the current status/version? + +A prominent plain-language section must distinguish the underlying asset from the rights represented by the instrument. + +Example: + +```text +WHAT IS THIS? + +VM07-TRN represents a defined commercial training right +associated with Vision Model 07. + +Purchasing this instrument grants: +• the rights explicitly listed below + +Purchasing this instrument does not grant: +• ownership of the underlying model +• copyright ownership +• unrestricted source redistribution +``` + +The Listing Information Sheet is disclosure. It does **not** substitute for the Rights Passport, Global Passport, canonical instrument record or governing terms. + +## Portable publication + +An instrument may be published anywhere while retaining exactly one canonical identity. + +Approved publication surfaces may include: + +- GitHub; +- project websites; +- API documentation; +- package metadata; +- research publications; +- product pages; +- contracts/invoices; +- QR codes; +- partner marketplaces. + +External publication is a reference surface, not a minting surface. + +A portable instrument package may contain: + +```text +.entity-instrument/ +├── LISTING_INFORMATION.pdf +├── LISTING_INFORMATION.md +├── ENTITY_INSTRUMENT.md +├── entity-instrument.json +├── instrument.json +├── listing.json +├── rights-passport.json +├── global-passport.json +├── provenance.json +├── verification.json +└── SHA256SUMS +``` + +Rule: + +> An instrument can be published anywhere, but it has one canonical instrument identity. + +## Wallet issuance flow + +The native wallet may expose: + +```text +Create / ingest asset + ↓ +Create DCO + ↓ +Establish provenance + lineage + ↓ +Rights Passport + ↓ +Global Passport + ↓ +Create Economic Instrument + ↓ +Choose rights being offered + ↓ +Set supply / divisibility / transfer rules + ↓ +Create Listing Information Sheet + ↓ +Choose venue + price mechanism + ↓ +Create Listing + ↓ +LIST +``` + +Instrument creation and listing are always explicit controller actions. DCO ingest alone does not issue rights or create market value. + +## Buyer inspection + +Before purchase the wallet should expose: + +- issuer; +- issuer ENTITY ID; +- market identifier; +- canonical instrument ID; +- underlying DCO; +- lineage; +- provenance; +- Rights Passport; +- Global Passport; +- rights granted; +- rights excluded; +- restrictions; +- supply/available quantity; +- price/bid/ask or pricing method; +- royalty/participation terms; +- jurisdiction; +- evidence; +- trade history. + +## Trade flow + +```text +BUY + ↓ +validate canonical instrument + ↓ +validate active listing + ↓ +validate buyer eligibility where required + ↓ +execute trade + ↓ +create clearing obligation + ↓ +settle payment-versus-right + ↓ +update entitlement balances + ↓ +apply bound royalty/participation rules + ↓ +generate signed ENTITY receipt + ↓ +append economic lineage +``` + +External payment truth remains evidence-bound. ENTITY does not claim money moved merely because a trade record exists. + +## Invariants + +1. ENTITY registry rules are issuer-neutral. +2. BTG is an issuer like any other issuer. +3. Symbol aliases are never authority. +4. Canonical instrument IDs are immutable. +5. One canonical instrument may have multiple listings. +6. Listings do not duplicate instruments. +7. Listing Information Sheets are required buyer-facing disclosures. +8. Rights Passports remain authoritative for rights semantics. +9. Protocol origin does not transfer asset ownership. +10. DCO creation does not create market value. +11. Instruments are optional after DCO creation. +12. Protocol tax remains 0. +13. Cryptocurrency is not required. +14. Venue operators do not become protocol or asset authority. +15. Historical signed economic records are preserved when withdrawn/superseded. + + +## Data Value Discovery + +The canonical market identifier (for example `ACME:WX26-TRN`) is a human-readable measurement key. It is **not** the authoritative database key. All analytics resolve to the immutable `instrument_id`. + +ENTITY may measure actual economic demand for each instrument from settled market activity: + +- last settled price; +- 24-hour and 30-day settled volume; +- settled notional by currency; +- trade count; +- unique buyers and sellers; +- high/low settled price; +- active holder count; +- issuer inventory and open sell offers; +- settled and externally verified royalty/participation obligations; +- market-integrity indicators. + +Open bids and asks are liquidity observations. They do **not** become price history, volume, realized demand or value merely because they exist. + +For a DCO with several rights instruments, ENTITY keeps each right economically distinct: + +```text +ACME:WX26-ACC Access rights +ACME:WX26-QRY Query rights +ACME:WX26-TRN AI training rights +ACME:WX26-COM Commercial-use rights +``` + +This allows ENTITY to observe whether demand is concentrated in access, query, training, commercial, derivative or other rights without assigning one arbitrary price to the underlying DCO. + +### Rollups + +Observed activity may roll upward through: + +```text +instrument + ↓ +underlying DCO + ↓ +asset class / subtype + ↓ +primary domain / industry + ↓ +ENTITY economy +``` + +Rollups preserve currency boundaries. CAD, USD and other quote units are never summed into one synthetic value without a separately evidenced conversion method. + +BTDU bindings may enrich semantic rollups across related assets, but BTDU topology does not create economic value, ownership or rights. + +### Market-integrity safeguards + +Raw volume is not proof of value. Economic intelligence therefore separates or flags: + +- self-trade attempts; +- reciprocal counterparty flows; +- concentrated counterparty activity; +- unsettled executions; +- externally unverified settlement; +- related-wallet relationships only when explicit authorized relationship evidence exists. + +ENTITY must not infer hidden relationships between wallets merely to strengthen a market metric. + +### Valuation boundary + +A settled price for a rights instrument means the market exchanged **that defined right under those terms** at that price. + +It does not prove: + +- intrinsic value of the underlying DCO; +- accounting fair value; +- enterprise value of the issuer; +- a universal price for all rights associated with the asset. + +Data Value Discovery is therefore market evidence, not protocol-created value. diff --git a/sdk/global_passport_sdk/canonical_global_passport_sdk.py b/sdk/global_passport_sdk/canonical_global_passport_sdk.py index 19536d95..077e514e 100644 --- a/sdk/global_passport_sdk/canonical_global_passport_sdk.py +++ b/sdk/global_passport_sdk/canonical_global_passport_sdk.py @@ -2,7 +2,9 @@ PROFILE_ALIASES={ "global":"entity-profile:global@1.0","healthcare":"entity-profile:healthcare@1.0","finance":"entity-profile:finance@1.0", - "manufacturing":"entity-profile:manufacturing@1.0","ai":"entity-profile:ai@1.0","robotics":"entity-profile:robotics@1.0", + "manufacturing":"entity-profile:manufacturing@1.0","ai":"entity-profile:ai@1.0", + "software":"entity-profile:software-engineering@1.0","software-engineering":"entity-profile:software-engineering@1.0", + "engineering":"entity-profile:software-engineering@1.0","robotics":"entity-profile:robotics@1.0", "defence":"entity-profile:defence-public@1.0","defense":"entity-profile:defence-public@1.0", } @@ -28,10 +30,10 @@ def compose_profiles(self,*names_or_refs:str)->dict: text=str(item); refs.append(text if text.startswith("entity-profile:") else self.profile_ref(text)) return self.profiles.resolve_stack(refs) def register_file(self,path,controller_entity_id:str,*profiles:str,logical_path:str|None=None, - previous_object_id:str|None=None,version:str="1.0",btdu_binding:dict|None=None)->dict: + previous_object_id:str|None=None,version:str="1.0",btdu_binding:dict|None=None,**kwargs)->dict: stack=self.compose_profiles(*profiles) result=self.ingestion.ingest_file(path,controller_entity_id,stack["profile_refs"],logical_path=logical_path, - previous_object_id=previous_object_id,version=version,btdu_binding=btdu_binding) + previous_object_id=previous_object_id,version=version,btdu_binding=btdu_binding,**kwargs) return {"object_id":result["object"]["object_id"],"content_sha256":result["object"]["content_sha256"], "rights_passport_id":result["rights_passport"]["passport_id"], "global_passport_id":result["global_passport"]["passport_id"], @@ -50,8 +52,20 @@ def map_external(self,package:str,standard:str,record:dict)->dict: def ingest_package_file(self,path,controller_entity_id:str,package:str,config:dict,asset_kind:str,**kwargs)->dict: plan=self.package_plan(package,config,asset_kind) - result=self.ingestion.ingest_file(path,controller_entity_id,plan["profile_refs"], - rights_actions=plan["rights_actions"],**kwargs) + options=dict(kwargs) + options.setdefault("object_type",plan["object_type"]) + extra_profiles=list(options.pop("additional_profile_refs",[]) or []) + profile_stack=self.profiles.resolve_stack(list(plan["profile_refs"])+extra_profiles) + extra_actions={str(x).upper() for x in (options.pop("additional_rights_actions",[]) or []) if str(x)} + rights_actions=sorted(set(plan["rights_actions"])|extra_actions) + context=dict(options.pop("industry_context",{}) or {}) + context.update({"industry_package":plan["package"],"asset_kind":str(asset_kind), + "configuration_sha256":plan["configuration_sha256"], + "primary_domain_profile":next((x for x in plan["profile_refs"] if "entity-profile:global@" not in x),None), + "associated_profile_refs":[x for x in profile_stack["profile_refs"] if x not in plan["profile_refs"]]}) + options["industry_context"]=context + result=self.ingestion.ingest_file(path,controller_entity_id,profile_stack["profile_refs"], + rights_actions=rights_actions,**options) return {"deployment_plan":plan,"object_id":result["object"]["object_id"], "global_passport_id":result["global_passport"]["passport_id"], "content_sha256":result["object"]["content_sha256"], diff --git a/src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py b/src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py index c49fc36a..ce113a70 100644 --- a/src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py +++ b/src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py @@ -6,7 +6,7 @@ import hashlib, json, secrets, sqlite3, time PRIMITIVES=("ENTITY","AUTHORITY","RIGHT","EVENT","VALUE") -OBJECT_TYPES={"PERSON","ORGANIZATION","AI_AGENT","DATASET","DOCUMENT","MODEL","SOFTWARE","API","DEVICE","VEHICLE","BUILDING","RESEARCH","DIGITAL_TWIN","FINANCIAL_INSTRUMENT","PHYSICAL_ASSET","OTHER"} +OBJECT_TYPES={"PERSON","ORGANIZATION","AI_AGENT","DATASET","DOCUMENT","MODEL","SOFTWARE","API","DEVICE","VEHICLE","BUILDING","RESEARCH","DIGITAL_TWIN","FINANCIAL_INSTRUMENT","PHYSICAL_ASSET","COLLECTION","CREATIVE_ASSET","ALGORITHM","INVENTION","DESIGN","MEDIA","KNOWLEDGE_ASSET","ROBOTICS_ASSET","OTHER"} RIGHT_ACTIONS={"INSPECT","READ","COPY","DERIVE","TRAIN","INFER","EXECUTE","MODIFY","REDISTRIBUTE","COMMERCIALIZE","CONTROL","TRANSFER"} AUTHORITY_ACTIONS={"ACT","SIGN","ACCESS_DATA","EXECUTE","PURCHASE","LICENSE","SETTLE","PUBLISH","RESOLVE","DELEGATE","CONTROL"} VALUE_STATES=("POTENTIAL","OFFER","CONTRACTED","ACCRUED","SETTLED","REALIZED") @@ -399,9 +399,15 @@ def visit(node): visiting.remove(node); visited.add(node); return True if any(not visit(oid) for oid in sorted(object_ids) if oid not in visited): failures.append("provenance_cycle") return {"valid":not failures,"failures":failures,"root_object_id":b.get("root_object_id"),"semantic_sha256":b.get("semantic_sha256"),"provider_independent":bool(b.get("provider_independent")),"portable_verification_uses_embedded_manifests":True,"primitive_set_complete":set(b.get("primitives") or [])==set(PRIMITIVES),"verified_manifest_count":len(manifests)} - def register_digital_commodity(self,controller_entity_id:str,title:str,content_sha256:str,*,commodity_class="DATA",measurement_unit="USE",metadata=None)->dict: - descriptor={"digital_commodity":True,"commodity_class":str(commodity_class).upper(),"measurement_unit":str(measurement_unit).upper(),"raw_data_transfer_default":False,"metadata":dict(metadata or {})} - result=self.register_object(controller_entity_id,"DATASET",title,descriptor=descriptor,content_sha256=content_sha256) + def register_digital_commodity(self,controller_entity_id:str,title:str,content_sha256:str,*,commodity_class="DATA",measurement_unit="USE",metadata=None,object_type="DATASET")->dict: + """Register the governed asset itself as a Digital Commodity Object. + + Registration does not issue an EEP instrument, list the asset, transfer + ownership, or establish market value. object_type preserves whether the + asset is SOFTWARE, MODEL, DATASET, DOCUMENT, or another ENTITY type. + """ + descriptor={"digital_commodity":True,"commodity_class":str(commodity_class).upper(),"measurement_unit":str(measurement_unit).upper(),"raw_data_transfer_default":False,"metadata":dict(metadata or {})} + result=self.register_object(controller_entity_id,str(object_type or "DATASET").upper(),title,descriptor=descriptor,content_sha256=content_sha256) result["digital_commodity_object"]=True; return result def register_ai_agent(self,controller_entity_id:str,agent_entity_id:str,title:str,*,model_ref=None,policy_refs=None,capabilities=None)->dict: diff --git a/src/31_Profiles/exchange_protocol.py b/src/31_Profiles/exchange_protocol.py index 8121df30..c87bb78d 100644 --- a/src/31_Profiles/exchange_protocol.py +++ b/src/31_Profiles/exchange_protocol.py @@ -46,7 +46,25 @@ def sha256(name): require_sha256(body.get(name)) actions=rights.get('actions') if type(actions) is not list or not actions or any(type(a) is not str or not a or a!=a.upper() for a in actions): raise ValueError('rights.actions must be non-empty canonical uppercase strings') if actions!=sorted(set(actions)): raise ValueError('rights.actions must be sorted and unique') - elif schema=='entity-eep-disclosure-v1': + elif schema=='entity-eep-instrument-withdrawal-v1': + for n in ('withdrawal_id','instrument_id','issuer','reason'): sid(n) + common_time() + evidence=body.get('evidence_sha256') + if evidence is not None: require_sha256(evidence) + if body.get('prior_status')!='ACTIVE' or body.get('new_status')!='WITHDRAWN': + raise ValueError('invalid instrument withdrawal state transition') + if body.get('no_new_rights_created') is not True: + raise ValueError('withdrawal rights-boundary flag required') + elif schema=='entity-eep-venue-retirement-v1': + for n in ('retirement_id','venue_id','operator','reason'): sid(n) + common_time() + evidence=body.get('evidence_sha256') + if evidence is not None: require_sha256(evidence) + if body.get('prior_status')!='ACTIVE' or body.get('new_status')!='RETIRED': + raise ValueError('invalid venue retirement state transition') + if body.get('venue_is_not_protocol_authority') is not True: + raise ValueError('venue authority boundary required') + elif schema=='entity-eep-disclosure-v1': for n in ('disclosure_id','venue_id','instrument_id','publisher'): sid(n) sup('disclosure_type'); sha256('content_sha256'); common_time() if body.get('protected_data_included') is not False: raise ValueError('protected_data_included must be false') @@ -122,7 +140,17 @@ def _init(self): jurisdiction TEXT NOT NULL, execution_models_json TEXT NOT NULL, policy_sha256 TEXT NOT NULL, status TEXT NOT NULL, created_at_ms INTEGER NOT NULL, signature_json TEXT NOT NULL); - CREATE TABLE IF NOT EXISTS instruments( + CREATE TABLE IF NOT EXISTS venue_retirements( + retirement_id TEXT PRIMARY KEY, venue_id TEXT NOT NULL UNIQUE, + operator TEXT NOT NULL, prior_status TEXT NOT NULL, new_status TEXT NOT NULL, + reason TEXT NOT NULL, evidence_sha256 TEXT, created_at_ms INTEGER NOT NULL, + signature_json TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS instrument_withdrawals( + withdrawal_id TEXT PRIMARY KEY, instrument_id TEXT NOT NULL UNIQUE, + issuer TEXT NOT NULL, prior_status TEXT NOT NULL, new_status TEXT NOT NULL, + reason TEXT NOT NULL, evidence_sha256 TEXT, created_at_ms INTEGER NOT NULL, + signature_json TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS instruments( instrument_id TEXT PRIMARY KEY, issuer TEXT NOT NULL, underlying_object_id TEXT NOT NULL, instrument_class TEXT NOT NULL, rights_json TEXT NOT NULL, total_units INTEGER NOT NULL, transferable INTEGER NOT NULL, duration_ms INTEGER, settlement_currency TEXT NOT NULL, @@ -242,8 +270,58 @@ def create_venue(self, operator: str, name: str, jurisdiction: str, body["venue_id"], operator, body["name"], body["jurisdiction"], json.dumps(models), policy_sha256, "ACTIVE", body["created_at_ms"], json.dumps(sig, sort_keys=True))) - return dict(body, signature=sig) - + return dict(body, signature=sig) + + def venue_status(self, venue_id: str) -> dict: + with self._db() as db: + venue=db.execute("SELECT * FROM venues WHERE venue_id=?",(str(venue_id),)).fetchone() + retirement=db.execute("SELECT * FROM venue_retirements WHERE venue_id=?",(str(venue_id),)).fetchone() + if not venue: raise KeyError("venue not found") + return {"venue_id":venue["venue_id"],"operator":venue["operator"],"name":venue["name"], + "status":venue["status"],"retirement":None if not retirement else + (dict(retirement)|{"signature":json.loads(retirement["signature_json"]), + "venue_is_not_protocol_authority":True})} + + def _record_signed_venue_retirement(self, body: dict, signature: dict) -> dict: + required={"schema","retirement_id","venue_id","operator","prior_status","new_status", + "reason","evidence_sha256","created_at_ms","venue_is_not_protocol_authority"} + validate_signed_wire(body) + if set(body)!=required or body.get("schema")!="entity-eep-venue-retirement-v1": + raise ValueError("invalid signed venue retirement shape") + operator=str(body["operator"]); venue_id=str(body["venue_id"]) + manifest=self.identity.load_manifest(operator) + if not self.identity.verify_signature(manifest,body,signature): + raise PermissionError("venue retirement signature invalid") + with self._db() as db: + db.execute("BEGIN IMMEDIATE") + venue=db.execute("SELECT * FROM venues WHERE venue_id=?",(venue_id,)).fetchone() + if not venue: raise KeyError("venue not found") + if venue["operator"]!=operator: raise PermissionError("venue operator required") + if venue["status"]!="ACTIVE": raise ValueError("active venue required") + if db.execute("SELECT 1 FROM listings WHERE venue_id=? AND status='ACTIVE' LIMIT 1",(venue_id,)).fetchone(): + raise ValueError("cannot retire venue with active listings") + if db.execute("SELECT 1 FROM orders WHERE venue_id=? AND status IN ('OPEN','PARTIAL') LIMIT 1",(venue_id,)).fetchone(): + raise ValueError("cannot retire venue with open orders") + if db.execute("SELECT 1 FROM rfqs WHERE venue_id=? AND status='OPEN' LIMIT 1",(venue_id,)).fetchone(): + raise ValueError("cannot retire venue with open RFQs") + unsettled=db.execute("""SELECT 1 FROM trades t JOIN clearing c ON c.trade_id=t.trade_id + WHERE t.venue_id=? AND c.status='PENDING' LIMIT 1""",(venue_id,)).fetchone() + if unsettled: raise ValueError("cannot retire venue with unsettled trades") + db.execute("INSERT INTO venue_retirements VALUES(?,?,?,?,?,?,?,?,?)",( + body["retirement_id"],venue_id,operator,body["prior_status"],body["new_status"], + body["reason"],body["evidence_sha256"],body["created_at_ms"],json.dumps(signature,sort_keys=True))) + db.execute("UPDATE venues SET status='RETIRED' WHERE venue_id=?",(venue_id,)) + return dict(body,signature=signature) + + def retire_venue(self, operator: str, venue_id: str, reason: str, *, evidence_sha256: str | None=None) -> dict: + evidence=require_sha256(evidence_sha256) if evidence_sha256 is not None else None + body={"schema":"entity-eep-venue-retirement-v1","retirement_id":rid("vret3"), + "venue_id":str(venue_id),"operator":str(operator),"prior_status":"ACTIVE","new_status":"RETIRED", + "reason":str(reason),"evidence_sha256":evidence,"created_at_ms":now_ms(), + "venue_is_not_protocol_authority":True} + sig=self.identity.sign(str(operator),body) + return self._record_signed_venue_retirement(body,sig) + def _record_signed_instrument(self, body: dict, signature: dict) -> dict: required={"schema","instrument_id","issuer","underlying_object_id","instrument_class","rights","total_units","transferable","duration_ms","settlement_currency","delivery_mode","status","created_at_ms","bytes_are_not_the_traded_scarcity"} validate_signed_wire(body) @@ -283,7 +361,56 @@ def define_instrument(self, issuer: str, underlying_object_id: str, instrument_c sig=self.identity.sign(issuer,body) return self._record_signed_instrument(body,sig) - def balance(self, instrument_id: str, holder: str) -> int: + def instrument_status(self, instrument_id: str) -> dict: + with self._db() as db: + inst=db.execute("SELECT instrument_id,issuer,status,created_at_ms FROM instruments WHERE instrument_id=?",(str(instrument_id),)).fetchone() + withdrawal=db.execute("SELECT * FROM instrument_withdrawals WHERE instrument_id=?",(str(instrument_id),)).fetchone() + if not inst: raise KeyError("instrument not found") + return {"instrument_id":inst["instrument_id"],"issuer":inst["issuer"],"status":inst["status"], + "issued_at_ms":int(inst["created_at_ms"]),"withdrawal":None if not withdrawal else + (dict(withdrawal)|{"signature":json.loads(withdrawal["signature_json"]), + "no_new_rights_created":True})} + + def _record_signed_instrument_withdrawal(self, body: dict, signature: dict) -> dict: + required={"schema","withdrawal_id","instrument_id","issuer","prior_status","new_status", + "reason","evidence_sha256","created_at_ms","no_new_rights_created"} + validate_signed_wire(body) + if set(body)!=required or body.get("schema")!="entity-eep-instrument-withdrawal-v1": + raise ValueError("invalid signed instrument withdrawal shape") + issuer=str(body["issuer"]); iid=str(body["instrument_id"]) + manifest=self.identity.load_manifest(issuer) + if not self.identity.verify_signature(manifest,body,signature): + raise PermissionError("instrument withdrawal signature invalid") + with self._db() as db: + db.execute("BEGIN IMMEDIATE") + inst=db.execute("SELECT * FROM instruments WHERE instrument_id=?",(iid,)).fetchone() + if not inst: raise KeyError("instrument not found") + if inst["issuer"]!=issuer: raise PermissionError("instrument issuer required") + if inst["status"]!="ACTIVE": raise ValueError("active instrument required") + if db.execute("SELECT 1 FROM orders WHERE instrument_id=? AND status IN ('OPEN','PARTIAL') LIMIT 1",(iid,)).fetchone(): + raise ValueError("cannot withdraw instrument with open orders") + if db.execute("""SELECT 1 FROM trades t JOIN clearing c ON c.trade_id=t.trade_id + WHERE t.instrument_id=? AND c.status='PENDING' LIMIT 1""",(iid,)).fetchone(): + raise ValueError("cannot withdraw instrument with unsettled trades") + if db.execute("SELECT 1 FROM rfqs WHERE instrument_id=? AND status='OPEN' LIMIT 1",(iid,)).fetchone(): + raise ValueError("cannot withdraw instrument with open RFQs") + db.execute("INSERT INTO instrument_withdrawals VALUES(?,?,?,?,?,?,?,?,?)",( + body["withdrawal_id"],iid,issuer,body["prior_status"],body["new_status"], + body["reason"],body["evidence_sha256"],body["created_at_ms"],json.dumps(signature,sort_keys=True))) + db.execute("UPDATE instruments SET status='WITHDRAWN' WHERE instrument_id=?",(iid,)) + db.execute("UPDATE listings SET status='WITHDRAWN' WHERE instrument_id=? AND status='ACTIVE'",(iid,)) + return dict(body,signature=signature) + + def withdraw_instrument(self, issuer: str, instrument_id: str, reason: str, *, evidence_sha256: str | None=None) -> dict: + evidence=require_sha256(evidence_sha256) if evidence_sha256 is not None else None + body={"schema":"entity-eep-instrument-withdrawal-v1","withdrawal_id":rid("withdraw3"), + "instrument_id":str(instrument_id),"issuer":str(issuer),"prior_status":"ACTIVE","new_status":"WITHDRAWN", + "reason":str(reason),"evidence_sha256":evidence,"created_at_ms":now_ms(), + "no_new_rights_created":True} + sig=self.identity.sign(str(issuer),body) + return self._record_signed_instrument_withdrawal(body,sig) + + def balance(self, instrument_id: str, holder: str) -> int: with self._db() as db: row = db.execute("SELECT units FROM balances WHERE instrument_id=? AND holder=?", (instrument_id, holder)).fetchone() @@ -912,7 +1039,7 @@ def surveillance_alerts(self, venue_id: str) -> list[dict]: def status(self) -> dict: with self._db() as db: - tables=("venues","instruments","listings","orders","order_cancellations","trades","clearing","payment_attestations","settlement_verifiers","entitlements","usage","surveillance","rfqs","quotes","rfq_acceptances","rfq_trade_sources","revenue_rule_sets","trade_revenue_bindings") + tables=("venues","venue_retirements","instruments","instrument_withdrawals","listings","orders","order_cancellations","trades","clearing","payment_attestations","settlement_verifiers","entitlements","usage","surveillance","rfqs","quotes","rfq_acceptances","rfq_trade_sources","revenue_rule_sets","trade_revenue_bindings") counts={name:int(db.execute(f"SELECT COUNT(*) FROM {name}").fetchone()[0]) for name in tables} return {"schema":"entity-eep-status-v1","profile":"ENTITY_EXCHANGE_PROTOCOL","version":"3.0.0", "rights_are_traded_not_bytes":True,"venue_neutral":True,"payment_versus_right_transfer":True, diff --git a/src/33_Economic_Participation/economic_participation.py b/src/33_Economic_Participation/economic_participation.py index d826c9dd..e7d01fa9 100644 --- a/src/33_Economic_Participation/economic_participation.py +++ b/src/33_Economic_Participation/economic_participation.py @@ -61,6 +61,10 @@ def _init(self): terms_sha256 TEXT NOT NULL, effective_at_ms INTEGER NOT NULL, status TEXT NOT NULL, created_at_ms INTEGER NOT NULL, signature_json TEXT NOT NULL, UNIQUE(instrument_id,version)); + CREATE TABLE IF NOT EXISTS participation_policy_withdrawals( + withdrawal_id TEXT PRIMARY KEY, policy_id TEXT NOT NULL UNIQUE, instrument_id TEXT NOT NULL, + originator_entity_id TEXT NOT NULL, reason TEXT NOT NULL, evidence_sha256 TEXT, + created_at_ms INTEGER NOT NULL, signature_json TEXT NOT NULL); CREATE TABLE IF NOT EXISTS reserve_allocations( allocation_id TEXT PRIMARY KEY, policy_id TEXT NOT NULL, instrument_id TEXT NOT NULL, originator_entity_id TEXT NOT NULL, treasury_entity_id TEXT NOT NULL, @@ -196,12 +200,43 @@ def define_participation(self,originator_entity_id,treasury_id,instrument_id,tot return dict(body,signature=sig) def _active_policy(self,instrument_id,at_ms=None): - at=int(at_ms or now_ms()) + at=int(now_ms() if at_ms is None else at_ms) with self._db() as db: row=db.execute("SELECT * FROM participation_policies WHERE instrument_id=? AND effective_at_ms<=? ORDER BY effective_at_ms DESC,version DESC LIMIT 1",(str(instrument_id),at)).fetchone() - if not row: raise KeyError('effective participation policy not found') + if not row: raise KeyError('effective participation policy not found') + withdrawal=db.execute("SELECT created_at_ms FROM participation_policy_withdrawals WHERE policy_id=?",(row['policy_id'],)).fetchone() + if withdrawal is not None and int(withdrawal['created_at_ms'])<=at: + raise KeyError('participation policy withdrawn at requested economic time') return row + def withdraw_participation(self,instrument_id,originator_entity_id,reason,*,evidence_sha256=None): + instrument_id=str(instrument_id); originator_entity_id=str(originator_entity_id); reason=str(reason).strip() + if not reason: raise ValueError('withdrawal reason required') + evidence=require_sha256(evidence_sha256) if evidence_sha256 else None + with self._db() as db: + policy=db.execute("SELECT * FROM participation_policies WHERE instrument_id=? AND status='ACTIVE' ORDER BY effective_at_ms DESC,version DESC LIMIT 1",(instrument_id,)).fetchone() + if not policy: raise KeyError('active participation policy not found') + if policy['originator_entity_id']!=originator_entity_id: + raise PermissionError('policy originator required') + prior=db.execute("SELECT * FROM participation_policy_withdrawals WHERE policy_id=?",(policy['policy_id'],)).fetchone() + if prior: + return dict(prior,signature=json.loads(prior['signature_json'])) + body={'schema':'entity-v3-participation-policy-withdrawal-v1','withdrawal_id':rid('opw3'), + 'policy_id':policy['policy_id'],'instrument_id':instrument_id, + 'originator_entity_id':originator_entity_id,'reason':reason, + 'evidence_sha256':evidence,'created_at_ms':now_ms(), + 'historical_obligations_and_reserves_preserved':True, + 'no_retroactive_economic_change':True,'protocol_tax_bps':0} + sig=self.identity.sign(originator_entity_id,body) + with self._db() as db: + current=db.execute("SELECT status FROM participation_policies WHERE policy_id=?",(policy['policy_id'],)).fetchone() + if not current or current['status']!='ACTIVE': raise ValueError('participation policy no longer active') + db.execute('INSERT INTO participation_policy_withdrawals VALUES(?,?,?,?,?,?,?,?)',( + body['withdrawal_id'],body['policy_id'],instrument_id,originator_entity_id,reason,evidence, + body['created_at_ms'],json.dumps(sig,sort_keys=True))) + db.execute("UPDATE participation_policies SET status='WITHDRAWN' WHERE policy_id=?",(body['policy_id'],)) + return dict(body,signature=sig) + @contextmanager def _exchange_db(self,exchange): db=sqlite3.connect(exchange.path); db.row_factory=sqlite3.Row diff --git a/src/38_Global_Passports/continuous_ingestion.py b/src/38_Global_Passports/continuous_ingestion.py index 8b08a4a7..bfd9dcf8 100644 --- a/src/38_Global_Passports/continuous_ingestion.py +++ b/src/38_Global_Passports/continuous_ingestion.py @@ -29,7 +29,10 @@ def __init__(self,state_root,identity,fabric,evidence_registry,rights_passports, self.protocol_release_ref=protocol_release_ref def ingest_file(self,path,controller_entity_id:str,profile_refs:list[str],*,logical_path:str|None=None, previous_object_id:str|None=None,rights_actions:list[str]|None=None,version:str="1.0", - protocol_release_ref:str|None=None,btdu_binding:dict|None=None)->dict: + protocol_release_ref:str|None=None,btdu_binding:dict|None=None,title:str|None=None, + object_type:str|None=None,digital_commodity:bool=False,commodity_class:str="DATA", + measurement_unit:str="ASSET",commodity_metadata:dict|None=None, + industry_context:dict|None=None,rights_constraints:dict|None=None)->dict: src=Path(path).resolve() if not src.is_file(): raise FileNotFoundError(src) content_sha=sha256_file(src); vault_path=self.vault/content_sha[:2]/content_sha @@ -38,10 +41,22 @@ def ingest_file(self,path,controller_entity_id:str,profile_refs:list[str],*,logi if sha256_file(vault_path)!=content_sha: raise RuntimeError("content vault verification failed") descriptor={"logical_path":logical_path or src.name,"source_filename":src.name,"source_bytes":src.stat().st_size, "content_addressed":True,"custody_provider":"BTG_LOCAL_CONTENT_VAULT","provider_is_authority":False} - obj=self.fabric.register_object(controller_entity_id,object_type_for(src),src.name,descriptor=descriptor,content_sha256=content_sha) + kind=str(object_type or object_type_for(src)).upper() + asset_title=str(title or src.name) + if digital_commodity: + commodity_meta={**descriptor,**dict(commodity_metadata or {}), + "market_instruments_created_automatically":False, + "market_value_created_by_ingest":False} + obj=self.fabric.register_digital_commodity(controller_entity_id,asset_title,content_sha, + commodity_class=commodity_class,measurement_unit=measurement_unit, + metadata=commodity_meta,object_type=kind) + else: + obj=self.fabric.register_object(controller_entity_id,kind,asset_title,descriptor=descriptor,content_sha256=content_sha) ev=self.evidence.issue_evidence(controller_entity_id,"DOCUMENT",obj["object_id"],content_sha,provenance_refs=[previous_object_id] if previous_object_id else []) actions=rights_actions or ["INSPECT","READ","COPY","DERIVE"] - right=self.fabric.grant_right(obj["object_id"],controller_entity_id,controller_entity_id,actions,constraints={"purpose":"BTG_ENGINEERING"},economic_terms={"monetary_value_asserted":False}) + constraints={"purpose":"ENTITY_ASSET_CONTROL",**dict(rights_constraints or {})} + right=self.fabric.grant_right(obj["object_id"],controller_entity_id,controller_entity_id,actions, + constraints=constraints,economic_terms={"monetary_value_asserted":False}) rp=self.rights.issue(controller_entity_id,obj["object_id"],[{"effect":"ALLOW","actions":actions}],version=version, custody=[{"provider":"BTG_LOCAL_CONTENT_VAULT","locator":f"sha256:{content_sha}","content_sha256":content_sha,"provider_is_authority":False,"credentials_included":False}], provenance_refs=[ev["evidence_id"]],economic_terms={"underlying_information_remains_nonrival":True}) @@ -51,13 +66,16 @@ def ingest_file(self,path,controller_entity_id:str,profile_refs:list[str],*,logi gp=self.global_passports.issue(controller_entity_id,obj["object_id"],rp["passport_id"],profile_refs,version=version, evidence_refs=[ev["evidence_id"]],provenance_refs=[p["edge_id"] for p in prov], standards_mappings=[],economic_state={"state":"POTENTIAL","amount_units":0,"currency":"UNSPECIFIED"}, - industry_context={"continuous_ingestion":True,"logical_path":descriptor["logical_path"]}, + industry_context={"continuous_ingestion":True,"logical_path":descriptor["logical_path"], + "digital_commodity":bool(digital_commodity),**dict(industry_context or {})}, protocol_release_ref=protocol_release_ref or self.protocol_release_ref,btdu_binding=btdu_binding) val=self.fabric.record_value(controller_entity_id,obj["object_id"],0,"UNSPECIFIED",state="POTENTIAL", basis_ref="v3.4-zero-value-baseline-no-market-or-accounting-value-asserted") return {"object":obj,"evidence":ev,"right":right,"rights_passport":rp,"global_passport":gp, "provenance":prov,"value":val,"vault_sha256":content_sha,"vault_path":str(vault_path), - "continuous_provenance":True,"custody_is_not_authority":True} + "continuous_provenance":True,"custody_is_not_authority":True, + "digital_commodity":bool(digital_commodity),"market_instruments_created":False, + "market_value_created_by_ingest":False} def ingest_directory(self,path,controller_entity_id:str,profile_refs:list[str],*,prefix:str="",rights_actions:list[str]|None=None)->dict: root=Path(path).resolve(); records=[] @@ -65,7 +83,9 @@ def ingest_directory(self,path,controller_entity_id:str,profile_refs:list[str],* if not src.is_file() or any(part in EXCLUDED_DIRS for part in src.parts): continue rel=src.relative_to(root).as_posix(); logical=(prefix.rstrip("/")+"/"+rel).lstrip("/") if prefix else rel records.append(self.ingest_file(src,controller_entity_id,profile_refs,logical_path=logical,rights_actions=rights_actions)) - inventory=hashlib.sha256("\n".join(f"{r['object']['content_sha256']} {r['object']['descriptor']['logical_path']}" for r in records).encode()).hexdigest() + inventory=hashlib.sha256("\n".join( + f"{r['object']['content_sha256']} {r['object']['descriptor'].get('logical_path') or (r['object']['descriptor'].get('metadata') or {}).get('logical_path','')}" + for r in records).encode()).hexdigest() return {"schema":"entity-v3-continuous-ingest-result-v1","files":len(records),"inventory_sha256":inventory, "object_ids":[r["object"]["object_id"] for r in records],"global_passport_ids":[r["global_passport"]["passport_id"] for r in records], "content_addressed":True,"custody_is_not_authority":True,"economic_value_invented":False} diff --git a/src/38_Global_Passports/global_passport_profile.py b/src/38_Global_Passports/global_passport_profile.py index 928d8ebd..9f92836a 100644 --- a/src/38_Global_Passports/global_passport_profile.py +++ b/src/38_Global_Passports/global_passport_profile.py @@ -2,7 +2,7 @@ CORE_PRIMITIVES=["ENTITY","AUTHORITY","RIGHT","EVENT","VALUE"] MARKET_LIFECYCLE=["DCO","INSTRUMENT","LISTING","DISCLOSURE","ORDER_RFQ_AUCTION","PRICE_DISCOVERY","TRADE","CLEARING","SETTLEMENT","ENTITLEMENT","USAGE","DERIVED_OUTPUT","ECONOMIC_CONSEQUENCE"] -BUILTIN_PROFILE_REFS=["entity-profile:global@1.0","entity-profile:healthcare@1.0","entity-profile:finance@1.0","entity-profile:manufacturing@1.0","entity-profile:ai@1.0","entity-profile:robotics@1.0","entity-profile:defence-public@1.0"] +BUILTIN_PROFILE_REFS=["entity-profile:global@1.0","entity-profile:healthcare@1.0","entity-profile:finance@1.0","entity-profile:manufacturing@1.0","entity-profile:ai@1.0","entity-profile:software-engineering@1.0","entity-profile:robotics@1.0","entity-profile:defence-public@1.0"] def passport_status()->dict: return {"schema":"entity-v3-global-passport-profile-status-v1","version":"3.4.0","core_primitives":CORE_PRIMITIVES, diff --git a/src/38_Global_Passports/industry_profiles.py b/src/38_Global_Passports/industry_profiles.py index aa9a6289..ac2eb1fe 100644 --- a/src/38_Global_Passports/industry_profiles.py +++ b/src/38_Global_Passports/industry_profiles.py @@ -10,6 +10,7 @@ def _std(name:str,role:str="MAPPING")->dict: return {"standard":name,"role":role "entity-profile:finance@1.0":{"kind":"INDUSTRY","standards":[_std("ISO-20022"),_std("FIX"),_std("LEI")],"parents":["entity-profile:global@1.0"],"objects":["FINANCIAL_INSTRUMENT","DOCUMENT","DATASET","SOFTWARE"],"evidence":["DOCUMENT","PAYMENT_RECORD","REGISTRY_RECORD"]}, "entity-profile:manufacturing@1.0":{"kind":"INDUSTRY","standards":[_std("OPC-UA"),_std("ASSET-ADMINISTRATION-SHELL")],"parents":["entity-profile:global@1.0"],"objects":["DEVICE","PHYSICAL_ASSET","DIGITAL_TWIN","DATASET","SOFTWARE"],"evidence":["SENSOR_OBSERVATION","DOCUMENT"]}, "entity-profile:ai@1.0":{"kind":"INDUSTRY","standards":[_std("NIST-AI-RMF"),_std("SPDX-3"),_std("CYCLONEDX")],"parents":["entity-profile:global@1.0"],"objects":["DATASET","MODEL","SOFTWARE","AI_AGENT","DOCUMENT"],"evidence":["DOCUMENT","OTHER","REGISTRY_RECORD"]}, +"entity-profile:software-engineering@1.0":{"kind":"INDUSTRY","standards":[_std("SPDX-3"),_std("CYCLONEDX"),_std("SLSA")],"parents":["entity-profile:global@1.0"],"objects":["SOFTWARE","ALGORITHM","DATASET","DOCUMENT","MODEL","OTHER"],"evidence":["DOCUMENT","OTHER","REGISTRY_RECORD"]}, "entity-profile:robotics@1.0":{"kind":"INDUSTRY","standards":[_std("ROS-2"),_std("OPEN-RMF")],"parents":["entity-profile:global@1.0"],"objects":["DEVICE","AI_AGENT","SOFTWARE","DATASET","PHYSICAL_ASSET"],"evidence":["SENSOR_OBSERVATION","OTHER","DOCUMENT"]}, "entity-profile:defence-public@1.0":{"kind":"INDUSTRY","standards":[_std("PUBLIC-DATA-GOVERNANCE"),_std("ORIGINATOR-CONTROL")],"parents":["entity-profile:global@1.0"],"objects":["DATASET","DOCUMENT","SOFTWARE","DEVICE","MODEL"],"evidence":["DOCUMENT","REGISTRY_RECORD"],"public_unclassified":True}, } diff --git a/src/39_Implementation_Packages/industry_packages.py b/src/39_Implementation_Packages/industry_packages.py index 6a4ef281..69a0520f 100644 --- a/src/39_Implementation_Packages/industry_packages.py +++ b/src/39_Implementation_Packages/industry_packages.py @@ -65,10 +65,27 @@ def _mapping(standard:str,fields:dict[str,str])->dict: _mapping("NIST-AI-RMF",{"risk_category":"descriptor.risk_category","control":"descriptor.control_ref"})], "privacy":{"default":"SELECTIVE_DISCLOSURE","minimum_data":True,"purpose_bound":True}, }, + "software-engineering":{ + "profile_refs":[GLOBAL_PROFILE,"entity-profile:software-engineering@1.0"], + "required_config":["organization","jurisdiction","authority_source","software_governance_policy"], + "object_types":{ + "repository":"SOFTWARE","source_code":"SOFTWARE","library":"SOFTWARE","application":"SOFTWARE", + "algorithm":"ALGORITHM","build_artifact":"SOFTWARE","test_evidence":"DOCUMENT", + "engineering_control":"DATASET" + }, + "rights":["INSPECT","READ","COPY","DERIVE","EXECUTE","MODIFY"], + "evidence_types":["DOCUMENT","OTHER","REGISTRY_RECORD"], + "mappings":[ + _mapping("SPDX-3",{"name":"descriptor.component_name","version":"descriptor.component_version","license":"descriptor.license_expression"}), + _mapping("CYCLONEDX",{"bom-ref":"descriptor.bom_ref","name":"descriptor.component_name","version":"descriptor.component_version"}), + _mapping("SLSA",{"buildType":"descriptor.build_type","builder.id":"descriptor.builder_id","invocationId":"descriptor.invocation_id"}) + ], + "privacy":{"default":"SELECTIVE_DISCLOSURE","minimum_data":True,"purpose_bound":True}, + }, "robotics":{ "profile_refs":[GLOBAL_PROFILE,"entity-profile:robotics@1.0"], "required_config":["organization","jurisdiction","authority_source","safety_policy"], - "object_types":{"robot":"DEVICE","controller":"DEVICE","model":"MODEL","software":"SOFTWARE","telemetry":"DATASET","action_record":"DOCUMENT"}, + "object_types":{"robot":"DEVICE","controller":"DEVICE","model":"MODEL","software":"SOFTWARE","algorithm":"ALGORITHM","telemetry":"DATASET","action_record":"DOCUMENT"}, "rights":["INSPECT","READ","INFER","EXECUTE","CONTROL"], "evidence_types":["SENSOR_OBSERVATION","DOCUMENT","OTHER"], "mappings":[_mapping("ROS-2",{"topic":"descriptor.ros_topic","type":"descriptor.ros_type","node":"descriptor.ros_node"}), diff --git a/src/42_ENTITY_Wallet/asset_ingest.py b/src/42_ENTITY_Wallet/asset_ingest.py new file mode 100644 index 00000000..48b7f56a --- /dev/null +++ b/src/42_ENTITY_Wallet/asset_ingest.py @@ -0,0 +1,167 @@ +from __future__ import annotations +from pathlib import Path +import hashlib, json, sqlite3 + +INGEST_SCHEMA="entity-wallet-canonical-asset-ingest-v2" + +PACKAGE_KINDS={ + "software-engineering":["repository","source_code","library","application","algorithm","build_artifact","test_evidence","engineering_control"], + "robotics":["robot","controller","model","software","algorithm","telemetry","action_record"], + "ai":["training_dataset","corpus","model","weights","evaluation","agent","output"], + "manufacturing":["machine","digital_twin","firmware","telemetry","maintenance_record"], + "healthcare":["clinical_dataset","clinical_document","diagnostic_model","medical_device"], + "finance":["instrument","trade_record","settlement_record","market_dataset"], + "defence-public":["public_asset","public_dataset","software","device","model","custody_record"], +} + +PACKAGE_POLICY_DEFAULTS={ + "software-engineering":{"software_governance_policy":"ENTITY_PROVENANCE_BOUND"}, + "robotics":{"safety_policy":"ENTITY_FAIL_CLOSED"}, + "ai":{"model_governance_policy":"ENTITY_PROVENANCE_BOUND"}, + "manufacturing":{"asset_namespace":"ENTITY_CONTROLLER"}, + "healthcare":{"privacy_policy":"ENTITY_RIGHTS_PASSPORT"}, + "finance":{"settlement_policy":"EXTERNAL_SETTLEMENT_EVIDENCE_REQUIRED"}, + "defence-public":{"release_policy":"PUBLIC_UNCLASSIFIED_ONLY","classification":"UNCLASSIFIED"}, +} + +class WalletAssetIngestor: + """Wallet facade over the canonical v3.4.3 ingestion stack. + + The wallet does not maintain a parallel object-registration path. Every new DCO + flows through the same protocol origin, BTDU, evidence, rights passport and + Global Passport code used by the canonical ENTITY v3.4 deployment CLI. + """ + def __init__(self,state_dir:str|Path,canonical_cli): + self.state=Path(state_dir) + self.cli=canonical_cli + + @staticmethod + def asset_kinds(package:str)->list[str]: + return list(PACKAGE_KINDS.get(str(package).lower(),[])) + + def _existing_dco(self,controller_entity_id:str,content_sha256:str)->str|None: + dbp=self.state/"entity_v3"/"universal_fabric.sqlite" + if not dbp.exists(): return None + db=sqlite3.connect(dbp); db.row_factory=sqlite3.Row + try: + rows=db.execute("""SELECT object_id,descriptor_json FROM objects + WHERE controller_entity_id=? AND content_sha256=? AND status='ACTIVE' + ORDER BY created_at_ms DESC""",(controller_entity_id,content_sha256)).fetchall() + for row in rows: + try: descriptor=json.loads(row["descriptor_json"] or "{}") + except Exception: descriptor={} + if descriptor.get("digital_commodity") is True: return str(row["object_id"]) + finally: db.close() + return None + + @staticmethod + def _configuration(identity,controller_entity_id:str,package:str,jurisdiction:str,authority_basis:str, + overrides:dict|None=None)->dict: + manifest=identity.load_manifest(controller_entity_id) + package=str(package).lower() + cfg={ + "organization":str(manifest.get("display_name") or controller_entity_id), + "jurisdiction":str(jurisdiction or "").strip(), + "authority_source":str(authority_basis or "").strip(), + **dict(PACKAGE_POLICY_DEFAULTS.get(package) or {}), + **dict(overrides or {}), + } + if cfg.get("asset_namespace")=="ENTITY_CONTROLLER": cfg["asset_namespace"]=controller_entity_id + return cfg + + def ingest_file(self,controller_entity_id:str,file_path:str|Path,*,title:str|None=None, + package:str="general",asset_kind:str|None=None,jurisdiction:str="", + authority_basis:str="CONTROLLER_ENTITY",commodity_class:str="DATA", + measurement_unit:str="ASSET",version:str="1.0", + previous_object_id:str|None=None,metadata:dict|None=None, + configuration:dict|None=None,additional_profiles:list[str]|None=None, + additional_rights_actions:list[str]|None=None)->dict: + src=Path(file_path).expanduser().resolve() + if not src.is_file(): raise FileNotFoundError(str(src)) + digest=hashlib.sha256() + with src.open("rb") as f: + for chunk in iter(lambda:f.read(1024*1024),b""): digest.update(chunk) + source_sha=digest.hexdigest() + existing=self._existing_dco(controller_entity_id,source_sha) + if existing and str(previous_object_id or "")!=str(existing): + raise ValueError(f"identical Digital Commodity Object already registered: {existing}") + if existing and str(previous_object_id or "")==str(existing): + # Explicit version/supersession path. Same bytes may be re-registered only + # when the caller names the exact previous DCO being corrected/versioned. + pass + identity,fabric,profiles,origin,passports,packages,sdk,origin_status=self.cli.runtime( + self.state,require_current_release=True) + release=origin.passport_binding("entity-release:v3.4.3") + if release.get("origin_lineage_id")!="entity-origin:shawn-btg-entity@1.0": + raise RuntimeError("canonical Shawn -> BTG -> ENTITY origin lineage required") + logical=src.name + universe=self.cli._btdu_for_controller(self.state,identity,controller_entity_id) + try: + receipt=self.cli._btdu_receipt(identity,controller_entity_id,src,logical) + btdu_obj=universe.ingest_file( + src,receipt,logical_path=logical,source_entity_id=controller_entity_id, + controller_entity_id=controller_entity_id,rights_holder_entity_id=controller_entity_id, + provenance_ref="entity-v3.4.3-wallet-ingest:"+logical) + binding=universe.passport_binding(btdu_obj["object_ref"]) + finally: + universe.close() + content_sha=str(binding["content_sha256"]) + if content_sha!=source_sha: raise RuntimeError("BTDU content hash differs from source asset") + commodity_meta={ + "wallet_ingest_schema":INGEST_SCHEMA, + "authority_basis":str(authority_basis), + "controller_asserted_registration_authority":True, + "canonical_protocol_lineage_required":True, + "profile_domain":str(package).lower(), + "explicit_previous_object_id":str(previous_object_id) if previous_object_id else None, + "same_content_version_correction":bool(existing and previous_object_id and str(existing)==str(previous_object_id)), + **dict(metadata or {}), + } + package=str(package or "general").lower() + common={ + "logical_path":logical,"previous_object_id":previous_object_id,"version":str(version), + "btdu_binding":binding,"title":str(title or src.name),"digital_commodity":True, + "commodity_class":str(commodity_class).upper(), + "measurement_unit":str(measurement_unit).upper(), + "commodity_metadata":commodity_meta, + "industry_context":{"wallet_ingest":True}, + } + if package=="general": + result=sdk.register_file(src,controller_entity_id,"global",**common) + object_id=result["object_id"]; passport_id=result["global_passport_id"] + else: + if package not in PACKAGE_KINDS: raise ValueError("unsupported ENTITY domain package") + if not asset_kind: raise ValueError("asset_kind required for domain package") + if asset_kind not in PACKAGE_KINDS[package]: raise ValueError("unsupported package asset kind") + if not str(jurisdiction or "").strip(): raise ValueError("jurisdiction required for domain package") + cfg=self._configuration(identity,controller_entity_id,package,jurisdiction,authority_basis,configuration) + profile_refs=[] + for p in (additional_profiles or []): + text=str(p).strip() + if not text: continue + profile_refs.append(text if text.startswith("entity-profile:") else sdk.profile_ref(text)) + result=sdk.ingest_package_file(src,controller_entity_id,package,cfg,asset_kind, + additional_profile_refs=profile_refs, + additional_rights_actions=[str(x).upper() for x in (additional_rights_actions or []) if str(x)], + **common) + object_id=result["object_id"]; passport_id=result["global_passport_id"] + gp=passports.get(passport_id) + check=passports.verify(gp) + if not check.get("valid"): raise RuntimeError("new Global Passport failed verification") + obj=fabric.get_object(object_id) + return { + "schema":INGEST_SCHEMA, + "digital_asset":obj, + "content_sha256":obj.get("content_sha256"), + "global_passport":gp, + "global_passport_verified":True, + "rights_passport_id":gp["rights_passport_id"], + "profile_refs":gp["profile_stack"]["profile_refs"], + "protocol_origin":gp["protocol_origin"], + "btdu_binding":gp.get("btdu_binding"), + "canonical_origin_installation":origin_status, + "market_instruments_created":False, + "listing_created":False, + "price_created":False, + "economic_value_invented":False, + } diff --git a/src/42_ENTITY_Wallet/canonical_wallet.py b/src/42_ENTITY_Wallet/canonical_wallet.py index 513ec7e2..dd728856 100644 --- a/src/42_ENTITY_Wallet/canonical_wallet.py +++ b/src/42_ENTITY_Wallet/canonical_wallet.py @@ -2,7 +2,7 @@ from contextlib import contextmanager from pathlib import Path from typing import Any -import html, json, secrets, sqlite3, time +import html, importlib.util, json, secrets, sqlite3, sys, time WALLET_PROFILE="ENTITY_ECONOMIC_WALLET" WALLET_VERSION="1.0.0" @@ -12,11 +12,18 @@ def now_ms()->int: return int(time.time()*1000) def rid(prefix:str)->str: return prefix+"-"+secrets.token_hex(12) class EntityEconomicWallet: - """Identity-bound stock-style view over canonical ENTITY economic records.""" + """Identity-bound wallet model over canonical ENTITY asset and economic records.""" def __init__(self,state_dir:str|Path): self.state=Path(state_dir) self.exchange_path=self.state/"entity_v3_exchange.sqlite" self.economic_path=self.state/"entity_v3_economic_participation.sqlite" + self.fabric_path=self.state/"entity_v3"/"universal_fabric.sqlite" + self.dco_factory_path=self.state/"dco_factory"/"entity_dco_factory.sqlite" + lineage_path=Path(__file__).with_name("lineage_resolver.py") + spec=importlib.util.spec_from_file_location("entity_wallet_lineage_resolver",lineage_path) + if spec is None or spec.loader is None: raise RuntimeError("wallet lineage resolver unavailable") + lineage_mod=importlib.util.module_from_spec(spec); sys.modules[spec.name]=lineage_mod; spec.loader.exec_module(lineage_mod) + self.lineage=lineage_mod.WalletLineageResolver(self.state) self.root=self.state/"wallet"; self.root.mkdir(parents=True,exist_ok=True) self.path=self.root/"entity_wallet.sqlite"; self._init() @@ -122,6 +129,47 @@ def _trade_basis(self,db,entity_id:str,instrument_id:str,current_units:int)->dic "realized_change_amount_units":realized,"unknown_sold_units":unknown_sold, "bought_units":bought,"sold_units":sold} + def _assets(self,entity_id:str)->list[dict]: + """Return controller-held Digital Commodity Objects independently of market positions.""" + if not self.fabric_path.exists(): return [] + master_by_hash={} + if self.dco_factory_path.exists(): + try: + with self._db(self.dco_factory_path) as fdb: + for r in fdb.execute("SELECT dco_id,source_sha256,lifecycle FROM dco_masters"): + master_by_hash[str(r["source_sha256"])]={ + "dco_id":str(r["dco_id"]),"lifecycle":str(r["lifecycle"]).upper()} + except sqlite3.OperationalError: + master_by_hash={} + with self._db(self.fabric_path) as db: + rows=db.execute("""SELECT object_id,controller_entity_id,object_type,title,content_sha256, + descriptor_json,status,created_at_ms + FROM objects WHERE controller_entity_id=? AND status='ACTIVE' + ORDER BY created_at_ms DESC,object_id""",(entity_id,)).fetchall() + version_parents={r["parent_object_id"] for r in db.execute( + """SELECT p.parent_object_id FROM provenance_edges p + JOIN objects c ON c.object_id=p.child_object_id + WHERE p.relation='VERSION_DERIVED_FROM' + AND c.controller_entity_id=? AND c.status='ACTIVE'""",(entity_id,)).fetchall()} + out=[] + for row in rows: + if row["object_id"] in version_parents: + continue + d=dict(row); descriptor=json.loads(d.pop("descriptor_json") or "{}") + if descriptor.get("digital_commodity") is not True: continue + master=master_by_hash.get(str(d.get("content_sha256") or "")) + if master and (master["lifecycle"].startswith("RETIRED") or master["lifecycle"].startswith("HISTORICAL")): + continue + d["descriptor"]=descriptor + if master: + d["dco_id"]=master["dco_id"]; d["dco_lifecycle"]=master["lifecycle"] + d["commodity_class"]=descriptor.get("commodity_class") + d["measurement_unit"]=descriptor.get("measurement_unit") + d["market_instruments_created_automatically"]=False + d["lineage"]=self.lineage.resolve(d) + out.append(d) + return out + def _positions(self,entity_id:str)->list[dict]: if not self.exchange_path.exists(): return [] with self._db(self.exchange_path) as db: @@ -129,11 +177,14 @@ def _positions(self,entity_id:str)->list[dict]: i.rights_json,i.total_units,i.transferable,i.duration_ms, i.settlement_currency,i.delivery_mode,i.status FROM balances b JOIN instruments i ON i.instrument_id=b.instrument_id - WHERE b.holder=? AND b.units>0 ORDER BY b.instrument_id""",(entity_id,)).fetchall() + WHERE b.holder=? AND b.units>0 AND i.status='ACTIVE' + ORDER BY b.instrument_id""",(entity_id,)).fetchall() out=[] for row in rows: p=dict(row); p["units"]=int(p["units"]); p["total_units"]=int(p["total_units"]) p["transferable"]=bool(p["transferable"]); p["rights"]=json.loads(p.pop("rights_json") or "{}") + if p["rights"].get("registration_only") is True: + continue p["market"]=self._market(db,p["instrument_id"]) p["cost_basis"]=self._trade_basis(db,entity_id,p["instrument_id"],p["units"]) last=p["market"]["last"] @@ -198,7 +249,7 @@ def _totals(positions:list[dict])->dict: return totals def snapshot(self,wallet_id:str)->dict: - w=self.wallet(wallet_id); entity=w["owner_entity_id"]; positions=self._positions(entity) + w=self.wallet(wallet_id); entity=w["owner_entity_id"]; assets=self._assets(entity); positions=self._positions(entity) with self._db(self.path) as db: watch=[r["instrument_id"] for r in db.execute( "SELECT instrument_id FROM watchlist WHERE wallet_id=? ORDER BY created_at_ms",(wallet_id,)).fetchall()] @@ -209,7 +260,7 @@ def snapshot(self,wallet_id:str)->dict: inst=db.execute("SELECT instrument_id,instrument_class,settlement_currency,status FROM instruments WHERE instrument_id=?",(iid,)).fetchone() if inst: watch_quotes.append({**dict(inst),"market":self._market(db,iid)}) return {"schema":"entity-economic-wallet-snapshot-v1","profile":WALLET_PROFILE,"version":WALLET_VERSION, - "created_at_ms":now_ms(),"wallet":w,"positions":positions, + "created_at_ms":now_ms(),"wallet":w,"assets":assets,"positions":positions, "portfolio_by_currency":self._totals(positions),"orders":self._orders(entity), "entitlements":self._entitlements(entity),"obligations":self._obligations(entity), "watchlist":watch_quotes, @@ -218,7 +269,12 @@ def snapshot(self,wallet_id:str)->dict: "market_value_policy":{"last_settled_trade_only":True,"bid_ask_are_quotes_not_value":True, "offers_are_not_realized_value":True,"unpriced_positions_remain_unpriced":True, "indicative_only":True,"not_accounting_fair_value":True}, - "stock_style":{"enabled":True,"features":["POSITIONS","QUANTITY","COST_BASIS","LAST","BID","ASK", + "asset_model":{"digital_assets_are_first_class_holdings":True, + "ingest_does_not_issue_market_instruments":True, + "ingest_does_not_create_market_value":True, + "protocol_lineage_and_asset_provenance_are_separate":True, + "adam_niki_btdu_do_not_create_ownership":True}, + "stock_style":{"enabled":True,"features":["ASSETS","POSITIONS","QUANTITY","COST_BASIS","LAST","BID","ASK", "INDICATIVE_MARKET_VALUE","REALIZED_CHANGE","UNREALIZED_CHANGE","ORDERS","ACTIVITY"], "rights_are_not_declared_corporate_shares":True, "legal_classification_not_inferred":True}, diff --git a/src/42_ENTITY_Wallet/lineage_resolver.py b/src/42_ENTITY_Wallet/lineage_resolver.py new file mode 100644 index 00000000..98294183 --- /dev/null +++ b/src/42_ENTITY_Wallet/lineage_resolver.py @@ -0,0 +1,159 @@ +from __future__ import annotations +from pathlib import Path +from contextlib import contextmanager +import json, sqlite3 + +CANONICAL_LINEAGE_ID="entity-origin:shawn-btg-entity@1.0" + +class WalletLineageResolver: + """Resolve the two lineage planes used by ENTITY without collapsing them. + + protocol_lineage describes canonical ENTITY ancestry/profile context. + asset_lineage describes actual controller/provenance relationships. + ADAM, NIKI and BTDU are reported as capability/evidence bindings, never as + inferred owners or economic beneficiaries. + """ + def __init__(self,state_dir:str|Path): + self.state=Path(state_dir) + self.origin_path=self.state/"entity_protocol_origin.sqlite" + self.passport_path=self.state/"entity_v3_4_global_passports.sqlite" + self.profile_path=self.state/"entity_v3_4_global_profiles.sqlite" + self.fabric_path=self.state/"entity_v3"/"universal_fabric.sqlite" + + def _name(self,entity_id:str|None)->str|None: + if not entity_id: return None + p=self.state/"identity"/"manifests"/f"{entity_id}.json" + try: + d=json.loads(p.read_text(encoding="utf-8")) + return str(d.get("display_name") or d.get("aliases",[None])[0] or entity_id) + except Exception: + return str(entity_id) + + @staticmethod + @contextmanager + def _db(path:Path): + db=sqlite3.connect(path); db.row_factory=sqlite3.Row + try: yield db + finally: db.close() + + def _origin(self)->dict|None: + if not self.origin_path.exists(): return None + try: + with self._db(self.origin_path) as db: + row=db.execute("SELECT body_json,body_sha256 FROM origin_chains WHERE lineage_id=?", + (CANONICAL_LINEAGE_ID,)).fetchone() + except sqlite3.OperationalError: + return None + if not row: return None + body=json.loads(row["body_json"]); body["body_sha256"]=row["body_sha256"]; return body + + def _passport(self,object_id:str)->dict|None: + if not self.passport_path.exists(): return None + try: + with self._db(self.passport_path) as db: + row=db.execute("""SELECT passport_id,version,body_sha256,body_json,created_at_ms + FROM global_passports WHERE object_id=? + ORDER BY created_at_ms DESC,passport_id DESC LIMIT 1""", + (str(object_id),)).fetchone() + except sqlite3.OperationalError: + return None + if not row: return None + body=json.loads(row["body_json"]) + return {**body,"body_sha256":row["body_sha256"],"created_at_ms":row["created_at_ms"]} + + def _profiles(self,refs:list[str])->list[dict]: + if not self.profile_path.exists(): return [{"profile_ref":r} for r in refs] + out=[] + try: + dbctx=self._db(self.profile_path) + with dbctx as db: + rows_available=True + for ref in refs: + row=db.execute("""SELECT profile_ref,kind,body_json,issuer_entity_id,body_sha256 + FROM profile_variants WHERE profile_ref=? + ORDER BY active_canonical DESC,created_at_ms DESC LIMIT 1""",(ref,)).fetchone() + if not row: + out.append({"profile_ref":ref}); continue + body=json.loads(row["body_json"]) + out.append({"profile_ref":ref,"profile_id":body.get("profile_id"),"kind":row["kind"], + "issuer_entity_id":row["issuer_entity_id"],"issuer_name":self._name(row["issuer_entity_id"]), + "body_sha256":row["body_sha256"]}) + except sqlite3.OperationalError: + return [{"profile_ref":r} for r in refs] + return out + + def _asset_parents(self,object_id:str)->list[dict]: + if not self.fabric_path.exists(): return [] + try: + with self._db(self.fabric_path) as db: + rows=db.execute("""SELECT p.edge_id,p.parent_object_id,p.relation,p.contribution_bps, + o.title,o.controller_entity_id + FROM provenance_edges p + LEFT JOIN objects o ON o.object_id=p.parent_object_id + WHERE p.child_object_id=? + ORDER BY p.created_at_ms,p.edge_id""",(str(object_id),)).fetchall() + except sqlite3.OperationalError: + return [] + return [{**dict(r),"controller_name":self._name(r["controller_entity_id"])} for r in rows] + + @staticmethod + def _profile_label(ref:str)->str: + core=str(ref).split("@",1)[0].split(":")[-1] + return {"global":"Global","robotics":"Robotics","ai":"AI","manufacturing":"Manufacturing", + "software-engineering":"Software Engineering","healthcare":"Healthcare", + "finance":"Finance","defence-public":"Defence-public"}.get(core,core.replace("-"," ").title()) + + def resolve(self,asset:dict)->dict: + object_id=str(asset["object_id"]); controller=str(asset["controller_entity_id"]) + origin=self._origin(); passport=self._passport(object_id) + embedded=dict((passport or {}).get("protocol_origin") or {}) + if embedded: + root=embedded.get("root_originator_entity_id"); steward=embedded.get("steward_entity_id") + protocol=embedded.get("protocol_entity_id"); origin_id=embedded.get("origin_lineage_id") + elif origin: + root=origin.get("root_originator_entity_id"); steward=origin.get("steward_entity_id") + protocol=origin.get("protocol_entity_id"); origin_id=origin.get("lineage_id") + else: + root=steward=protocol=origin_id=None + refs=list(((passport or {}).get("profile_stack") or {}).get("profile_refs") or []) + profiles=self._profiles(refs) + domains=[self._profile_label(x) for x in refs if "entity-profile:global@" not in x] + primary_domain="Robotics" if "Robotics" in domains else (domains[0] if domains else None) + composed_domains=[x for x in domains if x!=primary_domain] + path=[self._name(root),self._name(steward),self._name(protocol),primary_domain] + path=[x for x in path if x] + parents=self._asset_parents(object_id) + btdu=dict((passport or {}).get("btdu_binding") or {}) + return { + "schema":"entity-wallet-lineage-resolution-v1", + "protocol_lineage":{ + "lineage_id":origin_id, + "root_originator_entity_id":root,"root_originator_name":self._name(root), + "steward_entity_id":steward,"steward_name":self._name(steward), + "protocol_entity_id":protocol,"protocol_name":self._name(protocol), + "profiles":profiles,"display_path":" → ".join(path), + "primary_domain_profile":primary_domain,"composed_domain_profiles":composed_domains, + "profile_composition_is_not_parent_child_lineage":True, + "passport_protocol_origin_embedded":bool(embedded), + "resolved_from_canonical_origin_registry":bool(origin), + "needs_passport_lineage_supersession":bool(passport and not embedded), + "protocol_origin_is_not_asset_provenance":True, + }, + "asset_lineage":{ + "object_id":object_id,"controller_entity_id":controller, + "controller_name":self._name(controller),"parents":parents, + "protocol_origin_does_not_transfer_asset_ownership":True, + }, + "capability_bindings":{ + "BTDU":{"bound":bool(btdu),"binding":btdu or None, + "topology_does_not_create_ownership":True, + "topology_does_not_create_economic_entitlement":True}, + "ADAM":{"architecture_role":"DETERMINISTIC_EXECUTION_AND_EVIDENCE", + "sovereign_authority":False,"ownership_inferred":False}, + "NIKI":{"architecture_role":"BOUNDED_REASONING_AND_PROPOSALS", + "sovereign_authority":False,"ownership_inferred":False}, + }, + "global_passport":None if passport is None else { + "passport_id":passport.get("passport_id"),"version":passport.get("version"), + "body_sha256":passport.get("body_sha256"),"profile_refs":refs}, + } diff --git a/src/42_ENTITY_Wallet/onboarding.py b/src/42_ENTITY_Wallet/onboarding.py new file mode 100644 index 00000000..a8bb063f --- /dev/null +++ b/src/42_ENTITY_Wallet/onboarding.py @@ -0,0 +1,559 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any +import hashlib +import importlib.util +import json +import os +import platform +import secrets +import socket +import sqlite3 +import sys +import time +import uuid + +SCHEMA = "entity-wallet-onboarding-profile-v1" +VERSION = "1.0.0" +LOGIN_SCHEMA = "entity-wallet-device-login-v1" + +ROOT = Path(__file__).resolve().parents[2] + + +def _load(name: str, relative: str): + path = ROOT / relative + spec = importlib.util.spec_from_file_location(name, path) + if spec is None or spec.loader is None: + raise RuntimeError(f"cannot load {path}") + mod = importlib.util.module_from_spec(spec) + sys.modules[name] = mod + spec.loader.exec_module(mod) + return mod + + +IDENTITY = _load("wallet_onboarding_identity", "src/01_Core_Runtime/identity/canonical_identity.py") +DOMAIN = _load("wallet_onboarding_domain", "src/22_Sovereign_Domain/core/canonical_domain.py") +AUTHORITY = _load( + "wallet_onboarding_authority", + "genesis_master/04_Entity_Registry/relationships/canonical_sovereign_authority.py", +) + + +def _now() -> int: + return int(time.time() * 1000) + + +def _canon(value: Any) -> bytes: + return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, default=str).encode("utf-8") + + +def _sha(value: Any) -> str: + return hashlib.sha256(value if isinstance(value, (bytes, bytearray)) else _canon(value)).hexdigest() + + +def normalize_public_name(value: str) -> str: + raw = str(value or "").strip().lower() + if raw.endswith(".entity"): + raw = raw[:-7] + return DOMAIN.EntityDomainAuthority.normalize_name(raw) + + +def public_address(value: str) -> str: + return normalize_public_name(value) + ".entity" + + +class EntityWalletOnboarding: + """First-run sovereign identity, public-name, lineage and device-binding layer. + + Device metadata may bootstrap a DEVICE identity, but never a human or organization. + A public .entity name is a signed display/name claim; canonical authority remains the Entity ID. + """ + + def __init__(self, state_dir: str | Path): + self.state = Path(state_dir) + self.identity = IDENTITY.EntityIdentityVault(self.state) + self.domains = DOMAIN.EntityDomainAuthority(self.state, self.identity) + self.authority = AUTHORITY.SovereignAuthorityRegistry(self.state, self.identity) + self.root = self.state / "wallet" / "onboarding" + self.profiles = self.root / "profiles" + self.root.mkdir(parents=True, exist_ok=True) + self.profiles.mkdir(parents=True, exist_ok=True) + self.active_path = self.root / "active_profile.json" + self.device_path = self.root / "device.json" + + @staticmethod + def _alias_from_manifest(manifest: dict) -> str: + aliases = [str(x).strip().lower() for x in manifest.get("aliases") or [] if str(x).strip()] + entity_aliases = [x for x in aliases if x.endswith(".entity")] + if entity_aliases: + return entity_aliases[0] + if aliases: + return public_address(aliases[0]) + fallback = ".".join( + p.lower() for p in "".join( + c if c.isalnum() else " " for c in str(manifest.get("display_name") or "entity") + ).split() + ) + return public_address(fallback or ("entity-" + manifest["entity_id"][-8:])) + + def _existing_domain(self, entity_id: str) -> dict | None: + if not self.domains.path.exists(): + return None + db = sqlite3.connect(self.domains.path) + db.row_factory = sqlite3.Row + try: + row = db.execute( + """SELECT domain_id FROM domains + WHERE entity_id=? AND status='ACTIVE' + ORDER BY updated_at_ms DESC,created_at_ms DESC LIMIT 1""", + (str(entity_id),), + ).fetchone() + finally: + db.close() + return self.domains.get_domain(row["domain_id"]) if row else None + + def ensure_public_name(self, entity_id: str, requested_name: str) -> dict: + manifest = self.identity.load_manifest(str(entity_id)) + normalized = normalize_public_name(requested_name) + domain = self._existing_domain(entity_id) + if domain is None: + domain = self.domains.create_domain(entity_id, namespace="entity", requested_name=normalized) + claim = domain.get("name_claim") + domain = self.domains.get_domain(domain["domain_id"]) + elif domain.get("current_name") == normalized: + claim = { + "normalized_name": normalized, + "namespace": "entity", + "conflict_state": "CLEAR", + "existing": True, + } + else: + claim = self.domains.claim_name(entity_id, domain["domain_id"], normalized) + domain = self.domains.get_domain(domain["domain_id"]) + return { + "entity_id": entity_id, + "display_name": manifest["display_name"], + "public_name": normalized + ".entity", + "domain_id": domain["domain_id"], + "name_claim": claim, + "alias_is_authority": False, + "entity_id_is_authority": True, + } + + def _discover_device_identity(self) -> str | None: + if self.device_path.is_file(): + try: + d = json.loads(self.device_path.read_text(encoding="utf-8")) + entity_id = str(d.get("device_entity_id") or "") + manifest = self.identity.load_manifest(entity_id) + if ( + d.get("schema") == "entity-wallet-device-record-v1" + and d.get("device_information_is_not_user_identity") is True + and manifest.get("entity_type") == "system" + ): + return entity_id + except Exception: + pass + for manifest in self.identity.list_local(): + meta = dict(manifest.get("metadata") or {}) + if meta.get("wallet_device_identity") is True: + return manifest["entity_id"] + return None + + def ensure_device_identity(self, preferred_existing_entity_id: str | None = None) -> dict: + if preferred_existing_entity_id: + manifest = self.identity.load_manifest(str(preferred_existing_entity_id)) + if manifest.get("entity_type") != "system": + raise ValueError("device identity must be a system Entity") + device_id = manifest["entity_id"] + created = False + else: + found = self._discover_device_identity() + if found: + manifest = self.identity.load_manifest(found) + device_id = found + created = False + else: + host = (socket.gethostname() or platform.node() or "ENTITY Device").strip() + install_id = str(uuid.uuid4()) + alias_seed = "device-" + install_id.split("-")[0] + manifest = self.identity.create( + f"{host} ENTITY Device", + "system", + aliases=[public_address(alias_seed)], + metadata={ + "entity_subtype": "wallet_device", + "wallet_device_identity": True, + "software_device_identity": True, + "hardware_attested": False, + "installation_id": install_id, + "platform_system": platform.system(), + "platform_release": platform.release(), + "machine": platform.machine(), + "device_information_is_not_user_identity": True, + "device_identity_does_not_establish_person_or_organization": True, + }, + ) + device_id = manifest["entity_id"] + created = True + record = { + "schema": "entity-wallet-device-record-v1", + "device_entity_id": device_id, + "display_name": manifest["display_name"], + "created_by_onboarding": created, + "device_information_is_not_user_identity": True, + "updated_at_ms": _now(), + } + self.device_path.write_text(json.dumps(record, indent=2, sort_keys=True), encoding="utf-8") + return record + + def _ensure_relationship( + self, + entity_id: str, + party_ref: str, + relationship_type: str, + *, + scope: dict, + evidence: dict, + ) -> dict: + for rel in self.authority.active_relationships(str(entity_id), str(party_ref)): + if rel["relationship_type"] == str(relationship_type).upper(): + existing_scope = dict(rel.get("scope") or {}) + if all(existing_scope.get(k) == v for k, v in scope.items()): + return rel + return self.authority.grant_relationship( + str(entity_id), + str(party_ref), + str(relationship_type), + scope=scope, + evidence_origin="ENTITY_ASSERTION", + evidence=evidence, + ) + + def _write_profile(self, body: dict) -> dict: + principal = str(body["principal_entity_id"]) + active = str(body["active_entity_id"]) + signatures = {"principal": self.identity.sign(principal, body)} + if active != principal: + signatures["active_entity"] = self.identity.sign(active, body) + profile = dict(body, signatures=signatures, profile_sha256=_sha(body)) + path = self.profiles / f"{body['profile_id']}.json" + path.write_text(json.dumps(profile, indent=2, sort_keys=True), encoding="utf-8") + self.active_path.write_text( + json.dumps( + {"schema": "entity-wallet-active-profile-v1", "profile_id": body["profile_id"], "updated_at_ms": _now()}, + indent=2, + sort_keys=True, + ), + encoding="utf-8", + ) + return profile + + def _build_profile( + self, + *, + principal_entity_id: str, + active_entity_id: str, + lineage_nodes: list[dict], + device_entity_id: str, + migrated_existing: bool, + ) -> dict: + body = { + "schema": SCHEMA, + "version": VERSION, + "profile_id": "wallet-profile-" + secrets.token_hex(12), + "principal_entity_id": str(principal_entity_id), + "active_entity_id": str(active_entity_id), + "device_entity_id": str(device_entity_id), + "lineage": lineage_nodes, + "display_path": " → ".join(node["public_name"] for node in lineage_nodes), + "login_policy": { + "mode": "DEVICE_BOUND_SIGNING_KEY", + "password_required": False, + "private_key_possession_required": True, + "device_binding_required": True, + "recovery_required_on_new_device": True, + }, + "canonical_authority_is_entity_id": True, + "public_name_is_alias_not_authority": True, + "device_identity_is_not_user_identity": True, + "protocol_origin_is_separate_from_user_lineage": True, + "migrated_existing": bool(migrated_existing), + "created_at_ms": _now(), + } + return self._write_profile(body) + + def create_profile( + self, + *, + principal_display_name: str, + principal_public_name: str, + principal_entity_type: str = "person", + organization_display_name: str | None = None, + organization_public_name: str | None = None, + organization_entity_type: str = "business", + operate_as_organization: bool = True, + ) -> dict: + paddr = public_address(principal_public_name) + principal = self.identity.create( + principal_display_name, + principal_entity_type, + aliases=[paddr], + metadata={ + "wallet_onboarding_created": True, + "public_entity_name": paddr, + "public_name_is_alias_not_authority": True, + }, + ) + pclaim = self.ensure_public_name(principal["entity_id"], principal_public_name) + lineage = [{ + "entity_id": principal["entity_id"], + "display_name": principal["display_name"], + "entity_type": principal["entity_type"], + "public_name": pclaim["public_name"], + "domain_id": pclaim["domain_id"], + "role": "ROOT_PRINCIPAL", + }] + active = principal["entity_id"] + + if organization_display_name and organization_public_name: + oaddr = public_address(organization_public_name) + org = self.identity.create( + organization_display_name, + organization_entity_type, + aliases=[oaddr], + metadata={ + "wallet_onboarding_created": True, + "public_entity_name": oaddr, + "public_name_is_alias_not_authority": True, + "lineage_parent_entity_id": principal["entity_id"], + }, + ) + oclaim = self.ensure_public_name(org["entity_id"], organization_public_name) + self._ensure_relationship( + org["entity_id"], + principal["entity_id"], + "SOVEREIGN_AUTHORITY", + scope={ + "wallet_lineage_parent": True, + "organization_control": True, + "asset_ownership_not_implied": True, + }, + evidence={"profile": SCHEMA}, + ) + lineage.append({ + "entity_id": org["entity_id"], + "display_name": org["display_name"], + "entity_type": org["entity_type"], + "public_name": oclaim["public_name"], + "domain_id": oclaim["domain_id"], + "role": "OPERATING_ENTITY", + }) + if operate_as_organization: + active = org["entity_id"] + + device = self.ensure_device_identity() + self._ensure_relationship( + principal["entity_id"], + device["device_entity_id"], + "POSSESSION", + scope={ + "wallet_device_bound": True, + "device_possession_not_identity": True, + "device_possession_not_ownership": True, + }, + evidence={"profile": SCHEMA}, + ) + return self._build_profile( + principal_entity_id=principal["entity_id"], + active_entity_id=active, + lineage_nodes=lineage, + device_entity_id=device["device_entity_id"], + migrated_existing=False, + ) + + def adopt_existing( + self, + *, + principal_entity_id: str, + active_entity_id: str | None = None, + device_entity_id: str | None = None, + ) -> dict: + principal = self.identity.load_manifest(str(principal_entity_id)) + active_id = str(active_entity_id or principal_entity_id) + active = self.identity.load_manifest(active_id) + paddr = self._alias_from_manifest(principal) + pclaim = self.ensure_public_name(principal["entity_id"], paddr) + lineage = [{ + "entity_id": principal["entity_id"], + "display_name": principal["display_name"], + "entity_type": principal["entity_type"], + "public_name": pclaim["public_name"], + "domain_id": pclaim["domain_id"], + "role": "ROOT_PRINCIPAL", + }] + if active_id != principal["entity_id"]: + aaddr = self._alias_from_manifest(active) + aclaim = self.ensure_public_name(active_id, aaddr) + self._ensure_relationship( + active_id, + principal["entity_id"], + "SOVEREIGN_AUTHORITY", + scope={ + "wallet_lineage_parent": True, + "organization_control": True, + "asset_ownership_not_implied": True, + }, + evidence={"profile": SCHEMA, "migration": True}, + ) + lineage.append({ + "entity_id": active_id, + "display_name": active["display_name"], + "entity_type": active["entity_type"], + "public_name": aclaim["public_name"], + "domain_id": aclaim["domain_id"], + "role": "OPERATING_ENTITY", + }) + device = self.ensure_device_identity(device_entity_id) + self._ensure_relationship( + principal["entity_id"], + device["device_entity_id"], + "POSSESSION", + scope={ + "wallet_device_bound": True, + "device_possession_not_identity": True, + "device_possession_not_ownership": True, + }, + evidence={"profile": SCHEMA, "migration": True}, + ) + return self._build_profile( + principal_entity_id=principal["entity_id"], + active_entity_id=active_id, + lineage_nodes=lineage, + device_entity_id=device["device_entity_id"], + migrated_existing=True, + ) + + def _verify_profile(self, profile: dict) -> bool: + try: + if profile.get("schema") != SCHEMA: + return False + body = {k: v for k, v in profile.items() if k not in {"signatures", "profile_sha256"}} + if profile.get("profile_sha256") != _sha(body): + return False + signatures = dict(profile.get("signatures") or {}) + principal = self.identity.load_manifest(str(body["principal_entity_id"])) + if not self.identity.verify_signature(principal, body, dict(signatures.get("principal") or {})): + return False + if body["active_entity_id"] != body["principal_entity_id"]: + active = self.identity.load_manifest(str(body["active_entity_id"])) + if not self.identity.verify_signature(active, body, dict(signatures.get("active_entity") or {})): + return False + self.identity.load_manifest(str(body["device_entity_id"])) + return True + except Exception: + return False + + def profile(self, profile_id: str) -> dict: + path = self.profiles / f"{profile_id}.json" + if not path.is_file(): + raise KeyError("wallet onboarding profile not found") + profile = json.loads(path.read_text(encoding="utf-8")) + if not self._verify_profile(profile): + raise RuntimeError("wallet onboarding profile verification failed") + return profile + + def list_profiles(self) -> list[dict]: + out = [] + for path in sorted(self.profiles.glob("wallet-profile-*.json")): + try: + out.append(self.profile(path.stem)) + except Exception: + continue + return out + + def active_profile(self) -> dict | None: + if not self.active_path.is_file(): + return None + try: + active = json.loads(self.active_path.read_text(encoding="utf-8")) + return self.profile(str(active["profile_id"])) + except Exception: + return None + + def profile_for_entity(self, entity_id: str) -> dict | None: + target = str(entity_id) + active = self.active_profile() + if active and active.get("active_entity_id") == target: + return active + for profile in self.list_profiles(): + if profile.get("active_entity_id") == target: + return profile + return None + + def set_active(self, profile_id: str) -> dict: + profile = self.profile(profile_id) + self.active_path.write_text( + json.dumps( + {"schema": "entity-wallet-active-profile-v1", "profile_id": profile_id, "updated_at_ms": _now()}, + indent=2, + sort_keys=True, + ), + encoding="utf-8", + ) + return profile + + def authenticate(self, profile: dict | None = None) -> dict: + profile = profile or self.active_profile() + if not profile or not self._verify_profile(profile): + return {"authenticated": False, "reason": "profile_missing_or_invalid"} + current_device = self._discover_device_identity() + if not current_device or current_device != profile["device_entity_id"]: + return { + "authenticated": False, + "reason": "device_binding_mismatch", + "recovery_or_rebind_required": True, + } + challenge = { + "schema": LOGIN_SCHEMA, + "profile_id": profile["profile_id"], + "active_entity_id": profile["active_entity_id"], + "device_entity_id": profile["device_entity_id"], + "nonce": secrets.token_hex(32), + "created_at_ms": _now(), + } + active_manifest = self.identity.load_manifest(profile["active_entity_id"]) + active_sig = self.identity.sign(profile["active_entity_id"], challenge) + if not self.identity.verify_signature(active_manifest, challenge, active_sig): + return {"authenticated": False, "reason": "active_identity_signature_failed"} + device_manifest = self.identity.load_manifest(profile["device_entity_id"]) + device_sig = self.identity.sign(profile["device_entity_id"], challenge) + if not self.identity.verify_signature(device_manifest, challenge, device_sig): + return {"authenticated": False, "reason": "device_signature_failed"} + return { + "authenticated": True, + "profile_id": profile["profile_id"], + "principal_entity_id": profile["principal_entity_id"], + "active_entity_id": profile["active_entity_id"], + "device_entity_id": profile["device_entity_id"], + "display_path": profile["display_path"], + "mode": "DEVICE_BOUND_SIGNING_KEY", + "password_used": False, + "entity_id_is_authority": True, + } + + def status(self) -> dict: + active = self.active_profile() + return { + "ready": True, + "schema": SCHEMA, + "version": VERSION, + "profile_count": len(self.list_profiles()), + "active_profile_id": active.get("profile_id") if active else None, + "first_run_required": active is None, + "device_identity_auto_bootstrap": True, + "device_information_creates_user_identity": False, + "public_name_is_alias_not_authority": True, + "login_mode": "DEVICE_BOUND_SIGNING_KEY", + } diff --git a/src/43_DCO_Factory/canonical_dco_factory.py b/src/43_DCO_Factory/canonical_dco_factory.py index d3d453b2..a4bdf9b8 100644 --- a/src/43_DCO_Factory/canonical_dco_factory.py +++ b/src/43_DCO_Factory/canonical_dco_factory.py @@ -62,6 +62,9 @@ def _init(self): issuance_id TEXT PRIMARY KEY,dco_id TEXT NOT NULL,plan_sha256 TEXT NOT NULL, physical_instruments INTEGER NOT NULL,total_right_units INTEGER NOT NULL, status TEXT NOT NULL,created_at_ms INTEGER NOT NULL); + CREATE TABLE IF NOT EXISTS issuance_withdrawals( + issuance_id TEXT PRIMARY KEY,dco_id TEXT NOT NULL,reason TEXT NOT NULL, + evidence_sha256 TEXT,created_at_ms INTEGER NOT NULL); """) @staticmethod @@ -225,13 +228,46 @@ def record_issuance(self,dco_id:str,plan:dict,status="QUALIFIED")->dict: body["total_right_units"],body["status"],body["created_at_ms"])) return body + def withdraw_issuance(self,issuance_id:str,reason:str,*,evidence_sha256:str|None=None)->dict: + issuance_id=str(issuance_id); reason=str(reason).strip() + if not reason: raise ValueError("withdrawal reason required") + evidence=require_sha256(evidence_sha256,"evidence_sha256") if evidence_sha256 is not None else None + with self._db(True) as db: + row=db.execute("SELECT * FROM issuance_runs WHERE issuance_id=?",(issuance_id,)).fetchone() + if not row: raise KeyError("issuance run not found") + prior=db.execute("SELECT * FROM issuance_withdrawals WHERE issuance_id=?",(issuance_id,)).fetchone() + if prior: + return {"schema":"entity-dco-issuance-withdrawal-v1",**dict(prior),"history_preserved":True} + body={"schema":"entity-dco-issuance-withdrawal-v1","issuance_id":issuance_id, + "dco_id":row["dco_id"],"reason":reason,"evidence_sha256":evidence, + "created_at_ms":now_ms(),"history_preserved":True} + db.execute("INSERT INTO issuance_withdrawals VALUES(?,?,?,?,?)",( + issuance_id,row["dco_id"],reason,evidence,body["created_at_ms"])) + return body + + def active_issuance_runs(self,dco_id:str|None=None)->list[dict]: + sql="""SELECT r.* FROM issuance_runs r + LEFT JOIN issuance_withdrawals w ON w.issuance_id=r.issuance_id + WHERE r.status='QUALIFIED' AND w.issuance_id IS NULL""" + args=() + if dco_id is not None: + sql+=" AND r.dco_id=?"; args=(str(dco_id),) + sql+=" ORDER BY r.created_at_ms,r.issuance_id" + with self._db() as db: return [dict(x) for x in db.execute(sql,args)] + def portfolio_summary(self)->dict: with self._db() as db: total=int(db.execute("SELECT COUNT(*) FROM dco_masters").fetchone()[0]) active=int(db.execute("SELECT COUNT(*) FROM dco_masters WHERE lifecycle LIKE 'ACTIVE%'").fetchone()[0]) families={r["family"]:int(r["n"]) for r in db.execute("SELECT family,COUNT(*) n FROM dco_masters GROUP BY family")} - issued=db.execute("SELECT COALESCE(SUM(physical_instruments),0) i,COALESCE(SUM(total_right_units),0) u FROM issuance_runs WHERE status='QUALIFIED'").fetchone() + issued=db.execute("""SELECT COALESCE(SUM(r.physical_instruments),0) i, + COALESCE(SUM(r.total_right_units),0) u + FROM issuance_runs r + LEFT JOIN issuance_withdrawals w ON w.issuance_id=r.issuance_id + WHERE r.status='QUALIFIED' AND w.issuance_id IS NULL""").fetchone() + withdrawn=int(db.execute("SELECT COUNT(*) FROM issuance_withdrawals").fetchone()[0]) return {"schema":"entity-dco-factory-portfolio-summary-v1","total_dcos":total,"active_dcos":active, "families":families,"qualified_physical_instruments":int(issued["i"]), - "qualified_right_units":int(issued["u"]),"factory_version":FACTORY_VERSION, + "qualified_right_units":int(issued["u"]),"withdrawn_issuance_runs":withdrawn, + "factory_version":FACTORY_VERSION, "target_design_capacity_dcos":10000,"protocol_change_required":False} diff --git a/src/43_DCO_Factory/dco_production_pipeline.py b/src/43_DCO_Factory/dco_production_pipeline.py index 2d00765b..2689eb54 100644 --- a/src/43_DCO_Factory/dco_production_pipeline.py +++ b/src/43_DCO_Factory/dco_production_pipeline.py @@ -15,10 +15,9 @@ "RIGHTS_PASSPORT_ISSUED", "GLOBAL_PASSPORT_ISSUED", "PROFILE_BTDU_BOUND", - "ECONOMIC_INSTRUMENTS_ISSUED", "PUBLIC_SAFE_EVIDENCE_PUBLISHED", ) -OPTIONAL_STAGES=("BRIDGE_EXPOSURE_BOUND",) +OPTIONAL_STAGES=("ECONOMIC_INSTRUMENTS_ISSUED","BRIDGE_EXPOSURE_BOUND") DEPENDENCY={ "ASSET_BUILT":None, "ASSET_QUALIFIED":"ASSET_BUILT", @@ -28,8 +27,8 @@ "GLOBAL_PASSPORT_ISSUED":"RIGHTS_PASSPORT_ISSUED", "PROFILE_BTDU_BOUND":"GLOBAL_PASSPORT_ISSUED", "ECONOMIC_INSTRUMENTS_ISSUED":"PROFILE_BTDU_BOUND", - "BRIDGE_EXPOSURE_BOUND":"ECONOMIC_INSTRUMENTS_ISSUED", - "PUBLIC_SAFE_EVIDENCE_PUBLISHED":"ECONOMIC_INSTRUMENTS_ISSUED", + "BRIDGE_EXPOSURE_BOUND":"PROFILE_BTDU_BOUND", + "PUBLIC_SAFE_EVIDENCE_PUBLISHED":"PROFILE_BTDU_BOUND", } FORBIDDEN_PUBLIC_KEYS={ "object_id","instrument_id","instrument_ids","venue_id","wallet_id","wallet_ids", @@ -53,10 +52,12 @@ def sha256_hex(v:str,name:str)->str: return s class DCOProductionPipeline: - """Factory-side issuance gate for real DCO assets. + """Asset-first production gate for real DCO assets. - This is an application-layer state machine. It does not replace canonical ENTITY - object, passport, BTDU, EEP, wallet or settlement state. + Registration, qualification and passporting establish the digital asset. + Economic-instrument issuance is optional and controller-directed; it is never + an automatic consequence of ingest. This application layer does not replace + canonical ENTITY object, passport, BTDU, EEP, wallet or settlement state. """ def __init__(self,state_dir:str|Path): self.root=Path(state_dir)/"dco_factory" diff --git a/src/45_ENTITY_Market/canonical_market_registry.py b/src/45_ENTITY_Market/canonical_market_registry.py new file mode 100644 index 00000000..6360b2ee --- /dev/null +++ b/src/45_ENTITY_Market/canonical_market_registry.py @@ -0,0 +1,830 @@ +from __future__ import annotations +from contextlib import contextmanager +from pathlib import Path +from typing import Any +import hashlib, json, re, sqlite3, textwrap, time + +MARKET_SCHEMA="entity-economic-market-registry-v1" +MARKET_VERSION="1.1.0" + +def now_ms()->int: return int(time.time()*1000) +def canon(v:Any)->bytes: return json.dumps(v,sort_keys=True,separators=(",",":"),ensure_ascii=False,default=str).encode() +def sha(v:Any)->str: return hashlib.sha256(v if isinstance(v,(bytes,bytearray)) else canon(v)).hexdigest() + +def _token(v:str)->str: + s=re.sub(r"[^A-Z0-9]+","-",str(v or "").upper()).strip("-") + if not s: raise ValueError("non-empty canonical token required") + return s + +def _enum(v:str)->str: + s=str(v or "").upper().strip() + if not s or re.search(r"[^A-Z0-9_-]",s): raise ValueError("invalid canonical enum") + return s + +def _safe_symbol(v:str)->str: + s=re.sub(r"[^A-Z0-9._-]+","-",str(v or "").upper()).strip("-._") + if not s or len(s)>32: raise ValueError("display symbol must be 1-32 canonical characters") + return s + +def _namespace_candidate(manifest:dict)->str: + explicit=str(manifest.get("market_namespace") or "").strip() + if explicit: return _token(explicit)[:20] + aliases=[str(x) for x in (manifest.get("aliases") or []) if str(x)] + for alias in sorted(aliases,key=len): + first=alias.split(".",1)[0] + try: + candidate=_token(first) + if 2<=len(candidate)<=20: return candidate + except ValueError: pass + name=str(manifest.get("display_name") or "") + words=[w for w in re.findall(r"[A-Za-z0-9]+",name) + if w.upper() not in {"LIMITED","LTD","INC","INCORPORATED","CORP","CORPORATION","COMPANY","CO","LLC"}] + if len(words)>=2: + acr="".join(w[0] for w in words).upper() + if 2<=len(acr)<=12: return acr + compact="".join(words).upper() + if compact: return compact[:20] + return "ENTITY-"+str(manifest["entity_id"])[-8:].upper() + +def canonical_instrument_id(issuer_entity_id:str,underlying_dco_id:str,rights_class:str,series:int)->str: + s=int(series) + if s<1: raise ValueError("series must be positive") + return f"entity.instrument:v1:{issuer_entity_id}:{underlying_dco_id}:{_token(rights_class).lower()}:{s:06d}" + +def canonical_listing_id(venue_id:str,instrument_id:str,series:int)->str: + s=int(series) + if s<1: raise ValueError("listing series must be positive") + digest=sha({"venue_id":str(venue_id),"instrument_id":str(instrument_id)})[:24] + return f"entity.listing:v1:{venue_id}:{digest}:{s:06d}" + +def _simple_pdf(text:str,path:Path)->None: + """Dependency-free human-readable PDF for portable instrument packages.""" + lines=[] + for raw in text.splitlines(): + if not raw.strip(): lines.append(""); continue + lines.extend(textwrap.wrap(raw,92,replace_whitespace=False,drop_whitespace=False) or [""]) + pages=[lines[i:i+52] for i in range(0,len(lines),52)] or [[]] + objs=[] + font_id=3+len(pages)*2 + kids=[] + for idx,page in enumerate(pages): + page_id=3+idx*2; content_id=page_id+1; kids.append(f"{page_id} 0 R") + stream=["BT","/F1 9 Tf","48 760 Td","12 TL"] + first=True + for line in page: + esc=line.replace("\\","\\\\").replace("(","\\(").replace(")","\\)") + if first: stream.append(f"({esc}) Tj"); first=False + else: stream.append(f"T* ({esc}) Tj") + stream.append("ET") + data="\n".join(stream).encode("latin-1","replace") + objs.append((page_id,f"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 {font_id} 0 R >> >> /Contents {content_id} 0 R >>".encode())) + objs.append((content_id,b"<< /Length "+str(len(data)).encode()+b" >>\nstream\n"+data+b"\nendstream")) + objs.extend([ + (1,b"<< /Type /Catalog /Pages 2 0 R >>"), + (2,f"<< /Type /Pages /Kids [{' '.join(kids)}] /Count {len(pages)} >>".encode()), + (font_id,b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>"), + ]) + objs=sorted(objs,key=lambda x:x[0]); buf=bytearray(b"%PDF-1.4\n"); offsets={0:0} + for oid,data in objs: + offsets[oid]=len(buf); buf.extend(f"{oid} 0 obj\n".encode()); buf.extend(data); buf.extend(b"\nendobj\n") + xref=len(buf); max_id=max(offsets) + buf.extend(f"xref\n0 {max_id+1}\n".encode()); buf.extend(b"0000000000 65535 f \n") + for i in range(1,max_id+1): buf.extend(f"{offsets.get(i,0):010d} 00000 n \n".encode()) + buf.extend(f"trailer\n<< /Size {max_id+1} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n".encode()) + path.write_bytes(buf) + +class EntityEconomicMarketRegistry: + """Universal multi-issuer market registry above the EEP execution engine. + + ENTITY owns protocol/registry rules, never listed assets. Any verified ENTITY + controller may issue instruments over its own DCOs under the same rules. + """ + + def __init__(self,state_dir:str|Path,identity,fabric,exchange,rights_passports,global_passports): + self.state=Path(state_dir); self.identity=identity; self.fabric=fabric; self.exchange=exchange + self.rights=rights_passports; self.global_passports=global_passports + self.path=self.state/"entity_market_registry.sqlite" + self._init() + + @contextmanager + def _db(self): + db=sqlite3.connect(self.path); db.row_factory=sqlite3.Row + try: + yield db; db.commit() + except Exception: + db.rollback(); raise + finally: db.close() + + def _init(self): + with self._db() as db: + db.executescript(""" + CREATE TABLE IF NOT EXISTS issuer_namespaces( + namespace TEXT PRIMARY KEY, issuer_entity_id TEXT NOT NULL UNIQUE, + display_name TEXT NOT NULL, created_at_ms INTEGER NOT NULL, + signature_json TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS instrument_packages( + instrument_id TEXT PRIMARY KEY, issuer_entity_id TEXT NOT NULL, + issuer_namespace TEXT NOT NULL, underlying_dco_id TEXT NOT NULL, + instrument_name TEXT NOT NULL, display_symbol TEXT NOT NULL, + market_identifier TEXT NOT NULL UNIQUE, instrument_class TEXT NOT NULL, + rights_class TEXT NOT NULL, series INTEGER NOT NULL, + fungibility TEXT NOT NULL, divisibility INTEGER NOT NULL, supply INTEGER NOT NULL, + rights_passport_id TEXT NOT NULL, global_passport_id TEXT NOT NULL, + jurisdiction TEXT NOT NULL, transfer_rules_json TEXT NOT NULL, + economic_terms_json TEXT NOT NULL, royalty_terms_json TEXT NOT NULL, + buyer_receives_json TEXT NOT NULL, buyer_does_not_receive_json TEXT NOT NULL, + evidence_refs_json TEXT NOT NULL, status TEXT NOT NULL, + created_at_ms INTEGER NOT NULL, signature_json TEXT NOT NULL, + UNIQUE(issuer_entity_id,underlying_dco_id,rights_class,series), + UNIQUE(issuer_entity_id,display_symbol)); + CREATE TABLE IF NOT EXISTS listing_packages( + listing_id TEXT PRIMARY KEY, instrument_id TEXT NOT NULL, + venue_id TEXT NOT NULL, market_id TEXT NOT NULL, + display_symbol TEXT NOT NULL, quote_unit TEXT NOT NULL, + trade_mode TEXT NOT NULL, settlement_method TEXT NOT NULL, + minimum_quantity INTEGER NOT NULL, quantity_precision INTEGER NOT NULL, + price_precision INTEGER NOT NULL, pricing_method TEXT NOT NULL, + listing_information_sha256 TEXT NOT NULL, machine_manifest_sha256 TEXT NOT NULL, + status TEXT NOT NULL, listed_at_ms INTEGER NOT NULL, + signature_json TEXT NOT NULL, + UNIQUE(venue_id,instrument_id,listing_id)); + CREATE TABLE IF NOT EXISTS listing_information( + listing_id TEXT PRIMARY KEY, information_json TEXT NOT NULL, + information_markdown TEXT NOT NULL, created_at_ms INTEGER NOT NULL); + CREATE TABLE IF NOT EXISTS asset_disclosures( + disclosure_id TEXT PRIMARY KEY, underlying_dco_id TEXT NOT NULL, + issuer_entity_id TEXT NOT NULL, version INTEGER NOT NULL, + disclosure_sha256 TEXT NOT NULL, disclosure_json TEXT NOT NULL, + status TEXT NOT NULL, created_at_ms INTEGER NOT NULL, + signature_json TEXT NOT NULL, + UNIQUE(underlying_dco_id,version)); + CREATE INDEX IF NOT EXISTS idx_market_symbol ON instrument_packages(issuer_namespace,display_symbol); + CREATE INDEX IF NOT EXISTS idx_asset_disclosure_dco ON asset_disclosures(underlying_dco_id,version); + """) + + def suggest_namespace(self,issuer_entity_id:str)->dict: + issuer=str(issuer_entity_id); manifest=self.identity.load_manifest(issuer) + base=_namespace_candidate(manifest) + if len(base)>20: base=base[:20] + candidate=base + with self._db() as db: + existing=db.execute("SELECT namespace FROM issuer_namespaces WHERE issuer_entity_id=?",(issuer,)).fetchone() + if existing: + return {"namespace":existing["namespace"],"registered":True,"available":True} + n=1 + while True: + conflict=db.execute("SELECT issuer_entity_id FROM issuer_namespaces WHERE namespace=?",(candidate,)).fetchone() + if not conflict: + return {"namespace":candidate,"registered":False,"available":True} + n+=1; suffix="-"+str(n); candidate=base[:20-len(suffix)]+suffix + + def ensure_namespace(self,issuer_entity_id:str,preferred:str|None=None)->dict: + issuer=str(issuer_entity_id); manifest=self.identity.load_manifest(issuer) + with self._db() as db: + prior=db.execute("SELECT * FROM issuer_namespaces WHERE issuer_entity_id=?",(issuer,)).fetchone() + if prior: return dict(prior) | {"signature":json.loads(prior["signature_json"])} + base=_token(preferred) if preferred else _namespace_candidate(manifest) + if len(base)>20: base=base[:20] + candidate=base + with self._db() as db: + n=1 + while True: + conflict=db.execute("SELECT issuer_entity_id FROM issuer_namespaces WHERE namespace=?",(candidate,)).fetchone() + if not conflict or conflict["issuer_entity_id"]==issuer: break + n+=1; suffix="-"+str(n); candidate=base[:20-len(suffix)]+suffix + body={"schema":"entity-market-issuer-namespace-v1","namespace":candidate, + "issuer_entity_id":issuer,"display_name":str(manifest.get("display_name") or issuer), + "created_at_ms":now_ms(),"namespace_is_alias_not_authority":True} + sig=self.identity.sign(issuer,body) + db.execute("INSERT INTO issuer_namespaces VALUES(?,?,?,?,?)",( + candidate,issuer,body["display_name"],body["created_at_ms"],json.dumps(sig,sort_keys=True))) + return dict(body,signature=sig) + + def publish_asset_disclosure(self,issuer_entity_id:str,underlying_dco_id:str,*,description:str, + purpose:str="",capabilities:list[str]|None=None, + contents:list[str]|None=None,intended_uses:list[str]|None=None, + limitations:list[str]|None=None,dependencies:list[str]|None=None, + validation_notes:list[str]|None=None,release_notes:str="", + source_refs:list[str]|None=None,version:int|None=None)->dict: + issuer=str(issuer_entity_id); dco=str(underlying_dco_id) + asset=self.fabric.get_object(dco) + if asset["controller_entity_id"]!=issuer: + raise PermissionError("only the controlled DCO issuer may publish an asset disclosure") + text=str(description or "").strip() + if len(text)<20: + raise ValueError("asset description must provide meaningful buyer-facing detail") + with self._db() as db: + if version is None: + row=db.execute("SELECT COALESCE(MAX(version),0)+1 AS v FROM asset_disclosures WHERE underlying_dco_id=?",(dco,)).fetchone() + version=int(row["v"]) + else: + version=int(version) + if version<1: raise ValueError("asset disclosure version must be positive") + def clean(items): + return [str(x).strip() for x in (items or []) if str(x).strip()] + disclosure={ + "schema":"entity-asset-disclosure-v1", + "underlying_dco_id":dco, + "issuer_entity_id":issuer, + "asset_title":asset["title"], + "version":version, + "description":text, + "purpose":str(purpose or "").strip(), + "capabilities":clean(capabilities), + "contents":clean(contents), + "intended_uses":clean(intended_uses), + "limitations":clean(limitations), + "dependencies":clean(dependencies), + "validation_notes":clean(validation_notes), + "release_notes":str(release_notes or "").strip(), + "source_refs":sorted(set(clean(source_refs))), + "issuer_statement_not_protocol_truth":True, + "does_not_expand_rights_passport":True, + "created_at_ms":now_ms(), + } + disclosure_hash=sha(disclosure) + disclosure_id=f"asset-disclosure:v1:{sha({'dco':dco,'version':version,'hash':disclosure_hash})[:24]}" + body={**disclosure,"disclosure_id":disclosure_id,"disclosure_sha256":disclosure_hash} + sig=self.identity.sign(issuer,body) + with self._db() as db: + db.execute("""INSERT INTO asset_disclosures + (disclosure_id,underlying_dco_id,issuer_entity_id,version,disclosure_sha256, + disclosure_json,status,created_at_ms,signature_json) + VALUES(?,?,?,?,?,?,?,?,?)""",( + disclosure_id,dco,issuer,version,disclosure_hash,json.dumps(disclosure,sort_keys=True), + "ACTIVE",disclosure["created_at_ms"],json.dumps(sig,sort_keys=True))) + return dict(body,signature=sig) + + def latest_asset_disclosure(self,underlying_dco_id:str)->dict|None: + dco=str(underlying_dco_id) + with self._db() as db: + row=db.execute("""SELECT * FROM asset_disclosures + WHERE underlying_dco_id=? AND status='ACTIVE' + ORDER BY version DESC,created_at_ms DESC LIMIT 1""",(dco,)).fetchone() + if not row: return None + disclosure=json.loads(row["disclosure_json"]) + return {**disclosure,"disclosure_id":row["disclosure_id"], + "disclosure_sha256":row["disclosure_sha256"], + "signature":json.loads(row["signature_json"])} + + def _provenance_snapshot(self,underlying_dco_id:str)->list[dict]: + dco=str(underlying_dco_id); out=[] + path=getattr(self.fabric,"path",None) + if not path or not Path(path).exists(): return out + db=sqlite3.connect(path); db.row_factory=sqlite3.Row + try: + rows=db.execute("""SELECT p.edge_id,p.parent_object_id,p.relation,p.contribution_bps, + p.evidence_json,o.title,o.controller_entity_id + FROM provenance_edges p + LEFT JOIN objects o ON o.object_id=p.parent_object_id + WHERE p.child_object_id=? + ORDER BY p.created_at_ms,p.edge_id""",(dco,)).fetchall() + for r in rows: + out.append({"edge_id":r["edge_id"],"parent_object_id":r["parent_object_id"], + "parent_title":r["title"],"parent_controller_entity_id":r["controller_entity_id"], + "relation":r["relation"],"contribution_bps":int(r["contribution_bps"]), + "evidence":json.loads(r["evidence_json"] or "{}")}) + finally: db.close() + return out + + def _asset_dossier(self,instrument:dict,asset:dict)->dict: + descriptor=dict(asset.get("descriptor") or {}) + metadata=dict(descriptor.get("metadata") or {}) + gp=self.global_passports.get(instrument["global_passport_id"]) + rp=self.rights.get(instrument["rights_passport_id"]) + disclosure=self.latest_asset_disclosure(asset["object_id"]) + industry=dict(gp.get("industry_context") or {}) + profiles=dict(gp.get("profile_stack") or {}) + btdu=dict(gp.get("btdu_binding") or {}) + origin=dict(gp.get("protocol_origin") or {}) + evidence=sorted(set( + [str(x) for x in (gp.get("evidence_refs") or [])] + + [str(x) for x in (instrument.get("evidence_refs") or [])] + )) + facts={ + "asset_title":asset.get("title"), + "dco_id":asset.get("object_id"), + "dco_code":metadata.get("dco_code") or industry.get("dco_code"), + "short_name":metadata.get("short_name") or industry.get("asset"), + "version":metadata.get("version") or gp.get("version"), + "object_type":asset.get("object_type"), + "commodity_class":descriptor.get("commodity_class"), + "measurement_unit":descriptor.get("measurement_unit"), + "content_sha256":asset.get("content_sha256"), + "controller_entity_id":asset.get("controller_entity_id"), + "status":asset.get("status"), + "created_at_ms":asset.get("created_at_ms"), + "jurisdictions":list(gp.get("jurisdiction_profile_refs") or []), + } + auto_summary=(f"{facts['asset_title']} is a registered ENTITY Digital Commodity Object " + f"of type {facts['object_type']} in the {facts['commodity_class']} commodity class.") + if facts.get("version"): auto_summary+=f" Recorded asset version: {facts['version']}." + if facts.get("dco_code"): auto_summary+=f" DCO code: {facts['dco_code']}." + return { + "schema":"entity-asset-dossier-v1", + "facts":facts, + "issuer_disclosure":disclosure, + "canonical_summary":auto_summary, + "asset_metadata":metadata, + "industry_context":industry, + "profile_refs":list(profiles.get("profile_refs") or []), + "standards_mappings":list(gp.get("standards_mappings") or []), + "btdu_binding":{ + "bound":bool(btdu), + "btdu_version":btdu.get("btdu_version"), + "object_ref":btdu.get("object_ref"), + "binding_mode":btdu.get("binding_mode"), + "topology_does_not_create_ownership":btdu.get("topology_does_not_create_ownership",True), + }, + "protocol_origin":{ + "origin_lineage_id":origin.get("origin_lineage_id"), + "release_ref":origin.get("release_ref"), + "protocol_origin_is_not_asset_provenance":True, + "protocol_origin_does_not_transfer_user_asset_ownership":True, + }, + "provenance":self._provenance_snapshot(asset["object_id"]), + "provenance_refs":list(gp.get("provenance_refs") or []), + "evidence_refs":evidence, + "rights_passport_summary":{ + "passport_id":rp.get("passport_id"), + "rights":list(rp.get("rights") or []), + }, + "validation_and_maturity":{ + k:metadata.get(k) for k in ( + "qualification_pass","physical_robot_validation","commercial_quality_claimed", + "synthetic","external_sale_enabled","raw_data_transfer_default", + "registration_revision" + ) if k in metadata + }, + "dossier_is_disclosure_not_independent_certification":True, + } + + def _verify_passports(self,issuer:str,dco_id:str,rights_passport_id:str,global_passport_id:str)->tuple[dict,dict]: + rp=self.rights.get(str(rights_passport_id)); gp=self.global_passports.get(str(global_passport_id)) + if rp["object_id"]!=dco_id or rp["controller_entity_id"]!=issuer: + raise PermissionError("Rights Passport must bind issuer-controlled underlying DCO") + if gp["object_id"]!=dco_id or gp["controller_entity_id"]!=issuer: + raise PermissionError("Global Passport must bind issuer-controlled underlying DCO") + if gp["rights_passport_id"]!=rp["passport_id"]: + raise ValueError("Global Passport does not bind supplied Rights Passport") + if not self.rights.verify(rp).get("valid"): raise ValueError("Rights Passport verification failed") + if not self.global_passports.verify(gp).get("valid"): raise ValueError("Global Passport verification failed") + return rp,gp + + def create_instrument(self,issuer_entity_id:str,underlying_dco_id:str,*,instrument_name:str, + display_symbol:str,instrument_class:str,rights_class:str,rights:dict, + supply:int,rights_passport_id:str,global_passport_id:str, + settlement_currency:str,jurisdiction:str,series:int=1, + fungibility:str="FUNGIBLE",divisibility:int=0,transferable:bool=False, + duration_ms:int|None=None,delivery_mode:str="ENTITLEMENT", + transfer_rules:dict|None=None,economic_terms:dict|None=None, + royalty_terms:dict|None=None,buyer_receives:list[str]|None=None, + buyer_does_not_receive:list[str]|None=None,evidence_refs:list[str]|None=None, + namespace:str|None=None)->dict: + issuer=str(issuer_entity_id); dco=str(underlying_dco_id) + obj=self.fabric.get_object(dco) + if obj["controller_entity_id"]!=issuer: + raise PermissionError("only the controlled DCO issuer may create an instrument") + if not (obj.get("descriptor") or {}).get("digital_commodity"): + raise ValueError("underlying object must be a Digital Commodity Object") + rp,gp=self._verify_passports(issuer,dco,rights_passport_id,global_passport_id) + ns=self.ensure_namespace(issuer,namespace) + symbol=_safe_symbol(display_symbol); rc=_token(rights_class); series=int(series) + instrument_id=canonical_instrument_id(issuer,dco,rc,series) + market_identifier=f"{ns['namespace']}:{symbol}" + supply=int(supply); div=int(divisibility) + if supply<1: raise ValueError("positive supply required") + if div<0 or div>9: raise ValueError("divisibility must be between 0 and 9") + fung=_token(fungibility) + if fung not in {"FUNGIBLE","NON-FUNGIBLE","SERIES-FUNGIBLE"}: raise ValueError("unsupported fungibility") + actions=sorted({_token(a) for a in (rights.get("actions") or [])}) + if not actions: raise ValueError("instrument rights actions required") + allowed=set(); prohibited=set() + for rule in rp.get("rights") or []: + effect=str(rule.get("effect") or "").upper() + rule_actions={_token(a) for a in (rule.get("actions") or [])} + if effect=="ALLOW": allowed.update(rule_actions) + elif effect=="PROHIBIT": prohibited.update(rule_actions) + requested=set(actions) + if requested & prohibited: + raise PermissionError("instrument requests action prohibited by Rights Passport") + if not requested.issubset(allowed): + raise PermissionError("instrument actions exceed Rights Passport") + rights_doc={**dict(rights),"actions":actions, + "rights_passport_id":rp["passport_id"],"global_passport_id":gp["passport_id"], + "rights_class":rc,"market_identifier":market_identifier} + eep_body={"schema":"entity-eep-instrument-v1","instrument_id":instrument_id,"issuer":issuer, + "underlying_object_id":dco,"instrument_class":_enum(instrument_class), + "rights":rights_doc,"total_units":supply,"transferable":bool(transferable), + "duration_ms":int(duration_ms) if duration_ms is not None else None, + "settlement_currency":_enum(settlement_currency),"delivery_mode":_enum(delivery_mode), + "status":"ACTIVE","created_at_ms":now_ms(),"bytes_are_not_the_traded_scarcity":True} + sig=self.identity.sign(issuer,eep_body) + self.exchange.submit_signed_instrument(eep_body,sig) + package={"schema":"entity-economic-instrument-package-v1","instrument_id":instrument_id, + "issuer_entity_id":issuer,"issuer_namespace":ns["namespace"],"underlying_dco_id":dco, + "instrument_name":str(instrument_name),"display_symbol":symbol, + "market_identifier":market_identifier,"instrument_class":eep_body["instrument_class"], + "rights_class":rc,"series":series,"fungibility":fung,"divisibility":div,"supply":supply, + "rights_passport_id":rp["passport_id"],"global_passport_id":gp["passport_id"], + "jurisdiction":str(jurisdiction).upper(),"transfer_rules":dict(transfer_rules or {}), + "economic_terms":dict(economic_terms or {}),"royalty_terms":dict(royalty_terms or {}), + "buyer_receives":[str(x) for x in (buyer_receives or [])], + "buyer_does_not_receive":[str(x) for x in (buyer_does_not_receive or [])], + "evidence_refs":sorted({str(x) for x in (evidence_refs or [])}), + "status":"ACTIVE","created_at_ms":eep_body["created_at_ms"], + "entity_owns_protocol_not_asset":True,"namespace_is_alias_not_authority":True, + "canonical_identity_is_instrument_id":True} + psig=self.identity.sign(issuer,package) + with self._db() as db: + db.execute("""INSERT INTO instrument_packages VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",( + instrument_id,issuer,ns["namespace"],dco,package["instrument_name"],symbol,market_identifier, + package["instrument_class"],rc,series,fung,div,supply,rp["passport_id"],gp["passport_id"], + package["jurisdiction"],json.dumps(package["transfer_rules"],sort_keys=True), + json.dumps(package["economic_terms"],sort_keys=True),json.dumps(package["royalty_terms"],sort_keys=True), + json.dumps(package["buyer_receives"],sort_keys=True),json.dumps(package["buyer_does_not_receive"],sort_keys=True), + json.dumps(package["evidence_refs"],sort_keys=True),"ACTIVE",package["created_at_ms"],json.dumps(psig,sort_keys=True))) + return dict(package,signature=psig,eep_instrument=dict(eep_body,signature=sig)) + + def instrument(self,instrument_id:str)->dict: + iid=str(instrument_id) + with self._db() as db: + r=db.execute("SELECT * FROM instrument_packages WHERE instrument_id=?",(iid,)).fetchone() + if not r: raise KeyError("instrument package not found") + d=dict(r) + for src,dst in [("transfer_rules_json","transfer_rules"),("economic_terms_json","economic_terms"), + ("royalty_terms_json","royalty_terms"),("buyer_receives_json","buyer_receives"), + ("buyer_does_not_receive_json","buyer_does_not_receive"),("evidence_refs_json","evidence_refs")]: + d[dst]=json.loads(d.pop(src) or ("[]" if "buyer_" in dst or dst=="evidence_refs" else "{}")) + d["signature"]=json.loads(d.pop("signature_json")) + with self.exchange._db() as db: + eep=db.execute("""SELECT rights_json,settlement_currency,delivery_mode,transferable,duration_ms,status + FROM instruments WHERE instrument_id=?""",(iid,)).fetchone() + if not eep: + raise KeyError("canonical EEP instrument record not found") + d["rights"]=json.loads(eep["rights_json"]) + d["settlement_currency"]=eep["settlement_currency"] + d["delivery_mode"]=eep["delivery_mode"] + d["transferable"]=bool(eep["transferable"]) + d["duration_ms"]=eep["duration_ms"] + d["eep_status"]=eep["status"] + return d + + def resolve_symbol(self,issuer_namespace:str,display_symbol:str)->dict: + ns=_token(issuer_namespace); sym=_safe_symbol(display_symbol) + with self._db() as db: + r=db.execute("""SELECT instrument_id FROM instrument_packages + WHERE issuer_namespace=? AND display_symbol=? AND status='ACTIVE'""",(ns,sym)).fetchone() + if not r: raise KeyError("active market identifier not found") + return self.instrument(r["instrument_id"]) + + def _information(self,instrument:dict,listing:dict,asset:dict,issuer_manifest:dict)->tuple[dict,str]: + dossier=self._asset_dossier(instrument,asset) + disclosure=dossier.get("issuer_disclosure") or {} + description=str(disclosure.get("description") or dossier["canonical_summary"]) + info={ + "schema":"entity-listing-information-sheet-v2", + "what_is_this":( + f"{instrument['market_identifier']} is an economic instrument representing " + f"{instrument['rights_class']} rights associated with the underlying DCO " + f"{asset['title']}." + ), + "instrument_name":instrument["instrument_name"],"display_symbol":instrument["display_symbol"], + "market_identifier":instrument["market_identifier"],"instrument_id":instrument["instrument_id"], + "issuer":str(issuer_manifest.get("display_name") or instrument["issuer_entity_id"]), + "issuer_entity_id":instrument["issuer_entity_id"],"underlying_dco_id":instrument["underlying_dco_id"], + "asset_name":asset["title"],"asset_type":asset["object_type"], + "asset_description":description,"asset_dossier":dossier, + "asset_disclosure_id":disclosure.get("disclosure_id"), + "asset_disclosure_sha256":disclosure.get("disclosure_sha256"), + "asset_dossier_sha256":sha(dossier), + "instrument_class":instrument["instrument_class"],"rights_class":instrument["rights_class"], + "buyer_receives":instrument["buyer_receives"], + "buyer_does_not_receive":instrument["buyer_does_not_receive"], + "supply":instrument["supply"],"divisibility":instrument["divisibility"], + "fungibility":instrument["fungibility"],"jurisdiction":instrument["jurisdiction"], + "transfer_rules":instrument["transfer_rules"],"economic_terms":instrument["economic_terms"], + "royalty_terms":instrument["royalty_terms"],"rights_passport_id":instrument["rights_passport_id"], + "global_passport_id":instrument["global_passport_id"],"evidence_refs":dossier["evidence_refs"], + "listing_id":listing["listing_id"],"venue_id":listing["venue_id"],"market_id":listing["market_id"], + "quote_unit":listing["quote_unit"],"trade_mode":listing["trade_mode"], + "settlement_method":listing["settlement_method"],"minimum_quantity":listing["minimum_quantity"], + "quantity_precision":listing["quantity_precision"],"price_precision":listing["price_precision"], + "pricing_method":listing["pricing_method"],"status":listing["status"], + "instrument_created_at_ms":instrument["created_at_ms"],"listed_at_ms":listing["listed_at_ms"], + "sheet_does_not_replace_rights_passport":True, + "asset_disclosure_is_issuer_statement_not_protocol_truth":True, + "external_publication_does_not_create_new_instrument":True, + "observed_market_price_is_not_intrinsic_asset_value":True, + "observed_market_price_is_not_accounting_fair_value":True, + } + facts=dossier["facts"]; meta=dossier["asset_metadata"]; industry=dossier["industry_context"] + validation=dossier["validation_and_maturity"]; prov=dossier["provenance"] + def bullet(items,empty="- None stated."): + vals=[str(x).strip() for x in (items or []) if str(x).strip()] + return "\n".join("- "+x for x in vals) if vals else empty + def flag(v): + if isinstance(v,bool): return "Yes" if v else "No" + if v is None or v=="": return "—" + if isinstance(v,(dict,list)): return json.dumps(v,sort_keys=True,ensure_ascii=False) + return str(v) + def kv(mapping): + return "\n".join(f"- {str(k).replace('_',' ').title()}: {flag(v)}" + for k,v in sorted((mapping or {}).items()) if v is not None) or "- None recorded." + provenance_md="\n".join( + f"- {x['relation']}: **{x.get('parent_title') or x['parent_object_id']}** " + f"({x['parent_object_id']}), contribution {x['contribution_bps']} bps" + for x in prov + ) or "- No parent provenance edges recorded." + standards_md="\n".join( + f"- {x.get('standard','—')} — {x.get('role','mapping')} " + f"(normative equivalence claimed: {'yes' if x.get('normative_equivalence_claimed') else 'no'})" + for x in dossier["standards_mappings"] + ) or "- None recorded." + md=f"""# ENTITY Listing Information Sheet + +## Instrument at a glance + +**{info['market_identifier']} — {info['instrument_name']}** + +{info['what_is_this']} + +Issuer: **{info['issuer']}** +Underlying asset: **{info['asset_name']}** +Canonical Instrument ID: {info['instrument_id']} +Canonical Listing ID: {info['listing_id']} + +## Underlying Asset — what is being referenced? + +### Asset description + +{info['asset_description']} + +### Canonical asset identity + +- DCO ID: {facts.get('dco_id')} +- DCO code: {facts.get('dco_code') or '—'} +- Short name: {facts.get('short_name') or '—'} +- Version: {facts.get('version') or '—'} +- Object type: {facts.get('object_type') or '—'} +- Commodity class: {facts.get('commodity_class') or '—'} +- Measurement unit: {facts.get('measurement_unit') or '—'} +- Controller ENTITY ID: {facts.get('controller_entity_id')} +- Content SHA-256: {facts.get('content_sha256') or '—'} +- Asset status: {facts.get('status') or '—'} +- Jurisdiction profiles: {', '.join(facts.get('jurisdictions') or []) or '—'} + +### Purpose + +{disclosure.get('purpose') or 'No separate issuer-authored purpose statement has been published; rely on the canonical asset description and facts.'} + +### Key capabilities + +{bullet(disclosure.get('capabilities'))} + +### Included content / components + +{bullet(disclosure.get('contents'))} + +### Intended uses + +{bullet(disclosure.get('intended_uses'))} + +### Known limitations / exclusions + +{bullet(disclosure.get('limitations'))} + +### Dependencies / prerequisites + +{bullet(disclosure.get('dependencies'))} + +### Validation / qualification notes + +{bullet(disclosure.get('validation_notes'))} + +### Canonical technical metadata + +{kv(meta)} + +### Industry / profile context + +{kv(industry)} + +Profile stack: +{bullet(dossier['profile_refs'])} + +Standards mappings: +{standards_md} + +### Validation and maturity flags + +{kv(validation)} + +### Provenance + +{provenance_md} + +Additional provenance references: +{bullet(dossier['provenance_refs'])} + +Evidence references: +{bullet(dossier['evidence_refs'])} + +### BTDU binding + +- Bound: {'Yes' if dossier['btdu_binding']['bound'] else 'No'} +- BTDU version: {dossier['btdu_binding'].get('btdu_version') or '—'} +- Object reference: {dossier['btdu_binding'].get('object_ref') or '—'} +- Binding mode: {dossier['btdu_binding'].get('binding_mode') or '—'} + +A BTDU binding is a topology/evidence relationship. It does not create ownership or economic entitlement. + +## What does the buyer receive? + +{bullet(info['buyer_receives'],'- See Rights Passport.')} + +## What does the buyer NOT receive? + +{bullet(info['buyer_does_not_receive'],'- No rights beyond the Rights Passport.')} + +The underlying asset and the economic instrument are different objects. Buying this instrument does not silently transfer ownership of the DCO, copyright, source ownership, raw bytes, or any right that is not explicitly granted. + +## Rights and market terms + +- Instrument class: {info['instrument_class']} +- Rights class: {info['rights_class']} +- Supply: {info['supply']} +- Divisibility: {info['divisibility']} +- Fungibility: {info['fungibility']} +- Jurisdiction: {info['jurisdiction']} +- Quote unit: {info['quote_unit']} +- Trade mode: {info['trade_mode']} +- Settlement: {info['settlement_method']} +- Minimum quantity: {info['minimum_quantity']} +- Pricing method: {info['pricing_method']} +- Transfer rules: {json.dumps(info['transfer_rules'],sort_keys=True,ensure_ascii=False)} +- Economic terms: {json.dumps(info['economic_terms'],sort_keys=True,ensure_ascii=False)} +- Royalty terms: {json.dumps(info['royalty_terms'],sort_keys=True,ensure_ascii=False)} + +## Economic-value boundary + +A listed price, bid, ask or settled instrument price describes demand for these bounded rights under these terms. It does not establish the intrinsic value of the underlying DCO and is not accounting fair value. + +## Canonical references + +- Issuer ENTITY ID: {info['issuer_entity_id']} +- Underlying DCO: {info['underlying_dco_id']} +- Rights Passport: {info['rights_passport_id']} +- Global Passport: {info['global_passport_id']} +- Asset Disclosure: {info['asset_disclosure_id'] or 'none published'} +- Asset Disclosure SHA-256: {info['asset_disclosure_sha256'] or '—'} +- Asset Dossier SHA-256: {info['asset_dossier_sha256']} +- Venue: {info['venue_id']} + +## Disclosure boundary + +This sheet is a buyer-facing disclosure layer. It does not replace the Rights Passport, Global Passport, canonical DCO, canonical instrument, or canonical listing record. Issuer-authored asset description fields are signed issuer statements; ENTITY does not convert those statements into objective truth merely by recording them. +""" + info["verification_hash"]=sha(info) + return info,md + + def create_listing(self,issuer_entity_id:str,instrument_id:str,venue_id:str,*,market_id:str, + quote_unit:str,trade_mode:str="ORDER_BOOK",settlement_method:str="PAYMENT_VERSUS_RIGHT", + minimum_quantity:int=1,quantity_precision:int=0,price_precision:int=0, + pricing_method:str="ORDER_BOOK",listing_series:int=1,tick_size:int=1)->dict: + instrument=self.instrument(instrument_id); issuer=str(issuer_entity_id) + if instrument["issuer_entity_id"]!=issuer: raise PermissionError("instrument issuer required") + listing_id=canonical_listing_id(str(venue_id),instrument_id,int(listing_series)) + draft={"listing_id":listing_id,"venue_id":str(venue_id),"market_id":str(market_id), + "quote_unit":_enum(quote_unit),"trade_mode":_enum(trade_mode), + "settlement_method":_enum(settlement_method),"minimum_quantity":int(minimum_quantity), + "quantity_precision":int(quantity_precision),"price_precision":int(price_precision), + "pricing_method":_enum(pricing_method),"status":"ACTIVE","listed_at_ms":now_ms()} + if draft["minimum_quantity"]<1: raise ValueError("minimum quantity must be positive") + asset=self.fabric.get_object(instrument["underlying_dco_id"]) + disclosure=self.latest_asset_disclosure(instrument["underlying_dco_id"]) + if not disclosure: + raise ValueError("signed Asset Disclosure required before a market listing can be created") + manifest=self.identity.load_manifest(issuer) + info,md=self._information(instrument,draft,asset,manifest) + info_hash=sha(info) + machine={"protocol":"ENTITY-INSTRUMENT-1","instrument_symbol":instrument["display_symbol"], + "market_identifier":instrument["market_identifier"],"instrument_name":instrument["instrument_name"], + "instrument_id":instrument_id,"issuer_entity_id":issuer, + "underlying_dco_id":instrument["underlying_dco_id"],"listing_id":listing_id, + "rights_passport":instrument["rights_passport_id"],"global_passport":instrument["global_passport_id"], + "asset_disclosure_id":info.get("asset_disclosure_id"), + "asset_disclosure_sha256":info.get("asset_disclosure_sha256"), + "asset_dossier_sha256":info["asset_dossier_sha256"], + "listing_information_sha256":info_hash} + machine["verification_hash"]=sha(machine); machine_hash=sha(machine) + disclosure=self.exchange.publish_disclosure(str(venue_id),instrument_id,issuer,"LISTING_INFORMATION",machine_hash) + listing_body={"schema":"entity-eep-listing-v1","listing_id":listing_id,"venue_id":str(venue_id), + "instrument_id":instrument_id,"lister":issuer,"min_lot":draft["minimum_quantity"], + "tick_size":int(tick_size),"disclosure_sha256":machine_hash, + "status":"ACTIVE","created_at_ms":draft["listed_at_ms"]} + lsig=self.identity.sign(issuer,listing_body) + self.exchange.submit_signed_listing(listing_body,lsig) + package={**draft,"schema":"entity-market-listing-package-v1","instrument_id":instrument_id, + "display_symbol":instrument["display_symbol"],"listing_information_sha256":info_hash, + "machine_manifest_sha256":machine_hash} + psig=self.identity.sign(issuer,package) + with self._db() as db: + db.execute("INSERT INTO listing_packages VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",( + listing_id,instrument_id,str(venue_id),draft["market_id"],instrument["display_symbol"], + draft["quote_unit"],draft["trade_mode"],draft["settlement_method"],draft["minimum_quantity"], + draft["quantity_precision"],draft["price_precision"],draft["pricing_method"],info_hash,machine_hash, + "ACTIVE",draft["listed_at_ms"],json.dumps(psig,sort_keys=True))) + db.execute("INSERT INTO listing_information VALUES(?,?,?,?)",( + listing_id,json.dumps({"information":info,"machine_manifest":machine},sort_keys=True), + md,draft["listed_at_ms"])) + return dict(package,signature=psig,information=info,information_markdown=md,machine_manifest=machine, + eep_listing=dict(listing_body,signature=lsig),disclosure=disclosure) + + def listing(self,listing_id:str)->dict: + with self._db() as db: + r=db.execute("SELECT * FROM listing_packages WHERE listing_id=?",(str(listing_id),)).fetchone() + i=db.execute("SELECT * FROM listing_information WHERE listing_id=?",(str(listing_id),)).fetchone() + if not r or not i: raise KeyError("listing package not found") + payload=json.loads(i["information_json"]) + return dict(r) | {"signature":json.loads(r["signature_json"]), + "information":payload["information"],"machine_manifest":payload["machine_manifest"], + "information_markdown":i["information_markdown"]} + + def export_portable_package(self,listing_id:str,destination:str|Path)->dict: + listing=self.listing(listing_id); instrument=self.instrument(listing["instrument_id"]) + rp=self.rights.get(instrument["rights_passport_id"]); gp=self.global_passports.get(instrument["global_passport_id"]) + asset=self.fabric.get_object(instrument["underlying_dco_id"]) + out=Path(destination); out.mkdir(parents=True,exist_ok=True) + files={ + "LISTING_INFORMATION.md":listing["information_markdown"].encode(), + "instrument.json":json.dumps(instrument,indent=2,sort_keys=True).encode(), + "entity-instrument.json":json.dumps(listing["machine_manifest"],indent=2,sort_keys=True).encode(), + "listing.json":json.dumps({k:v for k,v in listing.items() if k not in {"information_markdown"}},indent=2,sort_keys=True,default=str).encode(), + "rights-passport.json":json.dumps(rp,indent=2,sort_keys=True).encode(), + "global-passport.json":json.dumps(gp,indent=2,sort_keys=True).encode(), + "provenance.json":json.dumps({"underlying_dco":asset,"evidence_refs":instrument["evidence_refs"]},indent=2,sort_keys=True).encode(), + "asset-dossier.json":json.dumps(listing["information"]["asset_dossier"],indent=2,sort_keys=True,default=str).encode(), + } + asset_disclosure=listing["information"]["asset_dossier"].get("issuer_disclosure") + if asset_disclosure: + files["asset-disclosure.json"]=json.dumps(asset_disclosure,indent=2,sort_keys=True,default=str).encode() + for name,data in files.items(): (out/name).write_bytes(data) + _simple_pdf(listing["information_markdown"],out/"LISTING_INFORMATION.pdf") + verification={"schema":"entity-portable-instrument-verification-v1","instrument_id":instrument["instrument_id"], + "listing_id":listing_id,"market_identifier":instrument["market_identifier"], + "canonical_identity_is_instrument_id":True, + "external_publication_does_not_create_new_instrument":True, + "manifest_sha256":listing["machine_manifest_sha256"]} + (out/"verification.json").write_text(json.dumps(verification,indent=2,sort_keys=True),encoding="utf-8") + names=sorted([p for p in out.iterdir() if p.is_file() and p.name!="SHA256SUMS"]) + sums="\n".join(f"{hashlib.sha256(p.read_bytes()).hexdigest()} {p.name}" for p in names)+"\n" + (out/"SHA256SUMS").write_text(sums,encoding="ascii") + badge=f"[ENTITY VERIFIED | {instrument['market_identifier']}]" + (out/"ENTITY_INSTRUMENT.md").write_text( + f"# {instrument['instrument_name']}\n\n{badge}\n\nInstrument: `{instrument['instrument_id']}`\n\n" + f"Underlying DCO: `{instrument['underlying_dco_id']}`\n\nIssuer: `{instrument['issuer_entity_id']}`\n\n" + f"Listing: `{listing_id}`\n\nRights Passport: `{instrument['rights_passport_id']}`\n\n" + f"Global Passport: `{instrument['global_passport_id']}`\n\nManifest SHA-256: `{listing['machine_manifest_sha256']}`\n", + encoding="utf-8") + return {"schema":"entity-portable-instrument-package-v1","destination":str(out), + "instrument_id":instrument["instrument_id"],"listing_id":listing_id, + "market_identifier":instrument["market_identifier"], + "files":sorted(p.name for p in out.iterdir() if p.is_file())} + + def issuer_instruments(self,issuer_entity_id:str,*,include_withdrawn:bool=False)->list[dict]: + sql="SELECT instrument_id FROM instrument_packages WHERE issuer_entity_id=?" + args=[str(issuer_entity_id)] + if not include_withdrawn: sql+=" AND status='ACTIVE'" + sql+=" ORDER BY created_at_ms,instrument_id" + with self._db() as db: ids=[r["instrument_id"] for r in db.execute(sql,args)] + return [self.instrument(i) for i in ids] + + def instrument_listings(self,instrument_id:str,*,active_only:bool=True)->list[dict]: + sql="SELECT listing_id FROM listing_packages WHERE instrument_id=?" + args=[str(instrument_id)] + if active_only: sql+=" AND status='ACTIVE'" + sql+=" ORDER BY listed_at_ms,listing_id" + with self._db() as db: ids=[r["listing_id"] for r in db.execute(sql,args)] + return [self.listing(i) for i in ids] + + def active_market(self)->list[dict]: + with self._db() as db: + rows=db.execute("""SELECT l.*,i.instrument_name,i.market_identifier,i.issuer_entity_id, + i.underlying_dco_id,i.rights_class,i.instrument_class + FROM listing_packages l JOIN instrument_packages i ON i.instrument_id=l.instrument_id + WHERE l.status='ACTIVE' AND i.status='ACTIVE' + ORDER BY i.market_identifier,l.listed_at_ms""").fetchall() + return [dict(r) for r in rows] + + def status(self)->dict: + with self._db() as db: + counts={t:int(db.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0]) + for t in ("issuer_namespaces","instrument_packages","listing_packages","listing_information","asset_disclosures")} + return {"schema":MARKET_SCHEMA,"version":MARKET_VERSION,"multi_issuer":True, + "entity_owns_protocol_not_assets":True,"canonical_instrument_identity":True, + "symbols_are_non_authoritative_aliases":True,"listing_information_required":True, + "signed_asset_disclosures_supported":True,"signed_asset_disclosure_required_for_listing":True, + "listing_information_asset_dossier_required":True, + "portable_publication_supported":True,"protocol_tax_bps":0, + "cryptocurrency_required":False,"counts":counts} diff --git a/src/45_ENTITY_Market/economic_intelligence.py b/src/45_ENTITY_Market/economic_intelligence.py new file mode 100644 index 00000000..04085cf2 --- /dev/null +++ b/src/45_ENTITY_Market/economic_intelligence.py @@ -0,0 +1,243 @@ +from __future__ import annotations +from pathlib import Path +from collections import defaultdict +import json, sqlite3, time + +DAY_MS=24*60*60*1000 +MONTH_MS=30*DAY_MS + +class EntityEconomicIntelligence: + """Observed, settlement-aware market analytics for ENTITY instruments. + + Metrics are keyed by canonical instrument_id. Market identifiers/tickers are + human aliases only. Observations describe trading in bounded rights and must + never be represented as intrinsic or accounting value of the underlying DCO. + """ + + def __init__(self,state_dir:str|Path,market_registry,fabric,global_passports=None): + self.state=Path(state_dir); self.market_registry=market_registry + self.fabric=fabric; self.global_passports=global_passports + self.exchange_path=self.state/"entity_v3_exchange.sqlite" + self.eopp_path=self.state/"entity_v3_economic_participation.sqlite" + self.passport_path=self.state/"entity_v3_4_global_passports.sqlite" + + @staticmethod + def _db(path:Path): + db=sqlite3.connect(path); db.row_factory=sqlite3.Row; return db + + def _passport(self,passport_id:str)->dict|None: + if self.global_passports is not None: + try: return self.global_passports.get(passport_id) + except Exception: pass + if not self.passport_path.exists(): return None + db=self._db(self.passport_path) + try: + row=db.execute("SELECT body_json FROM global_passports WHERE passport_id=?",(passport_id,)).fetchone() + return json.loads(row["body_json"]) if row else None + finally: db.close() + + @staticmethod + def _primary_domain(passport:dict|None)->str: + refs=list(((passport or {}).get("profile_stack") or {}).get("profile_refs") or []) + for ref in refs: + if "entity-profile:global@" in ref: continue + return str(ref).split("@",1)[0].split(":")[-1].upper().replace("-","_") + return "UNCLASSIFIED" + + def _classification(self,instrument:dict)->dict: + obj=self.fabric.get_object(instrument["underlying_dco_id"]) + descriptor=dict(obj.get("descriptor") or {}) + meta=dict(descriptor.get("metadata") or {}) + passport=self._passport(instrument["global_passport_id"]) + return { + "underlying_dco_id":instrument["underlying_dco_id"], + "asset_title":obj.get("title"), + "object_type":obj.get("object_type"), + "asset_class":descriptor.get("commodity_class") or obj.get("object_type") or "UNCLASSIFIED", + "asset_subtype":meta.get("asset_subtype") or meta.get("category") or descriptor.get("commodity_class") or "UNCLASSIFIED", + "primary_domain":self._primary_domain(passport), + "issuer_entity_id":instrument["issuer_entity_id"], + "rights_class":instrument["rights_class"], + } + + def _royalties(self,instrument_id:str)->dict: + out={"settled_amounts_by_currency":{},"externally_verified_amounts_by_currency":{}, + "settled_obligation_count":0,"externally_verified_obligation_count":0} + if not self.eopp_path.exists(): return out + db=self._db(self.eopp_path) + try: + rows=db.execute("""SELECT o.amount_units,o.currency,o.status,o.external_verified,o.basis + FROM obligations o + JOIN economic_events e ON e.event_id=o.event_id + WHERE e.instrument_id=? AND o.status='SETTLED' + AND o.basis IN ('SECONDARY_ORIGINATOR_ROYALTY','DERIVATIVE_PARTICIPATION', + 'PRIMARY_TREASURY_ALLOCATION')""",(instrument_id,)).fetchall() + except sqlite3.OperationalError: + rows=[] + finally: db.close() + settled=defaultdict(int); verified=defaultdict(int) + for r in rows: + cur=str(r["currency"]); amount=int(r["amount_units"]); settled[cur]+=amount + out["settled_obligation_count"]+=1 + if int(r["external_verified"]): + verified[cur]+=amount; out["externally_verified_obligation_count"]+=1 + out["settled_amounts_by_currency"]=dict(sorted(settled.items())) + out["externally_verified_amounts_by_currency"]=dict(sorted(verified.items())) + return out + + def instrument_metrics(self,instrument_id:str,*,as_of_ms:int|None=None)->dict: + instrument=self.market_registry.instrument(instrument_id) + now=int(as_of_ms if as_of_ms is not None else time.time()*1000) + start24=now-DAY_MS; start30=now-MONTH_MS + db=self._db(self.exchange_path) + try: + trades=db.execute("""SELECT t.*,c.external_verified,c.status clearing_status + FROM trades t LEFT JOIN clearing c ON c.trade_id=t.trade_id + WHERE t.instrument_id=? AND t.status='SETTLED' + ORDER BY t.created_at_ms,t.trade_id""",(instrument_id,)).fetchall() + balances=db.execute("SELECT holder,units FROM balances WHERE instrument_id=? AND units>0",(instrument_id,)).fetchall() + orders=db.execute("""SELECT participant,side,remaining,status FROM orders + WHERE instrument_id=? AND status IN ('OPEN','PARTIAL')""",(instrument_id,)).fetchall() + issuer_balance_row=db.execute("SELECT units FROM balances WHERE instrument_id=? AND holder=?", + (instrument_id,instrument["issuer_entity_id"])).fetchone() + finally: db.close() + + def summarize(rows): + units=sum(int(r["quantity"]) for r in rows) + notional=sum(int(r["quantity"])*int(r["price"]) for r in rows) + verified=[r for r in rows if int(r["external_verified"] or 0)==1] + self_rows=[r for r in rows if r["buyer"]==r["seller"]] + buyers={r["buyer"] for r in rows}; sellers={r["seller"] for r in rows} + prices=[int(r["price"]) for r in rows] + pairs=defaultdict(lambda:{"forward":0,"reverse":0,"notional":0}) + unordered=defaultdict(lambda:{"dirs":set(),"notional":0}) + for r in rows: + b,s=str(r["buyer"]),str(r["seller"]); n=int(r["quantity"])*int(r["price"]) + key=tuple(sorted((b,s))); unordered[key]["dirs"].add((b,s)); unordered[key]["notional"]+=n + reciprocal_notional=sum(v["notional"] for v in unordered.values() if len(v["dirs"])>1) + pair_max=max((v["notional"] for v in unordered.values()),default=0) + return { + "trade_count":len(rows),"volume_units":units,"notional_amount_units":notional, + "unique_buyers":len(buyers),"unique_sellers":len(sellers), + "high_price":max(prices) if prices else None,"low_price":min(prices) if prices else None, + "externally_verified_trade_count":len(verified), + "externally_verified_volume_units":sum(int(r["quantity"]) for r in verified), + "externally_verified_notional_amount_units":sum(int(r["quantity"])*int(r["price"]) for r in verified), + "self_trade_count":len(self_rows), + "self_trade_notional_amount_units":sum(int(r["quantity"])*int(r["price"]) for r in self_rows), + "reciprocal_counterparty_notional_amount_units":reciprocal_notional, + "largest_counterparty_pair_notional_amount_units":pair_max, + "largest_counterparty_pair_share":(pair_max/notional) if notional else None, + } + + rows24=[r for r in trades if int(r["created_at_ms"])>=start24] + rows30=[r for r in trades if int(r["created_at_ms"])>=start30] + last=trades[-1] if trades else None + last_verified=next((r for r in reversed(trades) if int(r["external_verified"] or 0)==1),None) + issuer_balance=int(issuer_balance_row["units"]) if issuer_balance_row else 0 + issuer_reserved=sum(int(r["remaining"]) for r in orders if r["side"]=="SELL" and r["participant"]==instrument["issuer_entity_id"]) + open_sell_units=sum(int(r["remaining"]) for r in orders if r["side"]=="SELL") + total_supply=int(instrument["supply"]) + active_holder_count=len(balances) + nonissuer_holder_count=sum(1 for r in balances if r["holder"]!=instrument["issuer_entity_id"]) + m30=summarize(rows30) + flags=[] + if m30["self_trade_count"]: flags.append("SELF_TRADE_ACTIVITY_PRESENT") + if m30["reciprocal_counterparty_notional_amount_units"]: flags.append("RECIPROCAL_COUNTERPARTY_FLOW_PRESENT") + if m30["largest_counterparty_pair_share"] is not None and m30["largest_counterparty_pair_share"]>=0.50: + flags.append("COUNTERPARTY_CONCENTRATION_HIGH") + if m30["trade_count"] and m30["externally_verified_trade_count"]==0: + flags.append("NO_EXTERNALLY_VERIFIED_SETTLEMENTS_IN_WINDOW") + classification=self._classification(instrument) + return { + "schema":"entity-economic-intelligence-instrument-v1", + "instrument_id":instrument_id, + "market_identifier":instrument["market_identifier"], + "display_symbol":instrument["display_symbol"], + "instrument_name":instrument["instrument_name"], + "issuer_entity_id":instrument["issuer_entity_id"], + **classification, + "settlement_currency":self._settlement_currency(instrument_id), + "last_settled_price":None if last is None else int(last["price"]), + "last_settled_trade_at_ms":None if last is None else int(last["created_at_ms"]), + "last_externally_verified_price":None if last_verified is None else int(last_verified["price"]), + "last_externally_verified_trade_at_ms":None if last_verified is None else int(last_verified["created_at_ms"]), + "window_24h":summarize(rows24),"window_30d":m30, + "lifetime":summarize(trades), + "total_supply_units":total_supply, + "issuer_inventory_units":issuer_balance, + "issuer_unreserved_inventory_units":max(0,issuer_balance-issuer_reserved), + "open_sell_offer_units":open_sell_units, + "circulating_units":max(0,total_supply-issuer_balance), + "active_holder_count":active_holder_count, + "nonissuer_holder_count":nonissuer_holder_count, + "royalties_and_participation":self._royalties(instrument_id), + "market_integrity":{ + "flags":flags, + "related_wallet_detection_performed":False, + "related_wallet_detection_requires_authorized_relationship_evidence":True, + "raw_volume_is_not_proof_of_value":True, + "orders_and_quotes_are_not_counted_as_realized_demand":True, + "settled_trades_only_for_price_volume_metrics":True, + }, + "valuation_boundary":{ + "observed_prices_value_instrument_rights_not_underlying_dco":True, + "instrument_price_times_supply_is_not_underlying_asset_value":True, + "market_observation_is_not_accounting_fair_value":True, + "intrinsic_value_not_claimed":True, + }, + "as_of_ms":now, + } + + def _settlement_currency(self,instrument_id:str)->str|None: + db=self._db(self.exchange_path) + try: + row=db.execute("SELECT settlement_currency FROM instruments WHERE instrument_id=?",(instrument_id,)).fetchone() + return row["settlement_currency"] if row else None + finally: db.close() + + def dco_rights_demand(self,dco_id:str,*,as_of_ms:int|None=None)->dict: + with self.market_registry._db() as db: + rows=db.execute("""SELECT instrument_id FROM instrument_packages + WHERE underlying_dco_id=? AND status='ACTIVE' + ORDER BY rights_class,market_identifier""",(str(dco_id),)).fetchall() + metrics=[self.instrument_metrics(r["instrument_id"],as_of_ms=as_of_ms) for r in rows] + by_rights=defaultdict(lambda:{"instruments":0,"volume_units_30d":0, + "notional_30d_by_currency":defaultdict(int), + "sum_instrument_unique_buyers_30d":0,"settled_trades":0}) + for m in metrics: + r=by_rights[m["rights_class"]]; r["instruments"]+=1 + r["volume_units_30d"]+=m["window_30d"]["volume_units"]; r["settled_trades"]+=m["window_30d"]["trade_count"] + r["sum_instrument_unique_buyers_30d"]+=m["window_30d"]["unique_buyers"] + cur=m["settlement_currency"] or "UNSPECIFIED" + r["notional_30d_by_currency"][cur]+=m["window_30d"]["notional_amount_units"] + clean={k:{**v,"notional_30d_by_currency":dict(v["notional_30d_by_currency"])} + for k,v in sorted(by_rights.items())} + return {"schema":"entity-dco-rights-demand-v1","underlying_dco_id":str(dco_id), + "instruments":metrics,"rights_class_rollup":clean, + "observed_demand_is_for_instrument_rights_not_intrinsic_asset_value":True} + + def economy_rollup(self,*,group_by:str="asset_class",as_of_ms:int|None=None)->dict: + allowed={"asset_class","asset_subtype","primary_domain","rights_class","issuer_entity_id","underlying_dco_id"} + if group_by not in allowed: raise ValueError("unsupported rollup dimension") + with self.market_registry._db() as db: + ids=[r["instrument_id"] for r in db.execute( + "SELECT instrument_id FROM instrument_packages WHERE status='ACTIVE' ORDER BY instrument_id").fetchall()] + groups=defaultdict(lambda:{"instrument_count":0,"settled_trades_30d":0,"volume_units_30d":0, + "notional_30d_by_currency":defaultdict(int),"unique_buyer_sum":0}) + for iid in ids: + m=self.instrument_metrics(iid,as_of_ms=as_of_ms); key=str(m.get(group_by) or "UNCLASSIFIED") + g=groups[key]; g["instrument_count"]+=1; g["settled_trades_30d"]+=m["window_30d"]["trade_count"] + g["volume_units_30d"]+=m["window_30d"]["volume_units"]; g["unique_buyer_sum"]+=m["window_30d"]["unique_buyers"] + g["notional_30d_by_currency"][m["settlement_currency"] or "UNSPECIFIED"]+=m["window_30d"]["notional_amount_units"] + rows=[] + for key,g in groups.items(): + rows.append({"group":key,"instrument_count":g["instrument_count"], + "settled_trades_30d":g["settled_trades_30d"],"volume_units_30d":g["volume_units_30d"], + "notional_30d_by_currency":dict(sorted(g["notional_30d_by_currency"].items())), + "sum_instrument_unique_buyers_30d":g["unique_buyer_sum"]}) + rows.sort(key=lambda x:(-x["volume_units_30d"],x["group"])) + return {"schema":"entity-economy-rollup-v1","group_by":group_by,"rows":rows, + "cross_currency_notional_is_not_summed_into_one_value":True, + "rollup_is_observed_market_activity_not_underlying_asset_valuation":True, + "as_of_ms":int(as_of_ms if as_of_ms is not None else time.time()*1000)} diff --git a/src/45_ENTITY_Market/prelaunch_withdrawal.py b/src/45_ENTITY_Market/prelaunch_withdrawal.py new file mode 100644 index 00000000..8558e657 --- /dev/null +++ b/src/45_ENTITY_Market/prelaunch_withdrawal.py @@ -0,0 +1,191 @@ +from __future__ import annotations +from pathlib import Path +from typing import Iterable +import hashlib,json,shutil,sqlite3,time + +def now_ms()->int: return int(time.time()*1000) +def canon(v)->bytes: return json.dumps(v,sort_keys=True,separators=(",",":"),ensure_ascii=False,default=str).encode() +def sha(v)->str: return hashlib.sha256(v if isinstance(v,(bytes,bytearray)) else canon(v)).hexdigest() + +class PrelaunchEconomicWithdrawal: + """Fail-closed application migration for unused prelaunch economic experiments. + + This does not add an EEP wire primitive or rewrite historical signed records. + It is only valid when the targeted instruments have never entered real market + activity and all non-issuer balances are proven internal treasury reserves. + """ + + def __init__(self,state_dir:str|Path,identity): + self.state=Path(state_dir); self.identity=identity + self.eep=self.state/"entity_v3_exchange.sqlite" + self.eopp=self.state/"entity_v3_economic_participation.sqlite" + self.factory=self.state/"dco_factory"/"entity_dco_factory.sqlite" + self.audit_dir=self.state/"economic_migrations"; self.audit_dir.mkdir(parents=True,exist_ok=True) + + @staticmethod + def _db(path:Path): + db=sqlite3.connect(path); db.row_factory=sqlite3.Row; return db + + def audit(self,object_ids:Iterable[str],dco_ids:Iterable[str]=())->dict: + object_ids=sorted({str(x) for x in object_ids}); dco_ids=sorted({str(x) for x in dco_ids}) + if not object_ids: raise ValueError("at least one underlying object_id required") + if not self.eep.exists(): raise FileNotFoundError(self.eep) + db=self._db(self.eep) + try: + marks=",".join("?" for _ in object_ids) + instruments=[dict(r) for r in db.execute( + f"SELECT * FROM instruments WHERE underlying_object_id IN ({marks}) ORDER BY instrument_id",object_ids)] + if not instruments: raise ValueError("no economic instruments found for selected objects") + ids=[r["instrument_id"] for r in instruments]; q=",".join("?" for _ in ids) + activity={} + for table in ("orders","trades","entitlements","usage"): + activity[table]=int(db.execute(f"SELECT COUNT(*) FROM {table} WHERE instrument_id IN ({q})",ids).fetchone()[0]) + trade_ids=[r["trade_id"] for r in db.execute(f"SELECT trade_id FROM trades WHERE instrument_id IN ({q})",ids)] + activity["clearing"]=0 if not trade_ids else int(db.execute( + f"SELECT COUNT(*) FROM clearing WHERE trade_id IN ({','.join('?' for _ in trade_ids)})",trade_ids).fetchone()[0]) + balances=[dict(r) for r in db.execute( + f"""SELECT b.instrument_id,b.holder,b.units,i.issuer + FROM balances b JOIN instruments i ON i.instrument_id=b.instrument_id + WHERE b.instrument_id IN ({q}) AND b.units>0 ORDER BY b.instrument_id,b.holder""",ids)] + listings=int(db.execute(f"SELECT COUNT(*) FROM listings WHERE instrument_id IN ({q}) AND status='ACTIVE'",ids).fetchone()[0]) + finally: db.close() + + reserve_by_instrument={}; economic_events=0 + if self.eopp.exists(): + ep=self._db(self.eopp) + try: + iq=",".join("?" for _ in ids) + economic_events=int(ep.execute( + f"SELECT COUNT(*) FROM economic_events WHERE instrument_id IN ({iq})",ids).fetchone()[0]) + rows=ep.execute(f"""SELECT r.instrument_id,r.units,r.treasury_entity_id,t.owner_entity_id + FROM reserve_allocations r + JOIN participation_policies p ON p.policy_id=r.policy_id + JOIN treasuries t ON t.treasury_id=p.treasury_id + WHERE r.instrument_id IN ({iq})""",ids).fetchall() + reserve_by_instrument={r["instrument_id"]:dict(r) for r in rows} + finally: ep.close() + + external=[] + for b in balances: + if b["holder"]==b["issuer"]: continue + reserve=reserve_by_instrument.get(b["instrument_id"]) + if not reserve or reserve["owner_entity_id"]!=b["issuer"] or reserve["treasury_entity_id"]!=b["holder"] or int(reserve["units"])!=int(b["units"]): + external.append(b) + safe=not any(activity.values()) and economic_events==0 and not external + return { + "schema":"entity-prelaunch-economic-withdrawal-audit-v1", + "object_ids":object_ids,"dco_ids":dco_ids, + "instrument_ids":ids,"instrument_count":len(ids),"active_listing_count":listings, + "activity_counts":activity,"economic_event_count":economic_events, + "positive_balances":balances,"internal_reserves":reserve_by_instrument, + "external_or_unexplained_balances":external, + "safe_to_withdraw":safe, + "requires_zero_market_history":True, + "history_rewrite_prohibited":True, + } + + def apply(self,object_ids:Iterable[str],dco_ids:Iterable[str],reason:str)->dict: + reason=str(reason).strip() + if not reason: raise ValueError("withdrawal reason required") + audit=self.audit(object_ids,dco_ids) + if not audit["safe_to_withdraw"]: + raise RuntimeError("prelaunch withdrawal refused: economic activity or external holdings exist") + ids=audit["instrument_ids"]; issuers=sorted({b["issuer"] for b in audit["positive_balances"]}) + if not issuers: + db=self._db(self.eep) + try: + q=",".join("?" for _ in ids) + issuers=sorted({r["issuer"] for r in db.execute(f"SELECT issuer FROM instruments WHERE instrument_id IN ({q})",ids)}) + finally: db.close() + for issuer in issuers: self.identity.load_manifest(issuer) + + created=now_ms() + body={"schema":"entity-prelaunch-economic-withdrawal-v1","reason":reason, + "object_ids":audit["object_ids"],"dco_ids":audit["dco_ids"],"instrument_ids":ids, + "audit_sha256":sha(audit),"created_at_ms":created, + "no_orders":audit["activity_counts"]["orders"]==0, + "no_trades":audit["activity_counts"]["trades"]==0, + "no_entitlements":audit["activity_counts"]["entitlements"]==0, + "no_usage":audit["activity_counts"]["usage"]==0, + "no_economic_events":audit["economic_event_count"]==0, + "no_external_holders":not audit["external_or_unexplained_balances"], + "eep_wire_protocol_changed":False,"historical_records_deleted":False} + evidence=sha(body) + signatures=[self.identity.sign(issuer,body) for issuer in issuers] + + backups={} + for p in (self.eep,self.eopp,self.factory): + if p.exists(): + dst=self.audit_dir/f"{p.name}.{created}.bak"; shutil.copy2(p,dst); backups[str(p)]=str(dst) + try: + db=self._db(self.eep) + db.execute("BEGIN IMMEDIATE") + q=",".join("?" for _ in ids) + db.execute(f"UPDATE instruments SET status='WITHDRAWN' WHERE instrument_id IN ({q}) AND status='ACTIVE'",ids) + db.execute(f"UPDATE listings SET status='WITHDRAWN' WHERE instrument_id IN ({q}) AND status='ACTIVE'",ids) + for iid,reserve in audit["internal_reserves"].items(): + issuer=next(r["issuer"] for r in audit["positive_balances"] if r["instrument_id"]==iid) + holder=reserve["treasury_entity_id"]; units=int(reserve["units"]) + db.execute("UPDATE balances SET units=units-? WHERE instrument_id=? AND holder=?",(units,iid,holder)) + db.execute("""INSERT INTO balances(instrument_id,holder,units) VALUES(?,?,?) + ON CONFLICT(instrument_id,holder) DO UPDATE SET units=units+excluded.units""",(iid,issuer,units)) + db.commit(); db.close() + + if self.eopp.exists(): + ep=self._db(self.eopp); ep.execute("BEGIN IMMEDIATE") + ep.execute("""CREATE TABLE IF NOT EXISTS participation_policy_withdrawals( + withdrawal_id TEXT PRIMARY KEY, policy_id TEXT NOT NULL UNIQUE, + instrument_id TEXT NOT NULL, originator_entity_id TEXT NOT NULL, + reason TEXT NOT NULL, evidence_sha256 TEXT, created_at_ms INTEGER NOT NULL, + signature_json TEXT NOT NULL)""") + iq=",".join("?" for _ in ids) + policies=ep.execute(f"""SELECT * FROM participation_policies + WHERE instrument_id IN ({iq}) AND status='ACTIVE' + ORDER BY instrument_id,version""",ids).fetchall() + for policy in policies: + withdrawal_id="opw3-"+sha({"policy_id":policy["policy_id"],"evidence":evidence})[:24] + wbody={"schema":"entity-v3-participation-policy-withdrawal-v1", + "withdrawal_id":withdrawal_id,"policy_id":policy["policy_id"], + "instrument_id":policy["instrument_id"], + "originator_entity_id":policy["originator_entity_id"], + "reason":reason,"evidence_sha256":evidence,"created_at_ms":created, + "historical_obligations_and_reserves_preserved":True, + "no_retroactive_economic_change":True,"protocol_tax_bps":0} + wsig=self.identity.sign(policy["originator_entity_id"],wbody) + ep.execute("""INSERT OR IGNORE INTO participation_policy_withdrawals + VALUES(?,?,?,?,?,?,?,?)""",( + withdrawal_id,policy["policy_id"],policy["instrument_id"], + policy["originator_entity_id"],reason,evidence,created, + json.dumps(wsig,sort_keys=True))) + ep.execute("UPDATE participation_policies SET status='WITHDRAWN' WHERE policy_id=?", + (policy["policy_id"],)) + ep.commit(); ep.close() + + if self.factory.exists() and audit["dco_ids"]: + fd=self._db(self.factory); fd.execute("BEGIN IMMEDIATE") + fd.execute("""CREATE TABLE IF NOT EXISTS issuance_withdrawals( + issuance_id TEXT PRIMARY KEY,dco_id TEXT NOT NULL,reason TEXT NOT NULL, + evidence_sha256 TEXT,created_at_ms INTEGER NOT NULL)""") + for dco in audit["dco_ids"]: + runs=fd.execute("SELECT issuance_id FROM issuance_runs WHERE dco_id=?",(dco,)).fetchall() + for run in runs: + fd.execute("""INSERT OR IGNORE INTO issuance_withdrawals + (issuance_id,dco_id,reason,evidence_sha256,created_at_ms) + VALUES(?,?,?,?,?)""",(run["issuance_id"],dco,reason,evidence,created)) + row=fd.execute("SELECT master_json FROM dco_masters WHERE dco_id=?",(dco,)).fetchone() + if row: + master=json.loads(row["master_json"]); master["lifecycle"]="ACTIVE" + meta=dict(master.get("metadata") or {}); meta["prelaunch_economic_issuance_withdrawn"]=True + meta["withdrawal_evidence_sha256"]=evidence; master["metadata"]=meta + fd.execute("UPDATE dco_masters SET lifecycle='ACTIVE',master_json=? WHERE dco_id=?", + (json.dumps(master,sort_keys=True,separators=(",",":")),dco)) + fd.commit(); fd.close() + except Exception: + for original,backup in backups.items(): shutil.copy2(backup,original) + raise + + receipt={**body,"evidence_sha256":evidence,"issuer_signatures":signatures, + "backup_files":backups,"status":"WITHDRAWN_UNUSED_PRELAUNCH_ECONOMICS"} + path=self.audit_dir/f"prelaunch-economic-withdrawal-{created}.json" + path.write_text(json.dumps(receipt,indent=2,sort_keys=True),encoding="utf-8") + return {**receipt,"receipt_path":str(path)} diff --git a/tests/test_dco_production_pipeline.py b/tests/test_dco_production_pipeline.py index 11289782..9cc1f2e9 100644 --- a/tests/test_dco_production_pipeline.py +++ b/tests/test_dco_production_pipeline.py @@ -11,7 +11,7 @@ def load(name,path): sys.modules[name]=m; spec.loader.exec_module(m); return m def h(s): return hashlib.sha256(str(s).encode()).hexdigest() -def test_production_pipeline_enforces_passports_before_economics(): +def test_production_pipeline_is_asset_first_and_economics_optional(): m=load("dco_pipeline_test",PIPE) with tempfile.TemporaryDirectory() as td: p=m.DCOProductionPipeline(td) @@ -21,13 +21,16 @@ def test_production_pipeline_enforces_passports_before_economics(): except ValueError: pass sequence=[ "ASSET_BUILT","ASSET_QUALIFIED","PROVENANCE_SEALED","DCO_REGISTERED", - "RIGHTS_PASSPORT_ISSUED","GLOBAL_PASSPORT_ISSUED","PROFILE_BTDU_BOUND", - "ECONOMIC_INSTRUMENTS_ISSUED"] + "RIGHTS_PASSPORT_ISSUED","GLOBAL_PASSPORT_ISSUED","PROFILE_BTDU_BOUND"] for i,s in enumerate(sequence): p.record_stage("DCO-1",s,h(i),{"i":i}) status=p.status("DCO-1") assert status["ready_for_economic_issuance"] is True - assert status["economic_instruments_issued"] is True + assert status["economic_instruments_issued"] is False assert status["bridge_bound"] is False + p.record_stage("DCO-1","BRIDGE_EXPOSURE_BOUND",h("bridge")) + assert p.status("DCO-1")["bridge_bound"] is True + p.record_stage("DCO-1","ECONOMIC_INSTRUMENTS_ISSUED",h("market")) + assert p.status("DCO-1")["economic_instruments_issued"] is True def test_public_safe_manifest_rejects_live_operational_keys(): m=load("dco_pipeline_public_test",PIPE) diff --git a/tests/test_economic_intelligence.py b/tests/test_economic_intelligence.py new file mode 100644 index 00000000..d9f0fea0 --- /dev/null +++ b/tests/test_economic_intelligence.py @@ -0,0 +1,97 @@ +from __future__ import annotations +from pathlib import Path +import importlib.util,sys,tempfile,time,sqlite3 + +ROOT=Path(__file__).resolve().parents[1] + +def load(name,rel): + p=ROOT/rel; spec=importlib.util.spec_from_file_location(name,p) + m=importlib.util.module_from_spec(spec); sys.modules[name]=m; spec.loader.exec_module(m); return m + +class FakePassports: + def __init__(self,kind): self.kind=kind; self.records={} + def bind(self,pid,object_id,controller,rp=None,actions=None): + if self.kind=="rights": + self.records[pid]={"passport_id":pid,"object_id":object_id,"controller_entity_id":controller, + "rights":[{"effect":"ALLOW","actions":list(actions or ["READ","TRAIN","COMMERCIALIZE"])}]} + else: + self.records[pid]={"passport_id":pid,"object_id":object_id,"controller_entity_id":controller, + "rights_passport_id":rp, + "profile_stack":{"profile_refs":["entity-profile:global@1.0","entity-profile:robotics@1.0"]}} + def get(self,pid): return dict(self.records[pid]) + def verify(self,p): return {"valid":True} + +def test_economic_intelligence_uses_settled_trades_and_flags_suspicious_flow(): + ident=load("intel_identity","src/01_Core_Runtime/identity/canonical_identity.py") + fabmod=load("intel_fabric","src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py") + exmod=load("intel_exchange","src/31_Profiles/exchange_protocol.py") + marketmod=load("intel_market","src/45_ENTITY_Market/canonical_market_registry.py") + intelmod=load("intel_engine","src/45_ENTITY_Market/economic_intelligence.py") + with tempfile.TemporaryDirectory() as td: + state=Path(td); vault=ident.EntityIdentityVault(state) + issuer=vault.create("Acme Corp","organization")["entity_id"] + a=vault.create("Buyer A","person")["entity_id"]; b=vault.create("Buyer B","person")["entity_id"] + fabric=fabmod.UniversalTransactionFabric(state,vault); ex=exmod.ExchangeProtocol(state,vault,fabric) + rights=FakePassports("rights"); gps=FakePassports("global") + reg=marketmod.EntityEconomicMarketRegistry(state,vault,fabric,ex,rights,gps) + obj=fabric.register_digital_commodity(issuer,"Robot Navigation Dataset","1"*64, + commodity_class="ROBOT_NAVIGATION",measurement_unit="ASSET",object_type="DATASET", + metadata={"asset_subtype":"NAVIGATION"}) + rights.bind("rp",obj["object_id"],issuer,actions=["TRAIN"]) + gps.bind("gp",obj["object_id"],issuer,"rp") + inst=reg.create_instrument(issuer,obj["object_id"],instrument_name="Robot Navigation Training Rights", + display_symbol="NAV26-TRN",instrument_class="SPOT_LICENSE",rights_class="TRAINING", + rights={"actions":["TRAIN"]},supply=1000,rights_passport_id="rp",global_passport_id="gp", + settlement_currency="CAD",jurisdiction="CA",namespace="ACME",transferable=True, + buyer_receives=["AI training right"],buyer_does_not_receive=["dataset ownership"]) + iid=inst["instrument_id"]; now=int(time.time()*1000) + + db=sqlite3.connect(state/"entity_v3_exchange.sqlite") + # settled clean trade + db.execute("INSERT INTO trades VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", + ("t1","venue",iid,None,None,a,issuer,100,18,"ORDER_BOOK","SETTLED",now-1000)) + db.execute("INSERT INTO clearing VALUES(?,?,?,?,?,?,?,?)", + ("t1",a,issuer,1800,"CAD","SETTLED","pay1",1)) + # settled self-trade: counted as market activity but integrity-flagged + db.execute("INSERT INTO trades VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", + ("t2","venue",iid,None,None,b,b,10,99,"ORDER_BOOK","SETTLED",now-900)) + db.execute("INSERT INTO clearing VALUES(?,?,?,?,?,?,?,?)", + ("t2",b,b,990,"CAD","SETTLED","pay2",1)) + # reciprocal flow between A and B + db.execute("INSERT INTO trades VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", + ("t3","venue",iid,None,None,a,b,5,20,"ORDER_BOOK","SETTLED",now-800)) + db.execute("INSERT INTO clearing VALUES(?,?,?,?,?,?,?,?)", + ("t3",a,b,100,"CAD","SETTLED","pay3",1)) + db.execute("INSERT INTO trades VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", + ("t4","venue",iid,None,None,b,a,5,20,"ORDER_BOOK","SETTLED",now-700)) + db.execute("INSERT INTO clearing VALUES(?,?,?,?,?,?,?,?)", + ("t4",b,a,100,"CAD","SETTLED","pay4",1)) + # unsettled trade must not affect observed price/volume + db.execute("INSERT INTO trades VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", + ("t5","venue",iid,None,None,a,issuer,999,777,"ORDER_BOOK","EXECUTED_UNSETTLED",now-600)) + db.execute("INSERT INTO clearing VALUES(?,?,?,?,?,?,?,?)", + ("t5",a,issuer,776223,"CAD","PENDING",None,0)) + db.execute("UPDATE balances SET units=700 WHERE instrument_id=? AND holder=?",(iid,issuer)) + db.execute("INSERT OR REPLACE INTO balances VALUES(?,?,?)",(iid,a,200)) + db.execute("INSERT OR REPLACE INTO balances VALUES(?,?,?)",(iid,b,100)) + db.commit(); db.close() + + intel=intelmod.EntityEconomicIntelligence(state,reg,fabric,gps) + m=intel.instrument_metrics(iid,as_of_ms=now) + assert m["last_settled_price"]==20 + assert m["window_30d"]["trade_count"]==4 + assert m["window_30d"]["volume_units"]==120 + assert m["window_30d"]["self_trade_count"]==1 + assert "SELF_TRADE_ACTIVITY_PRESENT" in m["market_integrity"]["flags"] + assert "RECIPROCAL_COUNTERPARTY_FLOW_PRESENT" in m["market_integrity"]["flags"] + assert m["valuation_boundary"]["observed_prices_value_instrument_rights_not_underlying_dco"] is True + assert m["asset_class"]=="ROBOT_NAVIGATION" + assert m["primary_domain"]=="ROBOTICS" + assert m["active_holder_count"]==3 + + d=intel.dco_rights_demand(obj["object_id"],as_of_ms=now) + assert d["rights_class_rollup"]["TRAINING"]["volume_units_30d"]==120 + roll=intel.economy_rollup(group_by="asset_class",as_of_ms=now) + assert roll["rows"][0]["group"]=="ROBOT_NAVIGATION" + assert roll["rows"][0]["volume_units_30d"]==120 + assert roll["rollup_is_observed_market_activity_not_underlying_asset_valuation"] is True diff --git a/tests/test_entity_economic_intelligence.py b/tests/test_entity_economic_intelligence.py new file mode 100644 index 00000000..98f46681 --- /dev/null +++ b/tests/test_entity_economic_intelligence.py @@ -0,0 +1,95 @@ +from __future__ import annotations +from pathlib import Path +import importlib.util,sys,tempfile,sqlite3,time + +ROOT=Path(__file__).resolve().parents[1] +def load(name,rel): + p=ROOT/rel; spec=importlib.util.spec_from_file_location(name,p) + m=importlib.util.module_from_spec(spec); sys.modules[name]=m; spec.loader.exec_module(m); return m + +class FakePassports: + def __init__(self,kind): self.kind=kind; self.records={} + def bind(self,pid,object_id,controller,rp=None,actions=None,profiles=None): + if self.kind=="rights": + self.records[pid]={"passport_id":pid,"object_id":object_id,"controller_entity_id":controller, + "rights":[{"effect":"ALLOW","actions":list(actions or ["READ","TRAIN","COMMERCIALIZE"])}]} + else: + self.records[pid]={"passport_id":pid,"object_id":object_id,"controller_entity_id":controller, + "rights_passport_id":rp,"profile_stack":{"profile_refs":list(profiles or ["entity-profile:global@1.0","entity-profile:ai@1.0"])}} + def get(self,pid): return dict(self.records[pid]) + def verify(self,p): return {"valid":True} + +def test_settlement_aware_instrument_metrics_and_rollups(): + ident=load("intel_identity","src/01_Core_Runtime/identity/canonical_identity.py") + fabmod=load("intel_fabric","src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py") + exmod=load("intel_exchange","src/31_Profiles/exchange_protocol.py") + marketmod=load("intel_market","src/45_ENTITY_Market/canonical_market_registry.py") + intelmod=load("intel_engine","src/45_ENTITY_Market/economic_intelligence.py") + with tempfile.TemporaryDirectory() as td: + state=Path(td); vault=ident.EntityIdentityVault(state) + issuer=vault.create("Acme Corp","organization")["entity_id"] + b1=vault.create("Buyer One","person")["entity_id"]; b2=vault.create("Buyer Two","person")["entity_id"] + venue_owner=vault.create("Market Operator","organization")["entity_id"] + fabric=fabmod.UniversalTransactionFabric(state,vault); ex=exmod.ExchangeProtocol(state,vault,fabric) + rights=FakePassports("rights"); gps=FakePassports("global") + reg=marketmod.EntityEconomicMarketRegistry(state,vault,fabric,ex,rights,gps) + venue=ex.create_venue(venue_owner,"Test Market","CA",["ORDER_BOOK"],"a"*64) + asset=fabric.register_digital_commodity(issuer,"Weather Data 2026","b"*64, + commodity_class="WEATHER_DATA",measurement_unit="ASSET",object_type="DATASET", + metadata={"asset_subtype":"WEATHER"}) + rights.bind("rp",asset["object_id"],issuer,actions=["READ","TRAIN","COMMERCIALIZE"]) + gps.bind("gp",asset["object_id"],issuer,"rp",profiles=["entity-profile:global@1.0","entity-profile:ai@1.0"]) + ins=reg.create_instrument(issuer,asset["object_id"],instrument_name="Weather Data AI Training Rights", + display_symbol="WX26-TRN",instrument_class="SPOT_LICENSE",rights_class="TRAINING", + rights={"actions":["TRAIN"]},supply=100,rights_passport_id="rp",global_passport_id="gp", + settlement_currency="CAD",jurisdiction="CA",namespace="ACME",transferable=True, + buyer_receives=["AI training right"],buyer_does_not_receive=["dataset ownership"]) + reg.create_listing(issuer,ins["instrument_id"],venue["venue_id"],market_id="ENTITY-PRIMARY", + quote_unit="CAD",minimum_quantity=1,pricing_method="ORDER_BOOK") + now=2_000_000_000_000 + db=sqlite3.connect(state/"entity_v3_exchange.sqlite") + db.execute("UPDATE balances SET units=70 WHERE instrument_id=? AND holder=?",(ins["instrument_id"],issuer)) + db.execute("INSERT OR REPLACE INTO balances(instrument_id,holder,units) VALUES(?,?,?)",(ins["instrument_id"],b1,20)) + db.execute("INSERT OR REPLACE INTO balances(instrument_id,holder,units) VALUES(?,?,?)",(ins["instrument_id"],b2,10)) + trades=[ + ("t1",b1,issuer,10,18,now-2*60*60*1000,1), + ("t2",b2,issuer,8,21,now-10*DAY if False else now-10*24*60*60*1000,0), + ("t3",b1,b1,2,20,now-5*24*60*60*1000,1), + ] + for tid,buyer,seller,qty,price,ts,verified in trades: + db.execute("""INSERT INTO trades(trade_id,venue_id,instrument_id,buy_order_id,sell_order_id,buyer,seller, + quantity,price,execution_model,status,created_at_ms) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)""", + (tid,venue["venue_id"],ins["instrument_id"],None,None,buyer,seller,qty,price,"ORDER_BOOK","SETTLED",ts)) + db.execute("INSERT INTO clearing(trade_id,payer,payee,amount_units,currency,status,payment_ref,external_verified) VALUES(?,?,?,?,?,?,?,?)", + (tid,buyer,seller,qty*price,"CAD","SETTLED","pay-"+tid,verified)) + db.commit(); db.close() + + ep=sqlite3.connect(state/"entity_v3_economic_participation.sqlite") + ep.execute("CREATE TABLE economic_events(event_id TEXT PRIMARY KEY,instrument_id TEXT)") + ep.execute("""CREATE TABLE obligations(event_id TEXT,amount_units INTEGER,currency TEXT,status TEXT, + external_verified INTEGER,basis TEXT)""") + ep.execute("INSERT INTO economic_events VALUES(?,?)",("e1",ins["instrument_id"])) + ep.execute("INSERT INTO obligations VALUES(?,?,?,?,?,?)",("e1",38,"CAD","SETTLED",1,"SECONDARY_ORIGINATOR_ROYALTY")) + ep.commit(); ep.close() + + eng=intelmod.EntityEconomicIntelligence(state,reg,fabric,gps) + m=eng.instrument_metrics(ins["instrument_id"],as_of_ms=now) + assert m["market_identifier"]=="ACME:WX26-TRN" + assert m["last_settled_price"]==18 + assert m["window_24h"]["volume_units"]==10 + assert m["window_30d"]["volume_units"]==20 + assert m["window_30d"]["unique_buyers"]==2 + assert m["window_30d"]["self_trade_count"]==1 + assert "SELF_TRADE_ACTIVITY_PRESENT" in m["market_integrity"]["flags"] + assert m["active_holder_count"]==3 + assert m["issuer_inventory_units"]==70 + assert m["royalties_and_participation"]["externally_verified_amounts_by_currency"]["CAD"]==38 + assert m["valuation_boundary"]["observed_prices_value_instrument_rights_not_underlying_dco"] is True + assert m["asset_class"]=="WEATHER_DATA" + assert m["primary_domain"]=="AI" + + d=eng.dco_rights_demand(asset["object_id"],as_of_ms=now) + assert d["rights_class_rollup"]["TRAINING"]["volume_units_30d"]==20 + roll=eng.economy_rollup(group_by="asset_class",as_of_ms=now) + assert roll["rows"][0]["group"]=="WEATHER_DATA" + assert roll["rows"][0]["volume_units_30d"]==20 diff --git a/tests/test_entity_market_registry.py b/tests/test_entity_market_registry.py new file mode 100644 index 00000000..7764e502 --- /dev/null +++ b/tests/test_entity_market_registry.py @@ -0,0 +1,183 @@ +from __future__ import annotations +from pathlib import Path +import importlib.util,sys,tempfile + +ROOT=Path(__file__).resolve().parents[1] + +def load(name,rel): + p=ROOT/rel; spec=importlib.util.spec_from_file_location(name,p) + m=importlib.util.module_from_spec(spec); sys.modules[name]=m; spec.loader.exec_module(m); return m + +class FakePassports: + def __init__(self,kind): self.kind=kind; self.records={} + def bind(self,pid,object_id,controller,rp=None,actions=None): + if self.kind=="rights": + self.records[pid]={"passport_id":pid,"object_id":object_id,"controller_entity_id":controller, + "rights":[{"effect":"ALLOW","actions":list(actions or ["READ","COPY","DERIVE","TRAIN","EVALUATE","COMMERCIALIZE"])}]} + else: + self.records[pid]={"passport_id":pid,"object_id":object_id,"controller_entity_id":controller, + "rights_passport_id":rp} + def get(self,pid): return dict(self.records[pid]) + def verify(self,p): return {"valid":True} + +def test_multi_issuer_symbols_canonical_ids_listing_sheet_and_portable_package(): + ident=load("market_test_identity","src/01_Core_Runtime/identity/canonical_identity.py") + fabmod=load("market_test_fabric","src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py") + exmod=load("market_test_exchange","src/31_Profiles/exchange_protocol.py") + marketmod=load("market_test_registry","src/45_ENTITY_Market/canonical_market_registry.py") + with tempfile.TemporaryDirectory() as td: + state=Path(td) + vault=ident.EntityIdentityVault(state) + acme=vault.create("Acme Corp","organization")["entity_id"] + jane=vault.create("Jane Smith","person")["entity_id"] + venue_owner=vault.create("Independent Market","organization")["entity_id"] + fabric=fabmod.UniversalTransactionFabric(state,vault) + ex=exmod.ExchangeProtocol(state,vault,fabric) + venue=ex.create_venue(venue_owner,"ENTITY Test Market","CA",["ORDER_BOOK"],"a"*64) + rights=FakePassports("rights"); gps=FakePassports("global") + reg=marketmod.EntityEconomicMarketRegistry(state,vault,fabric,ex,rights,gps) + + a=fabric.register_digital_commodity(acme,"Vision Model 07","1"*64, + commodity_class="AI_MODEL",measurement_unit="ASSET",object_type="MODEL") + j=fabric.register_digital_commodity(jane,"Photography Collection 2026","2"*64, + commodity_class="CREATIVE_COLLECTION",measurement_unit="ASSET",object_type="DOCUMENT") + rights.bind("rp-acme",a["object_id"],acme); gps.bind("gp-acme",a["object_id"],acme,"rp-acme") + rights.bind("rp-jane",j["object_id"],jane); gps.bind("gp-jane",j["object_id"],jane,"rp-jane") + + ia=reg.create_instrument(acme,a["object_id"],instrument_name="Vision Model 07 Commercial Training Rights", + display_symbol="SHARED-TRN",instrument_class="SPOT_LICENSE",rights_class="TRAINING", + rights={"actions":["TRAIN","EVALUATE"]},supply=100,rights_passport_id="rp-acme", + global_passport_id="gp-acme",settlement_currency="CAD",jurisdiction="CA", + namespace="ACME",transferable=True,buyer_receives=["commercial training right"], + buyer_does_not_receive=["ownership of the model","copyright ownership"]) + ij=reg.create_instrument(jane,j["object_id"],instrument_name="Photography Collection Commercial Rights", + display_symbol="SHARED-TRN",instrument_class="SPOT_LICENSE",rights_class="COMMERCIAL", + rights={"actions":["COMMERCIALIZE"]},supply=25,rights_passport_id="rp-jane", + global_passport_id="gp-jane",settlement_currency="CAD",jurisdiction="CA", + namespace="JSMITH",transferable=True,buyer_receives=["defined commercial-use right"], + buyer_does_not_receive=["copyright ownership"]) + + assert ia["market_identifier"]=="ACME:SHARED-TRN" + assert ij["market_identifier"]=="JSMITH:SHARED-TRN" + loaded=reg.instrument(ia["instrument_id"]) + assert loaded["rights"]["actions"]==["EVALUATE","TRAIN"] + assert loaded["settlement_currency"]=="CAD" + assert loaded["eep_status"]=="ACTIVE" + assert ia["instrument_id"]!=ij["instrument_id"] + assert ia["instrument_id"].startswith("entity.instrument:v1:") + assert reg.resolve_symbol("ACME","SHARED-TRN")["instrument_id"]==ia["instrument_id"] + assert reg.resolve_symbol("JSMITH","SHARED-TRN")["instrument_id"]==ij["instrument_id"] + + try: + reg.create_listing(acme,ia["instrument_id"],venue["venue_id"], + market_id="ENTITY-PRIMARY",quote_unit="CAD",trade_mode="ORDER_BOOK", + settlement_method="PAYMENT_VERSUS_RIGHT",minimum_quantity=1,pricing_method="ORDER_BOOK") + assert False,"listing must require signed Asset Disclosure" + except ValueError as exc: + assert "Asset Disclosure" in str(exc) + + disclosure=reg.publish_asset_disclosure( + acme,a["object_id"], + description=("Vision Model 07 is an issuer-controlled AI model DCO prepared for " + "evaluation and commercial training-right transactions."), + purpose="Support governed evaluation and training use without transferring model ownership.", + capabilities=["model evaluation","bounded training use"], + contents=["registered model artifact","canonical provenance references"], + intended_uses=["evaluation","commercial model training under granted rights"], + limitations=["does not transfer copyright","does not transfer underlying model ownership"], + validation_notes=["issuer disclosure is not independent certification"], + release_notes="Initial market disclosure.", + source_refs=["urn:example:vm07-evidence"] + ) + assert disclosure["schema"]=="entity-asset-disclosure-v1" + assert disclosure["version"]==1 + assert disclosure["disclosure_sha256"] + + listing=reg.create_listing(acme,ia["instrument_id"],venue["venue_id"], + market_id="ENTITY-PRIMARY",quote_unit="CAD",trade_mode="ORDER_BOOK", + settlement_method="PAYMENT_VERSUS_RIGHT",minimum_quantity=1,pricing_method="ORDER_BOOK") + assert listing["listing_id"].startswith("entity.listing:v1:") + assert listing["information"]["schema"]=="entity-listing-information-sheet-v2" + assert listing["information"]["sheet_does_not_replace_rights_passport"] is True + assert "ownership of the model" in listing["information"]["buyer_does_not_receive"] + assert listing["information"]["asset_disclosure_id"]==disclosure["disclosure_id"] + assert listing["information"]["asset_description"].startswith("Vision Model 07") + assert listing["information"]["asset_dossier"]["facts"]["object_type"]=="MODEL" + assert listing["information"]["asset_dossier"]["issuer_disclosure"]["purpose"].startswith("Support governed") + assert "## Underlying Asset" in listing["information_markdown"] + assert "### Key capabilities" in listing["information_markdown"] + assert listing["machine_manifest"]["instrument_id"]==ia["instrument_id"] + assert listing["machine_manifest"]["asset_disclosure_sha256"]==disclosure["disclosure_sha256"] + assert ia["status"]=="ACTIVE" + + pkg=reg.export_portable_package(listing["listing_id"],state/"portable") + required={"LISTING_INFORMATION.pdf","LISTING_INFORMATION.md","entity-instrument.json", + "instrument.json","listing.json","rights-passport.json","global-passport.json", + "provenance.json","asset-dossier.json","asset-disclosure.json", + "verification.json","SHA256SUMS","ENTITY_INSTRUMENT.md"} + assert required.issubset(set(pkg["files"])) + assert (state/"portable"/"LISTING_INFORMATION.pdf").read_bytes().startswith(b"%PDF-1.4") + assert "ACME:SHARED-TRN" in (state/"portable"/"ENTITY_INSTRUMENT.md").read_text(encoding="utf-8") + status=reg.status() + assert status["multi_issuer"] is True + assert status["entity_owns_protocol_not_assets"] is True + assert status["listing_information_required"] is True + assert status["signed_asset_disclosures_supported"] is True + assert status["signed_asset_disclosure_required_for_listing"] is True + assert status["listing_information_asset_dossier_required"] is True + +def test_instrument_requires_controlled_dco(): + ident=load("market_test_identity2","src/01_Core_Runtime/identity/canonical_identity.py") + fabmod=load("market_test_fabric2","src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py") + exmod=load("market_test_exchange2","src/31_Profiles/exchange_protocol.py") + marketmod=load("market_test_registry2","src/45_ENTITY_Market/canonical_market_registry.py") + with tempfile.TemporaryDirectory() as td: + state=Path(td); vault=ident.EntityIdentityVault(state) + owner=vault.create("Asset Owner","person")["entity_id"]; attacker=vault.create("Other Issuer","person")["entity_id"] + fabric=fabmod.UniversalTransactionFabric(state,vault); ex=exmod.ExchangeProtocol(state,vault,fabric) + rights=FakePassports("rights"); gps=FakePassports("global") + reg=marketmod.EntityEconomicMarketRegistry(state,vault,fabric,ex,rights,gps) + obj=fabric.register_digital_commodity(owner,"Asset","3"*64,commodity_class="DATA",measurement_unit="ASSET") + rights.bind("rp",obj["object_id"],owner); gps.bind("gp",obj["object_id"],owner,"rp") + try: + reg.create_instrument(attacker,obj["object_id"],instrument_name="Impersonation", + display_symbol="FAKE",instrument_class="SPOT_LICENSE",rights_class="COMMERCIAL", + rights={"actions":["COMMERCIALIZE"]},supply=1,rights_passport_id="rp", + global_passport_id="gp",settlement_currency="CAD",jurisdiction="CA") + assert False,"non-controller issuance must fail" + except PermissionError: + pass + +def test_instrument_actions_cannot_exceed_rights_passport(): + ident=load("market_test_identity3","src/01_Core_Runtime/identity/canonical_identity.py") + fabmod=load("market_test_fabric3","src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py") + exmod=load("market_test_exchange3","src/31_Profiles/exchange_protocol.py") + marketmod=load("market_test_registry3","src/45_ENTITY_Market/canonical_market_registry.py") + with tempfile.TemporaryDirectory() as td: + state=Path(td); vault=ident.EntityIdentityVault(state) + owner=vault.create("Rights Owner","person")["entity_id"] + fabric=fabmod.UniversalTransactionFabric(state,vault); ex=exmod.ExchangeProtocol(state,vault,fabric) + rights=FakePassports("rights"); gps=FakePassports("global") + reg=marketmod.EntityEconomicMarketRegistry(state,vault,fabric,ex,rights,gps) + obj=fabric.register_digital_commodity( + owner,"Restricted Asset","4"*64,commodity_class="DATA",measurement_unit="ASSET" + ) + rights.bind("rp-restricted",obj["object_id"],owner,actions=["READ","TRAIN"]) + gps.bind("gp-restricted",obj["object_id"],owner,"rp-restricted") + try: + reg.create_instrument( + owner,obj["object_id"], + instrument_name="Invalid Commercial Rights", + display_symbol="BAD-COM", + instrument_class="SPOT_LICENSE", + rights_class="COMMERCIAL", + rights={"actions":["COMMERCIALIZE"]}, + supply=1, + rights_passport_id="rp-restricted", + global_passport_id="gp-restricted", + settlement_currency="CAD", + jurisdiction="CA" + ) + assert False,"instrument actions exceeding the Rights Passport must fail" + except PermissionError as exc: + assert "exceed Rights Passport" in str(exc) diff --git a/tests/test_entity_wallet.py b/tests/test_entity_wallet.py index 09dccf5d..78cc954d 100644 --- a/tests/test_entity_wallet.py +++ b/tests/test_entity_wallet.py @@ -4,6 +4,7 @@ HERE=Path(__file__).resolve().parents[1] MOD=HERE/"src"/"42_ENTITY_Wallet"/"canonical_wallet.py" +ONBOARDING_MOD=HERE/"src"/"42_ENTITY_Wallet"/"onboarding.py" def load(): spec=importlib.util.spec_from_file_location("wallet_test_mod",MOD) @@ -82,4 +83,27 @@ def test_offer_does_not_create_market_value(self): self.assertIsNone(p["market"]["last"]) self.assertIsNone(p["market_value_amount_units"]) + def test_first_run_profile_keeps_user_device_and_public_name_authority_separate(self): + spec=importlib.util.spec_from_file_location("wallet_ci_onboarding",ONBOARDING_MOD) + mod=importlib.util.module_from_spec(spec); sys.modules[spec.name]=mod; spec.loader.exec_module(mod) + with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as td: + onboarding=mod.EntityWalletOnboarding(Path(td)) + profile=onboarding.create_profile( + principal_display_name="CI User", + principal_public_name="ci.user", + principal_entity_type="person", + organization_display_name="CI Labs", + organization_public_name="ci.labs", + organization_entity_type="business", + operate_as_organization=True, + ) + auth=onboarding.authenticate(profile) + self.assertTrue(auth["authenticated"]) + self.assertEqual(profile["display_path"],"ci.user.entity → ci.labs.entity") + self.assertNotEqual(profile["principal_entity_id"],profile["active_entity_id"]) + self.assertNotEqual(profile["device_entity_id"],profile["principal_entity_id"]) + self.assertTrue(profile["public_name_is_alias_not_authority"]) + self.assertTrue(profile["device_identity_is_not_user_identity"]) + self.assertTrue(profile["protocol_origin_is_separate_from_user_lineage"]) + if __name__=="__main__": unittest.main() diff --git a/tests/test_exchange_lifecycle.py b/tests/test_exchange_lifecycle.py new file mode 100644 index 00000000..b409313e --- /dev/null +++ b/tests/test_exchange_lifecycle.py @@ -0,0 +1,60 @@ +from __future__ import annotations +from pathlib import Path +import importlib.util,sys,tempfile,hashlib + +ROOT=Path(__file__).resolve().parents[1] +def load(name,rel): + p=ROOT/rel; spec=importlib.util.spec_from_file_location(name,p) + m=importlib.util.module_from_spec(spec); sys.modules[name]=m; spec.loader.exec_module(m); return m + +def test_venue_retirement_is_signed_fail_closed_and_preserves_history(): + ident=load("venue_retire_ident","src/01_Core_Runtime/identity/canonical_identity.py") + fabmod=load("venue_retire_fabric","src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py") + exmod=load("venue_retire_exchange","src/31_Profiles/exchange_protocol.py") + with tempfile.TemporaryDirectory() as td: + state=Path(td); vault=ident.EntityIdentityVault(state) + operator=vault.create("Reference Venue Operator","organization")["entity_id"] + issuer=vault.create("Issuer","organization")["entity_id"] + fabric=fabmod.UniversalTransactionFabric(state,vault); ex=exmod.ExchangeProtocol(state,vault,fabric) + venue=ex.create_venue(operator,"Reference Market","CA",["ORDER_BOOK"],hashlib.sha256(b"policy").hexdigest()) + asset=fabric.register_digital_commodity(issuer,"Asset","1"*64,commodity_class="DATA",measurement_unit="ASSET") + inst=ex.define_instrument(issuer,asset["object_id"],"SPOT_LICENSE",{"actions":["READ"]},10,"CAD") + disc=ex.publish_disclosure(venue["venue_id"],inst["instrument_id"],issuer,"LISTING_INFORMATION","2"*64) + ex.list_instrument(venue["venue_id"],inst["instrument_id"],issuer,disclosure_sha256=disc["content_sha256"]) + try: + ex.retire_venue(operator,venue["venue_id"],"must fail while listing is active") + assert False,"active listing must block venue retirement" + except ValueError: + pass + ex.withdraw_instrument(issuer,inst["instrument_id"],"unused prelaunch instrument") + retirement=ex.retire_venue(operator,venue["venue_id"],"unused test venue") + status=ex.venue_status(venue["venue_id"]) + assert status["status"]=="RETIRED" + assert status["retirement"]["retirement_id"]==retirement["retirement_id"] + assert status["retirement"]["venue_is_not_protocol_authority"] is True + assert ex.instrument_status(inst["instrument_id"])["status"]=="WITHDRAWN" + with ex._db() as db: + assert db.execute("select count(*) from venues where venue_id=?",(venue["venue_id"],)).fetchone()[0]==1 + assert db.execute("select count(*) from listings where instrument_id=?",(inst["instrument_id"],)).fetchone()[0]==1 + +def test_venue_retirement_refuses_open_orders(): + ident=load("venue_retire_ident2","src/01_Core_Runtime/identity/canonical_identity.py") + fabmod=load("venue_retire_fabric2","src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py") + exmod=load("venue_retire_exchange2","src/31_Profiles/exchange_protocol.py") + with tempfile.TemporaryDirectory() as td: + state=Path(td); vault=ident.EntityIdentityVault(state) + operator=vault.create("Operator","organization")["entity_id"] + issuer=vault.create("Issuer","organization")["entity_id"] + buyer=vault.create("Buyer","person")["entity_id"] + fabric=fabmod.UniversalTransactionFabric(state,vault); ex=exmod.ExchangeProtocol(state,vault,fabric) + venue=ex.create_venue(operator,"Market","CA",["ORDER_BOOK"],"a"*64) + asset=fabric.register_digital_commodity(issuer,"Asset","b"*64,commodity_class="DATA",measurement_unit="ASSET") + inst=ex.define_instrument(issuer,asset["object_id"],"SPOT_LICENSE",{"actions":["READ"]},10,"CAD") + disc=ex.publish_disclosure(venue["venue_id"],inst["instrument_id"],issuer,"LISTING_INFORMATION","c"*64) + ex.list_instrument(venue["venue_id"],inst["instrument_id"],issuer,disclosure_sha256=disc["content_sha256"]) + ex.submit_order(venue["venue_id"],inst["instrument_id"],buyer,"BUY",1,5,nonce="open-buy") + try: + ex.retire_venue(operator,venue["venue_id"],"must fail") + assert False,"open order must block retirement" + except ValueError: + pass diff --git a/tests/test_instrument_withdrawal.py b/tests/test_instrument_withdrawal.py new file mode 100644 index 00000000..d831e43c --- /dev/null +++ b/tests/test_instrument_withdrawal.py @@ -0,0 +1,69 @@ +from __future__ import annotations +from pathlib import Path +import importlib.util,sys,tempfile,sqlite3 + +ROOT=Path(__file__).resolve().parents[1] +def load(name,rel): + p=ROOT/rel; spec=importlib.util.spec_from_file_location(name,p) + m=importlib.util.module_from_spec(spec); sys.modules[name]=m; spec.loader.exec_module(m); return m + +def test_signed_instrument_withdrawal_preserves_history_and_deactivates_listing(): + ident=load("withdraw_identity","src/01_Core_Runtime/identity/canonical_identity.py") + fabmod=load("withdraw_fabric","src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py") + exmod=load("withdraw_exchange","src/31_Profiles/exchange_protocol.py") + migmod=load("withdraw_migration","src/45_ENTITY_Market/prelaunch_withdrawal.py") + with tempfile.TemporaryDirectory() as td: + state=Path(td); vault=ident.EntityIdentityVault(state) + issuer=vault.create("Issuer","organization")["entity_id"] + venue_owner=vault.create("Venue","organization")["entity_id"] + fabric=fabmod.UniversalTransactionFabric(state,vault) + ex=exmod.ExchangeProtocol(state,vault,fabric) + obj=fabric.register_digital_commodity(issuer,"Asset","a"*64, + commodity_class="ROBOTICS_ASSET",measurement_unit="ASSET",object_type="SOFTWARE") + venue=ex.create_venue(venue_owner,"Market","CA",["ORDER_BOOK"],"b"*64) + inst=ex.define_instrument(issuer,obj["object_id"],"SPOT_LICENSE",{"actions":["EXECUTE"]},100,"CAD") + disc=ex.publish_disclosure(venue["venue_id"],inst["instrument_id"],issuer,"LISTING_INFORMATION","c"*64) + listing=ex.list_instrument(venue["venue_id"],inst["instrument_id"],issuer, + disclosure_sha256=disc["content_sha256"]) + + migration=migmod.PrelaunchEconomicWithdrawal(state,vault) + audit=migration.audit([obj["object_id"]]) + assert audit["safe_to_withdraw"] is True + result=migration.apply([obj["object_id"]],[],"SUPERSEDED_PRELAUNCH_MODEL") + assert result["status"]=="WITHDRAWN_UNUSED_PRELAUNCH_ECONOMICS" + assert result["eep_wire_protocol_changed"] is False + assert result["historical_records_deleted"] is False + assert result["issuer_signatures"] + assert Path(result["receipt_path"]).is_file() + + db=sqlite3.connect(state/"entity_v3_exchange.sqlite"); db.row_factory=sqlite3.Row + try: + assert db.execute("SELECT COUNT(*) n FROM instruments WHERE instrument_id=?",(inst["instrument_id"],)).fetchone()["n"]==1 + assert db.execute("SELECT status FROM instruments WHERE instrument_id=?",(inst["instrument_id"],)).fetchone()["status"]=="WITHDRAWN" + assert db.execute("SELECT status FROM listings WHERE listing_id=?",(listing["listing_id"],)).fetchone()["status"]=="WITHDRAWN" + finally: db.close() + try: + ex.submit_order(venue["venue_id"],inst["instrument_id"],issuer,"SELL",1,1,nonce="after-withdraw") + assert False,"withdrawn instrument must not accept orders" + except KeyError: + pass + +def test_dco_factory_withdrawal_stops_counting_qualified_issuance_without_deleting_history(): + mod=load("withdraw_factory","src/43_DCO_Factory/canonical_dco_factory.py") + with tempfile.TemporaryDirectory() as td: + f=mod.DCOFactory(td) + plan={"plan_sha256":"a"*64,"physical_instrument_count":20,"total_right_units":1000} + run=f.record_issuance("DCO-TEST",plan,status="QUALIFIED") + before=f.portfolio_summary() + assert before["qualified_physical_instruments"]==20 + w=f.withdraw_issuance(run["issuance_id"],"SUPERSEDED_ASSET_FIRST_ALIGNMENT",evidence_sha256="b"*64) + assert w["history_preserved"] is True + after=f.portfolio_summary() + assert after["qualified_physical_instruments"]==0 + assert after["qualified_right_units"]==0 + assert after["withdrawn_issuance_runs"]==1 + db=sqlite3.connect(Path(td)/"dco_factory"/"entity_dco_factory.sqlite") + try: + assert db.execute("SELECT COUNT(*) FROM issuance_runs").fetchone()[0]==1 + assert db.execute("SELECT COUNT(*) FROM issuance_withdrawals").fetchone()[0]==1 + finally: db.close() diff --git a/tests/test_prelaunch_economic_withdrawal.py b/tests/test_prelaunch_economic_withdrawal.py new file mode 100644 index 00000000..4afb1c25 --- /dev/null +++ b/tests/test_prelaunch_economic_withdrawal.py @@ -0,0 +1,99 @@ +from __future__ import annotations +from pathlib import Path +import importlib.util,json,sqlite3,sys,tempfile + +ROOT=Path(__file__).resolve().parents[1] +def load(name,rel): + p=ROOT/rel; spec=importlib.util.spec_from_file_location(name,p) + m=importlib.util.module_from_spec(spec); sys.modules[name]=m; spec.loader.exec_module(m); return m + +def test_unused_prelaunch_issuance_with_reserve_is_withdrawn_without_history_deletion(): + ident=load("prelaunch_ident","src/01_Core_Runtime/identity/canonical_identity.py") + fabmod=load("prelaunch_fabric","src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py") + exmod=load("prelaunch_exchange","src/31_Profiles/exchange_protocol.py") + eoppmod=load("prelaunch_eopp","src/33_Economic_Participation/economic_participation.py") + factorymod=load("prelaunch_factory","src/43_DCO_Factory/canonical_dco_factory.py") + migmod=load("prelaunch_migration","src/45_ENTITY_Market/prelaunch_withdrawal.py") + with tempfile.TemporaryDirectory() as td: + state=Path(td); vault=ident.EntityIdentityVault(state) + issuer=vault.create("Issuer","organization")["entity_id"] + treasury_entity=vault.create("Issuer Treasury","organization")["entity_id"] + venue_owner=vault.create("Market","organization")["entity_id"] + fabric=fabmod.UniversalTransactionFabric(state,vault); ex=exmod.ExchangeProtocol(state,vault,fabric) + eopp=eoppmod.EconomicParticipationProfile(state,vault); factory=factorymod.DCOFactory(state) + asset=fabric.register_digital_commodity(issuer,"Robot Asset","a"*64,commodity_class="ROBOTICS_ASSET",measurement_unit="ASSET",object_type="ROBOTICS_ASSET") + inst=ex.define_instrument(issuer,asset["object_id"],"SPOT_LICENSE",{"actions":["COMMERCIALIZE"]},100,"CAD",transferable=True) + venue=ex.create_venue(venue_owner,"Prelaunch","CA",["ORDER_BOOK"],"b"*64) + disc=ex.publish_disclosure(venue["venue_id"],inst["instrument_id"],issuer,"LISTING_INFORMATION","c"*64) + ex.list_instrument(venue["venue_id"],inst["instrument_id"],issuer,min_lot=1,tick_size=1,disclosure_sha256=disc["content_sha256"]) + treasury=eopp.create_treasury(issuer,treasury_entity,"Treasury","CA","d"*64) + pol=eopp.define_participation(issuer,treasury["treasury_id"],inst["instrument_id"],100,25,"CAD") + eopp.allocate_eep_reserve(ex,pol["policy_id"]) + + template={"template_id":"ROBOTICS-TEST","profile":"STANDARD","products":[ + {"archetype":"EVALUATION","eep_class":"SPOT_LICENSE","scarcity_class":"CONTROLLED_CAPACITY","initial_supply":1}, + {"archetype":"COMMERCIAL","eep_class":"SPOT_LICENSE","scarcity_class":"FIXED_CAP","initial_supply":100}, + {"archetype":"TRAINING","eep_class":"SPOT_LICENSE","scarcity_class":"CONTROLLED_CAPACITY","initial_supply":10}, + ]} + factory.register_template(template) + plan=factory.plan("ROBOTICS-TEST",dco_code="DCO-TEST",family="ROBOTICS") + factory.create_master(dco_id="DCO-TEST",template_id="ROBOTICS-TEST",family="ROBOTICS", + source_sha256="a"*64,plan=plan,lifecycle="ACTIVE_PRELAUNCH") + issuance=factory.record_issuance("DCO-TEST",plan) + + migration=migmod.PrelaunchEconomicWithdrawal(state,vault) + audit=migration.audit([asset["object_id"]],["DCO-TEST"]) + assert audit["safe_to_withdraw"] is True + assert audit["instrument_count"]==1 + assert audit["economic_event_count"]==0 + assert audit["external_or_unexplained_balances"]==[] + + result=migration.apply([asset["object_id"]],["DCO-TEST"],"Superseded unused prelaunch rights experiment") + assert result["historical_records_deleted"] is False + assert result["eep_wire_protocol_changed"] is False + assert Path(result["receipt_path"]).is_file() + + db=sqlite3.connect(state/"entity_v3_exchange.sqlite"); db.row_factory=sqlite3.Row + assert db.execute("select status from instruments where instrument_id=?",(inst["instrument_id"],)).fetchone()["status"]=="WITHDRAWN" + assert db.execute("select status from listings where instrument_id=?",(inst["instrument_id"],)).fetchone()["status"]=="WITHDRAWN" + assert db.execute("select units from balances where instrument_id=? and holder=?",(inst["instrument_id"],issuer)).fetchone()["units"]==100 + assert db.execute("select units from balances where instrument_id=? and holder=?",(inst["instrument_id"],treasury_entity)).fetchone()["units"]==0 + assert db.execute("select count(*) from instruments where instrument_id=?",(inst["instrument_id"],)).fetchone()[0]==1 + db.close() + + ep=sqlite3.connect(state/"entity_v3_economic_participation.sqlite"); ep.row_factory=sqlite3.Row + assert ep.execute("select status from participation_policies where policy_id=?",(pol["policy_id"],)).fetchone()["status"]=="WITHDRAWN" + assert ep.execute("select count(*) from participation_policy_withdrawals where policy_id=?",(pol["policy_id"],)).fetchone()[0]==1 + assert ep.execute("select count(*) from reserve_allocations where instrument_id=?",(inst["instrument_id"],)).fetchone()[0]==1 + ep.close() + + fd=sqlite3.connect(state/"dco_factory"/"entity_dco_factory.sqlite"); fd.row_factory=sqlite3.Row + assert fd.execute("select lifecycle from dco_masters where dco_id='DCO-TEST'").fetchone()["lifecycle"]=="ACTIVE" + assert fd.execute("select count(*) from issuance_withdrawals where issuance_id=?",(issuance["issuance_id"],)).fetchone()[0]==1 + fd.close() + +def test_withdrawal_refuses_real_market_activity(): + ident=load("prelaunch_ident2","src/01_Core_Runtime/identity/canonical_identity.py") + fabmod=load("prelaunch_fabric2","src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py") + exmod=load("prelaunch_exchange2","src/31_Profiles/exchange_protocol.py") + migmod=load("prelaunch_migration2","src/45_ENTITY_Market/prelaunch_withdrawal.py") + with tempfile.TemporaryDirectory() as td: + state=Path(td); vault=ident.EntityIdentityVault(state) + issuer=vault.create("Issuer","organization")["entity_id"]; buyer=vault.create("Buyer","person")["entity_id"] + venue_owner=vault.create("Market","organization")["entity_id"] + fabric=fabmod.UniversalTransactionFabric(state,vault); ex=exmod.ExchangeProtocol(state,vault,fabric) + asset=fabric.register_digital_commodity(issuer,"Asset","e"*64,commodity_class="DATA",measurement_unit="ASSET") + inst=ex.define_instrument(issuer,asset["object_id"],"SPOT_LICENSE",{"actions":["READ"]},10,"CAD",transferable=True) + venue=ex.create_venue(venue_owner,"Market","CA",["ORDER_BOOK"],"f"*64) + disc=ex.publish_disclosure(venue["venue_id"],inst["instrument_id"],issuer,"LISTING_INFORMATION","1"*64) + ex.list_instrument(venue["venue_id"],inst["instrument_id"],issuer,disclosure_sha256=disc["content_sha256"]) + ex.submit_order(venue["venue_id"],inst["instrument_id"],buyer,"BUY",1,10,nonce="buyer-order") + migration=migmod.PrelaunchEconomicWithdrawal(state,vault) + audit=migration.audit([asset["object_id"]]) + assert audit["safe_to_withdraw"] is False + assert audit["activity_counts"]["orders"]==1 + try: + migration.apply([asset["object_id"]],[],"must refuse") + assert False,"migration must fail closed after market activity" + except RuntimeError: + pass diff --git a/tests/test_v3_economic_participation.py b/tests/test_v3_economic_participation.py index a32ea98e..54dc8c85 100644 --- a/tests/test_v3_economic_participation.py +++ b/tests/test_v3_economic_participation.py @@ -195,7 +195,25 @@ def test_external_settlement_requires_evidence_and_remains_attestation(self): pos=self.profile.position(self.treasury['treasury_id'],self.exchange) self.assertEqual(pos['monetary_obligations']['CAD']['SETTLED'],750) - def test_policy_version_cannot_be_rewritten(self): + def test_policy_withdrawal_stops_future_economics_but_preserves_historical_policy(self): + historical_at=int(self.policy['effective_at_ms']) + withdrawal=self.profile.withdraw_participation( + self.instrument['instrument_id'],self.originator,'unused prelaunch economics withdrawn', + evidence_sha256=hashlib.sha256(b'withdrawal evidence').hexdigest()) + self.assertTrue(withdrawal['historical_obligations_and_reserves_preserved']) + with self.assertRaises(KeyError): + self.profile._active_policy(self.instrument['instrument_id']) + historical=self.profile._active_policy(self.instrument['instrument_id'],historical_at) + self.assertEqual(historical['policy_id'],self.policy['policy_id']) + with self.profile._db() as db: + status=db.execute('SELECT status FROM participation_policies WHERE policy_id=?', + (self.policy['policy_id'],)).fetchone()['status'] + count=db.execute('SELECT COUNT(*) FROM participation_policy_withdrawals WHERE policy_id=?', + (self.policy['policy_id'],)).fetchone()[0] + self.assertEqual(status,'WITHDRAWN') + self.assertEqual(count,1) + + def test_policy_version_cannot_be_rewritten(self): with self.assertRaises(ValueError): self.profile.define_participation(self.originator,self.treasury['treasury_id'],self.instrument['instrument_id'],1000,300,'CAD',version=1) v2=self.profile.define_participation(self.originator,self.treasury['treasury_id'],self.instrument['instrument_id'],1000,200,'CAD',version=2,secondary_royalty_bps=200) diff --git a/tests/test_v3_global_passports.py b/tests/test_v3_global_passports.py index 5a72dce9..9883a82d 100644 --- a/tests/test_v3_global_passports.py +++ b/tests/test_v3_global_passports.py @@ -41,8 +41,9 @@ def test_01_core_market_and_truth_boundaries_preserved(self): s=status_mod.passport_status(); self.assertEqual(s["core_primitives"],["ENTITY","AUTHORITY","RIGHT","EVENT","VALUE"]) self.assertFalse(s["core_semantics_changed"]); self.assertTrue(s["market_engine_preserved"]); self.assertTrue(s["evidence_truth_boundary_preserved"]) - def test_02_builtin_global_and_six_industry_profiles_install(self): - self.assertEqual(len(self.installed),7); self.assertIn("entity-profile:global@1.0",self.installed) + def test_02_builtin_global_and_seven_industry_profiles_install(self): + self.assertEqual(len(self.installed),8); self.assertIn("entity-profile:global@1.0",self.installed) + self.assertIn("entity-profile:software-engineering@1.0",self.installed) self.assertTrue(all(self.profiles.verify(p)["valid"] for p in self.installed.values())) def test_03_profile_stack_requires_global_profile(self): @@ -83,6 +84,13 @@ def test_12_ai_profile_contains_risk_and_supply_chain_mappings(self): def test_13_robotics_profile_contains_ros_and_openrmf(self): names={x["standard"] for x in self.profiles.get("entity-profile:robotics@1.0")["standards"]}; self.assertEqual(names,{"ROS-2","OPEN-RMF"}) + def test_13b_software_engineering_profile_contains_supply_chain_mappings(self): + p=self.profiles.get("entity-profile:software-engineering@1.0") + names={x["standard"] for x in p["standards"]} + self.assertEqual(names,{"SPDX-3","CYCLONEDX","SLSA"}) + self.assertIn("SOFTWARE",p["object_types"]) + self.assertIn("ALGORITHM",p["object_types"]) + def test_14_continuous_ingest_registers_bytes_evidence_rights_and_global_passport(self): f=self.source/"model.py"; f.write_text("print('entity')\n",encoding="utf-8") r=self.ingest.ingest_file(f,self.owner,["entity-profile:global@1.0","entity-profile:ai@1.0"],logical_path="models/model.py") @@ -139,9 +147,9 @@ def test_23_sdk_status_preserves_authority_and_compliance_boundaries(self): status=sdk_mod.EntityGlobalPassportSDK.capability_status(); self.assertTrue(status["sdk_does_not_create_authority"]) self.assertTrue(status["profile_is_not_regulatory_compliance"]); self.assertTrue(status["external_standards_are_mapped_not_redefined"]) - def test_24_industry_package_registry_contains_six_deployable_families(self): + def test_24_industry_package_registry_contains_seven_deployable_families(self): r=package_mod.IndustryImplementationPackageRegistry() - self.assertEqual(r.list_packages(),["ai","defence-public","finance","healthcare","manufacturing","robotics"]) + self.assertEqual(r.list_packages(),["ai","defence-public","finance","healthcare","manufacturing","robotics","software-engineering"]) self.assertTrue(all(r.get(x)["developer_configures_not_redesigns"] if "developer_configures_not_redesigns" in r.get(x) else True for x in r.list_packages())) def test_25_healthcare_fhir_mapping_is_versioned_and_non_normative(self): @@ -157,6 +165,15 @@ def test_26_manufacturing_opcua_mapping_is_executable(self): self.assertEqual(out["descriptor"]["opcua_node_id"],"ns=2;s=Machine1") self.assertTrue(out["external_standard_not_redefined"]) + def test_26b_software_engineering_package_is_deployable(self): + r=package_mod.IndustryImplementationPackageRegistry() + cfg={"organization":"Example Dev","jurisdiction":"CA","authority_source":"controller:1", + "software_governance_policy":"ENTITY_PROVENANCE_BOUND"} + plan=r.deployment_plan("software-engineering",cfg,"engineering_control") + self.assertIn("entity-profile:software-engineering@1.0",plan["profile_refs"]) + self.assertEqual(plan["object_type"],"DATASET") + self.assertIn("DERIVE",plan["rights_actions"]) + def test_27_package_configuration_requires_organization_facts(self): r=package_mod.IndustryImplementationPackageRegistry() with self.assertRaises(ValueError): r.validate_configuration("finance",{}) diff --git a/tests/test_wallet_asset_ingest.py b/tests/test_wallet_asset_ingest.py new file mode 100644 index 00000000..4b0e496a --- /dev/null +++ b/tests/test_wallet_asset_ingest.py @@ -0,0 +1,145 @@ +from __future__ import annotations +from pathlib import Path +import hashlib, importlib.util, json, sqlite3, sys, tempfile + +ROOT=Path(__file__).resolve().parents[1] + +def load(name,rel): + p=ROOT/rel; spec=importlib.util.spec_from_file_location(name,p) + m=importlib.util.module_from_spec(spec); sys.modules[name]=m; spec.loader.exec_module(m); return m + +class FakeIdentity: + def load_manifest(self,eid): return {"entity_id":eid,"display_name":"Test Controller"} + +class FakeFabric: + def __init__(self): + self.obj={"object_id":"obj3-test","controller_entity_id":"BTG","object_type":"SOFTWARE", + "title":"Robot Asset","content_sha256":"a"*64, + "descriptor":{"digital_commodity":True,"commodity_class":"ROBOTICS_ASSET","measurement_unit":"ASSET"}} + def get_object(self,oid): return dict(self.obj) + +class FakeOrigin: + default_release_ref="entity-release:v3.4.3" + def passport_binding(self,ref): + return {"release_ref":ref,"release_tag":"v3.4.3", + "origin_lineage_id":"entity-origin:shawn-btg-entity@1.0", + "root_originator_entity_id":"SHAWN","steward_entity_id":"BTG", + "protocol_entity_id":"ENTITY","asset_provenance_is_separate_from_protocol_origin":True, + "protocol_origin_does_not_transfer_authority":True, + "protocol_origin_does_not_transfer_user_asset_ownership":True, + "economic_participation_requires_explicit_terms":True,"automatic_protocol_royalty_bps":0} + +class FakePassports: + def __init__(self,origin): self.origin=origin + def get(self,pid): + return {"passport_id":pid,"rights_passport_id":"rp-test", + "profile_stack":{"profile_refs":["entity-profile:global@1.0","entity-profile:robotics@1.0"]}, + "protocol_origin":self.origin.passport_binding("entity-release:v3.4.3"), + "btdu_binding":{"schema":"entity-btdu-passport-binding-v1","content_sha256":"a"*64}} + def verify(self,gp): return {"valid":True} + +class FakePackages: + def list_packages(self): return ["robotics"] + +class FakeSDK: + def __init__(self): self.last=None + def ingest_package_file(self,path,controller,package,config,asset_kind,**kwargs): + self.last={"path":str(path),"controller":controller,"package":package,"config":config, + "asset_kind":asset_kind,"kwargs":kwargs} + return {"object_id":"obj3-test","global_passport_id":"gp-test"} + +class FakeUniverse: + def __init__(self): self.content_sha256=None + def ingest_file(self,path,receipt,**kwargs): + self.content_sha256=hashlib.sha256(Path(path).read_bytes()).hexdigest() + return {"object_ref":"btdu:test","content_sha256":self.content_sha256} + def passport_binding(self,ref): + return {"schema":"entity-btdu-passport-binding-v1","universe_root":"u","object_ref":ref, + "content_sha256":self.content_sha256,"sovereign_entity_id":"BTG", + "protocol_origin_is_not_asset_provenance":True,"topology_does_not_create_ownership":True, + "topology_does_not_create_economic_entitlement":True,"automatic_protocol_royalty_bps":0} + def close(self): pass + +class FakeCLI: + def __init__(self): + self.identity=FakeIdentity(); self.fabric=FakeFabric(); self.origin=FakeOrigin() + self.passports=FakePassports(self.origin); self.packages=FakePackages(); self.sdk=FakeSDK() + def runtime(self,state,require_current_release=False): + assert require_current_release is True + return self.identity,self.fabric,None,self.origin,self.passports,self.packages,self.sdk,{"verified":True} + def _btdu_for_controller(self,state,identity,controller): return FakeUniverse() + def _btdu_receipt(self,identity,controller,path,logical): return {"body":{"scope":"BTDU_WRITE"},"signature":{}} + +def test_wallet_ingest_uses_canonical_robotics_pipeline_without_market_issue(): + m=load("wallet_asset_ingest_test","src/42_ENTITY_Wallet/asset_ingest.py") + with tempfile.TemporaryDirectory() as td: + src=Path(td)/"robot.py"; src.write_text("print('robot')",encoding="utf-8") + cli=FakeCLI(); ing=m.WalletAssetIngestor(Path(td)/"state",cli) + r=ing.ingest_file("BTG",src,title="Robot Asset",package="robotics",asset_kind="software", + jurisdiction="CA-BC",commodity_class="ROBOTICS_ASSET", + measurement_unit="ASSET",authority_basis="CONTROLLER_ENTITY:BTG") + assert r["global_passport_verified"] is True + assert r["market_instruments_created"] is False + assert r["listing_created"] is False + assert r["price_created"] is False + assert r["protocol_origin"]["origin_lineage_id"]=="entity-origin:shawn-btg-entity@1.0" + assert "entity-profile:robotics@1.0" in r["profile_refs"] + assert cli.sdk.last["kwargs"]["digital_commodity"] is True + assert cli.sdk.last["kwargs"]["btdu_binding"]["object_ref"]=="btdu:test" + +def test_dco_preserves_real_asset_type(): + identity=load("wallet_asset_identity","src/01_Core_Runtime/identity/canonical_identity.py") + fabric_mod=load("wallet_asset_fabric","src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py") + with tempfile.TemporaryDirectory() as td: + vault=identity.EntityIdentityVault(td); owner=vault.create("Owner","organization")["entity_id"] + fabric=fabric_mod.UniversalTransactionFabric(td,vault) + obj=fabric.register_digital_commodity(owner,"Algorithm","b"*64,commodity_class="ROBOTICS_ALGORITHM", + measurement_unit="ASSET",object_type="SOFTWARE") + assert obj["object_type"]=="SOFTWARE" + assert obj["descriptor"]["digital_commodity"] is True + +def test_wallet_lists_digital_assets_separately_from_positions(): + m=load("wallet_assets_view_test","src/42_ENTITY_Wallet/canonical_wallet.py") + with tempfile.TemporaryDirectory() as td: + state=Path(td); dbp=state/"entity_v3"/"universal_fabric.sqlite"; dbp.parent.mkdir(parents=True) + db=sqlite3.connect(dbp) + db.execute("""CREATE TABLE objects(object_id TEXT PRIMARY KEY,controller_entity_id TEXT,object_type TEXT, + title TEXT,content_sha256 TEXT,descriptor_json TEXT,status TEXT,created_at_ms INTEGER,signature_json TEXT)""") + db.execute("""CREATE TABLE provenance_edges(edge_id TEXT PRIMARY KEY,parent_object_id TEXT,child_object_id TEXT, + relation TEXT,contribution_bps INTEGER,evidence_json TEXT,actor_entity_id TEXT,created_at_ms INTEGER,signature_json TEXT)""") + descriptor=json.dumps({"digital_commodity":True,"commodity_class":"ROBOTICS","measurement_unit":"ASSET"}) + db.execute("INSERT INTO objects VALUES(?,?,?,?,?,?,?,?,?)", + ("obj3-r","OWNER","SOFTWARE","Robotics Asset","a"*64,descriptor,"ACTIVE",1,"{}")) + db.commit(); db.close() + w=m.EntityEconomicWallet(state); wr=w.ensure_wallet("OWNER","Owner Wallet") + snap=w.snapshot(wr["wallet_id"]) + assert len(snap["assets"])==1 + assert snap["assets"][0]["title"]=="Robotics Asset" + assert snap["positions"]==[] + assert snap["asset_model"]["ingest_does_not_issue_market_instruments"] is True + assert snap["asset_model"]["protocol_lineage_and_asset_provenance_are_separate"] is True + + +def test_retired_dco_factory_master_is_not_current_wallet_asset(): + m=load("wallet_retired_asset_view_test","src/42_ENTITY_Wallet/canonical_wallet.py") + with tempfile.TemporaryDirectory() as td: + state=Path(td); dbp=state/"entity_v3"/"universal_fabric.sqlite"; dbp.parent.mkdir(parents=True) + db=sqlite3.connect(dbp) + db.execute("""CREATE TABLE objects(object_id TEXT PRIMARY KEY,controller_entity_id TEXT,object_type TEXT, + title TEXT,content_sha256 TEXT,descriptor_json TEXT,status TEXT,created_at_ms INTEGER,signature_json TEXT)""") + db.execute("""CREATE TABLE provenance_edges(edge_id TEXT PRIMARY KEY,parent_object_id TEXT,child_object_id TEXT, + relation TEXT,contribution_bps INTEGER,evidence_json TEXT,actor_entity_id TEXT,created_at_ms INTEGER,signature_json TEXT)""") + desc=json.dumps({"digital_commodity":True,"commodity_class":"ROBOTICS_PILOT","measurement_unit":"ASSET"}) + db.execute("INSERT INTO objects VALUES(?,?,?,?,?,?,?,?,?)", + ("obj-pilot","OWNER","DATASET","Historical Pilot","f"*64,desc,"ACTIVE",1,"{}")) + db.commit(); db.close() + fdbp=state/"dco_factory"/"entity_dco_factory.sqlite"; fdbp.parent.mkdir(parents=True) + fdb=sqlite3.connect(fdbp) + fdb.execute("""CREATE TABLE dco_masters(dco_id TEXT,template_id TEXT,family TEXT,source_sha256 TEXT, + provenance_root TEXT,semantic_fingerprint TEXT,version TEXT,lifecycle TEXT, + master_json TEXT,created_at_ms INTEGER)""") + fdb.execute("INSERT INTO dco_masters VALUES(?,?,?,?,?,?,?,?,?,?)", + ("DCO-PILOT","T","ROBOTICS","f"*64,None,None,"1","RETIRED_EXPERIMENT","{}",1)) + fdb.commit(); fdb.close() + w=m.EntityEconomicWallet(state); wr=w.ensure_wallet("OWNER","Owner Wallet") + assert w.snapshot(wr["wallet_id"])["assets"]==[] diff --git a/tests/test_wallet_lineage_resolution.py b/tests/test_wallet_lineage_resolution.py new file mode 100644 index 00000000..0a3c355b --- /dev/null +++ b/tests/test_wallet_lineage_resolution.py @@ -0,0 +1,109 @@ +from __future__ import annotations +from pathlib import Path +import importlib.util,json,sqlite3,sys,tempfile + +ROOT=Path(__file__).resolve().parents[1] +SHAWN="ent2-shawn"; BTG="ent2-btg"; ENTITY="ent2-entity" + +def load(): + p=ROOT/"src"/"42_ENTITY_Wallet"/"lineage_resolver.py" + spec=importlib.util.spec_from_file_location("wallet_lineage_test",p) + m=importlib.util.module_from_spec(spec); sys.modules[spec.name]=m; spec.loader.exec_module(m); return m + +def write_manifest(state,eid,name): + p=state/"identity"/"manifests"; p.mkdir(parents=True,exist_ok=True) + (p/f"{eid}.json").write_text(json.dumps({"entity_id":eid,"display_name":name}),encoding="utf-8") + +def test_robotics_lineage_resolves_shawn_btg_entity_robotics_without_transferring_ownership(): + m=load() + with tempfile.TemporaryDirectory() as td: + state=Path(td) + write_manifest(state,SHAWN,"Shawn Blackmore") + write_manifest(state,BTG,"Blackmore Technology Group Limited") + write_manifest(state,ENTITY,"ENTITY") + + db=sqlite3.connect(state/"entity_protocol_origin.sqlite") + db.execute("CREATE TABLE origin_chains(lineage_id TEXT PRIMARY KEY,body_sha256 TEXT,body_json TEXT,signatures_json TEXT)") + origin={"lineage_id":"entity-origin:shawn-btg-entity@1.0","root_originator_entity_id":SHAWN, + "steward_entity_id":BTG,"protocol_entity_id":ENTITY} + db.execute("INSERT INTO origin_chains VALUES(?,?,?,?)",(origin["lineage_id"],"o"*64,json.dumps(origin),"{}")) + db.commit(); db.close() + + db=sqlite3.connect(state/"entity_v3_4_global_profiles.sqlite") + db.execute("""CREATE TABLE profile_variants(profile_ref TEXT,body_sha256 TEXT,profile_id TEXT,version TEXT, + kind TEXT,body_json TEXT,issuer_entity_id TEXT,signature_json TEXT,created_at_ms INTEGER, + active_canonical INTEGER)""") + for i,(ref,kind) in enumerate([ + ("entity-profile:global@1.0","GLOBAL"),("entity-profile:robotics@1.0","INDUSTRY"), + ("entity-profile:ai@1.0","INDUSTRY"),("entity-profile:manufacturing@1.0","INDUSTRY")]): + body={"profile_ref":ref,"profile_id":ref.split("@")[0],"kind":kind} + db.execute("INSERT INTO profile_variants VALUES(?,?,?,?,?,?,?,?,?,?)", + (ref,str(i)*64,body["profile_id"],"1.0",kind,json.dumps(body),ENTITY,"{}",i,1)) + db.commit(); db.close() + + db=sqlite3.connect(state/"entity_v3_4_global_passports.sqlite") + db.execute("""CREATE TABLE global_passports(passport_id TEXT,object_id TEXT,controller_entity_id TEXT, + version TEXT,body_sha256 TEXT,body_json TEXT,signature_json TEXT,created_at_ms INTEGER)""") + gp={"passport_id":"gp1","object_id":"obj-child","controller_entity_id":BTG,"version":"1.1", + "profile_stack":{"profile_refs":["entity-profile:global@1.0","entity-profile:robotics@1.0", + "entity-profile:ai@1.0","entity-profile:manufacturing@1.0"]}, + "protocol_origin":{"origin_lineage_id":"entity-origin:shawn-btg-entity@1.0", + "root_originator_entity_id":SHAWN,"steward_entity_id":BTG, + "protocol_entity_id":ENTITY}, + "btdu_binding":{"schema":"entity-btdu-passport-binding-v1","object_ref":"btdu:child"}} + db.execute("INSERT INTO global_passports VALUES(?,?,?,?,?,?,?,?)", + ("gp1","obj-child",BTG,"1.1","g"*64,json.dumps(gp),"{}",10)) + db.commit(); db.close() + + fp=state/"entity_v3"/"universal_fabric.sqlite"; fp.parent.mkdir(parents=True) + db=sqlite3.connect(fp) + db.execute("""CREATE TABLE objects(object_id TEXT,controller_entity_id TEXT,object_type TEXT,title TEXT, + content_sha256 TEXT,descriptor_json TEXT,status TEXT,created_at_ms INTEGER,signature_json TEXT)""") + db.execute("""CREATE TABLE provenance_edges(edge_id TEXT,parent_object_id TEXT,child_object_id TEXT, + relation TEXT,contribution_bps INTEGER,evidence_json TEXT,actor_entity_id TEXT, + created_at_ms INTEGER,signature_json TEXT)""") + db.execute("INSERT INTO objects VALUES(?,?,?,?,?,?,?,?,?)", + ("obj-parent",BTG,"DATASET","BIRFR-1","a"*64,"{}","ACTIVE",1,"{}")) + db.execute("INSERT INTO objects VALUES(?,?,?,?,?,?,?,?,?)", + ("obj-child",BTG,"SOFTWARE","Failure Detector","b"*64, + json.dumps({"digital_commodity":True}),"ACTIVE",2,"{}")) + db.execute("INSERT INTO provenance_edges VALUES(?,?,?,?,?,?,?,?,?)", + ("edge1","obj-parent","obj-child","TRAINED_AND_DERIVED_FROM",0,"{}",BTG,3,"{}")) + db.commit(); db.close() + + resolver=m.WalletLineageResolver(state) + result=resolver.resolve({"object_id":"obj-child","controller_entity_id":BTG}) + assert result["protocol_lineage"]["display_path"]=="Shawn Blackmore → Blackmore Technology Group Limited → ENTITY → Robotics" + assert result["protocol_lineage"]["composed_domain_profiles"]==["AI","Manufacturing"] + assert result["protocol_lineage"]["profile_composition_is_not_parent_child_lineage"] is True + assert result["protocol_lineage"]["passport_protocol_origin_embedded"] is True + assert result["asset_lineage"]["controller_entity_id"]==BTG + assert result["asset_lineage"]["parents"][0]["title"]=="BIRFR-1" + assert result["asset_lineage"]["protocol_origin_does_not_transfer_asset_ownership"] is True + assert result["capability_bindings"]["BTDU"]["bound"] is True + assert result["capability_bindings"]["ADAM"]["sovereign_authority"] is False + assert result["capability_bindings"]["NIKI"]["sovereign_authority"] is False + +def test_legacy_passport_without_embedded_origin_is_flagged_for_supersession(): + m=load() + with tempfile.TemporaryDirectory() as td: + state=Path(td) + for eid,name in ((SHAWN,"Shawn Blackmore"),(BTG,"Blackmore Technology Group Limited"),(ENTITY,"ENTITY")): + write_manifest(state,eid,name) + db=sqlite3.connect(state/"entity_protocol_origin.sqlite") + db.execute("CREATE TABLE origin_chains(lineage_id TEXT PRIMARY KEY,body_sha256 TEXT,body_json TEXT,signatures_json TEXT)") + body={"lineage_id":"entity-origin:shawn-btg-entity@1.0","root_originator_entity_id":SHAWN, + "steward_entity_id":BTG,"protocol_entity_id":ENTITY} + db.execute("INSERT INTO origin_chains VALUES(?,?,?,?)",(body["lineage_id"],"o"*64,json.dumps(body),"{}")); db.commit(); db.close() + db=sqlite3.connect(state/"entity_v3_4_global_passports.sqlite") + db.execute("""CREATE TABLE global_passports(passport_id TEXT,object_id TEXT,controller_entity_id TEXT, + version TEXT,body_sha256 TEXT,body_json TEXT,signature_json TEXT,created_at_ms INTEGER)""") + gp={"passport_id":"legacy","object_id":"obj","controller_entity_id":BTG,"version":"1.0", + "profile_stack":{"profile_refs":[]},"protocol_origin":None} + db.execute("INSERT INTO global_passports VALUES(?,?,?,?,?,?,?,?)",("legacy","obj",BTG,"1.0","x"*64,json.dumps(gp),"{}",1)); db.commit(); db.close() + fp=state/"entity_v3"/"universal_fabric.sqlite"; fp.parent.mkdir(parents=True) + db=sqlite3.connect(fp); db.execute("""CREATE TABLE provenance_edges(edge_id TEXT,parent_object_id TEXT,child_object_id TEXT, + relation TEXT,contribution_bps INTEGER,evidence_json TEXT,actor_entity_id TEXT,created_at_ms INTEGER,signature_json TEXT)"""); db.commit(); db.close() + result=m.WalletLineageResolver(state).resolve({"object_id":"obj","controller_entity_id":BTG}) + assert result["protocol_lineage"]["resolved_from_canonical_origin_registry"] is True + assert result["protocol_lineage"]["needs_passport_lineage_supersession"] is True diff --git a/tests/test_wallet_onboarding.py b/tests/test_wallet_onboarding.py new file mode 100644 index 00000000..2afcc04f --- /dev/null +++ b/tests/test_wallet_onboarding.py @@ -0,0 +1,123 @@ +from pathlib import Path +import importlib.util +import sys +import tempfile + + +ROOT = Path(__file__).resolve().parents[1] + + +def load(name, rel): + path = ROOT / rel + spec = importlib.util.spec_from_file_location(name, path) + mod = importlib.util.module_from_spec(spec) + sys.modules[name] = mod + spec.loader.exec_module(mod) + return mod + + +def test_clean_install_creates_user_lineage_device_binding_and_crypto_login(): + mod = load("wallet_onboarding_test", "src/42_ENTITY_Wallet/onboarding.py") + with tempfile.TemporaryDirectory() as td: + state = Path(td) + onboarding = mod.EntityWalletOnboarding(state) + + device = onboarding.ensure_device_identity() + device_manifest = onboarding.identity.load_manifest(device["device_entity_id"]) + assert device_manifest["entity_type"] == "system" + assert device_manifest["metadata"]["device_information_is_not_user_identity"] is True + assert onboarding.status()["profile_count"] == 0 + + profile = onboarding.create_profile( + principal_display_name="Jane Smith", + principal_public_name="jane.smith.entity", + principal_entity_type="person", + organization_display_name="Acme Corp", + organization_public_name="acme.entity", + organization_entity_type="business", + operate_as_organization=True, + ) + + assert profile["display_path"] == "jane.smith.entity → acme.entity" + assert profile["principal_entity_id"] != profile["active_entity_id"] + assert profile["device_entity_id"] == device["device_entity_id"] + assert profile["public_name_is_alias_not_authority"] is True + assert profile["device_identity_is_not_user_identity"] is True + assert profile["protocol_origin_is_separate_from_user_lineage"] is True + + principal = onboarding.identity.load_manifest(profile["principal_entity_id"]) + active = onboarding.identity.load_manifest(profile["active_entity_id"]) + assert principal["display_name"] == "Jane Smith" + assert principal["aliases"] == ["jane.smith.entity"] + assert active["display_name"] == "Acme Corp" + assert active["aliases"] == ["acme.entity"] + + auth = onboarding.authenticate(profile) + assert auth["authenticated"] is True + assert auth["active_entity_id"] == profile["active_entity_id"] + assert auth["mode"] == "DEVICE_BOUND_SIGNING_KEY" + assert auth["password_used"] is False + + rels = onboarding.authority.active_relationships(profile["active_entity_id"], profile["principal_entity_id"]) + assert any( + r["relationship_type"] == "SOVEREIGN_AUTHORITY" + and r["scope"].get("wallet_lineage_parent") is True + for r in rels + ) + device_rels = onboarding.authority.active_relationships(profile["principal_entity_id"], profile["device_entity_id"]) + assert any( + r["relationship_type"] == "POSSESSION" + and r["scope"].get("device_possession_not_identity") is True + for r in device_rels + ) + + loaded = onboarding.active_profile() + assert loaded["profile_id"] == profile["profile_id"] + assert onboarding.status()["first_run_required"] is False + + +def test_adopt_existing_preserves_existing_entity_ids_and_aliases(): + mod = load("wallet_onboarding_test_existing", "src/42_ENTITY_Wallet/onboarding.py") + with tempfile.TemporaryDirectory() as td: + state = Path(td) + onboarding = mod.EntityWalletOnboarding(state) + principal = onboarding.identity.create("Existing Person", "person", aliases=["existing.person.entity"]) + org = onboarding.identity.create("Existing Org", "business", aliases=["existing.org.entity"]) + device = onboarding.identity.create( + "Existing Device", + "system", + aliases=["existing.device.entity"], + metadata={"software_device_identity": True, "device_information_is_not_user_identity": True}, + ) + + profile = onboarding.adopt_existing( + principal_entity_id=principal["entity_id"], + active_entity_id=org["entity_id"], + device_entity_id=device["entity_id"], + ) + + assert profile["migrated_existing"] is True + assert profile["principal_entity_id"] == principal["entity_id"] + assert profile["active_entity_id"] == org["entity_id"] + assert profile["device_entity_id"] == device["entity_id"] + assert profile["display_path"] == "existing.person.entity → existing.org.entity" + assert onboarding.authenticate(profile)["authenticated"] is True + + +def test_public_names_are_aliases_and_local_conflicts_do_not_replace_entity_authority(): + mod = load("wallet_onboarding_test_alias", "src/42_ENTITY_Wallet/onboarding.py") + with tempfile.TemporaryDirectory() as td: + state = Path(td) + onboarding = mod.EntityWalletOnboarding(state) + a = onboarding.identity.create("A", "person", aliases=["shared.entity"]) + b = onboarding.identity.create("B", "person", aliases=["shared.entity"]) + + ca = onboarding.ensure_public_name(a["entity_id"], "shared.entity") + cb = onboarding.ensure_public_name(b["entity_id"], "shared.entity") + + assert ca["public_name"] == "shared.entity" + assert cb["public_name"] == "shared.entity" + assert ca["entity_id"] != cb["entity_id"] + assert cb["name_claim"]["conflict_state"] == "CONFLICT" + assert ca["entity_id_is_authority"] is True + assert cb["entity_id_is_authority"] is True diff --git a/tools/build_entity_wallet_desktop.py b/tools/build_entity_wallet_desktop.py new file mode 100644 index 00000000..8b5158d5 --- /dev/null +++ b/tools/build_entity_wallet_desktop.py @@ -0,0 +1,78 @@ +from __future__ import annotations +from pathlib import Path +import ast, importlib.util, os, subprocess, sys + +ROOT=Path(__file__).resolve().parents[1] +ENTRY=ROOT/"tools"/"entity_wallet_desktop.py" + +DATA_PATHS=[ + "ENTITY_CURRENT_RELEASE_ORIGIN.json", + "protocol/origin", + "protocol/profiles", + "tools/entity_v3_4_cli.py", + "src/01_Core_Runtime/identity/canonical_identity.py", + "src/11_ADAM/full_runtime", + "src/22_Sovereign_Domain", + "genesis_master/04_Entity_Registry/relationships/canonical_sovereign_authority.py", + "src/30_Universal_Transaction_Fabric/canonical_universal_fabric.py", + "src/31_Profiles/exchange_protocol.py", + "src/36_Adoption_Layer", + "src/37_Verifiable_Reality", + "src/38_Global_Passports", + "src/39_Implementation_Packages", + "src/40_BTDU", + "src/42_ENTITY_Wallet", + "src/45_ENTITY_Market", + "sdk/global_passport_sdk", +] + +def _python_sources(): + yield ENTRY + for rel in DATA_PATHS: + p=ROOT/rel + if p.is_file() and p.suffix.lower()==".py": + yield p + elif p.is_dir(): + yield from p.rglob("*.py") + +def discover_hidden_imports(): + """Collect importable dependencies referenced by modules shipped as runtime data. + + PyInstaller cannot see imports inside files loaded later via importlib, so + those dependencies must be made explicit at build time. + """ + modules={"hmac","cryptography.hazmat.primitives.asymmetric.ed25519", + "cryptography.hazmat.primitives.serialization"} + for path in _python_sources(): + try: + tree=ast.parse(path.read_text(encoding="utf-8-sig")) + except Exception: + continue + for node in ast.walk(tree): + if isinstance(node,ast.Import): + modules.update(alias.name for alias in node.names) + elif isinstance(node,ast.ImportFrom) and node.level==0 and node.module: + modules.add(node.module) + available=[] + for name in sorted(modules): + try: + if importlib.util.find_spec(name) is not None: + available.append(name) + except (ImportError,AttributeError,ModuleNotFoundError,ValueError): + pass + return available + +def main(): + sep=os.pathsep + cmd=[sys.executable,"-m","PyInstaller","--noconfirm","--clean","--windowed", + "--name","ENTITY-Wallet"] + for module in discover_hidden_imports(): + cmd += ["--hidden-import",module] + for rel in DATA_PATHS: + src=ROOT/rel; dest=str(Path(rel).parent if src.is_file() else Path(rel)) + cmd += ["--add-data",f"{src}{sep}{dest}"] + cmd.append(str(ENTRY)) + subprocess.run(cmd,cwd=ROOT,check=True) + print(ROOT/"dist") + +if __name__=="__main__": main() diff --git a/tools/entity_v3_4_cli.py b/tools/entity_v3_4_cli.py index f4392c3b..0e51db11 100644 --- a/tools/entity_v3_4_cli.py +++ b/tools/entity_v3_4_cli.py @@ -21,6 +21,9 @@ def load(name,rel): btdu_mod=load("cli_v342_btdu","src/40_BTDU/canonical_btdu.py") ORIGIN_BUNDLE=ROOT/"protocol"/"origin"/"ENTITY_PROTOCOL_ORIGIN_BUNDLE.json" CURRENT_RELEASE_TAG="v3.4.3"; CURRENT_RELEASE_REF="entity-release:"+CURRENT_RELEASE_TAG +SIGNED_PROFILE_PATHS=[ + ROOT/"protocol"/"profiles"/"ENTITY_SOFTWARE_ENGINEERING_PROFILE.json", +] def _current_release_path(explicit=None): candidates=[pathlib.Path(explicit)] if explicit else [] @@ -32,10 +35,30 @@ def runtime(state,release_origin_path=None,require_current_release=False): evidence=evidence_mod.EvidenceRegistry(state,identity); rights=rights_mod.RightsPassportRegistry(state,identity,fabric) profiles=profile_mod.GlobalProfileRegistry(state,identity); origin=origin_mod.ProtocolOriginRegistry(state,identity) bundle=read_json(ORIGIN_BUNDLE); origin_status=origin.install_bundle(bundle,profiles) + origin_record=origin.get_origin(origin_status["origin_lineage_id"]) + protocol_entity_id=origin_record["body"]["protocol_entity_id"] + signed_profile_imports=[] + for profile_path in SIGNED_PROFILE_PATHS: + if not profile_path.is_file(): + continue + record=read_json(profile_path) + if record.get("issuer_entity_id")!=protocol_entity_id: + raise RuntimeError("signed built-in profile issuer is not canonical ENTITY protocol identity") + imported=profiles.import_signed(record,allow_semantic_rebind=False) + signed_profile_imports.append({ + "profile_ref":imported["profile_ref"], + "body_sha256":imported["body_sha256"], + "issuer_entity_id":imported["issuer_entity_id"], + }) current_path=_current_release_path(release_origin_path); current_status=None if current_path: current_status=origin.install_current_release(read_json(current_path),CURRENT_RELEASE_TAG) if require_current_release and not current_status: raise RuntimeError("v3.4.3 current-release origin attestation required; supply --release-origin or use the signed release package") - origin_status=dict(origin_status,current_release_origin=current_status,current_release_origin_path=str(current_path) if current_path else None) + origin_status=dict( + origin_status, + current_release_origin=current_status, + current_release_origin_path=str(current_path) if current_path else None, + signed_builtin_profiles=signed_profile_imports, + ) passports=global_mod.GlobalPassportRegistry(state,identity,fabric,rights,profiles,origin,required_release_ref=CURRENT_RELEASE_REF if require_current_release else None) ingestion=ingest_mod.ContinuousProvenanceEngine(state,identity,fabric,evidence,rights,passports,origin.default_release_ref) packages=package_mod.IndustryImplementationPackageRegistry(); sdk=sdk_mod.EntityGlobalPassportSDK(profiles,passports,ingestion,packages) @@ -45,14 +68,23 @@ def emit(value): print(json.dumps(value,indent=2,sort_keys=True)) def _btdu_for_controller(state,identity,controller_entity_id): controller=str(controller_entity_id); identity.load_manifest(controller) + # BTG canonical assets live in the one canonical BTG universe rooted at + # shawn.blackmore.entity. Asset source/controller/rights-holder remain explicit + # per-object and are not replaced by the universe sovereign identity. + canonical_ids={ + btdu_mod.BTDU_CANONICAL_OWNER_ENTITY_ID, + btdu_mod.BTG_STEWARD_ENTITY_ID, + btdu_mod.ENTITY_PROTOCOL_ENTITY_ID, + } + universe_owner=btdu_mod.BTDU_CANONICAL_OWNER_ENTITY_ID if controller in canonical_ids else controller def verifier(record): try: body=dict(record["body"]); sig=dict(record["signature"]) if body.get("scope")!="BTDU_WRITE" or body.get("actor_entity_id")!=controller: return False return identity.verify_signature(identity.load_manifest(controller),body,sig) except Exception: return False - return btdu_mod.BlackmoreTechnologyDataUniverse(pathlib.Path(state)/"btdu"/controller, - authorization_verifier=verifier,sovereign_entity_id=controller, + return btdu_mod.BlackmoreTechnologyDataUniverse(pathlib.Path(state)/"btdu"/universe_owner, + authorization_verifier=verifier,sovereign_entity_id=universe_owner, protocol_entity_id=btdu_mod.ENTITY_PROTOCOL_ENTITY_ID,steward_entity_id=btdu_mod.BTG_STEWARD_ENTITY_ID) def _btdu_receipt(identity,controller_entity_id,path,logical_path): diff --git a/tools/entity_wallet_desktop.py b/tools/entity_wallet_desktop.py new file mode 100644 index 00000000..307e76f5 --- /dev/null +++ b/tools/entity_wallet_desktop.py @@ -0,0 +1,2055 @@ +from __future__ import annotations +from pathlib import Path +import argparse, html, importlib.util, json, os, sqlite3, sys, uuid + +from PySide6 import QtCore, QtGui, QtWidgets + +APP_NAME = "ENTITY Wallet" +VERSION = "3.4.3" + +ROOT_BG = "#090B0E" +SIDEBAR_BG = "#0D1014" +PANEL_BG = "#11151A" +PANEL_2 = "#151A20" +BORDER = "#242B34" +TEXT = "#F3F0E8" +MUTED = "#8F99A7" +ACCENT = "#C9A96A" +ACCENT_DARK = "#7D6642" +GREEN = "#43D39E" +RED = "#FF6B78" +YELLOW = "#E3BC64" + +def startup_stylesheet() -> str: + return f""" + * {{ + font-family: "Segoe UI"; + font-size: 10pt; + color: {TEXT}; + }} + QDialog, QMessageBox {{ background: {ROOT_BG}; }} + QLabel {{ background: transparent; }} + QLabel[class="brand"] {{ font-size: 26pt; font-weight: 800; letter-spacing: 2px; color: {TEXT}; }} + QLabel[class="eyebrow"] {{ color: {ACCENT}; font-size: 8pt; font-weight: 700; letter-spacing: 1px; }} + QLabel[class="dialogTitle"] {{ font-size: 18pt; font-weight: 700; }} + QLabel[class="muted"] {{ color: {MUTED}; }} + QLabel[class="chip"] {{ + background: {PANEL_2}; border: 1px solid {BORDER}; border-radius: 6px; padding: 10px; color: {MUTED}; + }} + QLabel[class="tickerPreview"] {{ + background: {SIDEBAR_BG}; border: 1px solid {ACCENT_DARK}; border-radius: 6px; + padding: 10px; color: {ACCENT}; font-family: Consolas; font-weight: 700; + }} + QFrame[class="panel"] {{ background: {PANEL_BG}; border: 1px solid {BORDER}; border-radius: 8px; }} + QPushButton {{ + background: {PANEL_2}; border: 1px solid {BORDER}; border-radius: 6px; + padding: 8px 13px; font-weight: 600; + }} + QPushButton:hover {{ border-color: {ACCENT_DARK}; background: #1A2027; }} + QPushButton[primary="true"] {{ + background: {ACCENT}; color: #0C0E11; border-color: {ACCENT}; font-weight: 800; + }} + QLineEdit, QComboBox {{ + background: {PANEL_2}; border: 1px solid {BORDER}; border-radius: 5px; padding: 8px; + selection-background-color: {ACCENT_DARK}; + }} + QLineEdit:focus, QComboBox:focus {{ border-color: {ACCENT}; }} + QCheckBox {{ spacing: 8px; }} + """ + + +def repo_root() -> Path: + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + return Path(sys._MEIPASS) + return Path(__file__).resolve().parents[1] + +def load_module(name: str, relative: str): + path = repo_root() / relative + spec = importlib.util.spec_from_file_location(name, path) + if spec is None or spec.loader is None: + raise RuntimeError(f"Cannot load {path}") + mod = importlib.util.module_from_spec(spec) + sys.modules[name] = mod + spec.loader.exec_module(mod) + return mod + +CLI = load_module("entity_wallet_v343_cli", "tools/entity_v3_4_cli.py") +WALLET = load_module("entity_wallet_model", "src/42_ENTITY_Wallet/canonical_wallet.py") +INGEST = load_module("entity_wallet_ingest", "src/42_ENTITY_Wallet/asset_ingest.py") +EXCHANGE = load_module("entity_wallet_exchange", "src/31_Profiles/exchange_protocol.py") +MARKET = load_module("entity_wallet_market", "src/45_ENTITY_Market/canonical_market_registry.py") +INTEL = load_module("entity_wallet_intelligence", "src/45_ENTITY_Market/economic_intelligence.py") +ONBOARDING = load_module("entity_wallet_onboarding", "src/42_ENTITY_Wallet/onboarding.py") + +def default_state() -> Path: + env = os.environ.get("ENTITY_STATE_DIR") + if env: + return Path(env).expanduser() + if sys.platform == "win32": + return Path(os.environ.get("LOCALAPPDATA", Path.home())) / "Blackmore Technology Group" / "ENTITY" + if sys.platform == "darwin": + return Path.home() / "Library" / "Application Support" / "Blackmore Technology Group" / "ENTITY" + return Path.home() / ".local" / "share" / "blackmore-technology-group" / "entity" + +def shorten(value: str | None, left: int = 10, right: int = 6) -> str: + v = str(value or "") + if len(v) <= left + right + 3: + return v + return v[:left] + "…" + v[-right:] + +def manifests(state: Path) -> list[dict]: + out = [] + root = state / "identity" / "manifests" + if not root.is_dir(): + return out + for p in sorted(root.glob("*.json")): + try: + d = json.loads(p.read_text(encoding="utf-8")) + out.append({"entity_id": d.get("entity_id"), "display_name": d.get("display_name") or d.get("entity_id")}) + except Exception: + pass + return [x for x in out if x["entity_id"]] + +class Backend: + def __init__(self, state: Path, entity_id: str): + self.state = state + self.entity_id = entity_id + identity, fabric, profiles, origin, passports, packages, sdk, origin_status = CLI.runtime(state) + self.identity = identity + self.fabric = fabric + self.profiles = profiles + self.origin = origin + self.passports = passports + self.packages = packages + self.sdk = sdk + self.origin_status = origin_status + self.rights = self.sdk.ingestion.rights + self.identity_manifest = self.identity.load_manifest(entity_id) + self.wallet = WALLET.EntityEconomicWallet(state) + self.exchange = EXCHANGE.ExchangeProtocol(state, self.identity, self.fabric) + self.market_registry = MARKET.EntityEconomicMarketRegistry( + state, self.identity, self.fabric, self.exchange, self.rights, self.passports + ) + self.intelligence = INTEL.EntityEconomicIntelligence( + state, self.market_registry, self.fabric, self.passports + ) + self.ingestor = INGEST.WalletAssetIngestor(state, CLI) + self.wallet_record = self.wallet.ensure_wallet( + entity_id, self.identity_manifest.get("display_name", "ENTITY") + " Wallet" + ) + + def snapshot(self): + return self.wallet.snapshot(self.wallet_record["wallet_id"]) + + def domains(self): + return ["general"] + self.packages.list_packages() + + def asset_kinds(self, domain): + return self.ingestor.asset_kinds(domain) + + def namespace_info(self) -> dict: + return self.market_registry.suggest_namespace(self.entity_id) + + def namespace(self) -> str | None: + return self.namespace_info().get("namespace") + + def asset_record(self, object_id: str) -> dict: + return self.fabric.get_object(object_id) + + def asset_disclosure(self, object_id: str) -> dict | None: + return self.market_registry.latest_asset_disclosure(object_id) + + def publish_asset_disclosure(self, object_id: str, options: dict) -> dict: + return self.market_registry.publish_asset_disclosure( + self.entity_id, object_id, + description=options["description"], + purpose=options.get("purpose", ""), + capabilities=options.get("capabilities", []), + contents=options.get("contents", []), + intended_uses=options.get("intended_uses", []), + limitations=options.get("limitations", []), + dependencies=options.get("dependencies", []), + validation_notes=options.get("validation_notes", []), + release_notes=options.get("release_notes", ""), + source_refs=options.get("source_refs", []), + ) + + def instrument_rights_profile(self, object_id: str) -> dict: + gp = self.latest_global_passport(object_id) + rp = self.rights.get(gp["rights_passport_id"]) + allowed = {} + prohibited = {} + other = {} + for rule in rp.get("rights") or []: + effect = str(rule.get("effect") or "").upper() + for action in rule.get("actions") or []: + token = str(action).strip().upper() + if not token: + continue + detail = { + "action": token, + "effect": effect, + "conditions": dict(rule.get("conditions") or {}), + "obligations": list(rule.get("obligations") or []), + "right_refs": list(rule.get("right_refs") or []), + } + if effect == "ALLOW": + allowed[token] = detail + elif effect == "PROHIBIT": + prohibited[token] = detail + else: + other[token] = detail + return { + "object_id": str(object_id), + "global_passport_id": gp["passport_id"], + "rights_passport_id": rp["passport_id"], + "allowed_actions": sorted(allowed), + "prohibited_actions": sorted(prohibited), + "other_actions": sorted(other), + "allowed": allowed, + "prohibited": prohibited, + "other": other, + } + + def latest_global_passport(self, object_id: str) -> dict: + dbp = self.state / "entity_v3_4_global_passports.sqlite" + db = sqlite3.connect(dbp) + db.row_factory = sqlite3.Row + try: + row = db.execute( + """SELECT passport_id FROM global_passports WHERE object_id=? + ORDER BY created_at_ms DESC, passport_id DESC LIMIT 1""", + (str(object_id),), + ).fetchone() + finally: + db.close() + if not row: + raise KeyError("asset has no Global Passport") + return self.passports.get(row["passport_id"]) + + def instruments(self) -> list[dict]: + if not self.market_registry.path.exists(): + return [] + db = sqlite3.connect(self.market_registry.path) + db.row_factory = sqlite3.Row + try: + rows = db.execute( + """SELECT instrument_id FROM instrument_packages + WHERE issuer_entity_id=? AND status='ACTIVE' + ORDER BY created_at_ms, instrument_id""", + (self.entity_id,), + ).fetchall() + finally: + db.close() + return [self.market_registry.instrument(r["instrument_id"]) for r in rows] + + def instrument_map_by_asset(self) -> dict[str, list[dict]]: + out: dict[str, list[dict]] = {} + if not self.market_registry.path.exists(): + return out + db = sqlite3.connect(self.market_registry.path) + db.row_factory = sqlite3.Row + try: + rows = db.execute( + """SELECT instrument_id,underlying_dco_id FROM instrument_packages + WHERE issuer_entity_id=? AND status='ACTIVE' + ORDER BY created_at_ms""", (self.entity_id,) + ).fetchall() + finally: + db.close() + for r in rows: + out.setdefault(r["underlying_dco_id"], []).append(self.market_registry.instrument(r["instrument_id"])) + return out + + def venues(self) -> list[dict]: + if not self.exchange.path.exists(): + return [] + db = sqlite3.connect(self.exchange.path) + db.row_factory = sqlite3.Row + try: + rows = db.execute( + "SELECT venue_id,name,jurisdiction,status FROM venues WHERE status='ACTIVE' ORDER BY name" + ).fetchall() + finally: + db.close() + return [dict(r) for r in rows] + + def market(self) -> list[dict]: + rows = self.market_registry.active_market() + if not rows: + return [] + db = sqlite3.connect(self.exchange.path) + db.row_factory = sqlite3.Row + try: + out = [] + for row in rows: + e = db.execute( + "SELECT settlement_currency FROM instruments WHERE instrument_id=? AND status='ACTIVE'", + (row["instrument_id"],), + ).fetchone() + if not e: + continue + d = dict(row) + d["settlement_currency"] = e["settlement_currency"] + d["market"] = self.wallet._market(db, d["instrument_id"]) + out.append(d) + return out + finally: + db.close() + + def economic_metrics(self) -> list[dict]: + seen = set() + out = [] + for row in self.market_registry.active_market(): + iid = row["instrument_id"] + if iid in seen: + continue + seen.add(iid) + out.append(self.intelligence.instrument_metrics(iid)) + return out + + def economy_rollup(self, group_by="asset_class") -> dict: + return self.intelligence.economy_rollup(group_by=group_by) + + def ingest(self, path, options): + return self.ingestor.ingest_file( + self.entity_id, path, + title=options["title"], package=options["domain"], + asset_kind=options.get("asset_kind") or None, + jurisdiction=options.get("jurisdiction", ""), + authority_basis=options["authority"], + commodity_class=options["class"], measurement_unit=options["unit"], + version=options.get("version") or "1.0", + previous_object_id=options.get("parent_object_id") or None, + ) + + def create_instrument(self, object_id: str, options: dict) -> dict: + gp = self.latest_global_passport(object_id) + rights_profile = self.instrument_rights_profile(object_id) + requested = {str(x).strip().upper() for x in options.get("actions") or [] if str(x).strip()} + allowed = set(rights_profile["allowed_actions"]) + prohibited = set(rights_profile["prohibited_actions"]) + if not requested: + raise ValueError("At least one Rights Passport action must be selected.") + if requested & prohibited: + bad = ", ".join(sorted(requested & prohibited)) + raise PermissionError(f"Selected action(s) are prohibited by the Rights Passport: {bad}") + if not requested.issubset(allowed): + missing = ", ".join(sorted(requested - allowed)) + permitted = ", ".join(rights_profile["allowed_actions"]) or "none" + raise PermissionError( + f"Selected action(s) are not granted by the Rights Passport: {missing}. " + f"Permitted actions: {permitted}" + ) + return self.market_registry.create_instrument( + self.entity_id, object_id, + instrument_name=options["name"], display_symbol=options["symbol"], + instrument_class=options["instrument_class"], rights_class=options["rights_class"], + rights={"actions": options["actions"]}, supply=options["supply"], + rights_passport_id=gp["rights_passport_id"], global_passport_id=gp["passport_id"], + settlement_currency=options["currency"], jurisdiction=options["jurisdiction"], + series=options["series"], fungibility=options["fungibility"], + divisibility=options["divisibility"], transferable=options["transferable"], + duration_ms=options.get("duration_ms"), + transfer_rules={"wallet_defined": True}, + economic_terms={"market_value_created_by_issuance": False}, + royalty_terms={"automatic_protocol_royalty_bps": 0}, + buyer_receives=options["buyer_receives"], + buyer_does_not_receive=options["buyer_does_not_receive"], + evidence_refs=gp.get("evidence_refs") or [], + namespace=options.get("namespace") or None, + ) + + def create_listing(self, instrument_id: str, options: dict) -> dict: + return self.market_registry.create_listing( + self.entity_id, instrument_id, options["venue_id"], + market_id=options["market_id"], quote_unit=options["quote_unit"], + trade_mode=options["trade_mode"], settlement_method=options["settlement_method"], + minimum_quantity=options["minimum_quantity"], + quantity_precision=options["quantity_precision"], price_precision=options["price_precision"], + pricing_method=options["pricing_method"], listing_series=options["listing_series"], + tick_size=options["tick_size"], + ) + + def submit_order(self, row: dict, side: str, quantity: int, limit_price: int): + venue = row.get("venue_id") + if not venue: + raise ValueError("This instrument has no active market listing.") + order = self.exchange.submit_order( + venue, row["instrument_id"], self.entity_id, side, quantity, limit_price, + nonce="wallet-" + uuid.uuid4().hex, tif="GTC" + ) + trades = self.exchange.match_order_book(venue, row["instrument_id"]) + return {"order": order, "trades": trades} + +class Ui: + @staticmethod + def label(text="", cls=""): + w = QtWidgets.QLabel(text) + if cls: + w.setProperty("class", cls) + return w + + @staticmethod + def button(text, primary=False): + b = QtWidgets.QPushButton(text) + b.setCursor(QtCore.Qt.PointingHandCursor) + b.setProperty("primary", primary) + return b + + @staticmethod + def table(headers): + t = QtWidgets.QTableWidget(0, len(headers)) + t.setHorizontalHeaderLabels(headers) + t.verticalHeader().setVisible(False) + t.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + t.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) + t.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + t.setShowGrid(False) + t.setAlternatingRowColors(False) + t.setWordWrap(False) + t.setTextElideMode(QtCore.Qt.ElideRight) + t.verticalHeader().setDefaultSectionSize(38) + t.horizontalHeader().setStretchLastSection(True) + t.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) + t.setSortingEnabled(False) + return t + + @staticmethod + def fill_table(table, rows): + table.setRowCount(0) + for row in rows: + i = table.rowCount() + table.insertRow(i) + for j, value in enumerate(row): + item = QtWidgets.QTableWidgetItem("" if value is None else str(value)) + item.setData(QtCore.Qt.UserRole, value) + table.setItem(i, j, item) + for i in range(table.rowCount()): + table.setRowHeight(i, 38) + +class MetricCard(QtWidgets.QFrame): + def __init__(self, title: str): + super().__init__() + self.setProperty("class", "metricCard") + lay = QtWidgets.QVBoxLayout(self) + lay.setContentsMargins(18, 15, 18, 15) + lay.setSpacing(5) + title_label = Ui.label(title.upper(), "eyebrow") + self.value = Ui.label("—", "metric") + self.note = Ui.label("", "muted") + lay.addWidget(title_label) + lay.addWidget(self.value) + lay.addWidget(self.note) + +class EmptyState(QtWidgets.QFrame): + def __init__(self, title, body): + super().__init__() + self.setProperty("class", "empty") + lay = QtWidgets.QVBoxLayout(self) + lay.setContentsMargins(28, 28, 28, 28) + lay.addWidget(Ui.label(title, "emptyTitle")) + body_label = Ui.label(body, "muted") + body_label.setWordWrap(True) + lay.addWidget(body_label) + +class EntitySetupDialog(QtWidgets.QDialog): + def __init__(self, parent, onboarding): + super().__init__(parent) + self.onboarding = onboarding + self.profile = None + self.setWindowTitle("Create your ENTITY identity") + self.resize(760, 690) + self.setMinimumSize(690, 610) + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(28, 24, 28, 24) + layout.setSpacing(14) + + layout.addWidget(Ui.label("WELCOME TO ENTITY", "eyebrow")) + layout.addWidget(Ui.label("Create your sovereign identity", "dialogTitle")) + intro = Ui.label( + "This creates your own cryptographic ENTITY identity and public .entity name. " + "Your device receives a separate device identity; device information never becomes your human or company identity.", + "muted" + ) + intro.setWordWrap(True) + layout.addWidget(intro) + + principal_box = QtWidgets.QFrame() + principal_box.setProperty("class", "panel") + pf = QtWidgets.QFormLayout(principal_box) + pf.setContentsMargins(18, 18, 18, 18) + pf.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow) + + self.principal_type = QtWidgets.QComboBox() + self.principal_type.addItems(["person", "business", "organization", "project", "community", "family"]) + self.principal_name = QtWidgets.QLineEdit() + self.principal_name.setPlaceholderText("Jane Smith") + self.principal_public = QtWidgets.QLineEdit() + self.principal_public.setPlaceholderText("jane.smith") + self.principal_preview = Ui.label("jane.smith.entity", "tickerPreview") + pf.addRow("Identity type", self.principal_type) + pf.addRow("Public display name", self.principal_name) + pf.addRow("Public ENTITY name", self.principal_public) + pf.addRow("Address preview", self.principal_preview) + layout.addWidget(principal_box) + + self.add_org = QtWidgets.QCheckBox("Create an operating organization/business under this identity") + self.add_org.setChecked(False) + layout.addWidget(self.add_org) + + self.org_box = QtWidgets.QFrame() + self.org_box.setProperty("class", "panel") + of = QtWidgets.QFormLayout(self.org_box) + of.setContentsMargins(18, 18, 18, 18) + of.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow) + self.org_type = QtWidgets.QComboBox() + self.org_type.addItems(["business", "organization", "project"]) + self.org_name = QtWidgets.QLineEdit() + self.org_name.setPlaceholderText("Acme Corporation") + self.org_public = QtWidgets.QLineEdit() + self.org_public.setPlaceholderText("acme") + self.org_preview = Ui.label("acme.entity", "tickerPreview") + self.operate_as_org = QtWidgets.QCheckBox("Open the wallet as this operating entity") + self.operate_as_org.setChecked(True) + of.addRow("Entity type", self.org_type) + of.addRow("Organization name", self.org_name) + of.addRow("Public ENTITY name", self.org_public) + of.addRow("Address preview", self.org_preview) + of.addRow("", self.operate_as_org) + layout.addWidget(self.org_box) + self.org_box.setVisible(False) + + self.lineage_preview = Ui.label("LINEAGE —", "chip") + self.lineage_preview.setWordWrap(True) + layout.addWidget(self.lineage_preview) + + note = Ui.label( + "Public names are human-readable aliases. The immutable ENTITY ID and signatures remain authoritative. " + "A local name conflict does not let one user impersonate another.", + "muted" + ) + note.setWordWrap(True) + layout.addWidget(note) + layout.addStretch() + + buttons = QtWidgets.QHBoxLayout() + cancel = Ui.button("Exit") + create = Ui.button("Create ENTITY and wallet", True) + cancel.clicked.connect(self.reject) + create.clicked.connect(self.create_identity) + buttons.addWidget(cancel) + buttons.addStretch() + buttons.addWidget(create) + layout.addLayout(buttons) + + self.add_org.toggled.connect(self.org_box.setVisible) + self.add_org.toggled.connect(self.update_preview) + self.principal_public.textChanged.connect(self.update_preview) + self.org_public.textChanged.connect(self.update_preview) + self.update_preview() + + @staticmethod + def _address(text): + raw = str(text or "").strip().lower() + if raw.endswith(".entity"): + raw = raw[:-7] + return (raw + ".entity") if raw else "—" + + def update_preview(self): + p = self._address(self.principal_public.text()) + self.principal_preview.setText(p) + if self.add_org.isChecked(): + o = self._address(self.org_public.text()) + self.org_preview.setText(o) + self.lineage_preview.setText(f"LINEAGE {p} → {o}") + else: + self.lineage_preview.setText(f"LINEAGE {p}") + + def create_identity(self): + if not self.principal_name.text().strip() or not self.principal_public.text().strip(): + QtWidgets.QMessageBox.warning(self, "ENTITY setup", "Display name and public ENTITY name are required.") + return + if self.add_org.isChecked() and (not self.org_name.text().strip() or not self.org_public.text().strip()): + QtWidgets.QMessageBox.warning( + self, "ENTITY setup", + "Organization name and public ENTITY name are required when an operating organization is enabled." + ) + return + try: + self.profile = self.onboarding.create_profile( + principal_display_name=self.principal_name.text().strip(), + principal_public_name=self.principal_public.text().strip(), + principal_entity_type=self.principal_type.currentText(), + organization_display_name=self.org_name.text().strip() if self.add_org.isChecked() else None, + organization_public_name=self.org_public.text().strip() if self.add_org.isChecked() else None, + organization_entity_type=self.org_type.currentText(), + operate_as_organization=self.operate_as_org.isChecked(), + ) + auth = self.onboarding.authenticate(self.profile) + if not auth.get("authenticated"): + raise RuntimeError("new identity was created but device-bound authentication failed") + self.accept() + except Exception as exc: + QtWidgets.QMessageBox.critical(self, "ENTITY setup failed", str(exc)) + + +class EntityLoginDialog(QtWidgets.QDialog): + def __init__(self, parent, onboarding): + super().__init__(parent) + self.onboarding = onboarding + self.profile = None + self.create_new = False + self.profiles = onboarding.list_profiles() + self.setWindowTitle("Unlock ENTITY") + self.resize(620, 390) + self.setMinimumWidth(580) + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(28, 24, 28, 24) + layout.setSpacing(14) + + layout.addWidget(Ui.label("ENTITY", "brand")) + layout.addWidget(Ui.label("SOVEREIGN WALLET ACCESS", "eyebrow")) + layout.addWidget(Ui.label("Unlock your local ENTITY identity", "dialogTitle")) + note = Ui.label( + "ENTITY uses possession of your local signing identity plus the bound device identity. " + "The operating-system account name is not treated as your ENTITY identity.", + "muted" + ) + note.setWordWrap(True) + layout.addWidget(note) + + self.selector = QtWidgets.QComboBox() + for profile in self.profiles: + active = onboarding.identity.load_manifest(profile["active_entity_id"]) + self.selector.addItem( + f"{profile['display_path']} · {active['display_name']}", + profile["profile_id"] + ) + layout.addWidget(self.selector) + + self.detail = Ui.label("", "chip") + self.detail.setWordWrap(True) + layout.addWidget(self.detail) + self.selector.currentIndexChanged.connect(self.update_detail) + self.update_detail() + layout.addStretch() + + buttons = QtWidgets.QHBoxLayout() + create = Ui.button("Create another identity") + unlock = Ui.button("Unlock wallet", True) + create.clicked.connect(self.choose_create) + unlock.clicked.connect(self.unlock) + buttons.addWidget(create) + buttons.addStretch() + buttons.addWidget(unlock) + layout.addLayout(buttons) + + def update_detail(self): + idx = self.selector.currentIndex() + if idx < 0 or idx >= len(self.profiles): + self.detail.setText("No local wallet profile.") + return + p = self.profiles[idx] + self.detail.setText( + f"LINEAGE {p['display_path']}\n" + f"LOGIN device-bound signing key\n" + f"ACTIVE ENTITY {shorten(p['active_entity_id'], 14, 10)}" + ) + + def choose_create(self): + self.create_new = True + self.accept() + + def unlock(self): + idx = self.selector.currentIndex() + if idx < 0 or idx >= len(self.profiles): + return + profile = self.profiles[idx] + auth = self.onboarding.authenticate(profile) + if not auth.get("authenticated"): + QtWidgets.QMessageBox.critical( + self, "ENTITY unlock failed", + "The local signing identity or bound device could not authenticate this wallet.\n\n" + + str(auth.get("reason") or "authentication failed") + + ("\n\nRecovery or device re-binding is required." if auth.get("recovery_or_rebind_required") else "") + ) + return + self.onboarding.set_active(profile["profile_id"]) + self.profile = profile + self.accept() + + +class AssetDisclosureDialog(QtWidgets.QDialog): + def __init__(self, parent, asset: dict, current: dict | None = None): + super().__init__(parent) + self.setWindowTitle("Asset Information") + self.resize(820, 820) + self.setMinimumSize(720, 680) + self.result_data = None + current = current or {} + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(22, 20, 22, 20) + layout.addWidget(Ui.label("SIGNED ASSET DISCLOSURE", "dialogTitle")) + title = Ui.label(asset.get("title") or asset.get("object_id"), "tickerHero") + title.setWordWrap(True) + layout.addWidget(title) + note = Ui.label( + "This is the buyer-facing description of the underlying DCO. It is signed and versioned. " + "It describes the asset; it does not expand the Rights Passport or manufacture market value.", + "muted" + ) + note.setWordWrap(True) + layout.addWidget(note) + + scroll = QtWidgets.QScrollArea() + scroll.setWidgetResizable(True) + host = QtWidgets.QWidget() + form = QtWidgets.QFormLayout(host) + form.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow) + + def area(value="", height=86): + w = QtWidgets.QPlainTextEdit(str(value or "")) + w.setMinimumHeight(height) + return w + + def lines(values): + return "\n".join(str(x) for x in (values or [])) + + self.description = area(current.get("description"), 130) + self.purpose = area(current.get("purpose"), 80) + self.capabilities = area(lines(current.get("capabilities")), 90) + self.contents = area(lines(current.get("contents")), 90) + self.intended = area(lines(current.get("intended_uses")), 90) + self.limitations = area(lines(current.get("limitations")), 90) + self.dependencies = area(lines(current.get("dependencies")), 80) + self.validation = area(lines(current.get("validation_notes")), 90) + self.release_notes = area(current.get("release_notes"), 80) + self.source_refs = area(lines(current.get("source_refs")), 70) + + for label, widget in [ + ("Detailed asset description", self.description), + ("Purpose / problem addressed", self.purpose), + ("Key capabilities\n(one per line)", self.capabilities), + ("Included content / components\n(one per line)", self.contents), + ("Intended uses\n(one per line)", self.intended), + ("Known limitations / exclusions\n(one per line)", self.limitations), + ("Dependencies / prerequisites\n(one per line)", self.dependencies), + ("Validation / qualification notes\n(one per line)", self.validation), + ("Version / release notes", self.release_notes), + ("Supporting source references\n(one per line)", self.source_refs), + ]: + form.addRow(label, widget) + scroll.setWidget(host) + layout.addWidget(scroll, 1) + + boundary = Ui.label( + "ENTITY records this as an issuer statement. Evidence and passport references remain independently inspectable.", + "muted" + ) + boundary.setWordWrap(True) + layout.addWidget(boundary) + + buttons = QtWidgets.QHBoxLayout() + buttons.addStretch() + cancel = Ui.button("Cancel") + publish = Ui.button("Sign and publish asset information", True) + cancel.clicked.connect(self.reject) + publish.clicked.connect(self.accept_data) + buttons.addWidget(cancel) + buttons.addWidget(publish) + layout.addLayout(buttons) + + @staticmethod + def _items(editor): + return [x.strip() for x in editor.toPlainText().splitlines() if x.strip()] + + def accept_data(self): + description = self.description.toPlainText().strip() + if len(description) < 20: + QtWidgets.QMessageBox.warning( + self, "Asset description", + "Provide a meaningful buyer-facing asset description before publishing." + ) + return + self.result_data = { + "description": description, + "purpose": self.purpose.toPlainText().strip(), + "capabilities": self._items(self.capabilities), + "contents": self._items(self.contents), + "intended_uses": self._items(self.intended), + "limitations": self._items(self.limitations), + "dependencies": self._items(self.dependencies), + "validation_notes": self._items(self.validation), + "release_notes": self.release_notes.toPlainText().strip(), + "source_refs": self._items(self.source_refs), + } + self.accept() + + +class ListingInformationDialog(QtWidgets.QDialog): + def __init__(self, parent, market_identifier: str, markdown: str): + super().__init__(parent) + self.setWindowTitle(f"Listing Information Sheet · {market_identifier}") + self.resize(1060, 820) + self.setMinimumSize(820, 620) + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(18, 16, 18, 16) + head = QtWidgets.QHBoxLayout() + head.addWidget(Ui.label("LISTING INFORMATION SHEET", "dialogTitle")) + head.addStretch() + head.addWidget(Ui.label(market_identifier, "tickerHero")) + layout.addLayout(head) + browser = QtWidgets.QTextBrowser() + browser.setProperty("class", "detail") + browser.setOpenExternalLinks(False) + browser.setMarkdown(markdown) + layout.addWidget(browser, 1) + foot = QtWidgets.QHBoxLayout() + foot.addWidget(Ui.label( + "Buyer-facing disclosure snapshot · canonical passports and instrument records remain authoritative.", + "muted" + )) + foot.addStretch() + close = Ui.button("Close", True) + close.clicked.connect(self.accept) + foot.addWidget(close) + layout.addLayout(foot) + + +class InstrumentDialog(QtWidgets.QDialog): + ACTION_META = { + "TRAIN": ("Training", "TRN", "TRAINING"), + "FINE_TUNE": ("Fine-tuning", "FTN", "FINE_TUNING"), + "INFER": ("Inference", "INF", "INFERENCE"), + "EVALUATE": ("Evaluation", "EVL", "EVALUATION"), + "BENCHMARK": ("Benchmark", "BNCH", "BENCHMARK"), + "CERTIFY": ("Certification", "CERT", "CERTIFICATION"), + "COMPUTE": ("Compute-to-data", "CMP", "COMPUTE"), + "DERIVE": ("Derivative", "DER", "DERIVATIVE"), + "OEM_DEPLOY": ("OEM deployment", "OEM", "OEM"), + "COMMERCIALIZE": ("Commercialization", "COM", "COMMERCIAL"), + "EXECUTE": ("Execution", "EXE", "EXECUTION"), + "CONTROL": ("Control", "CTL", "CONTROL"), + "MODIFY": ("Modification", "MOD", "MODIFICATION"), + "TRANSFER": ("Transfer", "XFR", "TRANSFER"), + "COPY": ("Copy", "CPY", "COPY"), + "READ": ("Read/access", "ACC", "ACCESS"), + "INSPECT": ("Inspection", "INSP", "INSPECTION"), + } + + def __init__(self, parent, asset, namespace, rights_profile): + super().__init__(parent) + self.setWindowTitle("Issue Economic Instrument") + self.resize(780, 820) + self.setMinimumSize(720, 700) + self.result_data = None + self.asset = asset + self.rights_profile = rights_profile + self._updating = False + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(22, 20, 22, 20) + layout.setSpacing(12) + + layout.addWidget(Ui.label("ISSUE ECONOMIC INSTRUMENT", "dialogTitle")) + subtitle = Ui.label( + "The DCO Rights Passport is authoritative. Select a subset of rights already granted " + "to this asset; the wallet cannot invent or broaden them.", + "muted") + subtitle.setWordWrap(True) + layout.addWidget(subtitle) + + passport_box = QtWidgets.QFrame() + passport_box.setProperty("class", "panel") + pb = QtWidgets.QVBoxLayout(passport_box) + pb.setContentsMargins(16, 13, 16, 13) + pb.addWidget(Ui.label("RIGHTS PASSPORT", "eyebrow")) + pb.addWidget(Ui.label( + f"{shorten(rights_profile['rights_passport_id'], 22, 12)} · " + f"{len(rights_profile['allowed_actions'])} selectable action(s)", + "monoMuted" + )) + layout.addWidget(passport_box) + + if not rights_profile["allowed_actions"]: + warning = Ui.label( + "This DCO currently has no ALLOW actions in its Rights Passport. " + "No economic instrument can be issued until the asset rights are explicitly authorized.", + "muted") + warning.setWordWrap(True) + layout.addWidget(warning) + + form = QtWidgets.QFormLayout() + form.setLabelAlignment(QtCore.Qt.AlignLeft) + form.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow) + + self.namespace = QtWidgets.QLineEdit(namespace or "") + self.symbol = QtWidgets.QLineEdit("") + self.name = QtWidgets.QLineEdit("") + self.iclass = QtWidgets.QComboBox() + self.iclass.addItems([ + "SPOT_LICENSE","SUBSCRIPTION","COMPUTE_TO_DATA", + "PROCUREMENT","CONTRIBUTION","SECONDARY_LICENSE" + ]) + self.rclass = QtWidgets.QLineEdit("") + self.rclass.setReadOnly(True) + + self.actions = QtWidgets.QListWidget() + self.actions.setMinimumHeight(190) + self.actions.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + for action in rights_profile["allowed_actions"]: + detail = rights_profile["allowed"].get(action, {}) + label = self.ACTION_META.get(action, (action.replace("_", " ").title(), action[:5], action))[0] + conditions = detail.get("conditions") or {} + obligations = detail.get("obligations") or [] + text = f"{action} — {label}" + if conditions: + text += " · conditions apply" + if obligations: + text += " · obligations apply" + item = QtWidgets.QListWidgetItem(text) + item.setData(QtCore.Qt.UserRole, action) + item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) + item.setCheckState(QtCore.Qt.Unchecked) + tip = [] + if conditions: + tip.append("Conditions: " + json.dumps(conditions, sort_keys=True)) + if obligations: + tip.append("Obligations: " + ", ".join(str(x) for x in obligations)) + item.setToolTip("\n".join(tip) if tip else "Granted by the current Rights Passport.") + self.actions.addItem(item) + + self.supply = QtWidgets.QSpinBox() + self.supply.setRange(1, 2_000_000_000) + self.supply.setValue(1) + self.currency = QtWidgets.QLineEdit("CAD") + self.jurisdiction = QtWidgets.QLineEdit("CA") + self.series = QtWidgets.QSpinBox() + self.series.setRange(1, 999999) + self.series.setValue(1) + self.fungibility = QtWidgets.QComboBox() + self.fungibility.addItems(["FUNGIBLE","SERIES-FUNGIBLE","NON-FUNGIBLE"]) + self.divisibility = QtWidgets.QSpinBox() + self.divisibility.setRange(0, 9) + self.transferable = QtWidgets.QCheckBox("Secondary transfer permitted") + self.receives = QtWidgets.QPlainTextEdit("") + self.receives.setMinimumHeight(70) + self.excludes = QtWidgets.QPlainTextEdit( + "Ownership of the underlying DCO\n" + "Copyright ownership\n" + "Rights not stated in the Rights Passport" + ) + self.excludes.setMinimumHeight(70) + + for label, widget in [ + ("Instrument name", self.name), + ("Issuer namespace", self.namespace), + ("Display ticker / symbol", self.symbol), + ("EEP class", self.iclass), + ("Rights class", self.rclass), + ("Rights Passport actions", self.actions), + ("Supply", self.supply), + ("Settlement currency", self.currency), + ("Jurisdiction", self.jurisdiction), + ("Series", self.series), + ("Fungibility", self.fungibility), + ("Divisibility", self.divisibility), + ("Transferability", self.transferable), + ("Buyer receives", self.receives), + ("Buyer does NOT receive", self.excludes), + ]: + form.addRow(label, widget) + layout.addLayout(form) + + excluded = [] + if rights_profile["prohibited_actions"]: + excluded.append("PROHIBITED: " + ", ".join(rights_profile["prohibited_actions"])) + if rights_profile["other_actions"]: + excluded.append("NOT DIRECTLY ISSUABLE: " + ", ".join(rights_profile["other_actions"])) + if excluded: + boundary = QtWidgets.QFrame() + boundary.setProperty("class", "notice") + bl = QtWidgets.QVBoxLayout(boundary) + bl.setContentsMargins(14, 10, 14, 10) + for line in excluded: + note = Ui.label(line, "muted") + note.setWordWrap(True) + bl.addWidget(note) + layout.addWidget(boundary) + + self.preview = Ui.label("", "tickerPreview") + layout.addWidget(self.preview) + self.namespace.textChanged.connect(self._preview) + self.symbol.textChanged.connect(self._preview) + self.actions.itemChanged.connect(self._rights_changed) + + buttons = QtWidgets.QHBoxLayout() + buttons.addStretch() + cancel = Ui.button("Cancel") + create = Ui.button("Issue instrument", True) + create.setEnabled(bool(rights_profile["allowed_actions"])) + cancel.clicked.connect(self.reject) + create.clicked.connect(self.accept_data) + buttons.addWidget(cancel) + buttons.addWidget(create) + layout.addLayout(buttons) + + self._preview() + + def selected_actions(self): + return [ + str(self.actions.item(i).data(QtCore.Qt.UserRole)) + for i in range(self.actions.count()) + if self.actions.item(i).checkState() == QtCore.Qt.Checked + ] + + def _base_symbol(self): + short_name = str(self.asset.get("short_name") or "").upper() + if short_name: + compact = "".join(ch for ch in short_name if ch.isalnum()) + if compact: + return compact[:10] + dco_code = str(self.asset.get("dco_code") or "").upper() + if dco_code: + compact = "".join(ch for ch in dco_code if ch.isalnum()) + if compact: + return compact[:10] + title = str(self.asset.get("title") or "ASSET").upper() + words = [w for w in "".join(ch if ch.isalnum() else " " for ch in title).split() if w] + ignored = { + "BLACKMORE","INDUSTRIAL","REAL","TIME","THE","AND","OF", + "ASSET","DATASET","ENGINE","DIGITAL","ROBOTICS" + } + useful = [w for w in words if w not in ignored] + if not useful: + useful = words + if len(useful) == 1: + base = useful[0][:10] + else: + initials = "".join(w[0] for w in useful[:5]) + base = initials if len(initials) >= 3 else "".join(useful)[:10] + return base[:10] or "ASSET" + + def _rights_changed(self, item=None): + if self._updating: + return + actions = self.selected_actions() + if not actions: + self.rclass.setText("") + self.name.setText("") + self.symbol.setText("") + self.receives.setPlainText("") + self._preview() + return + + metas = [self.ACTION_META.get(a, (a.replace("_", " ").title(), a[:5], a)) for a in actions] + if len(actions) == 1: + label, suffix, rights_class = metas[0] + rights_name = f"{label} Rights" + self.rclass.setText(rights_class) + else: + labels = [m[0] for m in metas] + suffix = "-".join(m[1] for m in metas[:3]) + rights_name = " + ".join(labels) + " Rights" + self.rclass.setText("BUNDLED_RIGHTS") + + title = str(self.asset.get("title") or "Asset") + self.name.setText(f"{title} {rights_name}") + self.symbol.setText(f"{self._base_symbol()}-{suffix}"[:24]) + receives = [] + for action in actions: + detail = self.rights_profile["allowed"].get(action, {}) + line = f"{action} right under the current Rights Passport" + if detail.get("conditions"): + line += " (subject to passport conditions)" + if detail.get("obligations"): + line += " (subject to passport obligations)" + receives.append(line) + self.receives.setPlainText("\n".join(receives)) + self._preview() + + def _preview(self): + ns = (self.namespace.text().strip() or "NAMESPACE").upper() + sym = (self.symbol.text().strip() or "SELECT-RIGHT").upper() + self.preview.setText(f"MARKET IDENTIFIER PREVIEW {ns}:{sym}") + + def accept_data(self): + actions = self.selected_actions() + if not actions: + QtWidgets.QMessageBox.warning( + self, "Instrument", + "Select at least one action granted by the DCO's Rights Passport." + ) + return + allowed = set(self.rights_profile["allowed_actions"]) + if not set(actions).issubset(allowed): + QtWidgets.QMessageBox.critical( + self, "Rights Passport", + "The selected actions are no longer a valid subset of the current Rights Passport." + ) + return + if not self.symbol.text().strip() or not self.name.text().strip(): + QtWidgets.QMessageBox.warning(self, "Instrument", "Instrument name and ticker are required.") + return + + self.result_data = { + "name": self.name.text().strip(), + "namespace": self.namespace.text().strip(), + "symbol": self.symbol.text().strip().upper(), + "instrument_class": self.iclass.currentText(), + "rights_class": self.rclass.text().strip(), + "actions": actions, + "supply": self.supply.value(), + "currency": self.currency.text().strip(), + "jurisdiction": self.jurisdiction.text().strip(), + "series": self.series.value(), + "fungibility": self.fungibility.currentText(), + "divisibility": self.divisibility.value(), + "transferable": self.transferable.isChecked(), + "duration_ms": None, + "buyer_receives": [ + x.strip() for x in self.receives.toPlainText().splitlines() if x.strip() + ], + "buyer_does_not_receive": [ + x.strip() for x in self.excludes.toPlainText().splitlines() if x.strip() + ], + } + self.accept() + +class ListingDialog(QtWidgets.QDialog): + def __init__(self, parent, backend: Backend, instrument: dict): + super().__init__(parent) + self.setWindowTitle("Create Market Listing") + self.setMinimumWidth(600) + self.result_data = None + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(Ui.label("CREATE MARKET LISTING", "dialogTitle")) + layout.addWidget(Ui.label(instrument["market_identifier"], "tickerHero")) + layout.addWidget(Ui.label( + "A listing is separate from the canonical instrument. It binds a buyer-facing Listing Information Sheet and machine manifest.", + "muted")) + form = QtWidgets.QFormLayout() + self.venue = QtWidgets.QComboBox() + self.venues = backend.venues() + for v in self.venues: + self.venue.addItem(f"{v['name']} · {v['jurisdiction']}", v["venue_id"]) + self.market_id = QtWidgets.QLineEdit("ENTITY-MKT") + self.quote = QtWidgets.QLineEdit("CAD") + self.mode = QtWidgets.QComboBox(); self.mode.addItems(["ORDER_BOOK","CALL_AUCTION","RFQ"]) + self.settlement = QtWidgets.QComboBox(); self.settlement.addItems(["PAYMENT_VERSUS_RIGHT"]) + self.min_qty = QtWidgets.QSpinBox(); self.min_qty.setRange(1, 2_000_000_000); self.min_qty.setValue(1) + self.qprec = QtWidgets.QSpinBox(); self.qprec.setRange(0, 9) + self.pprec = QtWidgets.QSpinBox(); self.pprec.setRange(0, 9) + self.pricing = QtWidgets.QComboBox(); self.pricing.addItems(["ORDER_BOOK","CALL_AUCTION","RFQ"]) + self.series = QtWidgets.QSpinBox(); self.series.setRange(1, 999999); self.series.setValue(1) + self.tick = QtWidgets.QSpinBox(); self.tick.setRange(1, 2_000_000_000); self.tick.setValue(1) + for label, widget in [ + ("Venue", self.venue), ("Market ID", self.market_id), ("Quote unit", self.quote), + ("Trade mode", self.mode), ("Settlement", self.settlement), ("Minimum quantity", self.min_qty), + ("Quantity precision", self.qprec), ("Price precision", self.pprec), + ("Pricing method", self.pricing), ("Listing series", self.series), ("Tick size", self.tick), + ]: + form.addRow(label, widget) + layout.addLayout(form) + buttons = QtWidgets.QHBoxLayout(); buttons.addStretch() + cancel = Ui.button("Cancel"); create = Ui.button("Create listing", True) + cancel.clicked.connect(self.reject); create.clicked.connect(self.accept_data) + buttons.addWidget(cancel); buttons.addWidget(create); layout.addLayout(buttons) + + def accept_data(self): + if self.venue.count() == 0: + QtWidgets.QMessageBox.warning(self, "Listing", "No active ENTITY-compatible venue is available.") + return + self.result_data = { + "venue_id": self.venue.currentData(), "market_id": self.market_id.text().strip(), + "quote_unit": self.quote.text().strip(), "trade_mode": self.mode.currentText(), + "settlement_method": self.settlement.currentText(), "minimum_quantity": self.min_qty.value(), + "quantity_precision": self.qprec.value(), "price_precision": self.pprec.value(), + "pricing_method": self.pricing.currentText(), "listing_series": self.series.value(), + "tick_size": self.tick.value(), + } + self.accept() + +class OrderDialog(QtWidgets.QDialog): + def __init__(self, parent, row, side): + super().__init__(parent) + self.setWindowTitle(f"{side.title()} {row.get('market_identifier') or row['instrument_id']}") + self.result_data = None + m = row["market"] + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(Ui.label(side, "eyebrow")) + layout.addWidget(Ui.label(row.get("market_identifier") or row["instrument_id"], "tickerHero")) + form = QtWidgets.QFormLayout() + self.qty = QtWidgets.QSpinBox(); self.qty.setRange(1, 2_000_000_000); self.qty.setValue(1) + self.price = QtWidgets.QSpinBox(); self.price.setRange(0, 2_000_000_000) + default = m.get("ask") if side == "BUY" else m.get("bid") + if default is not None: self.price.setValue(int(default)) + form.addRow("Quantity", self.qty); form.addRow(f"Limit price ({row['settlement_currency']})", self.price) + layout.addLayout(form) + layout.addWidget(Ui.label(f"Bid {m.get('bid') or '—'} Ask {m.get('ask') or '—'} Last {m.get('last') or '—'}", "muted")) + buttons = QtWidgets.QHBoxLayout(); buttons.addStretch() + cancel = Ui.button("Cancel"); submit = Ui.button(f"Submit {side}", True) + cancel.clicked.connect(self.reject); submit.clicked.connect(self.accept_data) + buttons.addWidget(cancel); buttons.addWidget(submit); layout.addLayout(buttons) + + def accept_data(self): + self.result_data = (self.qty.value(), self.price.value()) + self.accept() + +class AssetIngestDialog(QtWidgets.QDialog): + def __init__(self, parent, path: Path, backend: Backend): + super().__init__(parent) + self.setWindowTitle("Ingest Digital Asset") + self.setMinimumWidth(650) + self.result_data = None + self.backend = backend + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(Ui.label("INGEST DIGITAL ASSET", "dialogTitle")) + layout.addWidget(Ui.label(str(path), "monoMuted")) + form = QtWidgets.QFormLayout() + self.title = QtWidgets.QLineEdit(path.name) + self.domain = QtWidgets.QComboBox(); self.domain.addItems(backend.domains()) + self.kind = QtWidgets.QComboBox() + self.juris = QtWidgets.QLineEdit("") + self.cclass = QtWidgets.QLineEdit("DIGITAL_ASSET") + self.unit = QtWidgets.QLineEdit("ASSET") + self.authority = QtWidgets.QLineEdit(f"CONTROLLER_ENTITY:{backend.entity_id}") + self.version = QtWidgets.QLineEdit("1.0") + self.parent_id = QtWidgets.QLineEdit("") + self.attest = QtWidgets.QCheckBox("I have authority to register this asset and attest its provenance.") + for label, widget in [ + ("Asset title", self.title), ("Domain profile", self.domain), ("Asset kind", self.kind), + ("Jurisdiction", self.juris), ("Commodity class", self.cclass), ("Measurement unit", self.unit), + ("Authority basis", self.authority), ("Passport version", self.version), + ("Parent object ID", self.parent_id), ("Authority attestation", self.attest), + ]: + form.addRow(label, widget) + layout.addLayout(form) + layout.addWidget(Ui.label( + "Creates the governed DCO + evidence + Rights Passport + Global Passport + BTDU binding. No ticker, instrument, listing or price is created automatically.", + "muted")) + self.domain.currentTextChanged.connect(self._domain) + self._domain(self.domain.currentText()) + buttons = QtWidgets.QHBoxLayout(); buttons.addStretch() + cancel = Ui.button("Cancel"); ingest = Ui.button("Ingest asset", True) + cancel.clicked.connect(self.reject); ingest.clicked.connect(self.accept_data) + buttons.addWidget(cancel); buttons.addWidget(ingest); layout.addLayout(buttons) + + def _domain(self, domain): + self.kind.clear() + kinds = self.backend.asset_kinds(domain) + self.kind.addItems(kinds) + self.cclass.setText("DIGITAL_ASSET" if domain == "general" else domain.upper().replace("-", "_") + "_ASSET") + + def accept_data(self): + if not self.attest.isChecked(): + QtWidgets.QMessageBox.warning(self, "Authority", "Authority attestation is required.") + return + if self.domain.currentText() != "general" and not self.juris.text().strip(): + QtWidgets.QMessageBox.warning(self, "Jurisdiction", "Domain package ingest requires an explicit jurisdiction.") + return + self.result_data = { + "title": self.title.text().strip(), "domain": self.domain.currentText(), + "asset_kind": self.kind.currentText(), "jurisdiction": self.juris.text().strip(), + "class": self.cclass.text().strip(), "unit": self.unit.text().strip(), + "authority": self.authority.text().strip(), "version": self.version.text().strip(), + "parent_object_id": self.parent_id.text().strip(), + } + self.accept() + +class WalletWindow(QtWidgets.QMainWindow): + NAV = [ + ("overview", "OVERVIEW"), ("assets", "DIGITAL ASSETS"), ("instruments", "INSTRUMENTS"), + ("market", "ENTITY MARKET"), ("intelligence", "ECONOMIC INTELLIGENCE"), ("orders", "ORDERS"), + ] + + def __init__(self, state: Path, entity_id: str, wallet_profile: dict | None = None): + super().__init__() + self.state = state + self.entity_id = entity_id + self.wallet_profile = wallet_profile + self.backend = Backend(state, entity_id) + self.snapshot_data = {} + self.assets_rows: list[dict] = [] + self.instruments_rows: list[dict] = [] + self.market_rows: list[dict] = [] + self.metrics_rows: list[dict] = [] + self.setWindowTitle(f"{APP_NAME} · Data Economy Terminal") + self.resize(1560, 940) + self.setMinimumSize(1180, 720) + self._build() + self._apply_style() + self.refresh() + self.navigate("overview") + + def _build(self): + root = QtWidgets.QWidget(); self.setCentralWidget(root) + outer = QtWidgets.QHBoxLayout(root); outer.setContentsMargins(0, 0, 0, 0); outer.setSpacing(0) + + side = QtWidgets.QFrame(); side.setObjectName("sidebar"); side.setFixedWidth(238) + sl = QtWidgets.QVBoxLayout(side); sl.setContentsMargins(18, 22, 18, 20); sl.setSpacing(7) + sl.addWidget(Ui.label("ENTITY", "brand")) + sl.addWidget(Ui.label("DATA ECONOMY TERMINAL", "brandSub")) + sl.addSpacing(24) + self.nav_buttons = {} + for key, label in self.NAV: + b = QtWidgets.QPushButton(label); b.setCheckable(True); b.setCursor(QtCore.Qt.PointingHandCursor) + b.setProperty("nav", True); b.clicked.connect(lambda checked=False, k=key: self.navigate(k)) + self.nav_buttons[key] = b; sl.addWidget(b) + sl.addStretch() + sl.addWidget(Ui.label("SOVEREIGN LOCAL STATE", "eyebrow")) + sl.addWidget(Ui.label("Protocol tax 0\nNo cryptocurrency required\nExternal settlement evidence required", "sidebarFoot")) + outer.addWidget(side) + + body = QtWidgets.QWidget(); bl = QtWidgets.QVBoxLayout(body) + bl.setContentsMargins(26, 18, 26, 22); bl.setSpacing(14) + header = QtWidgets.QHBoxLayout() + titlebox = QtWidgets.QVBoxLayout() + self.page_title = Ui.label("", "pageTitle") + self.page_sub = Ui.label("", "pageSub") + titlebox.addWidget(self.page_title); titlebox.addWidget(self.page_sub) + header.addLayout(titlebox); header.addStretch() + self.lineage_chip = Ui.label("", "chip") + self.namespace_chip = Ui.label("", "chip") + self.market_chip = Ui.label("", "chip") + self.identity_chip = Ui.label("", "identityChip") + header.addWidget(self.lineage_chip); header.addWidget(self.namespace_chip); header.addWidget(self.market_chip); header.addWidget(self.identity_chip) + bl.addLayout(header) + + self.tape = Ui.label("", "tape") + bl.addWidget(self.tape) + + self.stack = QtWidgets.QStackedWidget() + bl.addWidget(self.stack, 1) + outer.addWidget(body, 1) + + self.pages = {} + self.pages["overview"] = self._overview_page() + self.pages["assets"] = self._assets_page() + self.pages["instruments"] = self._instruments_page() + self.pages["market"] = self._market_page() + self.pages["intelligence"] = self._intelligence_page() + self.pages["orders"] = self._orders_page() + for key, _ in self.NAV: + self.stack.addWidget(self.pages[key]) + + def _overview_page(self): + p = QtWidgets.QWidget(); lay = QtWidgets.QVBoxLayout(p); lay.setContentsMargins(0, 0, 0, 0); lay.setSpacing(14) + cards = QtWidgets.QHBoxLayout(); cards.setSpacing(12) + self.cards = {} + for key, title in [ + ("assets", "Digital Assets"), ("instruments", "Issued Instruments"), ("listings", "Active Listings"), + ("positions", "Market Positions"), ("settled", "30D Settled Volume"), + ]: + c = MetricCard(title); self.cards[key] = c; cards.addWidget(c) + lay.addLayout(cards) + row = QtWidgets.QHBoxLayout(); row.setSpacing(14) + market_box = QtWidgets.QFrame(); market_box.setProperty("class", "panel") + ml = QtWidgets.QVBoxLayout(market_box); ml.setContentsMargins(18, 16, 18, 18) + head = QtWidgets.QHBoxLayout() + head.addWidget(Ui.label("MARKET PULSE", "sectionTitle")); head.addStretch() + b = Ui.button("Open market"); b.clicked.connect(lambda: self.navigate("market")); head.addWidget(b) + ml.addLayout(head) + self.overview_market_stack=QtWidgets.QStackedWidget() + self.overview_market = Ui.table(["TICKER", "RIGHTS", "LAST", "24H VOL", "30D VOL", "BUYERS", "INTEGRITY"]) + self.overview_market_empty=EmptyState( + "NO ACTIVE LISTINGS", + "The universal market is clean. Issue an economic instrument from a controlled DCO, then create a listing to establish a ticker-addressable market." + ) + self.overview_market_stack.addWidget(self.overview_market) + self.overview_market_stack.addWidget(self.overview_market_empty) + ml.addWidget(self.overview_market_stack, 1) + row.addWidget(market_box, 2) + + asset_box = QtWidgets.QFrame(); asset_box.setProperty("class", "panel") + al = QtWidgets.QVBoxLayout(asset_box); al.setContentsMargins(18, 16, 18, 18) + ah = QtWidgets.QHBoxLayout(); ah.addWidget(Ui.label("ASSET PORTFOLIO", "sectionTitle")); ah.addStretch() + ab = Ui.button("+ Ingest asset", True); ab.clicked.connect(self.ingest_asset); ah.addWidget(ab); al.addLayout(ah) + self.overview_assets = Ui.table(["TICKER", "ASSET", "CLASS", "DOMAIN", "ASSET INFO"]) + self.overview_assets.setColumnWidth(0,150) + self.overview_assets.setColumnWidth(1,310) + self.overview_assets.setColumnWidth(2,170) + self.overview_assets.horizontalHeader().setSectionResizeMode(1,QtWidgets.QHeaderView.Stretch) + al.addWidget(self.overview_assets, 1) + row.addWidget(asset_box, 3) + lay.addLayout(row, 1) + + boundary = QtWidgets.QFrame(); boundary.setProperty("class", "notice") + nl = QtWidgets.QHBoxLayout(boundary); nl.setContentsMargins(16, 12, 16, 12) + nl.addWidget(Ui.label("VALUE BOUNDARY", "eyebrow")) + n = Ui.label("ENTITY measures market demand for bounded rights. A rights price is not intrinsic DCO value and is not accounting fair value.", "muted") + n.setWordWrap(True); nl.addWidget(n, 1) + lay.addWidget(boundary) + return p + + def _assets_page(self): + p = QtWidgets.QWidget(); lay = QtWidgets.QVBoxLayout(p); lay.setContentsMargins(0,0,0,0); lay.setSpacing(12) + actions = QtWidgets.QHBoxLayout() + ingest = Ui.button("+ Ingest digital asset", True); ingest.clicked.connect(self.ingest_asset) + disclose = Ui.button("Asset information"); disclose.clicked.connect(self.edit_asset_information) + issue = Ui.button("Issue economic instrument"); issue.clicked.connect(self.issue_selected_asset) + lineage = Ui.button("Inspect lineage"); lineage.clicked.connect(self.show_asset_lineage) + actions.addWidget(ingest); actions.addWidget(disclose); actions.addWidget(issue); actions.addWidget(lineage); actions.addStretch() + lay.addLayout(actions) + split = QtWidgets.QSplitter() + self.assets_table = Ui.table(["TICKER(S)", "ASSET", "CLASS", "TYPE", "DOMAIN", "ASSET INFO", "PASSPORT", "BTDU", "DCO ID"]) + self.assets_table.itemSelectionChanged.connect(self.update_asset_detail) + split.addWidget(self.assets_table) + self.asset_detail = QtWidgets.QTextBrowser(); self.asset_detail.setProperty("class", "detail") + split.addWidget(self.asset_detail); split.setSizes([1050, 410]) + lay.addWidget(split, 1) + return p + + def _instruments_page(self): + p = QtWidgets.QWidget(); lay = QtWidgets.QVBoxLayout(p); lay.setContentsMargins(0,0,0,0); lay.setSpacing(12) + actions = QtWidgets.QHBoxLayout() + list_btn = Ui.button("+ Create listing", True); list_btn.clicked.connect(self.list_selected_instrument) + actions.addWidget(list_btn); actions.addStretch() + lay.addLayout(actions) + split = QtWidgets.QSplitter() + self.instruments_table = Ui.table(["TICKER", "INSTRUMENT", "RIGHTS", "CLASS", "SUPPLY", "DCO", "STATUS"]) + self.instruments_table.itemSelectionChanged.connect(self.update_instrument_detail) + split.addWidget(self.instruments_table) + self.instrument_detail = QtWidgets.QTextBrowser(); self.instrument_detail.setProperty("class", "detail") + split.addWidget(self.instrument_detail); split.setSizes([1050, 410]) + lay.addWidget(split, 1) + return p + + def _market_page(self): + p = QtWidgets.QWidget(); lay = QtWidgets.QVBoxLayout(p); lay.setContentsMargins(0,0,0,0); lay.setSpacing(12) + actions = QtWidgets.QHBoxLayout() + buy = Ui.button("BUY", True); buy.clicked.connect(lambda: self.trade_selected("BUY")) + sell = Ui.button("SELL"); sell.clicked.connect(lambda: self.trade_selected("SELL")) + info = Ui.button("Listing information"); info.clicked.connect(self.show_listing_info) + export = Ui.button("Export instrument package"); export.clicked.connect(self.export_listing) + actions.addWidget(buy); actions.addWidget(sell); actions.addWidget(info); actions.addWidget(export); actions.addStretch() + lay.addLayout(actions) + split = QtWidgets.QSplitter() + self.market_table = Ui.table(["TICKER", "INSTRUMENT", "ISSUER", "RIGHTS", "LAST", "BID", "ASK", "24H VOL", "30D VOL", "BUYERS", "HOLDERS", "INTEGRITY"]) + self.market_table.itemSelectionChanged.connect(self.update_market_detail) + split.addWidget(self.market_table) + self.market_detail = QtWidgets.QTextBrowser(); self.market_detail.setProperty("class", "detail") + split.addWidget(self.market_detail); split.setSizes([1110, 350]) + lay.addWidget(split, 1) + return p + + def _intelligence_page(self): + p = QtWidgets.QWidget(); lay = QtWidgets.QVBoxLayout(p); lay.setContentsMargins(0,0,0,0); lay.setSpacing(12) + notice = EmptyState("DATA VALUE DISCOVERY", "Settled market activity reveals which bounded rights attract demand. Orders and quotes do not count as realized demand; self-trades and reciprocal flows are flagged.") + lay.addWidget(notice) + splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) + top = QtWidgets.QFrame(); top.setProperty("class", "panel"); tl = QtWidgets.QVBoxLayout(top) + tl.addWidget(Ui.label("INSTRUMENT RIGHTS DEMAND", "sectionTitle")) + self.intel_table = Ui.table(["TICKER", "RIGHTS", "ASSET CLASS", "LAST", "24H UNITS", "30D UNITS", "30D TRADES", "BUYERS", "HOLDERS", "LOW", "HIGH", "VERIFIED", "INTEGRITY"]) + tl.addWidget(self.intel_table) + splitter.addWidget(top) + bottom = QtWidgets.QFrame(); bottom.setProperty("class", "panel"); bl = QtWidgets.QVBoxLayout(bottom) + bl.addWidget(Ui.label("ASSET-CLASS ROLLUP · 30 DAYS", "sectionTitle")) + self.rollup_table = Ui.table(["ASSET CLASS", "INSTRUMENTS", "TRADES", "UNITS", "NOTIONAL BY CURRENCY"]) + bl.addWidget(self.rollup_table); splitter.addWidget(bottom) + splitter.setSizes([430, 280]); lay.addWidget(splitter, 1) + return p + + def _orders_page(self): + p = QtWidgets.QWidget(); lay = QtWidgets.QVBoxLayout(p); lay.setContentsMargins(0,0,0,0); lay.setSpacing(12) + box = QtWidgets.QFrame(); box.setProperty("class", "panel"); bl = QtWidgets.QVBoxLayout(box) + bl.addWidget(Ui.label("OPEN / RECENT ORDERS", "sectionTitle")) + self.orders_table = Ui.table(["TICKER / INSTRUMENT", "SIDE", "REMAINING", "LIMIT", "STATUS"]) + bl.addWidget(self.orders_table); lay.addWidget(box, 1) + return p + + def navigate(self, key): + idx = list(self.pages).index(key) + self.stack.setCurrentIndex(idx) + for k, b in self.nav_buttons.items(): + b.setChecked(k == key) + titles = { + "overview": ("Portfolio Overview", "Your sovereign asset state and market activity."), + "assets": ("Digital Assets", "DCO holdings, lineage, passports and optional economic issuance."), + "instruments": ("Economic Instruments", "Ticker-addressable bounded rights issued against controlled DCOs."), + "market": ("ENTITY Market", "Universal multi-issuer listings and wallet-to-wallet rights trading."), + "intelligence": ("Economic Intelligence", "Settlement-aware price discovery and rights-demand analytics."), + "orders": ("Orders", "Open and recent exchange instructions."), + } + self.page_title.setText(titles[key][0]); self.page_sub.setText(titles[key][1]) + + def refresh(self): + self.snapshot_data = self.backend.snapshot() + self.assets_rows = self.snapshot_data.get("assets", []) + self.instruments_rows = self.backend.instruments() + self.market_rows = self.backend.market() + self.metrics_rows = self.backend.economic_metrics() + rollup = self.backend.economy_rollup("asset_class") + amap = self.backend.instrument_map_by_asset() + ns_info=self.backend.namespace_info() + ns=ns_info.get("namespace") or "UNASSIGNED" + venues = self.backend.venues() + + name = self.backend.identity_manifest.get("display_name") or self.entity_id + public_aliases = [str(x) for x in (self.backend.identity_manifest.get("aliases") or []) if str(x).endswith(".entity")] + active_public = public_aliases[0] if public_aliases else shorten(self.entity_id, 12, 8) + self.identity_chip.setText(f"{name}\n{active_public}") + if self.wallet_profile: + self.lineage_chip.setText("LINEAGE " + self.wallet_profile.get("display_path", active_public)) + self.lineage_chip.setToolTip( + "User/organization lineage for this wallet. This is separate from ENTITY protocol origin." + ) + else: + self.lineage_chip.setText("LINEAGE " + active_public) + self.namespace_chip.setText(f"NAMESPACE {ns}" + ("" if ns_info.get("registered") else " · RESERVED ON ISSUE")) + self.market_chip.setText(f"ENTITY-MKT · {len(venues)} VENUE{'S' if len(venues)!=1 else ''}") + if self.market_rows: + tape = " ".join( + f"{r['market_identifier']} {r['market'].get('last') if r['market'].get('last') is not None else '—'}" + for r in self.market_rows[:8] + ) + self.tape.setText("MARKET TAPE " + tape) + else: + self.tape.setText("MARKET TAPE NO ACTIVE LISTINGS · instruments remain private until explicitly listed") + + self.cards["assets"].value.setText(str(len(self.assets_rows))) + self.cards["assets"].note.setText("Current DCO holdings") + self.cards["instruments"].value.setText(str(len(self.instruments_rows))) + self.cards["instruments"].note.setText("Canonical economic instruments") + self.cards["listings"].value.setText(str(len(self.market_rows))) + self.cards["listings"].note.setText("Visible market listings") + self.cards["positions"].value.setText(str(len(self.snapshot_data.get("positions", [])))) + self.cards["positions"].note.setText("Active rights positions") + by_currency = {} + for m in self.metrics_rows: + cur = m.get("settlement_currency") or "?" + by_currency[cur] = by_currency.get(cur, 0) + int(m["window_30d"]["notional_amount_units"]) + self.cards["settled"].value.setText(" · ".join(f"{k} {v:,}" for k,v in by_currency.items()) or "—") + self.cards["settled"].note.setText("Settled instrument-right notional") + + asset_table_rows = [] + overview_assets = [] + for a in self.assets_rows: + instruments = amap.get(a["object_id"], []) + tickers = ", ".join(i["market_identifier"] for i in instruments) or "NO TICKER" + lin = a.get("lineage", {}) + domain = (lin.get("protocol_lineage", {}) or {}).get("primary_domain_profile") or "—" + gp = lin.get("global_passport") or {} + btdu = (lin.get("capability_bindings", {}).get("BTDU", {}) or {}).get("bound", False) + disclosure = self.backend.asset_disclosure(a["object_id"]) + disclosure_status = f"SIGNED V{disclosure['version']}" if disclosure else "REQUIRED TO LIST" + asset_table_rows.append([ + tickers, a.get("title"), a.get("commodity_class"), a.get("object_type"), domain, + disclosure_status, gp.get("version") or "—", "BOUND" if btdu else "—", a.get("object_id") + ]) + overview_assets.append([tickers, a.get("title"), a.get("commodity_class"), domain, disclosure_status]) + Ui.fill_table(self.assets_table, asset_table_rows) + Ui.fill_table(self.overview_assets, overview_assets[:8]) + + inst_rows = [] + for x in self.instruments_rows: + inst_rows.append([ + x["market_identifier"], x["instrument_name"], x["rights_class"], x["instrument_class"], + x["supply"], shorten(x["underlying_dco_id"], 12, 8), x["status"] + ]) + Ui.fill_table(self.instruments_table, inst_rows) + + metrics_by_iid = {m["instrument_id"]: m for m in self.metrics_rows} + market_rows = [] + overview_market = [] + for r in self.market_rows: + mkt = r["market"]; im = metrics_by_iid.get(r["instrument_id"], {}) + w24 = im.get("window_24h", {}); w30 = im.get("window_30d", {}) + flags = ", ".join((im.get("market_integrity") or {}).get("flags") or []) or "CLEAN" + vals = [ + r["market_identifier"], r["instrument_name"], shorten(r["issuer_entity_id"], 12, 8), + r["rights_class"], mkt.get("last") or "—", mkt.get("bid") or "—", mkt.get("ask") or "—", + w24.get("volume_units", 0), w30.get("volume_units", 0), w30.get("unique_buyers", 0), + im.get("active_holder_count", 0), flags + ] + market_rows.append(vals) + overview_market.append([r["market_identifier"], r["rights_class"], mkt.get("last") or "—", + w24.get("volume_units", 0), w30.get("volume_units", 0), + w30.get("unique_buyers", 0), flags]) + Ui.fill_table(self.market_table, market_rows) + Ui.fill_table(self.overview_market, overview_market[:8]) + self.overview_market_stack.setCurrentIndex(0 if overview_market else 1) + + intel_rows = [] + for m in self.metrics_rows: + w = m["window_30d"] + flags = ", ".join(m["market_integrity"]["flags"]) or "CLEAN" + intel_rows.append([ + m["market_identifier"], m["rights_class"], m["asset_class"], m["last_settled_price"] or "—", + m["window_24h"]["volume_units"], w["volume_units"], w["trade_count"], w["unique_buyers"], + m["active_holder_count"], w["low_price"] or "—", w["high_price"] or "—", + w["externally_verified_trade_count"], flags + ]) + Ui.fill_table(self.intel_table, intel_rows) + Ui.fill_table(self.rollup_table, [ + [r["group"], r["instrument_count"], r["settled_trades_30d"], r["volume_units_30d"], + ", ".join(f"{k} {v:,}" for k,v in r["notional_30d_by_currency"].items()) or "—"] + for r in rollup.get("rows", []) + ]) + + Ui.fill_table(self.orders_table, [ + [o["instrument_id"], o["side"], o["remaining"], o["limit_price"], o["status"]] + for o in self.snapshot_data.get("orders", []) + ]) + if self.assets_rows: + self.assets_table.selectRow(0) + if self.instruments_rows: + self.instruments_table.selectRow(0) + if self.market_rows: + self.market_table.selectRow(0) + + def selected_asset(self): + row = self.assets_table.currentRow() + return self.assets_rows[row] if 0 <= row < len(self.assets_rows) else None + + def selected_instrument(self): + row = self.instruments_table.currentRow() + return self.instruments_rows[row] if 0 <= row < len(self.instruments_rows) else None + + def selected_market(self): + row = self.market_table.currentRow() + return self.market_rows[row] if 0 <= row < len(self.market_rows) else None + + def update_asset_detail(self): + a = self.selected_asset() + if not a: + self.asset_detail.setHtml("

No asset selected

"); return + lin = a.get("lineage", {}); p = lin.get("protocol_lineage", {}) or {} + al = lin.get("asset_lineage", {}) or {}; cap = lin.get("capability_bindings", {}) or {} + instruments = self.backend.instrument_map_by_asset().get(a["object_id"], []) + tickers = "
".join(html.escape(i["market_identifier"]) for i in instruments) or "NO ECONOMIC INSTRUMENT ISSUED" + parents = "
".join( + f"{html.escape(str(x.get('relation') or ''))} · {html.escape(str(x.get('title') or shorten(x.get('parent_object_id'))))}" + for x in al.get("parents", []) + ) or "None recorded" + obj = self.backend.asset_record(a["object_id"]) + descriptor = obj.get("descriptor") or {} + metadata = descriptor.get("metadata") or {} + disclosure = self.backend.asset_disclosure(a["object_id"]) + try: + gp = self.backend.latest_global_passport(a["object_id"]) + except Exception: + gp = {} + description = (disclosure or {}).get("description") or ( + f"{obj.get('title')} is registered as an ENTITY Digital Commodity Object of type " + f"{obj.get('object_type')} in the {descriptor.get('commodity_class')} commodity class. " + "No signed issuer-authored Asset Disclosure has been published yet." + ) + def ul(items): + vals=[html.escape(str(x)) for x in (items or []) if str(x).strip()] + return "
    "+"".join("
  • "+x+"
  • " for x in vals)+"
" if vals else "None stated" + metadata_rows = "".join( + f"{html.escape(str(k).replace('_',' ').title())}{html.escape(str(v))}" + for k,v in sorted(metadata.items()) + ) or "None recorded" + disclosure_version=(disclosure or {}).get("version") + profile_refs=(gp.get("profile_stack") or {}).get("profile_refs") or [] + self.asset_detail.setHtml(f""" +
DIGITAL COMMODITY OBJECT
+

{html.escape(str(a.get('title') or ''))}

+
{tickers}
+
+

Asset description

+

{html.escape(str(description))}

+

Disclosure: {"Signed version "+str(disclosure_version) if disclosure else "Not yet published"}

+ +

Canonical identity

+ Canonical lineage
{html.escape(str(p.get('display_path') or 'Unresolved'))}

+ Controller
{html.escape(str(al.get('controller_name') or a.get('controller_entity_id')))}

+ DCO ID
{html.escape(str(a.get('object_id')))}

+ Commodity class
{html.escape(str(a.get('commodity_class')))}

+ Object type
{html.escape(str(a.get('object_type')))}

+ Content SHA-256
{html.escape(str(obj.get('content_sha256') or '—'))}

+ +

Issuer disclosure

+ Purpose
{html.escape(str((disclosure or {}).get('purpose') or 'None stated'))}

+ Capabilities{ul((disclosure or {}).get('capabilities'))} + Included content / components{ul((disclosure or {}).get('contents'))} + Intended uses{ul((disclosure or {}).get('intended_uses'))} + Limitations{ul((disclosure or {}).get('limitations'))} + Validation notes{ul((disclosure or {}).get('validation_notes'))} + +

Technical metadata

+ {metadata_rows}

+ Profile stack{ul(profile_refs)} + Composed profiles
{html.escape(', '.join(p.get('composed_domain_profiles') or []) or 'Primary domain only')}

+ BTDU
{"Bound" if (cap.get('BTDU') or {}).get('bound') else "Not bound"}

+ Provenance parents
{parents}

+ Asset registration does not create a ticker, instrument, listing or market value. A signed Asset Disclosure is required before market listing. + """) + + + def update_instrument_detail(self): + x = self.selected_instrument() + if not x: + self.instrument_detail.setHtml("

No instrument selected

"); return + self.instrument_detail.setHtml(f""" +
CANONICAL ECONOMIC INSTRUMENT
+
{x['market_identifier']}
+

{x['instrument_name']}


+ Canonical instrument ID
{x['instrument_id']}

+ Underlying DCO
{x['underlying_dco_id']}

+ Rights class
{x['rights_class']}

+ Instrument class
{x['instrument_class']}

+ Supply
{x['supply']:,}

+ Fungibility
{x['fungibility']}

+ Jurisdiction
{x['jurisdiction']}

+ Rights Passport
{x['rights_passport_id']}

+ The ticker is a human market alias. The canonical instrument ID remains authoritative. + """) + + def update_market_detail(self): + r = self.selected_market() + if not r: + self.market_detail.setHtml("

No active listing

Assets remain private until an issuer explicitly creates an instrument and listing.

") + return + m = r["market"] + im = next((x for x in self.metrics_rows if x["instrument_id"] == r["instrument_id"]), {}) + w = im.get("window_30d", {}) + flags = ", ".join((im.get("market_integrity") or {}).get("flags") or []) or "CLEAN" + self.market_detail.setHtml(f""" +
ENTITY MARKET
+
{r['market_identifier']}
+

{r['instrument_name']}


+ + + + + + + + +
LAST{m.get('last') or '—'}
BID{m.get('bid') or '—'}
ASK{m.get('ask') or '—'}
30D UNITS{w.get('volume_units', 0):,}
30D TRADES{w.get('trade_count', 0):,}
BUYERS{w.get('unique_buyers', 0):,}
HOLDERS{im.get('active_holder_count', 0):,}

+ Rights
{r['rights_class']}

+ Issuer
{r['issuer_entity_id']}

+ Integrity
{flags}

+ Observed price describes this instrument's bounded rights under its terms—not intrinsic DCO value. + """) + + def ingest_asset(self): + path = QtWidgets.QFileDialog.getOpenFileName(self, "Choose digital asset to ingest")[0] + if not path: + return + d = AssetIngestDialog(self, Path(path), self.backend) + if d.exec() != QtWidgets.QDialog.Accepted: + return + try: + result = self.backend.ingest(path, d.result_data) + self.refresh() + QtWidgets.QMessageBox.information( + self, "Digital asset registered", + f"DCO\n{result['digital_asset']['object_id']}\n\n" + f"Global Passport verified: {result['global_passport_verified']}\n" + f"BTDU bound: {bool(result['btdu_binding'])}\n\n" + "No economic instrument, ticker, listing or price was created." + ) + except Exception as e: + QtWidgets.QMessageBox.critical(self, "Asset ingest failed", str(e)) + + def edit_asset_information(self): + asset = self.selected_asset() + if not asset: + QtWidgets.QMessageBox.warning(self, "Asset information", "Select a controlled DCO first.") + return + current = self.backend.asset_disclosure(asset["object_id"]) + dialog = AssetDisclosureDialog(self, asset, current) + if dialog.exec() != QtWidgets.QDialog.Accepted: + return + try: + result = self.backend.publish_asset_disclosure(asset["object_id"], dialog.result_data) + self.refresh() + self.update_asset_detail() + QtWidgets.QMessageBox.information( + self, "Asset information published", + f"Signed Asset Disclosure version {result['version']} was published.\n\n" + f"Disclosure ID\n{result['disclosure_id']}\n\n" + "Future listings will snapshot this disclosure together with the canonical asset dossier." + ) + except Exception as e: + QtWidgets.QMessageBox.critical(self, "Asset information failed", str(e)) + + def issue_selected_asset(self): + asset = self.selected_asset() + if not asset: + QtWidgets.QMessageBox.warning(self, "Instrument", "Select a controlled DCO first."); return + try: + rights_profile = self.backend.instrument_rights_profile(asset["object_id"]) + except Exception as e: + QtWidgets.QMessageBox.critical(self, "Rights Passport", str(e)) + return + if not rights_profile["allowed_actions"]: + QtWidgets.QMessageBox.warning( + self, "No issuable rights", + "This DCO's current Rights Passport contains no ALLOW actions. " + "No economic instrument can be created until the asset is explicitly authorized." + ) + return + dialog_asset = dict(asset) + try: + obj = self.backend.asset_record(asset["object_id"]) + metadata = ((obj.get("descriptor") or {}).get("metadata") or {}) + dialog_asset["short_name"] = metadata.get("short_name") + dialog_asset["dco_code"] = metadata.get("dco_code") + except Exception: + pass + d = InstrumentDialog(self, dialog_asset, self.backend.namespace(), rights_profile) + if d.exec() != QtWidgets.QDialog.Accepted: + return + try: + result = self.backend.create_instrument(asset["object_id"], d.result_data) + self.refresh() + QtWidgets.QMessageBox.information( + self, "Economic instrument issued", + f"{result['market_identifier']}\n\nCanonical ID\n{result['instrument_id']}\n\n" + "The instrument is not listed and no market value was created." + ) + except Exception as e: + QtWidgets.QMessageBox.critical(self, "Instrument creation failed", str(e)) + + def list_selected_instrument(self): + instrument = self.selected_instrument() + if not instrument: + QtWidgets.QMessageBox.warning(self, "Listing", "Select one of your economic instruments."); return + if not self.backend.asset_disclosure(instrument["underlying_dco_id"]): + QtWidgets.QMessageBox.warning( + self, "Asset information required", + "This DCO does not yet have a signed Asset Disclosure. Publish detailed Asset Information before creating a market listing." + ) + return + d = ListingDialog(self, self.backend, instrument) + if d.exec() != QtWidgets.QDialog.Accepted: + return + try: + result = self.backend.create_listing(instrument["instrument_id"], d.result_data) + self.refresh() + QtWidgets.QMessageBox.information( + self, "Listing created", + f"{instrument['market_identifier']}\n\nListing ID\n{result['listing_id']}\n\n" + "Listing Information Sheet and machine manifest are hash-bound." + ) + except Exception as e: + QtWidgets.QMessageBox.critical(self, "Listing creation failed", str(e)) + + def show_asset_lineage(self): + a = self.selected_asset() + if not a: + return + lin = a.get("lineage", {}) + p = lin.get("protocol_lineage", {}) or {} + al = lin.get("asset_lineage", {}) or {} + parents = "\n".join( + f"{x.get('relation')}: {x.get('title') or x.get('parent_object_id')}" + for x in al.get("parents", []) + ) or "None" + QtWidgets.QMessageBox.information( + self, "ENTITY lineage", + f"CANONICAL PROTOCOL / DOMAIN LINEAGE\n{p.get('display_path') or 'Unresolved'}\n\n" + f"ASSET CONTROLLER\n{al.get('controller_name') or al.get('controller_entity_id')}\n\n" + f"PROVENANCE PARENTS\n{parents}\n\n" + "ADAM, NIKI and BTDU are capability/evidence bindings and do not create ownership." + ) + + def show_listing_info(self): + r = self.selected_market() + if not r: + QtWidgets.QMessageBox.warning(self, "Listing", "No active market listing selected."); return + try: + listing = self.backend.market_registry.listing(r["listing_id"]) + dialog = ListingInformationDialog( + self, listing["information"]["market_identifier"], listing["information_markdown"] + ) + dialog.exec() + except Exception as e: + QtWidgets.QMessageBox.critical(self, "Listing Information", str(e)) + + + def export_listing(self): + r = self.selected_market() + if not r: + QtWidgets.QMessageBox.warning(self, "Export", "No active market listing selected."); return + dest = QtWidgets.QFileDialog.getExistingDirectory(self, "Choose export folder") + if not dest: + return + try: + instrument = self.backend.market_registry.instrument(r["instrument_id"]) + folder = Path(dest) / (instrument["market_identifier"].replace(":", "-") + ".entity-instrument") + result = self.backend.market_registry.export_portable_package(r["listing_id"], folder) + QtWidgets.QMessageBox.information( + self, "Portable instrument package", + f"{result['market_identifier']}\n\n{result['destination']}\n\n" + "Includes PDF, Markdown, JSON manifests/passports, verification and SHA256SUMS." + ) + except Exception as e: + QtWidgets.QMessageBox.critical(self, "Export failed", str(e)) + + def trade_selected(self, side): + r = self.selected_market() + if not r: + QtWidgets.QMessageBox.warning(self, "Market", "No active market listing selected."); return + d = OrderDialog(self, r, side) + if d.exec() != QtWidgets.QDialog.Accepted: + return + try: + qty, price = d.result_data + result = self.backend.submit_order(r, side, qty, price) + self.refresh() + QtWidgets.QMessageBox.information( + self, "Order submitted", + f"Order accepted.\nImmediate matches: {len(result['trades'])}\n\n" + "Matched trades remain subject to clearing and settlement evidence." + ) + except Exception as e: + QtWidgets.QMessageBox.critical(self, "Order failed", str(e)) + + def _apply_style(self): + self.setStyleSheet(f""" + * {{ + font-family: "Segoe UI"; + font-size: 10pt; + color: {TEXT}; + }} + QMainWindow, QStackedWidget {{ background: {ROOT_BG}; }} + QWidget {{ color: {TEXT}; }} + QLabel {{ background: transparent; }} + #sidebar {{ background: {SIDEBAR_BG}; border-right: 1px solid {BORDER}; }} + QLabel[class="brand"] {{ font-size: 26pt; font-weight: 800; letter-spacing: 2px; color: {TEXT}; }} + QLabel[class="brandSub"] {{ font-size: 8pt; font-weight: 700; letter-spacing: 2px; color: {ACCENT}; }} + QLabel[class="pageTitle"] {{ font-size: 22pt; font-weight: 700; }} + QLabel[class="pageSub"], QLabel[class="muted"], QLabel[class="monoMuted"] {{ color: {MUTED}; }} + QLabel[class="monoMuted"] {{ font-family: Consolas; }} + QLabel[class="eyebrow"] {{ color: {ACCENT}; font-size: 8pt; font-weight: 700; letter-spacing: 1px; }} + QLabel[class="sectionTitle"] {{ font-size: 11pt; font-weight: 700; letter-spacing: 1px; }} + QLabel[class="metric"] {{ font-size: 20pt; font-weight: 750; }} + QLabel[class="emptyTitle"] {{ font-size: 15pt; font-weight: 700; }} + QLabel[class="dialogTitle"] {{ font-size: 18pt; font-weight: 700; }} + QLabel[class="tickerHero"] {{ color: {ACCENT}; font-family: Consolas; font-size: 18pt; font-weight: 700; }} + QLabel[class="tickerPreview"] {{ background: {SIDEBAR_BG}; border: 1px solid {ACCENT_DARK}; border-radius: 6px; padding: 10px; color: {ACCENT}; font-family: Consolas; font-weight: 700; }} + QLabel[class="chip"] {{ background: {PANEL_2}; border: 1px solid {BORDER}; border-radius: 6px; padding: 7px 11px; color: {MUTED}; font-size: 8pt; font-weight: 700; }} + QLabel[class="identityChip"] {{ background: {PANEL_BG}; border: 1px solid {BORDER}; border-radius: 6px; padding: 6px 11px; font-size: 8pt; }} + QLabel[class="tape"] {{ background: {SIDEBAR_BG}; border: 1px solid {BORDER}; padding: 9px 12px; color: {ACCENT}; font-family: Consolas; font-size: 9pt; }} + QLabel[class="sidebarFoot"] {{ color: {MUTED}; font-size: 8pt; line-height: 1.4; }} + QFrame[class="panel"], QFrame[class="metricCard"], QFrame[class="empty"] {{ + background: {PANEL_BG}; border: 1px solid {BORDER}; border-radius: 8px; + }} + QFrame[class="notice"] {{ background: {PANEL_2}; border: 1px solid {ACCENT_DARK}; border-radius: 7px; }} + QPushButton {{ background: {PANEL_2}; border: 1px solid {BORDER}; border-radius: 6px; padding: 8px 13px; font-weight: 600; }} + QPushButton:hover {{ border-color: {ACCENT_DARK}; background: #1A2027; }} + QPushButton[primary="true"] {{ background: {ACCENT}; color: #0C0E11; border-color: {ACCENT}; font-weight: 800; }} + QPushButton[nav="true"] {{ background: transparent; border: none; text-align: left; padding: 10px 10px; color: {MUTED}; font-size: 9pt; font-weight: 650; }} + QPushButton[nav="true"]:hover {{ background: {PANEL_BG}; color: {TEXT}; }} + QPushButton[nav="true"]:checked {{ background: {PANEL_2}; color: {TEXT}; border-left: 3px solid {ACCENT}; }} + QTableWidget {{ background: {PANEL_BG}; alternate-background-color: {PANEL_BG}; border: 1px solid {BORDER}; border-radius: 7px; selection-background-color: #2A251A; selection-color: {TEXT}; }} + QHeaderView::section {{ background: {SIDEBAR_BG}; color: {MUTED}; border: none; border-bottom: 1px solid {BORDER}; padding: 9px; font-size: 8pt; font-weight: 700; }} + QTableWidget::item {{ border-bottom: 1px solid #181D23; padding: 7px; }} + QTableWidget::item:selected {{ background: #2A251A; color: {TEXT}; }} + QScrollBar:vertical {{ width: 10px; background: {ROOT_BG}; }} + QScrollBar::handle:vertical {{ background: #303844; min-height: 30px; border-radius: 4px; }} + QLineEdit, QComboBox, QSpinBox, QPlainTextEdit {{ + background: {PANEL_2}; border: 1px solid {BORDER}; border-radius: 5px; padding: 7px; selection-background-color: {ACCENT_DARK}; + }} + QLineEdit:focus, QComboBox:focus, QSpinBox:focus, QPlainTextEdit:focus {{ border-color: {ACCENT}; }} + QDialog {{ background: {ROOT_BG}; }} + QTextBrowser[class="detail"] {{ background: {PANEL_BG}; border: 1px solid {BORDER}; border-radius: 7px; padding: 14px; }} + QSplitter::handle {{ background: {ROOT_BG}; width: 8px; height: 8px; }} + QToolTip {{ background: {PANEL_2}; color: {TEXT}; border: 1px solid {BORDER}; }} + """) + +def self_test(state: Path, entity_id: str, wallet_profile: dict | None = None) -> dict: + backend = Backend(state, entity_id) + snap = backend.snapshot() + registry_status = backend.market_registry.status() + onboarding = ONBOARDING.EntityWalletOnboarding(state) + profile = wallet_profile or onboarding.profile_for_entity(entity_id) + auth = onboarding.authenticate(profile) if profile else {"authenticated": False, "reason": "legacy_unprofiled_identity"} + return { + "ready": True, "ui_engine": "PySide6", "design": "ENTITY_DATA_ECONOMY_TERMINAL", + "entity_id": entity_id, "assets": len(snap.get("assets", [])), + "instruments": len(backend.instruments()), "positions": len(snap.get("positions", [])), + "orders": len(snap.get("orders", [])), "venues": len(backend.venues()), + "market_listings": len(backend.market()), "economic_demand_series": len(backend.economic_metrics()), + "issuer_namespace": backend.namespace(), + "asset_disclosures": registry_status["counts"].get("asset_disclosures", 0), + "market_registry_version": registry_status["version"], + "signed_asset_disclosure_required_for_listing": registry_status.get("signed_asset_disclosure_required_for_listing", False), + "wallet_profile_id": profile.get("profile_id") if profile else None, + "wallet_lineage": profile.get("display_path") if profile else None, + "device_bound_login": bool(auth.get("authenticated")) if profile else False, + "login_mode": (profile.get("login_policy") or {}).get("mode") if profile else None, + "public_name_is_alias_not_authority": True, + "device_information_creates_user_identity": False, + "protocol_origin_is_separate_from_user_lineage": True, + "protocol_tax_bps": 0, "cryptocurrency_required": False, + } + +def main(): + ap = argparse.ArgumentParser(description="ENTITY Data Economy Terminal") + ap.add_argument("--state"); ap.add_argument("--entity") + ap.add_argument("--self-test", action="store_true"); ap.add_argument("--self-test-output") + ap.add_argument("--render-preview") + args = ap.parse_args() + state = Path(args.state) if args.state else default_state() + onboarding = ONBOARDING.EntityWalletOnboarding(state) + + wallet_profile = None + if args.entity: + wallet_profile = onboarding.profile_for_entity(args.entity) + else: + wallet_profile = onboarding.active_profile() + if wallet_profile: + args.entity = wallet_profile["active_entity_id"] + + if args.self_test: + if not args.entity: + ms = manifests(state) + if not ms: + raise SystemExit("No ENTITY identity or wallet profile found") + args.entity = ms[0]["entity_id"] + report = self_test(state, args.entity, wallet_profile) + raw = json.dumps(report, indent=2, sort_keys=True) + if args.self_test_output: + Path(args.self_test_output).write_text(raw + "\n", encoding="utf-8") + else: + print(raw) + return + + app = QtWidgets.QApplication(sys.argv) + app.setApplicationName(APP_NAME) + app.setStyle("Fusion") + app.setStyleSheet(startup_stylesheet()) + + if not args.entity: + setup = EntitySetupDialog(None, onboarding) + if setup.exec() != QtWidgets.QDialog.Accepted or not setup.profile: + return + wallet_profile = setup.profile + args.entity = wallet_profile["active_entity_id"] + elif wallet_profile is None and not args.render_preview: + # A legacy explicit --entity remains supported for managed deployments. + pass + elif wallet_profile is not None and not args.render_preview: + login = EntityLoginDialog(None, onboarding) + if login.exec() != QtWidgets.QDialog.Accepted: + return + if login.create_new: + setup = EntitySetupDialog(None, onboarding) + if setup.exec() != QtWidgets.QDialog.Accepted or not setup.profile: + return + wallet_profile = setup.profile + else: + wallet_profile = login.profile + args.entity = wallet_profile["active_entity_id"] + + window = WalletWindow(state, args.entity, wallet_profile) + window.show() + if args.render_preview: + app.processEvents() + QtCore.QTimer.singleShot(300, lambda: ( + window.grab().save(str(Path(args.render_preview))), + app.quit() + )) + sys.exit(app.exec()) + + +if __name__ == "__main__": + main() diff --git a/tools/retire_prelaunch_economic_issuance.py b/tools/retire_prelaunch_economic_issuance.py new file mode 100644 index 00000000..81f83bf5 --- /dev/null +++ b/tools/retire_prelaunch_economic_issuance.py @@ -0,0 +1,35 @@ +from __future__ import annotations +from pathlib import Path +import argparse, importlib.util, json, sys + +ROOT=Path(__file__).resolve().parents[1] + +def load(name:str,path:Path): + spec=importlib.util.spec_from_file_location(name,path) + if spec is None or spec.loader is None: raise RuntimeError(f"cannot load {path}") + mod=importlib.util.module_from_spec(spec); sys.modules[name]=mod; spec.loader.exec_module(mod); return mod + +IDENT=load("entity_prelaunch_identity",ROOT/"src"/"01_Core_Runtime"/"identity"/"canonical_identity.py") +MIG=load("entity_prelaunch_withdrawal",ROOT/"src"/"45_ENTITY_Market"/"prelaunch_withdrawal.py") + +def main(): + ap=argparse.ArgumentParser(description="Audit or withdraw unused prelaunch ENTITY economic issuance without changing EEP 3.0.") + ap.add_argument("--state",required=True,help="ENTITY state directory") + ap.add_argument("--object-id",action="append",default=[],help="Underlying ENTITY object ID; repeatable") + ap.add_argument("--dco-id",action="append",default=[],help="DCO Factory ID; repeatable") + ap.add_argument("--reason",default="Superseded unused prelaunch economic experiment; asset remains active.") + ap.add_argument("--apply",action="store_true",help="Apply withdrawal. Omit for fail-safe audit only.") + args=ap.parse_args() + state=Path(args.state).expanduser().resolve() + vault=IDENT.EntityIdentityVault(state) + migration=MIG.PrelaunchEconomicWithdrawal(state,vault) + if args.apply: + result=migration.apply(args.object_id,args.dco_id,args.reason) + else: + result=migration.audit(args.object_id,args.dco_id) + print(json.dumps(result,indent=2,sort_keys=True,default=str)) + if not args.apply and not result.get("safe_to_withdraw",False): + raise SystemExit(2) + +if __name__=="__main__": + main()