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
node/compartment/lint.py
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""Static declaration linter for Compartment subclasses."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
from . import lifecycle as lifecycle_mod
|
|
7
|
+
from . import stores as stores_mod
|
|
8
|
+
from .command import Command
|
|
9
|
+
from .every import Every
|
|
10
|
+
from .handler import Handler
|
|
11
|
+
from .need import Need
|
|
12
|
+
from .problem import (
|
|
13
|
+
DuplicateProblem,
|
|
14
|
+
IncompatibleProblem,
|
|
15
|
+
InvalidIdentifierProblem,
|
|
16
|
+
Problem,
|
|
17
|
+
Problems,
|
|
18
|
+
ReferenceProblem,
|
|
19
|
+
ShadowingProblem,
|
|
20
|
+
TypeMismatchProblem,
|
|
21
|
+
)
|
|
22
|
+
from .worker import Worker
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
'declaration_problems',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def declaration_problems(cls: type[Any], base_cls: type[Any]) -> list[str]:
|
|
30
|
+
"""Everything wrong with this class's declarations, as sentences.
|
|
31
|
+
|
|
32
|
+
Collected rather than raised one at a time, like `Metric.problems`
|
|
33
|
+
and `config.Problems`: whoever fixes one wants to see them all.
|
|
34
|
+
Returns Problems (a list subclass) holding typed Problem instances.
|
|
35
|
+
"""
|
|
36
|
+
out = Problems()
|
|
37
|
+
if not cls.abstract and not (isinstance(cls.name, str) and cls.name):
|
|
38
|
+
out.append(Problem(
|
|
39
|
+
'a compartment has a name (or is declared abstract=True, a kind to extend)',
|
|
40
|
+
surface='NAME',
|
|
41
|
+
code='missing_name',
|
|
42
|
+
target='name',
|
|
43
|
+
))
|
|
44
|
+
for surface, attr in (
|
|
45
|
+
('CHANNELS', 'name'),
|
|
46
|
+
('METRICS', 'name'),
|
|
47
|
+
('COMMANDS', 'key'),
|
|
48
|
+
('STORES', 'key'),
|
|
49
|
+
('NEEDS', 'key'),
|
|
50
|
+
('EVERY', 'name'),
|
|
51
|
+
('WORKERS', 'name'),
|
|
52
|
+
('HANDLERS', 'kind'),
|
|
53
|
+
):
|
|
54
|
+
seen: set[str] = set()
|
|
55
|
+
for d in getattr(cls, surface, ()) or ():
|
|
56
|
+
k = getattr(d, attr, None)
|
|
57
|
+
if k in seen:
|
|
58
|
+
out.append(DuplicateProblem(surface, str(k)))
|
|
59
|
+
seen.add(str(k))
|
|
60
|
+
# The annotation promises a Lifecycle; this is for the subclass that
|
|
61
|
+
# broke the promise, so look at it as whatever it actually is.
|
|
62
|
+
lc: object = cls.LIFECYCLE
|
|
63
|
+
if not isinstance(lc, lifecycle_mod.Lifecycle):
|
|
64
|
+
out.append(TypeMismatchProblem('LIFECYCLE', 'node.lifecycle.Lifecycle', type(lc).__name__,
|
|
65
|
+
message='LIFECYCLE is a node.lifecycle.Lifecycle'))
|
|
66
|
+
return out
|
|
67
|
+
for p in lc.problems():
|
|
68
|
+
out.add(p, surface='LIFECYCLE')
|
|
69
|
+
out.extend(store_problems(cls, base_cls))
|
|
70
|
+
out.extend(command_problems(cls, base_cls))
|
|
71
|
+
out.extend(command_data_problems(cls))
|
|
72
|
+
out.extend(need_problems(cls, base_cls))
|
|
73
|
+
out.extend(every_problems(cls))
|
|
74
|
+
out.extend(worker_problems(cls))
|
|
75
|
+
out.extend(schema_problems(cls))
|
|
76
|
+
out.extend(migration_problems(cls))
|
|
77
|
+
out.extend(handler_problems(cls, base_cls))
|
|
78
|
+
out.extend(dependency_problems(cls))
|
|
79
|
+
out.extend(channel_problems(cls))
|
|
80
|
+
out.extend(metric_problems(cls))
|
|
81
|
+
out.extend(cli_problems(cls))
|
|
82
|
+
out.extend(sidecar_problems(cls))
|
|
83
|
+
return out
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def schema_problems(cls: type[Any]) -> list[str]:
|
|
87
|
+
out = Problems()
|
|
88
|
+
declared: tuple[object, ...] = tuple(getattr(cls, 'SCHEMA', ()) or ())
|
|
89
|
+
from .schema import Index, Table
|
|
90
|
+
seen_tables: set[str] = set()
|
|
91
|
+
seen_indices: set[str] = set()
|
|
92
|
+
for s in declared:
|
|
93
|
+
if not isinstance(s, (Table, Index)):
|
|
94
|
+
out.append(TypeMismatchProblem('SCHEMA', 'Table or Index', type(s).__name__,
|
|
95
|
+
message='SCHEMA holds Table or Index, not %s' % type(s).__name__))
|
|
96
|
+
continue
|
|
97
|
+
if isinstance(s, Table):
|
|
98
|
+
if s.name in seen_tables:
|
|
99
|
+
out.append(DuplicateProblem('SCHEMA', s.name, message='SCHEMA declares table %r twice' % s.name))
|
|
100
|
+
seen_tables.add(s.name)
|
|
101
|
+
out.extend(s.problems())
|
|
102
|
+
for idx in s.indices:
|
|
103
|
+
if idx.name in seen_indices:
|
|
104
|
+
out.append(DuplicateProblem('SCHEMA', idx.name, message='SCHEMA declares index %r twice' % idx.name))
|
|
105
|
+
seen_indices.add(idx.name)
|
|
106
|
+
elif isinstance(s, Index):
|
|
107
|
+
if s.name in seen_indices:
|
|
108
|
+
out.append(DuplicateProblem('SCHEMA', s.name, message='SCHEMA declares index %r twice' % s.name))
|
|
109
|
+
seen_indices.add(s.name)
|
|
110
|
+
out.extend(s.problems())
|
|
111
|
+
return out
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def migration_problems(cls: type[Any]) -> list[str]:
|
|
115
|
+
out = Problems()
|
|
116
|
+
declared: tuple[object, ...] = tuple(getattr(cls, 'MIGRATIONS', ()) or ())
|
|
117
|
+
from .schema import Migration
|
|
118
|
+
last_version = 0
|
|
119
|
+
seen_versions: set[int] = set()
|
|
120
|
+
for m in declared:
|
|
121
|
+
if not isinstance(m, Migration):
|
|
122
|
+
out.append(TypeMismatchProblem('MIGRATIONS', 'node.compartment.schema.Migration', type(m).__name__,
|
|
123
|
+
message='MIGRATIONS holds node.compartment.schema.Migration, not %s' % type(m).__name__))
|
|
124
|
+
continue
|
|
125
|
+
if m.version in seen_versions:
|
|
126
|
+
out.append(DuplicateProblem('MIGRATIONS', str(m.version), message='MIGRATIONS declares version %d twice' % m.version))
|
|
127
|
+
seen_versions.add(m.version)
|
|
128
|
+
if m.version <= last_version:
|
|
129
|
+
out.append(IncompatibleProblem(
|
|
130
|
+
'MIGRATIONS', str(m.version),
|
|
131
|
+
f'MIGRATIONS versions must be strictly ascending (got {m.version} after {last_version})'
|
|
132
|
+
))
|
|
133
|
+
last_version = m.version
|
|
134
|
+
out.extend(m.problems())
|
|
135
|
+
if declared and not getattr(cls, 'SCHEMA', ()):
|
|
136
|
+
out.append(IncompatibleProblem(
|
|
137
|
+
'MIGRATIONS', 'SCHEMA',
|
|
138
|
+
'a compartment that declares MIGRATIONS must also declare SCHEMA'
|
|
139
|
+
))
|
|
140
|
+
return out
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def handler_problems(cls: type[Any], base_cls: type[Any]) -> list[str]:
|
|
144
|
+
out = Problems()
|
|
145
|
+
declared: tuple[object, ...] = tuple(getattr(cls, 'HANDLERS', ()) or ())
|
|
146
|
+
for h in declared:
|
|
147
|
+
if not isinstance(h, Handler):
|
|
148
|
+
out.append(TypeMismatchProblem('HANDLERS', 'node.compartment.Handler', type(h).__name__,
|
|
149
|
+
message='HANDLERS holds node.compartment.Handler, not %s' % type(h).__name__))
|
|
150
|
+
continue
|
|
151
|
+
out.extend(h.problems(cls))
|
|
152
|
+
if declared and cls.handle is not base_cls.handle:
|
|
153
|
+
out.append(IncompatibleProblem(
|
|
154
|
+
'HANDLERS', 'handle',
|
|
155
|
+
'a compartment that declares HANDLERS does not write handle(): '
|
|
156
|
+
'the base dispatches them, from the declarations'
|
|
157
|
+
))
|
|
158
|
+
return out
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def dependency_problems(cls: type[Any]) -> list[str]:
|
|
162
|
+
out = Problems()
|
|
163
|
+
req = getattr(cls, 'requires', ()) or ()
|
|
164
|
+
use = getattr(cls, 'uses', ()) or ()
|
|
165
|
+
if not isinstance(req, (tuple, list)):
|
|
166
|
+
out.append(TypeMismatchProblem('requires', 'tuple[str, ...]', type(req).__name__,
|
|
167
|
+
message='requires must be a tuple of strings, not %s' % type(req).__name__))
|
|
168
|
+
req = ()
|
|
169
|
+
if not isinstance(use, (tuple, list)):
|
|
170
|
+
out.append(TypeMismatchProblem('uses', 'tuple[str, ...]', type(use).__name__,
|
|
171
|
+
message='uses must be a tuple of strings, not %s' % type(use).__name__))
|
|
172
|
+
use = ()
|
|
173
|
+
|
|
174
|
+
seen_req: set[str] = set()
|
|
175
|
+
for r in req:
|
|
176
|
+
if not isinstance(r, str):
|
|
177
|
+
out.append(TypeMismatchProblem('requires', 'str', type(r).__name__,
|
|
178
|
+
message='requires entries must be strings, not %s' % type(r).__name__))
|
|
179
|
+
continue
|
|
180
|
+
if not r.isidentifier():
|
|
181
|
+
out.append(InvalidIdentifierProblem('requires', r, message='requires %r is not a valid identifier' % r))
|
|
182
|
+
if r in seen_req:
|
|
183
|
+
out.append(DuplicateProblem('requires', r, message='requires declares %r twice' % r))
|
|
184
|
+
seen_req.add(r)
|
|
185
|
+
if cls.name and r == cls.name:
|
|
186
|
+
out.append(IncompatibleProblem('requires', r, '%s cannot require itself' % cls.name))
|
|
187
|
+
|
|
188
|
+
seen_use: set[str] = set()
|
|
189
|
+
for u in use:
|
|
190
|
+
if not isinstance(u, str):
|
|
191
|
+
out.append(TypeMismatchProblem('uses', 'str', type(u).__name__,
|
|
192
|
+
message='uses entries must be strings, not %s' % type(u).__name__))
|
|
193
|
+
continue
|
|
194
|
+
if not u.isidentifier():
|
|
195
|
+
out.append(InvalidIdentifierProblem('uses', u, message='uses %r is not a valid identifier' % u))
|
|
196
|
+
if u in seen_use:
|
|
197
|
+
out.append(DuplicateProblem('uses', u, message='uses declares %r twice' % u))
|
|
198
|
+
seen_use.add(u)
|
|
199
|
+
if cls.name and u == cls.name:
|
|
200
|
+
out.append(IncompatibleProblem('uses', u, '%s cannot use itself' % cls.name))
|
|
201
|
+
|
|
202
|
+
overlap = seen_req & seen_use
|
|
203
|
+
for o in sorted(overlap):
|
|
204
|
+
out.append(IncompatibleProblem(
|
|
205
|
+
'requires/uses', o,
|
|
206
|
+
'%s cannot be both required and optional (in both requires and uses)' % o
|
|
207
|
+
))
|
|
208
|
+
return out
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def channel_problems(cls: type[Any]) -> list[str]:
|
|
212
|
+
out = Problems()
|
|
213
|
+
from ..channels import Channel
|
|
214
|
+
declared: tuple[object, ...] = tuple(getattr(cls, 'CHANNELS', ()) or ())
|
|
215
|
+
for ch in declared:
|
|
216
|
+
if not isinstance(ch, Channel):
|
|
217
|
+
out.append(TypeMismatchProblem('CHANNELS', 'node.channels.Channel', type(ch).__name__,
|
|
218
|
+
message='CHANNELS holds node.channels.Channel, not %s' % type(ch).__name__))
|
|
219
|
+
return out
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def metric_problems(cls: type[Any]) -> list[str]:
|
|
223
|
+
out = Problems()
|
|
224
|
+
from ..metrics import Metric
|
|
225
|
+
declared: tuple[object, ...] = tuple(getattr(cls, 'METRICS', ()) or ())
|
|
226
|
+
for m in declared:
|
|
227
|
+
if not isinstance(m, Metric):
|
|
228
|
+
out.append(TypeMismatchProblem('METRICS', 'node.metrics.Metric', type(m).__name__,
|
|
229
|
+
message='METRICS holds node.metrics.Metric, not %s' % type(m).__name__))
|
|
230
|
+
continue
|
|
231
|
+
for p in m.problems():
|
|
232
|
+
out.add(p, surface='METRICS')
|
|
233
|
+
return out
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def cli_problems(cls: type[Any]) -> list[str]:
|
|
237
|
+
out = Problems()
|
|
238
|
+
declared: tuple[object, ...] = tuple(getattr(cls, 'cli', ()) or ())
|
|
239
|
+
for item in declared:
|
|
240
|
+
if not (isinstance(item, (tuple, list)) and len(item) == 2):
|
|
241
|
+
out.append(TypeMismatchProblem('cli', 'tuple[str, Callable]', type(item).__name__,
|
|
242
|
+
message='cli entries must be (verb, register_fn) pairs, not %s' % type(item).__name__))
|
|
243
|
+
continue
|
|
244
|
+
verb, fn = item
|
|
245
|
+
if not (isinstance(verb, str) and verb.isidentifier()):
|
|
246
|
+
out.append(InvalidIdentifierProblem('cli', str(verb), message='cli verb %r is not a valid identifier' % verb))
|
|
247
|
+
if not callable(fn):
|
|
248
|
+
out.append(TypeMismatchProblem('cli', 'callable register_fn', type(fn).__name__,
|
|
249
|
+
message='cli register function for %r must be callable, not %s' % (verb, type(fn).__name__)))
|
|
250
|
+
return out
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def sidecar_problems(cls: type[Any]) -> list[str]:
|
|
254
|
+
out = Problems()
|
|
255
|
+
declared: tuple[object, ...] = tuple(getattr(cls, 'sidecars', ()) or ())
|
|
256
|
+
for item in declared:
|
|
257
|
+
if not (isinstance(item, Mapping) or (isinstance(item, str) and item.startswith('@'))):
|
|
258
|
+
out.append(TypeMismatchProblem(
|
|
259
|
+
'sidecars', 'manifest mapping or "@/path" string', type(item).__name__,
|
|
260
|
+
message='sidecars holds manifest dict or "@/path", not %s' % type(item).__name__
|
|
261
|
+
))
|
|
262
|
+
return out
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def worker_problems(cls: type[Any]) -> list[str]:
|
|
266
|
+
out = Problems()
|
|
267
|
+
declared: tuple[object, ...] = tuple(getattr(cls, 'WORKERS', ()) or ())
|
|
268
|
+
for w in declared:
|
|
269
|
+
if not isinstance(w, Worker):
|
|
270
|
+
out.append(TypeMismatchProblem('WORKERS', 'node.compartment.Worker', type(w).__name__,
|
|
271
|
+
message='WORKERS holds node.compartment.Worker, not %s' % type(w).__name__))
|
|
272
|
+
continue
|
|
273
|
+
out.extend(w.problems(cls))
|
|
274
|
+
return out
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def every_problems(cls: type[Any]) -> list[str]:
|
|
278
|
+
out = Problems()
|
|
279
|
+
declared: tuple[object, ...] = tuple(getattr(cls, 'EVERY', ()) or ())
|
|
280
|
+
for e in declared:
|
|
281
|
+
if not isinstance(e, Every):
|
|
282
|
+
out.append(TypeMismatchProblem('EVERY', 'node.compartment.Every', type(e).__name__,
|
|
283
|
+
message='EVERY holds node.compartment.Every, not %s' % type(e).__name__))
|
|
284
|
+
continue
|
|
285
|
+
out.extend(e.problems(cls))
|
|
286
|
+
return out
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def need_problems(cls: type[Any], base_cls: type[Any]) -> list[str]:
|
|
290
|
+
out = Problems()
|
|
291
|
+
declared: tuple[object, ...] = tuple(getattr(cls, 'NEEDS', ()) or ())
|
|
292
|
+
for n in declared:
|
|
293
|
+
if not isinstance(n, Need):
|
|
294
|
+
out.append(TypeMismatchProblem('NEEDS', 'node.compartment.Need', type(n).__name__,
|
|
295
|
+
message='NEEDS holds node.compartment.Need, not %s' % type(n).__name__))
|
|
296
|
+
continue
|
|
297
|
+
out.extend(n.problems(cls))
|
|
298
|
+
fc = getattr(cls.from_context, '__func__', None)
|
|
299
|
+
base_fc = getattr(base_cls.from_context, '__func__', None)
|
|
300
|
+
if declared and fc is not base_fc:
|
|
301
|
+
out.append(IncompatibleProblem(
|
|
302
|
+
'NEEDS', 'from_context',
|
|
303
|
+
'a compartment that declares NEEDS does not override from_context: '
|
|
304
|
+
'the base constructs it from the declarations'
|
|
305
|
+
))
|
|
306
|
+
return out
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def command_problems(cls: type[Any], base_cls: type[Any]) -> list[str]:
|
|
310
|
+
out = Problems()
|
|
311
|
+
declared: tuple[object, ...] = tuple(cls.COMMANDS)
|
|
312
|
+
for c in declared:
|
|
313
|
+
if not isinstance(c, Command):
|
|
314
|
+
out.append(TypeMismatchProblem('COMMANDS', 'node.compartment.Command', type(c).__name__,
|
|
315
|
+
message='COMMANDS holds node.compartment.Command, not %s' % type(c).__name__))
|
|
316
|
+
continue
|
|
317
|
+
out.extend(c.problems(cls))
|
|
318
|
+
if declared and cls.command is not base_cls.command:
|
|
319
|
+
out.append(IncompatibleProblem(
|
|
320
|
+
'COMMANDS', 'command',
|
|
321
|
+
'a compartment that declares COMMANDS does not write command(): '
|
|
322
|
+
'the base answers them, from the declarations'
|
|
323
|
+
))
|
|
324
|
+
return out
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def store_problems(cls: type[Any], base_cls: type[Any]) -> list[str]:
|
|
328
|
+
out = Problems()
|
|
329
|
+
persisted = []
|
|
330
|
+
declared: tuple[object, ...] = tuple(cls.STORES)
|
|
331
|
+
for s in declared:
|
|
332
|
+
if not isinstance(s, stores_mod.Store):
|
|
333
|
+
out.append(TypeMismatchProblem('STORES', 'node.stores.Store', type(s).__name__,
|
|
334
|
+
message='STORES holds node.stores.Store, not %s' % type(s).__name__))
|
|
335
|
+
continue
|
|
336
|
+
out.extend(s.problems())
|
|
337
|
+
if any(s.key in vars(k) for k in cls.__mro__ if k is not object):
|
|
338
|
+
out.append(ShadowingProblem('STORES', s.key, cls.__name__))
|
|
339
|
+
if s.persist == stores_mod.SNAPSHOT:
|
|
340
|
+
persisted.append(s.key)
|
|
341
|
+
snap = cls.LIFECYCLE.snapshot
|
|
342
|
+
if persisted and not snap:
|
|
343
|
+
out.append(IncompatibleProblem(
|
|
344
|
+
'STORES', ', '.join(persisted),
|
|
345
|
+
'stores %s persist across a restart, so LIFECYCLE must declare snapshot=N '
|
|
346
|
+
'(the version of their shape)' % ', '.join(persisted)
|
|
347
|
+
))
|
|
348
|
+
if snap and not persisted and cls.snapshot is base_cls.snapshot:
|
|
349
|
+
out.append(IncompatibleProblem(
|
|
350
|
+
'STORES', 'snapshot',
|
|
351
|
+
'Lifecycle(snapshot=%d) carries nothing: declare a SNAPSHOT store, or snapshot() '
|
|
352
|
+
'and restore(state)' % snap
|
|
353
|
+
))
|
|
354
|
+
if snap and (cls.snapshot is base_cls.snapshot) != (cls.restore is base_cls.restore):
|
|
355
|
+
out.append(IncompatibleProblem(
|
|
356
|
+
'STORES', 'snapshot/restore',
|
|
357
|
+
'snapshot() and restore(state) come as a pair'
|
|
358
|
+
))
|
|
359
|
+
return out
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def command_data_problems(cls: type[Any]) -> list[str]:
|
|
363
|
+
out = Problems()
|
|
364
|
+
own = {s.key for s in cls.STORES if isinstance(s, stores_mod.Store)}
|
|
365
|
+
reach = set(cls.requires) | set(cls.uses)
|
|
366
|
+
for c in cls.COMMANDS:
|
|
367
|
+
if not isinstance(c, Command):
|
|
368
|
+
continue
|
|
369
|
+
for verb, refs in (('reads', c.reads), ('writes', c.writes)):
|
|
370
|
+
for ref in refs:
|
|
371
|
+
other, dot, key = ref.partition('.')
|
|
372
|
+
if not dot and ref not in own:
|
|
373
|
+
out.append(ReferenceProblem(
|
|
374
|
+
'COMMANDS', f'{c.key}.{verb}', ref,
|
|
375
|
+
'command %s %s store %r, which this compartment does not declare' % (c.key, verb, ref)
|
|
376
|
+
))
|
|
377
|
+
elif dot and verb == 'writes':
|
|
378
|
+
out.append(IncompatibleProblem(
|
|
379
|
+
'COMMANDS', f'{c.key}.writes',
|
|
380
|
+
'command %s writes %s, a store of %s; a command writes only its own '
|
|
381
|
+
"compartment's stores - ask %s instead" % (c.key, ref, other, other)
|
|
382
|
+
))
|
|
383
|
+
elif dot and other not in reach:
|
|
384
|
+
out.append(ReferenceProblem(
|
|
385
|
+
'COMMANDS', f'{c.key}.reads', other,
|
|
386
|
+
'command %s reads %s from %s, which this compartment neither requires nor uses' % (c.key, key, other)
|
|
387
|
+
))
|
|
388
|
+
return out
|
node/compartment/need.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Declared dependencies a compartment takes from the node."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import dataclasses
|
|
5
|
+
import inspect
|
|
6
|
+
from typing import Any, Mapping, Optional
|
|
7
|
+
|
|
8
|
+
from .problem import (IncompatibleProblem, Problem, Problems,
|
|
9
|
+
ReferenceProblem, TypeMismatchProblem)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclasses.dataclass(frozen=True)
|
|
13
|
+
class Need:
|
|
14
|
+
"""One dependency a compartment takes from the node, declared where the
|
|
15
|
+
compartment is.
|
|
16
|
+
|
|
17
|
+
Resolved against NodeContext at assembly by the base `from_context`:
|
|
18
|
+
- a field (`ks`, `transport`, `stores.grants`, `clock`, `propose`, `pull`, `regrant`, etc.)
|
|
19
|
+
- a typed config block (`Need('settings', block=SettlementConfig)`)
|
|
20
|
+
- a compartment, which must appear in `requires` or `uses`, resolved from registry / context
|
|
21
|
+
- a live config getter (`Need('cfg', 'cfg', getter=True)` or `Need('cfg', block=UpdatesConfig, getter=True)`)
|
|
22
|
+
- a node-relative path (`Need('db_path', path='DB')` or `Need('db_path', path='updates.sqlite')`)
|
|
23
|
+
- or `late=True`: resolved and bound after all compartments are assembled, replacing wire().
|
|
24
|
+
"""
|
|
25
|
+
key: str
|
|
26
|
+
source: Optional[str] = None
|
|
27
|
+
block: Optional[type] = None
|
|
28
|
+
late: bool = False
|
|
29
|
+
getter: bool = False
|
|
30
|
+
path: Optional[str] = None
|
|
31
|
+
field: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
key: str,
|
|
36
|
+
source: Optional[str] = None,
|
|
37
|
+
*,
|
|
38
|
+
block: Optional[type] = None,
|
|
39
|
+
late: bool = False,
|
|
40
|
+
getter: bool = False,
|
|
41
|
+
path: Optional[str] = None,
|
|
42
|
+
field: Optional[str] = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
object.__setattr__(self, 'key', key)
|
|
45
|
+
object.__setattr__(self, 'source', source if source is not None else (None if (block is not None or path is not None) else key))
|
|
46
|
+
object.__setattr__(self, 'block', block)
|
|
47
|
+
object.__setattr__(self, 'late', late)
|
|
48
|
+
object.__setattr__(self, 'getter', getter)
|
|
49
|
+
object.__setattr__(self, 'path', path)
|
|
50
|
+
object.__setattr__(self, 'field', field)
|
|
51
|
+
|
|
52
|
+
def resolve(self, ctx: Any, cls: type) -> Any:
|
|
53
|
+
if self.late:
|
|
54
|
+
return None
|
|
55
|
+
if self.path is not None:
|
|
56
|
+
rel = getattr(cls, self.path, self.path)
|
|
57
|
+
return ctx.path(rel)
|
|
58
|
+
if self.block is not None:
|
|
59
|
+
if self.getter:
|
|
60
|
+
return ctx.block_getter(self.block)
|
|
61
|
+
b = ctx.block(self.block)
|
|
62
|
+
if self.field is not None:
|
|
63
|
+
return getattr(b, self.field)
|
|
64
|
+
return b
|
|
65
|
+
src = self.source or self.key
|
|
66
|
+
if self.getter:
|
|
67
|
+
if src == 'cfg':
|
|
68
|
+
return ctx.cfg_getter()
|
|
69
|
+
return lambda: self._resolve_src(ctx, cls, src)
|
|
70
|
+
res = self._resolve_src(ctx, cls, src)
|
|
71
|
+
if self.field is not None:
|
|
72
|
+
if isinstance(res, Mapping):
|
|
73
|
+
return res.get(self.field)
|
|
74
|
+
return getattr(res, self.field)
|
|
75
|
+
return res
|
|
76
|
+
|
|
77
|
+
def _resolve_src(self, ctx: Any, cls: type, src: str) -> Any:
|
|
78
|
+
reqs = getattr(cls, 'requires', ()) or ()
|
|
79
|
+
uses = getattr(cls, 'uses', ()) or ()
|
|
80
|
+
if src in reqs or src in uses:
|
|
81
|
+
return ctx.optional(src)
|
|
82
|
+
val = ctx
|
|
83
|
+
for part in src.split('.'):
|
|
84
|
+
if hasattr(val, part):
|
|
85
|
+
val = getattr(val, part)
|
|
86
|
+
elif isinstance(val, Mapping) and part in val:
|
|
87
|
+
val = val[part]
|
|
88
|
+
else:
|
|
89
|
+
raise AttributeError(f"NodeContext has no field {part!r} (resolving {src!r})")
|
|
90
|
+
return val
|
|
91
|
+
|
|
92
|
+
def resolve_late(self, ctx: Any, instance: Any, registry: Any) -> Any:
|
|
93
|
+
comp_name = self.source or self.key
|
|
94
|
+
val = registry.get(comp_name)
|
|
95
|
+
if val is None:
|
|
96
|
+
val = getattr(ctx, comp_name, None)
|
|
97
|
+
return val
|
|
98
|
+
|
|
99
|
+
def problems(self, cls: type) -> list[str]:
|
|
100
|
+
out = Problems()
|
|
101
|
+
if not self.key or not isinstance(self.key, str):
|
|
102
|
+
out.append(Problem('Need key must be a non-empty string', surface='NEEDS', code='empty_key', target=''))
|
|
103
|
+
return out
|
|
104
|
+
|
|
105
|
+
# Check __init__ parameter (unless late=True)
|
|
106
|
+
if not self.late:
|
|
107
|
+
try:
|
|
108
|
+
init_fn = getattr(cls, '__init__', None)
|
|
109
|
+
if init_fn is not None:
|
|
110
|
+
sig = inspect.signature(init_fn)
|
|
111
|
+
params = sig.parameters
|
|
112
|
+
has_varkw = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params.values())
|
|
113
|
+
if not has_varkw and self.key not in params:
|
|
114
|
+
out.append(IncompatibleProblem(
|
|
115
|
+
'NEEDS', self.key,
|
|
116
|
+
f"need {self.key!r} is not an __init__ parameter of {cls.__name__}"
|
|
117
|
+
))
|
|
118
|
+
except (ValueError, TypeError):
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
if self.path is not None:
|
|
122
|
+
if not isinstance(self.path, str) or not self.path:
|
|
123
|
+
out.append(TypeMismatchProblem('NEEDS', 'non-empty str path', type(self.path).__name__,
|
|
124
|
+
message=f"need {self.key!r}: path must be a non-empty string"))
|
|
125
|
+
elif self.block is not None:
|
|
126
|
+
blk: object = self.block
|
|
127
|
+
if not isinstance(blk, type):
|
|
128
|
+
out.append(TypeMismatchProblem('NEEDS', 'type', type(blk).__name__,
|
|
129
|
+
message=f"need {self.key!r}: block must be a type, got {type(blk).__name__}"))
|
|
130
|
+
else:
|
|
131
|
+
src = self.source or self.key
|
|
132
|
+
reqs = getattr(cls, 'requires', ()) or ()
|
|
133
|
+
uses = getattr(cls, 'uses', ()) or ()
|
|
134
|
+
top = src.split('.')[0]
|
|
135
|
+
from .context import NodeContext
|
|
136
|
+
known_context_fields = set(NodeContext.__dataclass_fields__.keys()) | {
|
|
137
|
+
'pubkey', 'did', 'node_class',
|
|
138
|
+
}
|
|
139
|
+
if top in known_context_fields:
|
|
140
|
+
if top == 'stores' and '.' in src:
|
|
141
|
+
store_field = src.split('.')[1]
|
|
142
|
+
from .context import NodeStores
|
|
143
|
+
if store_field not in NodeStores._fields:
|
|
144
|
+
out.append(ReferenceProblem(
|
|
145
|
+
'NEEDS', self.key, store_field,
|
|
146
|
+
message=f"need {self.key!r}: stores has no field {store_field!r} (declared: {', '.join(NodeStores._fields)})"
|
|
147
|
+
))
|
|
148
|
+
elif self.late:
|
|
149
|
+
pass # late needs can name any compartment built after this one
|
|
150
|
+
elif src in reqs or src in uses:
|
|
151
|
+
pass # valid declared dependency
|
|
152
|
+
else:
|
|
153
|
+
out.append(ReferenceProblem(
|
|
154
|
+
'NEEDS', self.key, src,
|
|
155
|
+
message=f"need {self.key!r}: source {src!r} is neither a NodeContext field nor in requires/uses"
|
|
156
|
+
))
|
|
157
|
+
return out
|
|
158
|
+
|