ucomm 0.0.1__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.
- ucomm-0.0.1/LICENSE +28 -0
- ucomm-0.0.1/PKG-INFO +116 -0
- ucomm-0.0.1/README.md +91 -0
- ucomm-0.0.1/pyproject.toml +43 -0
- ucomm-0.0.1/setup.cfg +4 -0
- ucomm-0.0.1/src/ucomm/__init__.py +56 -0
- ucomm-0.0.1/src/ucomm/attention.py +116 -0
- ucomm-0.0.1/src/ucomm/bee.py +51 -0
- ucomm-0.0.1/src/ucomm/bridges/__init__.py +8 -0
- ucomm-0.0.1/src/ucomm/bridges/imap.py +215 -0
- ucomm-0.0.1/src/ucomm/contact.py +83 -0
- ucomm-0.0.1/src/ucomm/daemon.py +149 -0
- ucomm-0.0.1/src/ucomm/encoding.py +51 -0
- ucomm-0.0.1/src/ucomm/envelope.py +214 -0
- ucomm-0.0.1/src/ucomm/hints.py +64 -0
- ucomm-0.0.1/src/ucomm/log.py +100 -0
- ucomm-0.0.1/src/ucomm/profiles/__init__.py +7 -0
- ucomm-0.0.1/src/ucomm/profiles/chat.py +139 -0
- ucomm-0.0.1/src/ucomm/profiles/mail.py +48 -0
- ucomm-0.0.1/src/ucomm/rendezvous.py +55 -0
- ucomm-0.0.1/src/ucomm/signing.py +68 -0
- ucomm-0.0.1/src/ucomm/store.py +105 -0
- ucomm-0.0.1/src/ucomm.egg-info/PKG-INFO +116 -0
- ucomm-0.0.1/src/ucomm.egg-info/SOURCES.txt +42 -0
- ucomm-0.0.1/src/ucomm.egg-info/dependency_links.txt +1 -0
- ucomm-0.0.1/src/ucomm.egg-info/requires.txt +12 -0
- ucomm-0.0.1/src/ucomm.egg-info/top_level.txt +1 -0
- ucomm-0.0.1/tests/test_attention.py +70 -0
- ucomm-0.0.1/tests/test_bee_live.py +55 -0
- ucomm-0.0.1/tests/test_chat_profile.py +127 -0
- ucomm-0.0.1/tests/test_contact.py +74 -0
- ucomm-0.0.1/tests/test_daemon.py +178 -0
- ucomm-0.0.1/tests/test_encoding.py +45 -0
- ucomm-0.0.1/tests/test_envelope.py +24 -0
- ucomm-0.0.1/tests/test_genesis_validation.py +86 -0
- ucomm-0.0.1/tests/test_hints.py +60 -0
- ucomm-0.0.1/tests/test_imap_bridge.py +120 -0
- ucomm-0.0.1/tests/test_imap_bridge_live.py +55 -0
- ucomm-0.0.1/tests/test_imap_mailbox.py +114 -0
- ucomm-0.0.1/tests/test_log.py +105 -0
- ucomm-0.0.1/tests/test_mail_profile.py +43 -0
- ucomm-0.0.1/tests/test_rendezvous.py +62 -0
- ucomm-0.0.1/tests/test_signing.py +62 -0
- ucomm-0.0.1/tests/test_store.py +87 -0
ucomm-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Peter Foldiak
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
ucomm-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ucomm
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Universal communicator middleware for Ethereum Swarm (working title)
|
|
5
|
+
Author: Peter Foldiak
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/petfold/ucomm
|
|
8
|
+
Project-URL: Repository, https://github.com/petfold/ucomm
|
|
9
|
+
Project-URL: Issues, https://github.com/petfold/ucomm/issues
|
|
10
|
+
Keywords: swarm,ethereum,web3,bee,messaging,chat,notifications,attention,middleware
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: recordstore[bee]>=0.13.1
|
|
15
|
+
Requires-Dist: swarm-bee<2,>=1.1
|
|
16
|
+
Requires-Dist: imapclient<4,>=3.1
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
19
|
+
Requires-Dist: hypothesis>=6; extra == "test"
|
|
20
|
+
Requires-Dist: mypy>=1.10; extra == "test"
|
|
21
|
+
Requires-Dist: ruff>=0.5; extra == "test"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: ucomm[test]; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# ucomm — universal communicator middleware for Ethereum Swarm
|
|
27
|
+
|
|
28
|
+
[](https://github.com/petfold/ucomm/actions/workflows/tests.yml)
|
|
29
|
+
[](LICENSE)
|
|
30
|
+
[](docs/ROADMAP.md)
|
|
31
|
+
|
|
32
|
+
*Working title. Naming is an open question (see ROADMAP.md, issue N-1).*
|
|
33
|
+
|
|
34
|
+
Every communication app — chat, email, calls, streams, forums, social feeds — moves
|
|
35
|
+
information of some kind from one or more people to one or more people. The apps
|
|
36
|
+
differ in a handful of parameters (topology, interactivity, persistence, privacy,
|
|
37
|
+
invitation granularity), not in principle. Yet on Swarm, as elsewhere, each project
|
|
38
|
+
rebuilds the same primitives from scratch and ships its own silo, its own inbox,
|
|
39
|
+
and its own notification settings.
|
|
40
|
+
|
|
41
|
+
**ucomm** is middleware that factors out the common core:
|
|
42
|
+
|
|
43
|
+
- a **channel kernel**: a channel is a set of per-author append-only logs, a merge
|
|
44
|
+
rule, and a parameter vector fixed at genesis; every event is one signed envelope.
|
|
45
|
+
- an **attention layer**: a first-class control plane for requesting human attention,
|
|
46
|
+
based on Attila Lendvai's priority taxonomy (importance × urgency, ceilings,
|
|
47
|
+
offsets, thresholds), made incentive-compatible with postage costs, attention
|
|
48
|
+
bonds, and local reputation.
|
|
49
|
+
- a **universal inbox**: one local notification daemon per device arbitrates all
|
|
50
|
+
attention requests across all channels and apps; apps become views and policy
|
|
51
|
+
defaults, not silos.
|
|
52
|
+
- a **discovery/recommendation layer**: WoT-weighted collaborative filtering plus
|
|
53
|
+
concept-based (OntoDAG) content filtering, feeding priors into the attention
|
|
54
|
+
engine.
|
|
55
|
+
|
|
56
|
+
Guiding principle: **receiver sovereignty**. Everything a sender or channel declares
|
|
57
|
+
(priority, offsets, importance) is advisory and signed; the receiver's local policy
|
|
58
|
+
engine is the only authority over the receiver's attention.
|
|
59
|
+
|
|
60
|
+
## Repository layout
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
docs/DESIGN.md Architecture: two planes, channel kernel, profiles, transports
|
|
64
|
+
docs/ATTENTION.md Attention economy: priority algebra, policy engine, mechanisms
|
|
65
|
+
docs/RECOMMENDATION.md Discovery & collaborative filtering (needs merge from prior notes)
|
|
66
|
+
docs/ROADMAP.md Module decomposition, milestones, initial issues
|
|
67
|
+
docs/USER_GUIDE.md Tutorial: motivation, project overview, install, worked examples
|
|
68
|
+
CLAUDE.md Instructions and invariants for Claude Code sessions
|
|
69
|
+
src/ucomm/ Python package (schema, policy engine, signing, chat profile)
|
|
70
|
+
tests/ pytest suite
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Status
|
|
74
|
+
|
|
75
|
+
**M0 (schema + algebra) and M1 (known-contact channels over Swarm) are done**
|
|
76
|
+
— see `docs/ROADMAP.md` for the full milestone/issue breakdown. Concretely:
|
|
77
|
+
the envelope/genesis schema with canonical encoding and validation; the
|
|
78
|
+
priority algebra and policy engine with golden decision tests; per-author
|
|
79
|
+
logs with deterministic causal-DAG merge, backed by either an in-memory
|
|
80
|
+
implementation or a real recordstore/Bee adapter; real secp256k1 envelope
|
|
81
|
+
signing (via `swarm-bee`) and out-of-band contact exchange; and a two-party/
|
|
82
|
+
group chat profile exercising all of the above end-to-end, confirmed against
|
|
83
|
+
a live Bee node. New here? Start with `docs/USER_GUIDE.md`.
|
|
84
|
+
|
|
85
|
+
**M2 (notification daemon + universal inbox) is in progress**: a private
|
|
86
|
+
channel directory, a graded active/obsolete dashboard (a projection,
|
|
87
|
+
recomputed on demand — never persisted), read-state aggregation across
|
|
88
|
+
every channel, a push/hint-delivery interface, and the IMAP bridge's
|
|
89
|
+
conversion layer (email → the same envelopes and dashboard, no live
|
|
90
|
+
mailbox yet) are done. Left: an actual hint backend, IMAP's live fetch
|
|
91
|
+
loop, and the Nostr bridge — see `docs/USER_GUIDE.md` sections 11–13 for
|
|
92
|
+
the directory/dashboard/hints/bridge demos. GSOC-based rendezvous
|
|
93
|
+
(unsolicited contact, group discovery) stays behind the `Rendezvous`
|
|
94
|
+
interface, pending the GSOC/pub-sub work by Viktor Tóth and Viktor Trón.
|
|
95
|
+
Broadcast-style 1:N live streaming is explicitly out of scope here —
|
|
96
|
+
Solar Punk Ltd's own `swarm-hls-stream`/`Swarmcast` line already owns that
|
|
97
|
+
(DESIGN.md §5).
|
|
98
|
+
|
|
99
|
+
**On decentralization, read this before evaluating further:** everything
|
|
100
|
+
above runs on Swarm feeds and needs no full node — a light client is
|
|
101
|
+
enough. Real-time push notification and unsolicited-contact discovery (PSS
|
|
102
|
+
and GSOC) are a different story: both require a full node, yours or a
|
|
103
|
+
relay's, and that relay sees traffic metadata, which is a real
|
|
104
|
+
centralization point still unresolved (not just a caveat) — see
|
|
105
|
+
`docs/DESIGN.md` §5 and §11. Nothing here should be evaluated as "fully
|
|
106
|
+
decentralized" without that qualification.
|
|
107
|
+
|
|
108
|
+
## Related projects
|
|
109
|
+
|
|
110
|
+
- **recordstore** (github.com/petfold/recordstore) — candidate persistence substrate
|
|
111
|
+
for author logs (versioned key → record over Swarm; POT track).
|
|
112
|
+
- **swarmfs** (github.com/petfold/swarmfs) — fsspec backend, useful for payload blobs.
|
|
113
|
+
- **OntoDAG / mdl-fca** — semantic concept DAG; content-based half of the
|
|
114
|
+
recommendation layer.
|
|
115
|
+
- Attila Lendvai, *Computer aided human communication*
|
|
116
|
+
(codeberg.org/attila.lendvai/publications) — source of the attention taxonomy.
|
ucomm-0.0.1/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# ucomm — universal communicator middleware for Ethereum Swarm
|
|
2
|
+
|
|
3
|
+
[](https://github.com/petfold/ucomm/actions/workflows/tests.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](docs/ROADMAP.md)
|
|
6
|
+
|
|
7
|
+
*Working title. Naming is an open question (see ROADMAP.md, issue N-1).*
|
|
8
|
+
|
|
9
|
+
Every communication app — chat, email, calls, streams, forums, social feeds — moves
|
|
10
|
+
information of some kind from one or more people to one or more people. The apps
|
|
11
|
+
differ in a handful of parameters (topology, interactivity, persistence, privacy,
|
|
12
|
+
invitation granularity), not in principle. Yet on Swarm, as elsewhere, each project
|
|
13
|
+
rebuilds the same primitives from scratch and ships its own silo, its own inbox,
|
|
14
|
+
and its own notification settings.
|
|
15
|
+
|
|
16
|
+
**ucomm** is middleware that factors out the common core:
|
|
17
|
+
|
|
18
|
+
- a **channel kernel**: a channel is a set of per-author append-only logs, a merge
|
|
19
|
+
rule, and a parameter vector fixed at genesis; every event is one signed envelope.
|
|
20
|
+
- an **attention layer**: a first-class control plane for requesting human attention,
|
|
21
|
+
based on Attila Lendvai's priority taxonomy (importance × urgency, ceilings,
|
|
22
|
+
offsets, thresholds), made incentive-compatible with postage costs, attention
|
|
23
|
+
bonds, and local reputation.
|
|
24
|
+
- a **universal inbox**: one local notification daemon per device arbitrates all
|
|
25
|
+
attention requests across all channels and apps; apps become views and policy
|
|
26
|
+
defaults, not silos.
|
|
27
|
+
- a **discovery/recommendation layer**: WoT-weighted collaborative filtering plus
|
|
28
|
+
concept-based (OntoDAG) content filtering, feeding priors into the attention
|
|
29
|
+
engine.
|
|
30
|
+
|
|
31
|
+
Guiding principle: **receiver sovereignty**. Everything a sender or channel declares
|
|
32
|
+
(priority, offsets, importance) is advisory and signed; the receiver's local policy
|
|
33
|
+
engine is the only authority over the receiver's attention.
|
|
34
|
+
|
|
35
|
+
## Repository layout
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
docs/DESIGN.md Architecture: two planes, channel kernel, profiles, transports
|
|
39
|
+
docs/ATTENTION.md Attention economy: priority algebra, policy engine, mechanisms
|
|
40
|
+
docs/RECOMMENDATION.md Discovery & collaborative filtering (needs merge from prior notes)
|
|
41
|
+
docs/ROADMAP.md Module decomposition, milestones, initial issues
|
|
42
|
+
docs/USER_GUIDE.md Tutorial: motivation, project overview, install, worked examples
|
|
43
|
+
CLAUDE.md Instructions and invariants for Claude Code sessions
|
|
44
|
+
src/ucomm/ Python package (schema, policy engine, signing, chat profile)
|
|
45
|
+
tests/ pytest suite
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Status
|
|
49
|
+
|
|
50
|
+
**M0 (schema + algebra) and M1 (known-contact channels over Swarm) are done**
|
|
51
|
+
— see `docs/ROADMAP.md` for the full milestone/issue breakdown. Concretely:
|
|
52
|
+
the envelope/genesis schema with canonical encoding and validation; the
|
|
53
|
+
priority algebra and policy engine with golden decision tests; per-author
|
|
54
|
+
logs with deterministic causal-DAG merge, backed by either an in-memory
|
|
55
|
+
implementation or a real recordstore/Bee adapter; real secp256k1 envelope
|
|
56
|
+
signing (via `swarm-bee`) and out-of-band contact exchange; and a two-party/
|
|
57
|
+
group chat profile exercising all of the above end-to-end, confirmed against
|
|
58
|
+
a live Bee node. New here? Start with `docs/USER_GUIDE.md`.
|
|
59
|
+
|
|
60
|
+
**M2 (notification daemon + universal inbox) is in progress**: a private
|
|
61
|
+
channel directory, a graded active/obsolete dashboard (a projection,
|
|
62
|
+
recomputed on demand — never persisted), read-state aggregation across
|
|
63
|
+
every channel, a push/hint-delivery interface, and the IMAP bridge's
|
|
64
|
+
conversion layer (email → the same envelopes and dashboard, no live
|
|
65
|
+
mailbox yet) are done. Left: an actual hint backend, IMAP's live fetch
|
|
66
|
+
loop, and the Nostr bridge — see `docs/USER_GUIDE.md` sections 11–13 for
|
|
67
|
+
the directory/dashboard/hints/bridge demos. GSOC-based rendezvous
|
|
68
|
+
(unsolicited contact, group discovery) stays behind the `Rendezvous`
|
|
69
|
+
interface, pending the GSOC/pub-sub work by Viktor Tóth and Viktor Trón.
|
|
70
|
+
Broadcast-style 1:N live streaming is explicitly out of scope here —
|
|
71
|
+
Solar Punk Ltd's own `swarm-hls-stream`/`Swarmcast` line already owns that
|
|
72
|
+
(DESIGN.md §5).
|
|
73
|
+
|
|
74
|
+
**On decentralization, read this before evaluating further:** everything
|
|
75
|
+
above runs on Swarm feeds and needs no full node — a light client is
|
|
76
|
+
enough. Real-time push notification and unsolicited-contact discovery (PSS
|
|
77
|
+
and GSOC) are a different story: both require a full node, yours or a
|
|
78
|
+
relay's, and that relay sees traffic metadata, which is a real
|
|
79
|
+
centralization point still unresolved (not just a caveat) — see
|
|
80
|
+
`docs/DESIGN.md` §5 and §11. Nothing here should be evaluated as "fully
|
|
81
|
+
decentralized" without that qualification.
|
|
82
|
+
|
|
83
|
+
## Related projects
|
|
84
|
+
|
|
85
|
+
- **recordstore** (github.com/petfold/recordstore) — candidate persistence substrate
|
|
86
|
+
for author logs (versioned key → record over Swarm; POT track).
|
|
87
|
+
- **swarmfs** (github.com/petfold/swarmfs) — fsspec backend, useful for payload blobs.
|
|
88
|
+
- **OntoDAG / mdl-fca** — semantic concept DAG; content-based half of the
|
|
89
|
+
recommendation layer.
|
|
90
|
+
- Attila Lendvai, *Computer aided human communication*
|
|
91
|
+
(codeberg.org/attila.lendvai/publications) — source of the attention taxonomy.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[project.urls]
|
|
2
|
+
Homepage = "https://github.com/petfold/ucomm"
|
|
3
|
+
Repository = "https://github.com/petfold/ucomm"
|
|
4
|
+
Issues = "https://github.com/petfold/ucomm/issues"
|
|
5
|
+
|
|
6
|
+
[build-system]
|
|
7
|
+
requires = ["setuptools>=68"]
|
|
8
|
+
build-backend = "setuptools.build_meta"
|
|
9
|
+
|
|
10
|
+
[project]
|
|
11
|
+
name = "ucomm"
|
|
12
|
+
version = "0.0.1"
|
|
13
|
+
description = "Universal communicator middleware for Ethereum Swarm (working title)"
|
|
14
|
+
requires-python = ">=3.11"
|
|
15
|
+
keywords = ["swarm", "ethereum", "web3", "bee", "messaging", "chat", "notifications", "attention", "middleware"]
|
|
16
|
+
readme = "README.md"
|
|
17
|
+
license = { text = "BSD-3-Clause" }
|
|
18
|
+
authors = [{ name = "Peter Foldiak" }]
|
|
19
|
+
dependencies = ["recordstore[bee]>=0.13.1", "swarm-bee>=1.1,<2", "imapclient>=3.1,<4"]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
test = ["pytest>=8", "hypothesis>=6", "mypy>=1.10", "ruff>=0.5"]
|
|
23
|
+
dev = ["ucomm[test]"] # back-compat alias; [test] is the stack-wide name
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["src"]
|
|
27
|
+
|
|
28
|
+
[tool.pytest.ini_options]
|
|
29
|
+
testpaths = ["tests"]
|
|
30
|
+
|
|
31
|
+
[tool.mypy]
|
|
32
|
+
strict = true
|
|
33
|
+
|
|
34
|
+
[[tool.mypy.overrides]]
|
|
35
|
+
module = "recordstore.*"
|
|
36
|
+
ignore_missing_imports = true
|
|
37
|
+
|
|
38
|
+
[[tool.mypy.overrides]]
|
|
39
|
+
module = "imapclient.*"
|
|
40
|
+
ignore_missing_imports = true
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
line-length = 96
|
ucomm-0.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""ucomm: universal communicator middleware for Ethereum Swarm (working title)."""
|
|
2
|
+
|
|
3
|
+
from .attention import Decision, Intensity, PolicyState, SenderContext, decide
|
|
4
|
+
from .contact import ContactCard, make_contact_card, verify_contact_card
|
|
5
|
+
from .daemon import (
|
|
6
|
+
ChannelDirectory,
|
|
7
|
+
Dashboard,
|
|
8
|
+
DashboardItem,
|
|
9
|
+
DirectoryEntry,
|
|
10
|
+
build_dashboard,
|
|
11
|
+
directory_read_state,
|
|
12
|
+
)
|
|
13
|
+
from .envelope import AttentionClaim, Envelope, EventKind, Genesis, GenesisError, TimeWindow
|
|
14
|
+
from .hints import HintSink, HintSource, InMemoryHints
|
|
15
|
+
from .log import AuthorLog, merge_causal, read_state
|
|
16
|
+
from .rendezvous import InMemoryRendezvous, Rendezvous
|
|
17
|
+
from .signing import InvalidSignature, address_of, sign_envelope, verify_envelope
|
|
18
|
+
from .store import RecordStoreAuthorLog, envelope_to_record, record_to_envelope
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"AttentionClaim",
|
|
22
|
+
"AuthorLog",
|
|
23
|
+
"ChannelDirectory",
|
|
24
|
+
"ContactCard",
|
|
25
|
+
"Dashboard",
|
|
26
|
+
"DashboardItem",
|
|
27
|
+
"Decision",
|
|
28
|
+
"DirectoryEntry",
|
|
29
|
+
"Envelope",
|
|
30
|
+
"EventKind",
|
|
31
|
+
"Genesis",
|
|
32
|
+
"GenesisError",
|
|
33
|
+
"HintSink",
|
|
34
|
+
"HintSource",
|
|
35
|
+
"InMemoryHints",
|
|
36
|
+
"InMemoryRendezvous",
|
|
37
|
+
"Intensity",
|
|
38
|
+
"InvalidSignature",
|
|
39
|
+
"PolicyState",
|
|
40
|
+
"RecordStoreAuthorLog",
|
|
41
|
+
"Rendezvous",
|
|
42
|
+
"SenderContext",
|
|
43
|
+
"TimeWindow",
|
|
44
|
+
"address_of",
|
|
45
|
+
"build_dashboard",
|
|
46
|
+
"decide",
|
|
47
|
+
"directory_read_state",
|
|
48
|
+
"envelope_to_record",
|
|
49
|
+
"make_contact_card",
|
|
50
|
+
"merge_causal",
|
|
51
|
+
"read_state",
|
|
52
|
+
"record_to_envelope",
|
|
53
|
+
"sign_envelope",
|
|
54
|
+
"verify_contact_card",
|
|
55
|
+
"verify_envelope",
|
|
56
|
+
]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Attention layer: priority algebra and policy engine (ATTENTION.md).
|
|
2
|
+
|
|
3
|
+
Pure and deterministic by design: decide(envelope, policy, now) -> Decision is a
|
|
4
|
+
function of its arguments only. All mutable state (ceilings, offsets, thresholds,
|
|
5
|
+
reputation ratchet) lives in PolicyState, owned by the daemon.
|
|
6
|
+
|
|
7
|
+
Receiver sovereignty: this module consumes AttentionClaim data and never
|
|
8
|
+
constructs envelopes. One-way dependency: attention -> envelope, never reverse.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections.abc import Mapping
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from enum import IntEnum
|
|
16
|
+
|
|
17
|
+
from .envelope import Envelope, EventKind, PubKey
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Intensity(IntEnum):
|
|
21
|
+
"""Graded notification output (ATTENTION.md section 3)."""
|
|
22
|
+
|
|
23
|
+
FILED = 0 # silently filed; dashboard history only
|
|
24
|
+
BADGE = 1 # badge / dashboard, no interruption
|
|
25
|
+
SOFT = 2 # ring once / vibrate only
|
|
26
|
+
FULL = 3 # full alert
|
|
27
|
+
BREAKTHROUGH = 4 # overrides DND
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Residual thresholds for grading, in log-scale priority units.
|
|
31
|
+
SOFT_BAND = 5 # |residual| <= SOFT_BAND counts as "near threshold"
|
|
32
|
+
BREAKTHROUGH_MARGIN = 20
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class PolicyState:
|
|
37
|
+
"""Receiver-local policy snapshot. Never published, never negotiated."""
|
|
38
|
+
|
|
39
|
+
threshold: int # current global threshold; DND = high value
|
|
40
|
+
default_ceiling_known: int = 40
|
|
41
|
+
default_ceiling_stranger: int = 0
|
|
42
|
+
wot_ceiling_slope: int = 10 # ceiling = base - slope * wot_distance (ATTENTION 5)
|
|
43
|
+
contact_ceilings: Mapping[PubKey, int] = field(default_factory=dict)
|
|
44
|
+
channel_offsets: Mapping[str, int] = field(default_factory=dict)
|
|
45
|
+
endpoint_offset: int = 0 # e.g. raised while in an interactive session
|
|
46
|
+
bond_credit: int = 10 # credibility bonus when valid collateral attached
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class SenderContext:
|
|
51
|
+
"""What the receiver knows about the sender (from identity/WoT + recsys)."""
|
|
52
|
+
|
|
53
|
+
known_contact: bool
|
|
54
|
+
wot_distance: int | None = None # None = no path
|
|
55
|
+
recsys_prior: int = 0 # advisory prior from RECOMMENDATION.md section 6
|
|
56
|
+
collateral_valid: bool = False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class Decision:
|
|
61
|
+
intensity: Intensity
|
|
62
|
+
effective_priority: int
|
|
63
|
+
residual: int
|
|
64
|
+
ceiling_applied: int
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def ceiling_for(sender: PubKey, ctx: SenderContext, policy: PolicyState) -> int:
|
|
68
|
+
"""Per-sender priority ceiling (ATTENTION.md sections 2, 5)."""
|
|
69
|
+
if sender in policy.contact_ceilings:
|
|
70
|
+
return policy.contact_ceilings[sender]
|
|
71
|
+
if ctx.known_contact:
|
|
72
|
+
return policy.default_ceiling_known
|
|
73
|
+
if ctx.wot_distance is not None:
|
|
74
|
+
base = policy.default_ceiling_known
|
|
75
|
+
return max(
|
|
76
|
+
policy.default_ceiling_stranger,
|
|
77
|
+
base - policy.wot_ceiling_slope * ctx.wot_distance + ctx.recsys_prior,
|
|
78
|
+
)
|
|
79
|
+
return policy.default_ceiling_stranger + ctx.recsys_prior
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def decide(env: Envelope, ctx: SenderContext, policy: PolicyState, now: float) -> Decision:
|
|
83
|
+
"""Map one control-plane envelope to a graded notification intensity.
|
|
84
|
+
|
|
85
|
+
Pure function: (envelope, sender context, policy state, clock) -> Decision.
|
|
86
|
+
"""
|
|
87
|
+
if env.kind is not EventKind.INVITATION or env.attention is None:
|
|
88
|
+
return Decision(Intensity.FILED, 0, -(10**6), 0)
|
|
89
|
+
|
|
90
|
+
claim = env.attention
|
|
91
|
+
if not claim.relevance.active(now):
|
|
92
|
+
return Decision(Intensity.FILED, 0, -(10**6), 0) # expired -> timeline
|
|
93
|
+
|
|
94
|
+
cap = ceiling_for(env.author, ctx, policy)
|
|
95
|
+
credibility = policy.bond_credit if ctx.collateral_valid else 0
|
|
96
|
+
|
|
97
|
+
effective = (
|
|
98
|
+
min(claim.claimed_priority, cap)
|
|
99
|
+
+ policy.channel_offsets.get(env.channel, 0)
|
|
100
|
+
+ policy.endpoint_offset
|
|
101
|
+
+ credibility
|
|
102
|
+
)
|
|
103
|
+
residual = effective - policy.threshold
|
|
104
|
+
|
|
105
|
+
if residual > BREAKTHROUGH_MARGIN:
|
|
106
|
+
intensity = Intensity.BREAKTHROUGH
|
|
107
|
+
elif residual > SOFT_BAND:
|
|
108
|
+
intensity = Intensity.FULL
|
|
109
|
+
elif residual > 0:
|
|
110
|
+
intensity = Intensity.SOFT
|
|
111
|
+
elif residual > -SOFT_BAND:
|
|
112
|
+
intensity = Intensity.BADGE
|
|
113
|
+
else:
|
|
114
|
+
intensity = Intensity.FILED
|
|
115
|
+
|
|
116
|
+
return Decision(intensity, effective, residual, cap)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Real Swarm-backed author logs (M1).
|
|
2
|
+
|
|
3
|
+
Thin adapter from `RecordStoreAuthorLog` (`ucomm.store`) to recordstore's Bee
|
|
4
|
+
backends -- `BeeBytesStore` + `SwarmFeedPointer` -- instead of the in-memory
|
|
5
|
+
ones used for M0 prototyping. This is the "drop-in swap, not a rewrite"
|
|
6
|
+
ROADMAP.md promised: `RecordStoreAuthorLog` only ever needed a
|
|
7
|
+
`recordstore.RecordStore`; it never knew or cared whether that store was
|
|
8
|
+
backed by memory or a live Bee node.
|
|
9
|
+
|
|
10
|
+
Feeds need an IMMUTABLE postage batch (see CLAUDE.md, "Swarm facts to
|
|
11
|
+
respect"). This module never buys one -- pass an existing usable batch id
|
|
12
|
+
(`GET /stamps` on the target node), bought e.g. with
|
|
13
|
+
`POST /stamps/{amount}/{depth}?immutable=true` or swarmfs's `StampManager`.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from recordstore import BeeBytesStore, RecordStore, SwarmFeedPointer
|
|
19
|
+
|
|
20
|
+
from .envelope import ChannelId, PubKey
|
|
21
|
+
from .signing import address_of
|
|
22
|
+
from .store import RecordStoreAuthorLog
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def feed_topic(channel: ChannelId, author: PubKey) -> str:
|
|
26
|
+
"""Feed topic namespacing one author's per-channel log."""
|
|
27
|
+
return f"ucomm/log/{channel}/{author}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def open_author_feed_log(
|
|
31
|
+
api_url: str, channel: ChannelId, *, signer_hex: str, postage_batch_id: str,
|
|
32
|
+
) -> tuple[RecordStore, RecordStoreAuthorLog]:
|
|
33
|
+
"""Open `signer_hex`'s own log for `channel`, backed by a real Swarm feed.
|
|
34
|
+
|
|
35
|
+
`signer_hex` is the author's private key (hex); the feed owner (and
|
|
36
|
+
hence `Envelope.author`, via `ucomm.signing.address_of`) is derived from
|
|
37
|
+
it. Returns `(store, log)`: append through `log`, then call
|
|
38
|
+
`store.commit()` to actually publish -- appends are staged locally until
|
|
39
|
+
then, same as any other `RecordStore` (issue K-4's batching model, not a
|
|
40
|
+
new one for Bee).
|
|
41
|
+
"""
|
|
42
|
+
from bee.swarm.keys import PrivateKey
|
|
43
|
+
|
|
44
|
+
author: PubKey = address_of(PrivateKey.from_hex(signer_hex))
|
|
45
|
+
pointer = SwarmFeedPointer(
|
|
46
|
+
api_url, feed_topic(channel, author),
|
|
47
|
+
signer=signer_hex, postage_batch_id=postage_batch_id,
|
|
48
|
+
)
|
|
49
|
+
bytes_store = BeeBytesStore(api_url, postage_batch_id=postage_batch_id)
|
|
50
|
+
store = RecordStore(bytes_store, pointer=pointer)
|
|
51
|
+
return store, RecordStoreAuthorLog(store, channel, author)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Bridges: adapters converting external protocols into ucomm envelopes.
|
|
2
|
+
|
|
3
|
+
DESIGN.md section 10: every bridge is an inbox adapter into the same
|
|
4
|
+
envelope schema and policy engine ucomm uses natively -- the "attention
|
|
5
|
+
firewall" is a unified inbox across everything, not just Swarm-native
|
|
6
|
+
channels. Kept out of the top-level `ucomm` namespace, same reasoning as
|
|
7
|
+
`ucomm.profiles`: a layer above the kernel, not part of it.
|
|
8
|
+
"""
|