docs(modules): classify missing statefulness metadata #263

Merged
pdurlej merged 1 commit from codex/hardening/statefulness-classification into main 2026-05-13 18:35:21 +02:00
Collaborator

Canary status: missing — module metadata batch touches modules/; fire canary before merge if operator wants full ADR-0007 enforcement. Local validation passed.

Canary Context Pack

Product story

Future F2 smoke dispatches must be built from repo metadata, not memory or optimism. This PR makes every module queryable by spec.runtime.statefulness so stateless candidates can be selected mechanically.

What changed

Added spec.runtime.statefulness to 49 module manifests that were missing the field.

Why it changed

Pan Herbatka's corrected F1.5 dispatch found that many modules lacked statefulness metadata, making the original F2 candidate list unsafe. Hardening D now creates a classified universe for the next session.

Files touched

  • modules/*/module.yaml — 49 one-line metadata additions

Relevant context

  • prompts/codex-f1.5-hardening-f2-2026-05-13.md Hardening D, overridden by the operator-pasted Pan Herbatka correction
  • schema/module.schema.json allows stateless, stateful, and sidecar-bound only. The correction mentioned ephemeral, but the schema does not currently accept it, so this PR does not introduce that value.
  • Issue #142 cutover thread

Runtime evidence

Read-only evidence used:

  • canonical compose service/mount declarations from compose/apps/compose.yaml and included compose files
  • live docker ps --format '{{.Names}}' on RS2000
  • live docker inspect mount metadata only for relevant containers; env/secret values were not read or printed

Known constraints

  • stateful here means the module owns local persistent state, writable data mounts, protocol identity, media/model state, or a dedicated backing store.
  • stateless may still depend on stateful upstreams, matching existing repo precedent (umami, n8n-worker).
  • sidecar-bound is used for one-shot or profile modules whose state is materially owned by a paired primary service.
  • F2 should still filter by lifecycle, host, canonical compose support, and health contract. This PR only supplies the missing statefulness dimension.

Explicit out-of-scope

  • No runtime mutation
  • No smoke trigger
  • No compose migration
  • No lifecycle edits
  • No new schema value for ephemeral

Requested decision

Merge if the classifications are acceptable as the next-session F2 candidate filter foundation.

Merge blockers

  • Any module whose classification contradicts known runtime state
  • Any desire to add ephemeral to schema first instead of using current enum values

Classification report

Module Statefulness Basis
agent-plane-shadow-control stateless control process; checkpoint state lives in shadow Postgres
agent-plane-shadow-honcho-status stateless mirror/status surface; no local state evidence
agent-plane-shadow-postgres stateful manifest data store agent-plane-shadow-postgres-data
agent-plane-shadow-projection stateless projection worker; state held by upstream control/backing stores
audio-mcp-legacy stateful sunset legacy audio path; replaced by stateful audio path
deploy-control stateful live RW /state bind mount under legacy root
element-web stateless web client; only config bind observed, user state lives in Synapse
excalidraw-app stateless live container has no mounts; app state not persisted locally
excalidraw-room stateless live container has no mounts; realtime relay only
git-mirror stateful live RW /state bind mount for integration state
gmail-openclaw-broker stateless live container has no mounts; headers broker/gateway behavior
gmail-private-mcp stateful live RW /state and /config integration binds
gmail-triage-mcp stateful live RW /state and /config integration binds
hermes-agency stateful systemd agency produces durable product artifacts/state
infisical-redis stateful live Redis /data volume
integrations-hub stateless conceptual hub; component state belongs to gmail/git-mirror modules
iskra-phase-1-0-bundle stateless parked bundle/spec artifact, not deployed runtime state
jellyfin stateful media-server intent implies media/library state if activated
kanboard stateful live Kanboard stack includes dedicated Postgres volume
landing-pdurlej stateless landing-page intent, no local runtime state
matrix-hub-private sidecar-bound private profile state would live in Synapse, not this module
minio stateful live MinIO /data volume and manifest data store
minio-init sidecar-bound one-shot initializer; state owned by MinIO
np stateful live RW Obsidian vault bind at /vault
np-meerkat-backend stateful live RW data/photos binds
np-meerkat-frontend stateless live frontend container has no mounts
np-memos stateful live RW memos assets bind
np-radicale stateful live RW collections bind + local volume
np-silverbullet stateful live RW Obsidian vault bind, even though lifecycle is sunset
ntfy stateful live cache/data volumes
obsidian-headless-sync stateful sync daemon owns/updates operator vault state
openclaw-gateway stateless gateway; state lives upstream in Honcho/OpenClaw runtime
openclaw-mail-gateway stateless live config is read-only; processing state upstream
openclaw-mail-worker stateless live config is read-only; processing state upstream
openclaw-searxng-mcp stateless MCP wrapper; search state belongs to upstream SearXNG
postgres stateful live Postgres data volume and manifest data store
products-agent-eval-lab stateful sunset product/eval lab with durable generated artifacts
redis stateful live Redis /data volume and AOF/RDB command
safe-session-api stateful live safe_session_state volume
safe-session-web stateless live web container has no mounts; API owns session state
signal-bridge-legacy stateful bridge/protocol identity state, sunset path
signal-bridge-mautrix stateful bridge/protocol identity state, parked path
signal-cli stateful Signal protocol daemon has account/session identity state
teamspeak3 stateful voice server intent has durable config/channel/user state
traefik stateful live writable ACME certificate file
vault stateful live Vault data/log volumes and manifest data store
vault-bootstrap sidecar-bound one-shot initializer; state owned by Vault
voice-transcription stateful live model and service state volumes
zeroclaw-guarded-self-edit stateless archived control artifact; no active runtime state

Verification

  • for f in modules/*/module.yaml; do grep -q "statefulness:" "$f" || echo "$f"; done → no output
  • python count after change → {'stateless': 31, 'stateful': 45, 'sidecar-bound': 4}
  • Per-module normal validation loop over all modules/*/module.yaml → all passed
  • pytest control-plane/platformctl/tests/test_validate.py -q → 6 passed

Spec sources read

  • schema/module.schema.json — valid enum and semantics
  • prompts/codex-f1.5-hardening-f2-2026-05-13.md — original Hardening D context
  • operator-pasted Pan Herbatka correction in the Codex thread — revised Hardening D scope
  • compose/apps/compose.yaml, compose/core/compose.yaml, compose/edge/compose.yaml — compose service/mount evidence
  • modules/*/module.yaml for missing-field modules — lifecycle, runtime service, dependencies, and intent
  • RS2000 read-only docker ps and docker inspect mount metadata — live evidence without env/secret values

Refs #142.

Canary status: missing — module metadata batch touches `modules/`; fire canary before merge if operator wants full ADR-0007 enforcement. Local validation passed. ## Canary Context Pack ### Product story Future F2 smoke dispatches must be built from repo metadata, not memory or optimism. This PR makes every module queryable by `spec.runtime.statefulness` so stateless candidates can be selected mechanically. ### What changed Added `spec.runtime.statefulness` to 49 module manifests that were missing the field. ### Why it changed Pan Herbatka's corrected F1.5 dispatch found that many modules lacked statefulness metadata, making the original F2 candidate list unsafe. Hardening D now creates a classified universe for the next session. ### Files touched - `modules/*/module.yaml` — 49 one-line metadata additions ### Relevant context - `prompts/codex-f1.5-hardening-f2-2026-05-13.md` Hardening D, overridden by the operator-pasted Pan Herbatka correction - `schema/module.schema.json` allows `stateless`, `stateful`, and `sidecar-bound` only. The correction mentioned `ephemeral`, but the schema does not currently accept it, so this PR does not introduce that value. - Issue #142 cutover thread ### Runtime evidence Read-only evidence used: - canonical compose service/mount declarations from `compose/apps/compose.yaml` and included compose files - live `docker ps --format '{{.Names}}'` on RS2000 - live `docker inspect` mount metadata only for relevant containers; env/secret values were not read or printed ### Known constraints - `stateful` here means the module owns local persistent state, writable data mounts, protocol identity, media/model state, or a dedicated backing store. - `stateless` may still depend on stateful upstreams, matching existing repo precedent (`umami`, `n8n-worker`). - `sidecar-bound` is used for one-shot or profile modules whose state is materially owned by a paired primary service. - F2 should still filter by lifecycle, host, canonical compose support, and health contract. This PR only supplies the missing statefulness dimension. ### Explicit out-of-scope - No runtime mutation - No smoke trigger - No compose migration - No lifecycle edits - No new schema value for `ephemeral` ### Requested decision Merge if the classifications are acceptable as the next-session F2 candidate filter foundation. ### Merge blockers - Any module whose classification contradicts known runtime state - Any desire to add `ephemeral` to schema first instead of using current enum values ## Classification report | Module | Statefulness | Basis | |---|---:|---| | agent-plane-shadow-control | stateless | control process; checkpoint state lives in shadow Postgres | | agent-plane-shadow-honcho-status | stateless | mirror/status surface; no local state evidence | | agent-plane-shadow-postgres | stateful | manifest data store `agent-plane-shadow-postgres-data` | | agent-plane-shadow-projection | stateless | projection worker; state held by upstream control/backing stores | | audio-mcp-legacy | stateful | sunset legacy audio path; replaced by stateful audio path | | deploy-control | stateful | live RW `/state` bind mount under legacy root | | element-web | stateless | web client; only config bind observed, user state lives in Synapse | | excalidraw-app | stateless | live container has no mounts; app state not persisted locally | | excalidraw-room | stateless | live container has no mounts; realtime relay only | | git-mirror | stateful | live RW `/state` bind mount for integration state | | gmail-openclaw-broker | stateless | live container has no mounts; headers broker/gateway behavior | | gmail-private-mcp | stateful | live RW `/state` and `/config` integration binds | | gmail-triage-mcp | stateful | live RW `/state` and `/config` integration binds | | hermes-agency | stateful | systemd agency produces durable product artifacts/state | | infisical-redis | stateful | live Redis `/data` volume | | integrations-hub | stateless | conceptual hub; component state belongs to gmail/git-mirror modules | | iskra-phase-1-0-bundle | stateless | parked bundle/spec artifact, not deployed runtime state | | jellyfin | stateful | media-server intent implies media/library state if activated | | kanboard | stateful | live Kanboard stack includes dedicated Postgres volume | | landing-pdurlej | stateless | landing-page intent, no local runtime state | | matrix-hub-private | sidecar-bound | private profile state would live in Synapse, not this module | | minio | stateful | live MinIO `/data` volume and manifest data store | | minio-init | sidecar-bound | one-shot initializer; state owned by MinIO | | np | stateful | live RW Obsidian vault bind at `/vault` | | np-meerkat-backend | stateful | live RW data/photos binds | | np-meerkat-frontend | stateless | live frontend container has no mounts | | np-memos | stateful | live RW memos assets bind | | np-radicale | stateful | live RW collections bind + local volume | | np-silverbullet | stateful | live RW Obsidian vault bind, even though lifecycle is sunset | | ntfy | stateful | live cache/data volumes | | obsidian-headless-sync | stateful | sync daemon owns/updates operator vault state | | openclaw-gateway | stateless | gateway; state lives upstream in Honcho/OpenClaw runtime | | openclaw-mail-gateway | stateless | live config is read-only; processing state upstream | | openclaw-mail-worker | stateless | live config is read-only; processing state upstream | | openclaw-searxng-mcp | stateless | MCP wrapper; search state belongs to upstream SearXNG | | postgres | stateful | live Postgres data volume and manifest data store | | products-agent-eval-lab | stateful | sunset product/eval lab with durable generated artifacts | | redis | stateful | live Redis `/data` volume and AOF/RDB command | | safe-session-api | stateful | live `safe_session_state` volume | | safe-session-web | stateless | live web container has no mounts; API owns session state | | signal-bridge-legacy | stateful | bridge/protocol identity state, sunset path | | signal-bridge-mautrix | stateful | bridge/protocol identity state, parked path | | signal-cli | stateful | Signal protocol daemon has account/session identity state | | teamspeak3 | stateful | voice server intent has durable config/channel/user state | | traefik | stateful | live writable ACME certificate file | | vault | stateful | live Vault data/log volumes and manifest data store | | vault-bootstrap | sidecar-bound | one-shot initializer; state owned by Vault | | voice-transcription | stateful | live model and service state volumes | | zeroclaw-guarded-self-edit | stateless | archived control artifact; no active runtime state | ## Verification - `for f in modules/*/module.yaml; do grep -q "statefulness:" "$f" || echo "$f"; done` → no output - `python` count after change → `{'stateless': 31, 'stateful': 45, 'sidecar-bound': 4}` - Per-module normal validation loop over all `modules/*/module.yaml` → all passed - `pytest control-plane/platformctl/tests/test_validate.py -q` → 6 passed ## Spec sources read - `schema/module.schema.json` — valid enum and semantics - `prompts/codex-f1.5-hardening-f2-2026-05-13.md` — original Hardening D context - operator-pasted Pan Herbatka correction in the Codex thread — revised Hardening D scope - `compose/apps/compose.yaml`, `compose/core/compose.yaml`, `compose/edge/compose.yaml` — compose service/mount evidence - `modules/*/module.yaml` for missing-field modules — lifecycle, runtime service, dependencies, and intent - RS2000 read-only `docker ps` and `docker inspect` mount metadata — live evidence without env/secret values Refs #142.
docs(modules): classify missing statefulness metadata
All checks were successful
canary-required / collect-diff (pull_request) Successful in 3s
platformctl plan / auto-apply scope (pull_request) Successful in 17s
canary-required / canary (pull_request) Successful in 12s
base-is-main / guard (pull_request) Successful in 1s
01a4d9daa0
Sign in to join this conversation.
No reviewers
No labels
W6d-automerge-calibration
agent/claude-code
agent/codex
agent/hermes
agent/iskra
agent/ollama
agent/patchwarden
automerge-candidate
class/security-sensitive
cutover-gate
dependency/blocked
dependency/blocks-others
dependency/cross-repo
dependency/needs-confirmation
domain:agents
domain:ci
domain:docs
domain:forgejo
domain:infra
domain:memory
domain:runtime
domain:signal
domain:ux
flow/architecture
flow/blocked
flow/deployed
flow/done
flow/implementation
flow/intake
flow/maintained
flow/observed
flow/ready
flow/refining
flow/retired
flow/review
iterating
judge/codex-candidate
judge/hermes-candidate
judge/low-confidence
judge/needs-refinement
judge/operator-needed
judge/p0
judge/p1
judge/p2
judge/p3
judge/park
judge/patchwarden-candidate
judge/stale-priority
kind/adr
kind/bug
kind/chore
kind/feature
kind/infra
kind/ops
kind/refactor
kind/research
large-impact
merge/auto
merge/manual
merge/manual-dependency-conflict
merge/manual-failing-tests
merge/manual-merge-conflict
merge/manual-missing-review
merge/manual-operator-preference
merge/manual-red-zone
merge/manual-security-sensitive
merge/manual-unclear-scope
merge/manual-unknown
meta
mode:operator-only
mode:patchwarden-iskra-approved
mode:safe-auto
needs-operator-decision
needs-triage
not-ready
observed/erroring
observed/needs-followup
observed/pending
observed/retire-candidate
observed/unused
observed/used
operator-emotional
owner-attention
phase/02
phase/03
priority:p0
priority:p1
priority:p2
priority:p3
proposed
ready-for-agent
ready-for-operator
recovery
review:claude-reviewed
review:codex-reviewed
review:dziadek-reviewed
review:needs-human
risk/exposure
risk/process
risk/product
risk/runtime
safety:external-write
safety:no-prod-mutation
safety:prod-impact
safety:secret-touch
size/large
size/medium
size/small
size/tiny
size/unknown
source/adr
source/agent-generated
source/manual
source/operator-chat
source/voice-note
status:blocked
status:codex-ready
status:merged:pending-evidence
status:needs-evidence
status:operator-needed
status:parked
tier/full
tier/lite
tier/stacked
tier:0-platform-substrate
tier:1-iskra-value-layer
tier:2-tools-products-modules
type:bug
type:chore
type:docs
type:feat
type:policy
type:research
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pdurlej/platform!263
No description provided.