fleetmesh-node 0.2.0__py3-none-any.whl
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_node-0.2.0.dist-info/METADATA +57 -0
- fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
- fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
- fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
- fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
- fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
- node/RELEASE +1 -0
- node/__init__.py +35 -0
- node/app/__init__.py +49 -0
- node/app/commands.py +265 -0
- node/app/config.py +131 -0
- node/app/control.py +670 -0
- node/app/core.py +867 -0
- node/app/plugins.py +76 -0
- node/app/router.py +83 -0
- node/app/socket.py +431 -0
- node/app/systemd.py +36 -0
- node/app/views.py +204 -0
- node/channels/__init__.py +378 -0
- node/channels/bus.py +331 -0
- node/channels/journal.py +46 -0
- node/commands/__init__.py +140 -0
- node/commands/adapter.py +210 -0
- node/commands/base.py +278 -0
- node/commands/cluster_cmds.py +183 -0
- node/commands/compute_cmds.py +401 -0
- node/commands/core.py +474 -0
- node/commands/gossip_cmds.py +106 -0
- node/commands/nsite_cmds.py +141 -0
- node/commands/peering.py +402 -0
- node/commands/plugin_cmds.py +135 -0
- node/commands/sidecar_cmds.py +120 -0
- node/commands/sync.py +388 -0
- node/commands/transport_cmds.py +451 -0
- node/commands/video_cmds.py +99 -0
- node/compartment/__init__.py +124 -0
- node/compartment/base.py +868 -0
- node/compartment/command.py +458 -0
- node/compartment/context.py +163 -0
- node/compartment/every.py +108 -0
- node/compartment/handler.py +131 -0
- node/compartment/lifecycle.py +460 -0
- node/compartment/lint.py +388 -0
- node/compartment/need.py +158 -0
- node/compartment/problem.py +521 -0
- node/compartment/refusals.py +120 -0
- node/compartment/registry.py +790 -0
- node/compartment/schema.py +290 -0
- node/compartment/sidecar.py +499 -0
- node/compartment/stores.py +662 -0
- node/compartment/wire.py +225 -0
- node/compartment/worker.py +61 -0
- node/config/__init__.py +43 -0
- node/config/base.py +116 -0
- node/config/change.py +441 -0
- node/config/changestore.py +201 -0
- node/config/compartments.py +1144 -0
- node/config/node.py +318 -0
- node/config/parameter.py +563 -0
- node/config/protocols.py +274 -0
- node/config/records.py +171 -0
- node/examples/__init__.py +1 -0
- node/examples/demo.py +226 -0
- node/examples/follower.py +46 -0
- node/interfaces.py +235 -0
- node/keystore/__init__.py +27 -0
- node/keystore/core.py +103 -0
- node/keystore/enclave.py +688 -0
- node/meshnode.py +111 -0
- node/metrics/__init__.py +396 -0
- node/metrics/adapters.py +102 -0
- node/metrics/face.py +110 -0
- node/planes/__init__.py +21 -0
- node/planes/identity/__init__.py +20 -0
- node/planes/identity/announce/__init__.py +9 -0
- node/planes/identity/announce/compartment.py +447 -0
- node/planes/identity/announce/types.py +26 -0
- node/planes/identity/discovery/__init__.py +9 -0
- node/planes/identity/discovery/compartment.py +522 -0
- node/planes/identity/discovery/types.py +13 -0
- node/planes/identity/linked/__init__.py +1 -0
- node/planes/identity/linked/compartment.py +270 -0
- node/planes/identity/linked/types.py +25 -0
- node/planes/identity/presence/__init__.py +7 -0
- node/planes/identity/presence/compartment.py +409 -0
- node/planes/identity/presence/types.py +14 -0
- node/planes/operations/__init__.py +13 -0
- node/planes/operations/scheduler/__init__.py +9 -0
- node/planes/operations/scheduler/compartment.py +353 -0
- node/planes/operations/scheduler/types.py +21 -0
- node/planes/operations/updates/__init__.py +17 -0
- node/planes/operations/updates/core.py +310 -0
- node/planes/operations/updates/store.py +79 -0
- node/planes/operations/updates/types.py +20 -0
- node/planes/peering/__init__.py +28 -0
- node/planes/peering/budget/__init__.py +43 -0
- node/planes/peering/budget/chain.py +43 -0
- node/planes/peering/budget/core.py +537 -0
- node/planes/peering/budget/evaluation.py +80 -0
- node/planes/peering/budget/meter.py +100 -0
- node/planes/peering/budget/receipts.py +439 -0
- node/planes/peering/budget/shape.py +101 -0
- node/planes/peering/budget/tick.py +110 -0
- node/planes/peering/budget/types.py +67 -0
- node/planes/peering/budget/windows.py +346 -0
- node/planes/peering/capacity/__init__.py +7 -0
- node/planes/peering/capacity/compartment.py +342 -0
- node/planes/peering/capacity/types.py +24 -0
- node/planes/peering/peering/__init__.py +25 -0
- node/planes/peering/peering/core.py +464 -0
- node/planes/peering/peering/envelope.py +142 -0
- node/planes/peering/peering/handshake.py +198 -0
- node/planes/peering/peering/shape.py +80 -0
- node/planes/peering/peering/types.py +39 -0
- node/planes/peering/reporter/__init__.py +42 -0
- node/planes/peering/reporter/constants.py +29 -0
- node/planes/peering/reporter/core.py +299 -0
- node/planes/peering/reporter/finding.py +74 -0
- node/planes/peering/reporter/findings.py +171 -0
- node/planes/peering/reporter/publish.py +285 -0
- node/planes/peering/reporter/reading.py +193 -0
- node/planes/peering/reporter/shape.py +86 -0
- node/planes/peering/reporter/types.py +50 -0
- node/planes/peering/strain/__init__.py +9 -0
- node/planes/peering/strain/compartment.py +204 -0
- node/planes/peering/strain/types.py +21 -0
- node/planes/peering/trust/__init__.py +9 -0
- node/planes/peering/trust/compartment.py +239 -0
- node/planes/peering/trust/types.py +30 -0
- node/planes/services/__init__.py +33 -0
- node/planes/services/compute/__init__.py +160 -0
- node/planes/services/compute/common.py +134 -0
- node/planes/services/compute/compartment.py +681 -0
- node/planes/services/compute/executors/__init__.py +121 -0
- node/planes/services/compute/executors/agent.py +840 -0
- node/planes/services/compute/executors/bacalhau.py +605 -0
- node/planes/services/compute/executors/base.py +127 -0
- node/planes/services/compute/executors/harness.py +439 -0
- node/planes/services/compute/executors/mcp.py +454 -0
- node/planes/services/compute/executors/media.py +898 -0
- node/planes/services/compute/executors/nondet.py +283 -0
- node/planes/services/compute/executors/providers.py +499 -0
- node/planes/services/compute/executors/registry.py +140 -0
- node/planes/services/compute/executors/wasm.py +244 -0
- node/planes/services/compute/executors/zk.py +604 -0
- node/planes/services/compute/jobs.py +227 -0
- node/planes/services/compute/load.py +180 -0
- node/planes/services/compute/market.py +433 -0
- node/planes/services/compute/sandbox.py +282 -0
- node/planes/services/compute/types.py +84 -0
- node/planes/services/compute_wire/__init__.py +58 -0
- node/planes/services/compute_wire/compartment.py +256 -0
- node/planes/services/compute_wire/messages.py +320 -0
- node/planes/services/compute_wire/sealing.py +147 -0
- node/planes/services/compute_wire/session.py +572 -0
- node/planes/services/compute_wire/types.py +49 -0
- node/planes/services/compute_wire/verify.py +331 -0
- node/planes/services/dvm/__init__.py +24 -0
- node/planes/services/dvm/gateway.py +526 -0
- node/planes/services/dvm/protocol.py +35 -0
- node/planes/services/dvm/translate.py +15 -0
- node/planes/services/dvm/types.py +25 -0
- node/planes/services/gateway/__init__.py +40 -0
- node/planes/services/gateway/settle.py +279 -0
- node/planes/services/gateway/translate.py +644 -0
- node/planes/services/gossip/__init__.py +22 -0
- node/planes/services/gossip/core.py +929 -0
- node/planes/services/gossip/envelope.py +154 -0
- node/planes/services/gossip/mesh.py +127 -0
- node/planes/services/gossip/types.py +50 -0
- node/planes/services/nsite/__init__.py +7 -0
- node/planes/services/nsite/client.py +62 -0
- node/planes/services/nsite/compartment.py +752 -0
- node/planes/services/nsite/types.py +44 -0
- node/planes/services/paidfetch/__init__.py +7 -0
- node/planes/services/paidfetch/compartment.py +862 -0
- node/planes/services/paidfetch/types.py +99 -0
- node/planes/services/settle/__init__.py +27 -0
- node/planes/services/settle/compartment.py +384 -0
- node/planes/services/settle/engine.py +456 -0
- node/planes/services/settle/gateways.py +541 -0
- node/planes/services/settle/payment.py +512 -0
- node/planes/services/settle/store.py +74 -0
- node/planes/services/settle/types.py +58 -0
- node/planes/services/sidecars/__init__.py +8 -0
- node/planes/services/sidecars/compartment.py +461 -0
- node/planes/services/sidecars/manifest.py +304 -0
- node/planes/services/sidecars/manifests/10-grasp.json +37 -0
- node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
- node/planes/services/sidecars/manifests/30-magpie.json +28 -0
- node/planes/services/sidecars/types.py +43 -0
- node/planes/services/video/__init__.py +106 -0
- node/planes/services/video/barter.py +407 -0
- node/planes/services/video/cli.py +158 -0
- node/planes/services/video/compartment.py +932 -0
- node/planes/services/video/ingest.py +524 -0
- node/planes/services/video/ledger.py +705 -0
- node/planes/services/video/packager.py +847 -0
- node/planes/services/video/player.py +456 -0
- node/planes/services/video/timed_segments.py +124 -0
- node/planes/services/video/transit.py +478 -0
- node/planes/store/__init__.py +24 -0
- node/planes/store/blob_replication/__init__.py +7 -0
- node/planes/store/blob_replication/compartment.py +496 -0
- node/planes/store/blob_replication/types.py +35 -0
- node/planes/store/blob_wire/__init__.py +48 -0
- node/planes/store/blob_wire/client.py +57 -0
- node/planes/store/blob_wire/core.py +359 -0
- node/planes/store/blob_wire/intake.py +74 -0
- node/planes/store/blob_wire/messages.py +85 -0
- node/planes/store/projected/__init__.py +7 -0
- node/planes/store/projected/compartment.py +258 -0
- node/planes/store/projected/types.py +29 -0
- node/planes/store/retention/__init__.py +7 -0
- node/planes/store/retention/compartment.py +214 -0
- node/planes/store/retention/types.py +12 -0
- node/planes/store/sync/__init__.py +35 -0
- node/planes/store/sync/core.py +311 -0
- node/planes/store/sync/envelope.py +106 -0
- node/planes/store/sync/handover.py +206 -0
- node/planes/store/sync/initiator.py +128 -0
- node/planes/store/sync/responder.py +114 -0
- node/planes/store/sync/rules.py +179 -0
- node/planes/store/sync/shape.py +80 -0
- node/planes/store/sync/types.py +51 -0
- node/planes/transport/__init__.py +23 -0
- node/planes/transport/bridge/__init__.py +13 -0
- node/planes/transport/bridge/compartment.py +785 -0
- node/planes/transport/bridge/ratelimit.py +44 -0
- node/planes/transport/bridge/types.py +43 -0
- node/planes/transport/lan/__init__.py +9 -0
- node/planes/transport/lan/compartment.py +446 -0
- node/planes/transport/lan/types.py +25 -0
- node/planes/transport/link/__init__.py +7 -0
- node/planes/transport/link/compartment.py +257 -0
- node/planes/transport/link/types.py +27 -0
- node/planes/transport/mediation/__init__.py +45 -0
- node/planes/transport/mediation/compartment.py +238 -0
- node/planes/transport/mediation/leaf.py +107 -0
- node/planes/transport/mediation/messages.py +302 -0
- node/planes/transport/mediation/service.py +233 -0
- node/planes/transport/mediation/store.py +37 -0
- node/planes/transport/mediation/types.py +20 -0
- node/planes/transport/p2p/__init__.py +7 -0
- node/planes/transport/p2p/compartment.py +349 -0
- node/planes/transport/p2p/types.py +22 -0
- node/protocols/__init__.py +277 -0
- node/protocols/activitypub/__init__.py +56 -0
- node/protocols/activitypub/actor.py +230 -0
- node/protocols/activitypub/federation.py +490 -0
- node/protocols/activitypub/media.py +145 -0
- node/protocols/activitypub/models.py +286 -0
- node/protocols/activitypub/package.py +271 -0
- node/protocols/activitypub/store.py +564 -0
- node/protocols/arcade/__init__.py +26 -0
- node/protocols/atproto/__init__.py +13 -0
- node/protocols/atproto/firehose.py +126 -0
- node/protocols/atproto/metering.py +165 -0
- node/protocols/atproto/package.py +155 -0
- node/protocols/atproto/repo.py +250 -0
- node/protocols/atproto/xrpc.py +285 -0
- node/protocols/base.py +109 -0
- node/protocols/circles/__init__.py +29 -0
- node/protocols/dm/__init__.py +50 -0
- node/protocols/grasp/__init__.py +22 -0
- node/protocols/isotime.py +15 -0
- node/protocols/nostr/__init__.py +51 -0
- node/protocols/nostr/blobstore.py +383 -0
- node/protocols/nostr/blossom.py +627 -0
- node/protocols/nostr/client.py +377 -0
- node/protocols/nostr/descriptor.py +211 -0
- node/protocols/nostr/eventstore.py +304 -0
- node/protocols/nostr/package.py +182 -0
- node/protocols/nostr/peers.py +109 -0
- node/protocols/nostr/relayserver.py +656 -0
- node/protocols/p2panda/__init__.py +22 -0
- node/protocols/p2panda/identity.py +19 -0
- node/protocols/p2panda/package.py +236 -0
- node/protocols/p2panda/rung.py +152 -0
- node/protocols/p2panda/store.py +273 -0
- node/protocols/rhio/__init__.py +33 -0
- node/protocols/rhio/package.py +275 -0
- node/protocols/rhio/rung.py +136 -0
- node/protocols/rhio/store.py +1423 -0
- node/protocols/ssb/muxrpc.py +256 -0
- node/protocols/ssb/package.py +267 -0
- node/protocols/textile/__init__.py +111 -0
- node/protocols/textile/transducers.py +259 -0
- node/release.py +119 -0
- node/storage/__init__.py +88 -0
- node/storage/accounts.py +305 -0
- node/storage/adapter.py +161 -0
- node/storage/base.py +385 -0
- node/storage/dialect.py +209 -0
- node/storage/grants.py +317 -0
- node/storage/locator.py +150 -0
- node/storage/postgres.py +294 -0
- node/storage/projected.py +953 -0
- node/storage/sharding.py +448 -0
- node/storage/standing.py +568 -0
- node/testnet/__init__.py +9 -0
- node/testnet/cluster.py +731 -0
- node/testnet/fips_daemon.py +230 -0
- node/transport/__init__.py +94 -0
- node/transport/const.py +110 -0
- node/transport/didcomm_inbound.py +251 -0
- node/transport/dnstxt.py +169 -0
- node/transport/fips_rung/__init__.py +67 -0
- node/transport/fips_rung/core.py +666 -0
- node/transport/fips_rung/listener.py +96 -0
- node/transport/fips_rung/types.py +12 -0
- node/transport/host.py +334 -0
- node/transport/iroh_rung/__init__.py +29 -0
- node/transport/iroh_rung/core.py +477 -0
- node/transport/iroh_rung/quic.py +942 -0
- node/transport/iroh_rung/types.py +22 -0
- node/transport/ladder.py +145 -0
- node/transport/lineproto.py +49 -0
- node/transport/mdns.py +303 -0
- node/transport/netfetch.py +283 -0
- node/transport/relay_rung/__init__.py +23 -0
- node/transport/relay_rung/cooldown.py +87 -0
- node/transport/relay_rung/core.py +138 -0
- node/transport/relay_rung/inbound.py +161 -0
- node/transport/relay_rung/ledger.py +104 -0
- node/transport/relay_rung/listen.py +121 -0
- node/transport/relay_rung/outbound.py +181 -0
- node/transport/relay_rung/shape.py +71 -0
- node/transport/relay_rung/types.py +58 -0
- node/transport/relay_rung/wire.py +63 -0
- node/transport/router.py +761 -0
- node/transport/rung.py +276 -0
- node/transport/ssb_rung.py +166 -0
- node/transport/ws.py +502 -0
- sidecar/fleetmesh_announce.py +338 -0
- sidecar/fleetmesh_p2p.py +373 -0
- sidecar/fleetmesh_rhio.py +352 -0
- sidecar/fleetmesh_ssb.py +603 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""The node's own ceiling, held across ticks, and the shed ladder it drives.
|
|
2
|
+
|
|
3
|
+
`fleetmesh_core/capacity.py` is the arithmetic and states why there is no default total.
|
|
4
|
+
This is the part that remembers: which rungs are engaged and since when, what a
|
|
5
|
+
grant's limits were before a shed so they can be handed back, and the largest
|
|
6
|
+
window this machine has actually carried so an operator has a number to start
|
|
7
|
+
from rather than a blank.
|
|
8
|
+
|
|
9
|
+
The total is read from config under `capacity` and is live. An operator may set
|
|
10
|
+
it, raise it or delete it while the node runs and the next tick follows the
|
|
11
|
+
file, which is the same contract every other live key has: the file is the
|
|
12
|
+
interface and the daemon follows it.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any, Callable, Mapping, Optional, cast
|
|
17
|
+
|
|
18
|
+
from fleetmesh_core.accounting import capacity as ref_capacity, window
|
|
19
|
+
from node import channels as ch_mod
|
|
20
|
+
from node.storage import grants as GRANTS
|
|
21
|
+
from node.compartment import lifecycle as lifecycle_mod
|
|
22
|
+
from node import metrics as mx_mod
|
|
23
|
+
from node.compartment import stores as st
|
|
24
|
+
from node.config import CapacityConfig
|
|
25
|
+
from node.compartment import Command, Compartment, Need
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
from .types import RungChange, CapacityStateReport
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Capacity(Compartment):
|
|
32
|
+
requires: tuple[str, ...] = ('budget',)
|
|
33
|
+
"""The node's own ceiling, as a compartment the operator can watch.
|
|
34
|
+
|
|
35
|
+
What it declares: the config key `capacity`, live; one control command; a
|
|
36
|
+
state an operator reads; the trouble it is in; and every peering it turned
|
|
37
|
+
away, with the reason, in the refusal channel every compartment has. Before
|
|
38
|
+
that channel existed a full node declined a stranger and recorded nothing,
|
|
39
|
+
so the only trace of the first shed rung was a line in somebody's journal.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
name: str = 'capacity'
|
|
43
|
+
reads: tuple[str, ...] = ('capacity',)
|
|
44
|
+
config_cls = CapacityConfig
|
|
45
|
+
live: tuple[str, ...] = ('capacity',)
|
|
46
|
+
NEEDS = (
|
|
47
|
+
Need('budget'),
|
|
48
|
+
Need('total', block=CapacityConfig, field='total'),
|
|
49
|
+
Need('grants', 'stores.grants'),
|
|
50
|
+
Need('amend', 'amend'),
|
|
51
|
+
)
|
|
52
|
+
COMMANDS = (
|
|
53
|
+
Command(
|
|
54
|
+
key='show',
|
|
55
|
+
name='Capacity',
|
|
56
|
+
description="the declared total, the pressure on it, the rungs engaged and the largest window carried",
|
|
57
|
+
tags=('capacity', 'status'),
|
|
58
|
+
permission='capacity/show',
|
|
59
|
+
run='show',
|
|
60
|
+
reply='{declared, total, pressure, binding, rungs, held, since, measured, peak, suggested, note}',
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# What it keeps. The ladder's own state (`held`, `since`) is a handful of
|
|
65
|
+
# rung names and is not a store: it does not grow with the outside world.
|
|
66
|
+
STORES = (
|
|
67
|
+
st.Store(key='log', description='rungs engaged and released, newest last',
|
|
68
|
+
kind=st.RING, shape=RungChange, bound=200),
|
|
69
|
+
# REFUSE, not OLDEST: dropping a peer's remembered limits would leave
|
|
70
|
+
# it shed for good when the pressure lifts. A peer that cannot be
|
|
71
|
+
# remembered is not cut instead (shed_grants).
|
|
72
|
+
#
|
|
73
|
+
# SNAPSHOT: a restart in the middle of a shed came back with an empty
|
|
74
|
+
# ladder and an empty table - the peers stayed cut, and nothing knew
|
|
75
|
+
# what to give back. Carried, it pairs with the reconcile in tick():
|
|
76
|
+
# whatever is still held back while amend_down is off is returned.
|
|
77
|
+
st.Store(key='before', description='the limits each peer held before a shed, to give back',
|
|
78
|
+
kind=st.TABLE, shape=dict, bound=4096, evict=st.REFUSE, keyed_by='peer',
|
|
79
|
+
persist=st.SNAPSHOT),
|
|
80
|
+
# Carried across a restart: "the largest window this machine has
|
|
81
|
+
# actually carried" is the number an operator sizes a total from,
|
|
82
|
+
# and an upgrade used to reset it to nothing.
|
|
83
|
+
st.Store(key='peak', description='the largest window carried, per counter',
|
|
84
|
+
kind=st.TABLE, shape=int, bound=64, evict=st.REFUSE, keyed_by='counter',
|
|
85
|
+
persist=st.SNAPSHOT),
|
|
86
|
+
)
|
|
87
|
+
CHANNELS = (
|
|
88
|
+
ch_mod.Channel('rung_engaged', 'A capacity shed ladder rung engaged under pressure',
|
|
89
|
+
level=ch_mod.TROUBLE, say='capacity shed rung {rung} engaged at {pressure}% pressure on {binding}',
|
|
90
|
+
tags=('capacity', 'shed')),
|
|
91
|
+
ch_mod.Channel('rung_released', 'A capacity shed ladder rung released as pressure eased',
|
|
92
|
+
level=ch_mod.NOTE, say='capacity shed rung {rung} released',
|
|
93
|
+
tags=('capacity', 'shed')),
|
|
94
|
+
)
|
|
95
|
+
METRICS = (
|
|
96
|
+
mx_mod.Metric(name='capacity_rungs_engaged', description='Times a capacity shed rung engaged.',
|
|
97
|
+
kind=mx_mod.COUNTER, counts='rung_engaged', tags=('capacity', 'shed')),
|
|
98
|
+
mx_mod.Metric(name='capacity_rungs_released', description='Times a capacity shed rung released.',
|
|
99
|
+
kind=mx_mod.COUNTER, counts='rung_released', tags=('capacity', 'shed')),
|
|
100
|
+
)
|
|
101
|
+
# v1 is `peak` and `before`. `before` joined v1 before v1 ever left this
|
|
102
|
+
# repository, so no deployed snapshot has the shape without it; a local
|
|
103
|
+
# one that does is refused by the restore ("no store before") and the
|
|
104
|
+
# compartment starts fresh, which is the honest outcome.
|
|
105
|
+
LIFECYCLE = lifecycle_mod.Lifecycle(snapshot=1)
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
budget: Any,
|
|
110
|
+
total: Optional[Mapping[str, float | int]] = None,
|
|
111
|
+
clock: Optional[Callable[[], int]] = None,
|
|
112
|
+
node: Optional[Any] = None,
|
|
113
|
+
grants: Optional[Any] = None,
|
|
114
|
+
amend: Optional[Callable[..., tuple[Any, Any]]] = None,
|
|
115
|
+
) -> None:
|
|
116
|
+
super().__init__(node, clock)
|
|
117
|
+
self.budget: Any = budget
|
|
118
|
+
# The grant store a shed reads live grants from and the node call
|
|
119
|
+
# that amends one; a fake node in a suite supplies both.
|
|
120
|
+
self.grants: Optional[Any] = grants if grants is not None else getattr(node, 'grants', None)
|
|
121
|
+
self.amend: Callable[..., tuple[Any, Any]] = amend or (lambda *a: getattr(node, '_amend')(*a))
|
|
122
|
+
self.total: dict[str, float | int] = dict(total or {}) # not a store: the operator's declared ceilings, from config
|
|
123
|
+
self.held: list[str] = [] # not a store: the rungs engaged, at most the four of the ladder
|
|
124
|
+
self.since: dict[str, int] = {} # not a store: when each held rung engaged, one per rung
|
|
125
|
+
# The stores (`log`, `before`, `peak`) were built and bound by the base.
|
|
126
|
+
self.log: st.Ring[RungChange]
|
|
127
|
+
self.before: st.Table[str, Mapping[str, Any]]
|
|
128
|
+
self.peak: st.Table[str, int]
|
|
129
|
+
|
|
130
|
+
# ---- the number the operator declares -------------------------------------
|
|
131
|
+
def set_total(self, total: Optional[Mapping[str, Any]]) -> bool:
|
|
132
|
+
"""Take up a new declaration. Returns True if it changed."""
|
|
133
|
+
new = {k: v for k, v in (total or {}).items() if isinstance(v, (int, float)) and v > 0}
|
|
134
|
+
if new == self.total:
|
|
135
|
+
return False
|
|
136
|
+
self.total = new
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
def declared(self) -> bool:
|
|
140
|
+
return bool(self.total)
|
|
141
|
+
|
|
142
|
+
# ---- what is happening ----------------------------------------------------
|
|
143
|
+
def measure(self, now: Optional[int] = None) -> dict[str, int]:
|
|
144
|
+
"""The open window, summed across every peer. See Budget.aggregate."""
|
|
145
|
+
m: dict[str, int] = self.budget.aggregate(now)
|
|
146
|
+
for k, v in m.items():
|
|
147
|
+
if v > self.peak.get(k, 0):
|
|
148
|
+
self.peak[k] = v
|
|
149
|
+
return m
|
|
150
|
+
|
|
151
|
+
def pressure(self, now: Optional[int] = None) -> tuple[float, Optional[str]]:
|
|
152
|
+
return ref_capacity.pressure(self.measure(now), self.total)
|
|
153
|
+
|
|
154
|
+
def transitions(self, now: Optional[int] = None) -> list[tuple[str, str]]:
|
|
155
|
+
"""Recompute and return the rungs that changed, as (action, what).
|
|
156
|
+
|
|
157
|
+
The caller acts on the transitions rather than on the state, because a
|
|
158
|
+
shed and a restore each cost the peers an amend and neither should
|
|
159
|
+
happen twice for one crossing.
|
|
160
|
+
"""
|
|
161
|
+
now_val = now if now is not None else self.clock()
|
|
162
|
+
if not self.declared():
|
|
163
|
+
was, self.held = self.held, []
|
|
164
|
+
return [(a, 'released') for a in was]
|
|
165
|
+
p, _binding = self.pressure(now_val)
|
|
166
|
+
want = ref_capacity.engaged(p, self.held)
|
|
167
|
+
changed = ([(a, 'engaged') for a in want if a not in self.held]
|
|
168
|
+
+ [(a, 'released') for a in self.held if a not in want])
|
|
169
|
+
for action, what in changed:
|
|
170
|
+
self.log.append({'at': now_val, 'action': action, 'what': what, 'pressure': round(p, 4)})
|
|
171
|
+
if what == 'engaged':
|
|
172
|
+
self.since[action] = now_val
|
|
173
|
+
self.say('rung_engaged', rung=action, pressure=round(p * 100), binding=_binding or 'capacity')
|
|
174
|
+
else:
|
|
175
|
+
self.since.pop(action, None)
|
|
176
|
+
self.say('rung_released', rung=action)
|
|
177
|
+
self.held = want
|
|
178
|
+
return changed
|
|
179
|
+
|
|
180
|
+
# ---- what each rung means to a caller -------------------------------------
|
|
181
|
+
def refusing(self) -> bool:
|
|
182
|
+
return 'refuse_peerings' in self.held
|
|
183
|
+
|
|
184
|
+
def narrowing(self) -> bool:
|
|
185
|
+
return 'narrow_scope' in self.held
|
|
186
|
+
|
|
187
|
+
def amending(self) -> bool:
|
|
188
|
+
return 'amend_down' in self.held
|
|
189
|
+
|
|
190
|
+
def leafing(self) -> bool:
|
|
191
|
+
return 'leaf_behaviour' in self.held
|
|
192
|
+
|
|
193
|
+
def why(self) -> str:
|
|
194
|
+
"""The message a decline carries. Names the binding counter and nothing
|
|
195
|
+
else: a stranger learns this node is full, never who is filling it."""
|
|
196
|
+
p, binding = self.pressure()
|
|
197
|
+
return 'at %d%% of this node\'s declared %s' % (round(p * 100), binding or 'capacity')
|
|
198
|
+
|
|
199
|
+
# ---- the limits a shed took, so they can be given back ---------------------
|
|
200
|
+
def remember(self, peer: str, limits: Mapping[str, Any]) -> bool:
|
|
201
|
+
"""Keep what a shed is about to take. False when the table is full:
|
|
202
|
+
the caller must not take what it could not promise to give back."""
|
|
203
|
+
if peer in self.before:
|
|
204
|
+
return True
|
|
205
|
+
return self.before.put(peer, dict(limits))
|
|
206
|
+
|
|
207
|
+
def restore_for(self, peer: str, tier: str) -> Optional[Mapping[str, Any]]:
|
|
208
|
+
"""What this peer should get back, or None if nothing was taken.
|
|
209
|
+
|
|
210
|
+
Held inside the ceilings of the tier it holds NOW, because the ladder
|
|
211
|
+
may have lowered it while the shed was on and a restore must never undo
|
|
212
|
+
a remedy.
|
|
213
|
+
"""
|
|
214
|
+
was = self.before.pop(peer, None)
|
|
215
|
+
if was is None:
|
|
216
|
+
return None
|
|
217
|
+
return window.within(was, tier)
|
|
218
|
+
|
|
219
|
+
def forget(self, peer: str) -> None:
|
|
220
|
+
self.before.pop(peer, None)
|
|
221
|
+
|
|
222
|
+
# ---- the compartment contract -------------------------------------------------
|
|
223
|
+
def tick(self, now: int) -> list[tuple[Any, ...]]:
|
|
224
|
+
"""Recompute pressure and act on the rungs that changed.
|
|
225
|
+
|
|
226
|
+
Transitions and not state: a shed and a restore each cost the peers an
|
|
227
|
+
amend, and neither should happen twice for one crossing of a threshold.
|
|
228
|
+
|
|
229
|
+
Then one invariant, checked every tick rather than on a transition:
|
|
230
|
+
nothing is held back while amend_down is off. The transition alone
|
|
231
|
+
misses the restart - the ladder comes back empty, so a shed that was
|
|
232
|
+
on is never seen to release, and `before` (carried) would sit there
|
|
233
|
+
with the peers still cut. Normally `before` is empty here and this is
|
|
234
|
+
one length check.
|
|
235
|
+
"""
|
|
236
|
+
self.set_total(self.settings().total) # live
|
|
237
|
+
did: list[tuple[Any, ...]] = []
|
|
238
|
+
for action, what in self.transitions(now):
|
|
239
|
+
p, binding = self.pressure(now)
|
|
240
|
+
did.append(('capacity', action, what, round(p, 3), binding))
|
|
241
|
+
if action == 'amend_down' and what == 'engaged':
|
|
242
|
+
did += self.shed_grants(now)
|
|
243
|
+
elif action == 'amend_down' and what == 'released':
|
|
244
|
+
did += self.restore_grants(now)
|
|
245
|
+
if self.before and not self.amending():
|
|
246
|
+
did += self.restore_grants(now)
|
|
247
|
+
return did
|
|
248
|
+
|
|
249
|
+
def trouble(self) -> list[str]:
|
|
250
|
+
if not self.held:
|
|
251
|
+
return []
|
|
252
|
+
p, binding = self.pressure()
|
|
253
|
+
return ['shedding at %d%% of the declared %s: %s'
|
|
254
|
+
% (round(p * 100), binding or 'total', ', '.join(self.held))]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def refuse_peering(self, peer: str) -> str:
|
|
258
|
+
"""A stranger turned away because this node is full.
|
|
259
|
+
|
|
260
|
+
Recorded rather than only answered: a decline is not a finding, but a
|
|
261
|
+
node that is refusing peerings and cannot say how many is a node whose
|
|
262
|
+
operator finds out from somebody else.
|
|
263
|
+
"""
|
|
264
|
+
self.refusals.refuse('peering', 'this node is at its declared ceiling',
|
|
265
|
+
subject=peer, detail={'pressure': self.why()}, at=self.clock())
|
|
266
|
+
return self.why()
|
|
267
|
+
|
|
268
|
+
# ---- the grants a shed moves ------------------------------------------------
|
|
269
|
+
def shed_grants(self, now: Optional[int] = None) -> list[tuple[Any, ...]]:
|
|
270
|
+
"""Reduce every live grant because this node is full, not because anyone
|
|
271
|
+
misbehaved. What each peer held is remembered so it can be given back."""
|
|
272
|
+
now_val = now if now is not None else self.clock()
|
|
273
|
+
floor = ref_capacity.shed_floor(window.ceilings('probation'))
|
|
274
|
+
did: list[tuple[Any, ...]] = []
|
|
275
|
+
if self.grants is None:
|
|
276
|
+
return did # no grant store: nothing to shed
|
|
277
|
+
for peer in self.grants.peers():
|
|
278
|
+
row = self.grants.get(peer, GRANTS.ISSUED)
|
|
279
|
+
if row is None or row['status'] != 'live':
|
|
280
|
+
continue
|
|
281
|
+
if peer in self.before:
|
|
282
|
+
# Shed and not yet given back. Only a restart reaches this -
|
|
283
|
+
# the ladder came back empty, `before` came back carried, and
|
|
284
|
+
# amend_down engaged again - and cutting the already-cut
|
|
285
|
+
# limits would squeeze the peer twice for one shed.
|
|
286
|
+
continue
|
|
287
|
+
limits = row['payload'].get('limits') or {}
|
|
288
|
+
cut = ref_capacity.reduce(limits, floor)
|
|
289
|
+
if cut == limits:
|
|
290
|
+
continue # already on the floor; nothing to take
|
|
291
|
+
if not self.remember(peer, limits):
|
|
292
|
+
self.refusals.refuse('grant', 'not reduced: no room to remember what it held',
|
|
293
|
+
subject=peer, at=now_val)
|
|
294
|
+
continue
|
|
295
|
+
env, _ = self.amend(peer, dict(row['payload'], limits=cut),
|
|
296
|
+
'this node is shedding load', 'capacity', now_val)
|
|
297
|
+
self.refusals.refuse('grant', 'reduced because this node is full',
|
|
298
|
+
subject=peer, detail={'was': limits.get('events_in'),
|
|
299
|
+
'now': cut.get('events_in')}, at=now_val)
|
|
300
|
+
did.append(('shed', peer, env['id']))
|
|
301
|
+
return did
|
|
302
|
+
|
|
303
|
+
def restore_grants(self, now: Optional[int] = None) -> list[tuple[Any, ...]]:
|
|
304
|
+
"""Give back what a shed took, held inside the tier each peer holds now."""
|
|
305
|
+
now_val = now if now is not None else self.clock()
|
|
306
|
+
did: list[tuple[Any, ...]] = []
|
|
307
|
+
if self.grants is None:
|
|
308
|
+
return did
|
|
309
|
+
for peer in list(self.before):
|
|
310
|
+
row = self.grants.get(peer, GRANTS.ISSUED)
|
|
311
|
+
if row is None or row['status'] != 'live':
|
|
312
|
+
self.forget(peer)
|
|
313
|
+
continue
|
|
314
|
+
back = self.restore_for(peer, row['payload'].get('tier', 'probation'))
|
|
315
|
+
if back is None or back == (row['payload'].get('limits') or {}):
|
|
316
|
+
continue
|
|
317
|
+
env, _ = self.amend(peer, dict(row['payload'], limits=back),
|
|
318
|
+
'this node has room again', 'capacity', now_val)
|
|
319
|
+
did.append(('restored', peer, env['id']))
|
|
320
|
+
return did
|
|
321
|
+
|
|
322
|
+
# ---- the operator's view ---------------------------------------------------
|
|
323
|
+
def show(self) -> Mapping[str, Any]:
|
|
324
|
+
st = self.state()
|
|
325
|
+
st['suggested'] = self.suggested()
|
|
326
|
+
return st
|
|
327
|
+
|
|
328
|
+
def suggested(self) -> Mapping[str, Any]:
|
|
329
|
+
return ref_capacity.suggest([dict(self.peak)]) if self.peak else {}
|
|
330
|
+
|
|
331
|
+
def state(self, now: Optional[int] = None) -> CapacityStateReport:
|
|
332
|
+
# The ref summary is a closed TypedDict; the operator's view adds the
|
|
333
|
+
# node-side memory on top, so widen it to a plain dict before extending.
|
|
334
|
+
s = dict(ref_capacity.summary(self.measure(now), self.total))
|
|
335
|
+
s['total'] = dict(self.total)
|
|
336
|
+
s['held'] = list(self.held)
|
|
337
|
+
s['since'] = {a: self.since[a] for a in self.held if a in self.since}
|
|
338
|
+
s['peak'] = dict(self.peak)
|
|
339
|
+
if not s['declared']:
|
|
340
|
+
s['note'] = ('no aggregate ceiling is declared, so this node will accept load '
|
|
341
|
+
'until it fails; meshnode capacity prints what it has carried')
|
|
342
|
+
return cast(CapacityStateReport, s)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Types and records for the Capacity compartment."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Mapping, Optional, TypedDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RungChange(TypedDict):
|
|
8
|
+
"""One rung of the shed ladder engaging or releasing."""
|
|
9
|
+
at: int
|
|
10
|
+
action: str
|
|
11
|
+
what: str # 'engaged' | 'released'
|
|
12
|
+
pressure: float
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CapacityStateReport(TypedDict, total=False):
|
|
16
|
+
declared: bool
|
|
17
|
+
pressure: float
|
|
18
|
+
binding: Optional[str]
|
|
19
|
+
total: dict[str, int]
|
|
20
|
+
held: list[str]
|
|
21
|
+
since: dict[str, int]
|
|
22
|
+
peak: dict[str, int]
|
|
23
|
+
suggested: Mapping[str, Any]
|
|
24
|
+
note: str
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""peering/1.0 over the wire.
|
|
2
|
+
|
|
3
|
+
fleetmesh_core/peering.py decides everything: what to propose, whether to admit, what
|
|
4
|
+
tier to open at, what the grant says. This module gives it what the
|
|
5
|
+
reference deliberately lacks. The kind 30801 gets a real nip44/2 ciphertext
|
|
6
|
+
of the grant to the subject and a signature, and is published. The messages
|
|
7
|
+
travel through the transport. The grants land in SQLite. And the subject,
|
|
8
|
+
having received an accept or an offer, fetches the envelope by id, opens it
|
|
9
|
+
with the conversation key only the pair shares, and checks that the
|
|
10
|
+
commitment in the public tags opens to what it decrypted, so the issuer
|
|
11
|
+
cannot later claim to have granted something else.
|
|
12
|
+
|
|
13
|
+
A package by seam: envelope.py (the kind 30801, written and opened),
|
|
14
|
+
core.py (the compartment: grant writers, reconciler, contract),
|
|
15
|
+
handshake.py (propose / handle / serve). Every name the module exported is
|
|
16
|
+
re-exported here.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from .core import Peering
|
|
21
|
+
from .shape import PeeringHost
|
|
22
|
+
from .envelope import CAUSES, GRANT_KIND, RECONCILE_EVERY, GrantUnpublished, PeeringError, WireNode, open_envelope
|
|
23
|
+
|
|
24
|
+
__all__ = ['CAUSES', 'GRANT_KIND', 'RECONCILE_EVERY', 'GrantUnpublished', 'PeeringError', 'WireNode',
|
|
25
|
+
'open_envelope', 'Peering', 'PeeringHost']
|