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.
- fleetmesh_core-0.2.0/LICENSE +121 -0
- fleetmesh_core-0.2.0/PKG-INFO +70 -0
- fleetmesh_core-0.2.0/README.md +55 -0
- fleetmesh_core-0.2.0/fleetmesh_core/__init__.py +10 -0
- fleetmesh_core-0.2.0/fleetmesh_core/accounting/__init__.py +7 -0
- fleetmesh_core-0.2.0/fleetmesh_core/accounting/accounting.py +215 -0
- fleetmesh_core-0.2.0/fleetmesh_core/accounting/capacity.py +142 -0
- fleetmesh_core-0.2.0/fleetmesh_core/accounting/cashu.py +661 -0
- fleetmesh_core-0.2.0/fleetmesh_core/accounting/market.py +111 -0
- fleetmesh_core-0.2.0/fleetmesh_core/accounting/paidfetch.py +97 -0
- fleetmesh_core-0.2.0/fleetmesh_core/accounting/standing.py +210 -0
- fleetmesh_core-0.2.0/fleetmesh_core/accounting/strain.py +124 -0
- fleetmesh_core-0.2.0/fleetmesh_core/accounting/window.py +425 -0
- fleetmesh_core-0.2.0/fleetmesh_core/conformance/__init__.py +7 -0
- fleetmesh_core-0.2.0/fleetmesh_core/conformance/__main__.py +6 -0
- fleetmesh_core-0.2.0/fleetmesh_core/conformance/conformance.py +124 -0
- fleetmesh_core-0.2.0/fleetmesh_core/conformance/conformance_ops.py +724 -0
- fleetmesh_core-0.2.0/fleetmesh_core/conformance/errors.py +24 -0
- fleetmesh_core-0.2.0/fleetmesh_core/conformance/harness.py +64 -0
- fleetmesh_core-0.2.0/fleetmesh_core/crypto/__init__.py +7 -0
- fleetmesh_core-0.2.0/fleetmesh_core/crypto/bech32.py +74 -0
- fleetmesh_core-0.2.0/fleetmesh_core/crypto/crypto.py +204 -0
- fleetmesh_core-0.2.0/fleetmesh_core/crypto/curve.py +236 -0
- fleetmesh_core-0.2.0/fleetmesh_core/crypto/giftwrap.py +169 -0
- fleetmesh_core-0.2.0/fleetmesh_core/crypto/http_signatures.py +250 -0
- fleetmesh_core-0.2.0/fleetmesh_core/crypto/keys.py +89 -0
- fleetmesh_core-0.2.0/fleetmesh_core/crypto/nip44.py +144 -0
- fleetmesh_core-0.2.0/fleetmesh_core/crypto/ssb.py +562 -0
- fleetmesh_core-0.2.0/fleetmesh_core/crypto/zk.py +812 -0
- fleetmesh_core-0.2.0/fleetmesh_core/identity/__init__.py +7 -0
- fleetmesh_core-0.2.0/fleetmesh_core/identity/atproto.py +509 -0
- fleetmesh_core-0.2.0/fleetmesh_core/identity/cid.py +199 -0
- fleetmesh_core-0.2.0/fleetmesh_core/identity/did.py +431 -0
- fleetmesh_core-0.2.0/fleetmesh_core/identity/didcomm.py +285 -0
- fleetmesh_core-0.2.0/fleetmesh_core/identity/identity.py +326 -0
- fleetmesh_core-0.2.0/fleetmesh_core/identity/iroh_ticket.py +111 -0
- fleetmesh_core-0.2.0/fleetmesh_core/identity/origin.py +42 -0
- fleetmesh_core-0.2.0/fleetmesh_core/identity/relaylist.py +75 -0
- fleetmesh_core-0.2.0/fleetmesh_core/identity/web_auth.py +518 -0
- fleetmesh_core-0.2.0/fleetmesh_core/peering/__init__.py +7 -0
- fleetmesh_core-0.2.0/fleetmesh_core/peering/badpeer.py +182 -0
- fleetmesh_core-0.2.0/fleetmesh_core/peering/complaint.py +181 -0
- fleetmesh_core-0.2.0/fleetmesh_core/peering/coverage.py +487 -0
- fleetmesh_core-0.2.0/fleetmesh_core/peering/peering.py +592 -0
- fleetmesh_core-0.2.0/fleetmesh_core/peering/projection.py +362 -0
- fleetmesh_core-0.2.0/fleetmesh_core/peering/release.py +219 -0
- fleetmesh_core-0.2.0/fleetmesh_core/peering/reports.py +306 -0
- fleetmesh_core-0.2.0/fleetmesh_core/services/__init__.py +3 -0
- fleetmesh_core-0.2.0/fleetmesh_core/services/discover.py +89 -0
- fleetmesh_core-0.2.0/fleetmesh_core/services/discovery.py +191 -0
- fleetmesh_core-0.2.0/fleetmesh_core/services/dvm.py +614 -0
- fleetmesh_core-0.2.0/fleetmesh_core/services/lifecycle.py +200 -0
- fleetmesh_core-0.2.0/fleetmesh_core/services/nsite.py +327 -0
- fleetmesh_core-0.2.0/fleetmesh_core/services/telemetry.py +89 -0
- fleetmesh_core-0.2.0/fleetmesh_core/services/unplug.py +187 -0
- fleetmesh_core-0.2.0/fleetmesh_core/storage/__init__.py +7 -0
- fleetmesh_core-0.2.0/fleetmesh_core/storage/blossom_auth.py +117 -0
- fleetmesh_core-0.2.0/fleetmesh_core/storage/car.py +139 -0
- fleetmesh_core-0.2.0/fleetmesh_core/storage/linked_data.py +346 -0
- fleetmesh_core-0.2.0/fleetmesh_core/storage/mst.py +498 -0
- fleetmesh_core-0.2.0/fleetmesh_core/storage/negentropy.py +394 -0
- fleetmesh_core-0.2.0/fleetmesh_core/storage/projection.py +630 -0
- fleetmesh_core-0.2.0/fleetmesh_core/storage/replication.py +187 -0
- fleetmesh_core-0.2.0/fleetmesh_core/storage/store.py +824 -0
- fleetmesh_core-0.2.0/fleetmesh_core/storage/store_postgres.py +283 -0
- fleetmesh_core-0.2.0/fleetmesh_core/storage/sync.py +478 -0
- fleetmesh_core-0.2.0/fleetmesh_core/wire/__init__.py +3 -0
- fleetmesh_core-0.2.0/fleetmesh_core/wire/cbor.py +223 -0
- fleetmesh_core-0.2.0/fleetmesh_core/wire/interfaces.py +629 -0
- fleetmesh_core-0.2.0/fleetmesh_core/wire/p2panda.py +409 -0
- fleetmesh_core-0.2.0/fleetmesh_core/wire/schema_files.py +93 -0
- fleetmesh_core-0.2.0/fleetmesh_core/wire/spelling.py +37 -0
- fleetmesh_core-0.2.0/fleetmesh_core/wire/validate.py +618 -0
- fleetmesh_core-0.2.0/fleetmesh_core/wire/varint.py +62 -0
- fleetmesh_core-0.2.0/fleetmesh_core/wire/video_transit.py +541 -0
- fleetmesh_core-0.2.0/fleetmesh_core/wire/wire_types.py +564 -0
- fleetmesh_core-0.2.0/fleetmesh_core.egg-info/PKG-INFO +70 -0
- fleetmesh_core-0.2.0/fleetmesh_core.egg-info/SOURCES.txt +112 -0
- fleetmesh_core-0.2.0/fleetmesh_core.egg-info/dependency_links.txt +1 -0
- fleetmesh_core-0.2.0/fleetmesh_core.egg-info/requires.txt +7 -0
- fleetmesh_core-0.2.0/fleetmesh_core.egg-info/top_level.txt +1 -0
- fleetmesh_core-0.2.0/pyproject.toml +24 -0
- fleetmesh_core-0.2.0/setup.cfg +4 -0
- fleetmesh_core-0.2.0/tests/test_atproto.py +255 -0
- fleetmesh_core-0.2.0/tests/test_badpeer.py +262 -0
- fleetmesh_core-0.2.0/tests/test_capacity.py +138 -0
- fleetmesh_core-0.2.0/tests/test_cashu.py +90 -0
- fleetmesh_core-0.2.0/tests/test_cid.py +59 -0
- fleetmesh_core-0.2.0/tests/test_conformance.py +69 -0
- fleetmesh_core-0.2.0/tests/test_curve.py +52 -0
- fleetmesh_core-0.2.0/tests/test_declared.py +101 -0
- fleetmesh_core-0.2.0/tests/test_did_verify.py +151 -0
- fleetmesh_core-0.2.0/tests/test_discovery.py +123 -0
- fleetmesh_core-0.2.0/tests/test_http_signatures.py +51 -0
- fleetmesh_core-0.2.0/tests/test_iroh_ticket.py +69 -0
- fleetmesh_core-0.2.0/tests/test_linked_data.py +207 -0
- fleetmesh_core-0.2.0/tests/test_mechanisms.py +918 -0
- fleetmesh_core-0.2.0/tests/test_negentropy.py +255 -0
- fleetmesh_core-0.2.0/tests/test_origin.py +57 -0
- fleetmesh_core-0.2.0/tests/test_p2panda.py +212 -0
- fleetmesh_core-0.2.0/tests/test_peering.py +375 -0
- fleetmesh_core-0.2.0/tests/test_peering_projection.py +173 -0
- fleetmesh_core-0.2.0/tests/test_profiles.py +151 -0
- fleetmesh_core-0.2.0/tests/test_projection.py +320 -0
- fleetmesh_core-0.2.0/tests/test_store.py +554 -0
- fleetmesh_core-0.2.0/tests/test_store_postgres.py +419 -0
- fleetmesh_core-0.2.0/tests/test_strain.py +118 -0
- fleetmesh_core-0.2.0/tests/test_strict.py +241 -0
- fleetmesh_core-0.2.0/tests/test_sync.py +261 -0
- fleetmesh_core-0.2.0/tests/test_unplug.py +256 -0
- fleetmesh_core-0.2.0/tests/test_validate.py +96 -0
- fleetmesh_core-0.2.0/tests/test_video_transit.py +230 -0
- fleetmesh_core-0.2.0/tests/test_wire_spelling.py +75 -0
- 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,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
|
+
|