fleetmesh-core 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. fleetmesh_core-0.2.0/LICENSE +121 -0
  2. fleetmesh_core-0.2.0/PKG-INFO +70 -0
  3. fleetmesh_core-0.2.0/README.md +55 -0
  4. fleetmesh_core-0.2.0/fleetmesh_core/__init__.py +10 -0
  5. fleetmesh_core-0.2.0/fleetmesh_core/accounting/__init__.py +7 -0
  6. fleetmesh_core-0.2.0/fleetmesh_core/accounting/accounting.py +215 -0
  7. fleetmesh_core-0.2.0/fleetmesh_core/accounting/capacity.py +142 -0
  8. fleetmesh_core-0.2.0/fleetmesh_core/accounting/cashu.py +661 -0
  9. fleetmesh_core-0.2.0/fleetmesh_core/accounting/market.py +111 -0
  10. fleetmesh_core-0.2.0/fleetmesh_core/accounting/paidfetch.py +97 -0
  11. fleetmesh_core-0.2.0/fleetmesh_core/accounting/standing.py +210 -0
  12. fleetmesh_core-0.2.0/fleetmesh_core/accounting/strain.py +124 -0
  13. fleetmesh_core-0.2.0/fleetmesh_core/accounting/window.py +425 -0
  14. fleetmesh_core-0.2.0/fleetmesh_core/conformance/__init__.py +7 -0
  15. fleetmesh_core-0.2.0/fleetmesh_core/conformance/__main__.py +6 -0
  16. fleetmesh_core-0.2.0/fleetmesh_core/conformance/conformance.py +124 -0
  17. fleetmesh_core-0.2.0/fleetmesh_core/conformance/conformance_ops.py +724 -0
  18. fleetmesh_core-0.2.0/fleetmesh_core/conformance/errors.py +24 -0
  19. fleetmesh_core-0.2.0/fleetmesh_core/conformance/harness.py +64 -0
  20. fleetmesh_core-0.2.0/fleetmesh_core/crypto/__init__.py +7 -0
  21. fleetmesh_core-0.2.0/fleetmesh_core/crypto/bech32.py +74 -0
  22. fleetmesh_core-0.2.0/fleetmesh_core/crypto/crypto.py +204 -0
  23. fleetmesh_core-0.2.0/fleetmesh_core/crypto/curve.py +236 -0
  24. fleetmesh_core-0.2.0/fleetmesh_core/crypto/giftwrap.py +169 -0
  25. fleetmesh_core-0.2.0/fleetmesh_core/crypto/http_signatures.py +250 -0
  26. fleetmesh_core-0.2.0/fleetmesh_core/crypto/keys.py +89 -0
  27. fleetmesh_core-0.2.0/fleetmesh_core/crypto/nip44.py +144 -0
  28. fleetmesh_core-0.2.0/fleetmesh_core/crypto/ssb.py +562 -0
  29. fleetmesh_core-0.2.0/fleetmesh_core/crypto/zk.py +812 -0
  30. fleetmesh_core-0.2.0/fleetmesh_core/identity/__init__.py +7 -0
  31. fleetmesh_core-0.2.0/fleetmesh_core/identity/atproto.py +509 -0
  32. fleetmesh_core-0.2.0/fleetmesh_core/identity/cid.py +199 -0
  33. fleetmesh_core-0.2.0/fleetmesh_core/identity/did.py +431 -0
  34. fleetmesh_core-0.2.0/fleetmesh_core/identity/didcomm.py +285 -0
  35. fleetmesh_core-0.2.0/fleetmesh_core/identity/identity.py +326 -0
  36. fleetmesh_core-0.2.0/fleetmesh_core/identity/iroh_ticket.py +111 -0
  37. fleetmesh_core-0.2.0/fleetmesh_core/identity/origin.py +42 -0
  38. fleetmesh_core-0.2.0/fleetmesh_core/identity/relaylist.py +75 -0
  39. fleetmesh_core-0.2.0/fleetmesh_core/identity/web_auth.py +518 -0
  40. fleetmesh_core-0.2.0/fleetmesh_core/peering/__init__.py +7 -0
  41. fleetmesh_core-0.2.0/fleetmesh_core/peering/badpeer.py +182 -0
  42. fleetmesh_core-0.2.0/fleetmesh_core/peering/complaint.py +181 -0
  43. fleetmesh_core-0.2.0/fleetmesh_core/peering/coverage.py +487 -0
  44. fleetmesh_core-0.2.0/fleetmesh_core/peering/peering.py +592 -0
  45. fleetmesh_core-0.2.0/fleetmesh_core/peering/projection.py +362 -0
  46. fleetmesh_core-0.2.0/fleetmesh_core/peering/release.py +219 -0
  47. fleetmesh_core-0.2.0/fleetmesh_core/peering/reports.py +306 -0
  48. fleetmesh_core-0.2.0/fleetmesh_core/services/__init__.py +3 -0
  49. fleetmesh_core-0.2.0/fleetmesh_core/services/discover.py +89 -0
  50. fleetmesh_core-0.2.0/fleetmesh_core/services/discovery.py +191 -0
  51. fleetmesh_core-0.2.0/fleetmesh_core/services/dvm.py +614 -0
  52. fleetmesh_core-0.2.0/fleetmesh_core/services/lifecycle.py +200 -0
  53. fleetmesh_core-0.2.0/fleetmesh_core/services/nsite.py +327 -0
  54. fleetmesh_core-0.2.0/fleetmesh_core/services/telemetry.py +89 -0
  55. fleetmesh_core-0.2.0/fleetmesh_core/services/unplug.py +187 -0
  56. fleetmesh_core-0.2.0/fleetmesh_core/storage/__init__.py +7 -0
  57. fleetmesh_core-0.2.0/fleetmesh_core/storage/blossom_auth.py +117 -0
  58. fleetmesh_core-0.2.0/fleetmesh_core/storage/car.py +139 -0
  59. fleetmesh_core-0.2.0/fleetmesh_core/storage/linked_data.py +346 -0
  60. fleetmesh_core-0.2.0/fleetmesh_core/storage/mst.py +498 -0
  61. fleetmesh_core-0.2.0/fleetmesh_core/storage/negentropy.py +394 -0
  62. fleetmesh_core-0.2.0/fleetmesh_core/storage/projection.py +630 -0
  63. fleetmesh_core-0.2.0/fleetmesh_core/storage/replication.py +187 -0
  64. fleetmesh_core-0.2.0/fleetmesh_core/storage/store.py +824 -0
  65. fleetmesh_core-0.2.0/fleetmesh_core/storage/store_postgres.py +283 -0
  66. fleetmesh_core-0.2.0/fleetmesh_core/storage/sync.py +478 -0
  67. fleetmesh_core-0.2.0/fleetmesh_core/wire/__init__.py +3 -0
  68. fleetmesh_core-0.2.0/fleetmesh_core/wire/cbor.py +223 -0
  69. fleetmesh_core-0.2.0/fleetmesh_core/wire/interfaces.py +629 -0
  70. fleetmesh_core-0.2.0/fleetmesh_core/wire/p2panda.py +409 -0
  71. fleetmesh_core-0.2.0/fleetmesh_core/wire/schema_files.py +93 -0
  72. fleetmesh_core-0.2.0/fleetmesh_core/wire/spelling.py +37 -0
  73. fleetmesh_core-0.2.0/fleetmesh_core/wire/validate.py +618 -0
  74. fleetmesh_core-0.2.0/fleetmesh_core/wire/varint.py +62 -0
  75. fleetmesh_core-0.2.0/fleetmesh_core/wire/video_transit.py +541 -0
  76. fleetmesh_core-0.2.0/fleetmesh_core/wire/wire_types.py +564 -0
  77. fleetmesh_core-0.2.0/fleetmesh_core.egg-info/PKG-INFO +70 -0
  78. fleetmesh_core-0.2.0/fleetmesh_core.egg-info/SOURCES.txt +112 -0
  79. fleetmesh_core-0.2.0/fleetmesh_core.egg-info/dependency_links.txt +1 -0
  80. fleetmesh_core-0.2.0/fleetmesh_core.egg-info/requires.txt +7 -0
  81. fleetmesh_core-0.2.0/fleetmesh_core.egg-info/top_level.txt +1 -0
  82. fleetmesh_core-0.2.0/pyproject.toml +24 -0
  83. fleetmesh_core-0.2.0/setup.cfg +4 -0
  84. fleetmesh_core-0.2.0/tests/test_atproto.py +255 -0
  85. fleetmesh_core-0.2.0/tests/test_badpeer.py +262 -0
  86. fleetmesh_core-0.2.0/tests/test_capacity.py +138 -0
  87. fleetmesh_core-0.2.0/tests/test_cashu.py +90 -0
  88. fleetmesh_core-0.2.0/tests/test_cid.py +59 -0
  89. fleetmesh_core-0.2.0/tests/test_conformance.py +69 -0
  90. fleetmesh_core-0.2.0/tests/test_curve.py +52 -0
  91. fleetmesh_core-0.2.0/tests/test_declared.py +101 -0
  92. fleetmesh_core-0.2.0/tests/test_did_verify.py +151 -0
  93. fleetmesh_core-0.2.0/tests/test_discovery.py +123 -0
  94. fleetmesh_core-0.2.0/tests/test_http_signatures.py +51 -0
  95. fleetmesh_core-0.2.0/tests/test_iroh_ticket.py +69 -0
  96. fleetmesh_core-0.2.0/tests/test_linked_data.py +207 -0
  97. fleetmesh_core-0.2.0/tests/test_mechanisms.py +918 -0
  98. fleetmesh_core-0.2.0/tests/test_negentropy.py +255 -0
  99. fleetmesh_core-0.2.0/tests/test_origin.py +57 -0
  100. fleetmesh_core-0.2.0/tests/test_p2panda.py +212 -0
  101. fleetmesh_core-0.2.0/tests/test_peering.py +375 -0
  102. fleetmesh_core-0.2.0/tests/test_peering_projection.py +173 -0
  103. fleetmesh_core-0.2.0/tests/test_profiles.py +151 -0
  104. fleetmesh_core-0.2.0/tests/test_projection.py +320 -0
  105. fleetmesh_core-0.2.0/tests/test_store.py +554 -0
  106. fleetmesh_core-0.2.0/tests/test_store_postgres.py +419 -0
  107. fleetmesh_core-0.2.0/tests/test_strain.py +118 -0
  108. fleetmesh_core-0.2.0/tests/test_strict.py +241 -0
  109. fleetmesh_core-0.2.0/tests/test_sync.py +261 -0
  110. fleetmesh_core-0.2.0/tests/test_unplug.py +256 -0
  111. fleetmesh_core-0.2.0/tests/test_validate.py +96 -0
  112. fleetmesh_core-0.2.0/tests/test_video_transit.py +230 -0
  113. fleetmesh_core-0.2.0/tests/test_wire_spelling.py +75 -0
  114. fleetmesh_core-0.2.0/tests/test_zk.py +54 -0
@@ -0,0 +1,121 @@
1
+ Creative Commons Legal Code
2
+
3
+ CC0 1.0 Universal
4
+
5
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12
+ HEREUNDER.
13
+
14
+ Statement of Purpose
15
+
16
+ The laws of most jurisdictions throughout the world automatically confer
17
+ exclusive Copyright and Related Rights (defined below) upon the creator
18
+ and subsequent owner(s) (each and all, an "owner") of an original work of
19
+ authorship and/or a database (each, a "Work").
20
+
21
+ Certain owners wish to permanently relinquish those rights to a Work for
22
+ the purpose of contributing to a commons of creative, cultural and
23
+ scientific works ("Commons") that the public can reliably and without fear
24
+ of later claims of infringement build upon, modify, incorporate in other
25
+ works, reuse and redistribute as freely as possible in any form whatsoever
26
+ and for any purposes, including without limitation commercial purposes.
27
+ These owners may contribute to the Commons to promote the ideal of a free
28
+ culture and the further production of creative, cultural and scientific
29
+ works, or to gain reputation or greater distribution for their Work in
30
+ part through the use and efforts of others.
31
+
32
+ For these and/or other purposes and motivations, and without any
33
+ expectation of additional consideration or compensation, the person
34
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35
+ is an owner of Copyright and Related Rights in the Work, voluntarily
36
+ elects to apply CC0 to the Work and publicly distribute the Work under its
37
+ terms, with knowledge of his or her Copyright and Related Rights in the
38
+ Work and the meaning and intended legal effect of CC0 on those rights.
39
+
40
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
41
+ protected by copyright and related or neighboring rights ("Copyright and
42
+ Related Rights"). Copyright and Related Rights include, but are not
43
+ limited to, the following:
44
+
45
+ i. the right to reproduce, adapt, distribute, perform, display,
46
+ communicate, and translate a Work;
47
+ ii. moral rights retained by the original author(s) and/or performer(s);
48
+ iii. publicity and privacy rights pertaining to a person's image or
49
+ likeness depicted in a Work;
50
+ iv. rights protecting against unfair competition in regards to a Work,
51
+ subject to the limitations in paragraph 4(a), below;
52
+ v. rights protecting the extraction, dissemination, use and reuse of data
53
+ in a Work;
54
+ vi. database rights (such as those arising under Directive 96/9/EC of the
55
+ European Parliament and of the Council of 11 March 1996 on the legal
56
+ protection of databases, and under any national implementation
57
+ thereof, including any amended or successor version of such
58
+ directive); and
59
+ vii. other similar, equivalent or corresponding rights throughout the
60
+ world based on applicable law or treaty, and any national
61
+ implementations thereof.
62
+
63
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
64
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
65
+ irrevocably and unconditionally waives, abandons, and surrenders all of
66
+ Affirmer's Copyright and Related Rights and associated claims and causes
67
+ of action, whether now known or unknown (including existing as well as
68
+ future claims and causes of action), in the Work (i) in all territories
69
+ worldwide, (ii) for the maximum duration provided by applicable law or
70
+ treaty (including future time extensions), (iii) in any current or future
71
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
72
+ including without limitation commercial, advertising or promotional
73
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74
+ member of the public at large and to the detriment of Affirmer's heirs and
75
+ successors, fully intending that such Waiver shall not be subject to
76
+ revocation, rescission, cancellation, termination, or any other legal or
77
+ equitable action to disrupt the quiet enjoyment of the Work by the public
78
+ as contemplated by Affirmer's express Statement of Purpose.
79
+
80
+ 3. Public License Fallback. Should any part of the Waiver for any reason
81
+ be judged legally invalid or ineffective under applicable law, then the
82
+ Waiver shall be preserved to the maximum extent permitted taking into
83
+ account Affirmer's express Statement of Purpose. In addition, to the
84
+ extent the Waiver is so judged Affirmer hereby grants to each affected
85
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
86
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
87
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
88
+ maximum duration provided by applicable law or treaty (including future
89
+ time extensions), (iii) in any current or future medium and for any number
90
+ of copies, and (iv) for any purpose whatsoever, including without
91
+ limitation commercial, advertising or promotional purposes (the
92
+ "License"). The License shall be deemed effective as of the date CC0 was
93
+ applied by Affirmer to the Work. Should any part of the License for any
94
+ reason be judged legally invalid or ineffective under applicable law, such
95
+ partial invalidity or ineffectiveness shall not invalidate the remainder
96
+ of the License, and in such case Affirmer hereby affirms that he or she
97
+ will not (i) exercise any of his or her remaining Copyright and Related
98
+ Rights in the Work or (ii) assert any associated claims and causes of
99
+ action with respect to the Work, in either case contrary to Affirmer's
100
+ express Statement of Purpose.
101
+
102
+ 4. Limitations and Disclaimers.
103
+
104
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
105
+ surrendered, licensed or otherwise affected by this document.
106
+ b. Affirmer offers the Work as-is and makes no representations or
107
+ warranties of any kind concerning the Work, express, implied,
108
+ statutory or otherwise, including without limitation warranties of
109
+ title, merchantability, fitness for a particular purpose, non
110
+ infringement, or the absence of latent or other defects, accuracy, or
111
+ the present or absence of errors, whether or not discoverable, all to
112
+ the greatest extent permissible under applicable law.
113
+ c. Affirmer disclaims responsibility for clearing rights of other persons
114
+ that may apply to the Work or any use thereof, including without
115
+ limitation any person's Copyright and Related Rights in the Work.
116
+ Further, Affirmer disclaims responsibility for obtaining any necessary
117
+ consents, permissions or other rights required for any use of the
118
+ Work.
119
+ d. Affirmer understands and acknowledges that Creative Commons is not a
120
+ party to this document and has no duty or obligation with respect to
121
+ this CC0 or use of the Work.
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: fleetmesh-core
3
+ Version: 0.2.0
4
+ Summary: Core protocols, typed contracts, and wire primitives for Fleetmesh
5
+ License: CC0-1.0
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: fleetmesh-schema>=0.1.0
10
+ Provides-Extra: postgres
11
+ Requires-Dist: psycopg[binary]==3.3.6; extra == "postgres"
12
+ Provides-Extra: crypto
13
+ Requires-Dist: cryptography>=42; extra == "crypto"
14
+ Dynamic: license-file
15
+
16
+ # Fleetmesh Core
17
+
18
+ Core protocol primitives, typed contracts, Negentropy reconciliation, and signed budget accounting for **Fleetmesh** ([fleetmesh.org](https://www.fleetmesh.org)).
19
+
20
+ ## Domain Subpackages
21
+
22
+ `fleetmesh_core` is standard-library-only Python (zero external dependencies, no I/O, no wall clock) organized into eight domain subpackages:
23
+
24
+ - **`fleetmesh_core.crypto`**:
25
+ - `crypto.py`, `keys.py`, `curve.py`, `bech32.py`: BIP-340 Schnorr, X25519 ECDH, Ed25519, Multikey (`z6Mk...`), HKDF-SHA256, ChaCha20-Poly1305.
26
+ - `nip44.py`, `giftwrap.py`: NIP-44 v2 padded encryption and NIP-59 gift-wrapping (kinds 1059 / 21059).
27
+ - `zk.py`: BN254 pairing and Groth16 verification for `compute:zk-verify-groth16/1.0` (`vectors/groth16.json`).
28
+ - `http_signatures.py`: RFC 9421 HTTP Message Signatures and Digest headers for ActivityPub federation.
29
+ - `ssb.py`: Secure Scuttlebutt Secret Handshake (SHS), Box Stream framing, and V1 feed verification.
30
+ - **`fleetmesh_core.identity`**:
31
+ - `identity.py`, `did.py`, `origin.py`, `relaylist.py`: Descriptor validation, PoW, blinded tags, `did:nostr`, `did:key`, `did:peer:2`, and NIP-65 relay lists.
32
+ - `atproto.py`: ATProto `did:plc` genesis operation signing, `did:web` resolution, and K-256 ECDSA verification.
33
+ - `didcomm.py`: DIDComm v2 ECDH-1PU / ECDH-ES envelope packing.
34
+ - `cid.py`, `iroh_ticket.py`: CIDv1 (`bafkrei...`) content addressing and Iroh `NodeTicket` serialization.
35
+ - **`fleetmesh_core.peering`**:
36
+ - `peering.py`, `coverage.py`, `projection.py`: Bilateral `peering/1.0` state machine, Kind 30803 coverage claims and handovers, and NIP-85 Kind 30385 projections.
37
+ - `complaint.py`, `reports.py`, `release.py`, `badpeer.py`: `complaint/1.0` notices, Kind 30802 admissibility, Kind 9900/9901 signed releases, and synthetic adversarial peer.
38
+ - **`fleetmesh_core.accounting`**:
39
+ - `accounting.py`, `window.py`, `capacity.py`, `strain.py`, `standing.py`: Monotonic receipt chains, AIMD window evaluation, capacity shedding, directional strain, and `standing/1.0`.
40
+ - `cashu.py`, `paidfetch.py`, `market.py`: Cashu BDHKE (NUT-00..24), HTTP 402 PaidFetch quotes, and compute market commitments.
41
+ - **`fleetmesh_core.storage`**:
42
+ - `store.py`, `store_postgres.py`: SQLite and PostgreSQL event store and range indexing.
43
+ - `negentropy.py`, `sync.py`, `replication.py`: NIP-77 V1 range-based set reconciliation, delivery envelopes, and blob replication.
44
+ - `blossom_auth.py`, `linked_data.py`: Blossom BUD-01/04 authorization and JSON-LD / Schema.org projections.
45
+ - `car.py`, `mst.py`: Content Addressable aRchives (CAR v1) and ATProto Merkle Search Trees (MST).
46
+ - **`fleetmesh_core.services`**:
47
+ - `compute.py`, `dvm.py`, `gossip.py`, `mediation.py`, `nsite.py`, `nwc.py`, `settlement.py`, `sidecar.py`, `ucan.py`.
48
+ - **`fleetmesh_core.wire`**:
49
+ - `interfaces.py`, `wire_types.py`, `validate.py`, `schema_files.py`: Typed contracts, strict wire parsers, and cached schema loaders.
50
+ - `cbor.py`, `p2panda.py`: Canonical DAG-CBOR (with Tag 42 CID links) and p2panda Ed25519 + CBOR append-only operation logs.
51
+ - **`fleetmesh_core.conformance`**:
52
+ - `runner.py`: Clean-room JSON-lines conformance harness responder (`python3 -m fleetmesh_core.conformance`).
53
+
54
+ ## Installation
55
+
56
+ ```sh
57
+ pip install -e .
58
+ ```
59
+
60
+ ## Running Tests
61
+
62
+ ```sh
63
+ PYTHONPATH=. python3 tests/test_discovery.py
64
+ PYTHONPATH=. python3 tests/test_negentropy.py
65
+ PYTHONPATH=. python3 tests/test_strain.py
66
+ ```
67
+
68
+ ## Licence
69
+
70
+ [CC0 1.0 Universal](LICENSE)
@@ -0,0 +1,55 @@
1
+ # Fleetmesh Core
2
+
3
+ Core protocol primitives, typed contracts, Negentropy reconciliation, and signed budget accounting for **Fleetmesh** ([fleetmesh.org](https://www.fleetmesh.org)).
4
+
5
+ ## Domain Subpackages
6
+
7
+ `fleetmesh_core` is standard-library-only Python (zero external dependencies, no I/O, no wall clock) organized into eight domain subpackages:
8
+
9
+ - **`fleetmesh_core.crypto`**:
10
+ - `crypto.py`, `keys.py`, `curve.py`, `bech32.py`: BIP-340 Schnorr, X25519 ECDH, Ed25519, Multikey (`z6Mk...`), HKDF-SHA256, ChaCha20-Poly1305.
11
+ - `nip44.py`, `giftwrap.py`: NIP-44 v2 padded encryption and NIP-59 gift-wrapping (kinds 1059 / 21059).
12
+ - `zk.py`: BN254 pairing and Groth16 verification for `compute:zk-verify-groth16/1.0` (`vectors/groth16.json`).
13
+ - `http_signatures.py`: RFC 9421 HTTP Message Signatures and Digest headers for ActivityPub federation.
14
+ - `ssb.py`: Secure Scuttlebutt Secret Handshake (SHS), Box Stream framing, and V1 feed verification.
15
+ - **`fleetmesh_core.identity`**:
16
+ - `identity.py`, `did.py`, `origin.py`, `relaylist.py`: Descriptor validation, PoW, blinded tags, `did:nostr`, `did:key`, `did:peer:2`, and NIP-65 relay lists.
17
+ - `atproto.py`: ATProto `did:plc` genesis operation signing, `did:web` resolution, and K-256 ECDSA verification.
18
+ - `didcomm.py`: DIDComm v2 ECDH-1PU / ECDH-ES envelope packing.
19
+ - `cid.py`, `iroh_ticket.py`: CIDv1 (`bafkrei...`) content addressing and Iroh `NodeTicket` serialization.
20
+ - **`fleetmesh_core.peering`**:
21
+ - `peering.py`, `coverage.py`, `projection.py`: Bilateral `peering/1.0` state machine, Kind 30803 coverage claims and handovers, and NIP-85 Kind 30385 projections.
22
+ - `complaint.py`, `reports.py`, `release.py`, `badpeer.py`: `complaint/1.0` notices, Kind 30802 admissibility, Kind 9900/9901 signed releases, and synthetic adversarial peer.
23
+ - **`fleetmesh_core.accounting`**:
24
+ - `accounting.py`, `window.py`, `capacity.py`, `strain.py`, `standing.py`: Monotonic receipt chains, AIMD window evaluation, capacity shedding, directional strain, and `standing/1.0`.
25
+ - `cashu.py`, `paidfetch.py`, `market.py`: Cashu BDHKE (NUT-00..24), HTTP 402 PaidFetch quotes, and compute market commitments.
26
+ - **`fleetmesh_core.storage`**:
27
+ - `store.py`, `store_postgres.py`: SQLite and PostgreSQL event store and range indexing.
28
+ - `negentropy.py`, `sync.py`, `replication.py`: NIP-77 V1 range-based set reconciliation, delivery envelopes, and blob replication.
29
+ - `blossom_auth.py`, `linked_data.py`: Blossom BUD-01/04 authorization and JSON-LD / Schema.org projections.
30
+ - `car.py`, `mst.py`: Content Addressable aRchives (CAR v1) and ATProto Merkle Search Trees (MST).
31
+ - **`fleetmesh_core.services`**:
32
+ - `compute.py`, `dvm.py`, `gossip.py`, `mediation.py`, `nsite.py`, `nwc.py`, `settlement.py`, `sidecar.py`, `ucan.py`.
33
+ - **`fleetmesh_core.wire`**:
34
+ - `interfaces.py`, `wire_types.py`, `validate.py`, `schema_files.py`: Typed contracts, strict wire parsers, and cached schema loaders.
35
+ - `cbor.py`, `p2panda.py`: Canonical DAG-CBOR (with Tag 42 CID links) and p2panda Ed25519 + CBOR append-only operation logs.
36
+ - **`fleetmesh_core.conformance`**:
37
+ - `runner.py`: Clean-room JSON-lines conformance harness responder (`python3 -m fleetmesh_core.conformance`).
38
+
39
+ ## Installation
40
+
41
+ ```sh
42
+ pip install -e .
43
+ ```
44
+
45
+ ## Running Tests
46
+
47
+ ```sh
48
+ PYTHONPATH=. python3 tests/test_discovery.py
49
+ PYTHONPATH=. python3 tests/test_negentropy.py
50
+ PYTHONPATH=. python3 tests/test_strain.py
51
+ ```
52
+
53
+ ## Licence
54
+
55
+ [CC0 1.0 Universal](LICENSE)
@@ -0,0 +1,10 @@
1
+ """fleetmesh executable reference implementation.
2
+
3
+ Standard library only, no I/O, no clock, no network, and every constant read
4
+ from schema/. Contains reference mechanisms for Nostr event storage,
5
+ reconciliation, peering, accounting, capacity management, and reporting.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ __version__ = '0.2.0'
10
+
@@ -0,0 +1,7 @@
1
+ """Accounting, capacity limits, settlement, Cashu, and strain."""
2
+ from __future__ import annotations
3
+
4
+ from . import accounting
5
+
6
+ __all__ = ['accounting']
7
+
@@ -0,0 +1,215 @@
1
+ """Receipts, their hash chain, and telling a dead peer from a lying one.
2
+
3
+ The chain is what stops a subject rewriting an earlier claim once it learns what
4
+ the issuer measured. The issuer keeps the head, which is 32 bytes per window,
5
+ and the most recent body, because that body is the claim the window is
6
+ evaluated against and the evidence any report about it must carry.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ from typing import Any, Mapping, Optional, Sequence
12
+
13
+ from ..crypto import crypto
14
+
15
+ from ..wire import schema_files
16
+ from ..wire.interfaces import ReceiptBody
17
+
18
+ CONSTANTS = schema_files.load_constants()
19
+
20
+ ZERO: str = '00' * 32
21
+
22
+
23
+ def canonical(receipt: Mapping[str, Any]) -> bytes:
24
+ """constants.json canonical.form: keys sorted by code point, no whitespace -
25
+ crypto.canonical, with one difference:
26
+
27
+ `sig` is excluded, because it is a signature over this. Excluding it means
28
+ the chain link is the same value whether or not the receipt has been signed
29
+ yet, so signing changes no head and breaks no chain.
30
+ """
31
+ return crypto.canonical({k: v for k, v in receipt.items() if k != 'sig'})
32
+
33
+
34
+ def link(receipt: Mapping[str, Any]) -> str:
35
+ return hashlib.sha256(canonical(receipt)).hexdigest()
36
+
37
+
38
+ def sign_receipt(body: ReceiptBody, sk_hex: str) -> ReceiptBody:
39
+ """The subject signs its own claim. BIP-340 over the chain link, which is
40
+ already SHA-256 of the canonical body, so no second construction is defined
41
+ and the head the issuer acknowledges is the message that was signed.
42
+
43
+ This is what makes a receipt evidence. Without it a report about divergence
44
+ cites a number only the reporter has ever seen the subject state, which is
45
+ hearsay; with it the subject's half of the disagreement is pinned and the
46
+ reporter is trusted only for its own measurement.
47
+ """
48
+ signed: ReceiptBody = {**body, 'sig': crypto.schnorr_sign(bytes.fromhex(link(body)), sk_hex)}
49
+ return signed
50
+
51
+
52
+ def verify_receipt(body: Mapping[str, Any], subject_pubkey: str) -> bool:
53
+ """True when this receipt carries the subject's signature over its own link."""
54
+ sig = body.get('sig')
55
+ if not sig:
56
+ return False
57
+ try:
58
+ return crypto.schnorr_verify(bytes.fromhex(link(body)), subject_pubkey, sig)
59
+ except (ValueError, TypeError):
60
+ return False
61
+
62
+
63
+ def build_chain(grant_id: str, window: int, counter_series: Sequence[Mapping[str, int]], start_at: int = 1787000000, step: int = 600) -> Mapping[str, Any]:
64
+ """Produce a well-formed chain. Counters are cumulative and monotonic."""
65
+ chain = []
66
+ prev = ZERO
67
+ for seq, counters in enumerate(counter_series):
68
+ body = {'grant': grant_id, 'window': window, 'seq': seq, 'prev': prev,
69
+ 'counters': dict(counters), 'at': start_at + seq * step}
70
+ h = link(body)
71
+ chain.append({'receipt': body, 'canonical': canonical(body).decode(), 'sha256': h})
72
+ prev = h
73
+ return {'chain': chain, 'head': prev}
74
+
75
+
76
+ def verify_chain(chain: Sequence[Mapping[str, Any]], head: Optional[str] = None) -> list[str]:
77
+ """Check every link, sequence number and monotonicity claim.
78
+
79
+ Returns a list of failures; empty means the chain holds. A subject that
80
+ rewrote seq 3 after the fact cannot also produce a matching seq 4.
81
+
82
+ This is strict about sequence, and stays strict, because what it checks is
83
+ a *complete* chain carried as evidence: a report cites what its subject
84
+ signed, and a chain with a hole in it is not the artefact it claims to be.
85
+ Live intake is the other case and is deliberately not this one -
86
+ `node.budget.on_receipt` tolerates a gap, because there the missing link is
87
+ the wire rather than the subject, and refusing it costs a whole window.
88
+ """
89
+ out: list[str] = []
90
+ prev = ZERO
91
+ last: Optional[Mapping[str, int]] = None
92
+ for i, entry in enumerate(chain):
93
+ r = entry['receipt']
94
+ if r.get('seq') != i:
95
+ out.append('link %d: seq is %s' % (i, r.get('seq')))
96
+ if r.get('prev') != prev:
97
+ out.append('link %d: prev does not chain' % i)
98
+ if entry.get('canonical') is not None and entry['canonical'] != canonical(r).decode():
99
+ out.append('link %d: canonical form does not match the body' % i)
100
+ h = link(r)
101
+ if entry.get('sha256') and entry['sha256'] != h:
102
+ out.append('link %d: stated hash is wrong' % i)
103
+ if last is not None:
104
+ for k, v in r.get('counters', {}).items():
105
+ if k in last and v < last[k]:
106
+ out.append('link %d: counter %s went backwards' % (i, k))
107
+ last = r.get('counters', {})
108
+ prev = h
109
+ if head is not None and prev != head:
110
+ out.append('head does not match the final link')
111
+ return out
112
+
113
+
114
+ def admissible_prefix(receipts: Sequence[Mapping[str, Any]]) -> tuple[list[Mapping[str, Any]], bool]:
115
+ """The longest gap-free, chain-verified prefix of a stored receipt list,
116
+ and whether anything was cut.
117
+
118
+ Live intake (`node.budget.on_receipt`) tolerates a lost link: a receipt
119
+ at seq 5 after seq 3 is stored, because the hole is the wire's and
120
+ refusing it costs a window. `verify_chain` is strict for the same reason
121
+ in the opposite direction: a chain cited as evidence must be the artefact
122
+ the subject signed, whole. Reporter used to attach every stored body, so
123
+ an S2 about a peer that lost one receipt on the wire carried a chain a
124
+ verifier would reject on sight - and a report with inadmissible evidence
125
+ is discarded unread. This keeps what verifies, from seq 0 up to the first
126
+ hole or broken link, so what is published is always admissible; partial
127
+ is reported so the caller can say so.
128
+ """
129
+ kept: list[Mapping[str, Any]] = []
130
+ prev = ZERO
131
+ last: Optional[Mapping[str, int]] = None
132
+ for i, r in enumerate(receipts):
133
+ if r.get('seq') != i or r.get('prev') != prev:
134
+ break
135
+ if last is not None and any(k in last and v < last[k] for k, v in r.get('counters', {}).items()):
136
+ break
137
+ kept.append(dict(r))
138
+ prev = link(r)
139
+ last = r.get('counters', {})
140
+ return kept, len(kept) != len(receipts)
141
+
142
+
143
+ def silence(measured: Mapping[str, int]) -> Mapping[str, Any]:
144
+ """Active or inactive silence, per the partition-tolerance rule.
145
+
146
+ Receipts missing while the peer moved bytes is a misreport. Receipts missing
147
+ while it moved nothing is a partition, and FLP says you cannot tell a dead
148
+ node from a quiet one - so the protocol does not try, and prices only the
149
+ case where physical traffic contradicts the silence.
150
+ """
151
+ egress = sum(v for k, v in measured.items()
152
+ if k in ('events_in', 'bytes_in', 'bytes_out', 'req', 'sync_ranges'))
153
+ if egress > 0:
154
+ return {'state': 'active-silence', 'severity': 'S2',
155
+ 'remedy': 'limit x %s, tier to probation, report published'
156
+ % CONSTANTS['grant']['aimd_decrease_s2']}
157
+ return {'state': 'inactive-silence', 'severity': None,
158
+ 'remedy': 'not evaluated, streak neither advanced nor broken, no penalty, no report'}
159
+
160
+
161
+ def window_claim(receipts: Sequence[Mapping[str, Any]], measured: Mapping[str, int]) -> tuple[str, Optional[dict[str, int]]]:
162
+ """What a window is evaluated against, per constants.json grant.window_evaluation.
163
+
164
+ receipts are the bodies received this window, in order. The most recent one
165
+ is the claim: counters are cumulative, so it already states the whole window,
166
+ and the chain makes it binding. A subject that sent receipts and then went
167
+ quiet is evaluated on what it last said against what was measured after -
168
+ which is how a leaf that syncs and then sleeps produces no finding, and a
169
+ peer that kept moving bytes after its last receipt is priced by divergence.
170
+ Returns (state, counters): ('claim', counters), ('active-silence', None) or
171
+ ('not-evaluated', None).
172
+
173
+ None means there is no claim, not that there is nothing to evaluate. A
174
+ window measured with no receipt is still evaluated - against the empty
175
+ claim, which is what vectors/window.json's "no receipt at all" case does -
176
+ and active silence is a *floor* of S2 on top of that. Skipping the
177
+ arithmetic would price a peer that overran and said nothing below the same
178
+ peer reporting it honestly, which is the inversion docs/mechanisms.md § 9
179
+ describes for divergence, in its other form.
180
+ """
181
+ if receipts:
182
+ return 'claim', dict(receipts[-1]['counters'])
183
+ return ('active-silence' if silence(measured)['severity'] else 'not-evaluated'), None
184
+
185
+
186
+ def close_is_final(close_at: int, window_end: int, window_seconds: int) -> bool:
187
+ """A close arriving within the grace period is accepted as the window's final
188
+ receipt; later than that it is evaluated like any other receipt.
189
+
190
+ The grace span is half-open like the window itself: [window_end,
191
+ window_end + grace). The evaluator fires once `now >= window_end + grace`
192
+ (node budget/windows.py), so a close stamped at exactly that instant
193
+ arrives at or after evaluation and is late, not final."""
194
+ return close_at < window_end + window_grace(window_seconds)
195
+
196
+
197
+ def epoch(unix_seconds: int, window_seconds: int) -> int:
198
+ """floor(t / window). Derived from the clock, so it cannot pause.
199
+
200
+ A dormant span advances the epoch counter and simply is not evaluated:
201
+ the streak neither advances nor breaks, and the next active window starts
202
+ a fresh chain at seq 0. The window is [epoch * window, (epoch+1) * window).
203
+ """
204
+ return unix_seconds // window_seconds
205
+
206
+
207
+ def window_bounds(epoch_index: int, window_seconds: int) -> tuple[int, int]:
208
+ return (epoch_index * window_seconds, (epoch_index + 1) * window_seconds)
209
+
210
+
211
+ def window_grace(window_seconds: int) -> int:
212
+ """Clock-skew allowance at window close, in whole seconds (floored, so
213
+ the allowance never exceeds the published fraction)."""
214
+ return int(window_seconds * CONSTANTS['grant']['window_grace_fraction'])
215
+
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env python3
2
+ """Reference implementation of the node's own ceiling and the shed ladder.
3
+
4
+ Standard library only. Every constant is read from schema/constants.json rather
5
+ than hardcoded, so this file and the protocol version digest cannot disagree.
6
+
7
+ A grant bounds one peer. Nothing bounded the node. `Meter` is per peer and the
8
+ tick loops them, so twenty anchor peerings each perfectly inside its grant is
9
+ 960,000 events an hour with nobody at fault anywhere: every branch within its
10
+ limit, and the trunk with none. That is the fragility this file answers.
11
+
12
+ Two decisions worth stating here rather than leaving to a reader.
13
+
14
+ **The total is the operator's number and this file states no default.** The
15
+ specification says connectivity and not capability is what separates one
16
+ deployment from another, so a Raspberry Pi and a forty-core gateway both declare
17
+ anchor and no per-class table could be honest about either. What is normative is
18
+ the ladder. A node with no declared total measures its pressure and reports it
19
+ and sheds nothing, because refusing an honest peer against a limit nobody chose
20
+ is worse than the overload it would be avoiding. `suggest` exists so an operator
21
+ has a number to start from rather than a blank.
22
+
23
+ **Shedding is announced, and it is not adverse.** Every rung says what it is
24
+ doing - a decline code, a republished coverage claim, an amend carrying
25
+ `cause: capacity`. A node that shed quietly would be read as partitioned, which
26
+ is the one conclusion this protocol keeps having to unlearn, and a subject that
27
+ read a capacity shed as a remedy would price its issuer for owning a small
28
+ machine.
29
+ """
30
+ from __future__ import annotations
31
+
32
+ from typing import Iterable, Mapping, Optional, Sequence
33
+
34
+ from . import window
35
+
36
+ from ..wire import schema_files# window.as_limits: a grant carries integers
37
+ from ..wire.interfaces import CapacitySummary
38
+
39
+ C = schema_files.load_constants()['capacity']
40
+
41
+ COUNTERS: tuple[str, ...] = tuple(C['counters'])
42
+ LADDER: tuple[tuple[float, str], ...] = tuple((r['at'], r['action']) for r in C['shed'])
43
+ ACTIONS: tuple[str, ...] = tuple(a for _, a in LADDER)
44
+
45
+
46
+ def pressure(measured: Mapping[str, int | float], total: Mapping[str, int | float]) -> tuple[float, Optional[str]]:
47
+ """(fraction, binding counter) for one window's aggregate against a total.
48
+
49
+ The fraction is the max over counters, not a mean: a node that has run out
50
+ of one thing has run out. `total` is a dict of the same counter names; a
51
+ counter the operator did not declare is not measured, and a total of zero or
52
+ less is not a declaration.
53
+ """
54
+ worst: float = 0.0
55
+ binding: Optional[str] = None
56
+ for name in COUNTERS:
57
+ cap = total.get(name)
58
+ if not cap or cap <= 0:
59
+ continue
60
+ share = measured.get(name, 0) / float(cap)
61
+ if share > worst:
62
+ worst, binding = share, name
63
+ return worst, binding
64
+
65
+
66
+ def rungs(p: float) -> list[str]:
67
+ """Every shed action engaged at pressure p, in ladder order."""
68
+ return [a for at, a in LADDER if p >= at]
69
+
70
+
71
+ def engaged(p: float, held: Sequence[str] = ()) -> list[str]:
72
+ """The rungs a node should be holding now, given what it holds already.
73
+
74
+ Rungs engage each at its own threshold and release together below
75
+ `restore_below`. Releasing each at the threshold that engaged it would flap
76
+ a node sitting on a boundary, and each flap costs its peers an amend.
77
+ """
78
+ if p < C['restore_below']:
79
+ return []
80
+ now = set(rungs(p)) | set(held)
81
+ return [a for a in ACTIONS if a in now]
82
+
83
+
84
+ def shed_floor(probation: Mapping[str, int | float]) -> dict[str, int]:
85
+ """The lowest a capacity shed may take a limit, from the probation ceilings.
86
+
87
+ Deliberately not the probation ceiling itself. A peering opens at that
88
+ ceiling, so a floor there would make the amend_down rung do nothing at all
89
+ for exactly the peers a busy node has most of - declared and never read,
90
+ which is a shape this specification keeps finding in itself. Deliberately
91
+ not zero either: zero for 24 hours is what S3 does to an offender, and a
92
+ shed is not a remedy. What is left is enough for a receipt and a trickle,
93
+ which is what keeps a small peering accountable rather than silent.
94
+ """
95
+ f: float = C['shed_floor_fraction']
96
+ # Floored, like every other limit. A fractional floor and an integer result
97
+ # do not compare the way anyone expects: max(335544.3, x) written into a
98
+ # grant becomes 335544, which is below the floor it was just held to.
99
+ return window.as_limits({k: v * f for k, v in probation.items()})
100
+
101
+
102
+ def reduce(limits: Mapping[str, int | float], floor: Mapping[str, int | float], fraction: Optional[float] = None) -> dict[str, int]:
103
+ """Scale a grant's limits for a capacity shed, never below the floor.
104
+
105
+ A capacity shed makes a peer smaller and never makes it a stranger: the tier
106
+ does not move and the peering is not ended. That is what separates it from
107
+ an S2, which is the ladder's business and not this file's. Pass the floor
108
+ from `shed_floor`.
109
+ """
110
+ f = C['amend_down_fraction'] if fraction is None else fraction
111
+ return window.as_limits({k: max(floor.get(k, 0), v * f) if k in floor else v * f
112
+ for k, v in limits.items()})
113
+
114
+
115
+ def suggest(windows: Iterable[Mapping[str, int | float]]) -> dict[str, int | float]:
116
+ """A total to start from, per counter: the largest window observed.
117
+
118
+ `windows` is an iterable of aggregate measurements, one per closed window.
119
+ This is a report of what the machine has already carried and not a claim
120
+ about what it can. An operator raises it deliberately or lowers it after
121
+ watching the box, and either way the number is theirs.
122
+ """
123
+ out: dict[str, int | float] = {}
124
+ for w in windows:
125
+ for name in COUNTERS:
126
+ v = w.get(name, 0)
127
+ if v > out.get(name, 0):
128
+ out[name] = v
129
+ return out
130
+
131
+
132
+ def summary(measured: Mapping[str, int | float], total: Mapping[str, int | float]) -> CapacitySummary:
133
+ """What the health block prints. Undeclared is a state, not a zero."""
134
+ if not any(total.get(n) for n in COUNTERS):
135
+ return {'declared': False, 'pressure': None, 'binding': None,
136
+ 'rungs': [], 'measured': {k: measured.get(k, 0) for k in COUNTERS
137
+ if measured.get(k)}}
138
+ p, binding = pressure(measured, total)
139
+ return {'declared': True, 'pressure': round(p, 4), 'binding': binding,
140
+ 'rungs': rungs(p),
141
+ 'measured': {k: measured.get(k, 0) for k in COUNTERS if measured.get(k)}}
142
+