Re-capture the catalogue at mesh-catalog feat/r6 3a1017f5 and excuse a derived subject whose module lives in another repository (issue 1945)

This commit is contained in:
2026-10-24 20:37:04 +02:00
parent 8c54139905
commit dfd81d8daf
20 changed files with 57 additions and 335 deletions
@@ -179,6 +179,15 @@ func TestARetiredSeatsConsumeIsSetAsideAndAnUnknownOneIsNot(t *testing.T) {
// nothing is silence.
func TestTheCataloguesDerivedSubjectsMeet(t *testing.T) {
emitters, consumers, seats := theCataloguesEvents(t)
installed := map[string]bool{} // the modules of this catalogue, whether they emit or not
root := beside.Catalogue(t)
if dirs, err := os.ReadDir(root); err == nil {
for _, d := range dirs {
if _, err := os.Stat(filepath.Join(root, d.Name(), "module.json")); err == nil {
installed[d.Name()] = true
}
}
}
// Every subject something publishes: a module's own events, and the events of every role.
published := map[string]bool{}
@@ -229,6 +238,14 @@ func TestTheCataloguesDerivedSubjectsMeet(t *testing.T) {
if strings.Contains(subject, "*") || strings.Contains(subject, ">") {
continue
}
// And a module that lives in another repository (mesh-delivery's view and its kin) is not installed here, so its
// events cannot be seen here: excused, as the comment above says, unless the name is a role's, which is the very
// mistake this test exists for.
if module, _, ok := strings.Cut(strings.TrimPrefix(subject, "mesh.mod."), ".event."); ok && strings.HasPrefix(subject, "mesh.mod.") {
if _, isASeat := byName[module]; !isASeat && !installed[module] {
continue
}
}
lonely = append(lonely, c.Module+" subscribes "+subject+", which nothing publishes")
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ The copy carries the catalogue's private details: domains, first names in module
private addresses. That is acceptable while mesh-controller is private; if it ever goes public, this copy is
a second place to clean besides the catalogue itself.
mesh-catalog 2246ccc492e59579a140752e6f574b2121d296af modules/*/module.json, modules/*/files, modules/*/templates, predefined.json
mesh-catalog 3a1017f5594217092a654dd1e2d72226766bc16a modules/*/module.json, modules/*/files, modules/*/templates, predefined.json
mesh-host 9d9e3a46c4f5943cfffb413d962ce48b75e2fd39 examples/foundation-first-node.lock
mesh-sdk e45bdc0891bb7c90be639c4903d51bb5dc61a2ac conformance/events/module-event.json
@@ -1,3 +0,0 @@
{
"accounts": {}
}
@@ -1,10 +0,0 @@
#!/bin/sh
# Betterbird's start, written by the mesh (module betterbird, novox/hq ADR 0316). Replaced at every push.
#
# Before Betterbird starts, the module's tools place its user.js in the profile (the settings prefs, accounts, identities, calendars,
# address-books and tags) and write what Betterbird rewrites itself: the account lists and each declared account's filters. They do
# it only while no Betterbird of this account runs, because Betterbird rewrites its saved preferences when it quits; a
# second start while one runs only hands over to it. What the preparation could not do it says here, and
# betterbird_check says it too. Betterbird starts either way: its configuration never holds the mail back.
/usr/local/lib/mesh-betterbird/betterbird-tools prepare || echo "betterbird: the mesh's preparation did not complete; betterbird_check says why" >&2
exec /opt/betterbird/betterbird "$@"
@@ -1,6 +0,0 @@
{
"identities": {},
"calendars": {},
"address-books": {},
"filters": {}
}
@@ -1,25 +0,0 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=Betterbird
GenericName=Mail Client
Comment=Send and receive mail with Betterbird
Exec=/usr/local/bin/betterbird %u
Icon=/opt/betterbird/chrome/icons/default/default128.png
Terminal=false
Categories=Network;Email;
MimeType=message/rfc822;x-scheme-handler/mailto;application/x-xpinstall;application/x-extension-ics;text/calendar;text/vcard;text/x-vcard;x-scheme-handler/webcal;x-scheme-handler/webcals;x-scheme-handler/mid;
StartupNotify=true
StartupWMClass=eu.betterbird.Betterbird
Keywords=Email;E-mail;Calendar;Thunderbird;
Actions=ComposeMessage;OpenAddressBook;
# Written by the mesh (module betterbird, novox/hq ADR 0316 §3), in place of the community package's entry:
# it starts Betterbird through the module's start, and names the icon in the installation, with no link.
[Desktop Action ComposeMessage]
Name=Write new message
Exec=/usr/local/bin/betterbird -compose
[Desktop Action OpenAddressBook]
Name=Open address book
Exec=/usr/local/bin/betterbird -addressbook
@@ -1,3 +0,0 @@
{
"prefs": {}
}
@@ -1,3 +0,0 @@
{
"tags": {}
}
@@ -1,253 +0,0 @@
{
"module": "betterbird",
"version": "1",
"upgrade": {
"policy": "record",
"why": "a new build may remove the community package betterbird-bin and replaces the installation in /opt/betterbird, which a running Betterbird has open: the switch on each node and every upgrade wait for the operator, who quits Betterbird first and then pushes (novox/hq ADR 0316 §4, §5)"
},
"requires": [
"x11-display"
],
"invokes": [
"seat:mesh-controller.settings",
"seat:mesh-controller.nodes",
"seat:mesh-controller.node",
"betterbird.betterbird_derive"
],
"tools": [
"betterbird_status",
"betterbird_prefs",
"betterbird_config",
"betterbird_config_apply",
"betterbird_user_js",
"betterbird_log",
"betterbird_restart",
"betterbird_check",
"betterbird_accounts",
"betterbird_identities",
"betterbird_calendars",
"betterbird_address_books",
"betterbird_filters",
"betterbird_tags",
"betterbird_derive",
"betterbird_drift",
"betterbird_reminder_test"
],
"config": {
"prefs": {
"type": "object",
"description": "Betterbird's preferences the operator sets, by their own names (mailnews.*, calendar.* and the like), laid into prefs.json; an account's, a server's and a tag's are refused, as accounts, the four kinds and tags own them (novox/hq ADR 0329 §6).",
"trusted": true,
"default": {}
},
"accounts": {
"type": "object",
"description": "The mail accounts Betterbird is set up with, each by a name of the operator's: its address, its sender's name and its incoming and outgoing servers; no password, token or cookie (novox/hq ADR 0329 §2).",
"trusted": true,
"default": {}
},
"identities": {
"type": "object",
"description": "The identities beside an account's own, each by a name of the operator's: the address and name it sends as, its signature as a list of lines, and the profile's key to adopt on a node.",
"trusted": true,
"default": {}
},
"calendars": {
"type": "object",
"description": "The calendars Betterbird shows, each by a name of the operator's: where it is and how it is reached, and the profile's key to adopt on a node.",
"trusted": true,
"default": {}
},
"address-books": {
"type": "object",
"description": "The address books Betterbird uses, each by a name of the operator's: where it is and how it is reached, and the profile's key to adopt on a node.",
"trusted": true,
"default": {}
},
"filters": {
"type": "object",
"description": "The message filters of each account, by account: what each matches and what it does with the mail.",
"trusted": true,
"default": {}
},
"tags": {
"type": "object",
"description": "The tags Betterbird offers, each by its own key: its name, colour and order; trusted since issue 751, so a change to it is the operator's to approve.",
"trusted": true,
"default": {}
}
},
"resources": [
{
"id": "configuration-dir",
"type": "directory",
"path": "${machine:account-home}/.config/betterbird",
"owner": "${machine:account}",
"mode": "0755",
"unknown": "move-out",
"knows": ["accounts-record.json", "configuration-record.json", "kept/"]
},
{
"id": "prefs",
"type": "file",
"path": "${machine:account-home}/.config/betterbird/prefs.json",
"owner": "${machine:account}",
"mode": "0644",
"merge": "json",
"takes-config": [
"prefs"
],
"template": "files/prefs.json",
"trusted": true
},
{
"id": "accounts",
"type": "file",
"path": "${machine:account-home}/.config/betterbird/accounts.json",
"owner": "${machine:account}",
"mode": "0600",
"merge": "json",
"takes-config": [
"accounts"
],
"template": "files/accounts.json",
"trusted": true
},
{
"id": "configuration",
"type": "file",
"path": "${machine:account-home}/.config/betterbird/configuration.json",
"owner": "${machine:account}",
"mode": "0600",
"merge": "json",
"takes-config": [
"identities",
"calendars",
"address-books",
"filters"
],
"template": "files/configuration.json",
"trusted": true
},
{
"id": "tags",
"type": "file",
"path": "${machine:account-home}/.config/betterbird/tags.json",
"owner": "${machine:account}",
"mode": "0644",
"merge": "json",
"takes-config": [
"tags"
],
"template": "files/tags.json",
"trusted": true
},
{
"id": "community-package",
"type": "package",
"package": "betterbird-bin",
"absent": true
},
{
"id": "installation",
"type": "archive",
"path": "/opt/betterbird",
"artifact": "archive",
"unknown": "program"
},
{
"id": "owl",
"type": "archive",
"path": "/opt/betterbird/owl",
"artifact": "owl",
"unknown": "program"
},
{
"id": "policies",
"type": "archive",
"path": "/opt/betterbird/distribution",
"artifact": "distribution",
"unknown": "program"
},
{
"id": "spelling",
"type": "archive",
"path": "/opt/betterbird/defaults/pref",
"artifact": "defaults",
"unknown": "program"
},
{
"id": "preparation",
"type": "archive",
"path": "/usr/local/lib/mesh-betterbird",
"artifact": "tools",
"unknown": "program"
},
{
"id": "start",
"type": "file",
"path": "/usr/local/bin/betterbird",
"mode": "0755",
"template": "files/betterbird"
},
{
"id": "launcher",
"type": "file",
"path": "/usr/local/share/applications/eu.betterbird.Betterbird.desktop",
"mode": "0644",
"template": "files/eu.betterbird.Betterbird.desktop"
}
],
"build": {
"artifacts": [
{
"name": "tools",
"kind": "bundle",
"language": "go",
"system": "arch",
"from": "cmd/betterbird-tools",
"binary": "betterbird-tools",
"loads": [
"betterbird-tools"
]
},
{
"name": "archive",
"kind": "archive",
"url": "https://www.betterbird.eu/downloads/LinuxArchive/betterbird-153.4.0esr-bb10.en-US.linux-x86_64.tar.xz",
"sha256": "38dd24890a66b4cf9a45bc24e15192e00c26b929e1bb8df7974c16a4539129e2",
"version": "153.4.0esr-bb10",
"within": "betterbird",
"names-on-purpose": {
"www.betterbird.eu": "Betterbird's project publishes its Linux release there; the build seat fetches it once, by its pinned sha256 (novox/hq ADR 0316 §2)"
}
},
{
"name": "owl",
"kind": "archive",
"url": "https://www.beonex.com/owl/owl.xpi",
"sha256": "a54cc3d52113b34f97942ee19190947dee23561e2217f18db3d80ac023991221",
"version": "1.5.6.1",
"file": "owl.xpi",
"names-on-purpose": {
"www.beonex.com": "Owl's vendor publishes its xpi there; the build seat fetches it once, by its pinned sha256 (novox/hq ADR 0316 §6)"
}
},
{
"name": "distribution",
"kind": "archive",
"from": "install/distribution"
},
{
"name": "defaults",
"kind": "archive",
"from": "install/defaults-pref"
},
{
"name": "files",
"kind": "archive",
"from": "files"
}
]
}
}
@@ -1 +1 @@
{}
{"main-mergers": ["jschoubben"]}
@@ -1 +1 @@
{"repositories": ${config:repositories}}
{"repositories": ${config:repositories}, "keep-main-rules": ${config:keep-main-rules}}
@@ -169,11 +169,23 @@
"trusted": true,
"default": {}
},
"keep-main-rules": {
"type": "boolean",
"description": "Whether the module keeps main's branch protection as the mesh wants it. True: every ten minutes, and at the start, main's rule in every repository of the organisation novox is put to no push and the tools' account alone merging, and each apply of the `repositories` value narrows the main rule of the repositories it lists, requiring no status. False (the default): the module changes no existing repository's branch protection, whoever set it at the forge, but takes away a required status it finds on a rule covering main, once (ADR 0534 §1); a repository listed in `repositories` that does not exist is still made, with its first protection.",
"trusted": true,
"default": false
},
"repositories": {
"type": "object",
"description": "The repositories of the organisation novox the mesh holds (ADR 0428): each by name, {\"<name>\": {\"private\": true, \"description\": \"...\"}}. One that does not exist is made by the module's setup login under its own account, its main protected and given a first commit (a README naming the description), its rule narrowed to no push and the tools' merge requiring mesh/check, and only then moved into the organisation; one that exists only has main's rule put right, mesh/check in place of mesh/merge-gate and mesh/repo-check and its other statuses kept (a main requiring mesh/repo-check alone keeps it). Every other repository whose main requires mesh/merge-gate has the two swapped for mesh/check; nothing else of a repository out of the value is touched.",
"description": "The repositories of the organisation novox the mesh holds (ADR 0428): each by name, {\"<name>\": {\"private\": true, \"description\": \"...\"}}. One that does not exist is made by the module's setup login under its own account, its main protected and given a first commit (a README naming the description ), its rule narrowed to no push, the tools' account and main-mergers alone merging, and no status required (ADR 0534), and only then moved into the organisation; one that exists only has main's rule put right so. Every other repository has the statuses its main requires taken off; nothing else of a repository out of the value is touched. With keep-main-rules false a listed repository that exists likewise has only its required statuses taken off, once.",
"trusted": true,
"default": {}
},
"main-mergers": {
"type": "list",
"description": "The forge logins that may merge into main beside the tools' account, on every repository of the organisation (ADR 0534 §2): the operator's own, so the operator can always merge by hand at the forge. The tools' account is always on main's merge whitelist; no agent's account belongs here.",
"trusted": true,
"default": ["jschoubben"]
}
},
"facts": {
@@ -302,7 +314,9 @@
"template": "files/config.json",
"merge": "json",
"takes-config": [
"url"
"url",
"keep-main-rules",
"main-mergers"
]
},
{
@@ -365,10 +379,12 @@
"MESH_GITEA_ADMIN_PASSWORD_FILE": "${dir:state}/admin.secret",
"MESH_GITEA_TOOLS_USER": "mesh-tools",
"MESH_REPOSITORIES_FILE": "${dir:mesh-state}/repositories.json",
"MESH_REPOSITORIES_OUTCOME": "${dir:runtime-state}/repositories-outcome.json"
"MESH_REPOSITORIES_OUTCOME": "${dir:runtime-state}/repositories-outcome.json",
"MESH_GITEA_CONFIG_FILE": "${dir:mesh-state}/config.json"
},
"restart-on": [
"repositories-file"
"repositories-file",
"runtime-config"
]
}
],
@@ -51,7 +51,8 @@
"requires": [
"forge-account",
"go-toolchain",
"artifact-store"
"artifact-store",
"node-tools"
],
"own-secrets": {
"broker": "${dir:state}/broker"
@@ -39,7 +39,8 @@
"requires": [
"forge-account",
"rootless-image-build",
"artifact-store"
"artifact-store",
"node-tools"
],
"own-secrets": {
"broker": "${dir:state}/broker"
@@ -50,7 +50,8 @@
"forge-account",
"npm-package-registry",
"node-toolchain",
"artifact-store"
"artifact-store",
"node-tools"
],
"contributes": {
"npm-package-registry": {
@@ -8,18 +8,13 @@
"scope": "mesh",
"serves": [
"deliveries",
"times",
"show",
"groups",
"what-if",
"checks",
"table",
"stalled",
"recheck",
"release",
"stop",
"close",
"retire-history"
"close"
]
}
],
@@ -30,7 +25,6 @@
"gitea.forge.status",
"gitea.forge.pull-request.edited",
"gitea.forge.pull-request.closed",
"mesh-controller.plan-moved",
"mesh-delivery-forge-bridge.forge.**",
"mesh-config.config-changed"
],
@@ -40,11 +34,6 @@
"merged"
],
"invokes": [
"seat:mesh-controller.deliver",
"seat:mesh-controller.delivery-stop",
"seat:mesh-controller.delivery-backups-again",
"seat:mesh-controller.delivery-walks",
"seat:mesh-controller.plans",
"gitea.gitea_commit_statuses",
"gitea.gitea_contains",
"gitea.gitea_open_pulls"
@@ -71,13 +60,8 @@
}
],
"reads": [
"mesh-delivery.deliveries",
"mesh-delivery.groups",
"mesh-delivery.pulls",
"mesh-delivery-teller.answers",
"mesh-delivery-checker.verdicts",
"mesh-controller.assignments",
"node-engine.have"
"mesh-delivery-checker.verdicts"
],
"tools": [
"delivery_status"
@@ -9,7 +9,6 @@
"mesh-catalog modules/audit-logger",
"mesh-catalog modules/avahi",
"mesh-catalog modules/baserow",
"mesh-catalog modules/betterbird",
"mesh-catalog modules/blueman",
"mesh-catalog modules/bluetooth",
"mesh-catalog modules/brother",
@@ -57,13 +57,15 @@
"consumes": [
"mesh-issues.opened",
"mesh-issues.moved",
"mesh-delivery-gate.transition"
"mesh-delivery-gate.transition",
"mesh-delivery-view.delivered"
],
"invokes": [
"claude-code.claude_code_judge",
"seat:mesh-delivery.checks",
"seat:operator-channel.notify",
"gitea.gitea_pull_reviews",
"gitea.gitea_contains",
"records.records_status",
"records.records_list",
"records.records_read",
@@ -102,7 +104,6 @@
"mesh-catalog modules/audit-logger",
"mesh-catalog modules/avahi",
"mesh-catalog modules/baserow",
"mesh-catalog modules/betterbird",
"mesh-catalog modules/blueman",
"mesh-catalog modules/bluetooth",
"mesh-catalog modules/brother",
@@ -77,7 +77,9 @@
"mesh-controller.condition-raised",
"mesh-controller.condition-changed",
"mesh-controller.condition-cleared",
"mesh-controller.plan-moved",
"mesh-delivery-view.moving",
"mesh-delivery-view.settled",
"mesh-delivery-view.stuck",
"intake.choice",
"intake.link",
"intake.failed",
@@ -13,6 +13,9 @@
]
}
],
"emits": [
"backed-up"
],
"tools": [
"backup_health",
"backup_delete_retired",