Files
mesh-catalog/modules/claude-licence-manager/module.json
T
jschoubben 8d2497b65e C1: the licence manager runs as its own account, its service and its tools in its own runtime (issue 1409)
Run inside the machine's tool runner, the manager was issued none of its own logs and states, so the carry-over
failed live ("keeps no log called usage ... it was issued: none"). It now declares runs-as claude-licence-manager,
makes that account, owns what it places on its machine and its secrets, reaches the bus with its own broker secret,
and invokes a node's claude_code_grant, which the tool runner's account could ask and its own could not.

The whole tests play the manager's own runtime: they issue only the states, logs, asks and events its manifest
names, and fail on a manifest without runs-as; a unit test holds every ask in the code to the manifest's invokes.

Builds: design 39 bus-and-switch C1

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 09:25:57 +01:00

324 lines
7.6 KiB
JSON

{
"module": "claude-licence-manager",
"version": "1",
"slug": "licmgr",
"runs-as": "claude-licence-manager",
"requires": [
"postgres-database",
"secret",
{
"name": "licences",
"storage": "key-value",
"template": "state",
"options": {
"history": 1
}
},
{
"name": "bindings",
"storage": "key-value",
"template": "state",
"options": {
"history": 8
}
},
{
"name": "leases",
"storage": "key-value",
"template": "state",
"options": {
"history": 1,
"key-ttl": true
}
},
{
"name": "offered",
"storage": "key-value",
"template": "state",
"options": {
"history": 1,
"key-ttl": true
}
},
{
"name": "usage",
"storage": "stream",
"template": "log",
"options": {
"max-age-days": 35,
"max-mib": 256
}
},
{
"name": "audit",
"storage": "stream",
"template": "log",
"options": {
"max-mib": 64
}
},
{
"name": "carried",
"storage": "key-value",
"template": "state",
"options": {
"history": 1
}
},
{
"name": "conditions",
"storage": "key-value",
"template": "state",
"options": {
"history": 1
}
}
],
"contributes": {
"postgres-database": {
"name": "claude_licences",
"readable": [
"audit",
"binding"
]
}
},
"binds": {
"postgres-database": "${dir:state}/database.json"
},
"secrets": {
"postgres-database": "${dir:state}/database.secret",
"secret": {
"grant-key": "${dir:state}/grant.key"
}
},
"own-secrets": {
"broker": "${dir:state}/broker"
},
"secrets-owner": "claude-licence-manager",
"uses": [
"operator-channel"
],
"invokes": [
"seat:operator-channel.tell",
"seat:mesh-vault.licence-put",
"seat:mesh-vault.get",
"seat:mesh-config.config",
"claude-code.claude_code_grant"
],
"seats": [
{
"name": "anthropic-licence-manager",
"scope": "mesh",
"serves": [
"licences",
"bindings",
"bind",
"switch",
"release",
"refresh",
"usage",
"adopt",
"current",
"public-key"
]
}
],
"claims": [
{
"name": "anthropic-licence-manager",
"scope": "mesh",
"serves": [
"licences",
"bindings",
"bind",
"switch",
"release",
"refresh",
"usage",
"adopt",
"current",
"public-key"
]
}
],
"emits": [
"licence.adopted",
"licence.refused",
"licence.switched",
"licence.failing",
"usage.read",
"binding.changed",
"holdings.asked"
],
"consumes": [
"claude-code.holdings-changed"
],
"config": {
"cadence_minutes": {
"type": "number",
"description": "How old a licence's grant may grow before it is rotated, in minutes; 0 keeps 240.",
"minimum": 0,
"trusted": true,
"default": 240
},
"floor_minutes": {
"type": "number",
"description": "How little time a grant may have left before it is refreshed in any case, in minutes; 0 keeps 60.",
"minimum": 0,
"trusted": true,
"default": 60
},
"failures_to_notify": {
"type": "number",
"description": "How many refreshes of a licence fail in a row before the operator is told, a count; 0 keeps 3.",
"minimum": 0,
"trusted": true,
"default": 3
},
"cooldown_hours": {
"type": "number",
"description": "How long after telling the operator about a licence nothing more is said about it, in hours; 0 keeps 24.",
"minimum": 0,
"trusted": true,
"default": 24
},
"refresh_warn_days": {
"type": "number",
"description": "How long before a licence's refresh token itself expires the operator is warned, in days; 0 keeps 3.",
"minimum": 0,
"trusted": true,
"default": 3
},
"switch_auto": {
"type": "boolean",
"description": "Whether the manager moves every consumer off a licence whose session or weekly usage reaches switch_at_pct, to the licence with the most room; off, it still raises its no-room and near-limit conditions.",
"trusted": true,
"default": true
},
"switch_at_pct": {
"type": "number",
"description": "The session or weekly usage of a licence, in percent, at which its consumers are moved to the licence with the most room.",
"minimum": 1,
"maximum": 100,
"trusted": true,
"default": 99
},
"room_below_pct": {
"type": "number",
"description": "What both the session and the weekly usage of a licence must be below, in percent, for it to be a target of a switch.",
"minimum": 1,
"maximum": 100,
"trusted": true,
"default": 90
},
"warn_at_pct": {
"type": "number",
"description": "The session or weekly usage of a licence with consumers, in percent, at which it is warned about once.",
"minimum": 1,
"maximum": 100,
"trusted": true,
"default": 90
},
"usage_minutes": {
"type": "number",
"description": "How often each subscription's usage is read, in minutes.",
"minimum": 1,
"maximum": 60,
"trusted": true,
"default": 5
},
"usage_stale_minutes": {
"type": "number",
"description": "How old a subscription's last successful usage reading may grow before its room is not known and it is said, in minutes.",
"minimum": 5,
"maximum": 180,
"trusted": true,
"default": 15
}
},
"resources": [
{
"id": "account",
"type": "user",
"name": "claude-licence-manager",
"shell": "/usr/bin/nologin",
"home": "/var/lib/claude-licence-manager"
},
{
"id": "state",
"type": "directory",
"mode": "0700",
"owner": "claude-licence-manager",
"place": ".",
"unknown": "program"
},
{
"id": "database-url",
"type": "file",
"path": "${dir:state}/database.url",
"mode": "0600",
"owner": "claude-licence-manager",
"template": "files/database.url"
},
{
"id": "settings",
"type": "file",
"path": "${dir:state}/settings.json",
"mode": "0600",
"owner": "claude-licence-manager",
"takes-config": [
"cadence_minutes",
"floor_minutes",
"failures_to_notify",
"cooldown_hours",
"refresh_warn_days",
"switch_auto",
"switch_at_pct",
"room_below_pct",
"warn_at_pct",
"usage_minutes",
"usage_stale_minutes"
],
"merge": "json",
"template": "files/settings.json"
}
],
"tools": [
"licences_switches",
"licences_read_usage",
"licences_switch_now",
"licences_settings",
"licences_kept",
"licences_resync",
"licences_conditions",
"licences_carried"
],
"build": {
"artifacts": [
{
"name": "code",
"kind": "bundle",
"language": "go",
"system": "arch",
"from": "cmd/claude-licence-manager",
"binary": "claude-licence-manager",
"loads": [
"claude-licence-manager"
],
"env": {
"DATABASE_URL_FILE": "${dir:state}/database.url",
"MESH_LICENCE_STATE": "${dir:state}",
"MESH_LICENCE_KEY_FILE": "${dir:state}/grant.key",
"MESH_LICENCE_SETTINGS": "${dir:state}/settings.json"
}
},
{
"name": "files",
"kind": "archive",
"from": "files"
}
]
}
}