sessionmail 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sessionmail-0.2.0 → sessionmail-0.3.0}/.claude-plugin/plugin.json +1 -1
- {sessionmail-0.2.0 → sessionmail-0.3.0}/PKG-INFO +4 -12
- {sessionmail-0.2.0 → sessionmail-0.3.0}/README.md +3 -11
- {sessionmail-0.2.0 → sessionmail-0.3.0}/hooks/mail-guard/README.md +3 -3
- {sessionmail-0.2.0 → sessionmail-0.3.0}/pyproject.toml +6 -9
- {sessionmail-0.2.0 → sessionmail-0.3.0}/scripts/seat-agent/README.md +12 -4
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_birth.py +28 -5
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_boxes.py +1 -1
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_drop.py +14 -7
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_init.py +1 -1
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_injectlog.py +1 -1
- sessionmail-0.3.0/src/sessionmail/_launchd.py +430 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_magicdns.py +52 -4
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_mhs.py +20 -2
- sessionmail-0.3.0/src/sessionmail/_pull_agent.py +315 -0
- sessionmail-0.3.0/src/sessionmail/_puller.py +1029 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_render.py +6 -3
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_seat.py +63 -1
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_sender.py +1 -1
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_settings.py +22 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_setup.py +72 -4
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_sig.py +13 -5
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_voice.py +1 -1
- sessionmail-0.3.0/src/sessionmail/arm.txt +1 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/mail.schema.json +144 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/message.py +44 -19
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/mhs/mail-ext.lib.sh +1 -1
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/mhs/mhs-thread +2 -2
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/mhs/session-mail-run +11 -8
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/mhs/session-mail-verify +2 -2
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/notmuch/post-new +8 -8
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/pager.py +27 -17
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/postfix/vmailbox-gen +1 -1
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/rules.py +2 -2
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/seat/seat-agent +4 -4
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/seat/seat-agent.plist.template +4 -3
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/seat_init.py +14 -9
- {sessionmail-0.2.0 → sessionmail-0.3.0}/tests/fixtures/sig_v2/README.md +1 -1
- {sessionmail-0.2.0 → sessionmail-0.3.0}/tests/fixtures/sm827_honest/README.md +4 -0
- sessionmail-0.2.0/src/sessionmail/arm.txt +0 -1
- {sessionmail-0.2.0 → sessionmail-0.3.0}/.gitignore +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/LICENSE +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/plugins/pager-monitor/README.md +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/__init__.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_archive.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_arm.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_detect.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_detect_imap.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_generator.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_headers.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_help.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_index.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_layer.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_maildir.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_mcp.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_metrics.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_migrate.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_notmuch.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_operator.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_parsers.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_pull_imap.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_reach.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_record.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_root.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_say.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_send.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_shorten.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_stub.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_subscribe.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_subscription.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_tokens.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_views.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/_why.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/hook_lines.toml +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/humans +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/memory.py +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/mhs/mail-box.lib.sh +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/mhs/session-mail-cat +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/mhs/session-mail-ls +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/mhs/session-mail-tree +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/mhs/session-mail-wake +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/notmuch/notmuch-config.sessionkit.template +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/notmuch/notmuch-config.store.template +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/notmuch/store-index +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/postfix/delivery-check +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/postfix/magicdns-suffix +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/postfix/nm-deliver +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/src/sessionmail/settings.template.json +0 -0
- {sessionmail-0.2.0 → sessionmail-0.3.0}/tests/fixtures/sm827_hint_root/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sessionmail
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: sessionmail - mail for coding agents: message, pager and memory over one maildir.
|
|
5
5
|
Project-URL: Homepage, https://github.com/sessionkit/sessionmail
|
|
6
6
|
Project-URL: Repository, https://github.com/sessionkit/sessionmail
|
|
@@ -104,16 +104,6 @@ message setup # again: it links the notmuch hook
|
|
|
104
104
|
- **Nothing runs `notmuch new` for you.** `message mark` and `memory`
|
|
105
105
|
read an empty index until something does: launchd, cron, or by hand.
|
|
106
106
|
|
|
107
|
-
**Status.** `sessionmail` 0.0.1 on PyPI is a name reservation, not this
|
|
108
|
-
code - a 1.3 KB placeholder with no dependencies.
|
|
109
|
-
Until 0.2.0 ships, install from source instead of the `uv tool install
|
|
110
|
-
sessionmail` line above:
|
|
111
|
-
|
|
112
|
-
```sh
|
|
113
|
-
uv tool install git+https://github.com/sessionkit/sessionmail # from the repo
|
|
114
|
-
make install # from a clone
|
|
115
|
-
```
|
|
116
|
-
|
|
117
107
|
`message`, `pager` and `memory` all run; the specs, in the git repository
|
|
118
108
|
(the release archive leaves them out), say what each verb is meant to do
|
|
119
109
|
and `make progress` counts how much of it is built.
|
|
@@ -188,7 +178,8 @@ Everything else is in `docs/`, one subject per file:
|
|
|
188
178
|
- [Gating on `message verify --json`][verify-json-verdict]: the verdict as
|
|
189
179
|
one JSON object.
|
|
190
180
|
- Delivery: [where mail comes from][mail-delivery-paths], [postfix
|
|
191
|
-
configuration][postfix-configuration], [the file drop][file-drop]
|
|
181
|
+
configuration][postfix-configuration], [the file drop][file-drop],
|
|
182
|
+
[pulling a mail host's boxes][pull].
|
|
192
183
|
- [Configuration][configuration]: the files you set up, by component;
|
|
193
184
|
[settings][settings], the keys of `mail.json`.
|
|
194
185
|
- Index: [notmuch][], [notmuch settings][notmuch-settings], [the post-new
|
|
@@ -210,6 +201,7 @@ Everything else is in `docs/`, one subject per file:
|
|
|
210
201
|
[configuration]: docs/configuration.md
|
|
211
202
|
[settings]: docs/settings.md
|
|
212
203
|
[file-drop]: docs/file-drop.md
|
|
204
|
+
[pull]: docs/pull.md
|
|
213
205
|
[notmuch]: docs/notmuch.md
|
|
214
206
|
[notmuch-settings]: docs/notmuch-settings.md
|
|
215
207
|
[post-new-hook]: docs/notmuch-settings.md#the-post-new-hook
|
|
@@ -85,16 +85,6 @@ message setup # again: it links the notmuch hook
|
|
|
85
85
|
- **Nothing runs `notmuch new` for you.** `message mark` and `memory`
|
|
86
86
|
read an empty index until something does: launchd, cron, or by hand.
|
|
87
87
|
|
|
88
|
-
**Status.** `sessionmail` 0.0.1 on PyPI is a name reservation, not this
|
|
89
|
-
code - a 1.3 KB placeholder with no dependencies.
|
|
90
|
-
Until 0.2.0 ships, install from source instead of the `uv tool install
|
|
91
|
-
sessionmail` line above:
|
|
92
|
-
|
|
93
|
-
```sh
|
|
94
|
-
uv tool install git+https://github.com/sessionkit/sessionmail # from the repo
|
|
95
|
-
make install # from a clone
|
|
96
|
-
```
|
|
97
|
-
|
|
98
88
|
`message`, `pager` and `memory` all run; the specs, in the git repository
|
|
99
89
|
(the release archive leaves them out), say what each verb is meant to do
|
|
100
90
|
and `make progress` counts how much of it is built.
|
|
@@ -169,7 +159,8 @@ Everything else is in `docs/`, one subject per file:
|
|
|
169
159
|
- [Gating on `message verify --json`][verify-json-verdict]: the verdict as
|
|
170
160
|
one JSON object.
|
|
171
161
|
- Delivery: [where mail comes from][mail-delivery-paths], [postfix
|
|
172
|
-
configuration][postfix-configuration], [the file drop][file-drop]
|
|
162
|
+
configuration][postfix-configuration], [the file drop][file-drop],
|
|
163
|
+
[pulling a mail host's boxes][pull].
|
|
173
164
|
- [Configuration][configuration]: the files you set up, by component;
|
|
174
165
|
[settings][settings], the keys of `mail.json`.
|
|
175
166
|
- Index: [notmuch][], [notmuch settings][notmuch-settings], [the post-new
|
|
@@ -191,6 +182,7 @@ Everything else is in `docs/`, one subject per file:
|
|
|
191
182
|
[configuration]: docs/configuration.md
|
|
192
183
|
[settings]: docs/settings.md
|
|
193
184
|
[file-drop]: docs/file-drop.md
|
|
185
|
+
[pull]: docs/pull.md
|
|
194
186
|
[notmuch]: docs/notmuch.md
|
|
195
187
|
[notmuch-settings]: docs/notmuch-settings.md
|
|
196
188
|
[post-new-hook]: docs/notmuch-settings.md#the-post-new-hook
|
|
@@ -29,7 +29,7 @@ script `session-mail-send`:
|
|
|
29
29
|
3. **a heredoc or pipe feeding the send with no `--body-file`** — the send does
|
|
30
30
|
not read stdin, so that delivers an *empty* message at exit 0.
|
|
31
31
|
|
|
32
|
-
`session mail send` is **retired** (2030-
|
|
32
|
+
`session mail send` is **retired** (2030-12-08): it now only prints where its
|
|
33
33
|
verbs went. The rules above still match it, but every deny sends the caller to
|
|
34
34
|
the live door, never back to the retired `--body-file` or MCP `mail_send`
|
|
35
35
|
forms.
|
|
@@ -118,11 +118,11 @@ the rest still judged; click refuses it in its own words). Commands the guard
|
|
|
118
118
|
does not match (`message list --anything`, a `grep` for the phrase) never
|
|
119
119
|
reach this rule.
|
|
120
120
|
|
|
121
|
-
Measured on 33,302 real agent Bash commands (2030-09
|
|
121
|
+
Measured on 33,302 real agent Bash commands (2030-06-09): 1,645 denied, every
|
|
122
122
|
one of them a genuine inline-body or damaged-subject send; **zero** denials of
|
|
123
123
|
a command that merely mentioned the phrase. Scanning cost ~16µs per command (0.54s for the whole corpus); the hook process itself, python startup included, runs in ~15ms.
|
|
124
124
|
|
|
125
|
-
Re-measured with the `message send` rule (2030-
|
|
125
|
+
Re-measured with the `message send` rule (2030-12-08), 30 days of transcripts:
|
|
126
126
|
168,906 Bash commands, 5,143 of them `message send` invocations. The session
|
|
127
127
|
rules' decisions were identical to the pre-move hook's (1,104 denials, zero
|
|
128
128
|
moved). `message send` denials: 2, both a real `$( )` in a subject; zero among
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sessionmail"
|
|
7
|
-
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "sessionmail - mail for coding agents: message, pager and memory over one maildir."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11,<3.15"
|
|
@@ -50,14 +50,11 @@ Homepage = "https://github.com/sessionkit/sessionmail"
|
|
|
50
50
|
Repository = "https://github.com/sessionkit/sessionmail"
|
|
51
51
|
|
|
52
52
|
# ── Version: one source of truth ──────────────────────────────────────
|
|
53
|
-
#
|
|
54
|
-
# its single writer (
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
source = "regex"
|
|
59
|
-
path = ".claude-plugin/plugin.json"
|
|
60
|
-
pattern = '"version"\s*:\s*"(?P<version>[^"]+)"'
|
|
53
|
+
# The ONE version is `version` in [project] above, static. scripts/release.py
|
|
54
|
+
# is its single writer (through `uv version`, which keeps uv.lock in step)
|
|
55
|
+
# and mirrors it into .claude-plugin/plugin.json and marketplace.json. This
|
|
56
|
+
# is the family rule (the kit's docs/family-release.md): the version lives
|
|
57
|
+
# in pyproject.toml and the release mirrors it.
|
|
61
58
|
|
|
62
59
|
# ── Package data: the postfix map generator, and the arm instruction ──
|
|
63
60
|
#
|
|
@@ -9,6 +9,8 @@ through `message`, which keeps the package at three commands:
|
|
|
9
9
|
|
|
10
10
|
message seat init --registry DIR ADDRESS mint the seat's key (was scripts/seat-init)
|
|
11
11
|
message seat agent ADDRESS [SOCKET] run its agent (was scripts/seat-agent/seat-agent)
|
|
12
|
+
message seat agent --install ADDRESS [SOCKET] render and load the LaunchAgent
|
|
13
|
+
message seat agent --uninstall ADDRESS unload it and remove its plist
|
|
12
14
|
message seat agent --template print the LaunchAgent template
|
|
13
15
|
|
|
14
16
|
The `seat` noun is hidden from `message --help` and from the MCP tools; `message
|
|
@@ -48,8 +50,14 @@ seat --help` answers when typed.
|
|
|
48
50
|
|
|
49
51
|
## The template
|
|
50
52
|
|
|
53
|
+
`message seat agent --install ADDRESS [SOCKET]` renders it, writes it into
|
|
54
|
+
`~/Library/LaunchAgents`, loads it with `launchctl bootstrap`, and checks that the agent
|
|
55
|
+
answers. `--uninstall ADDRESS` boots it out and removes the plist. Their contract
|
|
56
|
+
(output, exits, the `Capability: install` marker) is in
|
|
57
|
+
[`docs/message-seat.md`](../../docs/message-seat.md).
|
|
58
|
+
|
|
51
59
|
`message seat agent --template` prints it, and nothing else: it never renders it, loads
|
|
52
|
-
it or runs launchctl.
|
|
60
|
+
it or runs launchctl. To render it by hand, replace each `@PLACEHOLDER@`, then lint it:
|
|
53
61
|
|
|
54
62
|
message seat agent --template |
|
|
55
63
|
sed -e 's|@ADDRESS@|bot.alice|g' -e "s|@MESSAGE@|$(command -v message)|g" \
|
|
@@ -68,9 +76,9 @@ ProgramArguments renders to `message seat agent ADDRESS SOCKET`. The placeholder
|
|
|
68
76
|
On a host that runs `message` from an editable checkout, merging there changes the
|
|
69
77
|
seat's agent at its next start.
|
|
70
78
|
|
|
71
|
-
**
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
**No test and no make target loads a job on the real launchd.** The suite runs
|
|
80
|
+
`--install` against a fake `launchctl` (`SESSIONMAIL_LAUNCHCTL`). A live install is the
|
|
81
|
+
host operator's act: `message seat agent --install`, or the hand render above.
|
|
74
82
|
|
|
75
83
|
## The adapter's env (an example; the adapter owns it)
|
|
76
84
|
|
|
@@ -68,6 +68,19 @@ SUBSCRIBE_VAR = "SESSIONMAIL_SUBSCRIBE"
|
|
|
68
68
|
#: one: the detached generator's line.
|
|
69
69
|
BORN_FILE = "born"
|
|
70
70
|
|
|
71
|
+
#: What a generator line says on a mail-drop host, suffix or none: the drop
|
|
72
|
+
#: builds no maps, so no generator runs there (`_seat._run_generator`'s own
|
|
73
|
+
#: words for it; `tests/test_birth.py` holds the two equal).
|
|
74
|
+
DROP_REGEN = "generator not run: the carrier is the mail drop; it reads the maps per send"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _on_drop(env: Mapping[str, str] | None) -> bool:
|
|
78
|
+
"""Whether the carrier is the mail drop. Decides what a generator line
|
|
79
|
+
SAYS, never what runs."""
|
|
80
|
+
from . import _settings
|
|
81
|
+
return _settings.carrier(env).kind == "drop"
|
|
82
|
+
|
|
83
|
+
|
|
71
84
|
#: Who births when the caller does not say: the subscription journal's `argv`.
|
|
72
85
|
VIA_LISTEN = "pager listen"
|
|
73
86
|
|
|
@@ -350,13 +363,19 @@ def ensure_session_box(session: str, seat: "Seat | None", *,
|
|
|
350
363
|
f"{stale}, left by an earlier run of this session; its row "
|
|
351
364
|
f"goes at the next regen")
|
|
352
365
|
if has_session and seat is not None:
|
|
353
|
-
|
|
366
|
+
if detach and _settings.carrier(env).kind == "drop":
|
|
367
|
+
# The drop's doorbell runs the generator (`_drop.prepare`), and a
|
|
368
|
+
# detaching call never waits on it; the listen asks instead.
|
|
369
|
+
live, why = None, "not measured at session start; the first pager listen measures it"
|
|
370
|
+
else:
|
|
371
|
+
live, why = _seat.doorbell(seat.address, session, env)
|
|
354
372
|
regen_said = ""
|
|
355
373
|
if (birth.born or live is False) and detach:
|
|
356
374
|
_detach(birth, session, env, root, record_source)
|
|
357
375
|
regen_said = birth.regen_line
|
|
358
376
|
elif (birth.born or live is False) and birth.suffix_unknown:
|
|
359
|
-
regen_said =
|
|
377
|
+
regen_said = (DROP_REGEN if _on_drop(env)
|
|
378
|
+
else "generator NOT run (MagicDNS suffix unknown)")
|
|
360
379
|
elif birth.born or live is False:
|
|
361
380
|
regen_said = _seat.regen_maps(root, env=env)
|
|
362
381
|
birth.regen, birth.regen_line = "ran", regen_said
|
|
@@ -384,7 +403,8 @@ def ensure_session_box(session: str, seat: "Seat | None", *,
|
|
|
384
403
|
# included: the whole generator, detached, routes the boxes too.
|
|
385
404
|
_detach(birth, session, env, root, record_source)
|
|
386
405
|
elif owed and birth.suffix_unknown:
|
|
387
|
-
birth.sub_lines.append(" map :
|
|
406
|
+
birth.sub_lines.append(f" map : {DROP_REGEN}" if _on_drop(env) else
|
|
407
|
+
" map : NOT rebuilt - MagicDNS suffix unknown; the "
|
|
388
408
|
"subscriptions stand, and the next rebuild with a suffix "
|
|
389
409
|
"routes them")
|
|
390
410
|
elif owed:
|
|
@@ -429,8 +449,11 @@ def _main(argv: list[str] | None = None) -> int:
|
|
|
429
449
|
# The hook did not ask for the suffix (it costs the hook's
|
|
430
450
|
# budget), so the child asks, and never runs the generator without
|
|
431
451
|
# one: the record's line 2 says so instead.
|
|
452
|
+
# On a mail-drop host no generator runs either way, so the line
|
|
453
|
+
# names the carrier, not the suffix.
|
|
432
454
|
suffix, why = _magicdns.lookup()
|
|
433
455
|
line = (_seat.regen_maps(Path(args.regen)) if suffix is not None
|
|
456
|
+
else DROP_REGEN if _on_drop(None)
|
|
434
457
|
else f"generator NOT run (MagicDNS suffix unknown: {why})")
|
|
435
458
|
except Exception as exc: # noqa: BLE001 - regen_maps never raises; a child that dies says why
|
|
436
459
|
line = f"generator failed in the background: {exc}"
|
|
@@ -443,8 +466,8 @@ def _main(argv: list[str] | None = None) -> int:
|
|
|
443
466
|
return 0
|
|
444
467
|
|
|
445
468
|
|
|
446
|
-
__all__ = ["BORN_FILE", "SUBSCRIBE_VAR", "Birth", "BornRecord", "birth_subscriptions",
|
|
447
|
-
"detach_regen", "ensure_session_box", "read_born"]
|
|
469
|
+
__all__ = ["BORN_FILE", "DROP_REGEN", "SUBSCRIBE_VAR", "Birth", "BornRecord", "birth_subscriptions",
|
|
470
|
+
"born_record", "detach_regen", "ensure_session_box", "read_born"]
|
|
448
471
|
|
|
449
472
|
|
|
450
473
|
if __name__ == "__main__":
|
|
@@ -248,7 +248,7 @@ def live_sessions() -> set[str]:
|
|
|
248
248
|
"""The sessions this host counts live: a harness sidecar
|
|
249
249
|
(`sessiontools.sessions`) whose pid is alive - `session list`'s rule,
|
|
250
250
|
which the generator applies itself, from the same sidecars and `ps`
|
|
251
|
-
(`live_sessions_json`, since master 2030-
|
|
251
|
+
(`live_sessions_json`, since master 2030-12-08). Its procStart check is
|
|
252
252
|
not repeated here: a recycled pid can at worst name one extra subscriber
|
|
253
253
|
in a listing, and routes nothing. `sessiontools` also skips a sidecar
|
|
254
254
|
with no `status`/`updatedAt`, which the generator does not need; every
|
|
@@ -132,6 +132,11 @@ class Maps:
|
|
|
132
132
|
#: The domain a bare From is qualified with (`origin`); set by the
|
|
133
133
|
#: sender's drop pre-flight, empty when nothing asked.
|
|
134
134
|
origin: str = ""
|
|
135
|
+
#: Why the generator refused the alias print (`generator exit N: its
|
|
136
|
+
#: last stderr line`), set by `prepare`; empty when it printed. The
|
|
137
|
+
#: alias map is then empty, and a reader that must tell "no row" from
|
|
138
|
+
#: "no map" (the doorbell) reads this.
|
|
139
|
+
alias_refused: str = ""
|
|
135
140
|
|
|
136
141
|
|
|
137
142
|
def selected(env: Mapping[str, str]) -> bool:
|
|
@@ -183,16 +188,18 @@ def origin(env: Mapping[str, str], host: str, notes: list[str] | None = None) ->
|
|
|
183
188
|
- Else the FQDN from the ONE suffix definition (`_magicdns`), the form
|
|
184
189
|
postfix writes when `myorigin = $myhostname`, and a form
|
|
185
190
|
`_voice.host_forms` accepts.
|
|
186
|
-
- Else the short host
|
|
187
|
-
no
|
|
188
|
-
|
|
191
|
+
- Else the short host. A host with no tailnet gets here on every send
|
|
192
|
+
(the generator's no-fqdn rows), and there the short host is right, so
|
|
193
|
+
nothing is said (`_magicdns.drop_without_tailnet`). Any other host
|
|
194
|
+
(a malformed operator suffix, or a tailscale that is there but does
|
|
195
|
+
not answer) is said in NOTES."""
|
|
189
196
|
given = (env.get(ORIGIN_VAR) or "").strip() or _settings.file_text("postfix.origin", env)
|
|
190
197
|
if given:
|
|
191
198
|
return given
|
|
192
199
|
found, _why = _magicdns.lookup(env)
|
|
193
200
|
if found:
|
|
194
201
|
return f"{host}.{found}"
|
|
195
|
-
if notes is not None:
|
|
202
|
+
if notes is not None and not _magicdns.drop_without_tailnet(env):
|
|
196
203
|
notes.append(f"⚠ {_magicdns.UNKNOWN} - a bare From is qualified with the short "
|
|
197
204
|
f"host only (@{host})")
|
|
198
205
|
return host
|
|
@@ -308,9 +315,9 @@ def prepare(env: Mapping[str, str]) -> Maps:
|
|
|
308
315
|
if done.returncode == 0:
|
|
309
316
|
maps.alias = _rows(done.stdout)
|
|
310
317
|
else:
|
|
311
|
-
maps.
|
|
312
|
-
|
|
313
|
-
"only; no session box gets its doorbell copy")
|
|
318
|
+
maps.alias_refused = f"generator exit {done.returncode}: {_last(done.stderr)}"
|
|
319
|
+
maps.notes.append(f"⚠ no alias map ({maps.alias_refused}) - seat mail lands in the "
|
|
320
|
+
"seat's own box only; no session box gets its doorbell copy")
|
|
314
321
|
return maps
|
|
315
322
|
|
|
316
323
|
|
|
@@ -480,7 +480,7 @@ def probe(key, env: Mapping[str, str]) -> bool:
|
|
|
480
480
|
signer = SIGNER or _sender.sign_wire
|
|
481
481
|
verify = VERIFY or _root.ssh_verify
|
|
482
482
|
wire = (f"From: {key.principal}@probe.invalid\nTo: {key.principal}@probe.invalid\n"
|
|
483
|
-
f"Subject: [ROOT] signing probe\nDate:
|
|
483
|
+
f"Subject: [ROOT] signing probe\nDate: Sun, 09 Jun 2030 00:00:00 +0000\n"
|
|
484
484
|
f"Message-ID: <probe@probe.invalid>\nMIME-Version: 1.0\n"
|
|
485
485
|
f"Content-Type: text/plain; charset=utf-8\n\n"
|
|
486
486
|
f"memory init: a signing probe, never sent\n").encode("utf-8")
|
|
@@ -130,7 +130,7 @@ def tool_use_ids(payload: Mapping[str, Any]) -> list[str]:
|
|
|
130
130
|
"""The tool calls a hook run answers: `tool_calls[].tool_use_id`, then a
|
|
131
131
|
top-level `tool_use_id` (PreToolUse), the missing ones dropped; `[]`
|
|
132
132
|
when there are none. The inner key is Claude Code's hooks reference,
|
|
133
|
-
"PostToolBatch input" (code.claude.com/docs/en/hooks, read 2030-
|
|
133
|
+
"PostToolBatch input" (code.claude.com/docs/en/hooks, read 2030-06-09):
|
|
134
134
|
each `tool_calls` element carries `tool_name`, `tool_input`,
|
|
135
135
|
`tool_use_id` and `tool_response`. Never anything else of the call:
|
|
136
136
|
its input and response are not ours to log."""
|